Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/drivers/char/ChangeLog b/drivers/char/ChangeLog
new file mode 100644
index 0000000..56b8a2e
--- /dev/null
+++ b/drivers/char/ChangeLog
@@ -0,0 +1,775 @@
+2001-08-11  Tim Waugh  <twaugh@redhat.com>
+
+	* serial.c (get_pci_port): Deal with awkward Titan cards.
+
+1998-08-26  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c (rs_open): Correctly decrement the module in-use count
+		on errors.
+
+Thu Feb 19 14:24:08 1998  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* tty_io.c (tty_name): Remove the non-reentrant (and non-SMP safe)
+		version of tty_name, and rename the reentrant _tty_name
+		function to be tty_name.
+		(tty_open): Add a warning message stating callout devices
+		are deprecated.
+
+Mon Dec  1 08:24:15 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* tty_io.c (tty_get_baud_rate): Print a warning syslog if the
+		tty->alt_speed kludge is used; this means the system is
+		using the deprecated SPD_HI ioctls.
+
+Mon Nov 24 10:37:49 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c, esp.c, rocket.c: Change drivers to take advantage of
+	 	tty_get_baud_rate().
+	
+	* tty_io.c (tty_get_baud_rate): New function which computes the
+		correct baud rate for the tty.  More factoring out of
+		common code out of the serial driver to the high-level tty
+		functions....
+
+Sat Nov 22 07:53:36 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c, esp.c, rocket.c: Add tty->driver.break() routine, and
+ 		allow high-level tty code to handle the break and soft
+ 		carrier ioctls.
+	
+	* tty_ioctl.c (n_tty_ioctl): Support TIOCGSOFTCAR and
+ 		TIOCSSOFTCAR, so that device drivers don't have to support
+ 		it.
+
+	* serial.c (autoconfig): Change 16750 test to hopefully eliminate
+		false results by people with strange 16550As being
+		detected as 16750s.  Hopefully 16750s will still be
+		detected as 16750, and other weird UARTs won't get poorly
+		autodetected.  If this doesn't work, I'll have to disable
+		the auto identification for the 16750.
+
+	* tty_io.c (tty_hangup): Now actually do the tty hangup
+		processing during the timer processing, and disable
+		interrupts while doing the hangup processing.  This avoids
+		several nasty race conditions which happened when the
+		hangup processing was done asynchronously.
+		(tty_ioctl): Do break handling in the tty driver if
+		driver's break function is supported.  
+		(tty_flip_buffer_push): New exported function which should
+		be used by drivers to push characters in the flip buffer
+		to the tty handler.  This may either be done using a task
+		queue function for better CPU efficiency, or directly for
+		low latency operation.
+
+	* serial.c (rs_set_termios): Fix bug rs_set_termios when
+		transitioning away from B0, submitted by Stanislav
+		Voronyi. 
+
+Thu Jun 19 20:05:58 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c (begin_break, end_break, rs_ioctl): Applied patch
+		to support BSD ioctls to set and clear the break
+		condition explicitly.
+
+	* console.c (scrup, scrdown, insert_line, delete_line): Applied
+		fix suggested by Aaron Tiensivu to speed up block scrolls
+		up and down.
+
+	* n_tty.c (opost_block, write_chan): Added a modified "fast
+ 		console" patch which processes a block of text via
+		"cooking" efficiently.
+
+Wed Jun 18 15:25:50 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* tty_io.c (init_dev, release_dev): Applied fix suggested by Bill
+		Hawes to prevent race conditions in the tty code.
+
+	* n_tty.c (n_tty_chars_in_buffer): Applied fix suggested by Bill
+		Hawes so that n_tty_chars_in_buffer returns the correct
+		value in the case when the tty is in cannonical mode.  (To
+		avoid a pty deadlock with telnetd.)
+
+Thu Feb 27 01:53:08 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c (change_speed): Add support for the termios flag
+		CMSPAR, which allows the user to select stick parity.
+		(i.e, if PARODD is set, the parity bit is always 1; if
+		PARRODD is not set, then the parity bit is always 0).
+
+Wed Feb 26 19:03:10 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c (cleanup_module): Fix memory leak when using the serial
+		driver as a module; make sure tmp_buf gets freed!
+
+Tue Feb 25 11:01:59 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c (set_modem_info): Add support for setting and clearing
+		the OUT1 and OUT2 bits.  (For special case UART's, usually
+		for half-duplex.)
+		(autoconfig, change_speed): Fix TI 16750 support.
+
+Sun Feb 16 00:14:43 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* tty_io.c (release_dev): Add sanity check to make sure there are
+		no waiters on tty->read_wait or tty->write_wait.
+
+	* serial.c (rs_init): Don't autoconfig a device if the I/O region
+		is already reserved.
+
+	* serial.c (serial_proc_info): Add support for /proc/serial.
+
+Thu Feb 13 00:49:10 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c (receive_chars): When the UART repotrs an overrun
+ 		condition, it does so with a valid character.  Changed to
+ 		not throw away the valid character, but instead report the
+		overrun after the valid character.
+
+	* serial.c: Added new #ifdef's for some of the advanced serial
+		driver features.  A minimal driver that only supports COM
+		1/2/3/4 without sharing serial interrupts only takes 17k;
+		the full driver takes 32k.
+		
+Wed Feb 12 14:50:44 1997  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* vt.c:
+	* pty.c: 
+	* tty_ioctl.c: 
+	* serial.c: Update routines to use the new 2.1 memory access
+	 	routines.
+
+Wed Dec  4 07:51:52 1996  Theodore Ts'o  <tytso@localhost.mit.edu>
+
+	* serial.c (change_speed): Use save_flags(); cli() and
+		restore_flags() in order to ensure we don't accidentally
+		turn on interrupts when starting up the port.
+		(startup): Move the insertion of serial structure into the
+		IRQ chain earlier into the startup processing.  Interrupts
+		should be off this whole time, but we eventually will want
+		to reduce this window.
+
+Thu Nov 21 10:05:22 1996  Theodore Ts'o  <tytso@localhost.mit.edu>
+
+	* tty_ioctl.c (tty_wait_until_sent): Always check the driver
+ 		wait_until_ready routine, even if there are no characters
+ 		in the xmit buffer.  (There may be charactes in the device
+ 		FIFO.)
+		(n_tty_ioctl): Add new flag tty->flow_stopped which
+ 		indicates whether the tty is stopped due to a request by
+ 		the TCXONC ioctl (used by tcflow).  If so, don't let an
+ 		incoming XOFF character restart the tty.  The tty can only
+ 		be restarted by another TCXONC request.
+
+	* tty_io.c (start_tty): Don't allow the tty to be restarted if
+		tty->flow_stopped is true.
+
+	* n_tty.c (n_tty_receive_char): If tty->flow_stopped is true, and
+ 		IXANY is set, don't eat a character trying to restart the
+ 		tty.
+
+	* serial.c (startup): Remove need for MCR_noint from the
+ 		async_struct structure.  Only turn on DTR and RTS if the
+ 		baud rate is not zero.
+		(change_speed): More accurately calculate the timeout
+ 		value based on the word size.  Move responsibility of
+ 		hangup when speed becomes B0 to rs_set_termios()
+		(set_serial_info): When changing the UART type set the
+ 		current xmit_fifo_size as well as the permanent
+ 		xmit_fifo_size.
+		(rs_ioctl): Fix TCSBRK (used by tcdrain) and TCSBRKP
+ 		ioctls to return EINTR if interrupted by a signal.
+		(rs_set_termios): If the baud rate changes to or from B0,
+ 		this function is now responsible for setting or clearing
+ 		DTR and RTS.  DTR and RTS are only be changed on the
+ 		transition to or from the B0 state.
+		(rs_close): Wait for the characters to drain based on
+ 		info->timeout.  At low baud rates (50 bps), it may take a
+ 		long time for the FIFO to completely drain out!
+		(rs_wait_until_sent): Fixed timeout handling.  Now
+ 		releases control to the scheduler, but checks frequently
+ 		enough so that the function is sensitive enough to pass
+ 		the timing requirements of the NIST-PCTS.
+		(block_til_ready): When opening the device, don't turn on
+ 		DTR and RTS if the baud rate is B0.
+
+Thu Nov 14 00:06:09 1996  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c (autoconfig): Fix autoconfiguration problems;
+		info->flags wasn't getting initialized from the state
+		structure.  Put in more paranoid test for the 16750.
+
+Fri Nov  8 20:19:50 1996  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* n_tty.c (n_tty_flush_buffer): Only call driver->unthrottle() if
+ 		the tty was previous throttled.
+		(n_tty_set_termios, write_chan): Add changes suggested by
+ 			Simon P. Allen to allow hardware cooking.
+
+	* tty_ioctl.c (set_termios): If we get a signal while waiting for
+		the tty to drain, return -EINTR.
+	
+	* serial.c (change_speed): Add support for CREAD, as required by
+	 	POSIX.
+
+Sat Nov  2 20:43:10 1996  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* serial.c: Wholesale changes.  Added support for the Startech
+ 		16650 and 16650V2 chips.  (WARNING: the new startech
+ 		16650A may or may not work!)  Added support for the
+ 		TI16750 (not yet tested).  Split async_struct into a
+ 		transient part (async_struct) and a permanent part
+		(serial_state) which contains the configuration
+ 		information for the ports.  Added new driver routines
+ 		wait_until_sent() and send_xchar() to help with POSIX
+ 		compliance.  Added support for radio clocks which waggle
+		the carrier detect line (CONFIG_HARD_PPS).
+	
+	* tty_ioctl.c (tty_wait_until_sent): Added call to new driver
+		function tty->driver.wait_until_sent(), which returns when
+		the tty's device xmit buffers are drained.  Needed for
+		full POSIX compliance.
+
+		(send_prio_char): New function, called by the ioctl's
+		TCIOFF and TCION; uses the new driver call send_xchar(),
+		which will send the XON or XOFF character at high priority
+		(and even if tty output is stopped).
+
+Wed Jun  5 18:52:04 1996  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* pty.c (pty_close): When closing a pty, make sure packet mode is
+	 	cleared.
+
+Sun May 26 09:33:52 1996  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* vesa_blank.c (set_vesa_blanking): Add missing verify_area() call.
+
+	* selection.c (set_selection): Add missing verify_area() call.
+
+	* tty_io.c (tty_ioctl): Add missing verify_area() calls.
+
+	* serial.c (rs_ioctl): Add missing verify_area() calls.
+		(rs_init): Allow initialization of serial driver
+		configuration from a module.
+
+	* random.c (extract_entropy): Add missing verify_area call.
+		Don't limit number of characters returned to
+		32,768. Extract entropy is now no longer a inlined
+		function.
+
+		(random_read): Check return value in case extract_entropy
+		returns an error.
+
+		(secure_tcp_sequence_number): New function which returns a
+		secure TCP sequence number.  This is needed to prevent some
+		nasty TCP hijacking attacks.
+	
+		(init_std_data): Initialize using gettimeofday() instead of
+		struct timeval xtime.
+
+		(fast_add_entropy_word, add_entropy_word): Rename the
+		inline function add_entropy_word() to
+		fast_add_entropy_word().  Make add_entropy_word() be the
+		non-inlined function which is used in non-timing critical
+		places, in order to save space.
+
+		(initialize_benchmark, begin_benchmark, end_benchmark): New
+		functions defined when RANDOM_BENCHMARK is defined.  They
+		allow us to benchmark the speed of the
+		add_timer_randomness() call.
+
+		(int_ln, rotate_left): Add two new inline functions with
+		i386 optimized asm instructions.  This speeds up the
+		critical add_entropy_word() and add_timer_randomness()
+		functions, which are called from interrupt handlers.
+
+Tue May  7 22:51:11 1996    <tytso@rsts-11.mit.edu>
+
+	* random.c (add_timer_randomness): Limit the amount randomness
+		that we estimate to 12 bits.  (An arbitrary amount).
+
+		(extract_entropy): To make it harder to analyze the hash
+		function, fold the hash function in half using XOR, and
+		use the folded result as the value to emit to the user.
+		Also, add timer randomness each pass through the
+		exact_entropy call, to increase the amount of unknown
+		values during the extraction process.
+
+		(random_ioctl): Use IOR/IOW definitions to define the
+		ioctl values used by the /dev/random driver.  Allow the
+		old ioctl values to be used for backwards compatibility
+		(for a limited amount of time).
+
+Wed Apr 24 14:02:04 1996  Theodore Ts'o  <tytso@rsts-11.mit.edu>
+
+	* random.c (add_timer_randomness): Use 2nd derivative as well to
+		better estimate entropy.
+
+		(rand_initialize): Explicitly initialize all the pointers
+		to NULL.  (Clearing pointers using memset isn't portable.)
+		Initialize the random pool with OS-dependent data.
+
+		(random_write): Add sanity checking to the arguments to
+		random_write(), so that bad arguments won't cause a kernel
+		SEGV. 
+
+		(random_read): Update the access time of the device inode
+		when you return data to the user.
+
+		(random_ioctl): Wake up the random_wait channel when there
+		are only WAIT_INPUT_BITS available.  Add more paranoia
+		checks to make sure entropy_count doesn't go beyond the
+		bounds of (0, POOLSIZE).  Add a few missing verify_area
+		checks.  Add support for the RNDCLEARPOOL ioctl, which
+		zaps the random pool.
+
+		(add_timer_randomness): Wake up the random_wait
+		channel only when there are WAIT_INPUT_BITS available.
+
+		(random_select): Allow a random refresh daemon process to
+		select on /dev/random for writing; wake up the daemon when
+		there are less than WAIT_OUTPUT_BITS bits of randomness
+		available.
+
+Tue Apr 23 22:56:07 1996    <tytso@rsts-11.mit.edu>
+
+	* tty_io.c (init_dev): Change return code when user attempts to
+		open master pty which is already open from EAGAIN to EIO,
+		to match with BSD expectations.  EIO is more correct
+		anyway, since EAGAIN implies that retrying will be
+		successful --- which it might be.... Eventually!!
+
+	* pty.c (pty_open, pty_close): Fix wait loop so that we don't
+		busy loop while waiting for the master side to open.
+		Fix tty opening/closing logic.  TTY_SLAVE_CLOSED was
+		renamed to TTY_OTHER_CLOSED, so that the name is more
+		descriptive.  Also fixed code so that the tty flag
+		actually works correctly now....
+
+Mon Apr  1 10:22:01 1996    <tytso@rsts-11.mit.edu>
+
+	* serial.c (rs_close): Cleaned up modularization changes.
+		Remove code which forced line discipline back to N_TTY
+		this is done in the tty upper layers, and there's no
+		reason to do it here.  (Making this change also
+		removed the requirement that the serial module access
+		the internal kernel symbol "ldiscs".)
+
+	* tty_io.c (tty_init): Formally register a tty_driver entry for
+		/dev/tty (device 4, 0) and /dev/console (device 5, 0).
+		This guarantees that major device numbers 4 and 5 will be
+		reserved for the tty subsystem (as they have to be because
+		of /dev/tty and /dev/console).  Removed tty_regdev, as
+		this interface is no longer necessary.
+
+Sun Mar 17 20:42:47 GMT 1996 <ah@doc.ic.ac.uk>
+
+	* serial.c : modularisation (changes in linux/fs/device.c allow
+		kerneld to automatically load the serial module).
+
+	* Makefile, Config.in : serial modularisation adds.
+
+	* tty_io.c : tty_init_ctty used by to register "cua" driver just
+		for the /dev/tty device (5,0).  Added tty_regdev.
+	
+	* serial.c (shutdown, rs_ioctl) : when port shuts down wakeup processes
+	  waiting on delta_msr_wait. The TIOCMIWAIT ioctl returns EIO
+	  if no change was done since the time of call.
+
+Sat Mar 16 14:33:13 1996 <aeb@cwi.nl>
+
+	* tty_io.c (disassociate_ctty): If disassociate_ctty is called by
+		exit, do not perform an implicit vhangup on a pty.
+
+Fri Feb  9 14:15:47 1996    <tytso@rsts-11.mit.edu>
+
+	* serial.c (block_til_ready): Fixed another race condition which
+		happens if a hangup happens during the open.
+
+Wed Jan 10 10:08:00 1996    <tytso@rsts-11.mit.edu>
+
+	* serial.c (block_til_ready): Remove race condition which happened
+		if a hangup condition happened during the setup of the
+		UART, before rs_open() called block_til_ready().  This
+		caused the info->count counter to be erroneously
+		decremented.
+
+	* serial.c (startup, rs_open): Remove race condition that could
+		cause a memory leak of one page.  (Fortunately, both race
+		conditions were relatively rare in practice.)
+
+Tue Dec  5 13:21:27 1995    <tytso@rsts-11.mit.edu>
+
+	* serial.c (check_modem_status, rs_ioctl): Support the new
+		ioctl()'s TIOCGICOUNT, TIOCMIWAIT.  These allow an
+		application program to wait on a modem serial register
+		status bit change, and to find out how many changes have
+		taken place for the MSR bits.
+
+		(rs_write): Eliminate a race condition which is introduced
+		if it is necessary to wait for the semaphore.
+
+Sat Nov  4 17:14:45 1995    <tytso@rsts-11.mit.edu>
+
+	* tty_io.c (tty_init): Move registration of TTY_MAJOR and
+		TTY_AUX_MAJOR to the end, so that /proc/devices looks
+		prettier. 
+
+	* pty.c (pty_init): Use new major numbers for PTY master and slave
+		devices.  This allow us to have more than 64 pty's.  We
+		register the old pty devices for backwards compatibility.
+		Note that a system should either be using the old pty
+		devices or the new pty devices --- in general, it should
+		try to use both, since they map into the same pty table.
+		The old pty devices are strictly for backwards compatibility.
+
+Wed Oct 11 12:45:24 1995    <tytso@rsts-11.mit.edu>
+
+	* tty_io.c (disassociate_ctty): If disassociate_ctty is called by
+		exit, perform an implicit vhangup on the tty.
+
+	* pty.c (pty_close): When the master pty is closed, send a hangup
+		to the slave pty.
+		(pty_open): Use the flag TTY_SLAVE_CLOSED to test to see
+		if there are any open slave ptys, instead of using
+		tty->link->count.  The old method got confused if there
+		were processes that had hung-up file descriptors on the
+		slave tty.
+
+Tue May  2 00:53:25 1995    <tytso@rsx-11.mit.edu>
+
+	* tty_io.c (tty_set_ldisc): Wait until the output buffer is
+		drained before closing the old line discipline --- needed
+		in only one case: XON/XOFF processing.
+
+	* n_tty.c (n_tty_close): Don't bother waiting until the output
+		driver is closed; in general, the line discipline
+		shouldn't care if the hardware is finished
+		transmitting before the line discipline terminates.
+
+	* tty_io.c (release_dev): Shutdown the line discipline after
+		decrementing the tty count variable; but set the
+		TTY_CLOSING flag so that we know that this tty structure
+		isn't long for this world.
+
+	* tty_io.c (init_dev): Add sanity code to check to see if
+		TTY_CLOSING is set on a tty structure; if so, something
+		bad has happened (probably a line discipline close blocked
+		when it shouldn't have; so do a kernel printk and then
+		return an error).
+
+Wed Apr 26 10:23:44 1995  Theodore Y. Ts'o  <tytso@localhost>
+
+	* tty_io.c (release_dev): Try to shutdown the line discipline
+		*before* decrementing the tty count variable; this removes
+		a potential race condition which occurs when the line
+		discipline close blocks, and another process then tries
+		open the same serial port.
+
+	* serial.c (rs_hangup): When hanging up, flush the output buffer
+		before shutting down the UART.  Otherwise the line
+		discipline close blocks waiting for the characters to get
+		flushed, which never happens until the serial port gets reused.
+
+Wed Apr 12 08:06:16 1995  Theodore Y. Ts'o  <tytso@localhost>
+
+	* serial.c (do_serial_hangup, do_softint, check_modem_status,
+		rs_init):  Hangups are now scheduled via a separate tqueue
+		structure in the async_struct structure, tqueue_hangup.
+		This task is pushed on to the tq_schedule queue, so that
+		it is processed synchronously by the scheduler.
+
+Sat Feb 18 12:13:51 1995  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_io.c (disassociate_ctty, tty_open, tty_ioctl): Clear
+		current->tty_old_pgrp field when a session leader
+		acquires a controlling tty, and after a session leader
+		has disassociated from a controlling tty.
+
+Fri Feb 17 09:34:09 1995  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_interrupt_single, rs_interrupt, rs_interrupt_multi): 
+		Change the number of passes made from 64 to be 256,
+		configurable with the #define RS_ISR_PASS_LIMIT.
+
+	* serial.c (rs_init, set_serial_info, get_serial_info, rs_close):
+		Remove support for closing_wait2.  Instead, set
+		tty->closing and rely on the line discipline to prevent
+		echo wars.
+
+	* n_tty.c (n_tty_receive_char):  IEXTEN does not need to be
+		enabled in order for IXANY to be active.
+
+		If tty->closing is set, then only process XON and XOFF
+                characters.
+
+Sun Feb 12 23:57:48 1995  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_timer): Change the interrupt poll time from 60
+		seconds to 10 seconds, configurable with the #define
+		RS_STROBE_TIME.
+
+	* serial.c (rs_interrupt_multi, startup, shutdown, rs_ioctl,
+		set_multiport_struct, get_multiport_struct): Add
+		provisions for a new type of interrupt service routine,
+		which better supports multiple serial ports on a single
+		IRQ.  
+
+Sun Feb  5 19:35:11 1995  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_ioctl.c (n_tty_ioctl, set_termios, tty_wait_until_sent): 
+	* serial.c (rs_ioctl, rs_close): 
+	* cyclades.c (cy_ioctl, cy_close): 
+	* n_tty.c (n_tty_close):  Rename wait_until_sent to
+		tty_wait_until_sent, so that it's a better name to export
+		in ksyms.c.
+
+Sat Feb  4 23:36:20 1995  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_close): Added missing check for closing_wait2 being
+		ASYNC_CLOSING_WAIT_NONE.
+
+Thu Jan 26 09:02:49 1995  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_init, set_serial_info, get_serial_info,
+		rs_close): Support close_wait in the serial driver.
+		This is helpful for slow devices (like serial
+		plotters) so that their outputs don't get flushed upon
+		device close.  This has to be configurable because
+		normally we don't want ports to be hung up for long
+		periods of time during a close when they are not
+		connected to a device, or the device is powered off.
+
+		The default is to wait 30 seconds; in the case of a
+		very slow device, the close_wait timeout should be
+		lengthened.  If it is set to 0, the kernel will wait
+		forever for all of the data to be transmitted.
+
+Thu Jan 17 01:17:20 1995  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (startup, change_speed, rs_init): Add support to detect
+		the StarTech 16650 chip.  Treat it as a 16450 for now,
+		because of its FIFO bugs.
+
+Thu Jan  5 21:21:57 1995  <dahinds@users.sourceforge.net>
+
+	* serial.c: (receive_char): Added counter to prevent infinite loop
+		when a PCMCIA serial device is ejected.
+
+Thu Dec 29 17:53:48 1994    <tytso@rsx-11.mit.edu>
+
+	* tty_io.c (check_tty_count): New procedure which checks
+		tty->count to make sure that it matches with the number of
+		open file descriptors which point at the structure.  If
+		the number doesn't match, it prints a warning message.
+
+Wed Dec 28 15:41:51 1994    <tytso@rsx-11.mit.edu>
+
+	* tty_io.c (do_tty_hangup, disassociate_ctty): At hangup time,
+		save the tty's current foreground process group in the
+		session leader's task structure.  When the session leader
+		terminates, send a SIGHUP, SIGCONT to that process group.
+		This is not required by POSIX, but it's not prohibited
+		either, and it appears to be the least intrusive way
+		to fix a problem that dialup servers have with
+		orphaned process groups caused by modem hangups.
+
+Thu Dec  8 14:52:11 1994    <tytso@rsx-11.mit.edu>
+
+	* serial.c (rs_ioctl): Don't allow most ioctl's if the serial port
+		isn't initialized.
+
+	* serial.c (rs_close): Don't clear the IER if the serial port
+		isn't initialized. 
+
+	* serial.c (block_til_ready): Don't try to block on the dialin
+		port if the serial port isn't initialized.
+
+Wed Dec  7 10:48:30 1994  Si Park (si@wimpol.demon.co.uk)
+	* tty_io.c (tty_register_driver): Fix bug when linking onto
+		the tty_drivers list. We now test that there are elements
+		already on the list before setting the back link from the
+		first element to the new driver.
+
+	* tty_io.c (tty_unregister_driver): Fix bug in unlinking the
+		specified driver from the tty_drivers list. We were not
+		setting the back link correctly. This used to result in
+		a dangling back link pointer and cause panics on the next
+		call to get_tty_driver().
+
+Tue Nov 29 10:21:09 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_io.c (tty_unregister_driver): Fix bug in
+		tty_unregister_driver where the pointer to the refcount is
+		tested, instead of the refcount itself.  This caused
+		tty_unregister_driver to always return EBUSY.
+
+Sat Nov 26 11:59:24 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_io.c (tty_ioctl): Add support for the new ioctl
+		TIOCTTYGSTRUCT, which allow a kernel debugging program
+		direct read access to the tty and tty_driver structures.
+
+Fri Nov 25 17:26:22 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_set_termios): Don't wake up processes blocked in
+		open when the CLOCAL flag changes, since a blocking
+		open only samples the CLOCAL flag once when it blocks,
+		and doesn't check it again.  (n.b.  FreeBSD has a
+		different behavior for blocking opens; it's not clear
+		whether Linux or FreeBSD's interpretation is correct.
+		POSIX doesn't give clear guidance on this issue, so
+		this may change in the future....)
+
+	* serial.c (block_til_ready): Use the correct termios structure to
+		check the CLOCAL flag.  If the cuaXX device is active,
+		then check the saved termios for the ttySXX device.
+		Otherwise, use the currently active termios structure.
+
+Sun Nov  6 21:05:44 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (change_speed): Add support for direct access of
+		57,600 and 115,200 bps.
+
+Wed Nov  2 10:32:36 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* n_tty.c (n_tty_receive_room): Only allow excess characters
+		through if we are in ICANON mode *and* there are other no
+		pending lines in the buffer.  Otherwise cut and paste over
+		4k breaks.
+
+Sat Oct 29 18:17:34 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_ioctl, get_lsr_info): Added patch suggested by Arne
+		Riiber so that user mode programs can tell when the
+		transmitter shift register is empty.
+
+Thu Oct 27 23:14:29 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_ioctl.c (wait_until_sent): Added debugging printk statements
+		(under the #ifdef TTY_DEBUG_WAIT_UNTIL_SENT)  
+
+	* serial.c (rs_interrupt, rs_interrupt_single, receive_chars,
+		change_speed, rs_close): rs_close now disables receiver
+		interrupts when closing the serial port.  This allows the
+		serial port to close quickly when Linux and a modem (or a
+		mouse) are engaged in an echo war; when closing the serial
+		port, we now first stop listening to incoming characters,
+		and *then* wait for the transmit buffer to drain.  
+
+		In order to make this change, the info->read_status_mask
+		is now used to control what bits of the line status
+		register are looked at in the interrupt routine in all
+		cases; previously it was only used in receive_chars to
+		select a few of the status bits.
+
+Mon Oct 24 23:36:21 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_close): Add a timeout to the transmitter flush
+		loop; this is just a sanity check in case we have flaky
+		(or non-existent-but-configured-by-the-user) hardware.
+
+Fri Oct 21 09:37:23 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_io.c (tty_fasync): When asynchronous I/O is enabled, if the
+		process or process group has not be specified yet, set it
+		to be the tty's process group, or if that is not yet set,
+		to the current process's pid.
+
+Thu Oct 20 23:17:28 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* n_tty.c (n_tty_receive_room): If we are doing input
+		canonicalization, let as many characters through as
+		possible, so that the excess characters can be "beeped".
+
+Tue Oct 18 10:02:43 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_start): Removed an incorrect '!' that was
+		preventing transmit interrupts from being re-enabled in
+		rs_start().  Fortunately in most cases it would be
+		re-enabled elsewhere, but this still should be fixed
+		correctly.
+
+Sun Oct  9 23:46:03 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_io.c (do_tty_hangup): If the tty driver flags
+		TTY_DRIVER_RESET_TERMIOS is set, then reset the termios
+		settings back to the driver's initial configuration.  This
+		allows the termios settings to be reset even if a process
+		has hung up file descriptors keeping a pty's termios from
+		being freed and reset.
+
+	* tty_io.c (release_dev): Fix memory leak.  The pty's other
+		termios structure should also be freed.
+
+	* serial.c (rs_close, shutdown): Change how we wait for the
+		transmitter to completely drain before shutting down the
+		serial port.  We now do it by scheduling in another
+		process instead of busy looping with the interrupts turned
+		on.  This may eliminate some race condition problems that
+		some people seem to be reporting.
+
+Sun Sep 25 14:18:14 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_io.c (release_dev): When freeing a tty make sure that both
+		the tty and the o_tty (if present) aren't a process's
+		controlling tty.  (Previously, we only checked the tty.)
+
+	* serial.c (change_speed): Only enable the Modem Status
+		Interrupt for a port if CLOCAL is not set or CRTSCTS
+		is set.  If we're not checking the carrier detect and
+		CTS line, there's no point in enabling the modem
+		status interrupt.  This will save spurious interrupts
+		from slowing down systems who have terminals that
+		don't support either line.  (Of course, if you want
+		only one of CD and CTS support, you will need a
+		properly wired serial cable.)
+
+Thu Sep 22 08:32:48 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_io.c (do_SAK): Return if tty is null.
+
+	* tty_io.c (_tty_name): Return "NULL tty" if the passed in tty is
+		NULL.
+
+Sat Sep 17 13:19:25 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_ioctl.c (n_tty_ioctl): Fix TIOCGLCKTRMIOS and
+		TIOCSLCKTRMIOS, which were totally broken.  Remove
+		extra indirection from argument; it should be a struct
+		termios *, not a struct termios **.
+		&real_tty->termios_locked should have been
+		real_tty->termios_locked.  This caused us to be
+		reading and writing the termios_locked structure to
+		random places in kernel memory.  
+
+	* tty_io.c (release_dev): Oops!  Forgot to delete a critical kfree
+		of the locked_termios.  This leaves the locked_termios
+		structure pointed at a freed object.  
+
+Fri Sep 16 08:13:25 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* tty_io.c (tty_open): Don't check for an exclusive open until
+		after the device specific open routine has been called.
+		Otherwise, the serial device ref counting will be screwed
+		up.
+
+	* serial.c (rs_open, block_til_ready): Don't set termios structure
+		until after block_til_ready has returned successfully.
+		Modify block_til_ready to check the normal_termios
+		structure directly, so it doesn't rely on termios being
+		set before it's called.
+
+Thu Sep 15 23:34:01 1994  Theodore Y. Ts'o  (tytso@rt-11)
+
+	* serial.c (rs_close): Turn off interrupts during rs_close() to
+		prevent a race condition with the hangup code (which
+		runs during a software interrupt).
+
+	* tty_io.c (release_dev): Don't free the locked_termios structure;
+		its state must be retained across device opens.
+
+
+	* tty_io.c (tty_unregister_driver): Added function to unregister a
+		tty driver.  (For loadable device drivers.)
+
+
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
new file mode 100644
index 0000000..096a120
--- /dev/null
+++ b/drivers/char/Kconfig
@@ -0,0 +1,988 @@
+#
+# Character device configuration
+#
+
+menu "Character devices"
+
+config VT
+	bool "Virtual terminal" if EMBEDDED
+	select INPUT
+	default y if !VIOCONS
+	---help---
+	  If you say Y here, you will get support for terminal devices with
+	  display and keyboard devices. These are called "virtual" because you
+	  can run several virtual terminals (also called virtual consoles) on
+	  one physical terminal. This is rather useful, for example one
+	  virtual terminal can collect system messages and warnings, another
+	  one can be used for a text-mode user session, and a third could run
+	  an X session, all in parallel. Switching between virtual terminals
+	  is done with certain key combinations, usually Alt-<function key>.
+
+	  The setterm command ("man setterm") can be used to change the
+	  properties (such as colors or beeping) of a virtual terminal. The
+	  man page console_codes(4) ("man console_codes") contains the special
+	  character sequences that can be used to change those properties
+	  directly. The fonts used on virtual terminals can be changed with
+	  the setfont ("man setfont") command and the key bindings are defined
+	  with the loadkeys ("man loadkeys") command.
+
+	  You need at least one virtual terminal device in order to make use
+	  of your keyboard and monitor. Therefore, only people configuring an
+	  embedded system would want to say N here in order to save some
+	  memory; the only way to log into such a system is then via a serial
+	  or network connection.
+
+	  If unsure, say Y, or else you won't be able to do much with your new
+	  shiny Linux system :-)
+
+config VT_CONSOLE
+	bool "Support for console on virtual terminal" if EMBEDDED
+	depends on VT
+	default y
+	---help---
+	  The system console is the device which receives all kernel messages
+	  and warnings and which allows logins in single user mode. If you
+	  answer Y here, a virtual terminal (the device used to interact with
+	  a physical terminal) can be used as system console. This is the most
+	  common mode of operations, so you should say Y here unless you want
+	  the kernel messages be output only to a serial port (in which case
+	  you should say Y to "Console on serial port", below).
+
+	  If you do say Y here, by default the currently visible virtual
+	  terminal (/dev/tty0) will be used as system console. You can change
+	  that with a kernel command line option such as "console=tty3" which
+	  would use the third virtual terminal as system console. (Try "man
+	  bootparam" or see the documentation of your boot loader (lilo or
+	  loadlin) about how to pass options to the kernel at boot time.)
+
+	  If unsure, say Y.
+
+config HW_CONSOLE
+	bool
+	depends on VT && !S390 && !UML
+	default y
+
+config SERIAL_NONSTANDARD
+	bool "Non-standard serial port support"
+	---help---
+	  Say Y here if you have any non-standard serial boards -- boards
+	  which aren't supported using the standard "dumb" serial driver.
+	  This includes intelligent serial boards such as Cyclades,
+	  Digiboards, etc. These are usually used for systems that need many
+	  serial ports because they serve many terminals or dial-in
+	  connections.
+
+	  Note that the answer to this question won't directly affect the
+	  kernel: saying N will just cause the configurator to skip all
+	  the questions about non-standard serial boards.
+
+	  Most people can say N here.
+
+config COMPUTONE
+	tristate "Computone IntelliPort Plus serial support"
+	depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+	---help---
+	  This driver supports the entire family of Intelliport II/Plus
+	  controllers with the exception of the MicroChannel controllers and
+	  products previous to the Intelliport II. These are multiport cards,
+	  which give you many serial ports. You would need something like this
+	  to connect more than two modems to your Linux box, for instance in
+	  order to become a dial-in server. If you have a card like that, say
+	  Y here and read <file:Documentation/computone.txt>.
+
+	  To compile this driver as modules, choose M here: the
+	  modules will be called ip2 and ip2main.
+
+config ROCKETPORT
+	tristate "Comtrol RocketPort support"
+	depends on SERIAL_NONSTANDARD
+	help
+	  This driver supports Comtrol RocketPort and RocketModem PCI boards.   
+          These boards provide 2, 4, 8, 16, or 32 high-speed serial ports or
+          modems.  For information about the RocketPort/RocketModem  boards
+          and this driver read <file:Documentation/rocket.txt>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rocket.
+
+	  If you want to compile this driver into the kernel, say Y here.  If
+          you don't have a Comtrol RocketPort/RocketModem card installed, say N.
+
+config CYCLADES
+	tristate "Cyclades async mux support"
+	depends on SERIAL_NONSTANDARD
+	---help---
+	  This driver supports Cyclades Z and Y multiserial boards.
+	  You would need something like this to connect more than two modems to
+	  your Linux box, for instance in order to become a dial-in server.
+
+	  For information about the Cyclades-Z card, read
+	  <file:drivers/char/README.cycladesZ>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cyclades.
+
+	  If you haven't heard about it, it's safe to say N.
+
+config CYZ_INTR
+	bool "Cyclades-Z interrupt mode operation (EXPERIMENTAL)"
+	depends on EXPERIMENTAL && CYCLADES
+	help
+	  The Cyclades-Z family of multiport cards allows 2 (two) driver op
+	  modes: polling and interrupt. In polling mode, the driver will check
+	  the status of the Cyclades-Z ports every certain amount of time
+	  (which is called polling cycle and is configurable). In interrupt
+	  mode, it will use an interrupt line (IRQ) in order to check the
+	  status of the Cyclades-Z ports. The default op mode is polling. If
+	  unsure, say N.
+
+config DIGIEPCA
+	tristate "Digiboard Intelligent Async Support"
+	depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+	---help---
+	  This is a driver for Digi International's Xx, Xeve, and Xem series
+	  of cards which provide multiple serial ports. You would need
+	  something like this to connect more than two modems to your Linux
+	  box, for instance in order to become a dial-in server. This driver
+	  supports the original PC (ISA) boards as well as PCI, and EISA. If
+	  you have a card like this, say Y here and read the file
+	  <file:Documentation/digiepca.txt>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called epca.
+
+config ESPSERIAL
+	tristate "Hayes ESP serial port support"
+	depends on SERIAL_NONSTANDARD && ISA && BROKEN_ON_SMP
+	help
+	  This is a driver which supports Hayes ESP serial ports.  Both single
+	  port cards and multiport cards are supported.  Make sure to read
+	  <file:Documentation/hayes-esp.txt>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called esp.
+
+	  If unsure, say N.
+
+config MOXA_INTELLIO
+	tristate "Moxa Intellio support"
+	depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+	help
+	  Say Y here if you have a Moxa Intellio multiport serial card.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called moxa.
+
+config MOXA_SMARTIO
+	tristate "Moxa SmartIO support"
+	depends on SERIAL_NONSTANDARD
+	help
+	  Say Y here if you have a Moxa SmartIO multiport serial card.
+
+	  This driver can also be built as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you want).
+	  The module will be called mxser. If you want to do that, say M
+	  here.
+
+config ISI
+	tristate "Multi-Tech multiport card support (EXPERIMENTAL)"
+	depends on SERIAL_NONSTANDARD
+	help
+	  This is a driver for the Multi-Tech cards which provide several
+	  serial ports.  The driver is experimental and can currently only be
+	  built as a module. The module will be called isicom.
+	  If you want to do that, choose M here.
+
+config SYNCLINK
+	tristate "Microgate SyncLink card support"
+	depends on SERIAL_NONSTANDARD && PCI
+	help
+	  Provides support for the SyncLink ISA and PCI multiprotocol serial
+	  adapters. These adapters support asynchronous and HDLC bit
+	  synchronous communication up to 10Mbps (PCI adapter).
+
+	  This driver can only be built as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you want).
+	  The module will be called synclink.  If you want to do that, say M
+	  here.
+
+config SYNCLINKMP
+	tristate "SyncLink Multiport support"
+	depends on SERIAL_NONSTANDARD
+	help
+	  Enable support for the SyncLink Multiport (2 or 4 ports)
+	  serial adapter, running asynchronous and HDLC communications up
+	  to 2.048Mbps. Each ports is independently selectable for
+	  RS-232, V.35, RS-449, RS-530, and X.21
+
+	  This driver may be built as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you want).
+	  The module will be called synclinkmp.  If you want to do that, say M
+	  here.
+
+config N_HDLC
+	tristate "HDLC line discipline support"
+	depends on SERIAL_NONSTANDARD
+	help
+	  Allows synchronous HDLC communications with tty device drivers that
+	  support synchronous HDLC such as the Microgate SyncLink adapter.
+
+	  This driver can only be built as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you want).
+	  The module will be called n_hdlc. If you want to do that, say M
+	  here.
+
+config RISCOM8
+	tristate "SDL RISCom/8 card support"
+	depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+	help
+	  This is a driver for the SDL Communications RISCom/8 multiport card,
+	  which gives you many serial ports. You would need something like
+	  this to connect more than two modems to your Linux box, for instance
+	  in order to become a dial-in server. If you have a card like that,
+	  say Y here and read the file <file:Documentation/riscom8.txt>.
+
+	  Also it's possible to say M here and compile this driver as kernel
+	  loadable module; the module will be called riscom8.
+
+config SPECIALIX
+	tristate "Specialix IO8+ card support"
+	depends on SERIAL_NONSTANDARD
+	help
+	  This is a driver for the Specialix IO8+ multiport card (both the
+	  ISA and the PCI version) which gives you many serial ports. You
+	  would need something like this to connect more than two modems to
+	  your Linux box, for instance in order to become a dial-in server.
+
+	  If you have a card like that, say Y here and read the file
+	  <file:Documentation/specialix.txt>. Also it's possible to say M here
+	  and compile this driver as kernel loadable module which will be
+	  called specialix.
+
+config SPECIALIX_RTSCTS
+	bool "Specialix DTR/RTS pin is RTS"
+	depends on SPECIALIX
+	help
+	  The Specialix IO8+ card can only support either RTS or DTR. If you
+	  say N here, the driver will use the pin as "DTR" when the tty is in
+	  software handshake mode.  If you say Y here or hardware handshake is
+	  on, it will always be RTS.  Read the file
+	  <file:Documentation/specialix.txt> for more information.
+
+config SX
+	tristate "Specialix SX (and SI) card support"
+	depends on SERIAL_NONSTANDARD
+	help
+	  This is a driver for the SX and SI multiport serial cards.
+	  Please read the file <file:Documentation/sx.txt> for details.
+
+	  This driver can only be built as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you want).
+	  The module will be called sx. If you want to do that, say M here.
+
+config RIO
+	tristate "Specialix RIO system support"
+	depends on SERIAL_NONSTANDARD && BROKEN_ON_SMP
+	help
+	  This is a driver for the Specialix RIO, a smart serial card which
+	  drives an outboard box that can support up to 128 ports.  Product
+	  information is at <http://www.perle.com/support/documentation.html#multiport>.
+	  There are both ISA and PCI versions.
+
+config RIO_OLDPCI
+	bool "Support really old RIO/PCI cards"
+	depends on RIO
+	help
+	  Older RIO PCI cards need some initialization-time configuration to
+	  determine the IRQ and some control addresses.  If you have a RIO and
+	  this doesn't seem to work, try setting this to Y.
+
+config STALDRV
+	bool "Stallion multiport serial support"
+	depends on SERIAL_NONSTANDARD
+	help
+	  Stallion cards give you many serial ports.  You would need something
+	  like this to connect more than two modems to your Linux box, for
+	  instance in order to become a dial-in server.  If you say Y here,
+	  you will be asked for your specific card model in the next
+	  questions.  Make sure to read <file:Documentation/stallion.txt> in
+	  this case.  If you have never heard about all this, it's safe to
+	  say N.
+
+config STALLION
+	tristate "Stallion EasyIO or EC8/32 support"
+	depends on STALDRV && BROKEN_ON_SMP
+	help
+	  If you have an EasyIO or EasyConnection 8/32 multiport Stallion
+	  card, then this is for you; say Y.  Make sure to read
+	  <file:Documentation/stallion.txt>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stallion.
+
+config ISTALLION
+	tristate "Stallion EC8/64, ONboard, Brumby support"
+	depends on STALDRV && BROKEN_ON_SMP
+	help
+	  If you have an EasyConnection 8/64, ONboard, Brumby or Stallion
+	  serial multiport card, say Y here. Make sure to read
+	  <file:Documentation/stallion.txt>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called istallion.
+
+config AU1000_UART
+	bool "Enable Au1000 UART Support"
+	depends on SERIAL_NONSTANDARD && MIPS
+	help
+	  If you have an Alchemy AU1000 processor (MIPS based) and you want
+	  to use serial ports, say Y.  Otherwise, say N.
+
+config AU1000_SERIAL_CONSOLE
+	bool "Enable Au1000 serial console"
+	depends on AU1000_UART
+	help
+	  If you have an Alchemy AU1000 processor (MIPS based) and you want
+	  to use a console on a serial port, say Y.  Otherwise, say N.
+
+config QTRONIX_KEYBOARD
+	bool "Enable Qtronix 990P Keyboard Support"
+	depends on IT8712
+	help
+	  Images of Qtronix keyboards are at
+	  <http://www.qtronix.com/keyboard.html>.
+
+config IT8172_CIR
+	bool
+	depends on QTRONIX_KEYBOARD
+	default y
+
+config IT8172_SCR0
+	bool "Enable Smart Card Reader 0 Support "
+	depends on IT8712
+	help
+	  Say Y here to support smart-card reader 0 (SCR0) on the Integrated
+	  Technology Express, Inc. ITE8172 SBC.  Vendor page at
+	  <http://www.ite.com.tw/ia/brief_it8172bsp.htm>; picture of the
+	  board at <http://www.mvista.com/partners/semiconductor/ite.html>.
+
+config IT8172_SCR1
+	bool "Enable Smart Card Reader 1 Support "
+	depends on IT8712
+	help
+	  Say Y here to support smart-card reader 1 (SCR1) on the Integrated
+	  Technology Express, Inc. ITE8172 SBC.  Vendor page at
+	  <http://www.ite.com.tw/ia/brief_it8172bsp.htm>; picture of the
+	  board at <http://www.mvista.com/partners/semiconductor/ite.html>.
+
+config A2232
+	tristate "Commodore A2232 serial support (EXPERIMENTAL)"
+	depends on EXPERIMENTAL && ZORRO && BROKEN_ON_SMP
+	---help---
+	  This option supports the 2232 7-port serial card shipped with the
+	  Amiga 2000 and other Zorro-bus machines, dating from 1989.  At
+	  a max of 19,200 bps, the ports are served by a 6551 ACIA UART chip
+	  each, plus a 8520 CIA, and a master 6502 CPU and buffer as well. The
+	  ports were connected with 8 pin DIN connectors on the card bracket,
+	  for which 8 pin to DB25 adapters were supplied. The card also had
+	  jumpers internally to toggle various pinning configurations.
+
+	  This driver can be built as a module; but then "generic_serial"
+	  will also be built as a module. This has to be loaded before
+	  "ser_a2232". If you want to do this, answer M here.
+
+config SGI_SNSC
+	bool "SGI Altix system controller communication support"
+	depends on (IA64_SGI_SN2 || IA64_GENERIC)
+	help
+	  If you have an SGI Altix and you want to enable system
+	  controller communication from user space (you want this!),
+	  say Y.  Otherwise, say N.
+
+source "drivers/serial/Kconfig"
+
+config UNIX98_PTYS
+	bool "Unix98 PTY support" if EMBEDDED
+	default y
+	---help---
+	  A pseudo terminal (PTY) is a software device consisting of two
+	  halves: a master and a slave. The slave device behaves identical to
+	  a physical terminal; the master device is used by a process to
+	  read data from and write data to the slave, thereby emulating a
+	  terminal. Typical programs for the master side are telnet servers
+	  and xterms.
+
+	  Linux has traditionally used the BSD-like names /dev/ptyxx for
+	  masters and /dev/ttyxx for slaves of pseudo terminals. This scheme
+	  has a number of problems. The GNU C library glibc 2.1 and later,
+	  however, supports the Unix98 naming standard: in order to acquire a
+	  pseudo terminal, a process opens /dev/ptmx; the number of the pseudo
+	  terminal is then made available to the process and the pseudo
+	  terminal slave can be accessed as /dev/pts/<number>. What was
+	  traditionally /dev/ttyp2 will then be /dev/pts/2, for example.
+
+	  All modern Linux systems use the Unix98 ptys.  Say Y unless
+	  you're on an embedded system and want to conserve memory.
+
+config LEGACY_PTYS
+	bool "Legacy (BSD) PTY support"
+	default y
+	---help---
+	  A pseudo terminal (PTY) is a software device consisting of two
+	  halves: a master and a slave. The slave device behaves identical to
+	  a physical terminal; the master device is used by a process to
+	  read data from and write data to the slave, thereby emulating a
+	  terminal. Typical programs for the master side are telnet servers
+	  and xterms.
+
+	  Linux has traditionally used the BSD-like names /dev/ptyxx
+	  for masters and /dev/ttyxx for slaves of pseudo
+	  terminals. This scheme has a number of problems, including
+	  security.  This option enables these legacy devices; on most
+	  systems, it is safe to say N.
+
+
+config LEGACY_PTY_COUNT
+	int "Maximum number of legacy PTY in use"
+	depends on LEGACY_PTYS
+	range 1 256
+	default "256"
+	---help---
+	  The maximum number of legacy PTYs that can be used at any one time.
+	  The default is 256, and should be more than enough.  Embedded
+	  systems may want to reduce this to save memory.
+
+	  When not in use, each legacy PTY occupies 12 bytes on 32-bit
+	  architectures and 24 bytes on 64-bit architectures.
+
+config PRINTER
+	tristate "Parallel printer support"
+	depends on PARPORT
+	---help---
+	  If you intend to attach a printer to the parallel port of your Linux
+	  box (as opposed to using a serial printer; if the connector at the
+	  printer has 9 or 25 holes ["female"], then it's serial), say Y.
+	  Also read the Printing-HOWTO, available from
+	  <http://www.tldp.org/docs.html#howto>.
+
+	  It is possible to share one parallel port among several devices
+	  (e.g. printer and ZIP drive) and it is safe to compile the
+	  corresponding drivers into the kernel.
+
+	  To compile this driver as a module, choose M here and read
+	  <file:Documentation/parport.txt>.  The module will be called lp.
+
+	  If you have several parallel ports, you can specify which ports to
+	  use with the "lp" kernel command line option.  (Try "man bootparam"
+	  or see the documentation of your boot loader (lilo or loadlin) about
+	  how to pass options to the kernel at boot time.)  The syntax of the
+	  "lp" command line option can be found in <file:drivers/char/lp.c>.
+
+	  If you have more than 8 printers, you need to increase the LP_NO
+	  macro in lp.c and the PARPORT_MAX macro in parport.h.
+
+config LP_CONSOLE
+	bool "Support for console on line printer"
+	depends on PRINTER
+	---help---
+	  If you want kernel messages to be printed out as they occur, you
+	  can have a console on the printer. This option adds support for
+	  doing that; to actually get it to happen you need to pass the
+	  option "console=lp0" to the kernel at boot time.
+
+	  If the printer is out of paper (or off, or unplugged, or too
+	  busy..) the kernel will stall until the printer is ready again.
+	  By defining CONSOLE_LP_STRICT to 0 (at your own risk) you
+	  can make the kernel continue when this happens,
+	  but it'll lose the kernel messages.
+
+	  If unsure, say N.
+
+config PPDEV
+	tristate "Support for user-space parallel port device drivers"
+	depends on PARPORT
+	---help---
+	  Saying Y to this adds support for /dev/parport device nodes.  This
+	  is needed for programs that want portable access to the parallel
+	  port, for instance deviceid (which displays Plug-and-Play device
+	  IDs).
+
+	  This is the parallel port equivalent of SCSI generic support (sg).
+	  It is safe to say N to this -- it is not needed for normal printing
+	  or parallel port CD-ROM/disk support.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ppdev.
+
+	  If unsure, say N.
+
+config TIPAR
+	tristate "Texas Instruments parallel link cable support"
+	depends on PARPORT
+	---help---
+	  If you own a Texas Instruments graphing calculator and use a
+	  parallel link cable, then you might be interested in this driver.
+
+	  If you enable this driver, you will be able to communicate with
+	  your calculator through a set of device nodes under /dev. The
+	  main advantage of this driver is that you don't have to be root
+	  to use this precise link cable (depending on the permissions on
+	  the device nodes, though).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tipar.
+
+	  If you don't know what a parallel link cable is or what a Texas
+	  Instruments graphing calculator is, then you probably don't need this
+	  driver.
+
+	  If unsure, say N.
+
+config HVC_CONSOLE
+	bool "pSeries Hypervisor Virtual Console support"
+	depends on PPC_PSERIES
+	help
+	  pSeries machines when partitioned support a hypervisor virtual
+	  console. This driver allows each pSeries partition to have a console
+	  which is accessed via the HMC.
+
+config HVCS
+	tristate "IBM Hypervisor Virtual Console Server support"
+	depends on PPC_PSERIES
+	help
+	  Partitionable IBM Power5 ppc64 machines allow hosting of
+	  firmware virtual consoles from one Linux partition by
+	  another Linux partition.  This driver allows console data
+	  from Linux partitions to be accessed through TTY device
+	  interfaces in the device tree of a Linux partition running
+	  this driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hvcs.ko.  Additionally, this module
+	  will depend on arch specific APIs exported from hvcserver.ko
+	  which will also be compiled when this driver is built as a
+	  module.
+
+source "drivers/char/ipmi/Kconfig"
+
+source "drivers/char/watchdog/Kconfig"
+
+config DS1620
+	tristate "NetWinder thermometer support"
+	depends on ARCH_NETWINDER
+	help
+	  Say Y here to include support for the thermal management hardware
+	  found in the NetWinder. This driver allows the user to control the
+	  temperature set points and to read the current temperature.
+
+	  It is also possible to say M here to build it as a module (ds1620)
+	  It is recommended to be used on a NetWinder, but it is not a
+	  necessity.
+
+config NWBUTTON
+	tristate "NetWinder Button"
+	depends on ARCH_NETWINDER
+	---help---
+	  If you say Y here and create a character device node /dev/nwbutton
+	  with major and minor numbers 10 and 158 ("man mknod"), then every
+	  time the orange button is pressed a number of times, the number of
+	  times the button was pressed will be written to that device.
+
+	  This is most useful for applications, as yet unwritten, which
+	  perform actions based on how many times the button is pressed in a
+	  row.
+
+	  Do not hold the button down for too long, as the driver does not
+	  alter the behaviour of the hardware reset circuitry attached to the
+	  button; it will still execute a hard reset if the button is held
+	  down for longer than approximately five seconds.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called nwbutton.
+
+	  Most people will answer Y to this question and "Reboot Using Button"
+	  below to be able to initiate a system shutdown from the button.
+
+config NWBUTTON_REBOOT
+	bool "Reboot Using Button"
+	depends on NWBUTTON
+	help
+	  If you say Y here, then you will be able to initiate a system
+	  shutdown and reboot by pressing the orange button a number of times.
+	  The number of presses to initiate the shutdown is two by default,
+	  but this can be altered by modifying the value of NUM_PRESSES_REBOOT
+	  in nwbutton.h and recompiling the driver or, if you compile the
+	  driver as a module, you can specify the number of presses at load
+	  time with "insmod button reboot_count=<something>".
+
+config NWFLASH
+	tristate "NetWinder flash support"
+	depends on ARCH_NETWINDER
+	---help---
+	  If you say Y here and create a character device /dev/flash with
+	  major 10 and minor 160 you can manipulate the flash ROM containing
+	  the NetWinder firmware. Be careful as accidentally overwriting the
+	  flash contents can render your computer unbootable. On no account
+	  allow random users access to this device. :-)
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called nwflash.
+
+	  If you're not sure, say N.
+
+config HW_RANDOM
+	tristate "Intel/AMD/VIA HW Random Number Generator support"
+	depends on (X86 || IA64) && PCI
+	---help---
+	  This driver provides kernel-side support for the Random Number
+	  Generator hardware found on Intel i8xx-based motherboards,
+	  AMD 76x-based motherboards, and Via Nehemiah CPUs.
+
+	  Provides a character driver, used to read() entropy data.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hw_random.
+
+	  If unsure, say N.
+
+config NVRAM
+	tristate "/dev/nvram support"
+	depends on ATARI || X86 || X86_64 || ARM || GENERIC_NVRAM
+	---help---
+	  If you say Y here and create a character special file /dev/nvram
+	  with major number 10 and minor number 144 using mknod ("man mknod"),
+	  you get read and write access to the extra bytes of non-volatile
+	  memory in the real time clock (RTC), which is contained in every PC
+	  and most Ataris.  The actual number of bytes varies, depending on the
+	  nvram in the system, but is usually 114 (128-14 for the RTC).
+
+	  This memory is conventionally called "CMOS RAM" on PCs and "NVRAM"
+	  on Ataris. /dev/nvram may be used to view settings there, or to
+	  change them (with some utility). It could also be used to frequently
+	  save a few bits of very important data that may not be lost over
+	  power-off and for which writing to disk is too insecure. Note
+	  however that most NVRAM space in a PC belongs to the BIOS and you
+	  should NEVER idly tamper with it. See Ralf Brown's interrupt list
+	  for a guide to the use of CMOS bytes by your BIOS.
+
+	  On Atari machines, /dev/nvram is always configured and does not need
+	  to be selected.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called nvram.
+
+config RTC
+	tristate "Enhanced Real Time Clock Support"
+	depends on !PPC32 && !PARISC && !IA64 && !M68K
+	---help---
+	  If you say Y here and create a character special file /dev/rtc with
+	  major number 10 and minor number 135 using mknod ("man mknod"), you
+	  will get access to the real time clock (or hardware clock) built
+	  into your computer.
+
+	  Every PC has such a clock built in. It can be used to generate
+	  signals from as low as 1Hz up to 8192Hz, and can also be used
+	  as a 24 hour alarm. It reports status information via the file
+	  /proc/driver/rtc and its behaviour is set by various ioctls on
+	  /dev/rtc.
+
+	  If you run Linux on a multiprocessor machine and said Y to
+	  "Symmetric Multi Processing" above, you should say Y here to read
+	  and set the RTC in an SMP compatible fashion.
+
+	  If you think you have a use for such a device (such as periodic data
+	  sampling), then say Y here, and read <file:Documentation/rtc.txt>
+	  for details.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rtc.
+
+config SGI_DS1286
+	tristate "SGI DS1286 RTC support"
+	depends on SGI_IP22
+	help
+	  If you say Y here and create a character special file /dev/rtc with
+	  major number 10 and minor number 135 using mknod ("man mknod"), you
+	  will get access to the real time clock built into your computer.
+	  Every SGI has such a clock built in. It reports status information
+	  via the file /proc/rtc and its behaviour is set by various ioctls on
+	  /dev/rtc.
+
+config SGI_IP27_RTC
+	bool "SGI M48T35 RTC support"
+	depends on SGI_IP27
+	help
+	  If you say Y here and create a character special file /dev/rtc with
+	  major number 10 and minor number 135 using mknod ("man mknod"), you
+	  will get access to the real time clock built into your computer.
+	  Every SGI has such a clock built in. It reports status information
+	  via the file /proc/rtc and its behaviour is set by various ioctls on
+	  /dev/rtc.
+
+config GEN_RTC
+	tristate "Generic /dev/rtc emulation"
+	depends on RTC!=y && !IA64 && !ARM
+	---help---
+	  If you say Y here and create a character special file /dev/rtc with
+	  major number 10 and minor number 135 using mknod ("man mknod"), you
+	  will get access to the real time clock (or hardware clock) built
+	  into your computer.
+
+	  It reports status information via the file /proc/driver/rtc and its
+	  behaviour is set by various ioctls on /dev/rtc. If you enable the
+	  "extended RTC operation" below it will also provide an emulation
+	  for RTC_UIE which is required by some programs and may improve
+	  precision in some cases.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called genrtc.
+
+config GEN_RTC_X
+	bool "Extended RTC operation"
+	depends on GEN_RTC
+	help
+	  Provides an emulation for RTC_UIE which is required by some programs
+	  and may improve precision of the generic RTC support in some cases.
+
+config EFI_RTC
+	bool "EFI Real Time Clock Services"
+	depends on IA64
+
+config DS1302
+	tristate "DS1302 RTC support"
+	depends on M32R && (PLAT_M32700UT || PLAT_OPSPUT)
+	help
+	  If you say Y here and create a character special file /dev/rtc with
+	  major number 121 and minor number 0 using mknod ("man mknod"), you
+	  will get access to the real time clock (or hardware clock) built
+	  into your computer.
+
+config S3C2410_RTC
+	bool "S3C2410 RTC Driver"
+	depends on ARCH_S3C2410
+	help
+	  RTC (Realtime Clock) driver for the clock inbuilt into the
+	  Samsung S3C2410. This can provide periodic interrupt rates
+	  from 1Hz to 64Hz for user programs, and wakeup from Alarm.
+
+config RTC_VR41XX
+	tristate "NEC VR4100 series Real Time Clock Support"
+	depends on CPU_VR41XX
+
+config COBALT_LCD
+	bool "Support for Cobalt LCD"
+	depends on MIPS_COBALT
+	help
+	  This option enables support for the LCD display and buttons found
+	  on Cobalt systems through a misc device.
+
+config DTLK
+	tristate "Double Talk PC internal speech card support"
+	help
+	  This driver is for the DoubleTalk PC, a speech synthesizer
+	  manufactured by RC Systems (<http://www.rcsys.com/>).  It is also
+	  called the `internal DoubleTalk'.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called dtlk.
+
+config R3964
+	tristate "Siemens R3964 line discipline"
+	---help---
+	  This driver allows synchronous communication with devices using the
+	  Siemens R3964 packet protocol. Unless you are dealing with special
+	  hardware like PLCs, you are unlikely to need this.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called n_r3964.
+
+	  If unsure, say N.
+
+config APPLICOM
+	tristate "Applicom intelligent fieldbus card support"
+	depends on PCI
+	---help---
+	  This driver provides the kernel-side support for the intelligent
+	  fieldbus cards made by Applicom International. More information
+	  about these cards can be found on the WWW at the address
+	  <http://www.applicom-int.com/>, or by email from David Woodhouse
+	  <dwmw2@infradead.org>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called applicom.
+
+	  If unsure, say N.
+
+config SONYPI
+	tristate "Sony Vaio Programmable I/O Control Device support (EXPERIMENTAL)"
+	depends on EXPERIMENTAL && X86 && PCI && INPUT && !64BIT
+	---help---
+	  This driver enables access to the Sony Programmable I/O Control
+	  Device which can be found in many (all ?) Sony Vaio laptops.
+
+	  If you have one of those laptops, read
+	  <file:Documentation/sonypi.txt>, and say Y or M here.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sonypi.
+
+config TANBAC_TB0219
+	tristate "TANBAC TB0219 base board support"
+	depends TANBAC_TB0229
+
+
+menu "Ftape, the floppy tape device driver"
+
+config FTAPE
+	tristate "Ftape (QIC-80/Travan) support"
+	depends on BROKEN_ON_SMP && (ALPHA || X86)
+	---help---
+	  If you have a tape drive that is connected to your floppy
+	  controller, say Y here.
+
+	  Some tape drives (like the Seagate "Tape Store 3200" or the Iomega
+	  "Ditto 3200" or the Exabyte "Eagle TR-3") come with a "high speed"
+	  controller of their own. These drives (and their companion
+	  controllers) are also supported if you say Y here.
+
+	  If you have a special controller (such as the CMS FC-10, FC-20,
+	  Mountain Mach-II, or any controller that is based on the Intel 82078
+	  FDC like the high speed controllers by Seagate and Exabyte and
+	  Iomega's "Ditto Dash") you must configure it by selecting the
+	  appropriate entries from the "Floppy tape controllers" sub-menu
+	  below and possibly modify the default values for the IRQ and DMA
+	  channel and the IO base in ftape's configuration menu.
+
+	  If you want to use your floppy tape drive on a PCI-bus based system,
+	  please read the file <file:drivers/char/ftape/README.PCI>.
+
+	  The ftape kernel driver is also available as a runtime loadable
+	  module. To compile this driver as a module, choose M here: the
+	  module will be called ftape.
+
+	  Note that the Ftape-HOWTO is out of date (sorry) and documents the
+	  older version 2.08 of this software but still contains useful
+	  information.  There is a web page with more recent documentation at
+	  <http://www.instmath.rwth-aachen.de/~heine/ftape/>.  This page
+	  always contains the latest release of the ftape driver and useful
+	  information (backup software, ftape related patches and
+	  documentation, FAQ).  Note that the file system interface has
+	  changed quite a bit compared to previous versions of ftape.  Please
+	  read <file:Documentation/ftape.txt>.
+
+source "drivers/char/ftape/Kconfig"
+
+endmenu
+
+source "drivers/char/agp/Kconfig"
+
+source "drivers/char/drm/Kconfig"
+
+source "drivers/char/pcmcia/Kconfig"
+
+config MWAVE
+	tristate "ACP Modem (Mwave) support"
+	depends on X86
+	select SERIAL_8250
+	---help---
+	  The ACP modem (Mwave) for Linux is a WinModem. It is composed of a
+	  kernel driver and a user level application. Together these components
+	  support direct attachment to public switched telephone networks (PSTNs)
+	  and support selected world wide countries.
+
+	  This version of the ACP Modem driver supports the IBM Thinkpad 600E,
+	  600, and 770 that include on board ACP modem hardware.
+
+	  The modem also supports the standard communications port interface
+	  (ttySx) and is compatible with the Hayes AT Command Set.
+
+	  The user level application needed to use this driver can be found at
+	  the IBM Linux Technology Center (LTC) web site:
+	  <http://www.ibm.com/linux/ltc/>.
+
+	  If you own one of the above IBM Thinkpads which has the Mwave chipset
+	  in it, say Y.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mwave.
+
+config SCx200_GPIO
+	tristate "NatSemi SCx200 GPIO Support"
+	depends on SCx200
+	help
+	  Give userspace access to the GPIO pins on the National
+	  Semiconductor SCx200 processors.
+
+	  If compiled as a module, it will be called scx200_gpio.
+
+config RAW_DRIVER
+	tristate "RAW driver (/dev/raw/rawN) (OBSOLETE)"
+	help
+	  The raw driver permits block devices to be bound to /dev/raw/rawN. 
+	  Once bound, I/O against /dev/raw/rawN uses efficient zero-copy I/O. 
+	  See the raw(8) manpage for more details.
+
+          The raw driver is deprecated and may be removed from 2.7
+          kernels.  Applications should simply open the device (eg /dev/hda1)
+          with the O_DIRECT flag.
+
+config HPET
+	bool "HPET - High Precision Event Timer" if (X86 || IA64)
+	default n
+	depends on ACPI
+	help
+	  If you say Y here, you will have a miscdevice named "/dev/hpet/".  Each
+	  open selects one of the timers supported by the HPET.  The timers are
+	  non-periodioc and/or periodic.
+
+config HPET_RTC_IRQ
+	bool "HPET Control RTC IRQ" if !HPET_EMULATE_RTC
+	default n
+	depends on HPET
+	help
+	  If you say Y here, you will disable RTC_IRQ in drivers/char/rtc.c. It
+	  is assumed the platform called hpet_alloc with the RTC IRQ values for
+	  the HPET timers.
+
+config HPET_MMAP
+	bool "Allow mmap of HPET"
+	default y
+	depends on HPET
+	help
+	  If you say Y here, user applications will be able to mmap
+	  the HPET registers.
+
+	  In some hardware implementations, the page containing HPET
+	  registers may also contain other things that shouldn't be
+	  exposed to the user.  If this applies to your hardware,
+	  say N here.
+
+config MAX_RAW_DEVS
+	int "Maximum number of RAW devices to support (1-8192)"
+	depends on RAW_DRIVER
+	default "256"
+	help
+	  The maximum number of RAW devices that are supported.
+	  Default is 256. Increase this number in case you need lots of
+	  raw devices.
+
+config HANGCHECK_TIMER
+	tristate "Hangcheck timer"
+	depends on X86_64 || X86
+	help
+	  The hangcheck-timer module detects when the system has gone
+	  out to lunch past a certain margin.  It can reboot the system
+	  or merely print a warning.
+
+config MMTIMER
+	tristate "MMTIMER Memory mapped RTC for SGI Altix"
+	depends on IA64_GENERIC || IA64_SGI_SN2
+	default y
+	help
+	  The mmtimer device allows direct userspace access to the
+	  Altix system timer.
+
+source "drivers/char/tpm/Kconfig"
+
+endmenu
+
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
new file mode 100644
index 0000000..54ed76a
--- /dev/null
+++ b/drivers/char/Makefile
@@ -0,0 +1,118 @@
+#
+# Makefile for the kernel character device drivers.
+#
+
+#
+# This file contains the font map for the default (hardware) font
+#
+FONTMAPFILE = cp437.uni
+
+obj-y	 += mem.o random.o tty_io.o n_tty.o tty_ioctl.o
+
+obj-$(CONFIG_LEGACY_PTYS)	+= pty.o
+obj-$(CONFIG_UNIX98_PTYS)	+= pty.o
+obj-y				+= misc.o
+obj-$(CONFIG_VT)		+= vt_ioctl.o vc_screen.o consolemap.o \
+				   consolemap_deftbl.o selection.o keyboard.o
+obj-$(CONFIG_HW_CONSOLE)	+= vt.o defkeymap.o
+obj-$(CONFIG_MAGIC_SYSRQ)	+= sysrq.o
+obj-$(CONFIG_ESPSERIAL)		+= esp.o
+obj-$(CONFIG_MVME147_SCC)	+= generic_serial.o vme_scc.o
+obj-$(CONFIG_MVME162_SCC)	+= generic_serial.o vme_scc.o
+obj-$(CONFIG_BVME6000_SCC)	+= generic_serial.o vme_scc.o
+obj-$(CONFIG_ROCKETPORT)	+= rocket.o
+obj-$(CONFIG_SERIAL167)		+= serial167.o
+obj-$(CONFIG_CYCLADES)		+= cyclades.o
+obj-$(CONFIG_STALLION)		+= stallion.o
+obj-$(CONFIG_ISTALLION)		+= istallion.o
+obj-$(CONFIG_DIGIEPCA)		+= epca.o
+obj-$(CONFIG_SPECIALIX)		+= specialix.o
+obj-$(CONFIG_MOXA_INTELLIO)	+= moxa.o
+obj-$(CONFIG_A2232)		+= ser_a2232.o generic_serial.o
+obj-$(CONFIG_ATARI_DSP56K)	+= dsp56k.o
+obj-$(CONFIG_MOXA_SMARTIO)	+= mxser.o
+obj-$(CONFIG_COMPUTONE)		+= ip2.o ip2main.o
+obj-$(CONFIG_RISCOM8)		+= riscom8.o
+obj-$(CONFIG_ISI)		+= isicom.o
+obj-$(CONFIG_SYNCLINK)		+= synclink.o
+obj-$(CONFIG_SYNCLINKMP)	+= synclinkmp.o
+obj-$(CONFIG_N_HDLC)		+= n_hdlc.o
+obj-$(CONFIG_AMIGA_BUILTIN_SERIAL) += amiserial.o
+obj-$(CONFIG_SX)		+= sx.o generic_serial.o
+obj-$(CONFIG_RIO)		+= rio/ generic_serial.o
+obj-$(CONFIG_HVC_CONSOLE)	+= hvc_console.o hvsi.o
+obj-$(CONFIG_RAW_DRIVER)	+= raw.o
+obj-$(CONFIG_SGI_SNSC)		+= snsc.o
+obj-$(CONFIG_MMTIMER)		+= mmtimer.o
+obj-$(CONFIG_VIOCONS) += viocons.o
+obj-$(CONFIG_VIOTAPE)		+= viotape.o
+obj-$(CONFIG_HVCS)		+= hvcs.o
+
+obj-$(CONFIG_PRINTER) += lp.o
+obj-$(CONFIG_TIPAR) += tipar.o
+
+obj-$(CONFIG_DTLK) += dtlk.o
+obj-$(CONFIG_R3964) += n_r3964.o
+obj-$(CONFIG_APPLICOM) += applicom.o
+obj-$(CONFIG_SONYPI) += sonypi.o
+obj-$(CONFIG_RTC) += rtc.o
+obj-$(CONFIG_HPET) += hpet.o
+obj-$(CONFIG_GEN_RTC) += genrtc.o
+obj-$(CONFIG_EFI_RTC) += efirtc.o
+obj-$(CONFIG_SGI_DS1286) += ds1286.o
+obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o
+obj-$(CONFIG_DS1302) += ds1302.o
+obj-$(CONFIG_S3C2410_RTC) += s3c2410-rtc.o
+obj-$(CONFIG_RTC_VR41XX) += vr41xx_rtc.o
+ifeq ($(CONFIG_GENERIC_NVRAM),y)
+  obj-$(CONFIG_NVRAM) += generic_nvram.o
+else
+  obj-$(CONFIG_NVRAM) += nvram.o
+endif
+obj-$(CONFIG_TOSHIBA) += toshiba.o
+obj-$(CONFIG_I8K) += i8k.o
+obj-$(CONFIG_DS1620) += ds1620.o
+obj-$(CONFIG_HW_RANDOM) += hw_random.o
+obj-$(CONFIG_FTAPE) += ftape/
+obj-$(CONFIG_COBALT_LCD) += lcd.o
+obj-$(CONFIG_PPDEV) += ppdev.o
+obj-$(CONFIG_NWBUTTON) += nwbutton.o
+obj-$(CONFIG_NWFLASH) += nwflash.o
+obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
+obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+
+obj-$(CONFIG_WATCHDOG)	+= watchdog/
+obj-$(CONFIG_MWAVE) += mwave/
+obj-$(CONFIG_AGP) += agp/
+obj-$(CONFIG_DRM) += drm/
+obj-$(CONFIG_PCMCIA) += pcmcia/
+obj-$(CONFIG_IPMI_HANDLER) += ipmi/
+
+obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o
+obj-$(CONFIG_TCG_TPM) += tpm/
+# Files generated that shall be removed upon make clean
+clean-files := consolemap_deftbl.c defkeymap.c qtronixmap.c
+
+quiet_cmd_conmk = CONMK   $@
+      cmd_conmk = scripts/conmakehash $< > $@
+
+$(obj)/consolemap_deftbl.c: $(src)/$(FONTMAPFILE)
+	$(call cmd,conmk)
+
+$(obj)/defkeymap.o:  $(obj)/defkeymap.c
+
+$(obj)/qtronixmap.o: $(obj)/qtronixmap.c
+
+# Uncomment if you're changing the keymap and have an appropriate
+# loadkeys version for the map. By default, we'll use the shipped
+# versions.
+# GENERATE_KEYMAP := 1
+
+ifdef GENERATE_KEYMAP
+
+$(obj)/defkeymap.c $(obj)/qtronixmap.c: $(obj)/%.c: $(src)/%.map
+	loadkeys --mktable $< > $@.tmp
+	sed -e 's/^static *//' $@.tmp > $@
+	rm $@.tmp
+
+endif
diff --git a/drivers/char/agp/Kconfig b/drivers/char/agp/Kconfig
new file mode 100644
index 0000000..7f8c1b5
--- /dev/null
+++ b/drivers/char/agp/Kconfig
@@ -0,0 +1,171 @@
+config AGP
+	tristate "/dev/agpgart (AGP Support)" if !GART_IOMMU
+	depends on ALPHA || IA64 || PPC || X86
+	default y if GART_IOMMU
+	---help---
+	  AGP (Accelerated Graphics Port) is a bus system mainly used to
+	  connect graphics cards to the rest of the system.
+
+	  If you have an AGP system and you say Y here, it will be possible to
+	  use the AGP features of your 3D rendering video card. This code acts
+	  as a sort of "AGP driver" for the motherboard's chipset.
+
+	  If you need more texture memory than you can get with the AGP GART
+	  (theoretically up to 256 MB, but in practice usually 64 or 128 MB
+	  due to kernel allocation issues), you could use PCI accesses
+	  and have up to a couple gigs of texture space.
+
+	  Note that this is the only means to have XFree4/GLX use
+	  write-combining with MTRR support on the AGP bus. Without it, OpenGL
+	  direct rendering will be a lot slower but still faster than PIO.
+
+	  You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+	  use GLX or DRI.  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called agpgart.
+
+config AGP_ALI
+	tristate "ALI chipset support"
+	depends on AGP && X86 && !X86_64
+	---help---
+	  This option gives you AGP support for the GLX component of
+	  XFree86 4.x on the following ALi chipsets.  The supported chipsets
+	  include M1541, M1621, M1631, M1632, M1641,M1647,and M1651.
+	  For the ALi-chipset question, ALi suggests you refer to
+	  <http://www.ali.com.tw/eng/support/index.shtml>.
+
+	  The M1541 chipset can do AGP 1x and 2x, but note that there is an
+	  acknowledged incompatibility with Matrox G200 cards. Due to
+	  timing issues, this chipset cannot do AGP 2x with the G200.
+	  This is a hardware limitation. AGP 1x seems to be fine, though.
+
+	  You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+	  use GLX or DRI.  If unsure, say N.
+
+config AGP_ATI
+	tristate "ATI chipset support"
+	depends on AGP && X86 && !X86_64
+	---help---
+      This option gives you AGP support for the GLX component of
+      XFree86 4.x on the ATI RadeonIGP family of chipsets.
+
+      You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+      use GLX or DRI.  If unsure, say N.
+
+config AGP_AMD
+	tristate "AMD Irongate, 761, and 762 chipset support"
+	depends on AGP && X86 && !X86_64
+	help
+	  This option gives you AGP support for the GLX component of
+	  XFree86 4.x on AMD Irongate, 761, and 762 chipsets.
+
+	  You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+	  use GLX or DRI.  If unsure, say N.
+
+config AGP_AMD64
+	tristate "AMD Opteron/Athlon64 on-CPU GART support" if !GART_IOMMU
+	depends on AGP && X86
+	default y if GART_IOMMU
+	help
+	  This option gives you AGP support for the GLX component of
+	  XFree86 4.x using the on-CPU northbridge of the AMD Athlon64/Opteron CPUs.
+	  You still need an external AGP bridge like the AMD 8151, VIA
+          K8T400M, SiS755. It may also support other AGP bridges when loaded
+	  with agp_try_unsupported=1.
+	  You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+	  use GLX or DRI.  If unsure, say Y
+
+config AGP_INTEL
+	tristate "Intel 440LX/BX/GX, I8xx and E7x05 chipset support"
+	depends on AGP && X86
+	help
+	  This option gives you AGP support for the GLX component of XFree86 4.x
+	  on Intel 440LX/BX/GX, 815, 820, 830, 840, 845, 850, 860, 875,
+	  E7205 and E7505 chipsets and full support for the 810, 815, 830M, 845G,
+	  852GM, 855GM, 865G and I915 integrated graphics chipsets.
+
+	  You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+	  use GLX or DRI, or if you have any Intel integrated graphics
+	  chipsets.  If unsure, say Y.
+
+config AGP_NVIDIA
+	tristate "NVIDIA nForce/nForce2 chipset support"
+	depends on AGP && X86 && !X86_64
+	help
+	  This option gives you AGP support for the GLX component of
+	  XFree86 4.x on the following NVIDIA chipsets.  The supported chipsets
+	  include nForce and nForce2
+
+config AGP_SIS
+	tristate "SiS chipset support"
+	depends on AGP && X86 && !X86_64
+	help
+	  This option gives you AGP support for the GLX component of
+	  XFree86 4.x on Silicon Integrated Systems [SiS] chipsets.
+
+	  Note that 5591/5592 AGP chipsets are NOT supported.
+
+	  You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+	  use GLX or DRI.  If unsure, say N.
+
+config AGP_SWORKS
+	tristate "Serverworks LE/HE chipset support"
+	depends on AGP && X86 && !X86_64
+	help
+	  Say Y here to support the Serverworks AGP card.  See 
+	  <http://www.serverworks.com/> for product descriptions and images.
+
+config AGP_VIA
+	tristate "VIA chipset support"
+	depends on AGP && X86 && !X86_64
+	help
+	  This option gives you AGP support for the GLX component of
+	  XFree86 4.x on VIA MVP3/Apollo Pro chipsets.
+
+	  You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+	  use GLX or DRI.  If unsure, say N.
+
+config AGP_I460
+	tristate "Intel 460GX chipset support"
+	depends on AGP && (IA64_DIG || IA64_GENERIC)
+	help
+	  This option gives you AGP GART support for the Intel 460GX chipset
+	  for IA64 processors.
+
+config AGP_HP_ZX1
+	tristate "HP ZX1 chipset AGP support"
+	depends on AGP && (IA64_HP_ZX1 || IA64_HP_ZX1_SWIOTLB || IA64_GENERIC)
+	help
+	  This option gives you AGP GART support for the HP ZX1 chipset
+	  for IA64 processors.
+
+config AGP_ALPHA_CORE
+	tristate "Alpha AGP support"
+	depends on AGP && (ALPHA_GENERIC || ALPHA_TITAN || ALPHA_MARVEL)
+	default AGP
+
+config AGP_UNINORTH
+	tristate "Apple UniNorth & U3 AGP support"
+	depends on AGP && PPC_PMAC
+	help
+	  This option gives you AGP support for Apple machines with a
+	  UniNorth or U3 (Apple G5) bridge.
+
+config AGP_EFFICEON
+	tristate "Transmeta Efficeon support"
+	depends on AGP && X86 && !X86_64
+	help
+	  This option gives you AGP support for the Transmeta Efficeon
+	  series processors with integrated northbridges.
+
+	  You should say Y here if you use XFree86 3.3.6 or 4.x and want to
+	  use GLX or DRI.  If unsure, say Y.
+
+config AGP_SGI_TIOCA
+        tristate "SGI TIO chipset AGP support"
+        depends on AGP && (IA64_SGI_SN2 || IA64_GENERIC)
+        help
+          This option gives you AGP GART support for the SGI TIO chipset
+          for IA64 processors.
+
diff --git a/drivers/char/agp/Makefile b/drivers/char/agp/Makefile
new file mode 100644
index 0000000..d33a22f
--- /dev/null
+++ b/drivers/char/agp/Makefile
@@ -0,0 +1,18 @@
+agpgart-y := backend.o frontend.o generic.o isoch.o
+
+obj-$(CONFIG_AGP)		+= agpgart.o
+obj-$(CONFIG_AGP_ALI)		+= ali-agp.o
+obj-$(CONFIG_AGP_ATI)		+= ati-agp.o
+obj-$(CONFIG_AGP_AMD)		+= amd-k7-agp.o
+obj-$(CONFIG_AGP_AMD64)		+= amd64-agp.o
+obj-$(CONFIG_AGP_ALPHA_CORE)	+= alpha-agp.o
+obj-$(CONFIG_AGP_EFFICEON)	+= efficeon-agp.o
+obj-$(CONFIG_AGP_HP_ZX1)	+= hp-agp.o
+obj-$(CONFIG_AGP_I460)		+= i460-agp.o
+obj-$(CONFIG_AGP_INTEL)		+= intel-agp.o
+obj-$(CONFIG_AGP_NVIDIA)	+= nvidia-agp.o
+obj-$(CONFIG_AGP_SGI_TIOCA)	+= sgi-agp.o
+obj-$(CONFIG_AGP_SIS)		+= sis-agp.o
+obj-$(CONFIG_AGP_SWORKS)	+= sworks-agp.o
+obj-$(CONFIG_AGP_UNINORTH)	+= uninorth-agp.o
+obj-$(CONFIG_AGP_VIA)		+= via-agp.o
diff --git a/drivers/char/agp/agp.h b/drivers/char/agp/agp.h
new file mode 100644
index 0000000..ad9c113
--- /dev/null
+++ b/drivers/char/agp/agp.h
@@ -0,0 +1,331 @@
+/*
+ * AGPGART
+ * Copyright (C) 2004 Silicon Graphics, Inc.
+ * Copyright (C) 2002-2004 Dave Jones
+ * Copyright (C) 1999 Jeff Hartmann
+ * Copyright (C) 1999 Precision Insight, Inc.
+ * Copyright (C) 1999 Xi Graphics, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * JEFF HARTMANN, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM, 
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#ifndef _AGP_BACKEND_PRIV_H
+#define _AGP_BACKEND_PRIV_H 1
+
+#include <asm/agp.h>	/* for flush_agp_cache() */
+
+#define PFX "agpgart: "
+
+//#define AGP_DEBUG 1
+#ifdef AGP_DEBUG
+#define DBG(x,y...) printk (KERN_DEBUG PFX "%s: " x "\n", __FUNCTION__ , ## y)
+#else
+#define DBG(x,y...) do { } while (0)
+#endif
+
+extern struct agp_bridge_data *agp_bridge;
+
+enum aper_size_type {
+	U8_APER_SIZE,
+	U16_APER_SIZE,
+	U32_APER_SIZE,
+	LVL2_APER_SIZE,
+	FIXED_APER_SIZE
+};
+
+struct gatt_mask {
+	unsigned long mask;
+	u32 type;
+	/* totally device specific, for integrated chipsets that 
+	 * might have different types of memory masks.  For other
+	 * devices this will probably be ignored */
+};
+
+struct aper_size_info_8 {
+	int size;
+	int num_entries;
+	int page_order;
+	u8 size_value;
+};
+
+struct aper_size_info_16 {
+	int size;
+	int num_entries;
+	int page_order;
+	u16 size_value;
+};
+
+struct aper_size_info_32 {
+	int size;
+	int num_entries;
+	int page_order;
+	u32 size_value;
+};
+
+struct aper_size_info_lvl2 {
+	int size;
+	int num_entries;
+	u32 size_value;
+};
+
+struct aper_size_info_fixed {
+	int size;
+	int num_entries;
+	int page_order;
+};
+
+struct agp_bridge_driver {
+	struct module *owner;
+	void *aperture_sizes;
+	int num_aperture_sizes;
+	enum aper_size_type size_type;
+	int cant_use_aperture;
+	int needs_scratch_page;
+	struct gatt_mask *masks;
+	int (*fetch_size)(void);
+	int (*configure)(void);
+	void (*agp_enable)(struct agp_bridge_data *, u32);
+	void (*cleanup)(void);
+	void (*tlb_flush)(struct agp_memory *);
+	unsigned long (*mask_memory)(struct agp_bridge_data *,
+		unsigned long, int);
+	void (*cache_flush)(void);
+	int (*create_gatt_table)(struct agp_bridge_data *);
+	int (*free_gatt_table)(struct agp_bridge_data *);
+	int (*insert_memory)(struct agp_memory *, off_t, int);
+	int (*remove_memory)(struct agp_memory *, off_t, int);
+	struct agp_memory *(*alloc_by_type) (size_t, int);
+	void (*free_by_type)(struct agp_memory *);
+	void *(*agp_alloc_page)(struct agp_bridge_data *);
+	void (*agp_destroy_page)(void *);
+};
+
+struct agp_bridge_data {
+	struct agp_version *version;
+	struct agp_bridge_driver *driver;
+	struct vm_operations_struct *vm_ops;
+	void *previous_size;
+	void *current_size;
+	void *dev_private_data;
+	struct pci_dev *dev;
+	u32 __iomem *gatt_table;
+	u32 *gatt_table_real;
+	unsigned long scratch_page;
+	unsigned long scratch_page_real;
+	unsigned long gart_bus_addr;
+	unsigned long gatt_bus_addr;
+	u32 mode;
+	enum chipset_type type;
+	unsigned long *key_list;
+	atomic_t current_memory_agp;
+	atomic_t agp_in_use;
+	int max_memory_agp;	/* in number of pages */
+	int aperture_size_idx;
+	int capndx;
+	int flags;
+	char major_version;
+	char minor_version;
+	struct list_head list;
+};
+
+#define KB(x)	((x) * 1024)
+#define MB(x)	(KB (KB (x)))
+#define GB(x)	(MB (KB (x)))
+
+#define A_SIZE_8(x)	((struct aper_size_info_8 *) x)
+#define A_SIZE_16(x)	((struct aper_size_info_16 *) x)
+#define A_SIZE_32(x)	((struct aper_size_info_32 *) x)
+#define A_SIZE_LVL2(x)	((struct aper_size_info_lvl2 *) x)
+#define A_SIZE_FIX(x)	((struct aper_size_info_fixed *) x)
+#define A_IDX8(bridge)	(A_SIZE_8((bridge)->driver->aperture_sizes) + i)
+#define A_IDX16(bridge)	(A_SIZE_16((bridge)->driver->aperture_sizes) + i)
+#define A_IDX32(bridge)	(A_SIZE_32((bridge)->driver->aperture_sizes) + i)
+#define MAXKEY		(4096 * 32)
+
+#define PGE_EMPTY(b, p)	(!(p) || (p) == (unsigned long) (b)->scratch_page)
+
+
+/* Intel registers */
+#define INTEL_APSIZE	0xb4
+#define INTEL_ATTBASE	0xb8
+#define INTEL_AGPCTRL	0xb0
+#define INTEL_NBXCFG	0x50
+#define INTEL_ERRSTS	0x91
+
+/* Intel i830 registers */
+#define I830_GMCH_CTRL			0x52
+#define I830_GMCH_ENABLED		0x4
+#define I830_GMCH_MEM_MASK		0x1
+#define I830_GMCH_MEM_64M		0x1
+#define I830_GMCH_MEM_128M		0
+#define I830_GMCH_GMS_MASK		0x70
+#define I830_GMCH_GMS_DISABLED		0x00
+#define I830_GMCH_GMS_LOCAL		0x10
+#define I830_GMCH_GMS_STOLEN_512	0x20
+#define I830_GMCH_GMS_STOLEN_1024	0x30
+#define I830_GMCH_GMS_STOLEN_8192	0x40
+#define I830_RDRAM_CHANNEL_TYPE		0x03010
+#define I830_RDRAM_ND(x)		(((x) & 0x20) >> 5)
+#define I830_RDRAM_DDT(x)		(((x) & 0x18) >> 3)
+
+/* This one is for I830MP w. an external graphic card */
+#define INTEL_I830_ERRSTS	0x92
+
+/* Intel 855GM/852GM registers */
+#define I855_GMCH_GMS_STOLEN_0M		0x0
+#define I855_GMCH_GMS_STOLEN_1M		(0x1 << 4)
+#define I855_GMCH_GMS_STOLEN_4M		(0x2 << 4)
+#define I855_GMCH_GMS_STOLEN_8M		(0x3 << 4)
+#define I855_GMCH_GMS_STOLEN_16M	(0x4 << 4)
+#define I855_GMCH_GMS_STOLEN_32M	(0x5 << 4)
+#define I85X_CAPID			0x44
+#define I85X_VARIANT_MASK		0x7
+#define I85X_VARIANT_SHIFT		5
+#define I855_GME			0x0
+#define I855_GM				0x4
+#define I852_GME			0x2
+#define I852_GM				0x5
+
+/* Intel i845 registers */
+#define INTEL_I845_AGPM		0x51
+#define INTEL_I845_ERRSTS	0xc8
+
+/* Intel i860 registers */
+#define INTEL_I860_MCHCFG	0x50
+#define INTEL_I860_ERRSTS	0xc8
+
+/* Intel i810 registers */
+#define I810_GMADDR		0x10
+#define I810_MMADDR		0x14
+#define I810_PTE_BASE		0x10000
+#define I810_PTE_MAIN_UNCACHED	0x00000000
+#define I810_PTE_LOCAL		0x00000002
+#define I810_PTE_VALID		0x00000001
+#define I810_SMRAM_MISCC	0x70
+#define I810_GFX_MEM_WIN_SIZE	0x00010000
+#define I810_GFX_MEM_WIN_32M	0x00010000
+#define I810_GMS		0x000000c0
+#define I810_GMS_DISABLE	0x00000000
+#define I810_PGETBL_CTL		0x2020
+#define I810_PGETBL_ENABLED	0x00000001
+#define I810_DRAM_CTL		0x3000
+#define I810_DRAM_ROW_0		0x00000001
+#define I810_DRAM_ROW_0_SDRAM	0x00000001
+
+struct agp_device_ids {
+	unsigned short device_id; /* first, to make table easier to read */
+	enum chipset_type chipset;
+	const char *chipset_name;
+	int (*chipset_setup) (struct pci_dev *pdev);	/* used to override generic */
+};
+
+/* Driver registration */
+struct agp_bridge_data *agp_alloc_bridge(void);
+void agp_put_bridge(struct agp_bridge_data *bridge);
+int agp_add_bridge(struct agp_bridge_data *bridge);
+void agp_remove_bridge(struct agp_bridge_data *bridge);
+
+/* Frontend routines. */
+int agp_frontend_initialize(void);
+void agp_frontend_cleanup(void);
+
+/* Generic routines. */
+void agp_generic_enable(struct agp_bridge_data *bridge, u32 mode);
+int agp_generic_create_gatt_table(struct agp_bridge_data *bridge);
+int agp_generic_free_gatt_table(struct agp_bridge_data *bridge);
+struct agp_memory *agp_create_memory(int scratch_pages);
+int agp_generic_insert_memory(struct agp_memory *mem, off_t pg_start, int type);
+int agp_generic_remove_memory(struct agp_memory *mem, off_t pg_start, int type);
+struct agp_memory *agp_generic_alloc_by_type(size_t page_count, int type);
+void agp_generic_free_by_type(struct agp_memory *curr);
+void *agp_generic_alloc_page(struct agp_bridge_data *bridge);
+void agp_generic_destroy_page(void *addr);
+void agp_free_key(int key);
+int agp_num_entries(void);
+u32 agp_collect_device_status(struct agp_bridge_data *bridge, u32 mode, u32 command);
+void agp_device_command(u32 command, int agp_v3);
+int agp_3_5_enable(struct agp_bridge_data *bridge);
+void global_cache_flush(void);
+void get_agp_version(struct agp_bridge_data *bridge);
+unsigned long agp_generic_mask_memory(struct agp_bridge_data *bridge,
+	unsigned long addr, int type);
+struct agp_bridge_data *agp_generic_find_bridge(struct pci_dev *pdev);
+
+/* generic routines for agp>=3 */
+int agp3_generic_fetch_size(void);
+void agp3_generic_tlbflush(struct agp_memory *mem);
+int agp3_generic_configure(void);
+void agp3_generic_cleanup(void);
+
+/* aperture sizes have been standardised since v3 */
+#define AGP_GENERIC_SIZES_ENTRIES 11
+extern struct aper_size_info_16 agp3_generic_sizes[];
+
+
+extern int agp_off;
+extern int agp_try_unsupported_boot;
+
+/* Chipset independant registers (from AGP Spec) */
+#define AGP_APBASE	0x10
+
+#define AGPSTAT		0x4
+#define AGPCMD		0x8
+#define AGPNISTAT	0xc
+#define AGPCTRL		0x10
+#define AGPAPSIZE	0x14
+#define AGPNEPG		0x16
+#define AGPGARTLO	0x18
+#define AGPGARTHI	0x1c
+#define AGPNICMD	0x20
+
+#define AGP_MAJOR_VERSION_SHIFT	(20)
+#define AGP_MINOR_VERSION_SHIFT	(16)
+
+#define AGPSTAT_RQ_DEPTH	(0xff000000)
+#define AGPSTAT_RQ_DEPTH_SHIFT	24
+
+#define AGPSTAT_CAL_MASK	(1<<12|1<<11|1<<10)
+#define AGPSTAT_ARQSZ		(1<<15|1<<14|1<<13)
+#define AGPSTAT_ARQSZ_SHIFT	13
+
+#define AGPSTAT_SBA		(1<<9)
+#define AGPSTAT_AGP_ENABLE	(1<<8)
+#define AGPSTAT_FW		(1<<4)
+#define AGPSTAT_MODE_3_0	(1<<3)
+
+#define AGPSTAT2_1X		(1<<0)
+#define AGPSTAT2_2X		(1<<1)
+#define AGPSTAT2_4X		(1<<2)
+
+#define AGPSTAT3_RSVD		(1<<2)
+#define AGPSTAT3_8X		(1<<1)
+#define AGPSTAT3_4X		(1)
+
+#define AGPCTRL_APERENB		(1<<8)
+#define AGPCTRL_GTLBEN		(1<<7)
+
+#define AGP2_RESERVED_MASK 0x00fffcc8
+#define AGP3_RESERVED_MASK 0x00ff00c4
+
+#define AGP_ERRATA_FASTWRITES 1<<0
+#define AGP_ERRATA_SBA	 1<<1
+#define AGP_ERRATA_1X 1<<2
+
+#endif	/* _AGP_BACKEND_PRIV_H */
diff --git a/drivers/char/agp/ali-agp.c b/drivers/char/agp/ali-agp.c
new file mode 100644
index 0000000..c86a22c
--- /dev/null
+++ b/drivers/char/agp/ali-agp.c
@@ -0,0 +1,414 @@
+/*
+ * ALi AGPGART routines.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+#define ALI_AGPCTRL	0xb8
+#define ALI_ATTBASE	0xbc
+#define ALI_TLBCTRL	0xc0
+#define ALI_TAGCTRL	0xc4
+#define ALI_CACHE_FLUSH_CTRL	0xD0
+#define ALI_CACHE_FLUSH_ADDR_MASK	0xFFFFF000
+#define ALI_CACHE_FLUSH_EN	0x100
+
+static int ali_fetch_size(void)
+{
+	int i;
+	u32 temp;
+	struct aper_size_info_32 *values;
+
+	pci_read_config_dword(agp_bridge->dev, ALI_ATTBASE, &temp);
+	temp &= ~(0xfffffff0);
+	values = A_SIZE_32(agp_bridge->driver->aperture_sizes);
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+static void ali_tlbflush(struct agp_memory *mem)
+{
+	u32 temp;
+
+	pci_read_config_dword(agp_bridge->dev, ALI_TLBCTRL, &temp);
+	temp &= 0xfffffff0;
+	temp |= (1<<0 | 1<<1);
+	pci_write_config_dword(agp_bridge->dev, ALI_TAGCTRL, temp);
+}
+
+static void ali_cleanup(void)
+{
+	struct aper_size_info_32 *previous_size;
+	u32 temp;
+
+	previous_size = A_SIZE_32(agp_bridge->previous_size);
+
+	pci_read_config_dword(agp_bridge->dev, ALI_TLBCTRL, &temp);
+// clear tag
+	pci_write_config_dword(agp_bridge->dev, ALI_TAGCTRL,
+			((temp & 0xffffff00) | 0x00000001|0x00000002));
+
+	pci_read_config_dword(agp_bridge->dev,  ALI_ATTBASE, &temp);
+	pci_write_config_dword(agp_bridge->dev, ALI_ATTBASE,
+			((temp & 0x00000ff0) | previous_size->size_value));
+}
+
+static int ali_configure(void)
+{
+	u32 temp;
+	struct aper_size_info_32 *current_size;
+
+	current_size = A_SIZE_32(agp_bridge->current_size);
+
+	/* aperture size and gatt addr */
+	pci_read_config_dword(agp_bridge->dev, ALI_ATTBASE, &temp);
+	temp = (((temp & 0x00000ff0) | (agp_bridge->gatt_bus_addr & 0xfffff000))
+			| (current_size->size_value & 0xf));
+	pci_write_config_dword(agp_bridge->dev, ALI_ATTBASE, temp);
+
+	/* tlb control */
+	pci_read_config_dword(agp_bridge->dev, ALI_TLBCTRL, &temp);
+	pci_write_config_dword(agp_bridge->dev, ALI_TLBCTRL, ((temp & 0xffffff00) | 0x00000010));
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+#if 0
+	if (agp_bridge->type == ALI_M1541) {
+		u32 nlvm_addr = 0;
+
+		switch (current_size->size_value) {
+			case 0:  break;
+			case 1:  nlvm_addr = 0x100000;break;
+			case 2:  nlvm_addr = 0x200000;break;
+			case 3:  nlvm_addr = 0x400000;break;
+			case 4:  nlvm_addr = 0x800000;break;
+			case 6:  nlvm_addr = 0x1000000;break;
+			case 7:  nlvm_addr = 0x2000000;break;
+			case 8:  nlvm_addr = 0x4000000;break;
+			case 9:  nlvm_addr = 0x8000000;break;
+			case 10: nlvm_addr = 0x10000000;break;
+			default: break;
+		}
+		nlvm_addr--;
+		nlvm_addr&=0xfff00000;
+
+		nlvm_addr+= agp_bridge->gart_bus_addr;
+		nlvm_addr|=(agp_bridge->gart_bus_addr>>12);
+		printk(KERN_INFO PFX "nlvm top &base = %8x\n",nlvm_addr);
+	}
+#endif
+
+	pci_read_config_dword(agp_bridge->dev, ALI_TLBCTRL, &temp);
+	temp &= 0xffffff7f;		//enable TLB
+	pci_write_config_dword(agp_bridge->dev, ALI_TLBCTRL, temp);
+
+	return 0;
+}
+
+
+static void m1541_cache_flush(void)
+{
+	int i, page_count;
+	u32 temp;
+
+	global_cache_flush();
+
+	page_count = 1 << A_SIZE_32(agp_bridge->current_size)->page_order;
+	for (i = 0; i < PAGE_SIZE * page_count; i += PAGE_SIZE) {
+		pci_read_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL,
+				&temp);
+		pci_write_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL,
+				(((temp & ALI_CACHE_FLUSH_ADDR_MASK) |
+				  (agp_bridge->gatt_bus_addr + i)) |
+				 ALI_CACHE_FLUSH_EN));
+	}
+}
+
+static void *m1541_alloc_page(struct agp_bridge_data *bridge)
+{
+	void *addr = agp_generic_alloc_page(agp_bridge);
+	u32 temp;
+
+	if (!addr)
+		return NULL;
+	
+	pci_read_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL, &temp);
+	pci_write_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL,
+			(((temp & ALI_CACHE_FLUSH_ADDR_MASK) |
+			  virt_to_phys(addr)) | ALI_CACHE_FLUSH_EN ));
+	return addr;
+}
+
+static void ali_destroy_page(void * addr)
+{
+	if (addr) {
+		global_cache_flush();	/* is this really needed?  --hch */
+		agp_generic_destroy_page(addr);
+	}
+}
+
+static void m1541_destroy_page(void * addr)
+{
+	u32 temp;
+
+	if (addr == NULL)
+		return;
+
+	global_cache_flush();
+
+	pci_read_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL, &temp);
+	pci_write_config_dword(agp_bridge->dev, ALI_CACHE_FLUSH_CTRL,
+			(((temp & ALI_CACHE_FLUSH_ADDR_MASK) |
+			  virt_to_phys(addr)) | ALI_CACHE_FLUSH_EN));
+	agp_generic_destroy_page(addr);
+}
+
+
+/* Setup function */
+
+static struct aper_size_info_32 ali_generic_sizes[7] =
+{
+	{256, 65536, 6, 10},
+	{128, 32768, 5, 9},
+	{64, 16384, 4, 8},
+	{32, 8192, 3, 7},
+	{16, 4096, 2, 6},
+	{8, 2048, 1, 4},
+	{4, 1024, 0, 3}
+};
+
+struct agp_bridge_driver ali_generic_bridge = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= ali_generic_sizes,
+	.size_type		= U32_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= ali_configure,
+	.fetch_size		= ali_fetch_size,
+	.cleanup		= ali_cleanup,
+	.tlb_flush		= ali_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= NULL,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= ali_destroy_page,
+};
+
+struct agp_bridge_driver ali_m1541_bridge = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= ali_generic_sizes,
+	.size_type		= U32_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= ali_configure,
+	.fetch_size		= ali_fetch_size,
+	.cleanup		= ali_cleanup,
+	.tlb_flush		= ali_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= NULL,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= m1541_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= m1541_alloc_page,
+	.agp_destroy_page	= m1541_destroy_page,
+};
+
+
+static struct agp_device_ids ali_agp_device_ids[] __devinitdata =
+{
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1541,
+		.chipset_name	= "M1541",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1621,
+		.chipset_name	= "M1621",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1631,
+		.chipset_name	= "M1631",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1632,
+		.chipset_name	= "M1632",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1641,
+		.chipset_name	= "M1641",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1644,
+		.chipset_name	= "M1644",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1647,
+		.chipset_name	= "M1647",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1651,
+		.chipset_name	= "M1651",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1671,
+		.chipset_name	= "M1671",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1681,
+		.chipset_name	= "M1681",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AL_M1683,
+		.chipset_name	= "M1683",
+	},
+
+	{ }, /* dummy final entry, always present */
+};
+
+static int __devinit agp_ali_probe(struct pci_dev *pdev,
+				const struct pci_device_id *ent)
+{
+	struct agp_device_ids *devs = ali_agp_device_ids;
+	struct agp_bridge_data *bridge;
+	u8 hidden_1621_id, cap_ptr;
+	int j;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	/* probe for known chipsets */
+	for (j = 0; devs[j].chipset_name; j++) {
+		if (pdev->device == devs[j].device_id)
+			goto found;
+	}
+
+	printk(KERN_ERR PFX "Unsupported ALi chipset (device id: %04x)\n",
+	     pdev->device);
+	return -ENODEV;
+
+
+found:
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+
+	switch (pdev->device) {
+	case PCI_DEVICE_ID_AL_M1541:
+		bridge->driver = &ali_m1541_bridge;
+		break;
+	case PCI_DEVICE_ID_AL_M1621:
+		pci_read_config_byte(pdev, 0xFB, &hidden_1621_id);
+		switch (hidden_1621_id) {
+		case 0x31:
+			devs[j].chipset_name = "M1631";
+			break;
+		case 0x32:
+			devs[j].chipset_name = "M1632";
+			break;
+		case 0x41:
+			devs[j].chipset_name = "M1641";
+			break;
+		case 0x43:
+			devs[j].chipset_name = "M????";
+			break;
+		case 0x47:
+			devs[j].chipset_name = "M1647";
+			break;
+		case 0x51:
+			devs[j].chipset_name = "M1651";
+			break;
+		default:
+			break;
+		}
+		/*FALLTHROUGH*/
+	default:
+		bridge->driver = &ali_generic_bridge;
+	}
+
+	printk(KERN_INFO PFX "Detected ALi %s chipset\n",
+			devs[j].chipset_name);
+
+	/* Fill in the mode register */
+	pci_read_config_dword(pdev,
+			bridge->capndx+PCI_AGP_STATUS,
+			&bridge->mode);
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_ali_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_ali_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_AL,
+	.device		= PCI_ANY_ID,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_ali_pci_table);
+
+static struct pci_driver agp_ali_pci_driver = {
+	.name		= "agpgart-ali",
+	.id_table	= agp_ali_pci_table,
+	.probe		= agp_ali_probe,
+	.remove		= agp_ali_remove,
+};
+
+static int __init agp_ali_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_ali_pci_driver);
+}
+
+static void __exit agp_ali_cleanup(void)
+{
+	pci_unregister_driver(&agp_ali_pci_driver);
+}
+
+module_init(agp_ali_init);
+module_exit(agp_ali_cleanup);
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
+MODULE_LICENSE("GPL and additional rights");
+
diff --git a/drivers/char/agp/alpha-agp.c b/drivers/char/agp/alpha-agp.c
new file mode 100644
index 0000000..a072d32
--- /dev/null
+++ b/drivers/char/agp/alpha-agp.c
@@ -0,0 +1,216 @@
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+
+#include <asm/machvec.h>
+#include <asm/agp_backend.h>
+#include "../../../arch/alpha/kernel/pci_impl.h"
+
+#include "agp.h"
+
+static struct page *alpha_core_agp_vm_nopage(struct vm_area_struct *vma,
+					     unsigned long address,
+					     int *type)
+{
+	alpha_agp_info *agp = agp_bridge->dev_private_data;
+	dma_addr_t dma_addr;
+	unsigned long pa;
+	struct page *page;
+
+	dma_addr = address - vma->vm_start + agp->aperture.bus_base;
+	pa = agp->ops->translate(agp, dma_addr);
+
+	if (pa == (unsigned long)-EINVAL) return NULL;	/* no translation */
+	
+	/*
+	 * Get the page, inc the use count, and return it
+	 */
+	page = virt_to_page(__va(pa));
+	get_page(page);
+	if (type)
+		*type = VM_FAULT_MINOR;
+	return page;
+}
+
+static struct aper_size_info_fixed alpha_core_agp_sizes[] =
+{
+	{ 0, 0, 0 }, /* filled in by alpha_core_agp_setup */
+};
+
+struct vm_operations_struct alpha_core_agp_vm_ops = {
+	.nopage = alpha_core_agp_vm_nopage,
+};
+
+
+static int alpha_core_agp_nop(void)
+{
+	/* just return success */
+	return 0;
+}
+
+static int alpha_core_agp_fetch_size(void)
+{
+	return alpha_core_agp_sizes[0].size;
+}
+
+static int alpha_core_agp_configure(void)
+{
+	alpha_agp_info *agp = agp_bridge->dev_private_data;
+	agp_bridge->gart_bus_addr = agp->aperture.bus_base;
+	return 0;
+}
+
+static void alpha_core_agp_cleanup(void)
+{
+	alpha_agp_info *agp = agp_bridge->dev_private_data;
+
+	agp->ops->cleanup(agp);
+}
+
+static void alpha_core_agp_tlbflush(struct agp_memory *mem)
+{
+	alpha_agp_info *agp = agp_bridge->dev_private_data;
+	alpha_mv.mv_pci_tbi(agp->hose, 0, -1);
+}
+
+static void alpha_core_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+	alpha_agp_info *agp = bridge->dev_private_data;
+
+	agp->mode.lw = agp_collect_device_status(bridge, mode,
+					agp->capability.lw);
+
+	agp->mode.bits.enable = 1;
+	agp->ops->configure(agp);
+
+	agp_device_command(agp->mode.lw, 0);
+}
+
+static int alpha_core_agp_insert_memory(struct agp_memory *mem, off_t pg_start, 
+					int type)
+{
+	alpha_agp_info *agp = agp_bridge->dev_private_data;
+	int num_entries, status;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+	num_entries = A_SIZE_FIX(temp)->num_entries;
+	if ((pg_start + mem->page_count) > num_entries) return -EINVAL;
+
+	status = agp->ops->bind(agp, pg_start, mem);
+	mb();
+	alpha_core_agp_tlbflush(mem);
+
+	return status;
+}
+
+static int alpha_core_agp_remove_memory(struct agp_memory *mem, off_t pg_start, 
+					int type)
+{
+	alpha_agp_info *agp = agp_bridge->dev_private_data;
+	int status;
+
+	status = agp->ops->unbind(agp, pg_start, mem);
+	alpha_core_agp_tlbflush(mem);
+	return status;
+}
+
+struct agp_bridge_driver alpha_core_agp_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= alpha_core_agp_sizes,
+	.num_aperture_sizes	= 1,
+	.size_type		= FIXED_APER_SIZE,
+	.cant_use_aperture	= 1,
+	.masks			= NULL,
+	
+	.fetch_size		= alpha_core_agp_fetch_size,
+	.configure		= alpha_core_agp_configure,
+	.agp_enable		= alpha_core_agp_enable,
+	.cleanup		= alpha_core_agp_cleanup,
+	.tlb_flush		= alpha_core_agp_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= alpha_core_agp_nop,
+	.free_gatt_table	= alpha_core_agp_nop,
+	.insert_memory		= alpha_core_agp_insert_memory,
+	.remove_memory		= alpha_core_agp_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+struct agp_bridge_data *alpha_bridge;
+
+int __init
+alpha_core_agp_setup(void)
+{
+	alpha_agp_info *agp = alpha_mv.agp_info();
+	struct pci_dev *pdev;	/* faked */
+	struct aper_size_info_fixed *aper_size;
+
+	if (!agp)
+		return -ENODEV;
+	if (agp->ops->setup(agp))
+		return -ENODEV;
+
+	/*
+	 * Build the aperture size descriptor
+	 */
+	aper_size = alpha_core_agp_sizes;
+	aper_size->size = agp->aperture.size / (1024 * 1024);
+	aper_size->num_entries = agp->aperture.size / PAGE_SIZE;
+	aper_size->page_order = __ffs(aper_size->num_entries / 1024);
+
+	/*
+	 * Build a fake pci_dev struct
+	 */
+	pdev = kmalloc(sizeof(struct pci_dev), GFP_KERNEL);
+	if (!pdev)
+		return -ENOMEM;
+	pdev->vendor = 0xffff;
+	pdev->device = 0xffff;
+	pdev->sysdata = agp->hose;
+
+	alpha_bridge = agp_alloc_bridge();
+	if (!alpha_bridge)
+		goto fail;
+
+	alpha_bridge->driver = &alpha_core_agp_driver;
+	alpha_bridge->vm_ops = &alpha_core_agp_vm_ops;
+	alpha_bridge->current_size = aper_size; /* only 1 size */
+	alpha_bridge->dev_private_data = agp;
+	alpha_bridge->dev = pdev;
+	alpha_bridge->mode = agp->capability.lw;
+
+	printk(KERN_INFO PFX "Detected AGP on hose %d\n", agp->hose->index);
+	return agp_add_bridge(alpha_bridge);
+
+ fail:
+	kfree(pdev);
+	return -ENOMEM;
+}
+
+static int __init agp_alpha_core_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	if (alpha_mv.agp_info)
+		return alpha_core_agp_setup();
+	return -ENODEV;
+}
+
+static void __exit agp_alpha_core_cleanup(void)
+{
+	agp_remove_bridge(alpha_bridge);
+	agp_put_bridge(alpha_bridge);
+}
+
+module_init(agp_alpha_core_init);
+module_exit(agp_alpha_core_cleanup);
+
+MODULE_AUTHOR("Jeff Wiedemeier <Jeff.Wiedemeier@hp.com>");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/amd-k7-agp.c b/drivers/char/agp/amd-k7-agp.c
new file mode 100644
index 0000000..f1ea87e
--- /dev/null
+++ b/drivers/char/agp/amd-k7-agp.c
@@ -0,0 +1,542 @@
+/*
+ * AMD K7 AGPGART routines.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/gfp.h>
+#include <linux/page-flags.h>
+#include <linux/mm.h>
+#include "agp.h"
+
+#define AMD_MMBASE	0x14
+#define AMD_APSIZE	0xac
+#define AMD_MODECNTL	0xb0
+#define AMD_MODECNTL2	0xb2
+#define AMD_GARTENABLE	0x02	/* In mmio region (16-bit register) */
+#define AMD_ATTBASE	0x04	/* In mmio region (32-bit register) */
+#define AMD_TLBFLUSH	0x0c	/* In mmio region (32-bit register) */
+#define AMD_CACHEENTRY	0x10	/* In mmio region (32-bit register) */
+
+static struct pci_device_id agp_amdk7_pci_table[];
+
+struct amd_page_map {
+	unsigned long *real;
+	unsigned long __iomem *remapped;
+};
+
+static struct _amd_irongate_private {
+	volatile u8 __iomem *registers;
+	struct amd_page_map **gatt_pages;
+	int num_tables;
+} amd_irongate_private;
+
+static int amd_create_page_map(struct amd_page_map *page_map)
+{
+	int i;
+
+	page_map->real = (unsigned long *) __get_free_page(GFP_KERNEL);
+	if (page_map->real == NULL)
+		return -ENOMEM;
+
+	SetPageReserved(virt_to_page(page_map->real));
+	global_cache_flush();
+	page_map->remapped = ioremap_nocache(virt_to_phys(page_map->real),
+					    PAGE_SIZE);
+	if (page_map->remapped == NULL) {
+		ClearPageReserved(virt_to_page(page_map->real));
+		free_page((unsigned long) page_map->real);
+		page_map->real = NULL;
+		return -ENOMEM;
+	}
+	global_cache_flush();
+
+	for (i = 0; i < PAGE_SIZE / sizeof(unsigned long); i++) {
+		writel(agp_bridge->scratch_page, page_map->remapped+i);
+		readl(page_map->remapped+i);	/* PCI Posting. */
+	}
+
+	return 0;
+}
+
+static void amd_free_page_map(struct amd_page_map *page_map)
+{
+	iounmap(page_map->remapped);
+	ClearPageReserved(virt_to_page(page_map->real));
+	free_page((unsigned long) page_map->real);
+}
+
+static void amd_free_gatt_pages(void)
+{
+	int i;
+	struct amd_page_map **tables;
+	struct amd_page_map *entry;
+
+	tables = amd_irongate_private.gatt_pages;
+	for (i = 0; i < amd_irongate_private.num_tables; i++) {
+		entry = tables[i];
+		if (entry != NULL) {
+			if (entry->real != NULL)
+				amd_free_page_map(entry);
+			kfree(entry);
+		}
+	}
+	kfree(tables);
+	amd_irongate_private.gatt_pages = NULL;
+}
+
+static int amd_create_gatt_pages(int nr_tables)
+{
+	struct amd_page_map **tables;
+	struct amd_page_map *entry;
+	int retval = 0;
+	int i;
+
+	tables = kmalloc((nr_tables + 1) * sizeof(struct amd_page_map *),
+			 GFP_KERNEL);
+	if (tables == NULL)
+		return -ENOMEM;
+
+	memset (tables, 0, sizeof(struct amd_page_map *) * (nr_tables + 1));
+	for (i = 0; i < nr_tables; i++) {
+		entry = kmalloc(sizeof(struct amd_page_map), GFP_KERNEL);
+		if (entry == NULL) {
+			retval = -ENOMEM;
+			break;
+		}
+		memset (entry, 0, sizeof(struct amd_page_map));
+		tables[i] = entry;
+		retval = amd_create_page_map(entry);
+		if (retval != 0)
+			break;
+	}
+	amd_irongate_private.num_tables = nr_tables;
+	amd_irongate_private.gatt_pages = tables;
+
+	if (retval != 0)
+		amd_free_gatt_pages();
+
+	return retval;
+}
+
+/* Since we don't need contigious memory we just try
+ * to get the gatt table once
+ */
+
+#define GET_PAGE_DIR_OFF(addr) (addr >> 22)
+#define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
+	GET_PAGE_DIR_OFF(agp_bridge->gart_bus_addr))
+#define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
+#define GET_GATT(addr) (amd_irongate_private.gatt_pages[\
+	GET_PAGE_DIR_IDX(addr)]->remapped)
+
+static int amd_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	struct aper_size_info_lvl2 *value;
+	struct amd_page_map page_dir;
+	unsigned long addr;
+	int retval;
+	u32 temp;
+	int i;
+
+	value = A_SIZE_LVL2(agp_bridge->current_size);
+	retval = amd_create_page_map(&page_dir);
+	if (retval != 0)
+		return retval;
+
+	retval = amd_create_gatt_pages(value->num_entries / 1024);
+	if (retval != 0) {
+		amd_free_page_map(&page_dir);
+		return retval;
+	}
+
+	agp_bridge->gatt_table_real = (u32 *)page_dir.real;
+	agp_bridge->gatt_table = (u32 __iomem *)page_dir.remapped;
+	agp_bridge->gatt_bus_addr = virt_to_phys(page_dir.real);
+
+	/* Get the address for the gart region.
+	 * This is a bus address even on the alpha, b/c its
+	 * used to program the agp master not the cpu
+	 */
+
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+	agp_bridge->gart_bus_addr = addr;
+
+	/* Calculate the agp offset */
+	for (i = 0; i < value->num_entries / 1024; i++, addr += 0x00400000) {
+		writel(virt_to_phys(amd_irongate_private.gatt_pages[i]->real) | 1,
+			page_dir.remapped+GET_PAGE_DIR_OFF(addr));
+		readl(page_dir.remapped+GET_PAGE_DIR_OFF(addr));	/* PCI Posting. */
+	}
+
+	return 0;
+}
+
+static int amd_free_gatt_table(struct agp_bridge_data *bridge)
+{
+	struct amd_page_map page_dir;
+
+	page_dir.real = (unsigned long *)agp_bridge->gatt_table_real;
+	page_dir.remapped = (unsigned long __iomem *)agp_bridge->gatt_table;
+
+	amd_free_gatt_pages();
+	amd_free_page_map(&page_dir);
+	return 0;
+}
+
+static int amd_irongate_fetch_size(void)
+{
+	int i;
+	u32 temp;
+	struct aper_size_info_lvl2 *values;
+
+	pci_read_config_dword(agp_bridge->dev, AMD_APSIZE, &temp);
+	temp = (temp & 0x0000000e);
+	values = A_SIZE_LVL2(agp_bridge->driver->aperture_sizes);
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+static int amd_irongate_configure(void)
+{
+	struct aper_size_info_lvl2 *current_size;
+	u32 temp;
+	u16 enable_reg;
+
+	current_size = A_SIZE_LVL2(agp_bridge->current_size);
+
+	/* Get the memory mapped registers */
+	pci_read_config_dword(agp_bridge->dev, AMD_MMBASE, &temp);
+	temp = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+	amd_irongate_private.registers = (volatile u8 __iomem *) ioremap(temp, 4096);
+
+	/* Write out the address of the gatt table */
+	writel(agp_bridge->gatt_bus_addr, amd_irongate_private.registers+AMD_ATTBASE);
+	readl(amd_irongate_private.registers+AMD_ATTBASE);	/* PCI Posting. */
+
+	/* Write the Sync register */
+	pci_write_config_byte(agp_bridge->dev, AMD_MODECNTL, 0x80);
+
+	/* Set indexing mode */
+	pci_write_config_byte(agp_bridge->dev, AMD_MODECNTL2, 0x00);
+
+	/* Write the enable register */
+	enable_reg = readw(amd_irongate_private.registers+AMD_GARTENABLE);
+	enable_reg = (enable_reg | 0x0004);
+	writew(enable_reg, amd_irongate_private.registers+AMD_GARTENABLE);
+	readw(amd_irongate_private.registers+AMD_GARTENABLE);	/* PCI Posting. */
+
+	/* Write out the size register */
+	pci_read_config_dword(agp_bridge->dev, AMD_APSIZE, &temp);
+	temp = (((temp & ~(0x0000000e)) | current_size->size_value) | 1);
+	pci_write_config_dword(agp_bridge->dev, AMD_APSIZE, temp);
+
+	/* Flush the tlb */
+	writel(1, amd_irongate_private.registers+AMD_TLBFLUSH);
+	readl(amd_irongate_private.registers+AMD_TLBFLUSH);	/* PCI Posting.*/
+	return 0;
+}
+
+static void amd_irongate_cleanup(void)
+{
+	struct aper_size_info_lvl2 *previous_size;
+	u32 temp;
+	u16 enable_reg;
+
+	previous_size = A_SIZE_LVL2(agp_bridge->previous_size);
+
+	enable_reg = readw(amd_irongate_private.registers+AMD_GARTENABLE);
+	enable_reg = (enable_reg & ~(0x0004));
+	writew(enable_reg, amd_irongate_private.registers+AMD_GARTENABLE);
+	readw(amd_irongate_private.registers+AMD_GARTENABLE);	/* PCI Posting. */
+
+	/* Write back the previous size and disable gart translation */
+	pci_read_config_dword(agp_bridge->dev, AMD_APSIZE, &temp);
+	temp = ((temp & ~(0x0000000f)) | previous_size->size_value);
+	pci_write_config_dword(agp_bridge->dev, AMD_APSIZE, temp);
+	iounmap((void __iomem *) amd_irongate_private.registers);
+}
+
+/*
+ * This routine could be implemented by taking the addresses
+ * written to the GATT, and flushing them individually.  However
+ * currently it just flushes the whole table.  Which is probably
+ * more efficent, since agp_memory blocks can be a large number of
+ * entries.
+ */
+
+static void amd_irongate_tlbflush(struct agp_memory *temp)
+{
+	writel(1, amd_irongate_private.registers+AMD_TLBFLUSH);
+	readl(amd_irongate_private.registers+AMD_TLBFLUSH);	/* PCI Posting. */
+}
+
+static int amd_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+	int i, j, num_entries;
+	unsigned long __iomem *cur_gatt;
+	unsigned long addr;
+
+	num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+	if (type != 0 || mem->type != 0)
+		return -EINVAL;
+
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	j = pg_start;
+	while (j < (pg_start + mem->page_count)) {
+		addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = GET_GATT(addr);
+		if (!PGE_EMPTY(agp_bridge, readl(cur_gatt+GET_GATT_OFF(addr))))
+			return -EBUSY;
+		j++;
+	}
+
+	if (mem->is_flushed == FALSE) {
+		global_cache_flush();
+		mem->is_flushed = TRUE;
+	}
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = GET_GATT(addr);
+		writel(agp_generic_mask_memory(agp_bridge,
+			mem->memory[i], mem->type), cur_gatt+GET_GATT_OFF(addr));
+		readl(cur_gatt+GET_GATT_OFF(addr));	/* PCI Posting. */
+	}
+	amd_irongate_tlbflush(mem);
+	return 0;
+}
+
+static int amd_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+	int i;
+	unsigned long __iomem *cur_gatt;
+	unsigned long addr;
+
+	if (type != 0 || mem->type != 0)
+		return -EINVAL;
+
+	for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+		addr = (i * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = GET_GATT(addr);
+		writel(agp_bridge->scratch_page, cur_gatt+GET_GATT_OFF(addr));
+		readl(cur_gatt+GET_GATT_OFF(addr));	/* PCI Posting. */
+	}
+
+	amd_irongate_tlbflush(mem);
+	return 0;
+}
+
+static struct aper_size_info_lvl2 amd_irongate_sizes[7] =
+{
+	{2048, 524288, 0x0000000c},
+	{1024, 262144, 0x0000000a},
+	{512, 131072, 0x00000008},
+	{256, 65536, 0x00000006},
+	{128, 32768, 0x00000004},
+	{64, 16384, 0x00000002},
+	{32, 8192, 0x00000000}
+};
+
+static struct gatt_mask amd_irongate_masks[] =
+{
+	{.mask = 1, .type = 0}
+};
+
+struct agp_bridge_driver amd_irongate_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= amd_irongate_sizes,
+	.size_type		= LVL2_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= amd_irongate_configure,
+	.fetch_size		= amd_irongate_fetch_size,
+	.cleanup		= amd_irongate_cleanup,
+	.tlb_flush		= amd_irongate_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= amd_irongate_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= amd_create_gatt_table,
+	.free_gatt_table	= amd_free_gatt_table,
+	.insert_memory		= amd_insert_memory,
+	.remove_memory		= amd_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_device_ids amd_agp_device_ids[] __devinitdata =
+{
+	{
+		.device_id	= PCI_DEVICE_ID_AMD_FE_GATE_7006,
+		.chipset_name	= "Irongate",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AMD_FE_GATE_700E,
+		.chipset_name	= "761",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_AMD_FE_GATE_700C,
+		.chipset_name	= "760MP",
+	},
+	{ }, /* dummy final entry, always present */
+};
+
+static int __devinit agp_amdk7_probe(struct pci_dev *pdev,
+				     const struct pci_device_id *ent)
+{
+	struct agp_bridge_data *bridge;
+	u8 cap_ptr;
+	int j;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	j = ent - agp_amdk7_pci_table;
+	printk(KERN_INFO PFX "Detected AMD %s chipset\n",
+	       amd_agp_device_ids[j].chipset_name);
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->driver = &amd_irongate_driver;
+	bridge->dev_private_data = &amd_irongate_private,
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+
+	/* 751 Errata (22564_B-1.PDF)
+	   erratum 20: strobe glitch with Nvidia NV10 GeForce cards.
+	   system controller may experience noise due to strong drive strengths
+	 */
+	if (agp_bridge->dev->device == PCI_DEVICE_ID_AMD_FE_GATE_7006) {
+		u8 cap_ptr=0;
+		struct pci_dev *gfxcard=NULL;
+		while (!cap_ptr) {
+			gfxcard = pci_get_class(PCI_CLASS_DISPLAY_VGA<<8, gfxcard);
+			if (!gfxcard) {
+				printk (KERN_INFO PFX "Couldn't find an AGP VGA controller.\n");
+				return -ENODEV;
+			}
+			cap_ptr = pci_find_capability(gfxcard, PCI_CAP_ID_AGP);
+			if (!cap_ptr) {
+				pci_dev_put(gfxcard);
+				continue;
+			}
+		}
+
+		/* With so many variants of NVidia cards, it's simpler just
+		   to blacklist them all, and then whitelist them as needed
+		   (if necessary at all). */
+		if (gfxcard->vendor == PCI_VENDOR_ID_NVIDIA) {
+			agp_bridge->flags |= AGP_ERRATA_1X;
+			printk (KERN_INFO PFX "AMD 751 chipset with NVidia GeForce detected. Forcing to 1X due to errata.\n");
+		}
+		pci_dev_put(gfxcard);
+	}
+
+	/* 761 Errata (23613_F.pdf)
+	 * Revisions B0/B1 were a disaster.
+	 * erratum 44: SYSCLK/AGPCLK skew causes 2X failures -- Force mode to 1X
+	 * erratum 45: Timing problem prevents fast writes -- Disable fast write.
+	 * erratum 46: Setup violation on AGP SBA pins - Disable side band addressing.
+	 * With this lot disabled, we should prevent lockups. */
+	if (agp_bridge->dev->device == PCI_DEVICE_ID_AMD_FE_GATE_700E) {
+		u8 revision=0;
+		pci_read_config_byte(pdev, PCI_REVISION_ID, &revision);
+		if (revision == 0x10 || revision == 0x11) {
+			agp_bridge->flags = AGP_ERRATA_FASTWRITES;
+			agp_bridge->flags |= AGP_ERRATA_SBA;
+			agp_bridge->flags |= AGP_ERRATA_1X;
+			printk (KERN_INFO PFX "AMD 761 chipset with errata detected - disabling AGP fast writes & SBA and forcing to 1X.\n");
+		}
+	}
+
+	/* Fill in the mode register */
+	pci_read_config_dword(pdev,
+			bridge->capndx+PCI_AGP_STATUS,
+			&bridge->mode);
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_amdk7_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+/* must be the same order as name table above */
+static struct pci_device_id agp_amdk7_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_AMD,
+	.device		= PCI_DEVICE_ID_AMD_FE_GATE_7006,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_AMD,
+	.device		= PCI_DEVICE_ID_AMD_FE_GATE_700E,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_AMD,
+	.device		= PCI_DEVICE_ID_AMD_FE_GATE_700C,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_amdk7_pci_table);
+
+static struct pci_driver agp_amdk7_pci_driver = {
+	.name		= "agpgart-amdk7",
+	.id_table	= agp_amdk7_pci_table,
+	.probe		= agp_amdk7_probe,
+	.remove		= agp_amdk7_remove,
+};
+
+static int __init agp_amdk7_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_amdk7_pci_driver);
+}
+
+static void __exit agp_amdk7_cleanup(void)
+{
+	pci_unregister_driver(&agp_amdk7_pci_driver);
+}
+
+module_init(agp_amdk7_init);
+module_exit(agp_amdk7_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/amd64-agp.c b/drivers/char/agp/amd64-agp.c
new file mode 100644
index 0000000..905f062
--- /dev/null
+++ b/drivers/char/agp/amd64-agp.c
@@ -0,0 +1,761 @@
+/*
+ * Copyright 2001-2003 SuSE Labs.
+ * Distributed under the GNU public license, v2.
+ *
+ * This is a GART driver for the AMD Opteron/Athlon64 on-CPU northbridge.
+ * It also includes support for the AMD 8151 AGP bridge,
+ * although it doesn't actually do much, as all the real
+ * work is done in the northbridge(s).
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+/* Will need to be increased if AMD64 ever goes >8-way. */
+#define MAX_HAMMER_GARTS   8
+
+/* PTE bits. */
+#define GPTE_VALID	1
+#define GPTE_COHERENT	2
+
+/* Aperture control register bits. */
+#define GARTEN		(1<<0)
+#define DISGARTCPU	(1<<4)
+#define DISGARTIO	(1<<5)
+
+/* GART cache control register bits. */
+#define INVGART		(1<<0)
+#define GARTPTEERR	(1<<1)
+
+/* K8 On-cpu GART registers */
+#define AMD64_GARTAPERTURECTL	0x90
+#define AMD64_GARTAPERTUREBASE	0x94
+#define AMD64_GARTTABLEBASE	0x98
+#define AMD64_GARTCACHECTL	0x9c
+#define AMD64_GARTEN		(1<<0)
+
+/* NVIDIA K8 registers */
+#define NVIDIA_X86_64_0_APBASE		0x10
+#define NVIDIA_X86_64_1_APBASE1		0x50
+#define NVIDIA_X86_64_1_APLIMIT1	0x54
+#define NVIDIA_X86_64_1_APSIZE		0xa8
+#define NVIDIA_X86_64_1_APBASE2		0xd8
+#define NVIDIA_X86_64_1_APLIMIT2	0xdc
+
+/* ULi K8 registers */
+#define ULI_X86_64_BASE_ADDR		0x10
+#define ULI_X86_64_HTT_FEA_REG		0x50
+#define ULI_X86_64_ENU_SCR_REG		0x54
+
+static int nr_garts;
+static struct pci_dev * hammers[MAX_HAMMER_GARTS];
+
+static struct resource *aperture_resource;
+static int __initdata agp_try_unsupported;
+
+static int gart_iterator;
+#define for_each_nb() for(gart_iterator=0;gart_iterator<nr_garts;gart_iterator++)
+
+static void flush_amd64_tlb(struct pci_dev *dev)
+{
+	u32 tmp;
+
+	pci_read_config_dword (dev, AMD64_GARTCACHECTL, &tmp);
+	tmp |= INVGART;
+	pci_write_config_dword (dev, AMD64_GARTCACHECTL, tmp);
+}
+
+static void amd64_tlbflush(struct agp_memory *temp)
+{
+	for_each_nb()
+		flush_amd64_tlb(hammers[gart_iterator]);
+}
+
+static int amd64_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+	int i, j, num_entries;
+	long long tmp;
+	u32 pte;
+
+	num_entries = agp_num_entries();
+
+	if (type != 0 || mem->type != 0)
+		return -EINVAL;
+
+	/* Make sure we can fit the range in the gatt table. */
+	/* FIXME: could wrap */
+	if (((unsigned long)pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	j = pg_start;
+
+	/* gatt table should be empty. */
+	while (j < (pg_start + mem->page_count)) {
+		if (!PGE_EMPTY(agp_bridge, readl(agp_bridge->gatt_table+j)))
+			return -EBUSY;
+		j++;
+	}
+
+	if (mem->is_flushed == FALSE) {
+		global_cache_flush();
+		mem->is_flushed = TRUE;
+	}
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		tmp = agp_bridge->driver->mask_memory(agp_bridge,
+			mem->memory[i], mem->type);
+
+		BUG_ON(tmp & 0xffffff0000000ffcULL);
+		pte = (tmp & 0x000000ff00000000ULL) >> 28;
+		pte |=(tmp & 0x00000000fffff000ULL);
+		pte |= GPTE_VALID | GPTE_COHERENT;
+
+		writel(pte, agp_bridge->gatt_table+j);
+		readl(agp_bridge->gatt_table+j);	/* PCI Posting. */
+	}
+	amd64_tlbflush(mem);
+	return 0;
+}
+
+/*
+ * This hack alters the order element according
+ * to the size of a long. It sucks. I totally disown this, even
+ * though it does appear to work for the most part.
+ */
+static struct aper_size_info_32 amd64_aperture_sizes[7] =
+{
+	{32,   8192,   3+(sizeof(long)/8), 0 },
+	{64,   16384,  4+(sizeof(long)/8), 1<<1 },
+	{128,  32768,  5+(sizeof(long)/8), 1<<2 },
+	{256,  65536,  6+(sizeof(long)/8), 1<<1 | 1<<2 },
+	{512,  131072, 7+(sizeof(long)/8), 1<<3 },
+	{1024, 262144, 8+(sizeof(long)/8), 1<<1 | 1<<3},
+	{2048, 524288, 9+(sizeof(long)/8), 1<<2 | 1<<3}
+};
+
+
+/*
+ * Get the current Aperture size from the x86-64.
+ * Note, that there may be multiple x86-64's, but we just return
+ * the value from the first one we find. The set_size functions
+ * keep the rest coherent anyway. Or at least should do.
+ */
+static int amd64_fetch_size(void)
+{
+	struct pci_dev *dev;
+	int i;
+	u32 temp;
+	struct aper_size_info_32 *values;
+
+	dev = hammers[0];
+	if (dev==NULL)
+		return 0;
+
+	pci_read_config_dword(dev, AMD64_GARTAPERTURECTL, &temp);
+	temp = (temp & 0xe);
+	values = A_SIZE_32(amd64_aperture_sizes);
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+	return 0;
+}
+
+/*
+ * In a multiprocessor x86-64 system, this function gets
+ * called once for each CPU.
+ */
+static u64 amd64_configure (struct pci_dev *hammer, u64 gatt_table)
+{
+	u64 aperturebase;
+	u32 tmp;
+	u64 addr, aper_base;
+
+	/* Address to map to */
+	pci_read_config_dword (hammer, AMD64_GARTAPERTUREBASE, &tmp);
+	aperturebase = tmp << 25;
+	aper_base = (aperturebase & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* address of the mappings table */
+	addr = (u64) gatt_table;
+	addr >>= 12;
+	tmp = (u32) addr<<4;
+	tmp &= ~0xf;
+	pci_write_config_dword (hammer, AMD64_GARTTABLEBASE, tmp);
+
+	/* Enable GART translation for this hammer. */
+	pci_read_config_dword(hammer, AMD64_GARTAPERTURECTL, &tmp);
+	tmp |= GARTEN;
+	tmp &= ~(DISGARTCPU | DISGARTIO);
+	pci_write_config_dword(hammer, AMD64_GARTAPERTURECTL, tmp);
+
+	/* keep CPU's coherent. */
+	flush_amd64_tlb (hammer);
+
+	return aper_base;
+}
+
+
+static struct aper_size_info_32 amd_8151_sizes[7] =
+{
+	{2048, 524288, 9, 0x00000000 },	/* 0 0 0 0 0 0 */
+	{1024, 262144, 8, 0x00000400 },	/* 1 0 0 0 0 0 */
+	{512,  131072, 7, 0x00000600 },	/* 1 1 0 0 0 0 */
+	{256,  65536,  6, 0x00000700 },	/* 1 1 1 0 0 0 */
+	{128,  32768,  5, 0x00000720 },	/* 1 1 1 1 0 0 */
+	{64,   16384,  4, 0x00000730 },	/* 1 1 1 1 1 0 */
+	{32,   8192,   3, 0x00000738 } 	/* 1 1 1 1 1 1 */
+};
+
+static int amd_8151_configure(void)
+{
+	unsigned long gatt_bus = virt_to_phys(agp_bridge->gatt_table_real);
+
+	/* Configure AGP regs in each x86-64 host bridge. */
+	for_each_nb() {
+		agp_bridge->gart_bus_addr =
+				amd64_configure(hammers[gart_iterator],gatt_bus);
+	}
+	return 0;
+}
+
+
+static void amd64_cleanup(void)
+{
+	u32 tmp;
+
+	for_each_nb() {
+		/* disable gart translation */
+		pci_read_config_dword (hammers[gart_iterator], AMD64_GARTAPERTURECTL, &tmp);
+		tmp &= ~AMD64_GARTEN;
+		pci_write_config_dword (hammers[gart_iterator], AMD64_GARTAPERTURECTL, tmp);
+	}
+}
+
+
+struct agp_bridge_driver amd_8151_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= amd_8151_sizes,
+	.size_type		= U32_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= amd_8151_configure,
+	.fetch_size		= amd64_fetch_size,
+	.cleanup		= amd64_cleanup,
+	.tlb_flush		= amd64_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= NULL,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= amd64_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+/* Some basic sanity checks for the aperture. */
+static int __devinit aperture_valid(u64 aper, u32 size)
+{
+	u32 pfn, c;
+	if (aper == 0) {
+		printk(KERN_ERR PFX "No aperture\n");
+		return 0;
+	}
+	if (size < 32*1024*1024) {
+		printk(KERN_ERR PFX "Aperture too small (%d MB)\n", size>>20);
+		return 0;
+	}
+	if (aper + size > 0xffffffff) {
+		printk(KERN_ERR PFX "Aperture out of bounds\n");
+		return 0;
+	}
+	pfn = aper >> PAGE_SHIFT;
+	for (c = 0; c < size/PAGE_SIZE; c++) {
+		if (!pfn_valid(pfn + c))
+			break;
+		if (!PageReserved(pfn_to_page(pfn + c))) {
+			printk(KERN_ERR PFX "Aperture pointing to RAM\n");
+			return 0;
+		}
+	}
+
+	/* Request the Aperture. This catches cases when someone else
+	   already put a mapping in there - happens with some very broken BIOS
+
+	   Maybe better to use pci_assign_resource/pci_enable_device instead
+	   trusting the bridges? */
+	if (!aperture_resource &&
+	    !(aperture_resource = request_mem_region(aper, size, "aperture"))) {
+		printk(KERN_ERR PFX "Aperture conflicts with PCI mapping.\n");
+		return 0;
+	}
+	return 1;
+}
+
+/*
+ * W*s centric BIOS sometimes only set up the aperture in the AGP
+ * bridge, not the northbridge. On AMD64 this is handled early
+ * in aperture.c, but when GART_IOMMU is not enabled or we run
+ * on a 32bit kernel this needs to be redone.
+ * Unfortunately it is impossible to fix the aperture here because it's too late
+ * to allocate that much memory. But at least error out cleanly instead of
+ * crashing.
+ */
+static __devinit int fix_northbridge(struct pci_dev *nb, struct pci_dev *agp,
+								 u16 cap)
+{
+	u32 aper_low, aper_hi;
+	u64 aper, nb_aper;
+	int order = 0;
+	u32 nb_order, nb_base;
+	u16 apsize;
+
+	pci_read_config_dword(nb, 0x90, &nb_order);
+	nb_order = (nb_order >> 1) & 7;
+	pci_read_config_dword(nb, 0x94, &nb_base);
+	nb_aper = nb_base << 25;
+	if (aperture_valid(nb_aper, (32*1024*1024)<<nb_order)) {
+		return 0;
+	}
+
+	/* Northbridge seems to contain crap. Try the AGP bridge. */
+
+	pci_read_config_word(agp, cap+0x14, &apsize);
+	if (apsize == 0xffff)
+		return -1;
+
+	apsize &= 0xfff;
+	/* Some BIOS use weird encodings not in the AGPv3 table. */
+	if (apsize & 0xff)
+		apsize |= 0xf00;
+	order = 7 - hweight16(apsize);
+
+	pci_read_config_dword(agp, 0x10, &aper_low);
+	pci_read_config_dword(agp, 0x14, &aper_hi);
+	aper = (aper_low & ~((1<<22)-1)) | ((u64)aper_hi << 32);
+	printk(KERN_INFO PFX "Aperture from AGP @ %Lx size %u MB\n", aper, 32 << order);
+	if (order < 0 || !aperture_valid(aper, (32*1024*1024)<<order))
+		return -1;
+
+	pci_write_config_dword(nb, 0x90, order << 1);
+	pci_write_config_dword(nb, 0x94, aper >> 25);
+
+	return 0;
+}
+
+static __devinit int cache_nbs (struct pci_dev *pdev, u32 cap_ptr)
+{
+	struct pci_dev *loop_dev = NULL;
+	int i = 0;
+
+	/* cache pci_devs of northbridges. */
+	while ((loop_dev = pci_get_device(PCI_VENDOR_ID_AMD, 0x1103, loop_dev))
+			!= NULL) {
+		if (i == MAX_HAMMER_GARTS) {
+			printk(KERN_ERR PFX "Too many northbridges for AGP\n");
+			return -1;
+		}
+		if (fix_northbridge(loop_dev, pdev, cap_ptr) < 0) {
+			printk(KERN_ERR PFX "No usable aperture found.\n");
+#ifdef __x86_64__
+			/* should port this to i386 */
+			printk(KERN_ERR PFX "Consider rebooting with iommu=memaper=2 to get a good aperture.\n");
+#endif
+			return -1;
+		}
+		hammers[i++] = loop_dev;
+	}
+		nr_garts = i;
+	return i == 0 ? -1 : 0;
+}
+
+/* Handle AMD 8151 quirks */
+static void __devinit amd8151_init(struct pci_dev *pdev, struct agp_bridge_data *bridge)
+{
+	char *revstring;
+	u8 rev_id;
+
+	pci_read_config_byte(pdev, PCI_REVISION_ID, &rev_id);
+	switch (rev_id) {
+	case 0x01: revstring="A0"; break;
+	case 0x02: revstring="A1"; break;
+	case 0x11: revstring="B0"; break;
+	case 0x12: revstring="B1"; break;
+	case 0x13: revstring="B2"; break;
+	case 0x14: revstring="B3"; break;
+	default:   revstring="??"; break;
+	}
+
+	printk (KERN_INFO PFX "Detected AMD 8151 AGP Bridge rev %s\n", revstring);
+
+	/*
+	 * Work around errata.
+	 * Chips before B2 stepping incorrectly reporting v3.5
+	 */
+	if (rev_id < 0x13) {
+		printk (KERN_INFO PFX "Correcting AGP revision (reports 3.5, is really 3.0)\n");
+		bridge->major_version = 3;
+		bridge->minor_version = 0;
+	}
+}
+
+
+static struct aper_size_info_32 uli_sizes[7] =
+{
+	{256, 65536, 6, 10},
+	{128, 32768, 5, 9},
+	{64, 16384, 4, 8},
+	{32, 8192, 3, 7},
+	{16, 4096, 2, 6},
+	{8, 2048, 1, 4},
+	{4, 1024, 0, 3}
+};
+static int __devinit uli_agp_init(struct pci_dev *pdev)
+{
+	u32 httfea,baseaddr,enuscr;
+	struct pci_dev *dev1;
+	int i;
+	unsigned size = amd64_fetch_size();
+	printk(KERN_INFO "Setting up ULi AGP. \n");
+	dev1 = pci_find_slot ((unsigned int)pdev->bus->number,PCI_DEVFN(0,0));
+	if (dev1 == NULL) {
+		printk(KERN_INFO PFX "Detected a ULi chipset, "
+			"but could not fine the secondary device.\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(uli_sizes); i++)
+		if (uli_sizes[i].size == size)
+			break;
+
+	if (i == ARRAY_SIZE(uli_sizes)) {
+		printk(KERN_INFO PFX "No ULi size found for %d\n", size);
+		return -ENODEV;
+	}
+
+	/* shadow x86-64 registers into ULi registers */
+	pci_read_config_dword (hammers[0], AMD64_GARTAPERTUREBASE, &httfea);
+
+	/* if x86-64 aperture base is beyond 4G, exit here */
+	if ((httfea & 0x7fff) >> (32 - 25))
+		return -ENODEV;
+
+	httfea = (httfea& 0x7fff) << 25;
+
+	pci_read_config_dword(pdev, ULI_X86_64_BASE_ADDR, &baseaddr);
+	baseaddr&= ~PCI_BASE_ADDRESS_MEM_MASK;
+	baseaddr|= httfea;
+	pci_write_config_dword(pdev, ULI_X86_64_BASE_ADDR, baseaddr);
+
+	enuscr= httfea+ (size * 1024 * 1024) - 1;
+	pci_write_config_dword(dev1, ULI_X86_64_HTT_FEA_REG, httfea);
+	pci_write_config_dword(dev1, ULI_X86_64_ENU_SCR_REG, enuscr);
+	return 0;
+}
+
+
+static struct aper_size_info_32 nforce3_sizes[5] =
+{
+	{512,  131072, 7, 0x00000000 },
+	{256,  65536,  6, 0x00000008 },
+	{128,  32768,  5, 0x0000000C },
+	{64,   16384,  4, 0x0000000E },
+	{32,   8192,   3, 0x0000000F }
+};
+
+/* Handle shadow device of the Nvidia NForce3 */
+/* CHECK-ME original 2.4 version set up some IORRs. Check if that is needed. */
+static int __devinit nforce3_agp_init(struct pci_dev *pdev)
+{
+	u32 tmp, apbase, apbar, aplimit;
+	struct pci_dev *dev1;
+	int i;
+	unsigned size = amd64_fetch_size();
+
+	printk(KERN_INFO PFX "Setting up Nforce3 AGP.\n");
+
+	dev1 = pci_find_slot((unsigned int)pdev->bus->number, PCI_DEVFN(11, 0));
+	if (dev1 == NULL) {
+		printk(KERN_INFO PFX "agpgart: Detected an NVIDIA "
+			"nForce3 chipset, but could not find "
+			"the secondary device.\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(nforce3_sizes); i++)
+		if (nforce3_sizes[i].size == size)
+			break;
+
+	if (i == ARRAY_SIZE(nforce3_sizes)) {
+		printk(KERN_INFO PFX "No NForce3 size found for %d\n", size);
+		return -ENODEV;
+	}
+
+	pci_read_config_dword(dev1, NVIDIA_X86_64_1_APSIZE, &tmp);
+	tmp &= ~(0xf);
+	tmp |= nforce3_sizes[i].size_value;
+	pci_write_config_dword(dev1, NVIDIA_X86_64_1_APSIZE, tmp);
+
+	/* shadow x86-64 registers into NVIDIA registers */
+	pci_read_config_dword (hammers[0], AMD64_GARTAPERTUREBASE, &apbase);
+
+	/* if x86-64 aperture base is beyond 4G, exit here */
+	if ( (apbase & 0x7fff) >> (32 - 25) )
+		 return -ENODEV;
+
+	apbase = (apbase & 0x7fff) << 25;
+
+	pci_read_config_dword(pdev, NVIDIA_X86_64_0_APBASE, &apbar);
+	apbar &= ~PCI_BASE_ADDRESS_MEM_MASK;
+	apbar |= apbase;
+	pci_write_config_dword(pdev, NVIDIA_X86_64_0_APBASE, apbar);
+
+	aplimit = apbase + (size * 1024 * 1024) - 1;
+	pci_write_config_dword(dev1, NVIDIA_X86_64_1_APBASE1, apbase);
+	pci_write_config_dword(dev1, NVIDIA_X86_64_1_APLIMIT1, aplimit);
+	pci_write_config_dword(dev1, NVIDIA_X86_64_1_APBASE2, apbase);
+	pci_write_config_dword(dev1, NVIDIA_X86_64_1_APLIMIT2, aplimit);
+
+	return 0;
+}
+
+static int __devinit agp_amd64_probe(struct pci_dev *pdev,
+				     const struct pci_device_id *ent)
+{
+	struct agp_bridge_data *bridge;
+	u8 cap_ptr;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	/* Could check for AGPv3 here */
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	if (pdev->vendor == PCI_VENDOR_ID_AMD &&
+	    pdev->device == PCI_DEVICE_ID_AMD_8151_0) {
+		amd8151_init(pdev, bridge);
+	} else {
+		printk(KERN_INFO PFX "Detected AGP bridge %x\n", pdev->devfn);
+	}
+
+	bridge->driver = &amd_8151_driver;
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+
+	/* Fill in the mode register */
+	pci_read_config_dword(pdev, bridge->capndx+PCI_AGP_STATUS, &bridge->mode);
+
+	if (cache_nbs(pdev, cap_ptr) == -1) {
+		agp_put_bridge(bridge);
+		return -ENODEV;
+	}
+
+	if (pdev->vendor == PCI_VENDOR_ID_NVIDIA) {
+		int ret = nforce3_agp_init(pdev);
+		if (ret) {
+			agp_put_bridge(bridge);
+			return ret;
+		}
+	}
+
+	if (pdev->vendor == PCI_VENDOR_ID_AL) {
+		int ret = uli_agp_init(pdev);
+		if (ret) {
+			agp_put_bridge(bridge);
+			return ret;
+		}
+	}
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_amd64_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	release_mem_region(virt_to_phys(bridge->gatt_table_real),
+			   amd64_aperture_sizes[bridge->aperture_size_idx].size);
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_amd64_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_AMD,
+	.device		= PCI_DEVICE_ID_AMD_8151_0,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	/* ULi M1689 */
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_AL,
+	.device		= PCI_DEVICE_ID_AL_M1689,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	/* VIA K8T800Pro */
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_VIA,
+	.device		= PCI_DEVICE_ID_VIA_K8T800PRO_0,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	/* VIA K8T800 */
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_VIA,
+	.device		= PCI_DEVICE_ID_VIA_8385_0,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	/* VIA K8M800 / K8N800 */
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_VIA,
+	.device		= PCI_DEVICE_ID_VIA_8380_0,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	/* VIA K8T890 */
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_VIA,
+	.device		= PCI_DEVICE_ID_VIA_3238_0,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	/* VIA K8T800/K8M800/K8N800 */
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_VIA,
+	.device		= PCI_DEVICE_ID_VIA_838X_1,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	/* NForce3 */
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_NVIDIA,
+	.device		= PCI_DEVICE_ID_NVIDIA_NFORCE3,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_NVIDIA,
+	.device		= PCI_DEVICE_ID_NVIDIA_NFORCE3S,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	/* SIS 755 */
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_SI,
+	.device		= PCI_DEVICE_ID_SI_755,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_amd64_pci_table);
+
+static struct pci_driver agp_amd64_pci_driver = {
+	.name		= "agpgart-amd64",
+	.id_table	= agp_amd64_pci_table,
+	.probe		= agp_amd64_probe,
+	.remove		= agp_amd64_remove,
+};
+
+
+/* Not static due to IOMMU code calling it early. */
+int __init agp_amd64_init(void)
+{
+	int err = 0;
+	static struct pci_device_id amd64nb[] = {
+		{ PCI_DEVICE(PCI_VENDOR_ID_AMD, 0x1103) },
+		{ },
+	};
+
+	if (agp_off)
+		return -EINVAL;
+	if (pci_register_driver(&agp_amd64_pci_driver) > 0) {
+		struct pci_dev *dev;
+		if (!agp_try_unsupported && !agp_try_unsupported_boot) {
+			printk(KERN_INFO PFX "No supported AGP bridge found.\n");
+#ifdef MODULE
+			printk(KERN_INFO PFX "You can try agp_try_unsupported=1\n");
+#else
+			printk(KERN_INFO PFX "You can boot with agp=try_unsupported\n");
+#endif
+			return -ENODEV;
+		}
+
+		/* First check that we have at least one AMD64 NB */
+		if (!pci_dev_present(amd64nb))
+			return -ENODEV;
+
+		/* Look for any AGP bridge */
+		dev = NULL;
+		err = -ENODEV;
+		for_each_pci_dev(dev) {
+			if (!pci_find_capability(dev, PCI_CAP_ID_AGP))
+				continue;
+			/* Only one bridge supported right now */
+			if (agp_amd64_probe(dev, NULL) == 0) {
+				err = 0;
+				break;
+			}
+		}
+	}
+	return err;
+}
+
+static void __exit agp_amd64_cleanup(void)
+{
+	if (aperture_resource)
+		release_resource(aperture_resource);
+	pci_unregister_driver(&agp_amd64_pci_driver);
+}
+
+/* On AMD64 the PCI driver needs to initialize this driver early
+   for the IOMMU, so it has to be called via a backdoor. */
+#ifndef CONFIG_GART_IOMMU
+module_init(agp_amd64_init);
+module_exit(agp_amd64_cleanup);
+#endif
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>, Andi Kleen");
+module_param(agp_try_unsupported, bool, 0);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/agp/ati-agp.c b/drivers/char/agp/ati-agp.c
new file mode 100644
index 0000000..757dde0
--- /dev/null
+++ b/drivers/char/agp/ati-agp.c
@@ -0,0 +1,548 @@
+/*
+ * ATi AGPGART routines.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <asm/agp.h>
+#include "agp.h"
+
+#define ATI_GART_MMBASE_ADDR	0x14
+#define ATI_RS100_APSIZE	0xac
+#define ATI_RS100_IG_AGPMODE	0xb0
+#define ATI_RS300_APSIZE	0xf8
+#define ATI_RS300_IG_AGPMODE	0xfc
+#define ATI_GART_FEATURE_ID		0x00
+#define ATI_GART_BASE			0x04
+#define ATI_GART_CACHE_SZBASE		0x08
+#define ATI_GART_CACHE_CNTRL		0x0c
+#define ATI_GART_CACHE_ENTRY_CNTRL	0x10
+
+
+static struct aper_size_info_lvl2 ati_generic_sizes[7] =
+{
+	{2048, 524288, 0x0000000c},
+	{1024, 262144, 0x0000000a},
+	{512, 131072, 0x00000008},
+	{256, 65536, 0x00000006},
+	{128, 32768, 0x00000004},
+	{64, 16384, 0x00000002},
+	{32, 8192, 0x00000000}
+};
+
+static struct gatt_mask ati_generic_masks[] =
+{
+	{ .mask = 1, .type = 0}
+};
+
+
+
+typedef struct _ati_page_map {
+	unsigned long *real;
+	unsigned long __iomem *remapped;
+} ati_page_map;
+
+static struct _ati_generic_private {
+	volatile u8 __iomem *registers;
+	ati_page_map **gatt_pages;
+	int num_tables;
+} ati_generic_private;
+
+static int ati_create_page_map(ati_page_map *page_map)
+{
+	int i, err = 0;
+
+	page_map->real = (unsigned long *) __get_free_page(GFP_KERNEL);
+	if (page_map->real == NULL)
+		return -ENOMEM;
+
+	SetPageReserved(virt_to_page(page_map->real));
+	err = map_page_into_agp(virt_to_page(page_map->real));
+	page_map->remapped = ioremap_nocache(virt_to_phys(page_map->real),
+					    PAGE_SIZE);
+	if (page_map->remapped == NULL || err) {
+		ClearPageReserved(virt_to_page(page_map->real));
+		free_page((unsigned long) page_map->real);
+		page_map->real = NULL;
+		return -ENOMEM;
+	}
+	/*CACHE_FLUSH();*/
+	global_cache_flush();
+
+	for(i = 0; i < PAGE_SIZE / sizeof(unsigned long); i++) {
+		writel(agp_bridge->scratch_page, page_map->remapped+i);
+		readl(page_map->remapped+i);	/* PCI Posting. */
+	}
+
+	return 0;
+}
+
+
+static void ati_free_page_map(ati_page_map *page_map)
+{
+	unmap_page_from_agp(virt_to_page(page_map->real));
+	iounmap(page_map->remapped);
+	ClearPageReserved(virt_to_page(page_map->real));
+	free_page((unsigned long) page_map->real);
+}
+
+
+static void ati_free_gatt_pages(void)
+{
+	int i;
+	ati_page_map **tables;
+	ati_page_map *entry;
+
+	tables = ati_generic_private.gatt_pages;
+	for(i = 0; i < ati_generic_private.num_tables; i++) {
+		entry = tables[i];
+		if (entry != NULL) {
+			if (entry->real != NULL)
+				ati_free_page_map(entry);
+			kfree(entry);
+		}
+	}
+	kfree(tables);
+}
+
+
+static int ati_create_gatt_pages(int nr_tables)
+{
+	ati_page_map **tables;
+	ati_page_map *entry;
+	int retval = 0;
+	int i;
+
+	tables = kmalloc((nr_tables + 1) * sizeof(ati_page_map *),
+			 GFP_KERNEL);
+	if (tables == NULL)
+		return -ENOMEM;
+
+	memset(tables, 0, sizeof(ati_page_map *) * (nr_tables + 1));
+	for (i = 0; i < nr_tables; i++) {
+		entry = kmalloc(sizeof(ati_page_map), GFP_KERNEL);
+		if (entry == NULL) {
+			while (i>0) {
+				kfree (tables[i-1]);
+				i--;
+			}
+			kfree (tables);
+			tables = NULL;
+			retval = -ENOMEM;
+			break;
+		}
+		memset(entry, 0, sizeof(ati_page_map));
+		tables[i] = entry;
+		retval = ati_create_page_map(entry);
+		if (retval != 0) break;
+	}
+	ati_generic_private.num_tables = nr_tables;
+	ati_generic_private.gatt_pages = tables;
+
+	if (retval != 0) ati_free_gatt_pages();
+
+	return retval;
+}
+
+static int is_r200(void)
+{
+	if ((agp_bridge->dev->device == PCI_DEVICE_ID_ATI_RS100) ||
+	    (agp_bridge->dev->device == PCI_DEVICE_ID_ATI_RS200) ||
+	    (agp_bridge->dev->device == PCI_DEVICE_ID_ATI_RS200_B) ||
+	    (agp_bridge->dev->device == PCI_DEVICE_ID_ATI_RS250))
+		return 1;
+	return 0;
+}
+
+static int ati_fetch_size(void)
+{
+	int i;
+	u32 temp;
+	struct aper_size_info_lvl2 *values;
+
+	if (is_r200())
+		pci_read_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, &temp);
+	else
+		pci_read_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, &temp);
+
+	temp = (temp & 0x0000000e);
+	values = A_SIZE_LVL2(agp_bridge->driver->aperture_sizes);
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+static void ati_tlbflush(struct agp_memory * mem)
+{
+	writel(1, ati_generic_private.registers+ATI_GART_CACHE_CNTRL);
+	readl(ati_generic_private.registers+ATI_GART_CACHE_CNTRL);	/* PCI Posting. */
+}
+
+static void ati_cleanup(void)
+{
+	struct aper_size_info_lvl2 *previous_size;
+	u32 temp;
+
+	previous_size = A_SIZE_LVL2(agp_bridge->previous_size);
+
+	/* Write back the previous size and disable gart translation */
+	if (is_r200()) {
+		pci_read_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, &temp);
+		temp = ((temp & ~(0x0000000f)) | previous_size->size_value);
+		pci_write_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, temp);
+	} else {
+		pci_read_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, &temp);
+		temp = ((temp & ~(0x0000000f)) | previous_size->size_value);
+		pci_write_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, temp);
+	}
+	iounmap((volatile u8 __iomem *)ati_generic_private.registers);
+}
+
+
+static int ati_configure(void)
+{
+	u32 temp;
+
+	/* Get the memory mapped registers */
+	pci_read_config_dword(agp_bridge->dev, ATI_GART_MMBASE_ADDR, &temp);
+	temp = (temp & 0xfffff000);
+	ati_generic_private.registers = (volatile u8 __iomem *) ioremap(temp, 4096);
+
+	if (is_r200())
+       	pci_write_config_dword(agp_bridge->dev, ATI_RS100_IG_AGPMODE, 0x20000);
+	else
+		pci_write_config_dword(agp_bridge->dev, ATI_RS300_IG_AGPMODE, 0x20000);
+
+	/* address to map too */
+        /*
+	pci_read_config_dword(agp_bridge.dev, AGP_APBASE, &temp);
+	agp_bridge.gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+	printk(KERN_INFO PFX "IGP320 gart_bus_addr: %x\n", agp_bridge.gart_bus_addr);
+        */
+	writel(0x60000, ati_generic_private.registers+ATI_GART_FEATURE_ID);
+	readl(ati_generic_private.registers+ATI_GART_FEATURE_ID);	/* PCI Posting.*/
+
+	/* SIGNALED_SYSTEM_ERROR @ NB_STATUS */
+	pci_read_config_dword(agp_bridge->dev, 4, &temp);
+	pci_write_config_dword(agp_bridge->dev, 4, temp | (1<<14));
+
+	/* Write out the address of the gatt table */
+	writel(agp_bridge->gatt_bus_addr, ati_generic_private.registers+ATI_GART_BASE);
+	readl(ati_generic_private.registers+ATI_GART_BASE);	/* PCI Posting. */
+
+	return 0;
+}
+
+
+/*
+ *Since we don't need contigious memory we just try
+ * to get the gatt table once
+ */
+
+#define GET_PAGE_DIR_OFF(addr) (addr >> 22)
+#define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
+	GET_PAGE_DIR_OFF(agp_bridge->gart_bus_addr))
+#define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
+#undef  GET_GATT
+#define GET_GATT(addr) (ati_generic_private.gatt_pages[\
+	GET_PAGE_DIR_IDX(addr)]->remapped)
+
+static int ati_insert_memory(struct agp_memory * mem,
+			     off_t pg_start, int type)
+{
+	int i, j, num_entries;
+	unsigned long __iomem *cur_gatt;
+	unsigned long addr;
+
+	num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+	if (type != 0 || mem->type != 0)
+		return -EINVAL;
+
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	j = pg_start;
+	while (j < (pg_start + mem->page_count)) {
+		addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = GET_GATT(addr);
+		if (!PGE_EMPTY(agp_bridge,readl(cur_gatt+GET_GATT_OFF(addr))))
+			return -EBUSY;
+		j++;
+	}
+
+	if (mem->is_flushed == FALSE) {
+		/*CACHE_FLUSH(); */
+		global_cache_flush();
+		mem->is_flushed = TRUE;
+	}
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = GET_GATT(addr);
+		writel(agp_bridge->driver->mask_memory(agp_bridge,
+			mem->memory[i], mem->type), cur_gatt+GET_GATT_OFF(addr));
+		readl(cur_gatt+GET_GATT_OFF(addr));	/* PCI Posting. */
+	}
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int ati_remove_memory(struct agp_memory * mem, off_t pg_start,
+			     int type)
+{
+	int i;
+	unsigned long __iomem *cur_gatt;
+	unsigned long addr;
+
+	if (type != 0 || mem->type != 0) {
+		return -EINVAL;
+	}
+	for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+		addr = (i * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = GET_GATT(addr);
+		writel(agp_bridge->scratch_page, cur_gatt+GET_GATT_OFF(addr));
+		readl(cur_gatt+GET_GATT_OFF(addr)); /* PCI Posting. */
+	}
+
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int ati_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	struct aper_size_info_lvl2 *value;
+	ati_page_map page_dir;
+	unsigned long addr;
+	int retval;
+	u32 temp;
+	int i;
+	struct aper_size_info_lvl2 *current_size;
+
+	value = A_SIZE_LVL2(agp_bridge->current_size);
+	retval = ati_create_page_map(&page_dir);
+	if (retval != 0)
+		return retval;
+
+	retval = ati_create_gatt_pages(value->num_entries / 1024);
+	if (retval != 0) {
+		ati_free_page_map(&page_dir);
+		return retval;
+	}
+
+	agp_bridge->gatt_table_real = (u32 *)page_dir.real;
+	agp_bridge->gatt_table = (u32 __iomem *) page_dir.remapped;
+	agp_bridge->gatt_bus_addr = virt_to_bus(page_dir.real);
+
+	/* Write out the size register */
+	current_size = A_SIZE_LVL2(agp_bridge->current_size);
+
+	if (is_r200()) {
+		pci_read_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, &temp);
+		temp = (((temp & ~(0x0000000e)) | current_size->size_value)
+			| 0x00000001);
+		pci_write_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, temp);
+		pci_read_config_dword(agp_bridge->dev, ATI_RS100_APSIZE, &temp);
+	} else {
+		pci_read_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, &temp);
+		temp = (((temp & ~(0x0000000e)) | current_size->size_value)
+			| 0x00000001);
+		pci_write_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, temp);
+		pci_read_config_dword(agp_bridge->dev, ATI_RS300_APSIZE, &temp);
+	}
+
+	/*
+	 * Get the address for the gart region.
+	 * This is a bus address even on the alpha, b/c its
+	 * used to program the agp master not the cpu
+	 */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+	agp_bridge->gart_bus_addr = addr;
+
+	/* Calculate the agp offset */
+	for(i = 0; i < value->num_entries / 1024; i++, addr += 0x00400000) {
+		writel(virt_to_bus(ati_generic_private.gatt_pages[i]->real) | 1,
+			page_dir.remapped+GET_PAGE_DIR_OFF(addr));
+		readl(page_dir.remapped+GET_PAGE_DIR_OFF(addr));	/* PCI Posting. */
+	}
+
+	return 0;
+}
+
+static int ati_free_gatt_table(struct agp_bridge_data *bridge)
+{
+	ati_page_map page_dir;
+
+	page_dir.real = (unsigned long *)agp_bridge->gatt_table_real;
+	page_dir.remapped = (unsigned long __iomem *)agp_bridge->gatt_table;
+
+	ati_free_gatt_pages();
+	ati_free_page_map(&page_dir);
+	return 0;
+}
+
+struct agp_bridge_driver ati_generic_bridge = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= ati_generic_sizes,
+	.size_type		= LVL2_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= ati_configure,
+	.fetch_size		= ati_fetch_size,
+	.cleanup		= ati_cleanup,
+	.tlb_flush		= ati_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= ati_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= ati_create_gatt_table,
+	.free_gatt_table	= ati_free_gatt_table,
+	.insert_memory		= ati_insert_memory,
+	.remove_memory		= ati_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+
+static struct agp_device_ids ati_agp_device_ids[] __devinitdata =
+{
+	{
+		.device_id	= PCI_DEVICE_ID_ATI_RS100,
+		.chipset_name	= "IGP320/M",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_ATI_RS200,
+		.chipset_name	= "IGP330/340/345/350/M",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_ATI_RS200_B,
+		.chipset_name	= "IGP345M",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_ATI_RS250,
+		.chipset_name	= "IGP7000/M",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_ATI_RS300_100,
+		.chipset_name	= "IGP9100/M",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_ATI_RS300_133,
+		.chipset_name	= "IGP9100/M",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_ATI_RS300_166,
+		.chipset_name	= "IGP9100/M",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_ATI_RS300_200,
+		.chipset_name	= "IGP9100/M",
+	},
+	{ }, /* dummy final entry, always present */
+};
+
+static int __devinit agp_ati_probe(struct pci_dev *pdev,
+				   const struct pci_device_id *ent)
+{
+	struct agp_device_ids *devs = ati_agp_device_ids;
+	struct agp_bridge_data *bridge;
+	u8 cap_ptr;
+	int j;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	/* probe for known chipsets */
+	for (j = 0; devs[j].chipset_name; j++) {
+		if (pdev->device == devs[j].device_id)
+			goto found;
+	}
+
+	printk(KERN_ERR PFX
+	     "Unsupported Ati chipset (device id: %04x)\n", pdev->device);
+	return -ENODEV;
+
+found:
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+	
+	bridge->driver = &ati_generic_bridge;
+
+
+	printk(KERN_INFO PFX "Detected Ati %s chipset\n",
+			devs[j].chipset_name);
+
+	/* Fill in the mode register */
+	pci_read_config_dword(pdev,
+			bridge->capndx+PCI_AGP_STATUS,
+			&bridge->mode);
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_ati_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_ati_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_ATI,
+	.device		= PCI_ANY_ID,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_ati_pci_table);
+
+static struct pci_driver agp_ati_pci_driver = {
+	.name		= "agpgart-ati",
+	.id_table	= agp_ati_pci_table,
+	.probe		= agp_ati_probe,
+	.remove		= agp_ati_remove,
+};
+
+static int __init agp_ati_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_ati_pci_driver);
+}
+
+static void __exit agp_ati_cleanup(void)
+{
+	pci_unregister_driver(&agp_ati_pci_driver);
+}
+
+module_init(agp_ati_init);
+module_exit(agp_ati_cleanup);
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
+MODULE_LICENSE("GPL and additional rights");
+
diff --git a/drivers/char/agp/backend.c b/drivers/char/agp/backend.c
new file mode 100644
index 0000000..c3442f3c
--- /dev/null
+++ b/drivers/char/agp/backend.c
@@ -0,0 +1,348 @@
+/*
+ * AGPGART driver backend routines.
+ * Copyright (C) 2004 Silicon Graphics, Inc.
+ * Copyright (C) 2002-2003 Dave Jones.
+ * Copyright (C) 1999 Jeff Hartmann.
+ * Copyright (C) 1999 Precision Insight, Inc.
+ * Copyright (C) 1999 Xi Graphics, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * JEFF HARTMANN, DAVE JONES, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * TODO:
+ * - Allocate more than order 0 pages to avoid too much linear map splitting.
+ */
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/miscdevice.h>
+#include <linux/pm.h>
+#include <linux/agp_backend.h>
+#include <linux/agpgart.h>
+#include <linux/vmalloc.h>
+#include <asm/io.h>
+#include "agp.h"
+
+/* Due to XFree86 brain-damage, we can't go to 1.0 until they
+ * fix some real stupidity. It's only by chance we can bump
+ * past 0.99 at all due to some boolean logic error. */
+#define AGPGART_VERSION_MAJOR 0
+#define AGPGART_VERSION_MINOR 101
+static struct agp_version agp_current_version =
+{
+	.major = AGPGART_VERSION_MAJOR,
+	.minor = AGPGART_VERSION_MINOR,
+};
+
+struct agp_bridge_data *(*agp_find_bridge)(struct pci_dev *) =
+	&agp_generic_find_bridge;
+
+struct agp_bridge_data *agp_bridge;
+LIST_HEAD(agp_bridges);
+EXPORT_SYMBOL(agp_bridge);
+EXPORT_SYMBOL(agp_bridges);
+EXPORT_SYMBOL(agp_find_bridge);
+
+/**
+ *	agp_backend_acquire  -  attempt to acquire an agp backend.
+ *
+ */
+struct agp_bridge_data *agp_backend_acquire(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge;
+
+	bridge = agp_find_bridge(pdev);
+
+	if (!bridge)
+		return NULL;
+
+	if (atomic_read(&bridge->agp_in_use))
+		return NULL;
+	atomic_inc(&bridge->agp_in_use);
+	return bridge;
+}
+EXPORT_SYMBOL(agp_backend_acquire);
+
+
+/**
+ *	agp_backend_release  -  release the lock on the agp backend.
+ *
+ *	The caller must insure that the graphics aperture translation table
+ *	is read for use by another entity.
+ *
+ *	(Ensure that all memory it bound is unbound.)
+ */
+void agp_backend_release(struct agp_bridge_data *bridge)
+{
+
+	if (bridge)
+		atomic_dec(&bridge->agp_in_use);
+}
+EXPORT_SYMBOL(agp_backend_release);
+
+
+struct { int mem, agp; } maxes_table[] = {
+	{0, 0},
+	{32, 4},
+	{64, 28},
+	{128, 96},
+	{256, 204},
+	{512, 440},
+	{1024, 942},
+	{2048, 1920},
+	{4096, 3932}
+};
+
+static int agp_find_max(void)
+{
+	long memory, index, result;
+
+#if PAGE_SHIFT < 20
+	memory = num_physpages >> (20 - PAGE_SHIFT);
+#else
+	memory = num_physpages << (PAGE_SHIFT - 20);
+#endif
+	index = 1;
+
+	while ((memory > maxes_table[index].mem) && (index < 8))
+		index++;
+
+	result = maxes_table[index - 1].agp +
+	   ( (memory - maxes_table[index - 1].mem)  *
+	     (maxes_table[index].agp - maxes_table[index - 1].agp)) /
+	   (maxes_table[index].mem - maxes_table[index - 1].mem);
+
+	result = result << (20 - PAGE_SHIFT);
+	return result;
+}
+
+
+static int agp_backend_initialize(struct agp_bridge_data *bridge)
+{
+	int size_value, rc, got_gatt=0, got_keylist=0;
+
+	bridge->max_memory_agp = agp_find_max();
+	bridge->version = &agp_current_version;
+
+	if (bridge->driver->needs_scratch_page) {
+		void *addr = bridge->driver->agp_alloc_page(bridge);
+
+		if (!addr) {
+			printk(KERN_ERR PFX "unable to get memory for scratch page.\n");
+			return -ENOMEM;
+		}
+
+		bridge->scratch_page_real = virt_to_phys(addr);
+		bridge->scratch_page =
+		    bridge->driver->mask_memory(bridge, bridge->scratch_page_real, 0);
+	}
+
+	size_value = bridge->driver->fetch_size();
+	if (size_value == 0) {
+		printk(KERN_ERR PFX "unable to determine aperture size.\n");
+		rc = -EINVAL;
+		goto err_out;
+	}
+	if (bridge->driver->create_gatt_table(bridge)) {
+		printk(KERN_ERR PFX
+		    "unable to get memory for graphics translation table.\n");
+		rc = -ENOMEM;
+		goto err_out;
+	}
+	got_gatt = 1;
+
+	bridge->key_list = vmalloc(PAGE_SIZE * 4);
+	if (bridge->key_list == NULL) {
+		printk(KERN_ERR PFX "error allocating memory for key lists.\n");
+		rc = -ENOMEM;
+		goto err_out;
+	}
+	got_keylist = 1;
+
+	/* FIXME vmalloc'd memory not guaranteed contiguous */
+	memset(bridge->key_list, 0, PAGE_SIZE * 4);
+
+	if (bridge->driver->configure()) {
+		printk(KERN_ERR PFX "error configuring host chipset.\n");
+		rc = -EINVAL;
+		goto err_out;
+	}
+
+	return 0;
+
+err_out:
+	if (bridge->driver->needs_scratch_page)
+		bridge->driver->agp_destroy_page(
+				phys_to_virt(bridge->scratch_page_real));
+	if (got_gatt)
+		bridge->driver->free_gatt_table(bridge);
+	if (got_keylist) {
+		vfree(bridge->key_list);
+		bridge->key_list = NULL;
+	}
+	return rc;
+}
+
+/* cannot be __exit b/c as it could be called from __init code */
+static void agp_backend_cleanup(struct agp_bridge_data *bridge)
+{
+	if (bridge->driver->cleanup)
+		bridge->driver->cleanup();
+	if (bridge->driver->free_gatt_table)
+		bridge->driver->free_gatt_table(bridge);
+	if (bridge->key_list) {
+		vfree(bridge->key_list);
+		bridge->key_list = NULL;
+	}
+
+	if (bridge->driver->agp_destroy_page &&
+	    bridge->driver->needs_scratch_page)
+		bridge->driver->agp_destroy_page(
+				phys_to_virt(bridge->scratch_page_real));
+}
+
+/* When we remove the global variable agp_bridge from all drivers
+ * then agp_alloc_bridge and agp_generic_find_bridge need to be updated
+ */
+
+struct agp_bridge_data *agp_alloc_bridge(void)
+{
+	struct agp_bridge_data *bridge = kmalloc(sizeof(*bridge), GFP_KERNEL);
+
+	if (!bridge)
+		return NULL;
+
+	memset(bridge, 0, sizeof(*bridge));
+	atomic_set(&bridge->agp_in_use, 0);
+	atomic_set(&bridge->current_memory_agp, 0);
+
+	if (list_empty(&agp_bridges))
+		agp_bridge = bridge;
+
+	return bridge;
+}
+EXPORT_SYMBOL(agp_alloc_bridge);
+
+
+void agp_put_bridge(struct agp_bridge_data *bridge)
+{
+        kfree(bridge);
+
+        if (list_empty(&agp_bridges))
+                agp_bridge = NULL;
+}
+EXPORT_SYMBOL(agp_put_bridge);
+
+
+int agp_add_bridge(struct agp_bridge_data *bridge)
+{
+	int error;
+
+	if (agp_off)
+		return -ENODEV;
+
+	if (!bridge->dev) {
+		printk (KERN_DEBUG PFX "Erk, registering with no pci_dev!\n");
+		return -EINVAL;
+	}
+
+	/* Grab reference on the chipset driver. */
+	if (!try_module_get(bridge->driver->owner)) {
+		printk (KERN_INFO PFX "Couldn't lock chipset driver.\n");
+		return -EINVAL;
+	}
+
+	error = agp_backend_initialize(bridge);
+	if (error) {
+		printk (KERN_INFO PFX "agp_backend_initialize() failed.\n");
+		goto err_out;
+	}
+
+	if (list_empty(&agp_bridges)) {
+		error = agp_frontend_initialize();
+		if (error) {
+			printk (KERN_INFO PFX "agp_frontend_initialize() failed.\n");
+			goto frontend_err;
+		}
+
+		printk(KERN_INFO PFX "AGP aperture is %dM @ 0x%lx\n",
+			bridge->driver->fetch_size(), bridge->gart_bus_addr);
+
+	}
+
+	list_add(&bridge->list, &agp_bridges);
+	return 0;
+
+frontend_err:
+	agp_backend_cleanup(bridge);
+err_out:
+	module_put(bridge->driver->owner);
+	agp_put_bridge(bridge);
+	return error;
+}
+EXPORT_SYMBOL_GPL(agp_add_bridge);
+
+
+void agp_remove_bridge(struct agp_bridge_data *bridge)
+{
+	agp_backend_cleanup(bridge);
+	list_del(&bridge->list);
+	if (list_empty(&agp_bridges))
+		agp_frontend_cleanup();
+	module_put(bridge->driver->owner);
+}
+EXPORT_SYMBOL_GPL(agp_remove_bridge);
+
+int agp_off;
+int agp_try_unsupported_boot;
+EXPORT_SYMBOL(agp_off);
+EXPORT_SYMBOL(agp_try_unsupported_boot);
+
+static int __init agp_init(void)
+{
+	if (!agp_off)
+		printk(KERN_INFO "Linux agpgart interface v%d.%d (c) Dave Jones\n",
+			AGPGART_VERSION_MAJOR, AGPGART_VERSION_MINOR);
+	return 0;
+}
+
+void __exit agp_exit(void)
+{
+}
+
+#ifndef MODULE
+static __init int agp_setup(char *s)
+{
+	if (!strcmp(s,"off"))
+		agp_off = 1;
+	if (!strcmp(s,"try_unsupported"))
+		agp_try_unsupported_boot = 1;
+	return 1;
+}
+__setup("agp=", agp_setup);
+#endif
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
+MODULE_DESCRIPTION("AGP GART driver");
+MODULE_LICENSE("GPL and additional rights");
+MODULE_ALIAS_MISCDEV(AGPGART_MINOR);
+
+module_init(agp_init);
+module_exit(agp_exit);
+
diff --git a/drivers/char/agp/efficeon-agp.c b/drivers/char/agp/efficeon-agp.c
new file mode 100644
index 0000000..52c0a09
--- /dev/null
+++ b/drivers/char/agp/efficeon-agp.c
@@ -0,0 +1,463 @@
+/*
+ * Transmeta's Efficeon AGPGART driver.
+ * 
+ * Based upon a diff by Linus around November '02.
+ *
+ * Ported to the 2.6 kernel by Carlos Puchol <cpglinux@puchol.com>
+ * and H. Peter Anvin <hpa@transmeta.com>.
+ */
+
+/*
+ * NOTE-cpg-040217:
+ * 
+ *   - when compiled as a module, after loading the module,
+ *     it will refuse to unload, indicating it is in use,
+ *     when it is not.
+ *   - no s3 (suspend to ram) testing.
+ *   - tested on the efficeon integrated nothbridge for tens
+ *     of iterations of starting x and glxgears.
+ *   - tested with radeon 9000 and radeon mobility m9 cards
+ *   - tested with c3/c4 enabled (with the mobility m9 card)
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/gfp.h>
+#include <linux/page-flags.h>
+#include <linux/mm.h>
+#include "agp.h"
+
+/*
+ * The real differences to the generic AGP code is
+ * in the GART mappings - a two-level setup with the
+ * first level being an on-chip 64-entry table.
+ *
+ * The page array is filled through the ATTPAGE register
+ * (Aperture Translation Table Page Register) at 0xB8. Bits:
+ *  31:20: physical page address
+ *   11:9: Page Attribute Table Index (PATI)
+ *	   must match the PAT index for the
+ *	   mapped pages (the 2nd level page table pages
+ *	   themselves should be just regular WB-cacheable,
+ *	   so this is normally zero.)
+ *      8: Present
+ *    7:6: reserved, write as zero
+ *    5:0: GATT directory index: which 1st-level entry
+ * 
+ * The Efficeon AGP spec requires pages to be WB-cacheable
+ * but to be explicitly CLFLUSH'd after any changes.
+ */
+#define EFFICEON_ATTPAGE	0xb8
+#define EFFICEON_L1_SIZE	64	/* Number of PDE pages */
+
+#define EFFICEON_PATI		(0 << 9)
+#define EFFICEON_PRESENT	(1 << 8)
+
+static struct _efficeon_private {
+	unsigned long l1_table[EFFICEON_L1_SIZE];
+} efficeon_private;
+
+static struct gatt_mask efficeon_generic_masks[] =
+{
+	{.mask = 0x00000001, .type = 0}
+};
+
+static struct aper_size_info_lvl2 efficeon_generic_sizes[4] =
+{
+	{256, 65536, 0},
+	{128, 32768, 32},
+	{64, 16384, 48},
+	{32, 8192, 56}
+};
+
+/*
+ * Control interfaces are largely identical to
+ * the legacy Intel 440BX..
+ */
+
+static int efficeon_fetch_size(void)
+{
+	int i;
+	u16 temp;
+	struct aper_size_info_lvl2 *values;
+
+	pci_read_config_word(agp_bridge->dev, INTEL_APSIZE, &temp);
+	values = A_SIZE_LVL2(agp_bridge->driver->aperture_sizes);
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+static void efficeon_tlbflush(struct agp_memory * mem)
+{
+	printk(KERN_DEBUG PFX "efficeon_tlbflush()\n");
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2200);
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2280);
+}
+
+static void efficeon_cleanup(void)
+{
+	u16 temp;
+	struct aper_size_info_lvl2 *previous_size;
+
+	printk(KERN_DEBUG PFX "efficeon_cleanup()\n");
+	previous_size = A_SIZE_LVL2(agp_bridge->previous_size);
+	pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp);
+	pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG, temp & ~(1 << 9));
+	pci_write_config_word(agp_bridge->dev, INTEL_APSIZE,
+			      previous_size->size_value);
+}
+
+static int efficeon_configure(void)
+{
+	u32 temp;
+	u16 temp2;
+	struct aper_size_info_lvl2 *current_size;
+
+	printk(KERN_DEBUG PFX "efficeon_configure()\n");
+	
+	current_size = A_SIZE_LVL2(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_word(agp_bridge->dev, INTEL_APSIZE,
+			      current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2280);
+
+	/* paccfg/nbxcfg */
+	pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp2);
+	pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG,
+			      (temp2 & ~(1 << 10)) | (1 << 9) | (1 << 11));
+	/* clear any possible error conditions */
+	pci_write_config_byte(agp_bridge->dev, INTEL_ERRSTS + 1, 7);
+	return 0;
+}
+
+static int efficeon_free_gatt_table(struct agp_bridge_data *bridge)
+{
+	int index, freed = 0;
+
+	for (index = 0; index < EFFICEON_L1_SIZE; index++) {
+		unsigned long page = efficeon_private.l1_table[index];
+		if (page) {
+			efficeon_private.l1_table[index] = 0;
+			ClearPageReserved(virt_to_page((char *)page));
+			free_page(page);
+			freed++;
+		}
+		printk(KERN_DEBUG PFX "efficeon_free_gatt_table(%p, %02x, %08x)\n",
+			agp_bridge->dev, EFFICEON_ATTPAGE, index);
+		pci_write_config_dword(agp_bridge->dev,
+			EFFICEON_ATTPAGE, index);
+	}
+	printk(KERN_DEBUG PFX "efficeon_free_gatt_table() freed %d pages\n", freed);
+	return 0;
+}
+
+
+/*
+ * Since we don't need contigious memory we just try
+ * to get the gatt table once
+ */
+
+#define GET_PAGE_DIR_OFF(addr) (addr >> 22)
+#define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
+	GET_PAGE_DIR_OFF(agp_bridge->gart_bus_addr))
+#define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
+#undef  GET_GATT
+#define GET_GATT(addr) (efficeon_private.gatt_pages[\
+	GET_PAGE_DIR_IDX(addr)]->remapped)
+
+static int efficeon_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	int index;
+	const int pati    = EFFICEON_PATI;
+	const int present = EFFICEON_PRESENT;
+	const int clflush_chunk = ((cpuid_ebx(1) >> 8) & 0xff) << 3;
+	int num_entries, l1_pages;
+	
+	num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+	printk(KERN_DEBUG PFX "efficeon_create_gatt_table(%d)\n", num_entries);
+
+	/* There are 2^10 PTE pages per PDE page */
+	BUG_ON(num_entries & 0x3ff);
+	l1_pages = num_entries >> 10;
+
+	for (index = 0 ; index < l1_pages ; index++) {
+		int offset;
+		unsigned long page;
+		unsigned long value;
+
+		page = efficeon_private.l1_table[index];
+		BUG_ON(page);
+
+		page = get_zeroed_page(GFP_KERNEL);
+		if (!page) {
+			efficeon_free_gatt_table(agp_bridge);
+			return -ENOMEM;
+		}
+		SetPageReserved(virt_to_page((char *)page));
+
+		for (offset = 0; offset < PAGE_SIZE; offset += clflush_chunk)
+			asm volatile("clflush %0" : : "m" (*(char *)(page+offset)));
+
+		efficeon_private.l1_table[index] = page;
+
+		value = __pa(page) | pati | present | index;
+
+		pci_write_config_dword(agp_bridge->dev,
+			EFFICEON_ATTPAGE, value);
+	}
+
+	return 0;
+}
+
+static int efficeon_insert_memory(struct agp_memory * mem, off_t pg_start, int type)
+{
+	int i, count = mem->page_count, num_entries;
+	unsigned int *page, *last_page;
+	const int clflush_chunk = ((cpuid_ebx(1) >> 8) & 0xff) << 3;
+	const unsigned long clflush_mask = ~(clflush_chunk-1);
+
+	printk(KERN_DEBUG PFX "efficeon_insert_memory(%lx, %d)\n", pg_start, count);
+
+	num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+	if (type != 0 || mem->type != 0)
+		return -EINVAL;
+
+	if (mem->is_flushed == FALSE) {
+		global_cache_flush();
+		mem->is_flushed = TRUE;
+	}
+
+	last_page = NULL;
+	for (i = 0; i < count; i++) {
+		int index = pg_start + i;
+		unsigned long insert = mem->memory[i];
+
+		page = (unsigned int *) efficeon_private.l1_table[index >> 10];
+
+		if (!page)
+			continue;
+		
+		page += (index & 0x3ff);
+		*page = insert;
+
+		/* clflush is slow, so don't clflush until we have to */
+		if ( last_page && 
+		     ((unsigned long)page^(unsigned long)last_page) & clflush_mask )
+		    asm volatile("clflush %0" : : "m" (*last_page));
+
+		last_page = page;
+	}
+
+	if ( last_page )
+		asm volatile("clflush %0" : : "m" (*last_page));
+
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int efficeon_remove_memory(struct agp_memory * mem, off_t pg_start, int type)
+{
+	int i, count = mem->page_count, num_entries;
+
+	printk(KERN_DEBUG PFX "efficeon_remove_memory(%lx, %d)\n", pg_start, count);
+
+	num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+	if (type != 0 || mem->type != 0)
+		return -EINVAL;
+
+	for (i = 0; i < count; i++) {
+		int index = pg_start + i;
+		unsigned int *page = (unsigned int *) efficeon_private.l1_table[index >> 10];
+
+		if (!page)
+			continue;
+		page += (index & 0x3ff);
+		*page = 0;
+	}
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+
+struct agp_bridge_driver efficeon_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= efficeon_generic_sizes,
+	.size_type		= LVL2_APER_SIZE,
+	.num_aperture_sizes	= 4,
+	.configure		= efficeon_configure,
+	.fetch_size		= efficeon_fetch_size,
+	.cleanup		= efficeon_cleanup,
+	.tlb_flush		= efficeon_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= efficeon_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+
+	// Efficeon-specific GATT table setup / populate / teardown
+	.create_gatt_table	= efficeon_create_gatt_table,
+	.free_gatt_table	= efficeon_free_gatt_table,
+	.insert_memory		= efficeon_insert_memory,
+	.remove_memory		= efficeon_remove_memory,
+	.cant_use_aperture	= 0,	// 1 might be faster?
+
+	// Generic
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+
+static int agp_efficeon_resume(struct pci_dev *pdev)
+{
+	printk(KERN_DEBUG PFX "agp_efficeon_resume()\n");
+	return efficeon_configure();
+}
+
+static int __devinit agp_efficeon_probe(struct pci_dev *pdev,
+				     const struct pci_device_id *ent)
+{
+	struct agp_bridge_data *bridge;
+	u8 cap_ptr;
+	struct resource *r;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	/* Probe for Efficeon controller */
+	if (pdev->device != PCI_DEVICE_ID_EFFICEON) {
+		printk(KERN_ERR PFX "Unsupported Efficeon chipset (device id: %04x)\n",
+		    pdev->device);
+		return -ENODEV;
+	}
+
+	printk(KERN_INFO PFX "Detected Transmeta Efficeon TM8000 series chipset\n");
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->driver = &efficeon_driver;
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+
+	/*
+	* The following fixes the case where the BIOS has "forgotten" to
+	* provide an address range for the GART.
+	* 20030610 - hamish@zot.org
+	*/
+	r = &pdev->resource[0];
+	if (!r->start && r->end) {
+		if(pci_assign_resource(pdev, 0)) {
+			printk(KERN_ERR PFX "could not assign resource 0\n");
+			return -ENODEV;
+		}
+	}
+
+	/*
+	* If the device has not been properly setup, the following will catch
+	* the problem and should stop the system from crashing.
+	* 20030610 - hamish@zot.org
+	*/
+	if (pci_enable_device(pdev)) {
+		printk(KERN_ERR PFX "Unable to Enable PCI device\n");
+		return -ENODEV;
+	}
+
+	/* Fill in the mode register */
+	if (cap_ptr) {
+		pci_read_config_dword(pdev,
+				bridge->capndx+PCI_AGP_STATUS,
+				&bridge->mode);
+	}
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_efficeon_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static int agp_efficeon_suspend(struct pci_dev *dev, u32 state)
+{
+	return 0;
+}
+
+
+static struct pci_device_id agp_efficeon_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_TRANSMETA,
+	.device		= PCI_ANY_ID,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_efficeon_pci_table);
+
+static struct pci_driver agp_efficeon_pci_driver = {
+	.name		= "agpgart-efficeon",
+	.id_table	= agp_efficeon_pci_table,
+	.probe		= agp_efficeon_probe,
+	.remove		= agp_efficeon_remove,
+	.suspend	= agp_efficeon_suspend,
+	.resume		= agp_efficeon_resume,
+};
+
+static int __init agp_efficeon_init(void)
+{
+	static int agp_initialised=0;
+
+	if (agp_off)
+		return -EINVAL;
+
+	if (agp_initialised == 1)
+		return 0;
+	agp_initialised=1;
+
+	return pci_register_driver(&agp_efficeon_pci_driver);
+}
+
+static void __exit agp_efficeon_cleanup(void)
+{
+	pci_unregister_driver(&agp_efficeon_pci_driver);
+}
+
+module_init(agp_efficeon_init);
+module_exit(agp_efficeon_cleanup);
+
+MODULE_AUTHOR("Carlos Puchol <cpglinux@puchol.com>");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/frontend.c b/drivers/char/agp/frontend.c
new file mode 100644
index 0000000..f633623
--- /dev/null
+++ b/drivers/char/agp/frontend.c
@@ -0,0 +1,1103 @@
+/*
+ * AGPGART driver frontend
+ * Copyright (C) 2004 Silicon Graphics, Inc.
+ * Copyright (C) 2002-2003 Dave Jones
+ * Copyright (C) 1999 Jeff Hartmann
+ * Copyright (C) 1999 Precision Insight, Inc.
+ * Copyright (C) 1999 Xi Graphics, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * JEFF HARTMANN, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mman.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/agp_backend.h>
+#include <linux/agpgart.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include "agp.h"
+
+static struct agp_front_data agp_fe;
+
+static struct agp_memory *agp_find_mem_by_key(int key)
+{
+	struct agp_memory *curr;
+
+	if (agp_fe.current_controller == NULL)
+		return NULL;
+
+	curr = agp_fe.current_controller->pool;
+
+	while (curr != NULL) {
+		if (curr->key == key)
+			break;
+		curr = curr->next;
+	}
+
+	DBG("key=%d -> mem=%p", key, curr);
+	return curr;
+}
+
+static void agp_remove_from_pool(struct agp_memory *temp)
+{
+	struct agp_memory *prev;
+	struct agp_memory *next;
+
+	/* Check to see if this is even in the memory pool */
+
+	DBG("mem=%p", temp);
+	if (agp_find_mem_by_key(temp->key) != NULL) {
+		next = temp->next;
+		prev = temp->prev;
+
+		if (prev != NULL) {
+			prev->next = next;
+			if (next != NULL)
+				next->prev = prev;
+
+		} else {
+			/* This is the first item on the list */
+			if (next != NULL)
+				next->prev = NULL;
+
+			agp_fe.current_controller->pool = next;
+		}
+	}
+}
+
+/*
+ * Routines for managing each client's segment list -
+ * These routines handle adding and removing segments
+ * to each auth'ed client.
+ */
+
+static struct
+agp_segment_priv *agp_find_seg_in_client(const struct agp_client *client,
+						unsigned long offset,
+					    int size, pgprot_t page_prot)
+{
+	struct agp_segment_priv *seg;
+	int num_segments, i;
+	off_t pg_start;
+	size_t pg_count;
+
+	pg_start = offset / 4096;
+	pg_count = size / 4096;
+	seg = *(client->segments);
+	num_segments = client->num_segments;
+
+	for (i = 0; i < client->num_segments; i++) {
+		if ((seg[i].pg_start == pg_start) &&
+		    (seg[i].pg_count == pg_count) &&
+		    (pgprot_val(seg[i].prot) == pgprot_val(page_prot))) {
+			return seg + i;
+		}
+	}
+
+	return NULL;
+}
+
+static void agp_remove_seg_from_client(struct agp_client *client)
+{
+	DBG("client=%p", client);
+
+	if (client->segments != NULL) {
+		if (*(client->segments) != NULL) {
+			DBG("Freeing %p from client %p", *(client->segments), client);
+			kfree(*(client->segments));
+		}
+		DBG("Freeing %p from client %p", client->segments, client);
+		kfree(client->segments);
+		client->segments = NULL;
+	}
+}
+
+static void agp_add_seg_to_client(struct agp_client *client,
+			       struct agp_segment_priv ** seg, int num_segments)
+{
+	struct agp_segment_priv **prev_seg;
+
+	prev_seg = client->segments;
+
+	if (prev_seg != NULL)
+		agp_remove_seg_from_client(client);
+
+	DBG("Adding seg %p (%d segments) to client %p", seg, num_segments, client);
+	client->num_segments = num_segments;
+	client->segments = seg;
+}
+
+/* Originally taken from linux/mm/mmap.c from the array
+ * protection_map.
+ * The original really should be exported to modules, or
+ * some routine which does the conversion for you
+ */
+
+static const pgprot_t my_protect_map[16] =
+{
+	__P000, __P001, __P010, __P011, __P100, __P101, __P110, __P111,
+	__S000, __S001, __S010, __S011, __S100, __S101, __S110, __S111
+};
+
+static pgprot_t agp_convert_mmap_flags(int prot)
+{
+#define _trans(x,bit1,bit2) \
+((bit1==bit2)?(x&bit1):(x&bit1)?bit2:0)
+
+	unsigned long prot_bits;
+	pgprot_t temp;
+
+	prot_bits = _trans(prot, PROT_READ, VM_READ) |
+	    _trans(prot, PROT_WRITE, VM_WRITE) |
+	    _trans(prot, PROT_EXEC, VM_EXEC);
+
+	prot_bits |= VM_SHARED;
+
+	temp = my_protect_map[prot_bits & 0x0000000f];
+
+	return temp;
+}
+
+static int agp_create_segment(struct agp_client *client, struct agp_region *region)
+{
+	struct agp_segment_priv **ret_seg;
+	struct agp_segment_priv *seg;
+	struct agp_segment *user_seg;
+	size_t i;
+
+	seg = kmalloc((sizeof(struct agp_segment_priv) * region->seg_count), GFP_KERNEL);
+	if (seg == NULL) {
+		kfree(region->seg_list);
+		region->seg_list = NULL;
+		return -ENOMEM;
+	}
+	memset(seg, 0, (sizeof(struct agp_segment_priv) * region->seg_count));
+	user_seg = region->seg_list;
+
+	for (i = 0; i < region->seg_count; i++) {
+		seg[i].pg_start = user_seg[i].pg_start;
+		seg[i].pg_count = user_seg[i].pg_count;
+		seg[i].prot = agp_convert_mmap_flags(user_seg[i].prot);
+	}
+	kfree(region->seg_list);
+	region->seg_list = NULL;
+
+	ret_seg = kmalloc(sizeof(void *), GFP_KERNEL);
+	if (ret_seg == NULL) {
+		kfree(seg);
+		return -ENOMEM;
+	}
+	*ret_seg = seg;
+	agp_add_seg_to_client(client, ret_seg, region->seg_count);
+	return 0;
+}
+
+/* End - Routines for managing each client's segment list */
+
+/* This function must only be called when current_controller != NULL */
+static void agp_insert_into_pool(struct agp_memory * temp)
+{
+	struct agp_memory *prev;
+
+	prev = agp_fe.current_controller->pool;
+
+	if (prev != NULL) {
+		prev->prev = temp;
+		temp->next = prev;
+	}
+	agp_fe.current_controller->pool = temp;
+}
+
+
+/* File private list routines */
+
+struct agp_file_private *agp_find_private(pid_t pid)
+{
+	struct agp_file_private *curr;
+
+	curr = agp_fe.file_priv_list;
+
+	while (curr != NULL) {
+		if (curr->my_pid == pid)
+			return curr;
+		curr = curr->next;
+	}
+
+	return NULL;
+}
+
+void agp_insert_file_private(struct agp_file_private * priv)
+{
+	struct agp_file_private *prev;
+
+	prev = agp_fe.file_priv_list;
+
+	if (prev != NULL)
+		prev->prev = priv;
+	priv->next = prev;
+	agp_fe.file_priv_list = priv;
+}
+
+void agp_remove_file_private(struct agp_file_private * priv)
+{
+	struct agp_file_private *next;
+	struct agp_file_private *prev;
+
+	next = priv->next;
+	prev = priv->prev;
+
+	if (prev != NULL) {
+		prev->next = next;
+
+		if (next != NULL)
+			next->prev = prev;
+
+	} else {
+		if (next != NULL)
+			next->prev = NULL;
+
+		agp_fe.file_priv_list = next;
+	}
+}
+
+/* End - File flag list routines */
+
+/*
+ * Wrappers for agp_free_memory & agp_allocate_memory
+ * These make sure that internal lists are kept updated.
+ */
+static void agp_free_memory_wrap(struct agp_memory *memory)
+{
+	agp_remove_from_pool(memory);
+	agp_free_memory(memory);
+}
+
+static struct agp_memory *agp_allocate_memory_wrap(size_t pg_count, u32 type)
+{
+	struct agp_memory *memory;
+
+	memory = agp_allocate_memory(agp_bridge, pg_count, type);
+	if (memory == NULL)
+		return NULL;
+
+	agp_insert_into_pool(memory);
+	return memory;
+}
+
+/* Routines for managing the list of controllers -
+ * These routines manage the current controller, and the list of
+ * controllers
+ */
+
+static struct agp_controller *agp_find_controller_by_pid(pid_t id)
+{
+	struct agp_controller *controller;
+
+	controller = agp_fe.controllers;
+
+	while (controller != NULL) {
+		if (controller->pid == id)
+			return controller;
+		controller = controller->next;
+	}
+
+	return NULL;
+}
+
+static struct agp_controller *agp_create_controller(pid_t id)
+{
+	struct agp_controller *controller;
+
+	controller = kmalloc(sizeof(struct agp_controller), GFP_KERNEL);
+
+	if (controller == NULL)
+		return NULL;
+
+	memset(controller, 0, sizeof(struct agp_controller));
+	controller->pid = id;
+
+	return controller;
+}
+
+static int agp_insert_controller(struct agp_controller *controller)
+{
+	struct agp_controller *prev_controller;
+
+	prev_controller = agp_fe.controllers;
+	controller->next = prev_controller;
+
+	if (prev_controller != NULL)
+		prev_controller->prev = controller;
+
+	agp_fe.controllers = controller;
+
+	return 0;
+}
+
+static void agp_remove_all_clients(struct agp_controller *controller)
+{
+	struct agp_client *client;
+	struct agp_client *temp;
+
+	client = controller->clients;
+
+	while (client) {
+		struct agp_file_private *priv;
+
+		temp = client;
+		agp_remove_seg_from_client(temp);
+		priv = agp_find_private(temp->pid);
+
+		if (priv != NULL) {
+			clear_bit(AGP_FF_IS_VALID, &priv->access_flags);
+			clear_bit(AGP_FF_IS_CLIENT, &priv->access_flags);
+		}
+		client = client->next;
+		kfree(temp);
+	}
+}
+
+static void agp_remove_all_memory(struct agp_controller *controller)
+{
+	struct agp_memory *memory;
+	struct agp_memory *temp;
+
+	memory = controller->pool;
+
+	while (memory) {
+		temp = memory;
+		memory = memory->next;
+		agp_free_memory_wrap(temp);
+	}
+}
+
+static int agp_remove_controller(struct agp_controller *controller)
+{
+	struct agp_controller *prev_controller;
+	struct agp_controller *next_controller;
+
+	prev_controller = controller->prev;
+	next_controller = controller->next;
+
+	if (prev_controller != NULL) {
+		prev_controller->next = next_controller;
+		if (next_controller != NULL)
+			next_controller->prev = prev_controller;
+
+	} else {
+		if (next_controller != NULL)
+			next_controller->prev = NULL;
+
+		agp_fe.controllers = next_controller;
+	}
+
+	agp_remove_all_memory(controller);
+	agp_remove_all_clients(controller);
+
+	if (agp_fe.current_controller == controller) {
+		agp_fe.current_controller = NULL;
+		agp_fe.backend_acquired = FALSE;
+		agp_backend_release(agp_bridge);
+	}
+	kfree(controller);
+	return 0;
+}
+
+static void agp_controller_make_current(struct agp_controller *controller)
+{
+	struct agp_client *clients;
+
+	clients = controller->clients;
+
+	while (clients != NULL) {
+		struct agp_file_private *priv;
+
+		priv = agp_find_private(clients->pid);
+
+		if (priv != NULL) {
+			set_bit(AGP_FF_IS_VALID, &priv->access_flags);
+			set_bit(AGP_FF_IS_CLIENT, &priv->access_flags);
+		}
+		clients = clients->next;
+	}
+
+	agp_fe.current_controller = controller;
+}
+
+static void agp_controller_release_current(struct agp_controller *controller,
+				      struct agp_file_private *controller_priv)
+{
+	struct agp_client *clients;
+
+	clear_bit(AGP_FF_IS_VALID, &controller_priv->access_flags);
+	clients = controller->clients;
+
+	while (clients != NULL) {
+		struct agp_file_private *priv;
+
+		priv = agp_find_private(clients->pid);
+
+		if (priv != NULL)
+			clear_bit(AGP_FF_IS_VALID, &priv->access_flags);
+
+		clients = clients->next;
+	}
+
+	agp_fe.current_controller = NULL;
+	agp_fe.used_by_controller = FALSE;
+	agp_backend_release(agp_bridge);
+}
+
+/*
+ * Routines for managing client lists -
+ * These routines are for managing the list of auth'ed clients.
+ */
+
+static struct agp_client
+*agp_find_client_in_controller(struct agp_controller *controller, pid_t id)
+{
+	struct agp_client *client;
+
+	if (controller == NULL)
+		return NULL;
+
+	client = controller->clients;
+
+	while (client != NULL) {
+		if (client->pid == id)
+			return client;
+		client = client->next;
+	}
+
+	return NULL;
+}
+
+static struct agp_controller *agp_find_controller_for_client(pid_t id)
+{
+	struct agp_controller *controller;
+
+	controller = agp_fe.controllers;
+
+	while (controller != NULL) {
+		if ((agp_find_client_in_controller(controller, id)) != NULL)
+			return controller;
+		controller = controller->next;
+	}
+
+	return NULL;
+}
+
+static struct agp_client *agp_find_client_by_pid(pid_t id)
+{
+	struct agp_client *temp;
+
+	if (agp_fe.current_controller == NULL)
+		return NULL;
+
+	temp = agp_find_client_in_controller(agp_fe.current_controller, id);
+	return temp;
+}
+
+static void agp_insert_client(struct agp_client *client)
+{
+	struct agp_client *prev_client;
+
+	prev_client = agp_fe.current_controller->clients;
+	client->next = prev_client;
+
+	if (prev_client != NULL)
+		prev_client->prev = client;
+
+	agp_fe.current_controller->clients = client;
+	agp_fe.current_controller->num_clients++;
+}
+
+static struct agp_client *agp_create_client(pid_t id)
+{
+	struct agp_client *new_client;
+
+	new_client = kmalloc(sizeof(struct agp_client), GFP_KERNEL);
+
+	if (new_client == NULL)
+		return NULL;
+
+	memset(new_client, 0, sizeof(struct agp_client));
+	new_client->pid = id;
+	agp_insert_client(new_client);
+	return new_client;
+}
+
+static int agp_remove_client(pid_t id)
+{
+	struct agp_client *client;
+	struct agp_client *prev_client;
+	struct agp_client *next_client;
+	struct agp_controller *controller;
+
+	controller = agp_find_controller_for_client(id);
+	if (controller == NULL)
+		return -EINVAL;
+
+	client = agp_find_client_in_controller(controller, id);
+	if (client == NULL)
+		return -EINVAL;
+
+	prev_client = client->prev;
+	next_client = client->next;
+
+	if (prev_client != NULL) {
+		prev_client->next = next_client;
+		if (next_client != NULL)
+			next_client->prev = prev_client;
+
+	} else {
+		if (next_client != NULL)
+			next_client->prev = NULL;
+		controller->clients = next_client;
+	}
+
+	controller->num_clients--;
+	agp_remove_seg_from_client(client);
+	kfree(client);
+	return 0;
+}
+
+/* End - Routines for managing client lists */
+
+/* File Operations */
+
+static int agp_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	unsigned int size, current_size;
+	unsigned long offset;
+	struct agp_client *client;
+	struct agp_file_private *priv = file->private_data;
+	struct agp_kern_info kerninfo;
+
+	down(&(agp_fe.agp_mutex));
+
+	if (agp_fe.backend_acquired != TRUE)
+		goto out_eperm;
+
+	if (!(test_bit(AGP_FF_IS_VALID, &priv->access_flags)))
+		goto out_eperm;
+
+	agp_copy_info(agp_bridge, &kerninfo);
+	size = vma->vm_end - vma->vm_start;
+	current_size = kerninfo.aper_size;
+	current_size = current_size * 0x100000;
+	offset = vma->vm_pgoff << PAGE_SHIFT;
+	DBG("%lx:%lx", offset, offset+size);
+
+	if (test_bit(AGP_FF_IS_CLIENT, &priv->access_flags)) {
+		if ((size + offset) > current_size)
+			goto out_inval;
+
+		client = agp_find_client_by_pid(current->pid);
+
+		if (client == NULL)
+			goto out_eperm;
+
+		if (!agp_find_seg_in_client(client, offset, size, vma->vm_page_prot))
+			goto out_inval;
+
+		DBG("client vm_ops=%p", kerninfo.vm_ops);
+		if (kerninfo.vm_ops) {
+			vma->vm_ops = kerninfo.vm_ops;
+		} else if (io_remap_pfn_range(vma, vma->vm_start,
+				(kerninfo.aper_base + offset) >> PAGE_SHIFT,
+					    size, vma->vm_page_prot)) {
+			goto out_again;
+		}
+		up(&(agp_fe.agp_mutex));
+		return 0;
+	}
+
+	if (test_bit(AGP_FF_IS_CONTROLLER, &priv->access_flags)) {
+		if (size != current_size)
+			goto out_inval;
+
+		DBG("controller vm_ops=%p", kerninfo.vm_ops);
+		if (kerninfo.vm_ops) {
+			vma->vm_ops = kerninfo.vm_ops;
+		} else if (io_remap_pfn_range(vma, vma->vm_start,
+					    kerninfo.aper_base >> PAGE_SHIFT,
+					    size, vma->vm_page_prot)) {
+			goto out_again;
+		}
+		up(&(agp_fe.agp_mutex));
+		return 0;
+	}
+
+out_eperm:
+	up(&(agp_fe.agp_mutex));
+	return -EPERM;
+
+out_inval:
+	up(&(agp_fe.agp_mutex));
+	return -EINVAL;
+
+out_again:
+	up(&(agp_fe.agp_mutex));
+	return -EAGAIN;
+}
+
+static int agp_release(struct inode *inode, struct file *file)
+{
+	struct agp_file_private *priv = file->private_data;
+
+	down(&(agp_fe.agp_mutex));
+
+	DBG("priv=%p", priv);
+
+	if (test_bit(AGP_FF_IS_CONTROLLER, &priv->access_flags)) {
+		struct agp_controller *controller;
+
+		controller = agp_find_controller_by_pid(priv->my_pid);
+
+		if (controller != NULL) {
+			if (controller == agp_fe.current_controller)
+				agp_controller_release_current(controller, priv);
+			agp_remove_controller(controller);
+			controller = NULL;
+		}
+	}
+
+	if (test_bit(AGP_FF_IS_CLIENT, &priv->access_flags))
+		agp_remove_client(priv->my_pid);
+
+	agp_remove_file_private(priv);
+	kfree(priv);
+	file->private_data = NULL;
+	up(&(agp_fe.agp_mutex));
+	return 0;
+}
+
+static int agp_open(struct inode *inode, struct file *file)
+{
+	int minor = iminor(inode);
+	struct agp_file_private *priv;
+	struct agp_client *client;
+	int rc = -ENXIO;
+
+	down(&(agp_fe.agp_mutex));
+
+	if (minor != AGPGART_MINOR)
+		goto err_out;
+
+	priv = kmalloc(sizeof(struct agp_file_private), GFP_KERNEL);
+	if (priv == NULL)
+		goto err_out_nomem;
+
+	memset(priv, 0, sizeof(struct agp_file_private));
+	set_bit(AGP_FF_ALLOW_CLIENT, &priv->access_flags);
+	priv->my_pid = current->pid;
+
+	if ((current->uid == 0) || (current->suid == 0)) {
+		/* Root priv, can be controller */
+		set_bit(AGP_FF_ALLOW_CONTROLLER, &priv->access_flags);
+	}
+	client = agp_find_client_by_pid(current->pid);
+
+	if (client != NULL) {
+		set_bit(AGP_FF_IS_CLIENT, &priv->access_flags);
+		set_bit(AGP_FF_IS_VALID, &priv->access_flags);
+	}
+	file->private_data = (void *) priv;
+	agp_insert_file_private(priv);
+	DBG("private=%p, client=%p", priv, client);
+	up(&(agp_fe.agp_mutex));
+	return 0;
+
+err_out_nomem:
+	rc = -ENOMEM;
+err_out:
+	up(&(agp_fe.agp_mutex));
+	return rc;
+}
+
+
+static ssize_t agp_read(struct file *file, char __user *buf,
+			size_t count, loff_t * ppos)
+{
+	return -EINVAL;
+}
+
+static ssize_t agp_write(struct file *file, const char __user *buf,
+			 size_t count, loff_t * ppos)
+{
+	return -EINVAL;
+}
+
+static int agpioc_info_wrap(struct agp_file_private *priv, void __user *arg)
+{
+	struct agp_info userinfo;
+	struct agp_kern_info kerninfo;
+
+	agp_copy_info(agp_bridge, &kerninfo);
+
+	userinfo.version.major = kerninfo.version.major;
+	userinfo.version.minor = kerninfo.version.minor;
+	userinfo.bridge_id = kerninfo.device->vendor |
+	    (kerninfo.device->device << 16);
+	userinfo.agp_mode = kerninfo.mode;
+	userinfo.aper_base = kerninfo.aper_base;
+	userinfo.aper_size = kerninfo.aper_size;
+	userinfo.pg_total = userinfo.pg_system = kerninfo.max_memory;
+	userinfo.pg_used = kerninfo.current_memory;
+
+	if (copy_to_user(arg, &userinfo, sizeof(struct agp_info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int agpioc_acquire_wrap(struct agp_file_private *priv)
+{
+	struct agp_controller *controller;
+
+	DBG("");
+
+	if (!(test_bit(AGP_FF_ALLOW_CONTROLLER, &priv->access_flags)))
+		return -EPERM;
+
+	if (agp_fe.current_controller != NULL)
+		return -EBUSY;
+
+	if(!agp_bridge)
+		return -ENODEV;
+
+        if (atomic_read(&agp_bridge->agp_in_use))
+                return -EBUSY;
+
+	atomic_inc(&agp_bridge->agp_in_use);
+
+	agp_fe.backend_acquired = TRUE;
+
+	controller = agp_find_controller_by_pid(priv->my_pid);
+
+	if (controller != NULL) {
+		agp_controller_make_current(controller);
+	} else {
+		controller = agp_create_controller(priv->my_pid);
+
+		if (controller == NULL) {
+			agp_fe.backend_acquired = FALSE;
+			agp_backend_release(agp_bridge);
+			return -ENOMEM;
+		}
+		agp_insert_controller(controller);
+		agp_controller_make_current(controller);
+	}
+
+	set_bit(AGP_FF_IS_CONTROLLER, &priv->access_flags);
+	set_bit(AGP_FF_IS_VALID, &priv->access_flags);
+	return 0;
+}
+
+static int agpioc_release_wrap(struct agp_file_private *priv)
+{
+	DBG("");
+	agp_controller_release_current(agp_fe.current_controller, priv);
+	return 0;
+}
+
+static int agpioc_setup_wrap(struct agp_file_private *priv, void __user *arg)
+{
+	struct agp_setup mode;
+
+	DBG("");
+	if (copy_from_user(&mode, arg, sizeof(struct agp_setup)))
+		return -EFAULT;
+
+	agp_enable(agp_bridge, mode.agp_mode);
+	return 0;
+}
+
+static int agpioc_reserve_wrap(struct agp_file_private *priv, void __user *arg)
+{
+	struct agp_region reserve;
+	struct agp_client *client;
+	struct agp_file_private *client_priv;
+
+	DBG("");
+	if (copy_from_user(&reserve, arg, sizeof(struct agp_region)))
+		return -EFAULT;
+
+	if ((unsigned) reserve.seg_count >= ~0U/sizeof(struct agp_segment))
+		return -EFAULT;
+
+	client = agp_find_client_by_pid(reserve.pid);
+
+	if (reserve.seg_count == 0) {
+		/* remove a client */
+		client_priv = agp_find_private(reserve.pid);
+
+		if (client_priv != NULL) {
+			set_bit(AGP_FF_IS_CLIENT, &client_priv->access_flags);
+			set_bit(AGP_FF_IS_VALID, &client_priv->access_flags);
+		}
+		if (client == NULL) {
+			/* client is already removed */
+			return 0;
+		}
+		return agp_remove_client(reserve.pid);
+	} else {
+		struct agp_segment *segment;
+
+		if (reserve.seg_count >= 16384)
+			return -EINVAL;
+
+		segment = kmalloc((sizeof(struct agp_segment) * reserve.seg_count),
+				  GFP_KERNEL);
+
+		if (segment == NULL)
+			return -ENOMEM;
+
+		if (copy_from_user(segment, (void __user *) reserve.seg_list,
+				   sizeof(struct agp_segment) * reserve.seg_count)) {
+			kfree(segment);
+			return -EFAULT;
+		}
+		reserve.seg_list = segment;
+
+		if (client == NULL) {
+			/* Create the client and add the segment */
+			client = agp_create_client(reserve.pid);
+
+			if (client == NULL) {
+				kfree(segment);
+				return -ENOMEM;
+			}
+			client_priv = agp_find_private(reserve.pid);
+
+			if (client_priv != NULL) {
+				set_bit(AGP_FF_IS_CLIENT, &client_priv->access_flags);
+				set_bit(AGP_FF_IS_VALID, &client_priv->access_flags);
+			}
+		}
+		return agp_create_segment(client, &reserve);
+	}
+	/* Will never really happen */
+	return -EINVAL;
+}
+
+static int agpioc_protect_wrap(struct agp_file_private *priv)
+{
+	DBG("");
+	/* This function is not currently implemented */
+	return -EINVAL;
+}
+
+static int agpioc_allocate_wrap(struct agp_file_private *priv, void __user *arg)
+{
+	struct agp_memory *memory;
+	struct agp_allocate alloc;
+
+	DBG("");
+	if (copy_from_user(&alloc, arg, sizeof(struct agp_allocate)))
+		return -EFAULT;
+
+	memory = agp_allocate_memory_wrap(alloc.pg_count, alloc.type);
+
+	if (memory == NULL)
+		return -ENOMEM;
+
+	alloc.key = memory->key;
+	alloc.physical = memory->physical;
+
+	if (copy_to_user(arg, &alloc, sizeof(struct agp_allocate))) {
+		agp_free_memory_wrap(memory);
+		return -EFAULT;
+	}
+	return 0;
+}
+
+static int agpioc_deallocate_wrap(struct agp_file_private *priv, int arg)
+{
+	struct agp_memory *memory;
+
+	DBG("");
+	memory = agp_find_mem_by_key(arg);
+
+	if (memory == NULL)
+		return -EINVAL;
+
+	agp_free_memory_wrap(memory);
+	return 0;
+}
+
+static int agpioc_bind_wrap(struct agp_file_private *priv, void __user *arg)
+{
+	struct agp_bind bind_info;
+	struct agp_memory *memory;
+
+	DBG("");
+	if (copy_from_user(&bind_info, arg, sizeof(struct agp_bind)))
+		return -EFAULT;
+
+	memory = agp_find_mem_by_key(bind_info.key);
+
+	if (memory == NULL)
+		return -EINVAL;
+
+	return agp_bind_memory(memory, bind_info.pg_start);
+}
+
+static int agpioc_unbind_wrap(struct agp_file_private *priv, void __user *arg)
+{
+	struct agp_memory *memory;
+	struct agp_unbind unbind;
+
+	DBG("");
+	if (copy_from_user(&unbind, arg, sizeof(struct agp_unbind)))
+		return -EFAULT;
+
+	memory = agp_find_mem_by_key(unbind.key);
+
+	if (memory == NULL)
+		return -EINVAL;
+
+	return agp_unbind_memory(memory);
+}
+
+static int agp_ioctl(struct inode *inode, struct file *file,
+		     unsigned int cmd, unsigned long arg)
+{
+	struct agp_file_private *curr_priv = file->private_data;
+	int ret_val = -ENOTTY;
+
+	DBG("priv=%p, cmd=%x", curr_priv, cmd);
+	down(&(agp_fe.agp_mutex));
+
+	if ((agp_fe.current_controller == NULL) &&
+	    (cmd != AGPIOC_ACQUIRE)) {
+		ret_val = -EINVAL;
+		goto ioctl_out;
+	}
+	if ((agp_fe.backend_acquired != TRUE) &&
+	    (cmd != AGPIOC_ACQUIRE)) {
+		ret_val = -EBUSY;
+		goto ioctl_out;
+	}
+	if (cmd != AGPIOC_ACQUIRE) {
+		if (!(test_bit(AGP_FF_IS_CONTROLLER, &curr_priv->access_flags))) {
+			ret_val = -EPERM;
+			goto ioctl_out;
+		}
+		/* Use the original pid of the controller,
+		 * in case it's threaded */
+
+		if (agp_fe.current_controller->pid != curr_priv->my_pid) {
+			ret_val = -EBUSY;
+			goto ioctl_out;
+		}
+	}
+
+	switch (cmd) {
+	case AGPIOC_INFO:
+		ret_val = agpioc_info_wrap(curr_priv, (void __user *) arg);
+		break;
+
+	case AGPIOC_ACQUIRE:
+		ret_val = agpioc_acquire_wrap(curr_priv);
+		break;
+
+	case AGPIOC_RELEASE:
+		ret_val = agpioc_release_wrap(curr_priv);
+		break;
+
+	case AGPIOC_SETUP:
+		ret_val = agpioc_setup_wrap(curr_priv, (void __user *) arg);
+		break;
+
+	case AGPIOC_RESERVE:
+		ret_val = agpioc_reserve_wrap(curr_priv, (void __user *) arg);
+		break;
+
+	case AGPIOC_PROTECT:
+		ret_val = agpioc_protect_wrap(curr_priv);
+		break;
+
+	case AGPIOC_ALLOCATE:
+		ret_val = agpioc_allocate_wrap(curr_priv, (void __user *) arg);
+		break;
+
+	case AGPIOC_DEALLOCATE:
+		ret_val = agpioc_deallocate_wrap(curr_priv, (int) arg);
+		break;
+
+	case AGPIOC_BIND:
+		ret_val = agpioc_bind_wrap(curr_priv, (void __user *) arg);
+		break;
+
+	case AGPIOC_UNBIND:
+		ret_val = agpioc_unbind_wrap(curr_priv, (void __user *) arg);
+		break;
+	}
+
+ioctl_out:
+	DBG("ioctl returns %d\n", ret_val);
+	up(&(agp_fe.agp_mutex));
+	return ret_val;
+}
+
+static struct file_operations agp_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= agp_read,
+	.write		= agp_write,
+	.ioctl		= agp_ioctl,
+	.mmap		= agp_mmap,
+	.open		= agp_open,
+	.release	= agp_release,
+};
+
+static struct miscdevice agp_miscdev =
+{
+	.minor	= AGPGART_MINOR,
+	.name	= "agpgart",
+	.fops	= &agp_fops
+};
+
+int agp_frontend_initialize(void)
+{
+	memset(&agp_fe, 0, sizeof(struct agp_front_data));
+	sema_init(&(agp_fe.agp_mutex), 1);
+
+	if (misc_register(&agp_miscdev)) {
+		printk(KERN_ERR PFX "unable to get minor: %d\n", AGPGART_MINOR);
+		return -EIO;
+	}
+	return 0;
+}
+
+void agp_frontend_cleanup(void)
+{
+	misc_deregister(&agp_miscdev);
+}
diff --git a/drivers/char/agp/generic.c b/drivers/char/agp/generic.c
new file mode 100644
index 0000000..c321a92
--- /dev/null
+++ b/drivers/char/agp/generic.c
@@ -0,0 +1,1222 @@
+/*
+ * AGPGART driver.
+ * Copyright (C) 2004 Silicon Graphics, Inc.
+ * Copyright (C) 2002-2005 Dave Jones.
+ * Copyright (C) 1999 Jeff Hartmann.
+ * Copyright (C) 1999 Precision Insight, Inc.
+ * Copyright (C) 1999 Xi Graphics, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * JEFF HARTMANN, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * TODO:
+ * - Allocate more than order 0 pages to avoid too much linear map splitting.
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/miscdevice.h>
+#include <linux/pm.h>
+#include <linux/agp_backend.h>
+#include <linux/vmalloc.h>
+#include <linux/dma-mapping.h>
+#include <linux/mm.h>
+#include <asm/io.h>
+#include <asm/cacheflush.h>
+#include <asm/pgtable.h>
+#include "agp.h"
+
+__u32 *agp_gatt_table;
+int agp_memory_reserved;
+
+/*
+ * Needed by the Nforce GART driver for the time being. Would be
+ * nice to do this some other way instead of needing this export.
+ */
+EXPORT_SYMBOL_GPL(agp_memory_reserved);
+
+#if defined(CONFIG_X86)
+int map_page_into_agp(struct page *page)
+{
+	int i;
+	i = change_page_attr(page, 1, PAGE_KERNEL_NOCACHE);
+	global_flush_tlb();
+	return i;
+}
+EXPORT_SYMBOL_GPL(map_page_into_agp);
+
+int unmap_page_from_agp(struct page *page)
+{
+	int i;
+	i = change_page_attr(page, 1, PAGE_KERNEL);
+	global_flush_tlb();
+	return i;
+}
+EXPORT_SYMBOL_GPL(unmap_page_from_agp);
+#endif
+
+/*
+ * Generic routines for handling agp_memory structures -
+ * They use the basic page allocation routines to do the brunt of the work.
+ */
+
+void agp_free_key(int key)
+{
+	if (key < 0)
+		return;
+
+	if (key < MAXKEY)
+		clear_bit(key, agp_bridge->key_list);
+}
+EXPORT_SYMBOL(agp_free_key);
+
+
+static int agp_get_key(void)
+{
+	int bit;
+
+	bit = find_first_zero_bit(agp_bridge->key_list, MAXKEY);
+	if (bit < MAXKEY) {
+		set_bit(bit, agp_bridge->key_list);
+		return bit;
+	}
+	return -1;
+}
+
+
+struct agp_memory *agp_create_memory(int scratch_pages)
+{
+	struct agp_memory *new;
+
+	new = kmalloc(sizeof(struct agp_memory), GFP_KERNEL);
+
+	if (new == NULL)
+		return NULL;
+
+	memset(new, 0, sizeof(struct agp_memory));
+	new->key = agp_get_key();
+
+	if (new->key < 0) {
+		kfree(new);
+		return NULL;
+	}
+	new->memory = vmalloc(PAGE_SIZE * scratch_pages);
+
+	if (new->memory == NULL) {
+		agp_free_key(new->key);
+		kfree(new);
+		return NULL;
+	}
+	new->num_scratch_pages = scratch_pages;
+	return new;
+}
+EXPORT_SYMBOL(agp_create_memory);
+
+/**
+ *	agp_free_memory - free memory associated with an agp_memory pointer.
+ *
+ *	@curr:		agp_memory pointer to be freed.
+ *
+ *	It is the only function that can be called when the backend is not owned
+ *	by the caller.  (So it can free memory on client death.)
+ */
+void agp_free_memory(struct agp_memory *curr)
+{
+	size_t i;
+
+	if (curr == NULL)
+		return;
+
+	if (curr->is_bound == TRUE)
+		agp_unbind_memory(curr);
+
+	if (curr->type != 0) {
+		curr->bridge->driver->free_by_type(curr);
+		return;
+	}
+	if (curr->page_count != 0) {
+		for (i = 0; i < curr->page_count; i++) {
+			curr->bridge->driver->agp_destroy_page(phys_to_virt(curr->memory[i]));
+		}
+	}
+	agp_free_key(curr->key);
+	vfree(curr->memory);
+	kfree(curr);
+}
+EXPORT_SYMBOL(agp_free_memory);
+
+#define ENTRIES_PER_PAGE		(PAGE_SIZE / sizeof(unsigned long))
+
+/**
+ *	agp_allocate_memory  -  allocate a group of pages of a certain type.
+ *
+ *	@page_count:	size_t argument of the number of pages
+ *	@type:	u32 argument of the type of memory to be allocated.
+ *
+ *	Every agp bridge device will allow you to allocate AGP_NORMAL_MEMORY which
+ *	maps to physical ram.  Any other type is device dependent.
+ *
+ *	It returns NULL whenever memory is unavailable.
+ */
+struct agp_memory *agp_allocate_memory(struct agp_bridge_data *bridge,
+					size_t page_count, u32 type)
+{
+	int scratch_pages;
+	struct agp_memory *new;
+	size_t i;
+
+	if (!bridge)
+		return NULL;
+
+	if ((atomic_read(&bridge->current_memory_agp) + page_count) > bridge->max_memory_agp)
+		return NULL;
+
+	if (type != 0) {
+		new = bridge->driver->alloc_by_type(page_count, type);
+		if (new)
+			new->bridge = bridge;
+		return new;
+	}
+
+	scratch_pages = (page_count + ENTRIES_PER_PAGE - 1) / ENTRIES_PER_PAGE;
+
+	new = agp_create_memory(scratch_pages);
+
+	if (new == NULL)
+		return NULL;
+
+	for (i = 0; i < page_count; i++) {
+		void *addr = bridge->driver->agp_alloc_page(bridge);
+
+		if (addr == NULL) {
+			agp_free_memory(new);
+			return NULL;
+		}
+		new->memory[i] = virt_to_phys(addr);
+		new->page_count++;
+	}
+       new->bridge = bridge;
+
+	flush_agp_mappings();
+
+	return new;
+}
+EXPORT_SYMBOL(agp_allocate_memory);
+
+
+/* End - Generic routines for handling agp_memory structures */
+
+
+static int agp_return_size(void)
+{
+	int current_size;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+
+	switch (agp_bridge->driver->size_type) {
+	case U8_APER_SIZE:
+		current_size = A_SIZE_8(temp)->size;
+		break;
+	case U16_APER_SIZE:
+		current_size = A_SIZE_16(temp)->size;
+		break;
+	case U32_APER_SIZE:
+		current_size = A_SIZE_32(temp)->size;
+		break;
+	case LVL2_APER_SIZE:
+		current_size = A_SIZE_LVL2(temp)->size;
+		break;
+	case FIXED_APER_SIZE:
+		current_size = A_SIZE_FIX(temp)->size;
+		break;
+	default:
+		current_size = 0;
+		break;
+	}
+
+	current_size -= (agp_memory_reserved / (1024*1024));
+	if (current_size <0)
+		current_size = 0;
+	return current_size;
+}
+
+
+int agp_num_entries(void)
+{
+	int num_entries;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+
+	switch (agp_bridge->driver->size_type) {
+	case U8_APER_SIZE:
+		num_entries = A_SIZE_8(temp)->num_entries;
+		break;
+	case U16_APER_SIZE:
+		num_entries = A_SIZE_16(temp)->num_entries;
+		break;
+	case U32_APER_SIZE:
+		num_entries = A_SIZE_32(temp)->num_entries;
+		break;
+	case LVL2_APER_SIZE:
+		num_entries = A_SIZE_LVL2(temp)->num_entries;
+		break;
+	case FIXED_APER_SIZE:
+		num_entries = A_SIZE_FIX(temp)->num_entries;
+		break;
+	default:
+		num_entries = 0;
+		break;
+	}
+
+	num_entries -= agp_memory_reserved>>PAGE_SHIFT;
+	if (num_entries<0)
+		num_entries = 0;
+	return num_entries;
+}
+EXPORT_SYMBOL_GPL(agp_num_entries);
+
+
+static int check_bridge_mode(struct pci_dev *dev)
+{
+	u32 agp3;
+	u8 cap_ptr;
+
+	cap_ptr = pci_find_capability(dev, PCI_CAP_ID_AGP);
+	pci_read_config_dword(dev, cap_ptr+AGPSTAT, &agp3);
+	if (agp3 & AGPSTAT_MODE_3_0)
+		return 1;
+	return 0;
+}
+
+
+/**
+ *	agp_copy_info  -  copy bridge state information
+ *
+ *	@info:		agp_kern_info pointer.  The caller should insure that this pointer is valid. 
+ *
+ *	This function copies information about the agp bridge device and the state of
+ *	the agp backend into an agp_kern_info pointer.
+ */
+int agp_copy_info(struct agp_bridge_data *bridge, struct agp_kern_info *info)
+{
+	memset(info, 0, sizeof(struct agp_kern_info));
+	if (!bridge) {
+		info->chipset = NOT_SUPPORTED;
+		return -EIO;
+	}
+
+	info->version.major = bridge->version->major;
+	info->version.minor = bridge->version->minor;
+	info->chipset = SUPPORTED;
+	info->device = bridge->dev;
+	if (check_bridge_mode(bridge->dev))
+		info->mode = bridge->mode & ~AGP3_RESERVED_MASK;
+	else
+		info->mode = bridge->mode & ~AGP2_RESERVED_MASK;
+	info->mode = bridge->mode;
+	info->aper_base = bridge->gart_bus_addr;
+	info->aper_size = agp_return_size();
+	info->max_memory = bridge->max_memory_agp;
+	info->current_memory = atomic_read(&bridge->current_memory_agp);
+	info->cant_use_aperture = bridge->driver->cant_use_aperture;
+	info->vm_ops = bridge->vm_ops;
+	info->page_mask = ~0UL;
+	return 0;
+}
+EXPORT_SYMBOL(agp_copy_info);
+
+/* End - Routine to copy over information structure */
+
+/*
+ * Routines for handling swapping of agp_memory into the GATT -
+ * These routines take agp_memory and insert them into the GATT.
+ * They call device specific routines to actually write to the GATT.
+ */
+
+/**
+ *	agp_bind_memory  -  Bind an agp_memory structure into the GATT.
+ *
+ *	@curr:		agp_memory pointer
+ *	@pg_start:	an offset into the graphics aperture translation table
+ *
+ *	It returns -EINVAL if the pointer == NULL.
+ *	It returns -EBUSY if the area of the table requested is already in use.
+ */
+int agp_bind_memory(struct agp_memory *curr, off_t pg_start)
+{
+	int ret_val;
+
+	if (curr == NULL)
+		return -EINVAL;
+
+	if (curr->is_bound == TRUE) {
+		printk (KERN_INFO PFX "memory %p is already bound!\n", curr);
+		return -EINVAL;
+	}
+	if (curr->is_flushed == FALSE) {
+		curr->bridge->driver->cache_flush();
+		curr->is_flushed = TRUE;
+	}
+	ret_val = curr->bridge->driver->insert_memory(curr, pg_start, curr->type);
+
+	if (ret_val != 0)
+		return ret_val;
+
+	curr->is_bound = TRUE;
+	curr->pg_start = pg_start;
+	return 0;
+}
+EXPORT_SYMBOL(agp_bind_memory);
+
+
+/**
+ *	agp_unbind_memory  -  Removes an agp_memory structure from the GATT
+ *
+ * @curr:	agp_memory pointer to be removed from the GATT.
+ *
+ * It returns -EINVAL if this piece of agp_memory is not currently bound to
+ * the graphics aperture translation table or if the agp_memory pointer == NULL
+ */
+int agp_unbind_memory(struct agp_memory *curr)
+{
+	int ret_val;
+
+	if (curr == NULL)
+		return -EINVAL;
+
+	if (curr->is_bound != TRUE) {
+		printk (KERN_INFO PFX "memory %p was not bound!\n", curr);
+		return -EINVAL;
+	}
+
+	ret_val = curr->bridge->driver->remove_memory(curr, curr->pg_start, curr->type);
+
+	if (ret_val != 0)
+		return ret_val;
+
+	curr->is_bound = FALSE;
+	curr->pg_start = 0;
+	return 0;
+}
+EXPORT_SYMBOL(agp_unbind_memory);
+
+/* End - Routines for handling swapping of agp_memory into the GATT */
+
+
+/* Generic Agp routines - Start */
+static void agp_v2_parse_one(u32 *requested_mode, u32 *bridge_agpstat, u32 *vga_agpstat)
+{
+	u32 tmp;
+
+	if (*requested_mode & AGP2_RESERVED_MASK) {
+		printk (KERN_INFO PFX "reserved bits set in mode 0x%x. Fixed.\n", *requested_mode);
+		*requested_mode &= ~AGP2_RESERVED_MASK;
+	}
+
+	/* Check the speed bits make sense. Only one should be set. */
+	tmp = *requested_mode & 7;
+	switch (tmp) {
+		case 0:
+			printk (KERN_INFO PFX "%s tried to set rate=x0. Setting to x1 mode.\n", current->comm);
+			*requested_mode |= AGPSTAT2_1X;
+			break;
+		case 1:
+		case 2:
+			break;
+		case 3:
+			*requested_mode &= ~(AGPSTAT2_1X);	/* rate=2 */
+			break;
+		case 4:
+			break;
+		case 5:
+		case 6:
+		case 7:
+			*requested_mode &= ~(AGPSTAT2_1X|AGPSTAT2_2X); /* rate=4*/
+			break;
+	}
+
+	/* disable SBA if it's not supported */
+	if (!((*bridge_agpstat & AGPSTAT_SBA) && (*vga_agpstat & AGPSTAT_SBA) && (*requested_mode & AGPSTAT_SBA)))
+		*bridge_agpstat &= ~AGPSTAT_SBA;
+
+	/* Set rate */
+	if (!((*bridge_agpstat & AGPSTAT2_4X) && (*vga_agpstat & AGPSTAT2_4X) && (*requested_mode & AGPSTAT2_4X)))
+		*bridge_agpstat &= ~AGPSTAT2_4X;
+
+	if (!((*bridge_agpstat & AGPSTAT2_2X) && (*vga_agpstat & AGPSTAT2_2X) && (*requested_mode & AGPSTAT2_2X)))
+		*bridge_agpstat &= ~AGPSTAT2_2X;
+
+	if (!((*bridge_agpstat & AGPSTAT2_1X) && (*vga_agpstat & AGPSTAT2_1X) && (*requested_mode & AGPSTAT2_1X)))
+		*bridge_agpstat &= ~AGPSTAT2_1X;
+
+	/* Now we know what mode it should be, clear out the unwanted bits. */
+	if (*bridge_agpstat & AGPSTAT2_4X)
+		*bridge_agpstat &= ~(AGPSTAT2_1X | AGPSTAT2_2X);	/* 4X */
+
+	if (*bridge_agpstat & AGPSTAT2_2X)
+		*bridge_agpstat &= ~(AGPSTAT2_1X | AGPSTAT2_4X);	/* 2X */
+
+	if (*bridge_agpstat & AGPSTAT2_1X)
+		*bridge_agpstat &= ~(AGPSTAT2_2X | AGPSTAT2_4X);	/* 1X */
+
+	/* Apply any errata. */
+	if (agp_bridge->flags & AGP_ERRATA_FASTWRITES)
+		*bridge_agpstat &= ~AGPSTAT_FW;
+
+	if (agp_bridge->flags & AGP_ERRATA_SBA)
+		*bridge_agpstat &= ~AGPSTAT_SBA;
+
+	if (agp_bridge->flags & AGP_ERRATA_1X) {
+		*bridge_agpstat &= ~(AGPSTAT2_2X | AGPSTAT2_4X);
+		*bridge_agpstat |= AGPSTAT2_1X;
+	}
+
+	/* If we've dropped down to 1X, disable fast writes. */
+	if (*bridge_agpstat & AGPSTAT2_1X)
+		*bridge_agpstat &= ~AGPSTAT_FW;
+}
+
+/*
+ * requested_mode = Mode requested by (typically) X.
+ * bridge_agpstat = PCI_AGP_STATUS from agp bridge.
+ * vga_agpstat = PCI_AGP_STATUS from graphic card.
+ */
+static void agp_v3_parse_one(u32 *requested_mode, u32 *bridge_agpstat, u32 *vga_agpstat)
+{
+	u32 origbridge=*bridge_agpstat, origvga=*vga_agpstat;
+	u32 tmp;
+
+	if (*requested_mode & AGP3_RESERVED_MASK) {
+		printk (KERN_INFO PFX "reserved bits set in mode 0x%x. Fixed.\n", *requested_mode);
+		*requested_mode &= ~AGP3_RESERVED_MASK;
+	}
+
+	/* Check the speed bits make sense. */
+	tmp = *requested_mode & 7;
+	if (tmp == 0) {
+		printk (KERN_INFO PFX "%s tried to set rate=x0. Setting to AGP3 x4 mode.\n", current->comm);
+		*requested_mode |= AGPSTAT3_4X;
+	}
+	if (tmp >= 3) {
+		printk (KERN_INFO PFX "%s tried to set rate=x%d. Setting to AGP3 x8 mode.\n", current->comm, tmp * 4);
+		*requested_mode = (*requested_mode & ~7) | AGPSTAT3_8X;
+	}
+
+	/* ARQSZ - Set the value to the maximum one.
+	 * Don't allow the mode register to override values. */
+	*bridge_agpstat = ((*bridge_agpstat & ~AGPSTAT_ARQSZ) |
+		max_t(u32,(*bridge_agpstat & AGPSTAT_ARQSZ),(*vga_agpstat & AGPSTAT_ARQSZ)));
+
+	/* Calibration cycle.
+	 * Don't allow the mode register to override values. */
+	*bridge_agpstat = ((*bridge_agpstat & ~AGPSTAT_CAL_MASK) |
+		min_t(u32,(*bridge_agpstat & AGPSTAT_CAL_MASK),(*vga_agpstat & AGPSTAT_CAL_MASK)));
+
+	/* SBA *must* be supported for AGP v3 */
+	*bridge_agpstat |= AGPSTAT_SBA;
+
+	/*
+	 * Set speed.
+	 * Check for invalid speeds. This can happen when applications
+	 * written before the AGP 3.0 standard pass AGP2.x modes to AGP3 hardware
+	 */
+	if (*requested_mode & AGPSTAT_MODE_3_0) {
+		/*
+		 * Caller hasn't a clue what it is doing. Bridge is in 3.0 mode,
+		 * have been passed a 3.0 mode, but with 2.x speed bits set.
+		 * AGP2.x 4x -> AGP3.0 4x.
+		 */
+		if (*requested_mode & AGPSTAT2_4X) {
+			printk (KERN_INFO PFX "%s passes broken AGP3 flags (%x). Fixed.\n",
+						current->comm, *requested_mode);
+			*requested_mode &= ~AGPSTAT2_4X;
+			*requested_mode |= AGPSTAT3_4X;
+		}
+	} else {
+		/*
+		 * The caller doesn't know what they are doing. We are in 3.0 mode,
+		 * but have been passed an AGP 2.x mode.
+		 * Convert AGP 1x,2x,4x -> AGP 3.0 4x.
+		 */
+		printk (KERN_INFO PFX "%s passes broken AGP2 flags (%x) in AGP3 mode. Fixed.\n",
+					current->comm, *requested_mode);
+		*requested_mode &= ~(AGPSTAT2_4X | AGPSTAT2_2X | AGPSTAT2_1X);
+		*requested_mode |= AGPSTAT3_4X;
+	}
+
+	if (*requested_mode & AGPSTAT3_8X) {
+		if (!(*bridge_agpstat & AGPSTAT3_8X)) {
+			*bridge_agpstat &= ~(AGPSTAT3_8X | AGPSTAT3_RSVD);
+			*bridge_agpstat |= AGPSTAT3_4X;
+			printk ("%s requested AGPx8 but bridge not capable.\n", current->comm);
+			return;
+		}
+		if (!(*vga_agpstat & AGPSTAT3_8X)) {
+			*bridge_agpstat &= ~(AGPSTAT3_8X | AGPSTAT3_RSVD);
+			*bridge_agpstat |= AGPSTAT3_4X;
+			printk ("%s requested AGPx8 but graphic card not capable.\n", current->comm);
+			return;
+		}
+		/* All set, bridge & device can do AGP x8*/
+		*bridge_agpstat &= ~(AGPSTAT3_4X | AGPSTAT3_RSVD);
+		goto done;
+
+	} else {
+
+		/*
+		 * If we didn't specify AGPx8, we can only do x4.
+		 * If the hardware can't do x4, we're up shit creek, and never
+		 *  should have got this far.
+		 */
+		*bridge_agpstat &= ~(AGPSTAT3_8X | AGPSTAT3_RSVD);
+		if ((*bridge_agpstat & AGPSTAT3_4X) && (*vga_agpstat & AGPSTAT3_4X))
+			*bridge_agpstat |= AGPSTAT3_4X;
+		else {
+			printk (KERN_INFO PFX "Badness. Don't know which AGP mode to set. "
+							"[bridge_agpstat:%x vga_agpstat:%x fell back to:- bridge_agpstat:%x vga_agpstat:%x]\n",
+							origbridge, origvga, *bridge_agpstat, *vga_agpstat);
+			if (!(*bridge_agpstat & AGPSTAT3_4X))
+				printk (KERN_INFO PFX "Bridge couldn't do AGP x4.\n");
+			if (!(*vga_agpstat & AGPSTAT3_4X))
+				printk (KERN_INFO PFX "Graphic card couldn't do AGP x4.\n");
+			return;
+		}
+	}
+
+done:
+	/* Apply any errata. */
+	if (agp_bridge->flags & AGP_ERRATA_FASTWRITES)
+		*bridge_agpstat &= ~AGPSTAT_FW;
+
+	if (agp_bridge->flags & AGP_ERRATA_SBA)
+		*bridge_agpstat &= ~AGPSTAT_SBA;
+
+	if (agp_bridge->flags & AGP_ERRATA_1X) {
+		*bridge_agpstat &= ~(AGPSTAT2_2X | AGPSTAT2_4X);
+		*bridge_agpstat |= AGPSTAT2_1X;
+	}
+}
+
+
+/**
+ * agp_collect_device_status - determine correct agp_cmd from various agp_stat's
+ * @bridge: an agp_bridge_data struct allocated for the AGP host bridge.
+ * @requested_mode: requested agp_stat from userspace (Typically from X)
+ * @bridge_agpstat: current agp_stat from AGP bridge.
+ *
+ * This function will hunt for an AGP graphics card, and try to match
+ * the requested mode to the capabilities of both the bridge and the card.
+ */
+u32 agp_collect_device_status(struct agp_bridge_data *bridge, u32 requested_mode, u32 bridge_agpstat)
+{
+	struct pci_dev *device = NULL;
+	u32 vga_agpstat;
+	u8 cap_ptr;
+
+	for (;;) {
+		device = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, device);
+		if (!device) {
+			printk (KERN_INFO PFX "Couldn't find an AGP VGA controller.\n");
+			return 0;
+		}
+		cap_ptr = pci_find_capability(device, PCI_CAP_ID_AGP);
+		if (cap_ptr)
+			break;
+	}
+
+	/*
+	 * Ok, here we have a AGP device. Disable impossible
+	 * settings, and adjust the readqueue to the minimum.
+	 */
+	pci_read_config_dword(device, cap_ptr+PCI_AGP_STATUS, &vga_agpstat);
+
+	/* adjust RQ depth */
+	bridge_agpstat = ((bridge_agpstat & ~AGPSTAT_RQ_DEPTH) |
+	     min_t(u32, (requested_mode & AGPSTAT_RQ_DEPTH),
+		 min_t(u32, (bridge_agpstat & AGPSTAT_RQ_DEPTH), (vga_agpstat & AGPSTAT_RQ_DEPTH))));
+
+	/* disable FW if it's not supported */
+	if (!((bridge_agpstat & AGPSTAT_FW) &&
+		 (vga_agpstat & AGPSTAT_FW) &&
+		 (requested_mode & AGPSTAT_FW)))
+		bridge_agpstat &= ~AGPSTAT_FW;
+
+	/* Check to see if we are operating in 3.0 mode */
+	if (check_bridge_mode(agp_bridge->dev))
+		agp_v3_parse_one(&requested_mode, &bridge_agpstat, &vga_agpstat);
+	else
+		agp_v2_parse_one(&requested_mode, &bridge_agpstat, &vga_agpstat);
+
+	pci_dev_put(device);
+	return bridge_agpstat;
+}
+EXPORT_SYMBOL(agp_collect_device_status);
+
+
+void agp_device_command(u32 bridge_agpstat, int agp_v3)
+{
+	struct pci_dev *device = NULL;
+	int mode;
+
+	mode = bridge_agpstat & 0x7;
+	if (agp_v3)
+		mode *= 4;
+
+	for_each_pci_dev(device) {
+		u8 agp = pci_find_capability(device, PCI_CAP_ID_AGP);
+		if (!agp)
+			continue;
+
+		printk(KERN_INFO PFX "Putting AGP V%d device at %s into %dx mode\n",
+				agp_v3 ? 3 : 2, pci_name(device), mode);
+		pci_write_config_dword(device, agp + PCI_AGP_COMMAND, bridge_agpstat);
+	}
+}
+EXPORT_SYMBOL(agp_device_command);
+
+
+void get_agp_version(struct agp_bridge_data *bridge)
+{
+	u32 ncapid;
+
+	/* Exit early if already set by errata workarounds. */
+	if (bridge->major_version != 0)
+		return;
+
+	pci_read_config_dword(bridge->dev, bridge->capndx, &ncapid);
+	bridge->major_version = (ncapid >> AGP_MAJOR_VERSION_SHIFT) & 0xf;
+	bridge->minor_version = (ncapid >> AGP_MINOR_VERSION_SHIFT) & 0xf;
+}
+EXPORT_SYMBOL(get_agp_version);
+
+
+void agp_generic_enable(struct agp_bridge_data *bridge, u32 requested_mode)
+{
+	u32 bridge_agpstat, temp;
+
+	get_agp_version(agp_bridge);
+
+	printk(KERN_INFO PFX "Found an AGP %d.%d compliant device at %s.\n",
+				agp_bridge->major_version,
+				agp_bridge->minor_version,
+				pci_name(agp_bridge->dev));
+
+	pci_read_config_dword(agp_bridge->dev,
+		      agp_bridge->capndx + PCI_AGP_STATUS, &bridge_agpstat);
+
+	bridge_agpstat = agp_collect_device_status(agp_bridge, requested_mode, bridge_agpstat);
+	if (bridge_agpstat == 0)
+		/* Something bad happened. FIXME: Return error code? */
+		return;
+
+	bridge_agpstat |= AGPSTAT_AGP_ENABLE;
+
+	/* Do AGP version specific frobbing. */
+	if (bridge->major_version >= 3) {
+		if (check_bridge_mode(bridge->dev)) {
+			/* If we have 3.5, we can do the isoch stuff. */
+			if (bridge->minor_version >= 5)
+				agp_3_5_enable(bridge);
+			agp_device_command(bridge_agpstat, TRUE);
+			return;
+		} else {
+		    /* Disable calibration cycle in RX91<1> when not in AGP3.0 mode of operation.*/
+		    bridge_agpstat &= ~(7<<10) ;
+		    pci_read_config_dword(bridge->dev,
+					bridge->capndx+AGPCTRL, &temp);
+		    temp |= (1<<9);
+		    pci_write_config_dword(bridge->dev,
+					bridge->capndx+AGPCTRL, temp);
+
+		    printk (KERN_INFO PFX "Device is in legacy mode,"
+				" falling back to 2.x\n");
+		}
+	}
+
+	/* AGP v<3 */
+	agp_device_command(bridge_agpstat, FALSE);
+}
+EXPORT_SYMBOL(agp_generic_enable);
+
+
+int agp_generic_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	char *table;
+	char *table_end;
+	int size;
+	int page_order;
+	int num_entries;
+	int i;
+	void *temp;
+	struct page *page;
+
+	/* The generic routines can't handle 2 level gatt's */
+	if (bridge->driver->size_type == LVL2_APER_SIZE)
+		return -EINVAL;
+
+	table = NULL;
+	i = bridge->aperture_size_idx;
+	temp = bridge->current_size;
+	size = page_order = num_entries = 0;
+
+	if (bridge->driver->size_type != FIXED_APER_SIZE) {
+		do {
+			switch (bridge->driver->size_type) {
+			case U8_APER_SIZE:
+				size = A_SIZE_8(temp)->size;
+				page_order =
+				    A_SIZE_8(temp)->page_order;
+				num_entries =
+				    A_SIZE_8(temp)->num_entries;
+				break;
+			case U16_APER_SIZE:
+				size = A_SIZE_16(temp)->size;
+				page_order = A_SIZE_16(temp)->page_order;
+				num_entries = A_SIZE_16(temp)->num_entries;
+				break;
+			case U32_APER_SIZE:
+				size = A_SIZE_32(temp)->size;
+				page_order = A_SIZE_32(temp)->page_order;
+				num_entries = A_SIZE_32(temp)->num_entries;
+				break;
+				/* This case will never really happen. */
+			case FIXED_APER_SIZE:
+			case LVL2_APER_SIZE:
+			default:
+				size = page_order = num_entries = 0;
+				break;
+			}
+
+			table = (char *) __get_free_pages(GFP_KERNEL,
+							  page_order);
+
+			if (table == NULL) {
+				i++;
+				switch (bridge->driver->size_type) {
+				case U8_APER_SIZE:
+					bridge->current_size = A_IDX8(bridge);
+					break;
+				case U16_APER_SIZE:
+					bridge->current_size = A_IDX16(bridge);
+					break;
+				case U32_APER_SIZE:
+					bridge->current_size = A_IDX32(bridge);
+					break;
+					/* This case will never really happen. */
+				case FIXED_APER_SIZE:
+				case LVL2_APER_SIZE:
+				default:
+					bridge->current_size =
+					    bridge->current_size;
+					break;
+				}
+				temp = bridge->current_size;
+			} else {
+				bridge->aperture_size_idx = i;
+			}
+		} while (!table && (i < bridge->driver->num_aperture_sizes));
+	} else {
+		size = ((struct aper_size_info_fixed *) temp)->size;
+		page_order = ((struct aper_size_info_fixed *) temp)->page_order;
+		num_entries = ((struct aper_size_info_fixed *) temp)->num_entries;
+		table = (char *) __get_free_pages(GFP_KERNEL, page_order);
+	}
+
+	if (table == NULL)
+		return -ENOMEM;
+
+	table_end = table + ((PAGE_SIZE * (1 << page_order)) - 1);
+
+	for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+		SetPageReserved(page);
+
+	bridge->gatt_table_real = (u32 *) table;
+	agp_gatt_table = (void *)table;
+
+	bridge->driver->cache_flush();
+	bridge->gatt_table = ioremap_nocache(virt_to_phys(table),
+					(PAGE_SIZE * (1 << page_order)));
+	bridge->driver->cache_flush();
+
+	if (bridge->gatt_table == NULL) {
+		for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+			ClearPageReserved(page);
+
+		free_pages((unsigned long) table, page_order);
+
+		return -ENOMEM;
+	}
+	bridge->gatt_bus_addr = virt_to_phys(bridge->gatt_table_real);
+
+	/* AK: bogus, should encode addresses > 4GB */
+	for (i = 0; i < num_entries; i++) {
+		writel(bridge->scratch_page, bridge->gatt_table+i);
+		readl(bridge->gatt_table+i);	/* PCI Posting. */
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(agp_generic_create_gatt_table);
+
+int agp_generic_free_gatt_table(struct agp_bridge_data *bridge)
+{
+	int page_order;
+	char *table, *table_end;
+	void *temp;
+	struct page *page;
+
+	temp = bridge->current_size;
+
+	switch (bridge->driver->size_type) {
+	case U8_APER_SIZE:
+		page_order = A_SIZE_8(temp)->page_order;
+		break;
+	case U16_APER_SIZE:
+		page_order = A_SIZE_16(temp)->page_order;
+		break;
+	case U32_APER_SIZE:
+		page_order = A_SIZE_32(temp)->page_order;
+		break;
+	case FIXED_APER_SIZE:
+		page_order = A_SIZE_FIX(temp)->page_order;
+		break;
+	case LVL2_APER_SIZE:
+		/* The generic routines can't deal with 2 level gatt's */
+		return -EINVAL;
+		break;
+	default:
+		page_order = 0;
+		break;
+	}
+
+	/* Do not worry about freeing memory, because if this is
+	 * called, then all agp memory is deallocated and removed
+	 * from the table. */
+
+	iounmap(bridge->gatt_table);
+	table = (char *) bridge->gatt_table_real;
+	table_end = table + ((PAGE_SIZE * (1 << page_order)) - 1);
+
+	for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+		ClearPageReserved(page);
+
+	free_pages((unsigned long) bridge->gatt_table_real, page_order);
+
+	agp_gatt_table = NULL;
+	bridge->gatt_table = NULL;
+	bridge->gatt_table_real = NULL;
+	bridge->gatt_bus_addr = 0;
+
+	return 0;
+}
+EXPORT_SYMBOL(agp_generic_free_gatt_table);
+
+
+int agp_generic_insert_memory(struct agp_memory * mem, off_t pg_start, int type)
+{
+	int num_entries;
+	size_t i;
+	off_t j;
+	void *temp;
+	struct agp_bridge_data *bridge;
+
+	bridge = mem->bridge;
+	if (!bridge)
+		return -EINVAL;
+
+	temp = bridge->current_size;
+
+	switch (bridge->driver->size_type) {
+	case U8_APER_SIZE:
+		num_entries = A_SIZE_8(temp)->num_entries;
+		break;
+	case U16_APER_SIZE:
+		num_entries = A_SIZE_16(temp)->num_entries;
+		break;
+	case U32_APER_SIZE:
+		num_entries = A_SIZE_32(temp)->num_entries;
+		break;
+	case FIXED_APER_SIZE:
+		num_entries = A_SIZE_FIX(temp)->num_entries;
+		break;
+	case LVL2_APER_SIZE:
+		/* The generic routines can't deal with 2 level gatt's */
+		return -EINVAL;
+		break;
+	default:
+		num_entries = 0;
+		break;
+	}
+
+	num_entries -= agp_memory_reserved/PAGE_SIZE;
+	if (num_entries < 0) num_entries = 0;
+
+	if (type != 0 || mem->type != 0) {
+		/* The generic routines know nothing of memory types */
+		return -EINVAL;
+	}
+
+	/* AK: could wrap */
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	j = pg_start;
+
+	while (j < (pg_start + mem->page_count)) {
+		if (!PGE_EMPTY(bridge, readl(bridge->gatt_table+j)))
+			return -EBUSY;
+		j++;
+	}
+
+	if (mem->is_flushed == FALSE) {
+		bridge->driver->cache_flush();
+		mem->is_flushed = TRUE;
+	}
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		writel(bridge->driver->mask_memory(bridge, mem->memory[i], mem->type), bridge->gatt_table+j);
+		readl(bridge->gatt_table+j);	/* PCI Posting. */
+	}
+
+	bridge->driver->tlb_flush(mem);
+	return 0;
+}
+EXPORT_SYMBOL(agp_generic_insert_memory);
+
+
+int agp_generic_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+	size_t i;
+	struct agp_bridge_data *bridge;
+
+	bridge = mem->bridge;
+	if (!bridge)
+		return -EINVAL;
+
+	if (type != 0 || mem->type != 0) {
+		/* The generic routines know nothing of memory types */
+		return -EINVAL;
+	}
+
+	/* AK: bogus, should encode addresses > 4GB */
+	for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+		writel(bridge->scratch_page, bridge->gatt_table+i);
+		readl(bridge->gatt_table+i);	/* PCI Posting. */
+	}
+
+	global_cache_flush();
+	bridge->driver->tlb_flush(mem);
+	return 0;
+}
+EXPORT_SYMBOL(agp_generic_remove_memory);
+
+
+struct agp_memory *agp_generic_alloc_by_type(size_t page_count, int type)
+{
+	return NULL;
+}
+EXPORT_SYMBOL(agp_generic_alloc_by_type);
+
+
+void agp_generic_free_by_type(struct agp_memory *curr)
+{
+	vfree(curr->memory);
+	agp_free_key(curr->key);
+	kfree(curr);
+}
+EXPORT_SYMBOL(agp_generic_free_by_type);
+
+
+/*
+ * Basic Page Allocation Routines -
+ * These routines handle page allocation and by default they reserve the allocated
+ * memory.  They also handle incrementing the current_memory_agp value, Which is checked
+ * against a maximum value.
+ */
+
+void *agp_generic_alloc_page(struct agp_bridge_data *bridge)
+{
+	struct page * page;
+
+	page = alloc_page(GFP_KERNEL);
+	if (page == NULL)
+		return NULL;
+
+	map_page_into_agp(page);
+
+	get_page(page);
+	SetPageLocked(page);
+	atomic_inc(&agp_bridge->current_memory_agp);
+	return page_address(page);
+}
+EXPORT_SYMBOL(agp_generic_alloc_page);
+
+
+void agp_generic_destroy_page(void *addr)
+{
+	struct page *page;
+
+	if (addr == NULL)
+		return;
+
+	page = virt_to_page(addr);
+	unmap_page_from_agp(page);
+	put_page(page);
+	unlock_page(page);
+	free_page((unsigned long)addr);
+	atomic_dec(&agp_bridge->current_memory_agp);
+}
+EXPORT_SYMBOL(agp_generic_destroy_page);
+
+/* End Basic Page Allocation Routines */
+
+
+/**
+ * agp_enable  -  initialise the agp point-to-point connection.
+ *
+ * @mode:	agp mode register value to configure with.
+ */
+void agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+	if (!bridge)
+		return;
+	bridge->driver->agp_enable(bridge, mode);
+}
+EXPORT_SYMBOL(agp_enable);
+
+/* When we remove the global variable agp_bridge from all drivers
+ * then agp_alloc_bridge and agp_generic_find_bridge need to be updated
+ */
+
+struct agp_bridge_data *agp_generic_find_bridge(struct pci_dev *pdev)
+{
+	if (list_empty(&agp_bridges))
+		return NULL;
+
+	return agp_bridge;
+}
+
+static void ipi_handler(void *null)
+{
+	flush_agp_cache();
+}
+
+void global_cache_flush(void)
+{
+	if (on_each_cpu(ipi_handler, NULL, 1, 1) != 0)
+		panic(PFX "timed out waiting for the other CPUs!\n");
+}
+EXPORT_SYMBOL(global_cache_flush);
+
+unsigned long agp_generic_mask_memory(struct agp_bridge_data *bridge,
+	unsigned long addr, int type)
+{
+	/* memory type is ignored in the generic routine */
+	if (bridge->driver->masks)
+		return addr | bridge->driver->masks[0].mask;
+	else
+		return addr;
+}
+EXPORT_SYMBOL(agp_generic_mask_memory);
+
+/*
+ * These functions are implemented according to the AGPv3 spec,
+ * which covers implementation details that had previously been
+ * left open.
+ */
+
+int agp3_generic_fetch_size(void)
+{
+	u16 temp_size;
+	int i;
+	struct aper_size_info_16 *values;
+
+	pci_read_config_word(agp_bridge->dev, agp_bridge->capndx+AGPAPSIZE, &temp_size);
+	values = A_SIZE_16(agp_bridge->driver->aperture_sizes);
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp_size == values[i].size_value) {
+			agp_bridge->previous_size =
+				agp_bridge->current_size = (void *) (values + i);
+
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL(agp3_generic_fetch_size);
+
+void agp3_generic_tlbflush(struct agp_memory *mem)
+{
+	u32 ctrl;
+	pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, &ctrl);
+	pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, ctrl & ~AGPCTRL_GTLBEN);
+	pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, ctrl);
+}
+EXPORT_SYMBOL(agp3_generic_tlbflush);
+
+int agp3_generic_configure(void)
+{
+	u32 temp;
+	struct aper_size_info_16 *current_size;
+
+	current_size = A_SIZE_16(agp_bridge->current_size);
+
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* set aperture size */
+	pci_write_config_word(agp_bridge->dev, agp_bridge->capndx+AGPAPSIZE, current_size->size_value);
+	/* set gart pointer */
+	pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPGARTLO, agp_bridge->gatt_bus_addr);
+	/* enable aperture and GTLB */
+	pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, &temp);
+	pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, temp | AGPCTRL_APERENB | AGPCTRL_GTLBEN);
+	return 0;
+}
+EXPORT_SYMBOL(agp3_generic_configure);
+
+void agp3_generic_cleanup(void)
+{
+	u32 ctrl;
+	pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, &ctrl);
+	pci_write_config_dword(agp_bridge->dev, agp_bridge->capndx+AGPCTRL, ctrl & ~AGPCTRL_APERENB);
+}
+EXPORT_SYMBOL(agp3_generic_cleanup);
+
+struct aper_size_info_16 agp3_generic_sizes[AGP_GENERIC_SIZES_ENTRIES] =
+{
+	{4096, 1048576, 10,0x000},
+	{2048,  524288, 9, 0x800},
+	{1024,  262144, 8, 0xc00},
+	{ 512,  131072, 7, 0xe00},
+	{ 256,   65536, 6, 0xf00},
+	{ 128,   32768, 5, 0xf20},
+	{  64,   16384, 4, 0xf30},
+	{  32,    8192, 3, 0xf38},
+	{  16,    4096, 2, 0xf3c},
+	{   8,    2048, 1, 0xf3e},
+	{   4,    1024, 0, 0xf3f}
+};
+EXPORT_SYMBOL(agp3_generic_sizes);
+
diff --git a/drivers/char/agp/hp-agp.c b/drivers/char/agp/hp-agp.c
new file mode 100644
index 0000000..6052bfa
--- /dev/null
+++ b/drivers/char/agp/hp-agp.c
@@ -0,0 +1,552 @@
+/*
+ * HP zx1 AGPGART routines.
+ *
+ * (c) Copyright 2002, 2003 Hewlett-Packard Development Company, L.P.
+ *	Bjorn Helgaas <bjorn.helgaas@hp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+
+#include <asm/acpi-ext.h>
+
+#include "agp.h"
+
+#ifndef log2
+#define log2(x)		ffz(~(x))
+#endif
+
+#define HP_ZX1_IOC_OFFSET	0x1000  /* ACPI reports SBA, we want IOC */
+
+/* HP ZX1 IOC registers */
+#define HP_ZX1_IBASE		0x300
+#define HP_ZX1_IMASK		0x308
+#define HP_ZX1_PCOM		0x310
+#define HP_ZX1_TCNFG		0x318
+#define HP_ZX1_PDIR_BASE	0x320
+
+#define HP_ZX1_IOVA_BASE	GB(1UL)
+#define HP_ZX1_IOVA_SIZE	GB(1UL)
+#define HP_ZX1_GART_SIZE	(HP_ZX1_IOVA_SIZE / 2)
+#define HP_ZX1_SBA_IOMMU_COOKIE	0x0000badbadc0ffeeUL
+
+#define HP_ZX1_PDIR_VALID_BIT	0x8000000000000000UL
+#define HP_ZX1_IOVA_TO_PDIR(va)	((va - hp_private.iova_base) >> hp_private.io_tlb_shift)
+
+#define AGP8X_MODE_BIT		3
+#define AGP8X_MODE		(1 << AGP8X_MODE_BIT)
+
+/* AGP bridge need not be PCI device, but DRM thinks it is. */
+static struct pci_dev fake_bridge_dev;
+
+static int hp_zx1_gart_found;
+
+static struct aper_size_info_fixed hp_zx1_sizes[] =
+{
+	{0, 0, 0},		/* filled in by hp_zx1_fetch_size() */
+};
+
+static struct gatt_mask hp_zx1_masks[] =
+{
+	{.mask = HP_ZX1_PDIR_VALID_BIT, .type = 0}
+};
+
+static struct _hp_private {
+	volatile u8 __iomem *ioc_regs;
+	volatile u8 __iomem *lba_regs;
+	int lba_cap_offset;
+	u64 *io_pdir;		// PDIR for entire IOVA
+	u64 *gatt;		// PDIR just for GART (subset of above)
+	u64 gatt_entries;
+	u64 iova_base;
+	u64 gart_base;
+	u64 gart_size;
+	u64 io_pdir_size;
+	int io_pdir_owner;	// do we own it, or share it with sba_iommu?
+	int io_page_size;
+	int io_tlb_shift;
+	int io_tlb_ps;		// IOC ps config
+	int io_pages_per_kpage;
+} hp_private;
+
+static int __init hp_zx1_ioc_shared(void)
+{
+	struct _hp_private *hp = &hp_private;
+
+	printk(KERN_INFO PFX "HP ZX1 IOC: IOPDIR shared with sba_iommu\n");
+
+	/*
+	 * IOC already configured by sba_iommu module; just use
+	 * its setup.  We assume:
+	 * 	- IOVA space is 1Gb in size
+	 * 	- first 512Mb is IOMMU, second 512Mb is GART
+	 */
+	hp->io_tlb_ps = readq(hp->ioc_regs+HP_ZX1_TCNFG);
+	switch (hp->io_tlb_ps) {
+		case 0: hp->io_tlb_shift = 12; break;
+		case 1: hp->io_tlb_shift = 13; break;
+		case 2: hp->io_tlb_shift = 14; break;
+		case 3: hp->io_tlb_shift = 16; break;
+		default:
+			printk(KERN_ERR PFX "Invalid IOTLB page size "
+			       "configuration 0x%x\n", hp->io_tlb_ps);
+			hp->gatt = NULL;
+			hp->gatt_entries = 0;
+			return -ENODEV;
+	}
+	hp->io_page_size = 1 << hp->io_tlb_shift;
+	hp->io_pages_per_kpage = PAGE_SIZE / hp->io_page_size;
+
+	hp->iova_base = readq(hp->ioc_regs+HP_ZX1_IBASE) & ~0x1;
+	hp->gart_base = hp->iova_base + HP_ZX1_IOVA_SIZE - HP_ZX1_GART_SIZE;
+
+	hp->gart_size = HP_ZX1_GART_SIZE;
+	hp->gatt_entries = hp->gart_size / hp->io_page_size;
+
+	hp->io_pdir = phys_to_virt(readq(hp->ioc_regs+HP_ZX1_PDIR_BASE));
+	hp->gatt = &hp->io_pdir[HP_ZX1_IOVA_TO_PDIR(hp->gart_base)];
+
+	if (hp->gatt[0] != HP_ZX1_SBA_IOMMU_COOKIE) {
+		/* Normal case when no AGP device in system */
+	    	hp->gatt = NULL;
+		hp->gatt_entries = 0;
+		printk(KERN_ERR PFX "No reserved IO PDIR entry found; "
+		       "GART disabled\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int __init
+hp_zx1_ioc_owner (void)
+{
+	struct _hp_private *hp = &hp_private;
+
+	printk(KERN_INFO PFX "HP ZX1 IOC: IOPDIR dedicated to GART\n");
+
+	/*
+	 * Select an IOV page size no larger than system page size.
+	 */
+	if (PAGE_SIZE >= KB(64)) {
+		hp->io_tlb_shift = 16;
+		hp->io_tlb_ps = 3;
+	} else if (PAGE_SIZE >= KB(16)) {
+		hp->io_tlb_shift = 14;
+		hp->io_tlb_ps = 2;
+	} else if (PAGE_SIZE >= KB(8)) {
+		hp->io_tlb_shift = 13;
+		hp->io_tlb_ps = 1;
+	} else {
+		hp->io_tlb_shift = 12;
+		hp->io_tlb_ps = 0;
+	}
+	hp->io_page_size = 1 << hp->io_tlb_shift;
+	hp->io_pages_per_kpage = PAGE_SIZE / hp->io_page_size;
+
+	hp->iova_base = HP_ZX1_IOVA_BASE;
+	hp->gart_size = HP_ZX1_GART_SIZE;
+	hp->gart_base = hp->iova_base + HP_ZX1_IOVA_SIZE - hp->gart_size;
+
+	hp->gatt_entries = hp->gart_size / hp->io_page_size;
+	hp->io_pdir_size = (HP_ZX1_IOVA_SIZE / hp->io_page_size) * sizeof(u64);
+
+	return 0;
+}
+
+static int __init
+hp_zx1_ioc_init (u64 hpa)
+{
+	struct _hp_private *hp = &hp_private;
+
+	hp->ioc_regs = ioremap(hpa, 1024);
+	if (!hp->ioc_regs)
+		return -ENOMEM;
+
+	/*
+	 * If the IOTLB is currently disabled, we can take it over.
+	 * Otherwise, we have to share with sba_iommu.
+	 */
+	hp->io_pdir_owner = (readq(hp->ioc_regs+HP_ZX1_IBASE) & 0x1) == 0;
+
+	if (hp->io_pdir_owner)
+		return hp_zx1_ioc_owner();
+
+	return hp_zx1_ioc_shared();
+}
+
+static int
+hp_zx1_lba_find_capability (volatile u8 __iomem *hpa, int cap)
+{
+	u16 status;
+	u8 pos, id;
+	int ttl = 48;
+
+	status = readw(hpa+PCI_STATUS);
+	if (!(status & PCI_STATUS_CAP_LIST))
+		return 0;
+	pos = readb(hpa+PCI_CAPABILITY_LIST);
+	while (ttl-- && pos >= 0x40) {
+		pos &= ~3;
+		id = readb(hpa+pos+PCI_CAP_LIST_ID);
+		if (id == 0xff)
+			break;
+		if (id == cap)
+			return pos;
+		pos = readb(hpa+pos+PCI_CAP_LIST_NEXT);
+	}
+	return 0;
+}
+
+static int __init
+hp_zx1_lba_init (u64 hpa)
+{
+	struct _hp_private *hp = &hp_private;
+	int cap;
+
+	hp->lba_regs = ioremap(hpa, 256);
+	if (!hp->lba_regs)
+		return -ENOMEM;
+
+	hp->lba_cap_offset = hp_zx1_lba_find_capability(hp->lba_regs, PCI_CAP_ID_AGP);
+
+	cap = readl(hp->lba_regs+hp->lba_cap_offset) & 0xff;
+	if (cap != PCI_CAP_ID_AGP) {
+		printk(KERN_ERR PFX "Invalid capability ID 0x%02x at 0x%x\n",
+		       cap, hp->lba_cap_offset);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int
+hp_zx1_fetch_size(void)
+{
+	int size;
+
+	size = hp_private.gart_size / MB(1);
+	hp_zx1_sizes[0].size = size;
+	agp_bridge->current_size = (void *) &hp_zx1_sizes[0];
+	return size;
+}
+
+static int
+hp_zx1_configure (void)
+{
+	struct _hp_private *hp = &hp_private;
+
+	agp_bridge->gart_bus_addr = hp->gart_base;
+	agp_bridge->capndx = hp->lba_cap_offset;
+	agp_bridge->mode = readl(hp->lba_regs+hp->lba_cap_offset+PCI_AGP_STATUS);
+
+	if (hp->io_pdir_owner) {
+		writel(virt_to_phys(hp->io_pdir), hp->ioc_regs+HP_ZX1_PDIR_BASE);
+		readl(hp->ioc_regs+HP_ZX1_PDIR_BASE);
+		writel(hp->io_tlb_ps, hp->ioc_regs+HP_ZX1_TCNFG);
+		readl(hp->ioc_regs+HP_ZX1_TCNFG);
+		writel(~(HP_ZX1_IOVA_SIZE-1), hp->ioc_regs+HP_ZX1_IMASK);
+		readl(hp->ioc_regs+HP_ZX1_IMASK);
+		writel(hp->iova_base|1, hp->ioc_regs+HP_ZX1_IBASE);
+		readl(hp->ioc_regs+HP_ZX1_IBASE);
+		writel(hp->iova_base|log2(HP_ZX1_IOVA_SIZE), hp->ioc_regs+HP_ZX1_PCOM);
+		readl(hp->ioc_regs+HP_ZX1_PCOM);
+	}
+
+	return 0;
+}
+
+static void
+hp_zx1_cleanup (void)
+{
+	struct _hp_private *hp = &hp_private;
+
+	if (hp->ioc_regs) {
+		if (hp->io_pdir_owner) {
+			writeq(0, hp->ioc_regs+HP_ZX1_IBASE);
+			readq(hp->ioc_regs+HP_ZX1_IBASE);
+		}
+		iounmap(hp->ioc_regs);
+	}
+	if (hp->lba_regs)
+		iounmap(hp->lba_regs);
+}
+
+static void
+hp_zx1_tlbflush (struct agp_memory *mem)
+{
+	struct _hp_private *hp = &hp_private;
+
+	writeq(hp->gart_base | log2(hp->gart_size), hp->ioc_regs+HP_ZX1_PCOM);
+	readq(hp->ioc_regs+HP_ZX1_PCOM);
+}
+
+static int
+hp_zx1_create_gatt_table (struct agp_bridge_data *bridge)
+{
+	struct _hp_private *hp = &hp_private;
+	int i;
+
+	if (hp->io_pdir_owner) {
+		hp->io_pdir = (u64 *) __get_free_pages(GFP_KERNEL,
+						get_order(hp->io_pdir_size));
+		if (!hp->io_pdir) {
+			printk(KERN_ERR PFX "Couldn't allocate contiguous "
+				"memory for I/O PDIR\n");
+			hp->gatt = NULL;
+			hp->gatt_entries = 0;
+			return -ENOMEM;
+		}
+		memset(hp->io_pdir, 0, hp->io_pdir_size);
+
+		hp->gatt = &hp->io_pdir[HP_ZX1_IOVA_TO_PDIR(hp->gart_base)];
+	}
+
+	for (i = 0; i < hp->gatt_entries; i++) {
+		hp->gatt[i] = (unsigned long) agp_bridge->scratch_page;
+	}
+
+	return 0;
+}
+
+static int
+hp_zx1_free_gatt_table (struct agp_bridge_data *bridge)
+{
+	struct _hp_private *hp = &hp_private;
+
+	if (hp->io_pdir_owner)
+		free_pages((unsigned long) hp->io_pdir,
+			    get_order(hp->io_pdir_size));
+	else
+		hp->gatt[0] = HP_ZX1_SBA_IOMMU_COOKIE;
+	return 0;
+}
+
+static int
+hp_zx1_insert_memory (struct agp_memory *mem, off_t pg_start, int type)
+{
+	struct _hp_private *hp = &hp_private;
+	int i, k;
+	off_t j, io_pg_start;
+	int io_pg_count;
+
+	if (type != 0 || mem->type != 0) {
+		return -EINVAL;
+	}
+
+	io_pg_start = hp->io_pages_per_kpage * pg_start;
+	io_pg_count = hp->io_pages_per_kpage * mem->page_count;
+	if ((io_pg_start + io_pg_count) > hp->gatt_entries) {
+		return -EINVAL;
+	}
+
+	j = io_pg_start;
+	while (j < (io_pg_start + io_pg_count)) {
+		if (hp->gatt[j]) {
+			return -EBUSY;
+		}
+		j++;
+	}
+
+	if (mem->is_flushed == FALSE) {
+		global_cache_flush();
+		mem->is_flushed = TRUE;
+	}
+
+	for (i = 0, j = io_pg_start; i < mem->page_count; i++) {
+		unsigned long paddr;
+
+		paddr = mem->memory[i];
+		for (k = 0;
+		     k < hp->io_pages_per_kpage;
+		     k++, j++, paddr += hp->io_page_size) {
+			hp->gatt[j] =
+				agp_bridge->driver->mask_memory(agp_bridge,
+					paddr, type);
+		}
+	}
+
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int
+hp_zx1_remove_memory (struct agp_memory *mem, off_t pg_start, int type)
+{
+	struct _hp_private *hp = &hp_private;
+	int i, io_pg_start, io_pg_count;
+
+	if (type != 0 || mem->type != 0) {
+		return -EINVAL;
+	}
+
+	io_pg_start = hp->io_pages_per_kpage * pg_start;
+	io_pg_count = hp->io_pages_per_kpage * mem->page_count;
+	for (i = io_pg_start; i < io_pg_count + io_pg_start; i++) {
+		hp->gatt[i] = agp_bridge->scratch_page;
+	}
+
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static unsigned long
+hp_zx1_mask_memory (struct agp_bridge_data *bridge,
+	unsigned long addr, int type)
+{
+	return HP_ZX1_PDIR_VALID_BIT | addr;
+}
+
+static void
+hp_zx1_enable (struct agp_bridge_data *bridge, u32 mode)
+{
+	struct _hp_private *hp = &hp_private;
+	u32 command;
+
+	command = readl(hp->lba_regs+hp->lba_cap_offset+PCI_AGP_STATUS);
+	command = agp_collect_device_status(bridge, mode, command);
+	command |= 0x00000100;
+
+	writel(command, hp->lba_regs+hp->lba_cap_offset+PCI_AGP_COMMAND);
+
+	agp_device_command(command, (mode & AGP8X_MODE) != 0);
+}
+
+struct agp_bridge_driver hp_zx1_driver = {
+	.owner			= THIS_MODULE,
+	.size_type		= FIXED_APER_SIZE,
+	.configure		= hp_zx1_configure,
+	.fetch_size		= hp_zx1_fetch_size,
+	.cleanup		= hp_zx1_cleanup,
+	.tlb_flush		= hp_zx1_tlbflush,
+	.mask_memory		= hp_zx1_mask_memory,
+	.masks			= hp_zx1_masks,
+	.agp_enable		= hp_zx1_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= hp_zx1_create_gatt_table,
+	.free_gatt_table	= hp_zx1_free_gatt_table,
+	.insert_memory		= hp_zx1_insert_memory,
+	.remove_memory		= hp_zx1_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+	.cant_use_aperture	= 1,
+};
+
+static int __init
+hp_zx1_setup (u64 ioc_hpa, u64 lba_hpa)
+{
+	struct agp_bridge_data *bridge;
+	int error = 0;
+
+	error = hp_zx1_ioc_init(ioc_hpa);
+	if (error)
+		goto fail;
+
+	error = hp_zx1_lba_init(lba_hpa);
+	if (error)
+		goto fail;
+
+	bridge = agp_alloc_bridge();
+	if (!bridge) {
+		error = -ENOMEM;
+		goto fail;
+	}
+	bridge->driver = &hp_zx1_driver;
+
+	fake_bridge_dev.vendor = PCI_VENDOR_ID_HP;
+	fake_bridge_dev.device = PCI_DEVICE_ID_HP_PCIX_LBA;
+	bridge->dev = &fake_bridge_dev;
+
+	error = agp_add_bridge(bridge);
+  fail:
+	if (error)
+		hp_zx1_cleanup();
+	return error;
+}
+
+static acpi_status __init
+zx1_gart_probe (acpi_handle obj, u32 depth, void *context, void **ret)
+{
+	acpi_handle handle, parent;
+	acpi_status status;
+	struct acpi_buffer buffer;
+	struct acpi_device_info *info;
+	u64 lba_hpa, sba_hpa, length;
+	int match;
+
+	status = hp_acpi_csr_space(obj, &lba_hpa, &length);
+	if (ACPI_FAILURE(status))
+		return AE_OK; /* keep looking for another bridge */
+
+	/* Look for an enclosing IOC scope and find its CSR space */
+	handle = obj;
+	do {
+		buffer.length = ACPI_ALLOCATE_LOCAL_BUFFER;
+		status = acpi_get_object_info(handle, &buffer);
+		if (ACPI_SUCCESS(status)) {
+			/* TBD check _CID also */
+			info = buffer.pointer;
+			info->hardware_id.value[sizeof(info->hardware_id)-1] = '\0';
+			match = (strcmp(info->hardware_id.value, "HWP0001") == 0);
+			ACPI_MEM_FREE(info);
+			if (match) {
+				status = hp_acpi_csr_space(handle, &sba_hpa, &length);
+				if (ACPI_SUCCESS(status))
+					break;
+				else {
+					printk(KERN_ERR PFX "Detected HP ZX1 "
+					       "AGP LBA but no IOC.\n");
+					return AE_OK;
+				}
+			}
+		}
+
+		status = acpi_get_parent(handle, &parent);
+		handle = parent;
+	} while (ACPI_SUCCESS(status));
+
+	if (hp_zx1_setup(sba_hpa + HP_ZX1_IOC_OFFSET, lba_hpa))
+		return AE_OK;
+
+	printk(KERN_INFO PFX "Detected HP ZX1 %s AGP chipset (ioc=%lx, lba=%lx)\n",
+		(char *) context, sba_hpa + HP_ZX1_IOC_OFFSET, lba_hpa);
+
+	hp_zx1_gart_found = 1;
+	return AE_CTRL_TERMINATE; /* we only support one bridge; quit looking */
+}
+
+static int __init
+agp_hp_init (void)
+{
+	if (agp_off)
+		return -EINVAL;
+
+	acpi_get_devices("HWP0003", zx1_gart_probe, "HWP0003", NULL);
+	if (hp_zx1_gart_found)
+		return 0;
+
+	acpi_get_devices("HWP0007", zx1_gart_probe, "HWP0007", NULL);
+	if (hp_zx1_gart_found)
+		return 0;
+
+	return -ENODEV;
+}
+
+static void __exit
+agp_hp_cleanup (void)
+{
+}
+
+module_init(agp_hp_init);
+module_exit(agp_hp_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/i460-agp.c b/drivers/char/agp/i460-agp.c
new file mode 100644
index 0000000..adbea89
--- /dev/null
+++ b/drivers/char/agp/i460-agp.c
@@ -0,0 +1,642 @@
+/*
+ * For documentation on the i460 AGP interface, see Chapter 7 (AGP Subsystem) of
+ * the "Intel 460GTX Chipset Software Developer's Manual":
+ * http://developer.intel.com/design/itanium/downloads/24870401s.htm
+ */
+/*
+ * 460GX support by Chris Ahna <christopher.j.ahna@intel.com>
+ * Clean up & simplification by David Mosberger-Tang <davidm@hpl.hp.com>
+ */
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+
+#include "agp.h"
+
+#define INTEL_I460_BAPBASE		0x98
+#define INTEL_I460_GXBCTL		0xa0
+#define INTEL_I460_AGPSIZ		0xa2
+#define INTEL_I460_ATTBASE		0xfe200000
+#define INTEL_I460_GATT_VALID		(1UL << 24)
+#define INTEL_I460_GATT_COHERENT	(1UL << 25)
+
+/*
+ * The i460 can operate with large (4MB) pages, but there is no sane way to support this
+ * within the current kernel/DRM environment, so we disable the relevant code for now.
+ * See also comments in ia64_alloc_page()...
+ */
+#define I460_LARGE_IO_PAGES		0
+
+#if I460_LARGE_IO_PAGES
+# define I460_IO_PAGE_SHIFT		i460.io_page_shift
+#else
+# define I460_IO_PAGE_SHIFT		12
+#endif
+
+#define I460_IOPAGES_PER_KPAGE		(PAGE_SIZE >> I460_IO_PAGE_SHIFT)
+#define I460_KPAGES_PER_IOPAGE		(1 << (I460_IO_PAGE_SHIFT - PAGE_SHIFT))
+#define I460_SRAM_IO_DISABLE		(1 << 4)
+#define I460_BAPBASE_ENABLE		(1 << 3)
+#define I460_AGPSIZ_MASK		0x7
+#define I460_4M_PS			(1 << 1)
+
+/* Control bits for Out-Of-GART coherency and Burst Write Combining */
+#define I460_GXBCTL_OOG		(1UL << 0)
+#define I460_GXBCTL_BWC		(1UL << 2)
+
+/*
+ * gatt_table entries are 32-bits wide on the i460; the generic code ought to declare the
+ * gatt_table and gatt_table_real pointers a "void *"...
+ */
+#define RD_GATT(index)		readl((u32 *) i460.gatt + (index))
+#define WR_GATT(index, val)	writel((val), (u32 *) i460.gatt + (index))
+/*
+ * The 460 spec says we have to read the last location written to make sure that all
+ * writes have taken effect
+ */
+#define WR_FLUSH_GATT(index)	RD_GATT(index)
+
+#define log2(x)			ffz(~(x))
+
+static struct {
+	void *gatt;				/* ioremap'd GATT area */
+
+	/* i460 supports multiple GART page sizes, so GART pageshift is dynamic: */
+	u8 io_page_shift;
+
+	/* BIOS configures chipset to one of 2 possible apbase values: */
+	u8 dynamic_apbase;
+
+	/* structure for tracking partial use of 4MB GART pages: */
+	struct lp_desc {
+		unsigned long *alloced_map;	/* bitmap of kernel-pages in use */
+		int refcount;			/* number of kernel pages using the large page */
+		u64 paddr;			/* physical address of large page */
+	} *lp_desc;
+} i460;
+
+static struct aper_size_info_8 i460_sizes[3] =
+{
+	/*
+	 * The 32GB aperture is only available with a 4M GART page size.  Due to the
+	 * dynamic GART page size, we can't figure out page_order or num_entries until
+	 * runtime.
+	 */
+	{32768, 0, 0, 4},
+	{1024, 0, 0, 2},
+	{256, 0, 0, 1}
+};
+
+static struct gatt_mask i460_masks[] =
+{
+	{
+	  .mask = INTEL_I460_GATT_VALID | INTEL_I460_GATT_COHERENT,
+	  .type = 0
+	}
+};
+
+static int i460_fetch_size (void)
+{
+	int i;
+	u8 temp;
+	struct aper_size_info_8 *values;
+
+	/* Determine the GART page size */
+	pci_read_config_byte(agp_bridge->dev, INTEL_I460_GXBCTL, &temp);
+	i460.io_page_shift = (temp & I460_4M_PS) ? 22 : 12;
+	pr_debug("i460_fetch_size: io_page_shift=%d\n", i460.io_page_shift);
+
+	if (i460.io_page_shift != I460_IO_PAGE_SHIFT) {
+		printk(KERN_ERR PFX
+		       "I/O (GART) page-size %ZuKB doesn't match expected size %ZuKB\n",
+		       1UL << (i460.io_page_shift - 10), 1UL << (I460_IO_PAGE_SHIFT));
+		return 0;
+	}
+
+	values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+
+	pci_read_config_byte(agp_bridge->dev, INTEL_I460_AGPSIZ, &temp);
+
+	/* Exit now if the IO drivers for the GART SRAMS are turned off */
+	if (temp & I460_SRAM_IO_DISABLE) {
+		printk(KERN_ERR PFX "GART SRAMS disabled on 460GX chipset\n");
+		printk(KERN_ERR PFX "AGPGART operation not possible\n");
+		return 0;
+	}
+
+	/* Make sure we don't try to create an 2 ^ 23 entry GATT */
+	if ((i460.io_page_shift == 0) && ((temp & I460_AGPSIZ_MASK) == 4)) {
+		printk(KERN_ERR PFX "We can't have a 32GB aperture with 4KB GART pages\n");
+		return 0;
+	}
+
+	/* Determine the proper APBASE register */
+	if (temp & I460_BAPBASE_ENABLE)
+		i460.dynamic_apbase = INTEL_I460_BAPBASE;
+	else
+		i460.dynamic_apbase = AGP_APBASE;
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		/*
+		 * Dynamically calculate the proper num_entries and page_order values for
+		 * the define aperture sizes. Take care not to shift off the end of
+		 * values[i].size.
+		 */
+		values[i].num_entries = (values[i].size << 8) >> (I460_IO_PAGE_SHIFT - 12);
+		values[i].page_order = log2((sizeof(u32)*values[i].num_entries) >> PAGE_SHIFT);
+	}
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		/* Neglect control bits when matching up size_value */
+		if ((temp & I460_AGPSIZ_MASK) == values[i].size_value) {
+			agp_bridge->previous_size = agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+/* There isn't anything to do here since 460 has no GART TLB. */
+static void i460_tlb_flush (struct agp_memory *mem)
+{
+	return;
+}
+
+/*
+ * This utility function is needed to prevent corruption of the control bits
+ * which are stored along with the aperture size in 460's AGPSIZ register
+ */
+static void i460_write_agpsiz (u8 size_value)
+{
+	u8 temp;
+
+	pci_read_config_byte(agp_bridge->dev, INTEL_I460_AGPSIZ, &temp);
+	pci_write_config_byte(agp_bridge->dev, INTEL_I460_AGPSIZ,
+			      ((temp & ~I460_AGPSIZ_MASK) | size_value));
+}
+
+static void i460_cleanup (void)
+{
+	struct aper_size_info_8 *previous_size;
+
+	previous_size = A_SIZE_8(agp_bridge->previous_size);
+	i460_write_agpsiz(previous_size->size_value);
+
+	if (I460_IO_PAGE_SHIFT > PAGE_SHIFT)
+		kfree(i460.lp_desc);
+}
+
+static int i460_configure (void)
+{
+	union {
+		u32 small[2];
+		u64 large;
+	} temp;
+	size_t size;
+	u8 scratch;
+	struct aper_size_info_8 *current_size;
+
+	temp.large = 0;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+	i460_write_agpsiz(current_size->size_value);
+
+	/*
+	 * Do the necessary rigmarole to read all eight bytes of APBASE.
+	 * This has to be done since the AGP aperture can be above 4GB on
+	 * 460 based systems.
+	 */
+	pci_read_config_dword(agp_bridge->dev, i460.dynamic_apbase, &(temp.small[0]));
+	pci_read_config_dword(agp_bridge->dev, i460.dynamic_apbase + 4, &(temp.small[1]));
+
+	/* Clear BAR control bits */
+	agp_bridge->gart_bus_addr = temp.large & ~((1UL << 3) - 1);
+
+	pci_read_config_byte(agp_bridge->dev, INTEL_I460_GXBCTL, &scratch);
+	pci_write_config_byte(agp_bridge->dev, INTEL_I460_GXBCTL,
+			      (scratch & 0x02) | I460_GXBCTL_OOG | I460_GXBCTL_BWC);
+
+	/*
+	 * Initialize partial allocation trackers if a GART page is bigger than a kernel
+	 * page.
+	 */
+	if (I460_IO_PAGE_SHIFT > PAGE_SHIFT) {
+		size = current_size->num_entries * sizeof(i460.lp_desc[0]);
+		i460.lp_desc = kmalloc(size, GFP_KERNEL);
+		if (!i460.lp_desc)
+			return -ENOMEM;
+		memset(i460.lp_desc, 0, size);
+	}
+	return 0;
+}
+
+static int i460_create_gatt_table (struct agp_bridge_data *bridge)
+{
+	int page_order, num_entries, i;
+	void *temp;
+
+	/*
+	 * Load up the fixed address of the GART SRAMS which hold our GATT table.
+	 */
+	temp = agp_bridge->current_size;
+	page_order = A_SIZE_8(temp)->page_order;
+	num_entries = A_SIZE_8(temp)->num_entries;
+
+	i460.gatt = ioremap(INTEL_I460_ATTBASE, PAGE_SIZE << page_order);
+
+	/* These are no good, the should be removed from the agp_bridge strucure... */
+	agp_bridge->gatt_table_real = NULL;
+	agp_bridge->gatt_table = NULL;
+	agp_bridge->gatt_bus_addr = 0;
+
+	for (i = 0; i < num_entries; ++i)
+		WR_GATT(i, 0);
+	WR_FLUSH_GATT(i - 1);
+	return 0;
+}
+
+static int i460_free_gatt_table (struct agp_bridge_data *bridge)
+{
+	int num_entries, i;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+
+	num_entries = A_SIZE_8(temp)->num_entries;
+
+	for (i = 0; i < num_entries; ++i)
+		WR_GATT(i, 0);
+	WR_FLUSH_GATT(num_entries - 1);
+
+	iounmap(i460.gatt);
+	return 0;
+}
+
+/*
+ * The following functions are called when the I/O (GART) page size is smaller than
+ * PAGE_SIZE.
+ */
+
+static int i460_insert_memory_small_io_page (struct agp_memory *mem,
+				off_t pg_start, int type)
+{
+	unsigned long paddr, io_pg_start, io_page_size;
+	int i, j, k, num_entries;
+	void *temp;
+
+	pr_debug("i460_insert_memory_small_io_page(mem=%p, pg_start=%ld, type=%d, paddr0=0x%lx)\n",
+		 mem, pg_start, type, mem->memory[0]);
+
+	io_pg_start = I460_IOPAGES_PER_KPAGE * pg_start;
+
+	temp = agp_bridge->current_size;
+	num_entries = A_SIZE_8(temp)->num_entries;
+
+	if ((io_pg_start + I460_IOPAGES_PER_KPAGE * mem->page_count) > num_entries) {
+		printk(KERN_ERR PFX "Looks like we're out of AGP memory\n");
+		return -EINVAL;
+	}
+
+	j = io_pg_start;
+	while (j < (io_pg_start + I460_IOPAGES_PER_KPAGE * mem->page_count)) {
+		if (!PGE_EMPTY(agp_bridge, RD_GATT(j))) {
+			pr_debug("i460_insert_memory_small_io_page: GATT[%d]=0x%x is busy\n",
+				 j, RD_GATT(j));
+			return -EBUSY;
+		}
+		j++;
+	}
+
+	io_page_size = 1UL << I460_IO_PAGE_SHIFT;
+	for (i = 0, j = io_pg_start; i < mem->page_count; i++) {
+		paddr = mem->memory[i];
+		for (k = 0; k < I460_IOPAGES_PER_KPAGE; k++, j++, paddr += io_page_size)
+			WR_GATT(j, agp_bridge->driver->mask_memory(agp_bridge,
+				paddr, mem->type));
+	}
+	WR_FLUSH_GATT(j - 1);
+	return 0;
+}
+
+static int i460_remove_memory_small_io_page(struct agp_memory *mem,
+				off_t pg_start, int type)
+{
+	int i;
+
+	pr_debug("i460_remove_memory_small_io_page(mem=%p, pg_start=%ld, type=%d)\n",
+		 mem, pg_start, type);
+
+	pg_start = I460_IOPAGES_PER_KPAGE * pg_start;
+
+	for (i = pg_start; i < (pg_start + I460_IOPAGES_PER_KPAGE * mem->page_count); i++)
+		WR_GATT(i, 0);
+	WR_FLUSH_GATT(i - 1);
+	return 0;
+}
+
+#if I460_LARGE_IO_PAGES
+
+/*
+ * These functions are called when the I/O (GART) page size exceeds PAGE_SIZE.
+ *
+ * This situation is interesting since AGP memory allocations that are smaller than a
+ * single GART page are possible.  The i460.lp_desc array tracks partial allocation of the
+ * large GART pages to work around this issue.
+ *
+ * i460.lp_desc[pg_num].refcount tracks the number of kernel pages in use within GART page
+ * pg_num.  i460.lp_desc[pg_num].paddr is the physical address of the large page and
+ * i460.lp_desc[pg_num].alloced_map is a bitmap of kernel pages that are in use (allocated).
+ */
+
+static int i460_alloc_large_page (struct lp_desc *lp)
+{
+	unsigned long order = I460_IO_PAGE_SHIFT - PAGE_SHIFT;
+	size_t map_size;
+	void *lpage;
+
+	lpage = (void *) __get_free_pages(GFP_KERNEL, order);
+	if (!lpage) {
+		printk(KERN_ERR PFX "Couldn't alloc 4M GART page...\n");
+		return -ENOMEM;
+	}
+
+	map_size = ((I460_KPAGES_PER_IOPAGE + BITS_PER_LONG - 1) & -BITS_PER_LONG)/8;
+	lp->alloced_map = kmalloc(map_size, GFP_KERNEL);
+	if (!lp->alloced_map) {
+		free_pages((unsigned long) lpage, order);
+		printk(KERN_ERR PFX "Out of memory, we're in trouble...\n");
+		return -ENOMEM;
+	}
+	memset(lp->alloced_map, 0, map_size);
+
+	lp->paddr = virt_to_phys(lpage);
+	lp->refcount = 0;
+	atomic_add(I460_KPAGES_PER_IOPAGE, &agp_bridge->current_memory_agp);
+	return 0;
+}
+
+static void i460_free_large_page (struct lp_desc *lp)
+{
+	kfree(lp->alloced_map);
+	lp->alloced_map = NULL;
+
+	free_pages((unsigned long) phys_to_virt(lp->paddr), I460_IO_PAGE_SHIFT - PAGE_SHIFT);
+	atomic_sub(I460_KPAGES_PER_IOPAGE, &agp_bridge->current_memory_agp);
+}
+
+static int i460_insert_memory_large_io_page (struct agp_memory *mem,
+				off_t pg_start, int type)
+{
+	int i, start_offset, end_offset, idx, pg, num_entries;
+	struct lp_desc *start, *end, *lp;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+	num_entries = A_SIZE_8(temp)->num_entries;
+
+	/* Figure out what pg_start means in terms of our large GART pages */
+	start	 	= &i460.lp_desc[pg_start / I460_KPAGES_PER_IOPAGE];
+	end 		= &i460.lp_desc[(pg_start + mem->page_count - 1) / I460_KPAGES_PER_IOPAGE];
+	start_offset 	= pg_start % I460_KPAGES_PER_IOPAGE;
+	end_offset 	= (pg_start + mem->page_count - 1) % I460_KPAGES_PER_IOPAGE;
+
+	if (end > i460.lp_desc + num_entries) {
+		printk(KERN_ERR PFX "Looks like we're out of AGP memory\n");
+		return -EINVAL;
+	}
+
+	/* Check if the requested region of the aperture is free */
+	for (lp = start; lp <= end; ++lp) {
+		if (!lp->alloced_map)
+			continue;	/* OK, the entire large page is available... */
+
+		for (idx = ((lp == start) ? start_offset : 0);
+		     idx < ((lp == end) ? (end_offset + 1) : I460_KPAGES_PER_IOPAGE);
+		     idx++)
+		{
+			if (test_bit(idx, lp->alloced_map))
+				return -EBUSY;
+		}
+	}
+
+	for (lp = start, i = 0; lp <= end; ++lp) {
+		if (!lp->alloced_map) {
+			/* Allocate new GART pages... */
+			if (i460_alloc_large_page(lp) < 0)
+				return -ENOMEM;
+			pg = lp - i460.lp_desc;
+			WR_GATT(pg, agp_bridge->driver->mask_memory(agp_bridge,
+				lp->paddr, 0));
+			WR_FLUSH_GATT(pg);
+		}
+
+		for (idx = ((lp == start) ? start_offset : 0);
+		     idx < ((lp == end) ? (end_offset + 1) : I460_KPAGES_PER_IOPAGE);
+		     idx++, i++)
+		{
+			mem->memory[i] = lp->paddr + idx*PAGE_SIZE;
+			__set_bit(idx, lp->alloced_map);
+			++lp->refcount;
+		}
+	}
+	return 0;
+}
+
+static int i460_remove_memory_large_io_page (struct agp_memory *mem,
+				off_t pg_start, int type)
+{
+	int i, pg, start_offset, end_offset, idx, num_entries;
+	struct lp_desc *start, *end, *lp;
+	void *temp;
+
+	temp = agp_bridge->driver->current_size;
+	num_entries = A_SIZE_8(temp)->num_entries;
+
+	/* Figure out what pg_start means in terms of our large GART pages */
+	start	 	= &i460.lp_desc[pg_start / I460_KPAGES_PER_IOPAGE];
+	end 		= &i460.lp_desc[(pg_start + mem->page_count - 1) / I460_KPAGES_PER_IOPAGE];
+	start_offset 	= pg_start % I460_KPAGES_PER_IOPAGE;
+	end_offset 	= (pg_start + mem->page_count - 1) % I460_KPAGES_PER_IOPAGE;
+
+	for (i = 0, lp = start; lp <= end; ++lp) {
+		for (idx = ((lp == start) ? start_offset : 0);
+		     idx < ((lp == end) ? (end_offset + 1) : I460_KPAGES_PER_IOPAGE);
+		     idx++, i++)
+		{
+			mem->memory[i] = 0;
+			__clear_bit(idx, lp->alloced_map);
+			--lp->refcount;
+		}
+
+		/* Free GART pages if they are unused */
+		if (lp->refcount == 0) {
+			pg = lp - i460.lp_desc;
+			WR_GATT(pg, 0);
+			WR_FLUSH_GATT(pg);
+			i460_free_large_page(lp);
+		}
+	}
+	return 0;
+}
+
+/* Wrapper routines to call the approriate {small_io_page,large_io_page} function */
+
+static int i460_insert_memory (struct agp_memory *mem,
+				off_t pg_start, int type)
+{
+	if (I460_IO_PAGE_SHIFT <= PAGE_SHIFT)
+		return i460_insert_memory_small_io_page(mem, pg_start, type);
+	else
+		return i460_insert_memory_large_io_page(mem, pg_start, type);
+}
+
+static int i460_remove_memory (struct agp_memory *mem,
+				off_t pg_start, int type)
+{
+	if (I460_IO_PAGE_SHIFT <= PAGE_SHIFT)
+		return i460_remove_memory_small_io_page(mem, pg_start, type);
+	else
+		return i460_remove_memory_large_io_page(mem, pg_start, type);
+}
+
+/*
+ * If the I/O (GART) page size is bigger than the kernel page size, we don't want to
+ * allocate memory until we know where it is to be bound in the aperture (a
+ * multi-kernel-page alloc might fit inside of an already allocated GART page).
+ *
+ * Let's just hope nobody counts on the allocated AGP memory being there before bind time
+ * (I don't think current drivers do)...
+ */
+static void *i460_alloc_page (struct agp_bridge_data *bridge)
+{
+	void *page;
+
+	if (I460_IO_PAGE_SHIFT <= PAGE_SHIFT)
+		page = agp_generic_alloc_page(agp_bridge);
+	else
+		/* Returning NULL would cause problems */
+		/* AK: really dubious code. */
+		page = (void *)~0UL;
+	return page;
+}
+
+static void i460_destroy_page (void *page)
+{
+	if (I460_IO_PAGE_SHIFT <= PAGE_SHIFT)
+		agp_generic_destroy_page(page);
+}
+
+#endif /* I460_LARGE_IO_PAGES */
+
+static unsigned long i460_mask_memory (struct agp_bridge_data *bridge,
+	unsigned long addr, int type)
+{
+	/* Make sure the returned address is a valid GATT entry */
+	return bridge->driver->masks[0].mask
+		| (((addr & ~((1 << I460_IO_PAGE_SHIFT) - 1)) & 0xffffff000) >> 12);
+}
+
+struct agp_bridge_driver intel_i460_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= i460_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 3,
+	.configure		= i460_configure,
+	.fetch_size		= i460_fetch_size,
+	.cleanup		= i460_cleanup,
+	.tlb_flush		= i460_tlb_flush,
+	.mask_memory		= i460_mask_memory,
+	.masks			= i460_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= i460_create_gatt_table,
+	.free_gatt_table	= i460_free_gatt_table,
+#if I460_LARGE_IO_PAGES
+	.insert_memory		= i460_insert_memory,
+	.remove_memory		= i460_remove_memory,
+	.agp_alloc_page		= i460_alloc_page,
+	.agp_destroy_page	= i460_destroy_page,
+#else
+	.insert_memory		= i460_insert_memory_small_io_page,
+	.remove_memory		= i460_remove_memory_small_io_page,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+#endif
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.cant_use_aperture	= 1,
+};
+
+static int __devinit agp_intel_i460_probe(struct pci_dev *pdev,
+					  const struct pci_device_id *ent)
+{
+	struct agp_bridge_data *bridge;
+	u8 cap_ptr;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->driver = &intel_i460_driver;
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+
+	printk(KERN_INFO PFX "Detected Intel 460GX chipset\n");
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_intel_i460_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_intel_i460_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_INTEL,
+	.device		= PCI_DEVICE_ID_INTEL_84460GX,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_intel_i460_pci_table);
+
+static struct pci_driver agp_intel_i460_pci_driver = {
+	.name		= "agpgart-intel-i460",
+	.id_table	= agp_intel_i460_pci_table,
+	.probe		= agp_intel_i460_probe,
+	.remove		= __devexit_p(agp_intel_i460_remove),
+};
+
+static int __init agp_intel_i460_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_intel_i460_pci_driver);
+}
+
+static void __exit agp_intel_i460_cleanup(void)
+{
+	pci_unregister_driver(&agp_intel_i460_pci_driver);
+}
+
+module_init(agp_intel_i460_init);
+module_exit(agp_intel_i460_cleanup);
+
+MODULE_AUTHOR("Chris Ahna <Christopher.J.Ahna@intel.com>");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/intel-agp.c b/drivers/char/agp/intel-agp.c
new file mode 100644
index 0000000..8c7d727
--- /dev/null
+++ b/drivers/char/agp/intel-agp.c
@@ -0,0 +1,1833 @@
+/*
+ * Intel AGPGART routines.
+ */
+
+/*
+ * Intel(R) 855GM/852GM and 865G support added by David Dawes
+ * <dawes@tungstengraphics.com>.
+ *
+ * Intel(R) 915G/915GM support added by Alan Hourihane
+ * <alanh@tungstengraphics.com>.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+/* Intel 815 register */
+#define INTEL_815_APCONT	0x51
+#define INTEL_815_ATTBASE_MASK	~0x1FFFFFFF
+
+/* Intel i820 registers */
+#define INTEL_I820_RDCR		0x51
+#define INTEL_I820_ERRSTS	0xc8
+
+/* Intel i840 registers */
+#define INTEL_I840_MCHCFG	0x50
+#define INTEL_I840_ERRSTS	0xc8
+
+/* Intel i850 registers */
+#define INTEL_I850_MCHCFG	0x50
+#define INTEL_I850_ERRSTS	0xc8
+
+/* intel 915G registers */
+#define I915_GMADDR	0x18
+#define I915_MMADDR	0x10
+#define I915_PTEADDR	0x1C
+#define I915_GMCH_GMS_STOLEN_48M	(0x6 << 4)
+#define I915_GMCH_GMS_STOLEN_64M	(0x7 << 4)
+
+
+/* Intel 7505 registers */
+#define INTEL_I7505_APSIZE	0x74
+#define INTEL_I7505_NCAPID	0x60
+#define INTEL_I7505_NISTAT	0x6c
+#define INTEL_I7505_ATTBASE	0x78
+#define INTEL_I7505_ERRSTS	0x42
+#define INTEL_I7505_AGPCTRL	0x70
+#define INTEL_I7505_MCHCFG	0x50
+
+static struct aper_size_info_fixed intel_i810_sizes[] =
+{
+	{64, 16384, 4},
+	/* The 32M mode still requires a 64k gatt */
+	{32, 8192, 4}
+};
+
+#define AGP_DCACHE_MEMORY	1
+#define AGP_PHYS_MEMORY		2
+
+static struct gatt_mask intel_i810_masks[] =
+{
+	{.mask = I810_PTE_VALID, .type = 0},
+	{.mask = (I810_PTE_VALID | I810_PTE_LOCAL), .type = AGP_DCACHE_MEMORY},
+	{.mask = I810_PTE_VALID, .type = 0}
+};
+
+static struct _intel_i810_private {
+	struct pci_dev *i810_dev;	/* device one */
+	volatile u8 __iomem *registers;
+	int num_dcache_entries;
+} intel_i810_private;
+
+static int intel_i810_fetch_size(void)
+{
+	u32 smram_miscc;
+	struct aper_size_info_fixed *values;
+
+	pci_read_config_dword(agp_bridge->dev, I810_SMRAM_MISCC, &smram_miscc);
+	values = A_SIZE_FIX(agp_bridge->driver->aperture_sizes);
+
+	if ((smram_miscc & I810_GMS) == I810_GMS_DISABLE) {
+		printk(KERN_WARNING PFX "i810 is disabled\n");
+		return 0;
+	}
+	if ((smram_miscc & I810_GFX_MEM_WIN_SIZE) == I810_GFX_MEM_WIN_32M) {
+		agp_bridge->previous_size =
+			agp_bridge->current_size = (void *) (values + 1);
+		agp_bridge->aperture_size_idx = 1;
+		return values[1].size;
+	} else {
+		agp_bridge->previous_size =
+			agp_bridge->current_size = (void *) (values);
+		agp_bridge->aperture_size_idx = 0;
+		return values[0].size;
+	}
+
+	return 0;
+}
+
+static int intel_i810_configure(void)
+{
+	struct aper_size_info_fixed *current_size;
+	u32 temp;
+	int i;
+
+	current_size = A_SIZE_FIX(agp_bridge->current_size);
+
+	pci_read_config_dword(intel_i810_private.i810_dev, I810_MMADDR, &temp);
+	temp &= 0xfff80000;
+
+	intel_i810_private.registers = ioremap(temp, 128 * 4096);
+	if (!intel_i810_private.registers) {
+		printk(KERN_ERR PFX "Unable to remap memory.\n");
+		return -ENOMEM;
+	}
+
+	if ((readl(intel_i810_private.registers+I810_DRAM_CTL)
+		& I810_DRAM_ROW_0) == I810_DRAM_ROW_0_SDRAM) {
+		/* This will need to be dynamically assigned */
+		printk(KERN_INFO PFX "detected 4MB dedicated video ram.\n");
+		intel_i810_private.num_dcache_entries = 1024;
+	}
+	pci_read_config_dword(intel_i810_private.i810_dev, I810_GMADDR, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+	writel(agp_bridge->gatt_bus_addr | I810_PGETBL_ENABLED, intel_i810_private.registers+I810_PGETBL_CTL);
+	readl(intel_i810_private.registers+I810_PGETBL_CTL);	/* PCI Posting. */
+
+	if (agp_bridge->driver->needs_scratch_page) {
+		for (i = 0; i < current_size->num_entries; i++) {
+			writel(agp_bridge->scratch_page, intel_i810_private.registers+I810_PTE_BASE+(i*4));
+			readl(intel_i810_private.registers+I810_PTE_BASE+(i*4));	/* PCI posting. */
+		}
+	}
+	global_cache_flush();
+	return 0;
+}
+
+static void intel_i810_cleanup(void)
+{
+	writel(0, intel_i810_private.registers+I810_PGETBL_CTL);
+	readl(intel_i810_private.registers);	/* PCI Posting. */
+	iounmap(intel_i810_private.registers);
+}
+
+static void intel_i810_tlbflush(struct agp_memory *mem)
+{
+	return;
+}
+
+static void intel_i810_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+	return;
+}
+
+/* Exists to support ARGB cursors */
+static void *i8xx_alloc_pages(void)
+{
+	struct page * page;
+
+	page = alloc_pages(GFP_KERNEL, 2);
+	if (page == NULL)
+		return NULL;
+
+	if (change_page_attr(page, 4, PAGE_KERNEL_NOCACHE) < 0) {
+		global_flush_tlb();
+		__free_page(page);
+		return NULL;
+	}
+	global_flush_tlb();
+	get_page(page);
+	SetPageLocked(page);
+	atomic_inc(&agp_bridge->current_memory_agp);
+	return page_address(page);
+}
+
+static void i8xx_destroy_pages(void *addr)
+{
+	struct page *page;
+
+	if (addr == NULL)
+		return;
+
+	page = virt_to_page(addr);
+	change_page_attr(page, 4, PAGE_KERNEL);
+	global_flush_tlb();
+	put_page(page);
+	unlock_page(page);
+	free_pages((unsigned long)addr, 2);
+	atomic_dec(&agp_bridge->current_memory_agp);
+}
+
+static int intel_i810_insert_entries(struct agp_memory *mem, off_t pg_start,
+				int type)
+{
+	int i, j, num_entries;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+	num_entries = A_SIZE_FIX(temp)->num_entries;
+
+	if ((pg_start + mem->page_count) > num_entries) {
+		return -EINVAL;
+	}
+	for (j = pg_start; j < (pg_start + mem->page_count); j++) {
+		if (!PGE_EMPTY(agp_bridge, readl(agp_bridge->gatt_table+j)))
+			return -EBUSY;
+	}
+
+	if (type != 0 || mem->type != 0) {
+		if ((type == AGP_DCACHE_MEMORY) && (mem->type == AGP_DCACHE_MEMORY)) {
+			/* special insert */
+			global_cache_flush();
+			for (i = pg_start; i < (pg_start + mem->page_count); i++) {
+				writel((i*4096)|I810_PTE_LOCAL|I810_PTE_VALID, intel_i810_private.registers+I810_PTE_BASE+(i*4));
+				readl(intel_i810_private.registers+I810_PTE_BASE+(i*4));	/* PCI Posting. */
+			}
+			global_cache_flush();
+			agp_bridge->driver->tlb_flush(mem);
+			return 0;
+		}
+		if((type == AGP_PHYS_MEMORY) && (mem->type == AGP_PHYS_MEMORY))
+			goto insert;
+		return -EINVAL;
+	}
+
+insert:
+	global_cache_flush();
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		writel(agp_bridge->driver->mask_memory(agp_bridge,
+			mem->memory[i], mem->type),
+			intel_i810_private.registers+I810_PTE_BASE+(j*4));
+		readl(intel_i810_private.registers+I810_PTE_BASE+(j*4));	/* PCI Posting. */
+	}
+	global_cache_flush();
+
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int intel_i810_remove_entries(struct agp_memory *mem, off_t pg_start,
+				int type)
+{
+	int i;
+
+	for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+		writel(agp_bridge->scratch_page, intel_i810_private.registers+I810_PTE_BASE+(i*4));
+		readl(intel_i810_private.registers+I810_PTE_BASE+(i*4));	/* PCI Posting. */
+	}
+
+	global_cache_flush();
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+/*
+ * The i810/i830 requires a physical address to program its mouse
+ * pointer into hardware.
+ * However the Xserver still writes to it through the agp aperture.
+ */
+static struct agp_memory *alloc_agpphysmem_i8xx(size_t pg_count, int type)
+{
+	struct agp_memory *new;
+	void *addr;
+
+	if (pg_count != 1 && pg_count != 4)
+		return NULL;
+
+	switch (pg_count) {
+	case 1: addr = agp_bridge->driver->agp_alloc_page(agp_bridge);
+		break;
+	case 4:
+		/* kludge to get 4 physical pages for ARGB cursor */
+		addr = i8xx_alloc_pages();
+		break;
+	default:
+		return NULL;
+	}
+
+	if (addr == NULL)
+		return NULL;
+
+	new = agp_create_memory(pg_count);
+	if (new == NULL)
+		return NULL;
+
+	new->memory[0] = virt_to_phys(addr);
+	if (pg_count == 4) {
+		/* kludge to get 4 physical pages for ARGB cursor */
+		new->memory[1] = new->memory[0] + PAGE_SIZE;
+		new->memory[2] = new->memory[1] + PAGE_SIZE;
+		new->memory[3] = new->memory[2] + PAGE_SIZE;
+	}
+	new->page_count = pg_count;
+	new->num_scratch_pages = pg_count;
+	new->type = AGP_PHYS_MEMORY;
+	new->physical = new->memory[0];
+	return new;
+}
+
+static struct agp_memory *intel_i810_alloc_by_type(size_t pg_count, int type)
+{
+	struct agp_memory *new;
+
+	if (type == AGP_DCACHE_MEMORY) {
+		if (pg_count != intel_i810_private.num_dcache_entries)
+			return NULL;
+
+		new = agp_create_memory(1);
+		if (new == NULL)
+			return NULL;
+
+		new->type = AGP_DCACHE_MEMORY;
+		new->page_count = pg_count;
+		new->num_scratch_pages = 0;
+		vfree(new->memory);
+		return new;
+	}
+	if (type == AGP_PHYS_MEMORY)
+		return alloc_agpphysmem_i8xx(pg_count, type);
+
+	return NULL;
+}
+
+static void intel_i810_free_by_type(struct agp_memory *curr)
+{
+	agp_free_key(curr->key);
+	if(curr->type == AGP_PHYS_MEMORY) {
+		if (curr->page_count == 4)
+			i8xx_destroy_pages(phys_to_virt(curr->memory[0]));
+		else
+			agp_bridge->driver->agp_destroy_page(
+				 phys_to_virt(curr->memory[0]));
+		vfree(curr->memory);
+	}
+	kfree(curr);
+}
+
+static unsigned long intel_i810_mask_memory(struct agp_bridge_data *bridge,
+	unsigned long addr, int type)
+{
+	/* Type checking must be done elsewhere */
+	return addr | bridge->driver->masks[type].mask;
+}
+
+static struct aper_size_info_fixed intel_i830_sizes[] =
+{
+	{128, 32768, 5},
+	/* The 64M mode still requires a 128k gatt */
+	{64, 16384, 5},
+	{256, 65536, 6},
+};
+
+static struct _intel_i830_private {
+	struct pci_dev *i830_dev;		/* device one */
+	volatile u8 __iomem *registers;
+	volatile u32 __iomem *gtt;		/* I915G */
+	int gtt_entries;
+} intel_i830_private;
+
+static void intel_i830_init_gtt_entries(void)
+{
+	u16 gmch_ctrl;
+	int gtt_entries;
+	u8 rdct;
+	int local = 0;
+	static const int ddt[4] = { 0, 16, 32, 64 };
+	int size;
+
+	pci_read_config_word(agp_bridge->dev,I830_GMCH_CTRL,&gmch_ctrl);
+
+	/* We obtain the size of the GTT, which is also stored (for some
+	 * reason) at the top of stolen memory. Then we add 4KB to that
+	 * for the video BIOS popup, which is also stored in there. */
+	size = agp_bridge->driver->fetch_size() + 4;
+
+	if (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82830_HB ||
+	    agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82845G_HB) {
+		switch (gmch_ctrl & I830_GMCH_GMS_MASK) {
+		case I830_GMCH_GMS_STOLEN_512:
+			gtt_entries = KB(512) - KB(size);
+			break;
+		case I830_GMCH_GMS_STOLEN_1024:
+			gtt_entries = MB(1) - KB(size);
+			break;
+		case I830_GMCH_GMS_STOLEN_8192:
+			gtt_entries = MB(8) - KB(size);
+			break;
+		case I830_GMCH_GMS_LOCAL:
+			rdct = readb(intel_i830_private.registers+I830_RDRAM_CHANNEL_TYPE);
+			gtt_entries = (I830_RDRAM_ND(rdct) + 1) *
+					MB(ddt[I830_RDRAM_DDT(rdct)]);
+			local = 1;
+			break;
+		default:
+			gtt_entries = 0;
+			break;
+		}
+	} else {
+		switch (gmch_ctrl & I830_GMCH_GMS_MASK) {
+		case I855_GMCH_GMS_STOLEN_1M:
+			gtt_entries = MB(1) - KB(size);
+			break;
+		case I855_GMCH_GMS_STOLEN_4M:
+			gtt_entries = MB(4) - KB(size);
+			break;
+		case I855_GMCH_GMS_STOLEN_8M:
+			gtt_entries = MB(8) - KB(size);
+			break;
+		case I855_GMCH_GMS_STOLEN_16M:
+			gtt_entries = MB(16) - KB(size);
+			break;
+		case I855_GMCH_GMS_STOLEN_32M:
+			gtt_entries = MB(32) - KB(size);
+			break;
+		case I915_GMCH_GMS_STOLEN_48M:
+			/* Check it's really I915G */
+			if (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82915G_HB ||
+			    agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82915GM_HB)
+				gtt_entries = MB(48) - KB(size);
+			else
+				gtt_entries = 0;
+			break;
+		case I915_GMCH_GMS_STOLEN_64M:
+			/* Check it's really I915G */
+			if (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82915G_HB ||
+			    agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_82915GM_HB)
+				gtt_entries = MB(64) - KB(size);
+			else
+				gtt_entries = 0;
+		default:
+			gtt_entries = 0;
+			break;
+		}
+	}
+	if (gtt_entries > 0)
+		printk(KERN_INFO PFX "Detected %dK %s memory.\n",
+		       gtt_entries / KB(1), local ? "local" : "stolen");
+	else
+		printk(KERN_INFO PFX
+		       "No pre-allocated video memory detected.\n");
+	gtt_entries /= KB(4);
+
+	intel_i830_private.gtt_entries = gtt_entries;
+}
+
+/* The intel i830 automatically initializes the agp aperture during POST.
+ * Use the memory already set aside for in the GTT.
+ */
+static int intel_i830_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	int page_order;
+	struct aper_size_info_fixed *size;
+	int num_entries;
+	u32 temp;
+
+	size = agp_bridge->current_size;
+	page_order = size->page_order;
+	num_entries = size->num_entries;
+	agp_bridge->gatt_table_real = NULL;
+
+	pci_read_config_dword(intel_i830_private.i830_dev,I810_MMADDR,&temp);
+	temp &= 0xfff80000;
+
+	intel_i830_private.registers = ioremap(temp,128 * 4096);
+	if (!intel_i830_private.registers)
+		return -ENOMEM;
+
+	temp = readl(intel_i830_private.registers+I810_PGETBL_CTL) & 0xfffff000;
+	global_cache_flush();	/* FIXME: ?? */
+
+	/* we have to call this as early as possible after the MMIO base address is known */
+	intel_i830_init_gtt_entries();
+
+	agp_bridge->gatt_table = NULL;
+
+	agp_bridge->gatt_bus_addr = temp;
+
+	return 0;
+}
+
+/* Return the gatt table to a sane state. Use the top of stolen
+ * memory for the GTT.
+ */
+static int intel_i830_free_gatt_table(struct agp_bridge_data *bridge)
+{
+	return 0;
+}
+
+static int intel_i830_fetch_size(void)
+{
+	u16 gmch_ctrl;
+	struct aper_size_info_fixed *values;
+
+	values = A_SIZE_FIX(agp_bridge->driver->aperture_sizes);
+
+	if (agp_bridge->dev->device != PCI_DEVICE_ID_INTEL_82830_HB &&
+	    agp_bridge->dev->device != PCI_DEVICE_ID_INTEL_82845G_HB) {
+		/* 855GM/852GM/865G has 128MB aperture size */
+		agp_bridge->previous_size = agp_bridge->current_size = (void *) values;
+		agp_bridge->aperture_size_idx = 0;
+		return values[0].size;
+	}
+
+	pci_read_config_word(agp_bridge->dev,I830_GMCH_CTRL,&gmch_ctrl);
+
+	if ((gmch_ctrl & I830_GMCH_MEM_MASK) == I830_GMCH_MEM_128M) {
+		agp_bridge->previous_size = agp_bridge->current_size = (void *) values;
+		agp_bridge->aperture_size_idx = 0;
+		return values[0].size;
+	} else {
+		agp_bridge->previous_size = agp_bridge->current_size = (void *) (values + 1);
+		agp_bridge->aperture_size_idx = 1;
+		return values[1].size;
+	}
+
+	return 0;
+}
+
+static int intel_i830_configure(void)
+{
+	struct aper_size_info_fixed *current_size;
+	u32 temp;
+	u16 gmch_ctrl;
+	int i;
+
+	current_size = A_SIZE_FIX(agp_bridge->current_size);
+
+	pci_read_config_dword(intel_i830_private.i830_dev,I810_GMADDR,&temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	pci_read_config_word(agp_bridge->dev,I830_GMCH_CTRL,&gmch_ctrl);
+	gmch_ctrl |= I830_GMCH_ENABLED;
+	pci_write_config_word(agp_bridge->dev,I830_GMCH_CTRL,gmch_ctrl);
+
+	writel(agp_bridge->gatt_bus_addr|I810_PGETBL_ENABLED, intel_i830_private.registers+I810_PGETBL_CTL);
+	readl(intel_i830_private.registers+I810_PGETBL_CTL);	/* PCI Posting. */
+
+	if (agp_bridge->driver->needs_scratch_page) {
+		for (i = intel_i830_private.gtt_entries; i < current_size->num_entries; i++) {
+			writel(agp_bridge->scratch_page, intel_i830_private.registers+I810_PTE_BASE+(i*4));
+			readl(intel_i830_private.registers+I810_PTE_BASE+(i*4));	/* PCI Posting. */
+		}
+	}
+
+	global_cache_flush();
+	return 0;
+}
+
+static void intel_i830_cleanup(void)
+{
+	iounmap(intel_i830_private.registers);
+}
+
+static int intel_i830_insert_entries(struct agp_memory *mem,off_t pg_start, int type)
+{
+	int i,j,num_entries;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+	num_entries = A_SIZE_FIX(temp)->num_entries;
+
+	if (pg_start < intel_i830_private.gtt_entries) {
+		printk (KERN_DEBUG PFX "pg_start == 0x%.8lx,intel_i830_private.gtt_entries == 0x%.8x\n",
+				pg_start,intel_i830_private.gtt_entries);
+
+		printk (KERN_INFO PFX "Trying to insert into local/stolen memory\n");
+		return -EINVAL;
+	}
+
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	/* The i830 can't check the GTT for entries since its read only,
+	 * depend on the caller to make the correct offset decisions.
+	 */
+
+	if ((type != 0 && type != AGP_PHYS_MEMORY) ||
+		(mem->type != 0 && mem->type != AGP_PHYS_MEMORY))
+		return -EINVAL;
+
+	global_cache_flush();	/* FIXME: Necessary ?*/
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		writel(agp_bridge->driver->mask_memory(agp_bridge,
+			mem->memory[i], mem->type),
+			intel_i830_private.registers+I810_PTE_BASE+(j*4));
+		readl(intel_i830_private.registers+I810_PTE_BASE+(j*4));	/* PCI Posting. */
+	}
+
+	global_cache_flush();
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int intel_i830_remove_entries(struct agp_memory *mem,off_t pg_start,
+				int type)
+{
+	int i;
+
+	global_cache_flush();
+
+	if (pg_start < intel_i830_private.gtt_entries) {
+		printk (KERN_INFO PFX "Trying to disable local/stolen memory\n");
+		return -EINVAL;
+	}
+
+	for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+		writel(agp_bridge->scratch_page, intel_i830_private.registers+I810_PTE_BASE+(i*4));
+		readl(intel_i830_private.registers+I810_PTE_BASE+(i*4));	/* PCI Posting. */
+	}
+
+	global_cache_flush();
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static struct agp_memory *intel_i830_alloc_by_type(size_t pg_count,int type)
+{
+	if (type == AGP_PHYS_MEMORY)
+		return alloc_agpphysmem_i8xx(pg_count, type);
+
+	/* always return NULL for other allocation types for now */
+	return NULL;
+}
+
+static int intel_i915_configure(void)
+{
+	struct aper_size_info_fixed *current_size;
+	u32 temp;
+	u16 gmch_ctrl;
+	int i;
+
+	current_size = A_SIZE_FIX(agp_bridge->current_size);
+
+	pci_read_config_dword(intel_i830_private.i830_dev, I915_GMADDR, &temp);
+
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	pci_read_config_word(agp_bridge->dev,I830_GMCH_CTRL,&gmch_ctrl);
+	gmch_ctrl |= I830_GMCH_ENABLED;
+	pci_write_config_word(agp_bridge->dev,I830_GMCH_CTRL,gmch_ctrl);
+
+	writel(agp_bridge->gatt_bus_addr|I810_PGETBL_ENABLED, intel_i830_private.registers+I810_PGETBL_CTL);
+	readl(intel_i830_private.registers+I810_PGETBL_CTL);	/* PCI Posting. */
+
+	if (agp_bridge->driver->needs_scratch_page) {
+		for (i = intel_i830_private.gtt_entries; i < current_size->num_entries; i++) {
+			writel(agp_bridge->scratch_page, intel_i830_private.gtt+i);
+			readl(intel_i830_private.gtt+i);	/* PCI Posting. */
+		}
+	}
+
+	global_cache_flush();
+	return 0;
+}
+
+static void intel_i915_cleanup(void)
+{
+	iounmap(intel_i830_private.gtt);
+	iounmap(intel_i830_private.registers);
+}
+
+static int intel_i915_insert_entries(struct agp_memory *mem,off_t pg_start,
+				int type)
+{
+	int i,j,num_entries;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+	num_entries = A_SIZE_FIX(temp)->num_entries;
+
+	if (pg_start < intel_i830_private.gtt_entries) {
+		printk (KERN_DEBUG PFX "pg_start == 0x%.8lx,intel_i830_private.gtt_entries == 0x%.8x\n",
+				pg_start,intel_i830_private.gtt_entries);
+
+		printk (KERN_INFO PFX "Trying to insert into local/stolen memory\n");
+		return -EINVAL;
+	}
+
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	/* The i830 can't check the GTT for entries since its read only,
+	 * depend on the caller to make the correct offset decisions.
+	 */
+
+	if ((type != 0 && type != AGP_PHYS_MEMORY) ||
+		(mem->type != 0 && mem->type != AGP_PHYS_MEMORY))
+		return -EINVAL;
+
+	global_cache_flush();
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		writel(agp_bridge->driver->mask_memory(agp_bridge,
+			mem->memory[i], mem->type), intel_i830_private.gtt+j);
+		readl(intel_i830_private.gtt+j);	/* PCI Posting. */
+	}
+
+	global_cache_flush();
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int intel_i915_remove_entries(struct agp_memory *mem,off_t pg_start,
+				int type)
+{
+	int i;
+
+	global_cache_flush();
+
+	if (pg_start < intel_i830_private.gtt_entries) {
+		printk (KERN_INFO PFX "Trying to disable local/stolen memory\n");
+		return -EINVAL;
+	}
+
+	for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+		writel(agp_bridge->scratch_page, intel_i830_private.gtt+i);
+		readl(intel_i830_private.gtt+i);
+	}
+
+	global_cache_flush();
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int intel_i915_fetch_size(void)
+{
+	struct aper_size_info_fixed *values;
+	u32 temp, offset = 0;
+
+#define I915_256MB_ADDRESS_MASK (1<<27)
+
+	values = A_SIZE_FIX(agp_bridge->driver->aperture_sizes);
+
+	pci_read_config_dword(intel_i830_private.i830_dev, I915_GMADDR, &temp);
+	if (temp & I915_256MB_ADDRESS_MASK)
+		offset = 0;	/* 128MB aperture */
+	else
+		offset = 2;	/* 256MB aperture */
+	agp_bridge->previous_size = agp_bridge->current_size = (void *)(values + offset);
+	return values[offset].size;
+}
+
+/* The intel i915 automatically initializes the agp aperture during POST.
+ * Use the memory already set aside for in the GTT.
+ */
+static int intel_i915_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	int page_order;
+	struct aper_size_info_fixed *size;
+	int num_entries;
+	u32 temp, temp2;
+
+	size = agp_bridge->current_size;
+	page_order = size->page_order;
+	num_entries = size->num_entries;
+	agp_bridge->gatt_table_real = NULL;
+
+	pci_read_config_dword(intel_i830_private.i830_dev, I915_MMADDR, &temp);
+	pci_read_config_dword(intel_i830_private.i830_dev, I915_PTEADDR,&temp2);
+
+	intel_i830_private.gtt = ioremap(temp2, 256 * 1024);
+	if (!intel_i830_private.gtt)
+		return -ENOMEM;
+
+	temp &= 0xfff80000;
+
+	intel_i830_private.registers = ioremap(temp,128 * 4096);
+	if (!intel_i830_private.registers)
+		return -ENOMEM;
+
+	temp = readl(intel_i830_private.registers+I810_PGETBL_CTL) & 0xfffff000;
+	global_cache_flush();	/* FIXME: ? */
+
+	/* we have to call this as early as possible after the MMIO base address is known */
+	intel_i830_init_gtt_entries();
+
+	agp_bridge->gatt_table = NULL;
+
+	agp_bridge->gatt_bus_addr = temp;
+
+	return 0;
+}
+
+static int intel_fetch_size(void)
+{
+	int i;
+	u16 temp;
+	struct aper_size_info_16 *values;
+
+	pci_read_config_word(agp_bridge->dev, INTEL_APSIZE, &temp);
+	values = A_SIZE_16(agp_bridge->driver->aperture_sizes);
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size = agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+static int __intel_8xx_fetch_size(u8 temp)
+{
+	int i;
+	struct aper_size_info_8 *values;
+
+	values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+				agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+	return 0;
+}
+
+static int intel_8xx_fetch_size(void)
+{
+	u8 temp;
+
+	pci_read_config_byte(agp_bridge->dev, INTEL_APSIZE, &temp);
+	return __intel_8xx_fetch_size(temp);
+}
+
+static int intel_815_fetch_size(void)
+{
+	u8 temp;
+
+	/* Intel 815 chipsets have a _weird_ APSIZE register with only
+	 * one non-reserved bit, so mask the others out ... */
+	pci_read_config_byte(agp_bridge->dev, INTEL_APSIZE, &temp);
+	temp &= (1 << 3);
+
+	return __intel_8xx_fetch_size(temp);
+}
+
+static void intel_tlbflush(struct agp_memory *mem)
+{
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2200);
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2280);
+}
+
+
+static void intel_8xx_tlbflush(struct agp_memory *mem)
+{
+	u32 temp;
+	pci_read_config_dword(agp_bridge->dev, INTEL_AGPCTRL, &temp);
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, temp & ~(1 << 7));
+	pci_read_config_dword(agp_bridge->dev, INTEL_AGPCTRL, &temp);
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, temp | (1 << 7));
+}
+
+
+static void intel_cleanup(void)
+{
+	u16 temp;
+	struct aper_size_info_16 *previous_size;
+
+	previous_size = A_SIZE_16(agp_bridge->previous_size);
+	pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp);
+	pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG, temp & ~(1 << 9));
+	pci_write_config_word(agp_bridge->dev, INTEL_APSIZE, previous_size->size_value);
+}
+
+
+static void intel_8xx_cleanup(void)
+{
+	u16 temp;
+	struct aper_size_info_8 *previous_size;
+
+	previous_size = A_SIZE_8(agp_bridge->previous_size);
+	pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp);
+	pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG, temp & ~(1 << 9));
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, previous_size->size_value);
+}
+
+
+static int intel_configure(void)
+{
+	u32 temp;
+	u16 temp2;
+	struct aper_size_info_16 *current_size;
+
+	current_size = A_SIZE_16(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_word(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture base */
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x2280);
+
+	/* paccfg/nbxcfg */
+	pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp2);
+	pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG,
+			(temp2 & ~(1 << 10)) | (1 << 9));
+	/* clear any possible error conditions */
+	pci_write_config_byte(agp_bridge->dev, INTEL_ERRSTS + 1, 7);
+	return 0;
+}
+
+static int intel_815_configure(void)
+{
+	u32 temp, addr;
+	u8 temp2;
+	struct aper_size_info_8 *current_size;
+
+	/* attbase - aperture base */
+	/* the Intel 815 chipset spec. says that bits 29-31 in the
+	* ATTBASE register are reserved -> try not to write them */
+	if (agp_bridge->gatt_bus_addr & INTEL_815_ATTBASE_MASK) {
+		printk (KERN_EMERG PFX "gatt bus addr too high");
+		return -EINVAL;
+	}
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE,
+			current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	pci_read_config_dword(agp_bridge->dev, INTEL_ATTBASE, &addr);
+	addr &= INTEL_815_ATTBASE_MASK;
+	addr |= agp_bridge->gatt_bus_addr;
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+	/* apcont */
+	pci_read_config_byte(agp_bridge->dev, INTEL_815_APCONT, &temp2);
+	pci_write_config_byte(agp_bridge->dev, INTEL_815_APCONT, temp2 | (1 << 1));
+
+	/* clear any possible error conditions */
+	/* Oddness : this chipset seems to have no ERRSTS register ! */
+	return 0;
+}
+
+static void intel_820_tlbflush(struct agp_memory *mem)
+{
+	return;
+}
+
+static void intel_820_cleanup(void)
+{
+	u8 temp;
+	struct aper_size_info_8 *previous_size;
+
+	previous_size = A_SIZE_8(agp_bridge->previous_size);
+	pci_read_config_byte(agp_bridge->dev, INTEL_I820_RDCR, &temp);
+	pci_write_config_byte(agp_bridge->dev, INTEL_I820_RDCR,
+			temp & ~(1 << 1));
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE,
+			previous_size->size_value);
+}
+
+
+static int intel_820_configure(void)
+{
+	u32 temp;
+	u8 temp2;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture base */
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+	/* global enable aperture access */
+	/* This flag is not accessed through MCHCFG register as in */
+	/* i850 chipset. */
+	pci_read_config_byte(agp_bridge->dev, INTEL_I820_RDCR, &temp2);
+	pci_write_config_byte(agp_bridge->dev, INTEL_I820_RDCR, temp2 | (1 << 1));
+	/* clear any possible AGP-related error conditions */
+	pci_write_config_word(agp_bridge->dev, INTEL_I820_ERRSTS, 0x001c);
+	return 0;
+}
+
+static int intel_840_configure(void)
+{
+	u32 temp;
+	u16 temp2;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture base */
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+	/* mcgcfg */
+	pci_read_config_word(agp_bridge->dev, INTEL_I840_MCHCFG, &temp2);
+	pci_write_config_word(agp_bridge->dev, INTEL_I840_MCHCFG, temp2 | (1 << 9));
+	/* clear any possible error conditions */
+	pci_write_config_word(agp_bridge->dev, INTEL_I840_ERRSTS, 0xc000);
+	return 0;
+}
+
+static int intel_845_configure(void)
+{
+	u32 temp;
+	u8 temp2;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture base */
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+	/* agpm */
+	pci_read_config_byte(agp_bridge->dev, INTEL_I845_AGPM, &temp2);
+	pci_write_config_byte(agp_bridge->dev, INTEL_I845_AGPM, temp2 | (1 << 1));
+	/* clear any possible error conditions */
+	pci_write_config_word(agp_bridge->dev, INTEL_I845_ERRSTS, 0x001c);
+	return 0;
+}
+
+static int intel_850_configure(void)
+{
+	u32 temp;
+	u16 temp2;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture base */
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+	/* mcgcfg */
+	pci_read_config_word(agp_bridge->dev, INTEL_I850_MCHCFG, &temp2);
+	pci_write_config_word(agp_bridge->dev, INTEL_I850_MCHCFG, temp2 | (1 << 9));
+	/* clear any possible AGP-related error conditions */
+	pci_write_config_word(agp_bridge->dev, INTEL_I850_ERRSTS, 0x001c);
+	return 0;
+}
+
+static int intel_860_configure(void)
+{
+	u32 temp;
+	u16 temp2;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture base */
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+	/* mcgcfg */
+	pci_read_config_word(agp_bridge->dev, INTEL_I860_MCHCFG, &temp2);
+	pci_write_config_word(agp_bridge->dev, INTEL_I860_MCHCFG, temp2 | (1 << 9));
+	/* clear any possible AGP-related error conditions */
+	pci_write_config_word(agp_bridge->dev, INTEL_I860_ERRSTS, 0xf700);
+	return 0;
+}
+
+static int intel_830mp_configure(void)
+{
+	u32 temp;
+	u16 temp2;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture base */
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+	/* gmch */
+	pci_read_config_word(agp_bridge->dev, INTEL_NBXCFG, &temp2);
+	pci_write_config_word(agp_bridge->dev, INTEL_NBXCFG, temp2 | (1 << 9));
+	/* clear any possible AGP-related error conditions */
+	pci_write_config_word(agp_bridge->dev, INTEL_I830_ERRSTS, 0x1c);
+	return 0;
+}
+
+static int intel_7505_configure(void)
+{
+	u32 temp;
+	u16 temp2;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, INTEL_APSIZE, current_size->size_value);
+
+	/* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture base */
+	pci_write_config_dword(agp_bridge->dev, INTEL_ATTBASE, agp_bridge->gatt_bus_addr);
+
+	/* agpctrl */
+	pci_write_config_dword(agp_bridge->dev, INTEL_AGPCTRL, 0x0000);
+
+	/* mchcfg */
+	pci_read_config_word(agp_bridge->dev, INTEL_I7505_MCHCFG, &temp2);
+	pci_write_config_word(agp_bridge->dev, INTEL_I7505_MCHCFG, temp2 | (1 << 9));
+
+	return 0;
+}
+
+/* Setup function */
+static struct gatt_mask intel_generic_masks[] =
+{
+	{.mask = 0x00000017, .type = 0}
+};
+
+static struct aper_size_info_8 intel_815_sizes[2] =
+{
+	{64, 16384, 4, 0},
+	{32, 8192, 3, 8},
+};
+
+static struct aper_size_info_8 intel_8xx_sizes[7] =
+{
+	{256, 65536, 6, 0},
+	{128, 32768, 5, 32},
+	{64, 16384, 4, 48},
+	{32, 8192, 3, 56},
+	{16, 4096, 2, 60},
+	{8, 2048, 1, 62},
+	{4, 1024, 0, 63}
+};
+
+static struct aper_size_info_16 intel_generic_sizes[7] =
+{
+	{256, 65536, 6, 0},
+	{128, 32768, 5, 32},
+	{64, 16384, 4, 48},
+	{32, 8192, 3, 56},
+	{16, 4096, 2, 60},
+	{8, 2048, 1, 62},
+	{4, 1024, 0, 63}
+};
+
+static struct aper_size_info_8 intel_830mp_sizes[4] =
+{
+	{256, 65536, 6, 0},
+	{128, 32768, 5, 32},
+	{64, 16384, 4, 48},
+	{32, 8192, 3, 56}
+};
+
+static struct agp_bridge_driver intel_generic_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_generic_sizes,
+	.size_type		= U16_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= intel_configure,
+	.fetch_size		= intel_fetch_size,
+	.cleanup		= intel_cleanup,
+	.tlb_flush		= intel_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_810_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_i810_sizes,
+	.size_type		= FIXED_APER_SIZE,
+	.num_aperture_sizes	= 2,
+	.needs_scratch_page	= TRUE,
+	.configure		= intel_i810_configure,
+	.fetch_size		= intel_i810_fetch_size,
+	.cleanup		= intel_i810_cleanup,
+	.tlb_flush		= intel_i810_tlbflush,
+	.mask_memory		= intel_i810_mask_memory,
+	.masks			= intel_i810_masks,
+	.agp_enable		= intel_i810_agp_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= intel_i810_insert_entries,
+	.remove_memory		= intel_i810_remove_entries,
+	.alloc_by_type		= intel_i810_alloc_by_type,
+	.free_by_type		= intel_i810_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_815_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_815_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 2,
+	.configure		= intel_815_configure,
+	.fetch_size		= intel_815_fetch_size,
+	.cleanup		= intel_8xx_cleanup,
+	.tlb_flush		= intel_8xx_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_830_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_i830_sizes,
+	.size_type		= FIXED_APER_SIZE,
+	.num_aperture_sizes	= 3,
+	.needs_scratch_page	= TRUE,
+	.configure		= intel_i830_configure,
+	.fetch_size		= intel_i830_fetch_size,
+	.cleanup		= intel_i830_cleanup,
+	.tlb_flush		= intel_i810_tlbflush,
+	.mask_memory		= intel_i810_mask_memory,
+	.masks			= intel_i810_masks,
+	.agp_enable		= intel_i810_agp_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= intel_i830_create_gatt_table,
+	.free_gatt_table	= intel_i830_free_gatt_table,
+	.insert_memory		= intel_i830_insert_entries,
+	.remove_memory		= intel_i830_remove_entries,
+	.alloc_by_type		= intel_i830_alloc_by_type,
+	.free_by_type		= intel_i810_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_820_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_8xx_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= intel_820_configure,
+	.fetch_size		= intel_8xx_fetch_size,
+	.cleanup		= intel_820_cleanup,
+	.tlb_flush		= intel_820_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_830mp_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_830mp_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 4,
+	.configure		= intel_830mp_configure,
+	.fetch_size		= intel_8xx_fetch_size,
+	.cleanup		= intel_8xx_cleanup,
+	.tlb_flush		= intel_8xx_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_840_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_8xx_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= intel_840_configure,
+	.fetch_size		= intel_8xx_fetch_size,
+	.cleanup		= intel_8xx_cleanup,
+	.tlb_flush		= intel_8xx_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_845_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_8xx_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= intel_845_configure,
+	.fetch_size		= intel_8xx_fetch_size,
+	.cleanup		= intel_8xx_cleanup,
+	.tlb_flush		= intel_8xx_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_850_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_8xx_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= intel_850_configure,
+	.fetch_size		= intel_8xx_fetch_size,
+	.cleanup		= intel_8xx_cleanup,
+	.tlb_flush		= intel_8xx_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_860_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_8xx_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= intel_860_configure,
+	.fetch_size		= intel_8xx_fetch_size,
+	.cleanup		= intel_8xx_cleanup,
+	.tlb_flush		= intel_8xx_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_bridge_driver intel_915_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_i830_sizes,
+	.size_type		= FIXED_APER_SIZE,
+	.num_aperture_sizes	= 3,
+	.needs_scratch_page	= TRUE,
+	.configure		= intel_i915_configure,
+	.fetch_size		= intel_i915_fetch_size,
+	.cleanup		= intel_i915_cleanup,
+	.tlb_flush		= intel_i810_tlbflush,
+	.mask_memory		= intel_i810_mask_memory,
+	.masks			= intel_i810_masks,
+	.agp_enable		= intel_i810_agp_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= intel_i915_create_gatt_table,
+	.free_gatt_table	= intel_i830_free_gatt_table,
+	.insert_memory		= intel_i915_insert_entries,
+	.remove_memory		= intel_i915_remove_entries,
+	.alloc_by_type		= intel_i830_alloc_by_type,
+	.free_by_type		= intel_i810_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+
+static struct agp_bridge_driver intel_7505_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= intel_8xx_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= intel_7505_configure,
+	.fetch_size		= intel_8xx_fetch_size,
+	.cleanup		= intel_8xx_cleanup,
+	.tlb_flush		= intel_8xx_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= intel_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static int find_i810(u16 device)
+{
+	struct pci_dev *i810_dev;
+
+	i810_dev = pci_get_device(PCI_VENDOR_ID_INTEL, device, NULL);
+	if (!i810_dev)
+		return 0;
+	intel_i810_private.i810_dev = i810_dev;
+	return 1;
+}
+
+static int find_i830(u16 device)
+{
+	struct pci_dev *i830_dev;
+
+	i830_dev = pci_get_device(PCI_VENDOR_ID_INTEL, device, NULL);
+	if (i830_dev && PCI_FUNC(i830_dev->devfn) != 0) {
+		i830_dev = pci_get_device(PCI_VENDOR_ID_INTEL,
+				device, i830_dev);
+	}
+
+	if (!i830_dev)
+		return 0;
+
+	intel_i830_private.i830_dev = i830_dev;
+	return 1;
+}
+
+static int __devinit agp_intel_probe(struct pci_dev *pdev,
+				     const struct pci_device_id *ent)
+{
+	struct agp_bridge_data *bridge;
+	char *name = "(unknown)";
+	u8 cap_ptr = 0;
+	struct resource *r;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	switch (pdev->device) {
+	case PCI_DEVICE_ID_INTEL_82443LX_0:
+		bridge->driver = &intel_generic_driver;
+		name = "440LX";
+		break;
+	case PCI_DEVICE_ID_INTEL_82443BX_0:
+		bridge->driver = &intel_generic_driver;
+		name = "440BX";
+		break;
+	case PCI_DEVICE_ID_INTEL_82443GX_0:
+		bridge->driver = &intel_generic_driver;
+		name = "440GX";
+		break;
+	case PCI_DEVICE_ID_INTEL_82810_MC1:
+		name = "i810";
+		if (!find_i810(PCI_DEVICE_ID_INTEL_82810_IG1))
+			goto fail;
+		bridge->driver = &intel_810_driver;
+		break;
+	case PCI_DEVICE_ID_INTEL_82810_MC3:
+		name = "i810 DC100";
+		if (!find_i810(PCI_DEVICE_ID_INTEL_82810_IG3))
+			goto fail;
+		bridge->driver = &intel_810_driver;
+		break;
+	case PCI_DEVICE_ID_INTEL_82810E_MC:
+		name = "i810 E";
+		if (!find_i810(PCI_DEVICE_ID_INTEL_82810E_IG))
+			goto fail;
+		bridge->driver = &intel_810_driver;
+		break;
+	 case PCI_DEVICE_ID_INTEL_82815_MC:
+		/*
+		 * The i815 can operate either as an i810 style
+		 * integrated device, or as an AGP4X motherboard.
+		 */
+		if (find_i810(PCI_DEVICE_ID_INTEL_82815_CGC))
+			bridge->driver = &intel_810_driver;
+		else
+			bridge->driver = &intel_815_driver;
+		name = "i815";
+		break;
+	case PCI_DEVICE_ID_INTEL_82820_HB:
+	case PCI_DEVICE_ID_INTEL_82820_UP_HB:
+		bridge->driver = &intel_820_driver;
+		name = "i820";
+		break;
+	case PCI_DEVICE_ID_INTEL_82830_HB:
+		if (find_i830(PCI_DEVICE_ID_INTEL_82830_CGC)) {
+			bridge->driver = &intel_830_driver;
+		} else {
+			bridge->driver = &intel_830mp_driver;
+		}
+		name = "830M";
+		break;
+	case PCI_DEVICE_ID_INTEL_82840_HB:
+		bridge->driver = &intel_840_driver;
+		name = "i840";
+		break;
+	case PCI_DEVICE_ID_INTEL_82845_HB:
+		bridge->driver = &intel_845_driver;
+		name = "i845";
+		break;
+	case PCI_DEVICE_ID_INTEL_82845G_HB:
+		if (find_i830(PCI_DEVICE_ID_INTEL_82845G_IG)) {
+			bridge->driver = &intel_830_driver;
+		} else {
+			bridge->driver = &intel_845_driver;
+		}
+		name = "845G";
+		break;
+	case PCI_DEVICE_ID_INTEL_82850_HB:
+		bridge->driver = &intel_850_driver;
+		name = "i850";
+		break;
+	case PCI_DEVICE_ID_INTEL_82855PM_HB:
+		bridge->driver = &intel_845_driver;
+		name = "855PM";
+		break;
+	case PCI_DEVICE_ID_INTEL_82855GM_HB:
+		if (find_i830(PCI_DEVICE_ID_INTEL_82855GM_IG)) {
+			bridge->driver = &intel_830_driver;
+			name = "855";
+		} else {
+			bridge->driver = &intel_845_driver;
+			name = "855GM";
+		}
+		break;
+	case PCI_DEVICE_ID_INTEL_82860_HB:
+		bridge->driver = &intel_860_driver;
+		name = "i860";
+		break;
+	case PCI_DEVICE_ID_INTEL_82865_HB:
+		if (find_i830(PCI_DEVICE_ID_INTEL_82865_IG)) {
+			bridge->driver = &intel_830_driver;
+		} else {
+			bridge->driver = &intel_845_driver;
+		}
+		name = "865";
+		break;
+	case PCI_DEVICE_ID_INTEL_82875_HB:
+		bridge->driver = &intel_845_driver;
+		name = "i875";
+		break;
+	case PCI_DEVICE_ID_INTEL_82915G_HB:
+		if (find_i830(PCI_DEVICE_ID_INTEL_82915G_IG)) {
+			bridge->driver = &intel_915_driver;
+		} else {
+			bridge->driver = &intel_845_driver;
+		}
+		name = "915G";
+		break;
+	case PCI_DEVICE_ID_INTEL_82915GM_HB:
+		if (find_i830(PCI_DEVICE_ID_INTEL_82915GM_IG)) {
+			bridge->driver = &intel_915_driver;
+		} else {
+			bridge->driver = &intel_845_driver;
+		}
+		name = "915GM";
+		break;
+	case PCI_DEVICE_ID_INTEL_7505_0:
+		bridge->driver = &intel_7505_driver;
+		name = "E7505";
+		break;
+	case PCI_DEVICE_ID_INTEL_7205_0:
+		bridge->driver = &intel_7505_driver;
+		name = "E7205";
+		break;
+	default:
+		if (cap_ptr)
+			printk(KERN_WARNING PFX "Unsupported Intel chipset (device id: %04x)\n",
+			    pdev->device);
+		agp_put_bridge(bridge);
+		return -ENODEV;
+	};
+
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+
+	if (bridge->driver == &intel_810_driver)
+		bridge->dev_private_data = &intel_i810_private;
+	else if (bridge->driver == &intel_830_driver)
+		bridge->dev_private_data = &intel_i830_private;
+
+	printk(KERN_INFO PFX "Detected an Intel %s Chipset.\n", name);
+
+	/*
+	* The following fixes the case where the BIOS has "forgotten" to
+	* provide an address range for the GART.
+	* 20030610 - hamish@zot.org
+	*/
+	r = &pdev->resource[0];
+	if (!r->start && r->end) {
+		if(pci_assign_resource(pdev, 0)) {
+			printk(KERN_ERR PFX "could not assign resource 0\n");
+			agp_put_bridge(bridge);
+			return -ENODEV;
+		}
+	}
+
+	/*
+	* If the device has not been properly setup, the following will catch
+	* the problem and should stop the system from crashing.
+	* 20030610 - hamish@zot.org
+	*/
+	if (pci_enable_device(pdev)) {
+		printk(KERN_ERR PFX "Unable to Enable PCI device\n");
+		agp_put_bridge(bridge);
+		return -ENODEV;
+	}
+
+	/* Fill in the mode register */
+	if (cap_ptr) {
+		pci_read_config_dword(pdev,
+				bridge->capndx+PCI_AGP_STATUS,
+				&bridge->mode);
+	}
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+
+fail:
+	printk(KERN_ERR PFX "Detected an Intel %s chipset, "
+		"but could not find the secondary device.\n", name);
+	agp_put_bridge(bridge);
+	return -ENODEV;
+}
+
+static void __devexit agp_intel_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+
+	if (intel_i810_private.i810_dev)
+		pci_dev_put(intel_i810_private.i810_dev);
+	if (intel_i830_private.i830_dev)
+		pci_dev_put(intel_i830_private.i830_dev);
+
+	agp_put_bridge(bridge);
+}
+
+static int agp_intel_resume(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	pci_restore_state(pdev);
+
+	if (bridge->driver == &intel_generic_driver)
+		intel_configure();
+	else if (bridge->driver == &intel_850_driver)
+		intel_850_configure();
+	else if (bridge->driver == &intel_845_driver)
+		intel_845_configure();
+	else if (bridge->driver == &intel_830mp_driver)
+		intel_830mp_configure();
+	else if (bridge->driver == &intel_915_driver)
+		intel_i915_configure();
+	else if (bridge->driver == &intel_830_driver)
+		intel_i830_configure();
+	else if (bridge->driver == &intel_810_driver)
+		intel_i810_configure();
+
+	return 0;
+}
+
+static struct pci_device_id agp_intel_pci_table[] = {
+#define ID(x)						\
+	{						\
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),	\
+	.class_mask	= ~0,				\
+	.vendor		= PCI_VENDOR_ID_INTEL,		\
+	.device		= x,				\
+	.subvendor	= PCI_ANY_ID,			\
+	.subdevice	= PCI_ANY_ID,			\
+	}
+	ID(PCI_DEVICE_ID_INTEL_82443LX_0),
+	ID(PCI_DEVICE_ID_INTEL_82443BX_0),
+	ID(PCI_DEVICE_ID_INTEL_82443GX_0),
+	ID(PCI_DEVICE_ID_INTEL_82810_MC1),
+	ID(PCI_DEVICE_ID_INTEL_82810_MC3),
+	ID(PCI_DEVICE_ID_INTEL_82810E_MC),
+	ID(PCI_DEVICE_ID_INTEL_82815_MC),
+	ID(PCI_DEVICE_ID_INTEL_82820_HB),
+	ID(PCI_DEVICE_ID_INTEL_82820_UP_HB),
+	ID(PCI_DEVICE_ID_INTEL_82830_HB),
+	ID(PCI_DEVICE_ID_INTEL_82840_HB),
+	ID(PCI_DEVICE_ID_INTEL_82845_HB),
+	ID(PCI_DEVICE_ID_INTEL_82845G_HB),
+	ID(PCI_DEVICE_ID_INTEL_82850_HB),
+	ID(PCI_DEVICE_ID_INTEL_82855PM_HB),
+	ID(PCI_DEVICE_ID_INTEL_82855GM_HB),
+	ID(PCI_DEVICE_ID_INTEL_82860_HB),
+	ID(PCI_DEVICE_ID_INTEL_82865_HB),
+	ID(PCI_DEVICE_ID_INTEL_82875_HB),
+	ID(PCI_DEVICE_ID_INTEL_7505_0),
+	ID(PCI_DEVICE_ID_INTEL_7205_0),
+	ID(PCI_DEVICE_ID_INTEL_82915G_HB),
+	ID(PCI_DEVICE_ID_INTEL_82915GM_HB),
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_intel_pci_table);
+
+static struct pci_driver agp_intel_pci_driver = {
+	.name		= "agpgart-intel",
+	.id_table	= agp_intel_pci_table,
+	.probe		= agp_intel_probe,
+	.remove		= __devexit_p(agp_intel_remove),
+	.resume		= agp_intel_resume,
+};
+
+static int __init agp_intel_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_intel_pci_driver);
+}
+
+static void __exit agp_intel_cleanup(void)
+{
+	pci_unregister_driver(&agp_intel_pci_driver);
+}
+
+module_init(agp_intel_init);
+module_exit(agp_intel_cleanup);
+
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/isoch.c b/drivers/char/agp/isoch.c
new file mode 100644
index 0000000..c9ac731
--- /dev/null
+++ b/drivers/char/agp/isoch.c
@@ -0,0 +1,470 @@
+/*
+ * Setup routines for AGP 3.5 compliant bridges.
+ */
+
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/agp_backend.h>
+#include <linux/module.h>
+
+#include "agp.h"
+
+/* Generic AGP 3.5 enabling routines */
+
+struct agp_3_5_dev {
+	struct list_head list;
+	u8 capndx;
+	u32 maxbw;
+	struct pci_dev *dev;
+};
+
+static void agp_3_5_dev_list_insert(struct list_head *head, struct list_head *new)
+{
+	struct agp_3_5_dev *cur, *n = list_entry(new, struct agp_3_5_dev, list);
+	struct list_head *pos;
+
+	list_for_each(pos, head) {
+		cur = list_entry(pos, struct agp_3_5_dev, list);
+		if(cur->maxbw > n->maxbw)
+			break;
+	}
+	list_add_tail(new, pos);
+}
+
+static void agp_3_5_dev_list_sort(struct agp_3_5_dev *list, unsigned int ndevs)
+{
+	struct agp_3_5_dev *cur;
+	struct pci_dev *dev;
+	struct list_head *pos, *tmp, *head = &list->list, *start = head->next;
+	u32 nistat;
+
+	INIT_LIST_HEAD(head);
+
+	for (pos=start; pos!=head; ) {
+		cur = list_entry(pos, struct agp_3_5_dev, list);
+		dev = cur->dev;
+
+		pci_read_config_dword(dev, cur->capndx+AGPNISTAT, &nistat);
+		cur->maxbw = (nistat >> 16) & 0xff;
+
+		tmp = pos;
+		pos = pos->next;
+		agp_3_5_dev_list_insert(head, tmp);
+	}
+}
+
+/* 
+ * Initialize all isochronous transfer parameters for an AGP 3.0 
+ * node (i.e. a host bridge in combination with the adapters 
+ * lying behind it...)
+ */
+
+static int agp_3_5_isochronous_node_enable(struct agp_bridge_data *bridge,
+		struct agp_3_5_dev *dev_list, unsigned int ndevs)
+{
+	/*
+	 * Convenience structure to make the calculations clearer
+	 * here.  The field names come straight from the AGP 3.0 spec.
+	 */
+	struct isoch_data {
+		u32 maxbw;
+		u32 n;
+		u32 y;
+		u32 l;
+		u32 rq;
+		struct agp_3_5_dev *dev;
+	};
+
+	struct pci_dev *td = bridge->dev, *dev;
+	struct list_head *head = &dev_list->list, *pos;
+	struct agp_3_5_dev *cur;
+	struct isoch_data *master, target;
+	unsigned int cdev = 0;
+	u32 mnistat, tnistat, tstatus, mcmd;
+	u16 tnicmd, mnicmd;
+	u8 mcapndx;
+	u32 tot_bw = 0, tot_n = 0, tot_rq = 0, y_max, rq_isoch, rq_async;
+	u32 step, rem, rem_isoch, rem_async;
+	int ret = 0;
+
+	/*
+	 * We'll work with an array of isoch_data's (one for each
+	 * device in dev_list) throughout this function.
+	 */
+	if ((master = kmalloc(ndevs * sizeof(*master), GFP_KERNEL)) == NULL) {
+		ret = -ENOMEM;
+		goto get_out;
+	}
+
+	/*
+	 * Sort the device list by maxbw.  We need to do this because the
+	 * spec suggests that the devices with the smallest requirements
+	 * have their resources allocated first, with all remaining resources
+	 * falling to the device with the largest requirement.
+	 *
+	 * We don't exactly do this, we divide target resources by ndevs
+	 * and split them amongst the AGP 3.0 devices.  The remainder of such
+	 * division operations are dropped on the last device, sort of like
+	 * the spec mentions it should be done.
+	 *
+	 * We can't do this sort when we initially construct the dev_list
+	 * because we don't know until this function whether isochronous
+	 * transfers are enabled and consequently whether maxbw will mean
+	 * anything.
+	 */
+	agp_3_5_dev_list_sort(dev_list, ndevs);
+
+	pci_read_config_dword(td, bridge->capndx+AGPNISTAT, &tnistat);
+	pci_read_config_dword(td, bridge->capndx+AGPSTAT, &tstatus);
+
+	/* Extract power-on defaults from the target */
+	target.maxbw = (tnistat >> 16) & 0xff;
+	target.n     = (tnistat >> 8)  & 0xff;
+	target.y     = (tnistat >> 6)  & 0x3;
+	target.l     = (tnistat >> 3)  & 0x7;
+	target.rq    = (tstatus >> 24) & 0xff;
+
+	y_max = target.y;
+
+	/*
+	 * Extract power-on defaults for each device in dev_list.  Along
+	 * the way, calculate the total isochronous bandwidth required
+	 * by these devices and the largest requested payload size.
+	 */
+	list_for_each(pos, head) {
+		cur = list_entry(pos, struct agp_3_5_dev, list);
+		dev = cur->dev;
+
+		mcapndx = cur->capndx;
+
+		pci_read_config_dword(dev, cur->capndx+AGPNISTAT, &mnistat);
+
+		master[cdev].maxbw = (mnistat >> 16) & 0xff;
+		master[cdev].n     = (mnistat >> 8)  & 0xff;
+		master[cdev].y     = (mnistat >> 6)  & 0x3;
+		master[cdev].dev   = cur;
+
+		tot_bw += master[cdev].maxbw;
+		y_max = max(y_max, master[cdev].y);
+
+		cdev++;
+	}
+
+	/* Check if this configuration has any chance of working */
+	if (tot_bw > target.maxbw) {
+		printk(KERN_ERR PFX "isochronous bandwidth required "
+			"by AGP 3.0 devices exceeds that which is supported by "
+			"the AGP 3.0 bridge!\n");
+		ret = -ENODEV;
+		goto free_and_exit;
+	}
+
+	target.y = y_max;
+
+	/*
+	 * Write the calculated payload size into the target's NICMD
+	 * register.  Doing this directly effects the ISOCH_N value
+	 * in the target's NISTAT register, so we need to do this now
+	 * to get an accurate value for ISOCH_N later.
+	 */
+	pci_read_config_word(td, bridge->capndx+AGPNICMD, &tnicmd);
+	tnicmd &= ~(0x3 << 6);
+	tnicmd |= target.y << 6;
+	pci_write_config_word(td, bridge->capndx+AGPNICMD, tnicmd);
+
+	/* Reread the target's ISOCH_N */
+	pci_read_config_dword(td, bridge->capndx+AGPNISTAT, &tnistat);
+	target.n = (tnistat >> 8) & 0xff;
+
+	/* Calculate the minimum ISOCH_N needed by each master */
+	for (cdev=0; cdev<ndevs; cdev++) {
+		master[cdev].y = target.y;
+		master[cdev].n = master[cdev].maxbw / (master[cdev].y + 1);
+
+		tot_n += master[cdev].n;
+	}
+
+	/* Exit if the minimal ISOCH_N allocation among the masters is more
+	 * than the target can handle. */
+	if (tot_n > target.n) {
+		printk(KERN_ERR PFX "number of isochronous "
+			"transactions per period required by AGP 3.0 devices "
+			"exceeds that which is supported by the AGP 3.0 "
+			"bridge!\n");
+		ret = -ENODEV;
+		goto free_and_exit;
+	}
+
+	/* Calculate left over ISOCH_N capability in the target.  We'll give
+	 * this to the hungriest device (as per the spec) */
+	rem  = target.n - tot_n;
+
+	/* 
+	 * Calculate the minimum isochronous RQ depth needed by each master.
+	 * Along the way, distribute the extra ISOCH_N capability calculated
+	 * above.
+	 */
+	for (cdev=0; cdev<ndevs; cdev++) {
+		/*
+		 * This is a little subtle.  If ISOCH_Y > 64B, then ISOCH_Y
+		 * byte isochronous writes will be broken into 64B pieces.
+		 * This means we need to budget more RQ depth to account for
+		 * these kind of writes (each isochronous write is actually
+		 * many writes on the AGP bus).
+		 */
+		master[cdev].rq = master[cdev].n;
+		if(master[cdev].y > 0x1)
+			master[cdev].rq *= (1 << (master[cdev].y - 1));
+
+		tot_rq += master[cdev].rq;
+
+		if (cdev == ndevs-1)
+			master[cdev].n += rem;
+	}
+
+	/* Figure the number of isochronous and asynchronous RQ slots the
+	 * target is providing. */
+	rq_isoch = (target.y > 0x1) ? target.n * (1 << (target.y - 1)) : target.n;
+	rq_async = target.rq - rq_isoch;
+
+	/* Exit if the minimal RQ needs of the masters exceeds what the target
+	 * can provide. */
+	if (tot_rq > rq_isoch) {
+		printk(KERN_ERR PFX "number of request queue slots "
+			"required by the isochronous bandwidth requested by "
+			"AGP 3.0 devices exceeds the number provided by the "
+			"AGP 3.0 bridge!\n");
+		ret = -ENODEV;
+		goto free_and_exit;
+	}
+
+	/* Calculate asynchronous RQ capability in the target (per master) as
+	 * well as the total number of leftover isochronous RQ slots. */
+	step      = rq_async / ndevs;
+	rem_async = step + (rq_async % ndevs);
+	rem_isoch = rq_isoch - tot_rq;
+
+	/* Distribute the extra RQ slots calculated above and write our
+	 * isochronous settings out to the actual devices. */
+	for (cdev=0; cdev<ndevs; cdev++) {
+		cur = master[cdev].dev;
+		dev = cur->dev;
+
+		mcapndx = cur->capndx;
+
+		master[cdev].rq += (cdev == ndevs - 1)
+		              ? (rem_async + rem_isoch) : step;
+
+		pci_read_config_word(dev, cur->capndx+AGPNICMD, &mnicmd);
+		pci_read_config_dword(dev, cur->capndx+AGPCMD, &mcmd);
+
+		mnicmd &= ~(0xff << 8);
+		mnicmd &= ~(0x3  << 6);
+		mcmd   &= ~(0xff << 24);
+
+		mnicmd |= master[cdev].n  << 8;
+		mnicmd |= master[cdev].y  << 6;
+		mcmd   |= master[cdev].rq << 24;
+
+		pci_write_config_dword(dev, cur->capndx+AGPCMD, mcmd);
+		pci_write_config_word(dev, cur->capndx+AGPNICMD, mnicmd);
+	}
+
+free_and_exit:
+	kfree(master);
+
+get_out:
+	return ret;
+}
+
+/*
+ * This function basically allocates request queue slots among the
+ * AGP 3.0 systems in nonisochronous nodes.  The algorithm is
+ * pretty stupid, divide the total number of RQ slots provided by the
+ * target by ndevs.  Distribute this many slots to each AGP 3.0 device,
+ * giving any left over slots to the last device in dev_list.
+ */
+static void agp_3_5_nonisochronous_node_enable(struct agp_bridge_data *bridge,
+		struct agp_3_5_dev *dev_list, unsigned int ndevs)
+{
+	struct agp_3_5_dev *cur;
+	struct list_head *head = &dev_list->list, *pos;
+	u32 tstatus, mcmd;
+	u32 trq, mrq, rem;
+	unsigned int cdev = 0;
+
+	pci_read_config_dword(bridge->dev, bridge->capndx+AGPSTAT, &tstatus);
+
+	trq = (tstatus >> 24) & 0xff;
+	mrq = trq / ndevs;
+
+	rem = mrq + (trq % ndevs);
+
+	for (pos=head->next; cdev<ndevs; cdev++, pos=pos->next) {
+		cur = list_entry(pos, struct agp_3_5_dev, list);
+
+		pci_read_config_dword(cur->dev, cur->capndx+AGPCMD, &mcmd);
+		mcmd &= ~(0xff << 24);
+		mcmd |= ((cdev == ndevs - 1) ? rem : mrq) << 24;
+		pci_write_config_dword(cur->dev, cur->capndx+AGPCMD, mcmd);
+	}
+}
+
+/*
+ * Fully configure and enable an AGP 3.0 host bridge and all the devices
+ * lying behind it.
+ */
+int agp_3_5_enable(struct agp_bridge_data *bridge)
+{
+	struct pci_dev *td = bridge->dev, *dev = NULL;
+	u8 mcapndx;
+	u32 isoch, arqsz;
+	u32 tstatus, mstatus, ncapid;
+	u32 mmajor;
+	u16 mpstat;
+	struct agp_3_5_dev *dev_list, *cur;
+	struct list_head *head, *pos;
+	unsigned int ndevs = 0;
+	int ret = 0;
+
+	/* Extract some power-on defaults from the target */
+	pci_read_config_dword(td, bridge->capndx+AGPSTAT, &tstatus);
+	isoch     = (tstatus >> 17) & 0x1;
+	if (isoch == 0)	/* isoch xfers not available, bail out. */
+		return -ENODEV;
+
+	arqsz     = (tstatus >> 13) & 0x7;
+
+	/* 
+	 * Allocate a head for our AGP 3.5 device list
+	 * (multiple AGP v3 devices are allowed behind a single bridge). 
+	 */
+	if ((dev_list = kmalloc(sizeof(*dev_list), GFP_KERNEL)) == NULL) {
+		ret = -ENOMEM;
+		goto get_out;
+	}
+	head = &dev_list->list;
+	INIT_LIST_HEAD(head);
+
+	/* Find all AGP devices, and add them to dev_list. */
+	for_each_pci_dev(dev) {
+		mcapndx = pci_find_capability(dev, PCI_CAP_ID_AGP);
+		if (mcapndx == 0)
+			continue;
+
+		switch ((dev->class >>8) & 0xff00) {
+			case 0x0600:    /* Bridge */
+				/* Skip bridges. We should call this function for each one. */
+				continue;
+
+			case 0x0001:    /* Unclassified device */
+				/* Don't know what this is, but log it for investigation. */
+				if (mcapndx != 0) {
+					printk (KERN_INFO PFX "Wacky, found unclassified AGP device. %x:%x\n",
+						dev->vendor, dev->device);
+				}
+				continue;
+
+			case 0x0300:    /* Display controller */
+			case 0x0400:    /* Multimedia controller */
+				if((cur = kmalloc(sizeof(*cur), GFP_KERNEL)) == NULL) {
+					ret = -ENOMEM;
+					goto free_and_exit;
+				}
+				cur->dev = dev;
+
+				pos = &cur->list;
+				list_add(pos, head);
+				ndevs++;
+				continue;
+
+			default:
+				continue;
+		}
+	}
+
+	/*
+	 * Take an initial pass through the devices lying behind our host
+	 * bridge.  Make sure each one is actually an AGP 3.0 device, otherwise
+	 * exit with an error message.  Along the way store the AGP 3.0
+	 * cap_ptr for each device
+	 */
+	list_for_each(pos, head) {
+		cur = list_entry(pos, struct agp_3_5_dev, list);
+		dev = cur->dev;
+		
+		pci_read_config_word(dev, PCI_STATUS, &mpstat);
+		if ((mpstat & PCI_STATUS_CAP_LIST) == 0)
+			continue;
+
+		pci_read_config_byte(dev, PCI_CAPABILITY_LIST, &mcapndx);
+		if (mcapndx != 0) {
+			do {
+				pci_read_config_dword(dev, mcapndx, &ncapid);
+				if ((ncapid & 0xff) != 2)
+					mcapndx = (ncapid >> 8) & 0xff;
+			}
+			while (((ncapid & 0xff) != 2) && (mcapndx != 0));
+		}
+
+		if (mcapndx == 0) {
+			printk(KERN_ERR PFX "woah!  Non-AGP device "
+				"found on the secondary bus of an AGP 3.5 bridge!\n");
+			ret = -ENODEV;
+			goto free_and_exit;
+		}
+
+		mmajor = (ncapid >> AGP_MAJOR_VERSION_SHIFT) & 0xf;
+		if (mmajor < 3) {
+			printk(KERN_ERR PFX "woah!  AGP 2.0 device "
+				"found on the secondary bus of an AGP 3.5 "
+				"bridge operating with AGP 3.0 electricals!\n");
+			ret = -ENODEV;
+			goto free_and_exit;
+		}
+
+		cur->capndx = mcapndx;
+
+		pci_read_config_dword(dev, cur->capndx+AGPSTAT, &mstatus);
+
+		if (((mstatus >> 3) & 0x1) == 0) {
+			printk(KERN_ERR PFX "woah!  AGP 3.x device "
+				"not operating in AGP 3.x mode found on the "
+				"secondary bus of an AGP 3.5 bridge operating "
+				"with AGP 3.0 electricals!\n");
+			ret = -ENODEV;
+			goto free_and_exit;
+		}
+	}		
+
+	/*
+	 * Call functions to divide target resources amongst the AGP 3.0
+	 * masters.  This process is dramatically different depending on
+	 * whether isochronous transfers are supported.
+	 */
+	if (isoch) {
+		ret = agp_3_5_isochronous_node_enable(bridge, dev_list, ndevs);
+		if (ret) {
+			printk(KERN_INFO PFX "Something bad happened setting "
+			       "up isochronous xfers.  Falling back to "
+			       "non-isochronous xfer mode.\n");
+		} else {
+			goto free_and_exit;
+		}
+	}
+	agp_3_5_nonisochronous_node_enable(bridge, dev_list, ndevs);
+
+free_and_exit:
+	/* Be sure to free the dev_list */
+	for (pos=head->next; pos!=head; ) {
+		cur = list_entry(pos, struct agp_3_5_dev, list);
+
+		pos = pos->next;
+		kfree(cur);
+	}
+	kfree(dev_list);
+
+get_out:
+	return ret;
+}
+
diff --git a/drivers/char/agp/nvidia-agp.c b/drivers/char/agp/nvidia-agp.c
new file mode 100644
index 0000000..4f7a3e8
--- /dev/null
+++ b/drivers/char/agp/nvidia-agp.c
@@ -0,0 +1,424 @@
+/*
+ * Nvidia AGPGART routines.
+ * Based upon a 2.4 agpgart diff by the folks from NVIDIA, and hacked up
+ * to work in 2.5 by Dave Jones <davej@codemonkey.org.uk>
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/gfp.h>
+#include <linux/page-flags.h>
+#include <linux/mm.h>
+#include "agp.h"
+
+/* NVIDIA registers */
+#define NVIDIA_0_APSIZE		0x80
+#define NVIDIA_1_WBC		0xf0
+#define NVIDIA_2_GARTCTRL	0xd0
+#define NVIDIA_2_APBASE		0xd8
+#define NVIDIA_2_APLIMIT	0xdc
+#define NVIDIA_2_ATTBASE(i)	(0xe0 + (i) * 4)
+#define NVIDIA_3_APBASE		0x50
+#define NVIDIA_3_APLIMIT	0x54
+
+
+static struct _nvidia_private {
+	struct pci_dev *dev_1;
+	struct pci_dev *dev_2;
+	struct pci_dev *dev_3;
+	volatile u32 __iomem *aperture;
+	int num_active_entries;
+	off_t pg_offset;
+	u32 wbc_mask;
+} nvidia_private;
+
+
+static int nvidia_fetch_size(void)
+{
+	int i;
+	u8 size_value;
+	struct aper_size_info_8 *values;
+
+	pci_read_config_byte(agp_bridge->dev, NVIDIA_0_APSIZE, &size_value);
+	size_value &= 0x0f;
+	values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (size_value == values[i].size_value) {
+			agp_bridge->previous_size =
+				agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+#define SYSCFG          0xC0010010
+#define IORR_BASE0      0xC0010016
+#define IORR_MASK0      0xC0010017
+#define AMD_K7_NUM_IORR 2
+
+static int nvidia_init_iorr(u32 base, u32 size)
+{
+	u32 base_hi, base_lo;
+	u32 mask_hi, mask_lo;
+	u32 sys_hi, sys_lo;
+	u32 iorr_addr, free_iorr_addr;
+
+	/* Find the iorr that is already used for the base */
+	/* If not found, determine the uppermost available iorr */
+	free_iorr_addr = AMD_K7_NUM_IORR;
+	for(iorr_addr = 0; iorr_addr < AMD_K7_NUM_IORR; iorr_addr++) {
+		rdmsr(IORR_BASE0 + 2 * iorr_addr, base_lo, base_hi);
+		rdmsr(IORR_MASK0 + 2 * iorr_addr, mask_lo, mask_hi);
+
+		if ((base_lo & 0xfffff000) == (base & 0xfffff000))
+			break;
+
+		if ((mask_lo & 0x00000800) == 0)
+			free_iorr_addr = iorr_addr;
+	}
+	
+	if (iorr_addr >= AMD_K7_NUM_IORR) {
+		iorr_addr = free_iorr_addr;
+		if (iorr_addr >= AMD_K7_NUM_IORR)
+			return -EINVAL;
+	}
+    base_hi = 0x0;
+    base_lo = (base & ~0xfff) | 0x18;
+    mask_hi = 0xf;
+    mask_lo = ((~(size - 1)) & 0xfffff000) | 0x800;
+    wrmsr(IORR_BASE0 + 2 * iorr_addr, base_lo, base_hi);
+    wrmsr(IORR_MASK0 + 2 * iorr_addr, mask_lo, mask_hi);
+
+    rdmsr(SYSCFG, sys_lo, sys_hi);
+    sys_lo |= 0x00100000;
+    wrmsr(SYSCFG, sys_lo, sys_hi);
+
+	return 0;
+}
+
+static int nvidia_configure(void)
+{
+	int i, rc, num_dirs;
+	u32 apbase, aplimit;
+	struct aper_size_info_8 *current_size;
+	u32 temp;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, NVIDIA_0_APSIZE,
+		current_size->size_value);
+
+    /* address to map to */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &apbase);
+	apbase &= PCI_BASE_ADDRESS_MEM_MASK;
+	agp_bridge->gart_bus_addr = apbase;
+	aplimit = apbase + (current_size->size * 1024 * 1024) - 1;
+	pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_APBASE, apbase);
+	pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_APLIMIT, aplimit);
+	pci_write_config_dword(nvidia_private.dev_3, NVIDIA_3_APBASE, apbase);
+	pci_write_config_dword(nvidia_private.dev_3, NVIDIA_3_APLIMIT, aplimit);
+	if (0 != (rc = nvidia_init_iorr(apbase, current_size->size * 1024 * 1024)))
+		return rc;
+
+	/* directory size is 64k */
+	num_dirs = current_size->size / 64;
+	nvidia_private.num_active_entries = current_size->num_entries;
+	nvidia_private.pg_offset = 0;
+	if (num_dirs == 0) {
+		num_dirs = 1;
+		nvidia_private.num_active_entries /= (64 / current_size->size);
+		nvidia_private.pg_offset = (apbase & (64 * 1024 * 1024 - 1) &
+			~(current_size->size * 1024 * 1024 - 1)) / PAGE_SIZE;
+	}
+
+	/* attbase */
+	for(i = 0; i < 8; i++) {
+		pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_ATTBASE(i),
+			(agp_bridge->gatt_bus_addr + (i % num_dirs) * 64 * 1024) | 1);
+	}
+
+	/* gtlb control */
+	pci_read_config_dword(nvidia_private.dev_2, NVIDIA_2_GARTCTRL, &temp);
+	pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_GARTCTRL, temp | 0x11);
+
+	/* gart control */
+	pci_read_config_dword(agp_bridge->dev, NVIDIA_0_APSIZE, &temp);
+	pci_write_config_dword(agp_bridge->dev, NVIDIA_0_APSIZE, temp | 0x100);
+
+	/* map aperture */
+	nvidia_private.aperture =
+		(volatile u32 __iomem *) ioremap(apbase, 33 * PAGE_SIZE);
+
+	return 0;
+}
+
+static void nvidia_cleanup(void)
+{
+	struct aper_size_info_8 *previous_size;
+	u32 temp;
+
+	/* gart control */
+	pci_read_config_dword(agp_bridge->dev, NVIDIA_0_APSIZE, &temp);
+	pci_write_config_dword(agp_bridge->dev, NVIDIA_0_APSIZE, temp & ~(0x100));
+
+	/* gtlb control */
+	pci_read_config_dword(nvidia_private.dev_2, NVIDIA_2_GARTCTRL, &temp);
+	pci_write_config_dword(nvidia_private.dev_2, NVIDIA_2_GARTCTRL, temp & ~(0x11));
+
+	/* unmap aperture */
+	iounmap((void __iomem *) nvidia_private.aperture);
+
+	/* restore previous aperture size */
+	previous_size = A_SIZE_8(agp_bridge->previous_size);
+	pci_write_config_byte(agp_bridge->dev, NVIDIA_0_APSIZE,
+		previous_size->size_value);
+
+	/* restore iorr for previous aperture size */
+	nvidia_init_iorr(agp_bridge->gart_bus_addr,
+		previous_size->size * 1024 * 1024);
+}
+
+
+/*
+ * Note we can't use the generic routines, even though they are 99% the same.
+ * Aperture sizes <64M still requires a full 64k GART directory, but
+ * only use the portion of the TLB entries that correspond to the apertures
+ * alignment inside the surrounding 64M block.
+ */
+extern int agp_memory_reserved;
+
+static int nvidia_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+	int i, j;
+	
+	if ((type != 0) || (mem->type != 0))
+		return -EINVAL;
+	
+	if ((pg_start + mem->page_count) >
+		(nvidia_private.num_active_entries - agp_memory_reserved/PAGE_SIZE))
+		return -EINVAL;
+	
+	for(j = pg_start; j < (pg_start + mem->page_count); j++) {
+		if (!PGE_EMPTY(agp_bridge, readl(agp_bridge->gatt_table+nvidia_private.pg_offset+j)))
+			return -EBUSY;
+	}
+
+	if (mem->is_flushed == FALSE) {
+		global_cache_flush();
+		mem->is_flushed = TRUE;
+	}
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		writel(agp_bridge->driver->mask_memory(agp_bridge,
+			mem->memory[i], mem->type),
+			agp_bridge->gatt_table+nvidia_private.pg_offset+j);
+		readl(agp_bridge->gatt_table+nvidia_private.pg_offset+j);	/* PCI Posting. */
+	}
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+
+static int nvidia_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+	int i;
+
+	if ((type != 0) || (mem->type != 0))
+		return -EINVAL;
+
+	for (i = pg_start; i < (mem->page_count + pg_start); i++)
+		writel(agp_bridge->scratch_page, agp_bridge->gatt_table+nvidia_private.pg_offset+i);
+
+	agp_bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+
+static void nvidia_tlbflush(struct agp_memory *mem)
+{
+	unsigned long end;
+	u32 wbc_reg, temp;
+	int i;
+
+	/* flush chipset */
+	if (nvidia_private.wbc_mask) {
+		pci_read_config_dword(nvidia_private.dev_1, NVIDIA_1_WBC, &wbc_reg);
+		wbc_reg |= nvidia_private.wbc_mask;
+		pci_write_config_dword(nvidia_private.dev_1, NVIDIA_1_WBC, wbc_reg);
+
+		end = jiffies + 3*HZ;
+		do {
+			pci_read_config_dword(nvidia_private.dev_1,
+					NVIDIA_1_WBC, &wbc_reg);
+			if ((signed)(end - jiffies) <= 0) {
+				printk(KERN_ERR PFX
+				    "TLB flush took more than 3 seconds.\n");
+			}
+		} while (wbc_reg & nvidia_private.wbc_mask);
+	}
+
+	/* flush TLB entries */
+	for(i = 0; i < 32 + 1; i++)
+		temp = readl(nvidia_private.aperture+(i * PAGE_SIZE / sizeof(u32)));
+	for(i = 0; i < 32 + 1; i++)
+		temp = readl(nvidia_private.aperture+(i * PAGE_SIZE / sizeof(u32)));
+}
+
+
+static struct aper_size_info_8 nvidia_generic_sizes[5] =
+{
+	{512, 131072, 7, 0},
+	{256, 65536, 6, 8},
+	{128, 32768, 5, 12},
+	{64, 16384, 4, 14},
+	/* The 32M mode still requires a 64k gatt */
+	{32, 16384, 4, 15}
+};
+
+
+static struct gatt_mask nvidia_generic_masks[] =
+{
+	{ .mask = 1, .type = 0}
+};
+
+
+struct agp_bridge_driver nvidia_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= nvidia_generic_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 5,
+	.configure		= nvidia_configure,
+	.fetch_size		= nvidia_fetch_size,
+	.cleanup		= nvidia_cleanup,
+	.tlb_flush		= nvidia_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= nvidia_generic_masks,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= nvidia_insert_memory,
+	.remove_memory		= nvidia_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static int __devinit agp_nvidia_probe(struct pci_dev *pdev,
+				      const struct pci_device_id *ent)
+{
+	struct agp_bridge_data *bridge;
+	u8 cap_ptr;
+
+	nvidia_private.dev_1 =
+		pci_find_slot((unsigned int)pdev->bus->number, PCI_DEVFN(0, 1));
+	nvidia_private.dev_2 =
+		pci_find_slot((unsigned int)pdev->bus->number, PCI_DEVFN(0, 2));
+	nvidia_private.dev_3 =
+		pci_find_slot((unsigned int)pdev->bus->number, PCI_DEVFN(30, 0));
+	
+	if (!nvidia_private.dev_1 || !nvidia_private.dev_2 || !nvidia_private.dev_3) {
+		printk(KERN_INFO PFX "Detected an NVIDIA nForce/nForce2 "
+			"chipset, but could not find the secondary devices.\n");
+		return -ENODEV;
+	}
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	switch (pdev->device) {
+	case PCI_DEVICE_ID_NVIDIA_NFORCE:
+		printk(KERN_INFO PFX "Detected NVIDIA nForce chipset\n");
+		nvidia_private.wbc_mask = 0x00010000;
+		break;
+	case PCI_DEVICE_ID_NVIDIA_NFORCE2:
+		printk(KERN_INFO PFX "Detected NVIDIA nForce2 chipset\n");
+		nvidia_private.wbc_mask = 0x80000000;
+		break;
+	default:
+		printk(KERN_ERR PFX "Unsupported NVIDIA chipset (device id: %04x)\n",
+			    pdev->device);
+		return -ENODEV;
+	}
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->driver = &nvidia_driver;
+	bridge->dev_private_data = &nvidia_private,
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+
+	/* Fill in the mode register */
+	pci_read_config_dword(pdev,
+			bridge->capndx+PCI_AGP_STATUS,
+			&bridge->mode);
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_nvidia_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_nvidia_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_NVIDIA,
+	.device		= PCI_DEVICE_ID_NVIDIA_NFORCE,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_NVIDIA,
+	.device		= PCI_DEVICE_ID_NVIDIA_NFORCE2,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_nvidia_pci_table);
+
+static struct pci_driver agp_nvidia_pci_driver = {
+	.name		= "agpgart-nvidia",
+	.id_table	= agp_nvidia_pci_table,
+	.probe		= agp_nvidia_probe,
+	.remove		= agp_nvidia_remove,
+};
+
+static int __init agp_nvidia_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_nvidia_pci_driver);
+}
+
+static void __exit agp_nvidia_cleanup(void)
+{
+	pci_unregister_driver(&agp_nvidia_pci_driver);
+}
+
+module_init(agp_nvidia_init);
+module_exit(agp_nvidia_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
+MODULE_AUTHOR("NVIDIA Corporation");
+
diff --git a/drivers/char/agp/sgi-agp.c b/drivers/char/agp/sgi-agp.c
new file mode 100644
index 0000000..4b3eda2
--- /dev/null
+++ b/drivers/char/agp/sgi-agp.c
@@ -0,0 +1,331 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2003-2005 Silicon Graphics, Inc.  All Rights Reserved.
+ */
+
+/*
+ * SGI TIOCA AGPGART routines.
+ *
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/pcidev.h>
+#include <asm/sn/pcibus_provider_defs.h>
+#include <asm/sn/tioca_provider.h>
+#include "agp.h"
+
+extern int agp_memory_reserved;
+extern uint32_t tioca_gart_found;
+extern struct list_head tioca_list;
+static struct agp_bridge_data **sgi_tioca_agp_bridges;
+
+/*
+ * The aperature size and related information is set up at TIOCA init time.
+ * Values for this table will be extracted and filled in at
+ * sgi_tioca_fetch_size() time.
+ */
+
+static struct aper_size_info_fixed sgi_tioca_sizes[] = {
+	{0, 0, 0},
+};
+
+static void *sgi_tioca_alloc_page(struct agp_bridge_data *bridge)
+{
+	struct page *page;
+	int nid;
+	struct tioca_kernel *info =
+	    (struct tioca_kernel *)bridge->dev_private_data;
+
+	nid = info->ca_closest_node;
+	page = alloc_pages_node(nid, GFP_KERNEL, 0);
+	if (page == NULL) {
+		return 0;
+	}
+
+	get_page(page);
+	SetPageLocked(page);
+	atomic_inc(&agp_bridge->current_memory_agp);
+	return page_address(page);
+}
+
+/*
+ * Flush GART tlb's.  Cannot selectively flush based on memory so the mem
+ * arg is ignored.
+ */
+
+static void sgi_tioca_tlbflush(struct agp_memory *mem)
+{
+	tioca_tlbflush(mem->bridge->dev_private_data);
+}
+
+/*
+ * Given an address of a host physical page, turn it into a valid gart
+ * entry.
+ */
+static unsigned long
+sgi_tioca_mask_memory(struct agp_bridge_data *bridge,
+		      unsigned long addr, int type)
+{
+	return tioca_physpage_to_gart(addr);
+}
+
+static void sgi_tioca_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+	tioca_fastwrite_enable(bridge->dev_private_data);
+}
+
+/*
+ * sgi_tioca_configure() doesn't have anything to do since the base CA driver
+ * has alreay set up the GART.
+ */
+
+static int sgi_tioca_configure(void)
+{
+	return 0;
+}
+
+/*
+ * Determine gfx aperature size.  This has already been determined by the
+ * CA driver init, so just need to set agp_bridge values accordingly.
+ */
+
+static int sgi_tioca_fetch_size(void)
+{
+	struct tioca_kernel *info =
+	    (struct tioca_kernel *)agp_bridge->dev_private_data;
+
+	sgi_tioca_sizes[0].size = info->ca_gfxap_size / MB(1);
+	sgi_tioca_sizes[0].num_entries = info->ca_gfxgart_entries;
+
+	return sgi_tioca_sizes[0].size;
+}
+
+static int sgi_tioca_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	struct tioca_kernel *info =
+	    (struct tioca_kernel *)bridge->dev_private_data;
+
+	bridge->gatt_table_real = (u32 *) info->ca_gfxgart;
+	bridge->gatt_table = bridge->gatt_table_real;
+	bridge->gatt_bus_addr = info->ca_gfxgart_base;
+
+	return 0;
+}
+
+static int sgi_tioca_free_gatt_table(struct agp_bridge_data *bridge)
+{
+	return 0;
+}
+
+static int sgi_tioca_insert_memory(struct agp_memory *mem, off_t pg_start,
+				   int type)
+{
+	int num_entries;
+	size_t i;
+	off_t j;
+	void *temp;
+	struct agp_bridge_data *bridge;
+
+	bridge = mem->bridge;
+	if (!bridge)
+		return -EINVAL;
+
+	temp = bridge->current_size;
+
+	switch (bridge->driver->size_type) {
+	case U8_APER_SIZE:
+		num_entries = A_SIZE_8(temp)->num_entries;
+		break;
+	case U16_APER_SIZE:
+		num_entries = A_SIZE_16(temp)->num_entries;
+		break;
+	case U32_APER_SIZE:
+		num_entries = A_SIZE_32(temp)->num_entries;
+		break;
+	case FIXED_APER_SIZE:
+		num_entries = A_SIZE_FIX(temp)->num_entries;
+		break;
+	case LVL2_APER_SIZE:
+		return -EINVAL;
+		break;
+	default:
+		num_entries = 0;
+		break;
+	}
+
+	num_entries -= agp_memory_reserved / PAGE_SIZE;
+	if (num_entries < 0)
+		num_entries = 0;
+
+	if (type != 0 || mem->type != 0) {
+		return -EINVAL;
+	}
+
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	j = pg_start;
+
+	while (j < (pg_start + mem->page_count)) {
+		if (*(bridge->gatt_table + j))
+			return -EBUSY;
+		j++;
+	}
+
+	if (mem->is_flushed == FALSE) {
+		bridge->driver->cache_flush();
+		mem->is_flushed = TRUE;
+	}
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		*(bridge->gatt_table + j) =
+		    bridge->driver->mask_memory(bridge, mem->memory[i],
+						mem->type);
+	}
+
+	bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static int sgi_tioca_remove_memory(struct agp_memory *mem, off_t pg_start,
+				   int type)
+{
+	size_t i;
+	struct agp_bridge_data *bridge;
+
+	bridge = mem->bridge;
+	if (!bridge)
+		return -EINVAL;
+
+	if (type != 0 || mem->type != 0) {
+		return -EINVAL;
+	}
+
+	for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+		*(bridge->gatt_table + i) = 0;
+	}
+
+	bridge->driver->tlb_flush(mem);
+	return 0;
+}
+
+static void sgi_tioca_cache_flush(void)
+{
+}
+
+/*
+ * Cleanup.  Nothing to do as the CA driver owns the GART.
+ */
+
+static void sgi_tioca_cleanup(void)
+{
+}
+
+static struct agp_bridge_data *sgi_tioca_find_bridge(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge;
+
+	list_for_each_entry(bridge, &agp_bridges, list) {
+		if (bridge->dev->bus == pdev->bus)
+			break;
+	}
+	return bridge;
+}
+
+struct agp_bridge_driver sgi_tioca_driver = {
+	.owner = THIS_MODULE,
+	.size_type = U16_APER_SIZE,
+	.configure = sgi_tioca_configure,
+	.fetch_size = sgi_tioca_fetch_size,
+	.cleanup = sgi_tioca_cleanup,
+	.tlb_flush = sgi_tioca_tlbflush,
+	.mask_memory = sgi_tioca_mask_memory,
+	.agp_enable = sgi_tioca_agp_enable,
+	.cache_flush = sgi_tioca_cache_flush,
+	.create_gatt_table = sgi_tioca_create_gatt_table,
+	.free_gatt_table = sgi_tioca_free_gatt_table,
+	.insert_memory = sgi_tioca_insert_memory,
+	.remove_memory = sgi_tioca_remove_memory,
+	.alloc_by_type = agp_generic_alloc_by_type,
+	.free_by_type = agp_generic_free_by_type,
+	.agp_alloc_page = sgi_tioca_alloc_page,
+	.agp_destroy_page = agp_generic_destroy_page,
+	.cant_use_aperture = 1,
+	.needs_scratch_page = 0,
+	.num_aperture_sizes = 1,
+};
+
+static int __devinit agp_sgi_init(void)
+{
+	unsigned int j;
+	struct tioca_kernel *info;
+	struct pci_dev *pdev = NULL;
+
+	if (tioca_gart_found)
+		printk(KERN_INFO PFX "SGI TIO CA GART driver initialized.\n");
+	else
+		return 0;
+
+	sgi_tioca_agp_bridges =
+	    (struct agp_bridge_data **)kmalloc(tioca_gart_found *
+					       sizeof(struct agp_bridge_data *),
+					       GFP_KERNEL);
+
+	j = 0;
+	list_for_each_entry(info, &tioca_list, ca_list) {
+		struct list_head *tmp;
+		list_for_each(tmp, info->ca_devices) {
+			u8 cap_ptr;
+			pdev = pci_dev_b(tmp);
+			if (pdev->class != (PCI_CLASS_DISPLAY_VGA << 8))
+				continue;
+			cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+			if (!cap_ptr)
+				continue;
+		}
+		sgi_tioca_agp_bridges[j] = agp_alloc_bridge();
+		printk(KERN_INFO PFX "bridge %d = 0x%p\n", j,
+		       sgi_tioca_agp_bridges[j]);
+		if (sgi_tioca_agp_bridges[j]) {
+			sgi_tioca_agp_bridges[j]->dev = pdev;
+			sgi_tioca_agp_bridges[j]->dev_private_data = info;
+			sgi_tioca_agp_bridges[j]->driver = &sgi_tioca_driver;
+			sgi_tioca_agp_bridges[j]->gart_bus_addr =
+			    info->ca_gfxap_base;
+			sgi_tioca_agp_bridges[j]->mode = (0x7D << 24) |	/* 126 requests */
+			    (0x1 << 9) |	/* SBA supported */
+			    (0x1 << 5) |	/* 64-bit addresses supported */
+			    (0x1 << 4) |	/* FW supported */
+			    (0x1 << 3) |	/* AGP 3.0 mode */
+			    0x2;	/* 8x transfer only */
+			sgi_tioca_agp_bridges[j]->current_size =
+			    sgi_tioca_agp_bridges[j]->previous_size =
+			    (void *)&sgi_tioca_sizes[0];
+			agp_add_bridge(sgi_tioca_agp_bridges[j]);
+		}
+		j++;
+	}
+
+	agp_find_bridge = &sgi_tioca_find_bridge;
+	return 0;
+}
+
+static void __devexit agp_sgi_cleanup(void)
+{
+	if(sgi_tioca_agp_bridges)
+		kfree(sgi_tioca_agp_bridges);
+	sgi_tioca_agp_bridges=NULL;
+}
+
+module_init(agp_sgi_init);
+module_exit(agp_sgi_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/sis-agp.c b/drivers/char/agp/sis-agp.c
new file mode 100644
index 0000000..cfccacb
--- /dev/null
+++ b/drivers/char/agp/sis-agp.c
@@ -0,0 +1,360 @@
+/*
+ * SiS AGPGART routines.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include <linux/delay.h>
+#include "agp.h"
+
+#define SIS_ATTBASE	0x90
+#define SIS_APSIZE	0x94
+#define SIS_TLBCNTRL	0x97
+#define SIS_TLBFLUSH	0x98
+
+static int __devinitdata agp_sis_force_delay = 0;
+static int __devinitdata agp_sis_agp_spec = -1;
+
+static int sis_fetch_size(void)
+{
+	u8 temp_size;
+	int i;
+	struct aper_size_info_8 *values;
+
+	pci_read_config_byte(agp_bridge->dev, SIS_APSIZE, &temp_size);
+	values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if ((temp_size == values[i].size_value) ||
+		    ((temp_size & ~(0x03)) ==
+		     (values[i].size_value & ~(0x03)))) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+static void sis_tlbflush(struct agp_memory *mem)
+{
+	pci_write_config_byte(agp_bridge->dev, SIS_TLBFLUSH, 0x02);
+}
+
+static int sis_configure(void)
+{
+	u32 temp;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+	pci_write_config_byte(agp_bridge->dev, SIS_TLBCNTRL, 0x05);
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+	pci_write_config_dword(agp_bridge->dev, SIS_ATTBASE,
+			       agp_bridge->gatt_bus_addr);
+	pci_write_config_byte(agp_bridge->dev, SIS_APSIZE,
+			      current_size->size_value);
+	return 0;
+}
+
+static void sis_cleanup(void)
+{
+	struct aper_size_info_8 *previous_size;
+
+	previous_size = A_SIZE_8(agp_bridge->previous_size);
+	pci_write_config_byte(agp_bridge->dev, SIS_APSIZE,
+			      (previous_size->size_value & ~(0x03)));
+}
+
+static void sis_delayed_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+	struct pci_dev *device = NULL;
+	u32 command;
+	int rate;
+
+	printk(KERN_INFO PFX "Found an AGP %d.%d compliant device at %s.\n",
+		agp_bridge->major_version,
+		agp_bridge->minor_version,
+		pci_name(agp_bridge->dev));
+
+	pci_read_config_dword(agp_bridge->dev, agp_bridge->capndx + PCI_AGP_STATUS, &command);
+	command = agp_collect_device_status(bridge, mode, command);
+	command |= AGPSTAT_AGP_ENABLE;
+	rate = (command & 0x7) << 2;
+
+	for_each_pci_dev(device) {
+		u8 agp = pci_find_capability(device, PCI_CAP_ID_AGP);
+		if (!agp)
+			continue;
+
+		printk(KERN_INFO PFX "Putting AGP V3 device at %s into %dx mode\n",
+			pci_name(device), rate);
+
+		pci_write_config_dword(device, agp + PCI_AGP_COMMAND, command);
+
+		/*
+		 * Weird: on some sis chipsets any rate change in the target
+		 * command register triggers a 5ms screwup during which the master
+		 * cannot be configured
+		 */
+		if (device->device == bridge->dev->device) {
+			printk(KERN_INFO PFX "SiS delay workaround: giving bridge time to recover.\n");
+			msleep(10);
+		}
+	}
+}
+
+static struct aper_size_info_8 sis_generic_sizes[7] =
+{
+	{256, 65536, 6, 99},
+	{128, 32768, 5, 83},
+	{64, 16384, 4, 67},
+	{32, 8192, 3, 51},
+	{16, 4096, 2, 35},
+	{8, 2048, 1, 19},
+	{4, 1024, 0, 3}
+};
+
+struct agp_bridge_driver sis_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes 	= sis_generic_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= sis_configure,
+	.fetch_size		= sis_fetch_size,
+	.cleanup		= sis_cleanup,
+	.tlb_flush		= sis_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= NULL,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_device_ids sis_agp_device_ids[] __devinitdata =
+{
+	{
+		.device_id	= PCI_DEVICE_ID_SI_5591_AGP,
+		.chipset_name	= "5591",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_530,
+		.chipset_name	= "530",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_540,
+		.chipset_name	= "540",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_550,
+		.chipset_name	= "550",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_620,
+		.chipset_name	= "620",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_630,
+		.chipset_name	= "630",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_635,
+		.chipset_name	= "635",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_645,
+		.chipset_name	= "645",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_646,
+		.chipset_name	= "646",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_648,
+		.chipset_name	= "648",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_650,
+		.chipset_name	= "650",
+	},
+	{
+		.device_id  = PCI_DEVICE_ID_SI_651,
+		.chipset_name   = "651",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_655,
+		.chipset_name	= "655",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_661,
+		.chipset_name	= "661",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_730,
+		.chipset_name	= "730",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_735,
+		.chipset_name	= "735",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_740,
+		.chipset_name	= "740",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_741,
+		.chipset_name	= "741",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_745,
+		.chipset_name	= "745",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_746,
+		.chipset_name	= "746",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_SI_760,
+		.chipset_name	= "760",
+	},
+	{ }, /* dummy final entry, always present */
+};
+
+
+// chipsets that require the 'delay hack'
+static int sis_broken_chipsets[] __devinitdata = {
+	PCI_DEVICE_ID_SI_648,
+	PCI_DEVICE_ID_SI_746,
+	0 // terminator
+};
+
+static void __devinit sis_get_driver(struct agp_bridge_data *bridge)
+{
+	int i;
+
+	for(i=0; sis_broken_chipsets[i]!=0; ++i)
+		if(bridge->dev->device==sis_broken_chipsets[i])
+			break;
+
+	if(sis_broken_chipsets[i] || agp_sis_force_delay)
+		sis_driver.agp_enable=sis_delayed_enable;
+
+	// sis chipsets that indicate less than agp3.5
+	// are not actually fully agp3 compliant
+	if ((agp_bridge->major_version == 3 && agp_bridge->minor_version >= 5
+	     && agp_sis_agp_spec!=0) || agp_sis_agp_spec==1) {
+		sis_driver.aperture_sizes = agp3_generic_sizes;
+		sis_driver.size_type = U16_APER_SIZE;
+		sis_driver.num_aperture_sizes = AGP_GENERIC_SIZES_ENTRIES;
+		sis_driver.configure = agp3_generic_configure;
+		sis_driver.fetch_size = agp3_generic_fetch_size;
+		sis_driver.cleanup = agp3_generic_cleanup;
+		sis_driver.tlb_flush = agp3_generic_tlbflush;
+	}
+}
+
+
+static int __devinit agp_sis_probe(struct pci_dev *pdev,
+				   const struct pci_device_id *ent)
+{
+	struct agp_device_ids *devs = sis_agp_device_ids;
+	struct agp_bridge_data *bridge;
+	u8 cap_ptr;
+	int j;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	/* probe for known chipsets */
+	for (j = 0; devs[j].chipset_name; j++) {
+		if (pdev->device == devs[j].device_id) {
+			printk(KERN_INFO PFX "Detected SiS %s chipset\n",
+					devs[j].chipset_name);
+			goto found;
+		}
+	}
+
+	printk(KERN_ERR PFX "Unsupported SiS chipset (device id: %04x)\n",
+		    pdev->device);
+	return -ENODEV;
+
+found:
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->driver = &sis_driver;
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+
+	get_agp_version(bridge);
+
+	/* Fill in the mode register */
+	pci_read_config_dword(pdev, bridge->capndx+PCI_AGP_STATUS, &bridge->mode);
+	sis_get_driver(bridge);
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_sis_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_sis_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_SI,
+	.device		= PCI_ANY_ID,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_sis_pci_table);
+
+static struct pci_driver agp_sis_pci_driver = {
+	.name		= "agpgart-sis",
+	.id_table	= agp_sis_pci_table,
+	.probe		= agp_sis_probe,
+	.remove		= agp_sis_remove,
+};
+
+static int __init agp_sis_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_sis_pci_driver);
+}
+
+static void __exit agp_sis_cleanup(void)
+{
+	pci_unregister_driver(&agp_sis_pci_driver);
+}
+
+module_init(agp_sis_init);
+module_exit(agp_sis_cleanup);
+
+module_param(agp_sis_force_delay, bool, 0);
+MODULE_PARM_DESC(agp_sis_force_delay,"forces sis delay hack");
+module_param(agp_sis_agp_spec, int, 0);
+MODULE_PARM_DESC(agp_sis_agp_spec,"0=force sis init, 1=force generic agp3 init, default: autodetect");
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/agp/sworks-agp.c b/drivers/char/agp/sworks-agp.c
new file mode 100644
index 0000000..bb338d9
--- /dev/null
+++ b/drivers/char/agp/sworks-agp.c
@@ -0,0 +1,556 @@
+/*
+ * Serverworks AGPGART routines.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+#define SVWRKS_COMMAND		0x04
+#define SVWRKS_APSIZE		0x10
+#define SVWRKS_MMBASE		0x14
+#define SVWRKS_CACHING		0x4b
+#define SVWRKS_AGP_ENABLE	0x60
+#define SVWRKS_FEATURE		0x68
+
+#define SVWRKS_SIZE_MASK	0xfe000000
+
+/* Memory mapped registers */
+#define SVWRKS_GART_CACHE	0x02
+#define SVWRKS_GATTBASE		0x04
+#define SVWRKS_TLBFLUSH		0x10
+#define SVWRKS_POSTFLUSH	0x14
+#define SVWRKS_DIRFLUSH		0x0c
+
+
+struct serverworks_page_map {
+	unsigned long *real;
+	unsigned long __iomem *remapped;
+};
+
+static struct _serverworks_private {
+	struct pci_dev *svrwrks_dev;	/* device one */
+	volatile u8 __iomem *registers;
+	struct serverworks_page_map **gatt_pages;
+	int num_tables;
+	struct serverworks_page_map scratch_dir;
+
+	int gart_addr_ofs;
+	int mm_addr_ofs;
+} serverworks_private;
+
+static int serverworks_create_page_map(struct serverworks_page_map *page_map)
+{
+	int i;
+
+	page_map->real = (unsigned long *) __get_free_page(GFP_KERNEL);
+	if (page_map->real == NULL) {
+		return -ENOMEM;
+	}
+	SetPageReserved(virt_to_page(page_map->real));
+	global_cache_flush();
+	page_map->remapped = ioremap_nocache(virt_to_phys(page_map->real), 
+					    PAGE_SIZE);
+	if (page_map->remapped == NULL) {
+		ClearPageReserved(virt_to_page(page_map->real));
+		free_page((unsigned long) page_map->real);
+		page_map->real = NULL;
+		return -ENOMEM;
+	}
+	global_cache_flush();
+
+	for(i = 0; i < PAGE_SIZE / sizeof(unsigned long); i++)
+		writel(agp_bridge->scratch_page, page_map->remapped+i);
+
+	return 0;
+}
+
+static void serverworks_free_page_map(struct serverworks_page_map *page_map)
+{
+	iounmap(page_map->remapped);
+	ClearPageReserved(virt_to_page(page_map->real));
+	free_page((unsigned long) page_map->real);
+}
+
+static void serverworks_free_gatt_pages(void)
+{
+	int i;
+	struct serverworks_page_map **tables;
+	struct serverworks_page_map *entry;
+
+	tables = serverworks_private.gatt_pages;
+	for(i = 0; i < serverworks_private.num_tables; i++) {
+		entry = tables[i];
+		if (entry != NULL) {
+			if (entry->real != NULL) {
+				serverworks_free_page_map(entry);
+			}
+			kfree(entry);
+		}
+	}
+	kfree(tables);
+}
+
+static int serverworks_create_gatt_pages(int nr_tables)
+{
+	struct serverworks_page_map **tables;
+	struct serverworks_page_map *entry;
+	int retval = 0;
+	int i;
+
+	tables = kmalloc((nr_tables + 1) * sizeof(struct serverworks_page_map *), 
+			 GFP_KERNEL);
+	if (tables == NULL) {
+		return -ENOMEM;
+	}
+	memset(tables, 0, sizeof(struct serverworks_page_map *) * (nr_tables + 1));
+	for (i = 0; i < nr_tables; i++) {
+		entry = kmalloc(sizeof(struct serverworks_page_map), GFP_KERNEL);
+		if (entry == NULL) {
+			retval = -ENOMEM;
+			break;
+		}
+		memset(entry, 0, sizeof(struct serverworks_page_map));
+		tables[i] = entry;
+		retval = serverworks_create_page_map(entry);
+		if (retval != 0) break;
+	}
+	serverworks_private.num_tables = nr_tables;
+	serverworks_private.gatt_pages = tables;
+
+	if (retval != 0) serverworks_free_gatt_pages();
+
+	return retval;
+}
+
+#define SVRWRKS_GET_GATT(addr) (serverworks_private.gatt_pages[\
+	GET_PAGE_DIR_IDX(addr)]->remapped)
+
+#ifndef GET_PAGE_DIR_OFF
+#define GET_PAGE_DIR_OFF(addr) (addr >> 22)
+#endif
+
+#ifndef GET_PAGE_DIR_IDX
+#define GET_PAGE_DIR_IDX(addr) (GET_PAGE_DIR_OFF(addr) - \
+	GET_PAGE_DIR_OFF(agp_bridge->gart_bus_addr))
+#endif
+
+#ifndef GET_GATT_OFF
+#define GET_GATT_OFF(addr) ((addr & 0x003ff000) >> 12)
+#endif
+
+static int serverworks_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	struct aper_size_info_lvl2 *value;
+	struct serverworks_page_map page_dir;
+	int retval;
+	u32 temp;
+	int i;
+
+	value = A_SIZE_LVL2(agp_bridge->current_size);
+	retval = serverworks_create_page_map(&page_dir);
+	if (retval != 0) {
+		return retval;
+	}
+	retval = serverworks_create_page_map(&serverworks_private.scratch_dir);
+	if (retval != 0) {
+		serverworks_free_page_map(&page_dir);
+		return retval;
+	}
+	/* Create a fake scratch directory */
+	for(i = 0; i < 1024; i++) {
+		writel(agp_bridge->scratch_page, serverworks_private.scratch_dir.remapped+i);
+		writel(virt_to_phys(serverworks_private.scratch_dir.real) | 1, page_dir.remapped+i);
+	}
+
+	retval = serverworks_create_gatt_pages(value->num_entries / 1024);
+	if (retval != 0) {
+		serverworks_free_page_map(&page_dir);
+		serverworks_free_page_map(&serverworks_private.scratch_dir);
+		return retval;
+	}
+
+	agp_bridge->gatt_table_real = (u32 *)page_dir.real;
+	agp_bridge->gatt_table = (u32 __iomem *)page_dir.remapped;
+	agp_bridge->gatt_bus_addr = virt_to_phys(page_dir.real);
+
+	/* Get the address for the gart region.
+	 * This is a bus address even on the alpha, b/c its
+	 * used to program the agp master not the cpu
+	 */
+
+	pci_read_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,&temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* Calculate the agp offset */	
+
+	for(i = 0; i < value->num_entries / 1024; i++)
+		writel(virt_to_phys(serverworks_private.gatt_pages[i]->real)|1, page_dir.remapped+i);
+
+	return 0;
+}
+
+static int serverworks_free_gatt_table(struct agp_bridge_data *bridge)
+{
+	struct serverworks_page_map page_dir;
+   
+	page_dir.real = (unsigned long *)agp_bridge->gatt_table_real;
+	page_dir.remapped = (unsigned long __iomem *)agp_bridge->gatt_table;
+
+	serverworks_free_gatt_pages();
+	serverworks_free_page_map(&page_dir);
+	serverworks_free_page_map(&serverworks_private.scratch_dir);
+	return 0;
+}
+
+static int serverworks_fetch_size(void)
+{
+	int i;
+	u32 temp;
+	u32 temp2;
+	struct aper_size_info_lvl2 *values;
+
+	values = A_SIZE_LVL2(agp_bridge->driver->aperture_sizes);
+	pci_read_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,&temp);
+	pci_write_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,
+					SVWRKS_SIZE_MASK);
+	pci_read_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,&temp2);
+	pci_write_config_dword(agp_bridge->dev,serverworks_private.gart_addr_ofs,temp);
+	temp2 &= SVWRKS_SIZE_MASK;
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp2 == values[i].size_value) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * This routine could be implemented by taking the addresses
+ * written to the GATT, and flushing them individually.  However
+ * currently it just flushes the whole table.  Which is probably
+ * more efficent, since agp_memory blocks can be a large number of
+ * entries.
+ */
+static void serverworks_tlbflush(struct agp_memory *temp)
+{
+	writeb(1, serverworks_private.registers+SVWRKS_POSTFLUSH);
+	while (readb(serverworks_private.registers+SVWRKS_POSTFLUSH) == 1)
+		cpu_relax();
+
+	writel(1, serverworks_private.registers+SVWRKS_DIRFLUSH);
+	while(readl(serverworks_private.registers+SVWRKS_DIRFLUSH) == 1)
+		cpu_relax();
+}
+
+static int serverworks_configure(void)
+{
+	struct aper_size_info_lvl2 *current_size;
+	u32 temp;
+	u8 enable_reg;
+	u16 cap_reg;
+
+	current_size = A_SIZE_LVL2(agp_bridge->current_size);
+
+	/* Get the memory mapped registers */
+	pci_read_config_dword(agp_bridge->dev, serverworks_private.mm_addr_ofs, &temp);
+	temp = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+	serverworks_private.registers = (volatile u8 __iomem *) ioremap(temp, 4096);
+	if (!serverworks_private.registers) {
+		printk (KERN_ERR PFX "Unable to ioremap() memory.\n");
+		return -ENOMEM;
+	}
+
+	writeb(0xA, serverworks_private.registers+SVWRKS_GART_CACHE);
+	readb(serverworks_private.registers+SVWRKS_GART_CACHE);	/* PCI Posting. */
+
+	writel(agp_bridge->gatt_bus_addr, serverworks_private.registers+SVWRKS_GATTBASE);
+	readl(serverworks_private.registers+SVWRKS_GATTBASE);	/* PCI Posting. */
+
+	cap_reg = readw(serverworks_private.registers+SVWRKS_COMMAND);
+	cap_reg &= ~0x0007;
+	cap_reg |= 0x4;
+	writew(cap_reg, serverworks_private.registers+SVWRKS_COMMAND);
+	readw(serverworks_private.registers+SVWRKS_COMMAND);
+
+	pci_read_config_byte(serverworks_private.svrwrks_dev,SVWRKS_AGP_ENABLE, &enable_reg);
+	enable_reg |= 0x1; /* Agp Enable bit */
+	pci_write_config_byte(serverworks_private.svrwrks_dev,SVWRKS_AGP_ENABLE, enable_reg);
+	serverworks_tlbflush(NULL);
+
+	agp_bridge->capndx = pci_find_capability(serverworks_private.svrwrks_dev, PCI_CAP_ID_AGP);
+
+	/* Fill in the mode register */
+	pci_read_config_dword(serverworks_private.svrwrks_dev,
+			      agp_bridge->capndx+PCI_AGP_STATUS, &agp_bridge->mode);
+
+	pci_read_config_byte(agp_bridge->dev, SVWRKS_CACHING, &enable_reg);
+	enable_reg &= ~0x3;
+	pci_write_config_byte(agp_bridge->dev, SVWRKS_CACHING, enable_reg);
+
+	pci_read_config_byte(agp_bridge->dev, SVWRKS_FEATURE, &enable_reg);
+	enable_reg |= (1<<6);
+	pci_write_config_byte(agp_bridge->dev,SVWRKS_FEATURE, enable_reg);
+
+	return 0;
+}
+
+static void serverworks_cleanup(void)
+{
+	iounmap((void __iomem *) serverworks_private.registers);
+}
+
+static int serverworks_insert_memory(struct agp_memory *mem,
+			     off_t pg_start, int type)
+{
+	int i, j, num_entries;
+	unsigned long __iomem *cur_gatt;
+	unsigned long addr;
+
+	num_entries = A_SIZE_LVL2(agp_bridge->current_size)->num_entries;
+
+	if (type != 0 || mem->type != 0) {
+		return -EINVAL;
+	}
+	if ((pg_start + mem->page_count) > num_entries) {
+		return -EINVAL;
+	}
+
+	j = pg_start;
+	while (j < (pg_start + mem->page_count)) {
+		addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = SVRWRKS_GET_GATT(addr);
+		if (!PGE_EMPTY(agp_bridge, readl(cur_gatt+GET_GATT_OFF(addr))))
+			return -EBUSY;
+		j++;
+	}
+
+	if (mem->is_flushed == FALSE) {
+		global_cache_flush();
+		mem->is_flushed = TRUE;
+	}
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		addr = (j * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = SVRWRKS_GET_GATT(addr);
+		writel(agp_bridge->driver->mask_memory(agp_bridge, mem->memory[i], mem->type), cur_gatt+GET_GATT_OFF(addr));
+	}
+	serverworks_tlbflush(mem);
+	return 0;
+}
+
+static int serverworks_remove_memory(struct agp_memory *mem, off_t pg_start,
+			     int type)
+{
+	int i;
+	unsigned long __iomem *cur_gatt;
+	unsigned long addr;
+
+	if (type != 0 || mem->type != 0) {
+		return -EINVAL;
+	}
+
+	global_cache_flush();
+	serverworks_tlbflush(mem);
+
+	for (i = pg_start; i < (mem->page_count + pg_start); i++) {
+		addr = (i * PAGE_SIZE) + agp_bridge->gart_bus_addr;
+		cur_gatt = SVRWRKS_GET_GATT(addr);
+		writel(agp_bridge->scratch_page, cur_gatt+GET_GATT_OFF(addr));
+	}
+
+	serverworks_tlbflush(mem);
+	return 0;
+}
+
+static struct gatt_mask serverworks_masks[] =
+{
+	{.mask = 1, .type = 0}
+};
+
+static struct aper_size_info_lvl2 serverworks_sizes[7] =
+{
+	{2048, 524288, 0x80000000},
+	{1024, 262144, 0xc0000000},
+	{512, 131072, 0xe0000000},
+	{256, 65536, 0xf0000000},
+	{128, 32768, 0xf8000000},
+	{64, 16384, 0xfc000000},
+	{32, 8192, 0xfe000000}
+};
+
+static void serverworks_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+	u32 command;
+
+	pci_read_config_dword(serverworks_private.svrwrks_dev,
+			      bridge->capndx + PCI_AGP_STATUS,
+			      &command);
+
+	command = agp_collect_device_status(bridge, mode, command);
+
+	command &= ~0x10;	/* disable FW */
+	command &= ~0x08;
+
+	command |= 0x100;
+
+	pci_write_config_dword(serverworks_private.svrwrks_dev,
+			       bridge->capndx + PCI_AGP_COMMAND,
+			       command);
+
+	agp_device_command(command, 0);
+}
+
+struct agp_bridge_driver sworks_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= serverworks_sizes,
+	.size_type		= LVL2_APER_SIZE,
+	.num_aperture_sizes	= 7,
+	.configure		= serverworks_configure,
+	.fetch_size		= serverworks_fetch_size,
+	.cleanup		= serverworks_cleanup,
+	.tlb_flush		= serverworks_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= serverworks_masks,
+	.agp_enable		= serverworks_agp_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= serverworks_create_gatt_table,
+	.free_gatt_table	= serverworks_free_gatt_table,
+	.insert_memory		= serverworks_insert_memory,
+	.remove_memory		= serverworks_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static int __devinit agp_serverworks_probe(struct pci_dev *pdev,
+					   const struct pci_device_id *ent)
+{
+	struct agp_bridge_data *bridge;
+	struct pci_dev *bridge_dev;
+	u32 temp, temp2;
+	u8 cap_ptr = 0;
+
+	/* Everything is on func 1 here so we are hardcoding function one */
+	bridge_dev = pci_find_slot((unsigned int)pdev->bus->number,
+			PCI_DEVFN(0, 1));
+	if (!bridge_dev) {
+		printk(KERN_INFO PFX "Detected a Serverworks chipset "
+		       "but could not find the secondary device.\n");
+		return -ENODEV;
+	}
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+
+	switch (pdev->device) {
+	case 0x0006:
+		/* ServerWorks CNB20HE
+		Fail silently.*/
+		printk (KERN_ERR PFX "Detected ServerWorks CNB20HE chipset: No AGP present.\n");
+		return -ENODEV;
+
+	case PCI_DEVICE_ID_SERVERWORKS_HE:
+	case PCI_DEVICE_ID_SERVERWORKS_LE:
+	case 0x0007:
+		break;
+
+	default:
+		if (cap_ptr)
+			printk(KERN_ERR PFX "Unsupported Serverworks chipset "
+					"(device id: %04x)\n", pdev->device);
+		return -ENODEV;
+	}
+
+	serverworks_private.svrwrks_dev = bridge_dev;
+	serverworks_private.gart_addr_ofs = 0x10;
+
+	pci_read_config_dword(pdev, SVWRKS_APSIZE, &temp);
+	if (temp & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+		pci_read_config_dword(pdev, SVWRKS_APSIZE + 4, &temp2);
+		if (temp2 != 0) {
+			printk(KERN_INFO PFX "Detected 64 bit aperture address, "
+			       "but top bits are not zero.  Disabling agp\n");
+			return -ENODEV;
+		}
+		serverworks_private.mm_addr_ofs = 0x18;
+	} else
+		serverworks_private.mm_addr_ofs = 0x14;
+
+	pci_read_config_dword(pdev, serverworks_private.mm_addr_ofs, &temp);
+	if (temp & PCI_BASE_ADDRESS_MEM_TYPE_64) {
+		pci_read_config_dword(pdev,
+				serverworks_private.mm_addr_ofs + 4, &temp2);
+		if (temp2 != 0) {
+			printk(KERN_INFO PFX "Detected 64 bit MMIO address, "
+			       "but top bits are not zero.  Disabling agp\n");
+			return -ENODEV;
+		}
+	}
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->driver = &sworks_driver;
+	bridge->dev_private_data = &serverworks_private,
+	bridge->dev = pdev;
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_serverworks_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_serverworks_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_SERVERWORKS,
+	.device		= PCI_ANY_ID,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_serverworks_pci_table);
+
+static struct pci_driver agp_serverworks_pci_driver = {
+	.name		= "agpgart-serverworks",
+	.id_table	= agp_serverworks_pci_table,
+	.probe		= agp_serverworks_probe,
+	.remove		= agp_serverworks_remove,
+};
+
+static int __init agp_serverworks_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_serverworks_pci_driver);
+}
+
+static void __exit agp_serverworks_cleanup(void)
+{
+	pci_unregister_driver(&agp_serverworks_pci_driver);
+}
+
+module_init(agp_serverworks_init);
+module_exit(agp_serverworks_cleanup);
+
+MODULE_LICENSE("GPL and additional rights");
+
diff --git a/drivers/char/agp/uninorth-agp.c b/drivers/char/agp/uninorth-agp.c
new file mode 100644
index 0000000..0f24823
--- /dev/null
+++ b/drivers/char/agp/uninorth-agp.c
@@ -0,0 +1,647 @@
+/*
+ * UniNorth AGPGART routines.
+ */
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/pagemap.h>
+#include <linux/agp_backend.h>
+#include <linux/delay.h>
+#include <asm/uninorth.h>
+#include <asm/pci-bridge.h>
+#include <asm/prom.h>
+#include "agp.h"
+
+/*
+ * NOTES for uninorth3 (G5 AGP) supports :
+ *
+ * There maybe also possibility to have bigger cache line size for
+ * agp (see pmac_pci.c and look for cache line). Need to be investigated
+ * by someone.
+ *
+ * PAGE size are hardcoded but this may change, see asm/page.h.
+ *
+ * Jerome Glisse <j.glisse@gmail.com>
+ */
+static int uninorth_rev;
+static int is_u3;
+
+static int uninorth_fetch_size(void)
+{
+	int i;
+	u32 temp;
+	struct aper_size_info_32 *values;
+
+	pci_read_config_dword(agp_bridge->dev, UNI_N_CFG_GART_BASE, &temp);
+	temp &= ~(0xfffff000);
+	values = A_SIZE_32(agp_bridge->driver->aperture_sizes);
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+
+	agp_bridge->previous_size =
+	    agp_bridge->current_size = (void *) (values + 1);
+	agp_bridge->aperture_size_idx = 1;
+	return values[1].size;
+
+	return 0;
+}
+
+static void uninorth_tlbflush(struct agp_memory *mem)
+{
+	u32 ctrl = UNI_N_CFG_GART_ENABLE;
+
+	if (is_u3)
+		ctrl |= U3_N_CFG_GART_PERFRD;
+	pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+			       ctrl | UNI_N_CFG_GART_INVAL);
+	pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL, ctrl);
+
+	if (uninorth_rev <= 0x30) {
+		pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+				       ctrl | UNI_N_CFG_GART_2xRESET);
+		pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+				       ctrl);
+	}
+}
+
+static void uninorth_cleanup(void)
+{
+	u32 tmp;
+
+	pci_read_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL, &tmp);
+	if (!(tmp & UNI_N_CFG_GART_ENABLE))
+		return;
+	tmp |= UNI_N_CFG_GART_INVAL;
+	pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL, tmp);
+	pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL, 0);
+
+	if (uninorth_rev <= 0x30) {
+		pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+				       UNI_N_CFG_GART_2xRESET);
+		pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_GART_CTRL,
+				       0);
+	}
+}
+
+static int uninorth_configure(void)
+{
+	struct aper_size_info_32 *current_size;
+	
+	current_size = A_SIZE_32(agp_bridge->current_size);
+
+	printk(KERN_INFO PFX "configuring for size idx: %d\n",
+	       current_size->size_value);
+	
+	/* aperture size and gatt addr */
+	pci_write_config_dword(agp_bridge->dev,
+		UNI_N_CFG_GART_BASE,
+		(agp_bridge->gatt_bus_addr & 0xfffff000)
+			| current_size->size_value);
+
+	/* HACK ALERT
+	 * UniNorth seem to be buggy enough not to handle properly when
+	 * the AGP aperture isn't mapped at bus physical address 0
+	 */
+	agp_bridge->gart_bus_addr = 0;
+#ifdef CONFIG_PPC64
+	/* Assume U3 or later on PPC64 systems */
+	/* high 4 bits of GART physical address go in UNI_N_CFG_AGP_BASE */
+	pci_write_config_dword(agp_bridge->dev, UNI_N_CFG_AGP_BASE,
+			       (agp_bridge->gatt_bus_addr >> 32) & 0xf);
+#else
+	pci_write_config_dword(agp_bridge->dev,
+		UNI_N_CFG_AGP_BASE, agp_bridge->gart_bus_addr);
+#endif
+
+	if (is_u3) {
+		pci_write_config_dword(agp_bridge->dev,
+				       UNI_N_CFG_GART_DUMMY_PAGE,
+				       agp_bridge->scratch_page_real >> 12);
+	}
+	
+	return 0;
+}
+
+static int uninorth_insert_memory(struct agp_memory *mem, off_t pg_start,
+				int type)
+{
+	int i, j, num_entries;
+	void *temp;
+
+	temp = agp_bridge->current_size;
+	num_entries = A_SIZE_32(temp)->num_entries;
+
+	if (type != 0 || mem->type != 0)
+		/* We know nothing of memory types */
+		return -EINVAL;
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	j = pg_start;
+
+	while (j < (pg_start + mem->page_count)) {
+		if (agp_bridge->gatt_table[j])
+			return -EBUSY;
+		j++;
+	}
+
+	for (i = 0, j = pg_start; i < mem->page_count; i++, j++) {
+		agp_bridge->gatt_table[j] =
+		    cpu_to_le32((mem->memory[i] & 0xFFFFF000UL) | 0x1UL);
+		flush_dcache_range((unsigned long)__va(mem->memory[i]),
+				   (unsigned long)__va(mem->memory[i])+0x1000);
+	}
+	(void)in_le32((volatile u32*)&agp_bridge->gatt_table[pg_start]);
+	mb();
+	flush_dcache_range((unsigned long)&agp_bridge->gatt_table[pg_start], 
+		(unsigned long)&agp_bridge->gatt_table[pg_start + mem->page_count]);
+
+	uninorth_tlbflush(mem);
+	return 0;
+}
+
+static int u3_insert_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+	int i, num_entries;
+	void *temp;
+	u32 *gp;
+
+	temp = agp_bridge->current_size;
+	num_entries = A_SIZE_32(temp)->num_entries;
+
+	if (type != 0 || mem->type != 0)
+		/* We know nothing of memory types */
+		return -EINVAL;
+	if ((pg_start + mem->page_count) > num_entries)
+		return -EINVAL;
+
+	gp = (u32 *) &agp_bridge->gatt_table[pg_start];
+	for (i = 0; i < mem->page_count; ++i) {
+		if (gp[i]) {
+			printk("u3_insert_memory: entry 0x%x occupied (%x)\n",
+			       i, gp[i]);
+			return -EBUSY;
+		}
+	}
+
+	for (i = 0; i < mem->page_count; i++) {
+		gp[i] = (mem->memory[i] >> PAGE_SHIFT) | 0x80000000UL;
+		flush_dcache_range((unsigned long)__va(mem->memory[i]),
+				   (unsigned long)__va(mem->memory[i])+0x1000);
+	}
+	mb();
+	flush_dcache_range((unsigned long)gp, (unsigned long) &gp[i]);
+	uninorth_tlbflush(mem);
+
+	return 0;
+}
+
+int u3_remove_memory(struct agp_memory *mem, off_t pg_start, int type)
+{
+	size_t i;
+	u32 *gp;
+
+	if (type != 0 || mem->type != 0)
+		/* We know nothing of memory types */
+		return -EINVAL;
+
+	gp = (u32 *) &agp_bridge->gatt_table[pg_start];
+	for (i = 0; i < mem->page_count; ++i)
+		gp[i] = 0;
+	mb();
+	flush_dcache_range((unsigned long)gp, (unsigned long) &gp[i]);
+	uninorth_tlbflush(mem);
+
+	return 0;
+}
+
+static void uninorth_agp_enable(struct agp_bridge_data *bridge, u32 mode)
+{
+	u32 command, scratch, status;
+	int timeout;
+
+	pci_read_config_dword(bridge->dev,
+			      bridge->capndx + PCI_AGP_STATUS,
+			      &status);
+
+	command = agp_collect_device_status(bridge, mode, status);
+	command |= PCI_AGP_COMMAND_AGP;
+	
+	if (uninorth_rev == 0x21) {
+		/*
+		 * Darwin disable AGP 4x on this revision, thus we
+		 * may assume it's broken. This is an AGP2 controller.
+		 */
+		command &= ~AGPSTAT2_4X;
+	}
+
+	if ((uninorth_rev >= 0x30) && (uninorth_rev <= 0x33)) {
+		/*
+		 * We need to to set REQ_DEPTH to 7 for U3 versions 1.0, 2.1,
+		 * 2.2 and 2.3, Darwin do so.
+		 */
+		if ((command >> AGPSTAT_RQ_DEPTH_SHIFT) > 7)
+			command = (command & ~AGPSTAT_RQ_DEPTH)
+				| (7 << AGPSTAT_RQ_DEPTH_SHIFT);
+	}
+
+	uninorth_tlbflush(NULL);
+
+	timeout = 0;
+	do {
+		pci_write_config_dword(bridge->dev,
+				       bridge->capndx + PCI_AGP_COMMAND,
+				       command);
+		pci_read_config_dword(bridge->dev,
+				      bridge->capndx + PCI_AGP_COMMAND,
+				       &scratch);
+	} while ((scratch & PCI_AGP_COMMAND_AGP) == 0 && ++timeout < 1000);
+	if ((scratch & PCI_AGP_COMMAND_AGP) == 0)
+		printk(KERN_ERR PFX "failed to write UniNorth AGP command reg\n");
+
+	if (uninorth_rev >= 0x30) {
+		/* This is an AGP V3 */
+		agp_device_command(command, (status & AGPSTAT_MODE_3_0));
+	} else {
+		/* AGP V2 */
+		agp_device_command(command, 0);
+	}
+
+	uninorth_tlbflush(NULL);
+}
+
+#ifdef CONFIG_PM
+static int agp_uninorth_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	u32 cmd;
+	u8 agp;
+	struct pci_dev *device = NULL;
+
+	if (state != PMSG_SUSPEND)
+		return 0;
+
+	/* turn off AGP on the video chip, if it was enabled */
+	for_each_pci_dev(device) {
+		/* Don't touch the bridge yet, device first */
+		if (device == pdev)
+			continue;
+		/* Only deal with devices on the same bus here, no Mac has a P2P
+		 * bridge on the AGP port, and mucking around the entire PCI
+		 * tree is source of problems on some machines because of a bug
+		 * in some versions of pci_find_capability() when hitting a dead
+		 * device
+		 */
+		if (device->bus != pdev->bus)
+			continue;
+		agp = pci_find_capability(device, PCI_CAP_ID_AGP);
+		if (!agp)
+			continue;
+		pci_read_config_dword(device, agp + PCI_AGP_COMMAND, &cmd);
+		if (!(cmd & PCI_AGP_COMMAND_AGP))
+			continue;
+		printk("uninorth-agp: disabling AGP on device %s\n",
+				pci_name(device));
+		cmd &= ~PCI_AGP_COMMAND_AGP;
+		pci_write_config_dword(device, agp + PCI_AGP_COMMAND, cmd);
+	}
+
+	/* turn off AGP on the bridge */
+	agp = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	pci_read_config_dword(pdev, agp + PCI_AGP_COMMAND, &cmd);
+	if (cmd & PCI_AGP_COMMAND_AGP) {
+		printk("uninorth-agp: disabling AGP on bridge %s\n",
+				pci_name(pdev));
+		cmd &= ~PCI_AGP_COMMAND_AGP;
+		pci_write_config_dword(pdev, agp + PCI_AGP_COMMAND, cmd);
+	}
+	/* turn off the GART */
+	uninorth_cleanup();
+
+	return 0;
+}
+
+static int agp_uninorth_resume(struct pci_dev *pdev)
+{
+	return 0;
+}
+#endif
+
+static int uninorth_create_gatt_table(struct agp_bridge_data *bridge)
+{
+	char *table;
+	char *table_end;
+	int size;
+	int page_order;
+	int num_entries;
+	int i;
+	void *temp;
+	struct page *page;
+
+	/* We can't handle 2 level gatt's */
+	if (bridge->driver->size_type == LVL2_APER_SIZE)
+		return -EINVAL;
+
+	table = NULL;
+	i = bridge->aperture_size_idx;
+	temp = bridge->current_size;
+	size = page_order = num_entries = 0;
+
+	do {
+		size = A_SIZE_32(temp)->size;
+		page_order = A_SIZE_32(temp)->page_order;
+		num_entries = A_SIZE_32(temp)->num_entries;
+
+		table = (char *) __get_free_pages(GFP_KERNEL, page_order);
+
+		if (table == NULL) {
+			i++;
+			bridge->current_size = A_IDX32(bridge);
+		} else {
+			bridge->aperture_size_idx = i;
+		}
+	} while (!table && (i < bridge->driver->num_aperture_sizes));
+
+	if (table == NULL)
+		return -ENOMEM;
+
+	table_end = table + ((PAGE_SIZE * (1 << page_order)) - 1);
+
+	for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+		SetPageReserved(page);
+
+	bridge->gatt_table_real = (u32 *) table;
+	bridge->gatt_table = (u32 *)table;
+	bridge->gatt_bus_addr = virt_to_phys(table);
+
+	for (i = 0; i < num_entries; i++)
+		bridge->gatt_table[i] = 0;
+
+	flush_dcache_range((unsigned long)table, (unsigned long)table_end);
+
+	return 0;
+}
+
+static int uninorth_free_gatt_table(struct agp_bridge_data *bridge)
+{
+	int page_order;
+	char *table, *table_end;
+	void *temp;
+	struct page *page;
+
+	temp = bridge->current_size;
+	page_order = A_SIZE_32(temp)->page_order;
+
+	/* Do not worry about freeing memory, because if this is
+	 * called, then all agp memory is deallocated and removed
+	 * from the table.
+	 */
+
+	table = (char *) bridge->gatt_table_real;
+	table_end = table + ((PAGE_SIZE * (1 << page_order)) - 1);
+
+	for (page = virt_to_page(table); page <= virt_to_page(table_end); page++)
+		ClearPageReserved(page);
+
+	free_pages((unsigned long) bridge->gatt_table_real, page_order);
+
+	return 0;
+}
+
+void null_cache_flush(void)
+{
+	mb();
+}
+
+/* Setup function */
+
+static struct aper_size_info_32 uninorth_sizes[7] =
+{
+#if 0 /* Not sure uninorth supports that high aperture sizes */
+	{256, 65536, 6, 64},
+	{128, 32768, 5, 32},
+	{64, 16384, 4, 16},
+#endif	
+	{32, 8192, 3, 8},
+	{16, 4096, 2, 4},
+	{8, 2048, 1, 2},
+	{4, 1024, 0, 1}
+};
+
+/*
+ * Not sure that u3 supports that high aperture sizes but it
+ * would strange if it did not :)
+ */
+static struct aper_size_info_32 u3_sizes[8] =
+{
+	{512, 131072, 7, 128},
+	{256, 65536, 6, 64},
+	{128, 32768, 5, 32},
+	{64, 16384, 4, 16},
+	{32, 8192, 3, 8},
+	{16, 4096, 2, 4},
+	{8, 2048, 1, 2},
+	{4, 1024, 0, 1}
+};
+
+struct agp_bridge_driver uninorth_agp_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= (void *)uninorth_sizes,
+	.size_type		= U32_APER_SIZE,
+	.num_aperture_sizes	= 4,
+	.configure		= uninorth_configure,
+	.fetch_size		= uninorth_fetch_size,
+	.cleanup		= uninorth_cleanup,
+	.tlb_flush		= uninorth_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= NULL,
+	.cache_flush		= null_cache_flush,
+	.agp_enable		= uninorth_agp_enable,
+	.create_gatt_table	= uninorth_create_gatt_table,
+	.free_gatt_table	= uninorth_free_gatt_table,
+	.insert_memory		= uninorth_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+	.cant_use_aperture	= 1,
+};
+
+struct agp_bridge_driver u3_agp_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= (void *)u3_sizes,
+	.size_type		= U32_APER_SIZE,
+	.num_aperture_sizes	= 8,
+	.configure		= uninorth_configure,
+	.fetch_size		= uninorth_fetch_size,
+	.cleanup		= uninorth_cleanup,
+	.tlb_flush		= uninorth_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= NULL,
+	.cache_flush		= null_cache_flush,
+	.agp_enable		= uninorth_agp_enable,
+	.create_gatt_table	= uninorth_create_gatt_table,
+	.free_gatt_table	= uninorth_free_gatt_table,
+	.insert_memory		= u3_insert_memory,
+	.remove_memory		= u3_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+	.cant_use_aperture	= 1,
+	.needs_scratch_page	= 1,
+};
+
+static struct agp_device_ids uninorth_agp_device_ids[] __devinitdata = {
+	{
+		.device_id	= PCI_DEVICE_ID_APPLE_UNI_N_AGP,
+		.chipset_name	= "UniNorth",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_APPLE_UNI_N_AGP_P,
+		.chipset_name	= "UniNorth/Pangea",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_APPLE_UNI_N_AGP15,
+		.chipset_name	= "UniNorth 1.5",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_APPLE_UNI_N_AGP2,
+		.chipset_name	= "UniNorth 2",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_APPLE_U3_AGP,
+		.chipset_name	= "U3",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_APPLE_U3L_AGP,
+		.chipset_name	= "U3L",
+	},
+	{
+		.device_id	= PCI_DEVICE_ID_APPLE_U3H_AGP,
+		.chipset_name	= "U3H",
+	},
+};
+
+static int __devinit agp_uninorth_probe(struct pci_dev *pdev,
+					const struct pci_device_id *ent)
+{
+	struct agp_device_ids *devs = uninorth_agp_device_ids;
+	struct agp_bridge_data *bridge;
+	struct device_node *uninorth_node;
+	u8 cap_ptr;
+	int j;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (cap_ptr == 0)
+		return -ENODEV;
+
+	/* probe for known chipsets */
+	for (j = 0; devs[j].chipset_name != NULL; ++j) {
+		if (pdev->device == devs[j].device_id) {
+			printk(KERN_INFO PFX "Detected Apple %s chipset\n",
+			       devs[j].chipset_name);
+			goto found;
+		}
+	}
+
+	printk(KERN_ERR PFX "Unsupported Apple chipset (device id: %04x).\n",
+		pdev->device);
+	return -ENODEV;
+
+ found:
+	/* Set revision to 0 if we could not read it. */
+	uninorth_rev = 0;
+	is_u3 = 0;
+	/* Locate core99 Uni-N */
+	uninorth_node = of_find_node_by_name(NULL, "uni-n");
+	/* Locate G5 u3 */
+	if (uninorth_node == NULL) {
+		is_u3 = 1;
+		uninorth_node = of_find_node_by_name(NULL, "u3");
+	}
+	if (uninorth_node) {
+		int *revprop = (int *)
+			get_property(uninorth_node, "device-rev", NULL);
+		if (revprop != NULL)
+			uninorth_rev = *revprop & 0x3f;
+		of_node_put(uninorth_node);
+	}
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	if (is_u3)
+		bridge->driver = &u3_agp_driver;
+	else
+		bridge->driver = &uninorth_agp_driver;
+
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+	bridge->flags = AGP_ERRATA_FASTWRITES;
+
+	/* Fill in the mode register */
+	pci_read_config_dword(pdev, cap_ptr+PCI_AGP_STATUS, &bridge->mode);
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_uninorth_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+static struct pci_device_id agp_uninorth_pci_table[] = {
+	{
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),
+	.class_mask	= ~0,
+	.vendor		= PCI_VENDOR_ID_APPLE,
+	.device		= PCI_ANY_ID,
+	.subvendor	= PCI_ANY_ID,
+	.subdevice	= PCI_ANY_ID,
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_uninorth_pci_table);
+
+static struct pci_driver agp_uninorth_pci_driver = {
+	.name		= "agpgart-uninorth",
+	.id_table	= agp_uninorth_pci_table,
+	.probe		= agp_uninorth_probe,
+	.remove		= agp_uninorth_remove,
+#ifdef CONFIG_PM
+	.suspend	= agp_uninorth_suspend,
+	.resume		= agp_uninorth_resume,
+#endif
+};
+
+static int __init agp_uninorth_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_uninorth_pci_driver);
+}
+
+static void __exit agp_uninorth_cleanup(void)
+{
+	pci_unregister_driver(&agp_uninorth_pci_driver);
+}
+
+module_init(agp_uninorth_init);
+module_exit(agp_uninorth_cleanup);
+
+MODULE_AUTHOR("Ben Herrenschmidt & Paul Mackerras");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/agp/via-agp.c b/drivers/char/agp/via-agp.c
new file mode 100644
index 0000000..e1451dd
--- /dev/null
+++ b/drivers/char/agp/via-agp.c
@@ -0,0 +1,548 @@
+/*
+ * VIA AGPGART routines.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/agp_backend.h>
+#include "agp.h"
+
+static struct pci_device_id agp_via_pci_table[];
+
+#define VIA_GARTCTRL	0x80
+#define VIA_APSIZE	0x84
+#define VIA_ATTBASE	0x88
+
+#define VIA_AGP3_GARTCTRL	0x90
+#define VIA_AGP3_APSIZE		0x94
+#define VIA_AGP3_ATTBASE	0x98
+#define VIA_AGPSEL		0xfd
+
+static int via_fetch_size(void)
+{
+	int i;
+	u8 temp;
+	struct aper_size_info_8 *values;
+
+	values = A_SIZE_8(agp_bridge->driver->aperture_sizes);
+	pci_read_config_byte(agp_bridge->dev, VIA_APSIZE, &temp);
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+			    agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+	printk(KERN_ERR PFX "Unknown aperture size from AGP bridge (0x%x)\n", temp);
+	return 0;
+}
+
+
+static int via_configure(void)
+{
+	u32 temp;
+	struct aper_size_info_8 *current_size;
+
+	current_size = A_SIZE_8(agp_bridge->current_size);
+	/* aperture size */
+	pci_write_config_byte(agp_bridge->dev, VIA_APSIZE,
+			      current_size->size_value);
+	/* address to map too */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* GART control register */
+	pci_write_config_dword(agp_bridge->dev, VIA_GARTCTRL, 0x0000000f);
+
+	/* attbase - aperture GATT base */
+	pci_write_config_dword(agp_bridge->dev, VIA_ATTBASE,
+			    (agp_bridge->gatt_bus_addr & 0xfffff000) | 3);
+	return 0;
+}
+
+
+static void via_cleanup(void)
+{
+	struct aper_size_info_8 *previous_size;
+
+	previous_size = A_SIZE_8(agp_bridge->previous_size);
+	pci_write_config_byte(agp_bridge->dev, VIA_APSIZE,
+			      previous_size->size_value);
+	/* Do not disable by writing 0 to VIA_ATTBASE, it screws things up
+	 * during reinitialization.
+	 */
+}
+
+
+static void via_tlbflush(struct agp_memory *mem)
+{
+	u32 temp;
+
+	pci_read_config_dword(agp_bridge->dev, VIA_GARTCTRL, &temp);
+	temp |= (1<<7);
+	pci_write_config_dword(agp_bridge->dev, VIA_GARTCTRL, temp);
+	temp &= ~(1<<7);
+	pci_write_config_dword(agp_bridge->dev, VIA_GARTCTRL, temp);
+}
+
+
+static struct aper_size_info_8 via_generic_sizes[9] =
+{
+	{256, 65536, 6, 0},
+	{128, 32768, 5, 128},
+	{64, 16384, 4, 192},
+	{32, 8192, 3, 224},
+	{16, 4096, 2, 240},
+	{8, 2048, 1, 248},
+	{4, 1024, 0, 252},
+	{2, 512, 0, 254},
+	{1, 256, 0, 255}
+};
+
+
+static int via_fetch_size_agp3(void)
+{
+	int i;
+	u16 temp;
+	struct aper_size_info_16 *values;
+
+	values = A_SIZE_16(agp_bridge->driver->aperture_sizes);
+	pci_read_config_word(agp_bridge->dev, VIA_AGP3_APSIZE, &temp);
+	temp &= 0xfff;
+
+	for (i = 0; i < agp_bridge->driver->num_aperture_sizes; i++) {
+		if (temp == values[i].size_value) {
+			agp_bridge->previous_size =
+				agp_bridge->current_size = (void *) (values + i);
+			agp_bridge->aperture_size_idx = i;
+			return values[i].size;
+		}
+	}
+	return 0;
+}
+
+
+static int via_configure_agp3(void)
+{
+	u32 temp;
+	struct aper_size_info_16 *current_size;
+
+	current_size = A_SIZE_16(agp_bridge->current_size);
+
+	/* address to map too */
+	pci_read_config_dword(agp_bridge->dev, AGP_APBASE, &temp);
+	agp_bridge->gart_bus_addr = (temp & PCI_BASE_ADDRESS_MEM_MASK);
+
+	/* attbase - aperture GATT base */
+	pci_write_config_dword(agp_bridge->dev, VIA_AGP3_ATTBASE,
+		agp_bridge->gatt_bus_addr & 0xfffff000);
+
+	/* 1. Enable GTLB in RX90<7>, all AGP aperture access needs to fetch
+	 *    translation table first.
+	 * 2. Enable AGP aperture in RX91<0>. This bit controls the enabling of the
+	 *    graphics AGP aperture for the AGP3.0 port.
+	 */
+	pci_read_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, &temp);
+	pci_write_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, temp | (3<<7));
+	return 0;
+}
+
+
+static void via_cleanup_agp3(void)
+{
+	struct aper_size_info_16 *previous_size;
+
+	previous_size = A_SIZE_16(agp_bridge->previous_size);
+	pci_write_config_byte(agp_bridge->dev, VIA_APSIZE, previous_size->size_value);
+}
+
+
+static void via_tlbflush_agp3(struct agp_memory *mem)
+{
+	u32 temp;
+
+	pci_read_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, &temp);
+	pci_write_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, temp & ~(1<<7));
+	pci_write_config_dword(agp_bridge->dev, VIA_AGP3_GARTCTRL, temp);
+}
+
+
+struct agp_bridge_driver via_agp3_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= agp3_generic_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 10,
+	.configure		= via_configure_agp3,
+	.fetch_size		= via_fetch_size_agp3,
+	.cleanup		= via_cleanup_agp3,
+	.tlb_flush		= via_tlbflush_agp3,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= NULL,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+struct agp_bridge_driver via_driver = {
+	.owner			= THIS_MODULE,
+	.aperture_sizes		= via_generic_sizes,
+	.size_type		= U8_APER_SIZE,
+	.num_aperture_sizes	= 9,
+	.configure		= via_configure,
+	.fetch_size		= via_fetch_size,
+	.cleanup		= via_cleanup,
+	.tlb_flush		= via_tlbflush,
+	.mask_memory		= agp_generic_mask_memory,
+	.masks			= NULL,
+	.agp_enable		= agp_generic_enable,
+	.cache_flush		= global_cache_flush,
+	.create_gatt_table	= agp_generic_create_gatt_table,
+	.free_gatt_table	= agp_generic_free_gatt_table,
+	.insert_memory		= agp_generic_insert_memory,
+	.remove_memory		= agp_generic_remove_memory,
+	.alloc_by_type		= agp_generic_alloc_by_type,
+	.free_by_type		= agp_generic_free_by_type,
+	.agp_alloc_page		= agp_generic_alloc_page,
+	.agp_destroy_page	= agp_generic_destroy_page,
+};
+
+static struct agp_device_ids via_agp_device_ids[] __devinitdata =
+{
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_82C597_0,
+		.chipset_name	= "Apollo VP3",
+	},
+
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_82C598_0,
+		.chipset_name	= "Apollo MVP3",
+	},
+
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8501_0,
+		.chipset_name	= "Apollo MVP4",
+	},
+
+	/* VT8601 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8601_0,
+		.chipset_name	= "Apollo ProMedia/PLE133Ta",
+	},
+
+	/* VT82C693A / VT28C694T */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_82C691_0,
+		.chipset_name	= "Apollo Pro 133",
+	},
+
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8371_0,
+		.chipset_name	= "KX133",
+	},
+
+	/* VT8633 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8633_0,
+		.chipset_name	= "Pro 266",
+	},
+
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_XN266,
+		.chipset_name	= "Apollo Pro266",
+	},
+
+	/* VT8361 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8361,
+		.chipset_name	= "KLE133",
+	},
+
+	/* VT8365 / VT8362 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8363_0,
+		.chipset_name	= "Twister-K/KT133x/KM133",
+	},
+
+	/* VT8753A */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8753_0,
+		.chipset_name	= "P4X266",
+	},
+
+	/* VT8366 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8367_0,
+		.chipset_name	= "KT266/KY266x/KT333",
+	},
+
+	/* VT8633 (for CuMine/ Celeron) */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8653_0,
+		.chipset_name	= "Pro266T",
+	},
+
+	/* KM266 / PM266 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_XM266,
+		.chipset_name	= "PM266/KM266",
+	},
+
+	/* CLE266 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_862X_0,
+		.chipset_name	= "CLE266",
+	},
+
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8377_0,
+		.chipset_name	= "KT400/KT400A/KT600",
+	},
+
+	/* VT8604 / VT8605 / VT8603
+	 * (Apollo Pro133A chipset with S3 Savage4) */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8605_0,
+		.chipset_name	= "ProSavage PM133/PL133/PN133"
+	},
+
+	/* P4M266x/P4N266 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8703_51_0,
+		.chipset_name	= "P4M266x/P4N266",
+	},
+
+	/* VT8754 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8754C_0,
+		.chipset_name	= "PT800",
+	},
+
+	/* P4X600 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8763_0,
+		.chipset_name	= "P4X600"
+	},
+
+	/* KM400 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8378_0,
+		.chipset_name	= "KM400/KM400A",
+	},
+
+	/* PT880 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_PT880,
+		.chipset_name	= "PT880",
+	},
+
+	/* PT890 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_8783_0,
+		.chipset_name	= "PT890",
+	},
+
+	/* PM800/PN800/PM880/PN880 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_PX8X0_0,
+		.chipset_name	= "PM800/PN800/PM880/PN880",
+	},
+	/* KT880 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_3269_0,
+		.chipset_name	= "KT880",
+	},
+	/* KTxxx/Px8xx */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_83_87XX_1,
+		.chipset_name	= "VT83xx/VT87xx/KTxxx/Px8xx",
+	},
+	/* P4M800 */
+	{
+		.device_id	= PCI_DEVICE_ID_VIA_3296_0,
+		.chipset_name	= "P4M800",
+	},
+
+	{ }, /* dummy final entry, always present */
+};
+
+
+/*
+ * VIA's AGP3 chipsets do magick to put the AGP bridge compliant
+ * with the same standards version as the graphics card.
+ */
+static void check_via_agp3 (struct agp_bridge_data *bridge)
+{
+	u8 reg;
+
+	pci_read_config_byte(bridge->dev, VIA_AGPSEL, &reg);
+	/* Check AGP 2.0 compatibility mode. */
+	if ((reg & (1<<1))==0)
+		bridge->driver = &via_agp3_driver;
+}
+
+
+static int __devinit agp_via_probe(struct pci_dev *pdev,
+				   const struct pci_device_id *ent)
+{
+	struct agp_device_ids *devs = via_agp_device_ids;
+	struct agp_bridge_data *bridge;
+	int j = 0;
+	u8 cap_ptr;
+
+	cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP);
+	if (!cap_ptr)
+		return -ENODEV;
+
+	j = ent - agp_via_pci_table;
+	printk (KERN_INFO PFX "Detected VIA %s chipset\n", devs[j].chipset_name);
+
+	bridge = agp_alloc_bridge();
+	if (!bridge)
+		return -ENOMEM;
+
+	bridge->dev = pdev;
+	bridge->capndx = cap_ptr;
+	bridge->driver = &via_driver;
+
+	/*
+	 * Garg, there are KT400s with KT266 IDs.
+	 */
+	if (pdev->device == PCI_DEVICE_ID_VIA_8367_0) {
+		/* Is there a KT400 subsystem ? */
+		if (pdev->subsystem_device == PCI_DEVICE_ID_VIA_8377_0) {
+			printk(KERN_INFO PFX "Found KT400 in disguise as a KT266.\n");
+			check_via_agp3(bridge);
+		}
+	}
+
+	/* If this is an AGP3 bridge, check which mode its in and adjust. */
+	get_agp_version(bridge);
+	if (bridge->major_version >= 3)
+		check_via_agp3(bridge);
+
+	/* Fill in the mode register */
+	pci_read_config_dword(pdev,
+			bridge->capndx+PCI_AGP_STATUS, &bridge->mode);
+
+	pci_set_drvdata(pdev, bridge);
+	return agp_add_bridge(bridge);
+}
+
+static void __devexit agp_via_remove(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	agp_remove_bridge(bridge);
+	agp_put_bridge(bridge);
+}
+
+#ifdef CONFIG_PM
+
+static int agp_via_suspend(struct pci_dev *pdev, pm_message_t state)
+{
+	pci_save_state (pdev);
+	pci_set_power_state (pdev, PCI_D3hot);
+
+	return 0;
+}
+
+static int agp_via_resume(struct pci_dev *pdev)
+{
+	struct agp_bridge_data *bridge = pci_get_drvdata(pdev);
+
+	pci_set_power_state (pdev, PCI_D0);
+	pci_restore_state(pdev);
+
+	if (bridge->driver == &via_agp3_driver)
+		return via_configure_agp3();
+	else if (bridge->driver == &via_driver)
+		return via_configure();
+
+	return 0;
+}
+
+#endif /* CONFIG_PM */
+
+/* must be the same order as name table above */
+static struct pci_device_id agp_via_pci_table[] = {
+#define ID(x) \
+	{						\
+	.class		= (PCI_CLASS_BRIDGE_HOST << 8),	\
+	.class_mask	= ~0,				\
+	.vendor		= PCI_VENDOR_ID_VIA,		\
+	.device		= x,				\
+	.subvendor	= PCI_ANY_ID,			\
+	.subdevice	= PCI_ANY_ID,			\
+	}
+	ID(PCI_DEVICE_ID_VIA_82C597_0),
+	ID(PCI_DEVICE_ID_VIA_82C598_0),
+	ID(PCI_DEVICE_ID_VIA_8501_0),
+	ID(PCI_DEVICE_ID_VIA_8601_0),
+	ID(PCI_DEVICE_ID_VIA_82C691_0),
+	ID(PCI_DEVICE_ID_VIA_8371_0),
+	ID(PCI_DEVICE_ID_VIA_8633_0),
+	ID(PCI_DEVICE_ID_VIA_XN266),
+	ID(PCI_DEVICE_ID_VIA_8361),
+	ID(PCI_DEVICE_ID_VIA_8363_0),
+	ID(PCI_DEVICE_ID_VIA_8753_0),
+	ID(PCI_DEVICE_ID_VIA_8367_0),
+	ID(PCI_DEVICE_ID_VIA_8653_0),
+	ID(PCI_DEVICE_ID_VIA_XM266),
+	ID(PCI_DEVICE_ID_VIA_862X_0),
+	ID(PCI_DEVICE_ID_VIA_8377_0),
+	ID(PCI_DEVICE_ID_VIA_8605_0),
+	ID(PCI_DEVICE_ID_VIA_8703_51_0),
+	ID(PCI_DEVICE_ID_VIA_8754C_0),
+	ID(PCI_DEVICE_ID_VIA_8763_0),
+	ID(PCI_DEVICE_ID_VIA_8378_0),
+	ID(PCI_DEVICE_ID_VIA_PT880),
+	ID(PCI_DEVICE_ID_VIA_8783_0),
+	ID(PCI_DEVICE_ID_VIA_PX8X0_0),
+	ID(PCI_DEVICE_ID_VIA_3269_0),
+	ID(PCI_DEVICE_ID_VIA_83_87XX_1),
+	ID(PCI_DEVICE_ID_VIA_3296_0),
+	{ }
+};
+
+MODULE_DEVICE_TABLE(pci, agp_via_pci_table);
+
+
+static struct pci_driver agp_via_pci_driver = {
+	.name		= "agpgart-via",
+	.id_table	= agp_via_pci_table,
+	.probe		= agp_via_probe,
+	.remove		= agp_via_remove,
+#ifdef CONFIG_PM
+	.suspend	= agp_via_suspend,
+	.resume		= agp_via_resume,
+#endif
+};
+
+
+static int __init agp_via_init(void)
+{
+	if (agp_off)
+		return -EINVAL;
+	return pci_register_driver(&agp_via_pci_driver);
+}
+
+static void __exit agp_via_cleanup(void)
+{
+	pci_unregister_driver(&agp_via_pci_driver);
+}
+
+module_init(agp_via_init);
+module_exit(agp_via_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dave Jones <davej@codemonkey.org.uk>");
diff --git a/drivers/char/amiserial.c b/drivers/char/amiserial.c
new file mode 100644
index 0000000..1dc4259
--- /dev/null
+++ b/drivers/char/amiserial.c
@@ -0,0 +1,2179 @@
+/*
+ *  linux/drivers/char/amiserial.c
+ *
+ * Serial driver for the amiga builtin port.
+ *
+ * This code was created by taking serial.c version 4.30 from kernel
+ * release 2.3.22, replacing all hardware related stuff with the
+ * corresponding amiga hardware actions, and removing all irrelevant
+ * code. As a consequence, it uses many of the constants and names
+ * associated with the registers and bits of 16550 compatible UARTS -
+ * but only to keep track of status, etc in the state variables. It
+ * was done this was to make it easier to keep the code in line with
+ * (non hardware specific) changes to serial.c.
+ *
+ * The port is registered with the tty driver as minor device 64, and
+ * therefore other ports should should only use 65 upwards.
+ *
+ * Richard Lucock 28/12/99
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ *  Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 
+ * 		1998, 1999  Theodore Ts'o
+ *
+ */
+
+/*
+ * Serial driver configuration section.  Here are the various options:
+ *
+ * SERIAL_PARANOIA_CHECK
+ * 		Check the magic number for the async_structure where
+ * 		ever possible.
+ */
+
+#include <linux/config.h>
+#include <linux/delay.h>
+
+#undef SERIAL_PARANOIA_CHECK
+#define SERIAL_DO_RESTART
+
+/* Set of debugging defines */
+
+#undef SERIAL_DEBUG_INTR
+#undef SERIAL_DEBUG_OPEN
+#undef SERIAL_DEBUG_FLOW
+#undef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+
+/* Sanity checks */
+
+#define SERIAL_INLINE
+  
+#if defined(MODULE) && defined(SERIAL_DEBUG_MCOUNT)
+#define DBG_CNT(s) printk("(%s): [%x] refc=%d, serc=%d, ttyc=%d -> %s\n", \
+ tty->name, (info->flags), serial_driver->refcount,info->count,tty->count,s)
+#else
+#define DBG_CNT(s)
+#endif
+
+/*
+ * End of serial driver configuration section.
+ */
+
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/serial.h>
+#include <linux/serialP.h>
+#include <linux/serial_reg.h>
+static char *serial_version = "4.30";
+
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/console.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <asm/setup.h>
+
+#include <asm/system.h>
+
+#include <asm/irq.h>
+
+#include <asm/amigahw.h>
+#include <asm/amigaints.h>
+
+#ifdef SERIAL_INLINE
+#define _INLINE_ inline
+#endif
+
+static char *serial_name = "Amiga-builtin serial driver";
+
+static struct tty_driver *serial_driver;
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+static struct async_struct *IRQ_ports;
+
+static unsigned char current_ctl_bits;
+
+static void change_speed(struct async_struct *info, struct termios *old);
+static void rs_wait_until_sent(struct tty_struct *tty, int timeout);
+
+
+static struct serial_state rs_table[1];
+
+#define NR_PORTS	(sizeof(rs_table)/sizeof(struct serial_state))
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write.  We need to
+ * lock it in case the copy_from_user blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char *tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+#include <asm/uaccess.h>
+
+#define serial_isroot()	(capable(CAP_SYS_ADMIN))
+
+
+static inline int serial_paranoia_check(struct async_struct *info,
+					char *name, const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+	static const char *badmagic =
+		"Warning: bad magic number for serial struct (%s) in %s\n";
+	static const char *badinfo =
+		"Warning: null async_struct for (%s) in %s\n";
+
+	if (!info) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (info->magic != SERIAL_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+#endif
+	return 0;
+}
+
+/* some serial hardware definitions */
+#define SDR_OVRUN   (1<<15)
+#define SDR_RBF     (1<<14)
+#define SDR_TBE     (1<<13)
+#define SDR_TSRE    (1<<12)
+
+#define SERPER_PARENB    (1<<15)
+
+#define AC_SETCLR   (1<<15)
+#define AC_UARTBRK  (1<<11)
+
+#define SER_DTR     (1<<7)
+#define SER_RTS     (1<<6)
+#define SER_DCD     (1<<5)
+#define SER_CTS     (1<<4)
+#define SER_DSR     (1<<3)
+
+static __inline__ void rtsdtr_ctrl(int bits)
+{
+    ciab.pra = ((bits & (SER_RTS | SER_DTR)) ^ (SER_RTS | SER_DTR)) | (ciab.pra & ~(SER_RTS | SER_DTR));
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_stop() and rs_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * They enable or disable transmitter interrupts, as necessary.
+ * ------------------------------------------------------------
+ */
+static void rs_stop(struct tty_struct *tty)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_stop"))
+		return;
+
+	local_irq_save(flags);
+	if (info->IER & UART_IER_THRI) {
+		info->IER &= ~UART_IER_THRI;
+		/* disable Tx interrupt and remove any pending interrupts */
+		custom.intena = IF_TBE;
+		mb();
+		custom.intreq = IF_TBE;
+		mb();
+	}
+	local_irq_restore(flags);
+}
+
+static void rs_start(struct tty_struct *tty)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_start"))
+		return;
+
+	local_irq_save(flags);
+	if (info->xmit.head != info->xmit.tail
+	    && info->xmit.buf
+	    && !(info->IER & UART_IER_THRI)) {
+		info->IER |= UART_IER_THRI;
+		custom.intena = IF_SETCLR | IF_TBE;
+		mb();
+		/* set a pending Tx Interrupt, transmitter should restart now */
+		custom.intreq = IF_SETCLR | IF_TBE;
+		mb();
+	}
+	local_irq_restore(flags);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * Here starts the interrupt handling routines.  All of the following
+ * subroutines are declared as inline and are folded into
+ * rs_interrupt().  They were separated out for readability's sake.
+ *
+ * Note: rs_interrupt() is a "fast" interrupt, which means that it
+ * runs with interrupts turned off.  People who may want to modify
+ * rs_interrupt() should try to keep the interrupt handler as fast as
+ * possible.  After you are done making modifications, it is not a bad
+ * idea to do:
+ * 
+ * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c
+ *
+ * and look at the resulting assemble code in serial.s.
+ *
+ * 				- Ted Ts'o (tytso@mit.edu), 7-Mar-93
+ * -----------------------------------------------------------------------
+ */
+
+/*
+ * This routine is used by the interrupt handler to schedule
+ * processing in the software interrupt portion of the driver.
+ */
+static _INLINE_ void rs_sched_event(struct async_struct *info,
+				  int event)
+{
+	info->event |= 1 << event;
+	tasklet_schedule(&info->tlet);
+}
+
+static _INLINE_ void receive_chars(struct async_struct *info)
+{
+        int status;
+	int serdatr;
+	struct tty_struct *tty = info->tty;
+	unsigned char ch;
+	struct	async_icount *icount;
+
+	icount = &info->state->icount;
+
+	status = UART_LSR_DR; /* We obviously have a character! */
+	serdatr = custom.serdatr;
+	mb();
+	custom.intreq = IF_RBF;
+	mb();
+
+	if((serdatr & 0x1ff) == 0)
+	    status |= UART_LSR_BI;
+	if(serdatr & SDR_OVRUN)
+	    status |= UART_LSR_OE;
+
+	ch = serdatr & 0xff;
+	if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+	  goto ignore_char;
+	*tty->flip.char_buf_ptr = ch;
+	icount->rx++;
+
+#ifdef SERIAL_DEBUG_INTR
+	printk("DR%02x:%02x...", ch, status);
+#endif
+	*tty->flip.flag_buf_ptr = 0;
+
+	/*
+	 * We don't handle parity or frame errors - but I have left
+	 * the code in, since I'm not sure that the errors can't be
+	 * detected.
+	 */
+
+	if (status & (UART_LSR_BI | UART_LSR_PE |
+		      UART_LSR_FE | UART_LSR_OE)) {
+	  /*
+	   * For statistics only
+	   */
+	  if (status & UART_LSR_BI) {
+	    status &= ~(UART_LSR_FE | UART_LSR_PE);
+	    icount->brk++;
+	  } else if (status & UART_LSR_PE)
+	    icount->parity++;
+	  else if (status & UART_LSR_FE)
+	    icount->frame++;
+	  if (status & UART_LSR_OE)
+	    icount->overrun++;
+
+	  /*
+	   * Now check to see if character should be
+	   * ignored, and mask off conditions which
+	   * should be ignored.
+	   */
+	  if (status & info->ignore_status_mask)
+	    goto ignore_char;
+
+	  status &= info->read_status_mask;
+
+	  if (status & (UART_LSR_BI)) {
+#ifdef SERIAL_DEBUG_INTR
+	    printk("handling break....");
+#endif
+	    *tty->flip.flag_buf_ptr = TTY_BREAK;
+	    if (info->flags & ASYNC_SAK)
+	      do_SAK(tty);
+	  } else if (status & UART_LSR_PE)
+	    *tty->flip.flag_buf_ptr = TTY_PARITY;
+	  else if (status & UART_LSR_FE)
+	    *tty->flip.flag_buf_ptr = TTY_FRAME;
+	  if (status & UART_LSR_OE) {
+	    /*
+	     * Overrun is special, since it's
+	     * reported immediately, and doesn't
+	     * affect the current character
+	     */
+	    if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+	      tty->flip.count++;
+	      tty->flip.flag_buf_ptr++;
+	      tty->flip.char_buf_ptr++;
+	      *tty->flip.flag_buf_ptr = TTY_OVERRUN;
+	    }
+	  }
+	}
+	tty->flip.flag_buf_ptr++;
+	tty->flip.char_buf_ptr++;
+	tty->flip.count++;
+ ignore_char:
+
+	tty_flip_buffer_push(tty);
+}
+
+static _INLINE_ void transmit_chars(struct async_struct *info)
+{
+	custom.intreq = IF_TBE;
+	mb();
+	if (info->x_char) {
+	        custom.serdat = info->x_char | 0x100;
+		mb();
+		info->state->icount.tx++;
+		info->x_char = 0;
+		return;
+	}
+	if (info->xmit.head == info->xmit.tail
+	    || info->tty->stopped
+	    || info->tty->hw_stopped) {
+		info->IER &= ~UART_IER_THRI;
+	        custom.intena = IF_TBE;
+		mb();
+		return;
+	}
+
+	custom.serdat = info->xmit.buf[info->xmit.tail++] | 0x100;
+	mb();
+	info->xmit.tail = info->xmit.tail & (SERIAL_XMIT_SIZE-1);
+	info->state->icount.tx++;
+
+	if (CIRC_CNT(info->xmit.head,
+		     info->xmit.tail,
+		     SERIAL_XMIT_SIZE) < WAKEUP_CHARS)
+		rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
+
+#ifdef SERIAL_DEBUG_INTR
+	printk("THRE...");
+#endif
+	if (info->xmit.head == info->xmit.tail) {
+	        custom.intena = IF_TBE;
+		mb();
+		info->IER &= ~UART_IER_THRI;
+	}
+}
+
+static _INLINE_ void check_modem_status(struct async_struct *info)
+{
+	unsigned char status = ciab.pra & (SER_DCD | SER_CTS | SER_DSR);
+	unsigned char dstatus;
+	struct	async_icount *icount;
+
+	/* Determine bits that have changed */
+	dstatus = status ^ current_ctl_bits;
+	current_ctl_bits = status;
+
+	if (dstatus) {
+		icount = &info->state->icount;
+		/* update input line counters */
+		if (dstatus & SER_DSR)
+			icount->dsr++;
+		if (dstatus & SER_DCD) {
+			icount->dcd++;
+#ifdef CONFIG_HARD_PPS
+			if ((info->flags & ASYNC_HARDPPS_CD) &&
+			    !(status & SER_DCD))
+				hardpps();
+#endif
+		}
+		if (dstatus & SER_CTS)
+			icount->cts++;
+		wake_up_interruptible(&info->delta_msr_wait);
+	}
+
+	if ((info->flags & ASYNC_CHECK_CD) && (dstatus & SER_DCD)) {
+#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR))
+		printk("ttyS%d CD now %s...", info->line,
+		       (!(status & SER_DCD)) ? "on" : "off");
+#endif
+		if (!(status & SER_DCD))
+			wake_up_interruptible(&info->open_wait);
+		else {
+#ifdef SERIAL_DEBUG_OPEN
+			printk("doing serial hangup...");
+#endif
+			if (info->tty)
+				tty_hangup(info->tty);
+		}
+	}
+	if (info->flags & ASYNC_CTS_FLOW) {
+		if (info->tty->hw_stopped) {
+			if (!(status & SER_CTS)) {
+#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))
+				printk("CTS tx start...");
+#endif
+				info->tty->hw_stopped = 0;
+				info->IER |= UART_IER_THRI;
+				custom.intena = IF_SETCLR | IF_TBE;
+				mb();
+				/* set a pending Tx Interrupt, transmitter should restart now */
+				custom.intreq = IF_SETCLR | IF_TBE;
+				mb();
+				rs_sched_event(info, RS_EVENT_WRITE_WAKEUP);
+				return;
+			}
+		} else {
+			if ((status & SER_CTS)) {
+#if (defined(SERIAL_DEBUG_INTR) || defined(SERIAL_DEBUG_FLOW))
+				printk("CTS tx stop...");
+#endif
+				info->tty->hw_stopped = 1;
+				info->IER &= ~UART_IER_THRI;
+				/* disable Tx interrupt and remove any pending interrupts */
+				custom.intena = IF_TBE;
+				mb();
+				custom.intreq = IF_TBE;
+				mb();
+			}
+		}
+	}
+}
+
+static irqreturn_t ser_vbl_int( int irq, void *data, struct pt_regs *regs)
+{
+        /* vbl is just a periodic interrupt we tie into to update modem status */
+	struct async_struct * info = IRQ_ports;
+	/*
+	 * TBD - is it better to unregister from this interrupt or to
+	 * ignore it if MSI is clear ?
+	 */
+	if(info->IER & UART_IER_MSI)
+	  check_modem_status(info);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t ser_rx_int(int irq, void *dev_id, struct pt_regs * regs)
+{
+	struct async_struct * info;
+
+#ifdef SERIAL_DEBUG_INTR
+	printk("ser_rx_int...");
+#endif
+
+	info = IRQ_ports;
+	if (!info || !info->tty)
+		return IRQ_NONE;
+
+	receive_chars(info);
+	info->last_active = jiffies;
+#ifdef SERIAL_DEBUG_INTR
+	printk("end.\n");
+#endif
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t ser_tx_int(int irq, void *dev_id, struct pt_regs * regs)
+{
+	struct async_struct * info;
+
+	if (custom.serdatr & SDR_TBE) {
+#ifdef SERIAL_DEBUG_INTR
+	  printk("ser_tx_int...");
+#endif
+
+	  info = IRQ_ports;
+	  if (!info || !info->tty)
+		return IRQ_NONE;
+
+	  transmit_chars(info);
+	  info->last_active = jiffies;
+#ifdef SERIAL_DEBUG_INTR
+	  printk("end.\n");
+#endif
+	}
+	return IRQ_HANDLED;
+}
+
+/*
+ * -------------------------------------------------------------------
+ * Here ends the serial interrupt routines.
+ * -------------------------------------------------------------------
+ */
+
+/*
+ * This routine is used to handle the "bottom half" processing for the
+ * serial driver, known also the "software interrupt" processing.
+ * This processing is done at the kernel interrupt level, after the
+ * rs_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON.  This
+ * is where time-consuming activities which can not be done in the
+ * interrupt driver proper are done; the interrupt driver schedules
+ * them using rs_sched_event(), and they get done here.
+ */
+
+static void do_softint(unsigned long private_)
+{
+	struct async_struct	*info = (struct async_struct *) private_;
+	struct tty_struct	*tty;
+
+	tty = info->tty;
+	if (!tty)
+		return;
+
+	if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &info->event)) {
+		tty_wakeup(tty);
+		wake_up_interruptible(&tty->write_wait);
+	}
+}
+
+/*
+ * ---------------------------------------------------------------
+ * Low level utility subroutines for the serial driver:  routines to
+ * figure out the appropriate timeout for an interrupt chain, routines
+ * to initialize and startup a serial port, and routines to shutdown a
+ * serial port.  Useful stuff like that.
+ * ---------------------------------------------------------------
+ */
+
+static int startup(struct async_struct * info)
+{
+	unsigned long flags;
+	int	retval=0;
+	unsigned long page;
+
+	page = get_zeroed_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	local_irq_save(flags);
+
+	if (info->flags & ASYNC_INITIALIZED) {
+		free_page(page);
+		goto errout;
+	}
+
+	if (info->xmit.buf)
+		free_page(page);
+	else
+		info->xmit.buf = (unsigned char *) page;
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("starting up ttys%d ...", info->line);
+#endif
+
+	/* Clear anything in the input buffer */
+
+	custom.intreq = IF_RBF;
+	mb();
+
+	retval = request_irq(IRQ_AMIGA_VERTB, ser_vbl_int, 0, "serial status", info);
+	if (retval) {
+	  if (serial_isroot()) {
+	    if (info->tty)
+	      set_bit(TTY_IO_ERROR,
+		      &info->tty->flags);
+	    retval = 0;
+	  }
+	  goto errout;
+	}
+
+	/* enable both Rx and Tx interrupts */
+	custom.intena = IF_SETCLR | IF_RBF | IF_TBE;
+	mb();
+	info->IER = UART_IER_MSI;
+
+	/* remember current state of the DCD and CTS bits */
+	current_ctl_bits = ciab.pra & (SER_DCD | SER_CTS | SER_DSR);
+
+	IRQ_ports = info;
+
+	info->MCR = 0;
+	if (info->tty->termios->c_cflag & CBAUD)
+	  info->MCR = SER_DTR | SER_RTS;
+	rtsdtr_ctrl(info->MCR);
+
+	if (info->tty)
+		clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	info->xmit.head = info->xmit.tail = 0;
+
+	/*
+	 * Set up the tty->alt_speed kludge
+	 */
+	if (info->tty) {
+		if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			info->tty->alt_speed = 57600;
+		if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			info->tty->alt_speed = 115200;
+		if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+			info->tty->alt_speed = 230400;
+		if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+			info->tty->alt_speed = 460800;
+	}
+
+	/*
+	 * and set the speed of the serial port
+	 */
+	change_speed(info, NULL);
+
+	info->flags |= ASYNC_INITIALIZED;
+	local_irq_restore(flags);
+	return 0;
+
+errout:
+	local_irq_restore(flags);
+	return retval;
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts are disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void shutdown(struct async_struct * info)
+{
+	unsigned long	flags;
+	struct serial_state *state;
+
+	if (!(info->flags & ASYNC_INITIALIZED))
+		return;
+
+	state = info->state;
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("Shutting down serial port %d ....\n", info->line);
+#endif
+
+	local_irq_save(flags); /* 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(&info->delta_msr_wait);
+
+	IRQ_ports = NULL;
+
+	/*
+	 * Free the IRQ, if necessary
+	 */
+	free_irq(IRQ_AMIGA_VERTB, info);
+
+	if (info->xmit.buf) {
+		free_page((unsigned long) info->xmit.buf);
+		info->xmit.buf = NULL;
+	}
+
+	info->IER = 0;
+	custom.intena = IF_RBF | IF_TBE;
+	mb();
+
+	/* disable break condition */
+	custom.adkcon = AC_UARTBRK;
+	mb();
+
+	if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
+		info->MCR &= ~(SER_DTR|SER_RTS);
+	rtsdtr_ctrl(info->MCR);
+
+	if (info->tty)
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	info->flags &= ~ASYNC_INITIALIZED;
+	local_irq_restore(flags);
+}
+
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+static void change_speed(struct async_struct *info,
+			 struct termios *old_termios)
+{
+	int	quot = 0, baud_base, baud;
+	unsigned cflag, cval = 0;
+	int	bits;
+	unsigned long	flags;
+
+	if (!info->tty || !info->tty->termios)
+		return;
+	cflag = info->tty->termios->c_cflag;
+
+	/* Byte size is always 8 bits plus parity bit if requested */
+
+	cval = 3; bits = 10;
+	if (cflag & CSTOPB) {
+		cval |= 0x04;
+		bits++;
+	}
+	if (cflag & PARENB) {
+		cval |= UART_LCR_PARITY;
+		bits++;
+	}
+	if (!(cflag & PARODD))
+		cval |= UART_LCR_EPAR;
+#ifdef CMSPAR
+	if (cflag & CMSPAR)
+		cval |= UART_LCR_SPAR;
+#endif
+
+	/* Determine divisor based on baud rate */
+	baud = tty_get_baud_rate(info->tty);
+	if (!baud)
+		baud = 9600;	/* B0 transition handled in rs_set_termios */
+	baud_base = info->state->baud_base;
+	if (baud == 38400 &&
+	    ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
+		quot = info->state->custom_divisor;
+	else {
+		if (baud == 134)
+			/* Special case since 134 is really 134.5 */
+			quot = (2*baud_base / 269);
+		else if (baud)
+			quot = baud_base / baud;
+	}
+	/* If the quotient is zero refuse the change */
+	if (!quot && old_termios) {
+		info->tty->termios->c_cflag &= ~CBAUD;
+		info->tty->termios->c_cflag |= (old_termios->c_cflag & CBAUD);
+		baud = tty_get_baud_rate(info->tty);
+		if (!baud)
+			baud = 9600;
+		if (baud == 38400 &&
+		    ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
+			quot = info->state->custom_divisor;
+		else {
+			if (baud == 134)
+				/* Special case since 134 is really 134.5 */
+				quot = (2*baud_base / 269);
+			else if (baud)
+				quot = baud_base / baud;
+		}
+	}
+	/* As a last resort, if the quotient is zero, default to 9600 bps */
+	if (!quot)
+		quot = baud_base / 9600;
+	info->quot = quot;
+	info->timeout = ((info->xmit_fifo_size*HZ*bits*quot) / baud_base);
+	info->timeout += HZ/50;		/* Add .02 seconds of slop */
+
+	/* CTS flow control flag and modem status interrupts */
+	info->IER &= ~UART_IER_MSI;
+	if (info->flags & ASYNC_HARDPPS_CD)
+		info->IER |= UART_IER_MSI;
+	if (cflag & CRTSCTS) {
+		info->flags |= ASYNC_CTS_FLOW;
+		info->IER |= UART_IER_MSI;
+	} else
+		info->flags &= ~ASYNC_CTS_FLOW;
+	if (cflag & CLOCAL)
+		info->flags &= ~ASYNC_CHECK_CD;
+	else {
+		info->flags |= ASYNC_CHECK_CD;
+		info->IER |= UART_IER_MSI;
+	}
+	/* TBD:
+	 * Does clearing IER_MSI imply that we should disbale the VBL interrupt ?
+	 */
+
+	/*
+	 * Set up parity check flag
+	 */
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+	info->read_status_mask = UART_LSR_OE | UART_LSR_DR;
+	if (I_INPCK(info->tty))
+		info->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+	if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+		info->read_status_mask |= UART_LSR_BI;
+
+	/*
+	 * Characters to ignore
+	 */
+	info->ignore_status_mask = 0;
+	if (I_IGNPAR(info->tty))
+		info->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+	if (I_IGNBRK(info->tty)) {
+		info->ignore_status_mask |= UART_LSR_BI;
+		/*
+		 * If we're ignore parity and break indicators, ignore 
+		 * overruns too.  (For real raw support).
+		 */
+		if (I_IGNPAR(info->tty))
+			info->ignore_status_mask |= UART_LSR_OE;
+	}
+	/*
+	 * !!! ignore all characters if CREAD is not set
+	 */
+	if ((cflag & CREAD) == 0)
+		info->ignore_status_mask |= UART_LSR_DR;
+	local_irq_save(flags);
+
+	{
+	  short serper;
+
+	/* Set up the baud rate */
+	  serper = quot - 1;
+
+	/* Enable or disable parity bit */
+
+	if(cval & UART_LCR_PARITY)
+	  serper |= (SERPER_PARENB);
+
+	custom.serper = serper;
+	mb();
+	}
+
+	info->LCR = cval;				/* Save LCR */
+	local_irq_restore(flags);
+}
+
+static void rs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_put_char"))
+		return;
+
+	if (!tty || !info->xmit.buf)
+		return;
+
+	local_irq_save(flags);
+	if (CIRC_SPACE(info->xmit.head,
+		       info->xmit.tail,
+		       SERIAL_XMIT_SIZE) == 0) {
+		local_irq_restore(flags);
+		return;
+	}
+
+	info->xmit.buf[info->xmit.head++] = ch;
+	info->xmit.head &= SERIAL_XMIT_SIZE-1;
+	local_irq_restore(flags);
+}
+
+static void rs_flush_chars(struct tty_struct *tty)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_flush_chars"))
+		return;
+
+	if (info->xmit.head == info->xmit.tail
+	    || tty->stopped
+	    || tty->hw_stopped
+	    || !info->xmit.buf)
+		return;
+
+	local_irq_save(flags);
+	info->IER |= UART_IER_THRI;
+	custom.intena = IF_SETCLR | IF_TBE;
+	mb();
+	/* set a pending Tx Interrupt, transmitter should restart now */
+	custom.intreq = IF_SETCLR | IF_TBE;
+	mb();
+	local_irq_restore(flags);
+}
+
+static int rs_write(struct tty_struct * tty, const unsigned char *buf, int count)
+{
+	int	c, ret = 0;
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_write"))
+		return 0;
+
+	if (!tty || !info->xmit.buf || !tmp_buf)
+		return 0;
+
+	local_save_flags(flags);
+	local_irq_disable();
+	while (1) {
+		c = CIRC_SPACE_TO_END(info->xmit.head,
+				      info->xmit.tail,
+				      SERIAL_XMIT_SIZE);
+		if (count < c)
+			c = count;
+		if (c <= 0) {
+			break;
+		}
+		memcpy(info->xmit.buf + info->xmit.head, buf, c);
+		info->xmit.head = ((info->xmit.head + c) &
+				   (SERIAL_XMIT_SIZE-1));
+		buf += c;
+		count -= c;
+		ret += c;
+	}
+	local_irq_restore(flags);
+
+	if (info->xmit.head != info->xmit.tail
+	    && !tty->stopped
+	    && !tty->hw_stopped
+	    && !(info->IER & UART_IER_THRI)) {
+		info->IER |= UART_IER_THRI;
+		local_irq_disable();
+		custom.intena = IF_SETCLR | IF_TBE;
+		mb();
+		/* set a pending Tx Interrupt, transmitter should restart now */
+		custom.intreq = IF_SETCLR | IF_TBE;
+		mb();
+		local_irq_restore(flags);
+	}
+	return ret;
+}
+
+static int rs_write_room(struct tty_struct *tty)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+
+	if (serial_paranoia_check(info, tty->name, "rs_write_room"))
+		return 0;
+	return CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE);
+}
+
+static int rs_chars_in_buffer(struct tty_struct *tty)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+
+	if (serial_paranoia_check(info, tty->name, "rs_chars_in_buffer"))
+		return 0;
+	return CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE);
+}
+
+static void rs_flush_buffer(struct tty_struct *tty)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_flush_buffer"))
+		return;
+	local_irq_save(flags);
+	info->xmit.head = info->xmit.tail = 0;
+	local_irq_restore(flags);
+	wake_up_interruptible(&tty->write_wait);
+	tty_wakeup(tty);
+}
+
+/*
+ * This function is used to send a high-priority XON/XOFF character to
+ * the device
+ */
+static void rs_send_xchar(struct tty_struct *tty, char ch)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+        unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_send_char"))
+		return;
+
+	info->x_char = ch;
+	if (ch) {
+		/* Make sure transmit interrupts are on */
+
+	        /* Check this ! */
+	        local_irq_save(flags);
+		if(!(custom.intenar & IF_TBE)) {
+		    custom.intena = IF_SETCLR | IF_TBE;
+		    mb();
+		    /* set a pending Tx Interrupt, transmitter should restart now */
+		    custom.intreq = IF_SETCLR | IF_TBE;
+		    mb();
+		}
+		local_irq_restore(flags);
+
+		info->IER |= UART_IER_THRI;
+	}
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_throttle()
+ * 
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled.
+ * ------------------------------------------------------------
+ */
+static void rs_throttle(struct tty_struct * tty)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+#ifdef SERIAL_DEBUG_THROTTLE
+	char	buf[64];
+
+	printk("throttle %s: %d....\n", tty_name(tty, buf),
+	       tty->ldisc.chars_in_buffer(tty));
+#endif
+
+	if (serial_paranoia_check(info, tty->name, "rs_throttle"))
+		return;
+
+	if (I_IXOFF(tty))
+		rs_send_xchar(tty, STOP_CHAR(tty));
+
+	if (tty->termios->c_cflag & CRTSCTS)
+		info->MCR &= ~SER_RTS;
+
+	local_irq_save(flags);
+	rtsdtr_ctrl(info->MCR);
+	local_irq_restore(flags);
+}
+
+static void rs_unthrottle(struct tty_struct * tty)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+#ifdef SERIAL_DEBUG_THROTTLE
+	char	buf[64];
+
+	printk("unthrottle %s: %d....\n", tty_name(tty, buf),
+	       tty->ldisc.chars_in_buffer(tty));
+#endif
+
+	if (serial_paranoia_check(info, tty->name, "rs_unthrottle"))
+		return;
+
+	if (I_IXOFF(tty)) {
+		if (info->x_char)
+			info->x_char = 0;
+		else
+			rs_send_xchar(tty, START_CHAR(tty));
+	}
+	if (tty->termios->c_cflag & CRTSCTS)
+		info->MCR |= SER_RTS;
+	local_irq_save(flags);
+	rtsdtr_ctrl(info->MCR);
+	local_irq_restore(flags);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_ioctl() and friends
+ * ------------------------------------------------------------
+ */
+
+static int get_serial_info(struct async_struct * info,
+			   struct serial_struct * retinfo)
+{
+	struct serial_struct tmp;
+	struct serial_state *state = info->state;
+   
+	if (!retinfo)
+		return -EFAULT;
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.type = state->type;
+	tmp.line = state->line;
+	tmp.port = state->port;
+	tmp.irq = state->irq;
+	tmp.flags = state->flags;
+	tmp.xmit_fifo_size = state->xmit_fifo_size;
+	tmp.baud_base = state->baud_base;
+	tmp.close_delay = state->close_delay;
+	tmp.closing_wait = state->closing_wait;
+	tmp.custom_divisor = state->custom_divisor;
+	if (copy_to_user(retinfo,&tmp,sizeof(*retinfo)))
+		return -EFAULT;
+	return 0;
+}
+
+static int set_serial_info(struct async_struct * info,
+			   struct serial_struct * new_info)
+{
+	struct serial_struct new_serial;
+ 	struct serial_state old_state, *state;
+	unsigned int		change_irq,change_port;
+	int 			retval = 0;
+
+	if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+		return -EFAULT;
+	state = info->state;
+	old_state = *state;
+  
+	change_irq = new_serial.irq != state->irq;
+	change_port = (new_serial.port != state->port);
+	if(change_irq || change_port || (new_serial.xmit_fifo_size != state->xmit_fifo_size))
+	  return -EINVAL;
+  
+	if (!serial_isroot()) {
+		if ((new_serial.baud_base != state->baud_base) ||
+		    (new_serial.close_delay != state->close_delay) ||
+		    (new_serial.xmit_fifo_size != state->xmit_fifo_size) ||
+		    ((new_serial.flags & ~ASYNC_USR_MASK) !=
+		     (state->flags & ~ASYNC_USR_MASK)))
+			return -EPERM;
+		state->flags = ((state->flags & ~ASYNC_USR_MASK) |
+			       (new_serial.flags & ASYNC_USR_MASK));
+		info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+			       (new_serial.flags & ASYNC_USR_MASK));
+		state->custom_divisor = new_serial.custom_divisor;
+		goto check_and_exit;
+	}
+
+	if (new_serial.baud_base < 9600)
+		return -EINVAL;
+
+	/*
+	 * OK, past this point, all the error checking has been done.
+	 * At this point, we start making changes.....
+	 */
+
+	state->baud_base = new_serial.baud_base;
+	state->flags = ((state->flags & ~ASYNC_FLAGS) |
+			(new_serial.flags & ASYNC_FLAGS));
+	info->flags = ((state->flags & ~ASYNC_INTERNAL_FLAGS) |
+		       (info->flags & ASYNC_INTERNAL_FLAGS));
+	state->custom_divisor = new_serial.custom_divisor;
+	state->close_delay = new_serial.close_delay * HZ/100;
+	state->closing_wait = new_serial.closing_wait * HZ/100;
+	info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+check_and_exit:
+	if (info->flags & ASYNC_INITIALIZED) {
+		if (((old_state.flags & ASYNC_SPD_MASK) !=
+		     (state->flags & ASYNC_SPD_MASK)) ||
+		    (old_state.custom_divisor != state->custom_divisor)) {
+			if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+				info->tty->alt_speed = 57600;
+			if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+				info->tty->alt_speed = 115200;
+			if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+				info->tty->alt_speed = 230400;
+			if ((state->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+				info->tty->alt_speed = 460800;
+			change_speed(info, NULL);
+		}
+	} else
+		retval = startup(info);
+	return retval;
+}
+
+
+/*
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * 	    is emptied.  On bus types like RS485, the transmitter must
+ * 	    release the bus after transmitting. This must be done when
+ * 	    the transmit shift register is empty, not be done when the
+ * 	    transmit holding register is empty.  This functionality
+ * 	    allows an RS485 driver to be written in user space. 
+ */
+static int get_lsr_info(struct async_struct * info, unsigned int *value)
+{
+	unsigned char status;
+	unsigned int result;
+	unsigned long flags;
+
+	local_irq_save(flags);
+	status = custom.serdatr;
+	mb();
+	local_irq_restore(flags);
+	result = ((status & SDR_TSRE) ? TIOCSER_TEMT : 0);
+	if (copy_to_user(value, &result, sizeof(int)))
+		return -EFAULT;
+	return 0;
+}
+
+
+static int rs_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct async_struct * info = (struct async_struct *)tty->driver_data;
+	unsigned char control, status;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
+		return -ENODEV;
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return -EIO;
+
+	control = info->MCR;
+	local_irq_save(flags);
+	status = ciab.pra;
+	local_irq_restore(flags);
+	return    ((control & SER_RTS) ? TIOCM_RTS : 0)
+		| ((control & SER_DTR) ? TIOCM_DTR : 0)
+		| (!(status  & SER_DCD) ? TIOCM_CAR : 0)
+		| (!(status  & SER_DSR) ? TIOCM_DSR : 0)
+		| (!(status  & SER_CTS) ? TIOCM_CTS : 0);
+}
+
+static int rs_tiocmset(struct tty_struct *tty, struct file *file,
+		       unsigned int set, unsigned int clear)
+{
+	struct async_struct * info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
+		return -ENODEV;
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return -EIO;
+
+	local_irq_save(flags);
+	if (set & TIOCM_RTS)
+		info->MCR |= SER_RTS;
+	if (set & TIOCM_DTR)
+		info->MCR |= SER_DTR;
+	if (clear & TIOCM_RTS)
+		info->MCR &= ~SER_RTS;
+	if (clear & TIOCM_DTR)
+		info->MCR &= ~SER_DTR;
+	rtsdtr_ctrl(info->MCR);
+	local_irq_restore(flags);
+	return 0;
+}
+
+/*
+ * rs_break() --- routine which turns the break handling on or off
+ */
+static void rs_break(struct tty_struct *tty, int break_state)
+{
+	struct async_struct * info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_break"))
+		return;
+
+	local_irq_save(flags);
+	if (break_state == -1)
+	  custom.adkcon = AC_SETCLR | AC_UARTBRK;
+	else
+	  custom.adkcon = AC_UARTBRK;
+	mb();
+	local_irq_restore(flags);
+}
+
+
+static int rs_ioctl(struct tty_struct *tty, struct file * file,
+		    unsigned int cmd, unsigned long arg)
+{
+	struct async_struct * info = (struct async_struct *)tty->driver_data;
+	struct async_icount cprev, cnow;	/* kernel counter temps */
+	struct serial_icounter_struct icount;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
+		return -ENODEV;
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+	    (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) &&
+	    (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+		    return -EIO;
+	}
+
+	switch (cmd) {
+		case TIOCGSERIAL:
+			return get_serial_info(info,
+					       (struct serial_struct *) arg);
+		case TIOCSSERIAL:
+			return set_serial_info(info,
+					       (struct serial_struct *) arg);
+		case TIOCSERCONFIG:
+			return 0;
+
+		case TIOCSERGETLSR: /* Get line status register */
+			return get_lsr_info(info, (unsigned int *) arg);
+
+		case TIOCSERGSTRUCT:
+			if (copy_to_user((struct async_struct *) arg,
+					 info, sizeof(struct async_struct)))
+				return -EFAULT;
+			return 0;
+
+		/*
+		 * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+		 * - mask passed in arg for lines of interest
+ 		 *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+		 * Caller should use TIOCGICOUNT to see which one it was
+		 */
+		case TIOCMIWAIT:
+			local_irq_save(flags);
+			/* note the counters on entry */
+			cprev = info->state->icount;
+			local_irq_restore(flags);
+			while (1) {
+				interruptible_sleep_on(&info->delta_msr_wait);
+				/* see if a signal did it */
+				if (signal_pending(current))
+					return -ERESTARTSYS;
+				local_irq_save(flags);
+				cnow = info->state->icount; /* atomic copy */
+				local_irq_restore(flags);
+				if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && 
+				    cnow.dcd == cprev.dcd && cnow.cts == cprev.cts)
+					return -EIO; /* no change => error */
+				if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+				     ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+				     ((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd)) ||
+				     ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
+					return 0;
+				}
+				cprev = cnow;
+			}
+			/* NOTREACHED */
+
+		/* 
+		 * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+		 * Return: write counters to the user passed counter struct
+		 * NB: both 1->0 and 0->1 transitions are counted except for
+		 *     RI where only 0->1 is counted.
+		 */
+		case TIOCGICOUNT:
+			local_irq_save(flags);
+			cnow = info->state->icount;
+			local_irq_restore(flags);
+			icount.cts = cnow.cts;
+			icount.dsr = cnow.dsr;
+			icount.rng = cnow.rng;
+			icount.dcd = cnow.dcd;
+			icount.rx = cnow.rx;
+			icount.tx = cnow.tx;
+			icount.frame = cnow.frame;
+			icount.overrun = cnow.overrun;
+			icount.parity = cnow.parity;
+			icount.brk = cnow.brk;
+			icount.buf_overrun = cnow.buf_overrun;
+
+			if (copy_to_user((void *)arg, &icount, sizeof(icount)))
+				return -EFAULT;
+			return 0;
+		case TIOCSERGWILD:
+		case TIOCSERSWILD:
+			/* "setserial -W" is called in Debian boot */
+			printk ("TIOCSER?WILD ioctl obsolete, ignored.\n");
+			return 0;
+
+		default:
+			return -ENOIOCTLCMD;
+		}
+	return 0;
+}
+
+static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+	struct async_struct *info = (struct async_struct *)tty->driver_data;
+	unsigned long flags;
+	unsigned int cflag = tty->termios->c_cflag;
+
+	if (   (cflag == old_termios->c_cflag)
+	    && (   RELEVANT_IFLAG(tty->termios->c_iflag) 
+		== RELEVANT_IFLAG(old_termios->c_iflag)))
+	  return;
+
+	change_speed(info, old_termios);
+
+	/* Handle transition to B0 status */
+	if ((old_termios->c_cflag & CBAUD) &&
+	    !(cflag & CBAUD)) {
+		info->MCR &= ~(SER_DTR|SER_RTS);
+		local_irq_save(flags);
+		rtsdtr_ctrl(info->MCR);
+		local_irq_restore(flags);
+	}
+
+	/* Handle transition away from B0 status */
+	if (!(old_termios->c_cflag & CBAUD) &&
+	    (cflag & CBAUD)) {
+		info->MCR |= SER_DTR;
+		if (!(tty->termios->c_cflag & CRTSCTS) || 
+		    !test_bit(TTY_THROTTLED, &tty->flags)) {
+			info->MCR |= SER_RTS;
+		}
+		local_irq_save(flags);
+		rtsdtr_ctrl(info->MCR);
+		local_irq_restore(flags);
+	}
+
+	/* Handle turning off CRTSCTS */
+	if ((old_termios->c_cflag & CRTSCTS) &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {
+		tty->hw_stopped = 0;
+		rs_start(tty);
+	}
+
+#if 0
+	/*
+	 * No need to wake up processes in open wait, since they
+	 * sample the CLOCAL flag once, and don't recheck it.
+	 * XXX  It's not clear whether the current behavior is correct
+	 * or not.  Hence, this may change.....
+	 */
+	if (!(old_termios->c_cflag & CLOCAL) &&
+	    (tty->termios->c_cflag & CLOCAL))
+		wake_up_interruptible(&info->open_wait);
+#endif
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_close()
+ * 
+ * This routine is called when the serial port gets closed.  First, we
+ * wait for the last remaining data to be sent.  Then, we unlink its
+ * async structure from the interrupt chain if necessary, and we free
+ * that IRQ if nothing is left in the chain.
+ * ------------------------------------------------------------
+ */
+static void rs_close(struct tty_struct *tty, struct file * filp)
+{
+	struct async_struct * info = (struct async_struct *)tty->driver_data;
+	struct serial_state *state;
+	unsigned long flags;
+
+	if (!info || serial_paranoia_check(info, tty->name, "rs_close"))
+		return;
+
+	state = info->state;
+
+	local_irq_save(flags);
+
+	if (tty_hung_up_p(filp)) {
+		DBG_CNT("before DEC-hung");
+		local_irq_restore(flags);
+		return;
+	}
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("rs_close ttys%d, count = %d\n", info->line, state->count);
+#endif
+	if ((tty->count == 1) && (state->count != 1)) {
+		/*
+		 * Uh, oh.  tty->count is 1, which means that the tty
+		 * structure will be freed.  state->count should always
+		 * be one in these conditions.  If it's greater than
+		 * one, we've got real problems, since it means the
+		 * serial port won't be shutdown.
+		 */
+		printk("rs_close: bad serial port count; tty->count is 1, "
+		       "state->count is %d\n", state->count);
+		state->count = 1;
+	}
+	if (--state->count < 0) {
+		printk("rs_close: bad serial port count for ttys%d: %d\n",
+		       info->line, state->count);
+		state->count = 0;
+	}
+	if (state->count) {
+		DBG_CNT("before DEC-2");
+		local_irq_restore(flags);
+		return;
+	}
+	info->flags |= ASYNC_CLOSING;
+	/*
+	 * Now we wait for the transmit buffer to clear; and we notify 
+	 * the line discipline to only process XON/XOFF characters.
+	 */
+	tty->closing = 1;
+	if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, info->closing_wait);
+	/*
+	 * At this point we stop accepting input.  To do this, we
+	 * disable the receive line status interrupts, and tell the
+	 * interrupt driver to stop checking the data ready bit in the
+	 * line status register.
+	 */
+	info->read_status_mask &= ~UART_LSR_DR;
+	if (info->flags & ASYNC_INITIALIZED) {
+	        /* disable receive interrupts */
+	        custom.intena = IF_RBF;
+		mb();
+		/* clear any pending receive interrupt */
+		custom.intreq = IF_RBF;
+		mb();
+
+		/*
+		 * Before we drop DTR, make sure the UART transmitter
+		 * has completely drained; this is especially
+		 * important if there is a transmit FIFO!
+		 */
+		rs_wait_until_sent(tty, info->timeout);
+	}
+	shutdown(info);
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+		
+	tty_ldisc_flush(tty);
+	tty->closing = 0;
+	info->event = 0;
+	info->tty = NULL;
+	if (info->blocked_open) {
+		if (info->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(info->close_delay));
+		}
+		wake_up_interruptible(&info->open_wait);
+	}
+	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+	wake_up_interruptible(&info->close_wait);
+	local_irq_restore(flags);
+}
+
+/*
+ * rs_wait_until_sent() --- wait until the transmitter is empty
+ */
+static void rs_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct async_struct * info = (struct async_struct *)tty->driver_data;
+	unsigned long orig_jiffies, char_time;
+	int lsr;
+
+	if (serial_paranoia_check(info, tty->name, "rs_wait_until_sent"))
+		return;
+
+	if (info->xmit_fifo_size == 0)
+		return; /* Just in case.... */
+
+	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 = (info->timeout - HZ/50) / info->xmit_fifo_size;
+	char_time = char_time / 5;
+	if (char_time == 0)
+		char_time = 1;
+	if (timeout)
+	  char_time = min_t(unsigned long, char_time, timeout);
+	/*
+	 * If the transmitter hasn't cleared in twice the approximate
+	 * amount of time to send the entire FIFO, it probably won't
+	 * ever clear.  This assumes the UART isn't doing flow
+	 * control, which is currently the case.  Hence, if it ever
+	 * takes longer than info->timeout, this is probably due to a
+	 * UART bug of some kind.  So, we clamp the timeout parameter at
+	 * 2*info->timeout.
+	 */
+	if (!timeout || timeout > 2*info->timeout)
+		timeout = 2*info->timeout;
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+	printk("In rs_wait_until_sent(%d) check=%lu...", timeout, char_time);
+	printk("jiff=%lu...", jiffies);
+#endif
+	while(!((lsr = custom.serdatr) & SDR_TSRE)) {
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+		printk("serdatr = %d (jiff=%lu)...", lsr, jiffies);
+#endif
+		msleep_interruptible(jiffies_to_msecs(char_time));
+		if (signal_pending(current))
+			break;
+		if (timeout && time_after(jiffies, orig_jiffies + timeout))
+			break;
+	}
+	current->state = TASK_RUNNING;
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+	printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies);
+#endif
+}
+
+/*
+ * rs_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void rs_hangup(struct tty_struct *tty)
+{
+	struct async_struct * info = (struct async_struct *)tty->driver_data;
+	struct serial_state *state = info->state;
+
+	if (serial_paranoia_check(info, tty->name, "rs_hangup"))
+		return;
+
+	state = info->state;
+
+	rs_flush_buffer(tty);
+	shutdown(info);
+	info->event = 0;
+	state->count = 0;
+	info->flags &= ~ASYNC_NORMAL_ACTIVE;
+	info->tty = NULL;
+	wake_up_interruptible(&info->open_wait);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_open() and friends
+ * ------------------------------------------------------------
+ */
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+			   struct async_struct *info)
+{
+#ifdef DECLARE_WAITQUEUE
+	DECLARE_WAITQUEUE(wait, current);
+#else
+	struct wait_queue wait = { current, NULL };
+#endif
+	struct serial_state *state = info->state;
+	int		retval;
+	int		do_clocal = 0, extra_count = 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) ||
+	    (info->flags & ASYNC_CLOSING)) {
+		if (info->flags & ASYNC_CLOSING)
+			interruptible_sleep_on(&info->close_wait);
+#ifdef SERIAL_DO_RESTART
+		return ((info->flags & ASYNC_HUP_NOTIFY) ?
+			-EAGAIN : -ERESTARTSYS);
+#else
+		return -EAGAIN;
+#endif
+	}
+
+	/*
+	 * 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))) {
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	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, state->count is dropped by one, so that
+	 * rs_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	retval = 0;
+	add_wait_queue(&info->open_wait, &wait);
+#ifdef SERIAL_DEBUG_OPEN
+	printk("block_til_ready before block: ttys%d, count = %d\n",
+	       state->line, state->count);
+#endif
+	local_irq_save(flags);
+	if (!tty_hung_up_p(filp)) {
+		extra_count = 1;
+		state->count--;
+	}
+	local_irq_restore(flags);
+	info->blocked_open++;
+	while (1) {
+		local_irq_save(flags);
+		if (tty->termios->c_cflag & CBAUD)
+		        rtsdtr_ctrl(SER_DTR|SER_RTS);
+		local_irq_restore(flags);
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) ||
+		    !(info->flags & ASYNC_INITIALIZED)) {
+#ifdef SERIAL_DO_RESTART
+			if (info->flags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;
+#else
+			retval = -EAGAIN;
+#endif
+			break;
+		}
+		if (!(info->flags & ASYNC_CLOSING) &&
+		    (do_clocal || (!(ciab.pra & SER_DCD)) ))
+			break;
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+#ifdef SERIAL_DEBUG_OPEN
+		printk("block_til_ready blocking: ttys%d, count = %d\n",
+		       info->line, state->count);
+#endif
+		schedule();
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&info->open_wait, &wait);
+	if (extra_count)
+		state->count++;
+	info->blocked_open--;
+#ifdef SERIAL_DEBUG_OPEN
+	printk("block_til_ready after blocking: ttys%d, count = %d\n",
+	       info->line, state->count);
+#endif
+	if (retval)
+		return retval;
+	info->flags |= ASYNC_NORMAL_ACTIVE;
+	return 0;
+}
+
+static int get_async_struct(int line, struct async_struct **ret_info)
+{
+	struct async_struct *info;
+	struct serial_state *sstate;
+
+	sstate = rs_table + line;
+	sstate->count++;
+	if (sstate->info) {
+		*ret_info = sstate->info;
+		return 0;
+	}
+	info = kmalloc(sizeof(struct async_struct), GFP_KERNEL);
+	if (!info) {
+		sstate->count--;
+		return -ENOMEM;
+	}
+	memset(info, 0, sizeof(struct async_struct));
+#ifdef DECLARE_WAITQUEUE
+	init_waitqueue_head(&info->open_wait);
+	init_waitqueue_head(&info->close_wait);
+	init_waitqueue_head(&info->delta_msr_wait);
+#endif
+	info->magic = SERIAL_MAGIC;
+	info->port = sstate->port;
+	info->flags = sstate->flags;
+	info->xmit_fifo_size = sstate->xmit_fifo_size;
+	info->line = line;
+	tasklet_init(&info->tlet, do_softint, (unsigned long)info);
+	info->state = sstate;
+	if (sstate->info) {
+		kfree(info);
+		*ret_info = sstate->info;
+		return 0;
+	}
+	*ret_info = sstate->info = info;
+	return 0;
+}
+
+/*
+ * This routine is called whenever a serial port is opened.  It
+ * enables interrupts for a serial port, linking in its async structure into
+ * the IRQ chain.   It also performs the serial-specific
+ * initialization for the tty structure.
+ */
+static int rs_open(struct tty_struct *tty, struct file * filp)
+{
+	struct async_struct	*info;
+	int 			retval, line;
+	unsigned long		page;
+
+	line = tty->index;
+	if ((line < 0) || (line >= NR_PORTS)) {
+		return -ENODEV;
+	}
+	retval = get_async_struct(line, &info);
+	if (retval) {
+		return retval;
+	}
+	tty->driver_data = info;
+	info->tty = tty;
+	if (serial_paranoia_check(info, tty->name, "rs_open"))
+		return -ENODEV;
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("rs_open %s, count = %d\n", tty->name, info->state->count);
+#endif
+	info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+	if (!tmp_buf) {
+		page = get_zeroed_page(GFP_KERNEL);
+		if (!page) {
+			return -ENOMEM;
+		}
+		if (tmp_buf)
+			free_page(page);
+		else
+			tmp_buf = (unsigned char *) page;
+	}
+
+	/*
+	 * If the port is the middle of closing, bail out now
+	 */
+	if (tty_hung_up_p(filp) ||
+	    (info->flags & ASYNC_CLOSING)) {
+		if (info->flags & ASYNC_CLOSING)
+			interruptible_sleep_on(&info->close_wait);
+#ifdef SERIAL_DO_RESTART
+		return ((info->flags & ASYNC_HUP_NOTIFY) ?
+			-EAGAIN : -ERESTARTSYS);
+#else
+		return -EAGAIN;
+#endif
+	}
+
+	/*
+	 * Start up serial port
+	 */
+	retval = startup(info);
+	if (retval) {
+		return retval;
+	}
+
+	retval = block_til_ready(tty, filp, info);
+	if (retval) {
+#ifdef SERIAL_DEBUG_OPEN
+		printk("rs_open returning after block_til_ready with %d\n",
+		       retval);
+#endif
+		return retval;
+	}
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("rs_open %s successful...", tty->name);
+#endif
+	return 0;
+}
+
+/*
+ * /proc fs routines....
+ */
+
+static inline int line_info(char *buf, struct serial_state *state)
+{
+	struct async_struct *info = state->info, scr_info;
+	char	stat_buf[30], control, status;
+	int	ret;
+	unsigned long flags;
+
+	ret = sprintf(buf, "%d: uart:amiga_builtin",state->line);
+
+	/*
+	 * Figure out the current RS-232 lines
+	 */
+	if (!info) {
+		info = &scr_info;	/* This is just for serial_{in,out} */
+
+		info->magic = SERIAL_MAGIC;
+		info->flags = state->flags;
+		info->quot = 0;
+		info->tty = NULL;
+	}
+	local_irq_save(flags);
+	status = ciab.pra;
+	control = info ? info->MCR : status;
+	local_irq_restore(flags);
+
+	stat_buf[0] = 0;
+	stat_buf[1] = 0;
+	if(!(control & SER_RTS))
+		strcat(stat_buf, "|RTS");
+	if(!(status & SER_CTS))
+		strcat(stat_buf, "|CTS");
+	if(!(control & SER_DTR))
+		strcat(stat_buf, "|DTR");
+	if(!(status & SER_DSR))
+		strcat(stat_buf, "|DSR");
+	if(!(status & SER_DCD))
+		strcat(stat_buf, "|CD");
+
+	if (info->quot) {
+		ret += sprintf(buf+ret, " baud:%d",
+			       state->baud_base / info->quot);
+	}
+
+	ret += sprintf(buf+ret, " tx:%d rx:%d",
+		      state->icount.tx, state->icount.rx);
+
+	if (state->icount.frame)
+		ret += sprintf(buf+ret, " fe:%d", state->icount.frame);
+
+	if (state->icount.parity)
+		ret += sprintf(buf+ret, " pe:%d", state->icount.parity);
+
+	if (state->icount.brk)
+		ret += sprintf(buf+ret, " brk:%d", state->icount.brk);
+
+	if (state->icount.overrun)
+		ret += sprintf(buf+ret, " oe:%d", state->icount.overrun);
+
+	/*
+	 * Last thing is the RS-232 status lines
+	 */
+	ret += sprintf(buf+ret, " %s\n", stat_buf+1);
+	return ret;
+}
+
+static int rs_read_proc(char *page, char **start, off_t off, int count,
+			int *eof, void *data)
+{
+	int len = 0, l;
+	off_t	begin = 0;
+
+	len += sprintf(page, "serinfo:1.0 driver:%s\n", serial_version);
+	l = line_info(page + len, &rs_table[0]);
+	len += l;
+	if (len+begin > off+count)
+	  goto done;
+	if (len+begin < off) {
+	  begin += len;
+	  len = 0;
+	}
+	*eof = 1;
+done:
+	if (off >= len+begin)
+		return 0;
+	*start = page + (off-begin);
+	return ((count < begin+len-off) ? count : begin+len-off);
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * rs_init() and friends
+ *
+ * rs_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+/*
+ * This routine prints out the appropriate serial driver version
+ * number, and identifies which options were configured into this
+ * driver.
+ */
+static _INLINE_ void show_serial_version(void)
+{
+ 	printk(KERN_INFO "%s version %s\n", serial_name, serial_version);
+}
+
+
+int register_serial(struct serial_struct *req);
+void unregister_serial(int line);
+
+
+static struct tty_operations serial_ops = {
+	.open = rs_open,
+	.close = rs_close,
+	.write = rs_write,
+	.put_char = rs_put_char,
+	.flush_chars = rs_flush_chars,
+	.write_room = rs_write_room,
+	.chars_in_buffer = rs_chars_in_buffer,
+	.flush_buffer = rs_flush_buffer,
+	.ioctl = rs_ioctl,
+	.throttle = rs_throttle,
+	.unthrottle = rs_unthrottle,
+	.set_termios = rs_set_termios,
+	.stop = rs_stop,
+	.start = rs_start,
+	.hangup = rs_hangup,
+	.break_ctl = rs_break,
+	.send_xchar = rs_send_xchar,
+	.wait_until_sent = rs_wait_until_sent,
+	.read_proc = rs_read_proc,
+	.tiocmget = rs_tiocmget,
+	.tiocmset = rs_tiocmset,
+};
+
+/*
+ * The serial driver boot-time initialization code!
+ */
+static int __init rs_init(void)
+{
+	unsigned long flags;
+	struct serial_state * state;
+
+	if (!MACH_IS_AMIGA || !AMIGAHW_PRESENT(AMI_SERIAL))
+		return -ENODEV;
+
+	serial_driver = alloc_tty_driver(1);
+	if (!serial_driver)
+		return -ENOMEM;
+
+	/*
+	 *  We request SERDAT and SERPER only, because the serial registers are
+	 *  too spreaded over the custom register space
+	 */
+	if (!request_mem_region(CUSTOM_PHYSADDR+0x30, 4, "amiserial [Paula]"))
+		return -EBUSY;
+
+	IRQ_ports = NULL;
+
+	show_serial_version();
+
+	/* Initialize the tty_driver structure */
+
+	serial_driver->owner = THIS_MODULE;
+	serial_driver->driver_name = "amiserial";
+	serial_driver->name = "ttyS";
+	serial_driver->major = TTY_MAJOR;
+	serial_driver->minor_start = 64;
+	serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	serial_driver->subtype = SERIAL_TYPE_NORMAL;
+	serial_driver->init_termios = tty_std_termios;
+	serial_driver->init_termios.c_cflag =
+		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	serial_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(serial_driver, &serial_ops);
+
+	if (tty_register_driver(serial_driver))
+		panic("Couldn't register serial driver\n");
+
+	state = rs_table;
+	state->magic = SSTATE_MAGIC;
+	state->port = (int)&custom.serdatr; /* Just to give it a value */
+	state->line = 0;
+	state->custom_divisor = 0;
+	state->close_delay = 5*HZ/10;
+	state->closing_wait = 30*HZ;
+	state->icount.cts = state->icount.dsr = 
+	  state->icount.rng = state->icount.dcd = 0;
+	state->icount.rx = state->icount.tx = 0;
+	state->icount.frame = state->icount.parity = 0;
+	state->icount.overrun = state->icount.brk = 0;
+	/*
+	if(state->port && check_region(state->port,REGION_LENGTH(state)))
+	  continue;
+	*/
+
+	printk(KERN_INFO "ttyS%d is the amiga builtin serial port\n",
+		       state->line);
+
+	/* Hardware set up */
+
+	state->baud_base = amiga_colorclock;
+	state->xmit_fifo_size = 1;
+
+	local_irq_save(flags);
+
+	/* set ISRs, and then disable the rx interrupts */
+	request_irq(IRQ_AMIGA_TBE, ser_tx_int, 0, "serial TX", state);
+	request_irq(IRQ_AMIGA_RBF, ser_rx_int, SA_INTERRUPT, "serial RX", state);
+
+	/* turn off Rx and Tx interrupts */
+	custom.intena = IF_RBF | IF_TBE;
+	mb();
+
+	/* clear any pending interrupt */
+	custom.intreq = IF_RBF | IF_TBE;
+	mb();
+
+	local_irq_restore(flags);
+
+	/*
+	 * set the appropriate directions for the modem control flags,
+	 * and clear RTS and DTR
+	 */
+	ciab.ddra |= (SER_DTR | SER_RTS);   /* outputs */
+	ciab.ddra &= ~(SER_DCD | SER_CTS | SER_DSR);  /* inputs */
+
+	return 0;
+}
+
+static __exit void rs_exit(void) 
+{
+	int error;
+	struct async_struct *info = rs_table[0].info;
+
+	/* printk("Unloading %s: version %s\n", serial_name, serial_version); */
+	tasklet_kill(&info->tlet);
+	if ((error = tty_unregister_driver(serial_driver)))
+		printk("SERIAL: failed to unregister serial driver (%d)\n",
+		       error);
+	put_tty_driver(serial_driver);
+
+	if (info) {
+	  rs_table[0].info = NULL;
+	  kfree(info);
+	}
+
+	if (tmp_buf) {
+		free_page((unsigned long) tmp_buf);
+		tmp_buf = NULL;
+	}
+
+	release_mem_region(CUSTOM_PHYSADDR+0x30, 4);
+}
+
+module_init(rs_init)
+module_exit(rs_exit)
+
+
+/*
+ * ------------------------------------------------------------
+ * Serial console driver
+ * ------------------------------------------------------------
+ */
+#ifdef CONFIG_SERIAL_CONSOLE
+
+static void amiga_serial_putc(char c)
+{
+	custom.serdat = (unsigned char)c | 0x100;
+	while (!(custom.serdatr & 0x2000))
+		barrier();
+}
+
+/*
+ *	Print a string to the serial port trying not to disturb
+ *	any possible real use of the port...
+ *
+ *	The console must be locked when we get here.
+ */
+static void serial_console_write(struct console *co, const char *s,
+				unsigned count)
+{
+	unsigned short intena = custom.intenar;
+
+	custom.intena = IF_TBE;
+
+	while (count--) {
+		if (*s == '\n')
+			amiga_serial_putc('\r');
+		amiga_serial_putc(*s++);
+	}
+
+	custom.intena = IF_SETCLR | (intena & IF_TBE);
+}
+
+static struct tty_driver *serial_console_device(struct console *c, int *index)
+{
+	*index = 0;
+	return serial_driver;
+}
+
+static struct console sercons = {
+	.name =		"ttyS",
+	.write =	serial_console_write,
+	.device =	serial_console_device,
+	.flags =	CON_PRINTBUFFER,
+	.index =	-1,
+};
+
+/*
+ *	Register console.
+ */
+static int __init amiserial_console_init(void)
+{
+	register_console(&sercons);
+	return 0;
+}
+console_initcall(amiserial_console_init);
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/applicom.c b/drivers/char/applicom.c
new file mode 100644
index 0000000..6bf2e27
--- /dev/null
+++ b/drivers/char/applicom.c
@@ -0,0 +1,862 @@
+/* Derived from Applicom driver ac.c for SCO Unix                            */
+/* Ported by David Woodhouse, Axiom (Cambridge) Ltd.                         */
+/* dwmw2@infradead.org 30/8/98                                               */
+/* $Id: ac.c,v 1.30 2000/03/22 16:03:57 dwmw2 Exp $			     */
+/* This module is for Linux 2.1 and 2.2 series kernels.                      */
+/*****************************************************************************/
+/* J PAGET 18/02/94 passage V2.4.2 ioctl avec code 2 reset to les interrupt  */
+/* ceci pour reseter correctement apres une sortie sauvage                   */
+/* J PAGET 02/05/94 passage V2.4.3 dans le traitement de d'interruption,     */
+/* LoopCount n'etait pas initialise a 0.                                     */
+/* F LAFORSE 04/07/95 version V2.6.0 lecture bidon apres acces a une carte   */
+/*           pour liberer le bus                                             */
+/* J.PAGET 19/11/95 version V2.6.1 Nombre, addresse,irq n'est plus configure */
+/* et passe en argument a acinit, mais est scrute sur le bus pour s'adapter  */
+/* au nombre de cartes presentes sur le bus. IOCL code 6 affichait V2.4.3    */
+/* F.LAFORSE 28/11/95 creation de fichiers acXX.o avec les differentes       */
+/* adresses de base des cartes, IOCTL 6 plus complet                         */
+/* J.PAGET le 19/08/96 copie de la version V2.6 en V2.8.0 sans modification  */
+/* de code autre que le texte V2.6.1 en V2.8.0                               */
+/*****************************************************************************/
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/pci.h>
+#include <linux/wait.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include "applicom.h"
+
+
+/* NOTE: We use for loops with {write,read}b() instead of 
+   memcpy_{from,to}io throughout this driver. This is because
+   the board doesn't correctly handle word accesses - only
+   bytes. 
+*/
+
+
+#undef DEBUG
+
+#define MAX_BOARD 8		/* maximum of pc board possible */
+#define MAX_ISA_BOARD 4
+#define LEN_RAM_IO 0x800
+#define AC_MINOR 157
+
+#ifndef PCI_VENDOR_ID_APPLICOM
+#define PCI_VENDOR_ID_APPLICOM                0x1389
+#define PCI_DEVICE_ID_APPLICOM_PCIGENERIC     0x0001
+#define PCI_DEVICE_ID_APPLICOM_PCI2000IBS_CAN 0x0002
+#define PCI_DEVICE_ID_APPLICOM_PCI2000PFB     0x0003
+#endif
+#define MAX_PCI_DEVICE_NUM 3
+
+static char *applicom_pci_devnames[] = {
+	"PCI board",
+	"PCI2000IBS / PCI2000CAN",
+	"PCI2000PFB"
+};
+
+static struct pci_device_id applicom_pci_tbl[] = {
+	{ PCI_VENDOR_ID_APPLICOM, PCI_DEVICE_ID_APPLICOM_PCIGENERIC,
+	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ PCI_VENDOR_ID_APPLICOM, PCI_DEVICE_ID_APPLICOM_PCI2000IBS_CAN,
+	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ PCI_VENDOR_ID_APPLICOM, PCI_DEVICE_ID_APPLICOM_PCI2000PFB,
+	  PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ 0 }
+};
+MODULE_DEVICE_TABLE(pci, applicom_pci_tbl);
+
+MODULE_AUTHOR("David Woodhouse & Applicom International");
+MODULE_DESCRIPTION("Driver for Applicom Profibus card");
+MODULE_LICENSE("GPL");
+
+MODULE_SUPPORTED_DEVICE("ac");
+
+
+static struct applicom_board {
+	unsigned long PhysIO;
+	void __iomem *RamIO;
+	wait_queue_head_t FlagSleepSend;
+	long irq;
+	spinlock_t mutex;
+} apbs[MAX_BOARD];
+
+static unsigned int irq = 0;	/* interrupt number IRQ       */
+static unsigned long mem = 0;	/* physical segment of board  */
+
+module_param(irq, uint, 0);
+MODULE_PARM_DESC(irq, "IRQ of the Applicom board");
+module_param(mem, ulong, 0);
+MODULE_PARM_DESC(mem, "Shared Memory Address of Applicom board");
+
+static unsigned int numboards;	/* number of installed boards */
+static volatile unsigned char Dummy;
+static DECLARE_WAIT_QUEUE_HEAD(FlagSleepRec);
+static unsigned int WriteErrorCount;	/* number of write error      */
+static unsigned int ReadErrorCount;	/* number of read error       */
+static unsigned int DeviceErrorCount;	/* number of device error     */
+
+static ssize_t ac_read (struct file *, char __user *, size_t, loff_t *);
+static ssize_t ac_write (struct file *, const char __user *, size_t, loff_t *);
+static int ac_ioctl(struct inode *, struct file *, unsigned int,
+		    unsigned long);
+static irqreturn_t ac_interrupt(int, void *, struct pt_regs *);
+
+static struct file_operations ac_fops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.read = ac_read,
+	.write = ac_write,
+	.ioctl = ac_ioctl,
+};
+
+static struct miscdevice ac_miscdev = {
+	AC_MINOR,
+	"ac",
+	&ac_fops
+};
+
+static int dummy;	/* dev_id for request_irq() */
+
+static int ac_register_board(unsigned long physloc, void __iomem *loc, 
+		      unsigned char boardno)
+{
+	volatile unsigned char byte_reset_it;
+
+	if((readb(loc + CONF_END_TEST)     != 0x00) ||
+	   (readb(loc + CONF_END_TEST + 1) != 0x55) ||
+	   (readb(loc + CONF_END_TEST + 2) != 0xAA) ||
+	   (readb(loc + CONF_END_TEST + 3) != 0xFF))
+		return 0;
+
+	if (!boardno)
+		boardno = readb(loc + NUMCARD_OWNER_TO_PC);
+
+	if (!boardno && boardno > MAX_BOARD) {
+		printk(KERN_WARNING "Board #%d (at 0x%lx) is out of range (1 <= x <= %d).\n",
+		       boardno, physloc, MAX_BOARD);
+		return 0;
+	}
+
+	if (apbs[boardno - 1].RamIO) {
+		printk(KERN_WARNING "Board #%d (at 0x%lx) conflicts with previous board #%d (at 0x%lx)\n", 
+		       boardno, physloc, boardno, apbs[boardno-1].PhysIO);
+		return 0;
+	}
+
+	boardno--;
+
+	apbs[boardno].PhysIO = physloc;
+	apbs[boardno].RamIO = loc;
+	init_waitqueue_head(&apbs[boardno].FlagSleepSend);
+	spin_lock_init(&apbs[boardno].mutex);
+	byte_reset_it = readb(loc + RAM_IT_TO_PC);
+
+	numboards++;
+	return boardno + 1;
+}
+
+#ifdef MODULE
+
+#define applicom_init init_module
+
+void cleanup_module(void)
+{
+	int i;
+
+	misc_deregister(&ac_miscdev);
+
+	for (i = 0; i < MAX_BOARD; i++) {
+
+		if (!apbs[i].RamIO)
+			continue;
+
+		if (apbs[i].irq)
+			free_irq(apbs[i].irq, &dummy);
+
+		iounmap(apbs[i].RamIO);
+	}
+}
+
+#endif				/* MODULE */
+
+int __init applicom_init(void)
+{
+	int i, numisa = 0;
+	struct pci_dev *dev = NULL;
+	void __iomem *RamIO;
+	int boardno;
+
+	printk(KERN_INFO "Applicom driver: $Id: ac.c,v 1.30 2000/03/22 16:03:57 dwmw2 Exp $\n");
+
+	/* No mem and irq given - check for a PCI card */
+
+	while ( (dev = pci_get_class(PCI_CLASS_OTHERS << 16, dev))) {
+
+		if (dev->vendor != PCI_VENDOR_ID_APPLICOM)
+			continue;
+		
+		if (dev->device  > MAX_PCI_DEVICE_NUM || dev->device == 0)
+			continue;
+		
+		if (pci_enable_device(dev))
+			return -EIO;
+
+		RamIO = ioremap(dev->resource[0].start, LEN_RAM_IO);
+
+		if (!RamIO) {
+			printk(KERN_INFO "ac.o: Failed to ioremap PCI memory space at 0x%lx\n", dev->resource[0].start);
+			pci_disable_device(dev);
+			return -EIO;
+		}
+
+		printk(KERN_INFO "Applicom %s found at mem 0x%lx, irq %d\n",
+		       applicom_pci_devnames[dev->device-1], dev->resource[0].start, 
+		       dev->irq);
+
+		boardno = ac_register_board(dev->resource[0].start, RamIO,0);
+		if (!boardno) {
+			printk(KERN_INFO "ac.o: PCI Applicom device doesn't have correct signature.\n");
+			iounmap(RamIO);
+			pci_disable_device(dev);
+			continue;
+		}
+
+		if (request_irq(dev->irq, &ac_interrupt, SA_SHIRQ, "Applicom PCI", &dummy)) {
+			printk(KERN_INFO "Could not allocate IRQ %d for PCI Applicom device.\n", dev->irq);
+			iounmap(RamIO);
+			pci_disable_device(dev);
+			apbs[boardno - 1].RamIO = NULL;
+			continue;
+		}
+
+		/* Enable interrupts. */
+
+		writeb(0x40, apbs[boardno - 1].RamIO + RAM_IT_FROM_PC);
+
+		apbs[boardno - 1].irq = dev->irq;
+	}
+
+	/* Finished with PCI cards. If none registered, 
+	 * and there was no mem/irq specified, exit */
+
+	if (!mem || !irq) {
+		if (numboards)
+			goto fin;
+		else {
+			printk(KERN_INFO "ac.o: No PCI boards found.\n");
+			printk(KERN_INFO "ac.o: For an ISA board you must supply memory and irq parameters.\n");
+			return -ENXIO;
+		}
+	}
+
+	/* Now try the specified ISA cards */
+
+	for (i = 0; i < MAX_ISA_BOARD; i++) {
+		RamIO = ioremap(mem + (LEN_RAM_IO * i), LEN_RAM_IO);
+
+		if (!RamIO) {
+			printk(KERN_INFO "ac.o: Failed to ioremap the ISA card's memory space (slot #%d)\n", i + 1);
+			continue;
+		}
+
+		if (!(boardno = ac_register_board((unsigned long)mem+ (LEN_RAM_IO*i),
+						  RamIO,i+1))) {
+			iounmap(RamIO);
+			continue;
+		}
+
+		printk(KERN_NOTICE "Applicom ISA card found at mem 0x%lx, irq %d\n", mem + (LEN_RAM_IO*i), irq);
+
+		if (!numisa) {
+			if (request_irq(irq, &ac_interrupt, SA_SHIRQ, "Applicom ISA", &dummy)) {
+				printk(KERN_WARNING "Could not allocate IRQ %d for ISA Applicom device.\n", irq);
+				iounmap(RamIO);
+				apbs[boardno - 1].RamIO = NULL;
+			}
+			else
+				apbs[boardno - 1].irq = irq;
+		}
+		else
+			apbs[boardno - 1].irq = 0;
+
+		numisa++;
+	}
+
+	if (!numisa)
+		printk(KERN_WARNING"ac.o: No valid ISA Applicom boards found at mem 0x%lx\n",mem);
+
+ fin:
+	init_waitqueue_head(&FlagSleepRec);
+
+	WriteErrorCount = 0;
+	ReadErrorCount = 0;
+	DeviceErrorCount = 0;
+
+	if (numboards) {
+		misc_register(&ac_miscdev);
+		for (i = 0; i < MAX_BOARD; i++) {
+			int serial;
+			char boardname[(SERIAL_NUMBER - TYPE_CARD) + 1];
+
+			if (!apbs[i].RamIO)
+				continue;
+
+			for (serial = 0; serial < SERIAL_NUMBER - TYPE_CARD; serial++)
+				boardname[serial] = readb(apbs[i].RamIO + TYPE_CARD + serial);
+
+			boardname[serial] = 0;
+
+
+			printk(KERN_INFO "Applicom board %d: %s, PROM V%d.%d",
+			       i+1, boardname,
+			       (int)(readb(apbs[i].RamIO + VERS) >> 4),
+			       (int)(readb(apbs[i].RamIO + VERS) & 0xF));
+			
+			serial = (readb(apbs[i].RamIO + SERIAL_NUMBER) << 16) + 
+				(readb(apbs[i].RamIO + SERIAL_NUMBER + 1) << 8) + 
+				(readb(apbs[i].RamIO + SERIAL_NUMBER + 2) );
+
+			if (serial != 0)
+				printk(" S/N %d\n", serial);
+			else
+				printk("\n");
+		}
+		return 0;
+	}
+
+	else
+		return -ENXIO;
+}
+
+
+#ifndef MODULE
+__initcall(applicom_init);
+#endif
+
+static ssize_t ac_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
+{
+	unsigned int NumCard;	/* Board number 1 -> 8           */
+	unsigned int IndexCard;	/* Index board number 0 -> 7     */
+	unsigned char TicCard;	/* Board TIC to send             */
+	unsigned long flags;	/* Current priority              */
+	struct st_ram_io st_loc;
+	struct mailbox tmpmailbox;
+#ifdef DEBUG
+	int c;
+#endif
+	DECLARE_WAITQUEUE(wait, current);
+
+	if (count != sizeof(struct st_ram_io) + sizeof(struct mailbox)) {
+		static int warncount = 5;
+		if (warncount) {
+			printk(KERN_INFO "Hmmm. write() of Applicom card, length %zd != expected %zd\n",
+			       count, sizeof(struct st_ram_io) + sizeof(struct mailbox));
+			warncount--;
+		}
+		return -EINVAL;
+	}
+
+	if(copy_from_user(&st_loc, buf, sizeof(struct st_ram_io))) 
+		return -EFAULT;
+	
+	if(copy_from_user(&tmpmailbox, &buf[sizeof(struct st_ram_io)],
+			  sizeof(struct mailbox))) 
+		return -EFAULT;
+
+	NumCard = st_loc.num_card;	/* board number to send          */
+	TicCard = st_loc.tic_des_from_pc;	/* tic number to send            */
+	IndexCard = NumCard - 1;
+
+	if((NumCard < 1) || (NumCard > MAX_BOARD) || !apbs[IndexCard].RamIO)
+		return -EINVAL;
+
+#ifdef DEBUG
+	printk("Write to applicom card #%d. struct st_ram_io follows:",
+	       IndexCard+1);
+
+		for (c = 0; c < sizeof(struct st_ram_io);) {
+		
+			printk("\n%5.5X: %2.2X", c, ((unsigned char *) &st_loc)[c]);
+
+			for (c++; c % 8 && c < sizeof(struct st_ram_io); c++) {
+				printk(" %2.2X", ((unsigned char *) &st_loc)[c]);
+			}
+		}
+
+		printk("\nstruct mailbox follows:");
+
+		for (c = 0; c < sizeof(struct mailbox);) {
+			printk("\n%5.5X: %2.2X", c, ((unsigned char *) &tmpmailbox)[c]);
+
+			for (c++; c % 8 && c < sizeof(struct mailbox); c++) {
+				printk(" %2.2X", ((unsigned char *) &tmpmailbox)[c]);
+			}
+		}
+
+		printk("\n");
+#endif
+
+	spin_lock_irqsave(&apbs[IndexCard].mutex, flags);
+
+	/* Test octet ready correct */
+	if(readb(apbs[IndexCard].RamIO + DATA_FROM_PC_READY) > 2) { 
+		Dummy = readb(apbs[IndexCard].RamIO + VERS);
+		spin_unlock_irqrestore(&apbs[IndexCard].mutex, flags);
+		printk(KERN_WARNING "APPLICOM driver write error board %d, DataFromPcReady = %d\n",
+		       IndexCard,(int)readb(apbs[IndexCard].RamIO + DATA_FROM_PC_READY));
+		DeviceErrorCount++;
+		return -EIO;
+	}
+	
+	/* Place ourselves on the wait queue */
+	set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&apbs[IndexCard].FlagSleepSend, &wait);
+
+	/* Check whether the card is ready for us */
+	while (readb(apbs[IndexCard].RamIO + DATA_FROM_PC_READY) != 0) {
+		Dummy = readb(apbs[IndexCard].RamIO + VERS);
+		/* It's busy. Sleep. */
+
+		spin_unlock_irqrestore(&apbs[IndexCard].mutex, flags);
+		schedule();
+		if (signal_pending(current)) {
+			remove_wait_queue(&apbs[IndexCard].FlagSleepSend,
+					  &wait);
+			return -EINTR;
+		}
+		spin_lock_irqsave(&apbs[IndexCard].mutex, flags);
+		set_current_state(TASK_INTERRUPTIBLE);
+	}
+
+	/* We may not have actually slept */
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&apbs[IndexCard].FlagSleepSend, &wait);
+
+	writeb(1, apbs[IndexCard].RamIO + DATA_FROM_PC_READY);
+
+	/* Which is best - lock down the pages with rawio and then
+	   copy directly, or use bounce buffers? For now we do the latter 
+	   because it works with 2.2 still */
+	{
+		unsigned char *from = (unsigned char *) &tmpmailbox;
+		void __iomem *to = apbs[IndexCard].RamIO + RAM_FROM_PC;
+		int c;
+
+		for (c = 0; c < sizeof(struct mailbox); c++)
+			writeb(*(from++), to++);
+	}
+
+	writeb(0x20, apbs[IndexCard].RamIO + TIC_OWNER_FROM_PC);
+	writeb(0xff, apbs[IndexCard].RamIO + NUMCARD_OWNER_FROM_PC);
+	writeb(TicCard, apbs[IndexCard].RamIO + TIC_DES_FROM_PC);
+	writeb(NumCard, apbs[IndexCard].RamIO + NUMCARD_DES_FROM_PC);
+	writeb(2, apbs[IndexCard].RamIO + DATA_FROM_PC_READY);
+	writeb(1, apbs[IndexCard].RamIO + RAM_IT_FROM_PC);
+	Dummy = readb(apbs[IndexCard].RamIO + VERS);
+	spin_unlock_irqrestore(&apbs[IndexCard].mutex, flags);
+	return 0;
+}
+
+static int do_ac_read(int IndexCard, char __user *buf,
+		struct st_ram_io *st_loc, struct mailbox *mailbox)
+{
+	void __iomem *from = apbs[IndexCard].RamIO + RAM_TO_PC;
+	unsigned char *to = (unsigned char *)&mailbox;
+#ifdef DEBUG
+	int c;
+#endif
+
+	st_loc->tic_owner_to_pc = readb(apbs[IndexCard].RamIO + TIC_OWNER_TO_PC);
+	st_loc->numcard_owner_to_pc = readb(apbs[IndexCard].RamIO + NUMCARD_OWNER_TO_PC);
+
+
+	{
+		int c;
+
+		for (c = 0; c < sizeof(struct mailbox); c++)
+			*(to++) = readb(from++);
+	}
+	writeb(1, apbs[IndexCard].RamIO + ACK_FROM_PC_READY);
+	writeb(1, apbs[IndexCard].RamIO + TYP_ACK_FROM_PC);
+	writeb(IndexCard+1, apbs[IndexCard].RamIO + NUMCARD_ACK_FROM_PC);
+	writeb(readb(apbs[IndexCard].RamIO + TIC_OWNER_TO_PC), 
+	       apbs[IndexCard].RamIO + TIC_ACK_FROM_PC);
+	writeb(2, apbs[IndexCard].RamIO + ACK_FROM_PC_READY);
+	writeb(0, apbs[IndexCard].RamIO + DATA_TO_PC_READY);
+	writeb(2, apbs[IndexCard].RamIO + RAM_IT_FROM_PC);
+	Dummy = readb(apbs[IndexCard].RamIO + VERS);
+
+#ifdef DEBUG
+		printk("Read from applicom card #%d. struct st_ram_io follows:", NumCard);
+
+		for (c = 0; c < sizeof(struct st_ram_io);) {
+			printk("\n%5.5X: %2.2X", c, ((unsigned char *)st_loc)[c]);
+
+			for (c++; c % 8 && c < sizeof(struct st_ram_io); c++) {
+				printk(" %2.2X", ((unsigned char *)st_loc)[c]);
+			}
+		}
+
+		printk("\nstruct mailbox follows:");
+
+		for (c = 0; c < sizeof(struct mailbox);) {
+			printk("\n%5.5X: %2.2X", c, ((unsigned char *)mailbox)[c]);
+
+			for (c++; c % 8 && c < sizeof(struct mailbox); c++) {
+				printk(" %2.2X", ((unsigned char *)mailbox)[c]);
+			}
+		}
+		printk("\n");
+#endif
+	return (sizeof(struct st_ram_io) + sizeof(struct mailbox));
+}
+
+static ssize_t ac_read (struct file *filp, char __user *buf, size_t count, loff_t *ptr)
+{
+	unsigned long flags;
+	unsigned int i;
+	unsigned char tmp;
+	int ret = 0;
+	DECLARE_WAITQUEUE(wait, current);
+#ifdef DEBUG
+	int loopcount=0;
+#endif
+	/* No need to ratelimit this. Only root can trigger it anyway */
+	if (count != sizeof(struct st_ram_io) + sizeof(struct mailbox)) {
+		printk( KERN_WARNING "Hmmm. read() of Applicom card, length %zd != expected %zd\n",
+			count,sizeof(struct st_ram_io) + sizeof(struct mailbox));
+		return -EINVAL;
+	}
+	
+	while(1) {
+		/* Stick ourself on the wait queue */
+		set_current_state(TASK_INTERRUPTIBLE);
+		add_wait_queue(&FlagSleepRec, &wait);
+		
+		/* Scan each board, looking for one which has a packet for us */
+		for (i=0; i < MAX_BOARD; i++) {
+			if (!apbs[i].RamIO)
+				continue;
+			spin_lock_irqsave(&apbs[i].mutex, flags);
+			
+			tmp = readb(apbs[i].RamIO + DATA_TO_PC_READY);
+			
+			if (tmp == 2) {
+				struct st_ram_io st_loc;
+				struct mailbox mailbox;
+
+				/* Got a packet for us */
+				ret = do_ac_read(i, buf, &st_loc, &mailbox);
+				spin_unlock_irqrestore(&apbs[i].mutex, flags);
+				set_current_state(TASK_RUNNING);
+				remove_wait_queue(&FlagSleepRec, &wait);
+
+				if (copy_to_user(buf, &st_loc, sizeof(st_loc)))
+					return -EFAULT;
+				if (copy_to_user(buf + sizeof(st_loc), &mailbox, sizeof(mailbox)))
+					return -EFAULT;
+				return tmp;
+			}
+			
+			if (tmp > 2) {
+				/* Got an error */
+				Dummy = readb(apbs[i].RamIO + VERS);
+				
+				spin_unlock_irqrestore(&apbs[i].mutex, flags);
+				set_current_state(TASK_RUNNING);
+				remove_wait_queue(&FlagSleepRec, &wait);
+				
+				printk(KERN_WARNING "APPLICOM driver read error board %d, DataToPcReady = %d\n",
+				       i,(int)readb(apbs[i].RamIO + DATA_TO_PC_READY));
+				DeviceErrorCount++;
+				return -EIO;
+			}
+			
+			/* Nothing for us. Try the next board */
+			Dummy = readb(apbs[i].RamIO + VERS);
+			spin_unlock_irqrestore(&apbs[i].mutex, flags);
+			
+		} /* per board */
+
+		/* OK - No boards had data for us. Sleep now */
+
+		schedule();
+		remove_wait_queue(&FlagSleepRec, &wait);
+
+		if (signal_pending(current))
+			return -EINTR;
+
+#ifdef DEBUG
+		if (loopcount++ > 2) {
+			printk("Looping in ac_read. loopcount %d\n", loopcount);
+		}
+#endif
+	} 
+}
+
+static irqreturn_t ac_interrupt(int vec, void *dev_instance, struct pt_regs *regs)
+{
+	unsigned int i;
+	unsigned int FlagInt;
+	unsigned int LoopCount;
+	int handled = 0;
+
+	//    printk("Applicom interrupt on IRQ %d occurred\n", vec);
+
+	LoopCount = 0;
+
+	do {
+		FlagInt = 0;
+		for (i = 0; i < MAX_BOARD; i++) {
+			
+			/* Skip if this board doesn't exist */
+			if (!apbs[i].RamIO)
+				continue;
+
+			spin_lock(&apbs[i].mutex);
+
+			/* Skip if this board doesn't want attention */
+			if(readb(apbs[i].RamIO + RAM_IT_TO_PC) == 0) {
+				spin_unlock(&apbs[i].mutex);
+				continue;
+			}
+
+			handled = 1;
+			FlagInt = 1;
+			writeb(0, apbs[i].RamIO + RAM_IT_TO_PC);
+
+			if (readb(apbs[i].RamIO + DATA_TO_PC_READY) > 2) {
+				printk(KERN_WARNING "APPLICOM driver interrupt err board %d, DataToPcReady = %d\n",
+				       i+1,(int)readb(apbs[i].RamIO + DATA_TO_PC_READY));
+				DeviceErrorCount++;
+			}
+
+			if((readb(apbs[i].RamIO + DATA_FROM_PC_READY) > 2) && 
+			   (readb(apbs[i].RamIO + DATA_FROM_PC_READY) != 6)) {
+				
+				printk(KERN_WARNING "APPLICOM driver interrupt err board %d, DataFromPcReady = %d\n",
+				       i+1,(int)readb(apbs[i].RamIO + DATA_FROM_PC_READY));
+				DeviceErrorCount++;
+			}
+
+			if (readb(apbs[i].RamIO + DATA_TO_PC_READY) == 2) {	/* mailbox sent by the card ?   */
+				if (waitqueue_active(&FlagSleepRec)) {
+				wake_up_interruptible(&FlagSleepRec);
+			}
+			}
+
+			if (readb(apbs[i].RamIO + DATA_FROM_PC_READY) == 0) {	/* ram i/o free for write by pc ? */
+				if (waitqueue_active(&apbs[i].FlagSleepSend)) {	/* process sleep during read ?    */
+					wake_up_interruptible(&apbs[i].FlagSleepSend);
+				}
+			}
+			Dummy = readb(apbs[i].RamIO + VERS);
+
+			if(readb(apbs[i].RamIO + RAM_IT_TO_PC)) {
+				/* There's another int waiting on this card */
+				spin_unlock(&apbs[i].mutex);
+				i--;
+			} else {
+				spin_unlock(&apbs[i].mutex);
+			}
+		}
+		if (FlagInt)
+			LoopCount = 0;
+		else
+			LoopCount++;
+	} while(LoopCount < 2);
+	return IRQ_RETVAL(handled);
+}
+
+
+
+static int ac_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+     
+{				/* @ ADG ou ATO selon le cas */
+	int i;
+	unsigned char IndexCard;
+	void __iomem *pmem;
+	int ret = 0;
+	volatile unsigned char byte_reset_it;
+	struct st_ram_io *adgl;
+	void __user *argp = (void __user *)arg;
+
+	/* In general, the device is only openable by root anyway, so we're not
+	   particularly concerned that bogus ioctls can flood the console. */
+
+	adgl = kmalloc(sizeof(struct st_ram_io), GFP_KERNEL);
+	if (!adgl)
+		return -ENOMEM;
+
+	if (copy_from_user(adgl, argp, sizeof(struct st_ram_io))) {
+		kfree(adgl);
+		return -EFAULT;
+	}
+	
+	IndexCard = adgl->num_card-1;
+	 
+	if(cmd != 0 && cmd != 6 &&
+	   ((IndexCard >= MAX_BOARD) || !apbs[IndexCard].RamIO)) {
+		static int warncount = 10;
+		if (warncount) {
+			printk( KERN_WARNING "APPLICOM driver IOCTL, bad board number %d\n",(int)IndexCard+1);
+			warncount--;
+		}
+		kfree(adgl);
+		return -EINVAL;
+	}
+
+	switch (cmd) {
+		
+	case 0:
+		pmem = apbs[IndexCard].RamIO;
+		for (i = 0; i < sizeof(struct st_ram_io); i++)
+			((unsigned char *)adgl)[i]=readb(pmem++);
+		if (copy_to_user(argp, adgl, sizeof(struct st_ram_io)))
+			ret = -EFAULT;
+		break;
+	case 1:
+		pmem = apbs[IndexCard].RamIO + CONF_END_TEST;
+		for (i = 0; i < 4; i++)
+			adgl->conf_end_test[i] = readb(pmem++);
+		for (i = 0; i < 2; i++)
+			adgl->error_code[i] = readb(pmem++);
+		for (i = 0; i < 4; i++)
+			adgl->parameter_error[i] = readb(pmem++);
+		pmem = apbs[IndexCard].RamIO + VERS;
+		adgl->vers = readb(pmem);
+		pmem = apbs[IndexCard].RamIO + TYPE_CARD;
+		for (i = 0; i < 20; i++)
+			adgl->reserv1[i] = readb(pmem++);
+		*(int *)&adgl->reserv1[20] =  
+			(readb(apbs[IndexCard].RamIO + SERIAL_NUMBER) << 16) + 
+			(readb(apbs[IndexCard].RamIO + SERIAL_NUMBER + 1) << 8) + 
+			(readb(apbs[IndexCard].RamIO + SERIAL_NUMBER + 2) );
+
+		if (copy_to_user(argp, adgl, sizeof(struct st_ram_io)))
+			ret = -EFAULT;
+		break;
+	case 2:
+		pmem = apbs[IndexCard].RamIO + CONF_END_TEST;
+		for (i = 0; i < 10; i++)
+			writeb(0xff, pmem++);
+		writeb(adgl->data_from_pc_ready, 
+		       apbs[IndexCard].RamIO + DATA_FROM_PC_READY);
+
+		writeb(1, apbs[IndexCard].RamIO + RAM_IT_FROM_PC);
+		
+		for (i = 0; i < MAX_BOARD; i++) {
+			if (apbs[i].RamIO) {
+				byte_reset_it = readb(apbs[i].RamIO + RAM_IT_TO_PC);
+			}
+		}
+		break;
+	case 3:
+		pmem = apbs[IndexCard].RamIO + TIC_DES_FROM_PC;
+		writeb(adgl->tic_des_from_pc, pmem);
+		break;
+	case 4:
+		pmem = apbs[IndexCard].RamIO + TIC_OWNER_TO_PC;
+		adgl->tic_owner_to_pc     = readb(pmem++);
+		adgl->numcard_owner_to_pc = readb(pmem);
+		if (copy_to_user(argp, adgl,sizeof(struct st_ram_io)))
+			ret = -EFAULT;
+		break;
+	case 5:
+		writeb(adgl->num_card, apbs[IndexCard].RamIO + NUMCARD_OWNER_TO_PC);
+		writeb(adgl->num_card, apbs[IndexCard].RamIO + NUMCARD_DES_FROM_PC);
+		writeb(adgl->num_card, apbs[IndexCard].RamIO + NUMCARD_ACK_FROM_PC);
+		writeb(4, apbs[IndexCard].RamIO + DATA_FROM_PC_READY);
+		writeb(1, apbs[IndexCard].RamIO + RAM_IT_FROM_PC);
+		break;
+	case 6:
+		printk(KERN_INFO "APPLICOM driver release .... V2.8.0 ($Revision: 1.30 $)\n");
+		printk(KERN_INFO "Number of installed boards . %d\n", (int) numboards);
+		printk(KERN_INFO "Segment of board ........... %X\n", (int) mem);
+		printk(KERN_INFO "Interrupt IRQ number ....... %d\n", (int) irq);
+		for (i = 0; i < MAX_BOARD; i++) {
+			int serial;
+			char boardname[(SERIAL_NUMBER - TYPE_CARD) + 1];
+
+			if (!apbs[i].RamIO)
+				continue;
+
+			for (serial = 0; serial < SERIAL_NUMBER - TYPE_CARD; serial++)
+				boardname[serial] = readb(apbs[i].RamIO + TYPE_CARD + serial);
+			boardname[serial] = 0;
+
+			printk(KERN_INFO "Prom version board %d ....... V%d.%d %s",
+			       i+1,
+			       (int)(readb(apbs[IndexCard].RamIO + VERS) >> 4),
+			       (int)(readb(apbs[IndexCard].RamIO + VERS) & 0xF),
+			       boardname);
+
+
+			serial = (readb(apbs[i].RamIO + SERIAL_NUMBER) << 16) + 
+				(readb(apbs[i].RamIO + SERIAL_NUMBER + 1) << 8) + 
+				(readb(apbs[i].RamIO + SERIAL_NUMBER + 2) );
+
+			if (serial != 0)
+				printk(" S/N %d\n", serial);
+			else
+				printk("\n");
+		}
+		if (DeviceErrorCount != 0)
+			printk(KERN_INFO "DeviceErrorCount ........... %d\n", DeviceErrorCount);
+		if (ReadErrorCount != 0)
+			printk(KERN_INFO "ReadErrorCount ............. %d\n", ReadErrorCount);
+		if (WriteErrorCount != 0)
+			printk(KERN_INFO "WriteErrorCount ............ %d\n", WriteErrorCount);
+		if (waitqueue_active(&FlagSleepRec))
+			printk(KERN_INFO "Process in read pending\n");
+		for (i = 0; i < MAX_BOARD; i++) {
+			if (apbs[i].RamIO && waitqueue_active(&apbs[i].FlagSleepSend))
+				printk(KERN_INFO "Process in write pending board %d\n",i+1);
+		}
+		break;
+	default:
+		printk(KERN_INFO "APPLICOM driver ioctl, unknown function code %d\n",cmd) ;
+		ret = -EINVAL;
+		break;
+	}
+	Dummy = readb(apbs[IndexCard].RamIO + VERS);
+	kfree(adgl);
+	return 0;
+}
+
+#ifndef MODULE
+static int __init applicom_setup(char *str)
+{
+	int ints[4];
+
+	(void) get_options(str, 4, ints);
+
+	if (ints[0] > 2) {
+		printk(KERN_WARNING "Too many arguments to 'applicom=', expected mem,irq only.\n");
+	}
+
+	if (ints[0] < 2) {
+		printk(KERN_INFO"applicom numargs: %d\n", ints[0]);
+		return 0;
+	}
+
+	mem = ints[1];
+	irq = ints[2];
+	return 1;
+}
+
+__setup("applicom=", applicom_setup);
+
+#endif				/* MODULE */
+
diff --git a/drivers/char/applicom.h b/drivers/char/applicom.h
new file mode 100644
index 0000000..35530b3
--- /dev/null
+++ b/drivers/char/applicom.h
@@ -0,0 +1,85 @@
+/* $Id: applicom.h,v 1.2 1999/08/28 15:09:49 dwmw2 Exp $ */
+
+
+#ifndef __LINUX_APPLICOM_H__
+#define __LINUX_APPLICOM_H__
+
+
+#define DATA_TO_PC_READY      0x00
+#define TIC_OWNER_TO_PC       0x01
+#define NUMCARD_OWNER_TO_PC   0x02
+#define TIC_DES_TO_PC         0x03
+#define NUMCARD_DES_TO_PC     0x04
+#define DATA_FROM_PC_READY    0x05
+#define TIC_OWNER_FROM_PC     0x06
+#define NUMCARD_OWNER_FROM_PC 0x07
+#define TIC_DES_FROM_PC       0x08
+#define NUMCARD_DES_FROM_PC   0x09
+#define ACK_FROM_PC_READY     0x0E
+#define TIC_ACK_FROM_PC       0x0F
+#define NUMCARD_ACK_FROM_PC   0x010
+#define TYP_ACK_FROM_PC       0x011
+#define CONF_END_TEST         0x012
+#define ERROR_CODE            0x016 
+#define PARAMETER_ERROR       0x018 
+#define VERS                  0x01E 
+#define RAM_TO_PC             0x040
+#define RAM_FROM_PC           0x0170
+#define TYPE_CARD             0x03C0
+#define SERIAL_NUMBER         0x03DA
+#define RAM_IT_FROM_PC        0x03FE
+#define RAM_IT_TO_PC          0x03FF
+
+struct mailbox{
+	u16  stjb_codef;		/* offset 00 */
+	s16  stjb_status;     		/* offset 02 */
+	u16  stjb_ticuser_root;		/* offset 04 */
+	u8   stjb_piduser[4];		/* offset 06 */
+	u16  stjb_mode;			/* offset 0A */
+	u16  stjb_time;			/* offset 0C */
+	u16  stjb_stop;			/* offset 0E */
+	u16  stjb_nfonc;		/* offset 10 */
+	u16  stjb_ncard;		/* offset 12 */
+	u16  stjb_nchan;		/* offset 14 */
+	u16  stjb_nes;			/* offset 16 */
+	u16  stjb_nb;			/* offset 18 */
+	u16  stjb_typvar;		/* offset 1A */
+	u32  stjb_adr;			/* offset 1C */
+	u16  stjb_ticuser_dispcyc;	/* offset 20 */
+	u16  stjb_ticuser_protocol;	/* offset 22 */
+	u8   stjb_filler[12];		/* offset 24 */
+	u8   stjb_data[256];		/* offset 30 */
+	};
+
+struct st_ram_io 
+{
+	unsigned char data_to_pc_ready;
+	unsigned char tic_owner_to_pc;
+	unsigned char numcard_owner_to_pc;
+	unsigned char tic_des_to_pc;
+	unsigned char numcard_des_to_pc;
+	unsigned char data_from_pc_ready;
+	unsigned char tic_owner_from_pc;
+	unsigned char numcard_owner_from_pc;
+	unsigned char tic_des_from_pc;
+	unsigned char numcard_des_from_pc;
+	unsigned char ack_to_pc_ready;
+	unsigned char tic_ack_to_pc;
+	unsigned char numcard_ack_to_pc;
+	unsigned char typ_ack_to_pc;
+	unsigned char ack_from_pc_ready;
+	unsigned char tic_ack_from_pc;
+	unsigned char numcard_ack_from_pc;
+	unsigned char typ_ack_from_pc;
+	unsigned char conf_end_test[4];
+	unsigned char error_code[2];
+	unsigned char parameter_error[4];
+	unsigned char time_base;
+	unsigned char nul_inc;
+	unsigned char vers;
+	unsigned char num_card;
+	unsigned char reserv1[32];
+};
+
+
+#endif /* __LINUX_APPLICOM_H__ */
diff --git a/drivers/char/cd1865.h b/drivers/char/cd1865.h
new file mode 100644
index 0000000..9940966
--- /dev/null
+++ b/drivers/char/cd1865.h
@@ -0,0 +1,263 @@
+/*
+ *      linux/drivers/char/cd1865.h -- Definitions relating to the CD1865
+ *                          for the Specialix IO8+ multiport serial driver.
+ *
+ *      Copyright (C) 1997 Roger Wolff (R.E.Wolff@BitWizard.nl)
+ *      Copyright (C) 1994-1996  Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ *      Specialix pays for the development and support of this driver.
+ *      Please DO contact io8-linux@specialix.co.uk if you require
+ *      support.
+ *
+ *      This driver was developped in the BitWizard linux device
+ *      driver service. If you require a linux device driver for your
+ *      product, please contact devices@BitWizard.nl for a quote.
+ *
+ */
+
+/*
+ * Definitions for Driving CD180/CD1864/CD1865 based eightport serial cards.
+ */
+
+
+/* Values of choice for Interrupt ACKs */
+/* These values are "obligatory" if you use the register based
+ * interrupt acknowledgements. See page 99-101 of V2.0 of the CD1865
+ * databook */
+#define SX_ACK_MINT     0x75    /* goes to PILR1                           */
+#define SX_ACK_TINT     0x76    /* goes to PILR2                           */
+#define SX_ACK_RINT     0x77    /* goes to PILR3                           */
+
+/* Chip ID (is used when chips ar daisy chained.) */
+#define SX_ID           0x10
+
+/* Definitions for Cirrus Logic CL-CD186x 8-port async mux chip */
+ 
+#define CD186x_NCH       8       /* Total number of channels                */
+#define CD186x_TPC       16      /* Ticks per character                     */
+#define CD186x_NFIFO	 8	 /* TX FIFO size                            */
+
+
+/* Global registers */
+
+#define CD186x_GIVR      0x40    /* Global Interrupt Vector Register        */
+#define CD186x_GICR      0x41    /* Global Interrupting Channel Register    */
+#define CD186x_PILR1     0x61    /* Priority Interrupt Level Register 1     */
+#define CD186x_PILR2     0x62    /* Priority Interrupt Level Register 2     */
+#define CD186x_PILR3     0x63    /* Priority Interrupt Level Register 3     */
+#define CD186x_CAR       0x64    /* Channel Access Register                 */
+#define CD186x_SRSR      0x65    /* Channel Access Register                 */
+#define CD186x_GFRCR     0x6b    /* Global Firmware Revision Code Register  */
+#define CD186x_PPRH      0x70    /* Prescaler Period Register High          */
+#define CD186x_PPRL      0x71    /* Prescaler Period Register Low           */
+#define CD186x_RDR       0x78    /* Receiver Data Register                  */
+#define CD186x_RCSR      0x7a    /* Receiver Character Status Register      */
+#define CD186x_TDR       0x7b    /* Transmit Data Register                  */
+#define CD186x_EOIR      0x7f    /* End of Interrupt Register               */
+#define CD186x_MRAR      0x75    /* Modem Request Acknowledge register       */
+#define CD186x_TRAR      0x76    /* Transmit Request Acknowledge register    */
+#define CD186x_RRAR      0x77    /* Receive Request Acknowledge register     */
+#define CD186x_SRCR      0x66    /* Service Request Configuration register  */
+
+/* Channel Registers */
+
+#define CD186x_CCR       0x01    /* Channel Command Register                */
+#define CD186x_IER       0x02    /* Interrupt Enable Register               */
+#define CD186x_COR1      0x03    /* Channel Option Register 1               */
+#define CD186x_COR2      0x04    /* Channel Option Register 2               */
+#define CD186x_COR3      0x05    /* Channel Option Register 3               */
+#define CD186x_CCSR      0x06    /* Channel Control Status Register         */
+#define CD186x_RDCR      0x07    /* Receive Data Count Register             */
+#define CD186x_SCHR1     0x09    /* Special Character Register 1            */
+#define CD186x_SCHR2     0x0a    /* Special Character Register 2            */
+#define CD186x_SCHR3     0x0b    /* Special Character Register 3            */
+#define CD186x_SCHR4     0x0c    /* Special Character Register 4            */
+#define CD186x_MCOR1     0x10    /* Modem Change Option 1 Register          */
+#define CD186x_MCOR2     0x11    /* Modem Change Option 2 Register          */
+#define CD186x_MCR       0x12    /* Modem Change Register                   */
+#define CD186x_RTPR      0x18    /* Receive Timeout Period Register         */
+#define CD186x_MSVR      0x28    /* Modem Signal Value Register             */
+#define CD186x_MSVRTS    0x29    /* Modem Signal Value Register             */
+#define CD186x_MSVDTR    0x2a    /* Modem Signal Value Register             */
+#define CD186x_RBPRH     0x31    /* Receive Baud Rate Period Register High  */
+#define CD186x_RBPRL     0x32    /* Receive Baud Rate Period Register Low   */
+#define CD186x_TBPRH     0x39    /* Transmit Baud Rate Period Register High */
+#define CD186x_TBPRL     0x3a    /* Transmit Baud Rate Period Register Low  */
+
+
+/* Global Interrupt Vector Register (R/W) */
+
+#define GIVR_ITMASK     0x07     /* Interrupt type mask                     */
+#define  GIVR_IT_MODEM   0x01    /* Modem Signal Change Interrupt           */
+#define  GIVR_IT_TX      0x02    /* Transmit Data Interrupt                 */
+#define  GIVR_IT_RCV     0x03    /* Receive Good Data Interrupt             */
+#define  GIVR_IT_REXC    0x07    /* Receive Exception Interrupt             */
+
+
+/* Global Interrupt Channel Register (R/W) */
+ 
+#define GICR_CHAN       0x1c    /* Channel Number Mask                     */
+#define GICR_CHAN_OFF   2       /* Channel Number shift                    */
+
+
+/* Channel Address Register (R/W) */
+
+#define CAR_CHAN        0x07    /* Channel Number Mask                     */
+#define CAR_A7          0x08    /* A7 Address Extension (unused)           */
+
+
+/* Receive Character Status Register (R/O) */
+
+#define RCSR_TOUT       0x80    /* Rx Timeout                              */
+#define RCSR_SCDET      0x70    /* Special Character Detected Mask         */
+#define  RCSR_NO_SC      0x00   /* No Special Characters Detected          */
+#define  RCSR_SC_1       0x10   /* Special Char 1 (or 1 & 3) Detected      */
+#define  RCSR_SC_2       0x20   /* Special Char 2 (or 2 & 4) Detected      */
+#define  RCSR_SC_3       0x30   /* Special Char 3 Detected                 */
+#define  RCSR_SC_4       0x40   /* Special Char 4 Detected                 */
+#define RCSR_BREAK      0x08    /* Break has been detected                 */
+#define RCSR_PE         0x04    /* Parity Error                            */
+#define RCSR_FE         0x02    /* Frame Error                             */
+#define RCSR_OE         0x01    /* Overrun Error                           */
+
+
+/* Channel Command Register (R/W) (commands in groups can be OR-ed) */
+
+#define CCR_HARDRESET   0x81    /* Reset the chip                          */
+
+#define CCR_SOFTRESET   0x80    /* Soft Channel Reset                      */
+
+#define CCR_CORCHG1     0x42    /* Channel Option Register 1 Changed       */
+#define CCR_CORCHG2     0x44    /* Channel Option Register 2 Changed       */
+#define CCR_CORCHG3     0x48    /* Channel Option Register 3 Changed       */
+
+#define CCR_SSCH1       0x21    /* Send Special Character 1                */
+
+#define CCR_SSCH2       0x22    /* Send Special Character 2                */
+
+#define CCR_SSCH3       0x23    /* Send Special Character 3                */
+
+#define CCR_SSCH4       0x24    /* Send Special Character 4                */
+
+#define CCR_TXEN        0x18    /* Enable Transmitter                      */
+#define CCR_RXEN        0x12    /* Enable Receiver                         */
+
+#define CCR_TXDIS       0x14    /* Disable Transmitter                     */
+#define CCR_RXDIS       0x11    /* Disable Receiver                        */
+
+
+/* Interrupt Enable Register (R/W) */
+
+#define IER_DSR         0x80    /* Enable interrupt on DSR change          */
+#define IER_CD          0x40    /* Enable interrupt on CD change           */
+#define IER_CTS         0x20    /* Enable interrupt on CTS change          */
+#define IER_RXD         0x10    /* Enable interrupt on Receive Data        */
+#define IER_RXSC        0x08    /* Enable interrupt on Receive Spec. Char  */
+#define IER_TXRDY       0x04    /* Enable interrupt on TX FIFO empty       */
+#define IER_TXEMPTY     0x02    /* Enable interrupt on TX completely empty */
+#define IER_RET         0x01    /* Enable interrupt on RX Exc. Timeout     */
+
+
+/* Channel Option Register 1 (R/W) */
+
+#define COR1_ODDP       0x80    /* Odd Parity                              */
+#define COR1_PARMODE    0x60    /* Parity Mode mask                        */
+#define  COR1_NOPAR      0x00   /* No Parity                               */
+#define  COR1_FORCEPAR   0x20   /* Force Parity                            */
+#define  COR1_NORMPAR    0x40   /* Normal Parity                           */
+#define COR1_IGNORE     0x10    /* Ignore Parity on RX                     */
+#define COR1_STOPBITS   0x0c    /* Number of Stop Bits                     */
+#define  COR1_1SB        0x00   /* 1 Stop Bit                              */
+#define  COR1_15SB       0x04   /* 1.5 Stop Bits                           */
+#define  COR1_2SB        0x08   /* 2 Stop Bits                             */
+#define COR1_CHARLEN    0x03    /* Character Length                        */
+#define  COR1_5BITS      0x00   /* 5 bits                                  */
+#define  COR1_6BITS      0x01   /* 6 bits                                  */
+#define  COR1_7BITS      0x02   /* 7 bits                                  */
+#define  COR1_8BITS      0x03   /* 8 bits                                  */
+
+
+/* Channel Option Register 2 (R/W) */
+
+#define COR2_IXM        0x80    /* Implied XON mode                        */
+#define COR2_TXIBE      0x40    /* Enable In-Band (XON/XOFF) Flow Control  */
+#define COR2_ETC        0x20    /* Embedded Tx Commands Enable             */
+#define COR2_LLM        0x10    /* Local Loopback Mode                     */
+#define COR2_RLM        0x08    /* Remote Loopback Mode                    */
+#define COR2_RTSAO      0x04    /* RTS Automatic Output Enable             */
+#define COR2_CTSAE      0x02    /* CTS Automatic Enable                    */
+#define COR2_DSRAE      0x01    /* DSR Automatic Enable                    */
+
+
+/* Channel Option Register 3 (R/W) */
+
+#define COR3_XONCH      0x80    /* XON is a pair of characters (1 & 3)     */
+#define COR3_XOFFCH     0x40    /* XOFF is a pair of characters (2 & 4)    */
+#define COR3_FCT        0x20    /* Flow-Control Transparency Mode          */
+#define COR3_SCDE       0x10    /* Special Character Detection Enable      */
+#define COR3_RXTH       0x0f    /* RX FIFO Threshold value (1-8)           */
+
+
+/* Channel Control Status Register (R/O) */
+
+#define CCSR_RXEN       0x80    /* Receiver Enabled                        */
+#define CCSR_RXFLOFF    0x40    /* Receive Flow Off (XOFF was sent)        */
+#define CCSR_RXFLON     0x20    /* Receive Flow On (XON was sent)          */
+#define CCSR_TXEN       0x08    /* Transmitter Enabled                     */
+#define CCSR_TXFLOFF    0x04    /* Transmit Flow Off (got XOFF)            */
+#define CCSR_TXFLON     0x02    /* Transmit Flow On (got XON)              */
+
+
+/* Modem Change Option Register 1 (R/W) */
+
+#define MCOR1_DSRZD     0x80    /* Detect 0->1 transition of DSR           */
+#define MCOR1_CDZD      0x40    /* Detect 0->1 transition of CD            */
+#define MCOR1_CTSZD     0x20    /* Detect 0->1 transition of CTS           */
+#define MCOR1_DTRTH     0x0f    /* Auto DTR flow control Threshold (1-8)   */
+#define  MCOR1_NODTRFC   0x0     /* Automatic DTR flow control disabled     */
+
+
+/* Modem Change Option Register 2 (R/W) */
+
+#define MCOR2_DSROD     0x80    /* Detect 1->0 transition of DSR           */
+#define MCOR2_CDOD      0x40    /* Detect 1->0 transition of CD            */
+#define MCOR2_CTSOD     0x20    /* Detect 1->0 transition of CTS           */
+
+/* Modem Change Register (R/W) */
+
+#define MCR_DSRCHG      0x80    /* DSR Changed                             */
+#define MCR_CDCHG       0x40    /* CD Changed                              */
+#define MCR_CTSCHG      0x20    /* CTS Changed                             */
+
+
+/* Modem Signal Value Register (R/W) */
+
+#define MSVR_DSR        0x80    /* Current state of DSR input              */
+#define MSVR_CD         0x40    /* Current state of CD input               */
+#define MSVR_CTS        0x20    /* Current state of CTS input              */
+#define MSVR_DTR        0x02    /* Current state of DTR output             */
+#define MSVR_RTS        0x01    /* Current state of RTS output             */
+
+
+/* Escape characters */
+
+#define CD186x_C_ESC     0x00    /* Escape character                        */
+#define CD186x_C_SBRK    0x81    /* Start sending BREAK                     */
+#define CD186x_C_DELAY   0x82    /* Delay output                            */
+#define CD186x_C_EBRK    0x83    /* Stop sending BREAK                      */
+
+#define SRSR_RREQint     0x10    /* This chip wants "rec" serviced          */
+#define SRSR_TREQint     0x04    /* This chip wants "transmit" serviced     */
+#define SRSR_MREQint     0x01    /* This chip wants "mdm change" serviced   */
+
+
+
+#define SRCR_PKGTYPE    0x80
+#define SRCR_REGACKEN   0x40
+#define SRCR_DAISYEN    0x20
+#define SRCR_GLOBPRI    0x10
+#define SRCR_UNFAIR     0x08
+#define SRCR_AUTOPRI    0x02
+#define SRCR_PRISEL     0x01
+
+
diff --git a/drivers/char/consolemap.c b/drivers/char/consolemap.c
new file mode 100644
index 0000000..406dea9
--- /dev/null
+++ b/drivers/char/consolemap.c
@@ -0,0 +1,672 @@
+/*
+ * consolemap.c
+ *
+ * Mapping from internal code (such as Latin-1 or Unicode or IBM PC code)
+ * to font positions.
+ *
+ * aeb, 950210
+ *
+ * Support for multiple unimaps by Jakub Jelinek <jj@ultra.linux.cz>, July 1998
+ *
+ * Fix bug in inverse translation. Stanislav Voronyi <stas@cnti.uanet.kharkov.ua>, Dec 1998
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kd.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/tty.h>
+#include <asm/uaccess.h>
+#include <linux/consolemap.h>
+#include <linux/vt_kern.h>
+
+static unsigned short translations[][256] = {
+  /* 8-bit Latin-1 mapped to Unicode -- trivial mapping */
+  {
+    0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+    0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+    0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+    0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+    0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+    0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+    0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+    0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+    0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+    0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+    0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+    0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+    0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+    0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+    0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+    0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+    0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+    0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+    0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+    0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+    0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+    0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+    0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+    0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+    0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+    0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+    0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+  }, 
+  /* VT100 graphics mapped to Unicode */
+  {
+    0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+    0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+    0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+    0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+    0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f,
+    0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+    0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+    0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+    0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0,
+    0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
+    0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba,
+    0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c,
+    0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f,
+    0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+    0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+    0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+    0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+    0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+    0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+    0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+    0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+    0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+    0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+    0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+    0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+    0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+    0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+    0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+    0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+  },
+  /* IBM Codepage 437 mapped to Unicode */
+  {
+    0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 
+    0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
+    0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
+    0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
+    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+    0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+    0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+    0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+    0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+    0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+    0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+    0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
+    0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
+    0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+    0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
+    0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+    0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
+    0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+    0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+    0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+    0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
+    0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+    0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
+    0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+    0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
+    0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+    0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
+    0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
+  }, 
+  /* User mapping -- default to codes for direct font mapping */
+  {
+    0xf000, 0xf001, 0xf002, 0xf003, 0xf004, 0xf005, 0xf006, 0xf007,
+    0xf008, 0xf009, 0xf00a, 0xf00b, 0xf00c, 0xf00d, 0xf00e, 0xf00f,
+    0xf010, 0xf011, 0xf012, 0xf013, 0xf014, 0xf015, 0xf016, 0xf017,
+    0xf018, 0xf019, 0xf01a, 0xf01b, 0xf01c, 0xf01d, 0xf01e, 0xf01f,
+    0xf020, 0xf021, 0xf022, 0xf023, 0xf024, 0xf025, 0xf026, 0xf027,
+    0xf028, 0xf029, 0xf02a, 0xf02b, 0xf02c, 0xf02d, 0xf02e, 0xf02f,
+    0xf030, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, 0xf037,
+    0xf038, 0xf039, 0xf03a, 0xf03b, 0xf03c, 0xf03d, 0xf03e, 0xf03f,
+    0xf040, 0xf041, 0xf042, 0xf043, 0xf044, 0xf045, 0xf046, 0xf047,
+    0xf048, 0xf049, 0xf04a, 0xf04b, 0xf04c, 0xf04d, 0xf04e, 0xf04f,
+    0xf050, 0xf051, 0xf052, 0xf053, 0xf054, 0xf055, 0xf056, 0xf057,
+    0xf058, 0xf059, 0xf05a, 0xf05b, 0xf05c, 0xf05d, 0xf05e, 0xf05f,
+    0xf060, 0xf061, 0xf062, 0xf063, 0xf064, 0xf065, 0xf066, 0xf067,
+    0xf068, 0xf069, 0xf06a, 0xf06b, 0xf06c, 0xf06d, 0xf06e, 0xf06f,
+    0xf070, 0xf071, 0xf072, 0xf073, 0xf074, 0xf075, 0xf076, 0xf077,
+    0xf078, 0xf079, 0xf07a, 0xf07b, 0xf07c, 0xf07d, 0xf07e, 0xf07f,
+    0xf080, 0xf081, 0xf082, 0xf083, 0xf084, 0xf085, 0xf086, 0xf087,
+    0xf088, 0xf089, 0xf08a, 0xf08b, 0xf08c, 0xf08d, 0xf08e, 0xf08f,
+    0xf090, 0xf091, 0xf092, 0xf093, 0xf094, 0xf095, 0xf096, 0xf097,
+    0xf098, 0xf099, 0xf09a, 0xf09b, 0xf09c, 0xf09d, 0xf09e, 0xf09f,
+    0xf0a0, 0xf0a1, 0xf0a2, 0xf0a3, 0xf0a4, 0xf0a5, 0xf0a6, 0xf0a7,
+    0xf0a8, 0xf0a9, 0xf0aa, 0xf0ab, 0xf0ac, 0xf0ad, 0xf0ae, 0xf0af,
+    0xf0b0, 0xf0b1, 0xf0b2, 0xf0b3, 0xf0b4, 0xf0b5, 0xf0b6, 0xf0b7,
+    0xf0b8, 0xf0b9, 0xf0ba, 0xf0bb, 0xf0bc, 0xf0bd, 0xf0be, 0xf0bf,
+    0xf0c0, 0xf0c1, 0xf0c2, 0xf0c3, 0xf0c4, 0xf0c5, 0xf0c6, 0xf0c7,
+    0xf0c8, 0xf0c9, 0xf0ca, 0xf0cb, 0xf0cc, 0xf0cd, 0xf0ce, 0xf0cf,
+    0xf0d0, 0xf0d1, 0xf0d2, 0xf0d3, 0xf0d4, 0xf0d5, 0xf0d6, 0xf0d7,
+    0xf0d8, 0xf0d9, 0xf0da, 0xf0db, 0xf0dc, 0xf0dd, 0xf0de, 0xf0df,
+    0xf0e0, 0xf0e1, 0xf0e2, 0xf0e3, 0xf0e4, 0xf0e5, 0xf0e6, 0xf0e7,
+    0xf0e8, 0xf0e9, 0xf0ea, 0xf0eb, 0xf0ec, 0xf0ed, 0xf0ee, 0xf0ef,
+    0xf0f0, 0xf0f1, 0xf0f2, 0xf0f3, 0xf0f4, 0xf0f5, 0xf0f6, 0xf0f7,
+    0xf0f8, 0xf0f9, 0xf0fa, 0xf0fb, 0xf0fc, 0xf0fd, 0xf0fe, 0xf0ff
+  }
+};
+
+/* The standard kernel character-to-font mappings are not invertible
+   -- this is just a best effort. */
+
+#define MAX_GLYPH 512		/* Max possible glyph value */
+
+static int inv_translate[MAX_NR_CONSOLES];
+
+struct uni_pagedir {
+	u16 		**uni_pgdir[32];
+	unsigned long	refcount;
+	unsigned long	sum;
+	unsigned char	*inverse_translations[4];
+	int		readonly;
+};
+
+static struct uni_pagedir *dflt;
+
+static void set_inverse_transl(struct vc_data *conp, struct uni_pagedir *p, int i)
+{
+	int j, glyph;
+	unsigned short *t = translations[i];
+	unsigned char *q;
+	
+	if (!p) return;
+	q = p->inverse_translations[i];
+
+	if (!q) {
+		q = p->inverse_translations[i] = (unsigned char *) 
+			kmalloc(MAX_GLYPH, GFP_KERNEL);
+		if (!q) return;
+	}
+	memset(q, 0, MAX_GLYPH);
+
+	for (j = 0; j < E_TABSZ; j++) {
+		glyph = conv_uni_to_pc(conp, t[j]);
+		if (glyph >= 0 && glyph < MAX_GLYPH && q[glyph] < 32) {
+			/* prefer '-' above SHY etc. */
+		  	q[glyph] = j;
+		}
+	}
+}
+
+unsigned short *set_translate(int m, struct vc_data *vc)
+{
+	inv_translate[vc->vc_num] = m;
+	return translations[m];
+}
+
+/*
+ * Inverse translation is impossible for several reasons:
+ * 1. The font<->character maps are not 1-1.
+ * 2. The text may have been written while a different translation map
+ *    was active, or using Unicode.
+ * Still, it is now possible to a certain extent to cut and paste non-ASCII.
+ */
+unsigned char inverse_translate(struct vc_data *conp, int glyph)
+{
+	struct uni_pagedir *p;
+	if (glyph < 0 || glyph >= MAX_GLYPH)
+		return 0;
+	else if (!(p = (struct uni_pagedir *)*conp->vc_uni_pagedir_loc) ||
+		 !p->inverse_translations[inv_translate[conp->vc_num]])
+		return glyph;
+	else
+		return p->inverse_translations[inv_translate[conp->vc_num]][glyph];
+}
+
+static void update_user_maps(void)
+{
+	int i;
+	struct uni_pagedir *p, *q = NULL;
+	
+	for (i = 0; i < MAX_NR_CONSOLES; i++) {
+		if (!vc_cons_allocated(i))
+			continue;
+		p = (struct uni_pagedir *)*vc_cons[i].d->vc_uni_pagedir_loc;
+		if (p && p != q) {
+			set_inverse_transl(vc_cons[i].d, p, USER_MAP);
+			q = p;
+		}
+	}
+}
+
+/*
+ * Load customizable translation table
+ * arg points to a 256 byte translation table.
+ *
+ * The "old" variants are for translation directly to font (using the
+ * 0xf000-0xf0ff "transparent" Unicodes) whereas the "new" variants set
+ * Unicodes explicitly.
+ */
+int con_set_trans_old(unsigned char __user * arg)
+{
+	int i;
+	unsigned short *p = translations[USER_MAP];
+
+	if (!access_ok(VERIFY_READ, arg, E_TABSZ))
+		return -EFAULT;
+
+	for (i=0; i<E_TABSZ ; i++) {
+		unsigned char uc;
+		__get_user(uc, arg+i);
+		p[i] = UNI_DIRECT_BASE | uc;
+	}
+
+	update_user_maps();
+	return 0;
+}
+
+int con_get_trans_old(unsigned char __user * arg)
+{
+	int i, ch;
+	unsigned short *p = translations[USER_MAP];
+
+	if (!access_ok(VERIFY_WRITE, arg, E_TABSZ))
+		return -EFAULT;
+
+	for (i=0; i<E_TABSZ ; i++)
+	  {
+	    ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]);
+	    __put_user((ch & ~0xff) ? 0 : ch, arg+i);
+	  }
+	return 0;
+}
+
+int con_set_trans_new(ushort __user * arg)
+{
+	int i;
+	unsigned short *p = translations[USER_MAP];
+
+	if (!access_ok(VERIFY_READ, arg, E_TABSZ*sizeof(unsigned short)))
+		return -EFAULT;
+
+	for (i=0; i<E_TABSZ ; i++) {
+		unsigned short us;
+		__get_user(us, arg+i);
+		p[i] = us;
+	}
+
+	update_user_maps();
+	return 0;
+}
+
+int con_get_trans_new(ushort __user * arg)
+{
+	int i;
+	unsigned short *p = translations[USER_MAP];
+
+	if (!access_ok(VERIFY_WRITE, arg, E_TABSZ*sizeof(unsigned short)))
+		return -EFAULT;
+
+	for (i=0; i<E_TABSZ ; i++)
+	  __put_user(p[i], arg+i);
+	
+	return 0;
+}
+
+/*
+ * Unicode -> current font conversion 
+ *
+ * A font has at most 512 chars, usually 256.
+ * But one font position may represent several Unicode chars.
+ * A hashtable is somewhat of a pain to deal with, so use a
+ * "paged table" instead.  Simulation has shown the memory cost of
+ * this 3-level paged table scheme to be comparable to a hash table.
+ */
+
+extern u8 dfont_unicount[];	/* Defined in console_defmap.c */
+extern u16 dfont_unitable[];
+
+static void con_release_unimap(struct uni_pagedir *p)
+{
+	u16 **p1;
+	int i, j;
+
+	if (p == dflt) dflt = NULL;  
+	for (i = 0; i < 32; i++) {
+		if ((p1 = p->uni_pgdir[i]) != NULL) {
+			for (j = 0; j < 32; j++)
+				if (p1[j])
+					kfree(p1[j]);
+			kfree(p1);
+		}
+		p->uni_pgdir[i] = NULL;
+	}
+	for (i = 0; i < 4; i++)
+		if (p->inverse_translations[i]) {
+			kfree(p->inverse_translations[i]);
+			p->inverse_translations[i] = NULL;
+		}
+}
+
+void con_free_unimap(struct vc_data *vc)
+{
+	struct uni_pagedir *p;
+
+	p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+	if (!p)
+		return;
+	*vc->vc_uni_pagedir_loc = 0;
+	if (--p->refcount)
+		return;
+	con_release_unimap(p);
+	kfree(p);
+}
+  
+static int con_unify_unimap(struct vc_data *conp, struct uni_pagedir *p)
+{
+	int i, j, k;
+	struct uni_pagedir *q;
+	
+	for (i = 0; i < MAX_NR_CONSOLES; i++) {
+		if (!vc_cons_allocated(i))
+			continue;
+		q = (struct uni_pagedir *)*vc_cons[i].d->vc_uni_pagedir_loc;
+		if (!q || q == p || q->sum != p->sum)
+			continue;
+		for (j = 0; j < 32; j++) {
+			u16 **p1, **q1;
+			p1 = p->uni_pgdir[j]; q1 = q->uni_pgdir[j];
+			if (!p1 && !q1)
+				continue;
+			if (!p1 || !q1)
+				break;
+			for (k = 0; k < 32; k++) {
+				if (!p1[k] && !q1[k])
+					continue;
+				if (!p1[k] || !q1[k])
+					break;
+				if (memcmp(p1[k], q1[k], 64*sizeof(u16)))
+					break;
+			}
+			if (k < 32)
+				break;
+		}
+		if (j == 32) {
+			q->refcount++;
+			*conp->vc_uni_pagedir_loc = (unsigned long)q;
+			con_release_unimap(p);
+			kfree(p);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int
+con_insert_unipair(struct uni_pagedir *p, u_short unicode, u_short fontpos)
+{
+	int i, n;
+	u16 **p1, *p2;
+
+	if (!(p1 = p->uni_pgdir[n = unicode >> 11])) {
+		p1 = p->uni_pgdir[n] = kmalloc(32*sizeof(u16 *), GFP_KERNEL);
+		if (!p1) return -ENOMEM;
+		for (i = 0; i < 32; i++)
+			p1[i] = NULL;
+	}
+
+	if (!(p2 = p1[n = (unicode >> 6) & 0x1f])) {
+		p2 = p1[n] = kmalloc(64*sizeof(u16), GFP_KERNEL);
+		if (!p2) return -ENOMEM;
+		memset(p2, 0xff, 64*sizeof(u16)); /* No glyphs for the characters (yet) */
+	}
+
+	p2[unicode & 0x3f] = fontpos;
+	
+	p->sum += (fontpos << 20) + unicode;
+
+	return 0;
+}
+
+/* ui is a leftover from using a hashtable, but might be used again */
+int con_clear_unimap(struct vc_data *vc, struct unimapinit *ui)
+{
+	struct uni_pagedir *p, *q;
+  
+	p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+	if (p && p->readonly) return -EIO;
+	if (!p || --p->refcount) {
+		q = (struct uni_pagedir *)kmalloc(sizeof(*p), GFP_KERNEL);
+		if (!q) {
+			if (p) p->refcount++;
+			return -ENOMEM;
+		}
+		memset(q, 0, sizeof(*q));
+		q->refcount=1;
+		*vc->vc_uni_pagedir_loc = (unsigned long)q;
+	} else {
+		if (p == dflt) dflt = NULL;
+		p->refcount++;
+		p->sum = 0;
+		con_release_unimap(p);
+	}
+	return 0;
+}
+
+int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list)
+{
+	int err = 0, err1, i;
+	struct uni_pagedir *p, *q;
+
+	p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+	if (p->readonly) return -EIO;
+	
+	if (!ct) return 0;
+	
+	if (p->refcount > 1) {
+		int j, k;
+		u16 **p1, *p2, l;
+		
+		err1 = con_clear_unimap(vc, NULL);
+		if (err1) return err1;
+		
+		q = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+		for (i = 0, l = 0; i < 32; i++)
+		if ((p1 = p->uni_pgdir[i]))
+			for (j = 0; j < 32; j++)
+			if ((p2 = p1[j]))
+				for (k = 0; k < 64; k++, l++)
+				if (p2[k] != 0xffff) {
+					err1 = con_insert_unipair(q, l, p2[k]);
+					if (err1) {
+						p->refcount++;
+						*vc->vc_uni_pagedir_loc = (unsigned long)p;
+						con_release_unimap(q);
+						kfree(q);
+						return err1; 
+					}
+              			}
+              	p = q;
+	} else if (p == dflt)
+		dflt = NULL;
+	
+	while (ct--) {
+		unsigned short unicode, fontpos;
+		__get_user(unicode, &list->unicode);
+		__get_user(fontpos, &list->fontpos);
+		if ((err1 = con_insert_unipair(p, unicode,fontpos)) != 0)
+			err = err1;
+			list++;
+	}
+	
+	if (con_unify_unimap(vc, p))
+		return err;
+
+	for (i = 0; i <= 3; i++)
+		set_inverse_transl(vc, p, i); /* Update all inverse translations */
+  
+	return err;
+}
+
+/* Loads the unimap for the hardware font, as defined in uni_hash.tbl.
+   The representation used was the most compact I could come up
+   with.  This routine is executed at sys_setup time, and when the
+   PIO_FONTRESET ioctl is called. */
+
+int con_set_default_unimap(struct vc_data *vc)
+{
+	int i, j, err = 0, err1;
+	u16 *q;
+	struct uni_pagedir *p;
+
+	if (dflt) {
+		p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+		if (p == dflt)
+			return 0;
+		dflt->refcount++;
+		*vc->vc_uni_pagedir_loc = (unsigned long)dflt;
+		if (p && --p->refcount) {
+			con_release_unimap(p);
+			kfree(p);
+		}
+		return 0;
+	}
+	
+	/* The default font is always 256 characters */
+
+	err = con_clear_unimap(vc, NULL);
+	if (err) return err;
+    
+	p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+	q = dfont_unitable;
+	
+	for (i = 0; i < 256; i++)
+		for (j = dfont_unicount[i]; j; j--) {
+			err1 = con_insert_unipair(p, *(q++), i);
+			if (err1)
+				err = err1;
+		}
+			
+	if (con_unify_unimap(vc, p)) {
+		dflt = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+		return err;
+	}
+
+	for (i = 0; i <= 3; i++)
+		set_inverse_transl(vc, p, i);	/* Update all inverse translations */
+	dflt = p;
+	return err;
+}
+EXPORT_SYMBOL(con_set_default_unimap);
+
+int con_copy_unimap(struct vc_data *dst_vc, struct vc_data *src_vc)
+{
+	struct uni_pagedir *q;
+
+	if (!*src_vc->vc_uni_pagedir_loc)
+		return -EINVAL;
+	if (*dst_vc->vc_uni_pagedir_loc == *src_vc->vc_uni_pagedir_loc)
+		return 0;
+	con_free_unimap(dst_vc);
+	q = (struct uni_pagedir *)*src_vc->vc_uni_pagedir_loc;
+	q->refcount++;
+	*dst_vc->vc_uni_pagedir_loc = (long)q;
+	return 0;
+}
+
+int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct, struct unipair __user *list)
+{
+	int i, j, k, ect;
+	u16 **p1, *p2;
+	struct uni_pagedir *p;
+
+	ect = 0;
+	if (*vc->vc_uni_pagedir_loc) {
+		p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+		for (i = 0; i < 32; i++)
+		if ((p1 = p->uni_pgdir[i]))
+			for (j = 0; j < 32; j++)
+			if ((p2 = *(p1++)))
+				for (k = 0; k < 64; k++) {
+					if (*p2 < MAX_GLYPH && ect++ < ct) {
+						__put_user((u_short)((i<<11)+(j<<6)+k),
+							   &list->unicode);
+						__put_user((u_short) *p2, 
+							   &list->fontpos);
+						list++;
+					}
+					p2++;
+				}
+	}
+	__put_user(ect, uct);
+	return ((ect <= ct) ? 0 : -ENOMEM);
+}
+
+void con_protect_unimap(struct vc_data *vc, int rdonly)
+{
+	struct uni_pagedir *p = (struct uni_pagedir *)*vc->vc_uni_pagedir_loc;
+	
+	if (p)
+		p->readonly = rdonly;
+}
+
+int
+conv_uni_to_pc(struct vc_data *conp, long ucs) 
+{
+	int h;
+	u16 **p1, *p2;
+	struct uni_pagedir *p;
+  
+	/* Only 16-bit codes supported at this time */
+	if (ucs > 0xffff)
+		ucs = 0xfffd;		/* U+FFFD: REPLACEMENT CHARACTER */
+	else if (ucs < 0x20 || ucs >= 0xfffe)
+		return -1;		/* Not a printable character */
+	else if (ucs == 0xfeff || (ucs >= 0x200a && ucs <= 0x200f))
+		return -2;			/* Zero-width space */
+	/*
+	 * UNI_DIRECT_BASE indicates the start of the region in the User Zone
+	 * which always has a 1:1 mapping to the currently loaded font.  The
+	 * UNI_DIRECT_MASK indicates the bit span of the region.
+	 */
+	else if ((ucs & ~UNI_DIRECT_MASK) == UNI_DIRECT_BASE)
+		return ucs & UNI_DIRECT_MASK;
+  
+	if (!*conp->vc_uni_pagedir_loc)
+		return -3;
+
+	p = (struct uni_pagedir *)*conp->vc_uni_pagedir_loc;  
+	if ((p1 = p->uni_pgdir[ucs >> 11]) &&
+	    (p2 = p1[(ucs >> 6) & 0x1f]) &&
+	    (h = p2[ucs & 0x3f]) < MAX_GLYPH)
+		return h;
+
+	return -4;		/* not found */
+}
+
+/*
+ * This is called at sys_setup time, after memory and the console are
+ * initialized.  It must be possible to call kmalloc(..., GFP_KERNEL)
+ * from this function, hence the call from sys_setup.
+ */
+void __init 
+console_map_init(void)
+{
+	int i;
+	
+	for (i = 0; i < MAX_NR_CONSOLES; i++)
+		if (vc_cons_allocated(i) && !*vc_cons[i].d->vc_uni_pagedir_loc)
+			con_set_default_unimap(vc_cons[i].d);
+}
+
+EXPORT_SYMBOL(con_copy_unimap);
diff --git a/drivers/char/cp437.uni b/drivers/char/cp437.uni
new file mode 100644
index 0000000..1f06889
--- /dev/null
+++ b/drivers/char/cp437.uni
@@ -0,0 +1,291 @@
+#
+# Unicode table for IBM Codepage 437.  Note that there are many more
+# substitutions that could be conceived (for example, thick-line
+# graphs probably should be replaced with double-line ones, accented
+# Latin characters should replaced with their nonaccented versions,
+# and some upper case Greek characters could be replaced by Latin), however,
+# I have limited myself to the Unicodes used by the kernel ISO 8859-1,
+# DEC VT, and IBM CP 437 tables.
+#
+# --------------------------------
+#
+# Basic IBM dingbats, some of which will never have a purpose clear
+# to mankind
+#
+0x00	U+0000
+0x01	U+263a
+0x02	U+263b
+0x03	U+2665
+0x04	U+2666 U+25c6
+0x05	U+2663
+0x06	U+2660
+0x07	U+2022
+0x08	U+25d8
+0x09	U+25cb
+0x0a	U+25d9
+0x0b	U+2642
+0x0c	U+2640
+0x0d	U+266a
+0x0e	U+266b
+0x0f	U+263c
+0x10	U+25b6 U+25ba
+0x11	U+25c0 U+25c4
+0x12	U+2195
+0x13	U+203c
+0x14	U+00b6
+0x15	U+00a7
+0x16	U+25ac
+0x17	U+21a8
+0x18	U+2191
+0x19	U+2193
+0x1a	U+2192
+0x1b	U+2190
+0x1c	U+221f
+0x1d	U+2194
+0x1e	U+25b2
+0x1f	U+25bc
+#
+# The ASCII range is identity-mapped, but some of the characters also
+# have to act as substitutes, especially the upper-case characters.
+#
+0x20	U+0020
+0x21	U+0021
+0x22	U+0022 U+00a8
+0x23	U+0023
+0x24	U+0024
+0x25	U+0025
+0x26	U+0026
+0x27	U+0027
+0x28	U+0028
+0x29	U+0029
+0x2a	U+002a
+0x2b	U+002b
+0x2c	U+002c U+00b8
+0x2d	U+002d U+00ad
+0x2e	U+002e
+0x2f	U+002f
+0x30	U+0030
+0x31	U+0031
+0x32	U+0032
+0x33	U+0033
+0x34	U+0034
+0x35	U+0035
+0x36	U+0036
+0x37	U+0037
+0x38	U+0038
+0x39	U+0039
+0x3a	U+003a
+0x3b	U+003b
+0x3c	U+003c
+0x3d	U+003d
+0x3e	U+003e
+0x3f	U+003f
+0x40	U+0040
+0x41	U+0041 U+00c0 U+00c1 U+00c2 U+00c3
+0x42	U+0042
+0x43	U+0043 U+00a9
+0x44	U+0044
+0x45	U+0045 U+00c8 U+00ca U+00cb
+0x46	U+0046
+0x47	U+0047
+0x48	U+0048
+0x49	U+0049 U+00cc U+00cd U+00ce U+00cf
+0x4a	U+004a
+0x4b	U+004b U+212a
+0x4c	U+004c
+0x4d	U+004d
+0x4e	U+004e
+0x4f	U+004f U+00d2 U+00d3 U+00d4 U+00d5
+0x50	U+0050
+0x51	U+0051
+0x52	U+0052 U+00ae
+0x53	U+0053
+0x54	U+0054
+0x55	U+0055 U+00d9 U+00da U+00db
+0x56	U+0056
+0x57	U+0057
+0x58	U+0058
+0x59	U+0059 U+00dd
+0x5a	U+005a
+0x5b	U+005b
+0x5c	U+005c
+0x5d	U+005d
+0x5e	U+005e
+0x5f	U+005f U+23bd U+f804
+0x60	U+0060
+0x61	U+0061 U+00e3
+0x62	U+0062
+0x63	U+0063
+0x64	U+0064
+0x65	U+0065
+0x66	U+0066
+0x67	U+0067
+0x68	U+0068
+0x69	U+0069
+0x6a	U+006a
+0x6b	U+006b
+0x6c	U+006c
+0x6d	U+006d
+0x6e	U+006e
+0x6f	U+006f U+00f5
+0x70	U+0070
+0x71	U+0071
+0x72	U+0072
+0x73	U+0073
+0x74	U+0074
+0x75	U+0075
+0x76	U+0076
+0x77	U+0077
+0x78	U+0078 U+00d7
+0x79	U+0079 U+00fd
+0x7a	U+007a
+0x7b	U+007b
+0x7c	U+007c U+00a5
+0x7d	U+007d
+0x7e	U+007e
+#
+# Okay, what on Earth is this one supposed to be used for?
+#
+0x7f	U+2302
+#
+# Non-English characters, mostly lower case letters...
+#
+0x80	U+00c7
+0x81	U+00fc
+0x82	U+00e9
+0x83	U+00e2
+0x84	U+00e4
+0x85	U+00e0
+0x86	U+00e5
+0x87	U+00e7
+0x88	U+00ea
+0x89	U+00eb
+0x8a	U+00e8
+0x8b	U+00ef
+0x8c	U+00ee
+0x8d	U+00ec
+0x8e	U+00c4
+0x8f	U+00c5 U+212b
+0x90	U+00c9
+0x91	U+00e6
+0x92	U+00c6
+0x93	U+00f4
+0x94	U+00f6
+0x95	U+00f2
+0x96	U+00fb
+0x97	U+00f9
+0x98	U+00ff
+0x99	U+00d6
+0x9a	U+00dc
+0x9b	U+00a2
+0x9c	U+00a3
+0x9d	U+00a5
+0x9e	U+20a7
+0x9f	U+0192
+0xa0	U+00e1
+0xa1	U+00ed
+0xa2	U+00f3
+0xa3	U+00fa
+0xa4	U+00f1
+0xa5	U+00d1
+0xa6	U+00aa
+0xa7	U+00ba
+0xa8	U+00bf
+0xa9	U+2310
+0xaa	U+00ac
+0xab	U+00bd
+0xac	U+00bc
+0xad	U+00a1
+0xae	U+00ab
+0xaf	U+00bb
+#
+# Block graphics
+#
+0xb0	U+2591
+0xb1	U+2592
+0xb2	U+2593
+0xb3	U+2502
+0xb4	U+2524
+0xb5	U+2561
+0xb6	U+2562
+0xb7	U+2556
+0xb8	U+2555
+0xb9	U+2563
+0xba	U+2551
+0xbb	U+2557
+0xbc	U+255d
+0xbd	U+255c
+0xbe	U+255b
+0xbf	U+2510
+0xc0	U+2514
+0xc1	U+2534
+0xc2	U+252c
+0xc3	U+251c
+0xc4	U+2500
+0xc5	U+253c
+0xc6	U+255e
+0xc7	U+255f
+0xc8	U+255a
+0xc9	U+2554
+0xca	U+2569
+0xcb	U+2566
+0xcc	U+2560
+0xcd	U+2550
+0xce	U+256c
+0xcf	U+2567
+0xd0	U+2568
+0xd1	U+2564
+0xd2	U+2565
+0xd3	U+2559
+0xd4	U+2558
+0xd5	U+2552
+0xd6	U+2553
+0xd7	U+256b
+0xd8	U+256a
+0xd9	U+2518
+0xda	U+250c
+0xdb	U+2588
+0xdc	U+2584
+0xdd	U+258c
+0xde	U+2590
+0xdf	U+2580
+#
+# Greek letters and mathematical symbols
+#
+0xe0	U+03b1
+0xe1	U+03b2 U+00df
+0xe2	U+0393
+0xe3	U+03c0
+0xe4	U+03a3
+0xe5	U+03c3
+0xe6	U+00b5 U+03bc
+0xe7	U+03c4
+0xe8	U+03a6 U+00d8
+0xe9	U+0398
+0xea	U+03a9 U+2126
+0xeb	U+03b4
+0xec	U+221e
+0xed	U+03c6 U+00f8
+0xee	U+03b5
+0xef	U+2229
+0xf0	U+2261
+0xf1	U+00b1
+0xf2	U+2265
+0xf3	U+2264
+0xf4	U+2320
+0xf5	U+2321
+0xf6	U+00f7
+0xf7	U+2248
+0xf8	U+00b0
+0xf9	U+2219
+0xfa	U+00b7
+0xfb	U+221a
+0xfc	U+207f
+0xfd	U+00b2
+#
+# Square bullet, non-spacing blank
+# Mapping U+fffd to the square bullet means it is the substitution
+# character
+# 
+0xfe	U+25a0 U+fffd
+0xff	U+00a0
diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c
new file mode 100644
index 0000000..6a5337b
--- /dev/null
+++ b/drivers/char/cyclades.c
@@ -0,0 +1,5540 @@
+#undef	BLOCKMOVE
+#define	Z_WAKE
+#undef	Z_EXT_CHARS_IN_BUFFER
+static char rcsid[] =
+"$Revision: 2.3.2.20 $$Date: 2004/02/25 18:14:16 $";
+
+/*
+ *  linux/drivers/char/cyclades.c
+ *
+ * This file contains the driver for the Cyclades async multiport
+ * serial boards.
+ *
+ * Initially written by Randolph Bentson <bentson@grieg.seaslug.org>.
+ * Modified and maintained by Marcio Saito <marcio@cyclades.com>.
+ * Currently maintained by Cyclades team <async@cyclades.com>.
+ *
+ * For Technical support and installation problems, please send e-mail
+ * to support@cyclades.com.
+ *
+ * Much of the design and some of the code came from serial.c
+ * which was copyright (C) 1991, 1992  Linus Torvalds.  It was
+ * extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92,
+ * and then fixed as suggested by Michael K. Johnson 12/12/92.
+ *
+ * This version supports shared IRQ's (only for PCI boards).
+ *
+ * $Log: cyclades.c,v $
+ * Prevent users from opening non-existing Z ports.
+ *
+ * Revision 2.3.2.8   2000/07/06 18:14:16 ivan
+ * Fixed the PCI detection function to work properly on Alpha systems.
+ * Implemented support for TIOCSERGETLSR ioctl.
+ * Implemented full support for non-standard baud rates.
+ *
+ * Revision 2.3.2.7   2000/06/01 18:26:34 ivan
+ * Request PLX I/O region, although driver doesn't use it, to avoid
+ * problems with other drivers accessing it.
+ * Removed count for on-board buffer characters in cy_chars_in_buffer
+ * (Cyclades-Z only).
+ *
+ * Revision 2.3.2.6   2000/05/05 13:56:05 ivan
+ * Driver now reports physical instead of virtual memory addresses.
+ * Masks were added to some Cyclades-Z read accesses.
+ * Implemented workaround for PLX9050 bug that would cause a system lockup
+ * in certain systems, depending on the MMIO addresses allocated to the
+ * board.
+ * Changed the Tx interrupt programming in the CD1400 chips to boost up
+ * performance (Cyclom-Y only).
+ * Code is now compliant with the new module interface (module_[init|exit]).
+ * Make use of the PCI helper functions to access PCI resources.
+ * Did some code "housekeeping".
+ *
+ * Revision 2.3.2.5   2000/01/19 14:35:33 ivan
+ * Fixed bug in cy_set_termios on CRTSCTS flag turnoff.
+ *
+ * Revision 2.3.2.4   2000/01/17 09:19:40 ivan
+ * Fixed SMP locking in Cyclom-Y interrupt handler.
+ *
+ * Revision 2.3.2.3   1999/12/28 12:11:39 ivan
+ * Added a new cyclades_card field called nports to allow the driver to
+ * know the exact number of ports found by the Z firmware after its load;
+ * RX buffer contention prevention logic on interrupt op mode revisited
+ * (Cyclades-Z only);
+ * Revisited printk's for Z debug;
+ * Driver now makes sure that the constant SERIAL_XMIT_SIZE is defined;
+ *
+ * Revision 2.3.2.2   1999/10/01 11:27:43 ivan
+ * Fixed bug in cyz_poll that would make all ports but port 0 
+ * unable to transmit/receive data (Cyclades-Z only);
+ * Implemented logic to prevent the RX buffer from being stuck with data
+ * due to a driver / firmware race condition in interrupt op mode
+ * (Cyclades-Z only);
+ * Fixed bug in block_til_ready logic that would lead to a system crash;
+ * Revisited cy_close spinlock usage;
+ *
+ * Revision 2.3.2.1   1999/09/28 11:01:22 ivan
+ * Revisited CONFIG_PCI conditional compilation for PCI board support;
+ * Implemented TIOCGICOUNT and TIOCMIWAIT ioctl support;
+ * _Major_ cleanup on the Cyclades-Z interrupt support code / logic;
+ * Removed CTS handling from the driver -- this is now completely handled
+ * by the firmware (Cyclades-Z only);
+ * Flush RX on-board buffers on a port open (Cyclades-Z only);
+ * Fixed handling of ASYNC_SPD_* TTY flags;
+ * Module unload now unmaps all memory area allocated by ioremap;
+ *
+ * Revision 2.3.1.1   1999/07/15 16:45:53 ivan
+ * Removed CY_PROC conditional compilation;
+ * Implemented SMP-awareness for the driver;
+ * Implemented a new ISA IRQ autoprobe that uses the irq_probe_[on|off] 
+ * functions;
+ * The driver now accepts memory addresses (maddr=0xMMMMM) and IRQs
+ * (irq=NN) as parameters (only for ISA boards);
+ * Fixed bug in set_line_char that would prevent the Cyclades-Z 
+ * ports from being configured at speeds above 115.2Kbps;
+ * Fixed bug in cy_set_termios that would prevent XON/XOFF flow control
+ * switching from working properly;
+ * The driver now only prints IRQ info for the Cyclades-Z if it's 
+ * configured to work in interrupt mode;
+ *
+ * Revision 2.2.2.3   1999/06/28 11:13:29 ivan
+ * Added support for interrupt mode operation for the Z cards;
+ * Removed the driver inactivity control for the Z;
+ * Added a missing MOD_DEC_USE_COUNT in the cy_open function for when 
+ * the Z firmware is not loaded yet;
+ * Replaced the "manual" Z Tx flush buffer by a call to a FW command of 
+ * same functionality;
+ * Implemented workaround for IRQ setting loss on the PCI configuration 
+ * registers after a PCI bridge EEPROM reload (affects PLX9060 only);
+ *
+ * Revision 2.2.2.2  1999/05/14 17:18:15 ivan
+ * /proc entry location changed to /proc/tty/driver/cyclades;
+ * Added support to shared IRQ's (only for PCI boards);
+ * Added support for Cobalt Qube2 systems;
+ * IRQ [de]allocation scheme revisited;
+ * BREAK implementation changed in order to make use of the 'break_ctl'
+ * TTY facility;
+ * Fixed typo in TTY structure field 'driver_name';
+ * Included a PCI bridge reset and EEPROM reload in the board 
+ * initialization code (for both Y and Z series).
+ *
+ * Revision 2.2.2.1  1999/04/08 16:17:43 ivan
+ * Fixed a bug in cy_wait_until_sent that was preventing the port to be 
+ * closed properly after a SIGINT;
+ * Module usage counter scheme revisited;
+ * Added support to the upcoming Y PCI boards (i.e., support to additional
+ * PCI Device ID's).
+ * 
+ * Revision 2.2.1.10 1999/01/20 16:14:29 ivan
+ * Removed all unnecessary page-alignement operations in ioremap calls
+ * (ioremap is currently safe for these operations).
+ *
+ * Revision 2.2.1.9  1998/12/30 18:18:30 ivan
+ * Changed access to PLX PCI bridge registers from I/O to MMIO, in 
+ * order to make PLX9050-based boards work with certain motherboards.
+ *
+ * Revision 2.2.1.8  1998/11/13 12:46:20 ivan
+ * cy_close function now resets (correctly) the tty->closing flag;
+ * JIFFIES_DIFF macro fixed.
+ *
+ * Revision 2.2.1.7  1998/09/03 12:07:28 ivan
+ * Fixed bug in cy_close function, which was not informing HW of
+ * which port should have the reception disabled before doing so;
+ * fixed Cyclom-8YoP hardware detection bug.
+ *
+ * Revision 2.2.1.6  1998/08/20 17:15:39 ivan
+ * Fixed bug in cy_close function, which causes malfunction
+ * of one of the first 4 ports when a higher port is closed
+ * (Cyclom-Y only).
+ *
+ * Revision 2.2.1.5  1998/08/10 18:10:28 ivan
+ * Fixed Cyclom-4Yo hardware detection bug.
+ *
+ * Revision 2.2.1.4  1998/08/04 11:02:50 ivan
+ * /proc/cyclades implementation with great collaboration of 
+ * Marc Lewis <marc@blarg.net>;
+ * cyy_interrupt was changed to avoid occurrence of kernel oopses
+ * during PPP operation.
+ *
+ * Revision 2.2.1.3  1998/06/01 12:09:10 ivan
+ * General code review in order to comply with 2.1 kernel standards;
+ * data loss prevention for slow devices revisited (cy_wait_until_sent
+ * was created);
+ * removed conditional compilation for new/old PCI structure support 
+ * (now the driver only supports the new PCI structure).
+ *
+ * Revision 2.2.1.1  1998/03/19 16:43:12 ivan
+ * added conditional compilation for new/old PCI structure support;
+ * removed kernel series (2.0.x / 2.1.x) conditional compilation.
+ *
+ * Revision 2.1.1.3  1998/03/16 18:01:12 ivan
+ * cleaned up the data loss fix;
+ * fixed XON/XOFF handling once more (Cyclades-Z);
+ * general review of the driver routines;
+ * introduction of a mechanism to prevent data loss with slow 
+ * printers, by forcing a delay before closing the port.
+ *
+ * Revision 2.1.1.2  1998/02/17 16:50:00 ivan
+ * fixed detection/handling of new CD1400 in Ye boards;
+ * fixed XON/XOFF handling (Cyclades-Z);
+ * fixed data loss caused by a premature port close;
+ * introduction of a flag that holds the CD1400 version ID per port
+ * (used by the CYGETCD1400VER new ioctl).
+ *
+ * Revision 2.1.1.1  1997/12/03 17:31:19 ivan
+ * Code review for the module cleanup routine;
+ * fixed RTS and DTR status report for new CD1400's in get_modem_info;
+ * includes anonymous changes regarding signal_pending.
+ * 
+ * Revision 2.1  1997/11/01 17:42:41 ivan
+ * Changes in the driver to support Alpha systems (except 8Zo V_1);
+ * BREAK fix for the Cyclades-Z boards;
+ * driver inactivity control by FW implemented;
+ * introduction of flag that allows driver to take advantage of 
+ * a special CD1400 feature related to HW flow control;
+ * added support for the CD1400  rev. J (Cyclom-Y boards);
+ * introduction of ioctls to:
+ *  - control the rtsdtr_inv flag (Cyclom-Y);
+ *  - control the rflow flag (Cyclom-Y);
+ *  - adjust the polling interval (Cyclades-Z);
+ *
+ * Revision 1.36.4.33  1997/06/27 19:00:00  ivan
+ * Fixes related to kernel version conditional 
+ * compilation.
+ *  
+ * Revision 1.36.4.32  1997/06/14 19:30:00  ivan
+ * Compatibility issues between kernels 2.0.x and 
+ * 2.1.x (mainly related to clear_bit function).
+ *  
+ * Revision 1.36.4.31  1997/06/03 15:30:00  ivan
+ * Changes to define the memory window according to the 
+ * board type.
+ *  
+ * Revision 1.36.4.30  1997/05/16 15:30:00  daniel
+ * Changes to support new cycladesZ boards.
+ *
+ * Revision 1.36.4.29  1997/05/12 11:30:00  daniel
+ * Merge of Bentson's and Daniel's version 1.36.4.28.
+ * Corrects bug in cy_detect_pci: check if there are more
+ * ports than the number of static structs allocated.
+ * Warning message during initialization if this driver is
+ * used with the new generation of cycladesZ boards.  Those
+ * will be supported only in next release of the driver.
+ * Corrects bug in cy_detect_pci and cy_detect_isa that
+ * returned wrong number of VALID boards, when a cyclomY
+ * was found with no serial modules connected.
+ * Changes to use current (2.1.x) kernel subroutine names
+ * and created macros for compilation with 2.0.x kernel,
+ * instead of the other way around.
+ *
+ * Revision 1.36.4.28  1997/05/?? ??:00:00  bentson
+ * Change queue_task_irq_off to queue_task_irq.
+ * The inline function queue_task_irq_off (tqueue.h)
+ * was removed from latest releases of 2.1.x kernel.
+ * Use of macro __init to mark the initialization
+ * routines, so memory can be reused.
+ * Also incorporate implementation of critical region
+ * in function cleanup_module() created by anonymous
+ * linuxer.
+ *
+ * Revision 1.36.4.28  1997/04/25 16:00:00  daniel
+ * Change to support new firmware that solves DCD problem:
+ * application could fail to receive SIGHUP signal when DCD
+ * varying too fast.
+ *
+ * Revision 1.36.4.27  1997/03/26 10:30:00  daniel
+ * Changed for support linux versions 2.1.X.
+ * Backward compatible with linux versions 2.0.X.
+ * Corrected illegal use of filler field in
+ * CH_CTRL struct.
+ * Deleted some debug messages.
+ *
+ * Revision 1.36.4.26  1997/02/27 12:00:00  daniel
+ * Included check for NULL tty pointer in cyz_poll.
+ *
+ * Revision 1.36.4.25  1997/02/26 16:28:30  bentson
+ * Bill Foster at Blarg! Online services noticed that
+ * some of the switch elements of -Z modem control
+ * lacked a closing "break;"
+ *
+ * Revision 1.36.4.24  1997/02/24 11:00:00  daniel
+ * Changed low water threshold for buffer xmit_buf
+ *
+ * Revision 1.36.4.23  1996/12/02 21:50:16  bentson
+ * Marcio provided fix to modem status fetch for -Z
+ *
+ * Revision 1.36.4.22  1996/10/28 22:41:17  bentson
+ * improve mapping of -Z control page (thanks to Steve
+ * Price <stevep@fa.tdktca.com> for help on this)
+ *
+ * Revision 1.36.4.21  1996/09/10 17:00:10  bentson
+ * shift from CPU-bound to memcopy in cyz_polling operation
+ *
+ * Revision 1.36.4.20  1996/09/09 18:30:32  Bentson
+ * Added support to set and report higher speeds.
+ *
+ * Revision 1.36.4.19c  1996/08/09 10:00:00  Marcio Saito
+ * Some fixes in the HW flow control for the BETA release.
+ * Don't try to register the IRQ.
+ *
+ * Revision 1.36.4.19  1996/08/08 16:23:18  Bentson
+ * make sure "cyc" appears in all kernel messages; all soft interrupts
+ * handled by same routine; recognize out-of-band reception; comment
+ * out some diagnostic messages; leave RTS/CTS flow control to hardware;
+ * fix race condition in -Z buffer management; only -Y needs to explictly
+ * flush chars; tidy up some startup messages;
+ *
+ * Revision 1.36.4.18  1996/07/25 18:57:31  bentson
+ * shift MOD_INC_USE_COUNT location to match
+ * serial.c; purge some diagnostic messages;
+ *
+ * Revision 1.36.4.17  1996/07/25 18:01:08  bentson
+ * enable modem status messages and fetch & process them; note
+ * time of last activity type for each port; set_line_char now
+ * supports more than line 0 and treats 0 baud correctly;
+ * get_modem_info senses rs_status;
+ *
+ * Revision 1.36.4.16  1996/07/20 08:43:15  bentson
+ * barely works--now's time to turn on
+ * more features 'til it breaks
+ *
+ * Revision 1.36.4.15  1996/07/19 22:30:06  bentson
+ * check more -Z board status; shorten boot message
+ *
+ * Revision 1.36.4.14  1996/07/19 22:20:37  bentson
+ * fix reference to ch_ctrl in startup; verify return
+ * values from cyz_issue_cmd and cyz_update_channel;
+ * more stuff to get modem control correct;
+ *
+ * Revision 1.36.4.13  1996/07/11 19:53:33  bentson
+ * more -Z stuff folded in; re-order changes to put -Z stuff
+ * after -Y stuff (to make changes clearer)
+ *
+ * Revision 1.36.4.12  1996/07/11 15:40:55  bentson
+ * Add code to poll Cyclades-Z.  Add code to get & set RS-232 control.
+ * Add code to send break.  Clear firmware ID word at startup (so
+ * that other code won't talk to inactive board).
+ *
+ * Revision 1.36.4.11  1996/07/09 05:28:29  bentson
+ * add code for -Z in set_line_char
+ *
+ * Revision 1.36.4.10  1996/07/08 19:28:37  bentson
+ * fold more -Z stuff (or in some cases, error messages)
+ * into driver; add text to "don't know what to do" messages.
+ *
+ * Revision 1.36.4.9  1996/07/08 18:38:38  bentson
+ * moved compile-time flags near top of file; cosmetic changes
+ * to narrow text (to allow 2-up printing); changed many declarations
+ * to "static" to limit external symbols; shuffled code order to
+ * coalesce -Y and -Z specific code, also to put internal functions
+ * in order of tty_driver structure; added code to recognize -Z
+ * ports (and for moment, do nothing or report error); add cy_startup
+ * to parse boot command line for extra base addresses for ISA probes;
+ *
+ * Revision 1.36.4.8  1996/06/25 17:40:19  bentson
+ * reorder some code, fix types of some vars (int vs. long),
+ * add cy_setup to support user declared ISA addresses
+ *
+ * Revision 1.36.4.7  1996/06/21 23:06:18  bentson
+ * dump ioctl based firmware load (it's now a user level
+ * program); ensure uninitialzed ports cannot be used
+ *
+ * Revision 1.36.4.6  1996/06/20 23:17:19  bentson
+ * rename vars and restructure some code
+ *
+ * Revision 1.36.4.5  1996/06/14 15:09:44  bentson
+ * get right status back after boot load
+ *
+ * Revision 1.36.4.4  1996/06/13 19:51:44  bentson
+ * successfully loads firmware
+ *
+ * Revision 1.36.4.3  1996/06/13 06:08:33  bentson
+ * add more of the code for the boot/load ioctls
+ *
+ * Revision 1.36.4.2  1996/06/11 21:00:51  bentson
+ * start to add Z functionality--starting with ioctl
+ * for loading firmware
+ *
+ * Revision 1.36.4.1  1996/06/10 18:03:02  bentson
+ * added code to recognize Z/PCI card at initialization; report
+ * presence, but card is not initialized (because firmware needs
+ * to be loaded)
+ *
+ * Revision 1.36.3.8  1996/06/07 16:29:00  bentson
+ * starting minor number at zero; added missing verify_area
+ * as noted by Heiko Eissfeldt <heiko@colossus.escape.de>
+ *
+ * Revision 1.36.3.7  1996/04/19 21:06:18  bentson
+ * remove unneeded boot message & fix CLOCAL hardware flow
+ * control (Miquel van Smoorenburg <miquels@Q.cistron.nl>);
+ * remove unused diagnostic statements; minor 0 is first;
+ *
+ * Revision 1.36.3.6  1996/03/13 13:21:17  marcio
+ * The kernel function vremap (available only in later 1.3.xx kernels)
+ * allows the access to memory addresses above the RAM. This revision
+ * of the driver supports PCI boards below 1Mb (device id 0x100) and
+ * above 1Mb (device id 0x101).
+ *
+ * Revision 1.36.3.5  1996/03/07 15:20:17  bentson
+ * Some global changes to interrupt handling spilled into
+ * this driver--mostly unused arguments in system function
+ * calls.  Also added change by Marcio Saito which should
+ * reduce lost interrupts at startup by fast processors.
+ *
+ * Revision 1.36.3.4  1995/11/13  20:45:10  bentson
+ * Changes by Corey Minyard <minyard@wf-rch.cirr.com> distributed
+ * in 1.3.41 kernel to remove a possible race condition, extend
+ * some error messages, and let the driver run as a loadable module
+ * Change by Alan Wendt <alan@ez0.ezlink.com> to remove a
+ * possible race condition.
+ * Change by Marcio Saito <marcio@cyclades.com> to fix PCI addressing.
+ *
+ * Revision 1.36.3.3  1995/11/13  19:44:48  bentson
+ * Changes by Linus Torvalds in 1.3.33 kernel distribution
+ * required due to reordering of driver initialization.
+ * Drivers are now initialized *after* memory management.
+ *
+ * Revision 1.36.3.2  1995/09/08  22:07:14  bentson
+ * remove printk from ISR; fix typo
+ *
+ * Revision 1.36.3.1  1995/09/01  12:00:42  marcio
+ * Minor fixes in the PCI board support. PCI function calls in
+ * conditional compilation (CONFIG_PCI). Thanks to Jim Duncan
+ * <duncan@okay.com>. "bad serial count" message removed.
+ *
+ * Revision 1.36.3  1995/08/22  09:19:42  marcio
+ * Cyclom-Y/PCI support added. Changes in the cy_init routine and
+ * board initialization. Changes in the boot messages. The driver
+ * supports up to 4 boards and 64 ports by default.
+ *
+ * Revision 1.36.1.4  1995/03/29  06:14:14  bentson
+ * disambiguate between Cyclom-16Y and Cyclom-32Ye;
+ *
+ * Revision 1.36.1.3  1995/03/23  22:15:35  bentson
+ * add missing break in modem control block in ioctl switch statement
+ * (discovered by Michael Edward Chastain <mec@jobe.shell.portal.com>);
+ *
+ * Revision 1.36.1.2  1995/03/22  19:16:22  bentson
+ * make sure CTS flow control is set as soon as possible (thanks
+ * to note from David Lambert <lambert@chesapeake.rps.slb.com>);
+ *
+ * Revision 1.36.1.1  1995/03/13  15:44:43  bentson
+ * initialize defaults for receive threshold and stale data timeout;
+ * cosmetic changes;
+ *
+ * Revision 1.36  1995/03/10  23:33:53  bentson
+ * added support of chips 4-7 in 32 port Cyclom-Ye;
+ * fix cy_interrupt pointer dereference problem
+ * (Joe Portman <baron@aa.net>);
+ * give better error response if open is attempted on non-existent port
+ * (Zachariah Vaum <jchryslr@netcom.com>);
+ * correct command timeout (Kenneth Lerman <lerman@@seltd.newnet.com>);
+ * conditional compilation for -16Y on systems with fast, noisy bus;
+ * comment out diagnostic print function;
+ * cleaned up table of base addresses;
+ * set receiver time-out period register to correct value,
+ * set receive threshold to better default values,
+ * set chip timer to more accurate 200 Hz ticking,
+ * add code to monitor and modify receive parameters
+ * (Rik Faith <faith@cs.unc.edu> Nick Simicich
+ * <njs@scifi.emi.net>);
+ *
+ * Revision 1.35  1994/12/16  13:54:18  steffen
+ * additional patch by Marcio Saito for board detection
+ * Accidently left out in 1.34
+ *
+ * Revision 1.34  1994/12/10  12:37:12  steffen
+ * This is the corrected version as suggested by Marcio Saito
+ *
+ * Revision 1.33  1994/12/01  22:41:18  bentson
+ * add hooks to support more high speeds directly; add tytso
+ * patch regarding CLOCAL wakeups
+ *
+ * Revision 1.32  1994/11/23  19:50:04  bentson
+ * allow direct kernel control of higher signalling rates;
+ * look for cards at additional locations
+ *
+ * Revision 1.31  1994/11/16  04:33:28  bentson
+ * ANOTHER fix from Corey Minyard, minyard@wf-rch.cirr.com--
+ * a problem in chars_in_buffer has been resolved by some
+ * small changes;  this should yield smoother output
+ *
+ * Revision 1.30  1994/11/16  04:28:05  bentson
+ * Fix from Corey Minyard, Internet: minyard@metronet.com,
+ * UUCP: minyard@wf-rch.cirr.com, WORK: minyardbnr.ca, to
+ * cy_hangup that appears to clear up much (all?) of the
+ * DTR glitches; also he's added/cleaned-up diagnostic messages
+ *
+ * Revision 1.29  1994/11/16  04:16:07  bentson
+ * add change proposed by Ralph Sims, ralphs@halcyon.com, to
+ * operate higher speeds in same way as other serial ports;
+ * add more serial ports (for up to two 16-port muxes).
+ *
+ * Revision 1.28  1994/11/04  00:13:16  root
+ * turn off diagnostic messages
+ *
+ * Revision 1.27  1994/11/03  23:46:37  root
+ * bunch of changes to bring driver into greater conformance
+ * with the serial.c driver (looking for missed fixes)
+ *
+ * Revision 1.26  1994/11/03  22:40:36  root
+ * automatic interrupt probing fixed.
+ *
+ * Revision 1.25  1994/11/03  20:17:02  root
+ * start to implement auto-irq
+ *
+ * Revision 1.24  1994/11/03  18:01:55  root
+ * still working on modem signals--trying not to drop DTR
+ * during the getty/login processes
+ *
+ * Revision 1.23  1994/11/03  17:51:36  root
+ * extend baud rate support; set receive threshold as function
+ * of baud rate; fix some problems with RTS/CTS;
+ *
+ * Revision 1.22  1994/11/02  18:05:35  root
+ * changed arguments to udelay to type long to get
+ * delays to be of correct duration
+ *
+ * Revision 1.21  1994/11/02  17:37:30  root
+ * employ udelay (after calibrating loops_per_second earlier
+ * in init/main.c) instead of using home-grown delay routines
+ *
+ * Revision 1.20  1994/11/02  03:11:38  root
+ * cy_chars_in_buffer forces a return value of 0 to let
+ * login work (don't know why it does); some functions
+ * that were returning EFAULT, now executes the code;
+ * more work on deciding when to disable xmit interrupts;
+ *
+ * Revision 1.19  1994/11/01  20:10:14  root
+ * define routine to start transmission interrupts (by enabling
+ * transmit interrupts); directly enable/disable modem interrupts;
+ *
+ * Revision 1.18  1994/11/01  18:40:45  bentson
+ * Don't always enable transmit interrupts in startup; interrupt on
+ * TxMpty instead of TxRdy to help characters get out before shutdown;
+ * restructure xmit interrupt to check for chars first and quit if
+ * none are ready to go; modem status (MXVRx) is upright, _not_ inverted
+ * (to my view);
+ *
+ * Revision 1.17  1994/10/30  04:39:45  bentson
+ * rename serial_driver and callout_driver to cy_serial_driver and
+ * cy_callout_driver to avoid linkage interference; initialize
+ * info->type to PORT_CIRRUS; ruggedize paranoia test; elide ->port
+ * from cyclades_port structure; add paranoia check to cy_close;
+ *
+ * Revision 1.16  1994/10/30  01:14:33  bentson
+ * change major numbers; add some _early_ return statements;
+ *
+ * Revision 1.15  1994/10/29  06:43:15  bentson
+ * final tidying up for clean compile;  enable some error reporting
+ *
+ * Revision 1.14  1994/10/28  20:30:22  Bentson
+ * lots of changes to drag the driver towards the new tty_io
+ * structures and operation.  not expected to work, but may
+ * compile cleanly.
+ *
+ * Revision 1.13  1994/07/21  23:08:57  Bentson
+ * add some diagnostic cruft; support 24 lines (for testing
+ * both -8Y and -16Y cards; be more thorough in servicing all
+ * chips during interrupt; add "volatile" a few places to
+ * circumvent compiler optimizations; fix base & offset
+ * computations in block_til_ready (was causing chip 0 to
+ * stop operation)
+ *
+ * Revision 1.12  1994/07/19  16:42:11  Bentson
+ * add some hackery for kernel version 1.1.8; expand
+ * error messages; refine timing for delay loops and
+ * declare loop params volatile
+ *
+ * Revision 1.11  1994/06/11  21:53:10  bentson
+ * get use of save_car right in transmit interrupt service
+ *
+ * Revision 1.10.1.1  1994/06/11  21:31:18  bentson
+ * add some diagnostic printing; try to fix save_car stuff
+ *
+ * Revision 1.10  1994/06/11  20:36:08  bentson
+ * clean up compiler warnings
+ *
+ * Revision 1.9  1994/06/11  19:42:46  bentson
+ * added a bunch of code to support modem signalling
+ *
+ * Revision 1.8  1994/06/11  17:57:07  bentson
+ * recognize break & parity error
+ *
+ * Revision 1.7  1994/06/05  05:51:34  bentson
+ * Reorder baud table to be monotonic; add cli to CP; discard
+ * incoming characters and status if the line isn't open; start to
+ * fold code into cy_throttle; start to port get_serial_info,
+ * set_serial_info, get_modem_info, set_modem_info, and send_break
+ * from serial.c; expand cy_ioctl; relocate and expand config_setup;
+ * get flow control characters from tty struct; invalidate ports w/o
+ * hardware;
+ *
+ * Revision 1.6  1994/05/31  18:42:21  bentson
+ * add a loop-breaker in the interrupt service routine;
+ * note when port is initialized so that it can be shut
+ * down under the right conditions; receive works without
+ * any obvious errors
+ *
+ * Revision 1.5  1994/05/30  00:55:02  bentson
+ * transmit works without obvious errors
+ *
+ * Revision 1.4  1994/05/27  18:46:27  bentson
+ * incorporated more code from lib_y.c; can now print short
+ * strings under interrupt control to port zero; seems to
+ * select ports/channels/lines correctly
+ *
+ * Revision 1.3  1994/05/25  22:12:44  bentson
+ * shifting from multi-port on a card to proper multiplexor
+ * data structures;  added skeletons of most routines
+ *
+ * Revision 1.2  1994/05/19  13:21:43  bentson
+ * start to crib from other sources
+ *
+ */
+
+/* If you need to install more boards than NR_CARDS, change the constant
+   in the definition below. No other change is necessary to support up to
+   eight boards. Beyond that you'll have to extend cy_isa_addresses. */
+
+#define NR_CARDS        4
+
+/*
+   If the total number of ports is larger than NR_PORTS, change this
+   constant in the definition below. No other change is necessary to
+   support more boards/ports. */
+
+#define NR_PORTS        256
+
+#define ZE_V1_NPORTS	64
+#define ZO_V1	0
+#define ZO_V2	1
+#define ZE_V1	2
+
+#define	SERIAL_PARANOIA_CHECK
+#undef	CY_DEBUG_OPEN
+#undef	CY_DEBUG_THROTTLE
+#undef	CY_DEBUG_OTHER
+#undef	CY_DEBUG_IO
+#undef	CY_DEBUG_COUNT
+#undef	CY_DEBUG_DTR
+#undef	CY_DEBUG_WAIT_UNTIL_SENT
+#undef	CY_DEBUG_INTERRUPTS
+#undef	CY_16Y_HACK
+#undef	CY_ENABLE_MONITORING
+#undef	CY_PCI_DEBUG
+
+#if 0
+#define PAUSE __asm__("nop");
+#else
+#define PAUSE ;
+#endif
+
+/*
+ * Include section 
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/cyclades.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+
+#define	CY_LOCK(info,flags)					\
+		do {						\
+		spin_lock_irqsave(&cy_card[info->card].card_lock, flags); \
+		} while (0)
+		
+#define	CY_UNLOCK(info,flags)					\
+		do {						\
+		spin_unlock_irqrestore(&cy_card[info->card].card_lock, flags); \
+		} while (0)
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+
+static void cy_throttle (struct tty_struct *tty);
+static void cy_send_xchar (struct tty_struct *tty, char ch);
+
+#define IS_CYC_Z(card) ((card).num_chips == -1)
+
+#define Z_FPGA_CHECK(card) \
+    ((cy_readl(&((struct RUNTIME_9060 __iomem *) \
+		 ((card).ctl_addr))->init_ctrl) & (1<<17)) != 0)
+
+#define ISZLOADED(card)	(((ZO_V1==cy_readl(&((struct RUNTIME_9060 __iomem *) \
+			((card).ctl_addr))->mail_box_0)) || \
+			Z_FPGA_CHECK(card)) && \
+			(ZFIRM_ID==cy_readl(&((struct FIRM_ID __iomem *) \
+			((card).base_addr+ID_ADDRESS))->signature)))
+
+#ifndef SERIAL_XMIT_SIZE
+#define	SERIAL_XMIT_SIZE	(min(PAGE_SIZE, 4096))
+#endif
+#define WAKEUP_CHARS		256
+
+#define STD_COM_FLAGS (0)
+
+#define	JIFFIES_DIFF(n, j)	((j) - (n))
+
+static struct tty_driver *cy_serial_driver;
+
+#ifdef CONFIG_ISA
+/* This is the address lookup table. The driver will probe for
+   Cyclom-Y/ISA boards at all addresses in here. If you want the
+   driver to probe addresses at a different address, add it to
+   this table.  If the driver is probing some other board and
+   causing problems, remove the offending address from this table.
+   The cy_setup function extracts additional addresses from the
+   boot options line.  The form is "cyclades=address,address..."
+*/
+
+static unsigned int cy_isa_addresses[] = {
+        0xD0000,
+        0xD2000,
+        0xD4000,
+        0xD6000,
+        0xD8000,
+        0xDA000,
+        0xDC000,
+        0xDE000,
+        0,0,0,0,0,0,0,0
+};
+#define NR_ISA_ADDRS (sizeof(cy_isa_addresses)/sizeof(unsigned char*))
+
+#ifdef MODULE
+static long maddr[NR_CARDS] = { 0, };
+static int irq[NR_CARDS]  = { 0, };
+
+module_param_array(maddr, long, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+#endif
+
+#endif /* CONFIG_ISA */
+
+/* This is the per-card data structure containing address, irq, number of
+   channels, etc. This driver supports a maximum of NR_CARDS cards.
+*/
+static struct cyclades_card cy_card[NR_CARDS];
+
+/* This is the per-channel data structure containing pointers, flags
+ and variables for the port. This driver supports a maximum of NR_PORTS.
+*/
+static struct cyclades_port cy_port[NR_PORTS];
+
+static int cy_next_channel; /* next minor available */
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write.  We need to
+ * lock it in case the copy_from_user blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open.  This buffer is
+ * allocated when the first cy_open occurs.
+ */
+static unsigned char *tmp_buf;
+
+/*
+ * This is used to look up the divisor speeds and the timeouts
+ * We're normally limited to 15 distinct baud rates.  The extra
+ * are accessed via settings in info->flags.
+ *      0,     1,     2,     3,     4,     5,     6,     7,     8,     9,
+ *     10,    11,    12,    13,    14,    15,    16,    17,    18,    19,
+ *                                               HI            VHI
+ *     20
+ */
+static int baud_table[] = {
+       0,    50,    75,   110,   134,   150,   200,   300,   600,  1200,
+    1800,  2400,  4800,  9600, 19200, 38400, 57600, 76800,115200,150000,
+  230400,     0};
+
+static char baud_co_25[] = {  /* 25 MHz clock option table */
+    /* value =>    00    01   02    03    04 */
+    /* divide by    8    32   128   512  2048 */
+    0x00,  0x04,  0x04,  0x04,  0x04,  0x04,  0x03,  0x03,  0x03,  0x02,
+    0x02,  0x02,  0x01,  0x01,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00};
+
+static char baud_bpr_25[] = {  /* 25 MHz baud rate period table */
+    0x00,  0xf5,  0xa3,  0x6f,  0x5c,  0x51,  0xf5,  0xa3,  0x51,  0xa3,
+    0x6d,  0x51,  0xa3,  0x51,  0xa3,  0x51,  0x36,  0x29,  0x1b,  0x15};
+
+static char baud_co_60[] = {  /* 60 MHz clock option table (CD1400 J) */
+    /* value =>    00    01   02    03    04 */
+    /* divide by    8    32   128   512  2048 */
+    0x00,  0x00,  0x00,  0x04,  0x04,  0x04,  0x04,  0x04,  0x03,  0x03,
+    0x03,  0x02,  0x02,  0x01,  0x01,  0x00,  0x00,  0x00,  0x00,  0x00,
+    0x00};
+
+static char baud_bpr_60[] = {  /* 60 MHz baud rate period table (CD1400 J) */
+    0x00,  0x82,  0x21,  0xff,  0xdb,  0xc3,  0x92,  0x62,  0xc3,  0x62,
+    0x41,  0xc3,  0x62,  0xc3,  0x62,  0xc3,  0x82,  0x62,  0x41,  0x32,
+    0x21};
+
+static char baud_cor3[] = {  /* receive threshold */
+    0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,
+    0x0a,  0x0a,  0x0a,  0x09,  0x09,  0x08,  0x08,  0x08,  0x08,  0x07,
+    0x07};
+
+/*
+ * The Cyclades driver implements HW flow control as any serial driver.
+ * The cyclades_port structure member rflow and the vector rflow_thr 
+ * allows us to take advantage of a special feature in the CD1400 to avoid 
+ * data loss even when the system interrupt latency is too high. These flags 
+ * are to be used only with very special applications. Setting these flags 
+ * requires the use of a special cable (DTR and RTS reversed). In the new 
+ * CD1400-based boards (rev. 6.00 or later), there is no need for special 
+ * cables.
+ */
+
+static char rflow_thr[] = {  /* rflow threshold */
+    0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
+    0x00,  0x00,  0x00,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,
+    0x0a};
+
+/*  The Cyclom-Ye has placed the sequential chips in non-sequential
+ *  address order.  This look-up table overcomes that problem.
+ */
+static int cy_chip_offset [] =
+    { 0x0000,
+      0x0400,
+      0x0800,
+      0x0C00,
+      0x0200,
+      0x0600,
+      0x0A00,
+      0x0E00
+    };
+
+/* PCI related definitions */
+
+static unsigned short	cy_pci_nboard;
+static unsigned short	cy_isa_nboard;
+static unsigned short	cy_nboard;
+#ifdef CONFIG_PCI
+static unsigned short	cy_pci_dev_id[] = {
+			    PCI_DEVICE_ID_CYCLOM_Y_Lo,	/* PCI < 1Mb */
+			    PCI_DEVICE_ID_CYCLOM_Y_Hi,	/* PCI > 1Mb */
+			    PCI_DEVICE_ID_CYCLOM_4Y_Lo,	/* 4Y PCI < 1Mb */
+			    PCI_DEVICE_ID_CYCLOM_4Y_Hi,	/* 4Y PCI > 1Mb */
+			    PCI_DEVICE_ID_CYCLOM_8Y_Lo,	/* 8Y PCI < 1Mb */
+			    PCI_DEVICE_ID_CYCLOM_8Y_Hi,	/* 8Y PCI > 1Mb */
+			    PCI_DEVICE_ID_CYCLOM_Z_Lo,	/* Z PCI < 1Mb */
+			    PCI_DEVICE_ID_CYCLOM_Z_Hi,	/* Z PCI > 1Mb */
+			    0				/* end of table */
+			};
+#endif
+
+static void cy_start(struct tty_struct *);
+static void set_line_char(struct cyclades_port *);
+static int cyz_issue_cmd(struct cyclades_card *, uclong, ucchar, uclong);
+#ifdef CONFIG_ISA
+static unsigned detect_isa_irq(void __iomem *);
+#endif /* CONFIG_ISA */
+
+static int cyclades_get_proc_info(char *, char **, off_t , int , int *, void *);
+
+#ifndef CONFIG_CYZ_INTR
+static void cyz_poll(unsigned long);
+
+/* The Cyclades-Z polling cycle is defined by this variable */
+static long cyz_polling_cycle = CZ_DEF_POLL;
+
+static int cyz_timeron = 0;
+static struct timer_list cyz_timerlist = TIMER_INITIALIZER(cyz_poll, 0, 0);
+
+#else /* CONFIG_CYZ_INTR */
+static void cyz_rx_restart(unsigned long);
+static struct timer_list cyz_rx_full_timer[NR_PORTS];
+#endif /* CONFIG_CYZ_INTR */
+
+static inline int
+serial_paranoia_check(struct cyclades_port *info,
+                        char *name, const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+    static const char *badmagic =
+        "cyc Warning: bad magic number for serial struct (%s) in %s\n";
+    static const char *badinfo =
+        "cyc Warning: null cyclades_port for (%s) in %s\n";
+    static const char *badrange =
+        "cyc Warning: cyclades_port out of range for (%s) in %s\n";
+
+    if (!info) {
+        printk(badinfo, name, routine);
+        return 1;
+    }
+
+    if( (long)info < (long)(&cy_port[0])
+    || (long)(&cy_port[NR_PORTS]) < (long)info ){
+        printk(badrange, name, routine);
+        return 1;
+    }
+
+    if (info->magic != CYCLADES_MAGIC) {
+        printk(badmagic, name, routine);
+        return 1;
+    }
+#endif
+        return 0;
+} /* serial_paranoia_check */
+
+/*
+ * This routine is used by the interrupt handler to schedule
+ * processing in the software interrupt portion of the driver
+ * (also known as the "bottom half").  This can be called any
+ * number of times for any channel without harm.
+ */
+static inline void
+cy_sched_event(struct cyclades_port *info, int event)
+{
+    info->event |= 1 << event; /* remember what kind of event and who */
+    schedule_work(&info->tqueue);
+} /* cy_sched_event */
+
+
+/*
+ * This routine is used to handle the "bottom half" processing for the
+ * serial driver, known also the "software interrupt" processing.
+ * This processing is done at the kernel interrupt level, after the
+ * cy#/_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON.  This
+ * is where time-consuming activities which can not be done in the
+ * interrupt driver proper are done; the interrupt driver schedules
+ * them using cy_sched_event(), and they get done here.
+ *
+ * This is done through one level of indirection--the task queue.
+ * When a hardware interrupt service routine wants service by the
+ * driver's bottom half, it enqueues the appropriate tq_struct (one
+ * per port) to the keventd work queue and sets a request flag
+ * that the work queue be processed.
+ *
+ * Although this may seem unwieldy, it gives the system a way to
+ * pass an argument (in this case the pointer to the cyclades_port
+ * structure) to the bottom half of the driver.  Previous kernels
+ * had to poll every port to see if that port needed servicing.
+ */
+static void
+do_softint(void *private_)
+{
+  struct cyclades_port *info = (struct cyclades_port *) private_;
+  struct tty_struct    *tty;
+
+    tty = info->tty;
+    if (!tty)
+        return;
+
+    if (test_and_clear_bit(Cy_EVENT_HANGUP, &info->event)) {
+        tty_hangup(info->tty);
+        wake_up_interruptible(&info->open_wait);
+        info->flags &= ~ASYNC_NORMAL_ACTIVE;
+    }
+    if (test_and_clear_bit(Cy_EVENT_OPEN_WAKEUP, &info->event)) {
+        wake_up_interruptible(&info->open_wait);
+    }
+#ifdef CONFIG_CYZ_INTR
+    if (test_and_clear_bit(Cy_EVENT_Z_RX_FULL, &info->event)) {
+	if (cyz_rx_full_timer[info->line].function == NULL) {
+	    cyz_rx_full_timer[info->line].expires = jiffies + 1;
+	    cyz_rx_full_timer[info->line].function = cyz_rx_restart;
+	    cyz_rx_full_timer[info->line].data = (unsigned long)info;
+	    add_timer(&cyz_rx_full_timer[info->line]);
+	}
+    }
+#endif
+    if (test_and_clear_bit(Cy_EVENT_DELTA_WAKEUP, &info->event)) {
+	wake_up_interruptible(&info->delta_msr_wait);
+    }
+    if (test_and_clear_bit(Cy_EVENT_WRITE_WAKEUP, &info->event)) {
+        tty_wakeup(tty);
+        wake_up_interruptible(&tty->write_wait);
+    }
+#ifdef Z_WAKE
+    if (test_and_clear_bit(Cy_EVENT_SHUTDOWN_WAKEUP, &info->event)) {
+        wake_up_interruptible(&info->shutdown_wait);
+    }
+#endif
+} /* do_softint */
+
+
+/***********************************************************/
+/********* Start of block of Cyclom-Y specific code ********/
+
+/* This routine waits up to 1000 micro-seconds for the previous
+   command to the Cirrus chip to complete and then issues the
+   new command.  An error is returned if the previous command
+   didn't finish within the time limit.
+
+   This function is only called from inside spinlock-protected code.
+ */
+static int
+cyy_issue_cmd(void __iomem *base_addr, u_char cmd, int index)
+{
+  volatile int  i;
+
+    /* Check to see that the previous command has completed */
+    for(i = 0 ; i < 100 ; i++){
+	if (cy_readb(base_addr+(CyCCR<<index)) == 0){
+	    break;
+	}
+	udelay(10L);
+    }
+    /* if the CCR never cleared, the previous command
+       didn't finish within the "reasonable time" */
+    if (i == 100)	return (-1);
+
+    /* Issue the new command */
+    cy_writeb(base_addr+(CyCCR<<index), cmd);
+
+    return(0);
+} /* cyy_issue_cmd */
+
+#ifdef CONFIG_ISA
+/* ISA interrupt detection code */
+static unsigned 
+detect_isa_irq(void __iomem *address)
+{
+  int irq;
+  unsigned long irqs, flags;
+  int save_xir, save_car;
+  int index = 0; /* IRQ probing is only for ISA */
+
+    /* forget possible initially masked and pending IRQ */
+    irq = probe_irq_off(probe_irq_on());
+
+    /* Clear interrupts on the board first */
+    cy_writeb(address + (Cy_ClrIntr<<index), 0);
+			      /* Cy_ClrIntr is 0x1800 */
+
+    irqs = probe_irq_on();
+    /* Wait ... */
+    udelay(5000L);
+
+    /* Enable the Tx interrupts on the CD1400 */
+    local_irq_save(flags);
+	cy_writeb(address + (CyCAR<<index), 0);
+	cyy_issue_cmd(address, CyCHAN_CTL|CyENB_XMTR, index);
+
+	cy_writeb(address + (CyCAR<<index), 0);
+	cy_writeb(address + (CySRER<<index), 
+		cy_readb(address + (CySRER<<index)) | CyTxRdy);
+    local_irq_restore(flags);
+
+    /* Wait ... */
+    udelay(5000L);
+
+    /* Check which interrupt is in use */
+    irq = probe_irq_off(irqs);
+
+    /* Clean up */
+    save_xir = (u_char) cy_readb(address + (CyTIR<<index));
+    save_car = cy_readb(address + (CyCAR<<index));
+    cy_writeb(address + (CyCAR<<index), (save_xir & 0x3));
+    cy_writeb(address + (CySRER<<index),
+	cy_readb(address + (CySRER<<index)) & ~CyTxRdy);
+    cy_writeb(address + (CyTIR<<index), (save_xir & 0x3f));
+    cy_writeb(address + (CyCAR<<index), (save_car));
+    cy_writeb(address + (Cy_ClrIntr<<index), 0);
+			      /* Cy_ClrIntr is 0x1800 */
+
+    return (irq > 0)? irq : 0;
+}
+#endif /* CONFIG_ISA */
+
+/* The real interrupt service routine is called
+   whenever the card wants its hand held--chars
+   received, out buffer empty, modem change, etc.
+ */
+static irqreturn_t
+cyy_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+  struct tty_struct *tty;
+  int status;
+  struct cyclades_card *cinfo;
+  struct cyclades_port *info;
+  void __iomem *base_addr, *card_base_addr;
+  int chip;
+  int save_xir, channel, save_car;
+  char data;
+  volatile int char_count;
+  int outch;
+  int i,j,index;
+  int too_many;
+  int had_work;
+  int mdm_change;
+  int mdm_status;
+
+    if((cinfo = (struct cyclades_card *)dev_id) == 0){
+#ifdef CY_DEBUG_INTERRUPTS
+	printk("cyy_interrupt: spurious interrupt %d\n\r", irq);
+#endif
+        return IRQ_NONE; /* spurious interrupt */
+    }
+
+    card_base_addr = cinfo->base_addr;
+    index = cinfo->bus_index;
+
+
+    /* This loop checks all chips in the card.  Make a note whenever
+       _any_ chip had some work to do, as this is considered an
+       indication that there will be more to do.  Only when no chip
+       has any work does this outermost loop exit.
+     */
+    do{
+        had_work = 0;
+        for ( chip = 0 ; chip < cinfo->num_chips ; chip ++) {
+            base_addr = cinfo->base_addr + (cy_chip_offset[chip]<<index);
+            too_many = 0;
+            while ( (status = cy_readb(base_addr+(CySVRR<<index))) != 0x00) {
+                had_work++;
+                /* The purpose of the following test is to ensure that
+                   no chip can monopolize the driver.  This forces the
+                   chips to be checked in a round-robin fashion (after
+                   draining each of a bunch (1000) of characters).
+                 */
+                if(1000<too_many++){
+                    break;
+                }
+                if (status & CySRReceive) { /* reception interrupt */
+#ifdef CY_DEBUG_INTERRUPTS
+		    printk("cyy_interrupt: rcvd intr, chip %d\n\r", chip);
+#endif
+                    /* determine the channel & change to that context */
+		    spin_lock(&cinfo->card_lock);
+                    save_xir = (u_char) cy_readb(base_addr+(CyRIR<<index));
+                    channel = (u_short ) (save_xir & CyIRChannel);
+                    i = channel + chip * 4 + cinfo->first_line;
+                    info = &cy_port[i];
+                    info->last_active = jiffies;
+                    save_car = cy_readb(base_addr+(CyCAR<<index));
+                    cy_writeb(base_addr+(CyCAR<<index), save_xir);
+
+                    /* if there is nowhere to put the data, discard it */
+                    if(info->tty == 0){
+                        j = (cy_readb(base_addr+(CyRIVR<<index)) & CyIVRMask);
+                        if ( j == CyIVRRxEx ) { /* exception */
+                            data = cy_readb(base_addr+(CyRDSR<<index));
+                        } else { /* normal character reception */
+                            char_count = cy_readb(base_addr+(CyRDCR<<index));
+                            while(char_count--){
+                                data = cy_readb(base_addr+(CyRDSR<<index));
+                            }
+                        }
+                    }else{ /* there is an open port for this data */
+                        tty = info->tty;
+                        j = (cy_readb(base_addr+(CyRIVR<<index)) & CyIVRMask);
+                        if ( j == CyIVRRxEx ) { /* exception */
+                            data = cy_readb(base_addr+(CyRDSR<<index));
+
+			    /* For statistics only */
+			    if (data & CyBREAK)
+				info->icount.brk++;
+			    else if(data & CyFRAME)
+				info->icount.frame++;
+			    else if(data & CyPARITY)
+				info->icount.parity++;
+			    else if(data & CyOVERRUN)
+				info->icount.overrun++;
+
+                            if(data & info->ignore_status_mask){
+				info->icount.rx++;
+                                continue;
+                            }
+                            if (tty->flip.count < TTY_FLIPBUF_SIZE){
+                                tty->flip.count++;
+                                if (data & info->read_status_mask){
+                                    if(data & CyBREAK){
+                                        *tty->flip.flag_buf_ptr++ =
+	    						    TTY_BREAK;
+                                        *tty->flip.char_buf_ptr++ =
+					  cy_readb(base_addr+(CyRDSR<<index));
+					info->icount.rx++;
+                                        if (info->flags & ASYNC_SAK){
+                                            do_SAK(tty);
+                                        }
+                                    }else if(data & CyFRAME){
+                                        *tty->flip.flag_buf_ptr++ =
+							    TTY_FRAME;
+                                        *tty->flip.char_buf_ptr++ =
+					  cy_readb(base_addr+(CyRDSR<<index));
+					info->icount.rx++;
+					info->idle_stats.frame_errs++;
+                                    }else if(data & CyPARITY){
+                                        *tty->flip.flag_buf_ptr++ =
+							    TTY_PARITY;
+                                        *tty->flip.char_buf_ptr++ =
+					  cy_readb(base_addr+(CyRDSR<<index));
+					info->icount.rx++;
+					info->idle_stats.parity_errs++;
+                                    }else if(data & CyOVERRUN){
+                                        *tty->flip.flag_buf_ptr++ =
+							    TTY_OVERRUN;
+                                        *tty->flip.char_buf_ptr++ = 0;
+					info->icount.rx++;
+                                        /* If the flip buffer itself is
+                                           overflowing, we still lose
+                                           the next incoming character.
+                                         */
+                                        if(tty->flip.count
+					           < TTY_FLIPBUF_SIZE){
+                                            tty->flip.count++;
+                                            *tty->flip.flag_buf_ptr++ =
+							     TTY_NORMAL;
+                                           *tty->flip.char_buf_ptr++ =
+					    cy_readb(base_addr+(CyRDSR<<index));
+					    info->icount.rx++;
+                                        }
+					info->idle_stats.overruns++;
+                                    /* These two conditions may imply */
+                                    /* a normal read should be done. */
+                                    /* }else if(data & CyTIMEOUT){ */
+                                    /* }else if(data & CySPECHAR){ */
+                                    }else{
+                                        *tty->flip.flag_buf_ptr++ = 0;
+                                        *tty->flip.char_buf_ptr++ = 0;
+					info->icount.rx++;
+                                    }
+                                }else{
+                                    *tty->flip.flag_buf_ptr++ = 0;
+                                    *tty->flip.char_buf_ptr++ = 0;
+				    info->icount.rx++;
+                                }
+                            }else{
+                                /* there was a software buffer
+				   overrun and nothing could be
+				   done about it!!! */
+				info->icount.buf_overrun++;
+				info->idle_stats.overruns++;
+                            }
+                        } else { /* normal character reception */
+                            /* load # chars available from the chip */
+                            char_count = cy_readb(base_addr+(CyRDCR<<index));
+
+#ifdef CY_ENABLE_MONITORING
+                            ++info->mon.int_count;
+                            info->mon.char_count += char_count;
+                            if (char_count > info->mon.char_max)
+                               info->mon.char_max = char_count;
+                            info->mon.char_last = char_count;
+#endif
+                            while(char_count--){
+                                if (tty->flip.count >= TTY_FLIPBUF_SIZE){
+                                        break;
+                                }
+                                tty->flip.count++;
+                                data = cy_readb(base_addr+(CyRDSR<<index));
+                                *tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+                                *tty->flip.char_buf_ptr++ = data;
+				info->idle_stats.recv_bytes++;
+				info->icount.rx++;
+#ifdef CY_16Y_HACK
+                                udelay(10L);
+#endif
+                            }
+                             info->idle_stats.recv_idle = jiffies;
+                        }
+                        schedule_delayed_work(&tty->flip.work, 1);
+                    }
+                    /* end of service */
+                    cy_writeb(base_addr+(CyRIR<<index), (save_xir & 0x3f));
+                    cy_writeb(base_addr+(CyCAR<<index), (save_car));
+		    spin_unlock(&cinfo->card_lock);
+                }
+
+
+                if (status & CySRTransmit) { /* transmission interrupt */
+                    /* Since we only get here when the transmit buffer
+                       is empty, we know we can always stuff a dozen
+                       characters. */
+#ifdef CY_DEBUG_INTERRUPTS
+		    printk("cyy_interrupt: xmit intr, chip %d\n\r", chip);
+#endif
+
+                    /* determine the channel & change to that context */
+		    spin_lock(&cinfo->card_lock);
+                    save_xir = (u_char) cy_readb(base_addr+(CyTIR<<index));
+                    channel = (u_short ) (save_xir & CyIRChannel);
+                    i = channel + chip * 4 + cinfo->first_line;
+                    save_car = cy_readb(base_addr+(CyCAR<<index));
+                    cy_writeb(base_addr+(CyCAR<<index), save_xir);
+
+                    /* validate the port# (as configured and open) */
+                    if( (i < 0) || (NR_PORTS <= i) ){
+                        cy_writeb(base_addr+(CySRER<<index),
+                             cy_readb(base_addr+(CySRER<<index)) & ~CyTxRdy);
+                        goto txend;
+                    }
+                    info = &cy_port[i];
+                    info->last_active = jiffies;
+                    if(info->tty == 0){
+                        cy_writeb(base_addr+(CySRER<<index),
+                             cy_readb(base_addr+(CySRER<<index)) & ~CyTxRdy);
+                        goto txdone;
+                    }
+
+                    /* load the on-chip space for outbound data */
+                    char_count = info->xmit_fifo_size;
+
+                    if(info->x_char) { /* send special char */
+                        outch = info->x_char;
+                        cy_writeb(base_addr+(CyTDR<<index), outch);
+                        char_count--;
+			info->icount.tx++;
+                        info->x_char = 0;
+                    }
+
+                    if (info->breakon || info->breakoff) {
+			if (info->breakon) {
+			    cy_writeb(base_addr + (CyTDR<<index), 0); 
+			    cy_writeb(base_addr + (CyTDR<<index), 0x81);
+			    info->breakon = 0;
+                            char_count -= 2;
+			}
+			if (info->breakoff) {
+			    cy_writeb(base_addr + (CyTDR<<index), 0); 
+			    cy_writeb(base_addr + (CyTDR<<index), 0x83);
+			    info->breakoff = 0;
+                            char_count -= 2;
+			}
+                    }
+
+                    while (char_count-- > 0){
+			if (!info->xmit_cnt){
+			    if (cy_readb(base_addr+(CySRER<<index))&CyTxMpty) {
+				cy_writeb(base_addr+(CySRER<<index),
+					  cy_readb(base_addr+(CySRER<<index)) &
+					  ~CyTxMpty);
+			    } else {
+				cy_writeb(base_addr+(CySRER<<index),
+					  ((cy_readb(base_addr+(CySRER<<index))
+					    & ~CyTxRdy)
+					   | CyTxMpty));
+			    }
+			    goto txdone;
+			}
+			if (info->xmit_buf == 0){
+                            cy_writeb(base_addr+(CySRER<<index),
+				cy_readb(base_addr+(CySRER<<index)) & 
+					~CyTxRdy);
+                            goto txdone;
+			}
+			if (info->tty->stopped || info->tty->hw_stopped){
+                            cy_writeb(base_addr+(CySRER<<index),
+				cy_readb(base_addr+(CySRER<<index)) & 
+					~CyTxRdy);
+                            goto txdone;
+			}
+                        /* Because the Embedded Transmit Commands have
+                           been enabled, we must check to see if the
+			   escape character, NULL, is being sent.  If it
+			   is, we must ensure that there is room for it
+			   to be doubled in the output stream.  Therefore
+			   we no longer advance the pointer when the
+			   character is fetched, but rather wait until
+			   after the check for a NULL output character.
+			   This is necessary because there may not be
+			   room for the two chars needed to send a NULL.)
+                         */
+                        outch = info->xmit_buf[info->xmit_tail];
+                        if( outch ){
+                            info->xmit_cnt--;
+                            info->xmit_tail = (info->xmit_tail + 1)
+                                                      & (SERIAL_XMIT_SIZE - 1);
+                            cy_writeb(base_addr+(CyTDR<<index), outch);
+			    info->icount.tx++;
+                        }else{
+                            if(char_count > 1){
+                                info->xmit_cnt--;
+                                info->xmit_tail = (info->xmit_tail + 1)
+						      & (SERIAL_XMIT_SIZE - 1);
+                                cy_writeb(base_addr+(CyTDR<<index), 
+					  outch);
+                                cy_writeb(base_addr+(CyTDR<<index), 0);
+				info->icount.tx++;
+                                char_count--;
+                            }else{
+                            }
+                        }
+                    }
+
+        txdone:
+                    if (info->xmit_cnt < WAKEUP_CHARS) {
+                        cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+                    }
+        txend:
+                    /* end of service */
+                    cy_writeb(base_addr+(CyTIR<<index), 
+			      (save_xir & 0x3f));
+                    cy_writeb(base_addr+(CyCAR<<index), (save_car));
+		    spin_unlock(&cinfo->card_lock);
+                }
+
+                if (status & CySRModem) {        /* modem interrupt */
+
+                    /* determine the channel & change to that context */
+		    spin_lock(&cinfo->card_lock);
+                    save_xir = (u_char) cy_readb(base_addr+(CyMIR<<index));
+                    channel = (u_short ) (save_xir & CyIRChannel);
+                    info = &cy_port[channel + chip * 4
+		                           + cinfo->first_line];
+                    info->last_active = jiffies;
+                    save_car = cy_readb(base_addr+(CyCAR<<index));
+                    cy_writeb(base_addr+(CyCAR<<index), save_xir);
+
+                    mdm_change = cy_readb(base_addr+(CyMISR<<index));
+                    mdm_status = cy_readb(base_addr+(CyMSVR1<<index));
+
+                    if(info->tty == 0){/* no place for data, ignore it*/
+                        ;
+                    }else{
+			if (mdm_change & CyANY_DELTA) {
+			    /* For statistics only */
+			    if (mdm_change & CyDCD)	info->icount.dcd++;
+			    if (mdm_change & CyCTS)	info->icount.cts++;
+			    if (mdm_change & CyDSR)	info->icount.dsr++;
+			    if (mdm_change & CyRI)	info->icount.rng++;
+
+			    cy_sched_event(info, Cy_EVENT_DELTA_WAKEUP);
+			}
+
+                        if((mdm_change & CyDCD)
+                        && (info->flags & ASYNC_CHECK_CD)){
+                            if(mdm_status & CyDCD){
+                                cy_sched_event(info,
+				    Cy_EVENT_OPEN_WAKEUP);
+                            }else{
+                                cy_sched_event(info,
+				    Cy_EVENT_HANGUP);
+                            }
+                        }
+                        if((mdm_change & CyCTS)
+                        && (info->flags & ASYNC_CTS_FLOW)){
+                            if(info->tty->hw_stopped){
+                                if(mdm_status & CyCTS){
+                                    /* cy_start isn't used
+				         because... !!! */
+                                    info->tty->hw_stopped = 0;
+                                  cy_writeb(base_addr+(CySRER<<index),
+                                       cy_readb(base_addr+(CySRER<<index)) | 
+                                       CyTxRdy);
+                                    cy_sched_event(info,
+				        Cy_EVENT_WRITE_WAKEUP);
+                                }
+                            }else{
+                                if(!(mdm_status & CyCTS)){
+                                    /* cy_stop isn't used
+				         because ... !!! */
+                                    info->tty->hw_stopped = 1;
+                                  cy_writeb(base_addr+(CySRER<<index),
+                                       cy_readb(base_addr+(CySRER<<index)) & 
+                                       ~CyTxRdy);
+                                }
+                            }
+                        }
+                        if(mdm_change & CyDSR){
+                        }
+                        if(mdm_change & CyRI){
+                        }
+                    }
+                    /* end of service */
+                    cy_writeb(base_addr+(CyMIR<<index), 
+			      (save_xir & 0x3f));
+                    cy_writeb(base_addr+(CyCAR<<index), save_car);
+		    spin_unlock(&cinfo->card_lock);
+                }
+            }          /* end while status != 0 */
+        }            /* end loop for chips... */
+    } while(had_work);
+
+   /* clear interrupts */
+   spin_lock(&cinfo->card_lock);
+   cy_writeb(card_base_addr + (Cy_ClrIntr<<index), 0);
+                                /* Cy_ClrIntr is 0x1800 */
+   spin_unlock(&cinfo->card_lock);
+   return IRQ_HANDLED;
+} /* cyy_interrupt */
+
+/***********************************************************/
+/********* End of block of Cyclom-Y specific code **********/
+/******** Start of block of Cyclades-Z specific code *********/
+/***********************************************************/
+
+static int
+cyz_fetch_msg( struct cyclades_card *cinfo,
+	    uclong *channel, ucchar *cmd, uclong *param)
+{
+  struct FIRM_ID __iomem *firm_id;
+  struct ZFW_CTRL __iomem *zfw_ctrl;
+  struct BOARD_CTRL __iomem *board_ctrl;
+  unsigned long loc_doorbell;
+
+    firm_id = cinfo->base_addr + ID_ADDRESS;
+    if (!ISZLOADED(*cinfo)){
+	return (-1);
+    }
+    zfw_ctrl = cinfo->base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+    board_ctrl = &zfw_ctrl->board_ctrl;
+
+    loc_doorbell = cy_readl(&((struct RUNTIME_9060 __iomem *)
+                     (cinfo->ctl_addr))->loc_doorbell);
+    if (loc_doorbell){
+	*cmd = (char)(0xff & loc_doorbell);
+	*channel = cy_readl(&board_ctrl->fwcmd_channel);
+	*param = (uclong)cy_readl(&board_ctrl->fwcmd_param);
+	cy_writel(&((struct RUNTIME_9060 __iomem *)(cinfo->ctl_addr))->loc_doorbell, 
+                 0xffffffff);
+	return 1;
+    }
+    return 0;
+} /* cyz_fetch_msg */
+
+static int
+cyz_issue_cmd( struct cyclades_card *cinfo,
+	    uclong channel, ucchar cmd, uclong param)
+{
+  struct FIRM_ID __iomem *firm_id;
+  struct ZFW_CTRL __iomem *zfw_ctrl;
+  struct BOARD_CTRL __iomem *board_ctrl;
+  unsigned long __iomem *pci_doorbell;
+  int index;
+
+    firm_id = cinfo->base_addr + ID_ADDRESS;
+    if (!ISZLOADED(*cinfo)){
+	return (-1);
+    }
+    zfw_ctrl = cinfo->base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+    board_ctrl = &zfw_ctrl->board_ctrl;
+
+    index = 0;
+    pci_doorbell = &((struct RUNTIME_9060 __iomem *) (cinfo->ctl_addr))->pci_doorbell;
+    while( (cy_readl(pci_doorbell) & 0xff) != 0){
+        if (index++ == 1000){
+	    return((int)(cy_readl(pci_doorbell) & 0xff));
+        }
+	udelay(50L);
+    }
+    cy_writel(&board_ctrl->hcmd_channel, channel);
+    cy_writel(&board_ctrl->hcmd_param , param);
+    cy_writel(pci_doorbell, (long)cmd);
+
+    return(0);
+} /* cyz_issue_cmd */
+
+static void
+cyz_handle_rx(struct cyclades_port *info,
+	      volatile struct CH_CTRL __iomem *ch_ctrl,
+	      volatile struct BUF_CTRL __iomem *buf_ctrl)
+{
+  struct cyclades_card *cinfo = &cy_card[info->card];
+  struct tty_struct *tty = info->tty;
+  volatile int char_count;
+#ifdef BLOCKMOVE
+  int small_count;
+#else
+  char data;
+#endif
+  volatile uclong rx_put, rx_get, new_rx_get, rx_bufsize, rx_bufaddr;
+
+    rx_get = new_rx_get = cy_readl(&buf_ctrl->rx_get);
+    rx_put = cy_readl(&buf_ctrl->rx_put);
+    rx_bufsize = cy_readl(&buf_ctrl->rx_bufsize);
+    rx_bufaddr = cy_readl(&buf_ctrl->rx_bufaddr);
+    if (rx_put >= rx_get)
+	char_count = rx_put - rx_get;
+    else
+	char_count = rx_put - rx_get + rx_bufsize;
+
+    if ( char_count ) {
+	info->last_active = jiffies;
+	info->jiffies[1] = jiffies;
+
+#ifdef CY_ENABLE_MONITORING
+	info->mon.int_count++;
+	info->mon.char_count += char_count;
+	if (char_count > info->mon.char_max)
+	    info->mon.char_max = char_count;
+	info->mon.char_last = char_count;
+#endif
+	if(tty == 0){
+	    /* flush received characters */
+	    new_rx_get = (new_rx_get + char_count) & (rx_bufsize - 1);
+	    info->rflush_count++;
+	}else{
+#ifdef BLOCKMOVE
+	    /* we'd like to use memcpy(t, f, n) and memset(s, c, count)
+	       for performance, but because of buffer boundaries, there
+	       may be several steps to the operation */
+	    while(0 < (small_count = 
+		       min_t(unsigned int, (rx_bufsize - new_rx_get),
+		       min_t(unsigned int, (TTY_FLIPBUF_SIZE - tty->flip.count), char_count))
+		 )) {
+		memcpy_fromio(tty->flip.char_buf_ptr,
+			      (char *)(cinfo->base_addr
+				       + rx_bufaddr + new_rx_get),
+			      small_count);
+
+		tty->flip.char_buf_ptr += small_count;
+		memset(tty->flip.flag_buf_ptr, TTY_NORMAL, small_count);
+		tty->flip.flag_buf_ptr += small_count;
+		new_rx_get = (new_rx_get + small_count) & (rx_bufsize - 1);
+		char_count -= small_count;
+		info->icount.rx += small_count;
+		info->idle_stats.recv_bytes += small_count;
+		tty->flip.count += small_count;
+	    }
+#else
+	    while(char_count--){
+		if (tty->flip.count >= N_TTY_BUF_SIZE - tty->read_cnt)
+                    break;
+
+		if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+		    break;
+
+		data = cy_readb(cinfo->base_addr + rx_bufaddr + new_rx_get);
+		new_rx_get = (new_rx_get + 1) & (rx_bufsize - 1);
+		tty->flip.count++;
+		*tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+		*tty->flip.char_buf_ptr++ = data;
+		info->idle_stats.recv_bytes++;
+		info->icount.rx++;
+	    }
+#endif
+#ifdef CONFIG_CYZ_INTR
+	    /* Recalculate the number of chars in the RX buffer and issue
+	       a cmd in case it's higher than the RX high water mark */
+	    rx_put = cy_readl(&buf_ctrl->rx_put);
+	    if (rx_put >= rx_get)
+		char_count = rx_put - rx_get;
+	    else
+		char_count = rx_put - rx_get + rx_bufsize;
+	    if(char_count >= cy_readl(&buf_ctrl->rx_threshold)) {
+		cy_sched_event(info, Cy_EVENT_Z_RX_FULL);
+	    }
+#endif
+	    info->idle_stats.recv_idle = jiffies;
+	    schedule_delayed_work(&tty->flip.work, 1);
+	}
+	/* Update rx_get */
+	cy_writel(&buf_ctrl->rx_get, new_rx_get);
+    }
+}
+
+static void
+cyz_handle_tx(struct cyclades_port *info,
+	      volatile struct CH_CTRL __iomem *ch_ctrl,
+	      volatile struct BUF_CTRL __iomem *buf_ctrl)
+{
+  struct cyclades_card *cinfo = &cy_card[info->card];
+  struct tty_struct *tty = info->tty;
+  char data;
+  volatile int char_count;
+#ifdef BLOCKMOVE
+  int small_count;
+#endif
+  volatile uclong tx_put, tx_get, tx_bufsize, tx_bufaddr;
+
+    if (info->xmit_cnt <= 0)	/* Nothing to transmit */
+	return;
+
+    tx_get = cy_readl(&buf_ctrl->tx_get);
+    tx_put = cy_readl(&buf_ctrl->tx_put);
+    tx_bufsize = cy_readl(&buf_ctrl->tx_bufsize);
+    tx_bufaddr = cy_readl(&buf_ctrl->tx_bufaddr);
+    if (tx_put >= tx_get)
+	char_count = tx_get - tx_put - 1 + tx_bufsize;
+    else
+	char_count = tx_get - tx_put - 1;
+
+    if ( char_count ) {
+
+	if( tty == 0 ){
+	    goto ztxdone;
+	}
+
+	if(info->x_char) { /* send special char */
+	    data = info->x_char;
+
+	    cy_writeb((cinfo->base_addr + tx_bufaddr + tx_put), data);
+	    tx_put = (tx_put + 1) & (tx_bufsize - 1);
+	    info->x_char = 0;
+	    char_count--;
+	    info->icount.tx++;
+	    info->last_active = jiffies;
+	    info->jiffies[2] = jiffies;
+	}
+#ifdef BLOCKMOVE
+	while(0 < (small_count = 
+		   min_t(unsigned int, (tx_bufsize - tx_put),
+		       min_t(unsigned int, (SERIAL_XMIT_SIZE - info->xmit_tail),
+			   min_t(unsigned int, info->xmit_cnt, char_count))))) {
+
+	    memcpy_toio((char *)(cinfo->base_addr + tx_bufaddr + tx_put),
+			&info->xmit_buf[info->xmit_tail],
+			small_count);
+
+	    tx_put = (tx_put + small_count) & (tx_bufsize - 1);
+	    char_count -= small_count;
+	    info->icount.tx += small_count;
+	    info->xmit_cnt -= small_count;
+	    info->xmit_tail = 
+		(info->xmit_tail + small_count) & (SERIAL_XMIT_SIZE - 1);
+	    info->last_active = jiffies;
+	    info->jiffies[2] = jiffies;
+	}
+#else
+	while (info->xmit_cnt && char_count){
+	    data = info->xmit_buf[info->xmit_tail];
+	    info->xmit_cnt--;
+	    info->xmit_tail = (info->xmit_tail + 1) & (SERIAL_XMIT_SIZE - 1);
+
+	    cy_writeb(cinfo->base_addr + tx_bufaddr + tx_put, data);
+	    tx_put = (tx_put + 1) & (tx_bufsize - 1);
+	    char_count--;
+	    info->icount.tx++;
+	    info->last_active = jiffies;
+	    info->jiffies[2] = jiffies;
+	}
+#endif
+    ztxdone:
+	if (info->xmit_cnt < WAKEUP_CHARS) {
+	    cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+	}
+	/* Update tx_put */
+	cy_writel(&buf_ctrl->tx_put, tx_put);
+    }
+}
+
+static void
+cyz_handle_cmd(struct cyclades_card *cinfo)
+{
+  struct tty_struct *tty;
+  struct cyclades_port *info;
+  static volatile struct FIRM_ID __iomem *firm_id;
+  static volatile struct ZFW_CTRL __iomem *zfw_ctrl;
+  static volatile struct BOARD_CTRL __iomem *board_ctrl;
+  static volatile struct CH_CTRL __iomem *ch_ctrl;
+  static volatile struct BUF_CTRL __iomem *buf_ctrl;
+  uclong channel;
+  ucchar cmd;
+  uclong param;
+  uclong hw_ver, fw_ver;
+  int special_count;
+  int delta_count;
+
+    firm_id = cinfo->base_addr + ID_ADDRESS;
+    zfw_ctrl = cinfo->base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+    board_ctrl = &zfw_ctrl->board_ctrl;
+    fw_ver = cy_readl(&board_ctrl->fw_version);
+    hw_ver = cy_readl(&((struct RUNTIME_9060 __iomem *)(cinfo->ctl_addr))->mail_box_0);
+
+
+    while(cyz_fetch_msg(cinfo, &channel, &cmd, &param) == 1) {
+	special_count = 0;
+	delta_count = 0;
+	info = &cy_port[channel + cinfo->first_line];
+	if((tty = info->tty) == 0) {
+	    continue;
+	}
+	ch_ctrl = &(zfw_ctrl->ch_ctrl[channel]);
+	buf_ctrl = &(zfw_ctrl->buf_ctrl[channel]);
+
+	switch(cmd) {
+	    case C_CM_PR_ERROR:
+		tty->flip.count++;
+		*tty->flip.flag_buf_ptr++ = TTY_PARITY;
+		*tty->flip.char_buf_ptr++ = 0;
+		info->icount.rx++;
+		special_count++;
+		break;
+	    case C_CM_FR_ERROR:
+		tty->flip.count++;
+		*tty->flip.flag_buf_ptr++ = TTY_FRAME;
+		*tty->flip.char_buf_ptr++ = 0;
+		info->icount.rx++;
+		special_count++;
+		break;
+	    case C_CM_RXBRK:
+		tty->flip.count++;
+		*tty->flip.flag_buf_ptr++ = TTY_BREAK;
+		*tty->flip.char_buf_ptr++ = 0;
+		info->icount.rx++;
+		special_count++;
+		break;
+	    case C_CM_MDCD:
+		info->icount.dcd++;
+		delta_count++;
+		if (info->flags & ASYNC_CHECK_CD){
+		    if ((fw_ver > 241 ? 
+			  ((u_long)param) : 
+			  cy_readl(&ch_ctrl->rs_status)) & C_RS_DCD) {
+			cy_sched_event(info, Cy_EVENT_OPEN_WAKEUP);
+		    }else{
+			cy_sched_event(info, Cy_EVENT_HANGUP);
+		    }
+		}
+		break;
+	    case C_CM_MCTS:
+		info->icount.cts++;
+		delta_count++;
+		break;
+	    case C_CM_MRI:
+		info->icount.rng++;
+		delta_count++;
+		break;
+	    case C_CM_MDSR:
+		info->icount.dsr++;
+		delta_count++;
+		break;
+#ifdef Z_WAKE
+	    case C_CM_IOCTLW:
+		cy_sched_event(info, Cy_EVENT_SHUTDOWN_WAKEUP);
+		break;
+#endif
+#ifdef CONFIG_CYZ_INTR
+	    case C_CM_RXHIWM:
+	    case C_CM_RXNNDT:
+	    case C_CM_INTBACK2:
+		/* Reception Interrupt */
+#ifdef CY_DEBUG_INTERRUPTS
+		printk("cyz_interrupt: rcvd intr, card %d, port %ld\n\r", 
+			info->card, channel);
+#endif
+		cyz_handle_rx(info, ch_ctrl, buf_ctrl);
+		break;
+	    case C_CM_TXBEMPTY:
+	    case C_CM_TXLOWWM:
+	    case C_CM_INTBACK:
+		/* Transmission Interrupt */
+#ifdef CY_DEBUG_INTERRUPTS
+		printk("cyz_interrupt: xmit intr, card %d, port %ld\n\r", 
+			info->card, channel);
+#endif
+		cyz_handle_tx(info, ch_ctrl, buf_ctrl);
+		break;
+#endif /* CONFIG_CYZ_INTR */
+	    case C_CM_FATAL:
+		/* should do something with this !!! */
+		break;
+	    default:
+		break;
+	}
+	if(delta_count)
+	    cy_sched_event(info, Cy_EVENT_DELTA_WAKEUP);
+	if(special_count)
+	    schedule_delayed_work(&tty->flip.work, 1);
+    }
+}
+
+#ifdef CONFIG_CYZ_INTR
+static irqreturn_t
+cyz_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+  struct cyclades_card *cinfo;
+
+    if((cinfo = (struct cyclades_card *)dev_id) == 0){
+#ifdef CY_DEBUG_INTERRUPTS
+	printk("cyz_interrupt: spurious interrupt %d\n\r", irq);
+#endif
+        return IRQ_NONE; /* spurious interrupt */
+    }
+
+    if (!ISZLOADED(*cinfo)) {
+#ifdef CY_DEBUG_INTERRUPTS
+	printk("cyz_interrupt: board not yet loaded (IRQ%d).\n\r", irq);
+#endif
+	return IRQ_NONE;
+    }
+
+    /* Handle the interrupts */
+    cyz_handle_cmd(cinfo);
+
+    return IRQ_HANDLED;
+} /* cyz_interrupt */
+
+static void
+cyz_rx_restart(unsigned long arg)
+{
+    struct cyclades_port *info = (struct cyclades_port *)arg;
+    int retval;
+    int card = info->card;
+    uclong channel = (info->line) - (cy_card[card].first_line);
+    unsigned long flags;
+
+    CY_LOCK(info, flags);
+    retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_INTBACK2, 0L);
+    if (retval != 0){
+	printk("cyc:cyz_rx_restart retval on ttyC%d was %x\n", 
+	       info->line, retval);
+    }
+    cyz_rx_full_timer[info->line].function = NULL;
+    CY_UNLOCK(info, flags);
+}
+
+#else /* CONFIG_CYZ_INTR */
+
+static void
+cyz_poll(unsigned long arg)
+{
+  struct cyclades_card *cinfo;
+  struct cyclades_port *info;
+  struct tty_struct *tty;
+  static volatile struct FIRM_ID *firm_id;
+  static volatile struct ZFW_CTRL *zfw_ctrl;
+  static volatile struct BOARD_CTRL *board_ctrl;
+  static volatile struct CH_CTRL *ch_ctrl;
+  static volatile struct BUF_CTRL *buf_ctrl;
+  int card, port;
+
+    cyz_timerlist.expires = jiffies + (HZ);
+    for (card = 0 ; card < NR_CARDS ; card++){
+	cinfo = &cy_card[card];
+
+	if (!IS_CYC_Z(*cinfo)) continue;
+	if (!ISZLOADED(*cinfo)) continue;
+
+	firm_id = cinfo->base_addr + ID_ADDRESS;
+	zfw_ctrl = cinfo->base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	board_ctrl = &(zfw_ctrl->board_ctrl);
+
+	/* Skip first polling cycle to avoid racing conditions with the FW */
+	if (!cinfo->intr_enabled) {
+	    cinfo->nports = (int) cy_readl(&board_ctrl->n_channel);
+	    cinfo->intr_enabled = 1;
+	    continue;
+	}
+
+	cyz_handle_cmd(cinfo);
+
+	for (port = 0 ; port < cinfo->nports ; port++) {
+	    info = &cy_port[ port + cinfo->first_line ];
+            tty = info->tty;
+	    ch_ctrl = &(zfw_ctrl->ch_ctrl[port]);
+	    buf_ctrl = &(zfw_ctrl->buf_ctrl[port]);
+
+	    if (!info->throttle)
+	        cyz_handle_rx(info, ch_ctrl, buf_ctrl);
+	    cyz_handle_tx(info, ch_ctrl, buf_ctrl);
+	}
+	/* poll every 'cyz_polling_cycle' period */
+	cyz_timerlist.expires = jiffies + cyz_polling_cycle;
+    }
+    add_timer(&cyz_timerlist);
+
+    return;
+} /* cyz_poll */
+
+#endif /* CONFIG_CYZ_INTR */
+
+/********** End of block of Cyclades-Z specific code *********/
+/***********************************************************/
+
+
+/* This is called whenever a port becomes active;
+   interrupts are enabled and DTR & RTS are turned on.
+ */
+static int
+startup(struct cyclades_port * info)
+{
+  unsigned long flags;
+  int retval = 0;
+  void __iomem *base_addr;
+  int card,chip,channel,index;
+  unsigned long page;
+
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+
+    page = get_zeroed_page(GFP_KERNEL);
+    if (!page)
+	return -ENOMEM;
+
+    CY_LOCK(info, flags);
+
+    if (info->flags & ASYNC_INITIALIZED){
+	free_page(page);
+	goto errout;
+    }
+
+    if (!info->type){
+        if (info->tty){
+            set_bit(TTY_IO_ERROR, &info->tty->flags);
+        }
+	free_page(page);
+	goto errout;
+    }
+
+    if (info->xmit_buf)
+	free_page(page);
+    else
+	info->xmit_buf = (unsigned char *) page;
+
+    CY_UNLOCK(info, flags);
+
+    set_line_char(info);
+
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+#ifdef CY_DEBUG_OPEN
+	printk("cyc startup card %d, chip %d, channel %d, base_addr %lx\n",
+	     card, chip, channel, (long)base_addr);/**/
+#endif
+
+	CY_LOCK(info, flags);
+
+	cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+
+	cy_writeb(base_addr+(CyRTPR<<index), (info->default_timeout
+		 ? info->default_timeout : 0x02)); /* 10ms rx timeout */
+
+	cyy_issue_cmd(base_addr,CyCHAN_CTL|CyENB_RCVR|CyENB_XMTR,index);
+
+	cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+	cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+	cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+
+#ifdef CY_DEBUG_DTR
+	printk("cyc:startup raising DTR\n");
+	printk("     status: 0x%x, 0x%x\n",
+		cy_readb(base_addr+(CyMSVR1<<index)), 
+                cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+
+	cy_writeb(base_addr+(CySRER<<index),
+		cy_readb(base_addr+(CySRER<<index)) | CyRxData);
+	info->flags |= ASYNC_INITIALIZED;
+
+	if (info->tty){
+	    clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	}
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+	info->breakon = info->breakoff = 0;
+	memset((char *)&info->idle_stats, 0, sizeof(info->idle_stats));
+	info->idle_stats.in_use    =
+	info->idle_stats.recv_idle =
+	info->idle_stats.xmit_idle = jiffies;
+
+	CY_UNLOCK(info, flags);
+
+    } else {
+      struct FIRM_ID __iomem *firm_id;
+      struct ZFW_CTRL __iomem *zfw_ctrl;
+      struct BOARD_CTRL __iomem *board_ctrl;
+      struct CH_CTRL __iomem *ch_ctrl;
+      int retval;
+
+	base_addr = cy_card[card].base_addr;
+
+        firm_id = base_addr + ID_ADDRESS;
+        if (!ISZLOADED(cy_card[card])){
+	    return -ENODEV;
+	}
+
+	zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	board_ctrl = &zfw_ctrl->board_ctrl;
+	ch_ctrl = zfw_ctrl->ch_ctrl;
+
+#ifdef CY_DEBUG_OPEN
+	printk("cyc startup Z card %d, channel %d, base_addr %lx\n",
+	     card, channel, (long)base_addr);/**/
+#endif
+
+	CY_LOCK(info, flags);
+
+	cy_writel(&ch_ctrl[channel].op_mode, C_CH_ENABLE);
+#ifdef Z_WAKE
+#ifdef CONFIG_CYZ_INTR
+	cy_writel(&ch_ctrl[channel].intr_enable, 
+		  C_IN_TXBEMPTY|C_IN_TXLOWWM|C_IN_RXHIWM|C_IN_RXNNDT|
+		  C_IN_IOCTLW|
+		  C_IN_MDCD);
+#else
+	cy_writel(&ch_ctrl[channel].intr_enable, 
+		  C_IN_IOCTLW|
+		  C_IN_MDCD);
+#endif /* CONFIG_CYZ_INTR */
+#else
+#ifdef CONFIG_CYZ_INTR
+	cy_writel(&ch_ctrl[channel].intr_enable, 
+		  C_IN_TXBEMPTY|C_IN_TXLOWWM|C_IN_RXHIWM|C_IN_RXNNDT|
+		  C_IN_MDCD);
+#else
+	cy_writel(&ch_ctrl[channel].intr_enable, 
+		  C_IN_MDCD);
+#endif /* CONFIG_CYZ_INTR */
+#endif /* Z_WAKE */
+
+	retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_IOCTL, 0L);
+	if (retval != 0){
+	    printk("cyc:startup(1) retval on ttyC%d was %x\n",
+		   info->line, retval);
+	}
+
+	/* Flush RX buffers before raising DTR and RTS */
+	retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_FLUSH_RX, 0L);
+	if (retval != 0){
+	    printk("cyc:startup(2) retval on ttyC%d was %x\n",
+		   info->line, retval);
+	}
+
+	/* set timeout !!! */
+	/* set RTS and DTR !!! */
+	cy_writel(&ch_ctrl[channel].rs_control,
+             cy_readl(&ch_ctrl[channel].rs_control) | C_RS_RTS | C_RS_DTR) ;
+	retval = cyz_issue_cmd(&cy_card[info->card],
+	    channel, C_CM_IOCTLM, 0L);
+	if (retval != 0){
+	    printk("cyc:startup(3) retval on ttyC%d was %x\n",
+		   info->line, retval);
+	}
+#ifdef CY_DEBUG_DTR
+	    printk("cyc:startup raising Z DTR\n");
+#endif
+
+	/* enable send, recv, modem !!! */
+
+	info->flags |= ASYNC_INITIALIZED;
+	if (info->tty){
+	    clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	}
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+	info->breakon = info->breakoff = 0;
+	memset((char *)&info->idle_stats, 0, sizeof(info->idle_stats));
+	info->idle_stats.in_use    =
+	info->idle_stats.recv_idle =
+	info->idle_stats.xmit_idle = jiffies;
+
+	CY_UNLOCK(info, flags);
+    }
+
+#ifdef CY_DEBUG_OPEN
+	printk(" cyc startup done\n");
+#endif
+	return 0;
+
+errout:
+	CY_UNLOCK(info, flags);
+	return retval;
+} /* startup */
+
+
+static void
+start_xmit( struct cyclades_port *info )
+{
+  unsigned long flags;
+  void __iomem *base_addr;
+  int card,chip,channel,index;
+
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	CY_LOCK(info, flags);
+	    cy_writeb(base_addr+(CyCAR<<index), channel);
+	    cy_writeb(base_addr+(CySRER<<index), 
+               cy_readb(base_addr+(CySRER<<index)) | CyTxRdy);
+	CY_UNLOCK(info, flags);
+    } else {
+#ifdef CONFIG_CYZ_INTR
+      int retval;
+
+	CY_LOCK(info, flags);
+	    retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_INTBACK, 0L);
+	    if (retval != 0){
+		printk("cyc:start_xmit retval on ttyC%d was %x\n",
+		       info->line, retval);
+	    }
+	CY_UNLOCK(info, flags);
+#else /* CONFIG_CYZ_INTR */
+	/* Don't have to do anything at this time */
+#endif /* CONFIG_CYZ_INTR */
+    }
+} /* start_xmit */
+
+/*
+ * This routine shuts down a serial port; interrupts are disabled,
+ * and DTR is dropped if the hangup on close termio flag is on.
+ */
+static void
+shutdown(struct cyclades_port * info)
+{
+  unsigned long flags;
+  void __iomem *base_addr;
+  int card,chip,channel,index;
+
+    if (!(info->flags & ASYNC_INITIALIZED)){
+        return;
+    }
+
+    card = info->card;
+    channel = info->line - cy_card[card].first_line;
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+#ifdef CY_DEBUG_OPEN
+    printk("cyc shutdown Y card %d, chip %d, channel %d, base_addr %lx\n",
+		card, chip, channel, (long)base_addr);
+#endif
+
+	CY_LOCK(info, flags);
+
+	    /* Clear delta_msr_wait queue to avoid mem leaks. */
+	    wake_up_interruptible(&info->delta_msr_wait);
+
+	    if (info->xmit_buf){
+		unsigned char * temp;
+		temp = info->xmit_buf;
+		info->xmit_buf = NULL;
+		free_page((unsigned long) temp);
+	    }
+	    cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+	    if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) {
+		cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+		cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+#ifdef CY_DEBUG_DTR
+		printk("cyc shutdown dropping DTR\n");
+		printk("     status: 0x%x, 0x%x\n",
+		    cy_readb(base_addr+(CyMSVR1<<index)), 
+                    cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+	    }
+	    cyy_issue_cmd(base_addr,CyCHAN_CTL|CyDIS_RCVR,index);
+	     /* it may be appropriate to clear _XMIT at
+	       some later date (after testing)!!! */
+
+	    if (info->tty){
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+	    }
+	    info->flags &= ~ASYNC_INITIALIZED;
+	CY_UNLOCK(info, flags);
+    } else {
+      struct FIRM_ID __iomem *firm_id;
+      struct ZFW_CTRL __iomem *zfw_ctrl;
+      struct BOARD_CTRL __iomem *board_ctrl;
+      struct CH_CTRL __iomem *ch_ctrl;
+      int retval;
+
+	base_addr = cy_card[card].base_addr;
+#ifdef CY_DEBUG_OPEN
+    printk("cyc shutdown Z card %d, channel %d, base_addr %lx\n",
+		card, channel, (long)base_addr);
+#endif
+
+        firm_id = base_addr + ID_ADDRESS;
+        if (!ISZLOADED(cy_card[card])) {
+	    return;
+	}
+
+	zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	board_ctrl = &zfw_ctrl->board_ctrl;
+	ch_ctrl = zfw_ctrl->ch_ctrl;
+
+	CY_LOCK(info, flags);
+
+	    if (info->xmit_buf){
+		unsigned char * temp;
+		temp = info->xmit_buf;
+		info->xmit_buf = NULL;
+		free_page((unsigned long) temp);
+	    }
+	    
+	    if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) {
+		cy_writel(&ch_ctrl[channel].rs_control,
+                   (uclong)(cy_readl(&ch_ctrl[channel].rs_control) & 
+                   ~(C_RS_RTS | C_RS_DTR)));
+		retval = cyz_issue_cmd(&cy_card[info->card],
+			channel, C_CM_IOCTLM, 0L);
+		if (retval != 0){
+		    printk("cyc:shutdown retval on ttyC%d was %x\n",
+			   info->line, retval);
+		}
+#ifdef CY_DEBUG_DTR
+		printk("cyc:shutdown dropping Z DTR\n");
+#endif
+	    }
+	    
+	    if (info->tty){
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+	    }
+	    info->flags &= ~ASYNC_INITIALIZED;
+
+	CY_UNLOCK(info, flags);
+    }
+
+#ifdef CY_DEBUG_OPEN
+    printk(" cyc shutdown done\n");
+#endif
+    return;
+} /* shutdown */
+
+
+/*
+ * ------------------------------------------------------------
+ * cy_open() and friends
+ * ------------------------------------------------------------
+ */
+
+static int
+block_til_ready(struct tty_struct *tty, struct file * filp,
+                           struct cyclades_port *info)
+{
+  DECLARE_WAITQUEUE(wait, current);
+  struct cyclades_card *cinfo;
+  unsigned long flags;
+  int chip, channel,index;
+  int retval;
+  void __iomem *base_addr;
+
+    cinfo = &cy_card[info->card];
+    channel = info->line - cinfo->first_line;
+
+    /*
+     * 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) || (info->flags & ASYNC_CLOSING)) {
+	if (info->flags & ASYNC_CLOSING) {
+            interruptible_sleep_on(&info->close_wait);
+	}
+        return ((info->flags & ASYNC_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+    }
+
+    /*
+     * If non-blocking mode is set, then make the check up front
+     * and then exit.
+     */
+    if ((filp->f_flags & O_NONBLOCK) ||
+	(tty->flags & (1 << TTY_IO_ERROR))) {
+        info->flags |= ASYNC_NORMAL_ACTIVE;
+        return 0;
+    }
+
+    /*
+     * 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, info->count is dropped by one, so that
+     * cy_close() knows when to free things.  We restore it upon
+     * exit, either normal or abnormal.
+     */
+    retval = 0;
+    add_wait_queue(&info->open_wait, &wait);
+#ifdef CY_DEBUG_OPEN
+    printk("cyc block_til_ready before block: ttyC%d, count = %d\n",
+           info->line, info->count);/**/
+#endif
+    CY_LOCK(info, flags);
+    if (!tty_hung_up_p(filp))
+	info->count--;
+    CY_UNLOCK(info, flags);
+#ifdef CY_DEBUG_COUNT
+    printk("cyc block_til_ready: (%d): decrementing count to %d\n",
+        current->pid, info->count);
+#endif
+    info->blocked_open++;
+
+    if (!IS_CYC_Z(*cinfo)) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cinfo->bus_index;
+	base_addr = cinfo->base_addr + (cy_chip_offset[chip]<<index);
+
+	while (1) {
+	    CY_LOCK(info, flags);
+		if ((tty->termios->c_cflag & CBAUD)){
+		    cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+		    cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+		    cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+#ifdef CY_DEBUG_DTR
+		    printk("cyc:block_til_ready raising DTR\n");
+		    printk("     status: 0x%x, 0x%x\n",
+			cy_readb(base_addr+(CyMSVR1<<index)), 
+                        cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+		}
+	    CY_UNLOCK(info, flags);
+
+	    set_current_state(TASK_INTERRUPTIBLE);
+	    if (tty_hung_up_p(filp)
+	    || !(info->flags & ASYNC_INITIALIZED) ){
+		retval = ((info->flags & ASYNC_HUP_NOTIFY) ? 
+		    -EAGAIN : -ERESTARTSYS);
+		break;
+	    }
+
+	    CY_LOCK(info, flags);
+		cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+		if (!(info->flags & ASYNC_CLOSING)
+		&& (C_CLOCAL(tty)
+		    || (cy_readb(base_addr+(CyMSVR1<<index)) & CyDCD))) {
+			CY_UNLOCK(info, flags);
+			break;
+		}
+	    CY_UNLOCK(info, flags);
+
+	    if (signal_pending(current)) {
+		retval = -ERESTARTSYS;
+		break;
+	    }
+#ifdef CY_DEBUG_OPEN
+	    printk("cyc block_til_ready blocking: ttyC%d, count = %d\n",
+		   info->line, info->count);/**/
+#endif
+	    schedule();
+	}
+    } else {
+      struct FIRM_ID __iomem *firm_id;
+      struct ZFW_CTRL __iomem *zfw_ctrl;
+      struct BOARD_CTRL __iomem *board_ctrl;
+      struct CH_CTRL __iomem *ch_ctrl;
+      int retval;
+
+	base_addr = cinfo->base_addr;
+	firm_id = base_addr + ID_ADDRESS;
+        if (!ISZLOADED(*cinfo)){
+            current->state = TASK_RUNNING;
+	    remove_wait_queue(&info->open_wait, &wait);
+	    return -EINVAL;
+	}
+
+	zfw_ctrl = base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	board_ctrl = &zfw_ctrl->board_ctrl;
+	ch_ctrl = zfw_ctrl->ch_ctrl;
+
+	while (1) {
+	    if ((tty->termios->c_cflag & CBAUD)){
+		cy_writel(&ch_ctrl[channel].rs_control,
+			cy_readl(&ch_ctrl[channel].rs_control) |
+			(C_RS_RTS | C_RS_DTR));
+		retval = cyz_issue_cmd(&cy_card[info->card],
+				       channel, C_CM_IOCTLM, 0L);
+		if (retval != 0){
+		    printk("cyc:block_til_ready retval on ttyC%d was %x\n",
+			   info->line, retval);
+		}
+#ifdef CY_DEBUG_DTR
+		printk("cyc:block_til_ready raising Z DTR\n");
+#endif
+	    }
+
+	    set_current_state(TASK_INTERRUPTIBLE);
+	    if (tty_hung_up_p(filp)
+	    || !(info->flags & ASYNC_INITIALIZED) ){
+		retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+		    -EAGAIN : -ERESTARTSYS);
+		break;
+	    }
+	    if (!(info->flags & ASYNC_CLOSING)
+	    && (C_CLOCAL(tty)
+	      || (cy_readl(&ch_ctrl[channel].rs_status) & C_RS_DCD))) {
+		break;
+	    }
+	    if (signal_pending(current)) {
+		retval = -ERESTARTSYS;
+		break;
+	    }
+#ifdef CY_DEBUG_OPEN
+	    printk("cyc block_til_ready blocking: ttyC%d, count = %d\n",
+		   info->line, info->count);/**/
+#endif
+	    schedule();
+	}
+    }
+    current->state = TASK_RUNNING;
+    remove_wait_queue(&info->open_wait, &wait);
+    if (!tty_hung_up_p(filp)){
+	info->count++;
+#ifdef CY_DEBUG_COUNT
+	printk("cyc:block_til_ready (%d): incrementing count to %d\n",
+	    current->pid, info->count);
+#endif
+    }
+    info->blocked_open--;
+#ifdef CY_DEBUG_OPEN
+    printk("cyc:block_til_ready after blocking: ttyC%d, count = %d\n",
+	   info->line, info->count);/**/
+#endif
+    if (retval)
+	return retval;
+    info->flags |= ASYNC_NORMAL_ACTIVE;
+    return 0;
+} /* block_til_ready */
+
+
+/*
+ * This routine is called whenever a serial port is opened.  It
+ * performs the serial-specific initialization for the tty structure.
+ */
+static int
+cy_open(struct tty_struct *tty, struct file * filp)
+{
+  struct cyclades_port  *info;
+  int retval, line;
+  unsigned long page;
+
+    line = tty->index;
+    if ((line < 0) || (NR_PORTS <= line)){
+        return -ENODEV;
+    }
+    info = &cy_port[line];
+    if (info->line < 0){
+        return -ENODEV;
+    }
+    
+    /* If the card's firmware hasn't been loaded,
+       treat it as absent from the system.  This
+       will make the user pay attention.
+    */
+    if (IS_CYC_Z(cy_card[info->card])) {
+	struct cyclades_card *cinfo = &cy_card[info->card];
+	struct FIRM_ID __iomem *firm_id = cinfo->base_addr + ID_ADDRESS;
+
+        if (!ISZLOADED(*cinfo)) {
+	    if (((ZE_V1 ==cy_readl(&((struct RUNTIME_9060 __iomem *)
+		(cinfo->ctl_addr))->mail_box_0)) &&
+		Z_FPGA_CHECK (*cinfo)) &&
+		(ZFIRM_HLT == cy_readl (&firm_id->signature)))
+	    {
+		printk ("cyc:Cyclades-Z Error: you need an external power supply for this number of ports.\n\rFirmware halted.\r\n");
+	    } else {
+		printk("cyc:Cyclades-Z firmware not yet loaded\n");
+	    }
+	    return -ENODEV;
+	}
+#ifdef CONFIG_CYZ_INTR
+	else {
+	    /* In case this Z board is operating in interrupt mode, its 
+	       interrupts should be enabled as soon as the first open happens 
+	       to one of its ports. */
+            if (!cinfo->intr_enabled) {
+		struct ZFW_CTRL __iomem *zfw_ctrl;
+		struct BOARD_CTRL __iomem *board_ctrl;
+
+		zfw_ctrl = cinfo->base_addr + (cy_readl (&firm_id->zfwctrl_addr) & 0xfffff);
+
+		board_ctrl = &zfw_ctrl->board_ctrl;
+
+		/* Enable interrupts on the PLX chip */
+		cy_writew(cinfo->ctl_addr+0x68,
+			cy_readw(cinfo->ctl_addr+0x68)|0x0900);
+		/* Enable interrupts on the FW */
+		retval = cyz_issue_cmd(cinfo,
+					0, C_CM_IRQ_ENBL, 0L);
+		if (retval != 0){
+		    printk("cyc:IRQ enable retval was %x\n", retval);
+		}
+		cinfo->nports = (int) cy_readl (&board_ctrl->n_channel);
+		cinfo->intr_enabled = 1;
+	    }
+	}
+#endif /* CONFIG_CYZ_INTR */
+	/* Make sure this Z port really exists in hardware */
+	if (info->line > (cinfo->first_line + cinfo->nports - 1))
+		return -ENODEV;
+    }
+#ifdef CY_DEBUG_OTHER
+    printk("cyc:cy_open ttyC%d\n", info->line); /* */
+#endif
+    tty->driver_data = info;
+    info->tty = tty;
+    if (serial_paranoia_check(info, tty->name, "cy_open")){
+        return -ENODEV;
+    }
+#ifdef CY_DEBUG_OPEN
+    printk("cyc:cy_open ttyC%d, count = %d\n",
+        info->line, info->count);/**/
+#endif
+    info->count++;
+#ifdef CY_DEBUG_COUNT
+    printk("cyc:cy_open (%d): incrementing count to %d\n",
+        current->pid, info->count);
+#endif
+    if (!tmp_buf) {
+	page = get_zeroed_page(GFP_KERNEL);
+	if (!page)
+	    return -ENOMEM;
+	if (tmp_buf)
+	    free_page(page);
+	else
+	    tmp_buf = (unsigned char *) page;
+    }
+
+    /*
+     * If the port is the middle of closing, bail out now
+     */
+    if (tty_hung_up_p(filp) || (info->flags & ASYNC_CLOSING)) {
+	if (info->flags & ASYNC_CLOSING)
+	    interruptible_sleep_on(&info->close_wait);
+	return ((info->flags & ASYNC_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+    }
+
+    /*
+     * Start up serial port
+     */
+    retval = startup(info);
+    if (retval){
+        return retval;
+    }
+
+    retval = block_til_ready(tty, filp, info);
+    if (retval) {
+#ifdef CY_DEBUG_OPEN
+        printk("cyc:cy_open returning after block_til_ready with %d\n",
+               retval);
+#endif
+        return retval;
+    }
+
+    info->throttle = 0;
+
+#ifdef CY_DEBUG_OPEN
+    printk(" cyc:cy_open done\n");/**/
+#endif
+
+    return 0;
+} /* cy_open */
+
+
+/*
+ * cy_wait_until_sent() --- wait until the transmitter is empty
+ */
+static void 
+cy_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+  void __iomem *base_addr;
+  int card,chip,channel,index;
+  unsigned long orig_jiffies;
+  int char_time;
+	
+    if (serial_paranoia_check(info, tty->name, "cy_wait_until_sent"))
+	return;
+
+    if (info->xmit_fifo_size == 0)
+	return; /* Just in case.... */
+
+
+    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 = (info->timeout - HZ/50) / info->xmit_fifo_size;
+    char_time = char_time / 5;
+    if (char_time <= 0)
+	char_time = 1;
+    if (timeout < 0)
+	timeout = 0;
+    if (timeout)
+	char_time = min(char_time, timeout);
+    /*
+     * If the transmitter hasn't cleared in twice the approximate
+     * amount of time to send the entire FIFO, it probably won't
+     * ever clear.  This assumes the UART isn't doing flow
+     * control, which is currently the case.  Hence, if it ever
+     * takes longer than info->timeout, this is probably due to a
+     * UART bug of some kind.  So, we clamp the timeout parameter at
+     * 2*info->timeout.
+     */
+    if (!timeout || timeout > 2*info->timeout)
+	timeout = 2*info->timeout;
+#ifdef CY_DEBUG_WAIT_UNTIL_SENT
+    printk("In cy_wait_until_sent(%d) check=%lu...", timeout, char_time);
+    printk("jiff=%lu...", jiffies);
+#endif
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+	while (cy_readb(base_addr+(CySRER<<index)) & CyTxRdy) {
+#ifdef CY_DEBUG_WAIT_UNTIL_SENT
+	    printk("Not clean (jiff=%lu)...", jiffies);
+#endif
+	    if (msleep_interruptible(jiffies_to_msecs(char_time)))
+		break;
+	    if (timeout && time_after(jiffies, orig_jiffies + timeout))
+		break;
+	}
+    } else {
+	// Nothing to do!
+    }
+    /* Run one more char cycle */
+    msleep_interruptible(jiffies_to_msecs(char_time * 5));
+#ifdef CY_DEBUG_WAIT_UNTIL_SENT
+    printk("Clean (jiff=%lu)...done\n", jiffies);
+#endif
+}
+
+/*
+ * This routine is called when a particular tty device is closed.
+ */
+static void
+cy_close(struct tty_struct *tty, struct file *filp)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+
+#ifdef CY_DEBUG_OTHER
+    printk("cyc:cy_close ttyC%d\n", info->line);
+#endif
+
+    if (!info || serial_paranoia_check(info, tty->name, "cy_close")){
+        return;
+    }
+
+    CY_LOCK(info, flags);
+    /* If the TTY is being hung up, nothing to do */
+    if (tty_hung_up_p(filp)) {
+	CY_UNLOCK(info, flags);
+        return;
+    }
+        
+#ifdef CY_DEBUG_OPEN
+    printk("cyc:cy_close ttyC%d, count = %d\n", info->line, info->count);
+#endif
+    if ((tty->count == 1) && (info->count != 1)) {
+        /*
+         * Uh, oh.  tty->count is 1, which means that the tty
+         * structure will be freed.  Info->count should always
+         * be one in these conditions.  If it's greater than
+         * one, we've got real problems, since it means the
+         * serial port won't be shutdown.
+         */
+        printk("cyc:cy_close: bad serial port count; tty->count is 1, "
+           "info->count is %d\n", info->count);
+        info->count = 1;
+    }
+#ifdef CY_DEBUG_COUNT
+    printk("cyc:cy_close at (%d): decrementing count to %d\n",
+        current->pid, info->count - 1);
+#endif
+    if (--info->count < 0) {
+#ifdef CY_DEBUG_COUNT
+    printk("cyc:cyc_close setting count to 0\n");
+#endif
+        info->count = 0;
+    }
+    if (info->count) {
+	CY_UNLOCK(info, flags);
+        return;
+    }
+    info->flags |= ASYNC_CLOSING;
+
+    /*
+    * Now we wait for the transmit buffer to clear; and we notify
+    * the line discipline to only process XON/XOFF characters.
+    */
+    tty->closing = 1;
+    CY_UNLOCK(info, flags);
+    if (info->closing_wait != CY_CLOSING_WAIT_NONE) {
+	tty_wait_until_sent(tty, info->closing_wait);
+    }
+    CY_LOCK(info, flags);
+
+    if (!IS_CYC_Z(cy_card[info->card])) {
+	int channel = info->line - cy_card[info->card].first_line;
+	int index = cy_card[info->card].bus_index;
+	void __iomem *base_addr = cy_card[info->card].base_addr + (cy_chip_offset[channel>>2] << index);
+	/* Stop accepting input */
+	channel &= 0x03;
+	cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+	cy_writeb(base_addr+(CySRER<<index),
+			cy_readb(base_addr+(CySRER<<index)) & ~CyRxData);
+	if (info->flags & ASYNC_INITIALIZED) {
+	    /* Waiting for on-board buffers to be empty before closing 
+	       the port */
+	    CY_UNLOCK(info, flags);
+	    cy_wait_until_sent(tty, info->timeout);
+	    CY_LOCK(info, flags);
+	}
+    } else {
+#ifdef Z_WAKE
+	/* Waiting for on-board buffers to be empty before closing the port */
+	void __iomem *base_addr = cy_card[info->card].base_addr;
+	struct FIRM_ID __iomem *firm_id = base_addr + ID_ADDRESS;
+	struct ZFW_CTRL __iomem *zfw_ctrl = base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	struct CH_CTRL __iomem *ch_ctrl = zfw_ctrl->ch_ctrl;
+	int channel = info->line - cy_card[info->card].first_line;
+	int retval;
+
+	if (cy_readl(&ch_ctrl[channel].flow_status) != C_FS_TXIDLE) {
+	    retval = cyz_issue_cmd(&cy_card[info->card], channel, 
+				   C_CM_IOCTLW, 0L);
+	    if (retval != 0){
+		printk("cyc:cy_close retval on ttyC%d was %x\n",
+		       info->line, retval);
+	    }
+	    CY_UNLOCK(info, flags);
+	    interruptible_sleep_on(&info->shutdown_wait);
+	    CY_LOCK(info, flags);
+	}
+#endif
+    }
+
+    CY_UNLOCK(info, flags);
+    shutdown(info);
+    if (tty->driver->flush_buffer)
+        tty->driver->flush_buffer(tty);
+    tty_ldisc_flush(tty);        
+    CY_LOCK(info, flags);
+
+    tty->closing = 0;
+    info->event = 0;
+    info->tty = NULL;
+    if (info->blocked_open) {
+	CY_UNLOCK(info, flags);
+        if (info->close_delay) {
+            msleep_interruptible(jiffies_to_msecs(info->close_delay));
+        }
+        wake_up_interruptible(&info->open_wait);
+	CY_LOCK(info, flags);
+    }
+    info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+    wake_up_interruptible(&info->close_wait);
+
+#ifdef CY_DEBUG_OTHER
+    printk(" cyc:cy_close done\n");
+#endif
+
+    CY_UNLOCK(info, flags);
+    return;
+} /* cy_close */
+
+
+/* This routine gets called when tty_write has put something into
+ * the write_queue.  The characters may come from user space or
+ * kernel space.
+ *
+ * This routine will return the number of characters actually
+ * accepted for writing.
+ *
+ * If the port is not already transmitting stuff, start it off by
+ * enabling interrupts.  The interrupt service routine will then
+ * ensure that the characters are sent.
+ * If the port is already active, there is no need to kick it.
+ *
+ */
+static int
+cy_write(struct tty_struct * tty, const unsigned char *buf, int count)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+  int c, ret = 0;
+
+#ifdef CY_DEBUG_IO
+    printk("cyc:cy_write ttyC%d\n", info->line); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_write")){
+        return 0;
+    }
+        
+    if (!tty || !info->xmit_buf || !tmp_buf){
+        return 0;
+    }
+
+    CY_LOCK(info, flags);
+    while (1) {
+	c = min(count, min((int)(SERIAL_XMIT_SIZE - info->xmit_cnt - 1),
+			(int)(SERIAL_XMIT_SIZE - info->xmit_head)));
+	        
+	if (c <= 0)
+	    break;
+
+	memcpy(info->xmit_buf + info->xmit_head, buf, c);
+	info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+	info->xmit_cnt += c;
+	buf += c;
+	count -= c;
+	ret += c;
+    }
+    CY_UNLOCK(info, flags);
+
+    info->idle_stats.xmit_bytes += ret;
+    info->idle_stats.xmit_idle   = jiffies;
+
+    if (info->xmit_cnt && !tty->stopped && !tty->hw_stopped) {
+        start_xmit(info);
+    }
+    return ret;
+} /* cy_write */
+
+
+/*
+ * This routine is called by the kernel to write a single
+ * character to the tty device.  If the kernel uses this routine,
+ * it must call the flush_chars() routine (if defined) when it is
+ * done stuffing characters into the driver.  If there is no room
+ * in the queue, the character is ignored.
+ */
+static void
+cy_put_char(struct tty_struct *tty, unsigned char ch)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+
+#ifdef CY_DEBUG_IO
+    printk("cyc:cy_put_char ttyC%d\n", info->line);
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_put_char"))
+        return;
+
+    if (!tty || !info->xmit_buf)
+        return;
+
+    CY_LOCK(info, flags);
+        if (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+	    CY_UNLOCK(info, flags);
+            return;
+        }
+
+        info->xmit_buf[info->xmit_head++] = ch;
+        info->xmit_head &= SERIAL_XMIT_SIZE - 1;
+        info->xmit_cnt++;
+	info->idle_stats.xmit_bytes++;
+	info->idle_stats.xmit_idle = jiffies;
+    CY_UNLOCK(info, flags);
+} /* cy_put_char */
+
+
+/*
+ * This routine is called by the kernel after it has written a
+ * series of characters to the tty device using put_char().  
+ */
+static void
+cy_flush_chars(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+                                
+#ifdef CY_DEBUG_IO
+    printk("cyc:cy_flush_chars ttyC%d\n", info->line); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_flush_chars"))
+        return;
+
+    if (info->xmit_cnt <= 0 || tty->stopped
+    || tty->hw_stopped || !info->xmit_buf)
+        return;
+
+    start_xmit(info);
+} /* cy_flush_chars */
+
+
+/*
+ * This routine returns the numbers of characters the tty driver
+ * will accept for queuing to be written.  This number is subject
+ * to change as output buffers get emptied, or if the output flow
+ * control is activated.
+ */
+static int
+cy_write_room(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  int   ret;
+                                
+#ifdef CY_DEBUG_IO
+    printk("cyc:cy_write_room ttyC%d\n", info->line); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_write_room"))
+        return 0;
+    ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
+    if (ret < 0)
+        ret = 0;
+    return ret;
+} /* cy_write_room */
+
+
+static int
+cy_chars_in_buffer(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  int card, channel;
+                                
+    if (serial_paranoia_check(info, tty->name, "cy_chars_in_buffer"))
+        return 0;
+
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+
+#ifdef Z_EXT_CHARS_IN_BUFFER
+    if (!IS_CYC_Z(cy_card[card])) {
+#endif /* Z_EXT_CHARS_IN_BUFFER */
+#ifdef CY_DEBUG_IO
+	printk("cyc:cy_chars_in_buffer ttyC%d %d\n",
+		info->line, info->xmit_cnt); /* */
+#endif
+	return info->xmit_cnt;
+#ifdef Z_EXT_CHARS_IN_BUFFER
+    } else {
+	static volatile struct FIRM_ID *firm_id;
+	static volatile struct ZFW_CTRL *zfw_ctrl;
+	static volatile struct CH_CTRL *ch_ctrl;
+	static volatile struct BUF_CTRL *buf_ctrl;
+	int char_count;
+	volatile uclong tx_put, tx_get, tx_bufsize;
+
+	firm_id = cy_card[card].base_addr + ID_ADDRESS;
+	zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	ch_ctrl = &(zfw_ctrl->ch_ctrl[channel]);
+	buf_ctrl = &(zfw_ctrl->buf_ctrl[channel]);
+
+	tx_get = cy_readl(&buf_ctrl->tx_get);
+	tx_put = cy_readl(&buf_ctrl->tx_put);
+	tx_bufsize = cy_readl(&buf_ctrl->tx_bufsize);
+	if (tx_put >= tx_get)
+	    char_count = tx_put - tx_get;
+	else
+	    char_count = tx_put - tx_get + tx_bufsize;
+#ifdef CY_DEBUG_IO
+	printk("cyc:cy_chars_in_buffer ttyC%d %d\n",
+		info->line, info->xmit_cnt + char_count); /* */
+#endif
+	return (info->xmit_cnt + char_count);
+    }
+#endif /* Z_EXT_CHARS_IN_BUFFER */
+} /* cy_chars_in_buffer */
+
+
+/*
+ * ------------------------------------------------------------
+ * cy_ioctl() and friends
+ * ------------------------------------------------------------
+ */
+
+static void
+cyy_baud_calc(struct cyclades_port *info, uclong baud)
+{
+    int co, co_val, bpr;
+    uclong cy_clock = ((info->chip_rev >= CD1400_REV_J) ? 60000000 : 25000000);
+
+    if (baud == 0) {
+	info->tbpr = info->tco = info->rbpr = info->rco = 0;
+	return;
+    }
+
+    /* determine which prescaler to use */
+    for (co = 4, co_val = 2048; co; co--, co_val >>= 2) {
+	if (cy_clock / co_val / baud > 63)
+	    break;
+    }
+
+    bpr = (cy_clock / co_val * 2 / baud + 1) / 2;
+    if (bpr > 255)
+	bpr = 255;
+
+    info->tbpr = info->rbpr = bpr;
+    info->tco = info->rco = co;
+}
+
+/*
+ * This routine finds or computes the various line characteristics.
+ * It used to be called config_setup
+ */
+static void
+set_line_char(struct cyclades_port * info)
+{
+  unsigned long flags;
+  void __iomem *base_addr;
+  int card,chip,channel,index;
+  unsigned cflag, iflag;
+  unsigned short chip_number;
+  int baud, baud_rate = 0;
+  int   i;
+
+
+    if (!info->tty || !info->tty->termios){
+        return;
+    }
+    if (info->line == -1){
+        return;
+    }
+    cflag = info->tty->termios->c_cflag;
+    iflag = info->tty->termios->c_iflag;
+
+    /*
+     * Set up the tty->alt_speed kludge
+     */
+    if (info->tty) {
+	if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+	    info->tty->alt_speed = 57600;
+	if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+	    info->tty->alt_speed = 115200;
+	if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+	    info->tty->alt_speed = 230400;
+	if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+	    info->tty->alt_speed = 460800;
+    }
+
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+    chip_number = channel / 4;
+
+    if (!IS_CYC_Z(cy_card[card])) {
+
+	index = cy_card[card].bus_index;
+
+	/* baud rate */
+	baud = tty_get_baud_rate(info->tty);
+	if ((baud == 38400) &&
+	    ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+	    if (info->custom_divisor)
+		baud_rate = info->baud / info->custom_divisor;
+	    else
+		baud_rate = info->baud;
+	} else if (baud > CD1400_MAX_SPEED) {
+	    baud = CD1400_MAX_SPEED;
+	}
+	/* find the baud index */
+	for (i = 0; i < 20; i++) {
+	    if (baud == baud_table[i]) {
+		break;
+	    }
+	}
+	if (i == 20) {
+	    i = 19; /* CD1400_MAX_SPEED */
+	} 
+
+	if ((baud == 38400) &&
+	    ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+	    cyy_baud_calc(info, baud_rate);
+	} else {
+	    if(info->chip_rev >= CD1400_REV_J) {
+		/* It is a CD1400 rev. J or later */
+		info->tbpr = baud_bpr_60[i]; /* Tx BPR */
+		info->tco = baud_co_60[i]; /* Tx CO */
+		info->rbpr = baud_bpr_60[i]; /* Rx BPR */
+		info->rco = baud_co_60[i]; /* Rx CO */
+	    } else {
+		info->tbpr = baud_bpr_25[i]; /* Tx BPR */
+		info->tco = baud_co_25[i]; /* Tx CO */
+		info->rbpr = baud_bpr_25[i]; /* Rx BPR */
+		info->rco = baud_co_25[i]; /* Rx CO */
+	    }
+	}
+	if (baud_table[i] == 134) {
+	    /* get it right for 134.5 baud */
+	    info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2;
+	} else if ((baud == 38400) &&
+		   ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+	    info->timeout = (info->xmit_fifo_size*HZ*15/baud_rate) + 2;
+	} else if (baud_table[i]) {
+	    info->timeout = (info->xmit_fifo_size*HZ*15/baud_table[i]) + 2;
+	    /* this needs to be propagated into the card info */
+	} else {
+	    info->timeout = 0;
+	}
+	/* By tradition (is it a standard?) a baud rate of zero
+	   implies the line should be/has been closed.  A bit
+	   later in this routine such a test is performed. */
+
+	/* byte size and parity */
+	info->cor5 = 0;
+	info->cor4 = 0;
+	info->cor3 = (info->default_threshold
+		      ? info->default_threshold
+		      : baud_cor3[i]); /* receive threshold */
+	info->cor2 = CyETC;
+	switch(cflag & CSIZE){
+	case CS5:
+	    info->cor1 = Cy_5_BITS;
+	    break;
+	case CS6:
+	    info->cor1 = Cy_6_BITS;
+	    break;
+	case CS7:
+	    info->cor1 = Cy_7_BITS;
+	    break;
+	case CS8:
+	    info->cor1 = Cy_8_BITS;
+	    break;
+	}
+	if(cflag & CSTOPB){
+	    info->cor1 |= Cy_2_STOP;
+	}
+	if (cflag & PARENB){
+	    if (cflag & PARODD){
+		info->cor1 |= CyPARITY_O;
+	    }else{
+		info->cor1 |= CyPARITY_E;
+	    }
+	}else{
+	    info->cor1 |= CyPARITY_NONE;
+	}
+	    
+	/* CTS flow control flag */
+	if (cflag & CRTSCTS){
+	    info->flags |= ASYNC_CTS_FLOW;
+	    info->cor2 |= CyCtsAE;
+	}else{
+	    info->flags &= ~ASYNC_CTS_FLOW;
+	    info->cor2 &= ~CyCtsAE;
+	}
+	if (cflag & CLOCAL)
+	    info->flags &= ~ASYNC_CHECK_CD;
+	else
+	    info->flags |= ASYNC_CHECK_CD;
+
+	 /***********************************************
+	    The hardware option, CyRtsAO, presents RTS when
+	    the chip has characters to send.  Since most modems
+	    use RTS as reverse (inbound) flow control, this
+	    option is not used.  If inbound flow control is
+	    necessary, DTR can be programmed to provide the
+	    appropriate signals for use with a non-standard
+	    cable.  Contact Marcio Saito for details.
+	 ***********************************************/
+
+	chip = channel>>2;
+	channel &= 0x03;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	CY_LOCK(info, flags);
+	    cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+
+	   /* tx and rx baud rate */
+
+	    cy_writeb(base_addr+(CyTCOR<<index), info->tco);
+	    cy_writeb(base_addr+(CyTBPR<<index), info->tbpr);
+	    cy_writeb(base_addr+(CyRCOR<<index), info->rco);
+	    cy_writeb(base_addr+(CyRBPR<<index), info->rbpr);
+
+	    /* set line characteristics  according configuration */
+
+	    cy_writeb(base_addr+(CySCHR1<<index), 
+		      START_CHAR(info->tty));
+	    cy_writeb(base_addr+(CySCHR2<<index), 
+		      STOP_CHAR(info->tty));
+	    cy_writeb(base_addr+(CyCOR1<<index), info->cor1);
+	    cy_writeb(base_addr+(CyCOR2<<index), info->cor2);
+	    cy_writeb(base_addr+(CyCOR3<<index), info->cor3);
+	    cy_writeb(base_addr+(CyCOR4<<index), info->cor4);
+	    cy_writeb(base_addr+(CyCOR5<<index), info->cor5);
+
+	    cyy_issue_cmd(base_addr,
+		     CyCOR_CHANGE|CyCOR1ch|CyCOR2ch|CyCOR3ch,index);
+
+	    cy_writeb(base_addr+(CyCAR<<index), 
+		      (u_char)channel); /* !!! Is this needed? */
+	    cy_writeb(base_addr+(CyRTPR<<index), (info->default_timeout
+					         ? info->default_timeout
+					         : 0x02)); /* 10ms rx timeout */
+
+	    if (C_CLOCAL(info->tty)) {
+		/* without modem intr */
+		cy_writeb(base_addr+(CySRER<<index),
+                   cy_readb(base_addr+(CySRER<<index)) | CyMdmCh); 
+					/* act on 1->0 modem transitions */
+                if ((cflag & CRTSCTS) && info->rflow) {
+                        cy_writeb(base_addr+(CyMCOR1<<index), 
+                                  (CyCTS|rflow_thr[i]));
+                } else {
+                        cy_writeb(base_addr+(CyMCOR1<<index), CyCTS);
+                }
+					/* act on 0->1 modem transitions */
+		cy_writeb(base_addr+(CyMCOR2<<index), CyCTS);
+	    } else {
+		/* without modem intr */
+		cy_writeb(base_addr+(CySRER<<index),
+                   cy_readb(base_addr+(CySRER<<index)) | CyMdmCh); 
+					/* act on 1->0 modem transitions */
+                if ((cflag & CRTSCTS) && info->rflow) {
+			cy_writeb(base_addr+(CyMCOR1<<index), 
+        	                  (CyDSR|CyCTS|CyRI|CyDCD|rflow_thr[i]));
+                } else {
+			cy_writeb(base_addr+(CyMCOR1<<index), 
+                                  CyDSR|CyCTS|CyRI|CyDCD);
+                }
+					/* act on 0->1 modem transitions */
+		cy_writeb(base_addr+(CyMCOR2<<index), 
+			  CyDSR|CyCTS|CyRI|CyDCD);
+	    }
+
+	    if(i == 0){ /* baud rate is zero, turn off line */
+	        if (info->rtsdtr_inv) {
+			cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+		} else {
+                        cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+		}
+#ifdef CY_DEBUG_DTR
+		printk("cyc:set_line_char dropping DTR\n");
+		printk("     status: 0x%x, 0x%x\n", 
+		    cy_readb(base_addr+(CyMSVR1<<index)),
+		    cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+	    }else{
+                if (info->rtsdtr_inv) {
+			cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+                } else {
+			cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+                }
+#ifdef CY_DEBUG_DTR
+		printk("cyc:set_line_char raising DTR\n");
+		printk("     status: 0x%x, 0x%x\n",
+		    cy_readb(base_addr+(CyMSVR1<<index)),
+		    cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+	    }
+
+	    if (info->tty){
+		clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	    }
+	CY_UNLOCK(info, flags);
+
+    } else {
+      struct FIRM_ID __iomem *firm_id;
+      struct ZFW_CTRL __iomem *zfw_ctrl;
+      struct BOARD_CTRL __iomem *board_ctrl;
+      struct CH_CTRL __iomem *ch_ctrl;
+      struct BUF_CTRL __iomem *buf_ctrl;
+      uclong sw_flow;
+      int retval;
+
+        firm_id = cy_card[card].base_addr + ID_ADDRESS;
+        if (!ISZLOADED(cy_card[card])) {
+	    return;
+	}
+
+	zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	board_ctrl = &zfw_ctrl->board_ctrl;
+	ch_ctrl = &(zfw_ctrl->ch_ctrl[channel]);
+	buf_ctrl = &zfw_ctrl->buf_ctrl[channel];
+
+	/* baud rate */
+	baud = tty_get_baud_rate(info->tty);
+	if ((baud == 38400) &&
+	    ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+	    if (info->custom_divisor)
+		baud_rate = info->baud / info->custom_divisor;
+	    else
+		baud_rate = info->baud;
+	} else if (baud > CYZ_MAX_SPEED) {
+	    baud = CYZ_MAX_SPEED;
+	}
+	cy_writel(&ch_ctrl->comm_baud , baud);
+
+	if (baud == 134) {
+	    /* get it right for 134.5 baud */
+	    info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2;
+	} else if ((baud == 38400) &&
+		   ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)) {
+	    info->timeout = (info->xmit_fifo_size*HZ*15/baud_rate) + 2;
+	} else if (baud) {
+	    info->timeout = (info->xmit_fifo_size*HZ*15/baud) + 2;
+	    /* this needs to be propagated into the card info */
+	} else {
+	    info->timeout = 0;
+	}
+
+	/* byte size and parity */
+	switch(cflag & CSIZE){
+	case CS5: cy_writel(&ch_ctrl->comm_data_l , C_DL_CS5); break;
+	case CS6: cy_writel(&ch_ctrl->comm_data_l , C_DL_CS6); break;
+	case CS7: cy_writel(&ch_ctrl->comm_data_l , C_DL_CS7); break;
+	case CS8: cy_writel(&ch_ctrl->comm_data_l , C_DL_CS8); break;
+	}
+	if(cflag & CSTOPB){
+	    cy_writel(&ch_ctrl->comm_data_l,
+               cy_readl(&ch_ctrl->comm_data_l) | C_DL_2STOP);
+	}else{
+	    cy_writel(&ch_ctrl->comm_data_l,
+               cy_readl(&ch_ctrl->comm_data_l) | C_DL_1STOP);
+	}
+	if (cflag & PARENB){
+	    if (cflag & PARODD){
+		cy_writel(&ch_ctrl->comm_parity , C_PR_ODD);
+	    }else{
+		cy_writel(&ch_ctrl->comm_parity , C_PR_EVEN);
+	    }
+	}else{
+	    cy_writel(&ch_ctrl->comm_parity , C_PR_NONE);
+	}
+
+	/* CTS flow control flag */
+	if (cflag & CRTSCTS){
+	    cy_writel(&ch_ctrl->hw_flow,
+               cy_readl(&ch_ctrl->hw_flow) | C_RS_CTS | C_RS_RTS);
+	}else{
+	    cy_writel(&ch_ctrl->hw_flow,
+               cy_readl(&ch_ctrl->hw_flow) & ~(C_RS_CTS | C_RS_RTS));
+	}
+	/* As the HW flow control is done in firmware, the driver doesn't
+	   need to care about it */
+	info->flags &= ~ASYNC_CTS_FLOW;
+
+	/* XON/XOFF/XANY flow control flags */
+	sw_flow = 0;
+	if (iflag & IXON){
+	    sw_flow |= C_FL_OXX;
+	    if (iflag & IXANY)
+		sw_flow |= C_FL_OIXANY;
+	}
+	cy_writel(&ch_ctrl->sw_flow, sw_flow);
+
+	retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_IOCTL, 0L);
+	if (retval != 0){
+	    printk("cyc:set_line_char retval on ttyC%d was %x\n",
+		   info->line, retval);
+	}
+
+	/* CD sensitivity */
+	if (cflag & CLOCAL){
+	    info->flags &= ~ASYNC_CHECK_CD;
+	}else{
+	    info->flags |= ASYNC_CHECK_CD;
+	}
+
+	if(baud == 0){ /* baud rate is zero, turn off line */
+	    cy_writel(&ch_ctrl->rs_control,
+               cy_readl(&ch_ctrl->rs_control) & ~C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+	    printk("cyc:set_line_char dropping Z DTR\n");
+#endif
+	}else{
+	    cy_writel(&ch_ctrl->rs_control,
+               cy_readl(&ch_ctrl->rs_control) | C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+	    printk("cyc:set_line_char raising Z DTR\n");
+#endif
+	}
+
+	retval = cyz_issue_cmd( &cy_card[card], channel, C_CM_IOCTLM, 0L);
+	if (retval != 0){
+	    printk("cyc:set_line_char(2) retval on ttyC%d was %x\n",
+		   info->line, retval);
+	}
+
+	if (info->tty){
+	    clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	}
+    }
+} /* set_line_char */
+
+
+static int
+get_serial_info(struct cyclades_port * info,
+                           struct serial_struct __user * retinfo)
+{
+  struct serial_struct tmp;
+  struct cyclades_card *cinfo = &cy_card[info->card];
+
+    if (!retinfo)
+            return -EFAULT;
+    memset(&tmp, 0, sizeof(tmp));
+    tmp.type = info->type;
+    tmp.line = info->line;
+    tmp.port = info->card * 0x100 + info->line - cinfo->first_line;
+    tmp.irq = cinfo->irq;
+    tmp.flags = info->flags;
+    tmp.close_delay = info->close_delay;
+    tmp.baud_base = info->baud;
+    tmp.custom_divisor = info->custom_divisor;
+    tmp.hub6 = 0;               /*!!!*/
+    return copy_to_user(retinfo,&tmp,sizeof(*retinfo))?-EFAULT:0;
+} /* get_serial_info */
+
+
+static int
+set_serial_info(struct cyclades_port * info,
+                           struct serial_struct __user * new_info)
+{
+  struct serial_struct new_serial;
+  struct cyclades_port old_info;
+
+    if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+	return -EFAULT;
+    old_info = *info;
+
+    if (!capable(CAP_SYS_ADMIN)) {
+            if ((new_serial.close_delay != info->close_delay) ||
+		(new_serial.baud_base != info->baud) ||
+		((new_serial.flags & ASYNC_FLAGS & ~ASYNC_USR_MASK) !=
+		 (info->flags & ASYNC_FLAGS & ~ASYNC_USR_MASK)))
+                    return -EPERM;
+            info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+                           (new_serial.flags & ASYNC_USR_MASK));
+            info->baud = new_serial.baud_base;
+	    info->custom_divisor = new_serial.custom_divisor;
+            goto check_and_exit;
+    }
+
+
+    /*
+     * OK, past this point, all the error checking has been done.
+     * At this point, we start making changes.....
+     */
+
+    info->baud = new_serial.baud_base;
+    info->custom_divisor = new_serial.custom_divisor;
+    info->flags = ((info->flags & ~ASYNC_FLAGS) |
+                    (new_serial.flags & ASYNC_FLAGS));
+    info->close_delay = new_serial.close_delay * HZ/100;
+    info->closing_wait = new_serial.closing_wait * HZ/100;
+
+check_and_exit:
+    if (info->flags & ASYNC_INITIALIZED){
+        set_line_char(info);
+        return 0;
+    }else{
+        return startup(info);
+    }
+} /* set_serial_info */
+
+/*
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ *	    is emptied.  On bus types like RS485, the transmitter must
+ *	    release the bus after transmitting. This must be done when
+ *	    the transmit shift register is empty, not be done when the
+ *	    transmit holding register is empty.  This functionality
+ *	    allows an RS485 driver to be written in user space.
+ */
+static int get_lsr_info(struct cyclades_port *info, unsigned int __user *value)
+{
+    int card, chip, channel, index;
+    unsigned char status;
+    unsigned int result;
+    unsigned long flags;
+    void __iomem *base_addr;
+
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	CY_LOCK(info, flags);
+	status = cy_readb(base_addr+(CySRER<<index)) & (CyTxRdy|CyTxMpty);
+	CY_UNLOCK(info, flags);
+	result = (status ? 0 : TIOCSER_TEMT);
+    } else {
+	/* Not supported yet */
+	return -EINVAL;
+    }
+    return put_user(result, (unsigned long __user *) value);
+}
+
+static int
+cy_tiocmget(struct tty_struct *tty, struct file *file)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+  int card,chip,channel,index;
+  void __iomem *base_addr;
+  unsigned long flags;
+  unsigned char status;
+  unsigned long lstatus;
+  unsigned int result;
+  struct FIRM_ID __iomem *firm_id;
+  struct ZFW_CTRL __iomem *zfw_ctrl;
+  struct BOARD_CTRL __iomem *board_ctrl;
+  struct CH_CTRL __iomem *ch_ctrl;
+
+    if (serial_paranoia_check(info, tty->name, __FUNCTION__))
+	return -ENODEV;
+
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	CY_LOCK(info, flags);
+	    cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+	    status = cy_readb(base_addr+(CyMSVR1<<index));
+	    status |= cy_readb(base_addr+(CyMSVR2<<index));
+	CY_UNLOCK(info, flags);
+
+        if (info->rtsdtr_inv) {
+	    result =  ((status  & CyRTS) ? TIOCM_DTR : 0)
+		    | ((status  & CyDTR) ? TIOCM_RTS : 0);
+	} else {
+	    result =  ((status  & CyRTS) ? TIOCM_RTS : 0)
+		    | ((status  & CyDTR) ? TIOCM_DTR : 0);
+	}
+	result |=  ((status  & CyDCD) ? TIOCM_CAR : 0)
+		 | ((status  & CyRI) ? TIOCM_RNG : 0)
+		 | ((status  & CyDSR) ? TIOCM_DSR : 0)
+		 | ((status  & CyCTS) ? TIOCM_CTS : 0);
+    } else {
+	base_addr = cy_card[card].base_addr;
+
+        if (cy_card[card].num_chips != -1){
+	    return -EINVAL;
+	}
+
+	firm_id = cy_card[card].base_addr + ID_ADDRESS;
+        if (ISZLOADED(cy_card[card])) {
+	    zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	    board_ctrl = &zfw_ctrl->board_ctrl;
+	    ch_ctrl = zfw_ctrl->ch_ctrl;
+	    lstatus = cy_readl(&ch_ctrl[channel].rs_status);
+	    result =  ((lstatus  & C_RS_RTS) ? TIOCM_RTS : 0)
+		    | ((lstatus  & C_RS_DTR) ? TIOCM_DTR : 0)
+	            | ((lstatus  & C_RS_DCD) ? TIOCM_CAR : 0)
+		    | ((lstatus  & C_RS_RI) ? TIOCM_RNG : 0)
+		    | ((lstatus  & C_RS_DSR) ? TIOCM_DSR : 0)
+		    | ((lstatus  & C_RS_CTS) ? TIOCM_CTS : 0);
+	}else{
+	    result = 0;
+	    return -ENODEV;
+	}
+
+    }
+    return result;
+} /* cy_tiomget */
+
+
+static int
+cy_tiocmset(struct tty_struct *tty, struct file *file,
+            unsigned int set, unsigned int clear)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+  int card,chip,channel,index;
+  void __iomem *base_addr;
+  unsigned long flags;
+  struct FIRM_ID __iomem *firm_id;
+  struct ZFW_CTRL __iomem *zfw_ctrl;
+  struct BOARD_CTRL __iomem *board_ctrl;
+  struct CH_CTRL __iomem *ch_ctrl;
+  int retval;
+
+    if (serial_paranoia_check(info, tty->name, __FUNCTION__))
+	return -ENODEV;
+
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	if (set & TIOCM_RTS){
+		CY_LOCK(info, flags);
+	        cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+                if (info->rtsdtr_inv) {
+			cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+                } else {
+			cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+                }
+		CY_UNLOCK(info, flags);
+	}
+	if (clear & TIOCM_RTS) {
+		CY_LOCK(info, flags);
+		cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+                if (info->rtsdtr_inv) {
+			cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+                } else {
+			cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+                }
+		CY_UNLOCK(info, flags);
+	}
+	if (set & TIOCM_DTR){
+		CY_LOCK(info, flags);
+		cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+                if (info->rtsdtr_inv) {
+			cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+                } else {
+			cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+                }
+#ifdef CY_DEBUG_DTR
+		printk("cyc:set_modem_info raising DTR\n");
+		printk("     status: 0x%x, 0x%x\n",
+		    cy_readb(base_addr+(CyMSVR1<<index)), 
+                    cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+		CY_UNLOCK(info, flags);
+	}
+	if (clear & TIOCM_DTR) {
+		CY_LOCK(info, flags);
+		cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+                if (info->rtsdtr_inv) {
+			cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+                } else {
+			cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+                }
+
+#ifdef CY_DEBUG_DTR
+		printk("cyc:set_modem_info dropping DTR\n");
+		printk("     status: 0x%x, 0x%x\n",
+		    cy_readb(base_addr+(CyMSVR1<<index)), 
+                    cy_readb(base_addr+(CyMSVR2<<index)));
+#endif
+		CY_UNLOCK(info, flags);
+	}
+    } else {
+	base_addr = cy_card[card].base_addr;
+
+	firm_id = cy_card[card].base_addr + ID_ADDRESS;
+        if (ISZLOADED(cy_card[card])) {
+	    zfw_ctrl = cy_card[card].base_addr + (cy_readl(&firm_id->zfwctrl_addr) & 0xfffff);
+	    board_ctrl = &zfw_ctrl->board_ctrl;
+	    ch_ctrl = zfw_ctrl->ch_ctrl;
+
+	    if (set & TIOCM_RTS){
+		    CY_LOCK(info, flags);
+		    cy_writel(&ch_ctrl[channel].rs_control,
+                       cy_readl(&ch_ctrl[channel].rs_control) | C_RS_RTS);
+		    CY_UNLOCK(info, flags);
+	    }
+	    if (clear & TIOCM_RTS) {
+		    CY_LOCK(info, flags);
+		    cy_writel(&ch_ctrl[channel].rs_control,
+                       cy_readl(&ch_ctrl[channel].rs_control) & ~C_RS_RTS);
+		    CY_UNLOCK(info, flags);
+	    }
+	    if (set & TIOCM_DTR){
+		    CY_LOCK(info, flags);
+		    cy_writel(&ch_ctrl[channel].rs_control,
+                       cy_readl(&ch_ctrl[channel].rs_control) | C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+		    printk("cyc:set_modem_info raising Z DTR\n");
+#endif
+		    CY_UNLOCK(info, flags);
+	    }
+	    if (clear & TIOCM_DTR) {
+		    CY_LOCK(info, flags);
+		    cy_writel(&ch_ctrl[channel].rs_control,
+                       cy_readl(&ch_ctrl[channel].rs_control) & ~C_RS_DTR);
+#ifdef CY_DEBUG_DTR
+		    printk("cyc:set_modem_info clearing Z DTR\n");
+#endif
+		    CY_UNLOCK(info, flags);
+	    }
+	}else{
+	    return -ENODEV;
+	}
+	CY_LOCK(info, flags);
+        retval = cyz_issue_cmd(&cy_card[info->card],
+				    channel, C_CM_IOCTLM,0L);
+	if (retval != 0){
+	    printk("cyc:set_modem_info retval on ttyC%d was %x\n",
+		   info->line, retval);
+	}
+	CY_UNLOCK(info, flags);
+    }
+    return 0;
+} /* cy_tiocmset */
+
+/*
+ * cy_break() --- routine which turns the break handling on or off
+ */
+static void
+cy_break(struct tty_struct *tty, int break_state)
+{
+    struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+    unsigned long flags;
+
+    if (serial_paranoia_check(info, tty->name, "cy_break"))
+	return;
+
+    CY_LOCK(info, flags);
+    if (!IS_CYC_Z(cy_card[info->card])) {
+        /* Let the transmit ISR take care of this (since it
+	   requires stuffing characters into the output stream).
+        */
+	if (break_state == -1) {
+	    if (!info->breakon) {
+		info->breakon = 1;
+		if (!info->xmit_cnt) {
+		    CY_UNLOCK(info, flags);
+		    start_xmit(info);
+		    CY_LOCK(info, flags);
+		}
+	    }
+	} else {
+	    if (!info->breakoff) {
+		info->breakoff = 1;
+		if (!info->xmit_cnt) {
+		    CY_UNLOCK(info, flags);
+		    start_xmit(info);
+		    CY_LOCK(info, flags);
+		}
+	    }
+	}
+    } else {
+	int retval;
+
+	if (break_state == -1) {
+	    retval = cyz_issue_cmd(&cy_card[info->card],
+		(info->line) - (cy_card[info->card].first_line),
+		C_CM_SET_BREAK, 0L);
+	    if (retval != 0) {
+		printk("cyc:cy_break (set) retval on ttyC%d was %x\n",
+		       info->line, retval);
+	    }
+	} else {
+	    retval = cyz_issue_cmd(&cy_card[info->card],
+		(info->line) - (cy_card[info->card].first_line),
+		C_CM_CLR_BREAK, 0L);
+	    if (retval != 0) {
+		printk("cyc:cy_break (clr) retval on ttyC%d was %x\n",
+		       info->line, retval);
+	    }
+	}
+    }
+    CY_UNLOCK(info, flags);
+} /* cy_break */
+
+static int
+get_mon_info(struct cyclades_port * info, struct cyclades_monitor __user * mon)
+{
+
+    if(copy_to_user(mon, &info->mon, sizeof(struct cyclades_monitor)))
+    	return -EFAULT;
+    info->mon.int_count  = 0;
+    info->mon.char_count = 0;
+    info->mon.char_max   = 0;
+    info->mon.char_last  = 0;
+    return 0;
+}/* get_mon_info */
+
+
+static int
+set_threshold(struct cyclades_port * info, unsigned long value)
+{
+  void __iomem *base_addr;
+  int card,channel,chip,index;
+  unsigned long flags;
+   
+    card = info->card;
+    channel = info->line - cy_card[card].first_line;
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	info->cor3 &= ~CyREC_FIFO;
+	info->cor3 |= value & CyREC_FIFO;
+
+	CY_LOCK(info, flags);
+	    cy_writeb(base_addr+(CyCOR3<<index), info->cor3);
+	    cyy_issue_cmd(base_addr,CyCOR_CHANGE|CyCOR3ch,index);
+	CY_UNLOCK(info, flags);
+    } else {
+	// Nothing to do!
+    }
+    return 0;
+}/* set_threshold */
+
+
+static int
+get_threshold(struct cyclades_port * info, unsigned long __user *value)
+{
+  void __iomem *base_addr;
+  int card,channel,chip,index;
+  unsigned long tmp;
+   
+    card = info->card;
+    channel = info->line - cy_card[card].first_line;
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	tmp = cy_readb(base_addr+(CyCOR3<<index)) & CyREC_FIFO;
+	return put_user(tmp,value);
+    } else {
+	// Nothing to do!
+	return 0;
+    }
+}/* get_threshold */
+
+
+static int
+set_default_threshold(struct cyclades_port * info, unsigned long value)
+{
+    info->default_threshold = value & 0x0f;
+    return 0;
+}/* set_default_threshold */
+
+
+static int
+get_default_threshold(struct cyclades_port * info, unsigned long __user *value)
+{
+    return put_user(info->default_threshold,value);
+}/* get_default_threshold */
+
+
+static int
+set_timeout(struct cyclades_port * info, unsigned long value)
+{
+  void __iomem *base_addr;
+  int card,channel,chip,index;
+  unsigned long flags;
+   
+    card = info->card;
+    channel = info->line - cy_card[card].first_line;
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	CY_LOCK(info, flags);
+	    cy_writeb(base_addr+(CyRTPR<<index), value & 0xff);
+	CY_UNLOCK(info, flags);
+    } else {
+	// Nothing to do!
+    }
+    return 0;
+}/* set_timeout */
+
+
+static int
+get_timeout(struct cyclades_port * info, unsigned long __user *value)
+{
+  void __iomem *base_addr;
+  int card,channel,chip,index;
+  unsigned long tmp;
+   
+    card = info->card;
+    channel = info->line - cy_card[card].first_line;
+    if (!IS_CYC_Z(cy_card[card])) {
+	chip = channel>>2;
+	channel &= 0x03;
+	index = cy_card[card].bus_index;
+	base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	tmp = cy_readb(base_addr+(CyRTPR<<index));
+	return put_user(tmp,value);
+    } else {
+	// Nothing to do!
+	return 0;
+    }
+}/* get_timeout */
+
+
+static int
+set_default_timeout(struct cyclades_port * info, unsigned long value)
+{
+    info->default_timeout = value & 0xff;
+    return 0;
+}/* set_default_timeout */
+
+
+static int
+get_default_timeout(struct cyclades_port * info, unsigned long __user *value)
+{
+    return put_user(info->default_timeout,value);
+}/* get_default_timeout */
+
+/*
+ * This routine allows the tty driver to implement device-
+ * specific ioctl's.  If the ioctl number passed in cmd is
+ * not recognized by the driver, it should return ENOIOCTLCMD.
+ */
+static int
+cy_ioctl(struct tty_struct *tty, struct file * file,
+            unsigned int cmd, unsigned long arg)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+  struct cyclades_icount cprev, cnow;		/* kernel counter temps */
+  struct serial_icounter_struct __user *p_cuser;	/* user space */
+  int ret_val = 0;
+  unsigned long flags;
+  void __user *argp = (void __user *)arg;
+
+    if (serial_paranoia_check(info, tty->name, "cy_ioctl"))
+	return -ENODEV;
+
+#ifdef CY_DEBUG_OTHER
+    printk("cyc:cy_ioctl ttyC%d, cmd = %x arg = %lx\n",
+        info->line, cmd, arg); /* */
+#endif
+
+    switch (cmd) {
+        case CYGETMON:
+            ret_val = get_mon_info(info, argp);
+            break;
+        case CYGETTHRESH:
+            ret_val = get_threshold(info, argp);
+            break;
+        case CYSETTHRESH:
+            ret_val = set_threshold(info, arg);
+            break;
+        case CYGETDEFTHRESH:
+            ret_val = get_default_threshold(info, argp);
+            break;
+        case CYSETDEFTHRESH:
+            ret_val = set_default_threshold(info, arg);
+            break;
+        case CYGETTIMEOUT:
+            ret_val = get_timeout(info, argp);
+            break;
+        case CYSETTIMEOUT:
+            ret_val = set_timeout(info, arg);
+            break;
+        case CYGETDEFTIMEOUT:
+            ret_val = get_default_timeout(info, argp);
+            break;
+        case CYSETDEFTIMEOUT:
+            ret_val = set_default_timeout(info, arg);
+            break;
+	case CYSETRFLOW:
+    	    info->rflow = (int)arg;
+	    ret_val = 0;
+	    break;
+	case CYGETRFLOW:
+	    ret_val = info->rflow;
+	    break;
+	case CYSETRTSDTR_INV:
+    	    info->rtsdtr_inv = (int)arg;
+	    ret_val = 0;
+	    break;
+	case CYGETRTSDTR_INV:
+	    ret_val = info->rtsdtr_inv;
+	    break;
+	case CYGETCARDINFO:
+            if (copy_to_user(argp, &cy_card[info->card], 
+			sizeof (struct cyclades_card))) {
+		ret_val = -EFAULT;
+		break;
+	    }
+	    ret_val = 0;
+            break;
+	case CYGETCD1400VER:
+	    ret_val = info->chip_rev;
+	    break;
+#ifndef CONFIG_CYZ_INTR
+	case CYZSETPOLLCYCLE:
+            cyz_polling_cycle = (arg * HZ) / 1000;
+	    ret_val = 0;
+	    break;
+	case CYZGETPOLLCYCLE:
+            ret_val = (cyz_polling_cycle * 1000) / HZ;
+	    break;
+#endif /* CONFIG_CYZ_INTR */
+	case CYSETWAIT:
+    	    info->closing_wait = (unsigned short)arg * HZ/100;
+	    ret_val = 0;
+	    break;
+	case CYGETWAIT:
+	    ret_val = info->closing_wait / (HZ/100);
+	    break;
+        case TIOCGSERIAL:
+            ret_val = get_serial_info(info, argp);
+            break;
+        case TIOCSSERIAL:
+            ret_val = set_serial_info(info, argp);
+            break;
+	case TIOCSERGETLSR: /* Get line status register */
+	    ret_val = get_lsr_info(info, argp);
+	    break;
+	/*
+	 * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change 
+	 * - mask passed in arg for lines of interest
+	 *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+	 * Caller should use TIOCGICOUNT to see which one it was
+	 */
+	case TIOCMIWAIT:
+	    CY_LOCK(info, flags);
+	    /* note the counters on entry */
+	    cprev = info->icount;
+	    CY_UNLOCK(info, flags);
+	    while (1) {
+		interruptible_sleep_on(&info->delta_msr_wait);
+		/* see if a signal did it */
+		if (signal_pending(current)) {
+		    return -ERESTARTSYS;
+		}
+
+		CY_LOCK(info, flags);
+		cnow = info->icount; /* atomic copy */
+		CY_UNLOCK(info, flags);
+
+		if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr && 
+		    cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+		    return -EIO; /* no change => error */
+		}
+		if ( ((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || 
+		     ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || 
+		     ((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd)) || 
+		     ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
+		    return 0;
+		}
+		cprev = cnow;
+	    }
+	    /* NOTREACHED */
+
+	/*
+	 * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+	 * Return: write counters to the user passed counter struct
+	 * NB: both 1->0 and 0->1 transitions are counted except for
+	 *     RI where only 0->1 is counted.
+	 */
+	case TIOCGICOUNT:
+	    CY_LOCK(info, flags);
+	    cnow = info->icount;
+	    CY_UNLOCK(info, flags);
+	    p_cuser = argp;
+	    ret_val = put_user(cnow.cts, &p_cuser->cts);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.dsr, &p_cuser->dsr);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.rng, &p_cuser->rng);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.dcd, &p_cuser->dcd);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.rx, &p_cuser->rx);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.tx, &p_cuser->tx);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.frame, &p_cuser->frame);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.overrun, &p_cuser->overrun);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.parity, &p_cuser->parity);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.brk, &p_cuser->brk);
+	    if (ret_val) return ret_val;
+	    ret_val = put_user(cnow.buf_overrun, &p_cuser->buf_overrun);
+	    if (ret_val) return ret_val;
+	    ret_val = 0;
+	    break;
+        default:
+            ret_val = -ENOIOCTLCMD;
+    }
+
+#ifdef CY_DEBUG_OTHER
+    printk(" cyc:cy_ioctl done\n");
+#endif
+
+    return ret_val;
+} /* cy_ioctl */
+
+
+/*
+ * This routine allows the tty driver to be notified when
+ * device's termios settings have changed.  Note that a
+ * well-designed tty driver should be prepared to accept the case
+ * where old == NULL, and try to do something rational.
+ */
+static void
+cy_set_termios(struct tty_struct *tty, struct termios * old_termios)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+
+#ifdef CY_DEBUG_OTHER
+    printk("cyc:cy_set_termios ttyC%d\n", info->line);
+#endif
+
+    if ((tty->termios->c_cflag == old_termios->c_cflag) &&
+	((tty->termios->c_iflag & (IXON|IXANY)) == 
+	 (old_termios->c_iflag & (IXON|IXANY))))
+        return;
+    set_line_char(info);
+
+    if ((old_termios->c_cflag & CRTSCTS) &&
+        !(tty->termios->c_cflag & CRTSCTS)) {
+            tty->hw_stopped = 0;
+            cy_start(tty);
+    }
+#if 0
+    /*
+     * No need to wake up processes in open wait, since they
+     * sample the CLOCAL flag once, and don't recheck it.
+     * XXX  It's not clear whether the current behavior is correct
+     * or not.  Hence, this may change.....
+     */
+    if (!(old_termios->c_cflag & CLOCAL) &&
+        (tty->termios->c_cflag & CLOCAL))
+            wake_up_interruptible(&info->open_wait);
+#endif
+
+    return;
+} /* cy_set_termios */
+
+/* This function is used to send a high-priority XON/XOFF character to
+   the device.
+*/
+static void
+cy_send_xchar (struct tty_struct *tty, char ch)
+{
+	struct cyclades_port *info = (struct cyclades_port *) tty->driver_data;
+	int card, channel;
+
+	if (serial_paranoia_check (info, tty->name, "cy_send_xchar"))
+		return;
+
+  	info->x_char = ch;
+
+	if (ch)
+		cy_start (tty);
+
+	card = info->card;
+	channel = info->line - cy_card[card].first_line;
+
+	if (IS_CYC_Z (cy_card[card])) {
+		if (ch == STOP_CHAR (tty))
+	  		cyz_issue_cmd (&cy_card[card], channel, C_CM_SENDXOFF, 0L);
+		else if (ch == START_CHAR (tty))
+			cyz_issue_cmd (&cy_card[card], channel, C_CM_SENDXON, 0L);
+	}
+}
+
+/* This routine is called by the upper-layer tty layer to signal
+   that incoming characters should be throttled because the input
+   buffers are close to full.
+ */
+static void
+cy_throttle(struct tty_struct * tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+  void __iomem *base_addr;
+  int card,chip,channel,index;
+
+#ifdef CY_DEBUG_THROTTLE
+  char buf[64];
+
+    printk("cyc:throttle %s: %d....ttyC%d\n", 
+	   tty_name(tty, buf),
+           tty->ldisc.chars_in_buffer(tty), info->line);
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_throttle")){
+            return;
+    }
+
+    card = info->card;
+
+    if (I_IXOFF(tty)) {
+        if (!IS_CYC_Z (cy_card[card]))
+            cy_send_xchar (tty, STOP_CHAR (tty));
+        else
+            info->throttle = 1;
+    }
+
+    if (tty->termios->c_cflag & CRTSCTS) {
+        channel = info->line - cy_card[card].first_line;
+        if (!IS_CYC_Z(cy_card[card])) {
+            chip = channel>>2;
+            channel &= 0x03;
+            index = cy_card[card].bus_index;
+            base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+            CY_LOCK(info, flags);
+            cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+            if (info->rtsdtr_inv) {
+                cy_writeb(base_addr+(CyMSVR2<<index), ~CyDTR);
+             } else {
+                cy_writeb(base_addr+(CyMSVR1<<index), ~CyRTS);
+	     }
+	    CY_UNLOCK(info, flags);
+	} else {
+	    info->throttle = 1;
+        }
+    }
+
+    return;
+} /* cy_throttle */
+
+
+/*
+ * This routine notifies the tty driver that it should signal
+ * that characters can now be sent to the tty without fear of
+ * overrunning the input buffers of the line disciplines.
+ */
+static void
+cy_unthrottle(struct tty_struct * tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+  void __iomem *base_addr;
+  int card,chip,channel,index;
+
+#ifdef CY_DEBUG_THROTTLE
+  char buf[64];
+        
+    printk("cyc:unthrottle %s: %d....ttyC%d\n", 
+	   tty_name(tty, buf),
+           tty->ldisc.chars_in_buffer(tty), info->line);
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_unthrottle")){
+            return;
+    }
+
+    if (I_IXOFF(tty)) {
+	if (info->x_char)
+	    info->x_char = 0;
+	else
+	    cy_send_xchar (tty, START_CHAR (tty));
+    }
+
+    if (tty->termios->c_cflag & CRTSCTS) {
+        card = info->card;
+        channel = info->line - cy_card[card].first_line;
+        if (!IS_CYC_Z(cy_card[card])) {
+	    chip = channel>>2;
+	    channel &= 0x03;
+	    index = cy_card[card].bus_index;
+	    base_addr = cy_card[card].base_addr + (cy_chip_offset[chip]<<index);
+
+	    CY_LOCK(info, flags);
+	    cy_writeb(base_addr+(CyCAR<<index), (u_char)channel);
+	    if (info->rtsdtr_inv) {
+		    cy_writeb(base_addr+(CyMSVR2<<index), CyDTR);
+	    } else {
+		    cy_writeb(base_addr+(CyMSVR1<<index), CyRTS);
+	    }
+	    CY_UNLOCK(info, flags);
+        } else {
+	    info->throttle = 0;
+	}
+    }
+
+    return;
+} /* cy_unthrottle */
+
+
+/* cy_start and cy_stop provide software output flow control as a
+   function of XON/XOFF, software CTS, and other such stuff.
+*/
+static void
+cy_stop(struct tty_struct *tty)
+{
+  struct cyclades_card *cinfo;
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  void __iomem *base_addr;
+  int chip,channel,index;
+  unsigned long flags;
+
+#ifdef CY_DEBUG_OTHER
+    printk("cyc:cy_stop ttyC%d\n", info->line); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_stop"))
+        return;
+        
+    cinfo = &cy_card[info->card];
+    channel = info->line - cinfo->first_line;
+    if (!IS_CYC_Z(*cinfo)) {
+        index = cinfo->bus_index;
+        chip = channel>>2;
+        channel &= 0x03;
+        base_addr = cy_card[info->card].base_addr + (cy_chip_offset[chip]<<index);
+
+	CY_LOCK(info, flags);
+            cy_writeb(base_addr+(CyCAR<<index),
+	       (u_char)(channel & 0x0003)); /* index channel */
+            cy_writeb(base_addr+(CySRER<<index), 
+               cy_readb(base_addr+(CySRER<<index)) & ~CyTxRdy);
+	CY_UNLOCK(info, flags);
+    } else {
+	// Nothing to do!
+    }
+
+    return;
+} /* cy_stop */
+
+
+static void
+cy_start(struct tty_struct *tty)
+{
+  struct cyclades_card *cinfo;
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  void __iomem *base_addr;
+  int chip,channel,index;
+  unsigned long flags;
+
+#ifdef CY_DEBUG_OTHER
+    printk("cyc:cy_start ttyC%d\n", info->line); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_start"))
+        return;
+        
+    cinfo = &cy_card[info->card];
+    channel = info->line - cinfo->first_line;
+    index = cinfo->bus_index;
+    if (!IS_CYC_Z(*cinfo)) {
+        chip = channel>>2;
+        channel &= 0x03;
+        base_addr = cy_card[info->card].base_addr + (cy_chip_offset[chip]<<index);
+
+	CY_LOCK(info, flags);
+            cy_writeb(base_addr+(CyCAR<<index),
+	       (u_char)(channel & 0x0003)); /* index channel */
+            cy_writeb(base_addr+(CySRER<<index), 
+               cy_readb(base_addr+(CySRER<<index)) | CyTxRdy);
+	CY_UNLOCK(info, flags);
+    } else {
+	// Nothing to do!
+    }
+
+    return;
+} /* cy_start */
+
+
+static void
+cy_flush_buffer(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  int card, channel, retval;
+  unsigned long flags;
+                                
+#ifdef CY_DEBUG_IO
+    printk("cyc:cy_flush_buffer ttyC%d\n", info->line); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_flush_buffer"))
+        return;
+
+    card = info->card;
+    channel = (info->line) - (cy_card[card].first_line);
+
+    CY_LOCK(info, flags);
+    info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+    CY_UNLOCK(info, flags);
+
+    if (IS_CYC_Z(cy_card[card])) { /* If it is a Z card, flush the on-board 
+				      buffers as well */
+	CY_LOCK(info, flags);
+	retval = cyz_issue_cmd(&cy_card[card], channel, C_CM_FLUSH_TX, 0L);
+	if (retval != 0) {
+	    printk("cyc: flush_buffer retval on ttyC%d was %x\n",
+		   info->line, retval);
+	}
+	CY_UNLOCK(info, flags);
+    }
+    tty_wakeup(tty);
+    wake_up_interruptible(&tty->write_wait);
+} /* cy_flush_buffer */
+
+
+/*
+ * cy_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void
+cy_hangup(struct tty_struct *tty)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+        
+#ifdef CY_DEBUG_OTHER
+    printk("cyc:cy_hangup ttyC%d\n", info->line); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_hangup"))
+        return;
+
+    cy_flush_buffer(tty);
+    shutdown(info);
+    info->event = 0;
+    info->count = 0;
+#ifdef CY_DEBUG_COUNT
+    printk("cyc:cy_hangup (%d): setting count to 0\n", current->pid);
+#endif
+    info->tty = NULL;
+    info->flags &= ~ASYNC_NORMAL_ACTIVE;
+    wake_up_interruptible(&info->open_wait);
+} /* cy_hangup */
+
+
+/*
+ * ---------------------------------------------------------------------
+ * cy_init() and friends
+ *
+ * cy_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+/* initialize chips on Cyclom-Y card -- return number of valid
+   chips (which is number of ports/4) */
+static unsigned short __init
+cyy_init_card(void __iomem *true_base_addr,int index)
+{
+  unsigned int chip_number;
+  void __iomem *base_addr;
+
+    cy_writeb(true_base_addr+(Cy_HwReset<<index), 0); 
+						/* Cy_HwReset is 0x1400 */
+    cy_writeb(true_base_addr+(Cy_ClrIntr<<index), 0); 
+						/* Cy_ClrIntr is 0x1800 */
+    udelay(500L);
+
+    for(chip_number=0; chip_number<CyMAX_CHIPS_PER_CARD; chip_number++){
+        base_addr = true_base_addr + (cy_chip_offset[chip_number]<<index);
+        mdelay(1);
+        if(cy_readb(base_addr+(CyCCR<<index)) != 0x00){
+            /*************
+            printk(" chip #%d at %#6lx is never idle (CCR != 0)\n",
+               chip_number, (unsigned long)base_addr);
+            *************/
+            return chip_number;
+        }
+
+        cy_writeb(base_addr+(CyGFRCR<<index), 0);
+        udelay(10L);
+
+        /* The Cyclom-16Y does not decode address bit 9 and therefore
+           cannot distinguish between references to chip 0 and a non-
+           existent chip 4.  If the preceding clearing of the supposed
+           chip 4 GFRCR register appears at chip 0, there is no chip 4
+           and this must be a Cyclom-16Y, not a Cyclom-32Ye.
+        */
+        if (chip_number == 4
+        && cy_readb(true_base_addr
+	    + (cy_chip_offset[0]<<index)
+	    + (CyGFRCR<<index)) == 0){
+            return chip_number;
+        }
+
+        cy_writeb(base_addr+(CyCCR<<index), CyCHIP_RESET);
+        mdelay(1);
+
+        if(cy_readb(base_addr+(CyGFRCR<<index)) == 0x00){
+            /*
+            printk(" chip #%d at %#6lx is not responding ",
+               chip_number, (unsigned long)base_addr);
+            printk("(GFRCR stayed 0)\n",
+            */
+            return chip_number;
+        }
+        if((0xf0 & (cy_readb(base_addr+(CyGFRCR<<index)))) != 0x40){
+            /*
+            printk(" chip #%d at %#6lx is not valid (GFRCR == %#2x)\n",
+               chip_number, (unsigned long)base_addr,
+	       base_addr[CyGFRCR<<index]);
+            */
+            return chip_number;
+        }
+        cy_writeb(base_addr+(CyGCR<<index), CyCH0_SERIAL);
+        if (cy_readb(base_addr+(CyGFRCR<<index)) >= CD1400_REV_J){
+	    /* It is a CD1400 rev. J or later */
+	    /* Impossible to reach 5ms with this chip. 
+	       Changed to 2ms instead (f = 500 Hz). */
+	    cy_writeb(base_addr+(CyPPR<<index), CyCLOCK_60_2MS);
+	} else {
+	    /* f = 200 Hz */
+	    cy_writeb(base_addr+(CyPPR<<index), CyCLOCK_25_5MS);
+	}
+
+    /*
+        printk(" chip #%d at %#6lx is rev 0x%2x\n",
+               chip_number, (unsigned long)base_addr,
+	       cy_readb(base_addr+(CyGFRCR<<index)));
+    */
+    }
+    return chip_number;
+} /* cyy_init_card */
+
+/*
+ * ---------------------------------------------------------------------
+ * cy_detect_isa() - Probe for Cyclom-Y/ISA boards.
+ * sets global variables and return the number of ISA boards found.
+ * ---------------------------------------------------------------------
+ */
+static int __init
+cy_detect_isa(void)
+{
+#ifdef CONFIG_ISA
+  unsigned short	cy_isa_irq,nboard;
+  void __iomem		*cy_isa_address;
+  unsigned short	i,j,cy_isa_nchan;
+#ifdef MODULE
+  int isparam = 0;
+#endif
+
+        nboard = 0;
+
+#ifdef MODULE
+	/* Check for module parameters */
+	for(i = 0 ; i < NR_CARDS; i++) {
+	    if (maddr[i] || i) {
+		isparam = 1;
+		cy_isa_addresses[i] = maddr[i];
+	    }
+	    if (!maddr[i])
+		break;
+	}
+#endif
+
+        /* scan the address table probing for Cyclom-Y/ISA boards */
+        for (i = 0 ; i < NR_ISA_ADDRS ; i++) {
+        	unsigned int isa_address = cy_isa_addresses[i];
+                if (isa_address  == 0x0000) {
+                        return(nboard);
+                }
+
+                /* probe for CD1400... */
+		cy_isa_address = ioremap(isa_address, CyISA_Ywin);
+                cy_isa_nchan = CyPORTS_PER_CHIP * 
+                     cyy_init_card(cy_isa_address,0);
+                if (cy_isa_nchan == 0) {
+                        continue;
+                }
+
+#ifdef MODULE
+		if (isparam && irq[i])
+		    cy_isa_irq = irq[i];
+		else
+#endif
+                /* find out the board's irq by probing */
+                cy_isa_irq = detect_isa_irq(cy_isa_address);
+                if (cy_isa_irq == 0) {
+                        printk("Cyclom-Y/ISA found at 0x%lx ",
+                                (unsigned long) cy_isa_address);
+                        printk("but the IRQ could not be detected.\n");
+                        continue;
+                }
+
+                if((cy_next_channel+cy_isa_nchan) > NR_PORTS) {
+                        printk("Cyclom-Y/ISA found at 0x%lx ",
+                                (unsigned long) cy_isa_address);
+                        printk("but no more channels are available.\n");
+                        printk("Change NR_PORTS in cyclades.c and recompile kernel.\n");
+                        return(nboard);
+                }
+                /* fill the next cy_card structure available */
+                for (j = 0 ; j < NR_CARDS ; j++) {
+                        if (cy_card[j].base_addr == 0)  break;
+                }
+                if (j == NR_CARDS) {    /* no more cy_cards available */
+                        printk("Cyclom-Y/ISA found at 0x%lx ",
+                                (unsigned long) cy_isa_address);
+                        printk("but no more cards can be used .\n");
+                        printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+                        return(nboard);
+                }
+
+                /* allocate IRQ */
+                if(request_irq(cy_isa_irq, cyy_interrupt,
+				   SA_INTERRUPT, "Cyclom-Y", &cy_card[j]))
+                {
+                        printk("Cyclom-Y/ISA found at 0x%lx ",
+                                (unsigned long) cy_isa_address);
+                        printk("but could not allocate IRQ#%d.\n",
+                                cy_isa_irq);
+                        return(nboard);
+                }
+
+                /* set cy_card */
+                cy_card[j].base_addr = cy_isa_address;
+                cy_card[j].ctl_addr = NULL;
+                cy_card[j].irq = (int) cy_isa_irq;
+                cy_card[j].bus_index = 0;
+                cy_card[j].first_line = cy_next_channel;
+                cy_card[j].num_chips = cy_isa_nchan/4;
+                nboard++;
+                        
+                /* print message */
+                printk("Cyclom-Y/ISA #%d: 0x%lx-0x%lx, IRQ%d, ",
+                    j+1, (unsigned long) cy_isa_address,
+                    (unsigned long)(cy_isa_address + (CyISA_Ywin - 1)),
+		    cy_isa_irq);
+                printk("%d channels starting from port %d.\n",
+                        cy_isa_nchan, cy_next_channel);
+                cy_next_channel += cy_isa_nchan;
+        }
+        return(nboard);
+#else
+        return(0);
+#endif /* CONFIG_ISA */
+} /* cy_detect_isa */
+
+static void 
+plx_init(void __iomem *addr, uclong initctl)
+{
+    /* Reset PLX */
+    cy_writel(addr + initctl, cy_readl(addr + initctl) | 0x40000000);
+    udelay(100L);
+    cy_writel(addr + initctl, cy_readl(addr + initctl) & ~0x40000000);
+
+    /* Reload Config. Registers from EEPROM */
+    cy_writel(addr + initctl, cy_readl(addr + initctl) | 0x20000000);
+    udelay(100L);
+    cy_writel(addr + initctl, cy_readl(addr + initctl) & ~0x20000000);
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * cy_detect_pci() - Test PCI bus presence and Cyclom-Ye/PCI.
+ * sets global variables and return the number of PCI boards found.
+ * ---------------------------------------------------------------------
+ */
+static int __init
+cy_detect_pci(void)
+{
+#ifdef CONFIG_PCI
+
+  struct pci_dev	*pdev = NULL;
+  unsigned char		cyy_rev_id;
+  unsigned char		cy_pci_irq = 0;
+  uclong		cy_pci_phys0, cy_pci_phys2;
+  void __iomem		*cy_pci_addr0, *cy_pci_addr2;
+  unsigned short	i,j,cy_pci_nchan, plx_ver;
+  unsigned short	device_id,dev_index = 0;
+  uclong		mailbox;
+  uclong		ZeIndex = 0;
+  void __iomem		*Ze_addr0[NR_CARDS], *Ze_addr2[NR_CARDS];
+  uclong		Ze_phys0[NR_CARDS], Ze_phys2[NR_CARDS];
+  unsigned char		Ze_irq[NR_CARDS];
+  struct pci_dev	*Ze_pdev[NR_CARDS];
+
+        for (i = 0; i < NR_CARDS; i++) {
+                /* look for a Cyclades card by vendor and device id */
+                while((device_id = cy_pci_dev_id[dev_index]) != 0) {
+                        if((pdev = pci_get_device(PCI_VENDOR_ID_CYCLADES,
+                                        device_id, pdev)) == NULL) {
+                                dev_index++;    /* try next device id */
+                        } else {
+                                break;          /* found a board */
+                        }
+                }
+
+		if (device_id == 0)
+		    break;
+
+		if (pci_enable_device(pdev))
+		    continue;
+
+                /* read PCI configuration area */
+		cy_pci_irq = pdev->irq;
+		cy_pci_phys0 = pci_resource_start(pdev, 0);
+		cy_pci_phys2 = pci_resource_start(pdev, 2);
+		pci_read_config_byte(pdev, PCI_REVISION_ID, &cyy_rev_id);
+
+		device_id &= ~PCI_DEVICE_ID_MASK;
+
+    if ((device_id == PCI_DEVICE_ID_CYCLOM_Y_Lo)
+	   || (device_id == PCI_DEVICE_ID_CYCLOM_Y_Hi)){
+#ifdef CY_PCI_DEBUG
+            printk("Cyclom-Y/PCI (bus=0x0%x, pci_id=0x%x, ",
+		pdev->bus->number, pdev->devfn);
+            printk("rev_id=%d) IRQ%d\n",
+		cyy_rev_id, (int)cy_pci_irq);
+            printk("Cyclom-Y/PCI:found  winaddr=0x%lx ctladdr=0x%lx\n",
+		(ulong)cy_pci_phys2, (ulong)cy_pci_phys0);
+#endif
+
+		if (pci_resource_flags(pdev, 2) & IORESOURCE_IO) {
+		    printk("  Warning: PCI I/O bit incorrectly set. "
+			   "Ignoring it...\n");
+		    pdev->resource[2].flags &= ~IORESOURCE_IO;
+		}
+
+		/* Although we don't use this I/O region, we should
+		   request it from the kernel anyway, to avoid problems
+		   with other drivers accessing it. */
+		if (pci_request_regions(pdev, "Cyclom-Y") != 0) {
+			printk(KERN_ERR "cyclades: failed to reserve PCI resources\n");
+			continue;
+		}
+
+#if defined(__alpha__)
+                if (device_id  == PCI_DEVICE_ID_CYCLOM_Y_Lo) { /* below 1M? */
+		    printk("Cyclom-Y/PCI (bus=0x0%x, pci_id=0x%x, ",
+			pdev->bus->number, pdev->devfn);
+		    printk("rev_id=%d) IRQ%d\n",
+		        cyy_rev_id, (int)cy_pci_irq);
+                    printk("Cyclom-Y/PCI:found  winaddr=0x%lx ctladdr=0x%lx\n",
+		        (ulong)cy_pci_phys2, (ulong)cy_pci_phys0);
+	            printk("Cyclom-Y/PCI not supported for low addresses in "
+                           "Alpha systems.\n");
+		    i--;
+	            continue;
+                }
+#endif
+		cy_pci_addr0 = ioremap(cy_pci_phys0, CyPCI_Yctl);
+		cy_pci_addr2 = ioremap(cy_pci_phys2, CyPCI_Ywin);
+
+#ifdef CY_PCI_DEBUG
+            printk("Cyclom-Y/PCI: relocate winaddr=0x%lx ctladdr=0x%lx\n",
+		(u_long)cy_pci_addr2, (u_long)cy_pci_addr0);
+#endif
+                cy_pci_nchan = (unsigned short)(CyPORTS_PER_CHIP * 
+                       cyy_init_card(cy_pci_addr2, 1));
+                if(cy_pci_nchan == 0) {
+                        printk("Cyclom-Y PCI host card with ");
+                        printk("no Serial-Modules at 0x%lx.\n",
+			    (ulong) cy_pci_phys2);
+                        i--;
+                        continue;
+                }
+                if((cy_next_channel+cy_pci_nchan) > NR_PORTS) {
+                        printk("Cyclom-Y/PCI found at 0x%lx ",
+			    (ulong) cy_pci_phys2);
+                        printk("but no channels are available.\n");
+                        printk("Change NR_PORTS in cyclades.c and recompile kernel.\n");
+                        return(i);
+                }
+                /* fill the next cy_card structure available */
+                for (j = 0 ; j < NR_CARDS ; j++) {
+                        if (cy_card[j].base_addr == 0)  break;
+                }
+                if (j == NR_CARDS) {    /* no more cy_cards available */
+                        printk("Cyclom-Y/PCI found at 0x%lx ",
+			    (ulong) cy_pci_phys2);
+                        printk("but no more cards can be used.\n");
+                        printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+                        return(i);
+                }
+
+                /* allocate IRQ */
+                if(request_irq(cy_pci_irq, cyy_interrupt,
+		        SA_SHIRQ, "Cyclom-Y", &cy_card[j]))
+                {
+                        printk("Cyclom-Y/PCI found at 0x%lx ",
+			    (ulong) cy_pci_phys2);
+                        printk("but could not allocate IRQ%d.\n",
+			    cy_pci_irq);
+                        return(i);
+                }
+
+                /* set cy_card */
+                cy_card[j].base_phys = (ulong)cy_pci_phys2;
+                cy_card[j].ctl_phys = (ulong)cy_pci_phys0;
+                cy_card[j].base_addr = cy_pci_addr2;
+                cy_card[j].ctl_addr = cy_pci_addr0;
+                cy_card[j].irq = (int) cy_pci_irq;
+                cy_card[j].bus_index = 1;
+                cy_card[j].first_line = cy_next_channel;
+                cy_card[j].num_chips = cy_pci_nchan/4;
+		cy_card[j].pdev = pdev;
+	
+                /* enable interrupts in the PCI interface */
+		plx_ver = cy_readb(cy_pci_addr2 + CyPLX_VER) & 0x0f;
+		switch (plx_ver) {
+		    case PLX_9050:
+
+		    cy_writeb(cy_pci_addr0+0x4c, 0x43);
+		    break;
+
+		    case PLX_9060:
+		    case PLX_9080:
+		    default: /* Old boards, use PLX_9060 */
+
+		    plx_init(cy_pci_addr0, 0x6c);
+		    /* For some yet unknown reason, once the PLX9060 reloads
+		       the EEPROM, the IRQ is lost and, thus, we have to
+		       re-write it to the PCI config. registers.
+		       This will remain here until we find a permanent fix. */
+		    pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, cy_pci_irq);
+
+		    cy_writew(cy_pci_addr0+0x68, 
+			cy_readw(cy_pci_addr0+0x68)|0x0900);
+		    break;
+		}
+
+                /* print message */
+                printk("Cyclom-Y/PCI #%d: 0x%lx-0x%lx, IRQ%d, ",
+		       j+1, 
+		       (ulong)cy_pci_phys2, 
+		       (ulong)(cy_pci_phys2 + CyPCI_Ywin - 1),
+		       (int)cy_pci_irq);
+                printk("%d channels starting from port %d.\n",
+		    cy_pci_nchan, cy_next_channel);
+
+                cy_next_channel += cy_pci_nchan;
+    }else if (device_id == PCI_DEVICE_ID_CYCLOM_Z_Lo){
+	    /* print message */
+		printk("Cyclades-Z/PCI (bus=0x0%x, pci_id=0x%x, ",
+		    pdev->bus->number, pdev->devfn);
+		printk("rev_id=%d) IRQ%d\n",
+		    cyy_rev_id, (int)cy_pci_irq);
+		printk("Cyclades-Z/PCI: found winaddr=0x%lx ctladdr=0x%lx\n",
+		    (ulong)cy_pci_phys2, (ulong)cy_pci_phys0);
+	    printk("Cyclades-Z/PCI not supported for low addresses\n");
+	    break;
+    }else if (device_id == PCI_DEVICE_ID_CYCLOM_Z_Hi){
+#ifdef CY_PCI_DEBUG
+            printk("Cyclades-Z/PCI (bus=0x0%x, pci_id=0x%x, ",
+	        pdev->bus->number, pdev->devfn);
+            printk("rev_id=%d) IRQ%d\n",
+		cyy_rev_id, (int)cy_pci_irq);
+            printk("Cyclades-Z/PCI: found winaddr=0x%lx ctladdr=0x%lx\n",
+                (ulong)cy_pci_phys2, (ulong)cy_pci_phys0);
+#endif
+		cy_pci_addr0 = ioremap(cy_pci_phys0, CyPCI_Zctl);
+
+		/* Disable interrupts on the PLX before resetting it */
+		cy_writew(cy_pci_addr0+0x68,
+			cy_readw(cy_pci_addr0+0x68) & ~0x0900);
+
+		plx_init(cy_pci_addr0, 0x6c);
+		/* For some yet unknown reason, once the PLX9060 reloads
+		   the EEPROM, the IRQ is lost and, thus, we have to
+		   re-write it to the PCI config. registers.
+		   This will remain here until we find a permanent fix. */
+		pci_write_config_byte(pdev, PCI_INTERRUPT_LINE, cy_pci_irq);
+
+		mailbox = (uclong)cy_readl(&((struct RUNTIME_9060 __iomem *) 
+			   cy_pci_addr0)->mail_box_0);
+
+		if (pci_resource_flags(pdev, 2) & IORESOURCE_IO) {
+		    printk("  Warning: PCI I/O bit incorrectly set. "
+			   "Ignoring it...\n");
+		    pdev->resource[2].flags &= ~IORESOURCE_IO;
+		}
+
+		/* Although we don't use this I/O region, we should
+		   request it from the kernel anyway, to avoid problems
+		   with other drivers accessing it. */
+		if (pci_request_regions(pdev, "Cyclades-Z") != 0) {
+			printk(KERN_ERR "cyclades: failed to reserve PCI resources\n");
+			continue;
+		}
+	
+		if (mailbox == ZE_V1) {
+		    cy_pci_addr2 = ioremap(cy_pci_phys2, CyPCI_Ze_win);
+		    if (ZeIndex == NR_CARDS) {
+			printk("Cyclades-Ze/PCI found at 0x%lx ",
+				(ulong)cy_pci_phys2);
+			printk("but no more cards can be used.\n");
+                        printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+		    } else {
+			Ze_phys0[ZeIndex] = cy_pci_phys0;
+			Ze_phys2[ZeIndex] = cy_pci_phys2;
+			Ze_addr0[ZeIndex] = cy_pci_addr0;
+			Ze_addr2[ZeIndex] = cy_pci_addr2;
+			Ze_irq[ZeIndex] = cy_pci_irq;
+			Ze_pdev[ZeIndex] = pdev;
+			ZeIndex++;
+		    }
+		    i--;
+		    continue;
+		} else {
+		    cy_pci_addr2 = ioremap(cy_pci_phys2, CyPCI_Zwin);
+		}
+
+#ifdef CY_PCI_DEBUG
+            printk("Cyclades-Z/PCI: relocate winaddr=0x%lx ctladdr=0x%lx\n",
+                (ulong)cy_pci_addr2, (ulong)cy_pci_addr0);
+	    if (mailbox == ZO_V1) {
+		cy_writel(&((struct RUNTIME_9060 *)
+			  (cy_pci_addr0))->loc_addr_base, WIN_CREG);
+		PAUSE
+		printk("Cyclades-8Zo/PCI: FPGA id %lx, ver %lx\n",
+		       (ulong)(0xff & cy_readl(&((struct CUSTOM_REG *)
+		        (cy_pci_addr2))->fpga_id)),
+		       (ulong)(0xff & cy_readl(&((struct CUSTOM_REG *)
+		        (cy_pci_addr2))->fpga_version)));
+		cy_writel(&((struct RUNTIME_9060 *)
+			  (cy_pci_addr0))->loc_addr_base, WIN_RAM);
+	    } else {
+		printk("Cyclades-Z/PCI: New Cyclades-Z board.  FPGA not loaded\n");
+	    }
+#endif
+	    /* The following clears the firmware id word.  This ensures
+	       that the driver will not attempt to talk to the board
+	       until it has been properly initialized.
+	     */
+		PAUSE
+		if ((mailbox == ZO_V1) || (mailbox == ZO_V2))
+		    cy_writel(cy_pci_addr2 + ID_ADDRESS, 0L);
+
+                /* This must be a Cyclades-8Zo/PCI.  The extendable
+                   version will have a different device_id and will
+                   be allocated its maximum number of ports. */
+                cy_pci_nchan = 8;
+
+                if((cy_next_channel+cy_pci_nchan) > NR_PORTS) {
+                        printk("Cyclades-8Zo/PCI found at 0x%lx ",
+			    (ulong)cy_pci_phys2);
+                        printk("but no channels are available.\n");
+                        printk("Change NR_PORTS in cyclades.c and recompile kernel.\n");
+                        return(i);
+                }
+
+                /* fill the next cy_card structure available */
+                for (j = 0 ; j < NR_CARDS ; j++) {
+                        if (cy_card[j].base_addr == 0)  break;
+                }
+                if (j == NR_CARDS) {    /* no more cy_cards available */
+		    printk("Cyclades-8Zo/PCI found at 0x%lx ",
+			(ulong)cy_pci_phys2);
+		    printk("but no more cards can be used.\n");
+                    printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+		    return(i);
+                }
+
+#ifdef CONFIG_CYZ_INTR
+                /* allocate IRQ only if board has an IRQ */
+		if( (cy_pci_irq != 0) && (cy_pci_irq != 255) ) {
+		    if(request_irq(cy_pci_irq, cyz_interrupt,
+			SA_SHIRQ, "Cyclades-Z", &cy_card[j]))
+		    {
+                        printk("Cyclom-8Zo/PCI found at 0x%lx ",
+			    (ulong) cy_pci_phys2);
+                        printk("but could not allocate IRQ%d.\n",
+			    cy_pci_irq);
+			return(i);
+		    }
+		}
+#endif /* CONFIG_CYZ_INTR */
+
+
+                /* set cy_card */
+                cy_card[j].base_phys = cy_pci_phys2;
+                cy_card[j].ctl_phys = cy_pci_phys0;
+                cy_card[j].base_addr = cy_pci_addr2;
+                cy_card[j].ctl_addr = cy_pci_addr0;
+                cy_card[j].irq = (int) cy_pci_irq;
+                cy_card[j].bus_index = 1;
+                cy_card[j].first_line = cy_next_channel;
+                cy_card[j].num_chips = -1;
+		cy_card[j].pdev = pdev;
+
+                /* print message */
+#ifdef CONFIG_CYZ_INTR
+		/* don't report IRQ if board is no IRQ */
+		if( (cy_pci_irq != 0) && (cy_pci_irq != 255) )
+		    printk("Cyclades-8Zo/PCI #%d: 0x%lx-0x%lx, IRQ%d, ",
+			j+1,(ulong)cy_pci_phys2,
+			(ulong)(cy_pci_phys2 + CyPCI_Zwin - 1),
+			(int)cy_pci_irq);
+		else
+#endif /* CONFIG_CYZ_INTR */
+		    printk("Cyclades-8Zo/PCI #%d: 0x%lx-0x%lx, ",
+			j+1,(ulong)cy_pci_phys2,
+			(ulong)(cy_pci_phys2 + CyPCI_Zwin - 1));
+
+                printk("%d channels starting from port %d.\n",
+		    cy_pci_nchan,cy_next_channel);
+                cy_next_channel += cy_pci_nchan;
+	    }
+        }
+
+        for (; ZeIndex != 0 && i < NR_CARDS; i++) {
+	    cy_pci_phys0 = Ze_phys0[0];
+	    cy_pci_phys2 = Ze_phys2[0];
+	    cy_pci_addr0 = Ze_addr0[0];
+	    cy_pci_addr2 = Ze_addr2[0];
+	    cy_pci_irq = Ze_irq[0];
+	    pdev = Ze_pdev[0];
+	    for (j = 0 ; j < ZeIndex-1 ; j++) {
+		Ze_phys0[j] = Ze_phys0[j+1];
+		Ze_phys2[j] = Ze_phys2[j+1];
+		Ze_addr0[j] = Ze_addr0[j+1];
+		Ze_addr2[j] = Ze_addr2[j+1];
+		Ze_irq[j] = Ze_irq[j+1];
+		Ze_pdev[j] = Ze_pdev[j+1];
+	    }
+	    ZeIndex--;
+		mailbox = (uclong)cy_readl(&((struct RUNTIME_9060 __iomem *) 
+					   cy_pci_addr0)->mail_box_0);
+#ifdef CY_PCI_DEBUG
+            printk("Cyclades-Z/PCI: relocate winaddr=0x%lx ctladdr=0x%lx\n",
+                (ulong)cy_pci_addr2, (ulong)cy_pci_addr0);
+	    printk("Cyclades-Z/PCI: New Cyclades-Z board.  FPGA not loaded\n");
+#endif
+		PAUSE
+                /* This must be the new Cyclades-Ze/PCI. */
+                cy_pci_nchan = ZE_V1_NPORTS;
+
+                if((cy_next_channel+cy_pci_nchan) > NR_PORTS) {
+                        printk("Cyclades-Ze/PCI found at 0x%lx ",
+			    (ulong)cy_pci_phys2);
+                        printk("but no channels are available.\n");
+                        printk("Change NR_PORTS in cyclades.c and recompile kernel.\n");
+                        return(i);
+                }
+
+                /* fill the next cy_card structure available */
+                for (j = 0 ; j < NR_CARDS ; j++) {
+                        if (cy_card[j].base_addr == 0)  break;
+                }
+                if (j == NR_CARDS) {    /* no more cy_cards available */
+		    printk("Cyclades-Ze/PCI found at 0x%lx ",
+			(ulong)cy_pci_phys2);
+		    printk("but no more cards can be used.\n");
+                    printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+		    return(i);
+                }
+
+#ifdef CONFIG_CYZ_INTR
+                /* allocate IRQ only if board has an IRQ */
+		if( (cy_pci_irq != 0) && (cy_pci_irq != 255) ) {
+		    if(request_irq(cy_pci_irq, cyz_interrupt,
+			SA_SHIRQ, "Cyclades-Z", &cy_card[j]))
+		    {
+                        printk("Cyclom-Ze/PCI found at 0x%lx ",
+			    (ulong) cy_pci_phys2);
+                        printk("but could not allocate IRQ%d.\n",
+			    cy_pci_irq);
+			return(i);
+		    }
+		}
+#endif /* CONFIG_CYZ_INTR */
+
+                /* set cy_card */
+                cy_card[j].base_phys = cy_pci_phys2;
+                cy_card[j].ctl_phys = cy_pci_phys0;
+                cy_card[j].base_addr = cy_pci_addr2;
+                cy_card[j].ctl_addr = cy_pci_addr0;
+                cy_card[j].irq = (int) cy_pci_irq;
+                cy_card[j].bus_index = 1;
+                cy_card[j].first_line = cy_next_channel;
+                cy_card[j].num_chips = -1;
+		cy_card[j].pdev = pdev;
+
+                /* print message */
+#ifdef CONFIG_CYZ_INTR
+		/* don't report IRQ if board is no IRQ */
+		if( (cy_pci_irq != 0) && (cy_pci_irq != 255) )
+		    printk("Cyclades-Ze/PCI #%d: 0x%lx-0x%lx, IRQ%d, ",
+			j+1,(ulong)cy_pci_phys2,
+			(ulong)(cy_pci_phys2 + CyPCI_Ze_win - 1),
+			(int)cy_pci_irq);
+		else
+#endif /* CONFIG_CYZ_INTR */
+		    printk("Cyclades-Ze/PCI #%d: 0x%lx-0x%lx, ",
+			j+1,(ulong)cy_pci_phys2,
+			(ulong)(cy_pci_phys2 + CyPCI_Ze_win - 1));
+
+                printk("%d channels starting from port %d.\n",
+		    cy_pci_nchan,cy_next_channel);
+                cy_next_channel += cy_pci_nchan;
+        }
+	if (ZeIndex != 0) {
+	    printk("Cyclades-Ze/PCI found at 0x%x ",
+		(unsigned int) Ze_phys2[0]);
+	    printk("but no more cards can be used.\n");
+            printk("Change NR_CARDS in cyclades.c and recompile kernel.\n");
+	}
+        return(i);
+#else
+        return(0);
+#endif /* ifdef CONFIG_PCI */
+} /* cy_detect_pci */
+
+
+/*
+ * This routine prints out the appropriate serial driver version number
+ * and identifies which options were configured into this driver.
+ */
+static inline void
+show_version(void)
+{
+  char *rcsvers, *rcsdate, *tmp;
+    rcsvers = strchr(rcsid, ' '); rcsvers++;
+    tmp = strchr(rcsvers, ' '); *tmp++ = '\0';
+    rcsdate = strchr(tmp, ' '); rcsdate++;
+    tmp = strrchr(rcsdate, ' '); *tmp = '\0';
+    printk("Cyclades driver %s %s\n",
+        rcsvers, rcsdate);
+    printk("        built %s %s\n",
+	__DATE__, __TIME__);
+} /* show_version */
+
+static int 
+cyclades_get_proc_info(char *buf, char **start, off_t offset, int length,
+		       int *eof, void *data)
+{
+    struct cyclades_port  *info;
+    int i;
+    int len=0;
+    off_t begin=0;
+    off_t pos=0;
+    int size;
+    __u32 cur_jifs = jiffies;
+
+    size = sprintf(buf, "Dev TimeOpen   BytesOut  IdleOut    BytesIn   IdleIn  Overruns  Ldisc\n");
+
+    pos += size;
+    len += size;
+
+    /* Output one line for each known port */
+    for (i = 0; i < NR_PORTS && cy_port[i].line >= 0; i++) {
+	info = &cy_port[i];
+
+	if (info->count)
+	    size = sprintf(buf+len,
+			"%3d %8lu %10lu %8lu %10lu %8lu %9lu %6ld\n",
+			info->line,
+			JIFFIES_DIFF(info->idle_stats.in_use, cur_jifs) / HZ,
+			info->idle_stats.xmit_bytes,
+			JIFFIES_DIFF(info->idle_stats.xmit_idle, cur_jifs) / HZ,
+			info->idle_stats.recv_bytes,
+			JIFFIES_DIFF(info->idle_stats.recv_idle, cur_jifs) / HZ,
+			info->idle_stats.overruns,
+			(long) info->tty->ldisc.num);
+	else
+	    size = sprintf(buf+len,
+			"%3d %8lu %10lu %8lu %10lu %8lu %9lu %6ld\n",
+			info->line, 0L, 0L, 0L, 0L, 0L, 0L, 0L);
+	len += size;
+	pos = begin + len;
+
+	if (pos < offset) {
+	    len   = 0;
+	    begin = pos;
+	}
+	if (pos > offset + length)
+	    goto done;
+    }
+    *eof = 1;
+done:
+    *start = buf + (offset - begin);	/* Start of wanted data */
+    len -= (offset - begin);		/* Start slop */
+    if (len > length)
+	len = length;			/* Ending slop */
+    if (len < 0)
+	len = 0;
+    return len;
+}
+
+/* The serial driver boot-time initialization code!
+    Hardware I/O ports are mapped to character special devices on a
+    first found, first allocated manner.  That is, this code searches
+    for Cyclom cards in the system.  As each is found, it is probed
+    to discover how many chips (and thus how many ports) are present.
+    These ports are mapped to the tty ports 32 and upward in monotonic
+    fashion.  If an 8-port card is replaced with a 16-port card, the
+    port mapping on a following card will shift.
+
+    This approach is different from what is used in the other serial
+    device driver because the Cyclom is more properly a multiplexer,
+    not just an aggregation of serial ports on one card.
+
+    If there are more cards with more ports than have been
+    statically allocated above, a warning is printed and the
+    extra ports are ignored.
+ */
+
+static struct tty_operations cy_ops = {
+    .open = cy_open,
+    .close = cy_close,
+    .write = cy_write,
+    .put_char = cy_put_char,
+    .flush_chars = cy_flush_chars,
+    .write_room = cy_write_room,
+    .chars_in_buffer = cy_chars_in_buffer,
+    .flush_buffer = cy_flush_buffer,
+    .ioctl = cy_ioctl,
+    .throttle = cy_throttle,
+    .unthrottle = cy_unthrottle,
+    .set_termios = cy_set_termios,
+    .stop = cy_stop,
+    .start = cy_start,
+    .hangup = cy_hangup,
+    .break_ctl = cy_break,
+    .wait_until_sent = cy_wait_until_sent,
+    .read_proc = cyclades_get_proc_info,
+    .tiocmget = cy_tiocmget,
+    .tiocmset = cy_tiocmset,
+};
+
+static int __init
+cy_init(void)
+{
+  struct cyclades_port  *info;
+  struct cyclades_card *cinfo;
+  int number_z_boards = 0;
+  int board,port,i,index;
+  unsigned long mailbox;
+  unsigned short chip_number;
+  int nports;
+
+    cy_serial_driver = alloc_tty_driver(NR_PORTS);
+    if (!cy_serial_driver)
+	return -ENOMEM;
+    show_version();
+
+    /* Initialize the tty_driver structure */
+    
+    cy_serial_driver->owner = THIS_MODULE;
+    cy_serial_driver->driver_name = "cyclades";
+    cy_serial_driver->name = "ttyC";
+    cy_serial_driver->devfs_name = "tts/C";
+    cy_serial_driver->major = CYCLADES_MAJOR;
+    cy_serial_driver->minor_start = 0;
+    cy_serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+    cy_serial_driver->subtype = SERIAL_TYPE_NORMAL;
+    cy_serial_driver->init_termios = tty_std_termios;
+    cy_serial_driver->init_termios.c_cflag =
+            B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+    cy_serial_driver->flags = TTY_DRIVER_REAL_RAW;
+    tty_set_operations(cy_serial_driver, &cy_ops);
+
+    if (tty_register_driver(cy_serial_driver))
+            panic("Couldn't register Cyclades serial driver\n");
+
+    for (i = 0; i < NR_CARDS; i++) {
+            /* base_addr=0 indicates board not found */
+            cy_card[i].base_addr = NULL;
+    }
+
+    /* the code below is responsible to find the boards. Each different
+       type of board has its own detection routine. If a board is found,
+       the next cy_card structure available is set by the detection
+       routine. These functions are responsible for checking the
+       availability of cy_card and cy_port data structures and updating
+       the cy_next_channel. */
+
+    /* look for isa boards */
+    cy_isa_nboard = cy_detect_isa();
+
+    /* look for pci boards */
+    cy_pci_nboard = cy_detect_pci();
+
+    cy_nboard = cy_isa_nboard + cy_pci_nboard;
+
+    /* invalidate remaining cy_card structures */
+    for (i = 0 ; i < NR_CARDS ; i++) {
+        if (cy_card[i].base_addr == 0) {
+                cy_card[i].first_line = -1;
+                cy_card[i].ctl_addr = NULL;
+                cy_card[i].irq = 0;
+                cy_card[i].bus_index = 0;
+                cy_card[i].first_line = 0;
+                cy_card[i].num_chips = 0;
+        }
+    }
+    /* invalidate remaining cy_port structures */
+    for (i = cy_next_channel ; i < NR_PORTS ; i++) {
+        cy_port[i].line = -1;
+        cy_port[i].magic = -1;
+    }
+
+    /* initialize per-port data structures for each valid board found */
+    for (board = 0 ; board < cy_nboard ; board++) {
+            cinfo = &cy_card[board];
+            if (cinfo->num_chips == -1) { /* Cyclades-Z */
+		number_z_boards++;
+		mailbox = cy_readl(&((struct RUNTIME_9060 __iomem *)
+			     cy_card[board].ctl_addr)->mail_box_0);
+		nports = (mailbox == ZE_V1) ? ZE_V1_NPORTS : 8;
+		cinfo->intr_enabled = 0;
+		cinfo->nports = 0; /* Will be correctly set later, after 
+				      Z FW is loaded */
+		spin_lock_init(&cinfo->card_lock);
+                for (port = cinfo->first_line ;
+                     port < cinfo->first_line + nports;
+                     port++)
+                {
+                    info = &cy_port[port];
+                    info->magic = CYCLADES_MAGIC;
+                    info->type = PORT_STARTECH;
+                    info->card = board;
+                    info->line = port;
+		    info->chip_rev = 0;
+                    info->flags = STD_COM_FLAGS;
+                    info->tty = NULL;
+		    if (mailbox == ZO_V1)
+			info->xmit_fifo_size = CYZ_FIFO_SIZE;
+		    else
+			info->xmit_fifo_size = 4 * CYZ_FIFO_SIZE;
+                    info->cor1 = 0;
+                    info->cor2 = 0;
+                    info->cor3 = 0;
+                    info->cor4 = 0;
+                    info->cor5 = 0;
+                    info->tbpr = 0;
+                    info->tco = 0;
+                    info->rbpr = 0;
+                    info->rco = 0;
+		    info->custom_divisor = 0;
+                    info->close_delay = 5*HZ/10;
+		    info->closing_wait = CLOSING_WAIT_DELAY;
+		    info->icount.cts = info->icount.dsr = 
+			info->icount.rng = info->icount.dcd = 0;
+		    info->icount.rx = info->icount.tx = 0;
+		    info->icount.frame = info->icount.parity = 0;
+		    info->icount.overrun = info->icount.brk = 0;
+                    info->x_char = 0;
+                    info->event = 0;
+                    info->count = 0;
+                    info->blocked_open = 0;
+                    info->default_threshold = 0;
+                    info->default_timeout = 0;
+		    INIT_WORK(&info->tqueue, do_softint, info);
+		    init_waitqueue_head(&info->open_wait);
+		    init_waitqueue_head(&info->close_wait);
+		    init_waitqueue_head(&info->shutdown_wait);
+		    init_waitqueue_head(&info->delta_msr_wait);
+                    /* info->session */
+                    /* info->pgrp */
+                    info->read_status_mask = 0;
+                    /* info->timeout */
+		    /* Bentson's vars */
+                    info->jiffies[0] = 0;
+                    info->jiffies[1] = 0;
+                    info->jiffies[2] = 0;
+                    info->rflush_count = 0;
+#ifdef CONFIG_CYZ_INTR
+		    init_timer(&cyz_rx_full_timer[port]);
+		    cyz_rx_full_timer[port].function = NULL;
+#endif
+                }
+                continue;
+            }else{ /* Cyclom-Y of some kind*/
+                index = cinfo->bus_index;
+		spin_lock_init(&cinfo->card_lock);
+		cinfo->nports = CyPORTS_PER_CHIP * cinfo->num_chips;
+                for (port = cinfo->first_line ;
+                     port < cinfo->first_line + cinfo->nports ;
+                     port++)
+                {
+                    info = &cy_port[port];
+                    info->magic = CYCLADES_MAGIC;
+                    info->type = PORT_CIRRUS;
+                    info->card = board;
+                    info->line = port;
+                    info->flags = STD_COM_FLAGS;
+                    info->tty = NULL;
+                    info->xmit_fifo_size = CyMAX_CHAR_FIFO;
+                    info->cor1 = CyPARITY_NONE|Cy_1_STOP|Cy_8_BITS;
+                    info->cor2 = CyETC;
+                    info->cor3 = 0x08; /* _very_ small rcv threshold */
+                    info->cor4 = 0;
+                    info->cor5 = 0;
+		    info->custom_divisor = 0;
+                    info->close_delay = 5*HZ/10;
+		    info->closing_wait = CLOSING_WAIT_DELAY;
+		    info->icount.cts = info->icount.dsr = 
+			info->icount.rng = info->icount.dcd = 0;
+		    info->icount.rx = info->icount.tx = 0;
+		    info->icount.frame = info->icount.parity = 0;
+		    info->icount.overrun = info->icount.brk = 0;
+		    chip_number = (port - cinfo->first_line) / 4;
+		    if ((info->chip_rev =
+			 cy_readb(cinfo->base_addr +
+				  (cy_chip_offset[chip_number]<<index) +
+				  (CyGFRCR<<index))) >= CD1400_REV_J) {
+                        /* It is a CD1400 rev. J or later */
+                        info->tbpr = baud_bpr_60[13]; /* Tx BPR */
+                        info->tco = baud_co_60[13]; /* Tx CO */
+                        info->rbpr = baud_bpr_60[13]; /* Rx BPR */
+                        info->rco = baud_co_60[13]; /* Rx CO */
+                        info->rflow = 0;
+                        info->rtsdtr_inv = 1;
+                    } else {
+                        info->tbpr = baud_bpr_25[13]; /* Tx BPR */
+                        info->tco = baud_co_25[13]; /* Tx CO */
+                        info->rbpr = baud_bpr_25[13]; /* Rx BPR */
+                        info->rco = baud_co_25[13]; /* Rx CO */
+                        info->rflow = 0;
+                        info->rtsdtr_inv = 0;
+                    }
+                    info->x_char = 0;
+                    info->event = 0;
+                    info->count = 0;
+                    info->blocked_open = 0;
+                    info->default_threshold = 0;
+                    info->default_timeout = 0;
+		    INIT_WORK(&info->tqueue, do_softint, info);
+		    init_waitqueue_head(&info->open_wait);
+		    init_waitqueue_head(&info->close_wait);
+		    init_waitqueue_head(&info->shutdown_wait);
+		    init_waitqueue_head(&info->delta_msr_wait);
+                    /* info->session */
+                    /* info->pgrp */
+                    info->read_status_mask =
+		                  CyTIMEOUT| CySPECHAR| CyBREAK
+                                  | CyPARITY| CyFRAME| CyOVERRUN;
+                    /* info->timeout */
+                }
+            }
+    }
+
+#ifndef CONFIG_CYZ_INTR
+    if (number_z_boards && !cyz_timeron){
+	cyz_timeron++;
+	cyz_timerlist.expires = jiffies + 1;
+	add_timer(&cyz_timerlist);
+#ifdef CY_PCI_DEBUG
+	printk("Cyclades-Z polling initialized\n");
+#endif
+    }
+#endif /* CONFIG_CYZ_INTR */
+
+    return 0;
+    
+} /* cy_init */
+
+static void __exit
+cy_cleanup_module(void)
+{
+    int i, e1;
+
+#ifndef CONFIG_CYZ_INTR
+    if (cyz_timeron){
+	cyz_timeron = 0;
+	del_timer(&cyz_timerlist);
+    }
+#endif /* CONFIG_CYZ_INTR */
+
+    if ((e1 = tty_unregister_driver(cy_serial_driver)))
+            printk("cyc: failed to unregister Cyclades serial driver(%d)\n",
+		e1);
+
+    put_tty_driver(cy_serial_driver);
+
+    for (i = 0; i < NR_CARDS; i++) {
+        if (cy_card[i].base_addr) {
+	    iounmap(cy_card[i].base_addr);
+	    if (cy_card[i].ctl_addr)
+		iounmap(cy_card[i].ctl_addr);
+	    if (cy_card[i].irq
+#ifndef CONFIG_CYZ_INTR
+		&& cy_card[i].num_chips != -1 /* not a Z card */
+#endif /* CONFIG_CYZ_INTR */
+	    )
+		free_irq(cy_card[i].irq, &cy_card[i]);
+#ifdef CONFIG_PCI
+		if (cy_card[i].pdev)
+			pci_release_regions(cy_card[i].pdev);
+#endif
+        }
+    }
+    if (tmp_buf) {
+	free_page((unsigned long) tmp_buf);
+	tmp_buf = NULL;
+    }
+} /* cy_cleanup_module */
+
+module_init(cy_init);
+module_exit(cy_cleanup_module);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/decserial.c b/drivers/char/decserial.c
new file mode 100644
index 0000000..aa14409
--- /dev/null
+++ b/drivers/char/decserial.c
@@ -0,0 +1,100 @@
+/*
+ * sercons.c
+ *      choose the right serial device at boot time
+ *
+ * triemer 6-SEP-1998
+ *      sercons.c is designed to allow the three different kinds 
+ *      of serial devices under the decstation world to co-exist
+ *      in the same kernel.  The idea here is to abstract 
+ *      the pieces of the drivers that are common to this file
+ *      so that they do not clash at compile time and runtime.
+ *
+ * HK 16-SEP-1998 v0.002
+ *      removed the PROM console as this is not a real serial
+ *      device. Added support for PROM console in drivers/char/tty_io.c
+ *      instead. Although it may work to enable more than one 
+ *      console device I strongly recommend to use only one.
+ */
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <asm/dec/machtype.h>
+
+#ifdef CONFIG_ZS
+extern int zs_init(void);
+#endif
+
+#ifdef CONFIG_DZ
+extern int dz_init(void);
+#endif
+
+#ifdef CONFIG_SERIAL_CONSOLE
+
+#ifdef CONFIG_ZS
+extern void zs_serial_console_init(void);
+#endif
+
+#ifdef CONFIG_DZ
+extern void dz_serial_console_init(void);
+#endif
+
+#endif
+
+/* rs_init - starts up the serial interface -
+   handle normal case of starting up the serial interface */
+
+#ifdef CONFIG_SERIAL
+
+int __init rs_init(void)
+{
+
+#if defined(CONFIG_ZS) && defined(CONFIG_DZ)
+    if (IOASIC)
+	return zs_init();
+    else
+	return dz_init();
+#else
+
+#ifdef CONFIG_ZS
+    return zs_init();
+#endif
+
+#ifdef CONFIG_DZ
+    return dz_init();
+#endif
+
+#endif
+}
+
+__initcall(rs_init);
+
+#endif
+
+#ifdef CONFIG_SERIAL_CONSOLE
+
+/* serial_console_init handles the special case of starting
+ *   up the console on the serial port
+ */
+static int __init decserial_console_init(void)
+{
+#if defined(CONFIG_ZS) && defined(CONFIG_DZ)
+    if (IOASIC)
+	zs_serial_console_init();
+    else
+	dz_serial_console_init();
+#else
+
+#ifdef CONFIG_ZS
+    zs_serial_console_init();
+#endif
+
+#ifdef CONFIG_DZ
+    dz_serial_console_init();
+#endif
+
+#endif
+    return 0;
+}
+console_initcall(decserial_console_init);
+
+#endif
diff --git a/drivers/char/defkeymap.c_shipped b/drivers/char/defkeymap.c_shipped
new file mode 100644
index 0000000..453a2f1
--- /dev/null
+++ b/drivers/char/defkeymap.c_shipped
@@ -0,0 +1,262 @@
+/* Do not edit this file! It was automatically generated by   */
+/*    loadkeys --mktable defkeymap.map > defkeymap.c          */
+
+#include <linux/types.h>
+#include <linux/keyboard.h>
+#include <linux/kd.h>
+
+u_short plain_map[NR_KEYS] = {
+	0xf200,	0xf01b,	0xf031,	0xf032,	0xf033,	0xf034,	0xf035,	0xf036,
+	0xf037,	0xf038,	0xf039,	0xf030,	0xf02d,	0xf03d,	0xf07f,	0xf009,
+	0xfb71,	0xfb77,	0xfb65,	0xfb72,	0xfb74,	0xfb79,	0xfb75,	0xfb69,
+	0xfb6f,	0xfb70,	0xf05b,	0xf05d,	0xf201,	0xf702,	0xfb61,	0xfb73,
+	0xfb64,	0xfb66,	0xfb67,	0xfb68,	0xfb6a,	0xfb6b,	0xfb6c,	0xf03b,
+	0xf027,	0xf060,	0xf700,	0xf05c,	0xfb7a,	0xfb78,	0xfb63,	0xfb76,
+	0xfb62,	0xfb6e,	0xfb6d,	0xf02c,	0xf02e,	0xf02f,	0xf700,	0xf30c,
+	0xf703,	0xf020,	0xf207,	0xf100,	0xf101,	0xf102,	0xf103,	0xf104,
+	0xf105,	0xf106,	0xf107,	0xf108,	0xf109,	0xf208,	0xf209,	0xf307,
+	0xf308,	0xf309,	0xf30b,	0xf304,	0xf305,	0xf306,	0xf30a,	0xf301,
+	0xf302,	0xf303,	0xf300,	0xf310,	0xf206,	0xf200,	0xf03c,	0xf10a,
+	0xf10b,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf30e,	0xf702,	0xf30d,	0xf01c,	0xf701,	0xf205,	0xf114,	0xf603,
+	0xf118,	0xf601,	0xf602,	0xf117,	0xf600,	0xf119,	0xf115,	0xf116,
+	0xf11a,	0xf10c,	0xf10d,	0xf11b,	0xf11c,	0xf110,	0xf311,	0xf11d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+};
+
+u_short shift_map[NR_KEYS] = {
+	0xf200,	0xf01b,	0xf021,	0xf040,	0xf023,	0xf024,	0xf025,	0xf05e,
+	0xf026,	0xf02a,	0xf028,	0xf029,	0xf05f,	0xf02b,	0xf07f,	0xf009,
+	0xfb51,	0xfb57,	0xfb45,	0xfb52,	0xfb54,	0xfb59,	0xfb55,	0xfb49,
+	0xfb4f,	0xfb50,	0xf07b,	0xf07d,	0xf201,	0xf702,	0xfb41,	0xfb53,
+	0xfb44,	0xfb46,	0xfb47,	0xfb48,	0xfb4a,	0xfb4b,	0xfb4c,	0xf03a,
+	0xf022,	0xf07e,	0xf700,	0xf07c,	0xfb5a,	0xfb58,	0xfb43,	0xfb56,
+	0xfb42,	0xfb4e,	0xfb4d,	0xf03c,	0xf03e,	0xf03f,	0xf700,	0xf30c,
+	0xf703,	0xf020,	0xf207,	0xf10a,	0xf10b,	0xf10c,	0xf10d,	0xf10e,
+	0xf10f,	0xf110,	0xf111,	0xf112,	0xf113,	0xf213,	0xf203,	0xf307,
+	0xf308,	0xf309,	0xf30b,	0xf304,	0xf305,	0xf306,	0xf30a,	0xf301,
+	0xf302,	0xf303,	0xf300,	0xf310,	0xf206,	0xf200,	0xf03e,	0xf10a,
+	0xf10b,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf30e,	0xf702,	0xf30d,	0xf200,	0xf701,	0xf205,	0xf114,	0xf603,
+	0xf20b,	0xf601,	0xf602,	0xf117,	0xf600,	0xf20a,	0xf115,	0xf116,
+	0xf11a,	0xf10c,	0xf10d,	0xf11b,	0xf11c,	0xf110,	0xf311,	0xf11d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+};
+
+u_short altgr_map[NR_KEYS] = {
+	0xf200,	0xf200,	0xf200,	0xf040,	0xf200,	0xf024,	0xf200,	0xf200,
+	0xf07b,	0xf05b,	0xf05d,	0xf07d,	0xf05c,	0xf200,	0xf200,	0xf200,
+	0xfb71,	0xfb77,	0xf918,	0xfb72,	0xfb74,	0xfb79,	0xfb75,	0xfb69,
+	0xfb6f,	0xfb70,	0xf200,	0xf07e,	0xf201,	0xf702,	0xf914,	0xfb73,
+	0xf917,	0xf919,	0xfb67,	0xfb68,	0xfb6a,	0xfb6b,	0xfb6c,	0xf200,
+	0xf200,	0xf200,	0xf700,	0xf200,	0xfb7a,	0xfb78,	0xf916,	0xfb76,
+	0xf915,	0xfb6e,	0xfb6d,	0xf200,	0xf200,	0xf200,	0xf700,	0xf30c,
+	0xf703,	0xf200,	0xf207,	0xf50c,	0xf50d,	0xf50e,	0xf50f,	0xf510,
+	0xf511,	0xf512,	0xf513,	0xf514,	0xf515,	0xf208,	0xf202,	0xf911,
+	0xf912,	0xf913,	0xf30b,	0xf90e,	0xf90f,	0xf910,	0xf30a,	0xf90b,
+	0xf90c,	0xf90d,	0xf90a,	0xf310,	0xf206,	0xf200,	0xf07c,	0xf516,
+	0xf517,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf30e,	0xf702,	0xf30d,	0xf200,	0xf701,	0xf205,	0xf114,	0xf603,
+	0xf118,	0xf601,	0xf602,	0xf117,	0xf600,	0xf119,	0xf115,	0xf116,
+	0xf11a,	0xf10c,	0xf10d,	0xf11b,	0xf11c,	0xf110,	0xf311,	0xf11d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+};
+
+u_short ctrl_map[NR_KEYS] = {
+	0xf200,	0xf200,	0xf200,	0xf000,	0xf01b,	0xf01c,	0xf01d,	0xf01e,
+	0xf01f,	0xf07f,	0xf200,	0xf200,	0xf01f,	0xf200,	0xf008,	0xf200,
+	0xf011,	0xf017,	0xf005,	0xf012,	0xf014,	0xf019,	0xf015,	0xf009,
+	0xf00f,	0xf010,	0xf01b,	0xf01d,	0xf201,	0xf702,	0xf001,	0xf013,
+	0xf004,	0xf006,	0xf007,	0xf008,	0xf00a,	0xf00b,	0xf00c,	0xf200,
+	0xf007,	0xf000,	0xf700,	0xf01c,	0xf01a,	0xf018,	0xf003,	0xf016,
+	0xf002,	0xf00e,	0xf00d,	0xf200,	0xf20e,	0xf07f,	0xf700,	0xf30c,
+	0xf703,	0xf000,	0xf207,	0xf100,	0xf101,	0xf102,	0xf103,	0xf104,
+	0xf105,	0xf106,	0xf107,	0xf108,	0xf109,	0xf208,	0xf204,	0xf307,
+	0xf308,	0xf309,	0xf30b,	0xf304,	0xf305,	0xf306,	0xf30a,	0xf301,
+	0xf302,	0xf303,	0xf300,	0xf310,	0xf206,	0xf200,	0xf200,	0xf10a,
+	0xf10b,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf30e,	0xf702,	0xf30d,	0xf01c,	0xf701,	0xf205,	0xf114,	0xf603,
+	0xf118,	0xf601,	0xf602,	0xf117,	0xf600,	0xf119,	0xf115,	0xf116,
+	0xf11a,	0xf10c,	0xf10d,	0xf11b,	0xf11c,	0xf110,	0xf311,	0xf11d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+};
+
+u_short shift_ctrl_map[NR_KEYS] = {
+	0xf200,	0xf200,	0xf200,	0xf000,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf01f,	0xf200,	0xf200,	0xf200,
+	0xf011,	0xf017,	0xf005,	0xf012,	0xf014,	0xf019,	0xf015,	0xf009,
+	0xf00f,	0xf010,	0xf200,	0xf200,	0xf201,	0xf702,	0xf001,	0xf013,
+	0xf004,	0xf006,	0xf007,	0xf008,	0xf00a,	0xf00b,	0xf00c,	0xf200,
+	0xf200,	0xf200,	0xf700,	0xf200,	0xf01a,	0xf018,	0xf003,	0xf016,
+	0xf002,	0xf00e,	0xf00d,	0xf200,	0xf200,	0xf200,	0xf700,	0xf30c,
+	0xf703,	0xf200,	0xf207,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf208,	0xf200,	0xf307,
+	0xf308,	0xf309,	0xf30b,	0xf304,	0xf305,	0xf306,	0xf30a,	0xf301,
+	0xf302,	0xf303,	0xf300,	0xf310,	0xf206,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf30e,	0xf702,	0xf30d,	0xf200,	0xf701,	0xf205,	0xf114,	0xf603,
+	0xf118,	0xf601,	0xf602,	0xf117,	0xf600,	0xf119,	0xf115,	0xf116,
+	0xf11a,	0xf10c,	0xf10d,	0xf11b,	0xf11c,	0xf110,	0xf311,	0xf11d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+};
+
+u_short alt_map[NR_KEYS] = {
+	0xf200,	0xf81b,	0xf831,	0xf832,	0xf833,	0xf834,	0xf835,	0xf836,
+	0xf837,	0xf838,	0xf839,	0xf830,	0xf82d,	0xf83d,	0xf87f,	0xf809,
+	0xf871,	0xf877,	0xf865,	0xf872,	0xf874,	0xf879,	0xf875,	0xf869,
+	0xf86f,	0xf870,	0xf85b,	0xf85d,	0xf80d,	0xf702,	0xf861,	0xf873,
+	0xf864,	0xf866,	0xf867,	0xf868,	0xf86a,	0xf86b,	0xf86c,	0xf83b,
+	0xf827,	0xf860,	0xf700,	0xf85c,	0xf87a,	0xf878,	0xf863,	0xf876,
+	0xf862,	0xf86e,	0xf86d,	0xf82c,	0xf82e,	0xf82f,	0xf700,	0xf30c,
+	0xf703,	0xf820,	0xf207,	0xf500,	0xf501,	0xf502,	0xf503,	0xf504,
+	0xf505,	0xf506,	0xf507,	0xf508,	0xf509,	0xf208,	0xf209,	0xf907,
+	0xf908,	0xf909,	0xf30b,	0xf904,	0xf905,	0xf906,	0xf30a,	0xf901,
+	0xf902,	0xf903,	0xf900,	0xf310,	0xf206,	0xf200,	0xf83c,	0xf50a,
+	0xf50b,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf30e,	0xf702,	0xf30d,	0xf01c,	0xf701,	0xf205,	0xf114,	0xf603,
+	0xf118,	0xf210,	0xf211,	0xf117,	0xf600,	0xf119,	0xf115,	0xf116,
+	0xf11a,	0xf10c,	0xf10d,	0xf11b,	0xf11c,	0xf110,	0xf311,	0xf11d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+};
+
+u_short ctrl_alt_map[NR_KEYS] = {
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf811,	0xf817,	0xf805,	0xf812,	0xf814,	0xf819,	0xf815,	0xf809,
+	0xf80f,	0xf810,	0xf200,	0xf200,	0xf201,	0xf702,	0xf801,	0xf813,
+	0xf804,	0xf806,	0xf807,	0xf808,	0xf80a,	0xf80b,	0xf80c,	0xf200,
+	0xf200,	0xf200,	0xf700,	0xf200,	0xf81a,	0xf818,	0xf803,	0xf816,
+	0xf802,	0xf80e,	0xf80d,	0xf200,	0xf200,	0xf200,	0xf700,	0xf30c,
+	0xf703,	0xf200,	0xf207,	0xf500,	0xf501,	0xf502,	0xf503,	0xf504,
+	0xf505,	0xf506,	0xf507,	0xf508,	0xf509,	0xf208,	0xf200,	0xf307,
+	0xf308,	0xf309,	0xf30b,	0xf304,	0xf305,	0xf306,	0xf30a,	0xf301,
+	0xf302,	0xf303,	0xf300,	0xf20c,	0xf206,	0xf200,	0xf200,	0xf50a,
+	0xf50b,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf30e,	0xf702,	0xf30d,	0xf200,	0xf701,	0xf205,	0xf114,	0xf603,
+	0xf118,	0xf601,	0xf602,	0xf117,	0xf600,	0xf119,	0xf115,	0xf20c,
+	0xf11a,	0xf10c,	0xf10d,	0xf11b,	0xf11c,	0xf110,	0xf311,	0xf11d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+};
+
+ushort *key_maps[MAX_NR_KEYMAPS] = {
+	plain_map, shift_map, altgr_map, NULL,
+	ctrl_map, shift_ctrl_map, NULL, NULL,
+	alt_map, NULL, NULL, NULL,
+	ctrl_alt_map, NULL
+};
+
+unsigned int keymap_count = 7;
+
+/*
+ * Philosophy: most people do not define more strings, but they who do
+ * often want quite a lot of string space. So, we statically allocate
+ * the default and allocate dynamically in chunks of 512 bytes.
+ */
+
+char func_buf[] = {
+	'\033', '[', '[', 'A', 0, 
+	'\033', '[', '[', 'B', 0, 
+	'\033', '[', '[', 'C', 0, 
+	'\033', '[', '[', 'D', 0, 
+	'\033', '[', '[', 'E', 0, 
+	'\033', '[', '1', '7', '~', 0, 
+	'\033', '[', '1', '8', '~', 0, 
+	'\033', '[', '1', '9', '~', 0, 
+	'\033', '[', '2', '0', '~', 0, 
+	'\033', '[', '2', '1', '~', 0, 
+	'\033', '[', '2', '3', '~', 0, 
+	'\033', '[', '2', '4', '~', 0, 
+	'\033', '[', '2', '5', '~', 0, 
+	'\033', '[', '2', '6', '~', 0, 
+	'\033', '[', '2', '8', '~', 0, 
+	'\033', '[', '2', '9', '~', 0, 
+	'\033', '[', '3', '1', '~', 0, 
+	'\033', '[', '3', '2', '~', 0, 
+	'\033', '[', '3', '3', '~', 0, 
+	'\033', '[', '3', '4', '~', 0, 
+	'\033', '[', '1', '~', 0, 
+	'\033', '[', '2', '~', 0, 
+	'\033', '[', '3', '~', 0, 
+	'\033', '[', '4', '~', 0, 
+	'\033', '[', '5', '~', 0, 
+	'\033', '[', '6', '~', 0, 
+	'\033', '[', 'M', 0, 
+	'\033', '[', 'P', 0, 
+};
+
+char *funcbufptr = func_buf;
+int funcbufsize = sizeof(func_buf);
+int funcbufleft = 0;          /* space left */
+
+char *func_table[MAX_NR_FUNC] = {
+	func_buf + 0,
+	func_buf + 5,
+	func_buf + 10,
+	func_buf + 15,
+	func_buf + 20,
+	func_buf + 25,
+	func_buf + 31,
+	func_buf + 37,
+	func_buf + 43,
+	func_buf + 49,
+	func_buf + 55,
+	func_buf + 61,
+	func_buf + 67,
+	func_buf + 73,
+	func_buf + 79,
+	func_buf + 85,
+	func_buf + 91,
+	func_buf + 97,
+	func_buf + 103,
+	func_buf + 109,
+	func_buf + 115,
+	func_buf + 120,
+	func_buf + 125,
+	func_buf + 130,
+	func_buf + 135,
+	func_buf + 140,
+	func_buf + 145,
+	NULL,
+	NULL,
+	func_buf + 149,
+	NULL,
+};
+
+struct kbdiacr accent_table[MAX_DIACR] = {
+	{'`', 'A', '\300'},	{'`', 'a', '\340'},
+	{'\'', 'A', '\301'},	{'\'', 'a', '\341'},
+	{'^', 'A', '\302'},	{'^', 'a', '\342'},
+	{'~', 'A', '\303'},	{'~', 'a', '\343'},
+	{'"', 'A', '\304'},	{'"', 'a', '\344'},
+	{'O', 'A', '\305'},	{'o', 'a', '\345'},
+	{'0', 'A', '\305'},	{'0', 'a', '\345'},
+	{'A', 'A', '\305'},	{'a', 'a', '\345'},
+	{'A', 'E', '\306'},	{'a', 'e', '\346'},
+	{',', 'C', '\307'},	{',', 'c', '\347'},
+	{'`', 'E', '\310'},	{'`', 'e', '\350'},
+	{'\'', 'E', '\311'},	{'\'', 'e', '\351'},
+	{'^', 'E', '\312'},	{'^', 'e', '\352'},
+	{'"', 'E', '\313'},	{'"', 'e', '\353'},
+	{'`', 'I', '\314'},	{'`', 'i', '\354'},
+	{'\'', 'I', '\315'},	{'\'', 'i', '\355'},
+	{'^', 'I', '\316'},	{'^', 'i', '\356'},
+	{'"', 'I', '\317'},	{'"', 'i', '\357'},
+	{'-', 'D', '\320'},	{'-', 'd', '\360'},
+	{'~', 'N', '\321'},	{'~', 'n', '\361'},
+	{'`', 'O', '\322'},	{'`', 'o', '\362'},
+	{'\'', 'O', '\323'},	{'\'', 'o', '\363'},
+	{'^', 'O', '\324'},	{'^', 'o', '\364'},
+	{'~', 'O', '\325'},	{'~', 'o', '\365'},
+	{'"', 'O', '\326'},	{'"', 'o', '\366'},
+	{'/', 'O', '\330'},	{'/', 'o', '\370'},
+	{'`', 'U', '\331'},	{'`', 'u', '\371'},
+	{'\'', 'U', '\332'},	{'\'', 'u', '\372'},
+	{'^', 'U', '\333'},	{'^', 'u', '\373'},
+	{'"', 'U', '\334'},	{'"', 'u', '\374'},
+	{'\'', 'Y', '\335'},	{'\'', 'y', '\375'},
+	{'T', 'H', '\336'},	{'t', 'h', '\376'},
+	{'s', 's', '\337'},	{'"', 'y', '\377'},
+	{'s', 'z', '\337'},	{'i', 'j', '\377'},
+};
+
+unsigned int accent_table_size = 68;
diff --git a/drivers/char/defkeymap.map b/drivers/char/defkeymap.map
new file mode 100644
index 0000000..50b30ca
--- /dev/null
+++ b/drivers/char/defkeymap.map
@@ -0,0 +1,357 @@
+# Default kernel keymap. This uses 7 modifier combinations.
+keymaps 0-2,4-5,8,12
+# Change the above line into
+#	keymaps 0-2,4-6,8,12
+# in case you want the entries
+#	altgr   control keycode  83 = Boot            
+#	altgr   control keycode 111 = Boot            
+# below.
+#
+# In fact AltGr is used very little, and one more keymap can
+# be saved by mapping AltGr to Alt (and adapting a few entries):
+# keycode 100 = Alt
+#
+keycode   1 = Escape           Escape          
+	alt     keycode   1 = Meta_Escape     
+keycode   2 = one              exclam          
+	alt     keycode   2 = Meta_one        
+keycode   3 = two              at               at              
+	control	keycode   3 = nul             
+	shift	control	keycode   3 = nul             
+	alt	keycode   3 = Meta_two        
+keycode   4 = three            numbersign      
+	control keycode   4 = Escape          
+	alt     keycode   4 = Meta_three      
+keycode   5 = four             dollar           dollar          
+	control keycode   5 = Control_backslash
+	alt     keycode   5 = Meta_four       
+keycode   6 = five             percent         
+	control keycode   6 = Control_bracketright
+	alt     keycode   6 = Meta_five       
+keycode   7 = six              asciicircum     
+	control keycode   7 = Control_asciicircum
+	alt     keycode   7 = Meta_six        
+keycode   8 = seven            ampersand        braceleft       
+	control keycode   8 = Control_underscore
+	alt     keycode   8 = Meta_seven      
+keycode   9 = eight            asterisk         bracketleft     
+	control keycode   9 = Delete          
+	alt     keycode   9 = Meta_eight      
+keycode  10 = nine             parenleft        bracketright    
+	alt     keycode  10 = Meta_nine       
+keycode  11 = zero             parenright       braceright      
+	alt     keycode  11 = Meta_zero       
+keycode  12 = minus            underscore       backslash       
+	control	keycode  12 = Control_underscore
+	shift	control	keycode  12 = Control_underscore
+	alt	keycode  12 = Meta_minus      
+keycode  13 = equal            plus            
+	alt     keycode  13 = Meta_equal      
+keycode  14 = Delete           Delete          
+	control keycode  14 = BackSpace
+	alt     keycode  14 = Meta_Delete     
+keycode  15 = Tab              Tab             
+	alt     keycode  15 = Meta_Tab        
+keycode  16 = q               
+keycode  17 = w               
+keycode  18 = e
+	altgr   keycode  18 = Hex_E   
+keycode  19 = r               
+keycode  20 = t               
+keycode  21 = y               
+keycode  22 = u               
+keycode  23 = i               
+keycode  24 = o               
+keycode  25 = p               
+keycode  26 = bracketleft      braceleft       
+	control keycode  26 = Escape          
+	alt     keycode  26 = Meta_bracketleft
+keycode  27 = bracketright     braceright       asciitilde      
+	control keycode  27 = Control_bracketright
+	alt     keycode  27 = Meta_bracketright
+keycode  28 = Return          
+	alt     keycode  28 = Meta_Control_m  
+keycode  29 = Control         
+keycode  30 = a
+	altgr   keycode  30 = Hex_A
+keycode  31 = s               
+keycode  32 = d
+	altgr   keycode  32 = Hex_D   
+keycode  33 = f
+	altgr   keycode  33 = Hex_F               
+keycode  34 = g               
+keycode  35 = h               
+keycode  36 = j               
+keycode  37 = k               
+keycode  38 = l               
+keycode  39 = semicolon        colon           
+	alt     keycode  39 = Meta_semicolon  
+keycode  40 = apostrophe       quotedbl        
+	control keycode  40 = Control_g       
+	alt     keycode  40 = Meta_apostrophe 
+keycode  41 = grave            asciitilde      
+	control keycode  41 = nul             
+	alt     keycode  41 = Meta_grave      
+keycode  42 = Shift           
+keycode  43 = backslash        bar             
+	control keycode  43 = Control_backslash
+	alt     keycode  43 = Meta_backslash  
+keycode  44 = z               
+keycode  45 = x               
+keycode  46 = c
+	altgr   keycode  46 = Hex_C   
+keycode  47 = v               
+keycode  48 = b
+	altgr   keycode  48 = Hex_B
+keycode  49 = n               
+keycode  50 = m               
+keycode  51 = comma            less            
+	alt     keycode  51 = Meta_comma      
+keycode  52 = period           greater         
+	control keycode  52 = Compose         
+	alt     keycode  52 = Meta_period     
+keycode  53 = slash            question        
+	control keycode  53 = Delete          
+	alt     keycode  53 = Meta_slash      
+keycode  54 = Shift           
+keycode  55 = KP_Multiply     
+keycode  56 = Alt             
+keycode  57 = space            space           
+	control keycode  57 = nul             
+	alt     keycode  57 = Meta_space      
+keycode  58 = Caps_Lock       
+keycode  59 = F1               F11              Console_13      
+	control keycode  59 = F1              
+	alt     keycode  59 = Console_1       
+	control alt     keycode  59 = Console_1       
+keycode  60 = F2               F12              Console_14      
+	control keycode  60 = F2              
+	alt     keycode  60 = Console_2       
+	control alt     keycode  60 = Console_2       
+keycode  61 = F3               F13              Console_15      
+	control keycode  61 = F3              
+	alt     keycode  61 = Console_3       
+	control alt     keycode  61 = Console_3       
+keycode  62 = F4               F14              Console_16      
+	control keycode  62 = F4              
+	alt     keycode  62 = Console_4       
+	control alt     keycode  62 = Console_4       
+keycode  63 = F5               F15              Console_17      
+	control keycode  63 = F5              
+	alt     keycode  63 = Console_5       
+	control alt     keycode  63 = Console_5       
+keycode  64 = F6               F16              Console_18      
+	control keycode  64 = F6              
+	alt     keycode  64 = Console_6       
+	control alt     keycode  64 = Console_6       
+keycode  65 = F7               F17              Console_19      
+	control keycode  65 = F7              
+	alt     keycode  65 = Console_7       
+	control alt     keycode  65 = Console_7       
+keycode  66 = F8               F18              Console_20      
+	control keycode  66 = F8              
+	alt     keycode  66 = Console_8       
+	control alt     keycode  66 = Console_8       
+keycode  67 = F9               F19              Console_21      
+	control keycode  67 = F9              
+	alt     keycode  67 = Console_9       
+	control alt     keycode  67 = Console_9       
+keycode  68 = F10              F20              Console_22      
+	control keycode  68 = F10             
+	alt     keycode  68 = Console_10      
+	control alt     keycode  68 = Console_10      
+keycode  69 = Num_Lock
+	shift   keycode  69 = Bare_Num_Lock
+keycode  70 = Scroll_Lock      Show_Memory      Show_Registers  
+	control keycode  70 = Show_State      
+	alt     keycode  70 = Scroll_Lock     
+keycode  71 = KP_7            
+	alt     keycode  71 = Ascii_7         
+	altgr   keycode  71 = Hex_7         
+keycode  72 = KP_8            
+	alt     keycode  72 = Ascii_8         
+	altgr   keycode  72 = Hex_8         
+keycode  73 = KP_9            
+	alt     keycode  73 = Ascii_9         
+	altgr   keycode  73 = Hex_9         
+keycode  74 = KP_Subtract     
+keycode  75 = KP_4            
+	alt     keycode  75 = Ascii_4         
+	altgr   keycode  75 = Hex_4         
+keycode  76 = KP_5            
+	alt     keycode  76 = Ascii_5         
+	altgr   keycode  76 = Hex_5         
+keycode  77 = KP_6            
+	alt     keycode  77 = Ascii_6         
+	altgr   keycode  77 = Hex_6         
+keycode  78 = KP_Add          
+keycode  79 = KP_1            
+	alt     keycode  79 = Ascii_1         
+	altgr   keycode  79 = Hex_1         
+keycode  80 = KP_2            
+	alt     keycode  80 = Ascii_2         
+	altgr   keycode  80 = Hex_2         
+keycode  81 = KP_3            
+	alt     keycode  81 = Ascii_3         
+	altgr   keycode  81 = Hex_3         
+keycode  82 = KP_0            
+	alt     keycode  82 = Ascii_0         
+	altgr   keycode  82 = Hex_0         
+keycode  83 = KP_Period       
+#	altgr   control keycode  83 = Boot            
+	control alt     keycode  83 = Boot            
+keycode  84 = Last_Console    
+keycode  85 =
+keycode  86 = less             greater          bar             
+	alt     keycode  86 = Meta_less       
+keycode  87 = F11              F11              Console_23      
+	control keycode  87 = F11             
+	alt     keycode  87 = Console_11      
+	control alt     keycode  87 = Console_11      
+keycode  88 = F12              F12              Console_24      
+	control keycode  88 = F12             
+	alt     keycode  88 = Console_12      
+	control alt     keycode  88 = Console_12      
+keycode  89 =
+keycode  90 =
+keycode  91 =
+keycode  92 =
+keycode  93 =
+keycode  94 =
+keycode  95 =
+keycode  96 = KP_Enter        
+keycode  97 = Control         
+keycode  98 = KP_Divide       
+keycode  99 = Control_backslash
+	control keycode  99 = Control_backslash
+	alt     keycode  99 = Control_backslash
+keycode 100 = AltGr           
+keycode 101 = Break           
+keycode 102 = Find            
+keycode 103 = Up              
+keycode 104 = Prior           
+	shift   keycode 104 = Scroll_Backward 
+keycode 105 = Left            
+	alt     keycode 105 = Decr_Console
+keycode 106 = Right           
+	alt     keycode 106 = Incr_Console
+keycode 107 = Select          
+keycode 108 = Down            
+keycode 109 = Next            
+	shift   keycode 109 = Scroll_Forward  
+keycode 110 = Insert          
+keycode 111 = Remove          
+#	altgr   control keycode 111 = Boot            
+	control alt     keycode 111 = Boot            
+keycode 112 = Macro           
+keycode 113 = F13             
+keycode 114 = F14             
+keycode 115 = Help            
+keycode 116 = Do              
+keycode 117 = F17             
+keycode 118 = KP_MinPlus      
+keycode 119 = Pause           
+keycode 120 =
+keycode 121 =
+keycode 122 =
+keycode 123 =
+keycode 124 =
+keycode 125 =
+keycode 126 =
+keycode 127 =
+string F1 = "\033[[A"
+string F2 = "\033[[B"
+string F3 = "\033[[C"
+string F4 = "\033[[D"
+string F5 = "\033[[E"
+string F6 = "\033[17~"
+string F7 = "\033[18~"
+string F8 = "\033[19~"
+string F9 = "\033[20~"
+string F10 = "\033[21~"
+string F11 = "\033[23~"
+string F12 = "\033[24~"
+string F13 = "\033[25~"
+string F14 = "\033[26~"
+string F15 = "\033[28~"
+string F16 = "\033[29~"
+string F17 = "\033[31~"
+string F18 = "\033[32~"
+string F19 = "\033[33~"
+string F20 = "\033[34~"
+string Find = "\033[1~"
+string Insert = "\033[2~"
+string Remove = "\033[3~"
+string Select = "\033[4~"
+string Prior = "\033[5~"
+string Next = "\033[6~"
+string Macro = "\033[M"
+string Pause = "\033[P"
+compose '`' 'A' to 'À'
+compose '`' 'a' to 'à'
+compose '\'' 'A' to 'Á'
+compose '\'' 'a' to 'á'
+compose '^' 'A' to 'Â'
+compose '^' 'a' to 'â'
+compose '~' 'A' to 'Ã'
+compose '~' 'a' to 'ã'
+compose '"' 'A' to 'Ä'
+compose '"' 'a' to 'ä'
+compose 'O' 'A' to 'Å'
+compose 'o' 'a' to 'å'
+compose '0' 'A' to 'Å'
+compose '0' 'a' to 'å'
+compose 'A' 'A' to 'Å'
+compose 'a' 'a' to 'å'
+compose 'A' 'E' to 'Æ'
+compose 'a' 'e' to 'æ'
+compose ',' 'C' to 'Ç'
+compose ',' 'c' to 'ç'
+compose '`' 'E' to 'È'
+compose '`' 'e' to 'è'
+compose '\'' 'E' to 'É'
+compose '\'' 'e' to 'é'
+compose '^' 'E' to 'Ê'
+compose '^' 'e' to 'ê'
+compose '"' 'E' to 'Ë'
+compose '"' 'e' to 'ë'
+compose '`' 'I' to 'Ì'
+compose '`' 'i' to 'ì'
+compose '\'' 'I' to 'Í'
+compose '\'' 'i' to 'í'
+compose '^' 'I' to 'Î'
+compose '^' 'i' to 'î'
+compose '"' 'I' to 'Ï'
+compose '"' 'i' to 'ï'
+compose '-' 'D' to 'Ð'
+compose '-' 'd' to 'ð'
+compose '~' 'N' to 'Ñ'
+compose '~' 'n' to 'ñ'
+compose '`' 'O' to 'Ò'
+compose '`' 'o' to 'ò'
+compose '\'' 'O' to 'Ó'
+compose '\'' 'o' to 'ó'
+compose '^' 'O' to 'Ô'
+compose '^' 'o' to 'ô'
+compose '~' 'O' to 'Õ'
+compose '~' 'o' to 'õ'
+compose '"' 'O' to 'Ö'
+compose '"' 'o' to 'ö'
+compose '/' 'O' to 'Ø'
+compose '/' 'o' to 'ø'
+compose '`' 'U' to 'Ù'
+compose '`' 'u' to 'ù'
+compose '\'' 'U' to 'Ú'
+compose '\'' 'u' to 'ú'
+compose '^' 'U' to 'Û'
+compose '^' 'u' to 'û'
+compose '"' 'U' to 'Ü'
+compose '"' 'u' to 'ü'
+compose '\'' 'Y' to 'Ý'
+compose '\'' 'y' to 'ý'
+compose 'T' 'H' to 'Þ'
+compose 't' 'h' to 'þ'
+compose 's' 's' to 'ß'
+compose '"' 'y' to 'ÿ'
+compose 's' 'z' to 'ß'
+compose 'i' 'j' to 'ÿ'
diff --git a/drivers/char/digi.h b/drivers/char/digi.h
new file mode 100644
index 0000000..19df0e8
--- /dev/null
+++ b/drivers/char/digi.h
@@ -0,0 +1,71 @@
+/*          Definitions for DigiBoard ditty(1) command.                 */
+
+#if !defined(TIOCMODG)
+#define	TIOCMODG	(('d'<<8) | 250)		/* get modem ctrl state	*/
+#define	TIOCMODS	(('d'<<8) | 251)		/* set modem ctrl state	*/
+#endif
+
+#if !defined(TIOCMSET)
+#define	TIOCMSET	(('d'<<8) | 252)		/* set modem ctrl state	*/
+#define	TIOCMGET	(('d'<<8) | 253)		/* set modem ctrl state	*/
+#endif
+
+#if !defined(TIOCMBIC)
+#define	TIOCMBIC	(('d'<<8) | 254)		/* set modem ctrl state */
+#define	TIOCMBIS	(('d'<<8) | 255)		/* set modem ctrl state */
+#endif
+
+#if !defined(TIOCSDTR)
+#define	TIOCSDTR	(('e'<<8) | 0)		/* set DTR		*/
+#define	TIOCCDTR	(('e'<<8) | 1)		/* clear DTR		*/
+#endif
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+#define DIGI_GETA	(('e'<<8) | 94)		/* Read params		*/
+
+#define DIGI_SETA	(('e'<<8) | 95)		/* Set params		*/
+#define DIGI_SETAW	(('e'<<8) | 96)		/* Drain & set params	*/
+#define DIGI_SETAF	(('e'<<8) | 97)		/* Drain, flush & set params */
+
+#define	DIGI_GETFLOW	(('e'<<8) | 99)		/* Get startc/stopc flow */
+						/* control characters 	 */
+#define	DIGI_SETFLOW	(('e'<<8) | 100)		/* Set startc/stopc flow */
+						/* control characters	 */
+#define	DIGI_GETAFLOW	(('e'<<8) | 101)		/* Get Aux. startc/stopc */
+						/* flow control chars 	 */
+#define	DIGI_SETAFLOW	(('e'<<8) | 102)		/* Set Aux. startc/stopc */
+						/* flow control chars	 */
+
+struct	digiflow_struct {
+	unsigned char	startc;				/* flow cntl start char	*/
+	unsigned char	stopc;				/* flow cntl stop char	*/
+};
+
+typedef struct digiflow_struct digiflow_t;
+
+
+/************************************************************************
+ * Values for digi_flags 
+ ************************************************************************/
+#define DIGI_IXON	0x0001		/* Handle IXON in the FEP	*/
+#define DIGI_FAST	0x0002		/* Fast baud rates		*/
+#define RTSPACE		0x0004		/* RTS input flow control	*/
+#define CTSPACE		0x0008		/* CTS output flow control	*/
+#define DSRPACE		0x0010		/* DSR output flow control	*/
+#define DCDPACE		0x0020		/* DCD output flow control	*/
+#define DTRPACE		0x0040		/* DTR input flow control	*/
+#define DIGI_FORCEDCD	0x0100		/* Force carrier		*/
+#define	DIGI_ALTPIN	0x0200		/* Alternate RJ-45 pin config	*/
+#define	DIGI_AIXON	0x0400		/* Aux flow control in fep	*/
+
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_struct {
+	unsigned short	digi_flags;		/* Flags (see above)	*/
+};
+
+typedef struct digi_struct digi_t;
diff --git a/drivers/char/digi1.h b/drivers/char/digi1.h
new file mode 100644
index 0000000..184378d
--- /dev/null
+++ b/drivers/char/digi1.h
@@ -0,0 +1,100 @@
+/*          Definitions for DigiBoard ditty(1) command.                 */
+
+#if !defined(TIOCMODG)
+#define	TIOCMODG	('d'<<8) | 250		/* get modem ctrl state	*/
+#define	TIOCMODS	('d'<<8) | 251		/* set modem ctrl state	*/
+#endif
+
+#if !defined(TIOCMSET)
+#define	TIOCMSET	('d'<<8) | 252		/* set modem ctrl state	*/
+#define	TIOCMGET	('d'<<8) | 253		/* set modem ctrl state	*/
+#endif
+
+#if !defined(TIOCMBIC)
+#define	TIOCMBIC	('d'<<8) | 254		/* set modem ctrl state */
+#define	TIOCMBIS	('d'<<8) | 255		/* set modem ctrl state */
+#endif
+
+#if !defined(TIOCSDTR)
+#define	TIOCSDTR	('e'<<8) | 0		/* set DTR		*/
+#define	TIOCCDTR	('e'<<8) | 1		/* clear DTR		*/
+#endif
+
+/************************************************************************
+ * Ioctl command arguments for DIGI parameters.
+ ************************************************************************/
+#define DIGI_GETA	('e'<<8) | 94		/* Read params		*/
+
+#define DIGI_SETA	('e'<<8) | 95		/* Set params		*/
+#define DIGI_SETAW	('e'<<8) | 96		/* Drain & set params	*/
+#define DIGI_SETAF	('e'<<8) | 97		/* Drain, flush & set params */
+
+#define	DIGI_GETFLOW	('e'<<8) | 99		/* Get startc/stopc flow */
+						/* control characters 	 */
+#define	DIGI_SETFLOW	('e'<<8) | 100		/* Set startc/stopc flow */
+						/* control characters	 */
+#define	DIGI_GETAFLOW	('e'<<8) | 101		/* Get Aux. startc/stopc */
+						/* flow control chars 	 */
+#define	DIGI_SETAFLOW	('e'<<8) | 102		/* Set Aux. startc/stopc */
+						/* flow control chars	 */
+
+#define	DIGI_GETINFO	('e'<<8) | 103		/* Fill in digi_info */
+#define	DIGI_POLLER	('e'<<8) | 104		/* Turn on/off poller */
+#define	DIGI_INIT	('e'<<8) | 105		/* Allow things to run. */
+
+struct	digiflow_struct 
+{
+	unsigned char	startc;				/* flow cntl start char	*/
+	unsigned char	stopc;				/* flow cntl stop char	*/
+};
+
+typedef struct digiflow_struct digiflow_t;
+
+
+/************************************************************************
+ * Values for digi_flags 
+ ************************************************************************/
+#define DIGI_IXON	0x0001		/* Handle IXON in the FEP	*/
+#define DIGI_FAST	0x0002		/* Fast baud rates		*/
+#define RTSPACE		0x0004		/* RTS input flow control	*/
+#define CTSPACE		0x0008		/* CTS output flow control	*/
+#define DSRPACE		0x0010		/* DSR output flow control	*/
+#define DCDPACE		0x0020		/* DCD output flow control	*/
+#define DTRPACE		0x0040		/* DTR input flow control	*/
+#define DIGI_FORCEDCD	0x0100		/* Force carrier		*/
+#define	DIGI_ALTPIN	0x0200		/* Alternate RJ-45 pin config	*/
+#define	DIGI_AIXON	0x0400		/* Aux flow control in fep	*/
+
+
+/************************************************************************
+ * Values for digiDload
+ ************************************************************************/
+#define NORMAL  0
+#define PCI_CTL 1
+
+#define SIZE8  0
+#define SIZE16 1
+#define SIZE32 2
+
+/************************************************************************
+ * Structure used with ioctl commands for DIGI parameters.
+ ************************************************************************/
+struct digi_struct 
+{
+	unsigned short	digi_flags;		/* Flags (see above)	*/
+};
+
+typedef struct digi_struct digi_t;
+
+struct digi_info 
+{
+	unsigned long board;        /* Which board is this ? */
+	unsigned char status;       /* Alive or dead */
+	unsigned char type;         /* see epca.h */
+	unsigned char subtype;      /* For future XEM, XR, etc ... */
+	unsigned short numports;    /* Number of ports configured */
+	unsigned char *port;        /* I/O Address */
+	unsigned char *membase;     /* DPR Address */
+	unsigned char *version;     /* For future ... */
+	unsigned short windowData;  /* For future ... */
+} ;
diff --git a/drivers/char/digiFep1.h b/drivers/char/digiFep1.h
new file mode 100644
index 0000000..c47d7fc
--- /dev/null
+++ b/drivers/char/digiFep1.h
@@ -0,0 +1,136 @@
+
+#define CSTART       0x400L
+#define CMAX         0x800L
+#define ISTART       0x800L
+#define IMAX         0xC00L
+#define CIN          0xD10L
+#define GLOBAL       0xD10L
+#define EIN          0xD18L
+#define FEPSTAT      0xD20L
+#define CHANSTRUCT   0x1000L
+#define RXTXBUF      0x4000L
+
+
+struct global_data 
+{
+	volatile ushort cin;
+	volatile ushort cout;
+	volatile ushort cstart;
+	volatile ushort cmax;
+	volatile ushort ein;
+	volatile ushort eout;
+	volatile ushort istart;
+	volatile ushort imax;
+};
+
+
+struct board_chan 
+{
+	int filler1; 
+	int filler2;
+	volatile ushort tseg;
+	volatile ushort tin;
+	volatile ushort tout;
+	volatile ushort tmax;
+	
+	volatile ushort rseg;
+	volatile ushort rin;
+	volatile ushort rout;
+	volatile ushort rmax;
+	
+	volatile ushort tlow;
+	volatile ushort rlow;
+	volatile ushort rhigh;
+	volatile ushort incr;
+	
+	volatile ushort etime;
+	volatile ushort edelay;
+	volatile unchar *dev;
+	
+	volatile ushort iflag;
+	volatile ushort oflag;
+	volatile ushort cflag;
+	volatile ushort gmask;
+	
+	volatile ushort col;
+	volatile ushort delay;
+	volatile ushort imask;
+	volatile ushort tflush;
+
+	int filler3;
+	int filler4;
+	int filler5;
+	int filler6;
+	
+	volatile unchar num;
+	volatile unchar ract;
+	volatile unchar bstat;
+	volatile unchar tbusy;
+	volatile unchar iempty;
+	volatile unchar ilow;
+	volatile unchar idata;
+	volatile unchar eflag;
+	
+	volatile unchar tflag;
+	volatile unchar rflag;
+	volatile unchar xmask;
+	volatile unchar xval;
+	volatile unchar mstat;
+	volatile unchar mchange;
+	volatile unchar mint;
+	volatile unchar lstat;
+
+	volatile unchar mtran;
+	volatile unchar orun;
+	volatile unchar startca;
+	volatile unchar stopca;
+	volatile unchar startc;
+	volatile unchar stopc;
+	volatile unchar vnext;
+	volatile unchar hflow;
+
+	volatile unchar fillc;
+	volatile unchar ochar;
+	volatile unchar omask;
+
+	unchar filler7;
+	unchar filler8[28];
+}; 
+
+
+#define SRXLWATER      0xE0
+#define SRXHWATER      0xE1
+#define STOUT          0xE2
+#define PAUSETX        0xE3
+#define RESUMETX       0xE4
+#define SAUXONOFFC     0xE6
+#define SENDBREAK      0xE8
+#define SETMODEM       0xE9
+#define SETIFLAGS      0xEA
+#define SONOFFC        0xEB
+#define STXLWATER      0xEC
+#define PAUSERX        0xEE
+#define RESUMERX       0xEF
+#define SETBUFFER      0xF2
+#define SETCOOKED      0xF3
+#define SETHFLOW       0xF4
+#define SETCTRLFLAGS   0xF5
+#define SETVNEXT       0xF6
+
+
+
+#define BREAK_IND        0x01
+#define LOWTX_IND        0x02
+#define EMPTYTX_IND      0x04
+#define DATA_IND         0x08
+#define MODEMCHG_IND     0x20
+
+#define FEP_HUPCL  0002000
+#if 0
+#define RTS   0x02
+#define CD    0x08
+#define DSR   0x10
+#define CTS   0x20
+#define RI    0x40
+#define DTR   0x80
+#endif
diff --git a/drivers/char/digiPCI.h b/drivers/char/digiPCI.h
new file mode 100644
index 0000000..6ca7819
--- /dev/null
+++ b/drivers/char/digiPCI.h
@@ -0,0 +1,42 @@
+/*************************************************************************
+ * Defines and structure definitions for PCI BIOS Interface 
+ *************************************************************************/
+#define	PCIMAX  32		/* maximum number of PCI boards */
+
+
+#define	PCI_VENDOR_DIGI		0x114F
+#define	PCI_DEVICE_EPC		0x0002
+#define	PCI_DEVICE_RIGHTSWITCH 0x0003  /* For testing */
+#define	PCI_DEVICE_XEM		0x0004
+#define	PCI_DEVICE_XR		0x0005
+#define	PCI_DEVICE_CX		0x0006
+#define	PCI_DEVICE_XRJ		0x0009   /* Jupiter boards with */
+#define	PCI_DEVICE_EPCJ		0x000a   /* PLX 9060 chip for PCI  */
+
+
+/*
+ * On the PCI boards, there is no IO space allocated 
+ * The I/O registers will be in the first 3 bytes of the   
+ * upper 2MB of the 4MB memory space.  The board memory 
+ * will be mapped into the low 2MB of the 4MB memory space 
+ */
+
+/* Potential location of PCI Bios from E0000 to FFFFF*/
+#define PCI_BIOS_SIZE		0x00020000	
+
+/* Size of Memory and I/O for PCI (4MB) */
+#define PCI_RAM_SIZE		0x00400000	
+
+/* Size of Memory (2MB) */
+#define PCI_MEM_SIZE		0x00200000	
+
+/* Offset of I/0 in Memory (2MB) */
+#define PCI_IO_OFFSET 		0x00200000	
+
+#define MEMOUTB(basemem, pnum, setmemval)  *(caddr_t)((basemem) + ( PCI_IO_OFFSET | pnum << 4 | pnum )) = (setmemval)
+#define MEMINB(basemem, pnum)  *(caddr_t)((basemem) + (PCI_IO_OFFSET | pnum << 4 | pnum ))   /* for PCI I/O */
+
+
+
+
+
diff --git a/drivers/char/drm/Kconfig b/drivers/char/drm/Kconfig
new file mode 100644
index 0000000..d9a0299
--- /dev/null
+++ b/drivers/char/drm/Kconfig
@@ -0,0 +1,98 @@
+#
+# Drm device configuration
+#
+# This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+#
+config DRM
+	tristate "Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)"
+	depends on AGP || AGP=n
+	help
+	  Kernel-level support for the Direct Rendering Infrastructure (DRI)
+	  introduced in XFree86 4.0. If you say Y here, you need to select
+	  the module that's right for your graphics card from the list below.
+	  These modules provide support for synchronization, security, and
+	  DMA transfers. Please see <http://dri.sourceforge.net/> for more
+	  details.  You should also select and configure AGP
+	  (/dev/agpgart) support.
+
+config DRM_TDFX
+	tristate "3dfx Banshee/Voodoo3+"
+	depends on DRM && PCI
+	help
+	  Choose this option if you have a 3dfx Banshee or Voodoo3 (or later),
+	  graphics card.  If M is selected, the module will be called tdfx.
+
+config DRM_GAMMA
+	tristate "3dlabs GMX 2000"
+	depends on DRM && BROKEN
+	help
+	  This is the old gamma driver, please tell me if it might actually
+	  work.
+
+config DRM_R128
+	tristate "ATI Rage 128"
+	depends on DRM && PCI
+	help
+	  Choose this option if you have an ATI Rage 128 graphics card.  If M
+	  is selected, the module will be called r128.  AGP support for
+	  this card is strongly suggested (unless you have a PCI version).
+
+config DRM_RADEON
+	tristate "ATI Radeon"
+	depends on DRM && PCI
+	help
+	  Choose this option if you have an ATI Radeon graphics card.  There
+	  are both PCI and AGP versions.  You don't need to choose this to
+	  run the Radeon in plain VGA mode.  There is a product page at
+	  <http://www.ati.com/na/pages/products/pc/radeon32/index.html>.
+	  If M is selected, the module will be called radeon.
+
+config DRM_I810
+	tristate "Intel I810"
+	depends on DRM && AGP && AGP_INTEL
+	help
+	  Choose this option if you have an Intel I810 graphics card.  If M is
+	  selected, the module will be called i810.  AGP support is required
+	  for this driver to work.
+
+choice
+	prompt "Intel 830M, 845G, 852GM, 855GM, 865G"
+	depends on DRM && AGP && AGP_INTEL
+	optional
+
+config DRM_I830
+	tristate "i830 driver"
+	help
+	  Choose this option if you have a system that has Intel 830M, 845G,
+	  852GM, 855GM or 865G integrated graphics.  If M is selected, the
+	  module will be called i830.  AGP support is required for this driver
+	  to work. This driver will eventually be replaced by the i915 one.
+
+config DRM_I915
+	tristate "i915 driver"
+	help
+	  Choose this option if you have a system that has Intel 830M, 845G,
+	  852GM, 855GM 865G or 915G integrated graphics.  If M is selected, the
+	  module will be called i915.  AGP support is required for this driver
+	  to work. This driver will eventually replace the I830 driver, when
+	  later release of X start to use the new DDX and DRI.
+	
+endchoice
+
+config DRM_MGA
+	tristate "Matrox g200/g400"
+	depends on DRM && AGP
+	help
+	  Choose this option if you have a Matrox G200, G400 or G450 graphics
+	  card.  If M is selected, the module will be called mga.  AGP
+	  support is required for this driver to work.
+
+config DRM_SIS
+	tristate "SiS video cards"
+	depends on DRM && AGP
+	help
+	  Choose this option if you have a SiS 630 or compatible video 
+          chipset. If M is selected the module will be called sis. AGP
+          support is required for this driver to work.
+
diff --git a/drivers/char/drm/Makefile b/drivers/char/drm/Makefile
new file mode 100644
index 0000000..23ab263
--- /dev/null
+++ b/drivers/char/drm/Makefile
@@ -0,0 +1,33 @@
+#
+# Makefile for the drm device driver.  This driver provides support for the
+# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
+
+drm-objs    :=	drm_auth.o drm_bufs.o drm_context.o drm_dma.o drm_drawable.o \
+		drm_drv.o drm_fops.o drm_init.o drm_ioctl.o drm_irq.o \
+		drm_lock.o drm_memory.o drm_proc.o drm_stub.o drm_vm.o \
+		drm_agpsupport.o drm_scatter.o ati_pcigart.o drm_pci.o \
+		drm_sysfs.o
+
+gamma-objs  := gamma_drv.o gamma_dma.o
+tdfx-objs   := tdfx_drv.o
+r128-objs   := r128_drv.o r128_cce.o r128_state.o r128_irq.o
+mga-objs    := mga_drv.o mga_dma.o mga_state.o mga_warp.o mga_irq.o
+i810-objs   := i810_drv.o i810_dma.o
+i830-objs   := i830_drv.o i830_dma.o i830_irq.o
+i915-objs   := i915_drv.o i915_dma.o i915_irq.o i915_mem.o
+radeon-objs := radeon_drv.o radeon_cp.o radeon_state.o radeon_mem.o radeon_irq.o
+ffb-objs    := ffb_drv.o ffb_context.o
+sis-objs    := sis_drv.o sis_ds.o sis_mm.o
+
+obj-$(CONFIG_DRM)	+= drm.o
+obj-$(CONFIG_DRM_GAMMA) += gamma.o
+obj-$(CONFIG_DRM_TDFX)	+= tdfx.o
+obj-$(CONFIG_DRM_R128)	+= r128.o
+obj-$(CONFIG_DRM_RADEON)+= radeon.o
+obj-$(CONFIG_DRM_MGA)	+= mga.o
+obj-$(CONFIG_DRM_I810)	+= i810.o
+obj-$(CONFIG_DRM_I830)	+= i830.o
+obj-$(CONFIG_DRM_I915)  += i915.o
+obj-$(CONFIG_DRM_FFB)   += ffb.o
+obj-$(CONFIG_DRM_SIS)   += sis.o
+
diff --git a/drivers/char/drm/README.drm b/drivers/char/drm/README.drm
new file mode 100644
index 0000000..6441e01
--- /dev/null
+++ b/drivers/char/drm/README.drm
@@ -0,0 +1,46 @@
+************************************************************
+* For the very latest on DRI development, please see:      *
+*     http://dri.sourceforge.net/                          *
+************************************************************
+
+The Direct Rendering Manager (drm) is a device-independent kernel-level
+device driver that provides support for the XFree86 Direct Rendering
+Infrastructure (DRI).
+
+The DRM supports the Direct Rendering Infrastructure (DRI) in four major
+ways:
+
+    1. The DRM provides synchronized access to the graphics hardware via
+       the use of an optimized two-tiered lock.
+
+    2. The DRM enforces the DRI security policy for access to the graphics
+       hardware by only allowing authenticated X11 clients access to
+       restricted regions of memory.
+
+    3. The DRM provides a generic DMA engine, complete with multiple
+       queues and the ability to detect the need for an OpenGL context
+       switch.
+
+    4. The DRM is extensible via the use of small device-specific modules
+       that rely extensively on the API exported by the DRM module.
+
+
+Documentation on the DRI is available from:
+    http://precisioninsight.com/piinsights.html
+
+For specific information about kernel-level support, see:
+
+    The Direct Rendering Manager, Kernel Support for the Direct Rendering
+    Infrastructure
+    http://precisioninsight.com/dr/drm.html
+
+    Hardware Locking for the Direct Rendering Infrastructure
+    http://precisioninsight.com/dr/locking.html
+
+    A Security Analysis of the Direct Rendering Infrastructure
+    http://precisioninsight.com/dr/security.html
+
+************************************************************
+* For the very latest on DRI development, please see:      *
+*     http://dri.sourceforge.net/                          *
+************************************************************
diff --git a/drivers/char/drm/ati_pcigart.c b/drivers/char/drm/ati_pcigart.c
new file mode 100644
index 0000000..fdca187
--- /dev/null
+++ b/drivers/char/drm/ati_pcigart.c
@@ -0,0 +1,208 @@
+/**
+ * \file ati_pcigart.h 
+ * ATI PCI GART support
+ *
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Wed Dec 13 21:52:19 2000 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+#if PAGE_SIZE == 65536
+# define ATI_PCIGART_TABLE_ORDER	0
+# define ATI_PCIGART_TABLE_PAGES	(1 << 0)
+#elif PAGE_SIZE == 16384
+# define ATI_PCIGART_TABLE_ORDER	1
+# define ATI_PCIGART_TABLE_PAGES	(1 << 1)
+#elif PAGE_SIZE == 8192
+# define ATI_PCIGART_TABLE_ORDER 	2
+# define ATI_PCIGART_TABLE_PAGES 	(1 << 2)
+#elif PAGE_SIZE == 4096
+# define ATI_PCIGART_TABLE_ORDER 	3
+# define ATI_PCIGART_TABLE_PAGES 	(1 << 3)
+#else
+# error - PAGE_SIZE not 64K, 16K, 8K or 4K
+#endif
+
+# define ATI_MAX_PCIGART_PAGES		8192	/**< 32 MB aperture, 4K pages */
+# define ATI_PCIGART_PAGE_SIZE		4096	/**< PCI GART page size */
+
+unsigned long drm_ati_alloc_pcigart_table( void )
+{
+	unsigned long address;
+	struct page *page;
+	int i;
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	address = __get_free_pages( GFP_KERNEL, ATI_PCIGART_TABLE_ORDER );
+	if ( address == 0UL ) {
+		return 0;
+	}
+
+	page = virt_to_page( address );
+
+	for ( i = 0 ; i < ATI_PCIGART_TABLE_PAGES ; i++, page++ ) {
+		get_page(page);
+		SetPageReserved( page );
+	}
+
+	DRM_DEBUG( "%s: returning 0x%08lx\n", __FUNCTION__, address );
+	return address;
+}
+
+static void drm_ati_free_pcigart_table( unsigned long address )
+{
+	struct page *page;
+	int i;
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	page = virt_to_page( address );
+
+	for ( i = 0 ; i < ATI_PCIGART_TABLE_PAGES ; i++, page++ ) {
+		__put_page(page);
+		ClearPageReserved( page );
+	}
+
+	free_pages( address, ATI_PCIGART_TABLE_ORDER );
+}
+
+int drm_ati_pcigart_cleanup( drm_device_t *dev,
+			      unsigned long addr,
+			      dma_addr_t bus_addr)
+{
+	drm_sg_mem_t *entry = dev->sg;
+	unsigned long pages;
+	int i;
+
+	/* we need to support large memory configurations */
+	if ( !entry ) {
+		DRM_ERROR( "no scatter/gather memory!\n" );
+		return 0;
+	}
+
+	if ( bus_addr ) {
+		pci_unmap_single(dev->pdev, bus_addr,
+				 ATI_PCIGART_TABLE_PAGES * PAGE_SIZE,
+				 PCI_DMA_TODEVICE);
+
+		pages = ( entry->pages <= ATI_MAX_PCIGART_PAGES )
+		        ? entry->pages : ATI_MAX_PCIGART_PAGES;
+
+		for ( i = 0 ; i < pages ; i++ ) {
+			if ( !entry->busaddr[i] ) break;
+			pci_unmap_single(dev->pdev, entry->busaddr[i],
+					 PAGE_SIZE, PCI_DMA_TODEVICE);
+		}
+	}
+
+	if ( addr ) {
+		drm_ati_free_pcigart_table( addr );
+	}
+
+	return 1;
+}
+EXPORT_SYMBOL(drm_ati_pcigart_cleanup);
+
+int drm_ati_pcigart_init( drm_device_t *dev,
+			   unsigned long *addr,
+			   dma_addr_t *bus_addr)
+{
+	drm_sg_mem_t *entry = dev->sg;
+	unsigned long address = 0;
+	unsigned long pages;
+	u32 *pci_gart, page_base, bus_address = 0;
+	int i, j, ret = 0;
+
+	if ( !entry ) {
+		DRM_ERROR( "no scatter/gather memory!\n" );
+		goto done;
+	}
+
+	address = drm_ati_alloc_pcigart_table();
+	if ( !address ) {
+		DRM_ERROR( "cannot allocate PCI GART page!\n" );
+		goto done;
+	}
+
+	if ( !dev->pdev ) {
+		DRM_ERROR( "PCI device unknown!\n" );
+		goto done;
+	}
+
+	bus_address = pci_map_single(dev->pdev, (void *)address,
+				  ATI_PCIGART_TABLE_PAGES * PAGE_SIZE,
+				  PCI_DMA_TODEVICE);
+	if (bus_address == 0) {
+		DRM_ERROR( "unable to map PCIGART pages!\n" );
+		drm_ati_free_pcigart_table( address );
+		address = 0;
+		goto done;
+	}
+
+	pci_gart = (u32 *)address;
+
+	pages = ( entry->pages <= ATI_MAX_PCIGART_PAGES )
+		? entry->pages : ATI_MAX_PCIGART_PAGES;
+
+	memset( pci_gart, 0, ATI_MAX_PCIGART_PAGES * sizeof(u32) );
+
+	for ( i = 0 ; i < pages ; i++ ) {
+		/* we need to support large memory configurations */
+		entry->busaddr[i] = pci_map_single(dev->pdev,
+					   page_address( entry->pagelist[i] ),
+					   PAGE_SIZE,
+					   PCI_DMA_TODEVICE);
+		if (entry->busaddr[i] == 0) {
+			DRM_ERROR( "unable to map PCIGART pages!\n" );
+			drm_ati_pcigart_cleanup( dev, address, bus_address );
+			address = 0;
+			bus_address = 0;
+			goto done;
+		}
+		page_base = (u32) entry->busaddr[i];
+
+		for (j = 0; j < (PAGE_SIZE / ATI_PCIGART_PAGE_SIZE); j++) {
+			*pci_gart++ = cpu_to_le32( page_base );
+			page_base += ATI_PCIGART_PAGE_SIZE;
+		}
+	}
+
+	ret = 1;
+
+#if defined(__i386__) || defined(__x86_64__)
+	wbinvd();
+#else
+	mb();
+#endif
+
+done:
+	*addr = address;
+	*bus_addr = bus_address;
+	return ret;
+}
+EXPORT_SYMBOL(drm_ati_pcigart_init);
diff --git a/drivers/char/drm/drm.h b/drivers/char/drm/drm.h
new file mode 100644
index 0000000..5873052
--- /dev/null
+++ b/drivers/char/drm/drm.h
@@ -0,0 +1,675 @@
+/**
+ * \file drm.h 
+ * Header for the Direct Rendering Manager
+ * 
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ *
+ * \par Acknowledgments:
+ * Dec 1999, Richard Henderson <rth@twiddle.net>, move to generic \c cmpxchg.
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+#ifndef _DRM_H_
+#define _DRM_H_
+
+#if defined(__linux__)
+#include <linux/config.h>
+#include <asm/ioctl.h>		/* For _IO* macros */
+#define DRM_IOCTL_NR(n)		_IOC_NR(n)
+#define DRM_IOC_VOID		_IOC_NONE
+#define DRM_IOC_READ		_IOC_READ
+#define DRM_IOC_WRITE		_IOC_WRITE
+#define DRM_IOC_READWRITE	_IOC_READ|_IOC_WRITE
+#define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size)
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#if defined(__FreeBSD__) && defined(IN_MODULE)
+/* Prevent name collision when including sys/ioccom.h */
+#undef ioctl
+#include <sys/ioccom.h>
+#define ioctl(a,b,c)		xf86ioctl(a,b,c)
+#else
+#include <sys/ioccom.h>
+#endif /* __FreeBSD__ && xf86ioctl */
+#define DRM_IOCTL_NR(n)		((n) & 0xff)
+#define DRM_IOC_VOID		IOC_VOID
+#define DRM_IOC_READ		IOC_OUT
+#define DRM_IOC_WRITE		IOC_IN
+#define DRM_IOC_READWRITE	IOC_INOUT
+#define DRM_IOC(dir, group, nr, size) _IOC(dir, group, nr, size)
+#endif
+
+#define XFREE86_VERSION(major,minor,patch,snap) \
+		((major << 16) | (minor << 8) | patch)
+
+#ifndef CONFIG_XFREE86_VERSION
+#define CONFIG_XFREE86_VERSION XFREE86_VERSION(4,1,0,0)
+#endif
+
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+#define DRM_PROC_DEVICES "/proc/devices"
+#define DRM_PROC_MISC	 "/proc/misc"
+#define DRM_PROC_DRM	 "/proc/drm"
+#define DRM_DEV_DRM	 "/dev/drm"
+#define DRM_DEV_MODE	 (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP)
+#define DRM_DEV_UID	 0
+#define DRM_DEV_GID	 0
+#endif
+
+#if CONFIG_XFREE86_VERSION >= XFREE86_VERSION(4,1,0,0)
+#define DRM_MAJOR       226
+#define DRM_MAX_MINOR   15
+#endif
+#define DRM_NAME	"drm"	  /**< Name in kernel, /dev, and /proc */
+#define DRM_MIN_ORDER	5	  /**< At least 2^5 bytes = 32 bytes */
+#define DRM_MAX_ORDER	22	  /**< Up to 2^22 bytes = 4MB */
+#define DRM_RAM_PERCENT 10	  /**< How much system ram can we lock? */
+
+#define _DRM_LOCK_HELD	0x80000000 /**< Hardware lock is held */
+#define _DRM_LOCK_CONT	0x40000000 /**< Hardware lock is contended */
+#define _DRM_LOCK_IS_HELD(lock)	   ((lock) & _DRM_LOCK_HELD)
+#define _DRM_LOCK_IS_CONT(lock)	   ((lock) & _DRM_LOCK_CONT)
+#define _DRM_LOCKING_CONTEXT(lock) ((lock) & ~(_DRM_LOCK_HELD|_DRM_LOCK_CONT))
+
+
+typedef unsigned long drm_handle_t;
+typedef unsigned int  drm_context_t;
+typedef unsigned int  drm_drawable_t;
+typedef unsigned int  drm_magic_t;
+
+
+/**
+ * Cliprect.
+ * 
+ * \warning: If you change this structure, make sure you change
+ * XF86DRIClipRectRec in the server as well
+ *
+ * \note KW: Actually it's illegal to change either for
+ * backwards-compatibility reasons.
+ */
+typedef struct drm_clip_rect {
+	unsigned short	x1;
+	unsigned short	y1;
+	unsigned short	x2;
+	unsigned short	y2;
+} drm_clip_rect_t;
+
+
+/**
+ * Texture region,
+ */
+typedef struct drm_tex_region {
+	unsigned char	next;
+	unsigned char	prev;
+	unsigned char	in_use;
+	unsigned char	padding;
+	unsigned int	age;
+} drm_tex_region_t;
+
+/**
+ * Hardware lock.
+ *
+ * The lock structure is a simple cache-line aligned integer.  To avoid
+ * processor bus contention on a multiprocessor system, there should not be any
+ * other data stored in the same cache line.
+ */
+typedef struct drm_hw_lock {
+	__volatile__ unsigned int lock;		/**< lock variable */
+	char			  padding[60];	/**< Pad to cache line */
+} drm_hw_lock_t;
+
+
+/**
+ * DRM_IOCTL_VERSION ioctl argument type.
+ * 
+ * \sa drmGetVersion().
+ */
+typedef struct drm_version {
+	int    version_major;	  /**< Major version */
+	int    version_minor;	  /**< Minor version */
+	int    version_patchlevel;/**< Patch level */
+	size_t name_len;	  /**< Length of name buffer */
+	char   __user *name;	  /**< Name of driver */
+	size_t date_len;	  /**< Length of date buffer */
+	char   __user *date;	  /**< User-space buffer to hold date */
+	size_t desc_len;	  /**< Length of desc buffer */
+	char   __user *desc;	  /**< User-space buffer to hold desc */
+} drm_version_t;
+
+
+/**
+ * DRM_IOCTL_GET_UNIQUE ioctl argument type.
+ *
+ * \sa drmGetBusid() and drmSetBusId().
+ */
+typedef struct drm_unique {
+	size_t unique_len;	  /**< Length of unique */
+	char   __user *unique;	  /**< Unique name for driver instantiation */
+} drm_unique_t;
+
+
+typedef struct drm_list {
+	int		 count;	  /**< Length of user-space structures */
+	drm_version_t	 __user *version;
+} drm_list_t;
+
+
+typedef struct drm_block {
+	int		 unused;
+} drm_block_t;
+
+
+/**
+ * DRM_IOCTL_CONTROL ioctl argument type.
+ *
+ * \sa drmCtlInstHandler() and drmCtlUninstHandler().
+ */
+typedef struct drm_control {
+	enum {
+		DRM_ADD_COMMAND,
+		DRM_RM_COMMAND,
+		DRM_INST_HANDLER,
+		DRM_UNINST_HANDLER
+	}		 func;
+	int		 irq;
+} drm_control_t;
+
+
+/**
+ * Type of memory to map.
+ */
+typedef enum drm_map_type {
+	_DRM_FRAME_BUFFER   = 0,  /**< WC (no caching), no core dump */
+	_DRM_REGISTERS	    = 1,  /**< no caching, no core dump */
+	_DRM_SHM	    = 2,  /**< shared, cached */
+	_DRM_AGP            = 3,  /**< AGP/GART */
+	_DRM_SCATTER_GATHER = 4	  /**< Scatter/gather memory for PCI DMA */
+} drm_map_type_t;
+
+
+/**
+ * Memory mapping flags.
+ */
+typedef enum drm_map_flags {
+	_DRM_RESTRICTED	     = 0x01, /**< Cannot be mapped to user-virtual */
+	_DRM_READ_ONLY	     = 0x02,
+	_DRM_LOCKED	     = 0x04, /**< shared, cached, locked */
+	_DRM_KERNEL	     = 0x08, /**< kernel requires access */
+	_DRM_WRITE_COMBINING = 0x10, /**< use write-combining if available */
+	_DRM_CONTAINS_LOCK   = 0x20, /**< SHM page that contains lock */
+	_DRM_REMOVABLE	     = 0x40  /**< Removable mapping */
+} drm_map_flags_t;
+
+
+typedef struct drm_ctx_priv_map {
+	unsigned int	ctx_id;  /**< Context requesting private mapping */
+	void		*handle; /**< Handle of map */
+} drm_ctx_priv_map_t;
+
+
+/**
+ * DRM_IOCTL_GET_MAP, DRM_IOCTL_ADD_MAP and DRM_IOCTL_RM_MAP ioctls
+ * argument type.
+ *
+ * \sa drmAddMap().
+ */
+typedef struct drm_map {
+	unsigned long	offset;	 /**< Requested physical address (0 for SAREA)*/
+	unsigned long	size;	 /**< Requested physical size (bytes) */
+	drm_map_type_t	type;	 /**< Type of memory to map */
+	drm_map_flags_t flags;	 /**< Flags */
+	void		*handle; /**< User-space: "Handle" to pass to mmap() */
+				 /**< Kernel-space: kernel-virtual address */
+	int		mtrr;	 /**< MTRR slot used */
+				 /*   Private data */
+} drm_map_t;
+
+
+/**
+ * DRM_IOCTL_GET_CLIENT ioctl argument type.
+ */
+typedef struct drm_client {
+	int		idx;	/**< Which client desired? */
+	int		auth;	/**< Is client authenticated? */
+	unsigned long	pid;	/**< Process ID */
+	unsigned long	uid;	/**< User ID */
+	unsigned long	magic;	/**< Magic */
+	unsigned long	iocs;	/**< Ioctl count */
+} drm_client_t;
+
+
+typedef enum {
+	_DRM_STAT_LOCK,
+	_DRM_STAT_OPENS,
+	_DRM_STAT_CLOSES,
+	_DRM_STAT_IOCTLS,
+	_DRM_STAT_LOCKS,
+	_DRM_STAT_UNLOCKS,
+	_DRM_STAT_VALUE,	/**< Generic value */
+	_DRM_STAT_BYTE,		/**< Generic byte counter (1024bytes/K) */
+	_DRM_STAT_COUNT,	/**< Generic non-byte counter (1000/k) */
+
+	_DRM_STAT_IRQ,		/**< IRQ */
+	_DRM_STAT_PRIMARY,	/**< Primary DMA bytes */
+	_DRM_STAT_SECONDARY,	/**< Secondary DMA bytes */
+	_DRM_STAT_DMA,		/**< DMA */
+	_DRM_STAT_SPECIAL,	/**< Special DMA (e.g., priority or polled) */
+	_DRM_STAT_MISSED	/**< Missed DMA opportunity */
+
+				/* Add to the *END* of the list */
+} drm_stat_type_t;
+
+
+/**
+ * DRM_IOCTL_GET_STATS ioctl argument type.
+ */
+typedef struct drm_stats {
+	unsigned long count;
+	struct {
+		unsigned long   value;
+		drm_stat_type_t type;
+	} data[15];
+} drm_stats_t;
+
+
+/**
+ * Hardware locking flags.
+ */
+typedef enum drm_lock_flags {
+	_DRM_LOCK_READY	     = 0x01, /**< Wait until hardware is ready for DMA */
+	_DRM_LOCK_QUIESCENT  = 0x02, /**< Wait until hardware quiescent */
+	_DRM_LOCK_FLUSH	     = 0x04, /**< Flush this context's DMA queue first */
+	_DRM_LOCK_FLUSH_ALL  = 0x08, /**< Flush all DMA queues first */
+				/* These *HALT* flags aren't supported yet
+				   -- they will be used to support the
+				   full-screen DGA-like mode. */
+	_DRM_HALT_ALL_QUEUES = 0x10, /**< Halt all current and future queues */
+	_DRM_HALT_CUR_QUEUES = 0x20  /**< Halt all current queues */
+} drm_lock_flags_t;
+
+
+/**
+ * DRM_IOCTL_LOCK, DRM_IOCTL_UNLOCK and DRM_IOCTL_FINISH ioctl argument type.
+ * 
+ * \sa drmGetLock() and drmUnlock().
+ */
+typedef struct drm_lock {
+	int		 context;
+	drm_lock_flags_t flags;
+} drm_lock_t;
+
+
+/**
+ * DMA flags
+ *
+ * \warning 
+ * These values \e must match xf86drm.h.
+ *
+ * \sa drm_dma.
+ */
+typedef enum drm_dma_flags {	      
+				      /* Flags for DMA buffer dispatch */
+	_DRM_DMA_BLOCK	      = 0x01, /**<
+				       * Block until buffer dispatched.
+				       * 
+				       * \note The buffer may not yet have
+				       * been processed by the hardware --
+				       * getting a hardware lock with the
+				       * hardware quiescent will ensure
+				       * that the buffer has been
+				       * processed.
+				       */
+	_DRM_DMA_WHILE_LOCKED = 0x02, /**< Dispatch while lock held */
+	_DRM_DMA_PRIORITY     = 0x04, /**< High priority dispatch */
+
+				      /* Flags for DMA buffer request */
+	_DRM_DMA_WAIT	      = 0x10, /**< Wait for free buffers */
+	_DRM_DMA_SMALLER_OK   = 0x20, /**< Smaller-than-requested buffers OK */
+	_DRM_DMA_LARGER_OK    = 0x40  /**< Larger-than-requested buffers OK */
+} drm_dma_flags_t;
+
+
+/**
+ * DRM_IOCTL_ADD_BUFS and DRM_IOCTL_MARK_BUFS ioctl argument type.
+ *
+ * \sa drmAddBufs().
+ */
+typedef struct drm_buf_desc {
+	int	      count;	 /**< Number of buffers of this size */
+	int	      size;	 /**< Size in bytes */
+	int	      low_mark;	 /**< Low water mark */
+	int	      high_mark; /**< High water mark */
+	enum {
+		_DRM_PAGE_ALIGN = 0x01, /**< Align on page boundaries for DMA */
+		_DRM_AGP_BUFFER = 0x02, /**< Buffer is in AGP space */
+		_DRM_SG_BUFFER  = 0x04  /**< Scatter/gather memory buffer */
+	}	      flags;
+	unsigned long agp_start; /**< 
+				  * Start address of where the AGP buffers are
+				  * in the AGP aperture
+				  */
+} drm_buf_desc_t;
+
+
+/**
+ * DRM_IOCTL_INFO_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_info {
+	int	       count;	/**< Entries in list */
+	drm_buf_desc_t __user *list;
+} drm_buf_info_t;
+
+
+/**
+ * DRM_IOCTL_FREE_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_free {
+	int	       count;
+	int	       __user *list;
+} drm_buf_free_t;
+
+
+/**
+ * Buffer information
+ *
+ * \sa drm_buf_map.
+ */
+typedef struct drm_buf_pub {
+	int		  idx;	       /**< Index into the master buffer list */
+	int		  total;       /**< Buffer size */
+	int		  used;	       /**< Amount of buffer in use (for DMA) */
+	void	  __user *address;     /**< Address of buffer */
+} drm_buf_pub_t;
+
+
+/**
+ * DRM_IOCTL_MAP_BUFS ioctl argument type.
+ */
+typedef struct drm_buf_map {
+	int	      count;	/**< Length of the buffer list */
+	void	      __user *virtual;	/**< Mmap'd area in user-virtual */
+	drm_buf_pub_t __user *list;	/**< Buffer information */
+} drm_buf_map_t;
+
+
+/**
+ * DRM_IOCTL_DMA ioctl argument type.
+ *
+ * Indices here refer to the offset into the buffer list in drm_buf_get.
+ *
+ * \sa drmDMA().
+ */
+typedef struct drm_dma {
+	int		context;	  /**< Context handle */
+	int		send_count;	  /**< Number of buffers to send */
+	int	__user *send_indices;	  /**< List of handles to buffers */
+	int	__user *send_sizes;	  /**< Lengths of data to send */
+	drm_dma_flags_t flags;		  /**< Flags */
+	int		request_count;	  /**< Number of buffers requested */
+	int		request_size;	  /**< Desired size for buffers */
+	int	__user *request_indices;  /**< Buffer information */
+	int	__user *request_sizes;
+	int		granted_count;	  /**< Number of buffers granted */
+} drm_dma_t;
+
+
+typedef enum {
+	_DRM_CONTEXT_PRESERVED = 0x01,
+	_DRM_CONTEXT_2DONLY    = 0x02
+} drm_ctx_flags_t;
+
+
+/**
+ * DRM_IOCTL_ADD_CTX ioctl argument type.
+ *
+ * \sa drmCreateContext() and drmDestroyContext().
+ */
+typedef struct drm_ctx {
+	drm_context_t	handle;
+	drm_ctx_flags_t flags;
+} drm_ctx_t;
+
+
+/**
+ * DRM_IOCTL_RES_CTX ioctl argument type.
+ */
+typedef struct drm_ctx_res {
+	int		count;
+	drm_ctx_t	__user *contexts;
+} drm_ctx_res_t;
+
+
+/**
+ * DRM_IOCTL_ADD_DRAW and DRM_IOCTL_RM_DRAW ioctl argument type.
+ */
+typedef struct drm_draw {
+	drm_drawable_t	handle;
+} drm_draw_t;
+
+
+/**
+ * DRM_IOCTL_GET_MAGIC and DRM_IOCTL_AUTH_MAGIC ioctl argument type.
+ */
+typedef struct drm_auth {
+	drm_magic_t	magic;
+} drm_auth_t;
+
+
+/**
+ * DRM_IOCTL_IRQ_BUSID ioctl argument type.
+ *
+ * \sa drmGetInterruptFromBusID().
+ */
+typedef struct drm_irq_busid {
+	int irq;	/**< IRQ number */
+	int busnum;	/**< bus number */
+	int devnum;	/**< device number */
+	int funcnum;	/**< function number */
+} drm_irq_busid_t;
+
+
+typedef enum {
+    _DRM_VBLANK_ABSOLUTE = 0x0,		/**< Wait for specific vblank sequence number */
+    _DRM_VBLANK_RELATIVE = 0x1,		/**< Wait for given number of vblanks */
+    _DRM_VBLANK_SIGNAL   = 0x40000000	/**< Send signal instead of blocking */
+} drm_vblank_seq_type_t;
+
+
+#define _DRM_VBLANK_FLAGS_MASK _DRM_VBLANK_SIGNAL
+
+
+struct drm_wait_vblank_request {
+	drm_vblank_seq_type_t type;
+	unsigned int sequence;
+	unsigned long signal;
+};
+
+
+struct drm_wait_vblank_reply {
+	drm_vblank_seq_type_t type;
+	unsigned int sequence;
+	long tval_sec;
+	long tval_usec;
+};
+
+
+/**
+ * DRM_IOCTL_WAIT_VBLANK ioctl argument type.
+ *
+ * \sa drmWaitVBlank().
+ */
+typedef union drm_wait_vblank {
+	struct drm_wait_vblank_request request;
+	struct drm_wait_vblank_reply reply;
+} drm_wait_vblank_t;
+
+
+/**
+ * DRM_IOCTL_AGP_ENABLE ioctl argument type.
+ *
+ * \sa drmAgpEnable().
+ */
+typedef struct drm_agp_mode {
+	unsigned long mode;	/**< AGP mode */
+} drm_agp_mode_t;
+
+
+/**
+ * DRM_IOCTL_AGP_ALLOC and DRM_IOCTL_AGP_FREE ioctls argument type.
+ *
+ * \sa drmAgpAlloc() and drmAgpFree().
+ */
+typedef struct drm_agp_buffer {
+	unsigned long size;	/**< In bytes -- will round to page boundary */
+	unsigned long handle;	/**< Used for binding / unbinding */
+	unsigned long type;     /**< Type of memory to allocate */
+        unsigned long physical; /**< Physical used by i810 */
+} drm_agp_buffer_t;
+
+
+/**
+ * DRM_IOCTL_AGP_BIND and DRM_IOCTL_AGP_UNBIND ioctls argument type.
+ *
+ * \sa drmAgpBind() and drmAgpUnbind().
+ */
+typedef struct drm_agp_binding {
+	unsigned long handle;   /**< From drm_agp_buffer */
+	unsigned long offset;	/**< In bytes -- will round to page boundary */
+} drm_agp_binding_t;
+
+
+/**
+ * DRM_IOCTL_AGP_INFO ioctl argument type.
+ *
+ * \sa drmAgpVersionMajor(), drmAgpVersionMinor(), drmAgpGetMode(),
+ * drmAgpBase(), drmAgpSize(), drmAgpMemoryUsed(), drmAgpMemoryAvail(),
+ * drmAgpVendorId() and drmAgpDeviceId().
+ */
+typedef struct drm_agp_info {
+	int            agp_version_major;
+	int            agp_version_minor;
+	unsigned long  mode;
+	unsigned long  aperture_base;  /* physical address */
+	unsigned long  aperture_size;  /* bytes */
+	unsigned long  memory_allowed; /* bytes */
+	unsigned long  memory_used;
+
+				/* PCI information */
+	unsigned short id_vendor;
+	unsigned short id_device;
+} drm_agp_info_t;
+
+
+/**
+ * DRM_IOCTL_SG_ALLOC ioctl argument type.
+ */
+typedef struct drm_scatter_gather {
+	unsigned long size;	/**< In bytes -- will round to page boundary */
+	unsigned long handle;	/**< Used for mapping / unmapping */
+} drm_scatter_gather_t;
+
+/**
+ * DRM_IOCTL_SET_VERSION ioctl argument type.
+ */
+typedef struct drm_set_version {
+	int drm_di_major;
+	int drm_di_minor;
+	int drm_dd_major;
+	int drm_dd_minor;
+} drm_set_version_t;
+
+
+#define DRM_IOCTL_BASE			'd'
+#define DRM_IO(nr)			_IO(DRM_IOCTL_BASE,nr)
+#define DRM_IOR(nr,type)		_IOR(DRM_IOCTL_BASE,nr,type)
+#define DRM_IOW(nr,type)		_IOW(DRM_IOCTL_BASE,nr,type)
+#define DRM_IOWR(nr,type)		_IOWR(DRM_IOCTL_BASE,nr,type)
+
+#define DRM_IOCTL_VERSION		DRM_IOWR(0x00, drm_version_t)
+#define DRM_IOCTL_GET_UNIQUE		DRM_IOWR(0x01, drm_unique_t)
+#define DRM_IOCTL_GET_MAGIC		DRM_IOR( 0x02, drm_auth_t)
+#define DRM_IOCTL_IRQ_BUSID		DRM_IOWR(0x03, drm_irq_busid_t)
+#define DRM_IOCTL_GET_MAP               DRM_IOWR(0x04, drm_map_t)
+#define DRM_IOCTL_GET_CLIENT            DRM_IOWR(0x05, drm_client_t)
+#define DRM_IOCTL_GET_STATS             DRM_IOR( 0x06, drm_stats_t)
+#define DRM_IOCTL_SET_VERSION		DRM_IOWR(0x07, drm_set_version_t)
+
+#define DRM_IOCTL_SET_UNIQUE		DRM_IOW( 0x10, drm_unique_t)
+#define DRM_IOCTL_AUTH_MAGIC		DRM_IOW( 0x11, drm_auth_t)
+#define DRM_IOCTL_BLOCK			DRM_IOWR(0x12, drm_block_t)
+#define DRM_IOCTL_UNBLOCK		DRM_IOWR(0x13, drm_block_t)
+#define DRM_IOCTL_CONTROL		DRM_IOW( 0x14, drm_control_t)
+#define DRM_IOCTL_ADD_MAP		DRM_IOWR(0x15, drm_map_t)
+#define DRM_IOCTL_ADD_BUFS		DRM_IOWR(0x16, drm_buf_desc_t)
+#define DRM_IOCTL_MARK_BUFS		DRM_IOW( 0x17, drm_buf_desc_t)
+#define DRM_IOCTL_INFO_BUFS		DRM_IOWR(0x18, drm_buf_info_t)
+#define DRM_IOCTL_MAP_BUFS		DRM_IOWR(0x19, drm_buf_map_t)
+#define DRM_IOCTL_FREE_BUFS		DRM_IOW( 0x1a, drm_buf_free_t)
+
+#define DRM_IOCTL_RM_MAP		DRM_IOW( 0x1b, drm_map_t)
+
+#define DRM_IOCTL_SET_SAREA_CTX		DRM_IOW( 0x1c, drm_ctx_priv_map_t)
+#define DRM_IOCTL_GET_SAREA_CTX 	DRM_IOWR(0x1d, drm_ctx_priv_map_t)
+
+#define DRM_IOCTL_ADD_CTX		DRM_IOWR(0x20, drm_ctx_t)
+#define DRM_IOCTL_RM_CTX		DRM_IOWR(0x21, drm_ctx_t)
+#define DRM_IOCTL_MOD_CTX		DRM_IOW( 0x22, drm_ctx_t)
+#define DRM_IOCTL_GET_CTX		DRM_IOWR(0x23, drm_ctx_t)
+#define DRM_IOCTL_SWITCH_CTX		DRM_IOW( 0x24, drm_ctx_t)
+#define DRM_IOCTL_NEW_CTX		DRM_IOW( 0x25, drm_ctx_t)
+#define DRM_IOCTL_RES_CTX		DRM_IOWR(0x26, drm_ctx_res_t)
+#define DRM_IOCTL_ADD_DRAW		DRM_IOWR(0x27, drm_draw_t)
+#define DRM_IOCTL_RM_DRAW		DRM_IOWR(0x28, drm_draw_t)
+#define DRM_IOCTL_DMA			DRM_IOWR(0x29, drm_dma_t)
+#define DRM_IOCTL_LOCK			DRM_IOW( 0x2a, drm_lock_t)
+#define DRM_IOCTL_UNLOCK		DRM_IOW( 0x2b, drm_lock_t)
+#define DRM_IOCTL_FINISH		DRM_IOW( 0x2c, drm_lock_t)
+
+#define DRM_IOCTL_AGP_ACQUIRE		DRM_IO(  0x30)
+#define DRM_IOCTL_AGP_RELEASE		DRM_IO(  0x31)
+#define DRM_IOCTL_AGP_ENABLE		DRM_IOW( 0x32, drm_agp_mode_t)
+#define DRM_IOCTL_AGP_INFO		DRM_IOR( 0x33, drm_agp_info_t)
+#define DRM_IOCTL_AGP_ALLOC		DRM_IOWR(0x34, drm_agp_buffer_t)
+#define DRM_IOCTL_AGP_FREE		DRM_IOW( 0x35, drm_agp_buffer_t)
+#define DRM_IOCTL_AGP_BIND		DRM_IOW( 0x36, drm_agp_binding_t)
+#define DRM_IOCTL_AGP_UNBIND		DRM_IOW( 0x37, drm_agp_binding_t)
+
+#define DRM_IOCTL_SG_ALLOC		DRM_IOW( 0x38, drm_scatter_gather_t)
+#define DRM_IOCTL_SG_FREE		DRM_IOW( 0x39, drm_scatter_gather_t)
+
+#define DRM_IOCTL_WAIT_VBLANK		DRM_IOWR(0x3a, drm_wait_vblank_t)
+
+/**
+ * Device specific ioctls should only be in their respective headers
+ * The device specific ioctl range is from 0x40 to 0x79.
+ *
+ * \sa drmCommandNone(), drmCommandRead(), drmCommandWrite(), and
+ * drmCommandReadWrite().
+ */
+#define DRM_COMMAND_BASE                0x40
+
+#endif
diff --git a/drivers/char/drm/drmP.h b/drivers/char/drm/drmP.h
new file mode 100644
index 0000000..21f4c54
--- /dev/null
+++ b/drivers/char/drm/drmP.h
@@ -0,0 +1,1073 @@
+/**
+ * \file drmP.h 
+ * Private header for Direct Rendering Manager
+ * 
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _DRM_P_H_
+#define _DRM_P_H_
+
+/* If you want the memory alloc debug functionality, change define below */
+/* #define DEBUG_MEMORY */
+
+#ifdef __KERNEL__
+#ifdef __alpha__
+/* add include of current.h so that "current" is defined
+ * before static inline funcs in wait.h. Doing this so we
+ * can build the DRM (part of PI DRI). 4/21/2000 S + B */
+#include <asm/current.h>
+#endif /* __alpha__ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/file.h>
+#include <linux/pci.h>
+#include <linux/version.h>
+#include <linux/jiffies.h>
+#include <linux/smp_lock.h>	/* For (un)lock_kernel */
+#include <linux/mm.h>
+#include <linux/cdev.h>
+#if defined(__alpha__) || defined(__powerpc__)
+#include <asm/pgtable.h> /* For pte_wrprotect */
+#endif
+#include <asm/io.h>
+#include <asm/mman.h>
+#include <asm/uaccess.h>
+#ifdef CONFIG_MTRR
+#include <asm/mtrr.h>
+#endif
+#if defined(CONFIG_AGP) || defined(CONFIG_AGP_MODULE)
+#include <linux/types.h>
+#include <linux/agp_backend.h>
+#endif
+#include <linux/workqueue.h>
+#include <linux/poll.h>
+#include <asm/pgalloc.h>
+#include "drm.h"
+
+#define __OS_HAS_AGP (defined(CONFIG_AGP) || (defined(CONFIG_AGP_MODULE) && defined(MODULE)))
+#define __OS_HAS_MTRR (defined(CONFIG_MTRR))
+
+#include "drm_os_linux.h"
+
+/***********************************************************************/
+/** \name DRM template customization defaults */
+/*@{*/
+
+/* driver capabilities and requirements mask */
+#define DRIVER_USE_AGP     0x1
+#define DRIVER_REQUIRE_AGP 0x2
+#define DRIVER_USE_MTRR    0x4
+#define DRIVER_PCI_DMA     0x8
+#define DRIVER_SG          0x10
+#define DRIVER_HAVE_DMA    0x20
+#define DRIVER_HAVE_IRQ    0x40
+#define DRIVER_IRQ_SHARED  0x80
+#define DRIVER_IRQ_VBL     0x100
+#define DRIVER_DMA_QUEUE   0x200
+
+/***********************************************************************/
+/** \name Begin the DRM... */
+/*@{*/
+
+#define DRM_DEBUG_CODE 2	  /**< Include debugging code if > 1, then
+				     also include looping detection. */
+
+#define DRM_HASH_SIZE	      16 /**< Size of key hash table. Must be power of 2. */
+#define DRM_KERNEL_CONTEXT    0	 /**< Change drm_resctx if changed */
+#define DRM_RESERVED_CONTEXTS 1	 /**< Change drm_resctx if changed */
+#define DRM_LOOPING_LIMIT     5000000
+#define DRM_BSZ		      1024 /**< Buffer size for /dev/drm? output */
+#define DRM_TIME_SLICE	      (HZ/20)  /**< Time slice for GLXContexts */
+#define DRM_LOCK_SLICE	      1	/**< Time slice for lock, in jiffies */
+
+#define DRM_FLAG_DEBUG	  0x01
+
+#define DRM_MEM_DMA	   0
+#define DRM_MEM_SAREA	   1
+#define DRM_MEM_DRIVER	   2
+#define DRM_MEM_MAGIC	   3
+#define DRM_MEM_IOCTLS	   4
+#define DRM_MEM_MAPS	   5
+#define DRM_MEM_VMAS	   6
+#define DRM_MEM_BUFS	   7
+#define DRM_MEM_SEGS	   8
+#define DRM_MEM_PAGES	   9
+#define DRM_MEM_FILES	  10
+#define DRM_MEM_QUEUES	  11
+#define DRM_MEM_CMDS	  12
+#define DRM_MEM_MAPPINGS  13
+#define DRM_MEM_BUFLISTS  14
+#define DRM_MEM_AGPLISTS  15
+#define DRM_MEM_TOTALAGP  16
+#define DRM_MEM_BOUNDAGP  17
+#define DRM_MEM_CTXBITMAP 18
+#define DRM_MEM_STUB      19
+#define DRM_MEM_SGLISTS   20
+#define DRM_MEM_CTXLIST  21
+
+#define DRM_MAX_CTXBITMAP (PAGE_SIZE * 8)
+	
+/*@}*/
+
+
+/***********************************************************************/
+/** \name Backward compatibility section */
+/*@{*/
+
+#ifndef MODULE_LICENSE
+#define MODULE_LICENSE(x) 
+#endif
+
+#ifndef preempt_disable
+#define preempt_disable()
+#define preempt_enable()
+#endif
+
+#ifndef pte_offset_map 
+#define pte_offset_map pte_offset
+#define pte_unmap(pte)
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,19)
+static inline struct page * vmalloc_to_page(void * vmalloc_addr)
+{
+	unsigned long addr = (unsigned long) vmalloc_addr;
+	struct page *page = NULL;
+	pgd_t *pgd = pgd_offset_k(addr);
+	pmd_t *pmd;
+	pte_t *ptep, pte;
+  
+	if (!pgd_none(*pgd)) {
+		pmd = pmd_offset(pgd, addr);
+		if (!pmd_none(*pmd)) {
+			preempt_disable();
+			ptep = pte_offset_map(pmd, addr);
+			pte = *ptep;
+			if (pte_present(pte))
+				page = pte_page(pte);
+			pte_unmap(ptep);
+			preempt_enable();
+		}
+	}
+	return page;
+}
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+#define DRM_RPR_ARG(vma)
+#else
+#define DRM_RPR_ARG(vma) vma,
+#endif
+
+#define VM_OFFSET(vma) ((vma)->vm_pgoff << PAGE_SHIFT)
+
+/*@}*/
+
+
+/***********************************************************************/
+/** \name Macros to make printk easier */
+/*@{*/
+
+/**
+ * Error output.
+ *
+ * \param fmt printf() like format string.
+ * \param arg arguments
+ */
+#define DRM_ERROR(fmt, arg...) \
+	printk(KERN_ERR "[" DRM_NAME ":%s] *ERROR* " fmt , __FUNCTION__ , ##arg)
+
+/**
+ * Memory error output.
+ *
+ * \param area memory area where the error occurred.
+ * \param fmt printf() like format string.
+ * \param arg arguments
+ */
+#define DRM_MEM_ERROR(area, fmt, arg...) \
+	printk(KERN_ERR "[" DRM_NAME ":%s:%s] *ERROR* " fmt , __FUNCTION__, \
+	       drm_mem_stats[area].name , ##arg)
+
+#define DRM_INFO(fmt, arg...)  printk(KERN_INFO "[" DRM_NAME "] " fmt , ##arg)
+
+/**
+ * Debug output.
+ * 
+ * \param fmt printf() like format string.
+ * \param arg arguments
+ */
+#if DRM_DEBUG_CODE
+#define DRM_DEBUG(fmt, arg...)						\
+	do {								\
+		if ( drm_debug )			\
+			printk(KERN_DEBUG				\
+			       "[" DRM_NAME ":%s] " fmt ,	\
+			       __FUNCTION__ , ##arg);			\
+	} while (0)
+#else
+#define DRM_DEBUG(fmt, arg...)		 do { } while (0)
+#endif
+
+#define DRM_PROC_LIMIT (PAGE_SIZE-80)
+
+#define DRM_PROC_PRINT(fmt, arg...)					\
+   len += sprintf(&buf[len], fmt , ##arg);				\
+   if (len > DRM_PROC_LIMIT) { *eof = 1; return len - offset; }
+
+#define DRM_PROC_PRINT_RET(ret, fmt, arg...)				\
+   len += sprintf(&buf[len], fmt , ##arg);				\
+   if (len > DRM_PROC_LIMIT) { ret; *eof = 1; return len - offset; }
+
+/*@}*/
+
+
+/***********************************************************************/
+/** \name Internal types and structures */
+/*@{*/
+
+#define DRM_ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+#define DRM_MIN(a,b) ((a)<(b)?(a):(b))
+#define DRM_MAX(a,b) ((a)>(b)?(a):(b))
+
+#define DRM_LEFTCOUNT(x) (((x)->rp + (x)->count - (x)->wp) % ((x)->count + 1))
+#define DRM_BUFCOUNT(x) ((x)->count - DRM_LEFTCOUNT(x))
+#define DRM_WAITCOUNT(dev,idx) DRM_BUFCOUNT(&dev->queuelist[idx]->waitlist)
+
+#define DRM_IF_VERSION(maj, min) (maj << 16 | min)
+/**
+ * Get the private SAREA mapping.
+ *
+ * \param _dev DRM device.
+ * \param _ctx context number.
+ * \param _map output mapping.
+ */
+#define DRM_GET_PRIV_SAREA(_dev, _ctx, _map) do {	\
+	(_map) = (_dev)->context_sareas[_ctx];		\
+} while(0)
+
+/**
+ * Test that the hardware lock is held by the caller, returning otherwise.
+ *
+ * \param dev DRM device.
+ * \param filp file pointer of the caller.
+ */
+#define LOCK_TEST_WITH_RETURN( dev, filp )				\
+do {									\
+	if ( !_DRM_LOCK_IS_HELD( dev->lock.hw_lock->lock ) ||		\
+	     dev->lock.filp != filp ) {				\
+		DRM_ERROR( "%s called without lock held\n",		\
+			   __FUNCTION__ );				\
+		return -EINVAL;						\
+	}								\
+} while (0)
+
+/**
+ * Copy and IOCTL return string to user space
+ */
+#define DRM_COPY( name, value )						\
+	len = strlen( value );						\
+	if ( len > name##_len ) len = name##_len;			\
+	name##_len = strlen( value );					\
+	if ( len && name ) {						\
+		if ( copy_to_user( name, value, len ) )			\
+			return -EFAULT;					\
+	}
+	
+/**
+ * Ioctl function type.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg argument.
+ */
+typedef int drm_ioctl_t( struct inode *inode, struct file *filp,
+			 unsigned int cmd, unsigned long arg );
+
+typedef struct drm_ioctl_desc {
+	drm_ioctl_t	     *func;
+	int		     auth_needed;
+	int		     root_only;
+} drm_ioctl_desc_t;
+
+typedef struct drm_devstate {
+	pid_t		  owner;	/**< X server pid holding x_lock */
+} drm_devstate_t;
+
+typedef struct drm_magic_entry {
+	drm_magic_t	       magic;
+	struct drm_file	       *priv;
+	struct drm_magic_entry *next;
+} drm_magic_entry_t;
+
+typedef struct drm_magic_head {
+	struct drm_magic_entry *head;
+	struct drm_magic_entry *tail;
+} drm_magic_head_t;
+
+typedef struct drm_vma_entry {
+	struct vm_area_struct *vma;
+	struct drm_vma_entry  *next;
+	pid_t		      pid;
+} drm_vma_entry_t;
+
+/**
+ * DMA buffer.
+ */
+typedef struct drm_buf {
+	int		  idx;	       /**< Index into master buflist */
+	int		  total;       /**< Buffer size */
+	int		  order;       /**< log-base-2(total) */
+	int		  used;	       /**< Amount of buffer in use (for DMA) */
+	unsigned long	  offset;      /**< Byte offset (used internally) */
+	void		  *address;    /**< Address of buffer */
+	unsigned long	  bus_address; /**< Bus address of buffer */
+	struct drm_buf	  *next;       /**< Kernel-only: used for free list */
+	__volatile__ int  waiting;     /**< On kernel DMA queue */
+	__volatile__ int  pending;     /**< On hardware DMA queue */
+	wait_queue_head_t dma_wait;    /**< Processes waiting */
+	struct file       *filp;       /**< Pointer to holding file descr */
+	int		  context;     /**< Kernel queue for this buffer */
+	int		  while_locked;/**< Dispatch this buffer while locked */
+	enum {
+		DRM_LIST_NONE	 = 0,
+		DRM_LIST_FREE	 = 1,
+		DRM_LIST_WAIT	 = 2,
+		DRM_LIST_PEND	 = 3,
+		DRM_LIST_PRIO	 = 4,
+		DRM_LIST_RECLAIM = 5
+	}		  list;	       /**< Which list we're on */
+
+	int		  dev_priv_size; /**< Size of buffer private storage */
+	void		  *dev_private;  /**< Per-buffer private storage */
+} drm_buf_t;
+
+
+/** bufs is one longer than it has to be */
+typedef struct drm_waitlist {
+	int		  count;	/**< Number of possible buffers */
+	drm_buf_t	  **bufs;	/**< List of pointers to buffers */
+	drm_buf_t	  **rp;		/**< Read pointer */
+	drm_buf_t	  **wp;		/**< Write pointer */
+	drm_buf_t	  **end;	/**< End pointer */
+	spinlock_t	  read_lock;
+	spinlock_t	  write_lock;
+} drm_waitlist_t;
+
+typedef struct drm_freelist {
+	int		  initialized; /**< Freelist in use */
+	atomic_t	  count;       /**< Number of free buffers */
+	drm_buf_t	  *next;       /**< End pointer */
+
+	wait_queue_head_t waiting;     /**< Processes waiting on free bufs */
+	int		  low_mark;    /**< Low water mark */
+	int		  high_mark;   /**< High water mark */
+	atomic_t	  wfh;	       /**< If waiting for high mark */
+	spinlock_t        lock;
+} drm_freelist_t;
+
+/**
+ * Buffer entry.  There is one of this for each buffer size order.
+ */
+typedef struct drm_buf_entry {
+	int		  buf_size;	/**< size */
+	int		  buf_count;	/**< number of buffers */
+	drm_buf_t	  *buflist;	/**< buffer list */
+	int		  seg_count;
+	int		  page_order;
+	unsigned long	  *seglist;
+
+	drm_freelist_t	  freelist;
+} drm_buf_entry_t;
+
+/** File private data */
+typedef struct drm_file {
+	int		  authenticated;
+	int		  minor;
+	pid_t		  pid;
+	uid_t		  uid;
+	drm_magic_t	  magic;
+	unsigned long	  ioctl_count;
+	struct drm_file	  *next;
+	struct drm_file	  *prev;
+	struct drm_head   *head;
+	int 		  remove_auth_on_close;
+	unsigned long     lock_count;
+	void              *driver_priv;
+} drm_file_t;
+
+/** Wait queue */
+typedef struct drm_queue {
+	atomic_t	  use_count;	/**< Outstanding uses (+1) */
+	atomic_t	  finalization;	/**< Finalization in progress */
+	atomic_t	  block_count;	/**< Count of processes waiting */
+	atomic_t	  block_read;	/**< Queue blocked for reads */
+	wait_queue_head_t read_queue;	/**< Processes waiting on block_read */
+	atomic_t	  block_write;	/**< Queue blocked for writes */
+	wait_queue_head_t write_queue;	/**< Processes waiting on block_write */
+#if 1
+	atomic_t	  total_queued;	/**< Total queued statistic */
+	atomic_t	  total_flushed;/**< Total flushes statistic */
+	atomic_t	  total_locks;	/**< Total locks statistics */
+#endif
+	drm_ctx_flags_t	  flags;	/**< Context preserving and 2D-only */
+	drm_waitlist_t	  waitlist;	/**< Pending buffers */
+	wait_queue_head_t flush_queue;	/**< Processes waiting until flush */
+} drm_queue_t;
+
+/**
+ * Lock data.
+ */
+typedef struct drm_lock_data {
+	drm_hw_lock_t	  *hw_lock;	/**< Hardware lock */
+	struct file       *filp;	/**< File descr of lock holder (0=kernel) */
+	wait_queue_head_t lock_queue;	/**< Queue of blocked processes */
+	unsigned long	  lock_time;	/**< Time of last lock in jiffies */
+} drm_lock_data_t;
+
+/**
+ * DMA data.
+ */
+typedef struct drm_device_dma {
+
+	drm_buf_entry_t	  bufs[DRM_MAX_ORDER+1];	/**< buffers, grouped by their size order */
+	int		  buf_count;	/**< total number of buffers */
+	drm_buf_t	  **buflist;	/**< Vector of pointers into drm_device_dma::bufs */
+	int		  seg_count;
+	int		  page_count;	/**< number of pages */
+	unsigned long	  *pagelist;	/**< page list */
+	unsigned long	  byte_count;
+	enum {
+		_DRM_DMA_USE_AGP = 0x01,
+		_DRM_DMA_USE_SG  = 0x02
+	} flags;
+
+} drm_device_dma_t;
+
+/** 
+ * AGP memory entry.  Stored as a doubly linked list.
+ */
+typedef struct drm_agp_mem {
+	unsigned long      handle;	/**< handle */
+	DRM_AGP_MEM        *memory;	
+	unsigned long      bound;	/**< address */
+	int                pages;
+	struct drm_agp_mem *prev;	/**< previous entry */
+	struct drm_agp_mem *next;	/**< next entry */
+} drm_agp_mem_t;
+
+/**
+ * AGP data.
+ *
+ * \sa drm_agp_init() and drm_device::agp.
+ */
+typedef struct drm_agp_head {
+	DRM_AGP_KERN       agp_info;	/**< AGP device information */
+	drm_agp_mem_t      *memory;	/**< memory entries */
+	unsigned long      mode;	/**< AGP mode */
+	struct agp_bridge_data  *bridge;
+	int                enabled;	/**< whether the AGP bus as been enabled */
+	int                acquired;	/**< whether the AGP device has been acquired */
+	unsigned long      base;
+   	int 		   agp_mtrr;
+	int		   cant_use_aperture;
+	unsigned long	   page_mask;
+} drm_agp_head_t;
+
+/**
+ * Scatter-gather memory.
+ */
+typedef struct drm_sg_mem {
+	unsigned long   handle;
+	void            *virtual;
+	int             pages;
+	struct page     **pagelist;
+	dma_addr_t	*busaddr;
+} drm_sg_mem_t;
+
+typedef struct drm_sigdata {
+	int           context;
+	drm_hw_lock_t *lock;
+} drm_sigdata_t;
+
+/**
+ * Mappings list
+ */
+typedef struct drm_map_list {
+	struct list_head	head;	/**< list head */
+	drm_map_t		*map;	/**< mapping */
+} drm_map_list_t;
+
+typedef drm_map_t drm_local_map_t;
+
+/**
+ * Context handle list
+ */
+typedef struct drm_ctx_list {
+	struct list_head	head;   /**< list head */
+	drm_context_t		handle; /**< context handle */
+	drm_file_t		*tag;   /**< associated fd private data */
+} drm_ctx_list_t;
+
+
+typedef struct drm_vbl_sig {
+	struct list_head	head;
+	unsigned int		sequence;
+	struct siginfo		info;
+	struct task_struct	*task;
+} drm_vbl_sig_t;
+
+
+/**
+ * DRM driver structure. This structure represent the common code for
+ * a family of cards. There will one drm_device for each card present
+ * in this family
+ */
+struct drm_device;
+
+struct drm_driver {
+	int (*preinit)(struct drm_device *, unsigned long flags);
+	void (*prerelease)(struct drm_device *, struct file *filp);
+	void (*pretakedown)(struct drm_device *);
+	int (*postcleanup)(struct drm_device *);
+	int (*presetup)(struct drm_device *);
+	int (*postsetup)(struct drm_device *);
+ 	int (*dma_ioctl)( DRM_IOCTL_ARGS );
+	int (*open_helper)(struct drm_device *, drm_file_t *);
+	void (*free_filp_priv)(struct drm_device *, drm_file_t *);
+	void (*release)(struct drm_device *, struct file *filp);
+	void (*dma_ready)(struct drm_device *);
+	int (*dma_quiescent)(struct drm_device *);
+	int (*context_ctor)(struct drm_device *dev, int context);
+ 	int (*context_dtor)(struct drm_device *dev, int context);
+ 	int (*kernel_context_switch)(struct drm_device *dev, int old, int new);
+	void (*kernel_context_switch_unlock)(struct drm_device *dev, drm_lock_t *lock);
+	int (*vblank_wait)(struct drm_device *dev, unsigned int *sequence);
+	/* these have to be filled in */
+ 	int (*postinit)(struct drm_device *, unsigned long flags);
+	irqreturn_t (*irq_handler)( DRM_IRQ_ARGS );
+ 	void (*irq_preinstall)(struct drm_device *dev);
+ 	void (*irq_postinstall)(struct drm_device *dev);
+ 	void (*irq_uninstall)(struct drm_device *dev);
+	void (*reclaim_buffers)(struct drm_device *dev, struct file *filp);
+	unsigned long (*get_map_ofs)(drm_map_t *map);
+	unsigned long (*get_reg_ofs)(struct drm_device *dev);
+	void (*set_version)(struct drm_device *dev, drm_set_version_t *sv);
+ 	int (*version)(drm_version_t *version);
+	u32 driver_features;
+	int dev_priv_size;
+	drm_ioctl_desc_t *ioctls;
+	int num_ioctls;
+	struct file_operations fops;
+	struct pci_driver pci_driver;
+};
+
+/**
+ * DRM head structure. This structure represent a video head on a card
+ * that may contain multiple heads. Embed one per head of these in the
+ * private drm_device structure.
+ */
+typedef struct drm_head {
+	int minor;			/**< Minor device number */
+	struct drm_device *dev;
+	struct proc_dir_entry *dev_root;  /**< proc directory entry */
+	dev_t device;			/**< Device number for mknod */
+	struct class_device *dev_class;
+} drm_head_t;
+
+/**
+ * DRM device structure. This structure represent a complete card that
+ * may contain multiple heads.
+ */
+typedef struct drm_device {
+	char		  *unique;	/**< Unique identifier: e.g., busid */
+	int		  unique_len;	/**< Length of unique field */
+	char		  *devname;	/**< For /proc/interrupts */
+	int		  if_version;	/**< Highest interface version set */
+
+	int		  blocked;	/**< Blocked due to VC switch? */
+
+	/** \name Locks */
+	/*@{*/
+	spinlock_t	  count_lock;	/**< For inuse, drm_device::open_count, drm_device::buf_use */
+	struct semaphore  struct_sem;	/**< For others */
+	/*@}*/
+
+	/** \name Usage Counters */
+	/*@{*/
+	int		  open_count;	/**< Outstanding files open */
+	atomic_t	  ioctl_count;	/**< Outstanding IOCTLs pending */
+	atomic_t	  vma_count;	/**< Outstanding vma areas open */
+	int		  buf_use;	/**< Buffers in use -- cannot alloc */
+	atomic_t	  buf_alloc;	/**< Buffer allocation in progress */
+	/*@}*/
+
+	/** \name Performance counters */
+	/*@{*/
+	unsigned long     counters;
+	drm_stat_type_t   types[15];
+	atomic_t          counts[15];
+	/*@}*/
+
+	/** \name Authentication */
+	/*@{*/
+	drm_file_t	  *file_first;	/**< file list head */
+	drm_file_t	  *file_last;	/**< file list tail */
+	drm_magic_head_t  magiclist[DRM_HASH_SIZE];	/**< magic hash table */
+	/*@}*/
+
+	/** \name Memory management */
+	/*@{*/
+	drm_map_list_t	  *maplist;	/**< Linked list of regions */
+	int		  map_count;	/**< Number of mappable regions */
+
+	/** \name Context handle management */
+	/*@{*/
+	drm_ctx_list_t	  *ctxlist;	/**< Linked list of context handles */
+	int		  ctx_count;	/**< Number of context handles */
+	struct semaphore  ctxlist_sem;	/**< For ctxlist */
+
+	drm_map_t	  **context_sareas; /**< per-context SAREA's */
+	int		  max_context;
+
+	drm_vma_entry_t	  *vmalist;	/**< List of vmas (for debugging) */
+	drm_lock_data_t	  lock;		/**< Information on hardware lock */
+	/*@}*/
+
+	/** \name DMA queues (contexts) */
+	/*@{*/
+	int		  queue_count;	/**< Number of active DMA queues */
+	int		  queue_reserved; /**< Number of reserved DMA queues */
+	int		  queue_slots;	/**< Actual length of queuelist */
+	drm_queue_t	  **queuelist;	/**< Vector of pointers to DMA queues */
+	drm_device_dma_t  *dma;		/**< Optional pointer for DMA support */
+	/*@}*/
+
+	/** \name Context support */
+	/*@{*/
+	int		  irq;		/**< Interrupt used by board */
+	int		  irq_enabled;	/**< True if irq handler is enabled */
+	__volatile__ long context_flag;	/**< Context swapping flag */
+	__volatile__ long interrupt_flag; /**< Interruption handler flag */
+	__volatile__ long dma_flag;	/**< DMA dispatch flag */
+	struct timer_list timer;	/**< Timer for delaying ctx switch */
+	wait_queue_head_t context_wait; /**< Processes waiting on ctx switch */
+	int		  last_checked;	/**< Last context checked for DMA */
+	int		  last_context;	/**< Last current context */
+	unsigned long	  last_switch;	/**< jiffies at last context switch */
+	/*@}*/
+	
+	struct work_struct	work;
+	/** \name VBLANK IRQ support */
+	/*@{*/
+
+   	wait_queue_head_t vbl_queue;	/**< VBLANK wait queue */
+   	atomic_t          vbl_received;
+	spinlock_t        vbl_lock;
+	drm_vbl_sig_t     vbl_sigs;	/**< signal list to send on VBLANK */
+	unsigned int      vbl_pending;
+
+	/*@}*/
+	cycles_t	  ctx_start;
+	cycles_t	  lck_start;
+
+	char		  buf[DRM_BSZ]; /**< Output buffer */
+	char		  *buf_rp;	/**< Read pointer */
+	char		  *buf_wp;	/**< Write pointer */
+	char		  *buf_end;	/**< End pointer */
+	struct fasync_struct *buf_async;/**< Processes waiting for SIGIO */
+	wait_queue_head_t buf_readers;	/**< Processes waiting to read */
+	wait_queue_head_t buf_writers;	/**< Processes waiting to ctx switch */
+
+	drm_agp_head_t    *agp;	/**< AGP data */
+
+	struct pci_dev    *pdev;	/**< PCI device structure */
+	int               pci_domain;	/**< PCI bus domain number */
+	int               pci_bus;	/**< PCI bus number */
+	int               pci_slot;	/**< PCI slot number */
+	int               pci_func;	/**< PCI function number */
+#ifdef __alpha__
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,3)
+	struct pci_controler *hose;
+#else
+	struct pci_controller *hose;
+#endif
+#endif
+	drm_sg_mem_t      *sg;  /**< Scatter gather memory */
+	unsigned long     *ctx_bitmap;	/**< context bitmap */
+	void		  *dev_private; /**< device private data */
+	drm_sigdata_t     sigdata; /**< For block_all_signals */
+	sigset_t          sigmask;
+
+	struct            drm_driver *driver;
+	drm_local_map_t   *agp_buffer_map;
+	drm_head_t primary;		/**< primary screen head */
+} drm_device_t;
+
+static __inline__ int drm_core_check_feature(struct drm_device *dev, int feature)
+{
+	return ((dev->driver->driver_features & feature) ? 1 : 0);
+}
+
+#if __OS_HAS_AGP
+static inline int drm_core_has_AGP(struct drm_device *dev)
+{
+  return drm_core_check_feature(dev, DRIVER_USE_AGP);
+}
+#else
+#define drm_core_has_AGP(dev) (0)
+#endif
+
+#if __OS_HAS_MTRR
+static inline int drm_core_has_MTRR(struct drm_device *dev)
+{
+  return drm_core_check_feature(dev, DRIVER_USE_MTRR);
+}
+#else
+#define drm_core_has_MTRR(dev) (0)
+#endif
+
+/******************************************************************/
+/** \name Internal function definitions */
+/*@{*/
+
+				/* Misc. support (drm_init.h) */
+extern int	     drm_flags;
+extern void	     drm_parse_options( char *s );
+extern int           drm_cpu_valid( void );
+
+				/* Driver support (drm_drv.h) */
+extern int           drm_init(struct drm_driver *driver);
+extern void          drm_exit(struct drm_driver *driver);
+extern int           drm_version(struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg);
+extern int           drm_ioctl(struct inode *inode, struct file *filp,
+				unsigned int cmd, unsigned long arg);
+extern int           drm_takedown(drm_device_t * dev);
+
+				/* Device support (drm_fops.h) */
+extern int           drm_open(struct inode *inode, struct file *filp);
+extern int           drm_stub_open(struct inode *inode, struct file *filp);
+extern int	     drm_open_helper(struct inode *inode, struct file *filp,
+				      drm_device_t *dev);
+extern int	     drm_flush(struct file *filp);
+extern int	     drm_fasync(int fd, struct file *filp, int on);
+extern int           drm_release(struct inode *inode, struct file *filp);
+
+				/* Mapping support (drm_vm.h) */
+extern void	     drm_vm_open(struct vm_area_struct *vma);
+extern void	     drm_vm_close(struct vm_area_struct *vma);
+extern void	     drm_vm_shm_close(struct vm_area_struct *vma);
+extern int	     drm_mmap_dma(struct file *filp,
+				   struct vm_area_struct *vma);
+extern int	     drm_mmap(struct file *filp, struct vm_area_struct *vma);
+extern unsigned int  drm_poll(struct file *filp, struct poll_table_struct *wait);
+extern ssize_t       drm_read(struct file *filp, char __user *buf, size_t count, loff_t *off);
+
+				/* Memory management support (drm_memory.h) */
+#include "drm_memory.h"
+extern void	     drm_mem_init(void);
+extern int	     drm_mem_info(char *buf, char **start, off_t offset,
+				   int request, int *eof, void *data);
+extern void	     *drm_calloc(size_t nmemb, size_t size, int area);
+extern void	     *drm_realloc(void *oldpt, size_t oldsize, size_t size,
+				   int area);
+extern unsigned long drm_alloc_pages(int order, int area);
+extern void	     drm_free_pages(unsigned long address, int order,
+				     int area);
+extern void	     *drm_ioremap(unsigned long offset, unsigned long size, drm_device_t *dev);
+extern void	     *drm_ioremap_nocache(unsigned long offset, unsigned long size,
+					   drm_device_t *dev);
+extern void	     drm_ioremapfree(void *pt, unsigned long size, drm_device_t *dev);
+
+extern DRM_AGP_MEM   *drm_alloc_agp(struct agp_bridge_data *bridge, int pages, u32 type);
+extern int           drm_free_agp(DRM_AGP_MEM *handle, int pages);
+extern int           drm_bind_agp(DRM_AGP_MEM *handle, unsigned int start);
+extern int           drm_unbind_agp(DRM_AGP_MEM *handle);
+
+				/* Misc. IOCTL support (drm_ioctl.h) */
+extern int	     drm_irq_by_busid(struct inode *inode, struct file *filp,
+				       unsigned int cmd, unsigned long arg);
+extern int	     drm_getunique(struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg);
+extern int	     drm_setunique(struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg);
+extern int	     drm_getmap(struct inode *inode, struct file *filp,
+				 unsigned int cmd, unsigned long arg);
+extern int	     drm_getclient(struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg);
+extern int	     drm_getstats(struct inode *inode, struct file *filp,
+				   unsigned int cmd, unsigned long arg);
+extern int	     drm_setversion(struct inode *inode, struct file *filp,
+				     unsigned int cmd, unsigned long arg);
+
+				/* Context IOCTL support (drm_context.h) */
+extern int	     drm_resctx( struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg );
+extern int	     drm_addctx( struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg );
+extern int	     drm_modctx( struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg );
+extern int	     drm_getctx( struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg );
+extern int	     drm_switchctx( struct inode *inode, struct file *filp,
+				     unsigned int cmd, unsigned long arg );
+extern int	     drm_newctx( struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg );
+extern int	     drm_rmctx( struct inode *inode, struct file *filp,
+				 unsigned int cmd, unsigned long arg );
+
+extern int	     drm_context_switch(drm_device_t *dev, int old, int new);
+extern int	     drm_context_switch_complete(drm_device_t *dev, int new);
+
+extern int	     drm_ctxbitmap_init( drm_device_t *dev );
+extern void	     drm_ctxbitmap_cleanup( drm_device_t *dev );
+extern void          drm_ctxbitmap_free( drm_device_t *dev, int ctx_handle );
+
+extern int	     drm_setsareactx( struct inode *inode, struct file *filp,
+				       unsigned int cmd, unsigned long arg );
+extern int	     drm_getsareactx( struct inode *inode, struct file *filp,
+				       unsigned int cmd, unsigned long arg );
+
+				/* Drawable IOCTL support (drm_drawable.h) */
+extern int	     drm_adddraw(struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg);
+extern int	     drm_rmdraw(struct inode *inode, struct file *filp,
+				 unsigned int cmd, unsigned long arg);
+
+
+				/* Authentication IOCTL support (drm_auth.h) */
+extern int	     drm_add_magic(drm_device_t *dev, drm_file_t *priv,
+				    drm_magic_t magic);
+extern int	     drm_remove_magic(drm_device_t *dev, drm_magic_t magic);
+extern int	     drm_getmagic(struct inode *inode, struct file *filp,
+				   unsigned int cmd, unsigned long arg);
+extern int	     drm_authmagic(struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg);
+
+                                /* Placeholder for ioctls past */
+extern int	     drm_noop(struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg);
+
+				/* Locking IOCTL support (drm_lock.h) */
+extern int           drm_lock(struct inode *inode, struct file *filp,
+			       unsigned int cmd, unsigned long arg);
+extern int           drm_unlock(struct inode *inode, struct file *filp,
+				 unsigned int cmd, unsigned long arg);
+extern int	     drm_lock_take(__volatile__ unsigned int *lock,
+				    unsigned int context);
+extern int	     drm_lock_transfer(drm_device_t *dev,
+					__volatile__ unsigned int *lock,
+					unsigned int context);
+extern int	     drm_lock_free(drm_device_t *dev,
+				    __volatile__ unsigned int *lock,
+				    unsigned int context);
+extern int           drm_notifier(void *priv);
+
+				/* Buffer management support (drm_bufs.h) */
+extern int	     drm_order( unsigned long size );
+extern int	     drm_addmap( struct inode *inode, struct file *filp,
+				  unsigned int cmd, unsigned long arg );
+extern int	     drm_rmmap( struct inode *inode, struct file *filp,
+				 unsigned int cmd, unsigned long arg );
+extern int	     drm_addbufs( struct inode *inode, struct file *filp,
+				   unsigned int cmd, unsigned long arg );
+extern int	     drm_infobufs( struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg );
+extern int	     drm_markbufs( struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg );
+extern int	     drm_freebufs( struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg );
+extern int	     drm_mapbufs( struct inode *inode, struct file *filp,
+				   unsigned int cmd, unsigned long arg );
+
+				/* DMA support (drm_dma.h) */
+extern int	     drm_dma_setup(drm_device_t *dev);
+extern void	     drm_dma_takedown(drm_device_t *dev);
+extern void	     drm_free_buffer(drm_device_t *dev, drm_buf_t *buf);
+extern void	     drm_core_reclaim_buffers(drm_device_t *dev, struct file *filp);
+
+				/* IRQ support (drm_irq.h) */
+extern int           drm_control( struct inode *inode, struct file *filp,
+				   unsigned int cmd, unsigned long arg );
+extern int           drm_irq_install( drm_device_t *dev );
+extern int           drm_irq_uninstall( drm_device_t *dev );
+extern irqreturn_t   drm_irq_handler( DRM_IRQ_ARGS );
+extern void          drm_driver_irq_preinstall( drm_device_t *dev );
+extern void          drm_driver_irq_postinstall( drm_device_t *dev );
+extern void          drm_driver_irq_uninstall( drm_device_t *dev );
+
+extern int           drm_wait_vblank(struct inode *inode, struct file *filp,
+				      unsigned int cmd, unsigned long arg);
+extern int           drm_vblank_wait(drm_device_t *dev, unsigned int *vbl_seq);
+extern void          drm_vbl_send_signals( drm_device_t *dev );
+
+				/* AGP/GART support (drm_agpsupport.h) */
+extern drm_agp_head_t *drm_agp_init(drm_device_t *dev);
+extern int            drm_agp_acquire(struct inode *inode, struct file *filp,
+				       unsigned int cmd, unsigned long arg);
+extern void           drm_agp_do_release(drm_device_t *dev);
+extern int            drm_agp_release(struct inode *inode, struct file *filp,
+				       unsigned int cmd, unsigned long arg);
+extern int            drm_agp_enable(struct inode *inode, struct file *filp,
+				      unsigned int cmd, unsigned long arg);
+extern int            drm_agp_info(struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg);
+extern int            drm_agp_alloc(struct inode *inode, struct file *filp,
+				     unsigned int cmd, unsigned long arg);
+extern int            drm_agp_free(struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg);
+extern int            drm_agp_unbind(struct inode *inode, struct file *filp,
+				      unsigned int cmd, unsigned long arg);
+extern int            drm_agp_bind(struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg);
+extern DRM_AGP_MEM    *drm_agp_allocate_memory(struct agp_bridge_data *bridge, size_t pages, u32 type);
+extern int            drm_agp_free_memory(DRM_AGP_MEM *handle);
+extern int            drm_agp_bind_memory(DRM_AGP_MEM *handle, off_t start);
+extern int            drm_agp_unbind_memory(DRM_AGP_MEM *handle);
+
+				/* Stub support (drm_stub.h) */
+extern int drm_get_dev(struct pci_dev *pdev, const struct pci_device_id *ent,
+		     struct drm_driver *driver);
+extern int drm_put_dev(drm_device_t * dev);
+extern int drm_get_head(drm_device_t * dev, drm_head_t *head);
+extern int drm_put_head(drm_head_t * head);
+extern unsigned int   drm_debug;
+extern unsigned int   drm_cards_limit;
+extern drm_head_t **drm_heads;
+extern struct drm_sysfs_class *drm_class;
+extern struct proc_dir_entry *drm_proc_root;
+
+				/* Proc support (drm_proc.h) */
+extern int            drm_proc_init(drm_device_t *dev,
+					     int minor,
+					     struct proc_dir_entry *root,
+					     struct proc_dir_entry **dev_root);
+extern int            drm_proc_cleanup(int minor,
+					struct proc_dir_entry *root,
+					struct proc_dir_entry *dev_root);
+
+				/* Scatter Gather Support (drm_scatter.h) */
+extern void           drm_sg_cleanup(drm_sg_mem_t *entry);
+extern int            drm_sg_alloc(struct inode *inode, struct file *filp,
+				    unsigned int cmd, unsigned long arg);
+extern int            drm_sg_free(struct inode *inode, struct file *filp,
+				   unsigned int cmd, unsigned long arg);
+
+                               /* ATI PCIGART support (ati_pcigart.h) */
+extern int            drm_ati_pcigart_init(drm_device_t *dev,
+					    unsigned long *addr,
+					    dma_addr_t *bus_addr);
+extern int            drm_ati_pcigart_cleanup(drm_device_t *dev,
+					       unsigned long addr,
+					       dma_addr_t bus_addr);
+
+extern void *drm_pci_alloc(drm_device_t * dev, size_t size,
+			   size_t align, dma_addr_t maxaddr,
+			   dma_addr_t * busaddr);
+
+extern void drm_pci_free(drm_device_t * dev, size_t size,
+			 void *vaddr, dma_addr_t busaddr);
+
+			       /* sysfs support (drm_sysfs.c) */
+struct drm_sysfs_class;
+extern struct drm_sysfs_class *drm_sysfs_create(struct module *owner,
+						char *name);
+extern void drm_sysfs_destroy(struct drm_sysfs_class *cs);
+extern struct class_device *drm_sysfs_device_add(struct drm_sysfs_class *cs,
+						 dev_t dev,
+						 struct device *device,
+						 const char *fmt, ...);
+extern void drm_sysfs_device_remove(dev_t dev);
+
+
+/* Inline replacements for DRM_IOREMAP macros */
+static __inline__ void drm_core_ioremap(struct drm_map *map, struct drm_device *dev)
+{
+	map->handle = drm_ioremap( map->offset, map->size, dev );
+}
+
+static __inline__ void drm_core_ioremap_nocache(struct drm_map *map, struct drm_device *dev)
+{
+	map->handle = drm_ioremap_nocache(map->offset, map->size, dev);
+}
+
+static __inline__ void drm_core_ioremapfree(struct drm_map *map, struct drm_device *dev)
+{
+	if ( map->handle && map->size )
+		drm_ioremapfree( map->handle, map->size, dev );
+}
+
+static __inline__ struct drm_map *drm_core_findmap(struct drm_device *dev, unsigned long offset)
+{
+	struct list_head *_list;
+	list_for_each( _list, &dev->maplist->head ) {
+		drm_map_list_t *_entry = list_entry( _list, drm_map_list_t, head );
+		if ( _entry->map &&
+		     _entry->map->offset == offset ) {
+			return _entry->map;
+		}
+	}
+	return NULL;
+}
+
+static __inline__ void drm_core_dropmap(struct drm_map *map)
+{
+}
+
+#ifndef DEBUG_MEMORY
+/** Wrapper around kmalloc() */
+static __inline__ void *drm_alloc(size_t size, int area)
+{
+	return kmalloc(size, GFP_KERNEL);
+}
+
+/** Wrapper around kfree() */
+static __inline__ void drm_free(void *pt, size_t size, int area)
+{
+	kfree(pt);
+}
+#else
+extern void *drm_alloc(size_t size, int area);
+extern void drm_free(void *pt, size_t size, int area);
+#endif
+
+/*@}*/
+
+extern unsigned long drm_core_get_map_ofs(drm_map_t *map);
+extern unsigned long drm_core_get_reg_ofs(struct drm_device *dev);
+
+#endif /* __KERNEL__ */
+#endif
diff --git a/drivers/char/drm/drm_agpsupport.c b/drivers/char/drm/drm_agpsupport.c
new file mode 100644
index 0000000..8d94c0b
--- /dev/null
+++ b/drivers/char/drm/drm_agpsupport.c
@@ -0,0 +1,448 @@
+/**
+ * \file drm_agpsupport.h 
+ * DRM support for AGP/GART backend
+ *    
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#include <linux/module.h>
+
+#if __OS_HAS_AGP
+
+/**
+ * AGP information ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a (output) drm_agp_info structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device has been initialized and acquired and fills in the
+ * drm_agp_info structure with the information in drm_agp_head::agp_info.
+ */
+int drm_agp_info(struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+	DRM_AGP_KERN     *kern;
+	drm_agp_info_t   info;
+
+	if (!dev->agp || !dev->agp->acquired)
+		return -EINVAL;
+
+	kern                   = &dev->agp->agp_info;
+	info.agp_version_major = kern->version.major;
+	info.agp_version_minor = kern->version.minor;
+	info.mode              = kern->mode;
+	info.aperture_base     = kern->aper_base;
+	info.aperture_size     = kern->aper_size * 1024 * 1024;
+	info.memory_allowed    = kern->max_memory << PAGE_SHIFT;
+	info.memory_used       = kern->current_memory << PAGE_SHIFT;
+	info.id_vendor         = kern->device->vendor;
+	info.id_device         = kern->device->device;
+
+	if (copy_to_user((drm_agp_info_t __user *)arg, &info, sizeof(info)))
+		return -EFAULT;
+	return 0;
+}
+
+/**
+ * Acquire the AGP device (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument.
+ * \return zero on success or a negative number on failure. 
+ *
+ * Verifies the AGP device hasn't been acquired before and calls
+ * agp_acquire().
+ */
+int drm_agp_acquire(struct inode *inode, struct file *filp,
+		     unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+
+	if (!dev->agp)
+		return -ENODEV;
+	if (dev->agp->acquired)
+		return -EBUSY;
+	if (!(dev->agp->bridge = agp_backend_acquire(dev->pdev)))
+		return -ENODEV;
+	dev->agp->acquired = 1;
+	return 0;
+}
+
+/**
+ * Release the AGP device (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device has been acquired and calls agp_backend_release().
+ */
+int drm_agp_release(struct inode *inode, struct file *filp,
+		     unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+
+	if (!dev->agp || !dev->agp->acquired)
+		return -EINVAL;
+	agp_backend_release(dev->agp->bridge);
+	dev->agp->acquired = 0;
+	return 0;
+
+}
+
+/**
+ * Release the AGP device.
+ *
+ * Calls agp_backend_release().
+ */
+void drm_agp_do_release(drm_device_t *dev)
+{
+  agp_backend_release(dev->agp->bridge);
+}
+
+/**
+ * Enable the AGP bus.
+ * 
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_mode structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device has been acquired but not enabled, and calls
+ * agp_enable().
+ */
+int drm_agp_enable(struct inode *inode, struct file *filp,
+		    unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+	drm_agp_mode_t   mode;
+
+	if (!dev->agp || !dev->agp->acquired)
+		return -EINVAL;
+
+	if (copy_from_user(&mode, (drm_agp_mode_t __user *)arg, sizeof(mode)))
+		return -EFAULT;
+
+	dev->agp->mode    = mode.mode;
+	agp_enable(dev->agp->bridge, mode.mode);
+	dev->agp->base    = dev->agp->agp_info.aper_base;
+	dev->agp->enabled = 1;
+	return 0;
+}
+
+/**
+ * Allocate AGP memory.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_buffer structure.
+ * \return zero on success or a negative number on failure.
+ * 
+ * Verifies the AGP device is present and has been acquired, allocates the
+ * memory via alloc_agp() and creates a drm_agp_mem entry for it.
+ */
+int drm_agp_alloc(struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+	drm_agp_buffer_t request;
+	drm_agp_mem_t    *entry;
+	DRM_AGP_MEM      *memory;
+	unsigned long    pages;
+	u32 		 type;
+	drm_agp_buffer_t __user *argp = (void __user *)arg;
+
+	if (!dev->agp || !dev->agp->acquired)
+		return -EINVAL;
+	if (copy_from_user(&request, argp, sizeof(request)))
+		return -EFAULT;
+	if (!(entry = drm_alloc(sizeof(*entry), DRM_MEM_AGPLISTS)))
+		return -ENOMEM;
+
+   	memset(entry, 0, sizeof(*entry));
+
+	pages = (request.size + PAGE_SIZE - 1) / PAGE_SIZE;
+	type = (u32) request.type;
+
+	if (!(memory = drm_alloc_agp(dev->agp->bridge, pages, type))) {
+		drm_free(entry, sizeof(*entry), DRM_MEM_AGPLISTS);
+		return -ENOMEM;
+	}
+
+	entry->handle    = (unsigned long)memory->key + 1;
+	entry->memory    = memory;
+	entry->bound     = 0;
+	entry->pages     = pages;
+	entry->prev      = NULL;
+	entry->next      = dev->agp->memory;
+	if (dev->agp->memory)
+		dev->agp->memory->prev = entry;
+	dev->agp->memory = entry;
+
+	request.handle   = entry->handle;
+	request.physical = memory->physical;
+
+	if (copy_to_user(argp, &request, sizeof(request))) {
+		dev->agp->memory       = entry->next;
+		dev->agp->memory->prev = NULL;
+		drm_free_agp(memory, pages);
+		drm_free(entry, sizeof(*entry), DRM_MEM_AGPLISTS);
+		return -EFAULT;
+	}
+	return 0;
+}
+
+/**
+ * Search for the AGP memory entry associated with a handle.
+ *
+ * \param dev DRM device structure.
+ * \param handle AGP memory handle.
+ * \return pointer to the drm_agp_mem structure associated with \p handle.
+ * 
+ * Walks through drm_agp_head::memory until finding a matching handle.
+ */
+static drm_agp_mem_t *drm_agp_lookup_entry(drm_device_t *dev,
+					    unsigned long handle)
+{
+	drm_agp_mem_t *entry;
+
+	for (entry = dev->agp->memory; entry; entry = entry->next) {
+		if (entry->handle == handle)
+			return entry;
+	}
+	return NULL;
+}
+
+/**
+ * Unbind AGP memory from the GATT (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_binding structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device is present and acquired, looks-up the AGP memory
+ * entry and passes it to the unbind_agp() function.
+ */
+int drm_agp_unbind(struct inode *inode, struct file *filp,
+		    unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	  *priv	 = filp->private_data;
+	drm_device_t	  *dev	 = priv->head->dev;
+	drm_agp_binding_t request;
+	drm_agp_mem_t     *entry;
+	int ret;
+
+	if (!dev->agp || !dev->agp->acquired)
+		return -EINVAL;
+	if (copy_from_user(&request, (drm_agp_binding_t __user *)arg, sizeof(request)))
+		return -EFAULT;
+	if (!(entry = drm_agp_lookup_entry(dev, request.handle)))
+		return -EINVAL;
+	if (!entry->bound)
+		return -EINVAL;
+	ret = drm_unbind_agp(entry->memory);
+	if (ret == 0)
+	    entry->bound = 0;
+	return ret;
+}
+
+/**
+ * Bind AGP memory into the GATT (ioctl)
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_binding structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device is present and has been acquired and that no memory
+ * is currently bound into the GATT. Looks-up the AGP memory entry and passes
+ * it to bind_agp() function.
+ */
+int drm_agp_bind(struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	  *priv	 = filp->private_data;
+	drm_device_t	  *dev	 = priv->head->dev;
+	drm_agp_binding_t request;
+	drm_agp_mem_t     *entry;
+	int               retcode;
+	int               page;
+
+	if (!dev->agp || !dev->agp->acquired)
+		return -EINVAL;
+	if (copy_from_user(&request, (drm_agp_binding_t __user *)arg, sizeof(request)))
+		return -EFAULT;
+	if (!(entry = drm_agp_lookup_entry(dev, request.handle)))
+		return -EINVAL;
+	if (entry->bound)
+		return -EINVAL;
+	page = (request.offset + PAGE_SIZE - 1) / PAGE_SIZE;
+	if ((retcode = drm_bind_agp(entry->memory, page)))
+		return retcode;
+	entry->bound = dev->agp->base + (page << PAGE_SHIFT);
+	DRM_DEBUG("base = 0x%lx entry->bound = 0x%lx\n",
+		  dev->agp->base, entry->bound);
+	return 0;
+}
+
+/**
+ * Free AGP memory (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_agp_buffer structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the AGP device is present and has been acquired and looks up the
+ * AGP memory entry. If the memory it's currently bound, unbind it via
+ * unbind_agp(). Frees it via free_agp() as well as the entry itself
+ * and unlinks from the doubly linked list it's inserted in.
+ */
+int drm_agp_free(struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+	drm_agp_buffer_t request;
+	drm_agp_mem_t    *entry;
+
+	if (!dev->agp || !dev->agp->acquired)
+		return -EINVAL;
+	if (copy_from_user(&request, (drm_agp_buffer_t __user *)arg, sizeof(request)))
+		return -EFAULT;
+	if (!(entry = drm_agp_lookup_entry(dev, request.handle)))
+		return -EINVAL;
+	if (entry->bound)
+		drm_unbind_agp(entry->memory);
+
+	if (entry->prev)
+		entry->prev->next = entry->next;
+	else
+		dev->agp->memory = entry->next;
+
+	if (entry->next)
+		entry->next->prev = entry->prev;
+
+	drm_free_agp(entry->memory, entry->pages);
+	drm_free(entry, sizeof(*entry), DRM_MEM_AGPLISTS);
+	return 0;
+}
+
+/**
+ * Initialize the AGP resources.
+ *
+ * \return pointer to a drm_agp_head structure.
+ *
+ */
+drm_agp_head_t *drm_agp_init(drm_device_t *dev)
+{
+	drm_agp_head_t *head         = NULL;
+
+	if (!(head = drm_alloc(sizeof(*head), DRM_MEM_AGPLISTS)))
+		return NULL;
+	memset((void *)head, 0, sizeof(*head));
+	head->bridge = agp_find_bridge(dev->pdev);
+	if (!head->bridge) {
+		if (!(head->bridge = agp_backend_acquire(dev->pdev))) {
+			drm_free(head, sizeof(*head), DRM_MEM_AGPLISTS);
+			return NULL;
+		}
+		agp_copy_info(head->bridge, &head->agp_info);
+		agp_backend_release(head->bridge);
+	} else {
+		agp_copy_info(head->bridge, &head->agp_info);
+	}
+	if (head->agp_info.chipset == NOT_SUPPORTED) {
+		drm_free(head, sizeof(*head), DRM_MEM_AGPLISTS);
+		return NULL;
+	}
+	head->memory = NULL;
+#if LINUX_VERSION_CODE <= 0x020408
+	head->cant_use_aperture = 0;
+	head->page_mask = ~(0xfff);
+#else
+	head->cant_use_aperture = head->agp_info.cant_use_aperture;
+	head->page_mask = head->agp_info.page_mask;
+#endif
+
+	return head;
+}
+
+/** Calls agp_allocate_memory() */
+DRM_AGP_MEM *drm_agp_allocate_memory(struct agp_bridge_data *bridge, size_t pages, u32 type)
+{
+	return agp_allocate_memory(bridge, pages, type);
+}
+
+/** Calls agp_free_memory() */
+int drm_agp_free_memory(DRM_AGP_MEM *handle)
+{
+	if (!handle)
+		return 0;
+	agp_free_memory(handle);
+	return 1;
+}
+
+/** Calls agp_bind_memory() */
+int drm_agp_bind_memory(DRM_AGP_MEM *handle, off_t start)
+{
+	if (!handle)
+		return -EINVAL;
+	return agp_bind_memory(handle, start);
+}
+
+/** Calls agp_unbind_memory() */
+int drm_agp_unbind_memory(DRM_AGP_MEM *handle)
+{
+	if (!handle)
+		return -EINVAL;
+	return agp_unbind_memory(handle);
+}
+
+#endif /* __OS_HAS_AGP */
diff --git a/drivers/char/drm/drm_auth.c b/drivers/char/drm/drm_auth.c
new file mode 100644
index 0000000..b428761
--- /dev/null
+++ b/drivers/char/drm/drm_auth.c
@@ -0,0 +1,230 @@
+/**
+ * \file drm_auth.h 
+ * IOCTLs for authentication
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Tue Feb  2 08:37:54 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/**
+ * Generate a hash key from a magic.
+ *
+ * \param magic magic.
+ * \return hash key.
+ *
+ * The key is the modulus of the hash table size, #DRM_HASH_SIZE, which must be
+ * a power of 2.
+ */
+static int drm_hash_magic(drm_magic_t magic)
+{
+	return magic & (DRM_HASH_SIZE-1);
+}
+
+/**
+ * Find the file with the given magic number.
+ *
+ * \param dev DRM device.
+ * \param magic magic number.
+ *
+ * Searches in drm_device::magiclist within all files with the same hash key
+ * the one with matching magic number, while holding the drm_device::struct_sem
+ * lock.
+ */
+static drm_file_t *drm_find_file(drm_device_t *dev, drm_magic_t magic)
+{
+	drm_file_t	  *retval = NULL;
+	drm_magic_entry_t *pt;
+	int		  hash	  = drm_hash_magic(magic);
+
+	down(&dev->struct_sem);
+	for (pt = dev->magiclist[hash].head; pt; pt = pt->next) {
+		if (pt->magic == magic) {
+			retval = pt->priv;
+			break;
+		}
+	}
+	up(&dev->struct_sem);
+	return retval;
+}
+
+/**
+ * Adds a magic number.
+ * 
+ * \param dev DRM device.
+ * \param priv file private data.
+ * \param magic magic number.
+ *
+ * Creates a drm_magic_entry structure and appends to the linked list
+ * associated the magic number hash key in drm_device::magiclist, while holding
+ * the drm_device::struct_sem lock.
+ */
+int drm_add_magic(drm_device_t *dev, drm_file_t *priv, drm_magic_t magic)
+{
+	int		  hash;
+	drm_magic_entry_t *entry;
+
+	DRM_DEBUG("%d\n", magic);
+
+	hash	     = drm_hash_magic(magic);
+	entry	     = drm_alloc(sizeof(*entry), DRM_MEM_MAGIC);
+	if (!entry) return -ENOMEM;
+	memset(entry, 0, sizeof(*entry));
+	entry->magic = magic;
+	entry->priv  = priv;
+	entry->next  = NULL;
+
+	down(&dev->struct_sem);
+	if (dev->magiclist[hash].tail) {
+		dev->magiclist[hash].tail->next = entry;
+		dev->magiclist[hash].tail	= entry;
+	} else {
+		dev->magiclist[hash].head	= entry;
+		dev->magiclist[hash].tail	= entry;
+	}
+	up(&dev->struct_sem);
+
+	return 0;
+}
+
+/**
+ * Remove a magic number.
+ * 
+ * \param dev DRM device.
+ * \param magic magic number.
+ *
+ * Searches and unlinks the entry in drm_device::magiclist with the magic
+ * number hash key, while holding the drm_device::struct_sem lock.
+ */
+int drm_remove_magic(drm_device_t *dev, drm_magic_t magic)
+{
+	drm_magic_entry_t *prev = NULL;
+	drm_magic_entry_t *pt;
+	int		  hash;
+
+
+	DRM_DEBUG("%d\n", magic);
+	hash = drm_hash_magic(magic);
+
+	down(&dev->struct_sem);
+	for (pt = dev->magiclist[hash].head; pt; prev = pt, pt = pt->next) {
+		if (pt->magic == magic) {
+			if (dev->magiclist[hash].head == pt) {
+				dev->magiclist[hash].head = pt->next;
+			}
+			if (dev->magiclist[hash].tail == pt) {
+				dev->magiclist[hash].tail = prev;
+			}
+			if (prev) {
+				prev->next = pt->next;
+			}
+			up(&dev->struct_sem);
+			return 0;
+		}
+	}
+	up(&dev->struct_sem);
+
+	drm_free(pt, sizeof(*pt), DRM_MEM_MAGIC);
+
+	return -EINVAL;
+}
+
+/**
+ * Get a unique magic number (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a resulting drm_auth structure.
+ * \return zero on success, or a negative number on failure.
+ *
+ * If there is a magic number in drm_file::magic then use it, otherwise
+ * searches an unique non-zero magic number and add it associating it with \p
+ * filp.
+ */
+int drm_getmagic(struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg)
+{
+	static drm_magic_t sequence = 0;
+	static DEFINE_SPINLOCK(lock);
+	drm_file_t	   *priv    = filp->private_data;
+	drm_device_t	   *dev	    = priv->head->dev;
+	drm_auth_t	   auth;
+
+				/* Find unique magic */
+	if (priv->magic) {
+		auth.magic = priv->magic;
+	} else {
+		do {
+			spin_lock(&lock);
+			if (!sequence) ++sequence; /* reserve 0 */
+			auth.magic = sequence++;
+			spin_unlock(&lock);
+		} while (drm_find_file(dev, auth.magic));
+		priv->magic = auth.magic;
+		drm_add_magic(dev, priv, auth.magic);
+	}
+
+	DRM_DEBUG("%u\n", auth.magic);
+	if (copy_to_user((drm_auth_t __user *)arg, &auth, sizeof(auth)))
+		return -EFAULT;
+	return 0;
+}
+
+/**
+ * Authenticate with a magic.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_auth structure.
+ * \return zero if authentication successed, or a negative number otherwise.
+ *
+ * Checks if \p filp is associated with the magic number passed in \arg.
+ */
+int drm_authmagic(struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	   *priv    = filp->private_data;
+	drm_device_t	   *dev	    = priv->head->dev;
+	drm_auth_t	   auth;
+	drm_file_t	   *file;
+
+	if (copy_from_user(&auth, (drm_auth_t __user *)arg, sizeof(auth)))
+		return -EFAULT;
+	DRM_DEBUG("%u\n", auth.magic);
+	if ((file = drm_find_file(dev, auth.magic))) {
+		file->authenticated = 1;
+		drm_remove_magic(dev, auth.magic);
+		return 0;
+	}
+	return -EINVAL;
+}
diff --git a/drivers/char/drm/drm_bufs.c b/drivers/char/drm/drm_bufs.c
new file mode 100644
index 0000000..4113bcb
--- /dev/null
+++ b/drivers/char/drm/drm_bufs.c
@@ -0,0 +1,1270 @@
+/**
+ * \file drm_bufs.h 
+ * Generic buffer template
+ * 
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Thu Nov 23 03:10:50 2000 by gareth@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/vmalloc.h>
+#include "drmP.h"
+
+/**
+ * Compute size order.  Returns the exponent of the smaller power of two which
+ * is greater or equal to given number.
+ * 
+ * \param size size.
+ * \return order.
+ *
+ * \todo Can be made faster.
+ */
+int drm_order( unsigned long size )
+{
+	int order;
+	unsigned long tmp;
+
+	for (order = 0, tmp = size >> 1; tmp; tmp >>= 1, order++)
+		;
+
+	if (size & (size - 1))
+		++order;
+
+	return order;
+}
+EXPORT_SYMBOL(drm_order);
+
+/**
+ * Ioctl to specify a range of memory that is available for mapping by a non-root process.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_map structure.
+ * \return zero on success or a negative value on error.
+ *
+ * Adjusts the memory offset to its absolute value according to the mapping
+ * type.  Adds the map to the map list drm_device::maplist. Adds MTRR's where
+ * applicable and if supported by the kernel.
+ */
+int drm_addmap( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_map_t *map;
+	drm_map_t __user *argp = (void __user *)arg;
+	drm_map_list_t *list;
+
+	if ( !(filp->f_mode & 3) ) return -EACCES; /* Require read/write */
+
+	map = drm_alloc( sizeof(*map), DRM_MEM_MAPS );
+	if ( !map )
+		return -ENOMEM;
+
+	if ( copy_from_user( map, argp, sizeof(*map) ) ) {
+		drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+		return -EFAULT;
+	}
+
+	/* Only allow shared memory to be removable since we only keep enough
+	 * book keeping information about shared memory to allow for removal
+	 * when processes fork.
+	 */
+	if ( (map->flags & _DRM_REMOVABLE) && map->type != _DRM_SHM ) {
+		drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+		return -EINVAL;
+	}
+	DRM_DEBUG( "offset = 0x%08lx, size = 0x%08lx, type = %d\n",
+		   map->offset, map->size, map->type );
+	if ( (map->offset & (~PAGE_MASK)) || (map->size & (~PAGE_MASK)) ) {
+		drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+		return -EINVAL;
+	}
+	map->mtrr   = -1;
+	map->handle = NULL;
+
+	switch ( map->type ) {
+	case _DRM_REGISTERS:
+	case _DRM_FRAME_BUFFER:
+#if !defined(__sparc__) && !defined(__alpha__) && !defined(__ia64__)
+		if ( map->offset + map->size < map->offset ||
+		     map->offset < virt_to_phys(high_memory) ) {
+			drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+			return -EINVAL;
+		}
+#endif
+#ifdef __alpha__
+		map->offset += dev->hose->mem_space->start;
+#endif
+		if (drm_core_has_MTRR(dev)) {
+			if ( map->type == _DRM_FRAME_BUFFER ||
+			     (map->flags & _DRM_WRITE_COMBINING) ) {
+				map->mtrr = mtrr_add( map->offset, map->size,
+						      MTRR_TYPE_WRCOMB, 1 );
+			}
+		}
+		if (map->type == _DRM_REGISTERS)
+			map->handle = drm_ioremap( map->offset, map->size,
+						    dev );
+		break;
+
+	case _DRM_SHM:
+		map->handle = vmalloc_32(map->size);
+		DRM_DEBUG( "%lu %d %p\n",
+			   map->size, drm_order( map->size ), map->handle );
+		if ( !map->handle ) {
+			drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+			return -ENOMEM;
+		}
+		map->offset = (unsigned long)map->handle;
+		if ( map->flags & _DRM_CONTAINS_LOCK ) {
+			/* Prevent a 2nd X Server from creating a 2nd lock */
+			if (dev->lock.hw_lock != NULL) {
+				vfree( map->handle );
+				drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+				return -EBUSY;
+			}
+			dev->sigdata.lock =
+			dev->lock.hw_lock = map->handle; /* Pointer to lock */
+		}
+		break;
+	case _DRM_AGP:
+		if (drm_core_has_AGP(dev)) {
+#ifdef __alpha__
+			map->offset += dev->hose->mem_space->start;
+#endif
+			map->offset += dev->agp->base;
+			map->mtrr   = dev->agp->agp_mtrr; /* for getmap */
+		}
+		break;
+	case _DRM_SCATTER_GATHER:
+		if (!dev->sg) {
+			drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+			return -EINVAL;
+		}
+		map->offset += dev->sg->handle;
+		break;
+
+	default:
+		drm_free( map, sizeof(*map), DRM_MEM_MAPS );
+		return -EINVAL;
+	}
+
+	list = drm_alloc(sizeof(*list), DRM_MEM_MAPS);
+	if(!list) {
+		drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+		return -EINVAL;
+	}
+	memset(list, 0, sizeof(*list));
+	list->map = map;
+
+	down(&dev->struct_sem);
+	list_add(&list->head, &dev->maplist->head);
+ 	up(&dev->struct_sem);
+
+	if ( copy_to_user( argp, map, sizeof(*map) ) )
+		return -EFAULT;
+	if ( map->type != _DRM_SHM ) {
+		if ( copy_to_user( &argp->handle,
+				   &map->offset,
+				   sizeof(map->offset) ) )
+			return -EFAULT;
+	}
+	return 0;
+}
+
+
+/**
+ * Remove a map private from list and deallocate resources if the mapping
+ * isn't in use.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_map_t structure.
+ * \return zero on success or a negative value on error.
+ *
+ * Searches the map on drm_device::maplist, removes it from the list, see if
+ * its being used, and free any associate resource (such as MTRR's) if it's not
+ * being on use.
+ *
+ * \sa addmap().
+ */
+int drm_rmmap(struct inode *inode, struct file *filp,
+	       unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->head->dev;
+	struct list_head *list;
+	drm_map_list_t *r_list = NULL;
+	drm_vma_entry_t *pt, *prev;
+	drm_map_t *map;
+	drm_map_t request;
+	int found_maps = 0;
+
+	if (copy_from_user(&request, (drm_map_t __user *)arg,
+			   sizeof(request))) {
+		return -EFAULT;
+	}
+
+	down(&dev->struct_sem);
+	list = &dev->maplist->head;
+	list_for_each(list, &dev->maplist->head) {
+		r_list = list_entry(list, drm_map_list_t, head);
+
+		if(r_list->map &&
+		   r_list->map->handle == request.handle &&
+		   r_list->map->flags & _DRM_REMOVABLE) break;
+	}
+
+	/* List has wrapped around to the head pointer, or its empty we didn't
+	 * find anything.
+	 */
+	if(list == (&dev->maplist->head)) {
+		up(&dev->struct_sem);
+		return -EINVAL;
+	}
+	map = r_list->map;
+	list_del(list);
+	drm_free(list, sizeof(*list), DRM_MEM_MAPS);
+
+	for (pt = dev->vmalist, prev = NULL; pt; prev = pt, pt = pt->next) {
+		if (pt->vma->vm_private_data == map) found_maps++;
+	}
+
+	if(!found_maps) {
+		switch (map->type) {
+		case _DRM_REGISTERS:
+		case _DRM_FRAME_BUFFER:
+		  if (drm_core_has_MTRR(dev)) {
+				if (map->mtrr >= 0) {
+					int retcode;
+					retcode = mtrr_del(map->mtrr,
+							   map->offset,
+							   map->size);
+					DRM_DEBUG("mtrr_del = %d\n", retcode);
+				}
+			}
+			drm_ioremapfree(map->handle, map->size, dev);
+			break;
+		case _DRM_SHM:
+			vfree(map->handle);
+			break;
+		case _DRM_AGP:
+		case _DRM_SCATTER_GATHER:
+			break;
+		}
+		drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+	}
+	up(&dev->struct_sem);
+	return 0;
+}
+
+/**
+ * Cleanup after an error on one of the addbufs() functions.
+ *
+ * \param entry buffer entry where the error occurred.
+ *
+ * Frees any pages and buffers associated with the given entry.
+ */
+static void drm_cleanup_buf_error(drm_device_t *dev, drm_buf_entry_t *entry)
+{
+	int i;
+
+	if (entry->seg_count) {
+		for (i = 0; i < entry->seg_count; i++) {
+			if (entry->seglist[i]) {
+				drm_free_pages(entry->seglist[i],
+					        entry->page_order,
+					        DRM_MEM_DMA);
+			}
+		}
+		drm_free(entry->seglist,
+			  entry->seg_count *
+			  sizeof(*entry->seglist),
+			  DRM_MEM_SEGS);
+
+		entry->seg_count = 0;
+	}
+
+   	if (entry->buf_count) {
+	   	for (i = 0; i < entry->buf_count; i++) {
+			if (entry->buflist[i].dev_private) {
+				drm_free(entry->buflist[i].dev_private,
+					  entry->buflist[i].dev_priv_size,
+					  DRM_MEM_BUFS);
+			}
+		}
+		drm_free(entry->buflist,
+			  entry->buf_count *
+			  sizeof(*entry->buflist),
+			  DRM_MEM_BUFS);
+
+		entry->buf_count = 0;
+	}
+}
+
+#if __OS_HAS_AGP
+/**
+ * Add AGP buffers for DMA transfers (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_desc_t request.
+ * \return zero on success or a negative number on failure.
+ * 
+ * After some sanity checks creates a drm_buf structure for each buffer and
+ * reallocates the buffer list of the same size order to accommodate the new
+ * buffers.
+ */
+int drm_addbufs_agp( struct inode *inode, struct file *filp,
+		      unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_desc_t request;
+	drm_buf_entry_t *entry;
+	drm_buf_t *buf;
+	unsigned long offset;
+	unsigned long agp_offset;
+	int count;
+	int order;
+	int size;
+	int alignment;
+	int page_order;
+	int total;
+	int byte_count;
+	int i;
+	drm_buf_t **temp_buflist;
+	drm_buf_desc_t __user *argp = (void __user *)arg;
+
+	if ( !dma ) return -EINVAL;
+
+	if ( copy_from_user( &request, argp,
+			     sizeof(request) ) )
+		return -EFAULT;
+
+	count = request.count;
+	order = drm_order( request.size );
+	size = 1 << order;
+
+	alignment  = (request.flags & _DRM_PAGE_ALIGN)
+		? PAGE_ALIGN(size) : size;
+	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
+	total = PAGE_SIZE << page_order;
+
+	byte_count = 0;
+	agp_offset = dev->agp->base + request.agp_start;
+
+	DRM_DEBUG( "count:      %d\n",  count );
+	DRM_DEBUG( "order:      %d\n",  order );
+	DRM_DEBUG( "size:       %d\n",  size );
+	DRM_DEBUG( "agp_offset: %lu\n", agp_offset );
+	DRM_DEBUG( "alignment:  %d\n",  alignment );
+	DRM_DEBUG( "page_order: %d\n",  page_order );
+	DRM_DEBUG( "total:      %d\n",  total );
+
+	if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
+	if ( dev->queue_count ) return -EBUSY; /* Not while in use */
+
+	spin_lock( &dev->count_lock );
+	if ( dev->buf_use ) {
+		spin_unlock( &dev->count_lock );
+		return -EBUSY;
+	}
+	atomic_inc( &dev->buf_alloc );
+	spin_unlock( &dev->count_lock );
+
+	down( &dev->struct_sem );
+	entry = &dma->bufs[order];
+	if ( entry->buf_count ) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM; /* May only call once for each order */
+	}
+
+	if (count < 0 || count > 4096) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -EINVAL;
+	}
+
+	entry->buflist = drm_alloc( count * sizeof(*entry->buflist),
+				    DRM_MEM_BUFS );
+	if ( !entry->buflist ) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;
+	}
+	memset( entry->buflist, 0, count * sizeof(*entry->buflist) );
+
+	entry->buf_size = size;
+	entry->page_order = page_order;
+
+	offset = 0;
+
+	while ( entry->buf_count < count ) {
+		buf          = &entry->buflist[entry->buf_count];
+		buf->idx     = dma->buf_count + entry->buf_count;
+		buf->total   = alignment;
+		buf->order   = order;
+		buf->used    = 0;
+
+		buf->offset  = (dma->byte_count + offset);
+		buf->bus_address = agp_offset + offset;
+		buf->address = (void *)(agp_offset + offset);
+		buf->next    = NULL;
+		buf->waiting = 0;
+		buf->pending = 0;
+		init_waitqueue_head( &buf->dma_wait );
+		buf->filp    = NULL;
+
+		buf->dev_priv_size = dev->driver->dev_priv_size;
+		buf->dev_private = drm_alloc( buf->dev_priv_size,
+					       DRM_MEM_BUFS );
+		if(!buf->dev_private) {
+			/* Set count correctly so we free the proper amount. */
+			entry->buf_count = count;
+			drm_cleanup_buf_error(dev,entry);
+			up( &dev->struct_sem );
+			atomic_dec( &dev->buf_alloc );
+			return -ENOMEM;
+		}
+		memset( buf->dev_private, 0, buf->dev_priv_size );
+
+		DRM_DEBUG( "buffer %d @ %p\n",
+			   entry->buf_count, buf->address );
+
+		offset += alignment;
+		entry->buf_count++;
+		byte_count += PAGE_SIZE << page_order;
+	}
+
+	DRM_DEBUG( "byte_count: %d\n", byte_count );
+
+	temp_buflist = drm_realloc( dma->buflist,
+				     dma->buf_count * sizeof(*dma->buflist),
+				     (dma->buf_count + entry->buf_count)
+				     * sizeof(*dma->buflist),
+				     DRM_MEM_BUFS );
+	if(!temp_buflist) {
+		/* Free the entry because it isn't valid */
+		drm_cleanup_buf_error(dev,entry);
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;
+	}
+	dma->buflist = temp_buflist;
+
+	for ( i = 0 ; i < entry->buf_count ; i++ ) {
+		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
+	}
+
+	dma->buf_count += entry->buf_count;
+	dma->byte_count += byte_count;
+
+	DRM_DEBUG( "dma->buf_count : %d\n", dma->buf_count );
+	DRM_DEBUG( "entry->buf_count : %d\n", entry->buf_count );
+
+	up( &dev->struct_sem );
+
+	request.count = entry->buf_count;
+	request.size = size;
+
+	if ( copy_to_user( argp, &request, sizeof(request) ) )
+		return -EFAULT;
+
+	dma->flags = _DRM_DMA_USE_AGP;
+
+	atomic_dec( &dev->buf_alloc );
+	return 0;
+}
+#endif /* __OS_HAS_AGP */
+
+int drm_addbufs_pci( struct inode *inode, struct file *filp,
+		      unsigned int cmd, unsigned long arg )
+{
+   	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_desc_t request;
+	int count;
+	int order;
+	int size;
+	int total;
+	int page_order;
+	drm_buf_entry_t *entry;
+	unsigned long page;
+	drm_buf_t *buf;
+	int alignment;
+	unsigned long offset;
+	int i;
+	int byte_count;
+	int page_count;
+	unsigned long *temp_pagelist;
+	drm_buf_t **temp_buflist;
+	drm_buf_desc_t __user *argp = (void __user *)arg;
+
+	if (!drm_core_check_feature(dev, DRIVER_PCI_DMA)) return -EINVAL;
+	if ( !dma ) return -EINVAL;
+
+	if ( copy_from_user( &request, argp, sizeof(request) ) )
+		return -EFAULT;
+
+	count = request.count;
+	order = drm_order( request.size );
+	size = 1 << order;
+
+	DRM_DEBUG( "count=%d, size=%d (%d), order=%d, queue_count=%d\n",
+		   request.count, request.size, size,
+		   order, dev->queue_count );
+
+	if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
+	if ( dev->queue_count ) return -EBUSY; /* Not while in use */
+
+	alignment = (request.flags & _DRM_PAGE_ALIGN)
+		? PAGE_ALIGN(size) : size;
+	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
+	total = PAGE_SIZE << page_order;
+
+	spin_lock( &dev->count_lock );
+	if ( dev->buf_use ) {
+		spin_unlock( &dev->count_lock );
+		return -EBUSY;
+	}
+	atomic_inc( &dev->buf_alloc );
+	spin_unlock( &dev->count_lock );
+
+	down( &dev->struct_sem );
+	entry = &dma->bufs[order];
+	if ( entry->buf_count ) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;	/* May only call once for each order */
+	}
+
+	if (count < 0 || count > 4096) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -EINVAL;
+	}
+
+	entry->buflist = drm_alloc( count * sizeof(*entry->buflist),
+				    DRM_MEM_BUFS );
+	if ( !entry->buflist ) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;
+	}
+	memset( entry->buflist, 0, count * sizeof(*entry->buflist) );
+
+	entry->seglist = drm_alloc( count * sizeof(*entry->seglist),
+				    DRM_MEM_SEGS );
+	if ( !entry->seglist ) {
+		drm_free( entry->buflist,
+			  count * sizeof(*entry->buflist),
+			  DRM_MEM_BUFS );
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;
+	}
+	memset( entry->seglist, 0, count * sizeof(*entry->seglist) );
+
+	/* Keep the original pagelist until we know all the allocations
+	 * have succeeded
+	 */
+	temp_pagelist = drm_alloc( (dma->page_count + (count << page_order))
+				    * sizeof(*dma->pagelist),
+				    DRM_MEM_PAGES );
+	if (!temp_pagelist) {
+		drm_free( entry->buflist,
+			   count * sizeof(*entry->buflist),
+			   DRM_MEM_BUFS );
+		drm_free( entry->seglist,
+			   count * sizeof(*entry->seglist),
+			   DRM_MEM_SEGS );
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;
+	}
+	memcpy(temp_pagelist,
+	       dma->pagelist,
+	       dma->page_count * sizeof(*dma->pagelist));
+	DRM_DEBUG( "pagelist: %d entries\n",
+		   dma->page_count + (count << page_order) );
+
+	entry->buf_size	= size;
+	entry->page_order = page_order;
+	byte_count = 0;
+	page_count = 0;
+
+	while ( entry->buf_count < count ) {
+		page = drm_alloc_pages( page_order, DRM_MEM_DMA );
+		if ( !page ) {
+			/* Set count correctly so we free the proper amount. */
+			entry->buf_count = count;
+			entry->seg_count = count;
+			drm_cleanup_buf_error(dev, entry);
+			drm_free( temp_pagelist,
+				   (dma->page_count + (count << page_order))
+				   * sizeof(*dma->pagelist),
+				   DRM_MEM_PAGES );
+			up( &dev->struct_sem );
+			atomic_dec( &dev->buf_alloc );
+			return -ENOMEM;
+		}
+		entry->seglist[entry->seg_count++] = page;
+		for ( i = 0 ; i < (1 << page_order) ; i++ ) {
+			DRM_DEBUG( "page %d @ 0x%08lx\n",
+				   dma->page_count + page_count,
+				   page + PAGE_SIZE * i );
+			temp_pagelist[dma->page_count + page_count++]
+				= page + PAGE_SIZE * i;
+		}
+		for ( offset = 0 ;
+		      offset + size <= total && entry->buf_count < count ;
+		      offset += alignment, ++entry->buf_count ) {
+			buf	     = &entry->buflist[entry->buf_count];
+			buf->idx     = dma->buf_count + entry->buf_count;
+			buf->total   = alignment;
+			buf->order   = order;
+			buf->used    = 0;
+			buf->offset  = (dma->byte_count + byte_count + offset);
+			buf->address = (void *)(page + offset);
+			buf->next    = NULL;
+			buf->waiting = 0;
+			buf->pending = 0;
+			init_waitqueue_head( &buf->dma_wait );
+			buf->filp    = NULL;
+
+			buf->dev_priv_size = dev->driver->dev_priv_size;
+			buf->dev_private = drm_alloc( buf->dev_priv_size,
+						       DRM_MEM_BUFS );
+			if(!buf->dev_private) {
+				/* Set count correctly so we free the proper amount. */
+				entry->buf_count = count;
+				entry->seg_count = count;
+				drm_cleanup_buf_error(dev,entry);
+				drm_free( temp_pagelist,
+					   (dma->page_count + (count << page_order))
+					   * sizeof(*dma->pagelist),
+					   DRM_MEM_PAGES );
+				up( &dev->struct_sem );
+				atomic_dec( &dev->buf_alloc );
+				return -ENOMEM;
+			}
+			memset( buf->dev_private, 0, buf->dev_priv_size );
+
+			DRM_DEBUG( "buffer %d @ %p\n",
+				   entry->buf_count, buf->address );
+		}
+		byte_count += PAGE_SIZE << page_order;
+	}
+
+	temp_buflist = drm_realloc( dma->buflist,
+				     dma->buf_count * sizeof(*dma->buflist),
+				     (dma->buf_count + entry->buf_count)
+				     * sizeof(*dma->buflist),
+				     DRM_MEM_BUFS );
+	if (!temp_buflist) {
+		/* Free the entry because it isn't valid */
+		drm_cleanup_buf_error(dev,entry);
+		drm_free( temp_pagelist,
+			   (dma->page_count + (count << page_order))
+			   * sizeof(*dma->pagelist),
+			   DRM_MEM_PAGES );
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;
+	}
+	dma->buflist = temp_buflist;
+
+	for ( i = 0 ; i < entry->buf_count ; i++ ) {
+		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
+	}
+
+	/* No allocations failed, so now we can replace the orginal pagelist
+	 * with the new one.
+	 */
+	if (dma->page_count) {
+		drm_free(dma->pagelist,
+			  dma->page_count * sizeof(*dma->pagelist),
+			  DRM_MEM_PAGES);
+	}
+	dma->pagelist = temp_pagelist;
+
+	dma->buf_count += entry->buf_count;
+	dma->seg_count += entry->seg_count;
+	dma->page_count += entry->seg_count << page_order;
+	dma->byte_count += PAGE_SIZE * (entry->seg_count << page_order);
+
+	up( &dev->struct_sem );
+
+	request.count = entry->buf_count;
+	request.size = size;
+
+	if ( copy_to_user( argp, &request, sizeof(request) ) )
+		return -EFAULT;
+
+	atomic_dec( &dev->buf_alloc );
+	return 0;
+
+}
+
+int drm_addbufs_sg( struct inode *inode, struct file *filp,
+                     unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_desc_t __user *argp = (void __user *)arg;
+	drm_buf_desc_t request;
+	drm_buf_entry_t *entry;
+	drm_buf_t *buf;
+	unsigned long offset;
+	unsigned long agp_offset;
+	int count;
+	int order;
+	int size;
+	int alignment;
+	int page_order;
+	int total;
+	int byte_count;
+	int i;
+	drm_buf_t **temp_buflist;
+
+	if (!drm_core_check_feature(dev, DRIVER_SG)) return -EINVAL;
+	
+	if ( !dma ) return -EINVAL;
+
+	if ( copy_from_user( &request, argp, sizeof(request) ) )
+		return -EFAULT;
+
+	count = request.count;
+	order = drm_order( request.size );
+	size = 1 << order;
+
+	alignment  = (request.flags & _DRM_PAGE_ALIGN)
+			? PAGE_ALIGN(size) : size;
+	page_order = order - PAGE_SHIFT > 0 ? order - PAGE_SHIFT : 0;
+	total = PAGE_SIZE << page_order;
+
+	byte_count = 0;
+	agp_offset = request.agp_start;
+
+	DRM_DEBUG( "count:      %d\n",  count );
+	DRM_DEBUG( "order:      %d\n",  order );
+	DRM_DEBUG( "size:       %d\n",  size );
+	DRM_DEBUG( "agp_offset: %lu\n", agp_offset );
+	DRM_DEBUG( "alignment:  %d\n",  alignment );
+	DRM_DEBUG( "page_order: %d\n",  page_order );
+	DRM_DEBUG( "total:      %d\n",  total );
+
+	if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
+	if ( dev->queue_count ) return -EBUSY; /* Not while in use */
+
+	spin_lock( &dev->count_lock );
+	if ( dev->buf_use ) {
+		spin_unlock( &dev->count_lock );
+		return -EBUSY;
+	}
+	atomic_inc( &dev->buf_alloc );
+	spin_unlock( &dev->count_lock );
+
+	down( &dev->struct_sem );
+	entry = &dma->bufs[order];
+	if ( entry->buf_count ) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM; /* May only call once for each order */
+	}
+
+	if (count < 0 || count > 4096) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -EINVAL;
+	}
+
+	entry->buflist = drm_alloc( count * sizeof(*entry->buflist),
+				     DRM_MEM_BUFS );
+	if ( !entry->buflist ) {
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;
+	}
+	memset( entry->buflist, 0, count * sizeof(*entry->buflist) );
+
+	entry->buf_size = size;
+	entry->page_order = page_order;
+
+	offset = 0;
+
+	while ( entry->buf_count < count ) {
+		buf          = &entry->buflist[entry->buf_count];
+		buf->idx     = dma->buf_count + entry->buf_count;
+		buf->total   = alignment;
+		buf->order   = order;
+		buf->used    = 0;
+
+		buf->offset  = (dma->byte_count + offset);
+		buf->bus_address = agp_offset + offset;
+		buf->address = (void *)(agp_offset + offset + dev->sg->handle);
+		buf->next    = NULL;
+		buf->waiting = 0;
+		buf->pending = 0;
+		init_waitqueue_head( &buf->dma_wait );
+		buf->filp    = NULL;
+
+		buf->dev_priv_size = dev->driver->dev_priv_size;
+		buf->dev_private = drm_alloc( buf->dev_priv_size,
+					       DRM_MEM_BUFS );
+		if(!buf->dev_private) {
+			/* Set count correctly so we free the proper amount. */
+			entry->buf_count = count;
+			drm_cleanup_buf_error(dev,entry);
+			up( &dev->struct_sem );
+			atomic_dec( &dev->buf_alloc );
+			return -ENOMEM;
+		}
+
+		memset( buf->dev_private, 0, buf->dev_priv_size );
+
+		DRM_DEBUG( "buffer %d @ %p\n",
+			   entry->buf_count, buf->address );
+
+		offset += alignment;
+		entry->buf_count++;
+		byte_count += PAGE_SIZE << page_order;
+	}
+
+	DRM_DEBUG( "byte_count: %d\n", byte_count );
+
+	temp_buflist = drm_realloc( dma->buflist,
+				     dma->buf_count * sizeof(*dma->buflist),
+				     (dma->buf_count + entry->buf_count)
+				     * sizeof(*dma->buflist),
+				     DRM_MEM_BUFS );
+	if(!temp_buflist) {
+		/* Free the entry because it isn't valid */
+		drm_cleanup_buf_error(dev,entry);
+		up( &dev->struct_sem );
+		atomic_dec( &dev->buf_alloc );
+		return -ENOMEM;
+	}
+	dma->buflist = temp_buflist;
+
+	for ( i = 0 ; i < entry->buf_count ; i++ ) {
+		dma->buflist[i + dma->buf_count] = &entry->buflist[i];
+	}
+
+	dma->buf_count += entry->buf_count;
+	dma->byte_count += byte_count;
+
+	DRM_DEBUG( "dma->buf_count : %d\n", dma->buf_count );
+	DRM_DEBUG( "entry->buf_count : %d\n", entry->buf_count );
+
+	up( &dev->struct_sem );
+
+	request.count = entry->buf_count;
+	request.size = size;
+
+	if ( copy_to_user( argp, &request, sizeof(request) ) )
+		return -EFAULT;
+
+	dma->flags = _DRM_DMA_USE_SG;
+
+	atomic_dec( &dev->buf_alloc );
+	return 0;
+}
+
+/**
+ * Add buffers for DMA transfers (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_desc_t request.
+ * \return zero on success or a negative number on failure.
+ *
+ * According with the memory type specified in drm_buf_desc::flags and the
+ * build options, it dispatches the call either to addbufs_agp(),
+ * addbufs_sg() or addbufs_pci() for AGP, scatter-gather or consistent
+ * PCI memory respectively.
+ */
+int drm_addbufs( struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg )
+{
+	drm_buf_desc_t request;
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	
+	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+		return -EINVAL;
+
+	if ( copy_from_user( &request, (drm_buf_desc_t __user *)arg,
+			     sizeof(request) ) )
+		return -EFAULT;
+
+#if __OS_HAS_AGP
+	if ( request.flags & _DRM_AGP_BUFFER )
+		return drm_addbufs_agp( inode, filp, cmd, arg );
+	else
+#endif
+	if ( request.flags & _DRM_SG_BUFFER )
+		return drm_addbufs_sg( inode, filp, cmd, arg );
+	else
+		return drm_addbufs_pci( inode, filp, cmd, arg );
+}
+
+
+/**
+ * Get information about the buffer mappings.
+ *
+ * This was originally mean for debugging purposes, or by a sophisticated
+ * client library to determine how best to use the available buffers (e.g.,
+ * large buffers can be used for image transfer).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_info structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Increments drm_device::buf_use while holding the drm_device::count_lock
+ * lock, preventing of allocating more buffers after this call. Information
+ * about each requested buffer is then copied into user space.
+ */
+int drm_infobufs( struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_info_t request;
+	drm_buf_info_t __user *argp = (void __user *)arg;
+	int i;
+	int count;
+
+	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+		return -EINVAL;
+
+	if ( !dma ) return -EINVAL;
+
+	spin_lock( &dev->count_lock );
+	if ( atomic_read( &dev->buf_alloc ) ) {
+		spin_unlock( &dev->count_lock );
+		return -EBUSY;
+	}
+	++dev->buf_use;		/* Can't allocate more after this call */
+	spin_unlock( &dev->count_lock );
+
+	if ( copy_from_user( &request, argp, sizeof(request) ) )
+		return -EFAULT;
+
+	for ( i = 0, count = 0 ; i < DRM_MAX_ORDER + 1 ; i++ ) {
+		if ( dma->bufs[i].buf_count ) ++count;
+	}
+
+	DRM_DEBUG( "count = %d\n", count );
+
+	if ( request.count >= count ) {
+		for ( i = 0, count = 0 ; i < DRM_MAX_ORDER + 1 ; i++ ) {
+			if ( dma->bufs[i].buf_count ) {
+				drm_buf_desc_t __user *to = &request.list[count];
+				drm_buf_entry_t *from = &dma->bufs[i];
+				drm_freelist_t *list = &dma->bufs[i].freelist;
+				if ( copy_to_user( &to->count,
+						   &from->buf_count,
+						   sizeof(from->buf_count) ) ||
+				     copy_to_user( &to->size,
+						   &from->buf_size,
+						   sizeof(from->buf_size) ) ||
+				     copy_to_user( &to->low_mark,
+						   &list->low_mark,
+						   sizeof(list->low_mark) ) ||
+				     copy_to_user( &to->high_mark,
+						   &list->high_mark,
+						   sizeof(list->high_mark) ) )
+					return -EFAULT;
+
+				DRM_DEBUG( "%d %d %d %d %d\n",
+					   i,
+					   dma->bufs[i].buf_count,
+					   dma->bufs[i].buf_size,
+					   dma->bufs[i].freelist.low_mark,
+					   dma->bufs[i].freelist.high_mark );
+				++count;
+			}
+		}
+	}
+	request.count = count;
+
+	if ( copy_to_user( argp, &request, sizeof(request) ) )
+		return -EFAULT;
+
+	return 0;
+}
+
+/**
+ * Specifies a low and high water mark for buffer allocation
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg a pointer to a drm_buf_desc structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies that the size order is bounded between the admissible orders and
+ * updates the respective drm_device_dma::bufs entry low and high water mark.
+ *
+ * \note This ioctl is deprecated and mostly never used.
+ */
+int drm_markbufs( struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_desc_t request;
+	int order;
+	drm_buf_entry_t *entry;
+
+	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+		return -EINVAL;
+
+	if ( !dma ) return -EINVAL;
+
+	if ( copy_from_user( &request,
+			     (drm_buf_desc_t __user *)arg,
+			     sizeof(request) ) )
+		return -EFAULT;
+
+	DRM_DEBUG( "%d, %d, %d\n",
+		   request.size, request.low_mark, request.high_mark );
+	order = drm_order( request.size );
+	if ( order < DRM_MIN_ORDER || order > DRM_MAX_ORDER ) return -EINVAL;
+	entry = &dma->bufs[order];
+
+	if ( request.low_mark < 0 || request.low_mark > entry->buf_count )
+		return -EINVAL;
+	if ( request.high_mark < 0 || request.high_mark > entry->buf_count )
+		return -EINVAL;
+
+	entry->freelist.low_mark  = request.low_mark;
+	entry->freelist.high_mark = request.high_mark;
+
+	return 0;
+}
+
+/**
+ * Unreserve the buffers in list, previously reserved using drmDMA. 
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_free structure.
+ * \return zero on success or a negative number on failure.
+ * 
+ * Calls free_buffer() for each used buffer.
+ * This function is primarily used for debugging.
+ */
+int drm_freebufs( struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_free_t request;
+	int i;
+	int idx;
+	drm_buf_t *buf;
+
+	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+		return -EINVAL;
+
+	if ( !dma ) return -EINVAL;
+
+	if ( copy_from_user( &request,
+			     (drm_buf_free_t __user *)arg,
+			     sizeof(request) ) )
+		return -EFAULT;
+
+	DRM_DEBUG( "%d\n", request.count );
+	for ( i = 0 ; i < request.count ; i++ ) {
+		if ( copy_from_user( &idx,
+				     &request.list[i],
+				     sizeof(idx) ) )
+			return -EFAULT;
+		if ( idx < 0 || idx >= dma->buf_count ) {
+			DRM_ERROR( "Index %d (of %d max)\n",
+				   idx, dma->buf_count - 1 );
+			return -EINVAL;
+		}
+		buf = dma->buflist[idx];
+		if ( buf->filp != filp ) {
+			DRM_ERROR( "Process %d freeing buffer not owned\n",
+				   current->pid );
+			return -EINVAL;
+		}
+		drm_free_buffer( dev, buf );
+	}
+
+	return 0;
+}
+
+/**
+ * Maps all of the DMA buffers into client-virtual space (ioctl).
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg pointer to a drm_buf_map structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Maps the AGP or SG buffer region with do_mmap(), and copies information
+ * about each buffer into user space. The PCI buffers are already mapped on the
+ * addbufs_pci() call.
+ */
+int drm_mapbufs( struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_map_t __user *argp = (void __user *)arg;
+	int retcode = 0;
+	const int zero = 0;
+	unsigned long virtual;
+	unsigned long address;
+	drm_buf_map_t request;
+	int i;
+
+	if (!drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+		return -EINVAL;
+
+	if ( !dma ) return -EINVAL;
+
+	spin_lock( &dev->count_lock );
+	if ( atomic_read( &dev->buf_alloc ) ) {
+		spin_unlock( &dev->count_lock );
+		return -EBUSY;
+	}
+	dev->buf_use++;		/* Can't allocate more after this call */
+	spin_unlock( &dev->count_lock );
+
+	if ( copy_from_user( &request, argp, sizeof(request) ) )
+		return -EFAULT;
+
+	if ( request.count >= dma->buf_count ) {
+		if ((drm_core_has_AGP(dev) && (dma->flags & _DRM_DMA_USE_AGP)) ||
+		    (drm_core_check_feature(dev, DRIVER_SG) && (dma->flags & _DRM_DMA_USE_SG)) ) {
+			drm_map_t *map = dev->agp_buffer_map;
+
+			if ( !map ) {
+				retcode = -EINVAL;
+				goto done;
+			}
+
+#if LINUX_VERSION_CODE <= 0x020402
+			down( &current->mm->mmap_sem );
+#else
+			down_write( &current->mm->mmap_sem );
+#endif
+			virtual = do_mmap( filp, 0, map->size,
+					   PROT_READ | PROT_WRITE,
+					   MAP_SHARED,
+					   (unsigned long)map->offset );
+#if LINUX_VERSION_CODE <= 0x020402
+			up( &current->mm->mmap_sem );
+#else
+			up_write( &current->mm->mmap_sem );
+#endif
+		} else {
+#if LINUX_VERSION_CODE <= 0x020402
+			down( &current->mm->mmap_sem );
+#else
+			down_write( &current->mm->mmap_sem );
+#endif
+			virtual = do_mmap( filp, 0, dma->byte_count,
+					   PROT_READ | PROT_WRITE,
+					   MAP_SHARED, 0 );
+#if LINUX_VERSION_CODE <= 0x020402
+			up( &current->mm->mmap_sem );
+#else
+			up_write( &current->mm->mmap_sem );
+#endif
+		}
+		if ( virtual > -1024UL ) {
+			/* Real error */
+			retcode = (signed long)virtual;
+			goto done;
+		}
+		request.virtual = (void __user *)virtual;
+
+		for ( i = 0 ; i < dma->buf_count ; i++ ) {
+			if ( copy_to_user( &request.list[i].idx,
+					   &dma->buflist[i]->idx,
+					   sizeof(request.list[0].idx) ) ) {
+				retcode = -EFAULT;
+				goto done;
+			}
+			if ( copy_to_user( &request.list[i].total,
+					   &dma->buflist[i]->total,
+					   sizeof(request.list[0].total) ) ) {
+				retcode = -EFAULT;
+				goto done;
+			}
+			if ( copy_to_user( &request.list[i].used,
+					   &zero,
+					   sizeof(zero) ) ) {
+				retcode = -EFAULT;
+				goto done;
+			}
+			address = virtual + dma->buflist[i]->offset; /* *** */
+			if ( copy_to_user( &request.list[i].address,
+					   &address,
+					   sizeof(address) ) ) {
+				retcode = -EFAULT;
+				goto done;
+			}
+		}
+	}
+ done:
+	request.count = dma->buf_count;
+	DRM_DEBUG( "%d buffers, retcode = %d\n", request.count, retcode );
+
+	if ( copy_to_user( argp, &request, sizeof(request) ) )
+		return -EFAULT;
+
+	return retcode;
+}
+
diff --git a/drivers/char/drm/drm_context.c b/drivers/char/drm/drm_context.c
new file mode 100644
index 0000000..f15c86c
--- /dev/null
+++ b/drivers/char/drm/drm_context.c
@@ -0,0 +1,578 @@
+/**
+ * \file drm_context.h 
+ * IOCTLs for generic contexts
+ * 
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Fri Nov 24 18:31:37 2000 by gareth@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * ChangeLog:
+ *  2001-11-16	Torsten Duwe <duwe@caldera.de>
+ *		added context constructor/destructor hooks,
+ *		needed by SiS driver's memory management.
+ */
+
+#include "drmP.h"
+
+/******************************************************************/
+/** \name Context bitmap support */
+/*@{*/
+
+/**
+ * Free a handle from the context bitmap.
+ *
+ * \param dev DRM device.
+ * \param ctx_handle context handle.
+ *
+ * Clears the bit specified by \p ctx_handle in drm_device::ctx_bitmap and the entry
+ * in drm_device::context_sareas, while holding the drm_device::struct_sem
+ * lock.
+ */
+void drm_ctxbitmap_free( drm_device_t *dev, int ctx_handle )
+{
+	if ( ctx_handle < 0 ) goto failed;
+	if ( !dev->ctx_bitmap ) goto failed;
+
+	if ( ctx_handle < DRM_MAX_CTXBITMAP ) {
+		down(&dev->struct_sem);
+		clear_bit( ctx_handle, dev->ctx_bitmap );
+		dev->context_sareas[ctx_handle] = NULL;
+		up(&dev->struct_sem);
+		return;
+	}
+failed:
+       	DRM_ERROR( "Attempt to free invalid context handle: %d\n",
+		   ctx_handle );
+       	return;
+}
+
+/** 
+ * Context bitmap allocation.
+ *
+ * \param dev DRM device.
+ * \return (non-negative) context handle on success or a negative number on failure.
+ *
+ * Find the first zero bit in drm_device::ctx_bitmap and (re)allocates
+ * drm_device::context_sareas to accommodate the new entry while holding the
+ * drm_device::struct_sem lock.
+ */
+int drm_ctxbitmap_next( drm_device_t *dev )
+{
+	int bit;
+
+	if(!dev->ctx_bitmap) return -1;
+
+	down(&dev->struct_sem);
+	bit = find_first_zero_bit( dev->ctx_bitmap, DRM_MAX_CTXBITMAP );
+	if ( bit < DRM_MAX_CTXBITMAP ) {
+		set_bit( bit, dev->ctx_bitmap );
+	   	DRM_DEBUG( "drm_ctxbitmap_next bit : %d\n", bit );
+		if((bit+1) > dev->max_context) {
+			dev->max_context = (bit+1);
+			if(dev->context_sareas) {
+				drm_map_t **ctx_sareas;
+
+				ctx_sareas = drm_realloc(dev->context_sareas,
+						(dev->max_context - 1) * 
+						sizeof(*dev->context_sareas),
+						dev->max_context * 
+						sizeof(*dev->context_sareas),
+						DRM_MEM_MAPS);
+				if(!ctx_sareas) {
+					clear_bit(bit, dev->ctx_bitmap);
+					up(&dev->struct_sem);
+					return -1;
+				}
+				dev->context_sareas = ctx_sareas;
+				dev->context_sareas[bit] = NULL;
+			} else {
+				/* max_context == 1 at this point */
+				dev->context_sareas = drm_alloc(
+						dev->max_context * 
+						sizeof(*dev->context_sareas),
+						DRM_MEM_MAPS);
+				if(!dev->context_sareas) {
+					clear_bit(bit, dev->ctx_bitmap);
+					up(&dev->struct_sem);
+					return -1;
+				}
+				dev->context_sareas[bit] = NULL;
+			}
+		}
+		up(&dev->struct_sem);
+		return bit;
+	}
+	up(&dev->struct_sem);
+	return -1;
+}
+
+/**
+ * Context bitmap initialization.
+ *
+ * \param dev DRM device.
+ *
+ * Allocates and initialize drm_device::ctx_bitmap and drm_device::context_sareas, while holding
+ * the drm_device::struct_sem lock.
+ */
+int drm_ctxbitmap_init( drm_device_t *dev )
+{
+	int i;
+   	int temp;
+
+	down(&dev->struct_sem);
+	dev->ctx_bitmap = (unsigned long *) drm_alloc( PAGE_SIZE,
+							DRM_MEM_CTXBITMAP );
+	if ( dev->ctx_bitmap == NULL ) {
+		up(&dev->struct_sem);
+		return -ENOMEM;
+	}
+	memset( (void *)dev->ctx_bitmap, 0, PAGE_SIZE );
+	dev->context_sareas = NULL;
+	dev->max_context = -1;
+	up(&dev->struct_sem);
+
+	for ( i = 0 ; i < DRM_RESERVED_CONTEXTS ; i++ ) {
+		temp = drm_ctxbitmap_next( dev );
+	   	DRM_DEBUG( "drm_ctxbitmap_init : %d\n", temp );
+	}
+
+	return 0;
+}
+
+/**
+ * Context bitmap cleanup.
+ *
+ * \param dev DRM device.
+ *
+ * Frees drm_device::ctx_bitmap and drm_device::context_sareas, while holding
+ * the drm_device::struct_sem lock.
+ */
+void drm_ctxbitmap_cleanup( drm_device_t *dev )
+{
+	down(&dev->struct_sem);
+	if( dev->context_sareas ) drm_free( dev->context_sareas,
+					     sizeof(*dev->context_sareas) * 
+					     dev->max_context,
+					     DRM_MEM_MAPS );
+	drm_free( (void *)dev->ctx_bitmap, PAGE_SIZE, DRM_MEM_CTXBITMAP );
+	up(&dev->struct_sem);
+}
+
+/*@}*/
+
+/******************************************************************/
+/** \name Per Context SAREA Support */
+/*@{*/
+
+/**
+ * Get per-context SAREA.
+ * 
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx_priv_map structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Gets the map from drm_device::context_sareas with the handle specified and
+ * returns its handle.
+ */
+int drm_getsareactx(struct inode *inode, struct file *filp,
+		     unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->head->dev;
+	drm_ctx_priv_map_t __user *argp = (void __user *)arg;
+	drm_ctx_priv_map_t request;
+	drm_map_t *map;
+
+	if (copy_from_user(&request, argp, sizeof(request)))
+		return -EFAULT;
+
+	down(&dev->struct_sem);
+	if (dev->max_context < 0 || request.ctx_id >= (unsigned) dev->max_context) {
+		up(&dev->struct_sem);
+		return -EINVAL;
+	}
+
+	map = dev->context_sareas[request.ctx_id];
+	up(&dev->struct_sem);
+
+	request.handle = map->handle;
+	if (copy_to_user(argp, &request, sizeof(request)))
+		return -EFAULT;
+	return 0;
+}
+
+/**
+ * Set per-context SAREA.
+ * 
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx_priv_map structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Searches the mapping specified in \p arg and update the entry in
+ * drm_device::context_sareas with it.
+ */
+int drm_setsareactx(struct inode *inode, struct file *filp,
+		     unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->head->dev;
+	drm_ctx_priv_map_t request;
+	drm_map_t *map = NULL;
+	drm_map_list_t *r_list = NULL;
+	struct list_head *list;
+
+	if (copy_from_user(&request,
+			   (drm_ctx_priv_map_t __user *)arg,
+			   sizeof(request)))
+		return -EFAULT;
+
+	down(&dev->struct_sem);
+	list_for_each(list, &dev->maplist->head) {
+		r_list = list_entry(list, drm_map_list_t, head);
+		if(r_list->map &&
+		   r_list->map->handle == request.handle)
+			goto found;
+	}
+bad:
+	up(&dev->struct_sem);
+	return -EINVAL;
+
+found:
+	map = r_list->map;
+	if (!map) goto bad;
+	if (dev->max_context < 0)
+		goto bad;
+	if (request.ctx_id >= (unsigned) dev->max_context)
+		goto bad;
+	dev->context_sareas[request.ctx_id] = map;
+	up(&dev->struct_sem);
+	return 0;
+}
+
+/*@}*/
+
+/******************************************************************/
+/** \name The actual DRM context handling routines */
+/*@{*/
+
+/**
+ * Switch context.
+ *
+ * \param dev DRM device.
+ * \param old old context handle.
+ * \param new new context handle.
+ * \return zero on success or a negative number on failure.
+ *
+ * Attempt to set drm_device::context_flag.
+ */
+int drm_context_switch( drm_device_t *dev, int old, int new )
+{
+        if ( test_and_set_bit( 0, &dev->context_flag ) ) {
+                DRM_ERROR( "Reentering -- FIXME\n" );
+                return -EBUSY;
+        }
+
+
+        DRM_DEBUG( "Context switch from %d to %d\n", old, new );
+
+        if ( new == dev->last_context ) {
+                clear_bit( 0, &dev->context_flag );
+                return 0;
+        }
+
+        return 0;
+}
+
+/**
+ * Complete context switch.
+ *
+ * \param dev DRM device.
+ * \param new new context handle.
+ * \return zero on success or a negative number on failure.
+ *
+ * Updates drm_device::last_context and drm_device::last_switch. Verifies the
+ * hardware lock is held, clears the drm_device::context_flag and wakes up
+ * drm_device::context_wait.
+ */
+int drm_context_switch_complete( drm_device_t *dev, int new )
+{
+        dev->last_context = new;  /* PRE/POST: This is the _only_ writer. */
+        dev->last_switch  = jiffies;
+
+        if ( !_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock) ) {
+                DRM_ERROR( "Lock isn't held after context switch\n" );
+        }
+
+				/* If a context switch is ever initiated
+                                   when the kernel holds the lock, release
+                                   that lock here. */
+        clear_bit( 0, &dev->context_flag );
+        wake_up( &dev->context_wait );
+
+        return 0;
+}
+
+/**
+ * Reserve contexts.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx_res structure.
+ * \return zero on success or a negative number on failure.
+ */
+int drm_resctx( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	drm_ctx_res_t res;
+	drm_ctx_t __user *argp = (void __user *)arg;
+	drm_ctx_t ctx;
+	int i;
+
+	if ( copy_from_user( &res, argp, sizeof(res) ) )
+		return -EFAULT;
+
+	if ( res.count >= DRM_RESERVED_CONTEXTS ) {
+		memset( &ctx, 0, sizeof(ctx) );
+		for ( i = 0 ; i < DRM_RESERVED_CONTEXTS ; i++ ) {
+			ctx.handle = i;
+			if ( copy_to_user( &res.contexts[i],
+					   &i, sizeof(i) ) )
+				return -EFAULT;
+		}
+	}
+	res.count = DRM_RESERVED_CONTEXTS;
+
+	if ( copy_to_user( argp, &res, sizeof(res) ) )
+		return -EFAULT;
+	return 0;
+}
+
+/**
+ * Add context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Get a new handle for the context and copy to userspace.
+ */
+int drm_addctx( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_ctx_list_t * ctx_entry;
+	drm_ctx_t __user *argp = (void __user *)arg;
+	drm_ctx_t ctx;
+
+	if ( copy_from_user( &ctx, argp, sizeof(ctx) ) )
+		return -EFAULT;
+
+	ctx.handle = drm_ctxbitmap_next( dev );
+	if ( ctx.handle == DRM_KERNEL_CONTEXT ) {
+				/* Skip kernel's context and get a new one. */
+		ctx.handle = drm_ctxbitmap_next( dev );
+	}
+	DRM_DEBUG( "%d\n", ctx.handle );
+	if ( ctx.handle == -1 ) {
+		DRM_DEBUG( "Not enough free contexts.\n" );
+				/* Should this return -EBUSY instead? */
+		return -ENOMEM;
+	}
+
+	if ( ctx.handle != DRM_KERNEL_CONTEXT )
+	{
+		if (dev->driver->context_ctor)
+			dev->driver->context_ctor(dev, ctx.handle);
+	}
+
+	ctx_entry = drm_alloc( sizeof(*ctx_entry), DRM_MEM_CTXLIST );
+	if ( !ctx_entry ) {
+		DRM_DEBUG("out of memory\n");
+		return -ENOMEM;
+	}
+
+	INIT_LIST_HEAD( &ctx_entry->head );
+	ctx_entry->handle = ctx.handle;
+	ctx_entry->tag = priv;
+
+	down( &dev->ctxlist_sem );
+	list_add( &ctx_entry->head, &dev->ctxlist->head );
+	++dev->ctx_count;
+	up( &dev->ctxlist_sem );
+
+	if ( copy_to_user( argp, &ctx, sizeof(ctx) ) )
+		return -EFAULT;
+	return 0;
+}
+
+int drm_modctx( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	/* This does nothing */
+	return 0;
+}
+
+/**
+ * Get context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ */
+int drm_getctx( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	drm_ctx_t __user *argp = (void __user *)arg;
+	drm_ctx_t ctx;
+
+	if ( copy_from_user( &ctx, argp, sizeof(ctx) ) )
+		return -EFAULT;
+
+	/* This is 0, because we don't handle any context flags */
+	ctx.flags = 0;
+
+	if ( copy_to_user( argp, &ctx, sizeof(ctx) ) )
+		return -EFAULT;
+	return 0;
+}
+
+/**
+ * Switch context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Calls context_switch().
+ */
+int drm_switchctx( struct inode *inode, struct file *filp,
+		    unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_ctx_t ctx;
+
+	if ( copy_from_user( &ctx, (drm_ctx_t __user *)arg, sizeof(ctx) ) )
+		return -EFAULT;
+
+	DRM_DEBUG( "%d\n", ctx.handle );
+	return drm_context_switch( dev, dev->last_context, ctx.handle );
+}
+
+/**
+ * New context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Calls context_switch_complete().
+ */
+int drm_newctx( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_ctx_t ctx;
+
+	if ( copy_from_user( &ctx, (drm_ctx_t __user *)arg, sizeof(ctx) ) )
+		return -EFAULT;
+
+	DRM_DEBUG( "%d\n", ctx.handle );
+	drm_context_switch_complete( dev, ctx.handle );
+
+	return 0;
+}
+
+/**
+ * Remove context.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument pointing to a drm_ctx structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * If not the special kernel context, calls ctxbitmap_free() to free the specified context.
+ */
+int drm_rmctx( struct inode *inode, struct file *filp,
+		unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_ctx_t ctx;
+
+	if ( copy_from_user( &ctx, (drm_ctx_t __user *)arg, sizeof(ctx) ) )
+		return -EFAULT;
+
+	DRM_DEBUG( "%d\n", ctx.handle );
+	if ( ctx.handle == DRM_KERNEL_CONTEXT + 1 ) {
+		priv->remove_auth_on_close = 1;
+	}
+	if ( ctx.handle != DRM_KERNEL_CONTEXT ) {
+		if (dev->driver->context_dtor)
+			dev->driver->context_dtor(dev, ctx.handle);
+		drm_ctxbitmap_free( dev, ctx.handle );
+	}
+
+	down( &dev->ctxlist_sem );
+	if ( !list_empty( &dev->ctxlist->head ) ) {
+		drm_ctx_list_t *pos, *n;
+
+		list_for_each_entry_safe( pos, n, &dev->ctxlist->head, head ) {
+			if ( pos->handle == ctx.handle ) {
+				list_del( &pos->head );
+				drm_free( pos, sizeof(*pos), DRM_MEM_CTXLIST );
+				--dev->ctx_count;
+			}
+		}
+	}
+	up( &dev->ctxlist_sem );
+
+	return 0;
+}
+
+/*@}*/
+
diff --git a/drivers/char/drm/drm_core.h b/drivers/char/drm/drm_core.h
new file mode 100644
index 0000000..cc97bb9
--- /dev/null
+++ b/drivers/char/drm/drm_core.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2004 Jon Smirl <jonsmirl@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sub license,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
+ * VIA, S3 GRAPHICS, AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#define CORE_AUTHOR		"Gareth Hughes, Leif Delgass, José Fonseca, Jon Smirl"
+
+#define CORE_NAME		"drm"
+#define CORE_DESC		"DRM shared core routines"
+#define CORE_DATE		"20040925"
+
+#define DRM_IF_MAJOR	1
+#define DRM_IF_MINOR	2
+
+#define CORE_MAJOR	1
+#define CORE_MINOR	0
+#define CORE_PATCHLEVEL 0
diff --git a/drivers/char/drm/drm_dma.c b/drivers/char/drm/drm_dma.c
new file mode 100644
index 0000000..4a28c05
--- /dev/null
+++ b/drivers/char/drm/drm_dma.c
@@ -0,0 +1,180 @@
+/**
+ * \file drm_dma.h 
+ * DMA IOCTL and function support
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/**
+ * Initialize the DMA data.
+ * 
+ * \param dev DRM device.
+ * \return zero on success or a negative value on failure.
+ *
+ * Allocate and initialize a drm_device_dma structure.
+ */
+int drm_dma_setup( drm_device_t *dev )
+{
+	int i;
+
+	dev->dma = drm_alloc( sizeof(*dev->dma), DRM_MEM_DRIVER );
+	if ( !dev->dma )
+		return -ENOMEM;
+
+	memset( dev->dma, 0, sizeof(*dev->dma) );
+
+	for ( i = 0 ; i <= DRM_MAX_ORDER ; i++ )
+		memset(&dev->dma->bufs[i], 0, sizeof(dev->dma->bufs[0]));
+
+	return 0;
+}
+
+/**
+ * Cleanup the DMA resources.
+ *
+ * \param dev DRM device.
+ *
+ * Free all pages associated with DMA buffers, the buffers and pages lists, and
+ * finally the the drm_device::dma structure itself.
+ */
+void drm_dma_takedown(drm_device_t *dev)
+{
+	drm_device_dma_t  *dma = dev->dma;
+	int		  i, j;
+
+	if (!dma) return;
+
+				/* Clear dma buffers */
+	for (i = 0; i <= DRM_MAX_ORDER; i++) {
+		if (dma->bufs[i].seg_count) {
+			DRM_DEBUG("order %d: buf_count = %d,"
+				  " seg_count = %d\n",
+				  i,
+				  dma->bufs[i].buf_count,
+				  dma->bufs[i].seg_count);
+			for (j = 0; j < dma->bufs[i].seg_count; j++) {
+				if (dma->bufs[i].seglist[j]) {
+					drm_free_pages(dma->bufs[i].seglist[j],
+							dma->bufs[i].page_order,
+							DRM_MEM_DMA);
+				}
+			}
+			drm_free(dma->bufs[i].seglist,
+				  dma->bufs[i].seg_count
+				  * sizeof(*dma->bufs[0].seglist),
+				  DRM_MEM_SEGS);
+		}
+	   	if (dma->bufs[i].buf_count) {
+		   	for (j = 0; j < dma->bufs[i].buf_count; j++) {
+				if (dma->bufs[i].buflist[j].dev_private) {
+					drm_free(dma->bufs[i].buflist[j].dev_private,
+						  dma->bufs[i].buflist[j].dev_priv_size,
+						  DRM_MEM_BUFS);
+				}
+			}
+		   	drm_free(dma->bufs[i].buflist,
+				  dma->bufs[i].buf_count *
+				  sizeof(*dma->bufs[0].buflist),
+				  DRM_MEM_BUFS);
+		}
+	}
+
+	if (dma->buflist) {
+		drm_free(dma->buflist,
+			  dma->buf_count * sizeof(*dma->buflist),
+			  DRM_MEM_BUFS);
+	}
+
+	if (dma->pagelist) {
+		drm_free(dma->pagelist,
+			  dma->page_count * sizeof(*dma->pagelist),
+			  DRM_MEM_PAGES);
+	}
+	drm_free(dev->dma, sizeof(*dev->dma), DRM_MEM_DRIVER);
+	dev->dma = NULL;
+}
+
+
+/**
+ * Free a buffer.
+ *
+ * \param dev DRM device.
+ * \param buf buffer to free.
+ * 
+ * Resets the fields of \p buf.
+ */
+void drm_free_buffer(drm_device_t *dev, drm_buf_t *buf)
+{
+	if (!buf) return;
+
+	buf->waiting  = 0;
+	buf->pending  = 0;
+	buf->filp     = NULL;
+	buf->used     = 0;
+
+	if (drm_core_check_feature(dev, DRIVER_DMA_QUEUE) && waitqueue_active(&buf->dma_wait)) {
+		wake_up_interruptible(&buf->dma_wait);
+	}
+}
+
+/**
+ * Reclaim the buffers.
+ *
+ * \param filp file pointer.
+ *
+ * Frees each buffer associated with \p filp not already on the hardware.
+ */
+void drm_core_reclaim_buffers(drm_device_t *dev, struct file *filp)
+{
+	drm_device_dma_t *dma = dev->dma;
+	int		 i;
+
+	if (!dma) return;
+	for (i = 0; i < dma->buf_count; i++) {
+		if (dma->buflist[i]->filp == filp) {
+			switch (dma->buflist[i]->list) {
+			case DRM_LIST_NONE:
+				drm_free_buffer(dev, dma->buflist[i]);
+				break;
+			case DRM_LIST_WAIT:
+				dma->buflist[i]->list = DRM_LIST_RECLAIM;
+				break;
+			default:
+				/* Buffer already on hardware. */
+				break;
+			}
+		}
+	}
+}
+EXPORT_SYMBOL(drm_core_reclaim_buffers);
+
diff --git a/drivers/char/drm/drm_drawable.c b/drivers/char/drm/drm_drawable.c
new file mode 100644
index 0000000..e8e8e42
--- /dev/null
+++ b/drivers/char/drm/drm_drawable.c
@@ -0,0 +1,56 @@
+/**
+ * \file drm_drawable.h 
+ * IOCTLs for drawables
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Tue Feb  2 08:37:54 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/** No-op. */
+int drm_adddraw(struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg)
+{
+	drm_draw_t draw;
+
+	draw.handle = 0;	/* NOOP */
+	DRM_DEBUG("%d\n", draw.handle);
+	if (copy_to_user((drm_draw_t __user *)arg, &draw, sizeof(draw)))
+		return -EFAULT;
+	return 0;
+}
+
+/** No-op. */
+int drm_rmdraw(struct inode *inode, struct file *filp,
+		unsigned int cmd, unsigned long arg)
+{
+	return 0;		/* NOOP */
+}
diff --git a/drivers/char/drm/drm_drv.c b/drivers/char/drm/drm_drv.c
new file mode 100644
index 0000000..1e37ed0
--- /dev/null
+++ b/drivers/char/drm/drm_drv.c
@@ -0,0 +1,531 @@
+/**
+ * \file drm_drv.h 
+ * Generic driver template
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ *
+ * To use this template, you must at least define the following (samples
+ * given for the MGA driver):
+ *
+ * \code
+ * #define DRIVER_AUTHOR	"VA Linux Systems, Inc."
+ *
+ * #define DRIVER_NAME		"mga"
+ * #define DRIVER_DESC		"Matrox G200/G400"
+ * #define DRIVER_DATE		"20001127"
+ *
+ * #define DRIVER_IOCTL_COUNT	DRM_ARRAY_SIZE( mga_ioctls )
+ *
+ * #define drm_x		mga_##x
+ * \endcode
+ */
+
+/*
+ * Created: Thu Nov 23 03:10:50 2000 by gareth@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#include "drm_core.h"
+
+/** Ioctl table */
+drm_ioctl_desc_t		  drm_ioctls[] = {
+	[DRM_IOCTL_NR(DRM_IOCTL_VERSION)]       = { drm_version,     0, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_GET_UNIQUE)]    = { drm_getunique,   0, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_GET_MAGIC)]     = { drm_getmagic,    0, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_IRQ_BUSID)]     = { drm_irq_by_busid, 0, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_GET_MAP)]       = { drm_getmap,      0, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_GET_CLIENT)]    = { drm_getclient,   0, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_GET_STATS)]     = { drm_getstats,    0, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_SET_VERSION)]   = { drm_setversion,  0, 1 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_SET_UNIQUE)]    = { drm_setunique,   1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_BLOCK)]         = { drm_noop,        1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_UNBLOCK)]       = { drm_noop,        1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_AUTH_MAGIC)]    = { drm_authmagic,   1, 1 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_ADD_MAP)]       = { drm_addmap,      1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_RM_MAP)]        = { drm_rmmap,       1, 0 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_SET_SAREA_CTX)] = { drm_setsareactx, 1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_GET_SAREA_CTX)] = { drm_getsareactx, 1, 0 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_ADD_CTX)]       = { drm_addctx,      1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_RM_CTX)]        = { drm_rmctx,       1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_MOD_CTX)]       = { drm_modctx,      1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_GET_CTX)]       = { drm_getctx,      1, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_SWITCH_CTX)]    = { drm_switchctx,   1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_NEW_CTX)]       = { drm_newctx,      1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_RES_CTX)]       = { drm_resctx,      1, 0 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_ADD_DRAW)]      = { drm_adddraw,     1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_RM_DRAW)]       = { drm_rmdraw,      1, 1 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_LOCK)]	        = { drm_lock,        1, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_UNLOCK)]        = { drm_unlock,      1, 0 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_FINISH)]        = { drm_noop,      1, 0 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_ADD_BUFS)]      = { drm_addbufs,     1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_MARK_BUFS)]     = { drm_markbufs,    1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_INFO_BUFS)]     = { drm_infobufs,    1, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_MAP_BUFS)]      = { drm_mapbufs,     1, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_FREE_BUFS)]     = { drm_freebufs,    1, 0 },
+	/* The DRM_IOCTL_DMA ioctl should be defined by the driver. */
+
+	[DRM_IOCTL_NR(DRM_IOCTL_CONTROL)]       = { drm_control,     1, 1 },
+
+#if __OS_HAS_AGP
+	[DRM_IOCTL_NR(DRM_IOCTL_AGP_ACQUIRE)]   = { drm_agp_acquire, 1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_AGP_RELEASE)]   = { drm_agp_release, 1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_AGP_ENABLE)]    = { drm_agp_enable,  1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_AGP_INFO)]      = { drm_agp_info,    1, 0 },
+	[DRM_IOCTL_NR(DRM_IOCTL_AGP_ALLOC)]     = { drm_agp_alloc,   1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_AGP_FREE)]      = { drm_agp_free,    1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_AGP_BIND)]      = { drm_agp_bind,    1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_AGP_UNBIND)]    = { drm_agp_unbind,  1, 1 },
+#endif
+
+	[DRM_IOCTL_NR(DRM_IOCTL_SG_ALLOC)]      = { drm_sg_alloc,    1, 1 },
+	[DRM_IOCTL_NR(DRM_IOCTL_SG_FREE)]       = { drm_sg_free,     1, 1 },
+
+	[DRM_IOCTL_NR(DRM_IOCTL_WAIT_VBLANK)]   = { drm_wait_vblank, 0, 0 },
+};
+
+#define DRIVER_IOCTL_COUNT	DRM_ARRAY_SIZE( drm_ioctls )
+
+/**
+ * Take down the DRM device.
+ *
+ * \param dev DRM device structure.
+ *
+ * Frees every resource in \p dev.
+ *
+ * \sa drm_device and setup().
+ */
+int drm_takedown( drm_device_t *dev )
+{
+	drm_magic_entry_t *pt, *next;
+	drm_map_t *map;
+	drm_map_list_t *r_list;
+	struct list_head *list, *list_next;
+	drm_vma_entry_t *vma, *vma_next;
+	int i;
+
+	DRM_DEBUG( "\n" );
+
+	if (dev->driver->pretakedown)
+	  dev->driver->pretakedown(dev);
+
+	if (dev->unique) {
+		drm_free(dev->unique, strlen(dev->unique) + 1, DRM_MEM_DRIVER);
+		dev->unique = NULL;
+		dev->unique_len = 0;
+	}
+
+	if ( dev->irq_enabled ) drm_irq_uninstall( dev );
+
+	down( &dev->struct_sem );
+	del_timer( &dev->timer );
+
+				/* Clear pid list */
+	for ( i = 0 ; i < DRM_HASH_SIZE ; i++ ) {
+		for ( pt = dev->magiclist[i].head ; pt ; pt = next ) {
+			next = pt->next;
+			drm_free( pt, sizeof(*pt), DRM_MEM_MAGIC );
+		}
+		dev->magiclist[i].head = dev->magiclist[i].tail = NULL;
+	}
+
+				/* Clear AGP information */
+	if (drm_core_has_AGP(dev) && dev->agp) {
+		drm_agp_mem_t *entry;
+		drm_agp_mem_t *nexte;
+
+				/* Remove AGP resources, but leave dev->agp
+                                   intact until drv_cleanup is called. */
+		for ( entry = dev->agp->memory ; entry ; entry = nexte ) {
+			nexte = entry->next;
+			if ( entry->bound ) drm_unbind_agp( entry->memory );
+			drm_free_agp( entry->memory, entry->pages );
+			drm_free( entry, sizeof(*entry), DRM_MEM_AGPLISTS );
+		}
+		dev->agp->memory = NULL;
+
+		if ( dev->agp->acquired ) drm_agp_do_release(dev);
+
+		dev->agp->acquired = 0;
+		dev->agp->enabled  = 0;
+	}
+
+				/* Clear vma list (only built for debugging) */
+	if ( dev->vmalist ) {
+		for ( vma = dev->vmalist ; vma ; vma = vma_next ) {
+			vma_next = vma->next;
+			drm_free( vma, sizeof(*vma), DRM_MEM_VMAS );
+		}
+		dev->vmalist = NULL;
+	}
+
+	if( dev->maplist ) {
+		list_for_each_safe( list, list_next, &dev->maplist->head ) {
+			r_list = (drm_map_list_t *)list;
+
+			if ( ( map = r_list->map ) ) {
+				switch ( map->type ) {
+				case _DRM_REGISTERS:
+				case _DRM_FRAME_BUFFER:
+					if (drm_core_has_MTRR(dev)) {
+						if ( map->mtrr >= 0 ) {
+							int retcode;
+							retcode = mtrr_del( map->mtrr,
+									    map->offset,
+									    map->size );
+							DRM_DEBUG( "mtrr_del=%d\n", retcode );
+						}
+					}
+					drm_ioremapfree( map->handle, map->size, dev );
+					break;
+				case _DRM_SHM:
+					vfree(map->handle);
+					break;
+
+				case _DRM_AGP:
+					/* Do nothing here, because this is all
+					 * handled in the AGP/GART driver.
+					 */
+					break;
+				case _DRM_SCATTER_GATHER:
+					/* Handle it */
+					if (drm_core_check_feature(dev, DRIVER_SG) && dev->sg) {
+						drm_sg_cleanup(dev->sg);
+						dev->sg = NULL;
+					}
+					break;
+				}
+				drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+			}
+			list_del( list );
+			drm_free(r_list, sizeof(*r_list), DRM_MEM_MAPS);
+ 		}
+		drm_free(dev->maplist, sizeof(*dev->maplist), DRM_MEM_MAPS);
+		dev->maplist = NULL;
+ 	}
+
+	if (drm_core_check_feature(dev, DRIVER_DMA_QUEUE) && dev->queuelist ) {
+		for ( i = 0 ; i < dev->queue_count ; i++ ) {
+			if ( dev->queuelist[i] ) {
+				drm_free( dev->queuelist[i],
+					  sizeof(*dev->queuelist[0]),
+					  DRM_MEM_QUEUES );
+				dev->queuelist[i] = NULL;
+			}
+		}
+		drm_free( dev->queuelist,
+			  dev->queue_slots * sizeof(*dev->queuelist),
+			  DRM_MEM_QUEUES );
+		dev->queuelist = NULL;
+	}
+	dev->queue_count = 0;
+
+	if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+		drm_dma_takedown( dev );
+
+	if ( dev->lock.hw_lock ) {
+		dev->sigdata.lock = dev->lock.hw_lock = NULL; /* SHM removed */
+		dev->lock.filp = NULL;
+		wake_up_interruptible( &dev->lock.lock_queue );
+	}
+	up( &dev->struct_sem );
+
+	return 0;
+}
+
+
+
+/**
+ * Module initialization. Called via init_module at module load time, or via
+ * linux/init/main.c (this is not currently supported).
+ *
+ * \return zero on success or a negative number on failure.
+ *
+ * Initializes an array of drm_device structures, and attempts to
+ * initialize all available devices, using consecutive minors, registering the
+ * stubs and initializing the AGP device.
+ * 
+ * Expands the \c DRIVER_PREINIT and \c DRIVER_POST_INIT macros before and
+ * after the initialization for driver customization.
+ */
+int drm_init( struct drm_driver *driver )
+{
+	struct pci_dev *pdev = NULL;
+	struct pci_device_id *pid;
+	int i;
+
+	DRM_DEBUG( "\n" );
+
+	drm_mem_init();
+
+	for (i=0; driver->pci_driver.id_table[i].vendor != 0; i++) {
+		pid = (struct pci_device_id *)&driver->pci_driver.id_table[i];
+		
+		pdev=NULL;
+		/* pass back in pdev to account for multiple identical cards */		
+		while ((pdev = pci_get_subsys(pid->vendor, pid->device, pid->subvendor, pid->subdevice, pdev)) != NULL) {
+			/* stealth mode requires a manual probe */
+			pci_dev_get(pdev);
+			drm_get_dev(pdev, pid, driver);
+		}
+	}
+	return 0;
+}
+EXPORT_SYMBOL(drm_init);
+
+/**
+ * Called via cleanup_module() at module unload time.
+ *
+ * Cleans up all DRM device, calling takedown().
+ * 
+ * \sa drm_init().
+ */
+static void drm_cleanup( drm_device_t *dev )
+{
+	DRM_DEBUG( "\n" );
+
+	if (!dev) {
+		DRM_ERROR("cleanup called no dev\n");
+		return;
+	}
+
+	drm_takedown( dev );	
+
+	drm_ctxbitmap_cleanup( dev );
+	
+	if (drm_core_has_MTRR(dev) && drm_core_has_AGP(dev) &&
+	    dev->agp && dev->agp->agp_mtrr >= 0) {
+		int retval;
+		retval = mtrr_del( dev->agp->agp_mtrr,
+				   dev->agp->agp_info.aper_base,
+				   dev->agp->agp_info.aper_size*1024*1024 );
+		DRM_DEBUG( "mtrr_del=%d\n", retval );
+	}
+	
+	if (drm_core_has_AGP(dev) && dev->agp ) {
+		drm_free( dev->agp, sizeof(*dev->agp), DRM_MEM_AGPLISTS );
+		dev->agp = NULL;
+	}
+
+	if (dev->driver->postcleanup)
+		dev->driver->postcleanup(dev);
+	
+	drm_put_head(&dev->primary);
+	if ( drm_put_dev(dev) )
+		DRM_ERROR( "Cannot unload module\n" );
+}
+
+void drm_exit (struct drm_driver *driver)
+{
+	int i;
+	drm_device_t *dev = NULL;
+	drm_head_t *head;
+	
+	DRM_DEBUG( "\n" );
+
+	for (i = 0; i < drm_cards_limit; i++) {
+		head = drm_heads[i];
+		if (!head)
+			continue;
+		if (!head->dev)
+			continue;
+		if (head->dev->driver!=driver)
+			continue;
+		dev=head->dev;
+	}
+	if (dev) {
+		/* release the pci driver */
+		if (dev->pdev)
+			pci_dev_put(dev->pdev);
+		drm_cleanup(dev);
+	}
+	DRM_INFO( "Module unloaded\n" );
+}
+EXPORT_SYMBOL(drm_exit);
+
+/** File operations structure */
+static struct file_operations drm_stub_fops = {
+	.owner = THIS_MODULE,
+	.open  = drm_stub_open
+};
+
+static int __init drm_core_init(void)
+{
+	int ret = -ENOMEM;
+	
+	drm_cards_limit = (drm_cards_limit < DRM_MAX_MINOR + 1 ? drm_cards_limit : DRM_MAX_MINOR + 1);
+	drm_heads = drm_calloc(drm_cards_limit,
+				sizeof(*drm_heads), DRM_MEM_STUB);
+	if(!drm_heads) 
+		goto err_p1;
+	
+	if (register_chrdev(DRM_MAJOR, "drm", &drm_stub_fops))
+		goto err_p1;
+	
+	drm_class = drm_sysfs_create(THIS_MODULE, "drm");
+	if (IS_ERR(drm_class)) {
+		printk (KERN_ERR "DRM: Error creating drm class.\n");
+		ret = PTR_ERR(drm_class);
+		goto err_p2;
+	}
+
+	drm_proc_root = create_proc_entry("dri", S_IFDIR, NULL);
+	if (!drm_proc_root) {
+		DRM_ERROR("Cannot create /proc/dri\n");
+		ret = -1;
+		goto err_p3;
+	}
+		
+	DRM_INFO( "Initialized %s %d.%d.%d %s\n",
+		CORE_NAME, CORE_MAJOR, CORE_MINOR, CORE_PATCHLEVEL,
+		CORE_DATE);
+	return 0;
+err_p3:
+	drm_sysfs_destroy(drm_class);
+err_p2:
+	unregister_chrdev(DRM_MAJOR, "drm");
+	drm_free(drm_heads, sizeof(*drm_heads) * drm_cards_limit, DRM_MEM_STUB);
+err_p1:	
+	return ret;
+}
+
+static void __exit drm_core_exit (void)
+{
+	remove_proc_entry("dri", NULL);
+	drm_sysfs_destroy(drm_class);
+
+	unregister_chrdev(DRM_MAJOR, "drm");
+
+	drm_free(drm_heads, sizeof(*drm_heads) *
+				drm_cards_limit, DRM_MEM_STUB);
+}
+
+
+module_init( drm_core_init );
+module_exit( drm_core_exit );
+
+
+/**
+ * Get version information
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_version structure.
+ * \return zero on success or negative number on failure.
+ *
+ * Fills in the version information in \p arg.
+ */
+int drm_version( struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_version_t __user *argp = (void __user *)arg;
+	drm_version_t version;
+	int ret;
+
+	if ( copy_from_user( &version, argp, sizeof(version) ) )
+		return -EFAULT;
+
+	/* version is a required function to return the personality module version */
+	if ((ret = dev->driver->version(&version)))
+		return ret;
+		
+	if ( copy_to_user( argp, &version, sizeof(version) ) )
+		return -EFAULT;
+	return 0;
+}
+
+
+
+/** 
+ * Called whenever a process performs an ioctl on /dev/drm.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument.
+ * \return zero on success or negative number on failure.
+ *
+ * Looks up the ioctl function in the ::ioctls table, checking for root
+ * previleges if so required, and dispatches to the respective function.
+ */
+int drm_ioctl( struct inode *inode, struct file *filp,
+		unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_ioctl_desc_t *ioctl;
+	drm_ioctl_t *func;
+	unsigned int nr = DRM_IOCTL_NR(cmd);
+	int retcode = -EINVAL;
+
+	atomic_inc( &dev->ioctl_count );
+	atomic_inc( &dev->counts[_DRM_STAT_IOCTLS] );
+	++priv->ioctl_count;
+
+	DRM_DEBUG( "pid=%d, cmd=0x%02x, nr=0x%02x, dev 0x%lx, auth=%d\n",
+		   current->pid, cmd, nr, (long)old_encode_dev(priv->head->device), 
+		   priv->authenticated );
+	
+	if (nr < DRIVER_IOCTL_COUNT)
+		ioctl = &drm_ioctls[nr];
+	else if ((nr >= DRM_COMMAND_BASE) && (nr < DRM_COMMAND_BASE + dev->driver->num_ioctls))
+		ioctl = &dev->driver->ioctls[nr - DRM_COMMAND_BASE];
+	else
+		goto err_i1;
+	
+	func = ioctl->func;
+	/* is there a local override? */
+	if ((nr == DRM_IOCTL_NR(DRM_IOCTL_DMA)) && dev->driver->dma_ioctl)
+		func = dev->driver->dma_ioctl;
+	
+	if ( !func ) {
+		DRM_DEBUG( "no function\n" );
+		retcode = -EINVAL;
+	} else if ( ( ioctl->root_only && !capable( CAP_SYS_ADMIN ) )||
+		    ( ioctl->auth_needed && !priv->authenticated ) ) {
+		retcode = -EACCES;
+	} else {
+		retcode = func( inode, filp, cmd, arg );
+	}
+	
+err_i1:
+	atomic_dec( &dev->ioctl_count );
+	if (retcode) DRM_DEBUG( "ret = %x\n", retcode);
+	return retcode;
+}
+EXPORT_SYMBOL(drm_ioctl);
+
diff --git a/drivers/char/drm/drm_fops.c b/drivers/char/drm/drm_fops.c
new file mode 100644
index 0000000..9067942
--- /dev/null
+++ b/drivers/char/drm/drm_fops.c
@@ -0,0 +1,451 @@
+/**
+ * \file drm_fops.h 
+ * File operations for DRM
+ * 
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Daryll Strauss <daryll@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Mon Jan  4 08:58:31 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#include <linux/poll.h>
+
+static int drm_setup( drm_device_t *dev )
+{
+	int i;
+	int ret;
+
+	if (dev->driver->presetup)
+	{
+		ret=dev->driver->presetup(dev);
+		if (ret!=0) 
+			return ret;
+	}
+
+	atomic_set( &dev->ioctl_count, 0 );
+	atomic_set( &dev->vma_count, 0 );
+	dev->buf_use = 0;
+	atomic_set( &dev->buf_alloc, 0 );
+
+	if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+	{
+		i = drm_dma_setup( dev );
+		if ( i < 0 )
+			return i;
+	}
+
+	for ( i = 0 ; i < DRM_ARRAY_SIZE(dev->counts) ; i++ )
+		atomic_set( &dev->counts[i], 0 );
+
+	for ( i = 0 ; i < DRM_HASH_SIZE ; i++ ) {
+		dev->magiclist[i].head = NULL;
+		dev->magiclist[i].tail = NULL;
+	}
+
+	dev->maplist = drm_alloc(sizeof(*dev->maplist),
+				  DRM_MEM_MAPS);
+	if(dev->maplist == NULL) return -ENOMEM;
+	memset(dev->maplist, 0, sizeof(*dev->maplist));
+	INIT_LIST_HEAD(&dev->maplist->head);
+
+	dev->ctxlist = drm_alloc(sizeof(*dev->ctxlist),
+				  DRM_MEM_CTXLIST);
+	if(dev->ctxlist == NULL) return -ENOMEM;
+	memset(dev->ctxlist, 0, sizeof(*dev->ctxlist));
+	INIT_LIST_HEAD(&dev->ctxlist->head);
+
+	dev->vmalist = NULL;
+	dev->sigdata.lock = dev->lock.hw_lock = NULL;
+	init_waitqueue_head( &dev->lock.lock_queue );
+	dev->queue_count = 0;
+	dev->queue_reserved = 0;
+	dev->queue_slots = 0;
+	dev->queuelist = NULL;
+	dev->irq_enabled = 0;
+	dev->context_flag = 0;
+	dev->interrupt_flag = 0;
+	dev->dma_flag = 0;
+	dev->last_context = 0;
+	dev->last_switch = 0;
+	dev->last_checked = 0;
+	init_waitqueue_head( &dev->context_wait );
+	dev->if_version = 0;
+
+	dev->ctx_start = 0;
+	dev->lck_start = 0;
+
+	dev->buf_rp = dev->buf;
+	dev->buf_wp = dev->buf;
+	dev->buf_end = dev->buf + DRM_BSZ;
+	dev->buf_async = NULL;
+	init_waitqueue_head( &dev->buf_readers );
+	init_waitqueue_head( &dev->buf_writers );
+
+	DRM_DEBUG( "\n" );
+
+	/*
+	 * The kernel's context could be created here, but is now created
+	 * in drm_dma_enqueue.	This is more resource-efficient for
+	 * hardware that does not do DMA, but may mean that
+	 * drm_select_queue fails between the time the interrupt is
+	 * initialized and the time the queues are initialized.
+	 */
+	if (dev->driver->postsetup)
+		dev->driver->postsetup(dev);
+
+	return 0;
+}
+
+/**
+ * Open file.
+ * 
+ * \param inode device inode
+ * \param filp file pointer.
+ * \return zero on success or a negative number on failure.
+ *
+ * Searches the DRM device with the same minor number, calls open_helper(), and
+ * increments the device open count. If the open count was previous at zero,
+ * i.e., it's the first that the device is open, then calls setup().
+ */
+int drm_open( struct inode *inode, struct file *filp )
+{
+	drm_device_t *dev = NULL;
+	int minor = iminor(inode);
+	int retcode = 0;
+
+	if (!((minor >= 0) && (minor < drm_cards_limit)))
+		return -ENODEV;
+		
+	if (!drm_heads[minor])
+		return -ENODEV;
+
+	if (!(dev = drm_heads[minor]->dev))
+		return -ENODEV;
+	
+	retcode = drm_open_helper( inode, filp, dev );
+	if ( !retcode ) {
+		atomic_inc( &dev->counts[_DRM_STAT_OPENS] );
+		spin_lock( &dev->count_lock );
+		if ( !dev->open_count++ ) {
+			spin_unlock( &dev->count_lock );
+			return drm_setup( dev );
+		}
+		spin_unlock( &dev->count_lock );
+	}
+
+	return retcode;
+}
+EXPORT_SYMBOL(drm_open);
+
+/**
+ * Release file.
+ *
+ * \param inode device inode
+ * \param filp file pointer.
+ * \return zero on success or a negative number on failure.
+ *
+ * If the hardware lock is held then free it, and take it again for the kernel
+ * context since it's necessary to reclaim buffers. Unlink the file private
+ * data from its list and free it. Decreases the open count and if it reaches
+ * zero calls takedown().
+ */
+int drm_release( struct inode *inode, struct file *filp )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev;
+	int retcode = 0;
+
+	lock_kernel();
+	dev = priv->head->dev;
+
+	DRM_DEBUG( "open_count = %d\n", dev->open_count );
+
+	if (dev->driver->prerelease)
+		dev->driver->prerelease(dev, filp);
+
+	/* ========================================================
+	 * Begin inline drm_release
+	 */
+
+	DRM_DEBUG( "pid = %d, device = 0x%lx, open_count = %d\n",
+		   current->pid, (long)old_encode_dev(priv->head->device), dev->open_count );
+
+	if ( priv->lock_count && dev->lock.hw_lock &&
+	     _DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock) &&
+	     dev->lock.filp == filp ) {
+		DRM_DEBUG( "File %p released, freeing lock for context %d\n",
+			filp,
+			_DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock) );
+		
+		if (dev->driver->release)
+			dev->driver->release(dev, filp);
+
+		drm_lock_free( dev, &dev->lock.hw_lock->lock,
+				_DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock) );
+
+				/* FIXME: may require heavy-handed reset of
+                                   hardware at this point, possibly
+                                   processed via a callback to the X
+                                   server. */
+	}
+	else if ( dev->driver->release && priv->lock_count && dev->lock.hw_lock ) {
+		/* The lock is required to reclaim buffers */
+		DECLARE_WAITQUEUE( entry, current );
+
+		add_wait_queue( &dev->lock.lock_queue, &entry );
+		for (;;) {
+			__set_current_state(TASK_INTERRUPTIBLE);
+			if ( !dev->lock.hw_lock ) {
+				/* Device has been unregistered */
+				retcode = -EINTR;
+				break;
+			}
+			if ( drm_lock_take( &dev->lock.hw_lock->lock,
+					     DRM_KERNEL_CONTEXT ) ) {
+				dev->lock.filp	    = filp;
+				dev->lock.lock_time = jiffies;
+                                atomic_inc( &dev->counts[_DRM_STAT_LOCKS] );
+				break;	/* Got lock */
+			}
+				/* Contention */
+			schedule();
+			if ( signal_pending( current ) ) {
+				retcode = -ERESTARTSYS;
+				break;
+			}
+		}
+		__set_current_state(TASK_RUNNING);
+		remove_wait_queue( &dev->lock.lock_queue, &entry );
+		if( !retcode ) {
+			if (dev->driver->release)
+				dev->driver->release(dev, filp);
+			drm_lock_free( dev, &dev->lock.hw_lock->lock,
+					DRM_KERNEL_CONTEXT );
+		}
+	}
+	
+	if (drm_core_check_feature(dev, DRIVER_HAVE_DMA))
+	{
+		dev->driver->reclaim_buffers(dev, filp);
+	}
+
+	drm_fasync( -1, filp, 0 );
+
+	down( &dev->ctxlist_sem );
+	if ( !list_empty( &dev->ctxlist->head ) ) {
+		drm_ctx_list_t *pos, *n;
+
+		list_for_each_entry_safe( pos, n, &dev->ctxlist->head, head ) {
+			if ( pos->tag == priv &&
+			     pos->handle != DRM_KERNEL_CONTEXT ) {
+				if (dev->driver->context_dtor)
+					dev->driver->context_dtor(dev, pos->handle);
+
+				drm_ctxbitmap_free( dev, pos->handle );
+
+				list_del( &pos->head );
+				drm_free( pos, sizeof(*pos), DRM_MEM_CTXLIST );
+				--dev->ctx_count;
+			}
+		}
+	}
+	up( &dev->ctxlist_sem );
+
+	down( &dev->struct_sem );
+	if ( priv->remove_auth_on_close == 1 ) {
+		drm_file_t *temp = dev->file_first;
+		while ( temp ) {
+			temp->authenticated = 0;
+			temp = temp->next;
+		}
+	}
+	if ( priv->prev ) {
+		priv->prev->next = priv->next;
+	} else {
+		dev->file_first	 = priv->next;
+	}
+	if ( priv->next ) {
+		priv->next->prev = priv->prev;
+	} else {
+		dev->file_last	 = priv->prev;
+	}
+	up( &dev->struct_sem );
+	
+	if (dev->driver->free_filp_priv)
+		dev->driver->free_filp_priv(dev, priv);
+
+	drm_free( priv, sizeof(*priv), DRM_MEM_FILES );
+
+	/* ========================================================
+	 * End inline drm_release
+	 */
+
+	atomic_inc( &dev->counts[_DRM_STAT_CLOSES] );
+	spin_lock( &dev->count_lock );
+	if ( !--dev->open_count ) {
+		if ( atomic_read( &dev->ioctl_count ) || dev->blocked ) {
+			DRM_ERROR( "Device busy: %d %d\n",
+				   atomic_read( &dev->ioctl_count ),
+				   dev->blocked );
+			spin_unlock( &dev->count_lock );
+			unlock_kernel();
+			return -EBUSY;
+		}
+		spin_unlock( &dev->count_lock );
+		unlock_kernel();
+		return drm_takedown( dev );
+	}
+	spin_unlock( &dev->count_lock );
+
+	unlock_kernel();
+
+	return retcode;
+}
+EXPORT_SYMBOL(drm_release);
+
+/**
+ * Called whenever a process opens /dev/drm. 
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param dev device.
+ * \return zero on success or a negative number on failure.
+ * 
+ * Creates and initializes a drm_file structure for the file private data in \p
+ * filp and add it into the double linked list in \p dev.
+ */
+int drm_open_helper(struct inode *inode, struct file *filp, drm_device_t *dev)
+{
+	int	     minor = iminor(inode);
+	drm_file_t   *priv;
+	int ret;
+
+	if (filp->f_flags & O_EXCL)   return -EBUSY; /* No exclusive opens */
+	if (!drm_cpu_valid())        return -EINVAL;
+
+	DRM_DEBUG("pid = %d, minor = %d\n", current->pid, minor);
+
+	priv		    = drm_alloc(sizeof(*priv), DRM_MEM_FILES);
+	if(!priv) return -ENOMEM;
+
+	memset(priv, 0, sizeof(*priv));
+	filp->private_data  = priv;
+	priv->uid	    = current->euid;
+	priv->pid	    = current->pid;
+	priv->minor	    = minor;
+	priv->head          = drm_heads[minor];
+	priv->ioctl_count   = 0;
+	priv->authenticated = capable(CAP_SYS_ADMIN);
+	priv->lock_count    = 0;
+
+	if (dev->driver->open_helper) {
+		ret=dev->driver->open_helper(dev, priv);
+		if (ret < 0)
+			goto out_free;
+	}
+
+	down(&dev->struct_sem);
+	if (!dev->file_last) {
+		priv->next	= NULL;
+		priv->prev	= NULL;
+		dev->file_first = priv;
+		dev->file_last	= priv;
+	} else {
+		priv->next	     = NULL;
+		priv->prev	     = dev->file_last;
+		dev->file_last->next = priv;
+		dev->file_last	     = priv;
+	}
+	up(&dev->struct_sem);
+
+#ifdef __alpha__
+	/*
+	 * Default the hose
+	 */
+	if (!dev->hose) {
+		struct pci_dev *pci_dev;
+		pci_dev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL);
+		if (pci_dev) {
+			dev->hose = pci_dev->sysdata;
+			pci_dev_put(pci_dev);
+		}
+		if (!dev->hose) {
+			struct pci_bus *b = pci_bus_b(pci_root_buses.next);
+			if (b) dev->hose = b->sysdata;
+		}
+	}
+#endif
+
+	return 0;
+out_free:
+	drm_free(priv, sizeof(*priv), DRM_MEM_FILES);
+	filp->private_data=NULL;
+	return ret;
+}
+
+/** No-op. */
+int drm_flush(struct file *filp)
+{
+	drm_file_t    *priv   = filp->private_data;
+	drm_device_t  *dev    = priv->head->dev;
+
+	DRM_DEBUG("pid = %d, device = 0x%lx, open_count = %d\n",
+		  current->pid, (long)old_encode_dev(priv->head->device), dev->open_count);
+	return 0;
+}
+EXPORT_SYMBOL(drm_flush);
+
+/** No-op. */
+int drm_fasync(int fd, struct file *filp, int on)
+{
+	drm_file_t    *priv   = filp->private_data;
+	drm_device_t  *dev    = priv->head->dev;
+	int	      retcode;
+
+	DRM_DEBUG("fd = %d, device = 0x%lx\n", fd, (long)old_encode_dev(priv->head->device));
+	retcode = fasync_helper(fd, filp, on, &dev->buf_async);
+	if (retcode < 0) return retcode;
+	return 0;
+}
+EXPORT_SYMBOL(drm_fasync);
+
+/** No-op. */
+unsigned int drm_poll(struct file *filp, struct poll_table_struct *wait)
+{
+	return 0;
+}
+EXPORT_SYMBOL(drm_poll);
+
+
+/** No-op. */
+ssize_t drm_read(struct file *filp, char __user *buf, size_t count, loff_t *off)
+{
+	return 0;
+}
diff --git a/drivers/char/drm/drm_init.c b/drivers/char/drm/drm_init.c
new file mode 100644
index 0000000..62883b7
--- /dev/null
+++ b/drivers/char/drm/drm_init.c
@@ -0,0 +1,52 @@
+/**
+ * \file drm_init.h 
+ * Setup/Cleanup for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Mon Jan  4 08:58:31 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/**
+ * Check whether DRI will run on this CPU.
+ *
+ * \return non-zero if the DRI will run on this CPU, or zero otherwise.
+ */
+int drm_cpu_valid(void)
+{
+#if defined(__i386__)
+	if (boot_cpu_data.x86 == 3) return 0; /* No cmpxchg on a 386 */
+#endif
+#if defined(__sparc__) && !defined(__sparc_v9__)
+	return 0; /* No cmpxchg before v9 sparc. */
+#endif
+	return 1;
+}
diff --git a/drivers/char/drm/drm_ioctl.c b/drivers/char/drm/drm_ioctl.c
new file mode 100644
index 0000000..39afda0
--- /dev/null
+++ b/drivers/char/drm/drm_ioctl.c
@@ -0,0 +1,370 @@
+/**
+ * \file drm_ioctl.h 
+ * IOCTL processing for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Fri Jan  8 09:01:26 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#include "drm_core.h"
+
+#include "linux/pci.h"
+
+/**
+ * Get the bus id.
+ * 
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_unique structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Copies the bus id from drm_device::unique into user space.
+ */
+int drm_getunique(struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+	drm_unique_t	 __user *argp = (void __user *)arg;
+	drm_unique_t	 u;
+
+	if (copy_from_user(&u, argp, sizeof(u)))
+		return -EFAULT;
+	if (u.unique_len >= dev->unique_len) {
+		if (copy_to_user(u.unique, dev->unique, dev->unique_len))
+			return -EFAULT;
+	}
+	u.unique_len = dev->unique_len;
+	if (copy_to_user(argp, &u, sizeof(u)))
+		return -EFAULT;
+	return 0;
+}
+
+/**
+ * Set the bus id.
+ * 
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_unique structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Copies the bus id from userspace into drm_device::unique, and verifies that
+ * it matches the device this DRM is attached to (EINVAL otherwise).  Deprecated
+ * in interface version 1.1 and will return EBUSY when setversion has requested
+ * version 1.1 or greater.
+ */
+int drm_setunique(struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+	drm_unique_t	 u;
+	int		 domain, bus, slot, func, ret;
+
+	if (dev->unique_len || dev->unique) return -EBUSY;
+
+	if (copy_from_user(&u, (drm_unique_t __user *)arg, sizeof(u)))
+		return -EFAULT;
+
+	if (!u.unique_len || u.unique_len > 1024) return -EINVAL;
+
+	dev->unique_len = u.unique_len;
+	dev->unique	= drm_alloc(u.unique_len + 1, DRM_MEM_DRIVER);
+	if(!dev->unique) return -ENOMEM;
+	if (copy_from_user(dev->unique, u.unique, dev->unique_len))
+		return -EFAULT;
+
+	dev->unique[dev->unique_len] = '\0';
+
+	dev->devname = drm_alloc(strlen(dev->driver->pci_driver.name) + strlen(dev->unique) + 2,
+				  DRM_MEM_DRIVER);
+	if (!dev->devname)
+		return -ENOMEM;
+
+	sprintf(dev->devname, "%s@%s", dev->driver->pci_driver.name, dev->unique);
+
+	/* Return error if the busid submitted doesn't match the device's actual
+	 * busid.
+	 */
+	ret = sscanf(dev->unique, "PCI:%d:%d:%d", &bus, &slot, &func);
+	if (ret != 3)
+		return DRM_ERR(EINVAL);
+	domain = bus >> 8;
+	bus &= 0xff;
+	
+	if ((domain != dev->pci_domain) ||
+	    (bus != dev->pci_bus) ||
+	    (slot != dev->pci_slot) ||
+	    (func != dev->pci_func))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int
+drm_set_busid(drm_device_t *dev)
+{
+	if (dev->unique != NULL)
+		return EBUSY;
+
+	dev->unique_len = 20;
+	dev->unique = drm_alloc(dev->unique_len + 1, DRM_MEM_DRIVER);
+	if (dev->unique == NULL)
+		return ENOMEM;
+
+	snprintf(dev->unique, dev->unique_len, "pci:%04x:%02x:%02x.%d",
+		dev->pci_domain, dev->pci_bus, dev->pci_slot, dev->pci_func);
+
+	dev->devname = drm_alloc(strlen(dev->driver->pci_driver.name) + dev->unique_len + 2,
+				DRM_MEM_DRIVER);
+	if (dev->devname == NULL)
+		return ENOMEM;
+
+	sprintf(dev->devname, "%s@%s", dev->driver->pci_driver.name, dev->unique);
+
+	return 0;
+}
+
+
+/**
+ * Get a mapping information.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_map structure.
+ * 
+ * \return zero on success or a negative number on failure.
+ *
+ * Searches for the mapping with the specified offset and copies its information
+ * into userspace
+ */
+int drm_getmap( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	drm_file_t   *priv = filp->private_data;
+	drm_device_t *dev  = priv->head->dev;
+	drm_map_t    __user *argp = (void __user *)arg;
+	drm_map_t    map;
+	drm_map_list_t *r_list = NULL;
+	struct list_head *list;
+	int          idx;
+	int	     i;
+
+	if (copy_from_user(&map, argp, sizeof(map)))
+		return -EFAULT;
+	idx = map.offset;
+
+	down(&dev->struct_sem);
+	if (idx < 0) {
+		up(&dev->struct_sem);
+		return -EINVAL;
+	}
+
+	i = 0;
+	list_for_each(list, &dev->maplist->head) {
+		if(i == idx) {
+			r_list = list_entry(list, drm_map_list_t, head);
+			break;
+		}
+		i++;
+	}
+	if(!r_list || !r_list->map) {
+		up(&dev->struct_sem);
+		return -EINVAL;
+	}
+
+	map.offset = r_list->map->offset;
+	map.size   = r_list->map->size;
+	map.type   = r_list->map->type;
+	map.flags  = r_list->map->flags;
+	map.handle = r_list->map->handle;
+	map.mtrr   = r_list->map->mtrr;
+	up(&dev->struct_sem);
+
+	if (copy_to_user(argp, &map, sizeof(map))) return -EFAULT;
+	return 0;
+}
+
+/**
+ * Get client information.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_client structure.
+ * 
+ * \return zero on success or a negative number on failure.
+ *
+ * Searches for the client with the specified index and copies its information
+ * into userspace
+ */
+int drm_getclient( struct inode *inode, struct file *filp,
+		    unsigned int cmd, unsigned long arg )
+{
+	drm_file_t   *priv = filp->private_data;
+	drm_device_t *dev  = priv->head->dev;
+	drm_client_t __user *argp = (void __user *)arg;
+	drm_client_t client;
+	drm_file_t   *pt;
+	int          idx;
+	int          i;
+
+	if (copy_from_user(&client, argp, sizeof(client)))
+		return -EFAULT;
+	idx = client.idx;
+	down(&dev->struct_sem);
+	for (i = 0, pt = dev->file_first; i < idx && pt; i++, pt = pt->next)
+		;
+
+	if (!pt) {
+		up(&dev->struct_sem);
+		return -EINVAL;
+	}
+	client.auth  = pt->authenticated;
+	client.pid   = pt->pid;
+	client.uid   = pt->uid;
+	client.magic = pt->magic;
+	client.iocs  = pt->ioctl_count;
+	up(&dev->struct_sem);
+
+	if (copy_to_user((drm_client_t __user *)arg, &client, sizeof(client)))
+		return -EFAULT;
+	return 0;
+}
+
+/** 
+ * Get statistics information. 
+ * 
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_stats structure.
+ * 
+ * \return zero on success or a negative number on failure.
+ */
+int drm_getstats( struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg )
+{
+	drm_file_t   *priv = filp->private_data;
+	drm_device_t *dev  = priv->head->dev;
+	drm_stats_t  stats;
+	int          i;
+
+	memset(&stats, 0, sizeof(stats));
+	
+	down(&dev->struct_sem);
+
+	for (i = 0; i < dev->counters; i++) {
+		if (dev->types[i] == _DRM_STAT_LOCK)
+			stats.data[i].value
+				= (dev->lock.hw_lock
+				   ? dev->lock.hw_lock->lock : 0);
+		else 
+			stats.data[i].value = atomic_read(&dev->counts[i]);
+		stats.data[i].type  = dev->types[i];
+	}
+	
+	stats.count = dev->counters;
+
+	up(&dev->struct_sem);
+
+	if (copy_to_user((drm_stats_t __user *)arg, &stats, sizeof(stats)))
+		return -EFAULT;
+	return 0;
+}
+
+/**
+ * Setversion ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_lock structure.
+ * \return zero on success or negative number on failure.
+ *
+ * Sets the requested interface version
+ */
+int drm_setversion(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_set_version_t sv;
+	drm_set_version_t retv;
+	int if_version;
+	drm_set_version_t __user *argp = (void __user *)data;
+	drm_version_t version;
+
+	DRM_COPY_FROM_USER_IOCTL(sv, argp, sizeof(sv));
+
+	memset(&version, 0, sizeof(version));
+
+	dev->driver->version(&version);
+	retv.drm_di_major = DRM_IF_MAJOR;
+	retv.drm_di_minor = DRM_IF_MINOR;
+	retv.drm_dd_major = version.version_major;
+	retv.drm_dd_minor = version.version_minor;
+
+	DRM_COPY_TO_USER_IOCTL(argp, retv, sizeof(sv));
+
+	if (sv.drm_di_major != -1) {
+		if (sv.drm_di_major != DRM_IF_MAJOR ||
+		    sv.drm_di_minor < 0 || sv.drm_di_minor > DRM_IF_MINOR)
+			return EINVAL;
+		if_version = DRM_IF_VERSION(sv.drm_di_major, sv.drm_dd_minor);
+		dev->if_version = DRM_MAX(if_version, dev->if_version);
+		if (sv.drm_di_minor >= 1) {
+			/*
+			 * Version 1.1 includes tying of DRM to specific device
+			 */
+			drm_set_busid(dev);
+		}
+	}
+
+	if (sv.drm_dd_major != -1) {
+		if (sv.drm_dd_major != version.version_major ||
+		    sv.drm_dd_minor < 0 || sv.drm_dd_minor > version.version_minor)
+			return EINVAL;
+
+		if (dev->driver->set_version)
+			dev->driver->set_version(dev, &sv);
+	}
+	return 0;
+}
+
+/** No-op ioctl. */
+int drm_noop(struct inode *inode, struct file *filp, unsigned int cmd,
+	       unsigned long arg)
+{
+	DRM_DEBUG("\n");
+	return 0;
+}
diff --git a/drivers/char/drm/drm_irq.c b/drivers/char/drm/drm_irq.c
new file mode 100644
index 0000000..2e236eb
--- /dev/null
+++ b/drivers/char/drm/drm_irq.c
@@ -0,0 +1,370 @@
+/**
+ * \file drm_irq.h 
+ * IRQ support
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+#include <linux/interrupt.h>	/* For task queue support */
+
+/**
+ * Get interrupt from bus id.
+ * 
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_irq_busid structure.
+ * \return zero on success or a negative number on failure.
+ * 
+ * Finds the PCI device with the specified bus id and gets its IRQ number.
+ * This IOCTL is deprecated, and will now return EINVAL for any busid not equal
+ * to that of the device that this DRM instance attached to.
+ */
+int drm_irq_by_busid(struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_irq_busid_t __user *argp = (void __user *)arg;
+	drm_irq_busid_t p;
+
+	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+		return -EINVAL;
+
+	if (copy_from_user(&p, argp, sizeof(p)))
+		return -EFAULT;
+
+	if ((p.busnum >> 8) != dev->pci_domain ||
+	    (p.busnum & 0xff) != dev->pci_bus ||
+	    p.devnum != dev->pci_slot ||
+	    p.funcnum != dev->pci_func)
+		return -EINVAL;
+
+	p.irq = dev->irq;
+
+	DRM_DEBUG("%d:%d:%d => IRQ %d\n",
+		  p.busnum, p.devnum, p.funcnum, p.irq);
+	if (copy_to_user(argp, &p, sizeof(p)))
+		return -EFAULT;
+	return 0;
+}
+
+/**
+ * Install IRQ handler.
+ *
+ * \param dev DRM device.
+ * \param irq IRQ number.
+ *
+ * Initializes the IRQ related data, and setups drm_device::vbl_queue. Installs the handler, calling the driver
+ * \c drm_driver_irq_preinstall() and \c drm_driver_irq_postinstall() functions
+ * before and after the installation.
+ */
+int drm_irq_install( drm_device_t *dev )
+{
+	int ret;
+	unsigned long sh_flags=0;
+
+	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+		return -EINVAL;
+
+	if ( dev->irq == 0 )
+		return -EINVAL;
+
+	down( &dev->struct_sem );
+
+	/* Driver must have been initialized */
+	if ( !dev->dev_private ) {
+		up( &dev->struct_sem );
+		return -EINVAL;
+	}
+
+	if ( dev->irq_enabled ) {
+		up( &dev->struct_sem );
+		return -EBUSY;
+	}
+	dev->irq_enabled = 1;
+	up( &dev->struct_sem );
+
+	DRM_DEBUG( "%s: irq=%d\n", __FUNCTION__, dev->irq );
+
+	if (drm_core_check_feature(dev, DRIVER_IRQ_VBL)) {
+		init_waitqueue_head(&dev->vbl_queue);
+		
+		spin_lock_init( &dev->vbl_lock );
+		
+		INIT_LIST_HEAD( &dev->vbl_sigs.head );
+		
+		dev->vbl_pending = 0;
+	}
+
+				/* Before installing handler */
+	dev->driver->irq_preinstall(dev);
+
+				/* Install handler */
+	if (drm_core_check_feature(dev, DRIVER_IRQ_SHARED))
+		sh_flags = SA_SHIRQ;
+	
+	ret = request_irq( dev->irq, dev->driver->irq_handler,
+			   sh_flags, dev->devname, dev );
+	if ( ret < 0 ) {
+		down( &dev->struct_sem );
+		dev->irq_enabled = 0;
+		up( &dev->struct_sem );
+		return ret;
+	}
+
+				/* After installing handler */
+	dev->driver->irq_postinstall(dev);
+
+	return 0;
+}
+
+/**
+ * Uninstall the IRQ handler.
+ *
+ * \param dev DRM device.
+ *
+ * Calls the driver's \c drm_driver_irq_uninstall() function, and stops the irq.
+ */
+int drm_irq_uninstall( drm_device_t *dev )
+{
+	int irq_enabled;
+
+	if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+		return -EINVAL;
+
+	down( &dev->struct_sem );
+	irq_enabled = dev->irq_enabled;
+	dev->irq_enabled = 0;
+	up( &dev->struct_sem );
+
+	if ( !irq_enabled )
+		return -EINVAL;
+
+	DRM_DEBUG( "%s: irq=%d\n", __FUNCTION__, dev->irq );
+
+	dev->driver->irq_uninstall(dev);
+
+	free_irq( dev->irq, dev );
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_irq_uninstall);
+
+/**
+ * IRQ control ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_control structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Calls irq_install() or irq_uninstall() according to \p arg.
+ */
+int drm_control( struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_control_t ctl;
+	
+	/* if we haven't irq we fallback for compatibility reasons - this used to be a separate function in drm_dma.h */
+
+	if ( copy_from_user( &ctl, (drm_control_t __user *)arg, sizeof(ctl) ) )
+		return -EFAULT;
+
+	switch ( ctl.func ) {
+	case DRM_INST_HANDLER:
+		if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+			return 0;
+		if (dev->if_version < DRM_IF_VERSION(1, 2) &&
+		    ctl.irq != dev->irq)
+			return -EINVAL;
+		return drm_irq_install( dev );
+	case DRM_UNINST_HANDLER:
+		if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+			return 0;
+		return drm_irq_uninstall( dev );
+	default:
+		return -EINVAL;
+	}
+}
+
+/**
+ * Wait for VBLANK.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param data user argument, pointing to a drm_wait_vblank structure.
+ * \return zero on success or a negative number on failure.
+ *
+ * Verifies the IRQ is installed. 
+ *
+ * If a signal is requested checks if this task has already scheduled the same signal
+ * for the same vblank sequence number - nothing to be done in
+ * that case. If the number of tasks waiting for the interrupt exceeds 100 the
+ * function fails. Otherwise adds a new entry to drm_device::vbl_sigs for this
+ * task.
+ *
+ * If a signal is not requested, then calls vblank_wait().
+ */
+int drm_wait_vblank( DRM_IOCTL_ARGS )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_wait_vblank_t __user *argp = (void __user *)data;
+	drm_wait_vblank_t vblwait;
+	struct timeval now;
+	int ret = 0;
+	unsigned int flags;
+
+	if (!drm_core_check_feature(dev, DRIVER_IRQ_VBL))
+		return -EINVAL;
+
+	if (!dev->irq)
+		return -EINVAL;
+
+	DRM_COPY_FROM_USER_IOCTL( vblwait, argp, sizeof(vblwait) );
+
+	switch ( vblwait.request.type & ~_DRM_VBLANK_FLAGS_MASK ) {
+	case _DRM_VBLANK_RELATIVE:
+		vblwait.request.sequence += atomic_read( &dev->vbl_received );
+		vblwait.request.type &= ~_DRM_VBLANK_RELATIVE;
+	case _DRM_VBLANK_ABSOLUTE:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	flags = vblwait.request.type & _DRM_VBLANK_FLAGS_MASK;
+	
+	if ( flags & _DRM_VBLANK_SIGNAL ) {
+		unsigned long irqflags;
+		drm_vbl_sig_t *vbl_sig;
+		
+		vblwait.reply.sequence = atomic_read( &dev->vbl_received );
+
+		spin_lock_irqsave( &dev->vbl_lock, irqflags );
+
+		/* Check if this task has already scheduled the same signal
+		 * for the same vblank sequence number; nothing to be done in
+		 * that case
+		 */
+		list_for_each_entry( vbl_sig, &dev->vbl_sigs.head, head ) {
+			if (vbl_sig->sequence == vblwait.request.sequence
+			    && vbl_sig->info.si_signo == vblwait.request.signal
+			    && vbl_sig->task == current)
+			{
+				spin_unlock_irqrestore( &dev->vbl_lock, irqflags );
+				goto done;
+			}
+		}
+
+		if ( dev->vbl_pending >= 100 ) {
+			spin_unlock_irqrestore( &dev->vbl_lock, irqflags );
+			return -EBUSY;
+		}
+
+		dev->vbl_pending++;
+
+		spin_unlock_irqrestore( &dev->vbl_lock, irqflags );
+
+		if ( !( vbl_sig = drm_alloc( sizeof( drm_vbl_sig_t ), DRM_MEM_DRIVER ) ) ) {
+			return -ENOMEM;
+		}
+
+		memset( (void *)vbl_sig, 0, sizeof(*vbl_sig) );
+
+		vbl_sig->sequence = vblwait.request.sequence;
+		vbl_sig->info.si_signo = vblwait.request.signal;
+		vbl_sig->task = current;
+
+		spin_lock_irqsave( &dev->vbl_lock, irqflags );
+
+		list_add_tail( (struct list_head *) vbl_sig, &dev->vbl_sigs.head );
+
+		spin_unlock_irqrestore( &dev->vbl_lock, irqflags );
+	} else {
+		if (dev->driver->vblank_wait)
+			ret = dev->driver->vblank_wait( dev, &vblwait.request.sequence );
+
+		do_gettimeofday( &now );
+		vblwait.reply.tval_sec = now.tv_sec;
+		vblwait.reply.tval_usec = now.tv_usec;
+	}
+
+done:
+	DRM_COPY_TO_USER_IOCTL( argp, vblwait, sizeof(vblwait) );
+
+	return ret;
+}
+
+/**
+ * Send the VBLANK signals.
+ *
+ * \param dev DRM device.
+ *
+ * Sends a signal for each task in drm_device::vbl_sigs and empties the list.
+ *
+ * If a signal is not requested, then calls vblank_wait().
+ */
+void drm_vbl_send_signals( drm_device_t *dev )
+{
+	struct list_head *list, *tmp;
+	drm_vbl_sig_t *vbl_sig;
+	unsigned int vbl_seq = atomic_read( &dev->vbl_received );
+	unsigned long flags;
+
+	spin_lock_irqsave( &dev->vbl_lock, flags );
+
+	list_for_each_safe( list, tmp, &dev->vbl_sigs.head ) {
+		vbl_sig = list_entry( list, drm_vbl_sig_t, head );
+		if ( ( vbl_seq - vbl_sig->sequence ) <= (1<<23) ) {
+			vbl_sig->info.si_code = vbl_seq;
+			send_sig_info( vbl_sig->info.si_signo, &vbl_sig->info, vbl_sig->task );
+
+			list_del( list );
+
+			drm_free( vbl_sig, sizeof(*vbl_sig), DRM_MEM_DRIVER );
+
+			dev->vbl_pending--;
+		}
+	}
+
+	spin_unlock_irqrestore( &dev->vbl_lock, flags );
+}
+EXPORT_SYMBOL(drm_vbl_send_signals);
+
+
diff --git a/drivers/char/drm/drm_lock.c b/drivers/char/drm/drm_lock.c
new file mode 100644
index 0000000..d0d6fc6
--- /dev/null
+++ b/drivers/char/drm/drm_lock.c
@@ -0,0 +1,303 @@
+/**
+ * \file drm_lock.h 
+ * IOCTLs for locking
+ * 
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Tue Feb  2 08:37:54 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+/** 
+ * Lock ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_lock structure.
+ * \return zero on success or negative number on failure.
+ *
+ * Add the current task to the lock wait queue, and attempt to take to lock.
+ */
+int drm_lock( struct inode *inode, struct file *filp,
+	       unsigned int cmd, unsigned long arg )
+{
+        drm_file_t *priv = filp->private_data;
+        drm_device_t *dev = priv->head->dev;
+        DECLARE_WAITQUEUE( entry, current );
+        drm_lock_t lock;
+        int ret = 0;
+
+	++priv->lock_count;
+
+        if ( copy_from_user( &lock, (drm_lock_t __user *)arg, sizeof(lock) ) )
+		return -EFAULT;
+
+        if ( lock.context == DRM_KERNEL_CONTEXT ) {
+                DRM_ERROR( "Process %d using kernel context %d\n",
+			   current->pid, lock.context );
+                return -EINVAL;
+        }
+
+        DRM_DEBUG( "%d (pid %d) requests lock (0x%08x), flags = 0x%08x\n",
+		   lock.context, current->pid,
+		   dev->lock.hw_lock->lock, lock.flags );
+
+	if (drm_core_check_feature(dev, DRIVER_DMA_QUEUE))
+		if ( lock.context < 0 )
+			return -EINVAL;
+
+	add_wait_queue( &dev->lock.lock_queue, &entry );
+	for (;;) {
+		__set_current_state(TASK_INTERRUPTIBLE);
+		if ( !dev->lock.hw_lock ) {
+			/* Device has been unregistered */
+			ret = -EINTR;
+			break;
+		}
+		if ( drm_lock_take( &dev->lock.hw_lock->lock,
+				     lock.context ) ) {
+			dev->lock.filp      = filp;
+			dev->lock.lock_time = jiffies;
+			atomic_inc( &dev->counts[_DRM_STAT_LOCKS] );
+			break;  /* Got lock */
+		}
+		
+		/* Contention */
+		schedule();
+		if ( signal_pending( current ) ) {
+			ret = -ERESTARTSYS;
+			break;
+		}
+	}
+	__set_current_state(TASK_RUNNING);
+	remove_wait_queue( &dev->lock.lock_queue, &entry );
+
+	sigemptyset( &dev->sigmask );
+	sigaddset( &dev->sigmask, SIGSTOP );
+	sigaddset( &dev->sigmask, SIGTSTP );
+	sigaddset( &dev->sigmask, SIGTTIN );
+	sigaddset( &dev->sigmask, SIGTTOU );
+	dev->sigdata.context = lock.context;
+	dev->sigdata.lock    = dev->lock.hw_lock;
+	block_all_signals( drm_notifier,
+			   &dev->sigdata, &dev->sigmask );
+	
+	if (dev->driver->dma_ready && (lock.flags & _DRM_LOCK_READY))
+		dev->driver->dma_ready(dev);
+	
+	if ( dev->driver->dma_quiescent && (lock.flags & _DRM_LOCK_QUIESCENT ))
+		return dev->driver->dma_quiescent(dev);
+	
+	/* dev->driver->kernel_context_switch isn't used by any of the x86 
+	 *  drivers but is used by the Sparc driver.
+	 */
+	
+	if (dev->driver->kernel_context_switch && 
+	    dev->last_context != lock.context) {
+	  dev->driver->kernel_context_switch(dev, dev->last_context, 
+					    lock.context);
+	}
+        DRM_DEBUG( "%d %s\n", lock.context, ret ? "interrupted" : "has lock" );
+
+        return ret;
+}
+
+/** 
+ * Unlock ioctl.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ * \param cmd command.
+ * \param arg user argument, pointing to a drm_lock structure.
+ * \return zero on success or negative number on failure.
+ *
+ * Transfer and free the lock.
+ */
+int drm_unlock( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_lock_t lock;
+
+	if ( copy_from_user( &lock, (drm_lock_t __user *)arg, sizeof(lock) ) )
+		return -EFAULT;
+
+	if ( lock.context == DRM_KERNEL_CONTEXT ) {
+		DRM_ERROR( "Process %d using kernel context %d\n",
+			   current->pid, lock.context );
+		return -EINVAL;
+	}
+
+	atomic_inc( &dev->counts[_DRM_STAT_UNLOCKS] );
+
+	/* kernel_context_switch isn't used by any of the x86 drm
+	 * modules but is required by the Sparc driver.
+	 */
+	if (dev->driver->kernel_context_switch_unlock)
+		dev->driver->kernel_context_switch_unlock(dev, &lock);
+	else {
+		drm_lock_transfer( dev, &dev->lock.hw_lock->lock, 
+				    DRM_KERNEL_CONTEXT );
+		
+		if ( drm_lock_free( dev, &dev->lock.hw_lock->lock,
+				     DRM_KERNEL_CONTEXT ) ) {
+			DRM_ERROR( "\n" );
+		}
+	}
+
+	unblock_all_signals();
+	return 0;
+}
+
+/**
+ * Take the heavyweight lock.
+ *
+ * \param lock lock pointer.
+ * \param context locking context.
+ * \return one if the lock is held, or zero otherwise.
+ *
+ * Attempt to mark the lock as held by the given context, via the \p cmpxchg instruction.
+ */
+int drm_lock_take(__volatile__ unsigned int *lock, unsigned int context)
+{
+	unsigned int old, new, prev;
+
+	do {
+		old = *lock;
+		if (old & _DRM_LOCK_HELD) new = old | _DRM_LOCK_CONT;
+		else			  new = context | _DRM_LOCK_HELD;
+		prev = cmpxchg(lock, old, new);
+	} while (prev != old);
+	if (_DRM_LOCKING_CONTEXT(old) == context) {
+		if (old & _DRM_LOCK_HELD) {
+			if (context != DRM_KERNEL_CONTEXT) {
+				DRM_ERROR("%d holds heavyweight lock\n",
+					  context);
+			}
+			return 0;
+		}
+	}
+	if (new == (context | _DRM_LOCK_HELD)) {
+				/* Have lock */
+		return 1;
+	}
+	return 0;
+}
+
+/**
+ * This takes a lock forcibly and hands it to context.	Should ONLY be used
+ * inside *_unlock to give lock to kernel before calling *_dma_schedule. 
+ * 
+ * \param dev DRM device.
+ * \param lock lock pointer.
+ * \param context locking context.
+ * \return always one.
+ *
+ * Resets the lock file pointer.
+ * Marks the lock as held by the given context, via the \p cmpxchg instruction.
+ */
+int drm_lock_transfer(drm_device_t *dev,
+		       __volatile__ unsigned int *lock, unsigned int context)
+{
+	unsigned int old, new, prev;
+
+	dev->lock.filp = NULL;
+	do {
+		old  = *lock;
+		new  = context | _DRM_LOCK_HELD;
+		prev = cmpxchg(lock, old, new);
+	} while (prev != old);
+	return 1;
+}
+
+/**
+ * Free lock.
+ * 
+ * \param dev DRM device.
+ * \param lock lock.
+ * \param context context.
+ * 
+ * Resets the lock file pointer.
+ * Marks the lock as not held, via the \p cmpxchg instruction. Wakes any task
+ * waiting on the lock queue.
+ */
+int drm_lock_free(drm_device_t *dev,
+		   __volatile__ unsigned int *lock, unsigned int context)
+{
+	unsigned int old, new, prev;
+
+	dev->lock.filp = NULL;
+	do {
+		old  = *lock;
+		new  = 0;
+		prev = cmpxchg(lock, old, new);
+	} while (prev != old);
+	if (_DRM_LOCK_IS_HELD(old) && _DRM_LOCKING_CONTEXT(old) != context) {
+		DRM_ERROR("%d freed heavyweight lock held by %d\n",
+			  context,
+			  _DRM_LOCKING_CONTEXT(old));
+		return 1;
+	}
+	wake_up_interruptible(&dev->lock.lock_queue);
+	return 0;
+}
+
+/**
+ * If we get here, it means that the process has called DRM_IOCTL_LOCK
+ * without calling DRM_IOCTL_UNLOCK.
+ *
+ * If the lock is not held, then let the signal proceed as usual.  If the lock
+ * is held, then set the contended flag and keep the signal blocked.
+ *
+ * \param priv pointer to a drm_sigdata structure.
+ * \return one if the signal should be delivered normally, or zero if the
+ * signal should be blocked.
+ */
+int drm_notifier(void *priv)
+{
+	drm_sigdata_t *s = (drm_sigdata_t *)priv;
+	unsigned int  old, new, prev;
+
+
+				/* Allow signal delivery if lock isn't held */
+	if (!s->lock || !_DRM_LOCK_IS_HELD(s->lock->lock)
+	    || _DRM_LOCKING_CONTEXT(s->lock->lock) != s->context) return 1;
+
+				/* Otherwise, set flag to force call to
+                                   drmUnlock */
+	do {
+		old  = s->lock->lock;
+		new  = old | _DRM_LOCK_CONT;
+		prev = cmpxchg(&s->lock->lock, old, new);
+	} while (prev != old);
+	return 0;
+}
diff --git a/drivers/char/drm/drm_memory.c b/drivers/char/drm/drm_memory.c
new file mode 100644
index 0000000..7f53f75
--- /dev/null
+++ b/drivers/char/drm/drm_memory.c
@@ -0,0 +1,181 @@
+/** 
+ * \file drm_memory.h 
+ * Memory management wrappers for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/* 
+ * Created: Thu Feb  4 14:00:34 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/config.h>
+#include <linux/highmem.h>
+#include "drmP.h"
+
+#ifdef DEBUG_MEMORY
+#include "drm_memory_debug.h"
+#else
+
+/** No-op. */
+void drm_mem_init(void)
+{
+}
+
+/**
+ * Called when "/proc/dri/%dev%/mem" is read.
+ * 
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param len requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ *
+ * No-op. 
+ */
+int drm_mem_info(char *buf, char **start, off_t offset,
+		  int len, int *eof, void *data)
+{
+	return 0;
+}
+
+/** Wrapper around kmalloc() */
+void *drm_calloc(size_t nmemb, size_t size, int area)
+{
+	void *addr;
+
+	addr = kmalloc(size * nmemb, GFP_KERNEL);
+	if (addr != NULL)
+		memset((void *)addr, 0, size * nmemb);
+
+	return addr;
+}
+EXPORT_SYMBOL(drm_calloc);
+
+/** Wrapper around kmalloc() and kfree() */
+void *drm_realloc(void *oldpt, size_t oldsize, size_t size, int area)
+{
+	void *pt;
+
+	if (!(pt = kmalloc(size, GFP_KERNEL))) return NULL;
+	if (oldpt && oldsize) {
+		memcpy(pt, oldpt, oldsize);
+		kfree(oldpt);
+	}
+	return pt;
+}
+
+/**
+ * Allocate pages.
+ *
+ * \param order size order.
+ * \param area memory area. (Not used.)
+ * \return page address on success, or zero on failure.
+ *
+ * Allocate and reserve free pages.
+ */
+unsigned long drm_alloc_pages(int order, int area)
+{
+	unsigned long address;
+	unsigned long bytes	  = PAGE_SIZE << order;
+	unsigned long addr;
+	unsigned int  sz;
+
+	address = __get_free_pages(GFP_KERNEL, order);
+	if (!address) 
+		return 0;
+
+				/* Zero */
+	memset((void *)address, 0, bytes);
+
+				/* Reserve */
+	for (addr = address, sz = bytes;
+	     sz > 0;
+	     addr += PAGE_SIZE, sz -= PAGE_SIZE) {
+		SetPageReserved(virt_to_page(addr));
+	}
+
+	return address;
+}
+
+/**
+ * Free pages.
+ * 
+ * \param address address of the pages to free.
+ * \param order size order.
+ * \param area memory area. (Not used.)
+ *
+ * Unreserve and free pages allocated by alloc_pages().
+ */
+void drm_free_pages(unsigned long address, int order, int area)
+{
+	unsigned long bytes = PAGE_SIZE << order;
+	unsigned long addr;
+	unsigned int  sz;
+
+	if (!address) 
+		return;
+
+	/* Unreserve */
+	for (addr = address, sz = bytes;
+	     sz > 0;
+	     addr += PAGE_SIZE, sz -= PAGE_SIZE) {
+		ClearPageReserved(virt_to_page(addr));
+	}
+
+	free_pages(address, order);
+}
+
+
+#if __OS_HAS_AGP
+/** Wrapper around agp_allocate_memory() */
+DRM_AGP_MEM *drm_alloc_agp(struct agp_bridge_data *bridge, int pages, u32 type)
+{
+	return drm_agp_allocate_memory(bridge, pages, type);
+}
+
+/** Wrapper around agp_free_memory() */
+int drm_free_agp(DRM_AGP_MEM *handle, int pages)
+{
+	return drm_agp_free_memory(handle) ? 0 : -EINVAL;
+}
+
+/** Wrapper around agp_bind_memory() */
+int drm_bind_agp(DRM_AGP_MEM *handle, unsigned int start)
+{
+	return drm_agp_bind_memory(handle, start);
+}
+
+/** Wrapper around agp_unbind_memory() */
+int drm_unbind_agp(DRM_AGP_MEM *handle)
+{
+	return drm_agp_unbind_memory(handle);
+}
+#endif /* agp */
+#endif /* debug_memory */
diff --git a/drivers/char/drm/drm_memory.h b/drivers/char/drm/drm_memory.h
new file mode 100644
index 0000000..422b942
--- /dev/null
+++ b/drivers/char/drm/drm_memory.h
@@ -0,0 +1,197 @@
+/** 
+ * \file drm_memory.h 
+ * Memory management wrappers for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/* 
+ * Created: Thu Feb  4 14:00:34 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/config.h>
+#include <linux/highmem.h>
+#include <linux/vmalloc.h>
+#include "drmP.h"
+
+/**
+ * Cut down version of drm_memory_debug.h, which used to be called
+ * drm_memory.h.  
+ */
+
+#if __OS_HAS_AGP
+
+#include <linux/vmalloc.h>
+
+#ifdef HAVE_PAGE_AGP
+#include <asm/agp.h>
+#else
+# ifdef __powerpc__
+#  define PAGE_AGP	__pgprot(_PAGE_KERNEL | _PAGE_NO_CACHE)
+# else
+#  define PAGE_AGP	PAGE_KERNEL
+# endif
+#endif
+
+/*
+ * Find the drm_map that covers the range [offset, offset+size).
+ */
+static inline drm_map_t *
+drm_lookup_map (unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+	struct list_head *list;
+	drm_map_list_t *r_list;
+	drm_map_t *map;
+
+	list_for_each(list, &dev->maplist->head) {
+		r_list = (drm_map_list_t *) list;
+		map = r_list->map;
+		if (!map)
+			continue;
+		if (map->offset <= offset && (offset + size) <= (map->offset + map->size))
+			return map;
+	}
+	return NULL;
+}
+
+static inline void *
+agp_remap (unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+	unsigned long *phys_addr_map, i, num_pages = PAGE_ALIGN(size) / PAGE_SIZE;
+	struct drm_agp_mem *agpmem;
+	struct page **page_map;
+	void *addr;
+
+	size = PAGE_ALIGN(size);
+
+#ifdef __alpha__
+	offset -= dev->hose->mem_space->start;
+#endif
+
+	for (agpmem = dev->agp->memory; agpmem; agpmem = agpmem->next)
+		if (agpmem->bound <= offset
+		    && (agpmem->bound + (agpmem->pages << PAGE_SHIFT)) >= (offset + size))
+			break;
+	if (!agpmem)
+		return NULL;
+
+	/*
+	 * OK, we're mapping AGP space on a chipset/platform on which memory accesses by
+	 * the CPU do not get remapped by the GART.  We fix this by using the kernel's
+	 * page-table instead (that's probably faster anyhow...).
+	 */
+	/* note: use vmalloc() because num_pages could be large... */
+	page_map = vmalloc(num_pages * sizeof(struct page *));
+	if (!page_map)
+		return NULL;
+
+	phys_addr_map = agpmem->memory->memory + (offset - agpmem->bound) / PAGE_SIZE;
+	for (i = 0; i < num_pages; ++i)
+		page_map[i] = pfn_to_page(phys_addr_map[i] >> PAGE_SHIFT);
+	addr = vmap(page_map, num_pages, VM_IOREMAP, PAGE_AGP);
+	vfree(page_map);
+
+	return addr;
+}
+
+static inline unsigned long
+drm_follow_page (void *vaddr)
+{
+	pgd_t *pgd = pgd_offset_k((unsigned long) vaddr);
+	pud_t *pud = pud_offset(pgd, (unsigned long) vaddr);
+	pmd_t *pmd = pmd_offset(pud, (unsigned long) vaddr);
+	pte_t *ptep = pte_offset_kernel(pmd, (unsigned long) vaddr);
+	return pte_pfn(*ptep) << PAGE_SHIFT;
+}
+
+#else /* __OS_HAS_AGP */
+
+static inline drm_map_t *drm_lookup_map(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+  return NULL;
+}
+
+static inline void *agp_remap(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+  return NULL;
+}
+
+static inline unsigned long drm_follow_page (void *vaddr)
+{
+  return 0;
+}
+
+#endif
+
+static inline void *drm_ioremap(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+	if (drm_core_has_AGP(dev) && dev->agp && dev->agp->cant_use_aperture) {
+		drm_map_t *map = drm_lookup_map(offset, size, dev);
+
+		if (map && map->type == _DRM_AGP)
+			return agp_remap(offset, size, dev);
+	}
+	return ioremap(offset, size);
+}
+
+static inline void *drm_ioremap_nocache(unsigned long offset, unsigned long size,
+					drm_device_t *dev)
+{
+	if (drm_core_has_AGP(dev) && dev->agp && dev->agp->cant_use_aperture) {
+		drm_map_t *map = drm_lookup_map(offset, size, dev);
+
+		if (map && map->type == _DRM_AGP)
+			return agp_remap(offset, size, dev);
+	}
+	return ioremap_nocache(offset, size);
+}
+
+static inline void drm_ioremapfree(void *pt, unsigned long size, drm_device_t *dev)
+{
+	/*
+	 * This is a bit ugly.  It would be much cleaner if the DRM API would use separate
+	 * routines for handling mappings in the AGP space.  Hopefully this can be done in
+	 * a future revision of the interface...
+	 */
+	if (drm_core_has_AGP(dev) && dev->agp && dev->agp->cant_use_aperture
+	    && ((unsigned long) pt >= VMALLOC_START && (unsigned long) pt < VMALLOC_END))
+	{
+		unsigned long offset;
+		drm_map_t *map;
+
+		offset = drm_follow_page(pt) | ((unsigned long) pt & ~PAGE_MASK);
+		map = drm_lookup_map(offset, size, dev);
+		if (map && map->type == _DRM_AGP) {
+			vunmap(pt);
+			return;
+		}
+	}
+
+	iounmap(pt);
+}
+
+
diff --git a/drivers/char/drm/drm_memory_debug.h b/drivers/char/drm/drm_memory_debug.h
new file mode 100644
index 0000000..2c82e69
--- /dev/null
+++ b/drivers/char/drm/drm_memory_debug.h
@@ -0,0 +1,459 @@
+/**
+ * \file drm_memory.h 
+ * Memory management wrappers for DRM.
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+
+typedef struct drm_mem_stats {
+	const char	  *name;
+	int		  succeed_count;
+	int		  free_count;
+	int		  fail_count;
+	unsigned long	  bytes_allocated;
+	unsigned long	  bytes_freed;
+} drm_mem_stats_t;
+
+static DEFINE_SPINLOCK(DRM(mem_lock));
+static unsigned long	  DRM(ram_available) = 0; /* In pages */
+static unsigned long	  DRM(ram_used)      = 0;
+static drm_mem_stats_t	  DRM(mem_stats)[]   = {
+	[DRM_MEM_DMA]	    = { "dmabufs"  },
+	[DRM_MEM_SAREA]	    = { "sareas"   },
+	[DRM_MEM_DRIVER]    = { "driver"   },
+	[DRM_MEM_MAGIC]	    = { "magic"	   },
+	[DRM_MEM_IOCTLS]    = { "ioctltab" },
+	[DRM_MEM_MAPS]	    = { "maplist"  },
+	[DRM_MEM_VMAS]	    = { "vmalist"  },
+	[DRM_MEM_BUFS]	    = { "buflist"  },
+	[DRM_MEM_SEGS]	    = { "seglist"  },
+	[DRM_MEM_PAGES]	    = { "pagelist" },
+	[DRM_MEM_FILES]	    = { "files"	   },
+	[DRM_MEM_QUEUES]    = { "queues"   },
+	[DRM_MEM_CMDS]	    = { "commands" },
+	[DRM_MEM_MAPPINGS]  = { "mappings" },
+	[DRM_MEM_BUFLISTS]  = { "buflists" },
+	[DRM_MEM_AGPLISTS]  = { "agplist"  },
+	[DRM_MEM_SGLISTS]   = { "sglist"   },
+	[DRM_MEM_TOTALAGP]  = { "totalagp" },
+	[DRM_MEM_BOUNDAGP]  = { "boundagp" },
+	[DRM_MEM_CTXBITMAP] = { "ctxbitmap"},
+	[DRM_MEM_CTXLIST]   = { "ctxlist"  },
+	[DRM_MEM_STUB]      = { "stub"     },
+	{ NULL, 0, }		/* Last entry must be null */
+};
+
+void DRM(mem_init)(void)
+{
+	drm_mem_stats_t *mem;
+	struct sysinfo	si;
+
+	for (mem = DRM(mem_stats); mem->name; ++mem) {
+		mem->succeed_count   = 0;
+		mem->free_count	     = 0;
+		mem->fail_count	     = 0;
+		mem->bytes_allocated = 0;
+		mem->bytes_freed     = 0;
+	}
+
+	si_meminfo(&si);
+	DRM(ram_available) = si.totalram;
+	DRM(ram_used)	   = 0;
+}
+
+/* drm_mem_info is called whenever a process reads /dev/drm/mem. */
+
+static int DRM(_mem_info)(char *buf, char **start, off_t offset,
+			  int request, int *eof, void *data)
+{
+	drm_mem_stats_t *pt;
+	int             len = 0;
+
+	if (offset > DRM_PROC_LIMIT) {
+		*eof = 1;
+		return 0;
+	}
+
+	*eof   = 0;
+	*start = &buf[offset];
+
+	DRM_PROC_PRINT("		  total counts			"
+		       " |    outstanding  \n");
+	DRM_PROC_PRINT("type	   alloc freed fail	bytes	   freed"
+		       " | allocs      bytes\n\n");
+	DRM_PROC_PRINT("%-9.9s %5d %5d %4d %10lu kB         |\n",
+		       "system", 0, 0, 0,
+		       DRM(ram_available) << (PAGE_SHIFT - 10));
+	DRM_PROC_PRINT("%-9.9s %5d %5d %4d %10lu kB         |\n",
+		       "locked", 0, 0, 0, DRM(ram_used) >> 10);
+	DRM_PROC_PRINT("\n");
+	for (pt = DRM(mem_stats); pt->name; pt++) {
+		DRM_PROC_PRINT("%-9.9s %5d %5d %4d %10lu %10lu | %6d %10ld\n",
+			       pt->name,
+			       pt->succeed_count,
+			       pt->free_count,
+			       pt->fail_count,
+			       pt->bytes_allocated,
+			       pt->bytes_freed,
+			       pt->succeed_count - pt->free_count,
+			       (long)pt->bytes_allocated
+			       - (long)pt->bytes_freed);
+	}
+
+	if (len > request + offset) return request;
+	*eof = 1;
+	return len - offset;
+}
+
+int DRM(mem_info)(char *buf, char **start, off_t offset,
+		  int len, int *eof, void *data)
+{
+	int ret;
+
+	spin_lock(&DRM(mem_lock));
+	ret = DRM(_mem_info)(buf, start, offset, len, eof, data);
+	spin_unlock(&DRM(mem_lock));
+	return ret;
+}
+
+void *DRM(alloc)(size_t size, int area)
+{
+	void *pt;
+
+	if (!size) {
+		DRM_MEM_ERROR(area, "Allocating 0 bytes\n");
+		return NULL;
+	}
+
+	if (!(pt = kmalloc(size, GFP_KERNEL))) {
+		spin_lock(&DRM(mem_lock));
+		++DRM(mem_stats)[area].fail_count;
+		spin_unlock(&DRM(mem_lock));
+		return NULL;
+	}
+	spin_lock(&DRM(mem_lock));
+	++DRM(mem_stats)[area].succeed_count;
+	DRM(mem_stats)[area].bytes_allocated += size;
+	spin_unlock(&DRM(mem_lock));
+	return pt;
+}
+
+void *DRM(calloc)(size_t nmemb, size_t size, int area)
+{
+	void *addr;
+
+	addr = DRM(alloc)(nmemb * size, area);
+	if (addr != NULL)
+		memset((void *)addr, 0, size * nmemb);
+
+	return addr;
+}
+
+void *DRM(realloc)(void *oldpt, size_t oldsize, size_t size, int area)
+{
+	void *pt;
+
+	if (!(pt = DRM(alloc)(size, area))) return NULL;
+	if (oldpt && oldsize) {
+		memcpy(pt, oldpt, oldsize);
+		DRM(free)(oldpt, oldsize, area);
+	}
+	return pt;
+}
+
+void DRM(free)(void *pt, size_t size, int area)
+{
+	int alloc_count;
+	int free_count;
+
+	if (!pt) DRM_MEM_ERROR(area, "Attempt to free NULL pointer\n");
+	else	 kfree(pt);
+	spin_lock(&DRM(mem_lock));
+	DRM(mem_stats)[area].bytes_freed += size;
+	free_count  = ++DRM(mem_stats)[area].free_count;
+	alloc_count =	DRM(mem_stats)[area].succeed_count;
+	spin_unlock(&DRM(mem_lock));
+	if (free_count > alloc_count) {
+		DRM_MEM_ERROR(area, "Excess frees: %d frees, %d allocs\n",
+			      free_count, alloc_count);
+	}
+}
+
+unsigned long DRM(alloc_pages)(int order, int area)
+{
+	unsigned long address;
+	unsigned long bytes	  = PAGE_SIZE << order;
+	unsigned long addr;
+	unsigned int  sz;
+
+	spin_lock(&DRM(mem_lock));
+	if ((DRM(ram_used) >> PAGE_SHIFT)
+	    > (DRM_RAM_PERCENT * DRM(ram_available)) / 100) {
+		spin_unlock(&DRM(mem_lock));
+		return 0;
+	}
+	spin_unlock(&DRM(mem_lock));
+
+	address = __get_free_pages(GFP_KERNEL, order);
+	if (!address) {
+		spin_lock(&DRM(mem_lock));
+		++DRM(mem_stats)[area].fail_count;
+		spin_unlock(&DRM(mem_lock));
+		return 0;
+	}
+	spin_lock(&DRM(mem_lock));
+	++DRM(mem_stats)[area].succeed_count;
+	DRM(mem_stats)[area].bytes_allocated += bytes;
+	DRM(ram_used)		             += bytes;
+	spin_unlock(&DRM(mem_lock));
+
+
+				/* Zero outside the lock */
+	memset((void *)address, 0, bytes);
+
+				/* Reserve */
+	for (addr = address, sz = bytes;
+	     sz > 0;
+	     addr += PAGE_SIZE, sz -= PAGE_SIZE) {
+		SetPageReserved(virt_to_page(addr));
+	}
+
+	return address;
+}
+
+void DRM(free_pages)(unsigned long address, int order, int area)
+{
+	unsigned long bytes = PAGE_SIZE << order;
+	int		  alloc_count;
+	int		  free_count;
+	unsigned long addr;
+	unsigned int  sz;
+
+	if (!address) {
+		DRM_MEM_ERROR(area, "Attempt to free address 0\n");
+	} else {
+				/* Unreserve */
+		for (addr = address, sz = bytes;
+		     sz > 0;
+		     addr += PAGE_SIZE, sz -= PAGE_SIZE) {
+			ClearPageReserved(virt_to_page(addr));
+		}
+		free_pages(address, order);
+	}
+
+	spin_lock(&DRM(mem_lock));
+	free_count  = ++DRM(mem_stats)[area].free_count;
+	alloc_count =	DRM(mem_stats)[area].succeed_count;
+	DRM(mem_stats)[area].bytes_freed += bytes;
+	DRM(ram_used)			 -= bytes;
+	spin_unlock(&DRM(mem_lock));
+	if (free_count > alloc_count) {
+		DRM_MEM_ERROR(area,
+			      "Excess frees: %d frees, %d allocs\n",
+			      free_count, alloc_count);
+	}
+}
+
+void *DRM(ioremap)(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+	void *pt;
+
+	if (!size) {
+		DRM_MEM_ERROR(DRM_MEM_MAPPINGS,
+			      "Mapping 0 bytes at 0x%08lx\n", offset);
+		return NULL;
+	}
+
+	if (!(pt = drm_ioremap(offset, size, dev))) {
+		spin_lock(&DRM(mem_lock));
+		++DRM(mem_stats)[DRM_MEM_MAPPINGS].fail_count;
+		spin_unlock(&DRM(mem_lock));
+		return NULL;
+	}
+	spin_lock(&DRM(mem_lock));
+	++DRM(mem_stats)[DRM_MEM_MAPPINGS].succeed_count;
+	DRM(mem_stats)[DRM_MEM_MAPPINGS].bytes_allocated += size;
+	spin_unlock(&DRM(mem_lock));
+	return pt;
+}
+
+void *DRM(ioremap_nocache)(unsigned long offset, unsigned long size, drm_device_t *dev)
+{
+	void *pt;
+
+	if (!size) {
+		DRM_MEM_ERROR(DRM_MEM_MAPPINGS,
+			      "Mapping 0 bytes at 0x%08lx\n", offset);
+		return NULL;
+	}
+
+	if (!(pt = drm_ioremap_nocache(offset, size, dev))) {
+		spin_lock(&DRM(mem_lock));
+		++DRM(mem_stats)[DRM_MEM_MAPPINGS].fail_count;
+		spin_unlock(&DRM(mem_lock));
+		return NULL;
+	}
+	spin_lock(&DRM(mem_lock));
+	++DRM(mem_stats)[DRM_MEM_MAPPINGS].succeed_count;
+	DRM(mem_stats)[DRM_MEM_MAPPINGS].bytes_allocated += size;
+	spin_unlock(&DRM(mem_lock));
+	return pt;
+}
+
+void DRM(ioremapfree)(void *pt, unsigned long size, drm_device_t *dev)
+{
+	int alloc_count;
+	int free_count;
+
+	if (!pt)
+		DRM_MEM_ERROR(DRM_MEM_MAPPINGS,
+			      "Attempt to free NULL pointer\n");
+	else
+		drm_ioremapfree(pt, size, dev);
+
+	spin_lock(&DRM(mem_lock));
+	DRM(mem_stats)[DRM_MEM_MAPPINGS].bytes_freed += size;
+	free_count  = ++DRM(mem_stats)[DRM_MEM_MAPPINGS].free_count;
+	alloc_count =	DRM(mem_stats)[DRM_MEM_MAPPINGS].succeed_count;
+	spin_unlock(&DRM(mem_lock));
+	if (free_count > alloc_count) {
+		DRM_MEM_ERROR(DRM_MEM_MAPPINGS,
+			      "Excess frees: %d frees, %d allocs\n",
+			      free_count, alloc_count);
+	}
+}
+
+#if __OS_HAS_AGP
+
+DRM_AGP_MEM *DRM(alloc_agp)(int pages, u32 type)
+{
+	DRM_AGP_MEM *handle;
+
+	if (!pages) {
+		DRM_MEM_ERROR(DRM_MEM_TOTALAGP, "Allocating 0 pages\n");
+		return NULL;
+	}
+
+	if ((handle = DRM(agp_allocate_memory)(pages, type))) {
+		spin_lock(&DRM(mem_lock));
+		++DRM(mem_stats)[DRM_MEM_TOTALAGP].succeed_count;
+		DRM(mem_stats)[DRM_MEM_TOTALAGP].bytes_allocated
+			+= pages << PAGE_SHIFT;
+		spin_unlock(&DRM(mem_lock));
+		return handle;
+	}
+	spin_lock(&DRM(mem_lock));
+	++DRM(mem_stats)[DRM_MEM_TOTALAGP].fail_count;
+	spin_unlock(&DRM(mem_lock));
+	return NULL;
+}
+
+int DRM(free_agp)(DRM_AGP_MEM *handle, int pages)
+{
+	int           alloc_count;
+	int           free_count;
+	int           retval = -EINVAL;
+
+	if (!handle) {
+		DRM_MEM_ERROR(DRM_MEM_TOTALAGP,
+			      "Attempt to free NULL AGP handle\n");
+		return retval;
+	}
+
+	if (DRM(agp_free_memory)(handle)) {
+		spin_lock(&DRM(mem_lock));
+		free_count  = ++DRM(mem_stats)[DRM_MEM_TOTALAGP].free_count;
+		alloc_count =   DRM(mem_stats)[DRM_MEM_TOTALAGP].succeed_count;
+		DRM(mem_stats)[DRM_MEM_TOTALAGP].bytes_freed
+			+= pages << PAGE_SHIFT;
+		spin_unlock(&DRM(mem_lock));
+		if (free_count > alloc_count) {
+			DRM_MEM_ERROR(DRM_MEM_TOTALAGP,
+				      "Excess frees: %d frees, %d allocs\n",
+				      free_count, alloc_count);
+		}
+		return 0;
+	}
+	return retval;
+}
+
+int DRM(bind_agp)(DRM_AGP_MEM *handle, unsigned int start)
+{
+	int retcode = -EINVAL;
+
+	if (!handle) {
+		DRM_MEM_ERROR(DRM_MEM_BOUNDAGP,
+			      "Attempt to bind NULL AGP handle\n");
+		return retcode;
+	}
+
+	if (!(retcode = DRM(agp_bind_memory)(handle, start))) {
+		spin_lock(&DRM(mem_lock));
+		++DRM(mem_stats)[DRM_MEM_BOUNDAGP].succeed_count;
+		DRM(mem_stats)[DRM_MEM_BOUNDAGP].bytes_allocated
+			+= handle->page_count << PAGE_SHIFT;
+		spin_unlock(&DRM(mem_lock));
+		return retcode;
+	}
+	spin_lock(&DRM(mem_lock));
+	++DRM(mem_stats)[DRM_MEM_BOUNDAGP].fail_count;
+	spin_unlock(&DRM(mem_lock));
+	return retcode;
+}
+
+int DRM(unbind_agp)(DRM_AGP_MEM *handle)
+{
+	int alloc_count;
+	int free_count;
+	int retcode = -EINVAL;
+
+	if (!handle) {
+		DRM_MEM_ERROR(DRM_MEM_BOUNDAGP,
+			      "Attempt to unbind NULL AGP handle\n");
+		return retcode;
+	}
+
+	if ((retcode = DRM(agp_unbind_memory)(handle))) return retcode;
+	spin_lock(&DRM(mem_lock));
+	free_count  = ++DRM(mem_stats)[DRM_MEM_BOUNDAGP].free_count;
+	alloc_count = DRM(mem_stats)[DRM_MEM_BOUNDAGP].succeed_count;
+	DRM(mem_stats)[DRM_MEM_BOUNDAGP].bytes_freed
+		+= handle->page_count << PAGE_SHIFT;
+	spin_unlock(&DRM(mem_lock));
+	if (free_count > alloc_count) {
+		DRM_MEM_ERROR(DRM_MEM_BOUNDAGP,
+			      "Excess frees: %d frees, %d allocs\n",
+			      free_count, alloc_count);
+	}
+	return retcode;
+}
+#endif
diff --git a/drivers/char/drm/drm_os_linux.h b/drivers/char/drm/drm_os_linux.h
new file mode 100644
index 0000000..b14cd37
--- /dev/null
+++ b/drivers/char/drm/drm_os_linux.h
@@ -0,0 +1,149 @@
+/**
+ * \file drm_os_linux.h
+ * OS abstraction macros.
+ */
+
+
+#include <linux/interrupt.h>	/* For task queue support */
+#include <linux/delay.h>
+
+/** File pointer type */
+#define DRMFILE                         struct file *
+/** Ioctl arguments */
+#define DRM_IOCTL_ARGS			struct inode *inode, struct file *filp, unsigned int cmd, unsigned long data
+#define DRM_ERR(d)			-(d)
+/** Current process ID */
+#define DRM_CURRENTPID			current->pid
+#define DRM_UDELAY(d)			udelay(d)
+/** Read a byte from a MMIO region */
+#define DRM_READ8(map, offset)		readb(((void __iomem *)(map)->handle) + (offset))
+/** Read a word from a MMIO region */
+#define DRM_READ16(map, offset)         readw(((void __iomem *)(map)->handle) + (offset))
+/** Read a dword from a MMIO region */
+#define DRM_READ32(map, offset)		readl(((void __iomem *)(map)->handle) + (offset))
+/** Write a byte into a MMIO region */
+#define DRM_WRITE8(map, offset, val)	writeb(val, ((void __iomem *)(map)->handle) + (offset))
+/** Write a word into a MMIO region */
+#define DRM_WRITE16(map, offset, val)   writew(val, ((void __iomem *)(map)->handle) + (offset))
+/** Write a dword into a MMIO region */
+#define DRM_WRITE32(map, offset, val)	writel(val, ((void __iomem *)(map)->handle) + (offset))
+/** Read memory barrier */
+#define DRM_READMEMORYBARRIER()		rmb()
+/** Write memory barrier */
+#define DRM_WRITEMEMORYBARRIER()	wmb()
+/** Read/write memory barrier */
+#define DRM_MEMORYBARRIER()		mb()
+/** DRM device local declaration */
+#define DRM_DEVICE	drm_file_t	*priv	= filp->private_data; \
+			drm_device_t	*dev	= priv->head->dev
+
+/** IRQ handler arguments and return type and values */
+#define DRM_IRQ_ARGS		int irq, void *arg, struct pt_regs *regs
+
+/** AGP types */
+#if __OS_HAS_AGP
+#define DRM_AGP_MEM		struct agp_memory
+#define DRM_AGP_KERN		struct agp_kern_info
+#else
+/* define some dummy types for non AGP supporting kernels */
+struct no_agp_kern {
+  unsigned long aper_base;
+  unsigned long aper_size;
+};
+#define DRM_AGP_MEM             int
+#define DRM_AGP_KERN            struct no_agp_kern
+#endif
+
+#if !(__OS_HAS_MTRR)
+static __inline__ int mtrr_add (unsigned long base, unsigned long size,
+                                unsigned int type, char increment)
+{
+	return -ENODEV;
+}
+
+static __inline__ int mtrr_del (int reg, unsigned long base,
+                                unsigned long size)
+{
+	return -ENODEV;
+}
+#define MTRR_TYPE_WRCOMB     1
+
+#endif
+
+/** Task queue handler arguments */
+#define DRM_TASKQUEUE_ARGS	void *arg
+
+/** For data going into the kernel through the ioctl argument */
+#define DRM_COPY_FROM_USER_IOCTL(arg1, arg2, arg3)	\
+	if ( copy_from_user(&arg1, arg2, arg3) )	\
+		return -EFAULT
+/** For data going from the kernel through the ioctl argument */
+#define DRM_COPY_TO_USER_IOCTL(arg1, arg2, arg3)	\
+	if ( copy_to_user(arg1, &arg2, arg3) )		\
+		return -EFAULT
+/** Other copying of data to kernel space */
+#define DRM_COPY_FROM_USER(arg1, arg2, arg3)		\
+	copy_from_user(arg1, arg2, arg3)
+/** Other copying of data from kernel space */
+#define DRM_COPY_TO_USER(arg1, arg2, arg3)		\
+	copy_to_user(arg1, arg2, arg3)
+/* Macros for copyfrom user, but checking readability only once */
+#define DRM_VERIFYAREA_READ( uaddr, size ) 		\
+	(access_ok( VERIFY_READ, uaddr, size ) ? 0 : -EFAULT)
+#define DRM_COPY_FROM_USER_UNCHECKED(arg1, arg2, arg3) 	\
+	__copy_from_user(arg1, arg2, arg3)
+#define DRM_COPY_TO_USER_UNCHECKED(arg1, arg2, arg3)	\
+	__copy_to_user(arg1, arg2, arg3)
+#define DRM_GET_USER_UNCHECKED(val, uaddr)		\
+	__get_user(val, uaddr)
+
+#define DRM_GET_PRIV_WITH_RETURN(_priv, _filp) _priv = _filp->private_data
+
+/** 
+ * Get the pointer to the SAREA.
+ *
+ * Searches the SAREA on the mapping lists and points drm_device::sarea to it.
+ */
+#define DRM_GETSAREA()							 \
+do { 									 \
+	drm_map_list_t *entry;						 \
+	list_for_each_entry( entry, &dev->maplist->head, head ) {	 \
+		if ( entry->map &&					 \
+		     entry->map->type == _DRM_SHM &&			 \
+		     (entry->map->flags & _DRM_CONTAINS_LOCK) ) {	 \
+			dev_priv->sarea = entry->map;			 \
+ 			break;						 \
+ 		}							 \
+ 	}								 \
+} while (0)
+
+#define DRM_HZ HZ
+
+#define DRM_WAIT_ON( ret, queue, timeout, condition )		\
+do {								\
+	DECLARE_WAITQUEUE(entry, current);			\
+	unsigned long end = jiffies + (timeout);		\
+	add_wait_queue(&(queue), &entry);			\
+								\
+	for (;;) {						\
+		__set_current_state(TASK_INTERRUPTIBLE);	\
+		if (condition)					\
+			break;					\
+		if (time_after_eq(jiffies, end)) {		\
+			ret = -EBUSY;				\
+			break;					\
+		}						\
+		schedule_timeout((HZ/100 > 1) ? HZ/100 : 1);	\
+		if (signal_pending(current)) {			\
+			ret = -EINTR;				\
+			break;					\
+		}						\
+	}							\
+	__set_current_state(TASK_RUNNING);			\
+	remove_wait_queue(&(queue), &entry);			\
+} while (0)
+
+
+#define DRM_WAKEUP( queue ) wake_up_interruptible( queue )
+#define DRM_INIT_WAITQUEUE( queue ) init_waitqueue_head( queue )
+ 
diff --git a/drivers/char/drm/drm_pci.c b/drivers/char/drm/drm_pci.c
new file mode 100644
index 0000000..192e876
--- /dev/null
+++ b/drivers/char/drm/drm_pci.c
@@ -0,0 +1,140 @@
+/* drm_pci.h -- PCI DMA memory management wrappers for DRM -*- linux-c -*- */
+/**
+ * \file drm_pci.c
+ * \brief Functions and ioctls to manage PCI memory
+ *
+ * \warning These interfaces aren't stable yet.
+ *
+ * \todo Implement the remaining ioctl's for the PCI pools.
+ * \todo The wrappers here are so thin that they would be better off inlined..
+ *
+ * \author Jose Fonseca <jrfonseca@tungstengraphics.com>
+ * \author Leif Delgass <ldelgass@retinalburn.net>
+ */
+
+/*
+ * Copyright 2003 Jos�Fonseca.
+ * Copyright 2003 Leif Delgass.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+ * AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/pci.h>
+#include "drmP.h"
+
+/**********************************************************************/
+/** \name PCI memory */
+/*@{*/
+
+/**
+ * \brief Allocate a PCI consistent memory block, for DMA.
+ */
+void *drm_pci_alloc(drm_device_t * dev, size_t size, size_t align,
+		    dma_addr_t maxaddr, dma_addr_t * busaddr)
+{
+	void *address;
+#if DRM_DEBUG_MEMORY
+	int area = DRM_MEM_DMA;
+
+	spin_lock(&drm_mem_lock);
+	if ((drm_ram_used >> PAGE_SHIFT)
+	    > (DRM_RAM_PERCENT * drm_ram_available) / 100) {
+		spin_unlock(&drm_mem_lock);
+		return 0;
+	}
+	spin_unlock(&drm_mem_lock);
+#endif
+
+	/* pci_alloc_consistent only guarantees alignment to the smallest
+	 * PAGE_SIZE order which is greater than or equal to the requested size.
+	 * Return NULL here for now to make sure nobody tries for larger alignment
+	 */
+	if (align > size)
+		return NULL;
+
+	if (pci_set_dma_mask(dev->pdev, maxaddr) != 0) {
+		DRM_ERROR("Setting pci dma mask failed\n");
+		return NULL;
+	}
+
+	address = pci_alloc_consistent(dev->pdev, size, busaddr);
+
+#if DRM_DEBUG_MEMORY
+	if (address == NULL) {
+		spin_lock(&drm_mem_lock);
+		++drm_mem_stats[area].fail_count;
+		spin_unlock(&drm_mem_lock);
+		return NULL;
+	}
+
+	spin_lock(&drm_mem_lock);
+	++drm_mem_stats[area].succeed_count;
+	drm_mem_stats[area].bytes_allocated += size;
+	drm_ram_used += size;
+	spin_unlock(&drm_mem_lock);
+#else
+	if (address == NULL)
+		return NULL;
+#endif
+
+	memset(address, 0, size);
+
+	return address;
+}
+EXPORT_SYMBOL(drm_pci_alloc);
+
+/**
+ * \brief Free a PCI consistent memory block.
+ */
+void
+drm_pci_free(drm_device_t * dev, size_t size, void *vaddr, dma_addr_t busaddr)
+{
+#if DRM_DEBUG_MEMORY
+	int area = DRM_MEM_DMA;
+	int alloc_count;
+	int free_count;
+#endif
+
+	if (!vaddr) {
+#if DRM_DEBUG_MEMORY
+		DRM_MEM_ERROR(area, "Attempt to free address 0\n");
+#endif
+	} else {
+		pci_free_consistent(dev->pdev, size, vaddr, busaddr);
+	}
+
+#if DRM_DEBUG_MEMORY
+	spin_lock(&drm_mem_lock);
+	free_count = ++drm_mem_stats[area].free_count;
+	alloc_count = drm_mem_stats[area].succeed_count;
+	drm_mem_stats[area].bytes_freed += size;
+	drm_ram_used -= size;
+	spin_unlock(&drm_mem_lock);
+	if (free_count > alloc_count) {
+		DRM_MEM_ERROR(area,
+			      "Excess frees: %d frees, %d allocs\n",
+			      free_count, alloc_count);
+	}
+#endif
+
+}
+EXPORT_SYMBOL(drm_pci_free);
+
+/*@}*/
diff --git a/drivers/char/drm/drm_pciids.h b/drivers/char/drm/drm_pciids.h
new file mode 100644
index 0000000..54a2914
--- /dev/null
+++ b/drivers/char/drm/drm_pciids.h
@@ -0,0 +1,224 @@
+/*
+   This file is auto-generated from the drm_pciids.txt in the DRM CVS
+   Please contact dri-devel@lists.sf.net to add new cards to this list
+*/
+#define radeon_PCI_IDS \
+	{0x1002, 0x4136, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS100|CHIP_IS_IGP}, \
+	{0x1002, 0x4137, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS200|CHIP_IS_IGP}, \
+	{0x1002, 0x4144, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \
+	{0x1002, 0x4145, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \
+	{0x1002, 0x4146, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \
+	{0x1002, 0x4147, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R300}, \
+	{0x1002, 0x4150, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+	{0x1002, 0x4151, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+	{0x1002, 0x4152, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+	{0x1002, 0x4153, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+	{0x1002, 0x4154, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+	{0x1002, 0x4156, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350}, \
+	{0x1002, 0x4237, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS250|CHIP_IS_IGP}, \
+	{0x1002, 0x4242, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x4243, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x4336, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS100|CHIP_IS_IGP|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4337, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS200|CHIP_IS_IGP|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4437, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS250|CHIP_IS_IGP|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4964, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250}, \
+	{0x1002, 0x4965, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250}, \
+	{0x1002, 0x4966, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250}, \
+	{0x1002, 0x4967, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250}, \
+	{0x1002, 0x4C57, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4C58, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4C59, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4C5A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4C64, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4C65, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4C66, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4C67, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R250|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x4E50, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV350|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x5144, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|CHIP_SINGLE_CRTC}, \
+	{0x1002, 0x5145, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|CHIP_SINGLE_CRTC}, \
+	{0x1002, 0x5146, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|CHIP_SINGLE_CRTC}, \
+	{0x1002, 0x5147, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R100|CHIP_SINGLE_CRTC}, \
+	{0x1002, 0x5148, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x5149, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x514A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x514B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x514C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x514D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x514E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x514F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x5157, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200}, \
+	{0x1002, 0x5158, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV200}, \
+	{0x1002, 0x5159, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \
+	{0x1002, 0x515A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \
+	{0x1002, 0x515E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \
+	{0x1002, 0x5168, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x5169, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x516A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x516B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x516C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_R200}, \
+	{0x1002, 0x5834, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|CHIP_IS_IGP}, \
+	{0x1002, 0x5835, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|CHIP_IS_IGP|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x5836, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|CHIP_IS_IGP}, \
+	{0x1002, 0x5837, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RS300|CHIP_IS_IGP}, \
+	{0x1002, 0x5960, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x5961, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x5962, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x5963, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x5964, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x5968, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x5969, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV100}, \
+	{0x1002, 0x596A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x596B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x5c61, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x5c62, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0x1002, 0x5c63, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280|CHIP_IS_MOBILITY}, \
+	{0x1002, 0x5c64, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CHIP_RV280}, \
+	{0, 0, 0}
+
+#define r128_PCI_IDS \
+	{0x1002, 0x4c45, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c46, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4d46, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4d4c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5041, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5042, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5043, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5044, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5045, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5046, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5047, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5048, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5049, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x504A, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x504B, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x504C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x504D, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x504E, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x504F, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5050, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5051, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5052, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5053, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5054, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5056, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5057, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5058, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5245, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5246, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5247, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x524b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x524c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x534d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5446, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x544C, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x5452, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define mga_PCI_IDS \
+	{0x102b, 0x0521, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x102b, 0x0525, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x102b, 0x2527, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define mach64_PCI_IDS \
+	{0x1002, 0x4749, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4750, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4751, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4742, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4744, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c49, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c50, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c51, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c42, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c44, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x474c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x474f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4752, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4753, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x474d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x474e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c52, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c53, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c4d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1002, 0x4c4e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define sisdrv_PCI_IDS \
+	{0x1039, 0x0300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1039, 0x5300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1039, 0x6300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1039, 0x7300, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define tdfx_PCI_IDS \
+	{0x121a, 0x0003, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x121a, 0x0004, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x121a, 0x0005, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x121a, 0x0007, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x121a, 0x0009, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x121a, 0x000b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define viadrv_PCI_IDS \
+	{0x1106, 0x3022, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1106, 0x3122, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1106, 0x7205, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x1106, 0x7204, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define i810_PCI_IDS \
+	{0x8086, 0x7121, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x7123, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x7125, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x1132, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define i830_PCI_IDS \
+	{0x8086, 0x3577, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x2562, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x3582, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x2572, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define gamma_PCI_IDS \
+	{0x3d3d, 0x0008, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define savage_PCI_IDS \
+	{0x5333, 0x8a22, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8a23, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c10, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c11, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c12, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c13, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c20, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c21, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c22, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c24, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c26, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c2a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c2b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c2c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c2d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c2e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8c2f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8a25, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8a26, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8d01, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8d02, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x5333, 0x8d04, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
+#define ffb_PCI_IDS \
+	{0, 0, 0}
+
+#define i915_PCI_IDS \
+	{0x8086, 0x3577, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x2562, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x3582, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x2572, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x2582, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0x8086, 0x2592, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \
+	{0, 0, 0}
+
diff --git a/drivers/char/drm/drm_proc.c b/drivers/char/drm/drm_proc.c
new file mode 100644
index 0000000..6e06e8c
--- /dev/null
+++ b/drivers/char/drm/drm_proc.c
@@ -0,0 +1,539 @@
+/**
+ * \file drm_proc.h 
+ * /proc support for DRM
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ *
+ * \par Acknowledgements:
+ *    Matthew J Sottek <matthew.j.sottek@intel.com> sent in a patch to fix
+ *    the problem with the proc files not outputting all their information.
+ */
+
+/*
+ * Created: Mon Jan 11 09:48:47 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+
+static int	   drm_name_info(char *buf, char **start, off_t offset,
+				  int request, int *eof, void *data);
+static int	   drm_vm_info(char *buf, char **start, off_t offset,
+				int request, int *eof, void *data);
+static int	   drm_clients_info(char *buf, char **start, off_t offset,
+				     int request, int *eof, void *data);
+static int	   drm_queues_info(char *buf, char **start, off_t offset,
+				    int request, int *eof, void *data);
+static int	   drm_bufs_info(char *buf, char **start, off_t offset,
+				  int request, int *eof, void *data);
+#if DRM_DEBUG_CODE
+static int	   drm_vma_info(char *buf, char **start, off_t offset,
+				 int request, int *eof, void *data);
+#endif
+
+/**
+ * Proc file list.
+ */
+struct drm_proc_list {
+	const char *name;	/**< file name */
+	int	   (*f)(char *, char **, off_t, int, int *, void *);	/**< proc callback*/
+} drm_proc_list[] = {
+	{ "name",    drm_name_info    },
+	{ "mem",     drm_mem_info     },
+	{ "vm",	     drm_vm_info      },
+	{ "clients", drm_clients_info },
+	{ "queues",  drm_queues_info  },
+	{ "bufs",    drm_bufs_info    },
+#if DRM_DEBUG_CODE
+	{ "vma",     drm_vma_info     },
+#endif
+};
+#define DRM_PROC_ENTRIES (sizeof(drm_proc_list)/sizeof(drm_proc_list[0]))
+
+/**
+ * Initialize the DRI proc filesystem for a device.
+ *
+ * \param dev DRM device.
+ * \param minor device minor number.
+ * \param root DRI proc dir entry.
+ * \param dev_root resulting DRI device proc dir entry.
+ * \return root entry pointer on success, or NULL on failure.
+ * 
+ * Create the DRI proc root entry "/proc/dri", the device proc root entry
+ * "/proc/dri/%minor%/", and each entry in proc_list as
+ * "/proc/dri/%minor%/%name%".
+ */
+int drm_proc_init(drm_device_t *dev, int minor,
+		    struct proc_dir_entry *root,
+		    struct proc_dir_entry **dev_root)
+{
+	struct proc_dir_entry *ent;
+	int		      i, j;
+	char                  name[64];
+
+	sprintf(name, "%d", minor);
+	*dev_root = create_proc_entry(name, S_IFDIR, root);
+	if (!*dev_root) {
+		DRM_ERROR("Cannot create /proc/dri/%s\n", name);
+		return -1;
+	}
+
+	for (i = 0; i < DRM_PROC_ENTRIES; i++) {
+		ent = create_proc_entry(drm_proc_list[i].name,
+					S_IFREG|S_IRUGO, *dev_root);
+		if (!ent) {
+			DRM_ERROR("Cannot create /proc/dri/%s/%s\n",
+				  name, drm_proc_list[i].name);
+			for (j = 0; j < i; j++)
+				remove_proc_entry(drm_proc_list[i].name,
+						  *dev_root);
+			remove_proc_entry(name, root);
+			return -1;
+		}
+		ent->read_proc = drm_proc_list[i].f;
+		ent->data      = dev;
+	}
+
+	return 0;
+}
+
+
+/**
+ * Cleanup the proc filesystem resources.
+ *
+ * \param minor device minor number.
+ * \param root DRI proc dir entry.
+ * \param dev_root DRI device proc dir entry.
+ * \return always zero.
+ *
+ * Remove all proc entries created by proc_init().
+ */
+int drm_proc_cleanup(int minor, struct proc_dir_entry *root,
+		      struct proc_dir_entry *dev_root)
+{
+	int  i;
+	char name[64];
+
+	if (!root || !dev_root) return 0;
+
+	for (i = 0; i < DRM_PROC_ENTRIES; i++)
+		remove_proc_entry(drm_proc_list[i].name, dev_root);
+	sprintf(name, "%d", minor);
+	remove_proc_entry(name, root);
+
+	return 0;
+}
+
+/**
+ * Called when "/proc/dri/.../name" is read.
+ * 
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ * 
+ * Prints the device name together with the bus id if available.
+ */
+static int drm_name_info(char *buf, char **start, off_t offset, int request,
+			  int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int          len  = 0;
+
+	if (offset > DRM_PROC_LIMIT) {
+		*eof = 1;
+		return 0;
+	}
+
+	*start = &buf[offset];
+	*eof   = 0;
+
+	if (dev->unique) {
+		DRM_PROC_PRINT("%s %s %s\n",
+			       dev->driver->pci_driver.name, pci_name(dev->pdev), dev->unique);
+	} else {
+		DRM_PROC_PRINT("%s %s\n", dev->driver->pci_driver.name, pci_name(dev->pdev));
+	}
+
+	if (len > request + offset) return request;
+	*eof = 1;
+	return len - offset;
+}
+
+/**
+ * Called when "/proc/dri/.../vm" is read.
+ * 
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ * 
+ * Prints information about all mappings in drm_device::maplist.
+ */
+static int drm__vm_info(char *buf, char **start, off_t offset, int request,
+			 int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int          len  = 0;
+	drm_map_t    *map;
+	drm_map_list_t *r_list;
+	struct list_head *list;
+
+				/* Hardcoded from _DRM_FRAME_BUFFER,
+                                   _DRM_REGISTERS, _DRM_SHM, _DRM_AGP, and
+                                   _DRM_SCATTER_GATHER. */
+	const char   *types[] = { "FB", "REG", "SHM", "AGP", "SG" };
+	const char   *type;
+	int	     i;
+
+	if (offset > DRM_PROC_LIMIT) {
+		*eof = 1;
+		return 0;
+	}
+
+	*start = &buf[offset];
+	*eof   = 0;
+
+	DRM_PROC_PRINT("slot	 offset	      size type flags	 "
+		       "address mtrr\n\n");
+	i = 0;
+	if (dev->maplist != NULL) list_for_each(list, &dev->maplist->head) {
+		r_list = list_entry(list, drm_map_list_t, head);
+		map = r_list->map;
+		if(!map) continue;
+		if (map->type < 0 || map->type > 4) type = "??";
+		else				    type = types[map->type];
+		DRM_PROC_PRINT("%4d 0x%08lx 0x%08lx %4.4s  0x%02x 0x%08lx ",
+			       i,
+			       map->offset,
+			       map->size,
+			       type,
+			       map->flags,
+			       (unsigned long)map->handle);
+		if (map->mtrr < 0) {
+			DRM_PROC_PRINT("none\n");
+		} else {
+			DRM_PROC_PRINT("%4d\n", map->mtrr);
+		}
+		i++;
+	}
+
+	if (len > request + offset) return request;
+	*eof = 1;
+	return len - offset;
+}
+
+/**
+ * Simply calls _vm_info() while holding the drm_device::struct_sem lock.
+ */
+static int drm_vm_info(char *buf, char **start, off_t offset, int request,
+			int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int	     ret;
+
+	down(&dev->struct_sem);
+	ret = drm__vm_info(buf, start, offset, request, eof, data);
+	up(&dev->struct_sem);
+	return ret;
+}
+
+/**
+ * Called when "/proc/dri/.../queues" is read.
+ * 
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ */
+static int drm__queues_info(char *buf, char **start, off_t offset,
+			     int request, int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int          len  = 0;
+	int	     i;
+	drm_queue_t  *q;
+
+	if (offset > DRM_PROC_LIMIT) {
+		*eof = 1;
+		return 0;
+	}
+
+	*start = &buf[offset];
+	*eof   = 0;
+
+	DRM_PROC_PRINT("  ctx/flags   use   fin"
+		       "   blk/rw/rwf  wait    flushed	   queued"
+		       "      locks\n\n");
+	for (i = 0; i < dev->queue_count; i++) {
+		q = dev->queuelist[i];
+		atomic_inc(&q->use_count);
+		DRM_PROC_PRINT_RET(atomic_dec(&q->use_count),
+				   "%5d/0x%03x %5d %5d"
+				   " %5d/%c%c/%c%c%c %5Zd\n",
+				   i,
+				   q->flags,
+				   atomic_read(&q->use_count),
+				   atomic_read(&q->finalization),
+				   atomic_read(&q->block_count),
+				   atomic_read(&q->block_read) ? 'r' : '-',
+				   atomic_read(&q->block_write) ? 'w' : '-',
+				   waitqueue_active(&q->read_queue) ? 'r':'-',
+				   waitqueue_active(&q->write_queue) ? 'w':'-',
+				   waitqueue_active(&q->flush_queue) ? 'f':'-',
+				   DRM_BUFCOUNT(&q->waitlist));
+		atomic_dec(&q->use_count);
+	}
+
+	if (len > request + offset) return request;
+	*eof = 1;
+	return len - offset;
+}
+
+/**
+ * Simply calls _queues_info() while holding the drm_device::struct_sem lock.
+ */
+static int drm_queues_info(char *buf, char **start, off_t offset, int request,
+			    int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int	     ret;
+
+	down(&dev->struct_sem);
+	ret = drm__queues_info(buf, start, offset, request, eof, data);
+	up(&dev->struct_sem);
+	return ret;
+}
+
+/**
+ * Called when "/proc/dri/.../bufs" is read.
+ * 
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ */
+static int drm__bufs_info(char *buf, char **start, off_t offset, int request,
+			   int *eof, void *data)
+{
+	drm_device_t	 *dev = (drm_device_t *)data;
+	int              len  = 0;
+	drm_device_dma_t *dma = dev->dma;
+	int		 i;
+
+	if (!dma || offset > DRM_PROC_LIMIT) {
+		*eof = 1;
+		return 0;
+	}
+
+	*start = &buf[offset];
+	*eof   = 0;
+
+	DRM_PROC_PRINT(" o     size count  free	 segs pages    kB\n\n");
+	for (i = 0; i <= DRM_MAX_ORDER; i++) {
+		if (dma->bufs[i].buf_count)
+			DRM_PROC_PRINT("%2d %8d %5d %5d %5d %5d %5ld\n",
+				       i,
+				       dma->bufs[i].buf_size,
+				       dma->bufs[i].buf_count,
+				       atomic_read(&dma->bufs[i]
+						   .freelist.count),
+				       dma->bufs[i].seg_count,
+				       dma->bufs[i].seg_count
+				       *(1 << dma->bufs[i].page_order),
+				       (dma->bufs[i].seg_count
+					* (1 << dma->bufs[i].page_order))
+				       * PAGE_SIZE / 1024);
+	}
+	DRM_PROC_PRINT("\n");
+	for (i = 0; i < dma->buf_count; i++) {
+		if (i && !(i%32)) DRM_PROC_PRINT("\n");
+		DRM_PROC_PRINT(" %d", dma->buflist[i]->list);
+	}
+	DRM_PROC_PRINT("\n");
+
+	if (len > request + offset) return request;
+	*eof = 1;
+	return len - offset;
+}
+
+/**
+ * Simply calls _bufs_info() while holding the drm_device::struct_sem lock.
+ */
+static int drm_bufs_info(char *buf, char **start, off_t offset, int request,
+			  int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int	     ret;
+
+	down(&dev->struct_sem);
+	ret = drm__bufs_info(buf, start, offset, request, eof, data);
+	up(&dev->struct_sem);
+	return ret;
+}
+
+/**
+ * Called when "/proc/dri/.../clients" is read.
+ * 
+ * \param buf output buffer.
+ * \param start start of output data.
+ * \param offset requested start offset.
+ * \param request requested number of bytes.
+ * \param eof whether there is no more data to return.
+ * \param data private data.
+ * \return number of written bytes.
+ */
+static int drm__clients_info(char *buf, char **start, off_t offset,
+			      int request, int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int          len  = 0;
+	drm_file_t   *priv;
+
+	if (offset > DRM_PROC_LIMIT) {
+		*eof = 1;
+		return 0;
+	}
+
+	*start = &buf[offset];
+	*eof   = 0;
+
+	DRM_PROC_PRINT("a dev	pid    uid	magic	  ioctls\n\n");
+	for (priv = dev->file_first; priv; priv = priv->next) {
+		DRM_PROC_PRINT("%c %3d %5d %5d %10u %10lu\n",
+			       priv->authenticated ? 'y' : 'n',
+			       priv->minor,
+			       priv->pid,
+			       priv->uid,
+			       priv->magic,
+			       priv->ioctl_count);
+	}
+
+	if (len > request + offset) return request;
+	*eof = 1;
+	return len - offset;
+}
+
+/**
+ * Simply calls _clients_info() while holding the drm_device::struct_sem lock.
+ */
+static int drm_clients_info(char *buf, char **start, off_t offset,
+			     int request, int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int	     ret;
+
+	down(&dev->struct_sem);
+	ret = drm__clients_info(buf, start, offset, request, eof, data);
+	up(&dev->struct_sem);
+	return ret;
+}
+
+#if DRM_DEBUG_CODE
+
+static int drm__vma_info(char *buf, char **start, off_t offset, int request,
+			  int *eof, void *data)
+{
+	drm_device_t	      *dev = (drm_device_t *)data;
+	int                   len  = 0;
+	drm_vma_entry_t	      *pt;
+	struct vm_area_struct *vma;
+#if defined(__i386__)
+	unsigned int	      pgprot;
+#endif
+
+	if (offset > DRM_PROC_LIMIT) {
+		*eof = 1;
+		return 0;
+	}
+
+	*start = &buf[offset];
+	*eof   = 0;
+
+	DRM_PROC_PRINT("vma use count: %d, high_memory = %p, 0x%08lx\n",
+		       atomic_read(&dev->vma_count),
+		       high_memory, virt_to_phys(high_memory));
+	for (pt = dev->vmalist; pt; pt = pt->next) {
+		if (!(vma = pt->vma)) continue;
+		DRM_PROC_PRINT("\n%5d 0x%08lx-0x%08lx %c%c%c%c%c%c 0x%08lx",
+			       pt->pid,
+			       vma->vm_start,
+			       vma->vm_end,
+			       vma->vm_flags & VM_READ	   ? 'r' : '-',
+			       vma->vm_flags & VM_WRITE	   ? 'w' : '-',
+			       vma->vm_flags & VM_EXEC	   ? 'x' : '-',
+			       vma->vm_flags & VM_MAYSHARE ? 's' : 'p',
+			       vma->vm_flags & VM_LOCKED   ? 'l' : '-',
+			       vma->vm_flags & VM_IO	   ? 'i' : '-',
+			       VM_OFFSET(vma));
+
+#if defined(__i386__)
+		pgprot = pgprot_val(vma->vm_page_prot);
+		DRM_PROC_PRINT(" %c%c%c%c%c%c%c%c%c",
+			       pgprot & _PAGE_PRESENT  ? 'p' : '-',
+			       pgprot & _PAGE_RW       ? 'w' : 'r',
+			       pgprot & _PAGE_USER     ? 'u' : 's',
+			       pgprot & _PAGE_PWT      ? 't' : 'b',
+			       pgprot & _PAGE_PCD      ? 'u' : 'c',
+			       pgprot & _PAGE_ACCESSED ? 'a' : '-',
+			       pgprot & _PAGE_DIRTY    ? 'd' : '-',
+			       pgprot & _PAGE_PSE      ? 'm' : 'k',
+			       pgprot & _PAGE_GLOBAL   ? 'g' : 'l' );
+#endif
+		DRM_PROC_PRINT("\n");
+	}
+
+	if (len > request + offset) return request;
+	*eof = 1;
+	return len - offset;
+}
+
+static int drm_vma_info(char *buf, char **start, off_t offset, int request,
+			 int *eof, void *data)
+{
+	drm_device_t *dev = (drm_device_t *)data;
+	int	     ret;
+
+	down(&dev->struct_sem);
+	ret = drm__vma_info(buf, start, offset, request, eof, data);
+	up(&dev->struct_sem);
+	return ret;
+}
+#endif
+
+
diff --git a/drivers/char/drm/drm_sarea.h b/drivers/char/drm/drm_sarea.h
new file mode 100644
index 0000000..de782ed
--- /dev/null
+++ b/drivers/char/drm/drm_sarea.h
@@ -0,0 +1,78 @@
+/**
+ * \file drm_sarea.h 
+ * \brief SAREA definitions
+ *
+ * \author Michel Dänzer <michel@daenzer.net>
+ */
+
+/*
+ * Copyright 2002 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _DRM_SAREA_H_
+#define _DRM_SAREA_H_
+
+#include "drm.h"
+
+/* SAREA area needs to be at least a page */
+#if defined(__alpha__)
+#define SAREA_MAX                       0x2000
+#elif defined(__ia64__)
+#define SAREA_MAX                       0x10000         /* 64kB */
+#else
+/* Intel 830M driver needs at least 8k SAREA */
+#define SAREA_MAX                       0x2000
+#endif
+
+/** Maximum number of drawables in the SAREA */
+#define SAREA_MAX_DRAWABLES 		256
+
+#define SAREA_DRAWABLE_CLAIMED_ENTRY    0x80000000
+
+/** SAREA drawable */
+typedef struct drm_sarea_drawable {
+    unsigned int	stamp;
+    unsigned int	flags;
+} drm_sarea_drawable_t;
+
+/** SAREA frame */
+typedef struct drm_sarea_frame {
+    unsigned int        x;
+    unsigned int        y;
+    unsigned int        width;
+    unsigned int        height;
+    unsigned int        fullscreen;
+} drm_sarea_frame_t;
+
+/** SAREA */
+typedef struct drm_sarea {
+    /** first thing is always the DRM locking structure */
+    drm_hw_lock_t		lock;
+    /** \todo Use readers/writer lock for drm_sarea::drawable_lock */
+    drm_hw_lock_t		drawable_lock;
+    drm_sarea_drawable_t	drawableTable[SAREA_MAX_DRAWABLES];	/**< drawables */
+    drm_sarea_frame_t		frame;	/**< frame */
+    drm_context_t		dummy_context;
+} drm_sarea_t;
+
+#endif	/* _DRM_SAREA_H_ */
diff --git a/drivers/char/drm/drm_scatter.c b/drivers/char/drm/drm_scatter.c
new file mode 100644
index 0000000..54fddb6
--- /dev/null
+++ b/drivers/char/drm/drm_scatter.c
@@ -0,0 +1,231 @@
+/**
+ * \file drm_scatter.h 
+ * IOCTLs to manage scatter/gather memory
+ *
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Mon Dec 18 23:20:54 2000 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/config.h>
+#include <linux/vmalloc.h>
+#include "drmP.h"
+
+#define DEBUG_SCATTER 0
+
+void drm_sg_cleanup( drm_sg_mem_t *entry )
+{
+	struct page *page;
+	int i;
+
+	for ( i = 0 ; i < entry->pages ; i++ ) {
+		page = entry->pagelist[i];
+		if ( page )
+			ClearPageReserved( page );
+	}
+
+	vfree( entry->virtual );
+
+	drm_free( entry->busaddr,
+		   entry->pages * sizeof(*entry->busaddr),
+		   DRM_MEM_PAGES );
+	drm_free( entry->pagelist,
+		   entry->pages * sizeof(*entry->pagelist),
+		   DRM_MEM_PAGES );
+	drm_free( entry,
+		   sizeof(*entry),
+		   DRM_MEM_SGLISTS );
+}
+
+int drm_sg_alloc( struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_scatter_gather_t __user *argp = (void __user *)arg;
+	drm_scatter_gather_t request;
+	drm_sg_mem_t *entry;
+	unsigned long pages, i, j;
+
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	if (!drm_core_check_feature(dev, DRIVER_SG))
+		return -EINVAL;
+
+	if ( dev->sg )
+		return -EINVAL;
+
+	if ( copy_from_user( &request, argp, sizeof(request) ) )
+		return -EFAULT;
+
+	entry = drm_alloc( sizeof(*entry), DRM_MEM_SGLISTS );
+	if ( !entry )
+		return -ENOMEM;
+
+   	memset( entry, 0, sizeof(*entry) );
+
+	pages = (request.size + PAGE_SIZE - 1) / PAGE_SIZE;
+	DRM_DEBUG( "sg size=%ld pages=%ld\n", request.size, pages );
+
+	entry->pages = pages;
+	entry->pagelist = drm_alloc( pages * sizeof(*entry->pagelist),
+				     DRM_MEM_PAGES );
+	if ( !entry->pagelist ) {
+		drm_free( entry, sizeof(*entry), DRM_MEM_SGLISTS );
+		return -ENOMEM;
+	}
+
+	memset(entry->pagelist, 0, pages * sizeof(*entry->pagelist));
+
+	entry->busaddr = drm_alloc( pages * sizeof(*entry->busaddr),
+				     DRM_MEM_PAGES );
+	if ( !entry->busaddr ) {
+		drm_free( entry->pagelist,
+			   entry->pages * sizeof(*entry->pagelist),
+			   DRM_MEM_PAGES );
+		drm_free( entry,
+			   sizeof(*entry),
+			   DRM_MEM_SGLISTS );
+		return -ENOMEM;
+	}
+	memset( (void *)entry->busaddr, 0, pages * sizeof(*entry->busaddr) );
+
+	entry->virtual = vmalloc_32( pages << PAGE_SHIFT );
+	if ( !entry->virtual ) {
+		drm_free( entry->busaddr,
+			   entry->pages * sizeof(*entry->busaddr),
+			   DRM_MEM_PAGES );
+		drm_free( entry->pagelist,
+			   entry->pages * sizeof(*entry->pagelist),
+			   DRM_MEM_PAGES );
+		drm_free( entry,
+			   sizeof(*entry),
+			   DRM_MEM_SGLISTS );
+		return -ENOMEM;
+	}
+
+	/* This also forces the mapping of COW pages, so our page list
+	 * will be valid.  Please don't remove it...
+	 */
+	memset( entry->virtual, 0, pages << PAGE_SHIFT );
+
+	entry->handle = (unsigned long)entry->virtual;
+
+	DRM_DEBUG( "sg alloc handle  = %08lx\n", entry->handle );
+	DRM_DEBUG( "sg alloc virtual = %p\n", entry->virtual );
+
+	for ( i = entry->handle, j = 0 ; j < pages ; i += PAGE_SIZE, j++ ) {
+		entry->pagelist[j] = vmalloc_to_page((void *)i);
+		if (!entry->pagelist[j])
+			goto failed;
+		SetPageReserved(entry->pagelist[j]);
+	}
+
+	request.handle = entry->handle;
+
+	if ( copy_to_user( argp, &request, sizeof(request) ) ) {
+		drm_sg_cleanup( entry );
+		return -EFAULT;
+	}
+
+	dev->sg = entry;
+
+#if DEBUG_SCATTER
+	/* Verify that each page points to its virtual address, and vice
+	 * versa.
+	 */
+	{
+	int error = 0;
+
+	for ( i = 0 ; i < pages ; i++ ) {
+		unsigned long *tmp;
+
+		tmp = page_address( entry->pagelist[i] );
+		for ( j = 0 ;
+		      j < PAGE_SIZE / sizeof(unsigned long) ;
+		      j++, tmp++ ) {
+			*tmp = 0xcafebabe;
+		}
+		tmp = (unsigned long *)((u8 *)entry->virtual +
+					(PAGE_SIZE * i));
+		for( j = 0 ;
+		     j < PAGE_SIZE / sizeof(unsigned long) ;
+		     j++, tmp++ ) {
+			if ( *tmp != 0xcafebabe && error == 0 ) {
+				error = 1;
+				DRM_ERROR( "Scatter allocation error, "
+					   "pagelist does not match "
+					   "virtual mapping\n" );
+			}
+		}
+		tmp = page_address( entry->pagelist[i] );
+		for(j = 0 ;
+		    j < PAGE_SIZE / sizeof(unsigned long) ;
+		    j++, tmp++) {
+			*tmp = 0;
+		}
+	}
+	if (error == 0)
+		DRM_ERROR( "Scatter allocation matches pagelist\n" );
+	}
+#endif
+
+	return 0;
+
+ failed:
+	drm_sg_cleanup( entry );
+	return -ENOMEM;
+}
+
+int drm_sg_free( struct inode *inode, struct file *filp,
+		 unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_scatter_gather_t request;
+	drm_sg_mem_t *entry;
+
+	if (!drm_core_check_feature(dev, DRIVER_SG))
+		return -EINVAL;
+
+	if ( copy_from_user( &request,
+			     (drm_scatter_gather_t __user *)arg,
+			     sizeof(request) ) )
+		return -EFAULT;
+
+	entry = dev->sg;
+	dev->sg = NULL;
+
+	if ( !entry || entry->handle != request.handle )
+		return -EINVAL;
+
+	DRM_DEBUG( "sg free virtual  = %p\n", entry->virtual );
+
+	drm_sg_cleanup( entry );
+
+	return 0;
+}
diff --git a/drivers/char/drm/drm_stub.c b/drivers/char/drm/drm_stub.c
new file mode 100644
index 0000000..8ccbdef
--- /dev/null
+++ b/drivers/char/drm/drm_stub.c
@@ -0,0 +1,319 @@
+/**
+ * \file drm_stub.h
+ * Stub support
+ *
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ */
+
+/*
+ * Created: Fri Jan 19 10:48:35 2001 by faith@acm.org
+ *
+ * Copyright 2001 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include "drmP.h"
+#include "drm_core.h"
+
+unsigned int drm_cards_limit = 16;	/* Enough for one machine */
+unsigned int drm_debug = 0;		/* 1 to enable debug output */
+EXPORT_SYMBOL(drm_debug);
+
+MODULE_AUTHOR( CORE_AUTHOR );
+MODULE_DESCRIPTION( CORE_DESC );
+MODULE_LICENSE("GPL and additional rights");
+MODULE_PARM_DESC(cards_limit, "Maximum number of graphics cards");
+MODULE_PARM_DESC(debug, "Enable debug output");
+
+module_param_named(cards_limit, drm_cards_limit, int, 0444);
+module_param_named(debug, drm_debug, int, 0666);
+
+drm_head_t **drm_heads;
+struct drm_sysfs_class *drm_class;
+struct proc_dir_entry *drm_proc_root;
+
+static int drm_fill_in_dev(drm_device_t *dev, struct pci_dev *pdev, const struct pci_device_id *ent, struct drm_driver *driver)
+{
+	int retcode;
+
+	spin_lock_init(&dev->count_lock);
+	init_timer( &dev->timer );
+	sema_init( &dev->struct_sem, 1 );
+	sema_init( &dev->ctxlist_sem, 1 );
+
+	dev->pdev   = pdev;
+
+#ifdef __alpha__
+	dev->hose   = pdev->sysdata;
+	dev->pci_domain = dev->hose->bus->number;
+#else
+	dev->pci_domain = 0;
+#endif
+	dev->pci_bus = pdev->bus->number;
+	dev->pci_slot = PCI_SLOT(pdev->devfn);
+	dev->pci_func = PCI_FUNC(pdev->devfn);
+	dev->irq = pdev->irq;
+
+	/* the DRM has 6 basic counters */
+	dev->counters = 6;
+	dev->types[0]  = _DRM_STAT_LOCK;
+	dev->types[1]  = _DRM_STAT_OPENS;
+	dev->types[2]  = _DRM_STAT_CLOSES;
+	dev->types[3]  = _DRM_STAT_IOCTLS;
+	dev->types[4]  = _DRM_STAT_LOCKS;
+	dev->types[5]  = _DRM_STAT_UNLOCKS;
+
+	dev->driver = driver;
+	
+	if (dev->driver->preinit)
+		if ((retcode = dev->driver->preinit(dev, ent->driver_data)))
+			goto error_out_unreg;
+
+	if (drm_core_has_AGP(dev)) {
+		dev->agp = drm_agp_init(dev);
+		if (drm_core_check_feature(dev, DRIVER_REQUIRE_AGP) && (dev->agp == NULL)) {
+			DRM_ERROR( "Cannot initialize the agpgart module.\n" );
+			retcode = -EINVAL;
+			goto error_out_unreg;
+		}
+		if (drm_core_has_MTRR(dev)) {
+			if (dev->agp)
+				dev->agp->agp_mtrr = mtrr_add( dev->agp->agp_info.aper_base,
+							       dev->agp->agp_info.aper_size*1024*1024,
+							       MTRR_TYPE_WRCOMB,
+							       1 );
+		}
+	}
+
+	retcode = drm_ctxbitmap_init( dev );
+	if( retcode ) {
+		DRM_ERROR( "Cannot allocate memory for context bitmap.\n" );
+		goto error_out_unreg;
+	}
+
+	return 0;
+	
+error_out_unreg:
+	drm_takedown(dev);
+	return retcode;
+}
+
+/**
+ * File \c open operation.
+ *
+ * \param inode device inode.
+ * \param filp file pointer.
+ *
+ * Puts the dev->fops corresponding to the device minor number into
+ * \p filp, call the \c open method, and restore the file operations.
+ */
+int drm_stub_open(struct inode *inode, struct file *filp)
+{
+	drm_device_t *dev = NULL;
+	int minor = iminor(inode);
+	int err = -ENODEV;
+	struct file_operations *old_fops;
+	
+	DRM_DEBUG("\n");
+
+	if (!((minor >= 0) && (minor < drm_cards_limit)))
+		return -ENODEV;
+
+	if (!drm_heads[minor])
+		return -ENODEV;
+	
+	if (!(dev = drm_heads[minor]->dev))
+		return -ENODEV;
+
+	old_fops = filp->f_op;
+	filp->f_op = fops_get(&dev->driver->fops);
+	if (filp->f_op->open && (err = filp->f_op->open(inode, filp))) {
+		fops_put(filp->f_op);
+		filp->f_op = fops_get(old_fops);
+	}
+	fops_put(old_fops);
+
+	return err;
+}
+
+
+/**
+ * Register.
+ *
+ * \param pdev - PCI device structure
+ * \param ent entry from the PCI ID table with device type flags
+ * \return zero on success or a negative number on failure.
+ *
+ * Attempt to gets inter module "drm" information. If we are first
+ * then register the character device and inter module information.
+ * Try and register, if we fail to register, backout previous work.
+ */
+int drm_get_dev(struct pci_dev *pdev, const struct pci_device_id *ent,
+	      struct drm_driver *driver)
+{
+	drm_device_t *dev;
+	int ret;
+
+	DRM_DEBUG("\n");
+
+	dev = drm_calloc(1, sizeof(*dev), DRM_MEM_STUB);
+	if (!dev)
+		return -ENOMEM;
+
+	pci_enable_device(pdev);
+
+	if ((ret = drm_fill_in_dev(dev, pdev, ent, driver))) {
+		printk(KERN_ERR "DRM: Fill_in_dev failed.\n");
+		goto err_g1;
+	}
+	if ((ret = drm_get_head(dev, &dev->primary)))
+		goto err_g1;
+
+	/* postinit is a required function to display the signon banner */
+	/* drivers add secondary heads here if needed */
+	if ((ret = dev->driver->postinit(dev, ent->driver_data)))
+		goto err_g1;
+
+	return 0;
+
+err_g1:
+	drm_free(dev, sizeof(*dev), DRM_MEM_STUB);
+	return ret;
+}
+EXPORT_SYMBOL(drm_get_dev);
+
+/**
+ * Get a secondary minor number.
+ *
+ * \param dev device data structure
+ * \param sec-minor structure to hold the assigned minor
+ * \return negative number on failure.
+ *
+ * Search an empty entry and initialize it to the given parameters, and
+ * create the proc init entry via proc_init(). This routines assigns
+ * minor numbers to secondary heads of multi-headed cards
+ */
+int drm_get_head(drm_device_t *dev, drm_head_t *head)
+{
+	drm_head_t **heads = drm_heads;
+	int ret;
+	int minor;
+
+	DRM_DEBUG("\n");
+
+	for (minor = 0; minor < drm_cards_limit; minor++, heads++) {
+		if (!*heads) {
+			
+			*head = (drm_head_t) {
+				.dev = dev,
+				.device = MKDEV(DRM_MAJOR, minor),
+				.minor = minor,
+			};
+			
+			if ((ret = drm_proc_init(dev, minor, drm_proc_root, &head->dev_root))) {
+				printk (KERN_ERR "DRM: Failed to initialize /proc/dri.\n");
+				goto err_g1;
+			}
+
+			
+			head->dev_class = drm_sysfs_device_add(drm_class,
+							       MKDEV(DRM_MAJOR,
+								     minor),
+							       &dev->pdev->dev,
+							       "card%d", minor);
+			if (IS_ERR(head->dev_class)) {
+				printk(KERN_ERR "DRM: Error sysfs_device_add.\n");
+				ret = PTR_ERR(head->dev_class);
+				goto err_g2;
+			}
+			*heads = head;
+
+			DRM_DEBUG("new minor assigned %d\n", minor);
+			return 0;
+		}
+	}
+	DRM_ERROR("out of minors\n");
+	return -ENOMEM;
+err_g2:
+	drm_proc_cleanup(minor, drm_proc_root, head->dev_root);
+err_g1:
+	*head = (drm_head_t) {.dev = NULL};
+	return ret;
+}
+		
+
+/**
+ * Put a device minor number.
+ *
+ * \param dev device data structure
+ * \return always zero
+ *
+ * Cleans up the proc resources. If it is the last minor then release the foreign
+ * "drm" data, otherwise unregisters the "drm" data, frees the dev list and
+ * unregisters the character device.
+ */
+int drm_put_dev(drm_device_t * dev)
+{
+	DRM_DEBUG("release primary %s\n", dev->driver->pci_driver.name);
+
+	if (dev->unique) {
+		drm_free(dev->unique, strlen(dev->unique) + 1, DRM_MEM_DRIVER);
+		dev->unique = NULL;
+		dev->unique_len = 0;
+	}
+	if (dev->devname) {
+		drm_free(dev->devname, strlen(dev->devname) + 1,
+			 DRM_MEM_DRIVER);
+		dev->devname = NULL;
+	}
+	drm_free(dev, sizeof(*dev), DRM_MEM_STUB);
+	return 0;
+}
+
+/**
+ * Put a secondary minor number.
+ *
+ * \param sec_minor - structure to be released
+ * \return always zero
+ *
+ * Cleans up the proc resources. Not legal for this to be the
+ * last minor released.
+ *
+ */
+int drm_put_head(drm_head_t *head)
+{
+	int minor = head->minor;
+	
+	DRM_DEBUG("release secondary minor %d\n", minor);
+	
+	drm_proc_cleanup(minor, drm_proc_root, head->dev_root);
+	drm_sysfs_device_remove(MKDEV(DRM_MAJOR, head->minor));
+	
+	*head = (drm_head_t){.dev = NULL};
+
+	drm_heads[minor] = NULL;
+	
+	return 0;
+}
+
diff --git a/drivers/char/drm/drm_sysfs.c b/drivers/char/drm/drm_sysfs.c
new file mode 100644
index 0000000..2fc10c4
--- /dev/null
+++ b/drivers/char/drm/drm_sysfs.c
@@ -0,0 +1,208 @@
+/*
+ * drm_sysfs.c - Modifications to drm_sysfs_class.c to support
+ *               extra sysfs attribute from DRM. Normal drm_sysfs_class
+ *               does not allow adding attributes.
+ *
+ * Copyright (c) 2004 Jon Smirl <jonsmirl@gmail.com>
+ * Copyright (c) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (c) 2003-2004 IBM Corp.
+ *
+ * This file is released under the GPLv2
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/device.h>
+#include <linux/kdev_t.h>
+#include <linux/err.h>
+
+#include "drm_core.h"
+
+struct drm_sysfs_class {
+	struct class_device_attribute attr;
+	struct class class;
+};
+#define to_drm_sysfs_class(d) container_of(d, struct drm_sysfs_class, class)
+
+struct simple_dev {
+	struct list_head node;
+	dev_t dev;
+	struct class_device class_dev;
+};
+#define to_simple_dev(d) container_of(d, struct simple_dev, class_dev)
+
+static LIST_HEAD(simple_dev_list);
+static DEFINE_SPINLOCK(simple_dev_list_lock);
+
+static void release_simple_dev(struct class_device *class_dev)
+{
+	struct simple_dev *s_dev = to_simple_dev(class_dev);
+	kfree(s_dev);
+}
+
+static ssize_t show_dev(struct class_device *class_dev, char *buf)
+{
+	struct simple_dev *s_dev = to_simple_dev(class_dev);
+	return print_dev_t(buf, s_dev->dev);
+}
+
+static void drm_sysfs_class_release(struct class *class)
+{
+	struct drm_sysfs_class *cs = to_drm_sysfs_class(class);
+	kfree(cs);
+}
+
+/* Display the version of drm_core. This doesn't work right in current design */
+static ssize_t version_show(struct class *dev, char *buf)
+{
+	return sprintf(buf, "%s %d.%d.%d %s\n", CORE_NAME, CORE_MAJOR,
+		       CORE_MINOR, CORE_PATCHLEVEL, CORE_DATE);
+}
+
+static CLASS_ATTR(version, S_IRUGO, version_show, NULL);
+
+/**
+ * drm_sysfs_create - create a struct drm_sysfs_class structure
+ * @owner: pointer to the module that is to "own" this struct drm_sysfs_class
+ * @name: pointer to a string for the name of this class.
+ *
+ * This is used to create a struct drm_sysfs_class pointer that can then be used
+ * in calls to drm_sysfs_device_add().
+ *
+ * Note, the pointer created here is to be destroyed when finished by making a
+ * call to drm_sysfs_destroy().
+ */
+struct drm_sysfs_class *drm_sysfs_create(struct module *owner, char *name)
+{
+	struct drm_sysfs_class *cs;
+	int retval;
+
+	cs = kmalloc(sizeof(*cs), GFP_KERNEL);
+	if (!cs) {
+		retval = -ENOMEM;
+		goto error;
+	}
+	memset(cs, 0x00, sizeof(*cs));
+
+	cs->class.name = name;
+	cs->class.class_release = drm_sysfs_class_release;
+	cs->class.release = release_simple_dev;
+
+	cs->attr.attr.name = "dev";
+	cs->attr.attr.mode = S_IRUGO;
+	cs->attr.attr.owner = owner;
+	cs->attr.show = show_dev;
+	cs->attr.store = NULL;
+
+	retval = class_register(&cs->class);
+	if (retval)
+		goto error;
+	class_create_file(&cs->class, &class_attr_version);
+
+	return cs;
+
+      error:
+	kfree(cs);
+	return ERR_PTR(retval);
+}
+
+/**
+ * drm_sysfs_destroy - destroys a struct drm_sysfs_class structure
+ * @cs: pointer to the struct drm_sysfs_class that is to be destroyed
+ *
+ * Note, the pointer to be destroyed must have been created with a call to
+ * drm_sysfs_create().
+ */
+void drm_sysfs_destroy(struct drm_sysfs_class *cs)
+{
+	if ((cs == NULL) || (IS_ERR(cs)))
+		return;
+
+	class_unregister(&cs->class);
+}
+
+/**
+ * drm_sysfs_device_add - adds a class device to sysfs for a character driver
+ * @cs: pointer to the struct drm_sysfs_class that this device should be registered to.
+ * @dev: the dev_t for the device to be added.
+ * @device: a pointer to a struct device that is assiociated with this class device.
+ * @fmt: string for the class device's name
+ *
+ * A struct class_device will be created in sysfs, registered to the specified
+ * class.  A "dev" file will be created, showing the dev_t for the device.  The
+ * pointer to the struct class_device will be returned from the call.  Any further
+ * sysfs files that might be required can be created using this pointer.
+ * Note: the struct drm_sysfs_class passed to this function must have previously been
+ * created with a call to drm_sysfs_create().
+ */
+struct class_device *drm_sysfs_device_add(struct drm_sysfs_class *cs, dev_t dev,
+					  struct device *device,
+					  const char *fmt, ...)
+{
+	va_list args;
+	struct simple_dev *s_dev = NULL;
+	int retval;
+
+	if ((cs == NULL) || (IS_ERR(cs))) {
+		retval = -ENODEV;
+		goto error;
+	}
+
+	s_dev = kmalloc(sizeof(*s_dev), GFP_KERNEL);
+	if (!s_dev) {
+		retval = -ENOMEM;
+		goto error;
+	}
+	memset(s_dev, 0x00, sizeof(*s_dev));
+
+	s_dev->dev = dev;
+	s_dev->class_dev.dev = device;
+	s_dev->class_dev.class = &cs->class;
+
+	va_start(args, fmt);
+	vsnprintf(s_dev->class_dev.class_id, BUS_ID_SIZE, fmt, args);
+	va_end(args);
+	retval = class_device_register(&s_dev->class_dev);
+	if (retval)
+		goto error;
+
+	class_device_create_file(&s_dev->class_dev, &cs->attr);
+
+	spin_lock(&simple_dev_list_lock);
+	list_add(&s_dev->node, &simple_dev_list);
+	spin_unlock(&simple_dev_list_lock);
+
+	return &s_dev->class_dev;
+
+      error:
+	kfree(s_dev);
+	return ERR_PTR(retval);
+}
+
+/**
+ * drm_sysfs_device_remove - removes a class device that was created with drm_sysfs_device_add()
+ * @dev: the dev_t of the device that was previously registered.
+ *
+ * This call unregisters and cleans up a class device that was created with a
+ * call to drm_sysfs_device_add()
+ */
+void drm_sysfs_device_remove(dev_t dev)
+{
+	struct simple_dev *s_dev = NULL;
+	int found = 0;
+
+	spin_lock(&simple_dev_list_lock);
+	list_for_each_entry(s_dev, &simple_dev_list, node) {
+		if (s_dev->dev == dev) {
+			found = 1;
+			break;
+		}
+	}
+	if (found) {
+		list_del(&s_dev->node);
+		spin_unlock(&simple_dev_list_lock);
+		class_device_unregister(&s_dev->class_dev);
+	} else {
+		spin_unlock(&simple_dev_list_lock);
+	}
+}
diff --git a/drivers/char/drm/drm_vm.c b/drivers/char/drm/drm_vm.c
new file mode 100644
index 0000000..fc72f30
--- /dev/null
+++ b/drivers/char/drm/drm_vm.c
@@ -0,0 +1,678 @@
+/**
+ * \file drm_vm.h
+ * Memory mapping for DRM
+ * 
+ * \author Rickard E. (Rik) Faith <faith@valinux.com>
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Created: Mon Jan  4 08:58:31 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "drmP.h"
+#if defined(__ia64__)
+#include <linux/efi.h>
+#endif
+
+
+/**
+ * \c nopage method for AGP virtual memory.
+ *
+ * \param vma virtual memory area.
+ * \param address access address.
+ * \return pointer to the page structure.
+ * 
+ * Find the right map and if it's AGP memory find the real physical page to
+ * map, get the page, increment the use count and return it.
+ */
+#if __OS_HAS_AGP
+static __inline__ struct page *drm_do_vm_nopage(struct vm_area_struct *vma,
+						 unsigned long address)
+{
+	drm_file_t *priv  = vma->vm_file->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_map_t *map    = NULL;
+	drm_map_list_t  *r_list;
+	struct list_head *list;
+
+	/*
+         * Find the right map
+         */
+	if (!drm_core_has_AGP(dev))
+		goto vm_nopage_error;
+
+	if(!dev->agp || !dev->agp->cant_use_aperture) goto vm_nopage_error;
+
+	list_for_each(list, &dev->maplist->head) {
+		r_list = list_entry(list, drm_map_list_t, head);
+		map = r_list->map;
+		if (!map) continue;
+		if (map->offset == VM_OFFSET(vma)) break;
+	}
+
+	if (map && map->type == _DRM_AGP) {
+		unsigned long offset = address - vma->vm_start;
+		unsigned long baddr = VM_OFFSET(vma) + offset;
+		struct drm_agp_mem *agpmem;
+		struct page *page;
+
+#ifdef __alpha__
+		/*
+                 * Adjust to a bus-relative address
+                 */
+		baddr -= dev->hose->mem_space->start;
+#endif
+
+		/*
+                 * It's AGP memory - find the real physical page to map
+                 */
+		for(agpmem = dev->agp->memory; agpmem; agpmem = agpmem->next) {
+			if (agpmem->bound <= baddr &&
+			    agpmem->bound + agpmem->pages * PAGE_SIZE > baddr) 
+				break;
+		}
+
+		if (!agpmem) goto vm_nopage_error;
+
+		/*
+                 * Get the page, inc the use count, and return it
+                 */
+		offset = (baddr - agpmem->bound) >> PAGE_SHIFT;
+		page = virt_to_page(__va(agpmem->memory->memory[offset]));
+		get_page(page);
+
+		DRM_DEBUG("baddr = 0x%lx page = 0x%p, offset = 0x%lx, count=%d\n",
+			  baddr, __va(agpmem->memory->memory[offset]), offset,
+			  page_count(page));
+
+		return page;
+        }
+vm_nopage_error:
+	return NOPAGE_SIGBUS;		/* Disallow mremap */
+}
+#else /* __OS_HAS_AGP */
+static __inline__ struct page *drm_do_vm_nopage(struct vm_area_struct *vma,
+						 unsigned long address)
+{
+	return NOPAGE_SIGBUS;
+}
+#endif /* __OS_HAS_AGP */
+
+/**
+ * \c nopage method for shared virtual memory.
+ *
+ * \param vma virtual memory area.
+ * \param address access address.
+ * \return pointer to the page structure.
+ * 
+ * Get the the mapping, find the real physical page to map, get the page, and
+ * return it.
+ */
+static __inline__ struct page *drm_do_vm_shm_nopage(struct vm_area_struct *vma,
+						     unsigned long address)
+{
+	drm_map_t	 *map	 = (drm_map_t *)vma->vm_private_data;
+	unsigned long	 offset;
+	unsigned long	 i;
+	struct page	 *page;
+
+	if (address > vma->vm_end) return NOPAGE_SIGBUS; /* Disallow mremap */
+	if (!map)    		   return NOPAGE_OOM;  /* Nothing allocated */
+
+	offset	 = address - vma->vm_start;
+	i = (unsigned long)map->handle + offset;
+	page = vmalloc_to_page((void *)i);
+	if (!page)
+		return NOPAGE_OOM;
+	get_page(page);
+
+	DRM_DEBUG("shm_nopage 0x%lx\n", address);
+	return page;
+}
+
+
+/**
+ * \c close method for shared virtual memory.
+ * 
+ * \param vma virtual memory area.
+ * 
+ * Deletes map information if we are the last
+ * person to close a mapping and it's not in the global maplist.
+ */
+void drm_vm_shm_close(struct vm_area_struct *vma)
+{
+	drm_file_t	*priv	= vma->vm_file->private_data;
+	drm_device_t	*dev	= priv->head->dev;
+	drm_vma_entry_t *pt, *prev, *next;
+	drm_map_t *map;
+	drm_map_list_t *r_list;
+	struct list_head *list;
+	int found_maps = 0;
+
+	DRM_DEBUG("0x%08lx,0x%08lx\n",
+		  vma->vm_start, vma->vm_end - vma->vm_start);
+	atomic_dec(&dev->vma_count);
+
+	map = vma->vm_private_data;
+
+	down(&dev->struct_sem);
+	for (pt = dev->vmalist, prev = NULL; pt; pt = next) {
+		next = pt->next;
+		if (pt->vma->vm_private_data == map) found_maps++;
+		if (pt->vma == vma) {
+			if (prev) {
+				prev->next = pt->next;
+			} else {
+				dev->vmalist = pt->next;
+			}
+			drm_free(pt, sizeof(*pt), DRM_MEM_VMAS);
+		} else {
+			prev = pt;
+		}
+	}
+	/* We were the only map that was found */
+	if(found_maps == 1 &&
+	   map->flags & _DRM_REMOVABLE) {
+		/* Check to see if we are in the maplist, if we are not, then
+		 * we delete this mappings information.
+		 */
+		found_maps = 0;
+		list = &dev->maplist->head;
+		list_for_each(list, &dev->maplist->head) {
+			r_list = list_entry(list, drm_map_list_t, head);
+			if (r_list->map == map) found_maps++;
+		}
+
+		if(!found_maps) {
+			switch (map->type) {
+			case _DRM_REGISTERS:
+			case _DRM_FRAME_BUFFER:
+				if (drm_core_has_MTRR(dev) && map->mtrr >= 0) {
+					int retcode;
+					retcode = mtrr_del(map->mtrr,
+							   map->offset,
+							   map->size);
+					DRM_DEBUG("mtrr_del = %d\n", retcode);
+				}
+				drm_ioremapfree(map->handle, map->size, dev);
+				break;
+			case _DRM_SHM:
+				vfree(map->handle);
+				break;
+			case _DRM_AGP:
+			case _DRM_SCATTER_GATHER:
+				break;
+			}
+			drm_free(map, sizeof(*map), DRM_MEM_MAPS);
+		}
+	}
+	up(&dev->struct_sem);
+}
+
+/**
+ * \c nopage method for DMA virtual memory.
+ *
+ * \param vma virtual memory area.
+ * \param address access address.
+ * \return pointer to the page structure.
+ * 
+ * Determine the page number from the page offset and get it from drm_device_dma::pagelist.
+ */
+static __inline__ struct page *drm_do_vm_dma_nopage(struct vm_area_struct *vma,
+						     unsigned long address)
+{
+	drm_file_t	 *priv	 = vma->vm_file->private_data;
+	drm_device_t	 *dev	 = priv->head->dev;
+	drm_device_dma_t *dma	 = dev->dma;
+	unsigned long	 offset;
+	unsigned long	 page_nr;
+	struct page	 *page;
+
+	if (!dma)		   return NOPAGE_SIGBUS; /* Error */
+	if (address > vma->vm_end) return NOPAGE_SIGBUS; /* Disallow mremap */
+	if (!dma->pagelist)	   return NOPAGE_OOM ; /* Nothing allocated */
+
+	offset	 = address - vma->vm_start; /* vm_[pg]off[set] should be 0 */
+	page_nr  = offset >> PAGE_SHIFT;
+	page = virt_to_page((dma->pagelist[page_nr] + 
+			     (offset & (~PAGE_MASK))));
+
+	get_page(page);
+
+	DRM_DEBUG("dma_nopage 0x%lx (page %lu)\n", address, page_nr);
+	return page;
+}
+
+/**
+ * \c nopage method for scatter-gather virtual memory.
+ *
+ * \param vma virtual memory area.
+ * \param address access address.
+ * \return pointer to the page structure.
+ * 
+ * Determine the map offset from the page offset and get it from drm_sg_mem::pagelist.
+ */
+static __inline__ struct page *drm_do_vm_sg_nopage(struct vm_area_struct *vma,
+						    unsigned long address)
+{
+	drm_map_t        *map    = (drm_map_t *)vma->vm_private_data;
+	drm_file_t *priv = vma->vm_file->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_sg_mem_t *entry = dev->sg;
+	unsigned long offset;
+	unsigned long map_offset;
+	unsigned long page_offset;
+	struct page *page;
+
+	if (!entry)                return NOPAGE_SIGBUS; /* Error */
+	if (address > vma->vm_end) return NOPAGE_SIGBUS; /* Disallow mremap */
+	if (!entry->pagelist)      return NOPAGE_OOM ;  /* Nothing allocated */
+
+
+	offset = address - vma->vm_start;
+	map_offset = map->offset - dev->sg->handle;
+	page_offset = (offset >> PAGE_SHIFT) + (map_offset >> PAGE_SHIFT);
+	page = entry->pagelist[page_offset];
+	get_page(page);
+
+	return page;
+}
+
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
+
+static struct page *drm_vm_nopage(struct vm_area_struct *vma,
+				   unsigned long address,
+				   int *type) {
+	if (type) *type = VM_FAULT_MINOR;
+	return drm_do_vm_nopage(vma, address);
+}
+
+static struct page *drm_vm_shm_nopage(struct vm_area_struct *vma,
+				       unsigned long address,
+				       int *type) {
+	if (type) *type = VM_FAULT_MINOR;
+	return drm_do_vm_shm_nopage(vma, address);
+}
+
+static struct page *drm_vm_dma_nopage(struct vm_area_struct *vma,
+				       unsigned long address,
+				       int *type) {
+	if (type) *type = VM_FAULT_MINOR;
+	return drm_do_vm_dma_nopage(vma, address);
+}
+
+static struct page *drm_vm_sg_nopage(struct vm_area_struct *vma,
+				      unsigned long address,
+				      int *type) {
+	if (type) *type = VM_FAULT_MINOR;
+	return drm_do_vm_sg_nopage(vma, address);
+}
+
+#else	/* LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,0) */
+
+static struct page *drm_vm_nopage(struct vm_area_struct *vma,
+				   unsigned long address,
+				   int unused) {
+	return drm_do_vm_nopage(vma, address);
+}
+
+static struct page *drm_vm_shm_nopage(struct vm_area_struct *vma,
+				       unsigned long address,
+				       int unused) {
+	return drm_do_vm_shm_nopage(vma, address);
+}
+
+static struct page *drm_vm_dma_nopage(struct vm_area_struct *vma,
+				       unsigned long address,
+				       int unused) {
+	return drm_do_vm_dma_nopage(vma, address);
+}
+
+static struct page *drm_vm_sg_nopage(struct vm_area_struct *vma,
+				      unsigned long address,
+				      int unused) {
+	return drm_do_vm_sg_nopage(vma, address);
+}
+
+#endif
+
+
+/** AGP virtual memory operations */
+static struct vm_operations_struct   drm_vm_ops = {
+	.nopage = drm_vm_nopage,
+	.open	= drm_vm_open,
+	.close	= drm_vm_close,
+};
+
+/** Shared virtual memory operations */
+static struct vm_operations_struct   drm_vm_shm_ops = {
+	.nopage = drm_vm_shm_nopage,
+	.open	= drm_vm_open,
+	.close	= drm_vm_shm_close,
+};
+
+/** DMA virtual memory operations */
+static struct vm_operations_struct   drm_vm_dma_ops = {
+	.nopage = drm_vm_dma_nopage,
+	.open	= drm_vm_open,
+	.close	= drm_vm_close,
+};
+
+/** Scatter-gather virtual memory operations */
+static struct vm_operations_struct   drm_vm_sg_ops = {
+	.nopage = drm_vm_sg_nopage,
+	.open   = drm_vm_open,
+	.close  = drm_vm_close,
+};
+
+
+/**
+ * \c open method for shared virtual memory.
+ * 
+ * \param vma virtual memory area.
+ * 
+ * Create a new drm_vma_entry structure as the \p vma private data entry and
+ * add it to drm_device::vmalist.
+ */
+void drm_vm_open(struct vm_area_struct *vma)
+{
+	drm_file_t	*priv	= vma->vm_file->private_data;
+	drm_device_t	*dev	= priv->head->dev;
+	drm_vma_entry_t *vma_entry;
+
+	DRM_DEBUG("0x%08lx,0x%08lx\n",
+		  vma->vm_start, vma->vm_end - vma->vm_start);
+	atomic_inc(&dev->vma_count);
+
+	vma_entry = drm_alloc(sizeof(*vma_entry), DRM_MEM_VMAS);
+	if (vma_entry) {
+		down(&dev->struct_sem);
+		vma_entry->vma	= vma;
+		vma_entry->next = dev->vmalist;
+		vma_entry->pid	= current->pid;
+		dev->vmalist	= vma_entry;
+		up(&dev->struct_sem);
+	}
+}
+
+/**
+ * \c close method for all virtual memory types.
+ * 
+ * \param vma virtual memory area.
+ * 
+ * Search the \p vma private data entry in drm_device::vmalist, unlink it, and
+ * free it.
+ */
+void drm_vm_close(struct vm_area_struct *vma)
+{
+	drm_file_t	*priv	= vma->vm_file->private_data;
+	drm_device_t	*dev	= priv->head->dev;
+	drm_vma_entry_t *pt, *prev;
+
+	DRM_DEBUG("0x%08lx,0x%08lx\n",
+		  vma->vm_start, vma->vm_end - vma->vm_start);
+	atomic_dec(&dev->vma_count);
+
+	down(&dev->struct_sem);
+	for (pt = dev->vmalist, prev = NULL; pt; prev = pt, pt = pt->next) {
+		if (pt->vma == vma) {
+			if (prev) {
+				prev->next = pt->next;
+			} else {
+				dev->vmalist = pt->next;
+			}
+			drm_free(pt, sizeof(*pt), DRM_MEM_VMAS);
+			break;
+		}
+	}
+	up(&dev->struct_sem);
+}
+
+/**
+ * mmap DMA memory.
+ *
+ * \param filp file pointer.
+ * \param vma virtual memory area.
+ * \return zero on success or a negative number on failure.
+ * 
+ * Sets the virtual memory area operations structure to vm_dma_ops, the file
+ * pointer, and calls vm_open().
+ */
+int drm_mmap_dma(struct file *filp, struct vm_area_struct *vma)
+{
+	drm_file_t	 *priv	 = filp->private_data;
+	drm_device_t	 *dev;
+	drm_device_dma_t *dma;
+	unsigned long	 length	 = vma->vm_end - vma->vm_start;
+
+	lock_kernel();
+	dev	 = priv->head->dev;
+	dma	 = dev->dma;
+	DRM_DEBUG("start = 0x%lx, end = 0x%lx, offset = 0x%lx\n",
+		  vma->vm_start, vma->vm_end, VM_OFFSET(vma));
+
+				/* Length must match exact page count */
+	if (!dma || (length >> PAGE_SHIFT) != dma->page_count) {
+		unlock_kernel();
+		return -EINVAL;
+	}
+	unlock_kernel();
+
+	vma->vm_ops   = &drm_vm_dma_ops;
+
+#if LINUX_VERSION_CODE <= 0x02040e /* KERNEL_VERSION(2,4,14) */
+	vma->vm_flags |= VM_LOCKED | VM_SHM; /* Don't swap */
+#else
+	vma->vm_flags |= VM_RESERVED; /* Don't swap */
+#endif
+
+	vma->vm_file  =	 filp;	/* Needed for drm_vm_open() */
+	drm_vm_open(vma);
+	return 0;
+}
+
+unsigned long drm_core_get_map_ofs(drm_map_t *map)
+{
+	return map->offset;
+}
+EXPORT_SYMBOL(drm_core_get_map_ofs);
+
+unsigned long drm_core_get_reg_ofs(struct drm_device *dev)
+{
+#ifdef __alpha__
+	return dev->hose->dense_mem_base - dev->hose->mem_space->start;
+#else
+	return 0;
+#endif
+}
+EXPORT_SYMBOL(drm_core_get_reg_ofs);
+
+/**
+ * mmap DMA memory.
+ *
+ * \param filp file pointer.
+ * \param vma virtual memory area.
+ * \return zero on success or a negative number on failure.
+ * 
+ * If the virtual memory area has no offset associated with it then it's a DMA
+ * area, so calls mmap_dma(). Otherwise searches the map in drm_device::maplist,
+ * checks that the restricted flag is not set, sets the virtual memory operations
+ * according to the mapping type and remaps the pages. Finally sets the file
+ * pointer and calls vm_open().
+ */
+int drm_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->head->dev;
+	drm_map_t	*map	= NULL;
+	drm_map_list_t  *r_list;
+	unsigned long   offset  = 0;
+	struct list_head *list;
+
+	DRM_DEBUG("start = 0x%lx, end = 0x%lx, offset = 0x%lx\n",
+		  vma->vm_start, vma->vm_end, VM_OFFSET(vma));
+
+	if ( !priv->authenticated ) return -EACCES;
+
+	/* We check for "dma". On Apple's UniNorth, it's valid to have
+	 * the AGP mapped at physical address 0
+	 * --BenH.
+	 */
+	if (!VM_OFFSET(vma)
+#if __OS_HAS_AGP
+	    && (!dev->agp || dev->agp->agp_info.device->vendor != PCI_VENDOR_ID_APPLE)
+#endif
+	    )
+		return drm_mmap_dma(filp, vma);
+
+				/* A sequential search of a linked list is
+				   fine here because: 1) there will only be
+				   about 5-10 entries in the list and, 2) a
+				   DRI client only has to do this mapping
+				   once, so it doesn't have to be optimized
+				   for performance, even if the list was a
+				   bit longer. */
+	list_for_each(list, &dev->maplist->head) {
+		unsigned long off;
+
+		r_list = list_entry(list, drm_map_list_t, head);
+		map = r_list->map;
+		if (!map) continue;
+		off = dev->driver->get_map_ofs(map);
+		if (off == VM_OFFSET(vma)) break;
+	}
+
+	if (!map || ((map->flags&_DRM_RESTRICTED) && !capable(CAP_SYS_ADMIN)))
+		return -EPERM;
+
+				/* Check for valid size. */
+	if (map->size != vma->vm_end - vma->vm_start) return -EINVAL;
+
+	if (!capable(CAP_SYS_ADMIN) && (map->flags & _DRM_READ_ONLY)) {
+		vma->vm_flags &= ~(VM_WRITE | VM_MAYWRITE);
+#if defined(__i386__) || defined(__x86_64__)
+		pgprot_val(vma->vm_page_prot) &= ~_PAGE_RW;
+#else
+				/* Ye gads this is ugly.  With more thought
+                                   we could move this up higher and use
+                                   `protection_map' instead.  */
+		vma->vm_page_prot = __pgprot(pte_val(pte_wrprotect(
+			__pte(pgprot_val(vma->vm_page_prot)))));
+#endif
+	}
+
+	switch (map->type) {
+        case _DRM_AGP:
+	  if (drm_core_has_AGP(dev) && dev->agp->cant_use_aperture) {
+                /*
+                 * On some platforms we can't talk to bus dma address from the CPU, so for
+                 * memory of type DRM_AGP, we'll deal with sorting out the real physical
+                 * pages and mappings in nopage()
+                 */
+#if defined(__powerpc__)
+		pgprot_val(vma->vm_page_prot) |= _PAGE_NO_CACHE;
+#endif
+                vma->vm_ops = &drm_vm_ops;
+                break;
+	  }
+                /* fall through to _DRM_FRAME_BUFFER... */        
+	case _DRM_FRAME_BUFFER:
+	case _DRM_REGISTERS:
+		if (VM_OFFSET(vma) >= __pa(high_memory)) {
+#if defined(__i386__) || defined(__x86_64__)
+			if (boot_cpu_data.x86 > 3 && map->type != _DRM_AGP) {
+				pgprot_val(vma->vm_page_prot) |= _PAGE_PCD;
+				pgprot_val(vma->vm_page_prot) &= ~_PAGE_PWT;
+			}
+#elif defined(__powerpc__)
+			pgprot_val(vma->vm_page_prot) |= _PAGE_NO_CACHE | _PAGE_GUARDED;
+#endif
+			vma->vm_flags |= VM_IO;	/* not in core dump */
+		}
+#if defined(__ia64__)
+		if (efi_range_is_wc(vma->vm_start, vma->vm_end -
+				    vma->vm_start))
+			vma->vm_page_prot =
+				pgprot_writecombine(vma->vm_page_prot);
+		else
+			vma->vm_page_prot =
+				pgprot_noncached(vma->vm_page_prot);
+#endif
+		offset = dev->driver->get_reg_ofs(dev);
+#ifdef __sparc__
+		if (io_remap_pfn_range(DRM_RPR_ARG(vma) vma->vm_start,
+					(VM_OFFSET(vma) + offset) >> PAGE_SHIFT,
+					vma->vm_end - vma->vm_start,
+					vma->vm_page_prot))
+#else
+		if (io_remap_pfn_range(vma, vma->vm_start,
+				     (VM_OFFSET(vma) + offset) >> PAGE_SHIFT,
+				     vma->vm_end - vma->vm_start,
+				     vma->vm_page_prot))
+#endif
+				return -EAGAIN;
+		DRM_DEBUG("   Type = %d; start = 0x%lx, end = 0x%lx,"
+			  " offset = 0x%lx\n",
+			  map->type,
+			  vma->vm_start, vma->vm_end, VM_OFFSET(vma) + offset);
+		vma->vm_ops = &drm_vm_ops;
+		break;
+	case _DRM_SHM:
+		vma->vm_ops = &drm_vm_shm_ops;
+		vma->vm_private_data = (void *)map;
+				/* Don't let this area swap.  Change when
+				   DRM_KERNEL advisory is supported. */
+#if LINUX_VERSION_CODE <= 0x02040e /* KERNEL_VERSION(2,4,14) */
+		vma->vm_flags |= VM_LOCKED;
+#else
+		vma->vm_flags |= VM_RESERVED;
+#endif
+		break;
+	case _DRM_SCATTER_GATHER:
+		vma->vm_ops = &drm_vm_sg_ops;
+		vma->vm_private_data = (void *)map;
+#if LINUX_VERSION_CODE <= 0x02040e /* KERNEL_VERSION(2,4,14) */
+		vma->vm_flags |= VM_LOCKED;
+#else
+		vma->vm_flags |= VM_RESERVED;
+#endif
+                break;
+	default:
+		return -EINVAL;	/* This should never happen. */
+	}
+#if LINUX_VERSION_CODE <= 0x02040e /* KERNEL_VERSION(2,4,14) */
+	vma->vm_flags |= VM_LOCKED | VM_SHM; /* Don't swap */
+#else
+	vma->vm_flags |= VM_RESERVED; /* Don't swap */
+#endif
+
+	vma->vm_file  =	 filp;	/* Needed for drm_vm_open() */
+	drm_vm_open(vma);
+	return 0;
+}
+EXPORT_SYMBOL(drm_mmap);
diff --git a/drivers/char/drm/ffb_context.c b/drivers/char/drm/ffb_context.c
new file mode 100644
index 0000000..f518120
--- /dev/null
+++ b/drivers/char/drm/ffb_context.c
@@ -0,0 +1,551 @@
+/* $Id: ffb_context.c,v 1.5 2001/08/09 17:47:51 davem Exp $
+ * ffb_context.c: Creator/Creator3D DRI/DRM context switching.
+ *
+ * Copyright (C) 2000 David S. Miller (davem@redhat.com)
+ *
+ * Almost entirely stolen from tdfx_context.c, see there
+ * for authors.
+ */
+
+#include <linux/sched.h>
+#include <asm/upa.h>
+
+#include "ffb.h"
+#include "drmP.h"
+
+#include "ffb_drv.h"
+
+static int DRM(alloc_queue)(drm_device_t *dev, int is_2d_only)
+{
+	ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+	int i;
+
+	for (i = 0; i < FFB_MAX_CTXS; i++) {
+		if (fpriv->hw_state[i] == NULL)
+			break;
+	}
+	if (i == FFB_MAX_CTXS)
+		return -1;
+
+	fpriv->hw_state[i] = kmalloc(sizeof(struct ffb_hw_context), GFP_KERNEL);
+	if (fpriv->hw_state[i] == NULL)
+		return -1;
+
+	fpriv->hw_state[i]->is_2d_only = is_2d_only;
+
+	/* Plus one because 0 is the special DRM_KERNEL_CONTEXT. */
+	return i + 1;
+}
+
+static void ffb_save_context(ffb_dev_priv_t *fpriv, int idx)
+{
+	ffb_fbcPtr ffb = fpriv->regs;
+	struct ffb_hw_context *ctx;
+	int i;
+
+	ctx = fpriv->hw_state[idx - 1];
+	if (idx == 0 || ctx == NULL)
+		return;
+
+	if (ctx->is_2d_only) {
+		/* 2D applications only care about certain pieces
+		 * of state.
+		 */
+		ctx->drawop = upa_readl(&ffb->drawop);
+		ctx->ppc = upa_readl(&ffb->ppc);
+		ctx->wid = upa_readl(&ffb->wid);
+		ctx->fg = upa_readl(&ffb->fg);
+		ctx->bg = upa_readl(&ffb->bg);
+		ctx->xclip = upa_readl(&ffb->xclip);
+		ctx->fbc = upa_readl(&ffb->fbc);
+		ctx->rop = upa_readl(&ffb->rop);
+		ctx->cmp = upa_readl(&ffb->cmp);
+		ctx->matchab = upa_readl(&ffb->matchab);
+		ctx->magnab = upa_readl(&ffb->magnab);
+		ctx->pmask = upa_readl(&ffb->pmask);
+		ctx->xpmask = upa_readl(&ffb->xpmask);
+		ctx->lpat = upa_readl(&ffb->lpat);
+		ctx->fontxy = upa_readl(&ffb->fontxy);
+		ctx->fontw = upa_readl(&ffb->fontw);
+		ctx->fontinc = upa_readl(&ffb->fontinc);
+
+		/* stencil/stencilctl only exists on FFB2+ and later
+		 * due to the introduction of 3DRAM-III.
+		 */
+		if (fpriv->ffb_type == ffb2_vertical_plus ||
+		    fpriv->ffb_type == ffb2_horizontal_plus) {
+			ctx->stencil = upa_readl(&ffb->stencil);
+			ctx->stencilctl = upa_readl(&ffb->stencilctl);
+		}
+
+		for (i = 0; i < 32; i++)
+			ctx->area_pattern[i] = upa_readl(&ffb->pattern[i]);
+		ctx->ucsr = upa_readl(&ffb->ucsr);
+		return;
+	}
+
+	/* Fetch drawop. */
+	ctx->drawop = upa_readl(&ffb->drawop);
+
+	/* If we were saving the vertex registers, this is where
+	 * we would do it.  We would save 32 32-bit words starting
+	 * at ffb->suvtx.
+	 */
+
+	/* Capture rendering attributes. */
+
+	ctx->ppc = upa_readl(&ffb->ppc);		/* Pixel Processor Control */
+	ctx->wid = upa_readl(&ffb->wid);		/* Current WID */
+	ctx->fg = upa_readl(&ffb->fg);			/* Constant FG color */
+	ctx->bg = upa_readl(&ffb->bg);			/* Constant BG color */
+	ctx->consty = upa_readl(&ffb->consty);		/* Constant Y */
+	ctx->constz = upa_readl(&ffb->constz);		/* Constant Z */
+	ctx->xclip = upa_readl(&ffb->xclip);		/* X plane clip */
+	ctx->dcss = upa_readl(&ffb->dcss);		/* Depth Cue Scale Slope */
+	ctx->vclipmin = upa_readl(&ffb->vclipmin);	/* Primary XY clip, minimum */
+	ctx->vclipmax = upa_readl(&ffb->vclipmax);	/* Primary XY clip, maximum */
+	ctx->vclipzmin = upa_readl(&ffb->vclipzmin);	/* Primary Z clip, minimum */
+	ctx->vclipzmax = upa_readl(&ffb->vclipzmax);	/* Primary Z clip, maximum */
+	ctx->dcsf = upa_readl(&ffb->dcsf);		/* Depth Cue Scale Front Bound */
+	ctx->dcsb = upa_readl(&ffb->dcsb);		/* Depth Cue Scale Back Bound */
+	ctx->dczf = upa_readl(&ffb->dczf);		/* Depth Cue Scale Z Front */
+	ctx->dczb = upa_readl(&ffb->dczb);		/* Depth Cue Scale Z Back */
+	ctx->blendc = upa_readl(&ffb->blendc);		/* Alpha Blend Control */
+	ctx->blendc1 = upa_readl(&ffb->blendc1);	/* Alpha Blend Color 1 */
+	ctx->blendc2 = upa_readl(&ffb->blendc2);	/* Alpha Blend Color 2 */
+	ctx->fbc = upa_readl(&ffb->fbc);		/* Frame Buffer Control */
+	ctx->rop = upa_readl(&ffb->rop);		/* Raster Operation */
+	ctx->cmp = upa_readl(&ffb->cmp);		/* Compare Controls */
+	ctx->matchab = upa_readl(&ffb->matchab);	/* Buffer A/B Match Ops */
+	ctx->matchc = upa_readl(&ffb->matchc);		/* Buffer C Match Ops */
+	ctx->magnab = upa_readl(&ffb->magnab);		/* Buffer A/B Magnitude Ops */
+	ctx->magnc = upa_readl(&ffb->magnc);		/* Buffer C Magnitude Ops */
+	ctx->pmask = upa_readl(&ffb->pmask);		/* RGB Plane Mask */
+	ctx->xpmask = upa_readl(&ffb->xpmask);		/* X Plane Mask */
+	ctx->ypmask = upa_readl(&ffb->ypmask);		/* Y Plane Mask */
+	ctx->zpmask = upa_readl(&ffb->zpmask);		/* Z Plane Mask */
+
+	/* Auxiliary Clips. */
+	ctx->auxclip0min = upa_readl(&ffb->auxclip[0].min);
+	ctx->auxclip0max = upa_readl(&ffb->auxclip[0].max);
+	ctx->auxclip1min = upa_readl(&ffb->auxclip[1].min);
+	ctx->auxclip1max = upa_readl(&ffb->auxclip[1].max);
+	ctx->auxclip2min = upa_readl(&ffb->auxclip[2].min);
+	ctx->auxclip2max = upa_readl(&ffb->auxclip[2].max);
+	ctx->auxclip3min = upa_readl(&ffb->auxclip[3].min);
+	ctx->auxclip3max = upa_readl(&ffb->auxclip[3].max);
+
+	ctx->lpat = upa_readl(&ffb->lpat);		/* Line Pattern */
+	ctx->fontxy = upa_readl(&ffb->fontxy);		/* XY Font Coordinate */
+	ctx->fontw = upa_readl(&ffb->fontw);		/* Font Width */
+	ctx->fontinc = upa_readl(&ffb->fontinc);	/* Font X/Y Increment */
+
+	/* These registers/features only exist on FFB2 and later chips. */
+	if (fpriv->ffb_type >= ffb2_prototype) {
+		ctx->dcss1 = upa_readl(&ffb->dcss1);	/* Depth Cue Scale Slope 1 */
+		ctx->dcss2 = upa_readl(&ffb->dcss2);	/* Depth Cue Scale Slope 2 */
+		ctx->dcss2 = upa_readl(&ffb->dcss3);	/* Depth Cue Scale Slope 3 */
+		ctx->dcs2  = upa_readl(&ffb->dcs2);	/* Depth Cue Scale 2 */
+		ctx->dcs3  = upa_readl(&ffb->dcs3);	/* Depth Cue Scale 3 */
+		ctx->dcs4  = upa_readl(&ffb->dcs4);	/* Depth Cue Scale 4 */
+		ctx->dcd2  = upa_readl(&ffb->dcd2);	/* Depth Cue Depth 2 */
+		ctx->dcd3  = upa_readl(&ffb->dcd3);	/* Depth Cue Depth 3 */
+		ctx->dcd4  = upa_readl(&ffb->dcd4);	/* Depth Cue Depth 4 */
+
+		/* And stencil/stencilctl only exists on FFB2+ and later
+		 * due to the introduction of 3DRAM-III.
+		 */
+		if (fpriv->ffb_type == ffb2_vertical_plus ||
+		    fpriv->ffb_type == ffb2_horizontal_plus) {
+			ctx->stencil = upa_readl(&ffb->stencil);
+			ctx->stencilctl = upa_readl(&ffb->stencilctl);
+		}
+	}
+
+	/* Save the 32x32 area pattern. */
+	for (i = 0; i < 32; i++)
+		ctx->area_pattern[i] = upa_readl(&ffb->pattern[i]);
+
+	/* Finally, stash away the User Constol/Status Register. */
+	ctx->ucsr = upa_readl(&ffb->ucsr);
+}
+
+static void ffb_restore_context(ffb_dev_priv_t *fpriv, int old, int idx)
+{
+	ffb_fbcPtr ffb = fpriv->regs;
+	struct ffb_hw_context *ctx;
+	int i;
+
+	ctx = fpriv->hw_state[idx - 1];
+	if (idx == 0 || ctx == NULL)
+		return;
+
+	if (ctx->is_2d_only) {
+		/* 2D applications only care about certain pieces
+		 * of state.
+		 */
+		upa_writel(ctx->drawop, &ffb->drawop);
+
+		/* If we were restoring the vertex registers, this is where
+		 * we would do it.  We would restore 32 32-bit words starting
+		 * at ffb->suvtx.
+		 */
+
+		upa_writel(ctx->ppc, &ffb->ppc);
+		upa_writel(ctx->wid, &ffb->wid);
+		upa_writel(ctx->fg,  &ffb->fg);
+		upa_writel(ctx->bg, &ffb->bg);
+		upa_writel(ctx->xclip, &ffb->xclip);
+		upa_writel(ctx->fbc, &ffb->fbc);
+		upa_writel(ctx->rop, &ffb->rop);
+		upa_writel(ctx->cmp, &ffb->cmp);
+		upa_writel(ctx->matchab, &ffb->matchab);
+		upa_writel(ctx->magnab, &ffb->magnab);
+		upa_writel(ctx->pmask, &ffb->pmask);
+		upa_writel(ctx->xpmask, &ffb->xpmask);
+		upa_writel(ctx->lpat, &ffb->lpat);
+		upa_writel(ctx->fontxy, &ffb->fontxy);
+		upa_writel(ctx->fontw, &ffb->fontw);
+		upa_writel(ctx->fontinc, &ffb->fontinc);
+
+		/* stencil/stencilctl only exists on FFB2+ and later
+		 * due to the introduction of 3DRAM-III.
+		 */
+		if (fpriv->ffb_type == ffb2_vertical_plus ||
+		    fpriv->ffb_type == ffb2_horizontal_plus) {
+			upa_writel(ctx->stencil, &ffb->stencil);
+			upa_writel(ctx->stencilctl, &ffb->stencilctl);
+			upa_writel(0x80000000, &ffb->fbc);
+			upa_writel((ctx->stencilctl | 0x80000),
+				   &ffb->rawstencilctl);
+			upa_writel(ctx->fbc, &ffb->fbc);
+		}
+
+		for (i = 0; i < 32; i++)
+			upa_writel(ctx->area_pattern[i], &ffb->pattern[i]);
+		upa_writel((ctx->ucsr & 0xf0000), &ffb->ucsr);
+		return;
+	}
+
+	/* Restore drawop. */
+	upa_writel(ctx->drawop, &ffb->drawop);
+
+	/* If we were restoring the vertex registers, this is where
+	 * we would do it.  We would restore 32 32-bit words starting
+	 * at ffb->suvtx.
+	 */
+
+	/* Restore rendering attributes. */
+
+	upa_writel(ctx->ppc, &ffb->ppc);		/* Pixel Processor Control */
+	upa_writel(ctx->wid, &ffb->wid);		/* Current WID */
+	upa_writel(ctx->fg, &ffb->fg);			/* Constant FG color */
+	upa_writel(ctx->bg, &ffb->bg);			/* Constant BG color */
+	upa_writel(ctx->consty, &ffb->consty);		/* Constant Y */
+	upa_writel(ctx->constz, &ffb->constz);		/* Constant Z */
+	upa_writel(ctx->xclip, &ffb->xclip);		/* X plane clip */
+	upa_writel(ctx->dcss, &ffb->dcss);		/* Depth Cue Scale Slope */
+	upa_writel(ctx->vclipmin, &ffb->vclipmin);	/* Primary XY clip, minimum */
+	upa_writel(ctx->vclipmax, &ffb->vclipmax);	/* Primary XY clip, maximum */
+	upa_writel(ctx->vclipzmin, &ffb->vclipzmin);	/* Primary Z clip, minimum */
+	upa_writel(ctx->vclipzmax, &ffb->vclipzmax);	/* Primary Z clip, maximum */
+	upa_writel(ctx->dcsf, &ffb->dcsf);		/* Depth Cue Scale Front Bound */
+	upa_writel(ctx->dcsb, &ffb->dcsb);		/* Depth Cue Scale Back Bound */
+	upa_writel(ctx->dczf, &ffb->dczf);		/* Depth Cue Scale Z Front */
+	upa_writel(ctx->dczb, &ffb->dczb);		/* Depth Cue Scale Z Back */
+	upa_writel(ctx->blendc, &ffb->blendc);		/* Alpha Blend Control */
+	upa_writel(ctx->blendc1, &ffb->blendc1);	/* Alpha Blend Color 1 */
+	upa_writel(ctx->blendc2, &ffb->blendc2);	/* Alpha Blend Color 2 */
+	upa_writel(ctx->fbc, &ffb->fbc);		/* Frame Buffer Control */
+	upa_writel(ctx->rop, &ffb->rop);		/* Raster Operation */
+	upa_writel(ctx->cmp, &ffb->cmp);		/* Compare Controls */
+	upa_writel(ctx->matchab, &ffb->matchab);	/* Buffer A/B Match Ops */
+	upa_writel(ctx->matchc, &ffb->matchc);		/* Buffer C Match Ops */
+	upa_writel(ctx->magnab, &ffb->magnab);		/* Buffer A/B Magnitude Ops */
+	upa_writel(ctx->magnc, &ffb->magnc);		/* Buffer C Magnitude Ops */
+	upa_writel(ctx->pmask, &ffb->pmask);		/* RGB Plane Mask */
+	upa_writel(ctx->xpmask, &ffb->xpmask);		/* X Plane Mask */
+	upa_writel(ctx->ypmask, &ffb->ypmask);		/* Y Plane Mask */
+	upa_writel(ctx->zpmask, &ffb->zpmask);		/* Z Plane Mask */
+
+	/* Auxiliary Clips. */
+	upa_writel(ctx->auxclip0min, &ffb->auxclip[0].min);
+	upa_writel(ctx->auxclip0max, &ffb->auxclip[0].max);
+	upa_writel(ctx->auxclip1min, &ffb->auxclip[1].min);
+	upa_writel(ctx->auxclip1max, &ffb->auxclip[1].max);
+	upa_writel(ctx->auxclip2min, &ffb->auxclip[2].min);
+	upa_writel(ctx->auxclip2max, &ffb->auxclip[2].max);
+	upa_writel(ctx->auxclip3min, &ffb->auxclip[3].min);
+	upa_writel(ctx->auxclip3max, &ffb->auxclip[3].max);
+
+	upa_writel(ctx->lpat, &ffb->lpat);		/* Line Pattern */
+	upa_writel(ctx->fontxy, &ffb->fontxy);		/* XY Font Coordinate */
+	upa_writel(ctx->fontw, &ffb->fontw);		/* Font Width */
+	upa_writel(ctx->fontinc, &ffb->fontinc);	/* Font X/Y Increment */
+
+	/* These registers/features only exist on FFB2 and later chips. */
+	if (fpriv->ffb_type >= ffb2_prototype) {
+		upa_writel(ctx->dcss1, &ffb->dcss1);	/* Depth Cue Scale Slope 1 */
+		upa_writel(ctx->dcss2, &ffb->dcss2);	/* Depth Cue Scale Slope 2 */
+		upa_writel(ctx->dcss3, &ffb->dcss2);	/* Depth Cue Scale Slope 3 */
+		upa_writel(ctx->dcs2, &ffb->dcs2);	/* Depth Cue Scale 2 */
+		upa_writel(ctx->dcs3, &ffb->dcs3);	/* Depth Cue Scale 3 */
+		upa_writel(ctx->dcs4, &ffb->dcs4);	/* Depth Cue Scale 4 */
+		upa_writel(ctx->dcd2, &ffb->dcd2);	/* Depth Cue Depth 2 */
+		upa_writel(ctx->dcd3, &ffb->dcd3);	/* Depth Cue Depth 3 */
+		upa_writel(ctx->dcd4, &ffb->dcd4);	/* Depth Cue Depth 4 */
+
+		/* And stencil/stencilctl only exists on FFB2+ and later
+		 * due to the introduction of 3DRAM-III.
+		 */
+		if (fpriv->ffb_type == ffb2_vertical_plus ||
+		    fpriv->ffb_type == ffb2_horizontal_plus) {
+			/* Unfortunately, there is a hardware bug on
+			 * the FFB2+ chips which prevents a normal write
+			 * to the stencil control register from working
+			 * as it should.
+			 *
+			 * The state controlled by the FFB stencilctl register
+			 * really gets transferred to the per-buffer instances
+			 * of the stencilctl register in the 3DRAM chips.
+			 *
+			 * The bug is that FFB does not update buffer C correctly,
+			 * so we have to do it by hand for them.
+			 */
+
+			/* This will update buffers A and B. */
+			upa_writel(ctx->stencil, &ffb->stencil);
+			upa_writel(ctx->stencilctl, &ffb->stencilctl);
+
+			/* Force FFB to use buffer C 3dram regs. */
+			upa_writel(0x80000000, &ffb->fbc);
+			upa_writel((ctx->stencilctl | 0x80000),
+				   &ffb->rawstencilctl);
+
+			/* Now restore the correct FBC controls. */
+			upa_writel(ctx->fbc, &ffb->fbc);
+		}
+	}
+
+	/* Restore the 32x32 area pattern. */
+	for (i = 0; i < 32; i++)
+		upa_writel(ctx->area_pattern[i], &ffb->pattern[i]);
+
+	/* Finally, stash away the User Constol/Status Register.
+	 * The only state we really preserve here is the picking
+	 * control.
+	 */
+	upa_writel((ctx->ucsr & 0xf0000), &ffb->ucsr);
+}
+
+#define FFB_UCSR_FB_BUSY       0x01000000
+#define FFB_UCSR_RP_BUSY       0x02000000
+#define FFB_UCSR_ALL_BUSY      (FFB_UCSR_RP_BUSY|FFB_UCSR_FB_BUSY)
+
+static void FFBWait(ffb_fbcPtr ffb)
+{
+	int limit = 100000;
+
+	do {
+		u32 regval = upa_readl(&ffb->ucsr);
+
+		if ((regval & FFB_UCSR_ALL_BUSY) == 0)
+			break;
+	} while (--limit);
+}
+
+int ffb_driver_context_switch(drm_device_t *dev, int old, int new)
+{
+	ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+
+#ifdef DRM_DMA_HISTOGRAM
+        dev->ctx_start = get_cycles();
+#endif
+        
+        DRM_DEBUG("Context switch from %d to %d\n", old, new);
+
+        if (new == dev->last_context ||
+	    dev->last_context == 0) {
+		dev->last_context = new;
+                return 0;
+	}
+        
+	FFBWait(fpriv->regs);
+	ffb_save_context(fpriv, old);
+	ffb_restore_context(fpriv, old, new);
+	FFBWait(fpriv->regs);
+        
+	dev->last_context = new;
+
+        return 0;
+}
+
+int ffb_driver_resctx(struct inode *inode, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+{
+	drm_ctx_res_t	res;
+	drm_ctx_t	ctx;
+	int		i;
+
+	DRM_DEBUG("%d\n", DRM_RESERVED_CONTEXTS);
+	if (copy_from_user(&res, (drm_ctx_res_t __user *)arg, sizeof(res)))
+		return -EFAULT;
+	if (res.count >= DRM_RESERVED_CONTEXTS) {
+		memset(&ctx, 0, sizeof(ctx));
+		for (i = 0; i < DRM_RESERVED_CONTEXTS; i++) {
+			ctx.handle = i;
+			if (copy_to_user(&res.contexts[i],
+					 &i,
+					 sizeof(i)))
+				return -EFAULT;
+		}
+	}
+	res.count = DRM_RESERVED_CONTEXTS;
+	if (copy_to_user((drm_ctx_res_t __user *)arg, &res, sizeof(res)))
+		return -EFAULT;
+	return 0;
+}
+
+
+int ffb_driver_addctx(struct inode *inode, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_t	ctx;
+	int idx;
+
+	if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+	idx = DRM(alloc_queue)(dev, (ctx.flags & _DRM_CONTEXT_2DONLY));
+	if (idx < 0)
+		return -ENFILE;
+
+	DRM_DEBUG("%d\n", ctx.handle);
+	ctx.handle = idx;
+	if (copy_to_user((drm_ctx_t __user *)arg, &ctx, sizeof(ctx)))
+		return -EFAULT;
+	return 0;
+}
+
+int ffb_driver_modctx(struct inode *inode, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	ffb_dev_priv_t	*fpriv	= (ffb_dev_priv_t *) dev->dev_private;
+	struct ffb_hw_context *hwctx;
+	drm_ctx_t ctx;
+	int idx;
+
+	if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+
+	idx = ctx.handle;
+	if (idx <= 0 || idx >= FFB_MAX_CTXS)
+		return -EINVAL;
+
+	hwctx = fpriv->hw_state[idx - 1];
+	if (hwctx == NULL)
+		return -EINVAL;
+
+	if ((ctx.flags & _DRM_CONTEXT_2DONLY) == 0)
+		hwctx->is_2d_only = 0;
+	else
+		hwctx->is_2d_only = 1;
+
+	return 0;
+}
+
+int ffb_driver_getctx(struct inode *inode, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	ffb_dev_priv_t	*fpriv	= (ffb_dev_priv_t *) dev->dev_private;
+	struct ffb_hw_context *hwctx;
+	drm_ctx_t ctx;
+	int idx;
+
+	if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+
+	idx = ctx.handle;
+	if (idx <= 0 || idx >= FFB_MAX_CTXS)
+		return -EINVAL;
+
+	hwctx = fpriv->hw_state[idx - 1];
+	if (hwctx == NULL)
+		return -EINVAL;
+
+	if (hwctx->is_2d_only != 0)
+		ctx.flags = _DRM_CONTEXT_2DONLY;
+	else
+		ctx.flags = 0;
+
+	if (copy_to_user((drm_ctx_t __user *)arg, &ctx, sizeof(ctx)))
+		return -EFAULT;
+
+	return 0;
+}
+
+int ffb_driver_switchctx(struct inode *inode, struct file *filp, unsigned int cmd,
+		   unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_t	ctx;
+
+	if (copy_from_user(&ctx, (drm_ctx_t  __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+	DRM_DEBUG("%d\n", ctx.handle);
+	return ffb_driver_context_switch(dev, dev->last_context, ctx.handle);
+}
+
+int ffb_driver_newctx(struct inode *inode, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+{
+	drm_ctx_t	ctx;
+
+	if (copy_from_user(&ctx, (drm_ctx_t  __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+	DRM_DEBUG("%d\n", ctx.handle);
+
+	return 0;
+}
+
+int ffb_driver_rmctx(struct inode *inode, struct file *filp, unsigned int cmd,
+	       unsigned long arg)
+{
+	drm_ctx_t	ctx;
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	ffb_dev_priv_t	*fpriv	= (ffb_dev_priv_t *) dev->dev_private;
+	int idx;
+
+	if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+	DRM_DEBUG("%d\n", ctx.handle);
+
+	idx = ctx.handle - 1;
+	if (idx < 0 || idx >= FFB_MAX_CTXS)
+		return -EINVAL;
+
+	if (fpriv->hw_state[idx] != NULL) {
+		kfree(fpriv->hw_state[idx]);
+		fpriv->hw_state[idx] = NULL;
+	}
+	return 0;
+}
+
+void ffb_set_context_ioctls(void)
+{
+	DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_ADD_CTX)].func = ffb_driver_addctx;
+	DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_RM_CTX)].func = ffb_driver_rmctx;
+	DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_MOD_CTX)].func = ffb_driver_modctx;
+	DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_GET_CTX)].func = ffb_driver_getctx;
+	DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_SWITCH_CTX)].func = ffb_driver_switchctx;
+	DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_NEW_CTX)].func = ffb_driver_newctx;
+	DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_RES_CTX)].func = ffb_driver_resctx;
+
+}
diff --git a/drivers/char/drm/ffb_drv.c b/drivers/char/drm/ffb_drv.c
new file mode 100644
index 0000000..ec614ff
--- /dev/null
+++ b/drivers/char/drm/ffb_drv.c
@@ -0,0 +1,365 @@
+/* $Id: ffb_drv.c,v 1.16 2001/10/18 16:00:24 davem Exp $
+ * ffb_drv.c: Creator/Creator3D direct rendering driver.
+ *
+ * Copyright (C) 2000 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/config.h>
+#include "ffb.h"
+#include "drmP.h"
+
+#include "ffb_drv.h"
+
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <asm/shmparam.h>
+#include <asm/oplib.h>
+#include <asm/upa.h>
+
+#define DRIVER_AUTHOR		"David S. Miller"
+
+#define DRIVER_NAME		"ffb"
+#define DRIVER_DESC		"Creator/Creator3D"
+#define DRIVER_DATE		"20000517"
+
+#define DRIVER_MAJOR		0
+#define DRIVER_MINOR		0
+#define DRIVER_PATCHLEVEL	1
+
+typedef struct _ffb_position_t {
+	int node;
+	int root;
+} ffb_position_t;
+
+static ffb_position_t *ffb_position;
+
+static void get_ffb_type(ffb_dev_priv_t *ffb_priv, int instance)
+{
+	volatile unsigned char *strap_bits;
+	unsigned char val;
+
+	strap_bits = (volatile unsigned char *)
+		(ffb_priv->card_phys_base + 0x00200000UL);
+
+	/* Don't ask, you have to read the value twice for whatever
+	 * reason to get correct contents.
+	 */
+	val = upa_readb(strap_bits);
+	val = upa_readb(strap_bits);
+	switch (val & 0x78) {
+	case (0x0 << 5) | (0x0 << 3):
+		ffb_priv->ffb_type = ffb1_prototype;
+		printk("ffb%d: Detected FFB1 pre-FCS prototype\n", instance);
+		break;
+	case (0x0 << 5) | (0x1 << 3):
+		ffb_priv->ffb_type = ffb1_standard;
+		printk("ffb%d: Detected FFB1\n", instance);
+		break;
+	case (0x0 << 5) | (0x3 << 3):
+		ffb_priv->ffb_type = ffb1_speedsort;
+		printk("ffb%d: Detected FFB1-SpeedSort\n", instance);
+		break;
+	case (0x1 << 5) | (0x0 << 3):
+		ffb_priv->ffb_type = ffb2_prototype;
+		printk("ffb%d: Detected FFB2/vertical pre-FCS prototype\n", instance);
+		break;
+	case (0x1 << 5) | (0x1 << 3):
+		ffb_priv->ffb_type = ffb2_vertical;
+		printk("ffb%d: Detected FFB2/vertical\n", instance);
+		break;
+	case (0x1 << 5) | (0x2 << 3):
+		ffb_priv->ffb_type = ffb2_vertical_plus;
+		printk("ffb%d: Detected FFB2+/vertical\n", instance);
+		break;
+	case (0x2 << 5) | (0x0 << 3):
+		ffb_priv->ffb_type = ffb2_horizontal;
+		printk("ffb%d: Detected FFB2/horizontal\n", instance);
+		break;
+	case (0x2 << 5) | (0x2 << 3):
+		ffb_priv->ffb_type = ffb2_horizontal;
+		printk("ffb%d: Detected FFB2+/horizontal\n", instance);
+		break;
+	default:
+		ffb_priv->ffb_type = ffb2_vertical;
+		printk("ffb%d: Unknown boardID[%08x], assuming FFB2\n", instance, val);
+		break;
+	};
+}
+
+static void ffb_apply_upa_parent_ranges(int parent, 
+					struct linux_prom64_registers *regs)
+{
+	struct linux_prom64_ranges ranges[PROMREG_MAX];
+	char name[128];
+	int len, i;
+
+	prom_getproperty(parent, "name", name, sizeof(name));
+	if (strcmp(name, "upa") != 0)
+		return;
+
+	len = prom_getproperty(parent, "ranges", (void *) ranges, sizeof(ranges));
+	if (len <= 0)
+		return;
+
+	len /= sizeof(struct linux_prom64_ranges);
+	for (i = 0; i < len; i++) {
+		struct linux_prom64_ranges *rng = &ranges[i];
+		u64 phys_addr = regs->phys_addr;
+
+		if (phys_addr >= rng->ot_child_base &&
+		    phys_addr < (rng->ot_child_base + rng->or_size)) {
+			regs->phys_addr -= rng->ot_child_base;
+			regs->phys_addr += rng->ot_parent_base;
+			return;
+		}
+	}
+
+	return;
+}
+
+static int ffb_init_one(drm_device_t *dev, int prom_node, int parent_node,
+			int instance)
+{
+	struct linux_prom64_registers regs[2*PROMREG_MAX];
+	ffb_dev_priv_t *ffb_priv = (ffb_dev_priv_t *)dev->dev_private;
+	int i;
+
+	ffb_priv->prom_node = prom_node;
+	if (prom_getproperty(ffb_priv->prom_node, "reg",
+			     (void *)regs, sizeof(regs)) <= 0) {
+		return -EINVAL;
+	}
+	ffb_apply_upa_parent_ranges(parent_node, &regs[0]);
+	ffb_priv->card_phys_base = regs[0].phys_addr;
+	ffb_priv->regs = (ffb_fbcPtr)
+		(regs[0].phys_addr + 0x00600000UL);
+	get_ffb_type(ffb_priv, instance);
+	for (i = 0; i < FFB_MAX_CTXS; i++)
+		ffb_priv->hw_state[i] = NULL;
+	
+	return 0;
+}
+
+static drm_map_t *ffb_find_map(struct file *filp, unsigned long off)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev;
+	drm_map_list_t  *r_list;
+	struct list_head *list;
+	drm_map_t	*map;
+
+	if (!priv || (dev = priv->dev) == NULL)
+		return NULL;
+
+	list_for_each(list, &dev->maplist->head) {
+		unsigned long uoff;
+
+		r_list = (drm_map_list_t *)list;
+		map = r_list->map;
+		if (!map)
+			continue;
+		uoff = (map->offset & 0xffffffff);
+		if (uoff == off)
+			return map;
+	}
+
+	return NULL;
+}
+
+unsigned long ffb_get_unmapped_area(struct file *filp,
+				    unsigned long hint,
+				    unsigned long len,
+				    unsigned long pgoff,
+				    unsigned long flags)
+{
+	drm_map_t *map = ffb_find_map(filp, pgoff << PAGE_SHIFT);
+	unsigned long addr = -ENOMEM;
+
+	if (!map)
+		return get_unmapped_area(NULL, hint, len, pgoff, flags);
+
+	if (map->type == _DRM_FRAME_BUFFER ||
+	    map->type == _DRM_REGISTERS) {
+#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
+		addr = get_fb_unmapped_area(filp, hint, len, pgoff, flags);
+#else
+		addr = get_unmapped_area(NULL, hint, len, pgoff, flags);
+#endif
+	} else if (map->type == _DRM_SHM && SHMLBA > PAGE_SIZE) {
+		unsigned long slack = SHMLBA - PAGE_SIZE;
+
+		addr = get_unmapped_area(NULL, hint, len + slack, pgoff, flags);
+		if (!(addr & ~PAGE_MASK)) {
+			unsigned long kvirt = (unsigned long) map->handle;
+
+			if ((kvirt & (SHMLBA - 1)) != (addr & (SHMLBA - 1))) {
+				unsigned long koff, aoff;
+
+				koff = kvirt & (SHMLBA - 1);
+				aoff = addr & (SHMLBA - 1);
+				if (koff < aoff)
+					koff += SHMLBA;
+
+				addr += (koff - aoff);
+			}
+		}
+	} else {
+		addr = get_unmapped_area(NULL, hint, len, pgoff, flags);
+	}
+
+	return addr;
+}
+
+static int ffb_presetup(drm_device_t *dev)
+{
+	ffb_dev_priv_t	*ffb_priv;
+	int ret = 0;
+	int i = 0;
+
+	/* Check for the case where no device was found. */
+	if (ffb_position == NULL)
+		return -ENODEV;
+
+	/* code used to use numdevs no numdevs anymore */
+	ffb_priv = kmalloc(sizeof(ffb_dev_priv_t), GFP_KERNEL);
+	if (!ffb_priv)
+		return -ENOMEM;
+	memset(ffb_priv, 0, sizeof(*ffb_priv));
+	dev->dev_private = ffb_priv;
+
+	ret = ffb_init_one(dev,
+			   ffb_position[i].node,
+			   ffb_position[i].root,
+			   i);
+	return ret;
+}
+
+static void ffb_driver_release(drm_device_t *dev, struct file *filp)
+{
+	ffb_dev_priv_t *fpriv = (ffb_dev_priv_t *) dev->dev_private;
+	int context = _DRM_LOCKING_CONTEXT(dev->lock.hw_lock->lock);
+	int idx;
+
+	idx = context - 1;
+	if (fpriv &&
+	    context != DRM_KERNEL_CONTEXT &&
+	    fpriv->hw_state[idx] != NULL) {
+		kfree(fpriv->hw_state[idx]);
+		fpriv->hw_state[idx] = NULL;
+	}	
+}
+
+static void ffb_driver_pretakedown(drm_device_t *dev)
+{
+	if (dev->dev_private) kfree(dev->dev_private);
+}
+
+static int ffb_driver_postcleanup(drm_device_t *dev)
+{
+	if (ffb_position != NULL) kfree(ffb_position);
+	return 0;
+}
+
+static void ffb_driver_kernel_context_switch_unlock(struct drm_device *dev, drm_lock_t *lock)
+{
+	dev->lock.filp = 0;
+	{
+		__volatile__ unsigned int *plock = &dev->lock.hw_lock->lock;
+		unsigned int old, new, prev, ctx;
+		
+		ctx = lock->context;
+		do {
+			old  = *plock;
+			new  = ctx;
+			prev = cmpxchg(plock, old, new);
+		} while (prev != old);
+	}
+	wake_up_interruptible(&dev->lock.lock_queue);
+}
+
+static unsigned long ffb_driver_get_map_ofs(drm_map_t *map)
+{
+	return (map->offset & 0xffffffff);
+}
+
+static unsigned long ffb_driver_get_reg_ofs(drm_device_t *dev)
+{
+       ffb_dev_priv_t *ffb_priv = (ffb_dev_priv_t *)dev->dev_private;
+       
+       if (ffb_priv)
+               return ffb_priv->card_phys_base;
+       
+       return 0;
+}
+
+static int postinit( struct drm_device *dev, unsigned long flags ) 
+{
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->minor
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static drm_ioctl_desc_t ioctls[] = {
+	
+};
+
+static struct drm_driver driver = {
+	.driver_features = 0,
+	.dev_priv_size = sizeof(u32),
+	.release = ffb_driver_release,
+	.presetup = ffb_presetup,
+	.pretakedown = ffb_driver_pretakedown,
+	.postcleanup = ffb_driver_postcleanup,
+	.kernel_context_switch = ffb_driver_context_switch,
+	.kernel_context_switch_unlock = ffb_driver_kernel_context_switch_unlock,
+	.get_map_ofs = ffb_driver_get_map_ofs,
+	.get_reg_ofs = ffb_driver_get_reg_ofs,
+	.postinit = postinit,
+	.version = version,
+	.ioctls = ioctls,
+	.num_ioctls = DRM_ARRAY_SIZE(ioctls),
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+};
+
+static int __init ffb_init(void)
+{
+	return -ENODEV;
+}
+
+static void __exit ffb_exit(void)
+{
+}
+
+module_init(ffb_init);
+module_exit(ffb_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/ffb_drv.h b/drivers/char/drm/ffb_drv.h
new file mode 100644
index 0000000..8bf7f1e
--- /dev/null
+++ b/drivers/char/drm/ffb_drv.h
@@ -0,0 +1,286 @@
+/* $Id: ffb_drv.h,v 1.1 2000/06/01 04:24:39 davem Exp $
+ * ffb_drv.h: Creator/Creator3D direct rendering driver.
+ *
+ * Copyright (C) 2000 David S. Miller (davem@redhat.com)
+ */
+
+/* Auxilliary clips. */
+typedef struct  {
+	volatile unsigned int min;
+	volatile unsigned int max;
+} ffb_auxclip, *ffb_auxclipPtr;
+
+/* FFB register set. */
+typedef struct _ffb_fbc {
+	/* Next vertex registers, on the right we list which drawops
+	 * use said register and the logical name the register has in
+	 * that context.
+	 */					/* DESCRIPTION		DRAWOP(NAME)	*/
+/*0x00*/unsigned int		pad1[3];	/* Reserved				*/
+/*0x0c*/volatile unsigned int	alpha;		/* ALPHA Transparency			*/
+/*0x10*/volatile unsigned int	red;		/* RED					*/
+/*0x14*/volatile unsigned int	green;		/* GREEN				*/
+/*0x18*/volatile unsigned int	blue;		/* BLUE					*/
+/*0x1c*/volatile unsigned int	z;		/* DEPTH				*/
+/*0x20*/volatile unsigned int	y;		/* Y			triangle(DOYF)	*/
+						/*                      aadot(DYF)	*/
+						/*                      ddline(DYF)	*/
+						/*                      aaline(DYF)	*/
+/*0x24*/volatile unsigned int	x;		/* X			triangle(DOXF)	*/
+						/*                      aadot(DXF)	*/
+						/*                      ddline(DXF)	*/
+						/*                      aaline(DXF)	*/
+/*0x28*/unsigned int		pad2[2];	/* Reserved				*/
+/*0x30*/volatile unsigned int	ryf;		/* Y (alias to DOYF)	ddline(RYF)	*/
+						/*			aaline(RYF)	*/
+						/*			triangle(RYF)	*/
+/*0x34*/volatile unsigned int	rxf;		/* X			ddline(RXF)	*/
+						/*			aaline(RXF)	*/
+						/*			triangle(RXF)	*/
+/*0x38*/unsigned int		pad3[2];	/* Reserved				*/
+/*0x40*/volatile unsigned int	dmyf;		/* Y (alias to DOYF)	triangle(DMYF)	*/
+/*0x44*/volatile unsigned int	dmxf;		/* X			triangle(DMXF)	*/
+/*0x48*/unsigned int		pad4[2];	/* Reserved				*/
+/*0x50*/volatile unsigned int	ebyi;		/* Y (alias to RYI)	polygon(EBYI)	*/
+/*0x54*/volatile unsigned int	ebxi;		/* X			polygon(EBXI)	*/
+/*0x58*/unsigned int		pad5[2];	/* Reserved				*/
+/*0x60*/volatile unsigned int	by;		/* Y			brline(RYI)	*/
+						/*			fastfill(OP)	*/
+						/*			polygon(YI)	*/
+						/*			rectangle(YI)	*/
+						/*			bcopy(SRCY)	*/
+						/*			vscroll(SRCY)	*/
+/*0x64*/volatile unsigned int	bx;		/* X			brline(RXI)	*/
+						/*			polygon(XI)	*/
+						/*			rectangle(XI)	*/
+						/*			bcopy(SRCX)	*/
+						/*			vscroll(SRCX)	*/
+						/*			fastfill(GO)	*/
+/*0x68*/volatile unsigned int	dy;		/* destination Y	fastfill(DSTY)	*/
+						/*			bcopy(DSRY)	*/
+						/*			vscroll(DSRY)	*/
+/*0x6c*/volatile unsigned int	dx;		/* destination X	fastfill(DSTX)	*/
+						/*			bcopy(DSTX)	*/
+						/*			vscroll(DSTX)	*/
+/*0x70*/volatile unsigned int	bh;		/* Y (alias to RYI)	brline(DYI)	*/
+						/*			dot(DYI)	*/
+						/*			polygon(ETYI)	*/
+						/* Height		fastfill(H)	*/
+						/*			bcopy(H)	*/
+						/*			vscroll(H)	*/
+						/* Y count		fastfill(NY)	*/
+/*0x74*/volatile unsigned int	bw;		/* X			dot(DXI)	*/
+						/*			brline(DXI)	*/
+						/*			polygon(ETXI)	*/
+						/*			fastfill(W)	*/
+						/*			bcopy(W)	*/
+						/*			vscroll(W)	*/
+						/*			fastfill(NX)	*/
+/*0x78*/unsigned int		pad6[2];	/* Reserved				*/
+/*0x80*/unsigned int		pad7[32];	/* Reserved				*/
+	
+	/* Setup Unit's vertex state register */
+/*100*/	volatile unsigned int	suvtx;
+/*104*/	unsigned int		pad8[63];	/* Reserved				*/
+	
+	/* Frame Buffer Control Registers */
+/*200*/	volatile unsigned int	ppc;		/* Pixel Processor Control		*/
+/*204*/	volatile unsigned int	wid;		/* Current WID				*/
+/*208*/	volatile unsigned int	fg;		/* FG data				*/
+/*20c*/	volatile unsigned int	bg;		/* BG data				*/
+/*210*/	volatile unsigned int	consty;		/* Constant Y				*/
+/*214*/	volatile unsigned int	constz;		/* Constant Z				*/
+/*218*/	volatile unsigned int	xclip;		/* X Clip				*/
+/*21c*/	volatile unsigned int	dcss;		/* Depth Cue Scale Slope		*/
+/*220*/	volatile unsigned int	vclipmin;	/* Viewclip XY Min Bounds		*/
+/*224*/	volatile unsigned int	vclipmax;	/* Viewclip XY Max Bounds		*/
+/*228*/	volatile unsigned int	vclipzmin;	/* Viewclip Z Min Bounds		*/
+/*22c*/	volatile unsigned int	vclipzmax;	/* Viewclip Z Max Bounds		*/
+/*230*/	volatile unsigned int	dcsf;		/* Depth Cue Scale Front Bound		*/
+/*234*/	volatile unsigned int	dcsb;		/* Depth Cue Scale Back Bound		*/
+/*238*/	volatile unsigned int	dczf;		/* Depth Cue Z Front			*/
+/*23c*/	volatile unsigned int	dczb;		/* Depth Cue Z Back			*/
+/*240*/	unsigned int		pad9;		/* Reserved				*/
+/*244*/	volatile unsigned int	blendc;		/* Alpha Blend Control			*/
+/*248*/	volatile unsigned int	blendc1;	/* Alpha Blend Color 1			*/
+/*24c*/	volatile unsigned int	blendc2;	/* Alpha Blend Color 2			*/
+/*250*/	volatile unsigned int	fbramitc;	/* FB RAM Interleave Test Control	*/
+/*254*/	volatile unsigned int	fbc;		/* Frame Buffer Control			*/
+/*258*/	volatile unsigned int	rop;		/* Raster OPeration			*/
+/*25c*/	volatile unsigned int	cmp;		/* Frame Buffer Compare			*/
+/*260*/	volatile unsigned int	matchab;	/* Buffer AB Match Mask			*/
+/*264*/	volatile unsigned int	matchc;		/* Buffer C(YZ) Match Mask		*/
+/*268*/	volatile unsigned int	magnab;		/* Buffer AB Magnitude Mask		*/
+/*26c*/	volatile unsigned int	magnc;		/* Buffer C(YZ) Magnitude Mask		*/
+/*270*/	volatile unsigned int	fbcfg0;		/* Frame Buffer Config 0		*/
+/*274*/	volatile unsigned int	fbcfg1;		/* Frame Buffer Config 1		*/
+/*278*/	volatile unsigned int	fbcfg2;		/* Frame Buffer Config 2		*/
+/*27c*/	volatile unsigned int	fbcfg3;		/* Frame Buffer Config 3		*/
+/*280*/	volatile unsigned int	ppcfg;		/* Pixel Processor Config		*/
+/*284*/	volatile unsigned int	pick;		/* Picking Control			*/
+/*288*/	volatile unsigned int	fillmode;	/* FillMode				*/
+/*28c*/	volatile unsigned int	fbramwac;	/* FB RAM Write Address Control		*/
+/*290*/	volatile unsigned int	pmask;		/* RGB PlaneMask			*/
+/*294*/	volatile unsigned int	xpmask;		/* X PlaneMask				*/
+/*298*/	volatile unsigned int	ypmask;		/* Y PlaneMask				*/
+/*29c*/	volatile unsigned int	zpmask;		/* Z PlaneMask				*/
+/*2a0*/	ffb_auxclip		auxclip[4]; 	/* Auxilliary Viewport Clip		*/
+	
+	/* New 3dRAM III support regs */
+/*2c0*/	volatile unsigned int	rawblend2;
+/*2c4*/	volatile unsigned int	rawpreblend;
+/*2c8*/	volatile unsigned int	rawstencil;
+/*2cc*/	volatile unsigned int	rawstencilctl;
+/*2d0*/	volatile unsigned int	threedram1;
+/*2d4*/	volatile unsigned int	threedram2;
+/*2d8*/	volatile unsigned int	passin;
+/*2dc*/	volatile unsigned int	rawclrdepth;
+/*2e0*/	volatile unsigned int	rawpmask;
+/*2e4*/	volatile unsigned int	rawcsrc;
+/*2e8*/	volatile unsigned int	rawmatch;
+/*2ec*/	volatile unsigned int	rawmagn;
+/*2f0*/	volatile unsigned int	rawropblend;
+/*2f4*/	volatile unsigned int	rawcmp;
+/*2f8*/	volatile unsigned int	rawwac;
+/*2fc*/	volatile unsigned int	fbramid;
+	
+/*300*/	volatile unsigned int	drawop;		/* Draw OPeration			*/
+/*304*/	unsigned int		pad10[2];	/* Reserved				*/
+/*30c*/	volatile unsigned int	lpat;		/* Line Pattern control			*/
+/*310*/	unsigned int		pad11;		/* Reserved				*/
+/*314*/	volatile unsigned int	fontxy;		/* XY Font coordinate			*/
+/*318*/	volatile unsigned int	fontw;		/* Font Width				*/
+/*31c*/	volatile unsigned int	fontinc;	/* Font Increment			*/
+/*320*/	volatile unsigned int	font;		/* Font bits				*/
+/*324*/	unsigned int		pad12[3];	/* Reserved				*/
+/*330*/	volatile unsigned int	blend2;
+/*334*/	volatile unsigned int	preblend;
+/*338*/	volatile unsigned int	stencil;
+/*33c*/	volatile unsigned int	stencilctl;
+
+/*340*/	unsigned int		pad13[4];	/* Reserved				*/
+/*350*/	volatile unsigned int	dcss1;		/* Depth Cue Scale Slope 1		*/
+/*354*/	volatile unsigned int	dcss2;		/* Depth Cue Scale Slope 2		*/
+/*358*/	volatile unsigned int	dcss3;		/* Depth Cue Scale Slope 3		*/
+/*35c*/	volatile unsigned int	widpmask;
+/*360*/	volatile unsigned int	dcs2;
+/*364*/	volatile unsigned int	dcs3;
+/*368*/	volatile unsigned int	dcs4;
+/*36c*/	unsigned int		pad14;		/* Reserved				*/
+/*370*/	volatile unsigned int	dcd2;
+/*374*/	volatile unsigned int	dcd3;
+/*378*/	volatile unsigned int	dcd4;
+/*37c*/	unsigned int		pad15;		/* Reserved				*/
+/*380*/	volatile unsigned int	pattern[32];	/* area Pattern				*/
+/*400*/	unsigned int		pad16[8];	/* Reserved				*/
+/*420*/	volatile unsigned int	reset;		/* chip RESET				*/
+/*424*/	unsigned int		pad17[247];	/* Reserved				*/
+/*800*/	volatile unsigned int	devid;		/* Device ID				*/
+/*804*/	unsigned int		pad18[63];	/* Reserved				*/
+/*900*/	volatile unsigned int	ucsr;		/* User Control & Status Register	*/
+/*904*/	unsigned int		pad19[31];	/* Reserved				*/
+/*980*/	volatile unsigned int	mer;		/* Mode Enable Register			*/
+/*984*/	unsigned int		pad20[1439];	/* Reserved				*/
+} ffb_fbc, *ffb_fbcPtr;
+
+struct ffb_hw_context {
+	int is_2d_only;
+
+	unsigned int ppc;
+	unsigned int wid;
+	unsigned int fg;
+	unsigned int bg;
+	unsigned int consty;
+	unsigned int constz;
+	unsigned int xclip;
+	unsigned int dcss;
+	unsigned int vclipmin;
+	unsigned int vclipmax;
+	unsigned int vclipzmin;
+	unsigned int vclipzmax;
+	unsigned int dcsf;
+	unsigned int dcsb;
+	unsigned int dczf;
+	unsigned int dczb;
+	unsigned int blendc;
+	unsigned int blendc1;
+	unsigned int blendc2;
+	unsigned int fbc;
+	unsigned int rop;
+	unsigned int cmp;
+	unsigned int matchab;
+	unsigned int matchc;
+	unsigned int magnab;
+	unsigned int magnc;
+	unsigned int pmask;
+	unsigned int xpmask;
+	unsigned int ypmask;
+	unsigned int zpmask;
+	unsigned int auxclip0min;
+	unsigned int auxclip0max;
+	unsigned int auxclip1min;
+	unsigned int auxclip1max;
+	unsigned int auxclip2min;
+	unsigned int auxclip2max;
+	unsigned int auxclip3min;
+	unsigned int auxclip3max;
+	unsigned int drawop;
+	unsigned int lpat;
+	unsigned int fontxy;
+	unsigned int fontw;
+	unsigned int fontinc;
+	unsigned int area_pattern[32];
+	unsigned int ucsr;
+	unsigned int stencil;
+	unsigned int stencilctl;
+	unsigned int dcss1;
+	unsigned int dcss2;
+	unsigned int dcss3;
+	unsigned int dcs2;
+	unsigned int dcs3;
+	unsigned int dcs4;
+	unsigned int dcd2;
+	unsigned int dcd3;
+	unsigned int dcd4;
+	unsigned int mer;
+};
+
+#define FFB_MAX_CTXS	32
+
+enum ffb_chip_type {
+	ffb1_prototype = 0,	/* Early pre-FCS FFB */
+	ffb1_standard,		/* First FCS FFB, 100Mhz UPA, 66MHz gclk */
+	ffb1_speedsort,		/* Second FCS FFB, 100Mhz UPA, 75MHz gclk */
+	ffb2_prototype,		/* Early pre-FCS vertical FFB2 */
+	ffb2_vertical,		/* First FCS FFB2/vertical, 100Mhz UPA, 100MHZ gclk,
+				   75(SingleBuffer)/83(DoubleBuffer) MHz fclk */
+	ffb2_vertical_plus,	/* Second FCS FFB2/vertical, same timings */
+	ffb2_horizontal,	/* First FCS FFB2/horizontal, same timings as FFB2/vert */
+	ffb2_horizontal_plus,	/* Second FCS FFB2/horizontal, same timings */
+	afb_m3,			/* FCS Elite3D, 3 float chips */
+	afb_m6			/* FCS Elite3D, 6 float chips */
+};
+
+typedef struct ffb_dev_priv {
+	/* Misc software state. */
+	int			prom_node;
+	enum ffb_chip_type	ffb_type;
+	u64			card_phys_base;
+	struct miscdevice 	miscdev;
+
+	/* Controller registers. */
+	ffb_fbcPtr		regs;
+
+	/* Context table. */
+	struct ffb_hw_context	*hw_state[FFB_MAX_CTXS];
+} ffb_dev_priv_t;
+
+extern unsigned long ffb_get_unmapped_area(struct file *filp,
+					   unsigned long hint,
+					   unsigned long len,
+					   unsigned long pgoff,
+					   unsigned long flags);
+extern void ffb_set_context_ioctls(void);
+extern drm_ioctl_desc_t DRM(ioctls)[];
+
+extern int ffb_driver_context_switch(drm_device_t *dev, int old, int new);
diff --git a/drivers/char/drm/gamma_context.h b/drivers/char/drm/gamma_context.h
new file mode 100644
index 0000000..d11b507
--- /dev/null
+++ b/drivers/char/drm/gamma_context.h
@@ -0,0 +1,492 @@
+/* drm_context.h -- IOCTLs for generic contexts -*- linux-c -*-
+ * Created: Fri Nov 24 18:31:37 2000 by gareth@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ * ChangeLog:
+ *  2001-11-16	Torsten Duwe <duwe@caldera.de>
+ *		added context constructor/destructor hooks,
+ *		needed by SiS driver's memory management.
+ */
+
+/* ================================================================
+ * Old-style context support -- only used by gamma.  
+ */
+
+
+/* The drm_read and drm_write_string code (especially that which manages
+   the circular buffer), is based on Alessandro Rubini's LINUX DEVICE
+   DRIVERS (Cambridge: O'Reilly, 1998), pages 111-113. */
+
+ssize_t gamma_fops_read(struct file *filp, char __user *buf, size_t count, loff_t *off)
+{
+	drm_file_t    *priv   = filp->private_data;
+	drm_device_t  *dev    = priv->dev;
+	int	      left;
+	int	      avail;
+	int	      send;
+	int	      cur;
+
+	DRM_DEBUG("%p, %p\n", dev->buf_rp, dev->buf_wp);
+
+	while (dev->buf_rp == dev->buf_wp) {
+		DRM_DEBUG("  sleeping\n");
+		if (filp->f_flags & O_NONBLOCK) {
+			return -EAGAIN;
+		}
+		interruptible_sleep_on(&dev->buf_readers);
+		if (signal_pending(current)) {
+			DRM_DEBUG("  interrupted\n");
+			return -ERESTARTSYS;
+		}
+		DRM_DEBUG("  awake\n");
+	}
+
+	left  = (dev->buf_rp + DRM_BSZ - dev->buf_wp) % DRM_BSZ;
+	avail = DRM_BSZ - left;
+	send  = DRM_MIN(avail, count);
+
+	while (send) {
+		if (dev->buf_wp > dev->buf_rp) {
+			cur = DRM_MIN(send, dev->buf_wp - dev->buf_rp);
+		} else {
+			cur = DRM_MIN(send, dev->buf_end - dev->buf_rp);
+		}
+		if (copy_to_user(buf, dev->buf_rp, cur))
+			return -EFAULT;
+		dev->buf_rp += cur;
+		if (dev->buf_rp == dev->buf_end) dev->buf_rp = dev->buf;
+		send -= cur;
+	}
+
+	wake_up_interruptible(&dev->buf_writers);
+	return DRM_MIN(avail, count);
+}
+
+
+/* In an incredibly convoluted setup, the kernel module actually calls
+ * back into the X server to perform context switches on behalf of the
+ * 3d clients.
+ */
+int DRM(write_string)(drm_device_t *dev, const char *s)
+{
+	int left   = (dev->buf_rp + DRM_BSZ - dev->buf_wp) % DRM_BSZ;
+	int send   = strlen(s);
+	int count;
+
+	DRM_DEBUG("%d left, %d to send (%p, %p)\n",
+		  left, send, dev->buf_rp, dev->buf_wp);
+
+	if (left == 1 || dev->buf_wp != dev->buf_rp) {
+		DRM_ERROR("Buffer not empty (%d left, wp = %p, rp = %p)\n",
+			  left,
+			  dev->buf_wp,
+			  dev->buf_rp);
+	}
+
+	while (send) {
+		if (dev->buf_wp >= dev->buf_rp) {
+			count = DRM_MIN(send, dev->buf_end - dev->buf_wp);
+			if (count == left) --count; /* Leave a hole */
+		} else {
+			count = DRM_MIN(send, dev->buf_rp - dev->buf_wp - 1);
+		}
+		strncpy(dev->buf_wp, s, count);
+		dev->buf_wp += count;
+		if (dev->buf_wp == dev->buf_end) dev->buf_wp = dev->buf;
+		send -= count;
+	}
+
+	if (dev->buf_async) kill_fasync(&dev->buf_async, SIGIO, POLL_IN);
+
+	DRM_DEBUG("waking\n");
+	wake_up_interruptible(&dev->buf_readers);
+	return 0;
+}
+
+unsigned int gamma_fops_poll(struct file *filp, struct poll_table_struct *wait)
+{
+	drm_file_t   *priv = filp->private_data;
+	drm_device_t *dev  = priv->dev;
+
+	poll_wait(filp, &dev->buf_readers, wait);
+	if (dev->buf_wp != dev->buf_rp) return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+int DRM(context_switch)(drm_device_t *dev, int old, int new)
+{
+	char	    buf[64];
+	drm_queue_t *q;
+
+	if (test_and_set_bit(0, &dev->context_flag)) {
+		DRM_ERROR("Reentering -- FIXME\n");
+		return -EBUSY;
+	}
+
+	DRM_DEBUG("Context switch from %d to %d\n", old, new);
+
+	if (new >= dev->queue_count) {
+		clear_bit(0, &dev->context_flag);
+		return -EINVAL;
+	}
+
+	if (new == dev->last_context) {
+		clear_bit(0, &dev->context_flag);
+		return 0;
+	}
+
+	q = dev->queuelist[new];
+	atomic_inc(&q->use_count);
+	if (atomic_read(&q->use_count) == 1) {
+		atomic_dec(&q->use_count);
+		clear_bit(0, &dev->context_flag);
+		return -EINVAL;
+	}
+
+	/* This causes the X server to wake up & do a bunch of hardware
+	 * interaction to actually effect the context switch.
+	 */
+	sprintf(buf, "C %d %d\n", old, new);
+	DRM(write_string)(dev, buf);
+
+	atomic_dec(&q->use_count);
+
+	return 0;
+}
+
+int DRM(context_switch_complete)(drm_device_t *dev, int new)
+{
+	drm_device_dma_t *dma = dev->dma;
+
+	dev->last_context = new;  /* PRE/POST: This is the _only_ writer. */
+	dev->last_switch  = jiffies;
+
+	if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) {
+		DRM_ERROR("Lock isn't held after context switch\n");
+	}
+
+	if (!dma || !(dma->next_buffer && dma->next_buffer->while_locked)) {
+		if (DRM(lock_free)(dev, &dev->lock.hw_lock->lock,
+				  DRM_KERNEL_CONTEXT)) {
+			DRM_ERROR("Cannot free lock\n");
+		}
+	}
+
+	clear_bit(0, &dev->context_flag);
+	wake_up_interruptible(&dev->context_wait);
+
+	return 0;
+}
+
+static int DRM(init_queue)(drm_device_t *dev, drm_queue_t *q, drm_ctx_t *ctx)
+{
+	DRM_DEBUG("\n");
+
+	if (atomic_read(&q->use_count) != 1
+	    || atomic_read(&q->finalization)
+	    || atomic_read(&q->block_count)) {
+		DRM_ERROR("New queue is already in use: u%d f%d b%d\n",
+			  atomic_read(&q->use_count),
+			  atomic_read(&q->finalization),
+			  atomic_read(&q->block_count));
+	}
+
+	atomic_set(&q->finalization,  0);
+	atomic_set(&q->block_count,   0);
+	atomic_set(&q->block_read,    0);
+	atomic_set(&q->block_write,   0);
+	atomic_set(&q->total_queued,  0);
+	atomic_set(&q->total_flushed, 0);
+	atomic_set(&q->total_locks,   0);
+
+	init_waitqueue_head(&q->write_queue);
+	init_waitqueue_head(&q->read_queue);
+	init_waitqueue_head(&q->flush_queue);
+
+	q->flags = ctx->flags;
+
+	DRM(waitlist_create)(&q->waitlist, dev->dma->buf_count);
+
+	return 0;
+}
+
+
+/* drm_alloc_queue:
+PRE: 1) dev->queuelist[0..dev->queue_count] is allocated and will not
+	disappear (so all deallocation must be done after IOCTLs are off)
+     2) dev->queue_count < dev->queue_slots
+     3) dev->queuelist[i].use_count == 0 and
+	dev->queuelist[i].finalization == 0 if i not in use
+POST: 1) dev->queuelist[i].use_count == 1
+      2) dev->queue_count < dev->queue_slots */
+
+static int DRM(alloc_queue)(drm_device_t *dev)
+{
+	int	    i;
+	drm_queue_t *queue;
+	int	    oldslots;
+	int	    newslots;
+				/* Check for a free queue */
+	for (i = 0; i < dev->queue_count; i++) {
+		atomic_inc(&dev->queuelist[i]->use_count);
+		if (atomic_read(&dev->queuelist[i]->use_count) == 1
+		    && !atomic_read(&dev->queuelist[i]->finalization)) {
+			DRM_DEBUG("%d (free)\n", i);
+			return i;
+		}
+		atomic_dec(&dev->queuelist[i]->use_count);
+	}
+				/* Allocate a new queue */
+	down(&dev->struct_sem);
+
+	queue = DRM(alloc)(sizeof(*queue), DRM_MEM_QUEUES);
+	memset(queue, 0, sizeof(*queue));
+	atomic_set(&queue->use_count, 1);
+
+	++dev->queue_count;
+	if (dev->queue_count >= dev->queue_slots) {
+		oldslots = dev->queue_slots * sizeof(*dev->queuelist);
+		if (!dev->queue_slots) dev->queue_slots = 1;
+		dev->queue_slots *= 2;
+		newslots = dev->queue_slots * sizeof(*dev->queuelist);
+
+		dev->queuelist = DRM(realloc)(dev->queuelist,
+					      oldslots,
+					      newslots,
+					      DRM_MEM_QUEUES);
+		if (!dev->queuelist) {
+			up(&dev->struct_sem);
+			DRM_DEBUG("out of memory\n");
+			return -ENOMEM;
+		}
+	}
+	dev->queuelist[dev->queue_count-1] = queue;
+
+	up(&dev->struct_sem);
+	DRM_DEBUG("%d (new)\n", dev->queue_count - 1);
+	return dev->queue_count - 1;
+}
+
+int DRM(resctx)(struct inode *inode, struct file *filp,
+		unsigned int cmd, unsigned long arg)
+{
+	drm_ctx_res_t __user *argp = (void __user *)arg;
+	drm_ctx_res_t	res;
+	drm_ctx_t	ctx;
+	int		i;
+
+	DRM_DEBUG("%d\n", DRM_RESERVED_CONTEXTS);
+	if (copy_from_user(&res, argp, sizeof(res)))
+		return -EFAULT;
+	if (res.count >= DRM_RESERVED_CONTEXTS) {
+		memset(&ctx, 0, sizeof(ctx));
+		for (i = 0; i < DRM_RESERVED_CONTEXTS; i++) {
+			ctx.handle = i;
+			if (copy_to_user(&res.contexts[i],
+					 &i,
+					 sizeof(i)))
+				return -EFAULT;
+		}
+	}
+	res.count = DRM_RESERVED_CONTEXTS;
+	if (copy_to_user(argp, &res, sizeof(res)))
+		return -EFAULT;
+	return 0;
+}
+
+int DRM(addctx)(struct inode *inode, struct file *filp,
+		unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_t	ctx;
+	drm_ctx_t	__user *argp = (void __user *)arg;
+
+	if (copy_from_user(&ctx, argp, sizeof(ctx)))
+		return -EFAULT;
+	if ((ctx.handle = DRM(alloc_queue)(dev)) == DRM_KERNEL_CONTEXT) {
+				/* Init kernel's context and get a new one. */
+		DRM(init_queue)(dev, dev->queuelist[ctx.handle], &ctx);
+		ctx.handle = DRM(alloc_queue)(dev);
+	}
+	DRM(init_queue)(dev, dev->queuelist[ctx.handle], &ctx);
+	DRM_DEBUG("%d\n", ctx.handle);
+	if (copy_to_user(argp, &ctx, sizeof(ctx)))
+		return -EFAULT;
+	return 0;
+}
+
+int DRM(modctx)(struct inode *inode, struct file *filp,
+		unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_t	ctx;
+	drm_queue_t	*q;
+
+	if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+
+	DRM_DEBUG("%d\n", ctx.handle);
+
+	if (ctx.handle < 0 || ctx.handle >= dev->queue_count) return -EINVAL;
+	q = dev->queuelist[ctx.handle];
+
+	atomic_inc(&q->use_count);
+	if (atomic_read(&q->use_count) == 1) {
+				/* No longer in use */
+		atomic_dec(&q->use_count);
+		return -EINVAL;
+	}
+
+	if (DRM_BUFCOUNT(&q->waitlist)) {
+		atomic_dec(&q->use_count);
+		return -EBUSY;
+	}
+
+	q->flags = ctx.flags;
+
+	atomic_dec(&q->use_count);
+	return 0;
+}
+
+int DRM(getctx)(struct inode *inode, struct file *filp,
+		unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_t	__user *argp = (void __user *)arg;
+	drm_ctx_t	ctx;
+	drm_queue_t	*q;
+
+	if (copy_from_user(&ctx, argp, sizeof(ctx)))
+		return -EFAULT;
+
+	DRM_DEBUG("%d\n", ctx.handle);
+
+	if (ctx.handle >= dev->queue_count) return -EINVAL;
+	q = dev->queuelist[ctx.handle];
+
+	atomic_inc(&q->use_count);
+	if (atomic_read(&q->use_count) == 1) {
+				/* No longer in use */
+		atomic_dec(&q->use_count);
+		return -EINVAL;
+	}
+
+	ctx.flags = q->flags;
+	atomic_dec(&q->use_count);
+
+	if (copy_to_user(argp, &ctx, sizeof(ctx)))
+		return -EFAULT;
+
+	return 0;
+}
+
+int DRM(switchctx)(struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_t	ctx;
+
+	if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+	DRM_DEBUG("%d\n", ctx.handle);
+	return DRM(context_switch)(dev, dev->last_context, ctx.handle);
+}
+
+int DRM(newctx)(struct inode *inode, struct file *filp,
+		unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_t	ctx;
+
+	if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+	DRM_DEBUG("%d\n", ctx.handle);
+	DRM(context_switch_complete)(dev, ctx.handle);
+
+	return 0;
+}
+
+int DRM(rmctx)(struct inode *inode, struct file *filp,
+	       unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_t	ctx;
+	drm_queue_t	*q;
+	drm_buf_t	*buf;
+
+	if (copy_from_user(&ctx, (drm_ctx_t __user *)arg, sizeof(ctx)))
+		return -EFAULT;
+	DRM_DEBUG("%d\n", ctx.handle);
+
+	if (ctx.handle >= dev->queue_count) return -EINVAL;
+	q = dev->queuelist[ctx.handle];
+
+	atomic_inc(&q->use_count);
+	if (atomic_read(&q->use_count) == 1) {
+				/* No longer in use */
+		atomic_dec(&q->use_count);
+		return -EINVAL;
+	}
+
+	atomic_inc(&q->finalization); /* Mark queue in finalization state */
+	atomic_sub(2, &q->use_count); /* Mark queue as unused (pending
+					 finalization) */
+
+	while (test_and_set_bit(0, &dev->interrupt_flag)) {
+		schedule();
+		if (signal_pending(current)) {
+			clear_bit(0, &dev->interrupt_flag);
+			return -EINTR;
+		}
+	}
+				/* Remove queued buffers */
+	while ((buf = DRM(waitlist_get)(&q->waitlist))) {
+		DRM(free_buffer)(dev, buf);
+	}
+	clear_bit(0, &dev->interrupt_flag);
+
+				/* Wakeup blocked processes */
+	wake_up_interruptible(&q->read_queue);
+	wake_up_interruptible(&q->write_queue);
+	wake_up_interruptible(&q->flush_queue);
+
+				/* Finalization over.  Queue is made
+				   available when both use_count and
+				   finalization become 0, which won't
+				   happen until all the waiting processes
+				   stop waiting. */
+	atomic_dec(&q->finalization);
+	return 0;
+}
+
diff --git a/drivers/char/drm/gamma_dma.c b/drivers/char/drm/gamma_dma.c
new file mode 100644
index 0000000..e486fb8
--- /dev/null
+++ b/drivers/char/drm/gamma_dma.c
@@ -0,0 +1,946 @@
+/* gamma_dma.c -- DMA support for GMX 2000 -*- linux-c -*-
+ * Created: Fri Mar 19 14:30:16 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *
+ */
+
+#include "gamma.h"
+#include "drmP.h"
+#include "drm.h"
+#include "gamma_drm.h"
+#include "gamma_drv.h"
+
+#include <linux/interrupt.h>	/* For task queue support */
+#include <linux/delay.h>
+
+static inline void gamma_dma_dispatch(drm_device_t *dev, unsigned long address,
+				      unsigned long length)
+{
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+	mb();
+	while ( GAMMA_READ(GAMMA_INFIFOSPACE) < 2)
+		cpu_relax();
+
+	GAMMA_WRITE(GAMMA_DMAADDRESS, address);
+
+	while (GAMMA_READ(GAMMA_GCOMMANDSTATUS) != 4)
+		cpu_relax();
+
+	GAMMA_WRITE(GAMMA_DMACOUNT, length / 4);
+}
+
+void gamma_dma_quiescent_single(drm_device_t *dev)
+{
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+	while (GAMMA_READ(GAMMA_DMACOUNT))
+		cpu_relax();
+
+	while (GAMMA_READ(GAMMA_INFIFOSPACE) < 2)
+		cpu_relax();
+
+	GAMMA_WRITE(GAMMA_FILTERMODE, 1 << 10);
+	GAMMA_WRITE(GAMMA_SYNC, 0);
+
+	do {
+		while (!GAMMA_READ(GAMMA_OUTFIFOWORDS))
+			cpu_relax();
+	} while (GAMMA_READ(GAMMA_OUTPUTFIFO) != GAMMA_SYNC_TAG);
+}
+
+void gamma_dma_quiescent_dual(drm_device_t *dev)
+{
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+	while (GAMMA_READ(GAMMA_DMACOUNT))
+		cpu_relax();
+
+	while (GAMMA_READ(GAMMA_INFIFOSPACE) < 3)
+		cpu_relax();
+
+	GAMMA_WRITE(GAMMA_BROADCASTMASK, 3);
+	GAMMA_WRITE(GAMMA_FILTERMODE, 1 << 10);
+	GAMMA_WRITE(GAMMA_SYNC, 0);
+
+	/* Read from first MX */
+	do {
+		while (!GAMMA_READ(GAMMA_OUTFIFOWORDS))
+			cpu_relax();
+	} while (GAMMA_READ(GAMMA_OUTPUTFIFO) != GAMMA_SYNC_TAG);
+
+	/* Read from second MX */
+	do {
+		while (!GAMMA_READ(GAMMA_OUTFIFOWORDS + 0x10000))
+			cpu_relax();
+	} while (GAMMA_READ(GAMMA_OUTPUTFIFO + 0x10000) != GAMMA_SYNC_TAG);
+}
+
+void gamma_dma_ready(drm_device_t *dev)
+{
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+	while (GAMMA_READ(GAMMA_DMACOUNT))
+		cpu_relax();
+}
+
+static inline int gamma_dma_is_ready(drm_device_t *dev)
+{
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+	return (!GAMMA_READ(GAMMA_DMACOUNT));
+}
+
+irqreturn_t gamma_driver_irq_handler( DRM_IRQ_ARGS )
+{
+	drm_device_t	 *dev = (drm_device_t *)arg;
+	drm_device_dma_t *dma = dev->dma;
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+
+	/* FIXME: should check whether we're actually interested in the interrupt? */
+	atomic_inc(&dev->counts[6]); /* _DRM_STAT_IRQ */
+
+	while (GAMMA_READ(GAMMA_INFIFOSPACE) < 3)
+		cpu_relax();
+
+	GAMMA_WRITE(GAMMA_GDELAYTIMER, 0xc350/2); /* 0x05S */
+	GAMMA_WRITE(GAMMA_GCOMMANDINTFLAGS, 8);
+	GAMMA_WRITE(GAMMA_GINTFLAGS, 0x2001);
+	if (gamma_dma_is_ready(dev)) {
+				/* Free previous buffer */
+		if (test_and_set_bit(0, &dev->dma_flag))
+			return IRQ_HANDLED;
+		if (dma->this_buffer) {
+			gamma_free_buffer(dev, dma->this_buffer);
+			dma->this_buffer = NULL;
+		}
+		clear_bit(0, &dev->dma_flag);
+
+		/* Dispatch new buffer */
+		schedule_work(&dev->work);
+	}
+	return IRQ_HANDLED;
+}
+
+/* Only called by gamma_dma_schedule. */
+static int gamma_do_dma(drm_device_t *dev, int locked)
+{
+	unsigned long	 address;
+	unsigned long	 length;
+	drm_buf_t	 *buf;
+	int		 retcode = 0;
+	drm_device_dma_t *dma = dev->dma;
+
+	if (test_and_set_bit(0, &dev->dma_flag)) return -EBUSY;
+
+
+	if (!dma->next_buffer) {
+		DRM_ERROR("No next_buffer\n");
+		clear_bit(0, &dev->dma_flag);
+		return -EINVAL;
+	}
+
+	buf	= dma->next_buffer;
+	/* WE NOW ARE ON LOGICAL PAGES!! - using page table setup in dma_init */
+	/* So we pass the buffer index value into the physical page offset */
+	address = buf->idx << 12;
+	length	= buf->used;
+
+	DRM_DEBUG("context %d, buffer %d (%ld bytes)\n",
+		  buf->context, buf->idx, length);
+
+	if (buf->list == DRM_LIST_RECLAIM) {
+		gamma_clear_next_buffer(dev);
+		gamma_free_buffer(dev, buf);
+		clear_bit(0, &dev->dma_flag);
+		return -EINVAL;
+	}
+
+	if (!length) {
+		DRM_ERROR("0 length buffer\n");
+		gamma_clear_next_buffer(dev);
+		gamma_free_buffer(dev, buf);
+		clear_bit(0, &dev->dma_flag);
+		return 0;
+	}
+
+	if (!gamma_dma_is_ready(dev)) {
+		clear_bit(0, &dev->dma_flag);
+		return -EBUSY;
+	}
+
+	if (buf->while_locked) {
+		if (!_DRM_LOCK_IS_HELD(dev->lock.hw_lock->lock)) {
+			DRM_ERROR("Dispatching buffer %d from pid %d"
+				  " \"while locked\", but no lock held\n",
+				  buf->idx, current->pid);
+		}
+	} else {
+		if (!locked && !gamma_lock_take(&dev->lock.hw_lock->lock,
+					      DRM_KERNEL_CONTEXT)) {
+			clear_bit(0, &dev->dma_flag);
+			return -EBUSY;
+		}
+	}
+
+	if (dev->last_context != buf->context
+	    && !(dev->queuelist[buf->context]->flags
+		 & _DRM_CONTEXT_PRESERVED)) {
+				/* PRE: dev->last_context != buf->context */
+		if (DRM(context_switch)(dev, dev->last_context,
+					buf->context)) {
+			DRM(clear_next_buffer)(dev);
+			DRM(free_buffer)(dev, buf);
+		}
+		retcode = -EBUSY;
+		goto cleanup;
+
+				/* POST: we will wait for the context
+				   switch and will dispatch on a later call
+				   when dev->last_context == buf->context.
+				   NOTE WE HOLD THE LOCK THROUGHOUT THIS
+				   TIME! */
+	}
+
+	gamma_clear_next_buffer(dev);
+	buf->pending	 = 1;
+	buf->waiting	 = 0;
+	buf->list	 = DRM_LIST_PEND;
+
+	/* WE NOW ARE ON LOGICAL PAGES!!! - overriding address */
+	address = buf->idx << 12;
+
+	gamma_dma_dispatch(dev, address, length);
+	gamma_free_buffer(dev, dma->this_buffer);
+	dma->this_buffer = buf;
+
+	atomic_inc(&dev->counts[7]); /* _DRM_STAT_DMA */
+	atomic_add(length, &dev->counts[8]); /* _DRM_STAT_PRIMARY */
+
+	if (!buf->while_locked && !dev->context_flag && !locked) {
+		if (gamma_lock_free(dev, &dev->lock.hw_lock->lock,
+				  DRM_KERNEL_CONTEXT)) {
+			DRM_ERROR("\n");
+		}
+	}
+cleanup:
+
+	clear_bit(0, &dev->dma_flag);
+
+
+	return retcode;
+}
+
+static void gamma_dma_timer_bh(unsigned long dev)
+{
+	gamma_dma_schedule((drm_device_t *)dev, 0);
+}
+
+void gamma_irq_immediate_bh(void *dev)
+{
+	gamma_dma_schedule(dev, 0);
+}
+
+int gamma_dma_schedule(drm_device_t *dev, int locked)
+{
+	int		 next;
+	drm_queue_t	 *q;
+	drm_buf_t	 *buf;
+	int		 retcode   = 0;
+	int		 processed = 0;
+	int		 missed;
+	int		 expire	   = 20;
+	drm_device_dma_t *dma	   = dev->dma;
+
+	if (test_and_set_bit(0, &dev->interrupt_flag)) {
+				/* Not reentrant */
+		atomic_inc(&dev->counts[10]); /* _DRM_STAT_MISSED */
+		return -EBUSY;
+	}
+	missed = atomic_read(&dev->counts[10]);
+
+
+again:
+	if (dev->context_flag) {
+		clear_bit(0, &dev->interrupt_flag);
+		return -EBUSY;
+	}
+	if (dma->next_buffer) {
+				/* Unsent buffer that was previously
+				   selected, but that couldn't be sent
+				   because the lock could not be obtained
+				   or the DMA engine wasn't ready.  Try
+				   again. */
+		if (!(retcode = gamma_do_dma(dev, locked))) ++processed;
+	} else {
+		do {
+			next = gamma_select_queue(dev, gamma_dma_timer_bh);
+			if (next >= 0) {
+				q   = dev->queuelist[next];
+				buf = gamma_waitlist_get(&q->waitlist);
+				dma->next_buffer = buf;
+				dma->next_queue	 = q;
+				if (buf && buf->list == DRM_LIST_RECLAIM) {
+					gamma_clear_next_buffer(dev);
+					gamma_free_buffer(dev, buf);
+				}
+			}
+		} while (next >= 0 && !dma->next_buffer);
+		if (dma->next_buffer) {
+			if (!(retcode = gamma_do_dma(dev, locked))) {
+				++processed;
+			}
+		}
+	}
+
+	if (--expire) {
+		if (missed != atomic_read(&dev->counts[10])) {
+			if (gamma_dma_is_ready(dev)) goto again;
+		}
+		if (processed && gamma_dma_is_ready(dev)) {
+			processed = 0;
+			goto again;
+		}
+	}
+
+	clear_bit(0, &dev->interrupt_flag);
+
+	return retcode;
+}
+
+static int gamma_dma_priority(struct file *filp, 
+			      drm_device_t *dev, drm_dma_t *d)
+{
+	unsigned long	  address;
+	unsigned long	  length;
+	int		  must_free = 0;
+	int		  retcode   = 0;
+	int		  i;
+	int		  idx;
+	drm_buf_t	  *buf;
+	drm_buf_t	  *last_buf = NULL;
+	drm_device_dma_t  *dma	    = dev->dma;
+	int		  *send_indices = NULL;
+	int		  *send_sizes = NULL;
+
+	DECLARE_WAITQUEUE(entry, current);
+
+				/* Turn off interrupt handling */
+	while (test_and_set_bit(0, &dev->interrupt_flag)) {
+		schedule();
+		if (signal_pending(current)) return -EINTR;
+	}
+	if (!(d->flags & _DRM_DMA_WHILE_LOCKED)) {
+		while (!gamma_lock_take(&dev->lock.hw_lock->lock,
+				      DRM_KERNEL_CONTEXT)) {
+			schedule();
+			if (signal_pending(current)) {
+				clear_bit(0, &dev->interrupt_flag);
+				return -EINTR;
+			}
+		}
+		++must_free;
+	}
+
+	send_indices = DRM(alloc)(d->send_count * sizeof(*send_indices),
+				  DRM_MEM_DRIVER);
+	if (send_indices == NULL)
+		return -ENOMEM;
+	if (copy_from_user(send_indices, d->send_indices, 
+			   d->send_count * sizeof(*send_indices))) {
+		retcode = -EFAULT;
+                goto cleanup;
+	}
+	
+	send_sizes = DRM(alloc)(d->send_count * sizeof(*send_sizes),
+				DRM_MEM_DRIVER);
+	if (send_sizes == NULL)
+		return -ENOMEM;
+	if (copy_from_user(send_sizes, d->send_sizes, 
+			   d->send_count * sizeof(*send_sizes))) {
+		retcode = -EFAULT;
+                goto cleanup;
+	}
+
+	for (i = 0; i < d->send_count; i++) {
+		idx = send_indices[i];
+		if (idx < 0 || idx >= dma->buf_count) {
+			DRM_ERROR("Index %d (of %d max)\n",
+				  send_indices[i], dma->buf_count - 1);
+			continue;
+		}
+		buf = dma->buflist[ idx ];
+		if (buf->filp != filp) {
+			DRM_ERROR("Process %d using buffer not owned\n",
+				  current->pid);
+			retcode = -EINVAL;
+			goto cleanup;
+		}
+		if (buf->list != DRM_LIST_NONE) {
+			DRM_ERROR("Process %d using buffer on list %d\n",
+				  current->pid, buf->list);
+			retcode = -EINVAL;
+			goto cleanup;
+		}
+				/* This isn't a race condition on
+				   buf->list, since our concern is the
+				   buffer reclaim during the time the
+				   process closes the /dev/drm? handle, so
+				   it can't also be doing DMA. */
+		buf->list	  = DRM_LIST_PRIO;
+		buf->used	  = send_sizes[i];
+		buf->context	  = d->context;
+		buf->while_locked = d->flags & _DRM_DMA_WHILE_LOCKED;
+		address		  = (unsigned long)buf->address;
+		length		  = buf->used;
+		if (!length) {
+			DRM_ERROR("0 length buffer\n");
+		}
+		if (buf->pending) {
+			DRM_ERROR("Sending pending buffer:"
+				  " buffer %d, offset %d\n",
+				  send_indices[i], i);
+			retcode = -EINVAL;
+			goto cleanup;
+		}
+		if (buf->waiting) {
+			DRM_ERROR("Sending waiting buffer:"
+				  " buffer %d, offset %d\n",
+				  send_indices[i], i);
+			retcode = -EINVAL;
+			goto cleanup;
+		}
+		buf->pending = 1;
+
+		if (dev->last_context != buf->context
+		    && !(dev->queuelist[buf->context]->flags
+			 & _DRM_CONTEXT_PRESERVED)) {
+			add_wait_queue(&dev->context_wait, &entry);
+			current->state = TASK_INTERRUPTIBLE;
+				/* PRE: dev->last_context != buf->context */
+			DRM(context_switch)(dev, dev->last_context,
+					    buf->context);
+				/* POST: we will wait for the context
+				   switch and will dispatch on a later call
+				   when dev->last_context == buf->context.
+				   NOTE WE HOLD THE LOCK THROUGHOUT THIS
+				   TIME! */
+			schedule();
+			current->state = TASK_RUNNING;
+			remove_wait_queue(&dev->context_wait, &entry);
+			if (signal_pending(current)) {
+				retcode = -EINTR;
+				goto cleanup;
+			}
+			if (dev->last_context != buf->context) {
+				DRM_ERROR("Context mismatch: %d %d\n",
+					  dev->last_context,
+					  buf->context);
+			}
+		}
+
+		gamma_dma_dispatch(dev, address, length);
+		atomic_inc(&dev->counts[9]); /* _DRM_STAT_SPECIAL */
+		atomic_add(length, &dev->counts[8]); /* _DRM_STAT_PRIMARY */
+
+		if (last_buf) {
+			gamma_free_buffer(dev, last_buf);
+		}
+		last_buf = buf;
+	}
+
+
+cleanup:
+	if (last_buf) {
+		gamma_dma_ready(dev);
+		gamma_free_buffer(dev, last_buf);
+	}
+	if (send_indices)
+		DRM(free)(send_indices, d->send_count * sizeof(*send_indices), 
+			  DRM_MEM_DRIVER);
+	if (send_sizes)
+		DRM(free)(send_sizes, d->send_count * sizeof(*send_sizes), 
+			  DRM_MEM_DRIVER);
+
+	if (must_free && !dev->context_flag) {
+		if (gamma_lock_free(dev, &dev->lock.hw_lock->lock,
+				  DRM_KERNEL_CONTEXT)) {
+			DRM_ERROR("\n");
+		}
+	}
+	clear_bit(0, &dev->interrupt_flag);
+	return retcode;
+}
+
+static int gamma_dma_send_buffers(struct file *filp,
+				  drm_device_t *dev, drm_dma_t *d)
+{
+	DECLARE_WAITQUEUE(entry, current);
+	drm_buf_t	  *last_buf = NULL;
+	int		  retcode   = 0;
+	drm_device_dma_t  *dma	    = dev->dma;
+	int               send_index;
+
+	if (get_user(send_index, &d->send_indices[d->send_count-1]))
+		return -EFAULT;
+
+	if (d->flags & _DRM_DMA_BLOCK) {
+		last_buf = dma->buflist[send_index];
+		add_wait_queue(&last_buf->dma_wait, &entry);
+	}
+
+	if ((retcode = gamma_dma_enqueue(filp, d))) {
+		if (d->flags & _DRM_DMA_BLOCK)
+			remove_wait_queue(&last_buf->dma_wait, &entry);
+		return retcode;
+	}
+
+	gamma_dma_schedule(dev, 0);
+
+	if (d->flags & _DRM_DMA_BLOCK) {
+		DRM_DEBUG("%d waiting\n", current->pid);
+		for (;;) {
+			current->state = TASK_INTERRUPTIBLE;
+			if (!last_buf->waiting && !last_buf->pending)
+				break; /* finished */
+			schedule();
+			if (signal_pending(current)) {
+				retcode = -EINTR; /* Can't restart */
+				break;
+			}
+		}
+		current->state = TASK_RUNNING;
+		DRM_DEBUG("%d running\n", current->pid);
+		remove_wait_queue(&last_buf->dma_wait, &entry);
+		if (!retcode
+		    || (last_buf->list==DRM_LIST_PEND && !last_buf->pending)) {
+			if (!waitqueue_active(&last_buf->dma_wait)) {
+				gamma_free_buffer(dev, last_buf);
+			}
+		}
+		if (retcode) {
+			DRM_ERROR("ctx%d w%d p%d c%ld i%d l%d pid:%d\n",
+				  d->context,
+				  last_buf->waiting,
+				  last_buf->pending,
+				  (long)DRM_WAITCOUNT(dev, d->context),
+				  last_buf->idx,
+				  last_buf->list,
+				  current->pid);
+		}
+	}
+	return retcode;
+}
+
+int gamma_dma(struct inode *inode, struct file *filp, unsigned int cmd,
+	      unsigned long arg)
+{
+	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->dev;
+	drm_device_dma_t  *dma	    = dev->dma;
+	int		  retcode   = 0;
+	drm_dma_t	  __user *argp = (void __user *)arg;
+	drm_dma_t	  d;
+
+	if (copy_from_user(&d, argp, sizeof(d)))
+		return -EFAULT;
+
+	if (d.send_count < 0 || d.send_count > dma->buf_count) {
+		DRM_ERROR("Process %d trying to send %d buffers (of %d max)\n",
+			  current->pid, d.send_count, dma->buf_count);
+		return -EINVAL;
+	}
+
+	if (d.request_count < 0 || d.request_count > dma->buf_count) {
+		DRM_ERROR("Process %d trying to get %d buffers (of %d max)\n",
+			  current->pid, d.request_count, dma->buf_count);
+		return -EINVAL;
+	}
+
+	if (d.send_count) {
+		if (d.flags & _DRM_DMA_PRIORITY)
+			retcode = gamma_dma_priority(filp, dev, &d);
+		else
+			retcode = gamma_dma_send_buffers(filp, dev, &d);
+	}
+
+	d.granted_count = 0;
+
+	if (!retcode && d.request_count) {
+		retcode = gamma_dma_get_buffers(filp, &d);
+	}
+
+	DRM_DEBUG("%d returning, granted = %d\n",
+		  current->pid, d.granted_count);
+	if (copy_to_user(argp, &d, sizeof(d)))
+		return -EFAULT;
+
+	return retcode;
+}
+
+/* =============================================================
+ * DMA initialization, cleanup
+ */
+
+static int gamma_do_init_dma( drm_device_t *dev, drm_gamma_init_t *init )
+{
+	drm_gamma_private_t *dev_priv;
+	drm_device_dma_t    *dma = dev->dma;
+	drm_buf_t	    *buf;
+	int i;
+	struct list_head    *list;
+	unsigned long	    *pgt;
+
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	dev_priv = DRM(alloc)( sizeof(drm_gamma_private_t),
+							DRM_MEM_DRIVER );
+	if ( !dev_priv )
+		return -ENOMEM;
+
+	dev->dev_private = (void *)dev_priv;
+
+	memset( dev_priv, 0, sizeof(drm_gamma_private_t) );
+
+	dev_priv->num_rast = init->num_rast;
+
+	list_for_each(list, &dev->maplist->head) {
+		drm_map_list_t *r_list = list_entry(list, drm_map_list_t, head);
+		if( r_list->map &&
+		    r_list->map->type == _DRM_SHM &&
+		    r_list->map->flags & _DRM_CONTAINS_LOCK ) {
+			dev_priv->sarea = r_list->map;
+ 			break;
+ 		}
+ 	}
+	
+	dev_priv->mmio0 = drm_core_findmap(dev, init->mmio0);
+	dev_priv->mmio1 = drm_core_findmap(dev, init->mmio1);
+	dev_priv->mmio2 = drm_core_findmap(dev, init->mmio2);
+	dev_priv->mmio3 = drm_core_findmap(dev, init->mmio3);
+	
+	dev_priv->sarea_priv = (drm_gamma_sarea_t *)
+		((u8 *)dev_priv->sarea->handle +
+		 init->sarea_priv_offset);
+
+	if (init->pcimode) {
+		buf = dma->buflist[GLINT_DRI_BUF_COUNT];
+		pgt = buf->address;
+
+ 		for (i = 0; i < GLINT_DRI_BUF_COUNT; i++) {
+			buf = dma->buflist[i];
+			*pgt = virt_to_phys((void*)buf->address) | 0x07;
+			pgt++;
+		}
+
+		buf = dma->buflist[GLINT_DRI_BUF_COUNT];
+	} else {
+		dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+		drm_core_ioremap( dev->agp_buffer_map, dev);
+
+		buf = dma->buflist[GLINT_DRI_BUF_COUNT];
+		pgt = buf->address;
+
+ 		for (i = 0; i < GLINT_DRI_BUF_COUNT; i++) {
+			buf = dma->buflist[i];
+			*pgt = (unsigned long)buf->address + 0x07;
+			pgt++;
+		}
+
+		buf = dma->buflist[GLINT_DRI_BUF_COUNT];
+
+		while (GAMMA_READ(GAMMA_INFIFOSPACE) < 1);
+		GAMMA_WRITE( GAMMA_GDMACONTROL, 0xe);
+	}
+	while (GAMMA_READ(GAMMA_INFIFOSPACE) < 2);
+	GAMMA_WRITE( GAMMA_PAGETABLEADDR, virt_to_phys((void*)buf->address) );
+	GAMMA_WRITE( GAMMA_PAGETABLELENGTH, 2 );
+
+	return 0;
+}
+
+int gamma_do_cleanup_dma( drm_device_t *dev )
+{
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	/* Make sure interrupts are disabled here because the uninstall ioctl
+	 * may not have been called from userspace and after dev_private
+	 * is freed, it's too late.
+	 */
+	if (drm_core_check_feature(dev, DRIVER_HAVE_IRQ))
+		if ( dev->irq_enabled ) 
+			DRM(irq_uninstall)(dev);
+
+	if ( dev->dev_private ) {
+
+		if ( dev->agp_buffer_map != NULL )
+			drm_core_ioremapfree( dev->agp_buffer_map, dev );
+
+		DRM(free)( dev->dev_private, sizeof(drm_gamma_private_t),
+			   DRM_MEM_DRIVER );
+		dev->dev_private = NULL;
+	}
+
+	return 0;
+}
+
+int gamma_dma_init( struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->dev;
+	drm_gamma_init_t init;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( copy_from_user( &init, (drm_gamma_init_t __user *)arg, sizeof(init) ) )
+		return -EFAULT;
+
+	switch ( init.func ) {
+	case GAMMA_INIT_DMA:
+		return gamma_do_init_dma( dev, &init );
+	case GAMMA_CLEANUP_DMA:
+		return gamma_do_cleanup_dma( dev );
+	}
+
+	return -EINVAL;
+}
+
+static int gamma_do_copy_dma( drm_device_t *dev, drm_gamma_copy_t *copy )
+{
+	drm_device_dma_t    *dma = dev->dma;
+	unsigned int        *screenbuf;
+
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	/* We've DRM_RESTRICTED this DMA buffer */
+
+	screenbuf = dma->buflist[ GLINT_DRI_BUF_COUNT + 1 ]->address;
+
+#if 0
+	*buffer++ = 0x180;	/* Tag (FilterMode) */
+	*buffer++ = 0x200;	/* Allow FBColor through */
+	*buffer++ = 0x53B;	/* Tag */
+	*buffer++ = copy->Pitch;
+	*buffer++ = 0x53A;	/* Tag */
+	*buffer++ = copy->SrcAddress;
+	*buffer++ = 0x539;	/* Tag */
+	*buffer++ = copy->WidthHeight; /* Initiates transfer */
+	*buffer++ = 0x53C;	/* Tag - DMAOutputAddress */
+	*buffer++ = virt_to_phys((void*)screenbuf);
+	*buffer++ = 0x53D;	/* Tag - DMAOutputCount */
+	*buffer++ = copy->Count; /* Reads HostOutFifo BLOCKS until ..*/
+
+	/* Data now sitting in dma->buflist[ GLINT_DRI_BUF_COUNT + 1 ] */
+	/* Now put it back to the screen */
+
+	*buffer++ = 0x180;	/* Tag (FilterMode) */
+	*buffer++ = 0x400;	/* Allow Sync through */
+	*buffer++ = 0x538;	/* Tag - DMARectangleReadTarget */
+	*buffer++ = 0x155;	/* FBSourceData | count */
+	*buffer++ = 0x537;	/* Tag */
+	*buffer++ = copy->Pitch;
+	*buffer++ = 0x536;	/* Tag */
+	*buffer++ = copy->DstAddress;
+	*buffer++ = 0x535;	/* Tag */
+	*buffer++ = copy->WidthHeight; /* Initiates transfer */
+	*buffer++ = 0x530;	/* Tag - DMAAddr */
+	*buffer++ = virt_to_phys((void*)screenbuf);
+	*buffer++ = 0x531;
+	*buffer++ = copy->Count; /* initiates DMA transfer of color data */
+#endif
+
+	/* need to dispatch it now */
+
+	return 0;
+}
+
+int gamma_dma_copy( struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg )
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->dev;
+	drm_gamma_copy_t copy;
+
+	if ( copy_from_user( &copy, (drm_gamma_copy_t __user *)arg, sizeof(copy) ) )
+		return -EFAULT;
+
+	return gamma_do_copy_dma( dev, &copy );
+}
+
+/* =============================================================
+ * Per Context SAREA Support
+ */
+
+int gamma_getsareactx(struct inode *inode, struct file *filp,
+		     unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_priv_map_t __user *argp = (void __user *)arg;
+	drm_ctx_priv_map_t request;
+	drm_map_t *map;
+
+	if (copy_from_user(&request, argp, sizeof(request)))
+		return -EFAULT;
+
+	down(&dev->struct_sem);
+	if ((int)request.ctx_id >= dev->max_context) {
+		up(&dev->struct_sem);
+		return -EINVAL;
+	}
+
+	map = dev->context_sareas[request.ctx_id];
+	up(&dev->struct_sem);
+
+	request.handle = map->handle;
+	if (copy_to_user(argp, &request, sizeof(request)))
+		return -EFAULT;
+	return 0;
+}
+
+int gamma_setsareactx(struct inode *inode, struct file *filp,
+		     unsigned int cmd, unsigned long arg)
+{
+	drm_file_t	*priv	= filp->private_data;
+	drm_device_t	*dev	= priv->dev;
+	drm_ctx_priv_map_t request;
+	drm_map_t *map = NULL;
+	drm_map_list_t *r_list;
+	struct list_head *list;
+
+	if (copy_from_user(&request,
+			   (drm_ctx_priv_map_t __user *)arg,
+			   sizeof(request)))
+		return -EFAULT;
+
+	down(&dev->struct_sem);
+	r_list = NULL;
+	list_for_each(list, &dev->maplist->head) {
+		r_list = list_entry(list, drm_map_list_t, head);
+		if(r_list->map &&
+		   r_list->map->handle == request.handle) break;
+	}
+	if (list == &(dev->maplist->head)) {
+		up(&dev->struct_sem);
+		return -EINVAL;
+	}
+	map = r_list->map;
+	up(&dev->struct_sem);
+
+	if (!map) return -EINVAL;
+
+	down(&dev->struct_sem);
+	if ((int)request.ctx_id >= dev->max_context) {
+		up(&dev->struct_sem);
+		return -EINVAL;
+	}
+	dev->context_sareas[request.ctx_id] = map;
+	up(&dev->struct_sem);
+	return 0;
+}
+
+void gamma_driver_irq_preinstall( drm_device_t *dev ) {
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+
+	while(GAMMA_READ(GAMMA_INFIFOSPACE) < 2)
+		cpu_relax();
+
+	GAMMA_WRITE( GAMMA_GCOMMANDMODE,	0x00000004 );
+	GAMMA_WRITE( GAMMA_GDMACONTROL,		0x00000000 );
+}
+
+void gamma_driver_irq_postinstall( drm_device_t *dev ) {
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+
+	while(GAMMA_READ(GAMMA_INFIFOSPACE) < 3)
+		cpu_relax();
+
+	GAMMA_WRITE( GAMMA_GINTENABLE,		0x00002001 );
+	GAMMA_WRITE( GAMMA_COMMANDINTENABLE,	0x00000008 );
+	GAMMA_WRITE( GAMMA_GDELAYTIMER,		0x00039090 );
+}
+
+void gamma_driver_irq_uninstall( drm_device_t *dev ) {
+	drm_gamma_private_t *dev_priv =
+				(drm_gamma_private_t *)dev->dev_private;
+	if (!dev_priv)
+		return;
+
+	while(GAMMA_READ(GAMMA_INFIFOSPACE) < 3)
+		cpu_relax();
+
+	GAMMA_WRITE( GAMMA_GDELAYTIMER,		0x00000000 );
+	GAMMA_WRITE( GAMMA_COMMANDINTENABLE,	0x00000000 );
+	GAMMA_WRITE( GAMMA_GINTENABLE,		0x00000000 );
+}
+
+extern drm_ioctl_desc_t DRM(ioctls)[];
+
+static int gamma_driver_preinit(drm_device_t *dev)
+{
+	/* reset the finish ioctl */
+	DRM(ioctls)[DRM_IOCTL_NR(DRM_IOCTL_FINISH)].func = DRM(finish);
+	return 0;
+}
+
+static void gamma_driver_pretakedown(drm_device_t *dev)
+{
+	gamma_do_cleanup_dma(dev);
+}
+
+static void gamma_driver_dma_ready(drm_device_t *dev)
+{
+	gamma_dma_ready(dev);
+}
+
+static int gamma_driver_dma_quiescent(drm_device_t *dev)
+{
+	drm_gamma_private_t *dev_priv =	(
+		drm_gamma_private_t *)dev->dev_private;
+	if (dev_priv->num_rast == 2)
+		gamma_dma_quiescent_dual(dev);
+	else gamma_dma_quiescent_single(dev);
+	return 0;
+}
+
+void gamma_driver_register_fns(drm_device_t *dev)
+{
+	dev->driver_features = DRIVER_USE_AGP | DRIVER_USE_MTRR | DRIVER_PCI_DMA | DRIVER_HAVE_DMA | DRIVER_HAVE_IRQ;
+	DRM(fops).read = gamma_fops_read;
+	DRM(fops).poll = gamma_fops_poll;
+	dev->driver.preinit = gamma_driver_preinit;
+	dev->driver.pretakedown = gamma_driver_pretakedown;
+	dev->driver.dma_ready = gamma_driver_dma_ready;
+	dev->driver.dma_quiescent = gamma_driver_dma_quiescent;
+	dev->driver.dma_flush_block_and_flush = gamma_flush_block_and_flush;
+	dev->driver.dma_flush_unblock = gamma_flush_unblock;
+}
diff --git a/drivers/char/drm/gamma_drm.h b/drivers/char/drm/gamma_drm.h
new file mode 100644
index 0000000..20819de
--- /dev/null
+++ b/drivers/char/drm/gamma_drm.h
@@ -0,0 +1,90 @@
+#ifndef _GAMMA_DRM_H_
+#define _GAMMA_DRM_H_
+
+typedef struct _drm_gamma_tex_region {
+	unsigned char next, prev; /* indices to form a circular LRU  */
+	unsigned char in_use;	/* owned by a client, or free? */
+	int age;		/* tracked by clients to update local LRU's */
+} drm_gamma_tex_region_t;
+
+typedef struct {
+	unsigned int	GDeltaMode;
+	unsigned int	GDepthMode;
+	unsigned int	GGeometryMode;
+	unsigned int	GTransformMode;
+} drm_gamma_context_regs_t;
+
+typedef struct _drm_gamma_sarea {
+   	drm_gamma_context_regs_t context_state;
+
+	unsigned int dirty;
+
+
+	/* Maintain an LRU of contiguous regions of texture space.  If
+	 * you think you own a region of texture memory, and it has an
+	 * age different to the one you set, then you are mistaken and
+	 * it has been stolen by another client.  If global texAge
+	 * hasn't changed, there is no need to walk the list.
+	 *
+	 * These regions can be used as a proxy for the fine-grained
+	 * texture information of other clients - by maintaining them
+	 * in the same lru which is used to age their own textures,
+	 * clients have an approximate lru for the whole of global
+	 * texture space, and can make informed decisions as to which
+	 * areas to kick out.  There is no need to choose whether to
+	 * kick out your own texture or someone else's - simply eject
+	 * them all in LRU order.  
+	 */
+   
+#define GAMMA_NR_TEX_REGIONS 64
+	drm_gamma_tex_region_t texList[GAMMA_NR_TEX_REGIONS+1]; 
+				/* Last elt is sentinal */
+        int texAge;		/* last time texture was uploaded */
+        int last_enqueue;	/* last time a buffer was enqueued */
+	int last_dispatch;	/* age of the most recently dispatched buffer */
+	int last_quiescent;     /*  */
+	int ctxOwner;		/* last context to upload state */
+
+	int vertex_prim;
+} drm_gamma_sarea_t;
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmGamma.h)
+ */
+
+/* Gamma specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_IOCTL_GAMMA_INIT		DRM_IOW( 0x40, drm_gamma_init_t)
+#define DRM_IOCTL_GAMMA_COPY		DRM_IOW( 0x41, drm_gamma_copy_t)
+
+typedef struct drm_gamma_copy {
+	unsigned int	DMAOutputAddress;
+	unsigned int	DMAOutputCount;
+	unsigned int	DMAReadGLINTSource;
+	unsigned int	DMARectangleWriteAddress;
+	unsigned int	DMARectangleWriteLinePitch;
+	unsigned int	DMARectangleWrite;
+	unsigned int	DMARectangleReadAddress;
+	unsigned int	DMARectangleReadLinePitch;
+	unsigned int	DMARectangleRead;
+	unsigned int	DMARectangleReadTarget;
+} drm_gamma_copy_t;
+
+typedef struct drm_gamma_init {
+   	enum {
+	   	GAMMA_INIT_DMA    = 0x01,
+	       	GAMMA_CLEANUP_DMA = 0x02
+	} func;
+
+   	int sarea_priv_offset;
+	int pcimode;
+	unsigned int mmio0;
+	unsigned int mmio1;
+	unsigned int mmio2;
+	unsigned int mmio3;
+	unsigned int buffers_offset;
+	int num_rast;
+} drm_gamma_init_t;
+
+#endif /* _GAMMA_DRM_H_ */
diff --git a/drivers/char/drm/gamma_drv.c b/drivers/char/drm/gamma_drv.c
new file mode 100644
index 0000000..e7e64b6
--- /dev/null
+++ b/drivers/char/drm/gamma_drv.c
@@ -0,0 +1,59 @@
+/* gamma.c -- 3dlabs GMX 2000 driver -*- linux-c -*-
+ * Created: Mon Jan  4 08:58:31 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "gamma.h"
+#include "drmP.h"
+#include "drm.h"
+#include "gamma_drm.h"
+#include "gamma_drv.h"
+
+#include "drm_auth.h"
+#include "drm_agpsupport.h"
+#include "drm_bufs.h"
+#include "gamma_context.h"	/* NOTE! */
+#include "drm_dma.h"
+#include "gamma_old_dma.h"	/* NOTE */
+#include "drm_drawable.h"
+#include "drm_drv.h"
+
+#include "drm_fops.h"
+#include "drm_init.h"
+#include "drm_ioctl.h"
+#include "drm_irq.h"
+#include "gamma_lists.h"        /* NOTE */
+#include "drm_lock.h"
+#include "gamma_lock.h"		/* NOTE */
+#include "drm_memory.h"
+#include "drm_proc.h"
+#include "drm_vm.h"
+#include "drm_stub.h"
+#include "drm_scatter.h"
diff --git a/drivers/char/drm/gamma_drv.h b/drivers/char/drm/gamma_drv.h
new file mode 100644
index 0000000..146fcc6
--- /dev/null
+++ b/drivers/char/drm/gamma_drv.h
@@ -0,0 +1,147 @@
+/* gamma_drv.h -- Private header for 3dlabs GMX 2000 driver -*- linux-c -*-
+ * Created: Mon Jan  4 10:05:05 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *
+ */
+
+#ifndef _GAMMA_DRV_H_
+#define _GAMMA_DRV_H_
+
+typedef struct drm_gamma_private {
+	drm_gamma_sarea_t *sarea_priv;
+	drm_map_t *sarea;
+	drm_map_t *mmio0;
+	drm_map_t *mmio1;
+	drm_map_t *mmio2;
+	drm_map_t *mmio3;
+	int num_rast;
+} drm_gamma_private_t;
+
+				/* gamma_dma.c */
+extern int gamma_dma_init( struct inode *inode, struct file *filp,
+			 unsigned int cmd, unsigned long arg );
+extern int gamma_dma_copy( struct inode *inode, struct file *filp,
+			 unsigned int cmd, unsigned long arg );
+
+extern int gamma_do_cleanup_dma( drm_device_t *dev );
+extern void gamma_dma_ready(drm_device_t *dev);
+extern void gamma_dma_quiescent_single(drm_device_t *dev);
+extern void gamma_dma_quiescent_dual(drm_device_t *dev);
+
+				/* gamma_dma.c */
+extern int  gamma_dma_schedule(drm_device_t *dev, int locked);
+extern int  gamma_dma(struct inode *inode, struct file *filp,
+		      unsigned int cmd, unsigned long arg);
+extern int  gamma_find_devices(void);
+extern int  gamma_found(void);
+
+/* Gamma-specific code pulled from drm_fops.h:
+ */
+extern int	     DRM(finish)(struct inode *inode, struct file *filp,
+				 unsigned int cmd, unsigned long arg);
+extern int	     DRM(flush_unblock)(drm_device_t *dev, int context,
+					drm_lock_flags_t flags);
+extern int	     DRM(flush_block_and_flush)(drm_device_t *dev, int context,
+						drm_lock_flags_t flags);
+
+/* Gamma-specific code pulled from drm_dma.h:
+ */
+extern void	     DRM(clear_next_buffer)(drm_device_t *dev);
+extern int	     DRM(select_queue)(drm_device_t *dev,
+				       void (*wrapper)(unsigned long));
+extern int	     DRM(dma_enqueue)(struct file *filp, drm_dma_t *dma);
+extern int	     DRM(dma_get_buffers)(struct file *filp, drm_dma_t *dma);
+
+
+/* Gamma-specific code pulled from drm_lists.h (now renamed gamma_lists.h):
+ */
+extern int	     DRM(waitlist_create)(drm_waitlist_t *bl, int count);
+extern int	     DRM(waitlist_destroy)(drm_waitlist_t *bl);
+extern int	     DRM(waitlist_put)(drm_waitlist_t *bl, drm_buf_t *buf);
+extern drm_buf_t     *DRM(waitlist_get)(drm_waitlist_t *bl);
+extern int	     DRM(freelist_create)(drm_freelist_t *bl, int count);
+extern int	     DRM(freelist_destroy)(drm_freelist_t *bl);
+extern int	     DRM(freelist_put)(drm_device_t *dev, drm_freelist_t *bl,
+				       drm_buf_t *buf);
+extern drm_buf_t     *DRM(freelist_get)(drm_freelist_t *bl, int block);
+
+/* externs for gamma changes to the ops */
+extern struct file_operations DRM(fops);
+extern unsigned int gamma_fops_poll(struct file *filp, struct poll_table_struct *wait);
+extern ssize_t gamma_fops_read(struct file *filp, char __user *buf, size_t count, loff_t *off);
+
+
+#define GLINT_DRI_BUF_COUNT 256
+
+#define GAMMA_OFF(reg)						   \
+	((reg < 0x1000)						   \
+	 ? reg							   \
+	 : ((reg < 0x10000)					   \
+	    ? (reg - 0x1000)					   \
+	    : ((reg < 0x11000)					   \
+	       ? (reg - 0x10000)				   \
+	       : (reg - 0x11000))))
+
+#define GAMMA_BASE(reg)	 ((unsigned long)				     \
+			  ((reg < 0x1000)    ? dev_priv->mmio0->handle :     \
+			   ((reg < 0x10000)  ? dev_priv->mmio1->handle :     \
+			    ((reg < 0x11000) ? dev_priv->mmio2->handle :     \
+					       dev_priv->mmio3->handle))))
+#define GAMMA_ADDR(reg)	 (GAMMA_BASE(reg) + GAMMA_OFF(reg))
+#define GAMMA_DEREF(reg) *(__volatile__ int *)GAMMA_ADDR(reg)
+#define GAMMA_READ(reg)	 GAMMA_DEREF(reg)
+#define GAMMA_WRITE(reg,val) do { GAMMA_DEREF(reg) = val; } while (0)
+
+#define GAMMA_BROADCASTMASK    0x9378
+#define GAMMA_COMMANDINTENABLE 0x0c48
+#define GAMMA_DMAADDRESS       0x0028
+#define GAMMA_DMACOUNT	       0x0030
+#define GAMMA_FILTERMODE       0x8c00
+#define GAMMA_GCOMMANDINTFLAGS 0x0c50
+#define GAMMA_GCOMMANDMODE     0x0c40
+#define		GAMMA_QUEUED_DMA_MODE		1<<1
+#define GAMMA_GCOMMANDSTATUS   0x0c60
+#define GAMMA_GDELAYTIMER      0x0c38
+#define GAMMA_GDMACONTROL      0x0060
+#define 	GAMMA_USE_AGP			1<<1
+#define GAMMA_GINTENABLE       0x0808
+#define GAMMA_GINTFLAGS	       0x0810
+#define GAMMA_INFIFOSPACE      0x0018
+#define GAMMA_OUTFIFOWORDS     0x0020
+#define GAMMA_OUTPUTFIFO       0x2000
+#define GAMMA_SYNC	       0x8c40
+#define GAMMA_SYNC_TAG	       0x0188
+#define GAMMA_PAGETABLEADDR    0x0C00
+#define GAMMA_PAGETABLELENGTH  0x0C08
+
+#define GAMMA_PASSTHROUGH	0x1FE
+#define GAMMA_DMAADDRTAG	0x530
+#define GAMMA_DMACOUNTTAG	0x531
+#define GAMMA_COMMANDINTTAG	0x532
+
+#endif
diff --git a/drivers/char/drm/gamma_lists.h b/drivers/char/drm/gamma_lists.h
new file mode 100644
index 0000000..2d93f41
--- /dev/null
+++ b/drivers/char/drm/gamma_lists.h
@@ -0,0 +1,215 @@
+/* drm_lists.h -- Buffer list handling routines -*- linux-c -*-
+ * Created: Mon Apr 19 20:54:22 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+
+
+int DRM(waitlist_create)(drm_waitlist_t *bl, int count)
+{
+	if (bl->count) return -EINVAL;
+
+	bl->bufs       = DRM(alloc)((bl->count + 2) * sizeof(*bl->bufs),
+				    DRM_MEM_BUFLISTS);
+
+	if(!bl->bufs) return -ENOMEM;
+	memset(bl->bufs, 0, sizeof(*bl->bufs));
+	bl->count      = count;
+	bl->rp	       = bl->bufs;
+	bl->wp	       = bl->bufs;
+	bl->end	       = &bl->bufs[bl->count+1];
+	spin_lock_init(&bl->write_lock);
+	spin_lock_init(&bl->read_lock);
+	return 0;
+}
+
+int DRM(waitlist_destroy)(drm_waitlist_t *bl)
+{
+	if (bl->rp != bl->wp) return -EINVAL;
+	if (bl->bufs) DRM(free)(bl->bufs,
+				(bl->count + 2) * sizeof(*bl->bufs),
+				DRM_MEM_BUFLISTS);
+	bl->count = 0;
+	bl->bufs  = NULL;
+	bl->rp	  = NULL;
+	bl->wp	  = NULL;
+	bl->end	  = NULL;
+	return 0;
+}
+
+int DRM(waitlist_put)(drm_waitlist_t *bl, drm_buf_t *buf)
+{
+	int	      left;
+	unsigned long flags;
+
+	left = DRM_LEFTCOUNT(bl);
+	if (!left) {
+		DRM_ERROR("Overflow while adding buffer %d from filp %p\n",
+			  buf->idx, buf->filp);
+		return -EINVAL;
+	}
+	buf->list	 = DRM_LIST_WAIT;
+
+	spin_lock_irqsave(&bl->write_lock, flags);
+	*bl->wp = buf;
+	if (++bl->wp >= bl->end) bl->wp = bl->bufs;
+	spin_unlock_irqrestore(&bl->write_lock, flags);
+
+	return 0;
+}
+
+drm_buf_t *DRM(waitlist_get)(drm_waitlist_t *bl)
+{
+	drm_buf_t     *buf;
+	unsigned long flags;
+
+	spin_lock_irqsave(&bl->read_lock, flags);
+	buf = *bl->rp;
+	if (bl->rp == bl->wp) {
+		spin_unlock_irqrestore(&bl->read_lock, flags);
+		return NULL;
+	}
+	if (++bl->rp >= bl->end) bl->rp = bl->bufs;
+	spin_unlock_irqrestore(&bl->read_lock, flags);
+
+	return buf;
+}
+
+int DRM(freelist_create)(drm_freelist_t *bl, int count)
+{
+	atomic_set(&bl->count, 0);
+	bl->next      = NULL;
+	init_waitqueue_head(&bl->waiting);
+	bl->low_mark  = 0;
+	bl->high_mark = 0;
+	atomic_set(&bl->wfh,   0);
+	spin_lock_init(&bl->lock);
+	++bl->initialized;
+	return 0;
+}
+
+int DRM(freelist_destroy)(drm_freelist_t *bl)
+{
+	atomic_set(&bl->count, 0);
+	bl->next = NULL;
+	return 0;
+}
+
+int DRM(freelist_put)(drm_device_t *dev, drm_freelist_t *bl, drm_buf_t *buf)
+{
+	drm_device_dma_t *dma  = dev->dma;
+
+	if (!dma) {
+		DRM_ERROR("No DMA support\n");
+		return 1;
+	}
+
+	if (buf->waiting || buf->pending || buf->list == DRM_LIST_FREE) {
+		DRM_ERROR("Freed buffer %d: w%d, p%d, l%d\n",
+			  buf->idx, buf->waiting, buf->pending, buf->list);
+	}
+	if (!bl) return 1;
+	buf->list	= DRM_LIST_FREE;
+
+	spin_lock(&bl->lock);
+	buf->next	= bl->next;
+	bl->next	= buf;
+	spin_unlock(&bl->lock);
+
+	atomic_inc(&bl->count);
+	if (atomic_read(&bl->count) > dma->buf_count) {
+		DRM_ERROR("%d of %d buffers free after addition of %d\n",
+			  atomic_read(&bl->count), dma->buf_count, buf->idx);
+		return 1;
+	}
+				/* Check for high water mark */
+	if (atomic_read(&bl->wfh) && atomic_read(&bl->count)>=bl->high_mark) {
+		atomic_set(&bl->wfh, 0);
+		wake_up_interruptible(&bl->waiting);
+	}
+	return 0;
+}
+
+static drm_buf_t *DRM(freelist_try)(drm_freelist_t *bl)
+{
+	drm_buf_t	  *buf;
+
+	if (!bl) return NULL;
+
+				/* Get buffer */
+	spin_lock(&bl->lock);
+	if (!bl->next) {
+		spin_unlock(&bl->lock);
+		return NULL;
+	}
+	buf	  = bl->next;
+	bl->next  = bl->next->next;
+	spin_unlock(&bl->lock);
+
+	atomic_dec(&bl->count);
+	buf->next = NULL;
+	buf->list = DRM_LIST_NONE;
+	if (buf->waiting || buf->pending) {
+		DRM_ERROR("Free buffer %d: w%d, p%d, l%d\n",
+			  buf->idx, buf->waiting, buf->pending, buf->list);
+	}
+
+	return buf;
+}
+
+drm_buf_t *DRM(freelist_get)(drm_freelist_t *bl, int block)
+{
+	drm_buf_t	  *buf	= NULL;
+	DECLARE_WAITQUEUE(entry, current);
+
+	if (!bl || !bl->initialized) return NULL;
+
+				/* Check for low water mark */
+	if (atomic_read(&bl->count) <= bl->low_mark) /* Became low */
+		atomic_set(&bl->wfh, 1);
+	if (atomic_read(&bl->wfh)) {
+		if (block) {
+			add_wait_queue(&bl->waiting, &entry);
+			for (;;) {
+				current->state = TASK_INTERRUPTIBLE;
+				if (!atomic_read(&bl->wfh)
+				    && (buf = DRM(freelist_try)(bl))) break;
+				schedule();
+				if (signal_pending(current)) break;
+			}
+			current->state = TASK_RUNNING;
+			remove_wait_queue(&bl->waiting, &entry);
+		}
+		return buf;
+	}
+
+	return DRM(freelist_try)(bl);
+}
+
diff --git a/drivers/char/drm/gamma_lock.h b/drivers/char/drm/gamma_lock.h
new file mode 100644
index 0000000..ddec67e
--- /dev/null
+++ b/drivers/char/drm/gamma_lock.h
@@ -0,0 +1,140 @@
+/* lock.c -- IOCTLs for locking -*- linux-c -*-
+ * Created: Tue Feb  2 08:37:54 1999 by faith@valinux.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+
+/* Gamma-specific code extracted from drm_lock.h:
+ */
+static int DRM(flush_queue)(drm_device_t *dev, int context)
+{
+	DECLARE_WAITQUEUE(entry, current);
+	int		  ret	= 0;
+	drm_queue_t	  *q	= dev->queuelist[context];
+
+	DRM_DEBUG("\n");
+
+	atomic_inc(&q->use_count);
+	if (atomic_read(&q->use_count) > 1) {
+		atomic_inc(&q->block_write);
+		add_wait_queue(&q->flush_queue, &entry);
+		atomic_inc(&q->block_count);
+		for (;;) {
+			current->state = TASK_INTERRUPTIBLE;
+			if (!DRM_BUFCOUNT(&q->waitlist)) break;
+			schedule();
+			if (signal_pending(current)) {
+				ret = -EINTR; /* Can't restart */
+				break;
+			}
+		}
+		atomic_dec(&q->block_count);
+		current->state = TASK_RUNNING;
+		remove_wait_queue(&q->flush_queue, &entry);
+	}
+	atomic_dec(&q->use_count);
+
+				/* NOTE: block_write is still incremented!
+				   Use drm_flush_unlock_queue to decrement. */
+	return ret;
+}
+
+static int DRM(flush_unblock_queue)(drm_device_t *dev, int context)
+{
+	drm_queue_t	  *q	= dev->queuelist[context];
+
+	DRM_DEBUG("\n");
+
+	atomic_inc(&q->use_count);
+	if (atomic_read(&q->use_count) > 1) {
+		if (atomic_read(&q->block_write)) {
+			atomic_dec(&q->block_write);
+			wake_up_interruptible(&q->write_queue);
+		}
+	}
+	atomic_dec(&q->use_count);
+	return 0;
+}
+
+int DRM(flush_block_and_flush)(drm_device_t *dev, int context,
+			       drm_lock_flags_t flags)
+{
+	int ret = 0;
+	int i;
+
+	DRM_DEBUG("\n");
+
+	if (flags & _DRM_LOCK_FLUSH) {
+		ret = DRM(flush_queue)(dev, DRM_KERNEL_CONTEXT);
+		if (!ret) ret = DRM(flush_queue)(dev, context);
+	}
+	if (flags & _DRM_LOCK_FLUSH_ALL) {
+		for (i = 0; !ret && i < dev->queue_count; i++) {
+			ret = DRM(flush_queue)(dev, i);
+		}
+	}
+	return ret;
+}
+
+int DRM(flush_unblock)(drm_device_t *dev, int context, drm_lock_flags_t flags)
+{
+	int ret = 0;
+	int i;
+
+	DRM_DEBUG("\n");
+
+	if (flags & _DRM_LOCK_FLUSH) {
+		ret = DRM(flush_unblock_queue)(dev, DRM_KERNEL_CONTEXT);
+		if (!ret) ret = DRM(flush_unblock_queue)(dev, context);
+	}
+	if (flags & _DRM_LOCK_FLUSH_ALL) {
+		for (i = 0; !ret && i < dev->queue_count; i++) {
+			ret = DRM(flush_unblock_queue)(dev, i);
+		}
+	}
+
+	return ret;
+}
+
+int DRM(finish)(struct inode *inode, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+{
+	drm_file_t	  *priv	  = filp->private_data;
+	drm_device_t	  *dev	  = priv->dev;
+	int		  ret	  = 0;
+	drm_lock_t	  lock;
+
+	DRM_DEBUG("\n");
+
+	if (copy_from_user(&lock, (drm_lock_t __user *)arg, sizeof(lock)))
+		return -EFAULT;
+	ret = DRM(flush_block_and_flush)(dev, lock.context, lock.flags);
+	DRM(flush_unblock)(dev, lock.context, lock.flags);
+	return ret;
+}
diff --git a/drivers/char/drm/gamma_old_dma.h b/drivers/char/drm/gamma_old_dma.h
new file mode 100644
index 0000000..abdd454
--- /dev/null
+++ b/drivers/char/drm/gamma_old_dma.h
@@ -0,0 +1,313 @@
+/* drm_dma.c -- DMA IOCTL and function support -*- linux-c -*-
+ * Created: Fri Mar 19 14:30:16 1999 by faith@valinux.com
+ *
+ * Copyright 1999, 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+
+/* Gamma-specific code pulled from drm_dma.h:
+ */
+
+void DRM(clear_next_buffer)(drm_device_t *dev)
+{
+	drm_device_dma_t *dma = dev->dma;
+
+	dma->next_buffer = NULL;
+	if (dma->next_queue && !DRM_BUFCOUNT(&dma->next_queue->waitlist)) {
+		wake_up_interruptible(&dma->next_queue->flush_queue);
+	}
+	dma->next_queue	 = NULL;
+}
+
+int DRM(select_queue)(drm_device_t *dev, void (*wrapper)(unsigned long))
+{
+	int	   i;
+	int	   candidate = -1;
+	int	   j	     = jiffies;
+
+	if (!dev) {
+		DRM_ERROR("No device\n");
+		return -1;
+	}
+	if (!dev->queuelist || !dev->queuelist[DRM_KERNEL_CONTEXT]) {
+				/* This only happens between the time the
+				   interrupt is initialized and the time
+				   the queues are initialized. */
+		return -1;
+	}
+
+				/* Doing "while locked" DMA? */
+	if (DRM_WAITCOUNT(dev, DRM_KERNEL_CONTEXT)) {
+		return DRM_KERNEL_CONTEXT;
+	}
+
+				/* If there are buffers on the last_context
+				   queue, and we have not been executing
+				   this context very long, continue to
+				   execute this context. */
+	if (dev->last_switch <= j
+	    && dev->last_switch + DRM_TIME_SLICE > j
+	    && DRM_WAITCOUNT(dev, dev->last_context)) {
+		return dev->last_context;
+	}
+
+				/* Otherwise, find a candidate */
+	for (i = dev->last_checked + 1; i < dev->queue_count; i++) {
+		if (DRM_WAITCOUNT(dev, i)) {
+			candidate = dev->last_checked = i;
+			break;
+		}
+	}
+
+	if (candidate < 0) {
+		for (i = 0; i < dev->queue_count; i++) {
+			if (DRM_WAITCOUNT(dev, i)) {
+				candidate = dev->last_checked = i;
+				break;
+			}
+		}
+	}
+
+	if (wrapper
+	    && candidate >= 0
+	    && candidate != dev->last_context
+	    && dev->last_switch <= j
+	    && dev->last_switch + DRM_TIME_SLICE > j) {
+		if (dev->timer.expires != dev->last_switch + DRM_TIME_SLICE) {
+			del_timer(&dev->timer);
+			dev->timer.function = wrapper;
+			dev->timer.data	    = (unsigned long)dev;
+			dev->timer.expires  = dev->last_switch+DRM_TIME_SLICE;
+			add_timer(&dev->timer);
+		}
+		return -1;
+	}
+
+	return candidate;
+}
+
+
+int DRM(dma_enqueue)(struct file *filp, drm_dma_t *d)
+{
+	drm_file_t    *priv   = filp->private_data;
+	drm_device_t  *dev    = priv->dev;
+	int		  i;
+	drm_queue_t	  *q;
+	drm_buf_t	  *buf;
+	int		  idx;
+	int		  while_locked = 0;
+	drm_device_dma_t  *dma = dev->dma;
+	int		  *ind;
+	int		  err;
+	DECLARE_WAITQUEUE(entry, current);
+
+	DRM_DEBUG("%d\n", d->send_count);
+
+	if (d->flags & _DRM_DMA_WHILE_LOCKED) {
+		int context = dev->lock.hw_lock->lock;
+
+		if (!_DRM_LOCK_IS_HELD(context)) {
+			DRM_ERROR("No lock held during \"while locked\""
+				  " request\n");
+			return -EINVAL;
+		}
+		if (d->context != _DRM_LOCKING_CONTEXT(context)
+		    && _DRM_LOCKING_CONTEXT(context) != DRM_KERNEL_CONTEXT) {
+			DRM_ERROR("Lock held by %d while %d makes"
+				  " \"while locked\" request\n",
+				  _DRM_LOCKING_CONTEXT(context),
+				  d->context);
+			return -EINVAL;
+		}
+		q = dev->queuelist[DRM_KERNEL_CONTEXT];
+		while_locked = 1;
+	} else {
+		q = dev->queuelist[d->context];
+	}
+
+
+	atomic_inc(&q->use_count);
+	if (atomic_read(&q->block_write)) {
+		add_wait_queue(&q->write_queue, &entry);
+		atomic_inc(&q->block_count);
+		for (;;) {
+			current->state = TASK_INTERRUPTIBLE;
+			if (!atomic_read(&q->block_write)) break;
+			schedule();
+			if (signal_pending(current)) {
+				atomic_dec(&q->use_count);
+				remove_wait_queue(&q->write_queue, &entry);
+				return -EINTR;
+			}
+		}
+		atomic_dec(&q->block_count);
+		current->state = TASK_RUNNING;
+		remove_wait_queue(&q->write_queue, &entry);
+	}
+
+	ind = DRM(alloc)(d->send_count * sizeof(int), DRM_MEM_DRIVER);
+	if (!ind)
+		return -ENOMEM;
+
+	if (copy_from_user(ind, d->send_indices, d->send_count * sizeof(int))) {
+		err = -EFAULT;
+                goto out;
+	}
+
+	err = -EINVAL;
+	for (i = 0; i < d->send_count; i++) {
+		idx = ind[i];
+		if (idx < 0 || idx >= dma->buf_count) {
+			DRM_ERROR("Index %d (of %d max)\n",
+				  ind[i], dma->buf_count - 1);
+			goto out;
+		}
+		buf = dma->buflist[ idx ];
+		if (buf->filp != filp) {
+			DRM_ERROR("Process %d using buffer not owned\n",
+				  current->pid);
+			goto out;
+		}
+		if (buf->list != DRM_LIST_NONE) {
+			DRM_ERROR("Process %d using buffer %d on list %d\n",
+				  current->pid, buf->idx, buf->list);
+			goto out;
+		}
+		buf->used	  = ind[i];
+		buf->while_locked = while_locked;
+		buf->context	  = d->context;
+		if (!buf->used) {
+			DRM_ERROR("Queueing 0 length buffer\n");
+		}
+		if (buf->pending) {
+			DRM_ERROR("Queueing pending buffer:"
+				  " buffer %d, offset %d\n",
+				  ind[i], i);
+			goto out;
+		}
+		if (buf->waiting) {
+			DRM_ERROR("Queueing waiting buffer:"
+				  " buffer %d, offset %d\n",
+				  ind[i], i);
+			goto out;
+		}
+		buf->waiting = 1;
+		if (atomic_read(&q->use_count) == 1
+		    || atomic_read(&q->finalization)) {
+			DRM(free_buffer)(dev, buf);
+		} else {
+			DRM(waitlist_put)(&q->waitlist, buf);
+			atomic_inc(&q->total_queued);
+		}
+	}
+	atomic_dec(&q->use_count);
+
+	return 0;
+
+out:
+	DRM(free)(ind, d->send_count * sizeof(int), DRM_MEM_DRIVER);
+	atomic_dec(&q->use_count);
+	return err;
+}
+
+static int DRM(dma_get_buffers_of_order)(struct file *filp, drm_dma_t *d,
+					 int order)
+{
+	drm_file_t    *priv   = filp->private_data;
+	drm_device_t  *dev    = priv->dev;
+	int		  i;
+	drm_buf_t	  *buf;
+	drm_device_dma_t  *dma = dev->dma;
+
+	for (i = d->granted_count; i < d->request_count; i++) {
+		buf = DRM(freelist_get)(&dma->bufs[order].freelist,
+					d->flags & _DRM_DMA_WAIT);
+		if (!buf) break;
+		if (buf->pending || buf->waiting) {
+			DRM_ERROR("Free buffer %d in use: filp %p (w%d, p%d)\n",
+				  buf->idx,
+				  buf->filp,
+				  buf->waiting,
+				  buf->pending);
+		}
+		buf->filp     = filp;
+		if (copy_to_user(&d->request_indices[i],
+				 &buf->idx,
+				 sizeof(buf->idx)))
+			return -EFAULT;
+
+		if (copy_to_user(&d->request_sizes[i],
+				 &buf->total,
+				 sizeof(buf->total)))
+			return -EFAULT;
+
+		++d->granted_count;
+	}
+	return 0;
+}
+
+
+int DRM(dma_get_buffers)(struct file *filp, drm_dma_t *dma)
+{
+	int		  order;
+	int		  retcode = 0;
+	int		  tmp_order;
+
+	order = DRM(order)(dma->request_size);
+
+	dma->granted_count = 0;
+	retcode		   = DRM(dma_get_buffers_of_order)(filp, dma, order);
+
+	if (dma->granted_count < dma->request_count
+	    && (dma->flags & _DRM_DMA_SMALLER_OK)) {
+		for (tmp_order = order - 1;
+		     !retcode
+			     && dma->granted_count < dma->request_count
+			     && tmp_order >= DRM_MIN_ORDER;
+		     --tmp_order) {
+
+			retcode = DRM(dma_get_buffers_of_order)(filp, dma,
+								tmp_order);
+		}
+	}
+
+	if (dma->granted_count < dma->request_count
+	    && (dma->flags & _DRM_DMA_LARGER_OK)) {
+		for (tmp_order = order + 1;
+		     !retcode
+			     && dma->granted_count < dma->request_count
+			     && tmp_order <= DRM_MAX_ORDER;
+		     ++tmp_order) {
+
+			retcode = DRM(dma_get_buffers_of_order)(filp, dma,
+								tmp_order);
+		}
+	}
+	return 0;
+}
+
diff --git a/drivers/char/drm/i810_dma.c b/drivers/char/drm/i810_dma.c
new file mode 100644
index 0000000..24857cc
--- /dev/null
+++ b/drivers/char/drm/i810_dma.c
@@ -0,0 +1,1385 @@
+/* i810_dma.c -- DMA support for the i810 -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
+ *	    Jeff Hartmann <jhartmann@valinux.com>
+ *          Keith Whitwell <keith@tungstengraphics.com>
+ *
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "i810_drm.h"
+#include "i810_drv.h"
+#include <linux/interrupt.h>	/* For task queue support */
+#include <linux/delay.h>
+#include <linux/pagemap.h>
+
+#define I810_BUF_FREE		2
+#define I810_BUF_CLIENT		1
+#define I810_BUF_HARDWARE      	0
+
+#define I810_BUF_UNMAPPED 0
+#define I810_BUF_MAPPED   1
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,2)
+#define down_write down
+#define up_write up
+#endif
+
+static drm_buf_t *i810_freelist_get(drm_device_t *dev)
+{
+   	drm_device_dma_t *dma = dev->dma;
+	int		 i;
+   	int 		 used;
+
+	/* Linear search might not be the best solution */
+
+   	for (i = 0; i < dma->buf_count; i++) {
+	   	drm_buf_t *buf = dma->buflist[ i ];
+	   	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+		/* In use is already a pointer */
+	   	used = cmpxchg(buf_priv->in_use, I810_BUF_FREE,
+			       I810_BUF_CLIENT);
+		if (used == I810_BUF_FREE) {
+			return buf;
+		}
+	}
+   	return NULL;
+}
+
+/* This should only be called if the buffer is not sent to the hardware
+ * yet, the hardware updates in use for us once its on the ring buffer.
+ */
+
+static int i810_freelist_put(drm_device_t *dev, drm_buf_t *buf)
+{
+   	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+   	int used;
+
+   	/* In use is already a pointer */
+   	used = cmpxchg(buf_priv->in_use, I810_BUF_CLIENT, I810_BUF_FREE);
+	if (used != I810_BUF_CLIENT) {
+	   	DRM_ERROR("Freeing buffer thats not in use : %d\n", buf->idx);
+	   	return -EINVAL;
+	}
+
+   	return 0;
+}
+
+static struct file_operations i810_buffer_fops = {
+	.open	 = drm_open,
+	.flush	 = drm_flush,
+	.release = drm_release,
+	.ioctl	 = drm_ioctl,
+	.mmap	 = i810_mmap_buffers,
+	.fasync  = drm_fasync,
+};
+
+int i810_mmap_buffers(struct file *filp, struct vm_area_struct *vma)
+{
+	drm_file_t	    *priv	  = filp->private_data;
+	drm_device_t	    *dev;
+	drm_i810_private_t  *dev_priv;
+	drm_buf_t           *buf;
+	drm_i810_buf_priv_t *buf_priv;
+
+	lock_kernel();
+	dev	 = priv->head->dev;
+	dev_priv = dev->dev_private;
+	buf      = dev_priv->mmap_buffer;
+	buf_priv = buf->dev_private;
+
+	vma->vm_flags |= (VM_IO | VM_DONTCOPY);
+	vma->vm_file = filp;
+
+   	buf_priv->currently_mapped = I810_BUF_MAPPED;
+	unlock_kernel();
+
+	if (io_remap_pfn_range(vma, vma->vm_start,
+			     VM_OFFSET(vma) >> PAGE_SHIFT,
+			     vma->vm_end - vma->vm_start,
+			     vma->vm_page_prot)) return -EAGAIN;
+	return 0;
+}
+
+static int i810_map_buffer(drm_buf_t *buf, struct file *filp)
+{
+	drm_file_t	  *priv	  = filp->private_data;
+	drm_device_t	  *dev	  = priv->head->dev;
+	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+      	drm_i810_private_t *dev_priv = dev->dev_private;
+   	struct file_operations *old_fops;
+	int retcode = 0;
+
+	if (buf_priv->currently_mapped == I810_BUF_MAPPED) 
+		return -EINVAL;
+
+	down_write( &current->mm->mmap_sem );
+	old_fops = filp->f_op;
+	filp->f_op = &i810_buffer_fops;
+	dev_priv->mmap_buffer = buf;
+	buf_priv->virtual = (void *)do_mmap(filp, 0, buf->total,
+					    PROT_READ|PROT_WRITE,
+					    MAP_SHARED,
+					    buf->bus_address);
+	dev_priv->mmap_buffer = NULL;
+	filp->f_op = old_fops;
+	if ((unsigned long)buf_priv->virtual > -1024UL) {
+		/* Real error */
+		DRM_ERROR("mmap error\n");
+		retcode = (signed int)buf_priv->virtual;
+		buf_priv->virtual = NULL;
+	}
+	up_write( &current->mm->mmap_sem );
+
+	return retcode;
+}
+
+static int i810_unmap_buffer(drm_buf_t *buf)
+{
+	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+	int retcode = 0;
+
+	if (buf_priv->currently_mapped != I810_BUF_MAPPED)
+		return -EINVAL;
+
+	down_write(&current->mm->mmap_sem);
+	retcode = do_munmap(current->mm,
+			    (unsigned long)buf_priv->virtual,
+			    (size_t) buf->total);
+	up_write(&current->mm->mmap_sem);
+
+   	buf_priv->currently_mapped = I810_BUF_UNMAPPED;
+   	buf_priv->virtual = NULL;
+
+	return retcode;
+}
+
+static int i810_dma_get_buffer(drm_device_t *dev, drm_i810_dma_t *d,
+			       struct file *filp)
+{
+	drm_buf_t	  *buf;
+	drm_i810_buf_priv_t *buf_priv;
+	int retcode = 0;
+
+	buf = i810_freelist_get(dev);
+	if (!buf) {
+		retcode = -ENOMEM;
+	   	DRM_DEBUG("retcode=%d\n", retcode);
+		return retcode;
+	}
+
+	retcode = i810_map_buffer(buf, filp);
+	if (retcode) {
+		i810_freelist_put(dev, buf);
+	   	DRM_ERROR("mapbuf failed, retcode %d\n", retcode);
+		return retcode;
+	}
+	buf->filp = filp;
+	buf_priv = buf->dev_private;
+	d->granted = 1;
+   	d->request_idx = buf->idx;
+   	d->request_size = buf->total;
+   	d->virtual = buf_priv->virtual;
+
+	return retcode;
+}
+
+static int i810_dma_cleanup(drm_device_t *dev)
+{
+	drm_device_dma_t *dma = dev->dma;
+
+	/* Make sure interrupts are disabled here because the uninstall ioctl
+	 * may not have been called from userspace and after dev_private
+	 * is freed, it's too late.
+	 */
+	if (drm_core_check_feature(dev, DRIVER_HAVE_IRQ) && dev->irq_enabled)
+		drm_irq_uninstall(dev);
+
+	if (dev->dev_private) {
+		int i;
+	   	drm_i810_private_t *dev_priv =
+	     		(drm_i810_private_t *) dev->dev_private;
+
+		if (dev_priv->ring.virtual_start) {
+		   	drm_ioremapfree((void *) dev_priv->ring.virtual_start,
+					 dev_priv->ring.Size, dev);
+		}
+	   	if (dev_priv->hw_status_page) {
+		   	pci_free_consistent(dev->pdev, PAGE_SIZE,
+					    dev_priv->hw_status_page,
+					    dev_priv->dma_status_page);
+		   	/* Need to rewrite hardware status page */
+		   	I810_WRITE(0x02080, 0x1ffff000);
+		}
+	   	drm_free(dev->dev_private, sizeof(drm_i810_private_t),
+			 DRM_MEM_DRIVER);
+	   	dev->dev_private = NULL;
+
+		for (i = 0; i < dma->buf_count; i++) {
+			drm_buf_t *buf = dma->buflist[ i ];
+			drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+			if ( buf_priv->kernel_virtual && buf->total )
+				drm_ioremapfree(buf_priv->kernel_virtual, buf->total, dev);
+		}
+	}
+   	return 0;
+}
+
+static int i810_wait_ring(drm_device_t *dev, int n)
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+   	drm_i810_ring_buffer_t *ring = &(dev_priv->ring);
+   	int iters = 0;
+   	unsigned long end;
+	unsigned int last_head = I810_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+
+	end = jiffies + (HZ*3);
+   	while (ring->space < n) {
+	   	ring->head = I810_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+	   	ring->space = ring->head - (ring->tail+8);
+		if (ring->space < 0) ring->space += ring->Size;
+	   
+		if (ring->head != last_head) {
+			end = jiffies + (HZ*3);
+			last_head = ring->head;
+		}
+	  
+	   	iters++;
+		if (time_before(end, jiffies)) {
+		   	DRM_ERROR("space: %d wanted %d\n", ring->space, n);
+		   	DRM_ERROR("lockup\n");
+		   	goto out_wait_ring;
+		}
+		udelay(1);
+	}
+
+out_wait_ring:
+   	return iters;
+}
+
+static void i810_kernel_lost_context(drm_device_t *dev)
+{
+      	drm_i810_private_t *dev_priv = dev->dev_private;
+   	drm_i810_ring_buffer_t *ring = &(dev_priv->ring);
+
+   	ring->head = I810_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+     	ring->tail = I810_READ(LP_RING + RING_TAIL);
+     	ring->space = ring->head - (ring->tail+8);
+     	if (ring->space < 0) ring->space += ring->Size;
+}
+
+static int i810_freelist_init(drm_device_t *dev, drm_i810_private_t *dev_priv)
+{
+      	drm_device_dma_t *dma = dev->dma;
+   	int my_idx = 24;
+   	u32 *hw_status = (u32 *)(dev_priv->hw_status_page + my_idx);
+   	int i;
+
+	if (dma->buf_count > 1019) {
+	   	/* Not enough space in the status page for the freelist */
+	   	return -EINVAL;
+	}
+
+   	for (i = 0; i < dma->buf_count; i++) {
+	   	drm_buf_t *buf = dma->buflist[ i ];
+	   	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+
+	   	buf_priv->in_use = hw_status++;
+	   	buf_priv->my_use_idx = my_idx;
+	   	my_idx += 4;
+
+	   	*buf_priv->in_use = I810_BUF_FREE;
+
+		buf_priv->kernel_virtual = drm_ioremap(buf->bus_address,
+							buf->total, dev);
+	}
+	return 0;
+}
+
+static int i810_dma_initialize(drm_device_t *dev,
+			       drm_i810_private_t *dev_priv,
+			       drm_i810_init_t *init)
+{
+	struct list_head *list;
+
+   	memset(dev_priv, 0, sizeof(drm_i810_private_t));
+
+	list_for_each(list, &dev->maplist->head) {
+		drm_map_list_t *r_list = list_entry(list, drm_map_list_t, head);
+		if (r_list->map &&
+		    r_list->map->type == _DRM_SHM &&
+		    r_list->map->flags & _DRM_CONTAINS_LOCK ) {
+			dev_priv->sarea_map = r_list->map;
+ 			break;
+ 		}
+ 	}
+	if (!dev_priv->sarea_map) {
+		dev->dev_private = (void *)dev_priv;
+	   	i810_dma_cleanup(dev);
+	   	DRM_ERROR("can not find sarea!\n");
+	   	return -EINVAL;
+	}
+	dev_priv->mmio_map = drm_core_findmap(dev, init->mmio_offset);
+	if (!dev_priv->mmio_map) {
+		dev->dev_private = (void *)dev_priv;
+	   	i810_dma_cleanup(dev);
+	   	DRM_ERROR("can not find mmio map!\n");
+	   	return -EINVAL;
+	}
+	dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+	if (!dev->agp_buffer_map) {
+		dev->dev_private = (void *)dev_priv;
+	   	i810_dma_cleanup(dev);
+	   	DRM_ERROR("can not find dma buffer map!\n");
+	   	return -EINVAL;
+	}
+
+	dev_priv->sarea_priv = (drm_i810_sarea_t *)
+		((u8 *)dev_priv->sarea_map->handle +
+		 init->sarea_priv_offset);
+
+   	dev_priv->ring.Start = init->ring_start;
+   	dev_priv->ring.End = init->ring_end;
+   	dev_priv->ring.Size = init->ring_size;
+
+   	dev_priv->ring.virtual_start = drm_ioremap(dev->agp->base +
+						    init->ring_start,
+						    init->ring_size, dev);
+
+   	if (dev_priv->ring.virtual_start == NULL) {
+		dev->dev_private = (void *) dev_priv;
+	   	i810_dma_cleanup(dev);
+	   	DRM_ERROR("can not ioremap virtual address for"
+			  " ring buffer\n");
+	   	return -ENOMEM;
+	}
+
+   	dev_priv->ring.tail_mask = dev_priv->ring.Size - 1;
+
+	dev_priv->w = init->w;
+	dev_priv->h = init->h;
+	dev_priv->pitch = init->pitch;
+	dev_priv->back_offset = init->back_offset;
+	dev_priv->depth_offset = init->depth_offset;
+	dev_priv->front_offset = init->front_offset;
+
+	dev_priv->overlay_offset = init->overlay_offset;
+	dev_priv->overlay_physical = init->overlay_physical;
+
+	dev_priv->front_di1 = init->front_offset | init->pitch_bits;
+	dev_priv->back_di1 = init->back_offset | init->pitch_bits;
+	dev_priv->zi1 = init->depth_offset | init->pitch_bits;
+
+   	/* Program Hardware Status Page */
+   	dev_priv->hw_status_page =
+		pci_alloc_consistent(dev->pdev, PAGE_SIZE,
+						&dev_priv->dma_status_page);
+   	if (!dev_priv->hw_status_page) {
+		dev->dev_private = (void *)dev_priv;
+		i810_dma_cleanup(dev);
+		DRM_ERROR("Can not allocate hardware status page\n");
+		return -ENOMEM;
+	}
+   	memset(dev_priv->hw_status_page, 0, PAGE_SIZE);
+   	DRM_DEBUG("hw status page @ %p\n", dev_priv->hw_status_page);
+
+	I810_WRITE(0x02080, dev_priv->dma_status_page);
+   	DRM_DEBUG("Enabled hardware status page\n");
+
+   	/* Now we need to init our freelist */
+	if (i810_freelist_init(dev, dev_priv) != 0) {
+		dev->dev_private = (void *)dev_priv;
+	   	i810_dma_cleanup(dev);
+	   	DRM_ERROR("Not enough space in the status page for"
+			  " the freelist\n");
+	   	return -ENOMEM;
+	}
+	dev->dev_private = (void *)dev_priv;
+
+   	return 0;
+}
+
+/* i810 DRM version 1.1 used a smaller init structure with different
+ * ordering of values than is currently used (drm >= 1.2). There is
+ * no defined way to detect the XFree version to correct this problem,
+ * however by checking using this procedure we can detect the correct
+ * thing to do.
+ *
+ * #1 Read the Smaller init structure from user-space
+ * #2 Verify the overlay_physical is a valid physical address, or NULL
+ *    If it isn't then we have a v1.1 client. Fix up params.
+ *    If it is, then we have a 1.2 client... get the rest of the data.
+ */
+static int i810_dma_init_compat(drm_i810_init_t *init, unsigned long arg)
+{
+
+	/* Get v1.1 init data */
+	if (copy_from_user(init, (drm_i810_pre12_init_t __user *)arg,
+			  sizeof(drm_i810_pre12_init_t))) {
+		return -EFAULT;
+	}
+
+	if ((!init->overlay_physical) || (init->overlay_physical > 4096)) {
+
+		/* This is a v1.2 client, just get the v1.2 init data */
+		DRM_INFO("Using POST v1.2 init.\n");
+		if (copy_from_user(init, (drm_i810_init_t __user *)arg,
+				   sizeof(drm_i810_init_t))) {
+			return -EFAULT;
+		}
+	} else {
+
+		/* This is a v1.1 client, fix the params */
+		DRM_INFO("Using PRE v1.2 init.\n");
+	 	init->pitch_bits = init->h;
+	 	init->pitch = init->w;
+	 	init->h = init->overlay_physical;
+	 	init->w = init->overlay_offset;
+	 	init->overlay_physical = 0;
+	 	init->overlay_offset = 0;
+	}
+
+	return 0;
+}
+
+static int i810_dma_init(struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg)
+{
+   	drm_file_t *priv = filp->private_data;
+   	drm_device_t *dev = priv->head->dev;
+   	drm_i810_private_t *dev_priv;
+   	drm_i810_init_t init;
+   	int retcode = 0;
+
+	/* Get only the init func */
+	if (copy_from_user(&init, (void __user *)arg, sizeof(drm_i810_init_func_t))) 
+		return -EFAULT;
+
+   	switch(init.func) {
+	 	case I810_INIT_DMA:
+	 	       	/* This case is for backward compatibility. It
+			 * handles XFree 4.1.0 and 4.2.0, and has to
+			 * do some parameter checking as described below.
+			 * It will someday go away.
+			 */
+			retcode = i810_dma_init_compat(&init, arg);
+			if (retcode)
+				return retcode;
+
+	   		dev_priv = drm_alloc(sizeof(drm_i810_private_t),
+					     DRM_MEM_DRIVER);
+	   		if (dev_priv == NULL)
+				return -ENOMEM;
+	   		retcode = i810_dma_initialize(dev, dev_priv, &init);
+			break;
+
+		default:
+	 	case I810_INIT_DMA_1_4:
+			DRM_INFO("Using v1.4 init.\n");
+  			if (copy_from_user(&init, (drm_i810_init_t __user *)arg,
+					  sizeof(drm_i810_init_t))) {
+				return -EFAULT;
+			}
+	   		dev_priv = drm_alloc(sizeof(drm_i810_private_t),
+					     DRM_MEM_DRIVER);
+			if (dev_priv == NULL) 
+				return -ENOMEM;
+	   		retcode = i810_dma_initialize(dev, dev_priv, &init);
+			break;
+
+	 	case I810_CLEANUP_DMA:
+		        DRM_INFO("DMA Cleanup\n");
+	   		retcode = i810_dma_cleanup(dev);
+              	   	break;
+	}
+
+   	return retcode;
+}
+
+
+
+/* Most efficient way to verify state for the i810 is as it is
+ * emitted.  Non-conformant state is silently dropped.
+ *
+ * Use 'volatile' & local var tmp to force the emitted values to be
+ * identical to the verified ones.
+ */
+static void i810EmitContextVerified( drm_device_t *dev,
+				     volatile unsigned int *code )
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+	int i, j = 0;
+	unsigned int tmp;
+	RING_LOCALS;
+
+	BEGIN_LP_RING( I810_CTX_SETUP_SIZE );
+
+	OUT_RING( GFX_OP_COLOR_FACTOR );
+	OUT_RING( code[I810_CTXREG_CF1] );
+
+	OUT_RING( GFX_OP_STIPPLE );
+	OUT_RING( code[I810_CTXREG_ST1] );
+
+	for ( i = 4 ; i < I810_CTX_SETUP_SIZE ; i++ ) {
+		tmp = code[i];
+
+		if ((tmp & (7<<29)) == (3<<29) &&
+		    (tmp & (0x1f<<24)) < (0x1d<<24))
+		{
+			OUT_RING( tmp );
+			j++;
+		}
+		else printk("constext state dropped!!!\n");
+	}
+
+	if (j & 1)
+		OUT_RING( 0 );
+
+	ADVANCE_LP_RING();
+}
+
+static void i810EmitTexVerified( drm_device_t *dev,
+				 volatile unsigned int *code )
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+	int i, j = 0;
+	unsigned int tmp;
+	RING_LOCALS;
+
+	BEGIN_LP_RING( I810_TEX_SETUP_SIZE );
+
+	OUT_RING( GFX_OP_MAP_INFO );
+	OUT_RING( code[I810_TEXREG_MI1] );
+	OUT_RING( code[I810_TEXREG_MI2] );
+	OUT_RING( code[I810_TEXREG_MI3] );
+
+	for ( i = 4 ; i < I810_TEX_SETUP_SIZE ; i++ ) {
+		tmp = code[i];
+
+		if ((tmp & (7<<29)) == (3<<29) &&
+		    (tmp & (0x1f<<24)) < (0x1d<<24))
+		{
+			OUT_RING( tmp );
+			j++;
+		}
+		else printk("texture state dropped!!!\n");
+	}
+
+	if (j & 1)
+		OUT_RING( 0 );
+
+	ADVANCE_LP_RING();
+}
+
+
+/* Need to do some additional checking when setting the dest buffer.
+ */
+static void i810EmitDestVerified( drm_device_t *dev,
+				  volatile unsigned int *code )
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+	unsigned int tmp;
+	RING_LOCALS;
+
+	BEGIN_LP_RING( I810_DEST_SETUP_SIZE + 2 );
+
+	tmp = code[I810_DESTREG_DI1];
+	if (tmp == dev_priv->front_di1 || tmp == dev_priv->back_di1) {
+		OUT_RING( CMD_OP_DESTBUFFER_INFO );
+		OUT_RING( tmp );
+	} else
+	   DRM_DEBUG("bad di1 %x (allow %x or %x)\n",
+		     tmp, dev_priv->front_di1, dev_priv->back_di1);
+
+	/* invarient:
+	 */
+	OUT_RING( CMD_OP_Z_BUFFER_INFO );
+	OUT_RING( dev_priv->zi1 );
+
+	OUT_RING( GFX_OP_DESTBUFFER_VARS );
+	OUT_RING( code[I810_DESTREG_DV1] );
+
+	OUT_RING( GFX_OP_DRAWRECT_INFO );
+	OUT_RING( code[I810_DESTREG_DR1] );
+	OUT_RING( code[I810_DESTREG_DR2] );
+	OUT_RING( code[I810_DESTREG_DR3] );
+	OUT_RING( code[I810_DESTREG_DR4] );
+	OUT_RING( 0 );
+
+	ADVANCE_LP_RING();
+}
+
+
+
+static void i810EmitState( drm_device_t *dev )
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+      	drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned int dirty = sarea_priv->dirty;
+	
+	DRM_DEBUG("%s %x\n", __FUNCTION__, dirty);
+
+	if (dirty & I810_UPLOAD_BUFFERS) {
+		i810EmitDestVerified( dev, sarea_priv->BufferState );
+		sarea_priv->dirty &= ~I810_UPLOAD_BUFFERS;
+	}
+
+	if (dirty & I810_UPLOAD_CTX) {
+		i810EmitContextVerified( dev, sarea_priv->ContextState );
+		sarea_priv->dirty &= ~I810_UPLOAD_CTX;
+	}
+
+	if (dirty & I810_UPLOAD_TEX0) {
+		i810EmitTexVerified( dev, sarea_priv->TexState[0] );
+		sarea_priv->dirty &= ~I810_UPLOAD_TEX0;
+	}
+
+	if (dirty & I810_UPLOAD_TEX1) {
+		i810EmitTexVerified( dev, sarea_priv->TexState[1] );
+		sarea_priv->dirty &= ~I810_UPLOAD_TEX1;
+	}
+}
+
+
+
+/* need to verify
+ */
+static void i810_dma_dispatch_clear( drm_device_t *dev, int flags,
+				     unsigned int clear_color,
+				     unsigned int clear_zval )
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+      	drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int nbox = sarea_priv->nbox;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int pitch = dev_priv->pitch;
+	int cpp = 2;
+	int i;
+	RING_LOCALS;
+	
+	if ( dev_priv->current_page == 1 ) {
+	        unsigned int tmp = flags;
+	       
+		flags &= ~(I810_FRONT | I810_BACK);
+		if (tmp & I810_FRONT) flags |= I810_BACK;
+		if (tmp & I810_BACK) flags |= I810_FRONT;
+	}
+
+  	i810_kernel_lost_context(dev);
+
+      	if (nbox > I810_NR_SAREA_CLIPRECTS)
+     		nbox = I810_NR_SAREA_CLIPRECTS;
+
+	for (i = 0 ; i < nbox ; i++, pbox++) {
+		unsigned int x = pbox->x1;
+		unsigned int y = pbox->y1;
+		unsigned int width = (pbox->x2 - x) * cpp;
+		unsigned int height = pbox->y2 - y;
+		unsigned int start = y * pitch + x * cpp;
+
+		if (pbox->x1 > pbox->x2 ||
+		    pbox->y1 > pbox->y2 ||
+		    pbox->x2 > dev_priv->w ||
+		    pbox->y2 > dev_priv->h)
+			continue;
+
+	   	if ( flags & I810_FRONT ) {
+			BEGIN_LP_RING( 6 );
+			OUT_RING( BR00_BITBLT_CLIENT |
+				  BR00_OP_COLOR_BLT | 0x3 );
+			OUT_RING( BR13_SOLID_PATTERN | (0xF0 << 16) | pitch );
+			OUT_RING( (height << 16) | width );
+			OUT_RING( start );
+			OUT_RING( clear_color );
+			OUT_RING( 0 );
+			ADVANCE_LP_RING();
+		}
+
+		if ( flags & I810_BACK ) {
+			BEGIN_LP_RING( 6 );
+			OUT_RING( BR00_BITBLT_CLIENT |
+				  BR00_OP_COLOR_BLT | 0x3 );
+			OUT_RING( BR13_SOLID_PATTERN | (0xF0 << 16) | pitch );
+			OUT_RING( (height << 16) | width );
+			OUT_RING( dev_priv->back_offset + start );
+			OUT_RING( clear_color );
+			OUT_RING( 0 );
+			ADVANCE_LP_RING();
+		}
+
+		if ( flags & I810_DEPTH ) {
+			BEGIN_LP_RING( 6 );
+			OUT_RING( BR00_BITBLT_CLIENT |
+				  BR00_OP_COLOR_BLT | 0x3 );
+			OUT_RING( BR13_SOLID_PATTERN | (0xF0 << 16) | pitch );
+			OUT_RING( (height << 16) | width );
+			OUT_RING( dev_priv->depth_offset + start );
+			OUT_RING( clear_zval );
+			OUT_RING( 0 );
+			ADVANCE_LP_RING();
+		}
+	}
+}
+
+static void i810_dma_dispatch_swap( drm_device_t *dev )
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+      	drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int nbox = sarea_priv->nbox;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int pitch = dev_priv->pitch;
+	int cpp = 2;
+	int i;
+	RING_LOCALS;
+
+	DRM_DEBUG("swapbuffers\n");
+
+  	i810_kernel_lost_context(dev);
+
+      	if (nbox > I810_NR_SAREA_CLIPRECTS)
+     		nbox = I810_NR_SAREA_CLIPRECTS;
+
+	for (i = 0 ; i < nbox; i++, pbox++)
+	{
+		unsigned int w = pbox->x2 - pbox->x1;
+		unsigned int h = pbox->y2 - pbox->y1;
+		unsigned int dst = pbox->x1*cpp + pbox->y1*pitch;
+		unsigned int start = dst;
+
+		if (pbox->x1 > pbox->x2 ||
+		    pbox->y1 > pbox->y2 ||
+		    pbox->x2 > dev_priv->w ||
+		    pbox->y2 > dev_priv->h)
+			continue;
+
+		BEGIN_LP_RING( 6 );
+		OUT_RING( BR00_BITBLT_CLIENT | BR00_OP_SRC_COPY_BLT | 0x4 );
+		OUT_RING( pitch | (0xCC << 16));
+		OUT_RING( (h << 16) | (w * cpp));
+		if (dev_priv->current_page == 0)
+		  OUT_RING(dev_priv->front_offset + start);
+		else
+		  OUT_RING(dev_priv->back_offset + start);
+		OUT_RING( pitch );
+		if (dev_priv->current_page == 0)
+		  OUT_RING(dev_priv->back_offset + start);
+		else
+		  OUT_RING(dev_priv->front_offset + start);
+		ADVANCE_LP_RING();
+	}
+}
+
+
+static void i810_dma_dispatch_vertex(drm_device_t *dev,
+				     drm_buf_t *buf,
+				     int discard,
+				     int used)
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+   	drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+   	drm_clip_rect_t *box = sarea_priv->boxes;
+   	int nbox = sarea_priv->nbox;
+	unsigned long address = (unsigned long)buf->bus_address;
+	unsigned long start = address - dev->agp->base;
+	int i = 0;
+   	RING_LOCALS;
+
+   	i810_kernel_lost_context(dev);
+
+   	if (nbox > I810_NR_SAREA_CLIPRECTS)
+		nbox = I810_NR_SAREA_CLIPRECTS;
+
+	if (used > 4*1024)
+		used = 0;
+
+	if (sarea_priv->dirty)
+	   i810EmitState( dev );
+
+	if (buf_priv->currently_mapped == I810_BUF_MAPPED) {
+		unsigned int prim = (sarea_priv->vertex_prim & PR_MASK);
+
+		*(u32 *)buf_priv->kernel_virtual = ((GFX_OP_PRIMITIVE | prim | ((used/4)-2)));
+
+		if (used & 4) {
+			*(u32 *)((u32)buf_priv->kernel_virtual + used) = 0;
+			used += 4;
+		}
+
+		i810_unmap_buffer(buf);
+	}
+
+	if (used) {
+		do {
+			if (i < nbox) {
+				BEGIN_LP_RING(4);
+				OUT_RING( GFX_OP_SCISSOR | SC_UPDATE_SCISSOR |
+					  SC_ENABLE );
+				OUT_RING( GFX_OP_SCISSOR_INFO );
+				OUT_RING( box[i].x1 | (box[i].y1<<16) );
+				OUT_RING( (box[i].x2-1) | ((box[i].y2-1)<<16) );
+				ADVANCE_LP_RING();
+			}
+
+			BEGIN_LP_RING(4);
+			OUT_RING( CMD_OP_BATCH_BUFFER );
+			OUT_RING( start | BB1_PROTECTED );
+			OUT_RING( start + used - 4 );
+			OUT_RING( 0 );
+			ADVANCE_LP_RING();
+
+		} while (++i < nbox);
+	}
+
+	if (discard) {
+		dev_priv->counter++;
+
+		(void) cmpxchg(buf_priv->in_use, I810_BUF_CLIENT,
+			       I810_BUF_HARDWARE);
+
+		BEGIN_LP_RING(8);
+		OUT_RING( CMD_STORE_DWORD_IDX );
+		OUT_RING( 20 );
+		OUT_RING( dev_priv->counter );
+		OUT_RING( CMD_STORE_DWORD_IDX );
+		OUT_RING( buf_priv->my_use_idx );
+		OUT_RING( I810_BUF_FREE );
+		OUT_RING( CMD_REPORT_HEAD );
+		OUT_RING( 0 );
+		ADVANCE_LP_RING();
+	}
+}
+
+static void i810_dma_dispatch_flip( drm_device_t *dev )
+{
+        drm_i810_private_t *dev_priv = dev->dev_private;
+	int pitch = dev_priv->pitch;
+	RING_LOCALS;
+
+	DRM_DEBUG( "%s: page=%d pfCurrentPage=%d\n", 
+		__FUNCTION__, 
+		dev_priv->current_page,
+		dev_priv->sarea_priv->pf_current_page);
+	
+        i810_kernel_lost_context(dev);
+
+	BEGIN_LP_RING( 2 );
+   	OUT_RING( INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE ); 
+	OUT_RING( 0 );
+	ADVANCE_LP_RING();
+
+	BEGIN_LP_RING( I810_DEST_SETUP_SIZE + 2 );
+	/* On i815 at least ASYNC is buggy */
+	/* pitch<<5 is from 11.2.8 p158,
+	   its the pitch / 8 then left shifted 8,
+	   so (pitch >> 3) << 8 */
+	OUT_RING( CMD_OP_FRONTBUFFER_INFO | (pitch<<5) /*| ASYNC_FLIP */ );
+	if ( dev_priv->current_page == 0 ) {
+		OUT_RING( dev_priv->back_offset );
+		dev_priv->current_page = 1;
+	} else {
+		OUT_RING( dev_priv->front_offset );
+		dev_priv->current_page = 0;
+	}
+	OUT_RING(0);
+	ADVANCE_LP_RING();
+
+	BEGIN_LP_RING(2);
+	OUT_RING( CMD_OP_WAIT_FOR_EVENT | WAIT_FOR_PLANE_A_FLIP );
+	OUT_RING( 0 );
+	ADVANCE_LP_RING();
+
+	/* Increment the frame counter.  The client-side 3D driver must
+	 * throttle the framerate by waiting for this value before
+	 * performing the swapbuffer ioctl.
+	 */
+	dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+
+}
+
+static void i810_dma_quiescent(drm_device_t *dev)
+{
+      	drm_i810_private_t *dev_priv = dev->dev_private;
+   	RING_LOCALS;
+
+/*  	printk("%s\n", __FUNCTION__); */
+
+  	i810_kernel_lost_context(dev);
+
+   	BEGIN_LP_RING(4);
+   	OUT_RING( INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE );
+   	OUT_RING( CMD_REPORT_HEAD );
+      	OUT_RING( 0 );
+      	OUT_RING( 0 );
+   	ADVANCE_LP_RING();
+
+	i810_wait_ring( dev, dev_priv->ring.Size - 8 );
+}
+
+static int i810_flush_queue(drm_device_t *dev)
+{
+   	drm_i810_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+   	int i, ret = 0;
+   	RING_LOCALS;
+	
+/*  	printk("%s\n", __FUNCTION__); */
+
+   	i810_kernel_lost_context(dev);
+
+   	BEGIN_LP_RING(2);
+      	OUT_RING( CMD_REPORT_HEAD );
+      	OUT_RING( 0 );
+      	ADVANCE_LP_RING();
+
+	i810_wait_ring( dev, dev_priv->ring.Size - 8 );
+
+   	for (i = 0; i < dma->buf_count; i++) {
+	   	drm_buf_t *buf = dma->buflist[ i ];
+	   	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+
+		int used = cmpxchg(buf_priv->in_use, I810_BUF_HARDWARE,
+				   I810_BUF_FREE);
+
+		if (used == I810_BUF_HARDWARE)
+			DRM_DEBUG("reclaimed from HARDWARE\n");
+		if (used == I810_BUF_CLIENT)
+			DRM_DEBUG("still on client\n");
+	}
+
+   	return ret;
+}
+
+/* Must be called with the lock held */
+void i810_reclaim_buffers(drm_device_t *dev, struct file *filp)
+{
+	drm_device_dma_t *dma = dev->dma;
+	int		 i;
+
+	if (!dma) return;
+      	if (!dev->dev_private) return;
+	if (!dma->buflist) return;
+
+        i810_flush_queue(dev);
+
+	for (i = 0; i < dma->buf_count; i++) {
+	   	drm_buf_t *buf = dma->buflist[ i ];
+	   	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+
+		if (buf->filp == filp && buf_priv) {
+			int used = cmpxchg(buf_priv->in_use, I810_BUF_CLIENT,
+					   I810_BUF_FREE);
+
+			if (used == I810_BUF_CLIENT)
+				DRM_DEBUG("reclaimed from client\n");
+			if (buf_priv->currently_mapped == I810_BUF_MAPPED)
+		     		buf_priv->currently_mapped = I810_BUF_UNMAPPED;
+		}
+	}
+}
+
+int i810_flush_ioctl(struct inode *inode, struct file *filp,
+		     unsigned int cmd, unsigned long arg)
+{
+   	drm_file_t	  *priv	  = filp->private_data;
+   	drm_device_t	  *dev	  = priv->head->dev;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+   	i810_flush_queue(dev);
+   	return 0;
+}
+
+
+static int i810_dma_vertex(struct inode *inode, struct file *filp,
+	       unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+   	drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+      	u32 *hw_status = dev_priv->hw_status_page;
+   	drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *)
+     					dev_priv->sarea_priv;
+	drm_i810_vertex_t vertex;
+
+	if (copy_from_user(&vertex, (drm_i810_vertex_t __user *)arg, sizeof(vertex)))
+		return -EFAULT;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	DRM_DEBUG("i810 dma vertex, idx %d used %d discard %d\n",
+		  vertex.idx, vertex.used, vertex.discard);
+
+	if (vertex.idx < 0 || vertex.idx > dma->buf_count) 
+		return -EINVAL;
+
+	i810_dma_dispatch_vertex( dev,
+				  dma->buflist[ vertex.idx ],
+				  vertex.discard, vertex.used );
+
+   	atomic_add(vertex.used, &dev->counts[_DRM_STAT_SECONDARY]);
+	atomic_inc(&dev->counts[_DRM_STAT_DMA]);
+	sarea_priv->last_enqueue = dev_priv->counter-1;
+   	sarea_priv->last_dispatch = (int) hw_status[5];
+
+	return 0;
+}
+
+
+
+static int i810_clear_bufs(struct inode *inode, struct file *filp,
+		   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_i810_clear_t clear;
+
+   	if (copy_from_user(&clear, (drm_i810_clear_t __user *)arg, sizeof(clear)))
+		return -EFAULT;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+ 	/* GH: Someone's doing nasty things... */
+ 	if (!dev->dev_private) {
+ 		return -EINVAL;
+ 	}
+
+	i810_dma_dispatch_clear( dev, clear.flags,
+				 clear.clear_color,
+				 clear.clear_depth );
+   	return 0;
+}
+
+static int i810_swap_bufs(struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+
+	DRM_DEBUG("i810_swap_bufs\n");
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	i810_dma_dispatch_swap( dev );
+   	return 0;
+}
+
+static int i810_getage(struct inode *inode, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+{
+   	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->head->dev;
+   	drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+      	u32 *hw_status = dev_priv->hw_status_page;
+   	drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *)
+     					dev_priv->sarea_priv;
+
+      	sarea_priv->last_dispatch = (int) hw_status[5];
+	return 0;
+}
+
+static int i810_getbuf(struct inode *inode, struct file *filp, unsigned int cmd,
+		unsigned long arg)
+{
+	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->head->dev;
+	int		  retcode   = 0;
+	drm_i810_dma_t	  d;
+   	drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+   	u32 *hw_status = dev_priv->hw_status_page;
+   	drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *)
+     					dev_priv->sarea_priv;
+
+   	if (copy_from_user(&d, (drm_i810_dma_t __user *)arg, sizeof(d)))
+		return -EFAULT;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	d.granted = 0;
+
+	retcode = i810_dma_get_buffer(dev, &d, filp);
+
+	DRM_DEBUG("i810_dma: %d returning %d, granted = %d\n",
+		  current->pid, retcode, d.granted);
+
+	if (copy_to_user((drm_dma_t __user *)arg, &d, sizeof(d)))
+		return -EFAULT;
+   	sarea_priv->last_dispatch = (int) hw_status[5];
+
+	return retcode;
+}
+
+static int i810_copybuf(struct inode *inode,
+			 struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	/* Never copy - 2.4.x doesn't need it */
+	return 0;
+}
+
+static int i810_docopy(struct inode *inode, struct file *filp, unsigned int cmd,
+			unsigned long arg)
+{
+	/* Never copy - 2.4.x doesn't need it */
+	return 0;
+}
+
+static void i810_dma_dispatch_mc(drm_device_t *dev, drm_buf_t *buf, int used,
+		unsigned int last_render)
+{
+	drm_i810_private_t *dev_priv = dev->dev_private;
+	drm_i810_buf_priv_t *buf_priv = buf->dev_private;
+	drm_i810_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned long address = (unsigned long)buf->bus_address;
+	unsigned long start = address - dev->agp->base;
+	int u;
+	RING_LOCALS;
+
+	i810_kernel_lost_context(dev);
+
+	u = cmpxchg(buf_priv->in_use, I810_BUF_CLIENT,
+		I810_BUF_HARDWARE);
+	if (u != I810_BUF_CLIENT) {
+		DRM_DEBUG("MC found buffer that isn't mine!\n");
+	}
+
+	if (used > 4*1024)
+		used = 0;
+
+	sarea_priv->dirty = 0x7f;
+
+	DRM_DEBUG("dispatch mc addr 0x%lx, used 0x%x\n",
+		address, used);
+
+	dev_priv->counter++;
+	DRM_DEBUG("dispatch counter : %ld\n", dev_priv->counter);
+	DRM_DEBUG("i810_dma_dispatch_mc\n");
+	DRM_DEBUG("start : %lx\n", start);
+	DRM_DEBUG("used : %d\n", used);
+	DRM_DEBUG("start + used - 4 : %ld\n", start + used - 4);
+
+	if (buf_priv->currently_mapped == I810_BUF_MAPPED) {
+		if (used & 4) {
+			*(u32 *)((u32)buf_priv->virtual + used) = 0;
+			used += 4;
+		}
+
+		i810_unmap_buffer(buf);
+	}
+	BEGIN_LP_RING(4);
+	OUT_RING( CMD_OP_BATCH_BUFFER );
+	OUT_RING( start | BB1_PROTECTED );
+	OUT_RING( start + used - 4 );
+	OUT_RING( 0 );
+	ADVANCE_LP_RING();
+
+
+	BEGIN_LP_RING(8);
+	OUT_RING( CMD_STORE_DWORD_IDX );
+	OUT_RING( buf_priv->my_use_idx );
+	OUT_RING( I810_BUF_FREE );
+	OUT_RING( 0 );
+
+	OUT_RING( CMD_STORE_DWORD_IDX );
+	OUT_RING( 16 );
+	OUT_RING( last_render );
+	OUT_RING( 0 );
+	ADVANCE_LP_RING();
+}
+
+static int i810_dma_mc(struct inode *inode, struct file *filp,
+	unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+	drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+	u32 *hw_status = dev_priv->hw_status_page;
+	drm_i810_sarea_t *sarea_priv = (drm_i810_sarea_t *)
+		dev_priv->sarea_priv;
+	drm_i810_mc_t mc;
+
+	if (copy_from_user(&mc, (drm_i810_mc_t __user *)arg, sizeof(mc)))
+		return -EFAULT;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	if (mc.idx >= dma->buf_count || mc.idx < 0)
+		return -EINVAL;
+
+	i810_dma_dispatch_mc(dev, dma->buflist[mc.idx], mc.used,
+		mc.last_render );
+
+	atomic_add(mc.used, &dev->counts[_DRM_STAT_SECONDARY]);
+	atomic_inc(&dev->counts[_DRM_STAT_DMA]);
+	sarea_priv->last_enqueue = dev_priv->counter-1;
+	sarea_priv->last_dispatch = (int) hw_status[5];
+
+	return 0;
+}
+
+static int i810_rstatus(struct inode *inode, struct file *filp,
+			unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+
+	return (int)(((u32 *)(dev_priv->hw_status_page))[4]);
+}
+
+static int i810_ov0_info(struct inode *inode, struct file *filp,
+			unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+	drm_i810_overlay_t data;
+
+	data.offset = dev_priv->overlay_offset;
+	data.physical = dev_priv->overlay_physical;
+	if (copy_to_user((drm_i810_overlay_t __user *)arg,&data,sizeof(data)))
+		return -EFAULT;
+	return 0;
+}
+
+static int i810_fstatus(struct inode *inode, struct file *filp,
+			unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	return I810_READ(0x30008);
+}
+
+static int i810_ov0_flip(struct inode *inode, struct file *filp,
+			unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_i810_private_t *dev_priv = (drm_i810_private_t *)dev->dev_private;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	//Tell the overlay to update
+	I810_WRITE(0x30000,dev_priv->overlay_physical | 0x80000000);
+
+	return 0;
+}
+
+
+/* Not sure why this isn't set all the time:
+ */ 
+static void i810_do_init_pageflip( drm_device_t *dev )
+{
+	drm_i810_private_t *dev_priv = dev->dev_private;
+	
+	DRM_DEBUG("%s\n", __FUNCTION__);
+	dev_priv->page_flipping = 1;
+	dev_priv->current_page = 0;
+	dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+}
+
+static int i810_do_cleanup_pageflip( drm_device_t *dev )
+{
+	drm_i810_private_t *dev_priv = dev->dev_private;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+	if (dev_priv->current_page != 0)
+		i810_dma_dispatch_flip( dev );
+
+	dev_priv->page_flipping = 0;
+	return 0;
+}
+
+static int i810_flip_bufs(struct inode *inode, struct file *filp,
+			unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_i810_private_t *dev_priv = dev->dev_private;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	if (!dev_priv->page_flipping) 
+		i810_do_init_pageflip( dev );
+
+	i810_dma_dispatch_flip( dev );
+   	return 0;
+}
+
+void i810_driver_pretakedown(drm_device_t *dev)
+{
+	i810_dma_cleanup( dev );
+}
+
+void i810_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+	if (dev->dev_private) {
+		drm_i810_private_t *dev_priv = dev->dev_private;
+		if (dev_priv->page_flipping) {
+			i810_do_cleanup_pageflip(dev);
+		}
+	}
+}
+
+void i810_driver_release(drm_device_t *dev, struct file *filp)
+{
+	i810_reclaim_buffers(dev, filp);
+}
+
+int i810_driver_dma_quiescent(drm_device_t *dev)
+{
+	i810_dma_quiescent( dev );
+	return 0;
+}
+
+drm_ioctl_desc_t i810_ioctls[] = {
+	[DRM_IOCTL_NR(DRM_I810_INIT)]    = { i810_dma_init,    1, 1 },
+	[DRM_IOCTL_NR(DRM_I810_VERTEX)]  = { i810_dma_vertex,  1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_CLEAR)]   = { i810_clear_bufs,  1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_FLUSH)]   = { i810_flush_ioctl, 1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_GETAGE)]  = { i810_getage,      1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_GETBUF)]  = { i810_getbuf,      1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_SWAP)]    = { i810_swap_bufs,   1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_COPY)]    = { i810_copybuf,     1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_DOCOPY)]  = { i810_docopy,      1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_OV0INFO)] = { i810_ov0_info,    1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_FSTATUS)] = { i810_fstatus,     1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_OV0FLIP)] = { i810_ov0_flip,    1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_MC)]      = { i810_dma_mc,      1, 1 },
+	[DRM_IOCTL_NR(DRM_I810_RSTATUS)] = { i810_rstatus,     1, 0 },
+	[DRM_IOCTL_NR(DRM_I810_FLIP)]    = { i810_flip_bufs,   1, 0 }
+};
+
+int i810_max_ioctl = DRM_ARRAY_SIZE(i810_ioctls);
diff --git a/drivers/char/drm/i810_drm.h b/drivers/char/drm/i810_drm.h
new file mode 100644
index 0000000..73ac405
--- /dev/null
+++ b/drivers/char/drm/i810_drm.h
@@ -0,0 +1,289 @@
+#ifndef _I810_DRM_H_
+#define _I810_DRM_H_
+
+/* WARNING: These defines must be the same as what the Xserver uses.
+ * if you change them, you must change the defines in the Xserver.
+ */
+
+#ifndef _I810_DEFINES_
+#define _I810_DEFINES_
+
+#define I810_DMA_BUF_ORDER		12
+#define I810_DMA_BUF_SZ 		(1<<I810_DMA_BUF_ORDER)
+#define I810_DMA_BUF_NR 		256
+#define I810_NR_SAREA_CLIPRECTS 	8
+
+/* Each region is a minimum of 64k, and there are at most 64 of them.
+ */
+#define I810_NR_TEX_REGIONS 64
+#define I810_LOG_MIN_TEX_REGION_SIZE 16
+#endif
+
+#define I810_UPLOAD_TEX0IMAGE  0x1 /* handled clientside */
+#define I810_UPLOAD_TEX1IMAGE  0x2 /* handled clientside */
+#define I810_UPLOAD_CTX        0x4
+#define I810_UPLOAD_BUFFERS    0x8
+#define I810_UPLOAD_TEX0       0x10
+#define I810_UPLOAD_TEX1       0x20
+#define I810_UPLOAD_CLIPRECTS  0x40
+
+
+/* Indices into buf.Setup where various bits of state are mirrored per
+ * context and per buffer.  These can be fired at the card as a unit,
+ * or in a piecewise fashion as required.
+ */
+
+/* Destbuffer state 
+ *    - backbuffer linear offset and pitch -- invarient in the current dri
+ *    - zbuffer linear offset and pitch -- also invarient
+ *    - drawing origin in back and depth buffers.
+ *
+ * Keep the depth/back buffer state here to accommodate private buffers
+ * in the future.
+ */
+#define I810_DESTREG_DI0  0	/* CMD_OP_DESTBUFFER_INFO (2 dwords) */
+#define I810_DESTREG_DI1  1
+#define I810_DESTREG_DV0  2	/* GFX_OP_DESTBUFFER_VARS (2 dwords) */
+#define I810_DESTREG_DV1  3
+#define I810_DESTREG_DR0  4	/* GFX_OP_DRAWRECT_INFO (4 dwords) */
+#define I810_DESTREG_DR1  5
+#define I810_DESTREG_DR2  6
+#define I810_DESTREG_DR3  7
+#define I810_DESTREG_DR4  8
+#define I810_DEST_SETUP_SIZE 10
+
+/* Context state
+ */
+#define I810_CTXREG_CF0   0	/* GFX_OP_COLOR_FACTOR */
+#define I810_CTXREG_CF1   1	
+#define I810_CTXREG_ST0   2     /* GFX_OP_STIPPLE */
+#define I810_CTXREG_ST1   3
+#define I810_CTXREG_VF    4	/* GFX_OP_VERTEX_FMT */
+#define I810_CTXREG_MT    5	/* GFX_OP_MAP_TEXELS */
+#define I810_CTXREG_MC0   6	/* GFX_OP_MAP_COLOR_STAGES - stage 0 */
+#define I810_CTXREG_MC1   7     /* GFX_OP_MAP_COLOR_STAGES - stage 1 */
+#define I810_CTXREG_MC2   8	/* GFX_OP_MAP_COLOR_STAGES - stage 2 */
+#define I810_CTXREG_MA0   9	/* GFX_OP_MAP_ALPHA_STAGES - stage 0 */
+#define I810_CTXREG_MA1   10	/* GFX_OP_MAP_ALPHA_STAGES - stage 1 */
+#define I810_CTXREG_MA2   11	/* GFX_OP_MAP_ALPHA_STAGES - stage 2 */
+#define I810_CTXREG_SDM   12	/* GFX_OP_SRC_DEST_MONO */
+#define I810_CTXREG_FOG   13	/* GFX_OP_FOG_COLOR */
+#define I810_CTXREG_B1    14	/* GFX_OP_BOOL_1 */
+#define I810_CTXREG_B2    15	/* GFX_OP_BOOL_2 */
+#define I810_CTXREG_LCS   16	/* GFX_OP_LINEWIDTH_CULL_SHADE_MODE */
+#define I810_CTXREG_PV    17	/* GFX_OP_PV_RULE -- Invarient! */
+#define I810_CTXREG_ZA    18	/* GFX_OP_ZBIAS_ALPHAFUNC */
+#define I810_CTXREG_AA    19	/* GFX_OP_ANTIALIAS */
+#define I810_CTX_SETUP_SIZE 20 
+
+/* Texture state (per tex unit)
+ */
+#define I810_TEXREG_MI0  0	/* GFX_OP_MAP_INFO (4 dwords) */
+#define I810_TEXREG_MI1  1	
+#define I810_TEXREG_MI2  2	
+#define I810_TEXREG_MI3  3	
+#define I810_TEXREG_MF   4	/* GFX_OP_MAP_FILTER */
+#define I810_TEXREG_MLC  5	/* GFX_OP_MAP_LOD_CTL */
+#define I810_TEXREG_MLL  6	/* GFX_OP_MAP_LOD_LIMITS */
+#define I810_TEXREG_MCS  7	/* GFX_OP_MAP_COORD_SETS ??? */
+#define I810_TEX_SETUP_SIZE 8
+
+/* Flags for clear ioctl
+ */
+#define I810_FRONT   0x1
+#define I810_BACK    0x2
+#define I810_DEPTH   0x4
+
+typedef enum _drm_i810_init_func {
+	I810_INIT_DMA = 0x01,
+	I810_CLEANUP_DMA = 0x02,
+	I810_INIT_DMA_1_4 = 0x03
+ } drm_i810_init_func_t;
+
+/* This is the init structure after v1.2 */
+typedef struct _drm_i810_init {
+	drm_i810_init_func_t func;
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+	int ring_map_idx;
+	int buffer_map_idx;
+#else
+	unsigned int mmio_offset;
+	unsigned int buffers_offset;
+#endif
+	int sarea_priv_offset;
+	unsigned int ring_start;
+	unsigned int ring_end;
+	unsigned int ring_size;
+	unsigned int front_offset;
+	unsigned int back_offset;
+	unsigned int depth_offset;
+	unsigned int overlay_offset;
+	unsigned int overlay_physical;
+	unsigned int w;
+	unsigned int h;
+	unsigned int pitch;
+	unsigned int pitch_bits; 
+} drm_i810_init_t;
+
+/* This is the init structure prior to v1.2 */
+typedef struct _drm_i810_pre12_init {
+	drm_i810_init_func_t func;
+	unsigned int mmio_offset;
+	unsigned int buffers_offset;
+	int sarea_priv_offset;
+	unsigned int ring_start;
+	unsigned int ring_end;
+	unsigned int ring_size;
+	unsigned int front_offset;
+	unsigned int back_offset;
+	unsigned int depth_offset;
+	unsigned int w;
+	unsigned int h;
+	unsigned int pitch;
+	unsigned int pitch_bits; 
+} drm_i810_pre12_init_t;
+
+/* Warning: If you change the SAREA structure you must change the Xserver
+ * structure as well */
+
+typedef struct _drm_i810_tex_region {
+	unsigned char next, prev; /* indices to form a circular LRU  */
+	unsigned char in_use;	/* owned by a client, or free? */
+	int age;		/* tracked by clients to update local LRU's */
+} drm_i810_tex_region_t;
+
+typedef struct _drm_i810_sarea {
+   	unsigned int ContextState[I810_CTX_SETUP_SIZE];
+   	unsigned int BufferState[I810_DEST_SETUP_SIZE];
+   	unsigned int TexState[2][I810_TEX_SETUP_SIZE];
+   	unsigned int dirty;
+
+	unsigned int nbox;
+	drm_clip_rect_t boxes[I810_NR_SAREA_CLIPRECTS];
+
+	/* Maintain an LRU of contiguous regions of texture space.  If
+	 * you think you own a region of texture memory, and it has an
+	 * age different to the one you set, then you are mistaken and
+	 * it has been stolen by another client.  If global texAge
+	 * hasn't changed, there is no need to walk the list.
+	 *
+	 * These regions can be used as a proxy for the fine-grained
+	 * texture information of other clients - by maintaining them
+	 * in the same lru which is used to age their own textures,
+	 * clients have an approximate lru for the whole of global
+	 * texture space, and can make informed decisions as to which
+	 * areas to kick out.  There is no need to choose whether to
+	 * kick out your own texture or someone else's - simply eject
+	 * them all in LRU order.  
+	 */
+   
+	drm_i810_tex_region_t texList[I810_NR_TEX_REGIONS+1]; 
+				/* Last elt is sentinal */
+        int texAge;		/* last time texture was uploaded */
+        int last_enqueue;	/* last time a buffer was enqueued */
+	int last_dispatch;	/* age of the most recently dispatched buffer */
+	int last_quiescent;     /*  */
+	int ctxOwner;		/* last context to upload state */
+
+	int vertex_prim;
+
+	int pf_enabled;               /* is pageflipping allowed? */
+	int pf_active;
+	int pf_current_page;	    /* which buffer is being displayed? */
+} drm_i810_sarea_t;
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmMga.h)
+ */
+
+/* i810 specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_I810_INIT		0x00
+#define DRM_I810_VERTEX		0x01
+#define DRM_I810_CLEAR		0x02
+#define DRM_I810_FLUSH		0x03
+#define DRM_I810_GETAGE		0x04
+#define DRM_I810_GETBUF		0x05
+#define DRM_I810_SWAP		0x06
+#define DRM_I810_COPY		0x07
+#define DRM_I810_DOCOPY		0x08
+#define DRM_I810_OV0INFO	0x09
+#define DRM_I810_FSTATUS	0x0a
+#define DRM_I810_OV0FLIP	0x0b
+#define DRM_I810_MC		0x0c
+#define DRM_I810_RSTATUS	0x0d
+#define DRM_I810_FLIP		0x0e
+
+#define DRM_IOCTL_I810_INIT		DRM_IOW( DRM_COMMAND_BASE + DRM_I810_INIT, drm_i810_init_t)
+#define DRM_IOCTL_I810_VERTEX		DRM_IOW( DRM_COMMAND_BASE + DRM_I810_VERTEX, drm_i810_vertex_t)
+#define DRM_IOCTL_I810_CLEAR		DRM_IOW( DRM_COMMAND_BASE + DRM_I810_CLEAR, drm_i810_clear_t)
+#define DRM_IOCTL_I810_FLUSH		DRM_IO(  DRM_COMMAND_BASE + DRM_I810_FLUSH)
+#define DRM_IOCTL_I810_GETAGE		DRM_IO(  DRM_COMMAND_BASE + DRM_I810_GETAGE)
+#define DRM_IOCTL_I810_GETBUF		DRM_IOWR(DRM_COMMAND_BASE + DRM_I810_GETBUF, drm_i810_dma_t)
+#define DRM_IOCTL_I810_SWAP		DRM_IO(  DRM_COMMAND_BASE + DRM_I810_SWAP)
+#define DRM_IOCTL_I810_COPY		DRM_IOW( DRM_COMMAND_BASE + DRM_I810_COPY, drm_i810_copy_t)
+#define DRM_IOCTL_I810_DOCOPY		DRM_IO(  DRM_COMMAND_BASE + DRM_I810_DOCOPY)
+#define DRM_IOCTL_I810_OV0INFO		DRM_IOR( DRM_COMMAND_BASE + DRM_I810_OV0INFO, drm_i810_overlay_t)
+#define DRM_IOCTL_I810_FSTATUS		DRM_IO ( DRM_COMMAND_BASE + DRM_I810_FSTATUS)
+#define DRM_IOCTL_I810_OV0FLIP		DRM_IO ( DRM_COMMAND_BASE + DRM_I810_OV0FLIP)
+#define DRM_IOCTL_I810_MC		DRM_IOW( DRM_COMMAND_BASE + DRM_I810_MC, drm_i810_mc_t)
+#define DRM_IOCTL_I810_RSTATUS		DRM_IO ( DRM_COMMAND_BASE + DRM_I810_RSTATUS)
+#define DRM_IOCTL_I810_FLIP             DRM_IO ( DRM_COMMAND_BASE + DRM_I810_FLIP)
+
+typedef struct _drm_i810_clear {
+	int clear_color;
+	int clear_depth;
+	int flags;
+} drm_i810_clear_t;
+
+/* These may be placeholders if we have more cliprects than
+ * I810_NR_SAREA_CLIPRECTS.  In that case, the client sets discard to
+ * false, indicating that the buffer will be dispatched again with a
+ * new set of cliprects.
+ */
+typedef struct _drm_i810_vertex {
+   	int idx;		/* buffer index */
+	int used;		/* nr bytes in use */
+	int discard;		/* client is finished with the buffer? */
+} drm_i810_vertex_t;
+
+typedef struct _drm_i810_copy_t {
+   	int idx;		/* buffer index */
+	int used;		/* nr bytes in use */
+	void *address;		/* Address to copy from */
+} drm_i810_copy_t;
+
+#define PR_TRIANGLES         (0x0<<18)
+#define PR_TRISTRIP_0        (0x1<<18)
+#define PR_TRISTRIP_1        (0x2<<18)
+#define PR_TRIFAN            (0x3<<18)
+#define PR_POLYGON           (0x4<<18)
+#define PR_LINES             (0x5<<18)
+#define PR_LINESTRIP         (0x6<<18)
+#define PR_RECTS             (0x7<<18)
+#define PR_MASK              (0x7<<18)
+
+
+typedef struct drm_i810_dma {
+	void *virtual;
+	int request_idx;
+	int request_size;
+	int granted;
+} drm_i810_dma_t;
+
+typedef struct _drm_i810_overlay_t {
+	unsigned int offset;    /* Address of the Overlay Regs */
+	unsigned int physical;
+} drm_i810_overlay_t;
+
+typedef struct _drm_i810_mc {
+	int idx;                /* buffer index */
+	int used;               /* nr bytes in use */
+	int num_blocks;         /* number of GFXBlocks */
+	int *length;            /* List of lengths for GFXBlocks (FUTURE)*/
+	unsigned int last_render; /* Last Render Request */
+} drm_i810_mc_t;
+
+
+#endif /* _I810_DRM_H_ */
diff --git a/drivers/char/drm/i810_drv.c b/drivers/char/drm/i810_drv.c
new file mode 100644
index 0000000..ff51b32
--- /dev/null
+++ b/drivers/char/drm/i810_drv.c
@@ -0,0 +1,126 @@
+/* i810_drv.c -- I810 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:56:22 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Jeff Hartmann <jhartmann@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "i810_drm.h"
+#include "i810_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+	/* i810 has 4 more counters */
+	dev->counters += 4;
+	dev->types[6] = _DRM_STAT_IRQ;
+	dev->types[7] = _DRM_STAT_PRIMARY;
+	dev->types[8] = _DRM_STAT_SECONDARY;
+	dev->types[9] = _DRM_STAT_DMA;
+	
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->primary.minor,
+		pci_pretty_name(dev->pdev)
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+	i810_PCI_IDS
+};
+
+extern drm_ioctl_desc_t i810_ioctls[];
+extern int i810_max_ioctl;
+
+static struct drm_driver driver = {
+	.driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | DRIVER_USE_MTRR | DRIVER_HAVE_DMA | DRIVER_DMA_QUEUE,
+	.dev_priv_size = sizeof(drm_i810_buf_priv_t),
+	.pretakedown = i810_driver_pretakedown,
+	.prerelease = i810_driver_prerelease,
+	.release = i810_driver_release,
+	.dma_quiescent = i810_driver_dma_quiescent,
+	.reclaim_buffers = i810_reclaim_buffers,
+	.get_map_ofs = drm_core_get_map_ofs,
+	.get_reg_ofs = drm_core_get_reg_ofs,
+	.postinit = postinit,
+	.version = version,
+	.ioctls = i810_ioctls,
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+	.pci_driver = {
+		.name          = DRIVER_NAME,
+		.id_table      = pciidlist,
+	},
+};
+
+static int __init i810_init(void)
+{
+	driver.num_ioctls = i810_max_ioctl;
+	return drm_init(&driver);
+}
+
+static void __exit i810_exit(void)
+{
+	drm_exit(&driver);
+}
+
+module_init(i810_init);
+module_exit(i810_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/i810_drv.h b/drivers/char/drm/i810_drv.h
new file mode 100644
index 0000000..fa23ca4
--- /dev/null
+++ b/drivers/char/drm/i810_drv.h
@@ -0,0 +1,236 @@
+/* i810_drv.h -- Private header for the Matrox g200/g400 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
+ * 	    Jeff Hartmann <jhartmann@valinux.com>
+ *
+ */
+
+#ifndef _I810_DRV_H_
+#define _I810_DRV_H_
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR		"VA Linux Systems Inc."
+
+#define DRIVER_NAME		"i810"
+#define DRIVER_DESC		"Intel i810"
+#define DRIVER_DATE		"20030605"
+
+/* Interface history
+ *
+ * 1.1   - XFree86 4.1
+ * 1.2   - XvMC interfaces
+ *       - XFree86 4.2
+ * 1.2.1 - Disable copying code (leave stub ioctls for backwards compatibility)
+ *       - Remove requirement for interrupt (leave stubs again)
+ * 1.3   - Add page flipping.
+ * 1.4   - fix DRM interface
+ */
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		4
+#define DRIVER_PATCHLEVEL	0
+
+typedef struct drm_i810_buf_priv {
+   	u32 *in_use;
+   	int my_use_idx;
+	int currently_mapped;
+	void *virtual;
+	void *kernel_virtual;
+} drm_i810_buf_priv_t;
+
+typedef struct _drm_i810_ring_buffer{
+	int tail_mask;
+	unsigned long Start;
+	unsigned long End;
+	unsigned long Size;
+	u8 *virtual_start;
+	int head;
+	int tail;
+	int space;
+} drm_i810_ring_buffer_t;
+
+typedef struct drm_i810_private {
+	drm_map_t *sarea_map;
+	drm_map_t *mmio_map;
+
+	drm_i810_sarea_t *sarea_priv;
+   	drm_i810_ring_buffer_t ring;
+
+      	void *hw_status_page;
+   	unsigned long counter;
+
+	dma_addr_t dma_status_page;
+
+	drm_buf_t *mmap_buffer;
+
+
+	u32 front_di1, back_di1, zi1;
+
+	int back_offset;
+	int depth_offset;
+	int overlay_offset;
+	int overlay_physical;
+	int w, h;
+	int pitch;
+  	int back_pitch;
+	int depth_pitch;
+
+	int do_boxes;
+	int dma_used;
+
+	int current_page;
+	int page_flipping;
+
+	wait_queue_head_t irq_queue;
+   	atomic_t irq_received;
+   	atomic_t irq_emitted;
+  
+        int front_offset;
+} drm_i810_private_t;
+
+				/* i810_dma.c */
+extern void i810_reclaim_buffers(drm_device_t *dev, struct file *filp);
+extern int i810_mmap_buffers(struct file *filp, struct vm_area_struct *vma);
+
+extern int i810_driver_dma_quiescent(drm_device_t *dev);
+extern void i810_driver_release(drm_device_t *dev, struct file *filp);
+extern void i810_driver_pretakedown(drm_device_t *dev);
+extern void i810_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+
+#define I810_BASE(reg)		((unsigned long) \
+				dev_priv->mmio_map->handle)
+#define I810_ADDR(reg)		(I810_BASE(reg) + reg)
+#define I810_DEREF(reg)		*(__volatile__ int *)I810_ADDR(reg)
+#define I810_READ(reg)		I810_DEREF(reg)
+#define I810_WRITE(reg,val) 	do { I810_DEREF(reg) = val; } while (0)
+#define I810_DEREF16(reg)	*(__volatile__ u16 *)I810_ADDR(reg)
+#define I810_READ16(reg)	I810_DEREF16(reg)
+#define I810_WRITE16(reg,val)	do { I810_DEREF16(reg) = val; } while (0)
+
+#define I810_VERBOSE 0
+#define RING_LOCALS	unsigned int outring, ringmask; \
+                        volatile char *virt;
+
+#define BEGIN_LP_RING(n) do {						\
+	if (I810_VERBOSE)                                               \
+           DRM_DEBUG("BEGIN_LP_RING(%d) in %s\n", n, __FUNCTION__);	\
+	if (dev_priv->ring.space < n*4)					\
+		i810_wait_ring(dev, n*4);				\
+	dev_priv->ring.space -= n*4;					\
+	outring = dev_priv->ring.tail;					\
+	ringmask = dev_priv->ring.tail_mask;				\
+	virt = dev_priv->ring.virtual_start;				\
+} while (0)
+
+#define ADVANCE_LP_RING() do {				        \
+	if (I810_VERBOSE) DRM_DEBUG("ADVANCE_LP_RING\n");    	\
+	dev_priv->ring.tail = outring;		        	\
+	I810_WRITE(LP_RING + RING_TAIL, outring);	        \
+} while(0)
+
+#define OUT_RING(n) do {  				                \
+	if (I810_VERBOSE) DRM_DEBUG("   OUT_RING %x\n", (int)(n));	\
+	*(volatile unsigned int *)(virt + outring) = n;	                \
+	outring += 4;					                \
+	outring &= ringmask;			                        \
+} while (0)
+
+#define GFX_OP_USER_INTERRUPT 		((0<<29)|(2<<23))
+#define GFX_OP_BREAKPOINT_INTERRUPT	((0<<29)|(1<<23))
+#define CMD_REPORT_HEAD			(7<<23)
+#define CMD_STORE_DWORD_IDX		((0x21<<23) | 0x1)
+#define CMD_OP_BATCH_BUFFER  ((0x0<<29)|(0x30<<23)|0x1)
+
+#define INST_PARSER_CLIENT   0x00000000
+#define INST_OP_FLUSH        0x02000000
+#define INST_FLUSH_MAP_CACHE 0x00000001
+
+
+#define BB1_START_ADDR_MASK   (~0x7)
+#define BB1_PROTECTED         (1<<0)
+#define BB1_UNPROTECTED       (0<<0)
+#define BB2_END_ADDR_MASK     (~0x7)
+
+#define I810REG_HWSTAM		0x02098
+#define I810REG_INT_IDENTITY_R	0x020a4
+#define I810REG_INT_MASK_R 	0x020a8
+#define I810REG_INT_ENABLE_R	0x020a0
+
+#define LP_RING     		0x2030
+#define HP_RING     		0x2040
+#define RING_TAIL      		0x00
+#define TAIL_ADDR		0x000FFFF8
+#define RING_HEAD      		0x04
+#define HEAD_WRAP_COUNT     	0xFFE00000
+#define HEAD_WRAP_ONE       	0x00200000
+#define HEAD_ADDR           	0x001FFFFC
+#define RING_START     		0x08
+#define START_ADDR          	0x00FFFFF8
+#define RING_LEN       		0x0C
+#define RING_NR_PAGES       	0x000FF000
+#define RING_REPORT_MASK    	0x00000006
+#define RING_REPORT_64K     	0x00000002
+#define RING_REPORT_128K    	0x00000004
+#define RING_NO_REPORT      	0x00000000
+#define RING_VALID_MASK     	0x00000001
+#define RING_VALID          	0x00000001
+#define RING_INVALID        	0x00000000
+
+#define GFX_OP_SCISSOR         ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define SC_UPDATE_SCISSOR       (0x1<<1)
+#define SC_ENABLE_MASK          (0x1<<0)
+#define SC_ENABLE               (0x1<<0)
+
+#define GFX_OP_SCISSOR_INFO    ((0x3<<29)|(0x1d<<24)|(0x81<<16)|(0x1))
+#define SCI_YMIN_MASK      (0xffff<<16)
+#define SCI_XMIN_MASK      (0xffff<<0)
+#define SCI_YMAX_MASK      (0xffff<<16)
+#define SCI_XMAX_MASK      (0xffff<<0)
+
+#define GFX_OP_COLOR_FACTOR      ((0x3<<29)|(0x1d<<24)|(0x1<<16)|0x0)
+#define GFX_OP_STIPPLE           ((0x3<<29)|(0x1d<<24)|(0x83<<16))
+#define GFX_OP_MAP_INFO          ((0x3<<29)|(0x1d<<24)|0x2)
+#define GFX_OP_DESTBUFFER_VARS   ((0x3<<29)|(0x1d<<24)|(0x85<<16)|0x0)
+#define GFX_OP_DRAWRECT_INFO     ((0x3<<29)|(0x1d<<24)|(0x80<<16)|(0x3))
+#define GFX_OP_PRIMITIVE         ((0x3<<29)|(0x1f<<24))
+
+#define CMD_OP_Z_BUFFER_INFO     ((0x0<<29)|(0x16<<23))
+#define CMD_OP_DESTBUFFER_INFO   ((0x0<<29)|(0x15<<23))
+#define CMD_OP_FRONTBUFFER_INFO  ((0x0<<29)|(0x14<<23))
+#define CMD_OP_WAIT_FOR_EVENT    ((0x0<<29)|(0x03<<23))
+
+#define BR00_BITBLT_CLIENT   0x40000000
+#define BR00_OP_COLOR_BLT    0x10000000
+#define BR00_OP_SRC_COPY_BLT 0x10C00000
+#define BR13_SOLID_PATTERN   0x80000000
+
+#define WAIT_FOR_PLANE_A_SCANLINES (1<<1) 
+#define WAIT_FOR_PLANE_A_FLIP      (1<<2) 
+#define WAIT_FOR_VBLANK (1<<3)
+
+#endif
diff --git a/drivers/char/drm/i830_dma.c b/drivers/char/drm/i830_dma.c
new file mode 100644
index 0000000..98adccf
--- /dev/null
+++ b/drivers/char/drm/i830_dma.c
@@ -0,0 +1,1588 @@
+/* i830_dma.c -- DMA support for the I830 -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
+ *	    Jeff Hartmann <jhartmann@valinux.com>
+ *	    Keith Whitwell <keith@tungstengraphics.com>
+ *	    Abraham vd Merwe <abraham@2d3d.co.za>
+ *
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "i830_drm.h"
+#include "i830_drv.h"
+#include <linux/interrupt.h>	/* For task queue support */
+#include <linux/pagemap.h>	/* For FASTCALL on unlock_page() */
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+
+#define I830_BUF_FREE		2
+#define I830_BUF_CLIENT		1
+#define I830_BUF_HARDWARE      	0
+
+#define I830_BUF_UNMAPPED 0
+#define I830_BUF_MAPPED   1
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,2)
+#define down_write down
+#define up_write up
+#endif
+
+static drm_buf_t *i830_freelist_get(drm_device_t *dev)
+{
+   	drm_device_dma_t *dma = dev->dma;
+	int		 i;
+   	int 		 used;
+   
+	/* Linear search might not be the best solution */
+
+   	for (i = 0; i < dma->buf_count; i++) {
+	   	drm_buf_t *buf = dma->buflist[ i ];
+	   	drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+		/* In use is already a pointer */
+	   	used = cmpxchg(buf_priv->in_use, I830_BUF_FREE, 
+			       I830_BUF_CLIENT);
+	   	if(used == I830_BUF_FREE) {
+			return buf;
+		}
+	}
+   	return NULL;
+}
+
+/* This should only be called if the buffer is not sent to the hardware
+ * yet, the hardware updates in use for us once its on the ring buffer.
+ */
+
+static int i830_freelist_put(drm_device_t *dev, drm_buf_t *buf)
+{
+   	drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+   	int used;
+   
+   	/* In use is already a pointer */
+   	used = cmpxchg(buf_priv->in_use, I830_BUF_CLIENT, I830_BUF_FREE);
+   	if(used != I830_BUF_CLIENT) {
+	   	DRM_ERROR("Freeing buffer thats not in use : %d\n", buf->idx);
+	   	return -EINVAL;
+	}
+   
+   	return 0;
+}
+
+static struct file_operations i830_buffer_fops = {
+	.open	 = drm_open,
+	.flush	 = drm_flush,
+	.release = drm_release,
+	.ioctl	 = drm_ioctl,
+	.mmap	 = i830_mmap_buffers,
+	.fasync  = drm_fasync,
+};
+
+int i830_mmap_buffers(struct file *filp, struct vm_area_struct *vma)
+{
+	drm_file_t	    *priv	  = filp->private_data;
+	drm_device_t	    *dev;
+	drm_i830_private_t  *dev_priv;
+	drm_buf_t           *buf;
+	drm_i830_buf_priv_t *buf_priv;
+
+	lock_kernel();
+	dev	 = priv->head->dev;
+	dev_priv = dev->dev_private;
+	buf      = dev_priv->mmap_buffer;
+	buf_priv = buf->dev_private;
+   
+	vma->vm_flags |= (VM_IO | VM_DONTCOPY);
+	vma->vm_file = filp;
+   
+   	buf_priv->currently_mapped = I830_BUF_MAPPED;
+	unlock_kernel();
+
+	if (io_remap_pfn_range(vma, vma->vm_start,
+			     VM_OFFSET(vma) >> PAGE_SHIFT,
+			     vma->vm_end - vma->vm_start,
+			     vma->vm_page_prot)) return -EAGAIN;
+	return 0;
+}
+
+static int i830_map_buffer(drm_buf_t *buf, struct file *filp)
+{
+	drm_file_t	  *priv	  = filp->private_data;
+	drm_device_t	  *dev	  = priv->head->dev;
+	drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+      	drm_i830_private_t *dev_priv = dev->dev_private;
+   	struct file_operations *old_fops;
+	unsigned long virtual;
+	int retcode = 0;
+
+	if(buf_priv->currently_mapped == I830_BUF_MAPPED) return -EINVAL;
+
+	down_write( &current->mm->mmap_sem );
+	old_fops = filp->f_op;
+	filp->f_op = &i830_buffer_fops;
+	dev_priv->mmap_buffer = buf;
+	virtual = do_mmap(filp, 0, buf->total, PROT_READ|PROT_WRITE,
+			    MAP_SHARED, buf->bus_address);
+	dev_priv->mmap_buffer = NULL;
+	filp->f_op = old_fops;
+	if (IS_ERR((void *)virtual)) {		/* ugh */
+		/* Real error */
+		DRM_ERROR("mmap error\n");
+		retcode = virtual;
+		buf_priv->virtual = NULL;
+	} else {
+		buf_priv->virtual = (void __user *)virtual;
+	}
+	up_write( &current->mm->mmap_sem );
+
+	return retcode;
+}
+
+static int i830_unmap_buffer(drm_buf_t *buf)
+{
+	drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+	int retcode = 0;
+
+	if(buf_priv->currently_mapped != I830_BUF_MAPPED) 
+		return -EINVAL;
+
+	down_write(&current->mm->mmap_sem);
+	retcode = do_munmap(current->mm,
+			    (unsigned long)buf_priv->virtual,
+			    (size_t) buf->total);
+	up_write(&current->mm->mmap_sem);
+
+   	buf_priv->currently_mapped = I830_BUF_UNMAPPED;
+   	buf_priv->virtual = NULL;
+
+	return retcode;
+}
+
+static int i830_dma_get_buffer(drm_device_t *dev, drm_i830_dma_t *d, 
+			       struct file *filp)
+{
+	drm_buf_t	  *buf;
+	drm_i830_buf_priv_t *buf_priv;
+	int retcode = 0;
+
+	buf = i830_freelist_get(dev);
+	if (!buf) {
+		retcode = -ENOMEM;
+	   	DRM_DEBUG("retcode=%d\n", retcode);
+		return retcode;
+	}
+   
+	retcode = i830_map_buffer(buf, filp);
+	if(retcode) {
+		i830_freelist_put(dev, buf);
+	   	DRM_ERROR("mapbuf failed, retcode %d\n", retcode);
+		return retcode;
+	}
+	buf->filp = filp;
+	buf_priv = buf->dev_private;	
+	d->granted = 1;
+   	d->request_idx = buf->idx;
+   	d->request_size = buf->total;
+   	d->virtual = buf_priv->virtual;
+
+	return retcode;
+}
+
+static int i830_dma_cleanup(drm_device_t *dev)
+{
+	drm_device_dma_t *dma = dev->dma;
+
+	/* Make sure interrupts are disabled here because the uninstall ioctl
+	 * may not have been called from userspace and after dev_private
+	 * is freed, it's too late.
+	 */
+	if ( dev->irq_enabled ) drm_irq_uninstall(dev);
+
+	if (dev->dev_private) {
+		int i;
+	   	drm_i830_private_t *dev_priv = 
+	     		(drm_i830_private_t *) dev->dev_private;
+	   
+	   	if (dev_priv->ring.virtual_start) {
+		   	drm_ioremapfree((void *) dev_priv->ring.virtual_start,
+					 dev_priv->ring.Size, dev);
+		}
+	   	if (dev_priv->hw_status_page) {
+			pci_free_consistent(dev->pdev, PAGE_SIZE,
+					    dev_priv->hw_status_page,
+					    dev_priv->dma_status_page);
+		   	/* Need to rewrite hardware status page */
+		   	I830_WRITE(0x02080, 0x1ffff000);
+		}
+
+	   	drm_free(dev->dev_private, sizeof(drm_i830_private_t), 
+			 DRM_MEM_DRIVER);
+	   	dev->dev_private = NULL;
+
+		for (i = 0; i < dma->buf_count; i++) {
+			drm_buf_t *buf = dma->buflist[ i ];
+			drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+			if ( buf_priv->kernel_virtual && buf->total )
+				drm_ioremapfree(buf_priv->kernel_virtual, buf->total, dev);
+		}
+	}
+   	return 0;
+}
+
+int i830_wait_ring(drm_device_t *dev, int n, const char *caller)
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+   	drm_i830_ring_buffer_t *ring = &(dev_priv->ring);
+   	int iters = 0;
+   	unsigned long end;
+	unsigned int last_head = I830_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+
+	end = jiffies + (HZ*3);
+   	while (ring->space < n) {	
+	   	ring->head = I830_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+	   	ring->space = ring->head - (ring->tail+8);
+		if (ring->space < 0) ring->space += ring->Size;
+	   
+		if (ring->head != last_head) {
+			end = jiffies + (HZ*3);
+			last_head = ring->head;
+		}
+	  
+	   	iters++;
+		if(time_before(end, jiffies)) {
+		   	DRM_ERROR("space: %d wanted %d\n", ring->space, n);
+		   	DRM_ERROR("lockup\n");
+		   	goto out_wait_ring;
+		}
+		udelay(1);
+		dev_priv->sarea_priv->perf_boxes |= I830_BOX_WAIT;
+	}
+
+out_wait_ring:   
+   	return iters;
+}
+
+static void i830_kernel_lost_context(drm_device_t *dev)
+{
+      	drm_i830_private_t *dev_priv = dev->dev_private;
+   	drm_i830_ring_buffer_t *ring = &(dev_priv->ring);
+      
+   	ring->head = I830_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+     	ring->tail = I830_READ(LP_RING + RING_TAIL) & TAIL_ADDR;
+     	ring->space = ring->head - (ring->tail+8);
+     	if (ring->space < 0) ring->space += ring->Size;
+
+	if (ring->head == ring->tail)
+		dev_priv->sarea_priv->perf_boxes |= I830_BOX_RING_EMPTY;
+}
+
+static int i830_freelist_init(drm_device_t *dev, drm_i830_private_t *dev_priv)
+{
+      	drm_device_dma_t *dma = dev->dma;
+   	int my_idx = 36;
+   	u32 *hw_status = (u32 *)(dev_priv->hw_status_page + my_idx);
+   	int i;
+
+   	if(dma->buf_count > 1019) {
+	   	/* Not enough space in the status page for the freelist */
+	   	return -EINVAL;
+	}
+
+   	for (i = 0; i < dma->buf_count; i++) {
+	   	drm_buf_t *buf = dma->buflist[ i ];
+	   	drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+
+	   	buf_priv->in_use = hw_status++;
+	   	buf_priv->my_use_idx = my_idx;
+	   	my_idx += 4;
+
+	   	*buf_priv->in_use = I830_BUF_FREE;
+
+		buf_priv->kernel_virtual = drm_ioremap(buf->bus_address, 
+							buf->total, dev);
+	}
+	return 0;
+}
+
+static int i830_dma_initialize(drm_device_t *dev, 
+			       drm_i830_private_t *dev_priv,
+			       drm_i830_init_t *init)
+{
+	struct list_head *list;
+
+   	memset(dev_priv, 0, sizeof(drm_i830_private_t));
+
+	list_for_each(list, &dev->maplist->head) {
+		drm_map_list_t *r_list = list_entry(list, drm_map_list_t, head);
+		if( r_list->map &&
+		    r_list->map->type == _DRM_SHM &&
+		    r_list->map->flags & _DRM_CONTAINS_LOCK ) {
+			dev_priv->sarea_map = r_list->map;
+ 			break;
+ 		}
+ 	}
+
+	if(!dev_priv->sarea_map) {
+		dev->dev_private = (void *)dev_priv;
+		i830_dma_cleanup(dev);
+		DRM_ERROR("can not find sarea!\n");
+		return -EINVAL;
+	}
+	dev_priv->mmio_map = drm_core_findmap(dev, init->mmio_offset);
+	if(!dev_priv->mmio_map) {
+		dev->dev_private = (void *)dev_priv;
+		i830_dma_cleanup(dev);
+		DRM_ERROR("can not find mmio map!\n");
+		return -EINVAL;
+	}
+	dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+	if(!dev->agp_buffer_map) {
+		dev->dev_private = (void *)dev_priv;
+		i830_dma_cleanup(dev);
+		DRM_ERROR("can not find dma buffer map!\n");
+		return -EINVAL;
+	}
+
+	dev_priv->sarea_priv = (drm_i830_sarea_t *)
+		((u8 *)dev_priv->sarea_map->handle +
+		 init->sarea_priv_offset);
+
+   	dev_priv->ring.Start = init->ring_start;
+   	dev_priv->ring.End = init->ring_end;
+   	dev_priv->ring.Size = init->ring_size;
+
+   	dev_priv->ring.virtual_start = drm_ioremap(dev->agp->base + 
+						    init->ring_start, 
+						    init->ring_size, dev);
+
+   	if (dev_priv->ring.virtual_start == NULL) {
+		dev->dev_private = (void *) dev_priv;
+	   	i830_dma_cleanup(dev);
+	   	DRM_ERROR("can not ioremap virtual address for"
+			  " ring buffer\n");
+	   	return -ENOMEM;
+	}
+
+   	dev_priv->ring.tail_mask = dev_priv->ring.Size - 1;
+   
+	dev_priv->w = init->w;
+	dev_priv->h = init->h;
+	dev_priv->pitch = init->pitch;
+	dev_priv->back_offset = init->back_offset;
+	dev_priv->depth_offset = init->depth_offset;
+	dev_priv->front_offset = init->front_offset;
+
+	dev_priv->front_di1 = init->front_offset | init->pitch_bits;
+	dev_priv->back_di1 = init->back_offset | init->pitch_bits;
+	dev_priv->zi1 = init->depth_offset | init->pitch_bits;
+
+	DRM_DEBUG("front_di1 %x\n",    dev_priv->front_di1);
+	DRM_DEBUG("back_offset %x\n", dev_priv->back_offset);
+	DRM_DEBUG("back_di1 %x\n",    dev_priv->back_di1);
+	DRM_DEBUG("pitch_bits %x\n",    init->pitch_bits);
+
+	dev_priv->cpp = init->cpp;
+	/* We are using separate values as placeholders for mechanisms for
+	 * private backbuffer/depthbuffer usage.
+	 */
+
+	dev_priv->back_pitch = init->back_pitch;
+	dev_priv->depth_pitch = init->depth_pitch;
+	dev_priv->do_boxes = 0;
+	dev_priv->use_mi_batchbuffer_start = 0;
+
+   	/* Program Hardware Status Page */
+   	dev_priv->hw_status_page =
+		pci_alloc_consistent(dev->pdev, PAGE_SIZE,
+						&dev_priv->dma_status_page);
+   	if (!dev_priv->hw_status_page) {
+		dev->dev_private = (void *)dev_priv;
+		i830_dma_cleanup(dev);
+		DRM_ERROR("Can not allocate hardware status page\n");
+		return -ENOMEM;
+	}
+   	memset(dev_priv->hw_status_page, 0, PAGE_SIZE);
+	DRM_DEBUG("hw status page @ %p\n", dev_priv->hw_status_page);
+   
+   	I830_WRITE(0x02080, dev_priv->dma_status_page);
+	DRM_DEBUG("Enabled hardware status page\n");
+   
+   	/* Now we need to init our freelist */
+   	if(i830_freelist_init(dev, dev_priv) != 0) {
+		dev->dev_private = (void *)dev_priv;
+	   	i830_dma_cleanup(dev);
+	   	DRM_ERROR("Not enough space in the status page for"
+			  " the freelist\n");
+	   	return -ENOMEM;
+	}
+	dev->dev_private = (void *)dev_priv;
+
+   	return 0;
+}
+
+static int i830_dma_init(struct inode *inode, struct file *filp,
+		  unsigned int cmd, unsigned long arg)
+{
+   	drm_file_t *priv = filp->private_data;
+   	drm_device_t *dev = priv->head->dev;
+   	drm_i830_private_t *dev_priv;
+   	drm_i830_init_t init;
+   	int retcode = 0;
+	
+  	if (copy_from_user(&init, (void * __user) arg, sizeof(init)))
+		return -EFAULT;
+	
+   	switch(init.func) {
+	 	case I830_INIT_DMA:
+			dev_priv = drm_alloc(sizeof(drm_i830_private_t), 
+					      DRM_MEM_DRIVER);
+	   		if(dev_priv == NULL) return -ENOMEM;
+	   		retcode = i830_dma_initialize(dev, dev_priv, &init);
+	   	break;
+	 	case I830_CLEANUP_DMA:
+	   		retcode = i830_dma_cleanup(dev);
+	   	break;
+	 	default:
+	   		retcode = -EINVAL;
+	   	break;
+	}
+   
+   	return retcode;
+}
+
+#define GFX_OP_STIPPLE           ((0x3<<29)|(0x1d<<24)|(0x83<<16))
+#define ST1_ENABLE               (1<<16)
+#define ST1_MASK                 (0xffff)
+
+/* Most efficient way to verify state for the i830 is as it is
+ * emitted.  Non-conformant state is silently dropped.
+ */
+static void i830EmitContextVerified( drm_device_t *dev,
+				     unsigned int *code )
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	int i, j = 0;
+	unsigned int tmp;
+	RING_LOCALS;
+
+	BEGIN_LP_RING( I830_CTX_SETUP_SIZE + 4 );
+
+	for ( i = 0 ; i < I830_CTXREG_BLENDCOLR0 ; i++ ) {
+		tmp = code[i];
+		if ((tmp & (7<<29)) == CMD_3D &&
+		    (tmp & (0x1f<<24)) < (0x1d<<24)) {
+			OUT_RING( tmp ); 
+			j++;
+		} else {
+			DRM_ERROR("Skipping %d\n", i);
+		}
+	}
+
+	OUT_RING( STATE3D_CONST_BLEND_COLOR_CMD ); 
+	OUT_RING( code[I830_CTXREG_BLENDCOLR] ); 
+	j += 2;
+
+	for ( i = I830_CTXREG_VF ; i < I830_CTXREG_MCSB0 ; i++ ) {
+		tmp = code[i];
+		if ((tmp & (7<<29)) == CMD_3D &&
+		    (tmp & (0x1f<<24)) < (0x1d<<24)) {
+			OUT_RING( tmp ); 
+			j++;
+		} else {
+			DRM_ERROR("Skipping %d\n", i);
+		}
+	}
+
+	OUT_RING( STATE3D_MAP_COORD_SETBIND_CMD ); 
+	OUT_RING( code[I830_CTXREG_MCSB1] ); 
+	j += 2;
+
+	if (j & 1) 
+		OUT_RING( 0 ); 
+
+	ADVANCE_LP_RING();
+}
+
+static void i830EmitTexVerified( drm_device_t *dev, unsigned int *code ) 
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	int i, j = 0;
+	unsigned int tmp;
+	RING_LOCALS;
+
+	if (code[I830_TEXREG_MI0] == GFX_OP_MAP_INFO ||
+	    (code[I830_TEXREG_MI0] & ~(0xf*LOAD_TEXTURE_MAP0)) == 
+	    (STATE3D_LOAD_STATE_IMMEDIATE_2|4)) {
+
+		BEGIN_LP_RING( I830_TEX_SETUP_SIZE );
+
+		OUT_RING( code[I830_TEXREG_MI0] ); /* TM0LI */
+		OUT_RING( code[I830_TEXREG_MI1] ); /* TM0S0 */
+		OUT_RING( code[I830_TEXREG_MI2] ); /* TM0S1 */
+		OUT_RING( code[I830_TEXREG_MI3] ); /* TM0S2 */
+		OUT_RING( code[I830_TEXREG_MI4] ); /* TM0S3 */
+		OUT_RING( code[I830_TEXREG_MI5] ); /* TM0S4 */
+		
+		for ( i = 6 ; i < I830_TEX_SETUP_SIZE ; i++ ) {
+			tmp = code[i];
+			OUT_RING( tmp ); 
+			j++;
+		} 
+
+		if (j & 1) 
+			OUT_RING( 0 ); 
+
+		ADVANCE_LP_RING();
+	}
+	else
+		printk("rejected packet %x\n", code[0]);
+}
+
+static void i830EmitTexBlendVerified( drm_device_t *dev, 
+				      unsigned int *code,
+				      unsigned int num)
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	int i, j = 0;
+	unsigned int tmp;
+	RING_LOCALS;
+
+	if (!num)
+		return;
+
+	BEGIN_LP_RING( num + 1 );
+
+	for ( i = 0 ; i < num ; i++ ) {
+		tmp = code[i];
+		OUT_RING( tmp );
+		j++;
+	}
+
+	if (j & 1) 
+		OUT_RING( 0 ); 
+
+	ADVANCE_LP_RING();
+}
+
+static void i830EmitTexPalette( drm_device_t *dev,
+			        unsigned int *palette,
+			        int number,
+			        int is_shared )
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	int i;
+	RING_LOCALS;
+
+	return;
+
+	BEGIN_LP_RING( 258 );
+
+	if(is_shared == 1) {
+		OUT_RING(CMD_OP_MAP_PALETTE_LOAD |
+			 MAP_PALETTE_NUM(0) |
+			 MAP_PALETTE_BOTH);
+	} else {
+		OUT_RING(CMD_OP_MAP_PALETTE_LOAD | MAP_PALETTE_NUM(number));
+	}
+	for(i = 0; i < 256; i++) {
+		OUT_RING(palette[i]);
+	}
+	OUT_RING(0);
+	/* KW:  WHERE IS THE ADVANCE_LP_RING?  This is effectively a noop! 
+	 */
+}
+
+/* Need to do some additional checking when setting the dest buffer.
+ */
+static void i830EmitDestVerified( drm_device_t *dev, 
+				  unsigned int *code ) 
+{	
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	unsigned int tmp;
+	RING_LOCALS;
+
+	BEGIN_LP_RING( I830_DEST_SETUP_SIZE + 10 );
+
+
+	tmp = code[I830_DESTREG_CBUFADDR];
+	if (tmp == dev_priv->front_di1 || tmp == dev_priv->back_di1) {
+		if (((int)outring) & 8) {
+			OUT_RING(0);
+			OUT_RING(0);
+		}
+
+		OUT_RING( CMD_OP_DESTBUFFER_INFO );
+		OUT_RING( BUF_3D_ID_COLOR_BACK | 
+			  BUF_3D_PITCH(dev_priv->back_pitch * dev_priv->cpp) |
+			  BUF_3D_USE_FENCE);
+		OUT_RING( tmp );
+		OUT_RING( 0 );
+
+		OUT_RING( CMD_OP_DESTBUFFER_INFO );
+		OUT_RING( BUF_3D_ID_DEPTH | BUF_3D_USE_FENCE | 
+			  BUF_3D_PITCH(dev_priv->depth_pitch * dev_priv->cpp));
+		OUT_RING( dev_priv->zi1 );
+		OUT_RING( 0 );
+	} else {
+		DRM_ERROR("bad di1 %x (allow %x or %x)\n",
+			  tmp, dev_priv->front_di1, dev_priv->back_di1);
+	}
+
+	/* invarient:
+	 */
+
+
+	OUT_RING( GFX_OP_DESTBUFFER_VARS );
+	OUT_RING( code[I830_DESTREG_DV1] );
+
+	OUT_RING( GFX_OP_DRAWRECT_INFO );
+	OUT_RING( code[I830_DESTREG_DR1] );
+	OUT_RING( code[I830_DESTREG_DR2] );
+	OUT_RING( code[I830_DESTREG_DR3] );
+	OUT_RING( code[I830_DESTREG_DR4] );
+
+	/* Need to verify this */
+	tmp = code[I830_DESTREG_SENABLE];
+	if((tmp & ~0x3) == GFX_OP_SCISSOR_ENABLE) {
+		OUT_RING( tmp );
+	} else {
+		DRM_ERROR("bad scissor enable\n");
+		OUT_RING( 0 );
+	}
+
+	OUT_RING( GFX_OP_SCISSOR_RECT );
+	OUT_RING( code[I830_DESTREG_SR1] );
+	OUT_RING( code[I830_DESTREG_SR2] );
+	OUT_RING( 0 );
+
+	ADVANCE_LP_RING();
+}
+
+static void i830EmitStippleVerified( drm_device_t *dev, 
+				     unsigned int *code ) 
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	RING_LOCALS;
+
+	BEGIN_LP_RING( 2 );
+	OUT_RING( GFX_OP_STIPPLE );
+	OUT_RING( code[1] );
+	ADVANCE_LP_RING();	
+}
+
+
+static void i830EmitState( drm_device_t *dev )
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+      	drm_i830_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned int dirty = sarea_priv->dirty;
+
+	DRM_DEBUG("%s %x\n", __FUNCTION__, dirty);
+
+	if (dirty & I830_UPLOAD_BUFFERS) {
+		i830EmitDestVerified( dev, sarea_priv->BufferState );
+		sarea_priv->dirty &= ~I830_UPLOAD_BUFFERS;
+	}
+
+	if (dirty & I830_UPLOAD_CTX) {
+		i830EmitContextVerified( dev, sarea_priv->ContextState );
+		sarea_priv->dirty &= ~I830_UPLOAD_CTX;
+	}
+
+	if (dirty & I830_UPLOAD_TEX0) {
+		i830EmitTexVerified( dev, sarea_priv->TexState[0] );
+		sarea_priv->dirty &= ~I830_UPLOAD_TEX0;
+	}
+
+	if (dirty & I830_UPLOAD_TEX1) {
+		i830EmitTexVerified( dev, sarea_priv->TexState[1] );
+		sarea_priv->dirty &= ~I830_UPLOAD_TEX1;
+	}
+
+	if (dirty & I830_UPLOAD_TEXBLEND0) {
+		i830EmitTexBlendVerified( dev, sarea_priv->TexBlendState[0],
+				sarea_priv->TexBlendStateWordsUsed[0]);
+		sarea_priv->dirty &= ~I830_UPLOAD_TEXBLEND0;
+	}
+
+	if (dirty & I830_UPLOAD_TEXBLEND1) {
+		i830EmitTexBlendVerified( dev, sarea_priv->TexBlendState[1],
+				sarea_priv->TexBlendStateWordsUsed[1]);
+		sarea_priv->dirty &= ~I830_UPLOAD_TEXBLEND1;
+	}
+
+	if (dirty & I830_UPLOAD_TEX_PALETTE_SHARED) {
+		i830EmitTexPalette(dev, sarea_priv->Palette[0], 0, 1);
+	} else {
+		if (dirty & I830_UPLOAD_TEX_PALETTE_N(0)) {
+			i830EmitTexPalette(dev, sarea_priv->Palette[0], 0, 0);
+			sarea_priv->dirty &= ~I830_UPLOAD_TEX_PALETTE_N(0);
+		}
+		if (dirty & I830_UPLOAD_TEX_PALETTE_N(1)) {
+			i830EmitTexPalette(dev, sarea_priv->Palette[1], 1, 0);
+			sarea_priv->dirty &= ~I830_UPLOAD_TEX_PALETTE_N(1);
+		}
+
+		/* 1.3:
+		 */
+#if 0
+		if (dirty & I830_UPLOAD_TEX_PALETTE_N(2)) {
+			i830EmitTexPalette(dev, sarea_priv->Palette2[0], 0, 0);
+			sarea_priv->dirty &= ~I830_UPLOAD_TEX_PALETTE_N(2);
+		}
+		if (dirty & I830_UPLOAD_TEX_PALETTE_N(3)) {
+			i830EmitTexPalette(dev, sarea_priv->Palette2[1], 1, 0);
+			sarea_priv->dirty &= ~I830_UPLOAD_TEX_PALETTE_N(2);
+		}
+#endif
+	}
+
+	/* 1.3:
+	 */
+	if (dirty & I830_UPLOAD_STIPPLE) {
+		i830EmitStippleVerified( dev, 
+					 sarea_priv->StippleState);
+		sarea_priv->dirty &= ~I830_UPLOAD_STIPPLE;
+	}
+
+	if (dirty & I830_UPLOAD_TEX2) {
+		i830EmitTexVerified( dev, sarea_priv->TexState2 );
+		sarea_priv->dirty &= ~I830_UPLOAD_TEX2;
+	}
+
+	if (dirty & I830_UPLOAD_TEX3) {
+		i830EmitTexVerified( dev, sarea_priv->TexState3 );
+		sarea_priv->dirty &= ~I830_UPLOAD_TEX3;
+	}
+
+
+	if (dirty & I830_UPLOAD_TEXBLEND2) {
+		i830EmitTexBlendVerified( 
+			dev, 
+			sarea_priv->TexBlendState2,
+			sarea_priv->TexBlendStateWordsUsed2);
+
+		sarea_priv->dirty &= ~I830_UPLOAD_TEXBLEND2;
+	}
+
+	if (dirty & I830_UPLOAD_TEXBLEND3) {
+		i830EmitTexBlendVerified( 
+			dev, 
+			sarea_priv->TexBlendState3,
+			sarea_priv->TexBlendStateWordsUsed3);
+		sarea_priv->dirty &= ~I830_UPLOAD_TEXBLEND3;
+	}
+}
+
+/* ================================================================
+ * Performance monitoring functions
+ */
+
+static void i830_fill_box( drm_device_t *dev,
+			   int x, int y, int w, int h,
+			   int r, int g, int b )
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	u32 color;
+	unsigned int BR13, CMD;
+	RING_LOCALS;
+
+	BR13 = (0xF0 << 16) | (dev_priv->pitch * dev_priv->cpp) | (1<<24);
+	CMD = XY_COLOR_BLT_CMD;
+	x += dev_priv->sarea_priv->boxes[0].x1;
+	y += dev_priv->sarea_priv->boxes[0].y1;
+
+	if (dev_priv->cpp == 4) {
+		BR13 |= (1<<25);
+		CMD |= (XY_COLOR_BLT_WRITE_ALPHA | XY_COLOR_BLT_WRITE_RGB);
+		color = (((0xff) << 24) | (r << 16) | (g <<  8) | b);	
+	} else {
+		color = (((r & 0xf8) << 8) |
+			 ((g & 0xfc) << 3) |
+			 ((b & 0xf8) >> 3));
+	}
+
+	BEGIN_LP_RING( 6 );	    
+	OUT_RING( CMD );
+	OUT_RING( BR13 );
+	OUT_RING( (y << 16) | x );
+	OUT_RING( ((y+h) << 16) | (x+w) );
+
+ 	if ( dev_priv->current_page == 1 ) { 
+		OUT_RING( dev_priv->front_offset );
+ 	} else {	 
+		OUT_RING( dev_priv->back_offset );
+ 	} 
+
+	OUT_RING( color );
+	ADVANCE_LP_RING();
+}
+
+static void i830_cp_performance_boxes( drm_device_t *dev )
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+
+	/* Purple box for page flipping
+	 */
+	if ( dev_priv->sarea_priv->perf_boxes & I830_BOX_FLIP ) 
+		i830_fill_box( dev, 4, 4, 8, 8, 255, 0, 255 );
+
+	/* Red box if we have to wait for idle at any point
+	 */
+	if ( dev_priv->sarea_priv->perf_boxes & I830_BOX_WAIT ) 
+		i830_fill_box( dev, 16, 4, 8, 8, 255, 0, 0 );
+
+	/* Blue box: lost context?
+	 */
+	if ( dev_priv->sarea_priv->perf_boxes & I830_BOX_LOST_CONTEXT ) 
+		i830_fill_box( dev, 28, 4, 8, 8, 0, 0, 255 );
+
+	/* Yellow box for texture swaps
+	 */
+	if ( dev_priv->sarea_priv->perf_boxes & I830_BOX_TEXTURE_LOAD ) 
+		i830_fill_box( dev, 40, 4, 8, 8, 255, 255, 0 );
+
+	/* Green box if hardware never idles (as far as we can tell)
+	 */
+	if ( !(dev_priv->sarea_priv->perf_boxes & I830_BOX_RING_EMPTY) ) 
+		i830_fill_box( dev, 64, 4, 8, 8, 0, 255, 0 );
+
+
+	/* Draw bars indicating number of buffers allocated 
+	 * (not a great measure, easily confused)
+	 */
+	if (dev_priv->dma_used) {
+		int bar = dev_priv->dma_used / 10240;
+		if (bar > 100) bar = 100;
+		if (bar < 1) bar = 1;
+		i830_fill_box( dev, 4, 16, bar, 4, 196, 128, 128 );
+		dev_priv->dma_used = 0;
+	}
+
+	dev_priv->sarea_priv->perf_boxes = 0;
+}
+
+static void i830_dma_dispatch_clear( drm_device_t *dev, int flags, 
+				    unsigned int clear_color,
+				    unsigned int clear_zval,
+				    unsigned int clear_depthmask)
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+      	drm_i830_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int nbox = sarea_priv->nbox;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int pitch = dev_priv->pitch;
+	int cpp = dev_priv->cpp;
+	int i;
+	unsigned int BR13, CMD, D_CMD;
+	RING_LOCALS;
+
+
+	if ( dev_priv->current_page == 1 ) {
+		unsigned int tmp = flags;
+
+		flags &= ~(I830_FRONT | I830_BACK);
+		if ( tmp & I830_FRONT ) flags |= I830_BACK;
+		if ( tmp & I830_BACK )  flags |= I830_FRONT;
+	}
+
+  	i830_kernel_lost_context(dev);
+
+	switch(cpp) {
+	case 2: 
+		BR13 = (0xF0 << 16) | (pitch * cpp) | (1<<24);
+		D_CMD = CMD = XY_COLOR_BLT_CMD;
+		break;
+	case 4:
+		BR13 = (0xF0 << 16) | (pitch * cpp) | (1<<24) | (1<<25);
+		CMD = (XY_COLOR_BLT_CMD | XY_COLOR_BLT_WRITE_ALPHA | 
+		       XY_COLOR_BLT_WRITE_RGB);
+		D_CMD = XY_COLOR_BLT_CMD;
+		if(clear_depthmask & 0x00ffffff)
+			D_CMD |= XY_COLOR_BLT_WRITE_RGB;
+		if(clear_depthmask & 0xff000000)
+			D_CMD |= XY_COLOR_BLT_WRITE_ALPHA;
+		break;
+	default:
+		BR13 = (0xF0 << 16) | (pitch * cpp) | (1<<24);
+		D_CMD = CMD = XY_COLOR_BLT_CMD;
+		break;
+	}
+
+      	if (nbox > I830_NR_SAREA_CLIPRECTS)
+     		nbox = I830_NR_SAREA_CLIPRECTS;
+
+	for (i = 0 ; i < nbox ; i++, pbox++) {
+		if (pbox->x1 > pbox->x2 ||
+		    pbox->y1 > pbox->y2 ||
+		    pbox->x2 > dev_priv->w ||
+		    pbox->y2 > dev_priv->h)
+			continue;
+
+	   	if ( flags & I830_FRONT ) {	    
+		   	DRM_DEBUG("clear front\n");
+			BEGIN_LP_RING( 6 );	    
+			OUT_RING( CMD );
+			OUT_RING( BR13 );
+			OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+			OUT_RING( (pbox->y2 << 16) | pbox->x2 );
+			OUT_RING( dev_priv->front_offset );
+			OUT_RING( clear_color );
+			ADVANCE_LP_RING();
+		}
+
+		if ( flags & I830_BACK ) {
+			DRM_DEBUG("clear back\n");
+			BEGIN_LP_RING( 6 );	    
+			OUT_RING( CMD );
+			OUT_RING( BR13 );
+			OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+			OUT_RING( (pbox->y2 << 16) | pbox->x2 );
+			OUT_RING( dev_priv->back_offset );
+			OUT_RING( clear_color );
+			ADVANCE_LP_RING();
+		}
+
+		if ( flags & I830_DEPTH ) {
+			DRM_DEBUG("clear depth\n");
+			BEGIN_LP_RING( 6 );
+			OUT_RING( D_CMD );
+			OUT_RING( BR13 );
+			OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+			OUT_RING( (pbox->y2 << 16) | pbox->x2 );
+			OUT_RING( dev_priv->depth_offset );
+			OUT_RING( clear_zval );
+			ADVANCE_LP_RING();
+		}
+	}
+}
+
+static void i830_dma_dispatch_swap( drm_device_t *dev )
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+      	drm_i830_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int nbox = sarea_priv->nbox;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int pitch = dev_priv->pitch;
+	int cpp = dev_priv->cpp;
+	int i;
+	unsigned int CMD, BR13;
+	RING_LOCALS;
+
+	DRM_DEBUG("swapbuffers\n");
+
+  	i830_kernel_lost_context(dev);
+
+	if (dev_priv->do_boxes)
+		i830_cp_performance_boxes( dev );
+
+	switch(cpp) {
+	case 2: 
+		BR13 = (pitch * cpp) | (0xCC << 16) | (1<<24);
+		CMD = XY_SRC_COPY_BLT_CMD;
+		break;
+	case 4:
+		BR13 = (pitch * cpp) | (0xCC << 16) | (1<<24) | (1<<25);
+		CMD = (XY_SRC_COPY_BLT_CMD | XY_SRC_COPY_BLT_WRITE_ALPHA |
+		       XY_SRC_COPY_BLT_WRITE_RGB);
+		break;
+	default:
+		BR13 = (pitch * cpp) | (0xCC << 16) | (1<<24);
+		CMD = XY_SRC_COPY_BLT_CMD;
+		break;
+	}
+
+
+      	if (nbox > I830_NR_SAREA_CLIPRECTS)
+     		nbox = I830_NR_SAREA_CLIPRECTS;
+
+	for (i = 0 ; i < nbox; i++, pbox++) 
+	{
+		if (pbox->x1 > pbox->x2 ||
+		    pbox->y1 > pbox->y2 ||
+		    pbox->x2 > dev_priv->w ||
+		    pbox->y2 > dev_priv->h)
+			continue;
+ 
+		DRM_DEBUG("dispatch swap %d,%d-%d,%d!\n",
+			  pbox->x1, pbox->y1,
+			  pbox->x2, pbox->y2);
+
+		BEGIN_LP_RING( 8 );
+		OUT_RING( CMD );
+		OUT_RING( BR13 );
+		OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+		OUT_RING( (pbox->y2 << 16) | pbox->x2 );
+
+		if (dev_priv->current_page == 0) 
+			OUT_RING( dev_priv->front_offset );
+		else
+			OUT_RING( dev_priv->back_offset );			
+
+		OUT_RING( (pbox->y1 << 16) | pbox->x1 );
+		OUT_RING( BR13 & 0xffff );
+
+		if (dev_priv->current_page == 0) 
+			OUT_RING( dev_priv->back_offset );			
+		else
+			OUT_RING( dev_priv->front_offset );
+
+		ADVANCE_LP_RING();
+	}
+}
+
+static void i830_dma_dispatch_flip( drm_device_t *dev )
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	RING_LOCALS;
+
+	DRM_DEBUG( "%s: page=%d pfCurrentPage=%d\n", 
+		   __FUNCTION__, 
+		   dev_priv->current_page,
+		   dev_priv->sarea_priv->pf_current_page);
+
+  	i830_kernel_lost_context(dev);
+
+	if (dev_priv->do_boxes) {
+		dev_priv->sarea_priv->perf_boxes |= I830_BOX_FLIP;
+		i830_cp_performance_boxes( dev );
+	}
+
+
+	BEGIN_LP_RING( 2 );
+    	OUT_RING( INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE ); 
+	OUT_RING( 0 );
+	ADVANCE_LP_RING();
+
+	BEGIN_LP_RING( 6 );
+	OUT_RING( CMD_OP_DISPLAYBUFFER_INFO | ASYNC_FLIP );	
+	OUT_RING( 0 );
+	if ( dev_priv->current_page == 0 ) {
+		OUT_RING( dev_priv->back_offset );
+		dev_priv->current_page = 1;
+	} else {
+		OUT_RING( dev_priv->front_offset );
+		dev_priv->current_page = 0;
+	}
+	OUT_RING(0);
+	ADVANCE_LP_RING();
+
+
+	BEGIN_LP_RING( 2 );
+	OUT_RING( MI_WAIT_FOR_EVENT |
+		  MI_WAIT_FOR_PLANE_A_FLIP );
+	OUT_RING( 0 );
+	ADVANCE_LP_RING();
+	
+
+	dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+}
+
+static void i830_dma_dispatch_vertex(drm_device_t *dev, 
+				     drm_buf_t *buf,
+				     int discard,
+				     int used)
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+   	drm_i830_sarea_t *sarea_priv = dev_priv->sarea_priv;
+   	drm_clip_rect_t *box = sarea_priv->boxes;
+   	int nbox = sarea_priv->nbox;
+	unsigned long address = (unsigned long)buf->bus_address;
+	unsigned long start = address - dev->agp->base;     
+	int i = 0, u;
+   	RING_LOCALS;
+
+   	i830_kernel_lost_context(dev);
+
+   	if (nbox > I830_NR_SAREA_CLIPRECTS) 
+		nbox = I830_NR_SAREA_CLIPRECTS;
+
+	if (discard) {
+		u = cmpxchg(buf_priv->in_use, I830_BUF_CLIENT, 
+			    I830_BUF_HARDWARE);
+		if(u != I830_BUF_CLIENT) {
+			DRM_DEBUG("xxxx 2\n");
+		}
+	}
+
+	if (used > 4*1023) 
+		used = 0;
+
+	if (sarea_priv->dirty)
+	   i830EmitState( dev );
+
+  	DRM_DEBUG("dispatch vertex addr 0x%lx, used 0x%x nbox %d\n", 
+		  address, used, nbox);
+
+   	dev_priv->counter++;
+   	DRM_DEBUG(  "dispatch counter : %ld\n", dev_priv->counter);
+   	DRM_DEBUG(  "i830_dma_dispatch\n");
+   	DRM_DEBUG(  "start : %lx\n", start);
+	DRM_DEBUG(  "used : %d\n", used);
+   	DRM_DEBUG(  "start + used - 4 : %ld\n", start + used - 4);
+
+	if (buf_priv->currently_mapped == I830_BUF_MAPPED) {
+		u32 *vp = buf_priv->kernel_virtual;
+
+		vp[0] = (GFX_OP_PRIMITIVE |
+			sarea_priv->vertex_prim |
+			((used/4)-2));
+
+		if (dev_priv->use_mi_batchbuffer_start) {
+			vp[used/4] = MI_BATCH_BUFFER_END;
+			used += 4; 
+		}
+		
+		if (used & 4) {
+			vp[used/4] = 0;
+			used += 4;
+		}
+
+		i830_unmap_buffer(buf);
+	}
+		   
+	if (used) {
+		do {
+			if (i < nbox) {
+				BEGIN_LP_RING(6);
+				OUT_RING( GFX_OP_DRAWRECT_INFO );
+				OUT_RING( sarea_priv->BufferState[I830_DESTREG_DR1] );
+				OUT_RING( box[i].x1 | (box[i].y1<<16) );
+				OUT_RING( box[i].x2 | (box[i].y2<<16) );
+				OUT_RING( sarea_priv->BufferState[I830_DESTREG_DR4] );
+				OUT_RING( 0 );
+				ADVANCE_LP_RING();
+			}
+
+			if (dev_priv->use_mi_batchbuffer_start) {
+				BEGIN_LP_RING(2);
+				OUT_RING( MI_BATCH_BUFFER_START | (2<<6) );
+				OUT_RING( start | MI_BATCH_NON_SECURE );
+				ADVANCE_LP_RING();
+			} 
+			else {
+				BEGIN_LP_RING(4);
+				OUT_RING( MI_BATCH_BUFFER );
+				OUT_RING( start | MI_BATCH_NON_SECURE );
+				OUT_RING( start + used - 4 );
+				OUT_RING( 0 );
+				ADVANCE_LP_RING();
+			}
+
+		} while (++i < nbox);
+	}
+
+	if (discard) {
+		dev_priv->counter++;
+
+		(void) cmpxchg(buf_priv->in_use, I830_BUF_CLIENT,
+			       I830_BUF_HARDWARE);
+
+		BEGIN_LP_RING(8);
+		OUT_RING( CMD_STORE_DWORD_IDX );
+		OUT_RING( 20 );
+		OUT_RING( dev_priv->counter );
+		OUT_RING( CMD_STORE_DWORD_IDX );
+		OUT_RING( buf_priv->my_use_idx );
+		OUT_RING( I830_BUF_FREE );
+		OUT_RING( CMD_REPORT_HEAD );
+		OUT_RING( 0 );
+		ADVANCE_LP_RING();
+	}
+}
+
+
+static void i830_dma_quiescent(drm_device_t *dev)
+{
+      	drm_i830_private_t *dev_priv = dev->dev_private;
+   	RING_LOCALS;
+
+  	i830_kernel_lost_context(dev);
+
+   	BEGIN_LP_RING(4);
+   	OUT_RING( INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE );
+   	OUT_RING( CMD_REPORT_HEAD );
+      	OUT_RING( 0 );
+      	OUT_RING( 0 );
+   	ADVANCE_LP_RING();
+
+	i830_wait_ring( dev, dev_priv->ring.Size - 8, __FUNCTION__ );
+}
+
+static int i830_flush_queue(drm_device_t *dev)
+{
+   	drm_i830_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+   	int i, ret = 0;
+   	RING_LOCALS;
+	
+   	i830_kernel_lost_context(dev);
+
+   	BEGIN_LP_RING(2);
+      	OUT_RING( CMD_REPORT_HEAD );
+      	OUT_RING( 0 );
+      	ADVANCE_LP_RING();
+
+	i830_wait_ring( dev, dev_priv->ring.Size - 8, __FUNCTION__ );
+
+   	for (i = 0; i < dma->buf_count; i++) {
+	   	drm_buf_t *buf = dma->buflist[ i ];
+	   	drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+	   
+		int used = cmpxchg(buf_priv->in_use, I830_BUF_HARDWARE, 
+				   I830_BUF_FREE);
+
+		if (used == I830_BUF_HARDWARE)
+			DRM_DEBUG("reclaimed from HARDWARE\n");
+		if (used == I830_BUF_CLIENT)
+			DRM_DEBUG("still on client\n");
+	}
+
+   	return ret;
+}
+
+/* Must be called with the lock held */
+void i830_reclaim_buffers(drm_device_t *dev, struct file *filp)
+{
+	drm_device_dma_t *dma = dev->dma;
+	int		 i;
+
+	if (!dma) return;
+      	if (!dev->dev_private) return;
+	if (!dma->buflist) return;
+
+        i830_flush_queue(dev);
+
+	for (i = 0; i < dma->buf_count; i++) {
+	   	drm_buf_t *buf = dma->buflist[ i ];
+	   	drm_i830_buf_priv_t *buf_priv = buf->dev_private;
+	   
+		if (buf->filp == filp && buf_priv) {
+			int used = cmpxchg(buf_priv->in_use, I830_BUF_CLIENT, 
+					   I830_BUF_FREE);
+
+			if (used == I830_BUF_CLIENT)
+				DRM_DEBUG("reclaimed from client\n");
+		   	if(buf_priv->currently_mapped == I830_BUF_MAPPED)
+		     		buf_priv->currently_mapped = I830_BUF_UNMAPPED;
+		}
+	}
+}
+
+static int i830_flush_ioctl(struct inode *inode, struct file *filp, 
+			     unsigned int cmd, unsigned long arg)
+{
+   	drm_file_t	  *priv	  = filp->private_data;
+   	drm_device_t	  *dev	  = priv->head->dev;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+   	i830_flush_queue(dev);
+   	return 0;
+}
+
+static int i830_dma_vertex(struct inode *inode, struct file *filp,
+		       unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_device_dma_t *dma = dev->dma;
+   	drm_i830_private_t *dev_priv = (drm_i830_private_t *)dev->dev_private;
+      	u32 *hw_status = dev_priv->hw_status_page;
+   	drm_i830_sarea_t *sarea_priv = (drm_i830_sarea_t *) 
+     					dev_priv->sarea_priv; 
+	drm_i830_vertex_t vertex;
+
+	if (copy_from_user(&vertex, (drm_i830_vertex_t __user *)arg, sizeof(vertex)))
+		return -EFAULT;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	DRM_DEBUG("i830 dma vertex, idx %d used %d discard %d\n",
+		  vertex.idx, vertex.used, vertex.discard);
+
+	if(vertex.idx < 0 || vertex.idx > dma->buf_count) return -EINVAL;
+
+	i830_dma_dispatch_vertex( dev, 
+				  dma->buflist[ vertex.idx ], 
+				  vertex.discard, vertex.used );
+
+	sarea_priv->last_enqueue = dev_priv->counter-1;
+   	sarea_priv->last_dispatch = (int) hw_status[5];
+   
+	return 0;
+}
+
+static int i830_clear_bufs(struct inode *inode, struct file *filp,
+			   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_i830_clear_t clear;
+
+   	if (copy_from_user(&clear, (drm_i830_clear_t __user *)arg, sizeof(clear)))
+		return -EFAULT;
+   
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	/* GH: Someone's doing nasty things... */
+	if (!dev->dev_private) {
+		return -EINVAL;
+	}
+
+	i830_dma_dispatch_clear( dev, clear.flags, 
+				 clear.clear_color, 
+				 clear.clear_depth,
+			         clear.clear_depthmask);
+   	return 0;
+}
+
+static int i830_swap_bufs(struct inode *inode, struct file *filp,
+			  unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+   
+	DRM_DEBUG("i830_swap_bufs\n");
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	i830_dma_dispatch_swap( dev );
+   	return 0;
+}
+
+
+
+/* Not sure why this isn't set all the time:
+ */ 
+static void i830_do_init_pageflip( drm_device_t *dev )
+{
+	drm_i830_private_t *dev_priv = dev->dev_private;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+	dev_priv->page_flipping = 1;
+	dev_priv->current_page = 0;
+	dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+}
+
+static int i830_do_cleanup_pageflip( drm_device_t *dev )
+{
+	drm_i830_private_t *dev_priv = dev->dev_private;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+	if (dev_priv->current_page != 0)
+		i830_dma_dispatch_flip( dev );
+
+	dev_priv->page_flipping = 0;
+	return 0;
+}
+
+static int i830_flip_bufs(struct inode *inode, struct file *filp,
+			   unsigned int cmd, unsigned long arg)
+{
+	drm_file_t *priv = filp->private_data;
+	drm_device_t *dev = priv->head->dev;
+	drm_i830_private_t *dev_priv = dev->dev_private;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	if (!dev_priv->page_flipping) 
+		i830_do_init_pageflip( dev );
+
+	i830_dma_dispatch_flip( dev );
+   	return 0;
+}
+
+static int i830_getage(struct inode *inode, struct file *filp, unsigned int cmd,
+			unsigned long arg)
+{
+   	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->head->dev;
+   	drm_i830_private_t *dev_priv = (drm_i830_private_t *)dev->dev_private;
+      	u32 *hw_status = dev_priv->hw_status_page;
+   	drm_i830_sarea_t *sarea_priv = (drm_i830_sarea_t *) 
+     					dev_priv->sarea_priv; 
+
+      	sarea_priv->last_dispatch = (int) hw_status[5];
+	return 0;
+}
+
+static int i830_getbuf(struct inode *inode, struct file *filp, unsigned int cmd,
+			unsigned long arg)
+{
+	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->head->dev;
+	int		  retcode   = 0;
+	drm_i830_dma_t	  d;
+   	drm_i830_private_t *dev_priv = (drm_i830_private_t *)dev->dev_private;
+   	u32 *hw_status = dev_priv->hw_status_page;
+   	drm_i830_sarea_t *sarea_priv = (drm_i830_sarea_t *) 
+     					dev_priv->sarea_priv; 
+
+	DRM_DEBUG("getbuf\n");
+   	if (copy_from_user(&d, (drm_i830_dma_t __user *)arg, sizeof(d)))
+		return -EFAULT;
+   
+	LOCK_TEST_WITH_RETURN(dev, filp);
+	
+	d.granted = 0;
+
+	retcode = i830_dma_get_buffer(dev, &d, filp);
+
+	DRM_DEBUG("i830_dma: %d returning %d, granted = %d\n",
+		  current->pid, retcode, d.granted);
+
+	if (copy_to_user((drm_dma_t __user *)arg, &d, sizeof(d)))
+		return -EFAULT;
+   	sarea_priv->last_dispatch = (int) hw_status[5];
+
+	return retcode;
+}
+
+static int i830_copybuf(struct inode *inode,
+			 struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	/* Never copy - 2.4.x doesn't need it */
+	return 0;
+}
+
+static int i830_docopy(struct inode *inode, struct file *filp, unsigned int cmd,
+			unsigned long arg)
+{
+	return 0;
+}
+
+
+
+static int i830_getparam( struct inode *inode, struct file *filp, 
+			unsigned int cmd, unsigned long arg )
+{
+	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->head->dev;
+	drm_i830_private_t *dev_priv = dev->dev_private;
+	drm_i830_getparam_t param;
+	int value;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return -EINVAL;
+	}
+
+	if (copy_from_user(&param, (drm_i830_getparam_t __user *)arg, sizeof(param) ))
+		return -EFAULT;
+
+	switch( param.param ) {
+	case I830_PARAM_IRQ_ACTIVE:
+		value = dev->irq_enabled;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if ( copy_to_user( param.value, &value, sizeof(int) ) ) {
+		DRM_ERROR( "copy_to_user\n" );
+		return -EFAULT;
+	}
+	
+	return 0;
+}
+
+
+static int i830_setparam( struct inode *inode, struct file *filp,
+			unsigned int cmd, unsigned long arg )
+{
+	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->head->dev;
+	drm_i830_private_t *dev_priv = dev->dev_private;
+	drm_i830_setparam_t param;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return -EINVAL;
+	}
+
+	if (copy_from_user(&param, (drm_i830_setparam_t __user *)arg, sizeof(param) ))
+		return -EFAULT;
+
+	switch( param.param ) {
+	case I830_SETPARAM_USE_MI_BATCHBUFFER_START:
+		dev_priv->use_mi_batchbuffer_start = param.value;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+
+void i830_driver_pretakedown(drm_device_t *dev)
+{
+	i830_dma_cleanup( dev );
+}
+
+void i830_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+	if (dev->dev_private) {
+		drm_i830_private_t *dev_priv = dev->dev_private;
+		if (dev_priv->page_flipping) {
+			i830_do_cleanup_pageflip(dev);
+		}
+	}
+}
+
+void i830_driver_release(drm_device_t *dev, struct file *filp)
+{
+	i830_reclaim_buffers(dev, filp);
+}
+
+int i830_driver_dma_quiescent(drm_device_t *dev)
+{
+	i830_dma_quiescent( dev );
+	return 0;
+}
+
+drm_ioctl_desc_t i830_ioctls[] = {
+	[DRM_IOCTL_NR(DRM_I830_INIT)]     = { i830_dma_init,    1, 1 },
+	[DRM_IOCTL_NR(DRM_I830_VERTEX)]   = { i830_dma_vertex,  1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_CLEAR)]    = { i830_clear_bufs,  1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_FLUSH)]    = { i830_flush_ioctl, 1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_GETAGE)]   = { i830_getage,      1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_GETBUF)]   = { i830_getbuf,      1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_SWAP)]     = { i830_swap_bufs,   1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_COPY)]     = { i830_copybuf,     1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_DOCOPY)]   = { i830_docopy,      1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_FLIP)]     = { i830_flip_bufs,   1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_IRQ_EMIT)] = { i830_irq_emit,    1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_IRQ_WAIT)] = { i830_irq_wait,    1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_GETPARAM)] = { i830_getparam,    1, 0 },
+	[DRM_IOCTL_NR(DRM_I830_SETPARAM)] = { i830_setparam,    1, 0 } 
+};
+
+int i830_max_ioctl = DRM_ARRAY_SIZE(i830_ioctls);
diff --git a/drivers/char/drm/i830_drm.h b/drivers/char/drm/i830_drm.h
new file mode 100644
index 0000000..03382c0
--- /dev/null
+++ b/drivers/char/drm/i830_drm.h
@@ -0,0 +1,350 @@
+#ifndef _I830_DRM_H_
+#define _I830_DRM_H_
+
+/* WARNING: These defines must be the same as what the Xserver uses.
+ * if you change them, you must change the defines in the Xserver.
+ *
+ * KW: Actually, you can't ever change them because doing so would
+ * break backwards compatibility.
+ */
+
+#ifndef _I830_DEFINES_
+#define _I830_DEFINES_
+
+#define I830_DMA_BUF_ORDER		12
+#define I830_DMA_BUF_SZ 		(1<<I830_DMA_BUF_ORDER)
+#define I830_DMA_BUF_NR 		256
+#define I830_NR_SAREA_CLIPRECTS 	8
+
+/* Each region is a minimum of 64k, and there are at most 64 of them.
+ */
+#define I830_NR_TEX_REGIONS 64
+#define I830_LOG_MIN_TEX_REGION_SIZE 16
+
+/* KW: These aren't correct but someone set them to two and then
+ * released the module.  Now we can't change them as doing so would
+ * break backwards compatibility.
+ */
+#define I830_TEXTURE_COUNT	2
+#define I830_TEXBLEND_COUNT	I830_TEXTURE_COUNT
+
+#define I830_TEXBLEND_SIZE	12	/* (4 args + op) * 2 + COLOR_FACTOR */
+
+#define I830_UPLOAD_CTX			0x1
+#define I830_UPLOAD_BUFFERS		0x2
+#define I830_UPLOAD_CLIPRECTS		0x4
+#define I830_UPLOAD_TEX0_IMAGE		0x100 /* handled clientside */
+#define I830_UPLOAD_TEX0_CUBE		0x200 /* handled clientside */
+#define I830_UPLOAD_TEX1_IMAGE		0x400 /* handled clientside */
+#define I830_UPLOAD_TEX1_CUBE		0x800 /* handled clientside */
+#define I830_UPLOAD_TEX2_IMAGE		0x1000 /* handled clientside */
+#define I830_UPLOAD_TEX2_CUBE		0x2000 /* handled clientside */
+#define I830_UPLOAD_TEX3_IMAGE		0x4000 /* handled clientside */
+#define I830_UPLOAD_TEX3_CUBE		0x8000 /* handled clientside */
+#define I830_UPLOAD_TEX_N_IMAGE(n)	(0x100 << (n * 2))
+#define I830_UPLOAD_TEX_N_CUBE(n)	(0x200 << (n * 2))
+#define I830_UPLOAD_TEXIMAGE_MASK	0xff00
+#define I830_UPLOAD_TEX0			0x10000
+#define I830_UPLOAD_TEX1			0x20000
+#define I830_UPLOAD_TEX2			0x40000
+#define I830_UPLOAD_TEX3			0x80000
+#define I830_UPLOAD_TEX_N(n)		(0x10000 << (n))
+#define I830_UPLOAD_TEX_MASK		0xf0000
+#define I830_UPLOAD_TEXBLEND0		0x100000
+#define I830_UPLOAD_TEXBLEND1		0x200000
+#define I830_UPLOAD_TEXBLEND2		0x400000
+#define I830_UPLOAD_TEXBLEND3		0x800000
+#define I830_UPLOAD_TEXBLEND_N(n)	(0x100000 << (n))
+#define I830_UPLOAD_TEXBLEND_MASK	0xf00000
+#define I830_UPLOAD_TEX_PALETTE_N(n)    (0x1000000 << (n))
+#define I830_UPLOAD_TEX_PALETTE_SHARED	0x4000000
+#define I830_UPLOAD_STIPPLE         	0x8000000
+
+/* Indices into buf.Setup where various bits of state are mirrored per
+ * context and per buffer.  These can be fired at the card as a unit,
+ * or in a piecewise fashion as required.
+ */
+
+/* Destbuffer state 
+ *    - backbuffer linear offset and pitch -- invarient in the current dri
+ *    - zbuffer linear offset and pitch -- also invarient
+ *    - drawing origin in back and depth buffers.
+ *
+ * Keep the depth/back buffer state here to accommodate private buffers
+ * in the future.
+ */
+
+#define I830_DESTREG_CBUFADDR 0
+#define I830_DESTREG_DBUFADDR 1
+#define I830_DESTREG_DV0 2
+#define I830_DESTREG_DV1 3
+#define I830_DESTREG_SENABLE 4
+#define I830_DESTREG_SR0 5
+#define I830_DESTREG_SR1 6
+#define I830_DESTREG_SR2 7
+#define I830_DESTREG_DR0 8
+#define I830_DESTREG_DR1 9
+#define I830_DESTREG_DR2 10
+#define I830_DESTREG_DR3 11
+#define I830_DESTREG_DR4 12
+#define I830_DEST_SETUP_SIZE 13
+
+/* Context state
+ */
+#define I830_CTXREG_STATE1		0
+#define I830_CTXREG_STATE2		1
+#define I830_CTXREG_STATE3		2
+#define I830_CTXREG_STATE4		3
+#define I830_CTXREG_STATE5		4
+#define I830_CTXREG_IALPHAB		5
+#define I830_CTXREG_STENCILTST		6
+#define I830_CTXREG_ENABLES_1		7
+#define I830_CTXREG_ENABLES_2		8
+#define I830_CTXREG_AA			9
+#define I830_CTXREG_FOGCOLOR		10
+#define I830_CTXREG_BLENDCOLR0		11
+#define I830_CTXREG_BLENDCOLR		12 /* Dword 1 of 2 dword command */
+#define I830_CTXREG_VF			13
+#define I830_CTXREG_VF2			14
+#define I830_CTXREG_MCSB0		15
+#define I830_CTXREG_MCSB1		16
+#define I830_CTX_SETUP_SIZE		17
+
+/* 1.3: Stipple state
+ */ 
+#define I830_STPREG_ST0 0
+#define I830_STPREG_ST1 1
+#define I830_STP_SETUP_SIZE 2
+
+
+/* Texture state (per tex unit)
+ */
+
+#define I830_TEXREG_MI0	0	/* GFX_OP_MAP_INFO (6 dwords) */
+#define I830_TEXREG_MI1	1
+#define I830_TEXREG_MI2	2
+#define I830_TEXREG_MI3	3
+#define I830_TEXREG_MI4	4
+#define I830_TEXREG_MI5	5
+#define I830_TEXREG_MF	6	/* GFX_OP_MAP_FILTER */
+#define I830_TEXREG_MLC	7	/* GFX_OP_MAP_LOD_CTL */
+#define I830_TEXREG_MLL	8	/* GFX_OP_MAP_LOD_LIMITS */
+#define I830_TEXREG_MCS	9	/* GFX_OP_MAP_COORD_SETS */
+#define I830_TEX_SETUP_SIZE 10
+
+#define I830_TEXREG_TM0LI      0 /* load immediate 2 texture map n */
+#define I830_TEXREG_TM0S0      1
+#define I830_TEXREG_TM0S1      2
+#define I830_TEXREG_TM0S2      3
+#define I830_TEXREG_TM0S3      4
+#define I830_TEXREG_TM0S4      5
+#define I830_TEXREG_NOP0       6       /* noop */
+#define I830_TEXREG_NOP1       7       /* noop */
+#define I830_TEXREG_NOP2       8       /* noop */
+#define __I830_TEXREG_MCS      9       /* GFX_OP_MAP_COORD_SETS -- shared */
+#define __I830_TEX_SETUP_SIZE   10
+
+#define I830_FRONT   0x1
+#define I830_BACK    0x2
+#define I830_DEPTH   0x4
+
+#endif /* _I830_DEFINES_ */
+
+typedef struct _drm_i830_init {
+	enum {
+		I830_INIT_DMA = 0x01,
+		I830_CLEANUP_DMA = 0x02
+	} func;
+	unsigned int mmio_offset;
+	unsigned int buffers_offset;
+	int sarea_priv_offset;
+	unsigned int ring_start;
+	unsigned int ring_end;
+	unsigned int ring_size;
+	unsigned int front_offset;
+	unsigned int back_offset;
+	unsigned int depth_offset;
+	unsigned int w;
+	unsigned int h;
+	unsigned int pitch;
+	unsigned int pitch_bits;
+	unsigned int back_pitch;
+	unsigned int depth_pitch;
+	unsigned int cpp;
+} drm_i830_init_t;
+
+/* Warning: If you change the SAREA structure you must change the Xserver
+ * structure as well */
+
+typedef struct _drm_i830_tex_region {
+	unsigned char next, prev; /* indices to form a circular LRU  */
+	unsigned char in_use;	/* owned by a client, or free? */
+	int age;		/* tracked by clients to update local LRU's */
+} drm_i830_tex_region_t;
+
+typedef struct _drm_i830_sarea {
+	unsigned int ContextState[I830_CTX_SETUP_SIZE];
+   	unsigned int BufferState[I830_DEST_SETUP_SIZE];
+	unsigned int TexState[I830_TEXTURE_COUNT][I830_TEX_SETUP_SIZE];
+	unsigned int TexBlendState[I830_TEXBLEND_COUNT][I830_TEXBLEND_SIZE];
+	unsigned int TexBlendStateWordsUsed[I830_TEXBLEND_COUNT];
+	unsigned int Palette[2][256];
+   	unsigned int dirty;
+
+	unsigned int nbox;
+	drm_clip_rect_t boxes[I830_NR_SAREA_CLIPRECTS];
+
+	/* Maintain an LRU of contiguous regions of texture space.  If
+	 * you think you own a region of texture memory, and it has an
+	 * age different to the one you set, then you are mistaken and
+	 * it has been stolen by another client.  If global texAge
+	 * hasn't changed, there is no need to walk the list.
+	 *
+	 * These regions can be used as a proxy for the fine-grained
+	 * texture information of other clients - by maintaining them
+	 * in the same lru which is used to age their own textures,
+	 * clients have an approximate lru for the whole of global
+	 * texture space, and can make informed decisions as to which
+	 * areas to kick out.  There is no need to choose whether to
+	 * kick out your own texture or someone else's - simply eject
+	 * them all in LRU order.  
+	 */
+
+	drm_i830_tex_region_t texList[I830_NR_TEX_REGIONS+1]; 
+				/* Last elt is sentinal */
+        int texAge;		/* last time texture was uploaded */
+        int last_enqueue;	/* last time a buffer was enqueued */
+	int last_dispatch;	/* age of the most recently dispatched buffer */
+	int last_quiescent;     /*  */
+	int ctxOwner;		/* last context to upload state */
+
+	int vertex_prim;
+
+        int pf_enabled;               /* is pageflipping allowed? */
+        int pf_active;               
+        int pf_current_page;	    /* which buffer is being displayed? */
+
+        int perf_boxes;             /* performance boxes to be displayed */
+   
+        /* Here's the state for texunits 2,3:
+	 */
+	unsigned int TexState2[I830_TEX_SETUP_SIZE];
+	unsigned int TexBlendState2[I830_TEXBLEND_SIZE];
+	unsigned int TexBlendStateWordsUsed2;
+
+	unsigned int TexState3[I830_TEX_SETUP_SIZE];
+	unsigned int TexBlendState3[I830_TEXBLEND_SIZE];
+	unsigned int TexBlendStateWordsUsed3;
+
+	unsigned int StippleState[I830_STP_SETUP_SIZE];
+} drm_i830_sarea_t;
+
+/* Flags for perf_boxes
+ */
+#define I830_BOX_RING_EMPTY    0x1 /* populated by kernel */
+#define I830_BOX_FLIP          0x2 /* populated by kernel */
+#define I830_BOX_WAIT          0x4 /* populated by kernel & client */
+#define I830_BOX_TEXTURE_LOAD  0x8 /* populated by kernel */
+#define I830_BOX_LOST_CONTEXT  0x10 /* populated by client */
+
+
+/* I830 specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_I830_INIT	0x00
+#define DRM_I830_VERTEX	0x01
+#define DRM_I830_CLEAR	0x02
+#define DRM_I830_FLUSH	0x03
+#define DRM_I830_GETAGE	0x04
+#define DRM_I830_GETBUF	0x05
+#define DRM_I830_SWAP	0x06
+#define DRM_I830_COPY	0x07
+#define DRM_I830_DOCOPY	0x08
+#define DRM_I830_FLIP	0x09
+#define DRM_I830_IRQ_EMIT	0x0a
+#define DRM_I830_IRQ_WAIT	0x0b
+#define DRM_I830_GETPARAM	0x0c
+#define DRM_I830_SETPARAM	0x0d
+
+#define DRM_IOCTL_I830_INIT		DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_INIT, drm_i830_init_t)
+#define DRM_IOCTL_I830_VERTEX		DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_VERTEX, drm_i830_vertex_t)
+#define DRM_IOCTL_I830_CLEAR		DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_CLEAR, drm_i830_clear_t)
+#define DRM_IOCTL_I830_FLUSH		DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_FLUSH)
+#define DRM_IOCTL_I830_GETAGE		DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_GETAGE)
+#define DRM_IOCTL_I830_GETBUF		DRM_IOWR(DRM_COMMAND_BASE + DRM_IOCTL_I830_GETBUF, drm_i830_dma_t)
+#define DRM_IOCTL_I830_SWAP		DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_SWAP)
+#define DRM_IOCTL_I830_COPY		DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_COPY, drm_i830_copy_t)
+#define DRM_IOCTL_I830_DOCOPY		DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_DOCOPY)
+#define DRM_IOCTL_I830_FLIP		DRM_IO ( DRM_COMMAND_BASE + DRM_IOCTL_I830_FLIP)
+#define DRM_IOCTL_I830_IRQ_EMIT         DRM_IOWR(DRM_COMMAND_BASE + DRM_IOCTL_I830_IRQ_EMIT, drm_i830_irq_emit_t)
+#define DRM_IOCTL_I830_IRQ_WAIT         DRM_IOW( DRM_COMMAND_BASE + DRM_IOCTL_I830_IRQ_WAIT, drm_i830_irq_wait_t)
+#define DRM_IOCTL_I830_GETPARAM         DRM_IOWR(DRM_COMMAND_BASE + DRM_IOCTL_I830_GETPARAM, drm_i830_getparam_t)
+#define DRM_IOCTL_I830_SETPARAM         DRM_IOWR(DRM_COMMAND_BASE + DRM_IOCTL_I830_SETPARAM, drm_i830_setparam_t)
+
+typedef struct _drm_i830_clear {
+	int clear_color;
+	int clear_depth;
+	int flags;
+	unsigned int clear_colormask;
+	unsigned int clear_depthmask;
+} drm_i830_clear_t;
+
+
+
+/* These may be placeholders if we have more cliprects than
+ * I830_NR_SAREA_CLIPRECTS.  In that case, the client sets discard to
+ * false, indicating that the buffer will be dispatched again with a
+ * new set of cliprects.
+ */
+typedef struct _drm_i830_vertex {
+   	int idx;		/* buffer index */
+	int used;		/* nr bytes in use */
+	int discard;		/* client is finished with the buffer? */
+} drm_i830_vertex_t;
+
+typedef struct _drm_i830_copy_t {
+   	int idx;		/* buffer index */
+	int used;		/* nr bytes in use */
+	void __user *address;		/* Address to copy from */
+} drm_i830_copy_t;
+
+typedef struct drm_i830_dma {
+	void __user *virtual;
+	int request_idx;
+	int request_size;
+	int granted;
+} drm_i830_dma_t;
+
+
+/* 1.3: Userspace can request & wait on irq's:
+ */
+typedef struct drm_i830_irq_emit {
+	int __user *irq_seq;
+} drm_i830_irq_emit_t;
+
+typedef struct drm_i830_irq_wait {
+	int irq_seq;
+} drm_i830_irq_wait_t;
+
+
+/* 1.3: New ioctl to query kernel params:
+ */
+#define I830_PARAM_IRQ_ACTIVE            1
+
+typedef struct drm_i830_getparam {
+	int param;
+	int __user *value;
+} drm_i830_getparam_t;
+
+
+/* 1.3: New ioctl to set kernel params:
+ */
+#define I830_SETPARAM_USE_MI_BATCHBUFFER_START            1
+
+typedef struct drm_i830_setparam {
+	int param;
+	int value;
+} drm_i830_setparam_t;
+
+
+#endif /* _I830_DRM_H_ */
diff --git a/drivers/char/drm/i830_drv.c b/drivers/char/drm/i830_drv.c
new file mode 100644
index 0000000..aa80ad6
--- /dev/null
+++ b/drivers/char/drm/i830_drv.c
@@ -0,0 +1,137 @@
+/* i830_drv.c -- I810 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:56:22 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Jeff Hartmann <jhartmann@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ *    Abraham vd Merwe <abraham@2d3d.co.za>
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "i830_drm.h"
+#include "i830_drv.h"
+
+#include "drm_pciids.h"
+
+int postinit( struct drm_device *dev, unsigned long flags )
+{
+	dev->counters += 4;
+	dev->types[6] = _DRM_STAT_IRQ;
+	dev->types[7] = _DRM_STAT_PRIMARY;
+	dev->types[8] = _DRM_STAT_SECONDARY;
+	dev->types[9] = _DRM_STAT_DMA;
+	
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->primary.minor,
+		pci_pretty_name(dev->pdev)
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+	i830_PCI_IDS
+};
+
+extern drm_ioctl_desc_t i830_ioctls[];
+extern int i830_max_ioctl;
+
+static struct drm_driver driver = {
+	.driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | DRIVER_USE_MTRR | DRIVER_HAVE_DMA | DRIVER_DMA_QUEUE,
+#if USE_IRQS
+	.driver_features |= DRIVER_HAVE_IRQ | DRIVER_SHARED_IRQ,
+#endif
+	.dev_priv_size = sizeof(drm_i830_buf_priv_t),
+	.pretakedown = i830_driver_pretakedown,
+	.prerelease = i830_driver_prerelease,
+	.release = i830_driver_release,
+	.dma_quiescent = i830_driver_dma_quiescent,
+	.reclaim_buffers = i830_reclaim_buffers,
+	.get_map_ofs = drm_core_get_map_ofs,
+	.get_reg_ofs = drm_core_get_reg_ofs,
+#if USE_IRQS
+	.irq_preinstall = i830_driver_irq_preinstall,
+	.irq_postinstall = i830_driver_irq_postinstall,
+	.irq_uninstall = i830_driver_irq_uninstall,
+	.irq_handler = i830_driver_irq_handler,
+#endif
+	.postinit = postinit,
+	.version = version,
+	.ioctls = i830_ioctls,
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+	.pci_driver = {
+		.name          = DRIVER_NAME,
+		.id_table      = pciidlist,
+	}
+
+};
+
+static int __init i830_init(void)
+{
+	driver.num_ioctls = i830_max_ioctl;
+	return drm_init(&driver);
+}
+
+static void __exit i830_exit(void)
+{
+	drm_exit(&driver);
+}
+
+module_init(i830_init);
+module_exit(i830_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/i830_drv.h b/drivers/char/drm/i830_drv.h
new file mode 100644
index 0000000..d4b2d09
--- /dev/null
+++ b/drivers/char/drm/i830_drv.h
@@ -0,0 +1,301 @@
+/* i830_drv.h -- Private header for the I830 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Rickard E. (Rik) Faith <faith@valinux.com>
+ * 	    Jeff Hartmann <jhartmann@valinux.com>
+ *
+ */
+
+#ifndef _I830_DRV_H_
+#define _I830_DRV_H_
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR		"VA Linux Systems Inc."
+
+#define DRIVER_NAME		"i830"
+#define DRIVER_DESC		"Intel 830M"
+#define DRIVER_DATE		"20021108"
+
+/* Interface history:
+ *
+ * 1.1: Original.
+ * 1.2: ?
+ * 1.3: New irq emit/wait ioctls.
+ *      New pageflip ioctl.
+ *      New getparam ioctl.
+ *      State for texunits 3&4 in sarea.
+ *      New (alternative) layout for texture state.
+ */
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		3
+#define DRIVER_PATCHLEVEL	2
+
+/* Driver will work either way: IRQ's save cpu time when waiting for
+ * the card, but are subject to subtle interactions between bios,
+ * hardware and the driver.
+ */
+/* XXX: Add vblank support? */
+#define USE_IRQS 0
+
+typedef struct drm_i830_buf_priv {
+   	u32 *in_use;
+   	int my_use_idx;
+	int currently_mapped;
+	void __user *virtual;
+	void *kernel_virtual;
+} drm_i830_buf_priv_t;
+
+typedef struct _drm_i830_ring_buffer{
+	int tail_mask;
+	unsigned long Start;
+	unsigned long End;
+	unsigned long Size;
+	u8 *virtual_start;
+	int head;
+	int tail;
+	int space;
+} drm_i830_ring_buffer_t;
+
+typedef struct drm_i830_private {
+	drm_map_t *sarea_map;
+	drm_map_t *mmio_map;
+
+	drm_i830_sarea_t *sarea_priv;
+   	drm_i830_ring_buffer_t ring;
+
+      	void * hw_status_page;
+   	unsigned long counter;
+
+	dma_addr_t dma_status_page;
+
+	drm_buf_t *mmap_buffer;
+	
+	u32 front_di1, back_di1, zi1;
+	
+	int back_offset;
+	int depth_offset;
+	int front_offset;
+	int w, h;
+	int pitch;
+	int back_pitch;
+	int depth_pitch;
+	unsigned int cpp;
+
+	int do_boxes;
+	int dma_used;
+
+	int current_page;
+	int page_flipping;
+
+	wait_queue_head_t irq_queue;
+   	atomic_t irq_received;
+   	atomic_t irq_emitted;
+
+	int use_mi_batchbuffer_start;
+
+} drm_i830_private_t;
+
+/* i830_dma.c */
+extern void i830_reclaim_buffers(drm_device_t *dev, struct file *filp);
+
+extern int i830_mmap_buffers(struct file *filp, struct vm_area_struct *vma);
+
+/* i830_irq.c */
+extern int i830_irq_emit( struct inode *inode, struct file *filp, 
+			  unsigned int cmd, unsigned long arg );
+extern int i830_irq_wait( struct inode *inode, struct file *filp,
+			  unsigned int cmd, unsigned long arg );
+
+extern irqreturn_t i830_driver_irq_handler( DRM_IRQ_ARGS );
+extern void i830_driver_irq_preinstall( drm_device_t *dev );
+extern void i830_driver_irq_postinstall( drm_device_t *dev );
+extern void i830_driver_irq_uninstall( drm_device_t *dev );
+extern void i830_driver_pretakedown(drm_device_t *dev);
+extern void i830_driver_release(drm_device_t *dev, struct file *filp);
+extern int i830_driver_dma_quiescent(drm_device_t *dev);
+extern void i830_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+
+#define I830_BASE(reg)		((unsigned long) \
+				dev_priv->mmio_map->handle)
+#define I830_ADDR(reg)		(I830_BASE(reg) + reg)
+#define I830_DEREF(reg)		*(__volatile__ unsigned int *)I830_ADDR(reg)
+#define I830_READ(reg)		readl((volatile u32 *)I830_ADDR(reg))
+#define I830_WRITE(reg,val) 	writel(val, (volatile u32 *)I830_ADDR(reg))
+#define I830_DEREF16(reg)	*(__volatile__ u16 *)I830_ADDR(reg)
+#define I830_READ16(reg) 	I830_DEREF16(reg)
+#define I830_WRITE16(reg,val)	do { I830_DEREF16(reg) = val; } while (0)
+
+
+
+#define I830_VERBOSE 0
+
+#define RING_LOCALS	unsigned int outring, ringmask, outcount; \
+                        volatile char *virt;
+
+#define BEGIN_LP_RING(n) do {				\
+	if (I830_VERBOSE)				\
+		printk("BEGIN_LP_RING(%d) in %s\n",	\
+			  n, __FUNCTION__);		\
+	if (dev_priv->ring.space < n*4)			\
+		i830_wait_ring(dev, n*4, __FUNCTION__);		\
+	outcount = 0;					\
+	outring = dev_priv->ring.tail;			\
+	ringmask = dev_priv->ring.tail_mask;		\
+	virt = dev_priv->ring.virtual_start;		\
+} while (0)
+
+
+#define OUT_RING(n) do {					\
+	if (I830_VERBOSE) printk("   OUT_RING %x\n", (int)(n));	\
+	*(volatile unsigned int *)(virt + outring) = n;		\
+        outcount++;						\
+	outring += 4;						\
+	outring &= ringmask;					\
+} while (0)
+
+#define ADVANCE_LP_RING() do {						\
+	if (I830_VERBOSE) printk("ADVANCE_LP_RING %x\n", outring);	\
+	dev_priv->ring.tail = outring;					\
+	dev_priv->ring.space -= outcount * 4;				\
+	I830_WRITE(LP_RING + RING_TAIL, outring);			\
+} while(0)
+
+extern int i830_wait_ring(drm_device_t *dev, int n, const char *caller);
+
+
+#define GFX_OP_USER_INTERRUPT 		((0<<29)|(2<<23))
+#define GFX_OP_BREAKPOINT_INTERRUPT	((0<<29)|(1<<23))
+#define CMD_REPORT_HEAD			(7<<23)
+#define CMD_STORE_DWORD_IDX		((0x21<<23) | 0x1)
+#define CMD_OP_BATCH_BUFFER  ((0x0<<29)|(0x30<<23)|0x1)
+
+#define STATE3D_LOAD_STATE_IMMEDIATE_2      ((0x3<<29)|(0x1d<<24)|(0x03<<16))
+#define LOAD_TEXTURE_MAP0                   (1<<11)
+
+#define INST_PARSER_CLIENT   0x00000000
+#define INST_OP_FLUSH        0x02000000
+#define INST_FLUSH_MAP_CACHE 0x00000001
+
+
+#define BB1_START_ADDR_MASK   (~0x7)
+#define BB1_PROTECTED         (1<<0)
+#define BB1_UNPROTECTED       (0<<0)
+#define BB2_END_ADDR_MASK     (~0x7)
+
+#define I830REG_HWSTAM		0x02098
+#define I830REG_INT_IDENTITY_R	0x020a4
+#define I830REG_INT_MASK_R 	0x020a8
+#define I830REG_INT_ENABLE_R	0x020a0
+
+#define I830_IRQ_RESERVED ((1<<13)|(3<<2))
+
+
+#define LP_RING     		0x2030
+#define HP_RING     		0x2040
+#define RING_TAIL      		0x00
+#define TAIL_ADDR		0x001FFFF8
+#define RING_HEAD      		0x04
+#define HEAD_WRAP_COUNT     	0xFFE00000
+#define HEAD_WRAP_ONE       	0x00200000
+#define HEAD_ADDR           	0x001FFFFC
+#define RING_START     		0x08
+#define START_ADDR          	0x0xFFFFF000
+#define RING_LEN       		0x0C
+#define RING_NR_PAGES       	0x001FF000 
+#define RING_REPORT_MASK    	0x00000006
+#define RING_REPORT_64K     	0x00000002
+#define RING_REPORT_128K    	0x00000004
+#define RING_NO_REPORT      	0x00000000
+#define RING_VALID_MASK     	0x00000001
+#define RING_VALID          	0x00000001
+#define RING_INVALID        	0x00000000
+
+#define GFX_OP_SCISSOR         ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define SC_UPDATE_SCISSOR       (0x1<<1)
+#define SC_ENABLE_MASK          (0x1<<0)
+#define SC_ENABLE               (0x1<<0)
+
+#define GFX_OP_SCISSOR_INFO    ((0x3<<29)|(0x1d<<24)|(0x81<<16)|(0x1))
+#define SCI_YMIN_MASK      (0xffff<<16)
+#define SCI_XMIN_MASK      (0xffff<<0)
+#define SCI_YMAX_MASK      (0xffff<<16)
+#define SCI_XMAX_MASK      (0xffff<<0)
+
+#define GFX_OP_SCISSOR_ENABLE	 ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define GFX_OP_SCISSOR_RECT	 ((0x3<<29)|(0x1d<<24)|(0x81<<16)|1)
+#define GFX_OP_COLOR_FACTOR      ((0x3<<29)|(0x1d<<24)|(0x1<<16)|0x0)
+#define GFX_OP_STIPPLE           ((0x3<<29)|(0x1d<<24)|(0x83<<16))
+#define GFX_OP_MAP_INFO          ((0x3<<29)|(0x1d<<24)|0x4)
+#define GFX_OP_DESTBUFFER_VARS   ((0x3<<29)|(0x1d<<24)|(0x85<<16)|0x0)
+#define GFX_OP_DRAWRECT_INFO     ((0x3<<29)|(0x1d<<24)|(0x80<<16)|(0x3))
+#define GFX_OP_PRIMITIVE         ((0x3<<29)|(0x1f<<24))
+
+#define CMD_OP_DESTBUFFER_INFO	 ((0x3<<29)|(0x1d<<24)|(0x8e<<16)|1)
+
+#define CMD_OP_DISPLAYBUFFER_INFO ((0x0<<29)|(0x14<<23)|2)
+#define ASYNC_FLIP                (1<<22)
+
+#define CMD_3D                          (0x3<<29)
+#define STATE3D_CONST_BLEND_COLOR_CMD   (CMD_3D|(0x1d<<24)|(0x88<<16))
+#define STATE3D_MAP_COORD_SETBIND_CMD   (CMD_3D|(0x1d<<24)|(0x02<<16))
+
+#define BR00_BITBLT_CLIENT   0x40000000
+#define BR00_OP_COLOR_BLT    0x10000000
+#define BR00_OP_SRC_COPY_BLT 0x10C00000
+#define BR13_SOLID_PATTERN   0x80000000
+
+#define BUF_3D_ID_COLOR_BACK    (0x3<<24)
+#define BUF_3D_ID_DEPTH         (0x7<<24)
+#define BUF_3D_USE_FENCE        (1<<23)
+#define BUF_3D_PITCH(x)         (((x)/4)<<2)
+
+#define CMD_OP_MAP_PALETTE_LOAD	((3<<29)|(0x1d<<24)|(0x82<<16)|255)
+#define MAP_PALETTE_NUM(x)	((x<<8) & (1<<8))
+#define MAP_PALETTE_BOTH	(1<<11)
+
+#define XY_COLOR_BLT_CMD		((2<<29)|(0x50<<22)|0x4)
+#define XY_COLOR_BLT_WRITE_ALPHA	(1<<21)
+#define XY_COLOR_BLT_WRITE_RGB		(1<<20)
+
+#define XY_SRC_COPY_BLT_CMD             ((2<<29)|(0x53<<22)|6)
+#define XY_SRC_COPY_BLT_WRITE_ALPHA     (1<<21)
+#define XY_SRC_COPY_BLT_WRITE_RGB       (1<<20)
+
+#define MI_BATCH_BUFFER 	((0x30<<23)|1)
+#define MI_BATCH_BUFFER_START 	(0x31<<23)
+#define MI_BATCH_BUFFER_END 	(0xA<<23)
+#define MI_BATCH_NON_SECURE	(1)
+
+#define MI_WAIT_FOR_EVENT       ((0x3<<23))
+#define MI_WAIT_FOR_PLANE_A_FLIP      (1<<2) 
+#define MI_WAIT_FOR_PLANE_A_SCANLINES (1<<1) 
+
+#define MI_LOAD_SCAN_LINES_INCL  ((0x12<<23))
+
+#endif
+
diff --git a/drivers/char/drm/i830_irq.c b/drivers/char/drm/i830_irq.c
new file mode 100644
index 0000000..6d7729f
--- /dev/null
+++ b/drivers/char/drm/i830_irq.c
@@ -0,0 +1,204 @@
+/* i830_dma.c -- DMA support for the I830 -*- linux-c -*-
+ *
+ * Copyright 2002 Tungsten Graphics, Inc.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors: Keith Whitwell <keith@tungstengraphics.com>
+ *
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "i830_drm.h"
+#include "i830_drv.h"
+#include <linux/interrupt.h>	/* For task queue support */
+#include <linux/delay.h>
+
+
+irqreturn_t i830_driver_irq_handler( DRM_IRQ_ARGS )
+{
+	drm_device_t	 *dev = (drm_device_t *)arg;
+      	drm_i830_private_t *dev_priv = (drm_i830_private_t *)dev->dev_private;
+   	u16 temp;
+
+      	temp = I830_READ16(I830REG_INT_IDENTITY_R);
+	DRM_DEBUG("%x\n", temp);
+
+   	if ( !( temp & 2 ) ) 
+		return IRQ_NONE;
+
+	I830_WRITE16(I830REG_INT_IDENTITY_R, temp); 
+
+	atomic_inc(&dev_priv->irq_received);
+	wake_up_interruptible(&dev_priv->irq_queue); 
+
+	return IRQ_HANDLED;
+}
+
+
+int i830_emit_irq(drm_device_t *dev)
+{
+	drm_i830_private_t *dev_priv = dev->dev_private;
+	RING_LOCALS;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+
+	atomic_inc(&dev_priv->irq_emitted);
+
+   	BEGIN_LP_RING(2);
+      	OUT_RING( 0 );
+      	OUT_RING( GFX_OP_USER_INTERRUPT );
+      	ADVANCE_LP_RING();
+
+	return atomic_read(&dev_priv->irq_emitted);
+}
+
+
+int i830_wait_irq(drm_device_t *dev, int irq_nr)
+{
+  	drm_i830_private_t *dev_priv = 
+	   (drm_i830_private_t *)dev->dev_private;
+	DECLARE_WAITQUEUE(entry, current);
+	unsigned long end = jiffies + HZ*3;
+	int ret = 0;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+
+ 	if (atomic_read(&dev_priv->irq_received) >= irq_nr)  
+ 		return 0; 
+
+	dev_priv->sarea_priv->perf_boxes |= I830_BOX_WAIT;
+
+	add_wait_queue(&dev_priv->irq_queue, &entry);
+
+	for (;;) {
+		__set_current_state(TASK_INTERRUPTIBLE);
+	   	if (atomic_read(&dev_priv->irq_received) >= irq_nr) 
+		   break;
+		if((signed)(end - jiffies) <= 0) {
+			DRM_ERROR("timeout iir %x imr %x ier %x hwstam %x\n",
+				  I830_READ16( I830REG_INT_IDENTITY_R ),
+				  I830_READ16( I830REG_INT_MASK_R ),
+				  I830_READ16( I830REG_INT_ENABLE_R ),
+				  I830_READ16( I830REG_HWSTAM ));
+
+		   	ret = -EBUSY;	/* Lockup?  Missed irq? */
+			break;
+		}
+	      	schedule_timeout(HZ*3);
+	      	if (signal_pending(current)) {
+		   	ret = -EINTR;
+			break;
+		}
+	}
+
+	__set_current_state(TASK_RUNNING);
+	remove_wait_queue(&dev_priv->irq_queue, &entry);
+	return ret;
+}
+
+
+/* Needs the lock as it touches the ring.
+ */
+int i830_irq_emit( struct inode *inode, struct file *filp, unsigned int cmd,
+		   unsigned long arg )
+{
+	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->head->dev;
+	drm_i830_private_t *dev_priv = dev->dev_private;
+	drm_i830_irq_emit_t emit;
+	int result;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return -EINVAL;
+	}
+
+	if (copy_from_user( &emit, (drm_i830_irq_emit_t __user *)arg, sizeof(emit) ))
+		return -EFAULT;
+
+	result = i830_emit_irq( dev );
+
+	if ( copy_to_user( emit.irq_seq, &result, sizeof(int) ) ) {
+		DRM_ERROR( "copy_to_user\n" );
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+
+/* Doesn't need the hardware lock.
+ */
+int i830_irq_wait( struct inode *inode, struct file *filp, unsigned int cmd,
+		   unsigned long arg )
+{
+	drm_file_t	  *priv	    = filp->private_data;
+	drm_device_t	  *dev	    = priv->head->dev;
+	drm_i830_private_t *dev_priv = dev->dev_private;
+	drm_i830_irq_wait_t irqwait;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return -EINVAL;
+	}
+
+	if (copy_from_user( &irqwait, (drm_i830_irq_wait_t __user *)arg, 
+			    sizeof(irqwait) ))
+		return -EFAULT;
+
+	return i830_wait_irq( dev, irqwait.irq_seq );
+}
+
+
+/* drm_dma.h hooks
+*/
+void i830_driver_irq_preinstall( drm_device_t *dev ) {
+	drm_i830_private_t *dev_priv =
+		(drm_i830_private_t *)dev->dev_private;
+
+	I830_WRITE16( I830REG_HWSTAM, 0xffff );
+	I830_WRITE16( I830REG_INT_MASK_R, 0x0 );
+	I830_WRITE16( I830REG_INT_ENABLE_R, 0x0 );
+	atomic_set(&dev_priv->irq_received, 0);
+	atomic_set(&dev_priv->irq_emitted, 0);
+	init_waitqueue_head(&dev_priv->irq_queue);
+}
+
+void i830_driver_irq_postinstall( drm_device_t *dev ) {
+	drm_i830_private_t *dev_priv =
+		(drm_i830_private_t *)dev->dev_private;
+
+	I830_WRITE16( I830REG_INT_ENABLE_R, 0x2 );
+}
+
+void i830_driver_irq_uninstall( drm_device_t *dev ) {
+	drm_i830_private_t *dev_priv =
+		(drm_i830_private_t *)dev->dev_private;
+	if (!dev_priv)
+		return;
+
+	I830_WRITE16( I830REG_INT_MASK_R, 0xffff );
+	I830_WRITE16( I830REG_INT_ENABLE_R, 0x0 );
+}
diff --git a/drivers/char/drm/i915_dma.c b/drivers/char/drm/i915_dma.c
new file mode 100644
index 0000000..7300a09
--- /dev/null
+++ b/drivers/char/drm/i915_dma.c
@@ -0,0 +1,725 @@
+/* i915_dma.c -- DMA support for the I915 -*- linux-c -*-
+ */
+/**************************************************************************
+ * 
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ * 
+ **************************************************************************/
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+drm_ioctl_desc_t i915_ioctls[] = {
+	[DRM_IOCTL_NR(DRM_I915_INIT)] = {i915_dma_init, 1, 1},
+	[DRM_IOCTL_NR(DRM_I915_FLUSH)] = {i915_flush_ioctl, 1, 0},
+	[DRM_IOCTL_NR(DRM_I915_FLIP)] = {i915_flip_bufs, 1, 0},
+	[DRM_IOCTL_NR(DRM_I915_BATCHBUFFER)] = {i915_batchbuffer, 1, 0},
+	[DRM_IOCTL_NR(DRM_I915_IRQ_EMIT)] = {i915_irq_emit, 1, 0},
+	[DRM_IOCTL_NR(DRM_I915_IRQ_WAIT)] = {i915_irq_wait, 1, 0},
+	[DRM_IOCTL_NR(DRM_I915_GETPARAM)] = {i915_getparam, 1, 0},
+	[DRM_IOCTL_NR(DRM_I915_SETPARAM)] = {i915_setparam, 1, 1},
+	[DRM_IOCTL_NR(DRM_I915_ALLOC)] = {i915_mem_alloc, 1, 0},
+	[DRM_IOCTL_NR(DRM_I915_FREE)] = {i915_mem_free, 1, 0},
+	[DRM_IOCTL_NR(DRM_I915_INIT_HEAP)] = {i915_mem_init_heap, 1, 1},
+	[DRM_IOCTL_NR(DRM_I915_CMDBUFFER)] = {i915_cmdbuffer, 1, 0}
+};
+
+int i915_max_ioctl = DRM_ARRAY_SIZE(i915_ioctls);
+
+/* Really want an OS-independent resettable timer.  Would like to have
+ * this loop run for (eg) 3 sec, but have the timer reset every time
+ * the head pointer changes, so that EBUSY only happens if the ring
+ * actually stalls for (eg) 3 seconds.
+ */
+int i915_wait_ring(drm_device_t * dev, int n, const char *caller)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_ring_buffer_t *ring = &(dev_priv->ring);
+	u32 last_head = I915_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+	int i;
+
+	for (i = 0; i < 10000; i++) {
+		ring->head = I915_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+		ring->space = ring->head - (ring->tail + 8);
+		if (ring->space < 0)
+			ring->space += ring->Size;
+		if (ring->space >= n)
+			return 0;
+
+		dev_priv->sarea_priv->perf_boxes |= I915_BOX_WAIT;
+
+		if (ring->head != last_head)
+			i = 0;
+
+		last_head = ring->head;
+	}
+
+	return DRM_ERR(EBUSY);
+}
+
+void i915_kernel_lost_context(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_ring_buffer_t *ring = &(dev_priv->ring);
+
+	ring->head = I915_READ(LP_RING + RING_HEAD) & HEAD_ADDR;
+	ring->tail = I915_READ(LP_RING + RING_TAIL) & TAIL_ADDR;
+	ring->space = ring->head - (ring->tail + 8);
+	if (ring->space < 0)
+		ring->space += ring->Size;
+
+	if (ring->head == ring->tail)
+		dev_priv->sarea_priv->perf_boxes |= I915_BOX_RING_EMPTY;
+}
+
+int i915_dma_cleanup(drm_device_t * dev)
+{
+	/* Make sure interrupts are disabled here because the uninstall ioctl
+	 * may not have been called from userspace and after dev_private
+	 * is freed, it's too late.
+	 */
+	if (dev->irq)
+		drm_irq_uninstall (dev);
+
+	if (dev->dev_private) {
+		drm_i915_private_t *dev_priv =
+		    (drm_i915_private_t *) dev->dev_private;
+
+		if (dev_priv->ring.virtual_start) {
+			drm_core_ioremapfree( &dev_priv->ring.map, dev);
+		}
+
+		if (dev_priv->hw_status_page) {
+			drm_pci_free(dev, PAGE_SIZE, dev_priv->hw_status_page,
+				     dev_priv->dma_status_page);
+			/* Need to rewrite hardware status page */
+			I915_WRITE(0x02080, 0x1ffff000);
+		}
+
+		drm_free (dev->dev_private, sizeof(drm_i915_private_t),
+			   DRM_MEM_DRIVER);
+
+		dev->dev_private = NULL;
+	}
+
+	return 0;
+}
+
+static int i915_initialize(drm_device_t * dev,
+			   drm_i915_private_t * dev_priv,
+			   drm_i915_init_t * init)
+{
+	memset(dev_priv, 0, sizeof(drm_i915_private_t));
+
+	DRM_GETSAREA();
+	if (!dev_priv->sarea) {
+		DRM_ERROR("can not find sarea!\n");
+		dev->dev_private = (void *)dev_priv;
+		i915_dma_cleanup(dev);
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->mmio_map = drm_core_findmap(dev, init->mmio_offset);
+	if (!dev_priv->mmio_map) {
+		dev->dev_private = (void *)dev_priv;
+		i915_dma_cleanup(dev);
+		DRM_ERROR("can not find mmio map!\n");
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->sarea_priv = (drm_i915_sarea_t *)
+	    ((u8 *) dev_priv->sarea->handle + init->sarea_priv_offset);
+
+	dev_priv->ring.Start = init->ring_start;
+	dev_priv->ring.End = init->ring_end;
+	dev_priv->ring.Size = init->ring_size;
+	dev_priv->ring.tail_mask = dev_priv->ring.Size - 1;
+
+	dev_priv->ring.map.offset = init->ring_start;
+	dev_priv->ring.map.size = init->ring_size;
+	dev_priv->ring.map.type = 0;
+	dev_priv->ring.map.flags = 0;
+	dev_priv->ring.map.mtrr = 0;
+
+	drm_core_ioremap( &dev_priv->ring.map, dev );
+
+	if (dev_priv->ring.map.handle == NULL) {
+		dev->dev_private = (void *)dev_priv;
+		i915_dma_cleanup(dev);
+		DRM_ERROR("can not ioremap virtual address for"
+			  " ring buffer\n");
+		return DRM_ERR(ENOMEM);
+	}
+
+	dev_priv->ring.virtual_start = dev_priv->ring.map.handle;
+
+	dev_priv->back_offset = init->back_offset;
+	dev_priv->front_offset = init->front_offset;
+	dev_priv->current_page = 0;
+	dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+
+	/* We are using separate values as placeholders for mechanisms for
+	 * private backbuffer/depthbuffer usage.
+	 */
+	dev_priv->use_mi_batchbuffer_start = 0;
+
+	/* Allow hardware batchbuffers unless told otherwise.
+	 */
+	dev_priv->allow_batchbuffer = 1;
+
+	/* Program Hardware Status Page */
+	dev_priv->hw_status_page = drm_pci_alloc(dev, PAGE_SIZE, PAGE_SIZE,
+						 0xffffffff, 
+						 &dev_priv->dma_status_page);
+
+	if (!dev_priv->hw_status_page) {
+		dev->dev_private = (void *)dev_priv;
+		i915_dma_cleanup(dev);
+		DRM_ERROR("Can not allocate hardware status page\n");
+		return DRM_ERR(ENOMEM);
+	}
+	memset(dev_priv->hw_status_page, 0, PAGE_SIZE);
+	DRM_DEBUG("hw status page @ %p\n", dev_priv->hw_status_page);
+
+	I915_WRITE(0x02080, dev_priv->dma_status_page);
+	DRM_DEBUG("Enabled hardware status page\n");
+
+	dev->dev_private = (void *)dev_priv;
+
+	return 0;
+}
+
+static int i915_resume(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+
+	if (!dev_priv->sarea) {
+		DRM_ERROR("can not find sarea!\n");
+		return DRM_ERR(EINVAL);
+	}
+
+	if (!dev_priv->mmio_map) {
+		DRM_ERROR("can not find mmio map!\n");
+		return DRM_ERR(EINVAL);
+	}
+
+	if (dev_priv->ring.map.handle == NULL) {
+		DRM_ERROR("can not ioremap virtual address for"
+			  " ring buffer\n");
+		return DRM_ERR(ENOMEM);
+	}
+
+	/* Program Hardware Status Page */
+	if (!dev_priv->hw_status_page) {
+		DRM_ERROR("Can not find hardware status page\n");
+		return DRM_ERR(EINVAL);
+	}
+	DRM_DEBUG("hw status page @ %p\n", dev_priv->hw_status_page);
+
+	I915_WRITE(0x02080, dev_priv->dma_status_page);
+	DRM_DEBUG("Enabled hardware status page\n");
+
+	return 0;
+}
+
+int i915_dma_init(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv;
+	drm_i915_init_t init;
+	int retcode = 0;
+
+	DRM_COPY_FROM_USER_IOCTL(init, (drm_i915_init_t __user *) data,
+				 sizeof(init));
+
+	switch (init.func) {
+	case I915_INIT_DMA:
+		dev_priv = drm_alloc (sizeof(drm_i915_private_t),
+				       DRM_MEM_DRIVER);
+		if (dev_priv == NULL)
+			return DRM_ERR(ENOMEM);
+		retcode = i915_initialize(dev, dev_priv, &init);
+		break;
+	case I915_CLEANUP_DMA:
+		retcode = i915_dma_cleanup(dev);
+		break;
+	case I915_RESUME_DMA:
+		retcode = i915_resume(dev);
+		break;
+	default:
+		retcode = -EINVAL;
+		break;
+	}
+
+	return retcode;
+}
+
+/* Implement basically the same security restrictions as hardware does
+ * for MI_BATCH_NON_SECURE.  These can be made stricter at any time.
+ *
+ * Most of the calculations below involve calculating the size of a
+ * particular instruction.  It's important to get the size right as
+ * that tells us where the next instruction to check is.  Any illegal
+ * instruction detected will be given a size of zero, which is a
+ * signal to abort the rest of the buffer.
+ */
+static int do_validate_cmd(int cmd)
+{
+	switch (((cmd >> 29) & 0x7)) {
+	case 0x0:
+		switch ((cmd >> 23) & 0x3f) {
+		case 0x0:
+			return 1;	/* MI_NOOP */
+		case 0x4:
+			return 1;	/* MI_FLUSH */
+		default:
+			return 0;	/* disallow everything else */
+		}
+		break;
+	case 0x1:
+		return 0;	/* reserved */
+	case 0x2:
+		return (cmd & 0xff) + 2;	/* 2d commands */
+	case 0x3:
+		if (((cmd >> 24) & 0x1f) <= 0x18)
+			return 1;
+
+		switch ((cmd >> 24) & 0x1f) {
+		case 0x1c:
+			return 1;
+		case 0x1d:
+			switch ((cmd>>16)&0xff) {
+			case 0x3:
+				return (cmd & 0x1f) + 2;
+			case 0x4:
+				return (cmd & 0xf) + 2;
+			default:
+				return (cmd & 0xffff) + 2;
+			}
+		case 0x1e:
+			if (cmd & (1 << 23))
+				return (cmd & 0xffff) + 1;
+			else
+				return 1;
+		case 0x1f:
+			if ((cmd & (1 << 23)) == 0)	/* inline vertices */
+				return (cmd & 0x1ffff) + 2;
+			else if (cmd & (1 << 17))	/* indirect random */
+				if ((cmd & 0xffff) == 0)
+					return 0;	/* unknown length, too hard */
+				else
+					return (((cmd & 0xffff) + 1) / 2) + 1;
+			else
+				return 2;	/* indirect sequential */
+		default:
+			return 0;
+		}
+	default:
+		return 0;
+	}
+
+	return 0;
+}
+
+static int validate_cmd(int cmd)
+{
+	int ret = do_validate_cmd(cmd);
+
+/* 	printk("validate_cmd( %x ): %d\n", cmd, ret); */
+
+	return ret;
+}
+
+static int i915_emit_cmds(drm_device_t * dev, int __user * buffer, int dwords)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	int i;
+	RING_LOCALS;
+
+	for (i = 0; i < dwords;) {
+		int cmd, sz;
+
+		if (DRM_COPY_FROM_USER_UNCHECKED(&cmd, &buffer[i], sizeof(cmd)))
+			return DRM_ERR(EINVAL);
+
+/* 		printk("%d/%d ", i, dwords); */
+
+		if ((sz = validate_cmd(cmd)) == 0 || i + sz > dwords)
+			return DRM_ERR(EINVAL);
+
+		BEGIN_LP_RING(sz);
+		OUT_RING(cmd);
+
+		while (++i, --sz) {
+			if (DRM_COPY_FROM_USER_UNCHECKED(&cmd, &buffer[i],
+							 sizeof(cmd))) {
+				return DRM_ERR(EINVAL);
+			}
+			OUT_RING(cmd);
+		}
+		ADVANCE_LP_RING();
+	}
+
+	return 0;
+}
+
+static int i915_emit_box(drm_device_t * dev,
+			 drm_clip_rect_t __user * boxes,
+			 int i, int DR1, int DR4)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_clip_rect_t box;
+	RING_LOCALS;
+
+	if (DRM_COPY_FROM_USER_UNCHECKED(&box, &boxes[i], sizeof(box))) {
+		return EFAULT;
+	}
+
+	if (box.y2 <= box.y1 || box.x2 <= box.x1 || box.y2 <= 0 || box.x2 <= 0) {
+		DRM_ERROR("Bad box %d,%d..%d,%d\n",
+			  box.x1, box.y1, box.x2, box.y2);
+		return DRM_ERR(EINVAL);
+	}
+
+	BEGIN_LP_RING(6);
+	OUT_RING(GFX_OP_DRAWRECT_INFO);
+	OUT_RING(DR1);
+	OUT_RING((box.x1 & 0xffff) | (box.y1 << 16));
+	OUT_RING(((box.x2 - 1) & 0xffff) | ((box.y2 - 1) << 16));
+	OUT_RING(DR4);
+	OUT_RING(0);
+	ADVANCE_LP_RING();
+
+	return 0;
+}
+
+static int i915_dispatch_cmdbuffer(drm_device_t * dev,
+				   drm_i915_cmdbuffer_t * cmd)
+{
+	int nbox = cmd->num_cliprects;
+	int i = 0, count, ret;
+
+	if (cmd->sz & 0x3) {
+		DRM_ERROR("alignment");
+		return DRM_ERR(EINVAL);
+	}
+
+	i915_kernel_lost_context(dev);
+
+	count = nbox ? nbox : 1;
+
+	for (i = 0; i < count; i++) {
+		if (i < nbox) {
+			ret = i915_emit_box(dev, cmd->cliprects, i,
+					    cmd->DR1, cmd->DR4);
+			if (ret)
+				return ret;
+		}
+
+		ret = i915_emit_cmds(dev, (int __user *)cmd->buf, cmd->sz / 4);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int i915_dispatch_batchbuffer(drm_device_t * dev,
+				     drm_i915_batchbuffer_t * batch)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_clip_rect_t __user *boxes = batch->cliprects;
+	int nbox = batch->num_cliprects;
+	int i = 0, count;
+	RING_LOCALS;
+
+	if ((batch->start | batch->used) & 0x7) {
+		DRM_ERROR("alignment");
+		return DRM_ERR(EINVAL);
+	}
+
+	i915_kernel_lost_context(dev);
+
+	count = nbox ? nbox : 1;
+
+	for (i = 0; i < count; i++) {
+		if (i < nbox) {
+			int ret = i915_emit_box(dev, boxes, i,
+						batch->DR1, batch->DR4);
+			if (ret)
+				return ret;
+		}
+
+		if (dev_priv->use_mi_batchbuffer_start) {
+			BEGIN_LP_RING(2);
+			OUT_RING(MI_BATCH_BUFFER_START | (2 << 6));
+			OUT_RING(batch->start | MI_BATCH_NON_SECURE);
+			ADVANCE_LP_RING();
+		} else {
+			BEGIN_LP_RING(4);
+			OUT_RING(MI_BATCH_BUFFER);
+			OUT_RING(batch->start | MI_BATCH_NON_SECURE);
+			OUT_RING(batch->start + batch->used - 4);
+			OUT_RING(0);
+			ADVANCE_LP_RING();
+		}
+	}
+
+	dev_priv->sarea_priv->last_enqueue = dev_priv->counter++;
+
+	BEGIN_LP_RING(4);
+	OUT_RING(CMD_STORE_DWORD_IDX);
+	OUT_RING(20);
+	OUT_RING(dev_priv->counter);
+	OUT_RING(0);
+	ADVANCE_LP_RING();
+
+	return 0;
+}
+
+static int i915_dispatch_flip(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	RING_LOCALS;
+
+	DRM_DEBUG("%s: page=%d pfCurrentPage=%d\n",
+		  __FUNCTION__,
+		  dev_priv->current_page,
+		  dev_priv->sarea_priv->pf_current_page);
+
+	i915_kernel_lost_context(dev);
+
+	BEGIN_LP_RING(2);
+	OUT_RING(INST_PARSER_CLIENT | INST_OP_FLUSH | INST_FLUSH_MAP_CACHE);
+	OUT_RING(0);
+	ADVANCE_LP_RING();
+
+	BEGIN_LP_RING(6);
+	OUT_RING(CMD_OP_DISPLAYBUFFER_INFO | ASYNC_FLIP);
+	OUT_RING(0);
+	if (dev_priv->current_page == 0) {
+		OUT_RING(dev_priv->back_offset);
+		dev_priv->current_page = 1;
+	} else {
+		OUT_RING(dev_priv->front_offset);
+		dev_priv->current_page = 0;
+	}
+	OUT_RING(0);
+	ADVANCE_LP_RING();
+
+	BEGIN_LP_RING(2);
+	OUT_RING(MI_WAIT_FOR_EVENT | MI_WAIT_FOR_PLANE_A_FLIP);
+	OUT_RING(0);
+	ADVANCE_LP_RING();
+
+	dev_priv->sarea_priv->last_enqueue = dev_priv->counter++;
+
+	BEGIN_LP_RING(4);
+	OUT_RING(CMD_STORE_DWORD_IDX);
+	OUT_RING(20);
+	OUT_RING(dev_priv->counter);
+	OUT_RING(0);
+	ADVANCE_LP_RING();
+
+	dev_priv->sarea_priv->pf_current_page = dev_priv->current_page;
+	return 0;
+}
+
+static int i915_quiescent(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+
+	i915_kernel_lost_context(dev);
+	return i915_wait_ring(dev, dev_priv->ring.Size - 8, __FUNCTION__);
+}
+
+int i915_flush_ioctl(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	return i915_quiescent(dev);
+}
+
+int i915_batchbuffer(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+	u32 *hw_status = dev_priv->hw_status_page;
+	drm_i915_sarea_t *sarea_priv = (drm_i915_sarea_t *)
+	    dev_priv->sarea_priv;
+	drm_i915_batchbuffer_t batch;
+	int ret;
+
+	if (!dev_priv->allow_batchbuffer) {
+		DRM_ERROR("Batchbuffer ioctl disabled\n");
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(batch, (drm_i915_batchbuffer_t __user *) data,
+				 sizeof(batch));
+
+	DRM_DEBUG("i915 batchbuffer, start %x used %d cliprects %d\n",
+		  batch.start, batch.used, batch.num_cliprects);
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	if (batch.num_cliprects && DRM_VERIFYAREA_READ(batch.cliprects,
+						       batch.num_cliprects *
+						       sizeof(drm_clip_rect_t)))
+		return DRM_ERR(EFAULT);
+
+	ret = i915_dispatch_batchbuffer(dev, &batch);
+
+	sarea_priv->last_dispatch = (int)hw_status[5];
+	return ret;
+}
+
+int i915_cmdbuffer(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+	u32 *hw_status = dev_priv->hw_status_page;
+	drm_i915_sarea_t *sarea_priv = (drm_i915_sarea_t *)
+	    dev_priv->sarea_priv;
+	drm_i915_cmdbuffer_t cmdbuf;
+	int ret;
+
+	DRM_COPY_FROM_USER_IOCTL(cmdbuf, (drm_i915_cmdbuffer_t __user *) data,
+				 sizeof(cmdbuf));
+
+	DRM_DEBUG("i915 cmdbuffer, buf %p sz %d cliprects %d\n",
+		  cmdbuf.buf, cmdbuf.sz, cmdbuf.num_cliprects);
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	if (cmdbuf.num_cliprects &&
+	    DRM_VERIFYAREA_READ(cmdbuf.cliprects,
+				cmdbuf.num_cliprects *
+				sizeof(drm_clip_rect_t))) {
+		DRM_ERROR("Fault accessing cliprects\n");
+		return DRM_ERR(EFAULT);
+	}
+
+	ret = i915_dispatch_cmdbuffer(dev, &cmdbuf);
+	if (ret) {
+		DRM_ERROR("i915_dispatch_cmdbuffer failed\n");
+		return ret;
+	}
+
+	sarea_priv->last_dispatch = (int)hw_status[5];
+	return 0;
+}
+
+int i915_do_cleanup_pageflip(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+	if (dev_priv->current_page != 0)
+		i915_dispatch_flip(dev);
+
+	return 0;
+}
+
+int i915_flip_bufs(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	return i915_dispatch_flip(dev);
+}
+
+int i915_getparam(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_getparam_t param;
+	int value;
+
+	if (!dev_priv) {
+		DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(param, (drm_i915_getparam_t __user *) data,
+				 sizeof(param));
+
+	switch (param.param) {
+	case I915_PARAM_IRQ_ACTIVE:
+		value = dev->irq ? 1 : 0;
+		break;
+	case I915_PARAM_ALLOW_BATCHBUFFER:
+		value = dev_priv->allow_batchbuffer ? 1 : 0;
+		break;
+	default:
+		DRM_ERROR("Unkown parameter %d\n", param.param);
+		return DRM_ERR(EINVAL);
+	}
+
+	if (DRM_COPY_TO_USER(param.value, &value, sizeof(int))) {
+		DRM_ERROR("DRM_COPY_TO_USER failed\n");
+		return DRM_ERR(EFAULT);
+	}
+
+	return 0;
+}
+
+int i915_setparam(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_setparam_t param;
+
+	if (!dev_priv) {
+		DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(param, (drm_i915_setparam_t __user *) data,
+				 sizeof(param));
+
+	switch (param.param) {
+	case I915_SETPARAM_USE_MI_BATCHBUFFER_START:
+		dev_priv->use_mi_batchbuffer_start = param.value;
+		break;
+	case I915_SETPARAM_TEX_LRU_LOG_GRANULARITY:
+		dev_priv->tex_lru_log_granularity = param.value;
+		break;
+	case I915_SETPARAM_ALLOW_BATCHBUFFER:
+		dev_priv->allow_batchbuffer = param.value;
+		break;
+	default:
+		DRM_ERROR("unknown parameter %d\n", param.param);
+		return DRM_ERR(EINVAL);
+	}
+
+	return 0;
+}
+
+void i915_driver_pretakedown(drm_device_t *dev)
+{
+	if ( dev->dev_private ) {
+		drm_i915_private_t *dev_priv = dev->dev_private;
+	        i915_mem_takedown( &(dev_priv->agp_heap) );
+ 	}
+	i915_dma_cleanup( dev );
+}
+
+void i915_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+	if ( dev->dev_private ) {
+		drm_i915_private_t *dev_priv = dev->dev_private;
+                i915_mem_release( dev, filp, dev_priv->agp_heap );
+	}
+}
+
diff --git a/drivers/char/drm/i915_drm.h b/drivers/char/drm/i915_drm.h
new file mode 100644
index 0000000..7e55edf
--- /dev/null
+++ b/drivers/char/drm/i915_drm.h
@@ -0,0 +1,167 @@
+#ifndef _I915_DRM_H_
+#define _I915_DRM_H_
+
+/* Please note that modifications to all structs defined here are
+ * subject to backwards-compatibility constraints.
+ */
+
+#include "drm.h"
+
+/* Each region is a minimum of 16k, and there are at most 255 of them.
+ */
+#define I915_NR_TEX_REGIONS 255	/* table size 2k - maximum due to use
+				 * of chars for next/prev indices */
+#define I915_LOG_MIN_TEX_REGION_SIZE 14
+
+typedef struct _drm_i915_init {
+	enum {
+		I915_INIT_DMA = 0x01,
+		I915_CLEANUP_DMA = 0x02,
+		I915_RESUME_DMA = 0x03
+	} func;
+	unsigned int mmio_offset;
+	int sarea_priv_offset;
+	unsigned int ring_start;
+	unsigned int ring_end;
+	unsigned int ring_size;
+	unsigned int front_offset;
+	unsigned int back_offset;
+	unsigned int depth_offset;
+	unsigned int w;
+	unsigned int h;
+	unsigned int pitch;
+	unsigned int pitch_bits;
+	unsigned int back_pitch;
+	unsigned int depth_pitch;
+	unsigned int cpp;
+	unsigned int chipset;
+} drm_i915_init_t;
+
+typedef struct _drm_i915_sarea {
+	drm_tex_region_t texList[I915_NR_TEX_REGIONS + 1];
+	int last_upload;	/* last time texture was uploaded */
+	int last_enqueue;	/* last time a buffer was enqueued */
+	int last_dispatch;	/* age of the most recently dispatched buffer */
+	int ctxOwner;		/* last context to upload state */
+	int texAge;
+	int pf_enabled;		/* is pageflipping allowed? */
+	int pf_active;
+	int pf_current_page;	/* which buffer is being displayed? */
+	int perf_boxes;		/* performance boxes to be displayed */
+} drm_i915_sarea_t;
+
+/* Flags for perf_boxes
+ */
+#define I915_BOX_RING_EMPTY    0x1
+#define I915_BOX_FLIP          0x2
+#define I915_BOX_WAIT          0x4
+#define I915_BOX_TEXTURE_LOAD  0x8
+#define I915_BOX_LOST_CONTEXT  0x10
+
+/* I915 specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_I915_INIT		0x00
+#define DRM_I915_FLUSH		0x01
+#define DRM_I915_FLIP		0x02
+#define DRM_I915_BATCHBUFFER	0x03
+#define DRM_I915_IRQ_EMIT	0x04
+#define DRM_I915_IRQ_WAIT	0x05
+#define DRM_I915_GETPARAM	0x06
+#define DRM_I915_SETPARAM	0x07
+#define DRM_I915_ALLOC		0x08
+#define DRM_I915_FREE		0x09
+#define DRM_I915_INIT_HEAP	0x0a
+#define DRM_I915_CMDBUFFER	0x0b
+
+#define DRM_IOCTL_I915_INIT		DRM_IOW( DRM_COMMAND_BASE + DRM_I915_INIT, drm_i915_init_t)
+#define DRM_IOCTL_I915_FLUSH		DRM_IO ( DRM_COMMAND_BASE + DRM_I915_FLUSH)
+#define DRM_IOCTL_I915_FLIP		DRM_IO ( DRM_COMMAND_BASE + DRM_I915_FLIP)
+#define DRM_IOCTL_I915_BATCHBUFFER	DRM_IOW( DRM_COMMAND_BASE + DRM_I915_BATCHBUFFER, drm_i915_batchbuffer_t)
+#define DRM_IOCTL_I915_IRQ_EMIT         DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_IRQ_EMIT, drm_i915_irq_emit_t)
+#define DRM_IOCTL_I915_IRQ_WAIT         DRM_IOW( DRM_COMMAND_BASE + DRM_I915_IRQ_WAIT, drm_i915_irq_wait_t)
+#define DRM_IOCTL_I915_GETPARAM         DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GETPARAM, drm_i915_getparam_t)
+#define DRM_IOCTL_I915_SETPARAM         DRM_IOW( DRM_COMMAND_BASE + DRM_I915_SETPARAM, drm_i915_setparam_t)
+#define DRM_IOCTL_I915_ALLOC            DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_ALLOC, drm_i915_mem_alloc_t)
+#define DRM_IOCTL_I915_FREE             DRM_IOW( DRM_COMMAND_BASE + DRM_I915_FREE, drm_i915_mem_free_t)
+#define DRM_IOCTL_I915_INIT_HEAP        DRM_IOW( DRM_COMMAND_BASE + DRM_I915_INIT_HEAP, drm_i915_mem_init_heap_t)
+#define DRM_IOCTL_I915_CMDBUFFER	DRM_IOW( DRM_COMMAND_BASE + DRM_I915_CMDBUFFER, drm_i915_cmdbuffer_t)
+
+/* Allow drivers to submit batchbuffers directly to hardware, relying
+ * on the security mechanisms provided by hardware.
+ */
+typedef struct _drm_i915_batchbuffer {
+	int start;		/* agp offset */
+	int used;		/* nr bytes in use */
+	int DR1;		/* hw flags for GFX_OP_DRAWRECT_INFO */
+	int DR4;		/* window origin for GFX_OP_DRAWRECT_INFO */
+	int num_cliprects;	/* mulitpass with multiple cliprects? */
+	drm_clip_rect_t __user *cliprects;	/* pointer to userspace cliprects */
+} drm_i915_batchbuffer_t;
+
+/* As above, but pass a pointer to userspace buffer which can be
+ * validated by the kernel prior to sending to hardware.
+ */
+typedef struct _drm_i915_cmdbuffer {
+	char __user *buf;	/* pointer to userspace command buffer */
+	int sz;			/* nr bytes in buf */
+	int DR1;		/* hw flags for GFX_OP_DRAWRECT_INFO */
+	int DR4;		/* window origin for GFX_OP_DRAWRECT_INFO */
+	int num_cliprects;	/* mulitpass with multiple cliprects? */
+	drm_clip_rect_t __user *cliprects;	/* pointer to userspace cliprects */
+} drm_i915_cmdbuffer_t;
+
+/* Userspace can request & wait on irq's:
+ */
+typedef struct drm_i915_irq_emit {
+	int __user *irq_seq;
+} drm_i915_irq_emit_t;
+
+typedef struct drm_i915_irq_wait {
+	int irq_seq;
+} drm_i915_irq_wait_t;
+
+/* Ioctl to query kernel params:
+ */
+#define I915_PARAM_IRQ_ACTIVE            1
+#define I915_PARAM_ALLOW_BATCHBUFFER     2
+
+typedef struct drm_i915_getparam {
+	int param;
+	int __user *value;
+} drm_i915_getparam_t;
+
+/* Ioctl to set kernel params:
+ */
+#define I915_SETPARAM_USE_MI_BATCHBUFFER_START            1
+#define I915_SETPARAM_TEX_LRU_LOG_GRANULARITY             2
+#define I915_SETPARAM_ALLOW_BATCHBUFFER                   3
+
+typedef struct drm_i915_setparam {
+	int param;
+	int value;
+} drm_i915_setparam_t;
+
+/* A memory manager for regions of shared memory:
+ */
+#define I915_MEM_REGION_AGP 1
+
+typedef struct drm_i915_mem_alloc {
+	int region;
+	int alignment;
+	int size;
+	int __user *region_offset;	/* offset from start of fb or agp */
+} drm_i915_mem_alloc_t;
+
+typedef struct drm_i915_mem_free {
+	int region;
+	int region_offset;
+} drm_i915_mem_free_t;
+
+typedef struct drm_i915_mem_init_heap {
+	int region;
+	int size;
+	int start;
+} drm_i915_mem_init_heap_t;
+
+#endif				/* _I915_DRM_H_ */
diff --git a/drivers/char/drm/i915_drv.c b/drivers/char/drm/i915_drv.c
new file mode 100644
index 0000000..002b708
--- /dev/null
+++ b/drivers/char/drm/i915_drv.c
@@ -0,0 +1,104 @@
+/* i915_drv.c -- i830,i845,i855,i865,i915 driver -*- linux-c -*-
+ */
+
+/**************************************************************************
+ * 
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ * 
+ **************************************************************************/
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+#include "drm_pciids.h"
+
+int postinit( struct drm_device *dev, unsigned long flags )
+{
+	dev->counters += 4;
+	dev->types[6] = _DRM_STAT_IRQ;
+	dev->types[7] = _DRM_STAT_PRIMARY;
+	dev->types[8] = _DRM_STAT_SECONDARY;
+	dev->types[9] = _DRM_STAT_DMA;
+	
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->primary.minor,
+		pci_pretty_name(dev->pdev)
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+	i915_PCI_IDS
+};
+
+extern drm_ioctl_desc_t i915_ioctls[];
+extern int i915_max_ioctl;
+
+static struct drm_driver driver = {
+	.driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | DRIVER_USE_MTRR |
+				DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED,
+	.pretakedown = i915_driver_pretakedown,
+	.prerelease = i915_driver_prerelease,
+	.irq_preinstall = i915_driver_irq_preinstall,
+	.irq_postinstall = i915_driver_irq_postinstall,
+	.irq_uninstall = i915_driver_irq_uninstall,
+	.irq_handler = i915_driver_irq_handler,
+	.reclaim_buffers = drm_core_reclaim_buffers,
+	.get_map_ofs = drm_core_get_map_ofs,
+	.get_reg_ofs = drm_core_get_reg_ofs,
+	.postinit = postinit,
+	.version = version,
+	.ioctls = i915_ioctls,
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+	.pci_driver = {
+		.name          = DRIVER_NAME,
+		.id_table      = pciidlist,
+	}
+};
+
+static int __init i915_init(void)
+{
+	driver.num_ioctls = i915_max_ioctl;
+	return drm_init(&driver);
+}
+
+static void __exit i915_exit(void)
+{
+	drm_exit(&driver);
+}
+
+module_init(i915_init);
+module_exit(i915_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/i915_drv.h b/drivers/char/drm/i915_drv.h
new file mode 100644
index 0000000..f6ca92a
--- /dev/null
+++ b/drivers/char/drm/i915_drv.h
@@ -0,0 +1,243 @@
+/* i915_drv.h -- Private header for the I915 driver -*- linux-c -*-
+ */
+/**************************************************************************
+ * 
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ * 
+ **************************************************************************/
+
+#ifndef _I915_DRV_H_
+#define _I915_DRV_H_
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR		"Tungsten Graphics, Inc."
+
+#define DRIVER_NAME		"i915"
+#define DRIVER_DESC		"Intel Graphics"
+#define DRIVER_DATE		"20040405"
+
+/* Interface history:
+ *
+ * 1.1: Original.
+ */
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		1
+#define DRIVER_PATCHLEVEL	0
+
+/* We use our own dma mechanisms, not the drm template code.  However,
+ * the shared IRQ code is useful to us:
+ */
+#define __HAVE_PM		1
+
+typedef struct _drm_i915_ring_buffer {
+	int tail_mask;
+	unsigned long Start;
+	unsigned long End;
+	unsigned long Size;
+	u8 *virtual_start;
+	int head;
+	int tail;
+	int space;
+	drm_local_map_t map;
+} drm_i915_ring_buffer_t;
+
+struct mem_block {
+	struct mem_block *next;
+	struct mem_block *prev;
+	int start;
+	int size;
+	DRMFILE filp;		/* 0: free, -1: heap, other: real files */
+};
+
+typedef struct drm_i915_private {
+	drm_local_map_t *sarea;
+	drm_local_map_t *mmio_map;
+
+	drm_i915_sarea_t *sarea_priv;
+	drm_i915_ring_buffer_t ring;
+
+	void *hw_status_page;
+	unsigned long counter;
+	dma_addr_t dma_status_page;
+
+	int back_offset;
+	int front_offset;
+	int current_page;
+	int page_flipping;
+	int use_mi_batchbuffer_start;
+
+	wait_queue_head_t irq_queue;
+	atomic_t irq_received;
+	atomic_t irq_emitted;
+
+	int tex_lru_log_granularity;
+	int allow_batchbuffer;
+	struct mem_block *agp_heap;
+} drm_i915_private_t;
+
+				/* i915_dma.c */
+extern int i915_dma_init(DRM_IOCTL_ARGS);
+extern int i915_dma_cleanup(drm_device_t * dev);
+extern int i915_flush_ioctl(DRM_IOCTL_ARGS);
+extern int i915_batchbuffer(DRM_IOCTL_ARGS);
+extern int i915_flip_bufs(DRM_IOCTL_ARGS);
+extern int i915_getparam(DRM_IOCTL_ARGS);
+extern int i915_setparam(DRM_IOCTL_ARGS);
+extern int i915_cmdbuffer(DRM_IOCTL_ARGS);
+extern void i915_kernel_lost_context(drm_device_t * dev);
+extern void i915_driver_pretakedown(drm_device_t *dev);
+extern void i915_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+
+/* i915_irq.c */
+extern int i915_irq_emit(DRM_IOCTL_ARGS);
+extern int i915_irq_wait(DRM_IOCTL_ARGS);
+extern int i915_wait_irq(drm_device_t * dev, int irq_nr);
+extern int i915_emit_irq(drm_device_t * dev);
+
+extern irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS);
+extern void i915_driver_irq_preinstall(drm_device_t *dev);
+extern void i915_driver_irq_postinstall(drm_device_t *dev);
+extern void i915_driver_irq_uninstall(drm_device_t *dev);
+
+/* i915_mem.c */
+extern int i915_mem_alloc(DRM_IOCTL_ARGS);
+extern int i915_mem_free(DRM_IOCTL_ARGS);
+extern int i915_mem_init_heap(DRM_IOCTL_ARGS);
+extern void i915_mem_takedown(struct mem_block **heap);
+extern void i915_mem_release(drm_device_t * dev,
+			     DRMFILE filp, struct mem_block *heap);
+
+#define I915_READ(reg)          DRM_READ32(dev_priv->mmio_map, reg)
+#define I915_WRITE(reg,val)     DRM_WRITE32(dev_priv->mmio_map, reg, val)
+#define I915_READ16(reg) 	DRM_READ16(dev_priv->mmio_map, reg)
+#define I915_WRITE16(reg,val)	DRM_WRITE16(dev_priv->mmio_map, reg, val)
+
+#define I915_VERBOSE 0
+
+#define RING_LOCALS	unsigned int outring, ringmask, outcount; \
+                        volatile char *virt;
+
+#define BEGIN_LP_RING(n) do {				\
+	if (I915_VERBOSE)				\
+		DRM_DEBUG("BEGIN_LP_RING(%d) in %s\n",	\
+			  n, __FUNCTION__);		\
+	if (dev_priv->ring.space < n*4)			\
+		i915_wait_ring(dev, n*4, __FUNCTION__);		\
+	outcount = 0;					\
+	outring = dev_priv->ring.tail;			\
+	ringmask = dev_priv->ring.tail_mask;		\
+	virt = dev_priv->ring.virtual_start;		\
+} while (0)
+
+#define OUT_RING(n) do {					\
+	if (I915_VERBOSE) DRM_DEBUG("   OUT_RING %x\n", (int)(n));	\
+	*(volatile unsigned int *)(virt + outring) = n;		\
+        outcount++;						\
+	outring += 4;						\
+	outring &= ringmask;					\
+} while (0)
+
+#define ADVANCE_LP_RING() do {						\
+	if (I915_VERBOSE) DRM_DEBUG("ADVANCE_LP_RING %x\n", outring);	\
+	dev_priv->ring.tail = outring;					\
+	dev_priv->ring.space -= outcount * 4;				\
+	I915_WRITE(LP_RING + RING_TAIL, outring);			\
+} while(0)
+
+extern int i915_wait_ring(drm_device_t * dev, int n, const char *caller);
+
+#define GFX_OP_USER_INTERRUPT 		((0<<29)|(2<<23))
+#define GFX_OP_BREAKPOINT_INTERRUPT	((0<<29)|(1<<23))
+#define CMD_REPORT_HEAD			(7<<23)
+#define CMD_STORE_DWORD_IDX		((0x21<<23) | 0x1)
+#define CMD_OP_BATCH_BUFFER  ((0x0<<29)|(0x30<<23)|0x1)
+
+#define INST_PARSER_CLIENT   0x00000000
+#define INST_OP_FLUSH        0x02000000
+#define INST_FLUSH_MAP_CACHE 0x00000001
+
+#define BB1_START_ADDR_MASK   (~0x7)
+#define BB1_PROTECTED         (1<<0)
+#define BB1_UNPROTECTED       (0<<0)
+#define BB2_END_ADDR_MASK     (~0x7)
+
+#define I915REG_HWSTAM		0x02098
+#define I915REG_INT_IDENTITY_R	0x020a4
+#define I915REG_INT_MASK_R 	0x020a8
+#define I915REG_INT_ENABLE_R	0x020a0
+
+#define SRX_INDEX		0x3c4
+#define SRX_DATA		0x3c5
+#define SR01			1
+#define SR01_SCREEN_OFF 	(1<<5)
+
+#define PPCR			0x61204
+#define PPCR_ON			(1<<0)
+
+#define ADPA			0x61100
+#define ADPA_DPMS_MASK		(~(3<<10))
+#define ADPA_DPMS_ON		(0<<10)
+#define ADPA_DPMS_SUSPEND	(1<<10)
+#define ADPA_DPMS_STANDBY	(2<<10)
+#define ADPA_DPMS_OFF		(3<<10)
+
+#define NOPID                   0x2094
+#define LP_RING     		0x2030
+#define HP_RING     		0x2040
+#define RING_TAIL      		0x00
+#define TAIL_ADDR		0x001FFFF8
+#define RING_HEAD      		0x04
+#define HEAD_WRAP_COUNT     	0xFFE00000
+#define HEAD_WRAP_ONE       	0x00200000
+#define HEAD_ADDR           	0x001FFFFC
+#define RING_START     		0x08
+#define START_ADDR          	0x0xFFFFF000
+#define RING_LEN       		0x0C
+#define RING_NR_PAGES       	0x001FF000
+#define RING_REPORT_MASK    	0x00000006
+#define RING_REPORT_64K     	0x00000002
+#define RING_REPORT_128K    	0x00000004
+#define RING_NO_REPORT      	0x00000000
+#define RING_VALID_MASK     	0x00000001
+#define RING_VALID          	0x00000001
+#define RING_INVALID        	0x00000000
+
+#define GFX_OP_SCISSOR         ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define SC_UPDATE_SCISSOR       (0x1<<1)
+#define SC_ENABLE_MASK          (0x1<<0)
+#define SC_ENABLE               (0x1<<0)
+
+#define GFX_OP_SCISSOR_INFO    ((0x3<<29)|(0x1d<<24)|(0x81<<16)|(0x1))
+#define SCI_YMIN_MASK      (0xffff<<16)
+#define SCI_XMIN_MASK      (0xffff<<0)
+#define SCI_YMAX_MASK      (0xffff<<16)
+#define SCI_XMAX_MASK      (0xffff<<0)
+
+#define GFX_OP_SCISSOR_ENABLE	 ((0x3<<29)|(0x1c<<24)|(0x10<<19))
+#define GFX_OP_SCISSOR_RECT	 ((0x3<<29)|(0x1d<<24)|(0x81<<16)|1)
+#define GFX_OP_COLOR_FACTOR      ((0x3<<29)|(0x1d<<24)|(0x1<<16)|0x0)
+#define GFX_OP_STIPPLE           ((0x3<<29)|(0x1d<<24)|(0x83<<16))
+#define GFX_OP_MAP_INFO          ((0x3<<29)|(0x1d<<24)|0x4)
+#define GFX_OP_DESTBUFFER_VARS   ((0x3<<29)|(0x1d<<24)|(0x85<<16)|0x0)
+#define GFX_OP_DRAWRECT_INFO     ((0x3<<29)|(0x1d<<24)|(0x80<<16)|(0x3))
+
+#define MI_BATCH_BUFFER 	((0x30<<23)|1)
+#define MI_BATCH_BUFFER_START 	(0x31<<23)
+#define MI_BATCH_BUFFER_END 	(0xA<<23)
+#define MI_BATCH_NON_SECURE	(1)
+
+#define MI_WAIT_FOR_EVENT       ((0x3<<23))
+#define MI_WAIT_FOR_PLANE_A_FLIP      (1<<2)
+#define MI_WAIT_FOR_PLANE_A_SCANLINES (1<<1)
+
+#define MI_LOAD_SCAN_LINES_INCL  ((0x12<<23))
+
+#define CMD_OP_DISPLAYBUFFER_INFO ((0x0<<29)|(0x14<<23)|2)
+#define ASYNC_FLIP                (1<<22)
+
+#define CMD_OP_DESTBUFFER_INFO	 ((0x3<<29)|(0x1d<<24)|(0x8e<<16)|1)
+
+#endif
diff --git a/drivers/char/drm/i915_irq.c b/drivers/char/drm/i915_irq.c
new file mode 100644
index 0000000..b023926
--- /dev/null
+++ b/drivers/char/drm/i915_irq.c
@@ -0,0 +1,161 @@
+/* i915_dma.c -- DMA support for the I915 -*- linux-c -*-
+ */
+/**************************************************************************
+ * 
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ * 
+ **************************************************************************/
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+#define USER_INT_FLAG 0x2
+#define MAX_NOPID ((u32)~0)
+#define READ_BREADCRUMB(dev_priv)  (((u32*)(dev_priv->hw_status_page))[5])
+
+irqreturn_t i915_driver_irq_handler(DRM_IRQ_ARGS)
+{
+	drm_device_t *dev = (drm_device_t *) arg;
+	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+	u16 temp;
+
+	temp = I915_READ16(I915REG_INT_IDENTITY_R);
+	temp &= USER_INT_FLAG;
+
+	DRM_DEBUG("%s flag=%08x\n", __FUNCTION__, temp);
+
+	if (temp == 0)
+		return IRQ_NONE;
+
+	I915_WRITE16(I915REG_INT_IDENTITY_R, temp);
+	DRM_WAKEUP(&dev_priv->irq_queue);
+
+	return IRQ_HANDLED;
+}
+
+int i915_emit_irq(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	u32 ret;
+	RING_LOCALS;
+
+	i915_kernel_lost_context(dev);
+
+	DRM_DEBUG("%s\n", __FUNCTION__);
+
+	ret = dev_priv->counter;
+
+	BEGIN_LP_RING(2);
+	OUT_RING(0);
+	OUT_RING(GFX_OP_USER_INTERRUPT);
+	ADVANCE_LP_RING();
+
+	return ret;
+}
+
+int i915_wait_irq(drm_device_t * dev, int irq_nr)
+{
+	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+	int ret = 0;
+
+	DRM_DEBUG("%s irq_nr=%d breadcrumb=%d\n", __FUNCTION__, irq_nr,
+		  READ_BREADCRUMB(dev_priv));
+
+	if (READ_BREADCRUMB(dev_priv) >= irq_nr)
+		return 0;
+
+	dev_priv->sarea_priv->perf_boxes |= I915_BOX_WAIT;
+
+	DRM_WAIT_ON(ret, dev_priv->irq_queue, 3 * DRM_HZ,
+		    READ_BREADCRUMB(dev_priv) >= irq_nr);
+
+	if (ret == DRM_ERR(EBUSY)) {
+		DRM_ERROR("%s: EBUSY -- rec: %d emitted: %d\n",
+			  __FUNCTION__,
+			  READ_BREADCRUMB(dev_priv), (int)dev_priv->counter);
+	}
+
+	dev_priv->sarea_priv->last_dispatch = READ_BREADCRUMB(dev_priv);
+	return ret;
+}
+
+/* Needs the lock as it touches the ring.
+ */
+int i915_irq_emit(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_irq_emit_t emit;
+	int result;
+
+	LOCK_TEST_WITH_RETURN(dev, filp);
+
+	if (!dev_priv) {
+		DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(emit, (drm_i915_irq_emit_t __user *) data,
+				 sizeof(emit));
+
+	result = i915_emit_irq(dev);
+
+	if (DRM_COPY_TO_USER(emit.irq_seq, &result, sizeof(int))) {
+		DRM_ERROR("copy_to_user\n");
+		return DRM_ERR(EFAULT);
+	}
+
+	return 0;
+}
+
+/* Doesn't need the hardware lock.
+ */
+int i915_irq_wait(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_irq_wait_t irqwait;
+
+	if (!dev_priv) {
+		DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(irqwait, (drm_i915_irq_wait_t __user *) data,
+				 sizeof(irqwait));
+
+	return i915_wait_irq(dev, irqwait.irq_seq);
+}
+
+/* drm_dma.h hooks
+*/
+void i915_driver_irq_preinstall(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+
+	I915_WRITE16(I915REG_HWSTAM, 0xfffe);
+	I915_WRITE16(I915REG_INT_MASK_R, 0x0);
+	I915_WRITE16(I915REG_INT_ENABLE_R, 0x0);
+}
+
+void i915_driver_irq_postinstall(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+
+	I915_WRITE16(I915REG_INT_ENABLE_R, USER_INT_FLAG);
+	DRM_INIT_WAITQUEUE(&dev_priv->irq_queue);
+}
+
+void i915_driver_irq_uninstall(drm_device_t * dev)
+{
+	drm_i915_private_t *dev_priv = (drm_i915_private_t *) dev->dev_private;
+	if (!dev_priv)
+		return;
+
+	I915_WRITE16(I915REG_HWSTAM, 0xffff);
+	I915_WRITE16(I915REG_INT_MASK_R, 0xffff);
+	I915_WRITE16(I915REG_INT_ENABLE_R, 0x0);
+}
diff --git a/drivers/char/drm/i915_mem.c b/drivers/char/drm/i915_mem.c
new file mode 100644
index 0000000..d54a300
--- /dev/null
+++ b/drivers/char/drm/i915_mem.c
@@ -0,0 +1,346 @@
+/* i915_mem.c -- Simple agp/fb memory manager for i915 -*- linux-c -*-
+ */
+/**************************************************************************
+ * 
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ * 
+ **************************************************************************/
+
+#include "drmP.h"
+#include "drm.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+
+/* This memory manager is integrated into the global/local lru
+ * mechanisms used by the clients.  Specifically, it operates by
+ * setting the 'in_use' fields of the global LRU to indicate whether
+ * this region is privately allocated to a client.
+ *
+ * This does require the client to actually respect that field.
+ *
+ * Currently no effort is made to allocate 'private' memory in any
+ * clever way - the LRU information isn't used to determine which
+ * block to allocate, and the ring is drained prior to allocations --
+ * in other words allocation is expensive.
+ */
+static void mark_block(drm_device_t * dev, struct mem_block *p, int in_use)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_tex_region_t *list;
+	unsigned shift, nr;
+	unsigned start;
+	unsigned end;
+	unsigned i;
+	int age;
+
+	shift = dev_priv->tex_lru_log_granularity;
+	nr = I915_NR_TEX_REGIONS;
+
+	start = p->start >> shift;
+	end = (p->start + p->size - 1) >> shift;
+
+	age = ++sarea_priv->texAge;
+	list = sarea_priv->texList;
+
+	/* Mark the regions with the new flag and update their age.  Move
+	 * them to head of list to preserve LRU semantics.
+	 */
+	for (i = start; i <= end; i++) {
+		list[i].in_use = in_use;
+		list[i].age = age;
+
+		/* remove_from_list(i)
+		 */
+		list[(unsigned)list[i].next].prev = list[i].prev;
+		list[(unsigned)list[i].prev].next = list[i].next;
+
+		/* insert_at_head(list, i)
+		 */
+		list[i].prev = nr;
+		list[i].next = list[nr].next;
+		list[(unsigned)list[nr].next].prev = i;
+		list[nr].next = i;
+	}
+}
+
+/* Very simple allocator for agp memory, working on a static range
+ * already mapped into each client's address space.  
+ */
+
+static struct mem_block *split_block(struct mem_block *p, int start, int size,
+				     DRMFILE filp)
+{
+	/* Maybe cut off the start of an existing block */
+	if (start > p->start) {
+		struct mem_block *newblock = drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS);
+		if (!newblock)
+			goto out;
+		newblock->start = start;
+		newblock->size = p->size - (start - p->start);
+		newblock->filp = NULL;
+		newblock->next = p->next;
+		newblock->prev = p;
+		p->next->prev = newblock;
+		p->next = newblock;
+		p->size -= newblock->size;
+		p = newblock;
+	}
+
+	/* Maybe cut off the end of an existing block */
+	if (size < p->size) {
+		struct mem_block *newblock = drm_alloc(sizeof(*newblock), DRM_MEM_BUFLISTS);
+		if (!newblock)
+			goto out;
+		newblock->start = start + size;
+		newblock->size = p->size - size;
+		newblock->filp = NULL;
+		newblock->next = p->next;
+		newblock->prev = p;
+		p->next->prev = newblock;
+		p->next = newblock;
+		p->size = size;
+	}
+
+      out:
+	/* Our block is in the middle */
+	p->filp = filp;
+	return p;
+}
+
+static struct mem_block *alloc_block(struct mem_block *heap, int size,
+				     int align2, DRMFILE filp)
+{
+	struct mem_block *p;
+	int mask = (1 << align2) - 1;
+
+	for (p = heap->next; p != heap; p = p->next) {
+		int start = (p->start + mask) & ~mask;
+		if (p->filp == NULL && start + size <= p->start + p->size)
+			return split_block(p, start, size, filp);
+	}
+
+	return NULL;
+}
+
+static struct mem_block *find_block(struct mem_block *heap, int start)
+{
+	struct mem_block *p;
+
+	for (p = heap->next; p != heap; p = p->next)
+		if (p->start == start)
+			return p;
+
+	return NULL;
+}
+
+static void free_block(struct mem_block *p)
+{
+	p->filp = NULL;
+
+	/* Assumes a single contiguous range.  Needs a special filp in
+	 * 'heap' to stop it being subsumed.
+	 */
+	if (p->next->filp == NULL) {
+		struct mem_block *q = p->next;
+		p->size += q->size;
+		p->next = q->next;
+		p->next->prev = p;
+		drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
+	}
+
+	if (p->prev->filp == NULL) {
+		struct mem_block *q = p->prev;
+		q->size += p->size;
+		q->next = p->next;
+		q->next->prev = q;
+		drm_free(p, sizeof(*q), DRM_MEM_BUFLISTS);
+	}
+}
+
+/* Initialize.  How to check for an uninitialized heap?
+ */
+static int init_heap(struct mem_block **heap, int start, int size)
+{
+	struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFLISTS);
+
+	if (!blocks)
+		return -ENOMEM;
+
+	*heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFLISTS);
+	if (!*heap) {
+		drm_free(blocks, sizeof(*blocks), DRM_MEM_BUFLISTS);
+		return -ENOMEM;
+	}
+
+	blocks->start = start;
+	blocks->size = size;
+	blocks->filp = NULL;
+	blocks->next = blocks->prev = *heap;
+
+	memset(*heap, 0, sizeof(**heap));
+	(*heap)->filp = (DRMFILE) - 1;
+	(*heap)->next = (*heap)->prev = blocks;
+	return 0;
+}
+
+/* Free all blocks associated with the releasing file.
+ */
+void i915_mem_release(drm_device_t * dev, DRMFILE filp, struct mem_block *heap)
+{
+	struct mem_block *p;
+
+	if (!heap || !heap->next)
+		return;
+
+	for (p = heap->next; p != heap; p = p->next) {
+		if (p->filp == filp) {
+			p->filp = NULL;
+			mark_block(dev, p, 0);
+		}
+	}
+
+	/* Assumes a single contiguous range.  Needs a special filp in
+	 * 'heap' to stop it being subsumed.
+	 */
+	for (p = heap->next; p != heap; p = p->next) {
+		while (p->filp == NULL && p->next->filp == NULL) {
+			struct mem_block *q = p->next;
+			p->size += q->size;
+			p->next = q->next;
+			p->next->prev = p;
+			drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
+		}
+	}
+}
+
+/* Shutdown.
+ */
+void i915_mem_takedown(struct mem_block **heap)
+{
+	struct mem_block *p;
+
+	if (!*heap)
+		return;
+
+	for (p = (*heap)->next; p != *heap;) {
+		struct mem_block *q = p;
+		p = p->next;
+		drm_free(q, sizeof(*q), DRM_MEM_BUFLISTS);
+	}
+
+	drm_free(*heap, sizeof(**heap), DRM_MEM_BUFLISTS);
+	*heap = NULL;
+}
+
+static struct mem_block **get_heap(drm_i915_private_t * dev_priv, int region)
+{
+	switch (region) {
+	case I915_MEM_REGION_AGP:
+		return &dev_priv->agp_heap;
+	default:
+		return NULL;
+	}
+}
+
+/* IOCTL HANDLERS */
+
+int i915_mem_alloc(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_mem_alloc_t alloc;
+	struct mem_block *block, **heap;
+
+	if (!dev_priv) {
+		DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(alloc, (drm_i915_mem_alloc_t __user *) data,
+				 sizeof(alloc));
+
+	heap = get_heap(dev_priv, alloc.region);
+	if (!heap || !*heap)
+		return DRM_ERR(EFAULT);
+
+	/* Make things easier on ourselves: all allocations at least
+	 * 4k aligned.
+	 */
+	if (alloc.alignment < 12)
+		alloc.alignment = 12;
+
+	block = alloc_block(*heap, alloc.size, alloc.alignment, filp);
+
+	if (!block)
+		return DRM_ERR(ENOMEM);
+
+	mark_block(dev, block, 1);
+
+	if (DRM_COPY_TO_USER(alloc.region_offset, &block->start, sizeof(int))) {
+		DRM_ERROR("copy_to_user\n");
+		return DRM_ERR(EFAULT);
+	}
+
+	return 0;
+}
+
+int i915_mem_free(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_mem_free_t memfree;
+	struct mem_block *block, **heap;
+
+	if (!dev_priv) {
+		DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(memfree, (drm_i915_mem_free_t __user *) data,
+				 sizeof(memfree));
+
+	heap = get_heap(dev_priv, memfree.region);
+	if (!heap || !*heap)
+		return DRM_ERR(EFAULT);
+
+	block = find_block(*heap, memfree.region_offset);
+	if (!block)
+		return DRM_ERR(EFAULT);
+
+	if (block->filp != filp)
+		return DRM_ERR(EPERM);
+
+	mark_block(dev, block, 0);
+	free_block(block);
+	return 0;
+}
+
+int i915_mem_init_heap(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	drm_i915_mem_init_heap_t initheap;
+	struct mem_block **heap;
+
+	if (!dev_priv) {
+		DRM_ERROR("%s called with no initialization\n", __FUNCTION__);
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(initheap,
+				 (drm_i915_mem_init_heap_t __user *) data,
+				 sizeof(initheap));
+
+	heap = get_heap(dev_priv, initheap.region);
+	if (!heap)
+		return DRM_ERR(EFAULT);
+
+	if (*heap) {
+		DRM_ERROR("heap already initialized?");
+		return DRM_ERR(EFAULT);
+	}
+
+	return init_heap(heap, initheap.start, initheap.size);
+}
diff --git a/drivers/char/drm/mga_dma.c b/drivers/char/drm/mga_dma.c
new file mode 100644
index 0000000..832eaf8
--- /dev/null
+++ b/drivers/char/drm/mga_dma.c
@@ -0,0 +1,754 @@
+/* mga_dma.c -- DMA support for mga g200/g400 -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Jeff Hartmann <jhartmann@valinux.com>
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ *
+ * Rewritten by:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+
+#define MGA_DEFAULT_USEC_TIMEOUT	10000
+#define MGA_FREELIST_DEBUG		0
+
+static int mga_do_cleanup_dma( drm_device_t *dev );
+
+/* ================================================================
+ * Engine control
+ */
+
+int mga_do_wait_for_idle( drm_mga_private_t *dev_priv )
+{
+	u32 status = 0;
+	int i;
+	DRM_DEBUG( "\n" );
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		status = MGA_READ( MGA_STATUS ) & MGA_ENGINE_IDLE_MASK;
+		if ( status == MGA_ENDPRDMASTS ) {
+			MGA_WRITE8( MGA_CRTC_INDEX, 0 );
+			return 0;
+		}
+		DRM_UDELAY( 1 );
+	}
+
+#if MGA_DMA_DEBUG
+	DRM_ERROR( "failed!\n" );
+	DRM_INFO( "   status=0x%08x\n", status );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+static int mga_do_dma_reset( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_primary_buffer_t *primary = &dev_priv->prim;
+
+	DRM_DEBUG( "\n" );
+
+	/* The primary DMA stream should look like new right about now.
+	 */
+	primary->tail = 0;
+	primary->space = primary->size;
+	primary->last_flush = 0;
+
+	sarea_priv->last_wrap = 0;
+
+	/* FIXME: Reset counters, buffer ages etc...
+	 */
+
+	/* FIXME: What else do we need to reinitialize?  WARP stuff?
+	 */
+
+	return 0;
+}
+
+/* ================================================================
+ * Primary DMA stream
+ */
+
+void mga_do_dma_flush( drm_mga_private_t *dev_priv )
+{
+	drm_mga_primary_buffer_t *primary = &dev_priv->prim;
+	u32 head, tail;
+	u32 status = 0;
+	int i;
+ 	DMA_LOCALS;
+	DRM_DEBUG( "\n" );
+
+        /* We need to wait so that we can do an safe flush */
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		status = MGA_READ( MGA_STATUS ) & MGA_ENGINE_IDLE_MASK;
+		if ( status == MGA_ENDPRDMASTS ) break;
+		DRM_UDELAY( 1 );
+	}
+
+	if ( primary->tail == primary->last_flush ) {
+		DRM_DEBUG( "   bailing out...\n" );
+		return;
+	}
+
+	tail = primary->tail + dev_priv->primary->offset;
+
+	/* We need to pad the stream between flushes, as the card
+	 * actually (partially?) reads the first of these commands.
+	 * See page 4-16 in the G400 manual, middle of the page or so.
+	 */
+	BEGIN_DMA( 1 );
+
+	DMA_BLOCK( MGA_DMAPAD,  0x00000000,
+		   MGA_DMAPAD,  0x00000000,
+		   MGA_DMAPAD,  0x00000000,
+		   MGA_DMAPAD,	0x00000000 );
+
+	ADVANCE_DMA();
+
+	primary->last_flush = primary->tail;
+
+	head = MGA_READ( MGA_PRIMADDRESS );
+
+	if ( head <= tail ) {
+		primary->space = primary->size - primary->tail;
+	} else {
+		primary->space = head - tail;
+	}
+
+	DRM_DEBUG( "   head = 0x%06lx\n", head - dev_priv->primary->offset );
+	DRM_DEBUG( "   tail = 0x%06lx\n", tail - dev_priv->primary->offset );
+	DRM_DEBUG( "  space = 0x%06x\n", primary->space );
+
+	mga_flush_write_combine();
+	MGA_WRITE( MGA_PRIMEND, tail | MGA_PAGPXFER );
+
+	DRM_DEBUG( "done.\n" );
+}
+
+void mga_do_dma_wrap_start( drm_mga_private_t *dev_priv )
+{
+	drm_mga_primary_buffer_t *primary = &dev_priv->prim;
+	u32 head, tail;
+	DMA_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	BEGIN_DMA_WRAP();
+
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000 );
+
+	ADVANCE_DMA();
+
+	tail = primary->tail + dev_priv->primary->offset;
+
+	primary->tail = 0;
+	primary->last_flush = 0;
+	primary->last_wrap++;
+
+	head = MGA_READ( MGA_PRIMADDRESS );
+
+	if ( head == dev_priv->primary->offset ) {
+		primary->space = primary->size;
+	} else {
+		primary->space = head - dev_priv->primary->offset;
+	}
+
+	DRM_DEBUG( "   head = 0x%06lx\n",
+		  head - dev_priv->primary->offset );
+	DRM_DEBUG( "   tail = 0x%06x\n", primary->tail );
+	DRM_DEBUG( "   wrap = %d\n", primary->last_wrap );
+	DRM_DEBUG( "  space = 0x%06x\n", primary->space );
+
+	mga_flush_write_combine();
+	MGA_WRITE( MGA_PRIMEND, tail | MGA_PAGPXFER );
+
+	set_bit( 0, &primary->wrapped );
+	DRM_DEBUG( "done.\n" );
+}
+
+void mga_do_dma_wrap_end( drm_mga_private_t *dev_priv )
+{
+	drm_mga_primary_buffer_t *primary = &dev_priv->prim;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	u32 head = dev_priv->primary->offset;
+	DRM_DEBUG( "\n" );
+
+	sarea_priv->last_wrap++;
+	DRM_DEBUG( "   wrap = %d\n", sarea_priv->last_wrap );
+
+	mga_flush_write_combine();
+	MGA_WRITE( MGA_PRIMADDRESS, head | MGA_DMA_GENERAL );
+
+	clear_bit( 0, &primary->wrapped );
+	DRM_DEBUG( "done.\n" );
+}
+
+
+/* ================================================================
+ * Freelist management
+ */
+
+#define MGA_BUFFER_USED		~0
+#define MGA_BUFFER_FREE		0
+
+#if MGA_FREELIST_DEBUG
+static void mga_freelist_print( drm_device_t *dev )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_freelist_t *entry;
+
+	DRM_INFO( "\n" );
+	DRM_INFO( "current dispatch: last=0x%x done=0x%x\n",
+		  dev_priv->sarea_priv->last_dispatch,
+		  (unsigned int)(MGA_READ( MGA_PRIMADDRESS ) -
+				 dev_priv->primary->offset) );
+	DRM_INFO( "current freelist:\n" );
+
+	for ( entry = dev_priv->head->next ; entry ; entry = entry->next ) {
+		DRM_INFO( "   %p   idx=%2d  age=0x%x 0x%06lx\n",
+			  entry, entry->buf->idx, entry->age.head,
+			  entry->age.head - dev_priv->primary->offset );
+	}
+	DRM_INFO( "\n" );
+}
+#endif
+
+static int mga_freelist_init( drm_device_t *dev, drm_mga_private_t *dev_priv )
+{
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_mga_buf_priv_t *buf_priv;
+	drm_mga_freelist_t *entry;
+	int i;
+	DRM_DEBUG( "count=%d\n", dma->buf_count );
+
+	dev_priv->head = drm_alloc( sizeof(drm_mga_freelist_t),
+				     DRM_MEM_DRIVER );
+	if ( dev_priv->head == NULL )
+		return DRM_ERR(ENOMEM);
+
+	memset( dev_priv->head, 0, sizeof(drm_mga_freelist_t) );
+	SET_AGE( &dev_priv->head->age, MGA_BUFFER_USED, 0 );
+
+	for ( i = 0 ; i < dma->buf_count ; i++ ) {
+		buf = dma->buflist[i];
+	        buf_priv = buf->dev_private;
+
+		entry = drm_alloc( sizeof(drm_mga_freelist_t),
+				    DRM_MEM_DRIVER );
+		if ( entry == NULL )
+			return DRM_ERR(ENOMEM);
+
+		memset( entry, 0, sizeof(drm_mga_freelist_t) );
+
+		entry->next = dev_priv->head->next;
+		entry->prev = dev_priv->head;
+		SET_AGE( &entry->age, MGA_BUFFER_FREE, 0 );
+		entry->buf = buf;
+
+		if ( dev_priv->head->next != NULL )
+			dev_priv->head->next->prev = entry;
+		if ( entry->next == NULL )
+			dev_priv->tail = entry;
+
+		buf_priv->list_entry = entry;
+		buf_priv->discard = 0;
+		buf_priv->dispatched = 0;
+
+		dev_priv->head->next = entry;
+	}
+
+	return 0;
+}
+
+static void mga_freelist_cleanup( drm_device_t *dev )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_freelist_t *entry;
+	drm_mga_freelist_t *next;
+	DRM_DEBUG( "\n" );
+
+	entry = dev_priv->head;
+	while ( entry ) {
+		next = entry->next;
+		drm_free( entry, sizeof(drm_mga_freelist_t), DRM_MEM_DRIVER );
+		entry = next;
+	}
+
+	dev_priv->head = dev_priv->tail = NULL;
+}
+
+#if 0
+/* FIXME: Still needed?
+ */
+static void mga_freelist_reset( drm_device_t *dev )
+{
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_mga_buf_priv_t *buf_priv;
+	int i;
+
+	for ( i = 0 ; i < dma->buf_count ; i++ ) {
+		buf = dma->buflist[i];
+	        buf_priv = buf->dev_private;
+		SET_AGE( &buf_priv->list_entry->age,
+			 MGA_BUFFER_FREE, 0 );
+	}
+}
+#endif
+
+static drm_buf_t *mga_freelist_get( drm_device_t *dev )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_freelist_t *next;
+	drm_mga_freelist_t *prev;
+	drm_mga_freelist_t *tail = dev_priv->tail;
+	u32 head, wrap;
+	DRM_DEBUG( "\n" );
+
+	head = MGA_READ( MGA_PRIMADDRESS );
+	wrap = dev_priv->sarea_priv->last_wrap;
+
+	DRM_DEBUG( "   tail=0x%06lx %d\n",
+		   tail->age.head ?
+		   tail->age.head - dev_priv->primary->offset : 0,
+		   tail->age.wrap );
+	DRM_DEBUG( "   head=0x%06lx %d\n",
+		   head - dev_priv->primary->offset, wrap );
+
+	if ( TEST_AGE( &tail->age, head, wrap ) ) {
+		prev = dev_priv->tail->prev;
+		next = dev_priv->tail;
+		prev->next = NULL;
+		next->prev = next->next = NULL;
+		dev_priv->tail = prev;
+		SET_AGE( &next->age, MGA_BUFFER_USED, 0 );
+		return next->buf;
+	}
+
+	DRM_DEBUG( "returning NULL!\n" );
+	return NULL;
+}
+
+int mga_freelist_put( drm_device_t *dev, drm_buf_t *buf )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_buf_priv_t *buf_priv = buf->dev_private;
+	drm_mga_freelist_t *head, *entry, *prev;
+
+	DRM_DEBUG( "age=0x%06lx wrap=%d\n",
+		   buf_priv->list_entry->age.head -
+		   dev_priv->primary->offset,
+		   buf_priv->list_entry->age.wrap );
+
+	entry = buf_priv->list_entry;
+	head = dev_priv->head;
+
+	if ( buf_priv->list_entry->age.head == MGA_BUFFER_USED ) {
+		SET_AGE( &entry->age, MGA_BUFFER_FREE, 0 );
+		prev = dev_priv->tail;
+		prev->next = entry;
+		entry->prev = prev;
+		entry->next = NULL;
+	} else {
+		prev = head->next;
+		head->next = entry;
+		prev->prev = entry;
+		entry->prev = head;
+		entry->next = prev;
+	}
+
+	return 0;
+}
+
+
+/* ================================================================
+ * DMA initialization, cleanup
+ */
+
+static int mga_do_init_dma( drm_device_t *dev, drm_mga_init_t *init )
+{
+	drm_mga_private_t *dev_priv;
+	int ret;
+	DRM_DEBUG( "\n" );
+
+	dev_priv = drm_alloc( sizeof(drm_mga_private_t), DRM_MEM_DRIVER );
+	if ( !dev_priv )
+		return DRM_ERR(ENOMEM);
+
+	memset( dev_priv, 0, sizeof(drm_mga_private_t) );
+
+	dev_priv->chipset = init->chipset;
+
+	dev_priv->usec_timeout = MGA_DEFAULT_USEC_TIMEOUT;
+
+	if ( init->sgram ) {
+		dev_priv->clear_cmd = MGA_DWGCTL_CLEAR | MGA_ATYPE_BLK;
+	} else {
+		dev_priv->clear_cmd = MGA_DWGCTL_CLEAR | MGA_ATYPE_RSTR;
+	}
+	dev_priv->maccess	= init->maccess;
+
+	dev_priv->fb_cpp	= init->fb_cpp;
+	dev_priv->front_offset	= init->front_offset;
+	dev_priv->front_pitch	= init->front_pitch;
+	dev_priv->back_offset	= init->back_offset;
+	dev_priv->back_pitch	= init->back_pitch;
+
+	dev_priv->depth_cpp	= init->depth_cpp;
+	dev_priv->depth_offset	= init->depth_offset;
+	dev_priv->depth_pitch	= init->depth_pitch;
+
+	/* FIXME: Need to support AGP textures...
+	 */
+	dev_priv->texture_offset = init->texture_offset[0];
+	dev_priv->texture_size = init->texture_size[0];
+
+	DRM_GETSAREA();
+
+	if(!dev_priv->sarea) {
+		DRM_ERROR( "failed to find sarea!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->mmio = drm_core_findmap(dev, init->mmio_offset);
+	if(!dev_priv->mmio) {
+		DRM_ERROR( "failed to find mmio region!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return DRM_ERR(EINVAL);
+	}
+	dev_priv->status = drm_core_findmap(dev, init->status_offset);
+	if(!dev_priv->status) {
+		DRM_ERROR( "failed to find status page!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return DRM_ERR(EINVAL);
+	}
+	dev_priv->warp = drm_core_findmap(dev, init->warp_offset);
+	if(!dev_priv->warp) {
+		DRM_ERROR( "failed to find warp microcode region!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return DRM_ERR(EINVAL);
+	}
+	dev_priv->primary = drm_core_findmap(dev, init->primary_offset);
+	if(!dev_priv->primary) {
+		DRM_ERROR( "failed to find primary dma region!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return DRM_ERR(EINVAL);
+	}
+	dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+	if(!dev->agp_buffer_map) {
+		DRM_ERROR( "failed to find dma buffer region!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->sarea_priv =
+		(drm_mga_sarea_t *)((u8 *)dev_priv->sarea->handle +
+				    init->sarea_priv_offset);
+
+	drm_core_ioremap( dev_priv->warp, dev );
+	drm_core_ioremap( dev_priv->primary, dev );
+	drm_core_ioremap( dev->agp_buffer_map, dev );
+
+	if(!dev_priv->warp->handle ||
+	   !dev_priv->primary->handle ||
+	   !dev->agp_buffer_map->handle ) {
+		DRM_ERROR( "failed to ioremap agp regions!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return DRM_ERR(ENOMEM);
+	}
+
+	ret = mga_warp_install_microcode( dev_priv );
+	if ( ret < 0 ) {
+		DRM_ERROR( "failed to install WARP ucode!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return ret;
+	}
+
+	ret = mga_warp_init( dev_priv );
+	if ( ret < 0 ) {
+		DRM_ERROR( "failed to init WARP engine!\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return ret;
+	}
+
+	dev_priv->prim.status = (u32 *)dev_priv->status->handle;
+
+	mga_do_wait_for_idle( dev_priv );
+
+	/* Init the primary DMA registers.
+	 */
+	MGA_WRITE( MGA_PRIMADDRESS,
+		   dev_priv->primary->offset | MGA_DMA_GENERAL );
+#if 0
+	MGA_WRITE( MGA_PRIMPTR,
+		   virt_to_bus((void *)dev_priv->prim.status) |
+		   MGA_PRIMPTREN0 |	/* Soft trap, SECEND, SETUPEND */
+		   MGA_PRIMPTREN1 );	/* DWGSYNC */
+#endif
+
+	dev_priv->prim.start = (u8 *)dev_priv->primary->handle;
+	dev_priv->prim.end = ((u8 *)dev_priv->primary->handle
+			      + dev_priv->primary->size);
+	dev_priv->prim.size = dev_priv->primary->size;
+
+	dev_priv->prim.tail = 0;
+	dev_priv->prim.space = dev_priv->prim.size;
+	dev_priv->prim.wrapped = 0;
+
+	dev_priv->prim.last_flush = 0;
+	dev_priv->prim.last_wrap = 0;
+
+	dev_priv->prim.high_mark = 256 * DMA_BLOCK_SIZE;
+
+	dev_priv->prim.status[0] = dev_priv->primary->offset;
+	dev_priv->prim.status[1] = 0;
+
+	dev_priv->sarea_priv->last_wrap = 0;
+	dev_priv->sarea_priv->last_frame.head = 0;
+	dev_priv->sarea_priv->last_frame.wrap = 0;
+
+	if ( mga_freelist_init( dev, dev_priv ) < 0 ) {
+		DRM_ERROR( "could not initialize freelist\n" );
+		/* Assign dev_private so we can do cleanup. */
+		dev->dev_private = (void *)dev_priv;
+		mga_do_cleanup_dma( dev );
+		return DRM_ERR(ENOMEM);
+	}
+
+	/* Make dev_private visable to others. */
+	dev->dev_private = (void *)dev_priv;
+	return 0;
+}
+
+static int mga_do_cleanup_dma( drm_device_t *dev )
+{
+	DRM_DEBUG( "\n" );
+
+	/* Make sure interrupts are disabled here because the uninstall ioctl
+	 * may not have been called from userspace and after dev_private
+	 * is freed, it's too late.
+	 */
+	if ( dev->irq_enabled ) drm_irq_uninstall(dev);
+
+	if ( dev->dev_private ) {
+		drm_mga_private_t *dev_priv = dev->dev_private;
+
+		if ( dev_priv->warp != NULL )
+			drm_core_ioremapfree( dev_priv->warp, dev );
+		if ( dev_priv->primary != NULL )
+			drm_core_ioremapfree( dev_priv->primary, dev );
+		if ( dev->agp_buffer_map != NULL )
+			drm_core_ioremapfree( dev->agp_buffer_map, dev );
+
+		if ( dev_priv->head != NULL ) {
+			mga_freelist_cleanup( dev );
+		}
+
+		drm_free( dev->dev_private, sizeof(drm_mga_private_t),
+			   DRM_MEM_DRIVER );
+		dev->dev_private = NULL;
+	}
+
+	return 0;
+}
+
+int mga_dma_init( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_init_t init;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( init, (drm_mga_init_t __user *)data, sizeof(init) );
+
+	switch ( init.func ) {
+	case MGA_INIT_DMA:
+		return mga_do_init_dma( dev, &init );
+	case MGA_CLEANUP_DMA:
+		return mga_do_cleanup_dma( dev );
+	}
+
+	return DRM_ERR(EINVAL);
+}
+
+
+/* ================================================================
+ * Primary DMA stream management
+ */
+
+int mga_dma_flush( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
+	drm_lock_t lock;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( lock, (drm_lock_t __user *)data, sizeof(lock) );
+
+	DRM_DEBUG( "%s%s%s\n",
+		   (lock.flags & _DRM_LOCK_FLUSH) ?	"flush, " : "",
+		   (lock.flags & _DRM_LOCK_FLUSH_ALL) ?	"flush all, " : "",
+		   (lock.flags & _DRM_LOCK_QUIESCENT) ?	"idle, " : "" );
+
+	WRAP_WAIT_WITH_RETURN( dev_priv );
+
+	if ( lock.flags & (_DRM_LOCK_FLUSH | _DRM_LOCK_FLUSH_ALL) ) {
+		mga_do_dma_flush( dev_priv );
+	}
+
+	if ( lock.flags & _DRM_LOCK_QUIESCENT ) {
+#if MGA_DMA_DEBUG
+		int ret = mga_do_wait_for_idle( dev_priv );
+		if ( ret < 0 )
+			DRM_INFO( "%s: -EBUSY\n", __FUNCTION__ );
+		return ret;
+#else
+		return mga_do_wait_for_idle( dev_priv );
+#endif
+	} else {
+		return 0;
+	}
+}
+
+int mga_dma_reset( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	return mga_do_dma_reset( dev_priv );
+}
+
+
+/* ================================================================
+ * DMA buffer management
+ */
+
+static int mga_dma_get_buffers( DRMFILE filp,
+				drm_device_t *dev, drm_dma_t *d )
+{
+	drm_buf_t *buf;
+	int i;
+
+	for ( i = d->granted_count ; i < d->request_count ; i++ ) {
+		buf = mga_freelist_get( dev );
+		if ( !buf ) return DRM_ERR(EAGAIN);
+
+		buf->filp = filp;
+
+		if ( DRM_COPY_TO_USER( &d->request_indices[i],
+				   &buf->idx, sizeof(buf->idx) ) )
+			return DRM_ERR(EFAULT);
+		if ( DRM_COPY_TO_USER( &d->request_sizes[i],
+				   &buf->total, sizeof(buf->total) ) )
+			return DRM_ERR(EFAULT);
+
+		d->granted_count++;
+	}
+	return 0;
+}
+
+int mga_dma_buffers( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_device_dma_t *dma = dev->dma;
+	drm_mga_private_t *dev_priv = (drm_mga_private_t *)dev->dev_private;
+	drm_dma_t __user *argp = (void __user *)data;
+	drm_dma_t d;
+	int ret = 0;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( d, argp, sizeof(d) );
+
+	/* Please don't send us buffers.
+	 */
+	if ( d.send_count != 0 ) {
+		DRM_ERROR( "Process %d trying to send %d buffers via drmDMA\n",
+			   DRM_CURRENTPID, d.send_count );
+		return DRM_ERR(EINVAL);
+	}
+
+	/* We'll send you buffers.
+	 */
+	if ( d.request_count < 0 || d.request_count > dma->buf_count ) {
+		DRM_ERROR( "Process %d trying to get %d buffers (of %d max)\n",
+			   DRM_CURRENTPID, d.request_count, dma->buf_count );
+		return DRM_ERR(EINVAL);
+	}
+
+	WRAP_TEST_WITH_RETURN( dev_priv );
+
+	d.granted_count = 0;
+
+	if ( d.request_count ) {
+		ret = mga_dma_get_buffers( filp, dev, &d );
+	}
+
+	DRM_COPY_TO_USER_IOCTL( argp, d, sizeof(d) );
+
+	return ret;
+}
+
+void mga_driver_pretakedown(drm_device_t *dev)
+{
+	mga_do_cleanup_dma( dev );
+}
+
+int mga_driver_dma_quiescent(drm_device_t *dev)
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	return mga_do_wait_for_idle( dev_priv );
+}
diff --git a/drivers/char/drm/mga_drm.h b/drivers/char/drm/mga_drm.h
new file mode 100644
index 0000000..521d445
--- /dev/null
+++ b/drivers/char/drm/mga_drm.h
@@ -0,0 +1,349 @@
+/* mga_drm.h -- Public header for the Matrox g200/g400 driver -*- linux-c -*-
+ * Created: Tue Jan 25 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Jeff Hartmann <jhartmann@valinux.com>
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ *
+ * Rewritten by:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#ifndef __MGA_DRM_H__
+#define __MGA_DRM_H__
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (mga_sarea.h)
+ */
+
+#ifndef __MGA_SAREA_DEFINES__
+#define __MGA_SAREA_DEFINES__
+
+/* WARP pipe flags
+ */
+#define MGA_F			0x1		/* fog */
+#define MGA_A			0x2		/* alpha */
+#define MGA_S			0x4		/* specular */
+#define MGA_T2			0x8		/* multitexture */
+
+#define MGA_WARP_TGZ		0
+#define MGA_WARP_TGZF		(MGA_F)
+#define MGA_WARP_TGZA		(MGA_A)
+#define MGA_WARP_TGZAF		(MGA_F|MGA_A)
+#define MGA_WARP_TGZS		(MGA_S)
+#define MGA_WARP_TGZSF		(MGA_S|MGA_F)
+#define MGA_WARP_TGZSA		(MGA_S|MGA_A)
+#define MGA_WARP_TGZSAF		(MGA_S|MGA_F|MGA_A)
+#define MGA_WARP_T2GZ		(MGA_T2)
+#define MGA_WARP_T2GZF		(MGA_T2|MGA_F)
+#define MGA_WARP_T2GZA		(MGA_T2|MGA_A)
+#define MGA_WARP_T2GZAF		(MGA_T2|MGA_A|MGA_F)
+#define MGA_WARP_T2GZS		(MGA_T2|MGA_S)
+#define MGA_WARP_T2GZSF		(MGA_T2|MGA_S|MGA_F)
+#define MGA_WARP_T2GZSA		(MGA_T2|MGA_S|MGA_A)
+#define MGA_WARP_T2GZSAF	(MGA_T2|MGA_S|MGA_F|MGA_A)
+
+#define MGA_MAX_G200_PIPES	8		/* no multitex */
+#define MGA_MAX_G400_PIPES	16
+#define MGA_MAX_WARP_PIPES	MGA_MAX_G400_PIPES
+#define MGA_WARP_UCODE_SIZE	32768		/* in bytes */
+
+#define MGA_CARD_TYPE_G200	1
+#define MGA_CARD_TYPE_G400	2
+
+
+#define MGA_FRONT		0x1
+#define MGA_BACK		0x2
+#define MGA_DEPTH		0x4
+
+/* What needs to be changed for the current vertex dma buffer?
+ */
+#define MGA_UPLOAD_CONTEXT	0x1
+#define MGA_UPLOAD_TEX0		0x2
+#define MGA_UPLOAD_TEX1		0x4
+#define MGA_UPLOAD_PIPE		0x8
+#define MGA_UPLOAD_TEX0IMAGE	0x10 /* handled client-side */
+#define MGA_UPLOAD_TEX1IMAGE	0x20 /* handled client-side */
+#define MGA_UPLOAD_2D		0x40
+#define MGA_WAIT_AGE		0x80 /* handled client-side */
+#define MGA_UPLOAD_CLIPRECTS	0x100 /* handled client-side */
+#if 0
+#define MGA_DMA_FLUSH		0x200 /* set when someone gets the lock
+					 quiescent */
+#endif
+
+/* 32 buffers of 64k each, total 2 meg.
+ */
+#define MGA_BUFFER_SIZE		(1 << 16)
+#define MGA_NUM_BUFFERS		128
+
+/* Keep these small for testing.
+ */
+#define MGA_NR_SAREA_CLIPRECTS	8
+
+/* 2 heaps (1 for card, 1 for agp), each divided into upto 128
+ * regions, subject to a minimum region size of (1<<16) == 64k.
+ *
+ * Clients may subdivide regions internally, but when sharing between
+ * clients, the region size is the minimum granularity.
+ */
+
+#define MGA_CARD_HEAP			0
+#define MGA_AGP_HEAP			1
+#define MGA_NR_TEX_HEAPS		2
+#define MGA_NR_TEX_REGIONS		16
+#define MGA_LOG_MIN_TEX_REGION_SIZE	16
+
+#define  DRM_MGA_IDLE_RETRY          2048
+
+#endif /* __MGA_SAREA_DEFINES__ */
+
+
+/* Setup registers for 3D context
+ */
+typedef struct {
+	unsigned int dstorg;
+	unsigned int maccess;
+	unsigned int plnwt;
+	unsigned int dwgctl;
+	unsigned int alphactrl;
+	unsigned int fogcolor;
+	unsigned int wflag;
+	unsigned int tdualstage0;
+	unsigned int tdualstage1;
+	unsigned int fcol;
+	unsigned int stencil;
+	unsigned int stencilctl;
+} drm_mga_context_regs_t;
+
+/* Setup registers for 2D, X server
+ */
+typedef struct {
+	unsigned int pitch;
+} drm_mga_server_regs_t;
+
+/* Setup registers for each texture unit
+ */
+typedef struct {
+	unsigned int texctl;
+	unsigned int texctl2;
+	unsigned int texfilter;
+	unsigned int texbordercol;
+	unsigned int texorg;
+	unsigned int texwidth;
+	unsigned int texheight;
+	unsigned int texorg1;
+	unsigned int texorg2;
+	unsigned int texorg3;
+	unsigned int texorg4;
+} drm_mga_texture_regs_t;
+
+/* General aging mechanism
+ */
+typedef struct {
+	unsigned int head;		/* Position of head pointer          */
+	unsigned int wrap;		/* Primary DMA wrap count            */
+} drm_mga_age_t;
+
+typedef struct _drm_mga_sarea {
+	/* The channel for communication of state information to the kernel
+	 * on firing a vertex dma buffer.
+	 */
+   	drm_mga_context_regs_t context_state;
+   	drm_mga_server_regs_t server_state;
+   	drm_mga_texture_regs_t tex_state[2];
+   	unsigned int warp_pipe;
+   	unsigned int dirty;
+   	unsigned int vertsize;
+
+	/* The current cliprects, or a subset thereof.
+	 */
+   	drm_clip_rect_t boxes[MGA_NR_SAREA_CLIPRECTS];
+   	unsigned int nbox;
+
+	/* Information about the most recently used 3d drawable.  The
+	 * client fills in the req_* fields, the server fills in the
+	 * exported_ fields and puts the cliprects into boxes, above.
+	 *
+	 * The client clears the exported_drawable field before
+	 * clobbering the boxes data.
+	 */
+        unsigned int req_drawable;	 /* the X drawable id */
+	unsigned int req_draw_buffer;	 /* MGA_FRONT or MGA_BACK */
+
+        unsigned int exported_drawable;
+	unsigned int exported_index;
+        unsigned int exported_stamp;
+        unsigned int exported_buffers;
+        unsigned int exported_nfront;
+        unsigned int exported_nback;
+	int exported_back_x, exported_front_x, exported_w;
+	int exported_back_y, exported_front_y, exported_h;
+   	drm_clip_rect_t exported_boxes[MGA_NR_SAREA_CLIPRECTS];
+
+	/* Counters for aging textures and for client-side throttling.
+	 */
+	unsigned int status[4];
+	unsigned int last_wrap;
+
+	drm_mga_age_t last_frame;
+        unsigned int last_enqueue;	/* last time a buffer was enqueued */
+	unsigned int last_dispatch;	/* age of the most recently dispatched buffer */
+	unsigned int last_quiescent;     /*  */
+
+	/* LRU lists for texture memory in agp space and on the card.
+	 */
+	drm_tex_region_t texList[MGA_NR_TEX_HEAPS][MGA_NR_TEX_REGIONS+1];
+	unsigned int texAge[MGA_NR_TEX_HEAPS];
+
+	/* Mechanism to validate card state.
+	 */
+   	int ctxOwner;
+} drm_mga_sarea_t;
+
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmMga.h)
+ */
+
+/* MGA specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_MGA_INIT     0x00
+#define DRM_MGA_FLUSH    0x01
+#define DRM_MGA_RESET    0x02
+#define DRM_MGA_SWAP     0x03
+#define DRM_MGA_CLEAR    0x04
+#define DRM_MGA_VERTEX   0x05
+#define DRM_MGA_INDICES  0x06
+#define DRM_MGA_ILOAD    0x07
+#define DRM_MGA_BLIT     0x08
+#define DRM_MGA_GETPARAM 0x09
+
+#define DRM_IOCTL_MGA_INIT     DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_INIT, drm_mga_init_t)
+#define DRM_IOCTL_MGA_FLUSH    DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_FLUSH, drm_lock_t)
+#define DRM_IOCTL_MGA_RESET    DRM_IO(  DRM_COMMAND_BASE + DRM_MGA_RESET)
+#define DRM_IOCTL_MGA_SWAP     DRM_IO(  DRM_COMMAND_BASE + DRM_MGA_SWAP)
+#define DRM_IOCTL_MGA_CLEAR    DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_CLEAR, drm_mga_clear_t)
+#define DRM_IOCTL_MGA_VERTEX   DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_VERTEX, drm_mga_vertex_t)
+#define DRM_IOCTL_MGA_INDICES  DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_INDICES, drm_mga_indices_t)
+#define DRM_IOCTL_MGA_ILOAD    DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_ILOAD, drm_mga_iload_t)
+#define DRM_IOCTL_MGA_BLIT     DRM_IOW( DRM_COMMAND_BASE + DRM_MGA_BLIT, drm_mga_blit_t)
+#define DRM_IOCTL_MGA_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_MGA_GETPARAM, drm_mga_getparam_t)
+
+typedef struct _drm_mga_warp_index {
+   	int installed;
+   	unsigned long phys_addr;
+   	int size;
+} drm_mga_warp_index_t;
+
+typedef struct drm_mga_init {
+   	enum {
+	   	MGA_INIT_DMA    = 0x01,
+	       	MGA_CLEANUP_DMA = 0x02
+	} func;
+
+   	unsigned long sarea_priv_offset;
+
+	int chipset;
+   	int sgram;
+
+	unsigned int maccess;
+
+   	unsigned int fb_cpp;
+	unsigned int front_offset, front_pitch;
+   	unsigned int back_offset, back_pitch;
+
+   	unsigned int depth_cpp;
+   	unsigned int depth_offset, depth_pitch;
+
+   	unsigned int texture_offset[MGA_NR_TEX_HEAPS];
+   	unsigned int texture_size[MGA_NR_TEX_HEAPS];
+
+	unsigned long fb_offset;
+	unsigned long mmio_offset;
+	unsigned long status_offset;
+	unsigned long warp_offset;
+	unsigned long primary_offset;
+	unsigned long buffers_offset;
+} drm_mga_init_t;
+
+typedef struct drm_mga_fullscreen {
+	enum {
+		MGA_INIT_FULLSCREEN    = 0x01,
+		MGA_CLEANUP_FULLSCREEN = 0x02
+	} func;
+} drm_mga_fullscreen_t;
+
+typedef struct drm_mga_clear {
+	unsigned int flags;
+	unsigned int clear_color;
+	unsigned int clear_depth;
+	unsigned int color_mask;
+	unsigned int depth_mask;
+} drm_mga_clear_t;
+
+typedef struct drm_mga_vertex {
+   	int idx;			/* buffer to queue */
+	int used;			/* bytes in use */
+	int discard;			/* client finished with buffer?  */
+} drm_mga_vertex_t;
+
+typedef struct drm_mga_indices {
+   	int idx;			/* buffer to queue */
+	unsigned int start;
+	unsigned int end;
+	int discard;			/* client finished with buffer?  */
+} drm_mga_indices_t;
+
+typedef struct drm_mga_iload {
+	int idx;
+	unsigned int dstorg;
+	unsigned int length;
+} drm_mga_iload_t;
+
+typedef struct _drm_mga_blit {
+	unsigned int planemask;
+	unsigned int srcorg;
+	unsigned int dstorg;
+	int src_pitch, dst_pitch;
+	int delta_sx, delta_sy;
+	int delta_dx, delta_dy;
+	int height, ydir;		/* flip image vertically */
+	int source_pitch, dest_pitch;
+} drm_mga_blit_t;
+
+/* 3.1: An ioctl to get parameters that aren't available to the 3d
+ * client any other way.  
+ */
+#define MGA_PARAM_IRQ_NR            1
+
+typedef struct drm_mga_getparam {
+	int param;
+	void __user *value;
+} drm_mga_getparam_t;
+
+#endif
diff --git a/drivers/char/drm/mga_drv.c b/drivers/char/drm/mga_drv.c
new file mode 100644
index 0000000..22dab3e
--- /dev/null
+++ b/drivers/char/drm/mga_drv.c
@@ -0,0 +1,127 @@
+/* mga_drv.c -- Matrox G200/G400 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:56:22 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+
+  
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+	dev->counters += 3;
+	dev->types[6] = _DRM_STAT_IRQ;
+	dev->types[7] = _DRM_STAT_PRIMARY;
+	dev->types[8] = _DRM_STAT_SECONDARY;
+
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->primary.minor,
+		pci_pretty_name(dev->pdev)
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+	mga_PCI_IDS
+};
+
+extern drm_ioctl_desc_t mga_ioctls[];
+extern int mga_max_ioctl;
+
+static struct drm_driver driver = {
+	.driver_features = DRIVER_USE_AGP | DRIVER_REQUIRE_AGP | DRIVER_USE_MTRR | DRIVER_HAVE_DMA | DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | DRIVER_IRQ_VBL,
+	.pretakedown = mga_driver_pretakedown,
+	.dma_quiescent = mga_driver_dma_quiescent,
+	.vblank_wait = mga_driver_vblank_wait,
+	.irq_preinstall = mga_driver_irq_preinstall,
+	.irq_postinstall = mga_driver_irq_postinstall,
+	.irq_uninstall = mga_driver_irq_uninstall,
+	.irq_handler = mga_driver_irq_handler,
+	.reclaim_buffers = drm_core_reclaim_buffers,
+	.get_map_ofs = drm_core_get_map_ofs,
+	.get_reg_ofs = drm_core_get_reg_ofs,
+	.postinit = postinit,
+	.version = version,
+	.ioctls = mga_ioctls,
+	.dma_ioctl = mga_dma_buffers,
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+	.pci_driver = {
+		.name = DRIVER_NAME,
+		.id_table = pciidlist,
+	}
+};
+
+static int __init mga_init(void)
+{
+	driver.num_ioctls = mga_max_ioctl;
+	return drm_init(&driver);
+}
+
+static void __exit mga_exit(void)
+{
+	drm_exit(&driver);
+}
+
+module_init(mga_init);
+module_exit(mga_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/mga_drv.h b/drivers/char/drm/mga_drv.h
new file mode 100644
index 0000000..1d84a1e
--- /dev/null
+++ b/drivers/char/drm/mga_drv.h
@@ -0,0 +1,638 @@
+/* mga_drv.h -- Private header for the Matrox G200/G400 driver -*- linux-c -*-
+ * Created: Mon Dec 13 01:50:01 1999 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#ifndef __MGA_DRV_H__
+#define __MGA_DRV_H__
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR		"Gareth Hughes, VA Linux Systems Inc."
+
+#define DRIVER_NAME		"mga"
+#define DRIVER_DESC		"Matrox G200/G400"
+#define DRIVER_DATE		"20021029"
+
+#define DRIVER_MAJOR		3
+#define DRIVER_MINOR		1
+#define DRIVER_PATCHLEVEL	0
+
+typedef struct drm_mga_primary_buffer {
+	u8 *start;
+	u8 *end;
+	int size;
+
+	u32 tail;
+	int space;
+	volatile long wrapped;
+
+	volatile u32 *status;
+
+	u32 last_flush;
+	u32 last_wrap;
+
+	u32 high_mark;
+} drm_mga_primary_buffer_t;
+
+typedef struct drm_mga_freelist {
+   	struct drm_mga_freelist *next;
+   	struct drm_mga_freelist *prev;
+	drm_mga_age_t age;
+   	drm_buf_t *buf;
+} drm_mga_freelist_t;
+
+typedef struct {
+   	drm_mga_freelist_t *list_entry;
+	int discard;
+	int dispatched;
+} drm_mga_buf_priv_t;
+
+typedef struct drm_mga_private {
+	drm_mga_primary_buffer_t prim;
+	drm_mga_sarea_t *sarea_priv;
+
+   	drm_mga_freelist_t *head;
+   	drm_mga_freelist_t *tail;
+
+	unsigned int warp_pipe;
+	unsigned long warp_pipe_phys[MGA_MAX_WARP_PIPES];
+
+	int chipset;
+	int usec_timeout;
+
+	u32 clear_cmd;
+	u32 maccess;
+
+	unsigned int fb_cpp;
+	unsigned int front_offset;
+	unsigned int front_pitch;
+	unsigned int back_offset;
+	unsigned int back_pitch;
+
+	unsigned int depth_cpp;
+	unsigned int depth_offset;
+	unsigned int depth_pitch;
+
+	unsigned int texture_offset;
+	unsigned int texture_size;
+
+	drm_local_map_t *sarea;
+	drm_local_map_t *mmio;
+	drm_local_map_t *status;
+	drm_local_map_t *warp;
+	drm_local_map_t *primary;
+	drm_local_map_t *buffers;
+	drm_local_map_t *agp_textures;
+} drm_mga_private_t;
+
+				/* mga_dma.c */
+extern int mga_dma_init( DRM_IOCTL_ARGS );
+extern int mga_dma_flush( DRM_IOCTL_ARGS );
+extern int mga_dma_reset( DRM_IOCTL_ARGS );
+extern int mga_dma_buffers( DRM_IOCTL_ARGS );
+extern void mga_driver_pretakedown(drm_device_t *dev);
+extern int mga_driver_dma_quiescent(drm_device_t *dev);
+
+extern int mga_do_wait_for_idle( drm_mga_private_t *dev_priv );
+
+extern void mga_do_dma_flush( drm_mga_private_t *dev_priv );
+extern void mga_do_dma_wrap_start( drm_mga_private_t *dev_priv );
+extern void mga_do_dma_wrap_end( drm_mga_private_t *dev_priv );
+
+extern int mga_freelist_put( drm_device_t *dev, drm_buf_t *buf );
+
+				/* mga_warp.c */
+extern int mga_warp_install_microcode( drm_mga_private_t *dev_priv );
+extern int mga_warp_init( drm_mga_private_t *dev_priv );
+
+extern int mga_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence);
+extern irqreturn_t mga_driver_irq_handler( DRM_IRQ_ARGS );
+extern void mga_driver_irq_preinstall( drm_device_t *dev );
+extern void mga_driver_irq_postinstall( drm_device_t *dev );
+extern void mga_driver_irq_uninstall( drm_device_t *dev );
+
+#define mga_flush_write_combine()	DRM_WRITEMEMORYBARRIER()
+
+#if defined(__linux__) && defined(__alpha__)
+#define MGA_BASE( reg )		((unsigned long)(dev_priv->mmio->handle))
+#define MGA_ADDR( reg )		(MGA_BASE(reg) + reg)
+
+#define MGA_DEREF( reg )	*(volatile u32 *)MGA_ADDR( reg )
+#define MGA_DEREF8( reg )	*(volatile u8 *)MGA_ADDR( reg )
+
+#define MGA_READ( reg )		(_MGA_READ((u32 *)MGA_ADDR(reg)))
+#define MGA_READ8( reg )	(_MGA_READ((u8 *)MGA_ADDR(reg)))
+#define MGA_WRITE( reg, val )	do { DRM_WRITEMEMORYBARRIER(); MGA_DEREF( reg ) = val; } while (0)
+#define MGA_WRITE8( reg, val )  do { DRM_WRITEMEMORYBARRIER(); MGA_DEREF8( reg ) = val; } while (0)
+
+static inline u32 _MGA_READ(u32 *addr)
+{
+	DRM_MEMORYBARRIER();
+	return *(volatile u32 *)addr;
+}
+#else
+#define MGA_READ8( reg )	DRM_READ8(dev_priv->mmio, (reg))
+#define MGA_READ( reg )		DRM_READ32(dev_priv->mmio, (reg))
+#define MGA_WRITE8( reg, val )  DRM_WRITE8(dev_priv->mmio, (reg), (val))
+#define MGA_WRITE( reg, val )	DRM_WRITE32(dev_priv->mmio, (reg), (val))
+#endif
+
+#define DWGREG0 	0x1c00
+#define DWGREG0_END 	0x1dff
+#define DWGREG1		0x2c00
+#define DWGREG1_END	0x2dff
+
+#define ISREG0(r)	(r >= DWGREG0 && r <= DWGREG0_END)
+#define DMAREG0(r)	(u8)((r - DWGREG0) >> 2)
+#define DMAREG1(r)	(u8)(((r - DWGREG1) >> 2) | 0x80)
+#define DMAREG(r)	(ISREG0(r) ? DMAREG0(r) : DMAREG1(r))
+
+
+
+/* ================================================================
+ * Helper macross...
+ */
+
+#define MGA_EMIT_STATE( dev_priv, dirty )				\
+do {									\
+	if ( (dirty) & ~MGA_UPLOAD_CLIPRECTS ) {			\
+		if ( dev_priv->chipset == MGA_CARD_TYPE_G400 ) {	\
+			mga_g400_emit_state( dev_priv );		\
+		} else {						\
+			mga_g200_emit_state( dev_priv );		\
+		}							\
+	}								\
+} while (0)
+
+#define WRAP_TEST_WITH_RETURN( dev_priv )				\
+do {									\
+	if ( test_bit( 0, &dev_priv->prim.wrapped ) ) {			\
+		if ( mga_is_idle( dev_priv ) ) {			\
+			mga_do_dma_wrap_end( dev_priv );		\
+		} else if ( dev_priv->prim.space <			\
+			    dev_priv->prim.high_mark ) {		\
+			if ( MGA_DMA_DEBUG )				\
+				DRM_INFO( "%s: wrap...\n", __FUNCTION__ );	\
+			return DRM_ERR(EBUSY);			\
+		}							\
+	}								\
+} while (0)
+
+#define WRAP_WAIT_WITH_RETURN( dev_priv )				\
+do {									\
+	if ( test_bit( 0, &dev_priv->prim.wrapped ) ) {			\
+		if ( mga_do_wait_for_idle( dev_priv ) < 0 ) {		\
+			if ( MGA_DMA_DEBUG )				\
+				DRM_INFO( "%s: wrap...\n", __FUNCTION__ );	\
+			return DRM_ERR(EBUSY);			\
+		}							\
+		mga_do_dma_wrap_end( dev_priv );			\
+	}								\
+} while (0)
+
+
+/* ================================================================
+ * Primary DMA command stream
+ */
+
+#define MGA_VERBOSE	0
+
+#define DMA_LOCALS	unsigned int write; volatile u8 *prim;
+
+#define DMA_BLOCK_SIZE	(5 * sizeof(u32))
+
+#define BEGIN_DMA( n )							\
+do {									\
+	if ( MGA_VERBOSE ) {						\
+		DRM_INFO( "BEGIN_DMA( %d ) in %s\n",			\
+			  (n), __FUNCTION__ );				\
+		DRM_INFO( "   space=0x%x req=0x%Zx\n",			\
+			  dev_priv->prim.space, (n) * DMA_BLOCK_SIZE );	\
+	}								\
+	prim = dev_priv->prim.start;					\
+	write = dev_priv->prim.tail;					\
+} while (0)
+
+#define BEGIN_DMA_WRAP()						\
+do {									\
+	if ( MGA_VERBOSE ) {						\
+		DRM_INFO( "BEGIN_DMA() in %s\n", __FUNCTION__ );		\
+		DRM_INFO( "   space=0x%x\n", dev_priv->prim.space );	\
+	}								\
+	prim = dev_priv->prim.start;					\
+	write = dev_priv->prim.tail;					\
+} while (0)
+
+#define ADVANCE_DMA()							\
+do {									\
+	dev_priv->prim.tail = write;					\
+	if ( MGA_VERBOSE ) {						\
+		DRM_INFO( "ADVANCE_DMA() tail=0x%05x sp=0x%x\n",	\
+			  write, dev_priv->prim.space );		\
+	}								\
+} while (0)
+
+#define FLUSH_DMA()							\
+do {									\
+	if ( 0 ) {							\
+		DRM_INFO( "%s:\n", __FUNCTION__ );				\
+		DRM_INFO( "   tail=0x%06x head=0x%06lx\n",		\
+			  dev_priv->prim.tail,				\
+			  MGA_READ( MGA_PRIMADDRESS ) -			\
+			  dev_priv->primary->offset );			\
+	}								\
+	if ( !test_bit( 0, &dev_priv->prim.wrapped ) ) {		\
+		if ( dev_priv->prim.space <				\
+		     dev_priv->prim.high_mark ) {			\
+			mga_do_dma_wrap_start( dev_priv );		\
+		} else {						\
+			mga_do_dma_flush( dev_priv );			\
+		}							\
+	}								\
+} while (0)
+
+/* Never use this, always use DMA_BLOCK(...) for primary DMA output.
+ */
+#define DMA_WRITE( offset, val )					\
+do {									\
+	if ( MGA_VERBOSE ) {						\
+		DRM_INFO( "   DMA_WRITE( 0x%08x ) at 0x%04Zx\n",	\
+			  (u32)(val), write + (offset) * sizeof(u32) );	\
+	}								\
+	*(volatile u32 *)(prim + write + (offset) * sizeof(u32)) = val;	\
+} while (0)
+
+#define DMA_BLOCK( reg0, val0, reg1, val1, reg2, val2, reg3, val3 )	\
+do {									\
+	DMA_WRITE( 0, ((DMAREG( reg0 ) << 0) |				\
+		       (DMAREG( reg1 ) << 8) |				\
+		       (DMAREG( reg2 ) << 16) |				\
+		       (DMAREG( reg3 ) << 24)) );			\
+	DMA_WRITE( 1, val0 );						\
+	DMA_WRITE( 2, val1 );						\
+	DMA_WRITE( 3, val2 );						\
+	DMA_WRITE( 4, val3 );						\
+	write += DMA_BLOCK_SIZE;					\
+} while (0)
+
+
+/* Buffer aging via primary DMA stream head pointer.
+ */
+
+#define SET_AGE( age, h, w )						\
+do {									\
+	(age)->head = h;						\
+	(age)->wrap = w;						\
+} while (0)
+
+#define TEST_AGE( age, h, w )		( (age)->wrap < w ||		\
+					  ( (age)->wrap == w &&		\
+					    (age)->head < h ) )
+
+#define AGE_BUFFER( buf_priv )						\
+do {									\
+	drm_mga_freelist_t *entry = (buf_priv)->list_entry;		\
+	if ( (buf_priv)->dispatched ) {					\
+		entry->age.head = (dev_priv->prim.tail +		\
+				   dev_priv->primary->offset);		\
+		entry->age.wrap = dev_priv->sarea_priv->last_wrap;	\
+	} else {							\
+		entry->age.head = 0;					\
+		entry->age.wrap = 0;					\
+	}								\
+} while (0)
+
+
+#define MGA_ENGINE_IDLE_MASK		(MGA_SOFTRAPEN |		\
+					 MGA_DWGENGSTS |		\
+					 MGA_ENDPRDMASTS)
+#define MGA_DMA_IDLE_MASK		(MGA_SOFTRAPEN |		\
+					 MGA_ENDPRDMASTS)
+
+#define MGA_DMA_DEBUG			0
+
+
+
+/* A reduced set of the mga registers.
+ */
+#define MGA_CRTC_INDEX			0x1fd4
+#define MGA_CRTC_DATA			0x1fd5
+
+/* CRTC11 */
+#define MGA_VINTCLR			(1 << 4)
+#define MGA_VINTEN			(1 << 5)
+
+#define MGA_ALPHACTRL 			0x2c7c
+#define MGA_AR0 			0x1c60
+#define MGA_AR1 			0x1c64
+#define MGA_AR2 			0x1c68
+#define MGA_AR3 			0x1c6c
+#define MGA_AR4 			0x1c70
+#define MGA_AR5 			0x1c74
+#define MGA_AR6 			0x1c78
+
+#define MGA_CXBNDRY			0x1c80
+#define MGA_CXLEFT 			0x1ca0
+#define MGA_CXRIGHT			0x1ca4
+
+#define MGA_DMAPAD 			0x1c54
+#define MGA_DSTORG 			0x2cb8
+#define MGA_DWGCTL 			0x1c00
+#	define MGA_OPCOD_MASK			(15 << 0)
+#	define MGA_OPCOD_TRAP			(4 << 0)
+#	define MGA_OPCOD_TEXTURE_TRAP		(6 << 0)
+#	define MGA_OPCOD_BITBLT			(8 << 0)
+#	define MGA_OPCOD_ILOAD			(9 << 0)
+#	define MGA_ATYPE_MASK			(7 << 4)
+#	define MGA_ATYPE_RPL			(0 << 4)
+#	define MGA_ATYPE_RSTR			(1 << 4)
+#	define MGA_ATYPE_ZI			(3 << 4)
+#	define MGA_ATYPE_BLK			(4 << 4)
+#	define MGA_ATYPE_I			(7 << 4)
+#	define MGA_LINEAR			(1 << 7)
+#	define MGA_ZMODE_MASK			(7 << 8)
+#	define MGA_ZMODE_NOZCMP			(0 << 8)
+#	define MGA_ZMODE_ZE			(2 << 8)
+#	define MGA_ZMODE_ZNE			(3 << 8)
+#	define MGA_ZMODE_ZLT			(4 << 8)
+#	define MGA_ZMODE_ZLTE			(5 << 8)
+#	define MGA_ZMODE_ZGT			(6 << 8)
+#	define MGA_ZMODE_ZGTE			(7 << 8)
+#	define MGA_SOLID			(1 << 11)
+#	define MGA_ARZERO			(1 << 12)
+#	define MGA_SGNZERO			(1 << 13)
+#	define MGA_SHIFTZERO			(1 << 14)
+#	define MGA_BOP_MASK			(15 << 16)
+#	define MGA_BOP_ZERO			(0 << 16)
+#	define MGA_BOP_DST			(10 << 16)
+#	define MGA_BOP_SRC			(12 << 16)
+#	define MGA_BOP_ONE			(15 << 16)
+#	define MGA_TRANS_SHIFT			20
+#	define MGA_TRANS_MASK			(15 << 20)
+#	define MGA_BLTMOD_MASK			(15 << 25)
+#	define MGA_BLTMOD_BMONOLEF		(0 << 25)
+#	define MGA_BLTMOD_BMONOWF		(4 << 25)
+#	define MGA_BLTMOD_PLAN			(1 << 25)
+#	define MGA_BLTMOD_BFCOL			(2 << 25)
+#	define MGA_BLTMOD_BU32BGR		(3 << 25)
+#	define MGA_BLTMOD_BU32RGB		(7 << 25)
+#	define MGA_BLTMOD_BU24BGR		(11 << 25)
+#	define MGA_BLTMOD_BU24RGB		(15 << 25)
+#	define MGA_PATTERN			(1 << 29)
+#	define MGA_TRANSC			(1 << 30)
+#	define MGA_CLIPDIS			(1 << 31)
+#define MGA_DWGSYNC			0x2c4c
+
+#define MGA_FCOL 			0x1c24
+#define MGA_FIFOSTATUS 			0x1e10
+#define MGA_FOGCOL 			0x1cf4
+#define MGA_FXBNDRY			0x1c84
+#define MGA_FXLEFT 			0x1ca8
+#define MGA_FXRIGHT			0x1cac
+
+#define MGA_ICLEAR 			0x1e18
+#	define MGA_SOFTRAPICLR			(1 << 0)
+#	define MGA_VLINEICLR			(1 << 5)
+#define MGA_IEN 			0x1e1c
+#	define MGA_SOFTRAPIEN			(1 << 0)
+#	define MGA_VLINEIEN			(1 << 5)
+
+#define MGA_LEN 			0x1c5c
+
+#define MGA_MACCESS			0x1c04
+
+#define MGA_PITCH 			0x1c8c
+#define MGA_PLNWT 			0x1c1c
+#define MGA_PRIMADDRESS 		0x1e58
+#	define MGA_DMA_GENERAL			(0 << 0)
+#	define MGA_DMA_BLIT			(1 << 0)
+#	define MGA_DMA_VECTOR			(2 << 0)
+#	define MGA_DMA_VERTEX			(3 << 0)
+#define MGA_PRIMEND			0x1e5c
+#	define MGA_PRIMNOSTART			(1 << 0)
+#	define MGA_PAGPXFER			(1 << 1)
+#define MGA_PRIMPTR			0x1e50
+#	define MGA_PRIMPTREN0			(1 << 0)
+#	define MGA_PRIMPTREN1			(1 << 1)
+
+#define MGA_RST 			0x1e40
+#	define MGA_SOFTRESET			(1 << 0)
+#	define MGA_SOFTEXTRST			(1 << 1)
+
+#define MGA_SECADDRESS 			0x2c40
+#define MGA_SECEND 			0x2c44
+#define MGA_SETUPADDRESS 		0x2cd0
+#define MGA_SETUPEND 			0x2cd4
+#define MGA_SGN				0x1c58
+#define MGA_SOFTRAP			0x2c48
+#define MGA_SRCORG 			0x2cb4
+#	define MGA_SRMMAP_MASK			(1 << 0)
+#	define MGA_SRCMAP_FB			(0 << 0)
+#	define MGA_SRCMAP_SYSMEM		(1 << 0)
+#	define MGA_SRCACC_MASK			(1 << 1)
+#	define MGA_SRCACC_PCI			(0 << 1)
+#	define MGA_SRCACC_AGP			(1 << 1)
+#define MGA_STATUS 			0x1e14
+#	define MGA_SOFTRAPEN			(1 << 0)
+#	define MGA_VSYNCPEN			(1 << 4)
+#	define MGA_VLINEPEN			(1 << 5)
+#	define MGA_DWGENGSTS			(1 << 16)
+#	define MGA_ENDPRDMASTS			(1 << 17)
+#define MGA_STENCIL			0x2cc8
+#define MGA_STENCILCTL 			0x2ccc
+
+#define MGA_TDUALSTAGE0 		0x2cf8
+#define MGA_TDUALSTAGE1 		0x2cfc
+#define MGA_TEXBORDERCOL 		0x2c5c
+#define MGA_TEXCTL 			0x2c30
+#define MGA_TEXCTL2			0x2c3c
+#	define MGA_DUALTEX			(1 << 7)
+#	define MGA_G400_TC2_MAGIC		(1 << 15)
+#	define MGA_MAP1_ENABLE			(1 << 31)
+#define MGA_TEXFILTER 			0x2c58
+#define MGA_TEXHEIGHT 			0x2c2c
+#define MGA_TEXORG 			0x2c24
+#	define MGA_TEXORGMAP_MASK		(1 << 0)
+#	define MGA_TEXORGMAP_FB			(0 << 0)
+#	define MGA_TEXORGMAP_SYSMEM		(1 << 0)
+#	define MGA_TEXORGACC_MASK		(1 << 1)
+#	define MGA_TEXORGACC_PCI		(0 << 1)
+#	define MGA_TEXORGACC_AGP		(1 << 1)
+#define MGA_TEXORG1			0x2ca4
+#define MGA_TEXORG2			0x2ca8
+#define MGA_TEXORG3			0x2cac
+#define MGA_TEXORG4			0x2cb0
+#define MGA_TEXTRANS 			0x2c34
+#define MGA_TEXTRANSHIGH 		0x2c38
+#define MGA_TEXWIDTH 			0x2c28
+
+#define MGA_WACCEPTSEQ 			0x1dd4
+#define MGA_WCODEADDR 			0x1e6c
+#define MGA_WFLAG 			0x1dc4
+#define MGA_WFLAG1 			0x1de0
+#define MGA_WFLAGNB			0x1e64
+#define MGA_WFLAGNB1 			0x1e08
+#define MGA_WGETMSB			0x1dc8
+#define MGA_WIADDR 			0x1dc0
+#define MGA_WIADDR2			0x1dd8
+#	define MGA_WMODE_SUSPEND		(0 << 0)
+#	define MGA_WMODE_RESUME			(1 << 0)
+#	define MGA_WMODE_JUMP			(2 << 0)
+#	define MGA_WMODE_START			(3 << 0)
+#	define MGA_WAGP_ENABLE			(1 << 2)
+#define MGA_WMISC 			0x1e70
+#	define MGA_WUCODECACHE_ENABLE		(1 << 0)
+#	define MGA_WMASTER_ENABLE		(1 << 1)
+#	define MGA_WCACHEFLUSH_ENABLE		(1 << 3)
+#define MGA_WVRTXSZ			0x1dcc
+
+#define MGA_YBOT 			0x1c9c
+#define MGA_YDST 			0x1c90
+#define MGA_YDSTLEN			0x1c88
+#define MGA_YDSTORG			0x1c94
+#define MGA_YTOP 			0x1c98
+
+#define MGA_ZORG 			0x1c0c
+
+/* This finishes the current batch of commands
+ */
+#define MGA_EXEC 			0x0100
+
+/* Warp registers
+ */
+#define MGA_WR0				0x2d00
+#define MGA_WR1				0x2d04
+#define MGA_WR2				0x2d08
+#define MGA_WR3				0x2d0c
+#define MGA_WR4				0x2d10
+#define MGA_WR5				0x2d14
+#define MGA_WR6				0x2d18
+#define MGA_WR7				0x2d1c
+#define MGA_WR8				0x2d20
+#define MGA_WR9				0x2d24
+#define MGA_WR10			0x2d28
+#define MGA_WR11			0x2d2c
+#define MGA_WR12			0x2d30
+#define MGA_WR13			0x2d34
+#define MGA_WR14			0x2d38
+#define MGA_WR15			0x2d3c
+#define MGA_WR16			0x2d40
+#define MGA_WR17			0x2d44
+#define MGA_WR18			0x2d48
+#define MGA_WR19			0x2d4c
+#define MGA_WR20			0x2d50
+#define MGA_WR21			0x2d54
+#define MGA_WR22			0x2d58
+#define MGA_WR23			0x2d5c
+#define MGA_WR24			0x2d60
+#define MGA_WR25			0x2d64
+#define MGA_WR26			0x2d68
+#define MGA_WR27			0x2d6c
+#define MGA_WR28			0x2d70
+#define MGA_WR29			0x2d74
+#define MGA_WR30			0x2d78
+#define MGA_WR31			0x2d7c
+#define MGA_WR32			0x2d80
+#define MGA_WR33			0x2d84
+#define MGA_WR34			0x2d88
+#define MGA_WR35			0x2d8c
+#define MGA_WR36			0x2d90
+#define MGA_WR37			0x2d94
+#define MGA_WR38			0x2d98
+#define MGA_WR39			0x2d9c
+#define MGA_WR40			0x2da0
+#define MGA_WR41			0x2da4
+#define MGA_WR42			0x2da8
+#define MGA_WR43			0x2dac
+#define MGA_WR44			0x2db0
+#define MGA_WR45			0x2db4
+#define MGA_WR46			0x2db8
+#define MGA_WR47			0x2dbc
+#define MGA_WR48			0x2dc0
+#define MGA_WR49			0x2dc4
+#define MGA_WR50			0x2dc8
+#define MGA_WR51			0x2dcc
+#define MGA_WR52			0x2dd0
+#define MGA_WR53			0x2dd4
+#define MGA_WR54			0x2dd8
+#define MGA_WR55			0x2ddc
+#define MGA_WR56			0x2de0
+#define MGA_WR57			0x2de4
+#define MGA_WR58			0x2de8
+#define MGA_WR59			0x2dec
+#define MGA_WR60			0x2df0
+#define MGA_WR61			0x2df4
+#define MGA_WR62			0x2df8
+#define MGA_WR63			0x2dfc
+#	define MGA_G400_WR_MAGIC		(1 << 6)
+#	define MGA_G400_WR56_MAGIC		0x46480000	/* 12800.0f */
+
+
+#define MGA_ILOAD_ALIGN		64
+#define MGA_ILOAD_MASK		(MGA_ILOAD_ALIGN - 1)
+
+#define MGA_DWGCTL_FLUSH	(MGA_OPCOD_TEXTURE_TRAP |		\
+				 MGA_ATYPE_I |				\
+				 MGA_ZMODE_NOZCMP |			\
+				 MGA_ARZERO |				\
+				 MGA_SGNZERO |				\
+				 MGA_BOP_SRC |				\
+				 (15 << MGA_TRANS_SHIFT))
+
+#define MGA_DWGCTL_CLEAR	(MGA_OPCOD_TRAP |			\
+				 MGA_ZMODE_NOZCMP |			\
+				 MGA_SOLID |				\
+				 MGA_ARZERO |				\
+				 MGA_SGNZERO |				\
+				 MGA_SHIFTZERO |			\
+				 MGA_BOP_SRC |				\
+				 (0 << MGA_TRANS_SHIFT) |		\
+				 MGA_BLTMOD_BMONOLEF |			\
+				 MGA_TRANSC |				\
+				 MGA_CLIPDIS)
+
+#define MGA_DWGCTL_COPY		(MGA_OPCOD_BITBLT |			\
+				 MGA_ATYPE_RPL |			\
+				 MGA_SGNZERO |				\
+				 MGA_SHIFTZERO |			\
+				 MGA_BOP_SRC |				\
+				 (0 << MGA_TRANS_SHIFT) |		\
+				 MGA_BLTMOD_BFCOL |			\
+				 MGA_CLIPDIS)
+
+/* Simple idle test.
+ */
+static __inline__ int mga_is_idle( drm_mga_private_t *dev_priv )
+{
+	u32 status = MGA_READ( MGA_STATUS ) & MGA_ENGINE_IDLE_MASK;
+	return ( status == MGA_ENDPRDMASTS );
+}
+
+#endif
diff --git a/drivers/char/drm/mga_irq.c b/drivers/char/drm/mga_irq.c
new file mode 100644
index 0000000..bc0b6b5
--- /dev/null
+++ b/drivers/char/drm/mga_irq.c
@@ -0,0 +1,102 @@
+/* mga_irq.c -- IRQ handling for radeon -*- linux-c -*-
+ *
+ * Copyright (C) The Weather Channel, Inc.  2002.  All Rights Reserved.
+ * 
+ * The Weather Channel (TM) funded Tungsten Graphics to develop the
+ * initial release of the Radeon 8500 driver under the XFree86 license.
+ * This notice must be preserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ *    Eric Anholt <anholt@FreeBSD.org>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+
+irqreturn_t mga_driver_irq_handler( DRM_IRQ_ARGS )
+{
+	drm_device_t *dev = (drm_device_t *) arg;
+	drm_mga_private_t *dev_priv = 
+	   (drm_mga_private_t *)dev->dev_private;
+	int status;
+
+	status = MGA_READ( MGA_STATUS );
+	
+	/* VBLANK interrupt */
+	if ( status & MGA_VLINEPEN ) {
+		MGA_WRITE( MGA_ICLEAR, MGA_VLINEICLR );
+		atomic_inc(&dev->vbl_received);
+		DRM_WAKEUP(&dev->vbl_queue);
+		drm_vbl_send_signals( dev );
+		return IRQ_HANDLED;
+	}
+	return IRQ_NONE;
+}
+
+int mga_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence)
+{
+	unsigned int cur_vblank;
+	int ret = 0;
+
+	/* Assume that the user has missed the current sequence number
+	 * by about a day rather than she wants to wait for years
+	 * using vertical blanks... 
+	 */
+	DRM_WAIT_ON( ret, dev->vbl_queue, 3*DRM_HZ, 
+		     ( ( ( cur_vblank = atomic_read(&dev->vbl_received ) )
+			 - *sequence ) <= (1<<23) ) );
+
+	*sequence = cur_vblank;
+
+	return ret;
+}
+
+void mga_driver_irq_preinstall( drm_device_t *dev ) {
+  	drm_mga_private_t *dev_priv = 
+	   (drm_mga_private_t *)dev->dev_private;
+
+	/* Disable *all* interrupts */
+      	MGA_WRITE( MGA_IEN, 0 );
+	/* Clear bits if they're already high */
+   	MGA_WRITE( MGA_ICLEAR, ~0 );
+}
+
+void mga_driver_irq_postinstall( drm_device_t *dev ) {
+  	drm_mga_private_t *dev_priv = 
+	   (drm_mga_private_t *)dev->dev_private;
+
+	/* Turn on VBL interrupt */
+   	MGA_WRITE( MGA_IEN, MGA_VLINEIEN );
+}
+
+void mga_driver_irq_uninstall( drm_device_t *dev ) {
+  	drm_mga_private_t *dev_priv = 
+	   (drm_mga_private_t *)dev->dev_private;
+	if (!dev_priv)
+		return;
+
+	/* Disable *all* interrupts */
+	MGA_WRITE( MGA_IEN, 0 );
+}
diff --git a/drivers/char/drm/mga_state.c b/drivers/char/drm/mga_state.c
new file mode 100644
index 0000000..3c7a8f5
--- /dev/null
+++ b/drivers/char/drm/mga_state.c
@@ -0,0 +1,1123 @@
+/* mga_state.c -- State support for MGA G200/G400 -*- linux-c -*-
+ * Created: Thu Jan 27 02:53:43 2000 by jhartmann@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Jeff Hartmann <jhartmann@valinux.com>
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ *
+ * Rewritten by:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+
+/* ================================================================
+ * DMA hardware state programming functions
+ */
+
+static void mga_emit_clip_rect( drm_mga_private_t *dev_priv,
+				drm_clip_rect_t *box )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+	unsigned int pitch = dev_priv->front_pitch;
+	DMA_LOCALS;
+
+	BEGIN_DMA( 2 );
+
+	/* Force reset of DWGCTL on G400 (eliminates clip disable bit).
+	 */
+	if ( dev_priv->chipset == MGA_CARD_TYPE_G400 ) {
+		DMA_BLOCK( MGA_DWGCTL,		ctx->dwgctl,
+			   MGA_LEN + MGA_EXEC,	0x80000000,
+			   MGA_DWGCTL,		ctx->dwgctl,
+			   MGA_LEN + MGA_EXEC,	0x80000000 );
+	}
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_CXBNDRY,	(box->x2 << 16) | box->x1,
+		   MGA_YTOP,	box->y1 * pitch,
+		   MGA_YBOT,	box->y2 * pitch );
+
+	ADVANCE_DMA();
+}
+
+static __inline__ void mga_g200_emit_context( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+	DMA_LOCALS;
+
+	BEGIN_DMA( 3 );
+
+	DMA_BLOCK( MGA_DSTORG,		ctx->dstorg,
+		   MGA_MACCESS,		ctx->maccess,
+		   MGA_PLNWT,		ctx->plnwt,
+		   MGA_DWGCTL,		ctx->dwgctl );
+
+	DMA_BLOCK( MGA_ALPHACTRL,	ctx->alphactrl,
+		   MGA_FOGCOL,		ctx->fogcolor,
+		   MGA_WFLAG,		ctx->wflag,
+		   MGA_ZORG,		dev_priv->depth_offset );
+
+	DMA_BLOCK( MGA_FCOL,		ctx->fcol,
+		   MGA_DMAPAD,		0x00000000,
+		   MGA_DMAPAD,		0x00000000,
+		   MGA_DMAPAD,		0x00000000 );
+
+	ADVANCE_DMA();
+}
+
+static __inline__ void mga_g400_emit_context( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+	DMA_LOCALS;
+
+	BEGIN_DMA( 4 );
+
+	DMA_BLOCK( MGA_DSTORG,		ctx->dstorg,
+		   MGA_MACCESS,		ctx->maccess,
+		   MGA_PLNWT,		ctx->plnwt,
+		   MGA_DWGCTL,		ctx->dwgctl );
+
+	DMA_BLOCK( MGA_ALPHACTRL,	ctx->alphactrl,
+		   MGA_FOGCOL,		ctx->fogcolor,
+		   MGA_WFLAG,		ctx->wflag,
+		   MGA_ZORG,		dev_priv->depth_offset );
+
+	DMA_BLOCK( MGA_WFLAG1,		ctx->wflag,
+		   MGA_TDUALSTAGE0,	ctx->tdualstage0,
+		   MGA_TDUALSTAGE1,	ctx->tdualstage1,
+		   MGA_FCOL,		ctx->fcol );
+
+	DMA_BLOCK( MGA_STENCIL,		ctx->stencil,
+		   MGA_STENCILCTL,	ctx->stencilctl,
+		   MGA_DMAPAD,		0x00000000,
+		   MGA_DMAPAD,		0x00000000 );
+
+	ADVANCE_DMA();
+}
+
+static __inline__ void mga_g200_emit_tex0( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_texture_regs_t *tex = &sarea_priv->tex_state[0];
+	DMA_LOCALS;
+
+	BEGIN_DMA( 4 );
+
+	DMA_BLOCK( MGA_TEXCTL2,		tex->texctl2,
+		   MGA_TEXCTL,		tex->texctl,
+		   MGA_TEXFILTER,	tex->texfilter,
+		   MGA_TEXBORDERCOL,	tex->texbordercol );
+
+	DMA_BLOCK( MGA_TEXORG,		tex->texorg,
+		   MGA_TEXORG1,		tex->texorg1,
+		   MGA_TEXORG2,		tex->texorg2,
+		   MGA_TEXORG3,		tex->texorg3 );
+
+	DMA_BLOCK( MGA_TEXORG4,		tex->texorg4,
+		   MGA_TEXWIDTH,	tex->texwidth,
+		   MGA_TEXHEIGHT,	tex->texheight,
+		   MGA_WR24,		tex->texwidth );
+
+	DMA_BLOCK( MGA_WR34,		tex->texheight,
+		   MGA_TEXTRANS,	0x0000ffff,
+		   MGA_TEXTRANSHIGH,	0x0000ffff,
+		   MGA_DMAPAD,		0x00000000 );
+
+	ADVANCE_DMA();
+}
+
+static __inline__ void mga_g400_emit_tex0( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_texture_regs_t *tex = &sarea_priv->tex_state[0];
+	DMA_LOCALS;
+
+/*  	printk("mga_g400_emit_tex0 %x %x %x\n", tex->texorg, */
+/*  	       tex->texctl, tex->texctl2); */
+
+	BEGIN_DMA( 6 );
+
+	DMA_BLOCK( MGA_TEXCTL2,		tex->texctl2 | MGA_G400_TC2_MAGIC,
+		   MGA_TEXCTL,		tex->texctl,
+		   MGA_TEXFILTER,	tex->texfilter,
+		   MGA_TEXBORDERCOL,	tex->texbordercol );
+
+	DMA_BLOCK( MGA_TEXORG,		tex->texorg,
+		   MGA_TEXORG1,		tex->texorg1,
+		   MGA_TEXORG2,		tex->texorg2,
+		   MGA_TEXORG3,		tex->texorg3 );
+
+	DMA_BLOCK( MGA_TEXORG4,		tex->texorg4,
+		   MGA_TEXWIDTH,	tex->texwidth,
+		   MGA_TEXHEIGHT,	tex->texheight,
+		   MGA_WR49,		0x00000000 );
+
+	DMA_BLOCK( MGA_WR57,		0x00000000,
+		   MGA_WR53,		0x00000000,
+		   MGA_WR61,		0x00000000,
+		   MGA_WR52,		MGA_G400_WR_MAGIC );
+
+	DMA_BLOCK( MGA_WR60,		MGA_G400_WR_MAGIC,
+		   MGA_WR54,		tex->texwidth | MGA_G400_WR_MAGIC,
+		   MGA_WR62,		tex->texheight | MGA_G400_WR_MAGIC,
+		   MGA_DMAPAD,		0x00000000 );
+
+	DMA_BLOCK( MGA_DMAPAD,		0x00000000,
+		   MGA_DMAPAD,		0x00000000,
+		   MGA_TEXTRANS,	0x0000ffff,
+		   MGA_TEXTRANSHIGH,	0x0000ffff );
+
+	ADVANCE_DMA();
+}
+
+static __inline__ void mga_g400_emit_tex1( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_texture_regs_t *tex = &sarea_priv->tex_state[1];
+	DMA_LOCALS;
+
+/*  	printk("mga_g400_emit_tex1 %x %x %x\n", tex->texorg,  */
+/*  	       tex->texctl, tex->texctl2); */
+
+	BEGIN_DMA( 5 );
+
+	DMA_BLOCK( MGA_TEXCTL2,		(tex->texctl2 |
+					 MGA_MAP1_ENABLE |
+					 MGA_G400_TC2_MAGIC),
+		   MGA_TEXCTL,		tex->texctl,
+		   MGA_TEXFILTER,	tex->texfilter,
+		   MGA_TEXBORDERCOL,	tex->texbordercol );
+
+	DMA_BLOCK( MGA_TEXORG,		tex->texorg,
+		   MGA_TEXORG1,		tex->texorg1,
+		   MGA_TEXORG2,		tex->texorg2,
+		   MGA_TEXORG3,		tex->texorg3 );
+
+	DMA_BLOCK( MGA_TEXORG4,		tex->texorg4,
+		   MGA_TEXWIDTH,	tex->texwidth,
+		   MGA_TEXHEIGHT,	tex->texheight,
+		   MGA_WR49,		0x00000000 );
+
+	DMA_BLOCK( MGA_WR57,		0x00000000,
+		   MGA_WR53,		0x00000000,
+		   MGA_WR61,		0x00000000,
+		   MGA_WR52,		tex->texwidth | MGA_G400_WR_MAGIC );
+
+	DMA_BLOCK( MGA_WR60,		tex->texheight | MGA_G400_WR_MAGIC,
+		   MGA_TEXTRANS,	0x0000ffff,
+		   MGA_TEXTRANSHIGH,	0x0000ffff,
+		   MGA_TEXCTL2,		tex->texctl2 | MGA_G400_TC2_MAGIC );
+
+	ADVANCE_DMA();
+}
+
+static __inline__ void mga_g200_emit_pipe( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned int pipe = sarea_priv->warp_pipe;
+	DMA_LOCALS;
+
+	BEGIN_DMA( 3 );
+
+	DMA_BLOCK( MGA_WIADDR,	MGA_WMODE_SUSPEND,
+		   MGA_WVRTXSZ,	0x00000007,
+		   MGA_WFLAG,	0x00000000,
+		   MGA_WR24,	0x00000000 );
+
+	DMA_BLOCK( MGA_WR25,	0x00000100,
+		   MGA_WR34,	0x00000000,
+		   MGA_WR42,	0x0000ffff,
+		   MGA_WR60,	0x0000ffff );
+
+	/* Padding required to to hardware bug.
+	 */
+	DMA_BLOCK( MGA_DMAPAD,	0xffffffff,
+		   MGA_DMAPAD,	0xffffffff,
+		   MGA_DMAPAD,	0xffffffff,
+		   MGA_WIADDR,	(dev_priv->warp_pipe_phys[pipe] |
+				 MGA_WMODE_START |
+				 MGA_WAGP_ENABLE) );
+
+	ADVANCE_DMA();
+}
+
+static __inline__ void mga_g400_emit_pipe( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned int pipe = sarea_priv->warp_pipe;
+	DMA_LOCALS;
+
+/*  	printk("mga_g400_emit_pipe %x\n", pipe); */
+
+	BEGIN_DMA( 10 );
+
+	DMA_BLOCK( MGA_WIADDR2,	MGA_WMODE_SUSPEND,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000 );
+
+	if ( pipe & MGA_T2 ) {
+		DMA_BLOCK( MGA_WVRTXSZ,		0x00001e09,
+			   MGA_DMAPAD,		0x00000000,
+			   MGA_DMAPAD,		0x00000000,
+			   MGA_DMAPAD,		0x00000000 );
+
+		DMA_BLOCK( MGA_WACCEPTSEQ,	0x00000000,
+			   MGA_WACCEPTSEQ,	0x00000000,
+			   MGA_WACCEPTSEQ,	0x00000000,
+			   MGA_WACCEPTSEQ,	0x1e000000 );
+	} else {
+		if ( dev_priv->warp_pipe & MGA_T2 ) {
+			/* Flush the WARP pipe */
+			DMA_BLOCK( MGA_YDST,		0x00000000,
+				   MGA_FXLEFT,		0x00000000,
+				   MGA_FXRIGHT,		0x00000001,
+				   MGA_DWGCTL,		MGA_DWGCTL_FLUSH );
+
+			DMA_BLOCK( MGA_LEN + MGA_EXEC,	0x00000001,
+				   MGA_DWGSYNC,		0x00007000,
+				   MGA_TEXCTL2,		MGA_G400_TC2_MAGIC,
+				   MGA_LEN + MGA_EXEC,	0x00000000 );
+
+			DMA_BLOCK( MGA_TEXCTL2,		(MGA_DUALTEX |
+							 MGA_G400_TC2_MAGIC),
+				   MGA_LEN + MGA_EXEC,	0x00000000,
+				   MGA_TEXCTL2,		MGA_G400_TC2_MAGIC,
+				   MGA_DMAPAD,		0x00000000 );
+		}
+
+		DMA_BLOCK( MGA_WVRTXSZ,		0x00001807,
+			   MGA_DMAPAD,		0x00000000,
+			   MGA_DMAPAD,		0x00000000,
+			   MGA_DMAPAD,		0x00000000 );
+
+		DMA_BLOCK( MGA_WACCEPTSEQ,	0x00000000,
+			   MGA_WACCEPTSEQ,	0x00000000,
+			   MGA_WACCEPTSEQ,	0x00000000,
+			   MGA_WACCEPTSEQ,	0x18000000 );
+	}
+
+	DMA_BLOCK( MGA_WFLAG,	0x00000000,
+		   MGA_WFLAG1,	0x00000000,
+		   MGA_WR56,	MGA_G400_WR56_MAGIC,
+		   MGA_DMAPAD,	0x00000000 );
+
+	DMA_BLOCK( MGA_WR49,	0x00000000,		/* tex0              */
+		   MGA_WR57,	0x00000000,		/* tex0              */
+		   MGA_WR53,	0x00000000,		/* tex1              */
+		   MGA_WR61,	0x00000000 );		/* tex1              */
+
+	DMA_BLOCK( MGA_WR54,	MGA_G400_WR_MAGIC,	/* tex0 width        */
+		   MGA_WR62,	MGA_G400_WR_MAGIC,	/* tex0 height       */
+		   MGA_WR52,	MGA_G400_WR_MAGIC,	/* tex1 width        */
+		   MGA_WR60,	MGA_G400_WR_MAGIC );	/* tex1 height       */
+
+	/* Padding required to to hardware bug */
+	DMA_BLOCK( MGA_DMAPAD,	0xffffffff,
+		   MGA_DMAPAD,	0xffffffff,
+		   MGA_DMAPAD,	0xffffffff,
+		   MGA_WIADDR2,	(dev_priv->warp_pipe_phys[pipe] |
+				 MGA_WMODE_START |
+				 MGA_WAGP_ENABLE) );
+
+	ADVANCE_DMA();
+}
+
+static void mga_g200_emit_state( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned int dirty = sarea_priv->dirty;
+
+	if ( sarea_priv->warp_pipe != dev_priv->warp_pipe ) {
+		mga_g200_emit_pipe( dev_priv );
+		dev_priv->warp_pipe = sarea_priv->warp_pipe;
+	}
+
+	if ( dirty & MGA_UPLOAD_CONTEXT ) {
+		mga_g200_emit_context( dev_priv );
+		sarea_priv->dirty &= ~MGA_UPLOAD_CONTEXT;
+	}
+
+	if ( dirty & MGA_UPLOAD_TEX0 ) {
+		mga_g200_emit_tex0( dev_priv );
+		sarea_priv->dirty &= ~MGA_UPLOAD_TEX0;
+	}
+}
+
+static void mga_g400_emit_state( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned int dirty = sarea_priv->dirty;
+	int multitex = sarea_priv->warp_pipe & MGA_T2;
+
+	if ( sarea_priv->warp_pipe != dev_priv->warp_pipe ) {
+		mga_g400_emit_pipe( dev_priv );
+		dev_priv->warp_pipe = sarea_priv->warp_pipe;
+	}
+
+	if ( dirty & MGA_UPLOAD_CONTEXT ) {
+		mga_g400_emit_context( dev_priv );
+		sarea_priv->dirty &= ~MGA_UPLOAD_CONTEXT;
+	}
+
+	if ( dirty & MGA_UPLOAD_TEX0 ) {
+		mga_g400_emit_tex0( dev_priv );
+		sarea_priv->dirty &= ~MGA_UPLOAD_TEX0;
+	}
+
+	if ( (dirty & MGA_UPLOAD_TEX1) && multitex ) {
+		mga_g400_emit_tex1( dev_priv );
+		sarea_priv->dirty &= ~MGA_UPLOAD_TEX1;
+	}
+}
+
+
+/* ================================================================
+ * SAREA state verification
+ */
+
+/* Disallow all write destinations except the front and backbuffer.
+ */
+static int mga_verify_context( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+
+	if ( ctx->dstorg != dev_priv->front_offset &&
+	     ctx->dstorg != dev_priv->back_offset ) {
+		DRM_ERROR( "*** bad DSTORG: %x (front %x, back %x)\n\n",
+			   ctx->dstorg, dev_priv->front_offset,
+			   dev_priv->back_offset );
+		ctx->dstorg = 0;
+		return DRM_ERR(EINVAL);
+	}
+
+	return 0;
+}
+
+/* Disallow texture reads from PCI space.
+ */
+static int mga_verify_tex( drm_mga_private_t *dev_priv, int unit )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_texture_regs_t *tex = &sarea_priv->tex_state[unit];
+	unsigned int org;
+
+	org = tex->texorg & (MGA_TEXORGMAP_MASK | MGA_TEXORGACC_MASK);
+
+	if ( org == (MGA_TEXORGMAP_SYSMEM | MGA_TEXORGACC_PCI) ) {
+		DRM_ERROR( "*** bad TEXORG: 0x%x, unit %d\n",
+			   tex->texorg, unit );
+		tex->texorg = 0;
+		return DRM_ERR(EINVAL);
+	}
+
+	return 0;
+}
+
+static int mga_verify_state( drm_mga_private_t *dev_priv )
+{
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned int dirty = sarea_priv->dirty;
+	int ret = 0;
+
+	if ( sarea_priv->nbox > MGA_NR_SAREA_CLIPRECTS )
+		sarea_priv->nbox = MGA_NR_SAREA_CLIPRECTS;
+
+	if ( dirty & MGA_UPLOAD_CONTEXT )
+		ret |= mga_verify_context( dev_priv );
+
+	if ( dirty & MGA_UPLOAD_TEX0 )
+		ret |= mga_verify_tex( dev_priv, 0 );
+
+	if ( dev_priv->chipset == MGA_CARD_TYPE_G400 ) {
+		if ( dirty & MGA_UPLOAD_TEX1 )
+			ret |= mga_verify_tex( dev_priv, 1 );
+
+		if ( dirty & MGA_UPLOAD_PIPE )
+			ret |= ( sarea_priv->warp_pipe > MGA_MAX_G400_PIPES );
+	} else {
+		if ( dirty & MGA_UPLOAD_PIPE )
+			ret |= ( sarea_priv->warp_pipe > MGA_MAX_G200_PIPES );
+	}
+
+	return ( ret == 0 );
+}
+
+static int mga_verify_iload( drm_mga_private_t *dev_priv,
+			     unsigned int dstorg, unsigned int length )
+{
+	if ( dstorg < dev_priv->texture_offset ||
+	     dstorg + length > (dev_priv->texture_offset +
+				dev_priv->texture_size) ) {
+		DRM_ERROR( "*** bad iload DSTORG: 0x%x\n", dstorg );
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( length & MGA_ILOAD_MASK ) {
+		DRM_ERROR( "*** bad iload length: 0x%x\n",
+			   length & MGA_ILOAD_MASK );
+		return DRM_ERR(EINVAL);
+	}
+
+	return 0;
+}
+
+static int mga_verify_blit( drm_mga_private_t *dev_priv,
+			    unsigned int srcorg, unsigned int dstorg )
+{
+	if ( (srcorg & 0x3) == (MGA_SRCACC_PCI | MGA_SRCMAP_SYSMEM) ||
+	     (dstorg & 0x3) == (MGA_SRCACC_PCI | MGA_SRCMAP_SYSMEM) ) {
+		DRM_ERROR( "*** bad blit: src=0x%x dst=0x%x\n",
+			   srcorg, dstorg );
+		return DRM_ERR(EINVAL);
+	}
+	return 0;
+}
+
+
+/* ================================================================
+ *
+ */
+
+static void mga_dma_dispatch_clear( drm_device_t *dev,
+				    drm_mga_clear_t *clear )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int nbox = sarea_priv->nbox;
+	int i;
+	DMA_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	BEGIN_DMA( 1 );
+
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DWGSYNC,	0x00007100,
+		   MGA_DWGSYNC,	0x00007000 );
+
+	ADVANCE_DMA();
+
+	for ( i = 0 ; i < nbox ; i++ ) {
+		drm_clip_rect_t *box = &pbox[i];
+		u32 height = box->y2 - box->y1;
+
+		DRM_DEBUG( "   from=%d,%d to=%d,%d\n",
+			   box->x1, box->y1, box->x2, box->y2 );
+
+		if ( clear->flags & MGA_FRONT ) {
+			BEGIN_DMA( 2 );
+
+			DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+				   MGA_PLNWT,	clear->color_mask,
+				   MGA_YDSTLEN, (box->y1 << 16) | height,
+				   MGA_FXBNDRY, (box->x2 << 16) | box->x1 );
+
+			DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+				   MGA_FCOL,	clear->clear_color,
+				   MGA_DSTORG,	dev_priv->front_offset,
+				   MGA_DWGCTL + MGA_EXEC,
+						dev_priv->clear_cmd );
+
+			ADVANCE_DMA();
+		}
+
+
+		if ( clear->flags & MGA_BACK ) {
+			BEGIN_DMA( 2 );
+
+			DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+				   MGA_PLNWT,	clear->color_mask,
+				   MGA_YDSTLEN, (box->y1 << 16) | height,
+				   MGA_FXBNDRY, (box->x2 << 16) | box->x1 );
+
+			DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+				   MGA_FCOL,	clear->clear_color,
+				   MGA_DSTORG,	dev_priv->back_offset,
+				   MGA_DWGCTL + MGA_EXEC,
+						dev_priv->clear_cmd );
+
+			ADVANCE_DMA();
+		}
+
+		if ( clear->flags & MGA_DEPTH ) {
+			BEGIN_DMA( 2 );
+
+			DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+				   MGA_PLNWT,	clear->depth_mask,
+				   MGA_YDSTLEN, (box->y1 << 16) | height,
+				   MGA_FXBNDRY, (box->x2 << 16) | box->x1 );
+
+			DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+				   MGA_FCOL,	clear->clear_depth,
+				   MGA_DSTORG,	dev_priv->depth_offset,
+				   MGA_DWGCTL + MGA_EXEC,
+						dev_priv->clear_cmd );
+
+			ADVANCE_DMA();
+		}
+
+	}
+
+	BEGIN_DMA( 1 );
+
+	/* Force reset of DWGCTL */
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_PLNWT,	ctx->plnwt,
+		   MGA_DWGCTL,	ctx->dwgctl );
+
+	ADVANCE_DMA();
+
+	FLUSH_DMA();
+}
+
+static void mga_dma_dispatch_swap( drm_device_t *dev )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int nbox = sarea_priv->nbox;
+	int i;
+	DMA_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	sarea_priv->last_frame.head = dev_priv->prim.tail;
+	sarea_priv->last_frame.wrap = dev_priv->prim.last_wrap;
+
+	BEGIN_DMA( 4 + nbox );
+
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DWGSYNC,	0x00007100,
+		   MGA_DWGSYNC,	0x00007000 );
+
+	DMA_BLOCK( MGA_DSTORG,	dev_priv->front_offset,
+		   MGA_MACCESS,	dev_priv->maccess,
+		   MGA_SRCORG,	dev_priv->back_offset,
+		   MGA_AR5,	dev_priv->front_pitch );
+
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_PLNWT,	0xffffffff,
+		   MGA_DWGCTL,	MGA_DWGCTL_COPY );
+
+	for ( i = 0 ; i < nbox ; i++ ) {
+		drm_clip_rect_t *box = &pbox[i];
+		u32 height = box->y2 - box->y1;
+		u32 start = box->y1 * dev_priv->front_pitch;
+
+		DRM_DEBUG( "   from=%d,%d to=%d,%d\n",
+			   box->x1, box->y1, box->x2, box->y2 );
+
+		DMA_BLOCK( MGA_AR0,	start + box->x2 - 1,
+			   MGA_AR3,	start + box->x1,
+			   MGA_FXBNDRY,	((box->x2 - 1) << 16) | box->x1,
+			   MGA_YDSTLEN + MGA_EXEC,
+					(box->y1 << 16) | height );
+	}
+
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_PLNWT,	ctx->plnwt,
+		   MGA_SRCORG,	dev_priv->front_offset,
+		   MGA_DWGCTL,	ctx->dwgctl );
+
+	ADVANCE_DMA();
+
+	FLUSH_DMA();
+
+	DRM_DEBUG( "%s... done.\n", __FUNCTION__ );
+}
+
+static void mga_dma_dispatch_vertex( drm_device_t *dev, drm_buf_t *buf )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_buf_priv_t *buf_priv = buf->dev_private;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	u32 address = (u32) buf->bus_address;
+	u32 length = (u32) buf->used;
+	int i = 0;
+	DMA_LOCALS;
+	DRM_DEBUG( "vertex: buf=%d used=%d\n", buf->idx, buf->used );
+
+	if ( buf->used ) {
+		buf_priv->dispatched = 1;
+
+		MGA_EMIT_STATE( dev_priv, sarea_priv->dirty );
+
+		do {
+			if ( i < sarea_priv->nbox ) {
+				mga_emit_clip_rect( dev_priv,
+						    &sarea_priv->boxes[i] );
+			}
+
+			BEGIN_DMA( 1 );
+
+			DMA_BLOCK( MGA_DMAPAD,		0x00000000,
+				   MGA_DMAPAD,		0x00000000,
+				   MGA_SECADDRESS,	(address |
+							 MGA_DMA_VERTEX),
+				   MGA_SECEND,		((address + length) |
+							 MGA_PAGPXFER) );
+
+			ADVANCE_DMA();
+		} while ( ++i < sarea_priv->nbox );
+	}
+
+	if ( buf_priv->discard ) {
+		AGE_BUFFER( buf_priv );
+		buf->pending = 0;
+		buf->used = 0;
+		buf_priv->dispatched = 0;
+
+		mga_freelist_put( dev, buf );
+	}
+
+	FLUSH_DMA();
+}
+
+static void mga_dma_dispatch_indices( drm_device_t *dev, drm_buf_t *buf,
+				      unsigned int start, unsigned int end )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_buf_priv_t *buf_priv = buf->dev_private;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	u32 address = (u32) buf->bus_address;
+	int i = 0;
+	DMA_LOCALS;
+	DRM_DEBUG( "indices: buf=%d start=%d end=%d\n", buf->idx, start, end );
+
+	if ( start != end ) {
+		buf_priv->dispatched = 1;
+
+		MGA_EMIT_STATE( dev_priv, sarea_priv->dirty );
+
+		do {
+			if ( i < sarea_priv->nbox ) {
+				mga_emit_clip_rect( dev_priv,
+						    &sarea_priv->boxes[i] );
+			}
+
+			BEGIN_DMA( 1 );
+
+			DMA_BLOCK( MGA_DMAPAD,		0x00000000,
+				   MGA_DMAPAD,		0x00000000,
+				   MGA_SETUPADDRESS,	address + start,
+				   MGA_SETUPEND,	((address + end) |
+							 MGA_PAGPXFER) );
+
+			ADVANCE_DMA();
+		} while ( ++i < sarea_priv->nbox );
+	}
+
+	if ( buf_priv->discard ) {
+		AGE_BUFFER( buf_priv );
+		buf->pending = 0;
+		buf->used = 0;
+		buf_priv->dispatched = 0;
+
+		mga_freelist_put( dev, buf );
+	}
+
+	FLUSH_DMA();
+}
+
+/* This copies a 64 byte aligned agp region to the frambuffer with a
+ * standard blit, the ioctl needs to do checking.
+ */
+static void mga_dma_dispatch_iload( drm_device_t *dev, drm_buf_t *buf,
+				    unsigned int dstorg, unsigned int length )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_buf_priv_t *buf_priv = buf->dev_private;
+	drm_mga_context_regs_t *ctx = &dev_priv->sarea_priv->context_state;
+	u32 srcorg = buf->bus_address | MGA_SRCACC_AGP | MGA_SRCMAP_SYSMEM;
+	u32 y2;
+	DMA_LOCALS;
+	DRM_DEBUG( "buf=%d used=%d\n", buf->idx, buf->used );
+
+	y2 = length / 64;
+
+	BEGIN_DMA( 5 );
+
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DWGSYNC,	0x00007100,
+		   MGA_DWGSYNC,	0x00007000 );
+
+	DMA_BLOCK( MGA_DSTORG,	dstorg,
+		   MGA_MACCESS,	0x00000000,
+		   MGA_SRCORG,	srcorg,
+		   MGA_AR5,	64 );
+
+	DMA_BLOCK( MGA_PITCH,	64,
+		   MGA_PLNWT,	0xffffffff,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DWGCTL,	MGA_DWGCTL_COPY );
+
+	DMA_BLOCK( MGA_AR0,	63,
+		   MGA_AR3,	0,
+		   MGA_FXBNDRY,	(63 << 16) | 0,
+		   MGA_YDSTLEN + MGA_EXEC, y2 );
+
+	DMA_BLOCK( MGA_PLNWT,	ctx->plnwt,
+		   MGA_SRCORG,	dev_priv->front_offset,
+		   MGA_PITCH,	dev_priv->front_pitch,
+		   MGA_DWGSYNC,	0x00007000 );
+
+	ADVANCE_DMA();
+
+	AGE_BUFFER( buf_priv );
+
+	buf->pending = 0;
+	buf->used = 0;
+	buf_priv->dispatched = 0;
+
+	mga_freelist_put( dev, buf );
+
+	FLUSH_DMA();
+}
+
+static void mga_dma_dispatch_blit( drm_device_t *dev,
+				   drm_mga_blit_t *blit )
+{
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_context_regs_t *ctx = &sarea_priv->context_state;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int nbox = sarea_priv->nbox;
+	u32 scandir = 0, i;
+	DMA_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	BEGIN_DMA( 4 + nbox );
+
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_DMAPAD,	0x00000000,
+		   MGA_DWGSYNC,	0x00007100,
+		   MGA_DWGSYNC,	0x00007000 );
+
+	DMA_BLOCK( MGA_DWGCTL,	MGA_DWGCTL_COPY,
+		   MGA_PLNWT,	blit->planemask,
+		   MGA_SRCORG,	blit->srcorg,
+		   MGA_DSTORG,	blit->dstorg );
+
+	DMA_BLOCK( MGA_SGN,	scandir,
+		   MGA_MACCESS,	dev_priv->maccess,
+		   MGA_AR5,	blit->ydir * blit->src_pitch,
+		   MGA_PITCH,	blit->dst_pitch );
+
+	for ( i = 0 ; i < nbox ; i++ ) {
+		int srcx = pbox[i].x1 + blit->delta_sx;
+		int srcy = pbox[i].y1 + blit->delta_sy;
+		int dstx = pbox[i].x1 + blit->delta_dx;
+		int dsty = pbox[i].y1 + blit->delta_dy;
+		int h = pbox[i].y2 - pbox[i].y1;
+		int w = pbox[i].x2 - pbox[i].x1 - 1;
+		int start;
+
+		if ( blit->ydir == -1 ) {
+			srcy = blit->height - srcy - 1;
+		}
+
+		start = srcy * blit->src_pitch + srcx;
+
+		DMA_BLOCK( MGA_AR0,	start + w,
+			   MGA_AR3,	start,
+			   MGA_FXBNDRY,	((dstx + w) << 16) | (dstx & 0xffff),
+			   MGA_YDSTLEN + MGA_EXEC, (dsty << 16) | h );
+	}
+
+	/* Do something to flush AGP?
+	 */
+
+	/* Force reset of DWGCTL */
+	DMA_BLOCK( MGA_DMAPAD,	0x00000000,
+		   MGA_PLNWT,	ctx->plnwt,
+		   MGA_PITCH,	dev_priv->front_pitch,
+		   MGA_DWGCTL,	ctx->dwgctl );
+
+	ADVANCE_DMA();
+}
+
+
+/* ================================================================
+ *
+ */
+
+static int mga_dma_clear( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_clear_t clear;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( clear, (drm_mga_clear_t __user *)data, sizeof(clear) );
+
+	if ( sarea_priv->nbox > MGA_NR_SAREA_CLIPRECTS )
+		sarea_priv->nbox = MGA_NR_SAREA_CLIPRECTS;
+
+	WRAP_TEST_WITH_RETURN( dev_priv );
+
+	mga_dma_dispatch_clear( dev, &clear );
+
+	/* Make sure we restore the 3D state next time.
+	 */
+	dev_priv->sarea_priv->dirty |= MGA_UPLOAD_CONTEXT;
+
+	return 0;
+}
+
+static int mga_dma_swap( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( sarea_priv->nbox > MGA_NR_SAREA_CLIPRECTS )
+		sarea_priv->nbox = MGA_NR_SAREA_CLIPRECTS;
+
+	WRAP_TEST_WITH_RETURN( dev_priv );
+
+	mga_dma_dispatch_swap( dev );
+
+	/* Make sure we restore the 3D state next time.
+	 */
+	dev_priv->sarea_priv->dirty |= MGA_UPLOAD_CONTEXT;
+
+	return 0;
+}
+
+static int mga_dma_vertex( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_mga_buf_priv_t *buf_priv;
+	drm_mga_vertex_t vertex;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( vertex,
+			     (drm_mga_vertex_t __user *)data,
+			     sizeof(vertex) );
+
+        if(vertex.idx < 0 || vertex.idx > dma->buf_count) return DRM_ERR(EINVAL);
+	buf = dma->buflist[vertex.idx];
+	buf_priv = buf->dev_private;
+
+	buf->used = vertex.used;
+	buf_priv->discard = vertex.discard;
+
+	if ( !mga_verify_state( dev_priv ) ) {
+		if ( vertex.discard ) {
+			if ( buf_priv->dispatched == 1 )
+				AGE_BUFFER( buf_priv );
+			buf_priv->dispatched = 0;
+			mga_freelist_put( dev, buf );
+		}
+		return DRM_ERR(EINVAL);
+	}
+
+	WRAP_TEST_WITH_RETURN( dev_priv );
+
+	mga_dma_dispatch_vertex( dev, buf );
+
+	return 0;
+}
+
+static int mga_dma_indices( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_mga_buf_priv_t *buf_priv;
+	drm_mga_indices_t indices;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( indices,
+			     (drm_mga_indices_t __user *)data,
+			     sizeof(indices) );
+
+        if(indices.idx < 0 || indices.idx > dma->buf_count) return DRM_ERR(EINVAL);
+
+	buf = dma->buflist[indices.idx];
+	buf_priv = buf->dev_private;
+
+	buf_priv->discard = indices.discard;
+
+	if ( !mga_verify_state( dev_priv ) ) {
+		if ( indices.discard ) {
+			if ( buf_priv->dispatched == 1 )
+				AGE_BUFFER( buf_priv );
+			buf_priv->dispatched = 0;
+			mga_freelist_put( dev, buf );
+		}
+		return DRM_ERR(EINVAL);
+	}
+
+	WRAP_TEST_WITH_RETURN( dev_priv );
+
+	mga_dma_dispatch_indices( dev, buf, indices.start, indices.end );
+
+	return 0;
+}
+
+static int mga_dma_iload( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_device_dma_t *dma = dev->dma;
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_buf_t *buf;
+	drm_mga_buf_priv_t *buf_priv;
+	drm_mga_iload_t iload;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( iload, (drm_mga_iload_t __user *)data, sizeof(iload) );
+
+#if 0
+	if ( mga_do_wait_for_idle( dev_priv ) < 0 ) {
+		if ( MGA_DMA_DEBUG )
+			DRM_INFO( "%s: -EBUSY\n", __FUNCTION__ );
+		return DRM_ERR(EBUSY);
+	}
+#endif
+        if(iload.idx < 0 || iload.idx > dma->buf_count) return DRM_ERR(EINVAL);
+
+	buf = dma->buflist[iload.idx];
+	buf_priv = buf->dev_private;
+
+	if ( mga_verify_iload( dev_priv, iload.dstorg, iload.length ) ) {
+		mga_freelist_put( dev, buf );
+		return DRM_ERR(EINVAL);
+	}
+
+	WRAP_TEST_WITH_RETURN( dev_priv );
+
+	mga_dma_dispatch_iload( dev, buf, iload.dstorg, iload.length );
+
+	/* Make sure we restore the 3D state next time.
+	 */
+	dev_priv->sarea_priv->dirty |= MGA_UPLOAD_CONTEXT;
+
+	return 0;
+}
+
+static int mga_dma_blit( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_mga_blit_t blit;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( blit, (drm_mga_blit_t __user *)data, sizeof(blit) );
+
+	if ( sarea_priv->nbox > MGA_NR_SAREA_CLIPRECTS )
+		sarea_priv->nbox = MGA_NR_SAREA_CLIPRECTS;
+
+	if ( mga_verify_blit( dev_priv, blit.srcorg, blit.dstorg ) )
+		return DRM_ERR(EINVAL);
+
+	WRAP_TEST_WITH_RETURN( dev_priv );
+
+	mga_dma_dispatch_blit( dev, &blit );
+
+	/* Make sure we restore the 3D state next time.
+	 */
+	dev_priv->sarea_priv->dirty |= MGA_UPLOAD_CONTEXT;
+
+	return 0;
+}
+
+static int mga_getparam( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_mga_private_t *dev_priv = dev->dev_private;
+	drm_mga_getparam_t param;
+	int value;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( param, (drm_mga_getparam_t __user *)data,
+			     sizeof(param) );
+
+	DRM_DEBUG( "pid=%d\n", DRM_CURRENTPID );
+
+	switch( param.param ) {
+	case MGA_PARAM_IRQ_NR:
+		value = dev->irq;
+		break;
+	default:
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( DRM_COPY_TO_USER( param.value, &value, sizeof(int) ) ) {
+		DRM_ERROR( "copy_to_user\n" );
+		return DRM_ERR(EFAULT);
+	}
+	
+	return 0;
+}
+
+drm_ioctl_desc_t mga_ioctls[] = {
+	[DRM_IOCTL_NR(DRM_MGA_INIT)]    = { mga_dma_init,    1, 1 },
+	[DRM_IOCTL_NR(DRM_MGA_FLUSH)]   = { mga_dma_flush,   1, 0 },
+	[DRM_IOCTL_NR(DRM_MGA_RESET)]   = { mga_dma_reset,   1, 0 },
+	[DRM_IOCTL_NR(DRM_MGA_SWAP)]    = { mga_dma_swap,    1, 0 },
+	[DRM_IOCTL_NR(DRM_MGA_CLEAR)]   = { mga_dma_clear,   1, 0 },
+	[DRM_IOCTL_NR(DRM_MGA_VERTEX)]  = { mga_dma_vertex,  1, 0 },
+	[DRM_IOCTL_NR(DRM_MGA_INDICES)] = { mga_dma_indices, 1, 0 },
+	[DRM_IOCTL_NR(DRM_MGA_ILOAD)]   = { mga_dma_iload,   1, 0 },
+	[DRM_IOCTL_NR(DRM_MGA_BLIT)]    = { mga_dma_blit,    1, 0 },
+	[DRM_IOCTL_NR(DRM_MGA_GETPARAM)]= { mga_getparam,    1, 0 },
+};
+
+int mga_max_ioctl = DRM_ARRAY_SIZE(mga_ioctls);
diff --git a/drivers/char/drm/mga_ucode.h b/drivers/char/drm/mga_ucode.h
new file mode 100644
index 0000000..fa0f82e
--- /dev/null
+++ b/drivers/char/drm/mga_ucode.h
@@ -0,0 +1,11645 @@
+/* mga_ucode.h -- Matrox G200/G400 WARP engine microcode -*- linux-c -*-
+ * Created: Thu Jan 11 21:20:43 2001 by gareth@valinux.com
+ *
+ * Copyright 1999 Matrox Graphics Inc.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * MATROX GRAPHICS INC., OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Kernel-based WARP engine management:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * WARP pipes are named according to the functions they perform, where:
+ *
+ *   - T stands for computation of texture stage 0
+ *   - T2 stands for computation of both texture stage 0 and texture stage 1
+ *   - G stands for computation of triangle intensity (Gouraud interpolation)
+ *   - Z stands for computation of Z buffer interpolation
+ *   - S stands for computation of specular highlight
+ *   - A stands for computation of the alpha channel
+ *   - F stands for computation of vertex fog interpolation
+ */
+
+static unsigned char warp_g200_tgz[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x72, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x60, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x03, 0x80, 0x0A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x57, 0x39, 0x20, 0xE9,
+0x28, 0x19, 0x60, 0xEC,
+
+0x2B, 0x32, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0xB3, 0x05,
+0x00, 0xE0,
+0x16, 0x28, 0x20, 0xE9,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x2B, 0x20, 0xE9,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x85, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x84, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x82, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x7F, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgza[] = {
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x7D, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x6B, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x44, 0x4C, 0xB6,
+0x25, 0x44, 0x54, 0xB6,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x07, 0xC0, 0x44, 0xC6,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1F, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x3F, 0x3D, 0x5D, 0x9F,
+0x00, 0xE0,
+0x07, 0x20,
+
+0x00, 0x80, 0x00, 0xE8,
+0x28, 0x19, 0x60, 0xEC,
+
+0xB3, 0x05,
+0x00, 0xE0,
+0x00, 0x80, 0x00, 0xE8,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0x26, 0x1F, 0xDF,
+0x9D, 0x1F, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x9E, 0x3F, 0x4F, 0xE9,
+
+0x07, 0x07, 0x1F, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x9C, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x7A, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x79, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x77, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x74, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzaf[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x83, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x6F, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0D, 0x21, 0x1A, 0xB6,
+0x05, 0x21, 0x31, 0xB6,
+
+0x2D, 0x44, 0x4C, 0xB6,
+0x25, 0x44, 0x54, 0xB6,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x2F, 0xC0, 0x21, 0xC6,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x07, 0xC0, 0x44, 0xC6,
+
+0x17, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x2D, 0x20,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x1F, 0x62, 0x57, 0x9F,
+0x00, 0xE0,
+0x07, 0x20,
+
+0x3F, 0x3D, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x28, 0x19, 0x60, 0xEC,
+
+0xB3, 0x05,
+0x00, 0xE0,
+0x17, 0x26, 0x17, 0xDF,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x35, 0x17, 0x4F, 0xE9,
+
+0x1F, 0x26, 0x1F, 0xDF,
+0x9D, 0x1F, 0x4F, 0xE9,
+
+0x9E, 0x3F, 0x4F, 0xE9,
+0x39, 0x37, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x17, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x07, 0x07, 0x1F, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x31, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x9C, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x74, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x73, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x71, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6E, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzf[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x7F, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x6B, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0D, 0x21, 0x1A, 0xB6,
+0x05, 0x21, 0x31, 0xB6,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x2F, 0xC0, 0x21, 0xC6,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x17, 0x50, 0x56, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x00, 0x80, 0x00, 0xE8,
+0x28, 0x19, 0x60, 0xEC,
+
+0xB3, 0x05,
+0x00, 0xE0,
+0x00, 0x80, 0x00, 0xE8,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x17, 0x26, 0x17, 0xDF,
+0x35, 0x17, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x39, 0x37, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x17, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x31, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x78, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x77, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x75, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x72, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzs[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x8B, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x77, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x21, 0x1A, 0xB0,
+0x25, 0x21, 0x31, 0xB0,
+
+0x0D, 0x21, 0x1A, 0xB2,
+0x05, 0x21, 0x31, 0xB2,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x05, 0x20,
+0x0D, 0x20,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x2F, 0xC0, 0x21, 0xC0,
+
+0x16, 0x42, 0x56, 0x9F,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x21, 0x31, 0xB4,
+0x2D, 0x21, 0x1A, 0xB4,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0x05,
+0x00, 0xE0,
+0x28, 0x19, 0x60, 0xEC,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x26, 0x1E, 0xDF,
+
+0xA7, 0x1E, 0x4F, 0xE9,
+0x17, 0x26, 0x16, 0xDF,
+
+0x2D, 0x20,
+0x00, 0xE0,
+0xA8, 0x3F, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x1E, 0xAF,
+0x25, 0x20,
+0x00, 0xE0,
+
+0xA4, 0x16, 0x4F, 0xE9,
+0x0F, 0xC0, 0x21, 0xC2,
+
+0xA6, 0x80, 0x4F, 0xE9,
+0x1F, 0x62, 0x57, 0x9F,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0xE0,
+0x8F, 0x20,
+
+0xA5, 0x37, 0x4F, 0xE9,
+0x0F, 0x17, 0x0F, 0xAF,
+
+0x06, 0xC0, 0x21, 0xC4,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0xA3, 0x80, 0x4F, 0xE9,
+
+0x06, 0x20,
+0x00, 0xE0,
+0x1F, 0x26, 0x1F, 0xDF,
+
+0xA1, 0x1F, 0x4F, 0xE9,
+0xA2, 0x3F, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x06, 0x06, 0x1F, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x6C, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6B, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x69, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzsa[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x8F, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x7B, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x21, 0x1A, 0xB0,
+0x25, 0x21, 0x31, 0xB0,
+
+0x0D, 0x21, 0x1A, 0xB2,
+0x05, 0x21, 0x31, 0xB2,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x05, 0x20,
+0x0D, 0x20,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x2F, 0xC0, 0x21, 0xC0,
+
+0x16, 0x42, 0x56, 0x9F,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x21, 0x31, 0xB4,
+0x2D, 0x21, 0x1A, 0xB4,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0x05,
+0x00, 0xE0,
+0x28, 0x19, 0x60, 0xEC,
+
+0x0D, 0x44, 0x4C, 0xB6,
+0x05, 0x44, 0x54, 0xB6,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x26, 0x1E, 0xDF,
+
+0xA7, 0x1E, 0x4F, 0xE9,
+0x17, 0x26, 0x16, 0xDF,
+
+0x2D, 0x20,
+0x00, 0xE0,
+0xA8, 0x3F, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x1E, 0xAF,
+0x25, 0x20,
+0x00, 0xE0,
+
+0xA4, 0x16, 0x4F, 0xE9,
+0x0F, 0xC0, 0x21, 0xC2,
+
+0xA6, 0x80, 0x4F, 0xE9,
+0x1F, 0x62, 0x57, 0x9F,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x00, 0x80, 0x00, 0xE8,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0xE0,
+0x0F, 0x20,
+
+0x17, 0x50, 0x56, 0x9F,
+0xA5, 0x37, 0x4F, 0xE9,
+
+0x06, 0xC0, 0x21, 0xC4,
+0x0F, 0x17, 0x0F, 0xAF,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2F, 0xC0, 0x44, 0xC6,
+0xA3, 0x80, 0x4F, 0xE9,
+
+0x06, 0x20,
+0x00, 0xE0,
+0x1F, 0x26, 0x1F, 0xDF,
+
+0x17, 0x26, 0x17, 0xDF,
+0x9D, 0x17, 0x4F, 0xE9,
+
+0xA1, 0x1F, 0x4F, 0xE9,
+0xA2, 0x3F, 0x4F, 0xE9,
+
+0x06, 0x06, 0x1F, 0xAF,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x9E, 0x37, 0x4F, 0xE9,
+0x2F, 0x17, 0x2F, 0xAF,
+
+0xA0, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x9C, 0x80, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x68, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x67, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x65, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x62, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzsaf[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x94, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x80, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x21, 0x1A, 0xB0,
+0x25, 0x21, 0x31, 0xB0,
+
+0x0D, 0x21, 0x1A, 0xB2,
+0x05, 0x21, 0x31, 0xB2,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x05, 0x20,
+0x0D, 0x20,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x2F, 0xC0, 0x21, 0xC0,
+
+0x16, 0x42, 0x56, 0x9F,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x21, 0x31, 0xB4,
+0x2D, 0x21, 0x1A, 0xB4,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0x05,
+0x00, 0xE0,
+0x28, 0x19, 0x60, 0xEC,
+
+0x0D, 0x21, 0x1A, 0xB6,
+0x05, 0x21, 0x31, 0xB6,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x26, 0x1E, 0xDF,
+
+0xA7, 0x1E, 0x4F, 0xE9,
+0x17, 0x26, 0x16, 0xDF,
+
+0x2D, 0x20,
+0x00, 0xE0,
+0xA8, 0x3F, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x1E, 0xAF,
+0x25, 0x20,
+0x00, 0xE0,
+
+0xA4, 0x16, 0x4F, 0xE9,
+0x0F, 0xC0, 0x21, 0xC2,
+
+0xA6, 0x80, 0x4F, 0xE9,
+0x1F, 0x62, 0x57, 0x9F,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x2F, 0xC0, 0x21, 0xC6,
+
+0x2D, 0x44, 0x4C, 0xB6,
+0x25, 0x44, 0x54, 0xB6,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0xE0,
+0x0F, 0x20,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x07, 0xC0, 0x44, 0xC6,
+
+0x17, 0x50, 0x56, 0x9F,
+0xA5, 0x37, 0x4F, 0xE9,
+
+0x06, 0xC0, 0x21, 0xC4,
+0x0F, 0x17, 0x0F, 0xAF,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x3E, 0x3D, 0x5D, 0x9F,
+0x00, 0xE0,
+0x07, 0x20,
+
+0x2F, 0x20,
+0x00, 0xE0,
+0xA3, 0x0F, 0x4F, 0xE9,
+
+0x06, 0x20,
+0x00, 0xE0,
+0x1F, 0x26, 0x1F, 0xDF,
+
+0x17, 0x26, 0x17, 0xDF,
+0xA1, 0x1F, 0x4F, 0xE9,
+
+0x1E, 0x26, 0x1E, 0xDF,
+0x9D, 0x1E, 0x4F, 0xE9,
+
+0x35, 0x17, 0x4F, 0xE9,
+0xA2, 0x3F, 0x4F, 0xE9,
+
+0x06, 0x06, 0x1F, 0xAF,
+0x39, 0x37, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x17, 0xAF,
+0x07, 0x07, 0x1E, 0xAF,
+
+0xA0, 0x80, 0x4F, 0xE9,
+0x9E, 0x3E, 0x4F, 0xE9,
+
+0x31, 0x80, 0x4F, 0xE9,
+0x9C, 0x80, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x63, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x62, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x60, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x5D, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g200_tgzsf[] = {
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x98, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x81, 0x04,
+0x89, 0x04,
+0x01, 0x04,
+0x09, 0x04,
+
+0xC9, 0x41, 0xC0, 0xEC,
+0x11, 0x04,
+0x00, 0xE0,
+
+0x41, 0xCC, 0x41, 0xCD,
+0x49, 0xCC, 0x49, 0xCD,
+
+0xD1, 0x41, 0xC0, 0xEC,
+0x51, 0xCC, 0x51, 0xCD,
+
+0x80, 0x04,
+0x10, 0x04,
+0x08, 0x04,
+0x00, 0xE0,
+
+0x00, 0xCC, 0xC0, 0xCD,
+0xD1, 0x49, 0xC0, 0xEC,
+
+0x8A, 0x1F, 0x20, 0xE9,
+0x8B, 0x3F, 0x20, 0xE9,
+
+0x41, 0x3C, 0x41, 0xAD,
+0x49, 0x3C, 0x49, 0xAD,
+
+0x10, 0xCC, 0x10, 0xCD,
+0x08, 0xCC, 0x08, 0xCD,
+
+0xB9, 0x41, 0x49, 0xBB,
+0x1F, 0xF0, 0x41, 0xCD,
+
+0x51, 0x3C, 0x51, 0xAD,
+0x00, 0x98, 0x80, 0xE9,
+
+0x8F, 0x80, 0x07, 0xEA,
+0x24, 0x1F, 0x20, 0xE9,
+
+0x21, 0x45, 0x80, 0xE8,
+0x1A, 0x4D, 0x80, 0xE8,
+
+0x31, 0x55, 0x80, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0x41, 0x49, 0xBD,
+0x1D, 0x41, 0x51, 0xBD,
+
+0x2E, 0x41, 0x2A, 0xB8,
+0x34, 0x53, 0xA0, 0xE8,
+
+0x15, 0x30,
+0x1D, 0x30,
+0x58, 0xE3,
+0x00, 0xE0,
+
+0xB5, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x24, 0x43, 0xA0, 0xE8,
+0x2C, 0x4B, 0xA0, 0xE8,
+
+0x15, 0x72,
+0x09, 0xE3,
+0x00, 0xE0,
+0x1D, 0x72,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0x97, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x6C, 0x64, 0xC8, 0xEC,
+0x98, 0xE1,
+0xB5, 0x05,
+
+0xBD, 0x05,
+0x2E, 0x30,
+0x32, 0xC0, 0xA0, 0xE8,
+
+0x33, 0xC0, 0xA0, 0xE8,
+0x74, 0x64, 0xC8, 0xEC,
+
+0x40, 0x3C, 0x40, 0xAD,
+0x32, 0x6A,
+0x2A, 0x30,
+
+0x20, 0x73,
+0x33, 0x6A,
+0x00, 0xE0,
+0x28, 0x73,
+
+0x1C, 0x72,
+0x83, 0xE2,
+0x7B, 0x80, 0x15, 0xEA,
+
+0xB8, 0x3D, 0x28, 0xDF,
+0x30, 0x35, 0x20, 0xDF,
+
+0x40, 0x30,
+0x00, 0xE0,
+0xCC, 0xE2,
+0x64, 0x72,
+
+0x25, 0x42, 0x52, 0xBF,
+0x2D, 0x42, 0x4A, 0xBF,
+
+0x30, 0x2E, 0x30, 0xDF,
+0x38, 0x2E, 0x38, 0xDF,
+
+0x18, 0x1D, 0x45, 0xE9,
+0x1E, 0x15, 0x45, 0xE9,
+
+0x2B, 0x49, 0x51, 0xBD,
+0x00, 0xE0,
+0x1F, 0x73,
+
+0x38, 0x38, 0x40, 0xAF,
+0x30, 0x30, 0x40, 0xAF,
+
+0x24, 0x1F, 0x24, 0xDF,
+0x1D, 0x32, 0x20, 0xE9,
+
+0x2C, 0x1F, 0x2C, 0xDF,
+0x1A, 0x33, 0x20, 0xE9,
+
+0xB0, 0x10,
+0x08, 0xE3,
+0x40, 0x10,
+0xB8, 0x10,
+
+0x26, 0xF0, 0x30, 0xCD,
+0x2F, 0xF0, 0x38, 0xCD,
+
+0x2B, 0x80, 0x20, 0xE9,
+0x2A, 0x80, 0x20, 0xE9,
+
+0xA6, 0x20,
+0x88, 0xE2,
+0x00, 0xE0,
+0xAF, 0x20,
+
+0x28, 0x2A, 0x26, 0xAF,
+0x20, 0x2A, 0xC0, 0xAF,
+
+0x34, 0x1F, 0x34, 0xDF,
+0x46, 0x24, 0x46, 0xDF,
+
+0x28, 0x30, 0x80, 0xBF,
+0x20, 0x38, 0x80, 0xBF,
+
+0x47, 0x24, 0x47, 0xDF,
+0x4E, 0x2C, 0x4E, 0xDF,
+
+0x4F, 0x2C, 0x4F, 0xDF,
+0x56, 0x34, 0x56, 0xDF,
+
+0x28, 0x15, 0x28, 0xDF,
+0x20, 0x1D, 0x20, 0xDF,
+
+0x57, 0x34, 0x57, 0xDF,
+0x00, 0xE0,
+0x1D, 0x05,
+
+0x04, 0x80, 0x10, 0xEA,
+0x89, 0xE2,
+0x2B, 0x30,
+
+0x3F, 0xC1, 0x1D, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x68,
+0xBF, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x20, 0xC0, 0x20, 0xAF,
+0x28, 0x05,
+0x97, 0x74,
+
+0x00, 0xE0,
+0x2A, 0x10,
+0x16, 0xC0, 0x20, 0xE9,
+
+0x04, 0x80, 0x10, 0xEA,
+0x8C, 0xE2,
+0x95, 0x05,
+
+0x28, 0xC1, 0x28, 0xAD,
+0x1F, 0xC1, 0x15, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA8, 0x67,
+0x9F, 0x6B,
+0x00, 0x80, 0x00, 0xE8,
+
+0x28, 0xC0, 0x28, 0xAD,
+0x1D, 0x25,
+0x20, 0x05,
+
+0x28, 0x32, 0x80, 0xAD,
+0x40, 0x2A, 0x40, 0xBD,
+
+0x1C, 0x80, 0x20, 0xE9,
+0x20, 0x33, 0x20, 0xAD,
+
+0x20, 0x73,
+0x00, 0xE0,
+0xB6, 0x49, 0x51, 0xBB,
+
+0x26, 0x2F, 0xB0, 0xE8,
+0x19, 0x20, 0x20, 0xE9,
+
+0x35, 0x20, 0x35, 0xDF,
+0x3D, 0x20, 0x3D, 0xDF,
+
+0x15, 0x20, 0x15, 0xDF,
+0x1D, 0x20, 0x1D, 0xDF,
+
+0x26, 0xD0, 0x26, 0xCD,
+0x29, 0x49, 0x2A, 0xB8,
+
+0x26, 0x40, 0x80, 0xBD,
+0x3B, 0x48, 0x50, 0xBD,
+
+0x3E, 0x54, 0x57, 0x9F,
+0x00, 0xE0,
+0x82, 0xE1,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x26, 0x30,
+0x29, 0x30,
+0x48, 0x3C, 0x48, 0xAD,
+
+0x2B, 0x72,
+0xC2, 0xE1,
+0x2C, 0xC0, 0x44, 0xC2,
+
+0x05, 0x24, 0x34, 0xBF,
+0x0D, 0x24, 0x2C, 0xBF,
+
+0x2D, 0x46, 0x4E, 0xBF,
+0x25, 0x46, 0x56, 0xBF,
+
+0x20, 0x1D, 0x6F, 0x8F,
+0x32, 0x3E, 0x5F, 0xE9,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x30,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x33, 0x1E, 0x5F, 0xE9,
+
+0x05, 0x44, 0x54, 0xB2,
+0x0D, 0x44, 0x4C, 0xB2,
+
+0x19, 0xC0, 0xB0, 0xE8,
+0x34, 0xC0, 0x44, 0xC4,
+
+0x33, 0x73,
+0x00, 0xE0,
+0x3E, 0x62, 0x57, 0x9F,
+
+0x1E, 0xAF, 0x59, 0x9F,
+0x00, 0xE0,
+0x0D, 0x20,
+
+0x84, 0x3E, 0x58, 0xE9,
+0x28, 0x1D, 0x6F, 0x8F,
+
+0x05, 0x20,
+0x00, 0xE0,
+0x85, 0x1E, 0x58, 0xE9,
+
+0x9B, 0x3B, 0x33, 0xDF,
+0x20, 0x20, 0x42, 0xAF,
+
+0x30, 0x42, 0x56, 0x9F,
+0x80, 0x3E, 0x57, 0xE9,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x30, 0x80, 0x5F, 0xE9,
+
+0x28, 0x28, 0x24, 0xAF,
+0x81, 0x1E, 0x57, 0xE9,
+
+0x05, 0x47, 0x57, 0xBF,
+0x0D, 0x47, 0x4F, 0xBF,
+
+0x88, 0x80, 0x58, 0xE9,
+0x1B, 0x29, 0x1B, 0xDF,
+
+0x30, 0x1D, 0x6F, 0x8F,
+0x3A, 0x30, 0x4F, 0xE9,
+
+0x1C, 0x30, 0x26, 0xDF,
+0x09, 0xE3,
+0x3B, 0x05,
+
+0x3E, 0x50, 0x56, 0x9F,
+0x3B, 0x3F, 0x4F, 0xE9,
+
+0x1E, 0x8F, 0x51, 0x9F,
+0x00, 0xE0,
+0xAC, 0x20,
+
+0x2D, 0x44, 0x4C, 0xB4,
+0x2C, 0x1C, 0xC0, 0xAF,
+
+0x25, 0x44, 0x54, 0xB4,
+0x00, 0xE0,
+0xC8, 0x30,
+
+0x30, 0x46, 0x30, 0xAF,
+0x1B, 0x1B, 0x48, 0xAF,
+
+0x00, 0xE0,
+0x25, 0x20,
+0x38, 0x2C, 0x4F, 0xE9,
+
+0x86, 0x80, 0x57, 0xE9,
+0x38, 0x1D, 0x6F, 0x8F,
+
+0x28, 0x74,
+0x00, 0xE0,
+0x0D, 0x44, 0x4C, 0xB0,
+
+0x05, 0x44, 0x54, 0xB0,
+0x2D, 0x20,
+0x9B, 0x10,
+
+0x82, 0x3E, 0x57, 0xE9,
+0x32, 0xF0, 0x1B, 0xCD,
+
+0x1E, 0xBD, 0x59, 0x9F,
+0x83, 0x1E, 0x57, 0xE9,
+
+0x38, 0x47, 0x38, 0xAF,
+0x34, 0x20,
+0x2A, 0x30,
+
+0x00, 0xE0,
+0x0D, 0x20,
+0x32, 0x20,
+0x05, 0x20,
+
+0x87, 0x80, 0x57, 0xE9,
+0x1F, 0x54, 0x57, 0x9F,
+
+0x17, 0x42, 0x56, 0x9F,
+0x00, 0xE0,
+0x3B, 0x6A,
+
+0x3F, 0x8F, 0x51, 0x9F,
+0x37, 0x1E, 0x4F, 0xE9,
+
+0x37, 0x32, 0x2A, 0xAF,
+0x00, 0xE0,
+0x32, 0x00,
+
+0x00, 0x80, 0x00, 0xE8,
+0x27, 0xC0, 0x44, 0xC0,
+
+0x36, 0x1F, 0x4F, 0xE9,
+0x1F, 0x1F, 0x26, 0xDF,
+
+0x37, 0x1B, 0x37, 0xBF,
+0x17, 0x26, 0x17, 0xDF,
+
+0x3E, 0x17, 0x4F, 0xE9,
+0x3F, 0x3F, 0x4F, 0xE9,
+
+0x34, 0x1F, 0x34, 0xAF,
+0x2B, 0x05,
+0xA7, 0x20,
+
+0x33, 0x2B, 0x37, 0xDF,
+0x27, 0x17, 0xC0, 0xAF,
+
+0x34, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2D, 0x21, 0x1A, 0xB0,
+0x25, 0x21, 0x31, 0xB0,
+
+0x0D, 0x21, 0x1A, 0xB2,
+0x05, 0x21, 0x31, 0xB2,
+
+0x03, 0x80, 0x2A, 0xEA,
+0x17, 0xC1, 0x2B, 0xBD,
+
+0x2D, 0x20,
+0x25, 0x20,
+0x05, 0x20,
+0x0D, 0x20,
+
+0xB3, 0x68,
+0x97, 0x25,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0xC0, 0x33, 0xAF,
+0x2F, 0xC0, 0x21, 0xC0,
+
+0x16, 0x42, 0x56, 0x9F,
+0x3C, 0x27, 0x4F, 0xE9,
+
+0x1E, 0x62, 0x57, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x21, 0x31, 0xB4,
+0x2D, 0x21, 0x1A, 0xB4,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x33, 0x05,
+0x00, 0xE0,
+0x28, 0x19, 0x60, 0xEC,
+
+0x0D, 0x21, 0x1A, 0xB6,
+0x05, 0x21, 0x31, 0xB6,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0xE0,
+0x2F, 0x20,
+
+0x23, 0x3B, 0x33, 0xAD,
+0x1E, 0x26, 0x1E, 0xDF,
+
+0xA7, 0x1E, 0x4F, 0xE9,
+0x17, 0x26, 0x16, 0xDF,
+
+0x2D, 0x20,
+0x00, 0xE0,
+0xA8, 0x3F, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x1E, 0xAF,
+0x25, 0x20,
+0x00, 0xE0,
+
+0xA4, 0x16, 0x4F, 0xE9,
+0x0F, 0xC0, 0x21, 0xC2,
+
+0xA6, 0x80, 0x4F, 0xE9,
+0x1F, 0x62, 0x57, 0x9F,
+
+0x0D, 0x20,
+0x05, 0x20,
+0x2F, 0xC0, 0x21, 0xC6,
+
+0x3F, 0x2F, 0x5D, 0x9F,
+0x00, 0xE0,
+0x0F, 0x20,
+
+0x17, 0x50, 0x56, 0x9F,
+0xA5, 0x37, 0x4F, 0xE9,
+
+0x06, 0xC0, 0x21, 0xC4,
+0x0F, 0x17, 0x0F, 0xAF,
+
+0x37, 0x0F, 0x5C, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2F, 0x20,
+0x00, 0xE0,
+0xA3, 0x80, 0x4F, 0xE9,
+
+0x06, 0x20,
+0x00, 0xE0,
+0x1F, 0x26, 0x1F, 0xDF,
+
+0x17, 0x26, 0x17, 0xDF,
+0x35, 0x17, 0x4F, 0xE9,
+
+0xA1, 0x1F, 0x4F, 0xE9,
+0xA2, 0x3F, 0x4F, 0xE9,
+
+0x06, 0x06, 0x1F, 0xAF,
+0x39, 0x37, 0x4F, 0xE9,
+
+0x2F, 0x2F, 0x17, 0xAF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xA0, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x31, 0x80, 0x4F, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x57, 0x39, 0x20, 0xE9,
+
+0x16, 0x28, 0x20, 0xE9,
+0x1D, 0x3B, 0x20, 0xE9,
+
+0x1E, 0x2B, 0x20, 0xE9,
+0x2B, 0x32, 0x20, 0xE9,
+
+0x1C, 0x23, 0x20, 0xE9,
+0x57, 0x36, 0x20, 0xE9,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x40, 0x40, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x90, 0xE2,
+0x00, 0xE0,
+
+0x68, 0xFF, 0x20, 0xEA,
+0x19, 0xC8, 0xC1, 0xCD,
+
+0x1F, 0xD7, 0x18, 0xBD,
+0x3F, 0xD7, 0x22, 0xBD,
+
+0x9F, 0x41, 0x49, 0xBD,
+0x00, 0x80, 0x00, 0xE8,
+
+0x25, 0x41, 0x49, 0xBD,
+0x2D, 0x41, 0x51, 0xBD,
+
+0x0D, 0x80, 0x07, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x35, 0x40, 0x48, 0xBD,
+0x3D, 0x40, 0x50, 0xBD,
+
+0x00, 0x80, 0x00, 0xE8,
+0x25, 0x30,
+0x2D, 0x30,
+
+0x35, 0x30,
+0xB5, 0x30,
+0xBD, 0x30,
+0x3D, 0x30,
+
+0x9C, 0xA7, 0x5B, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x67, 0xFF, 0x0A, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC9, 0x41, 0xC8, 0xEC,
+0x42, 0xE1,
+0x00, 0xE0,
+
+0x65, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0xC8, 0x40, 0xC0, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x62, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+};
+
+static unsigned char warp_g400_t2gz[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x78, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x69, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x25, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x9F, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xBE, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x7D, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gza[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x7C, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x6D, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x29, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x74, 0xC6,
+0x3D, 0xCF, 0x74, 0xC2,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x0F, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB4,
+0x02, 0x44, 0x64, 0xB4,
+
+0x2A, 0x44, 0x54, 0xB6,
+0x1A, 0x44, 0x64, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x9B, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xBA, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x79, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzaf[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x81, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x72, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x37, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x2E, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x0F, 0xCF, 0x74, 0xC6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x0F, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB4,
+0x02, 0x44, 0x64, 0xB4,
+
+0x2A, 0x44, 0x54, 0xB6,
+0x1A, 0x44, 0x64, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x3D, 0xCF, 0x75, 0xC6,
+0x00, 0x80, 0x00, 0xE8,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x45, 0x55, 0xB6,
+0x02, 0x45, 0x65, 0xB6,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x3D, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x96, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xB5, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x74, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzf[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x7D, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x6E, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0F, 0xCF, 0x75, 0xC6,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x28, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x31, 0x0F, 0x20, 0xE9,
+
+0x0A, 0x44, 0x54, 0xB4,
+0x02, 0x44, 0x64, 0xB4,
+
+0x2A, 0x45, 0x55, 0xB6,
+0x1A, 0x45, 0x65, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x9A, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xBB, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x78, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzs[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x85, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x76, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x0F, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x31, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x0A, 0x45, 0x55, 0xB0,
+0x02, 0x45, 0x65, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB2,
+0x1A, 0x45, 0x65, 0xB2,
+
+0x0A, 0x45, 0x55, 0xB4,
+0x02, 0x45, 0x65, 0xB4,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x20,
+0x1A, 0x20,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA7, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x92, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xB2, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x70, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzsa[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x8A, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x7B, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x0F, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x36, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x0A, 0x45, 0x55, 0xB0,
+0x02, 0x45, 0x65, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB2,
+0x1A, 0x45, 0x65, 0xB2,
+
+0x0A, 0x45, 0x55, 0xB4,
+0x02, 0x45, 0x65, 0xB4,
+
+0x0F, 0xCF, 0x74, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB6,
+0x1A, 0x44, 0x64, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x8D, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xAD, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x6B, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzsaf[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x8E, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x7F, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x0F, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x3A, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x0A, 0x45, 0x55, 0xB0,
+0x02, 0x45, 0x65, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB2,
+0x1A, 0x45, 0x65, 0xB2,
+
+0x0A, 0x45, 0x55, 0xB4,
+0x02, 0x45, 0x65, 0xB4,
+
+0x0F, 0xCF, 0x74, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB6,
+0x1A, 0x44, 0x64, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x45, 0x55, 0xB6,
+0x02, 0x45, 0x65, 0xB6,
+
+0x3D, 0xCF, 0x75, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x31, 0x3D, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x89, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xA9, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x67, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_t2gzsf[] = {
+
+0x00, 0x8A, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x0A, 0x40, 0x50, 0xBF,
+0x2A, 0x40, 0x60, 0xBF,
+
+0x32, 0x41, 0x51, 0xBF,
+0x3A, 0x41, 0x61, 0xBF,
+
+0xC3, 0x6B,
+0xD3, 0x6B,
+0x00, 0x8A, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x53, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x23, 0x9F,
+0x00, 0xE0,
+0x51, 0x04,
+
+0x90, 0xE2,
+0x61, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x51, 0x41, 0xE0, 0xEC,
+0x39, 0x67, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x63, 0xA0, 0xE8,
+
+0x61, 0x41, 0xE0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x8A, 0x80, 0x15, 0xEA,
+0x10, 0x04,
+0x20, 0x04,
+
+0x61, 0x51, 0xE0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x52, 0xBF,
+0x0F, 0x52, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x62, 0xBF,
+0x1E, 0x51, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x0E, 0x61, 0x60, 0xEA,
+
+0x32, 0x40, 0x50, 0xBD,
+0x22, 0x40, 0x60, 0xBD,
+
+0x12, 0x41, 0x51, 0xBD,
+0x3A, 0x41, 0x61, 0xBD,
+
+0xBF, 0x2F, 0x0E, 0xBD,
+0x97, 0xE2,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x35, 0x48, 0xB1, 0xE8,
+0x3D, 0x59, 0xB1, 0xE8,
+
+0x46, 0x31, 0x46, 0xBF,
+0x56, 0x31, 0x56, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x66, 0x31, 0x66, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x57, 0x39, 0x57, 0xBF,
+0x67, 0x39, 0x67, 0xBF,
+
+0x7B, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x35, 0x00,
+0x3D, 0x00,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0x8D, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x75, 0xF8, 0xEC,
+0x35, 0x20,
+0x3D, 0x20,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x53, 0x53, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x0E, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x48, 0x35, 0x48, 0xBF,
+0x58, 0x35, 0x58, 0xBF,
+
+0x68, 0x35, 0x68, 0xBF,
+0x49, 0x3D, 0x49, 0xBF,
+
+0x59, 0x3D, 0x59, 0xBF,
+0x69, 0x3D, 0x69, 0xBF,
+
+0x63, 0x63, 0x2D, 0xDF,
+0x4D, 0x7D, 0xF8, 0xEC,
+
+0x59, 0xE3,
+0x00, 0xE0,
+0xB8, 0x38, 0x33, 0xBF,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x18, 0x3A, 0x41, 0xE9,
+
+0x3F, 0x53, 0xA0, 0xE8,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x63, 0xA0, 0xE8,
+
+0x50, 0x70, 0xF8, 0xEC,
+0x2B, 0x50, 0x3C, 0xE9,
+
+0x1F, 0x0F, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x59, 0x78, 0xF8, 0xEC,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x46, 0x37, 0x46, 0xDF,
+0x56, 0x3F, 0x56, 0xDF,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x66, 0x3D, 0x66, 0xDF,
+
+0x1D, 0x32, 0x41, 0xE9,
+0x67, 0x3D, 0x67, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3F, 0x57, 0xDF,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x59, 0x3F, 0x59, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x69, 0x3D, 0x69, 0xDF,
+
+0x48, 0x37, 0x48, 0xDF,
+0x58, 0x3F, 0x58, 0xDF,
+
+0x68, 0x3D, 0x68, 0xDF,
+0x49, 0x37, 0x49, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x0F, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x54, 0xB0,
+0x02, 0x44, 0x64, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB2,
+0x1A, 0x44, 0x64, 0xB2,
+
+0x36, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0F, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x54, 0xB4,
+0x1A, 0x44, 0x64, 0xB4,
+
+0x0A, 0x45, 0x55, 0xB0,
+0x02, 0x45, 0x65, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB2,
+0x1A, 0x45, 0x65, 0xB2,
+
+0x0A, 0x45, 0x55, 0xB4,
+0x02, 0x45, 0x65, 0xB4,
+
+0x0F, 0xCF, 0x75, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x31, 0x0F, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x55, 0xB6,
+0x1A, 0x45, 0x65, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x56, 0xBF,
+0x1A, 0x46, 0x66, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x57, 0xBF,
+0x02, 0x47, 0x67, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x53, 0xBF,
+0x1A, 0x43, 0x63, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x48, 0x58, 0xBF,
+0x02, 0x48, 0x68, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x2A, 0x49, 0x59, 0xBF,
+0x1A, 0x49, 0x69, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x82, 0x30, 0x57, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x83, 0x38, 0x57, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x84, 0x31, 0x5E, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x85, 0x39, 0x5E, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x87, 0x77, 0x57, 0xE9,
+0x8B, 0x3E, 0xBF, 0xEA,
+
+0x80, 0x30, 0x57, 0xE9,
+0x81, 0x38, 0x57, 0xE9,
+
+0x82, 0x31, 0x57, 0xE9,
+0x86, 0x78, 0x57, 0xE9,
+
+0x83, 0x39, 0x57, 0xE9,
+0x87, 0x79, 0x57, 0xE9,
+
+0x30, 0x1F, 0x5F, 0xE9,
+0x8A, 0x34, 0x20, 0xE9,
+
+0x8B, 0x3C, 0x20, 0xE9,
+0x37, 0x50, 0x60, 0xBD,
+
+0x57, 0x0D, 0x20, 0xE9,
+0x35, 0x51, 0x61, 0xBD,
+
+0x2B, 0x50, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x0E, 0x77,
+
+0x24, 0x51, 0x20, 0xE9,
+0x8D, 0xFF, 0x20, 0xEA,
+
+0x16, 0x0E, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x0B, 0x46, 0xA0, 0xE8,
+0x1B, 0x56, 0xA0, 0xE8,
+
+0x2B, 0x66, 0xA0, 0xE8,
+0x0C, 0x47, 0xA0, 0xE8,
+
+0x1C, 0x57, 0xA0, 0xE8,
+0x2C, 0x67, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x57, 0x80, 0x57, 0xCF,
+
+0x66, 0x33, 0x66, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x67, 0x3B, 0x67, 0xCF,
+
+0x0B, 0x48, 0xA0, 0xE8,
+0x1B, 0x58, 0xA0, 0xE8,
+
+0x2B, 0x68, 0xA0, 0xE8,
+0x0C, 0x49, 0xA0, 0xE8,
+
+0x1C, 0x59, 0xA0, 0xE8,
+0x2C, 0x69, 0xA0, 0xE8,
+
+0x0B, 0x00,
+0x1B, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x0C, 0x00,
+0x1C, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x0B, 0x65,
+0x1B, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x0C, 0x65,
+0x1C, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x0B, 0x1B, 0x60, 0xEC,
+0x34, 0xD7, 0x34, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x0C, 0x1C, 0x60, 0xEC,
+
+0x3C, 0xD7, 0x3C, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x0B, 0x2B, 0xDE, 0xE8,
+0x1B, 0x80, 0xDE, 0xE8,
+
+0x34, 0x80, 0x34, 0xBD,
+0x3C, 0x80, 0x3C, 0xBD,
+
+0x33, 0xD7, 0x0B, 0xBD,
+0x3B, 0xD7, 0x1B, 0xBD,
+
+0x48, 0x80, 0x48, 0xCF,
+0x59, 0x80, 0x59, 0xCF,
+
+0x68, 0x33, 0x68, 0xCF,
+0x49, 0x3B, 0x49, 0xCF,
+
+0xAD, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x58, 0x33, 0x58, 0xCF,
+0x69, 0x3B, 0x69, 0xCF,
+
+0x6B, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgz[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x58, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x4A, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x1D, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xAF, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xD6, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x9D, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgza[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x5C, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x4E, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x27, 0xCF, 0x74, 0xC6,
+0x3D, 0xCF, 0x74, 0xC2,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x20, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x27, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB4,
+0x02, 0x44, 0x54, 0xB4,
+
+0x2A, 0x44, 0x4C, 0xB6,
+0x1A, 0x44, 0x54, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xAB, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xD3, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x99, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzaf[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x61, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x53, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x34, 0x37, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x26, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x27, 0xCF, 0x74, 0xC6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x27, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB4,
+0x02, 0x44, 0x54, 0xB4,
+
+0x2A, 0x44, 0x4C, 0xB6,
+0x1A, 0x44, 0x54, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x3D, 0xCF, 0x75, 0xC6,
+0x00, 0x80, 0x00, 0xE8,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x45, 0x4D, 0xB6,
+0x02, 0x45, 0x55, 0xB6,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x3D, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xA6, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xCD, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x94, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzf[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x5D, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x4F, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x34, 0x80, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x27, 0xCF, 0x75, 0xC6,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x20, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x3D, 0xCF, 0x74, 0xC2,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x31, 0x27, 0x20, 0xE9,
+
+0x0A, 0x44, 0x4C, 0xB4,
+0x02, 0x44, 0x54, 0xB4,
+
+0x2A, 0x45, 0x4D, 0xB6,
+0x1A, 0x45, 0x55, 0xB6,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x38, 0x3D, 0x20, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xAA, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xD3, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x98, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzs[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x65, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x57, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x27, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x29, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x27, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x0A, 0x45, 0x4D, 0xB0,
+0x02, 0x45, 0x55, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB2,
+0x1A, 0x45, 0x55, 0xB2,
+
+0x0A, 0x45, 0x4D, 0xB4,
+0x02, 0x45, 0x55, 0xB4,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x20,
+0x02, 0x20,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA7, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0xA2, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xCA, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x90, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzsa[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x6A, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x5C, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x27, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x2E, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x27, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x0A, 0x45, 0x4D, 0xB0,
+0x02, 0x45, 0x55, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB2,
+0x1A, 0x45, 0x55, 0xB2,
+
+0x0A, 0x45, 0x4D, 0xB4,
+0x02, 0x45, 0x55, 0xB4,
+
+0x27, 0xCF, 0x74, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB6,
+0x1A, 0x44, 0x54, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0x9D, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xC5, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x8B, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzsaf[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x6E, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x60, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x27, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x32, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x27, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x0A, 0x45, 0x4D, 0xB0,
+0x02, 0x45, 0x55, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB2,
+0x1A, 0x45, 0x55, 0xB2,
+
+0x0A, 0x45, 0x4D, 0xB4,
+0x02, 0x45, 0x55, 0xB4,
+
+0x27, 0xCF, 0x74, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9C, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB6,
+0x1A, 0x44, 0x54, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x45, 0x4D, 0xB6,
+0x02, 0x45, 0x55, 0xB6,
+
+0x3D, 0xCF, 0x75, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x31, 0x3D, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x9D, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x9E, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x30, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x38, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0x99, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xC1, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x87, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
+
+static unsigned char warp_g400_tgzsf[] = {
+
+0x00, 0x88, 0x98, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+0xFF, 0x80, 0xC0, 0xE9,
+0x00, 0x80, 0x00, 0xE8,
+
+0x22, 0x40, 0x48, 0xBF,
+0x2A, 0x40, 0x50, 0xBF,
+
+0x32, 0x41, 0x49, 0xBF,
+0x3A, 0x41, 0x51, 0xBF,
+
+0xC3, 0x6B,
+0xCB, 0x6B,
+0x00, 0x88, 0x98, 0xE9,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x96, 0xE2,
+0x41, 0x04,
+
+0x7B, 0x43, 0xA0, 0xE8,
+0x73, 0x4B, 0xA0, 0xE8,
+
+0xAD, 0xEE, 0x29, 0x9F,
+0x00, 0xE0,
+0x49, 0x04,
+
+0x90, 0xE2,
+0x51, 0x04,
+0x31, 0x46, 0xB1, 0xE8,
+
+0x49, 0x41, 0xC0, 0xEC,
+0x39, 0x57, 0xB1, 0xE8,
+
+0x00, 0x04,
+0x46, 0xE2,
+0x73, 0x53, 0xA0, 0xE8,
+
+0x51, 0x41, 0xC0, 0xEC,
+0x31, 0x00,
+0x39, 0x00,
+
+0x6A, 0x80, 0x15, 0xEA,
+0x08, 0x04,
+0x10, 0x04,
+
+0x51, 0x49, 0xC0, 0xEC,
+0x2F, 0x41, 0x60, 0xEA,
+
+0x31, 0x20,
+0x39, 0x20,
+0x1F, 0x42, 0xA0, 0xE8,
+
+0x2A, 0x42, 0x4A, 0xBF,
+0x27, 0x4A, 0xA0, 0xE8,
+
+0x1A, 0x42, 0x52, 0xBF,
+0x1E, 0x49, 0x60, 0xEA,
+
+0x73, 0x7B, 0xC8, 0xEC,
+0x26, 0x51, 0x60, 0xEA,
+
+0x32, 0x40, 0x48, 0xBD,
+0x22, 0x40, 0x50, 0xBD,
+
+0x12, 0x41, 0x49, 0xBD,
+0x3A, 0x41, 0x51, 0xBD,
+
+0xBF, 0x2F, 0x26, 0xBD,
+0x00, 0xE0,
+0x7B, 0x72,
+
+0x32, 0x20,
+0x22, 0x20,
+0x12, 0x20,
+0x3A, 0x20,
+
+0x46, 0x31, 0x46, 0xBF,
+0x4E, 0x31, 0x4E, 0xBF,
+
+0xB3, 0xE2, 0x2D, 0x9F,
+0x00, 0x80, 0x00, 0xE8,
+
+0x56, 0x31, 0x56, 0xBF,
+0x47, 0x39, 0x47, 0xBF,
+
+0x4F, 0x39, 0x4F, 0xBF,
+0x57, 0x39, 0x57, 0xBF,
+
+0x5C, 0x80, 0x07, 0xEA,
+0x24, 0x41, 0x20, 0xE9,
+
+0x42, 0x73, 0xF8, 0xEC,
+0x00, 0xE0,
+0x2D, 0x73,
+
+0x33, 0x72,
+0x0C, 0xE3,
+0xA5, 0x2F, 0x1E, 0xBD,
+
+0x43, 0x43, 0x2D, 0xDF,
+0x4B, 0x4B, 0x2D, 0xDF,
+
+0xAE, 0x1E, 0x26, 0xBD,
+0x58, 0xE3,
+0x33, 0x66,
+
+0x53, 0x53, 0x2D, 0xDF,
+0x00, 0x80, 0x00, 0xE8,
+
+0xB8, 0x38, 0x33, 0xBF,
+0x00, 0xE0,
+0x59, 0xE3,
+
+0x1E, 0x12, 0x41, 0xE9,
+0x1A, 0x22, 0x41, 0xE9,
+
+0x2B, 0x40, 0x3D, 0xE9,
+0x3F, 0x4B, 0xA0, 0xE8,
+
+0x2D, 0x73,
+0x30, 0x76,
+0x05, 0x80, 0x3D, 0xEA,
+
+0x37, 0x43, 0xA0, 0xE8,
+0x3D, 0x53, 0xA0, 0xE8,
+
+0x48, 0x70, 0xF8, 0xEC,
+0x2B, 0x48, 0x3C, 0xE9,
+
+0x1F, 0x27, 0xBC, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x00, 0x80, 0x00, 0xE8,
+0x00, 0x80, 0x00, 0xE8,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x15, 0xC0, 0x20, 0xE9,
+0x15, 0xC0, 0x20, 0xE9,
+
+0x18, 0x3A, 0x41, 0xE9,
+0x1D, 0x32, 0x41, 0xE9,
+
+0x2A, 0x40, 0x20, 0xE9,
+0x56, 0x3D, 0x56, 0xDF,
+
+0x46, 0x37, 0x46, 0xDF,
+0x4E, 0x3F, 0x4E, 0xDF,
+
+0x16, 0x30, 0x20, 0xE9,
+0x4F, 0x3F, 0x4F, 0xDF,
+
+0x47, 0x37, 0x47, 0xDF,
+0x57, 0x3D, 0x57, 0xDF,
+
+0x32, 0x32, 0x2D, 0xDF,
+0x22, 0x22, 0x2D, 0xDF,
+
+0x12, 0x12, 0x2D, 0xDF,
+0x3A, 0x3A, 0x2D, 0xDF,
+
+0x27, 0xCF, 0x74, 0xC2,
+0x37, 0xCF, 0x74, 0xC4,
+
+0x0A, 0x44, 0x4C, 0xB0,
+0x02, 0x44, 0x54, 0xB0,
+
+0x3D, 0xCF, 0x74, 0xC0,
+0x34, 0x37, 0x20, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x38, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3C, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB2,
+0x1A, 0x44, 0x54, 0xB2,
+
+0x2E, 0x80, 0x3A, 0xEA,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x27, 0xCF, 0x75, 0xC0,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x32, 0x31, 0x5F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x33, 0x39, 0x5F, 0xE9,
+
+0x3D, 0xCF, 0x75, 0xC2,
+0x37, 0xCF, 0x75, 0xC4,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA6, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA3, 0x3D, 0x20, 0xE9,
+
+0x2A, 0x44, 0x4C, 0xB4,
+0x1A, 0x44, 0x54, 0xB4,
+
+0x0A, 0x45, 0x4D, 0xB0,
+0x02, 0x45, 0x55, 0xB0,
+
+0x88, 0x73, 0x5E, 0xE9,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA0, 0x37, 0x20, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x3E, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x3F, 0x38, 0x4F, 0xE9,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x3A, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x3B, 0x39, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB2,
+0x1A, 0x45, 0x55, 0xB2,
+
+0x0A, 0x45, 0x4D, 0xB4,
+0x02, 0x45, 0x55, 0xB4,
+
+0x27, 0xCF, 0x75, 0xC6,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0xA7, 0x30, 0x4F, 0xE9,
+0x0A, 0x20,
+0x02, 0x20,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x31, 0x27, 0x20, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA8, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x45, 0x4D, 0xB6,
+0x1A, 0x45, 0x55, 0xB6,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x36, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x37, 0x39, 0x4F, 0xE9,
+
+0x00, 0x80, 0x00, 0xE8,
+0x2A, 0x20,
+0x1A, 0x20,
+
+0x2A, 0x46, 0x4E, 0xBF,
+0x1A, 0x46, 0x56, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA4, 0x31, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA5, 0x39, 0x4F, 0xE9,
+
+0x0A, 0x47, 0x4F, 0xBF,
+0x02, 0x47, 0x57, 0xBF,
+
+0x31, 0x53, 0x2F, 0x9F,
+0xA1, 0x30, 0x4F, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0xA2, 0x38, 0x4F, 0xE9,
+
+0x2A, 0x43, 0x4B, 0xBF,
+0x1A, 0x43, 0x53, 0xBF,
+
+0x30, 0x50, 0x2E, 0x9F,
+0x35, 0x31, 0x4F, 0xE9,
+
+0x38, 0x21, 0x2C, 0x9F,
+0x39, 0x39, 0x4F, 0xE9,
+
+0x31, 0x53, 0x2F, 0x9F,
+0x80, 0x31, 0x57, 0xE9,
+
+0x39, 0xE5, 0x2C, 0x9F,
+0x81, 0x39, 0x57, 0xE9,
+
+0x37, 0x48, 0x50, 0xBD,
+0x8A, 0x36, 0x20, 0xE9,
+
+0x86, 0x76, 0x57, 0xE9,
+0x8B, 0x3E, 0x20, 0xE9,
+
+0x82, 0x30, 0x57, 0xE9,
+0x87, 0x77, 0x57, 0xE9,
+
+0x83, 0x38, 0x57, 0xE9,
+0x35, 0x49, 0x51, 0xBD,
+
+0x84, 0x31, 0x5E, 0xE9,
+0x30, 0x1F, 0x5F, 0xE9,
+
+0x85, 0x39, 0x5E, 0xE9,
+0x57, 0x25, 0x20, 0xE9,
+
+0x2B, 0x48, 0x20, 0xE9,
+0x1D, 0x37, 0xE1, 0xEA,
+
+0x1E, 0x35, 0xE1, 0xEA,
+0x00, 0xE0,
+0x26, 0x77,
+
+0x24, 0x49, 0x20, 0xE9,
+0x9D, 0xFF, 0x20, 0xEA,
+
+0x16, 0x26, 0x20, 0xE9,
+0x57, 0x2E, 0xBF, 0xEA,
+
+0x1C, 0x46, 0xA0, 0xE8,
+0x23, 0x4E, 0xA0, 0xE8,
+
+0x2B, 0x56, 0xA0, 0xE8,
+0x1D, 0x47, 0xA0, 0xE8,
+
+0x24, 0x4F, 0xA0, 0xE8,
+0x2C, 0x57, 0xA0, 0xE8,
+
+0x1C, 0x00,
+0x23, 0x00,
+0x2B, 0x00,
+0x00, 0xE0,
+
+0x1D, 0x00,
+0x24, 0x00,
+0x2C, 0x00,
+0x00, 0xE0,
+
+0x1C, 0x65,
+0x23, 0x65,
+0x2B, 0x65,
+0x00, 0xE0,
+
+0x1D, 0x65,
+0x24, 0x65,
+0x2C, 0x65,
+0x00, 0xE0,
+
+0x1C, 0x23, 0x60, 0xEC,
+0x36, 0xD7, 0x36, 0xAD,
+
+0x2B, 0x80, 0x60, 0xEC,
+0x1D, 0x24, 0x60, 0xEC,
+
+0x3E, 0xD7, 0x3E, 0xAD,
+0x2C, 0x80, 0x60, 0xEC,
+
+0x1C, 0x2B, 0xDE, 0xE8,
+0x23, 0x80, 0xDE, 0xE8,
+
+0x36, 0x80, 0x36, 0xBD,
+0x3E, 0x80, 0x3E, 0xBD,
+
+0x33, 0xD7, 0x1C, 0xBD,
+0x3B, 0xD7, 0x23, 0xBD,
+
+0x46, 0x80, 0x46, 0xCF,
+0x4F, 0x80, 0x4F, 0xCF,
+
+0x56, 0x33, 0x56, 0xCF,
+0x47, 0x3B, 0x47, 0xCF,
+
+0xC5, 0xFF, 0x20, 0xEA,
+0x00, 0x80, 0x00, 0xE8,
+
+0x4E, 0x33, 0x4E, 0xCF,
+0x57, 0x3B, 0x57, 0xCF,
+
+0x8B, 0xFF, 0x20, 0xEA,
+0x57, 0xC0, 0xBF, 0xEA,
+
+0x00, 0x80, 0xA0, 0xE9,
+0x00, 0x00, 0xD8, 0xEC,
+
+};
diff --git a/drivers/char/drm/mga_warp.c b/drivers/char/drm/mga_warp.c
new file mode 100644
index 0000000..0a3a0cc
--- /dev/null
+++ b/drivers/char/drm/mga_warp.c
@@ -0,0 +1,210 @@
+/* mga_warp.c -- Matrox G200/G400 WARP engine management -*- linux-c -*-
+ * Created: Thu Jan 11 21:29:32 2001 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "mga_drm.h"
+#include "mga_drv.h"
+#include "mga_ucode.h"
+
+
+#define MGA_WARP_CODE_ALIGN		256		/* in bytes */
+
+#define WARP_UCODE_SIZE( which )					\
+	((sizeof(which) / MGA_WARP_CODE_ALIGN + 1) * MGA_WARP_CODE_ALIGN)
+
+#define WARP_UCODE_INSTALL( which, where )				\
+do {									\
+	DRM_DEBUG( " pcbase = 0x%08lx  vcbase = %p\n", pcbase, vcbase );\
+	dev_priv->warp_pipe_phys[where] = pcbase;			\
+	memcpy( vcbase, which, sizeof(which) );				\
+	pcbase += WARP_UCODE_SIZE( which );				\
+	vcbase += WARP_UCODE_SIZE( which );				\
+} while (0)
+
+
+static unsigned int mga_warp_g400_microcode_size( drm_mga_private_t *dev_priv )
+{
+	unsigned int size;
+
+	size = ( WARP_UCODE_SIZE( warp_g400_tgz ) +
+		 WARP_UCODE_SIZE( warp_g400_tgza ) +
+		 WARP_UCODE_SIZE( warp_g400_tgzaf ) +
+		 WARP_UCODE_SIZE( warp_g400_tgzf ) +
+		 WARP_UCODE_SIZE( warp_g400_tgzs ) +
+		 WARP_UCODE_SIZE( warp_g400_tgzsa ) +
+		 WARP_UCODE_SIZE( warp_g400_tgzsaf ) +
+		 WARP_UCODE_SIZE( warp_g400_tgzsf ) +
+		 WARP_UCODE_SIZE( warp_g400_t2gz ) +
+		 WARP_UCODE_SIZE( warp_g400_t2gza ) +
+		 WARP_UCODE_SIZE( warp_g400_t2gzaf ) +
+		 WARP_UCODE_SIZE( warp_g400_t2gzf ) +
+		 WARP_UCODE_SIZE( warp_g400_t2gzs ) +
+		 WARP_UCODE_SIZE( warp_g400_t2gzsa ) +
+		 WARP_UCODE_SIZE( warp_g400_t2gzsaf ) +
+		 WARP_UCODE_SIZE( warp_g400_t2gzsf ) );
+
+	size = PAGE_ALIGN( size );
+
+	DRM_DEBUG( "G400 ucode size = %d bytes\n", size );
+	return size;
+}
+
+static unsigned int mga_warp_g200_microcode_size( drm_mga_private_t *dev_priv )
+{
+	unsigned int size;
+
+	size = ( WARP_UCODE_SIZE( warp_g200_tgz ) +
+		 WARP_UCODE_SIZE( warp_g200_tgza ) +
+		 WARP_UCODE_SIZE( warp_g200_tgzaf ) +
+		 WARP_UCODE_SIZE( warp_g200_tgzf ) +
+		 WARP_UCODE_SIZE( warp_g200_tgzs ) +
+		 WARP_UCODE_SIZE( warp_g200_tgzsa ) +
+		 WARP_UCODE_SIZE( warp_g200_tgzsaf ) +
+		 WARP_UCODE_SIZE( warp_g200_tgzsf ) );
+
+	size = PAGE_ALIGN( size );
+
+	DRM_DEBUG( "G200 ucode size = %d bytes\n", size );
+	return size;
+}
+
+static int mga_warp_install_g400_microcode( drm_mga_private_t *dev_priv )
+{
+	unsigned char *vcbase = dev_priv->warp->handle;
+	unsigned long pcbase = dev_priv->warp->offset;
+	unsigned int size;
+
+	size = mga_warp_g400_microcode_size( dev_priv );
+	if ( size > dev_priv->warp->size ) {
+		DRM_ERROR( "microcode too large! (%u > %lu)\n",
+			   size, dev_priv->warp->size );
+		return DRM_ERR(ENOMEM);
+	}
+
+	memset( dev_priv->warp_pipe_phys, 0,
+		sizeof(dev_priv->warp_pipe_phys) );
+
+	WARP_UCODE_INSTALL( warp_g400_tgz,	MGA_WARP_TGZ );
+	WARP_UCODE_INSTALL( warp_g400_tgzf,	MGA_WARP_TGZF );
+	WARP_UCODE_INSTALL( warp_g400_tgza,	MGA_WARP_TGZA );
+	WARP_UCODE_INSTALL( warp_g400_tgzaf,	MGA_WARP_TGZAF );
+	WARP_UCODE_INSTALL( warp_g400_tgzs,	MGA_WARP_TGZS );
+	WARP_UCODE_INSTALL( warp_g400_tgzsf,	MGA_WARP_TGZSF );
+	WARP_UCODE_INSTALL( warp_g400_tgzsa,	MGA_WARP_TGZSA );
+	WARP_UCODE_INSTALL( warp_g400_tgzsaf,	MGA_WARP_TGZSAF );
+
+	WARP_UCODE_INSTALL( warp_g400_t2gz,	MGA_WARP_T2GZ );
+	WARP_UCODE_INSTALL( warp_g400_t2gzf,	MGA_WARP_T2GZF );
+	WARP_UCODE_INSTALL( warp_g400_t2gza,	MGA_WARP_T2GZA );
+	WARP_UCODE_INSTALL( warp_g400_t2gzaf,	MGA_WARP_T2GZAF );
+	WARP_UCODE_INSTALL( warp_g400_t2gzs,	MGA_WARP_T2GZS );
+	WARP_UCODE_INSTALL( warp_g400_t2gzsf,	MGA_WARP_T2GZSF );
+	WARP_UCODE_INSTALL( warp_g400_t2gzsa,	MGA_WARP_T2GZSA );
+	WARP_UCODE_INSTALL( warp_g400_t2gzsaf,	MGA_WARP_T2GZSAF );
+
+	return 0;
+}
+
+static int mga_warp_install_g200_microcode( drm_mga_private_t *dev_priv )
+{
+	unsigned char *vcbase = dev_priv->warp->handle;
+	unsigned long pcbase = dev_priv->warp->offset;
+	unsigned int size;
+
+	size = mga_warp_g200_microcode_size( dev_priv );
+	if ( size > dev_priv->warp->size ) {
+		DRM_ERROR( "microcode too large! (%u > %lu)\n",
+			   size, dev_priv->warp->size );
+		return DRM_ERR(ENOMEM);
+	}
+
+	memset( dev_priv->warp_pipe_phys, 0,
+		sizeof(dev_priv->warp_pipe_phys) );
+
+	WARP_UCODE_INSTALL( warp_g200_tgz,	MGA_WARP_TGZ );
+	WARP_UCODE_INSTALL( warp_g200_tgzf,	MGA_WARP_TGZF );
+	WARP_UCODE_INSTALL( warp_g200_tgza,	MGA_WARP_TGZA );
+	WARP_UCODE_INSTALL( warp_g200_tgzaf,	MGA_WARP_TGZAF );
+	WARP_UCODE_INSTALL( warp_g200_tgzs,	MGA_WARP_TGZS );
+	WARP_UCODE_INSTALL( warp_g200_tgzsf,	MGA_WARP_TGZSF );
+	WARP_UCODE_INSTALL( warp_g200_tgzsa,	MGA_WARP_TGZSA );
+	WARP_UCODE_INSTALL( warp_g200_tgzsaf,	MGA_WARP_TGZSAF );
+
+	return 0;
+}
+
+int mga_warp_install_microcode(	drm_mga_private_t *dev_priv )
+{
+	switch ( dev_priv->chipset ) {
+	case MGA_CARD_TYPE_G400:
+		return mga_warp_install_g400_microcode( dev_priv );
+	case MGA_CARD_TYPE_G200:
+		return mga_warp_install_g200_microcode( dev_priv );
+	default:
+		return DRM_ERR(EINVAL);
+	}
+}
+
+#define WMISC_EXPECTED		(MGA_WUCODECACHE_ENABLE | MGA_WMASTER_ENABLE)
+
+int mga_warp_init( drm_mga_private_t *dev_priv )
+{
+	u32 wmisc;
+
+	/* FIXME: Get rid of these damned magic numbers...
+	 */
+	switch ( dev_priv->chipset ) {
+	case MGA_CARD_TYPE_G400:
+		MGA_WRITE( MGA_WIADDR2, MGA_WMODE_SUSPEND );
+		MGA_WRITE( MGA_WGETMSB, 0x00000E00 );
+		MGA_WRITE( MGA_WVRTXSZ, 0x00001807 );
+		MGA_WRITE( MGA_WACCEPTSEQ, 0x18000000 );
+		break;
+	case MGA_CARD_TYPE_G200:
+		MGA_WRITE( MGA_WIADDR, MGA_WMODE_SUSPEND );
+		MGA_WRITE( MGA_WGETMSB, 0x1606 );
+		MGA_WRITE( MGA_WVRTXSZ, 7 );
+		break;
+	default:
+		return DRM_ERR(EINVAL);
+	}
+
+	MGA_WRITE( MGA_WMISC, (MGA_WUCODECACHE_ENABLE |
+			       MGA_WMASTER_ENABLE |
+			       MGA_WCACHEFLUSH_ENABLE) );
+	wmisc = MGA_READ( MGA_WMISC );
+	if ( wmisc != WMISC_EXPECTED ) {
+		DRM_ERROR( "WARP engine config failed! 0x%x != 0x%x\n",
+			   wmisc, WMISC_EXPECTED );
+		return DRM_ERR(EINVAL);
+	}
+
+	return 0;
+}
diff --git a/drivers/char/drm/r128_cce.c b/drivers/char/drm/r128_cce.c
new file mode 100644
index 0000000..08ed8d0
--- /dev/null
+++ b/drivers/char/drm/r128_cce.c
@@ -0,0 +1,943 @@
+/* r128_cce.c -- ATI Rage 128 driver -*- linux-c -*-
+ * Created: Wed Apr  5 19:24:19 2000 by kevin@precisioninsight.com
+ *
+ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "r128_drm.h"
+#include "r128_drv.h"
+
+#define R128_FIFO_DEBUG		0
+
+/* CCE microcode (from ATI) */
+static u32 r128_cce_microcode[] = {
+	0, 276838400, 0, 268449792, 2, 142, 2, 145, 0, 1076765731, 0,
+	1617039951, 0, 774592877, 0, 1987540286, 0, 2307490946U, 0,
+	599558925, 0, 589505315, 0, 596487092, 0, 589505315, 1,
+	11544576, 1, 206848, 1, 311296, 1, 198656, 2, 912273422, 11,
+	262144, 0, 0, 1, 33559837, 1, 7438, 1, 14809, 1, 6615, 12, 28,
+	1, 6614, 12, 28, 2, 23, 11, 18874368, 0, 16790922, 1, 409600, 9,
+	30, 1, 147854772, 16, 420483072, 3, 8192, 0, 10240, 1, 198656,
+	1, 15630, 1, 51200, 10, 34858, 9, 42, 1, 33559823, 2, 10276, 1,
+	15717, 1, 15718, 2, 43, 1, 15936948, 1, 570480831, 1, 14715071,
+	12, 322123831, 1, 33953125, 12, 55, 1, 33559908, 1, 15718, 2,
+	46, 4, 2099258, 1, 526336, 1, 442623, 4, 4194365, 1, 509952, 1,
+	459007, 3, 0, 12, 92, 2, 46, 12, 176, 1, 15734, 1, 206848, 1,
+	18432, 1, 133120, 1, 100670734, 1, 149504, 1, 165888, 1,
+	15975928, 1, 1048576, 6, 3145806, 1, 15715, 16, 2150645232U, 2,
+	268449859, 2, 10307, 12, 176, 1, 15734, 1, 15735, 1, 15630, 1,
+	15631, 1, 5253120, 6, 3145810, 16, 2150645232U, 1, 15864, 2, 82,
+	1, 343310, 1, 1064207, 2, 3145813, 1, 15728, 1, 7817, 1, 15729,
+	3, 15730, 12, 92, 2, 98, 1, 16168, 1, 16167, 1, 16002, 1, 16008,
+	1, 15974, 1, 15975, 1, 15990, 1, 15976, 1, 15977, 1, 15980, 0,
+	15981, 1, 10240, 1, 5253120, 1, 15720, 1, 198656, 6, 110, 1,
+	180224, 1, 103824738, 2, 112, 2, 3145839, 0, 536885440, 1,
+	114880, 14, 125, 12, 206975, 1, 33559995, 12, 198784, 0,
+	33570236, 1, 15803, 0, 15804, 3, 294912, 1, 294912, 3, 442370,
+	1, 11544576, 0, 811612160, 1, 12593152, 1, 11536384, 1,
+	14024704, 7, 310382726, 0, 10240, 1, 14796, 1, 14797, 1, 14793,
+	1, 14794, 0, 14795, 1, 268679168, 1, 9437184, 1, 268449792, 1,
+	198656, 1, 9452827, 1, 1075854602, 1, 1075854603, 1, 557056, 1,
+	114880, 14, 159, 12, 198784, 1, 1109409213, 12, 198783, 1,
+	1107312059, 12, 198784, 1, 1109409212, 2, 162, 1, 1075854781, 1,
+	1073757627, 1, 1075854780, 1, 540672, 1, 10485760, 6, 3145894,
+	16, 274741248, 9, 168, 3, 4194304, 3, 4209949, 0, 0, 0, 256, 14,
+	174, 1, 114857, 1, 33560007, 12, 176, 0, 10240, 1, 114858, 1,
+	33560018, 1, 114857, 3, 33560007, 1, 16008, 1, 114874, 1,
+	33560360, 1, 114875, 1, 33560154, 0, 15963, 0, 256, 0, 4096, 1,
+	409611, 9, 188, 0, 10240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+static int R128_READ_PLL(drm_device_t *dev, int addr)
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+
+	R128_WRITE8(R128_CLOCK_CNTL_INDEX, addr & 0x1f);
+	return R128_READ(R128_CLOCK_CNTL_DATA);
+}
+
+#if R128_FIFO_DEBUG
+static void r128_status( drm_r128_private_t *dev_priv )
+{
+	printk( "GUI_STAT           = 0x%08x\n",
+		(unsigned int)R128_READ( R128_GUI_STAT ) );
+	printk( "PM4_STAT           = 0x%08x\n",
+		(unsigned int)R128_READ( R128_PM4_STAT ) );
+	printk( "PM4_BUFFER_DL_WPTR = 0x%08x\n",
+		(unsigned int)R128_READ( R128_PM4_BUFFER_DL_WPTR ) );
+	printk( "PM4_BUFFER_DL_RPTR = 0x%08x\n",
+		(unsigned int)R128_READ( R128_PM4_BUFFER_DL_RPTR ) );
+	printk( "PM4_MICRO_CNTL     = 0x%08x\n",
+		(unsigned int)R128_READ( R128_PM4_MICRO_CNTL ) );
+	printk( "PM4_BUFFER_CNTL    = 0x%08x\n",
+		(unsigned int)R128_READ( R128_PM4_BUFFER_CNTL ) );
+}
+#endif
+
+
+/* ================================================================
+ * Engine, FIFO control
+ */
+
+static int r128_do_pixcache_flush( drm_r128_private_t *dev_priv )
+{
+	u32 tmp;
+	int i;
+
+	tmp = R128_READ( R128_PC_NGUI_CTLSTAT ) | R128_PC_FLUSH_ALL;
+	R128_WRITE( R128_PC_NGUI_CTLSTAT, tmp );
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		if ( !(R128_READ( R128_PC_NGUI_CTLSTAT ) & R128_PC_BUSY) ) {
+			return 0;
+		}
+		DRM_UDELAY( 1 );
+	}
+
+#if R128_FIFO_DEBUG
+	DRM_ERROR( "failed!\n" );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+static int r128_do_wait_for_fifo( drm_r128_private_t *dev_priv, int entries )
+{
+	int i;
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		int slots = R128_READ( R128_GUI_STAT ) & R128_GUI_FIFOCNT_MASK;
+		if ( slots >= entries ) return 0;
+		DRM_UDELAY( 1 );
+	}
+
+#if R128_FIFO_DEBUG
+	DRM_ERROR( "failed!\n" );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+static int r128_do_wait_for_idle( drm_r128_private_t *dev_priv )
+{
+	int i, ret;
+
+	ret = r128_do_wait_for_fifo( dev_priv, 64 );
+	if ( ret ) return ret;
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		if ( !(R128_READ( R128_GUI_STAT ) & R128_GUI_ACTIVE) ) {
+			r128_do_pixcache_flush( dev_priv );
+			return 0;
+		}
+		DRM_UDELAY( 1 );
+	}
+
+#if R128_FIFO_DEBUG
+	DRM_ERROR( "failed!\n" );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+
+/* ================================================================
+ * CCE control, initialization
+ */
+
+/* Load the microcode for the CCE */
+static void r128_cce_load_microcode( drm_r128_private_t *dev_priv )
+{
+	int i;
+
+	DRM_DEBUG( "\n" );
+
+	r128_do_wait_for_idle( dev_priv );
+
+	R128_WRITE( R128_PM4_MICROCODE_ADDR, 0 );
+	for ( i = 0 ; i < 256 ; i++ ) {
+		R128_WRITE( R128_PM4_MICROCODE_DATAH,
+			    r128_cce_microcode[i * 2] );
+		R128_WRITE( R128_PM4_MICROCODE_DATAL,
+			    r128_cce_microcode[i * 2 + 1] );
+	}
+}
+
+/* Flush any pending commands to the CCE.  This should only be used just
+ * prior to a wait for idle, as it informs the engine that the command
+ * stream is ending.
+ */
+static void r128_do_cce_flush( drm_r128_private_t *dev_priv )
+{
+	u32 tmp;
+
+	tmp = R128_READ( R128_PM4_BUFFER_DL_WPTR ) | R128_PM4_BUFFER_DL_DONE;
+	R128_WRITE( R128_PM4_BUFFER_DL_WPTR, tmp );
+}
+
+/* Wait for the CCE to go idle.
+ */
+int r128_do_cce_idle( drm_r128_private_t *dev_priv )
+{
+	int i;
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		if ( GET_RING_HEAD( dev_priv ) == dev_priv->ring.tail ) {
+			int pm4stat = R128_READ( R128_PM4_STAT );
+			if ( ( (pm4stat & R128_PM4_FIFOCNT_MASK) >=
+			       dev_priv->cce_fifo_size ) &&
+			     !(pm4stat & (R128_PM4_BUSY |
+					  R128_PM4_GUI_ACTIVE)) ) {
+				return r128_do_pixcache_flush( dev_priv );
+			}
+		}
+		DRM_UDELAY( 1 );
+	}
+
+#if R128_FIFO_DEBUG
+	DRM_ERROR( "failed!\n" );
+	r128_status( dev_priv );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+/* Start the Concurrent Command Engine.
+ */
+static void r128_do_cce_start( drm_r128_private_t *dev_priv )
+{
+	r128_do_wait_for_idle( dev_priv );
+
+	R128_WRITE( R128_PM4_BUFFER_CNTL,
+		    dev_priv->cce_mode | dev_priv->ring.size_l2qw
+		    | R128_PM4_BUFFER_CNTL_NOUPDATE );
+	R128_READ( R128_PM4_BUFFER_ADDR ); /* as per the sample code */
+	R128_WRITE( R128_PM4_MICRO_CNTL, R128_PM4_MICRO_FREERUN );
+
+	dev_priv->cce_running = 1;
+}
+
+/* Reset the Concurrent Command Engine.  This will not flush any pending
+ * commands, so you must wait for the CCE command stream to complete
+ * before calling this routine.
+ */
+static void r128_do_cce_reset( drm_r128_private_t *dev_priv )
+{
+	R128_WRITE( R128_PM4_BUFFER_DL_WPTR, 0 );
+	R128_WRITE( R128_PM4_BUFFER_DL_RPTR, 0 );
+	dev_priv->ring.tail = 0;
+}
+
+/* Stop the Concurrent Command Engine.  This will not flush any pending
+ * commands, so you must flush the command stream and wait for the CCE
+ * to go idle before calling this routine.
+ */
+static void r128_do_cce_stop( drm_r128_private_t *dev_priv )
+{
+	R128_WRITE( R128_PM4_MICRO_CNTL, 0 );
+	R128_WRITE( R128_PM4_BUFFER_CNTL,
+		    R128_PM4_NONPM4 | R128_PM4_BUFFER_CNTL_NOUPDATE );
+
+	dev_priv->cce_running = 0;
+}
+
+/* Reset the engine.  This will stop the CCE if it is running.
+ */
+static int r128_do_engine_reset( drm_device_t *dev )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	u32 clock_cntl_index, mclk_cntl, gen_reset_cntl;
+
+	r128_do_pixcache_flush( dev_priv );
+
+	clock_cntl_index = R128_READ( R128_CLOCK_CNTL_INDEX );
+	mclk_cntl = R128_READ_PLL( dev, R128_MCLK_CNTL );
+
+	R128_WRITE_PLL( R128_MCLK_CNTL,
+			mclk_cntl | R128_FORCE_GCP | R128_FORCE_PIPE3D_CP );
+
+	gen_reset_cntl = R128_READ( R128_GEN_RESET_CNTL );
+
+	/* Taken from the sample code - do not change */
+	R128_WRITE( R128_GEN_RESET_CNTL,
+		    gen_reset_cntl | R128_SOFT_RESET_GUI );
+	R128_READ( R128_GEN_RESET_CNTL );
+	R128_WRITE( R128_GEN_RESET_CNTL,
+		    gen_reset_cntl & ~R128_SOFT_RESET_GUI );
+	R128_READ( R128_GEN_RESET_CNTL );
+
+	R128_WRITE_PLL( R128_MCLK_CNTL, mclk_cntl );
+	R128_WRITE( R128_CLOCK_CNTL_INDEX, clock_cntl_index );
+	R128_WRITE( R128_GEN_RESET_CNTL, gen_reset_cntl );
+
+	/* Reset the CCE ring */
+	r128_do_cce_reset( dev_priv );
+
+	/* The CCE is no longer running after an engine reset */
+	dev_priv->cce_running = 0;
+
+	/* Reset any pending vertex, indirect buffers */
+	r128_freelist_reset( dev );
+
+	return 0;
+}
+
+static void r128_cce_init_ring_buffer( drm_device_t *dev,
+				       drm_r128_private_t *dev_priv )
+{
+	u32 ring_start;
+	u32 tmp;
+
+	DRM_DEBUG( "\n" );
+
+	/* The manual (p. 2) says this address is in "VM space".  This
+	 * means it's an offset from the start of AGP space.
+	 */
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci )
+		ring_start = dev_priv->cce_ring->offset - dev->agp->base;
+	else
+#endif
+		ring_start = dev_priv->cce_ring->offset - dev->sg->handle;
+
+	R128_WRITE( R128_PM4_BUFFER_OFFSET, ring_start | R128_AGP_OFFSET );
+
+	R128_WRITE( R128_PM4_BUFFER_DL_WPTR, 0 );
+	R128_WRITE( R128_PM4_BUFFER_DL_RPTR, 0 );
+
+	/* Set watermark control */
+	R128_WRITE( R128_PM4_BUFFER_WM_CNTL,
+		    ((R128_WATERMARK_L/4) << R128_WMA_SHIFT)
+		    | ((R128_WATERMARK_M/4) << R128_WMB_SHIFT)
+		    | ((R128_WATERMARK_N/4) << R128_WMC_SHIFT)
+		    | ((R128_WATERMARK_K/64) << R128_WB_WM_SHIFT) );
+
+	/* Force read.  Why?  Because it's in the examples... */
+	R128_READ( R128_PM4_BUFFER_ADDR );
+
+	/* Turn on bus mastering */
+	tmp = R128_READ( R128_BUS_CNTL ) & ~R128_BUS_MASTER_DIS;
+	R128_WRITE( R128_BUS_CNTL, tmp );
+}
+
+static int r128_do_init_cce( drm_device_t *dev, drm_r128_init_t *init )
+{
+	drm_r128_private_t *dev_priv;
+
+	DRM_DEBUG( "\n" );
+
+	dev_priv = drm_alloc( sizeof(drm_r128_private_t), DRM_MEM_DRIVER );
+	if ( dev_priv == NULL )
+		return DRM_ERR(ENOMEM);
+
+	memset( dev_priv, 0, sizeof(drm_r128_private_t) );
+
+	dev_priv->is_pci = init->is_pci;
+
+	if ( dev_priv->is_pci && !dev->sg ) {
+		DRM_ERROR( "PCI GART memory not allocated!\n" );
+		dev->dev_private = (void *)dev_priv;
+		r128_do_cleanup_cce( dev );
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->usec_timeout = init->usec_timeout;
+	if ( dev_priv->usec_timeout < 1 ||
+	     dev_priv->usec_timeout > R128_MAX_USEC_TIMEOUT ) {
+		DRM_DEBUG( "TIMEOUT problem!\n" );
+		dev->dev_private = (void *)dev_priv;
+		r128_do_cleanup_cce( dev );
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->cce_mode = init->cce_mode;
+
+	/* GH: Simple idle check.
+	 */
+	atomic_set( &dev_priv->idle_count, 0 );
+
+	/* We don't support anything other than bus-mastering ring mode,
+	 * but the ring can be in either AGP or PCI space for the ring
+	 * read pointer.
+	 */
+	if ( ( init->cce_mode != R128_PM4_192BM ) &&
+	     ( init->cce_mode != R128_PM4_128BM_64INDBM ) &&
+	     ( init->cce_mode != R128_PM4_64BM_128INDBM ) &&
+	     ( init->cce_mode != R128_PM4_64BM_64VCBM_64INDBM ) ) {
+		DRM_DEBUG( "Bad cce_mode!\n" );
+		dev->dev_private = (void *)dev_priv;
+		r128_do_cleanup_cce( dev );
+		return DRM_ERR(EINVAL);
+	}
+
+	switch ( init->cce_mode ) {
+	case R128_PM4_NONPM4:
+		dev_priv->cce_fifo_size = 0;
+		break;
+	case R128_PM4_192PIO:
+	case R128_PM4_192BM:
+		dev_priv->cce_fifo_size = 192;
+		break;
+	case R128_PM4_128PIO_64INDBM:
+	case R128_PM4_128BM_64INDBM:
+		dev_priv->cce_fifo_size = 128;
+		break;
+	case R128_PM4_64PIO_128INDBM:
+	case R128_PM4_64BM_128INDBM:
+	case R128_PM4_64PIO_64VCBM_64INDBM:
+	case R128_PM4_64BM_64VCBM_64INDBM:
+	case R128_PM4_64PIO_64VCPIO_64INDPIO:
+		dev_priv->cce_fifo_size = 64;
+		break;
+	}
+
+	switch ( init->fb_bpp ) {
+	case 16:
+		dev_priv->color_fmt = R128_DATATYPE_RGB565;
+		break;
+	case 32:
+	default:
+		dev_priv->color_fmt = R128_DATATYPE_ARGB8888;
+		break;
+	}
+	dev_priv->front_offset	= init->front_offset;
+	dev_priv->front_pitch	= init->front_pitch;
+	dev_priv->back_offset	= init->back_offset;
+	dev_priv->back_pitch	= init->back_pitch;
+
+	switch ( init->depth_bpp ) {
+	case 16:
+		dev_priv->depth_fmt = R128_DATATYPE_RGB565;
+		break;
+	case 24:
+	case 32:
+	default:
+		dev_priv->depth_fmt = R128_DATATYPE_ARGB8888;
+		break;
+	}
+	dev_priv->depth_offset	= init->depth_offset;
+	dev_priv->depth_pitch	= init->depth_pitch;
+	dev_priv->span_offset	= init->span_offset;
+
+	dev_priv->front_pitch_offset_c = (((dev_priv->front_pitch/8) << 21) |
+					  (dev_priv->front_offset >> 5));
+	dev_priv->back_pitch_offset_c = (((dev_priv->back_pitch/8) << 21) |
+					 (dev_priv->back_offset >> 5));
+	dev_priv->depth_pitch_offset_c = (((dev_priv->depth_pitch/8) << 21) |
+					  (dev_priv->depth_offset >> 5) |
+					  R128_DST_TILE);
+	dev_priv->span_pitch_offset_c = (((dev_priv->depth_pitch/8) << 21) |
+					 (dev_priv->span_offset >> 5));
+
+	DRM_GETSAREA();
+	
+	if(!dev_priv->sarea) {
+		DRM_ERROR("could not find sarea!\n");
+		dev->dev_private = (void *)dev_priv;
+		r128_do_cleanup_cce( dev );
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->mmio = drm_core_findmap(dev, init->mmio_offset);
+	if(!dev_priv->mmio) {
+		DRM_ERROR("could not find mmio region!\n");
+		dev->dev_private = (void *)dev_priv;
+		r128_do_cleanup_cce( dev );
+		return DRM_ERR(EINVAL);
+	}
+	dev_priv->cce_ring = drm_core_findmap(dev, init->ring_offset);
+	if(!dev_priv->cce_ring) {
+		DRM_ERROR("could not find cce ring region!\n");
+		dev->dev_private = (void *)dev_priv;
+		r128_do_cleanup_cce( dev );
+		return DRM_ERR(EINVAL);
+	}
+	dev_priv->ring_rptr = drm_core_findmap(dev, init->ring_rptr_offset);
+	if(!dev_priv->ring_rptr) {
+		DRM_ERROR("could not find ring read pointer!\n");
+		dev->dev_private = (void *)dev_priv;
+		r128_do_cleanup_cce( dev );
+		return DRM_ERR(EINVAL);
+	}
+	dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+	if(!dev->agp_buffer_map) {
+		DRM_ERROR("could not find dma buffer region!\n");
+		dev->dev_private = (void *)dev_priv;
+		r128_do_cleanup_cce( dev );
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( !dev_priv->is_pci ) {
+		dev_priv->agp_textures = drm_core_findmap(dev, init->agp_textures_offset);
+		if(!dev_priv->agp_textures) {
+			DRM_ERROR("could not find agp texture region!\n");
+			dev->dev_private = (void *)dev_priv;
+			r128_do_cleanup_cce( dev );
+			return DRM_ERR(EINVAL);
+		}
+	}
+
+	dev_priv->sarea_priv =
+		(drm_r128_sarea_t *)((u8 *)dev_priv->sarea->handle +
+				     init->sarea_priv_offset);
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci ) {
+		drm_core_ioremap( dev_priv->cce_ring, dev );
+		drm_core_ioremap( dev_priv->ring_rptr, dev );
+		drm_core_ioremap( dev->agp_buffer_map, dev );
+		if(!dev_priv->cce_ring->handle ||
+		   !dev_priv->ring_rptr->handle ||
+		   !dev->agp_buffer_map->handle) {
+			DRM_ERROR("Could not ioremap agp regions!\n");
+			dev->dev_private = (void *)dev_priv;
+			r128_do_cleanup_cce( dev );
+			return DRM_ERR(ENOMEM);
+		}
+	} else
+#endif
+	{
+		dev_priv->cce_ring->handle =
+			(void *)dev_priv->cce_ring->offset;
+		dev_priv->ring_rptr->handle =
+			(void *)dev_priv->ring_rptr->offset;
+		dev->agp_buffer_map->handle = (void *)dev->agp_buffer_map->offset;
+	}
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci )
+		dev_priv->cce_buffers_offset = dev->agp->base;
+	else
+#endif
+		dev_priv->cce_buffers_offset = dev->sg->handle;
+
+	dev_priv->ring.start = (u32 *)dev_priv->cce_ring->handle;
+	dev_priv->ring.end = ((u32 *)dev_priv->cce_ring->handle
+			      + init->ring_size / sizeof(u32));
+	dev_priv->ring.size = init->ring_size;
+	dev_priv->ring.size_l2qw = drm_order( init->ring_size / 8 );
+
+	dev_priv->ring.tail_mask =
+		(dev_priv->ring.size / sizeof(u32)) - 1;
+
+	dev_priv->ring.high_mark = 128;
+
+	dev_priv->sarea_priv->last_frame = 0;
+	R128_WRITE( R128_LAST_FRAME_REG, dev_priv->sarea_priv->last_frame );
+
+	dev_priv->sarea_priv->last_dispatch = 0;
+	R128_WRITE( R128_LAST_DISPATCH_REG,
+		    dev_priv->sarea_priv->last_dispatch );
+
+#if __OS_HAS_AGP
+	if ( dev_priv->is_pci ) {
+#endif
+		if (!drm_ati_pcigart_init( dev, &dev_priv->phys_pci_gart,
+     					    &dev_priv->bus_pci_gart) ) {
+			DRM_ERROR( "failed to init PCI GART!\n" );
+			dev->dev_private = (void *)dev_priv;
+			r128_do_cleanup_cce( dev );
+			return DRM_ERR(ENOMEM);
+		}
+		R128_WRITE( R128_PCI_GART_PAGE, dev_priv->bus_pci_gart );
+#if __OS_HAS_AGP
+	}
+#endif
+
+	r128_cce_init_ring_buffer( dev, dev_priv );
+	r128_cce_load_microcode( dev_priv );
+
+	dev->dev_private = (void *)dev_priv;
+
+	r128_do_engine_reset( dev );
+
+	return 0;
+}
+
+int r128_do_cleanup_cce( drm_device_t *dev )
+{
+
+	/* Make sure interrupts are disabled here because the uninstall ioctl
+	 * may not have been called from userspace and after dev_private
+	 * is freed, it's too late.
+	 */
+	if ( dev->irq_enabled ) drm_irq_uninstall(dev);
+
+	if ( dev->dev_private ) {
+		drm_r128_private_t *dev_priv = dev->dev_private;
+
+#if __OS_HAS_AGP
+		if ( !dev_priv->is_pci ) {
+			if ( dev_priv->cce_ring != NULL )
+				drm_core_ioremapfree( dev_priv->cce_ring, dev );
+			if ( dev_priv->ring_rptr != NULL )
+				drm_core_ioremapfree( dev_priv->ring_rptr, dev );
+			if ( dev->agp_buffer_map != NULL )
+				drm_core_ioremapfree( dev->agp_buffer_map, dev );
+		} else
+#endif
+		{
+			if (!drm_ati_pcigart_cleanup( dev,
+						dev_priv->phys_pci_gart,
+						dev_priv->bus_pci_gart ))
+				DRM_ERROR( "failed to cleanup PCI GART!\n" );
+		}
+
+		drm_free( dev->dev_private, sizeof(drm_r128_private_t),
+			   DRM_MEM_DRIVER );
+		dev->dev_private = NULL;
+	}
+
+	return 0;
+}
+
+int r128_cce_init( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_init_t init;
+
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( init, (drm_r128_init_t __user *)data, sizeof(init) );
+
+	switch ( init.func ) {
+	case R128_INIT_CCE:
+		return r128_do_init_cce( dev, &init );
+	case R128_CLEANUP_CCE:
+		return r128_do_cleanup_cce( dev );
+	}
+
+	return DRM_ERR(EINVAL);
+}
+
+int r128_cce_start( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( dev_priv->cce_running || dev_priv->cce_mode == R128_PM4_NONPM4 ) {
+		DRM_DEBUG( "%s while CCE running\n", __FUNCTION__ );
+		return 0;
+	}
+
+	r128_do_cce_start( dev_priv );
+
+	return 0;
+}
+
+/* Stop the CCE.  The engine must have been idled before calling this
+ * routine.
+ */
+int r128_cce_stop( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_cce_stop_t stop;
+	int ret;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL(stop, (drm_r128_cce_stop_t __user *)data, sizeof(stop) );
+
+	/* Flush any pending CCE commands.  This ensures any outstanding
+	 * commands are exectuted by the engine before we turn it off.
+	 */
+	if ( stop.flush ) {
+		r128_do_cce_flush( dev_priv );
+	}
+
+	/* If we fail to make the engine go idle, we return an error
+	 * code so that the DRM ioctl wrapper can try again.
+	 */
+	if ( stop.idle ) {
+		ret = r128_do_cce_idle( dev_priv );
+		if ( ret ) return ret;
+	}
+
+	/* Finally, we can turn off the CCE.  If the engine isn't idle,
+	 * we will get some dropped triangles as they won't be fully
+	 * rendered before the CCE is shut down.
+	 */
+	r128_do_cce_stop( dev_priv );
+
+	/* Reset the engine */
+	r128_do_engine_reset( dev );
+
+	return 0;
+}
+
+/* Just reset the CCE ring.  Called as part of an X Server engine reset.
+ */
+int r128_cce_reset( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_DEBUG( "%s called before init done\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	r128_do_cce_reset( dev_priv );
+
+	/* The CCE is no longer running after an engine reset */
+	dev_priv->cce_running = 0;
+
+	return 0;
+}
+
+int r128_cce_idle( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( dev_priv->cce_running ) {
+		r128_do_cce_flush( dev_priv );
+	}
+
+	return r128_do_cce_idle( dev_priv );
+}
+
+int r128_engine_reset( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	return r128_do_engine_reset( dev );
+}
+
+int r128_fullscreen( DRM_IOCTL_ARGS )
+{
+	return DRM_ERR(EINVAL);
+}
+
+
+/* ================================================================
+ * Freelist management
+ */
+#define R128_BUFFER_USED	0xffffffff
+#define R128_BUFFER_FREE	0
+
+#if 0
+static int r128_freelist_init( drm_device_t *dev )
+{
+	drm_device_dma_t *dma = dev->dma;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_buf_t *buf;
+	drm_r128_buf_priv_t *buf_priv;
+	drm_r128_freelist_t *entry;
+	int i;
+
+	dev_priv->head = drm_alloc( sizeof(drm_r128_freelist_t),
+				     DRM_MEM_DRIVER );
+	if ( dev_priv->head == NULL )
+		return DRM_ERR(ENOMEM);
+
+	memset( dev_priv->head, 0, sizeof(drm_r128_freelist_t) );
+	dev_priv->head->age = R128_BUFFER_USED;
+
+	for ( i = 0 ; i < dma->buf_count ; i++ ) {
+		buf = dma->buflist[i];
+		buf_priv = buf->dev_private;
+
+		entry = drm_alloc( sizeof(drm_r128_freelist_t),
+				    DRM_MEM_DRIVER );
+		if ( !entry ) return DRM_ERR(ENOMEM);
+
+		entry->age = R128_BUFFER_FREE;
+		entry->buf = buf;
+		entry->prev = dev_priv->head;
+		entry->next = dev_priv->head->next;
+		if ( !entry->next )
+			dev_priv->tail = entry;
+
+		buf_priv->discard = 0;
+		buf_priv->dispatched = 0;
+		buf_priv->list_entry = entry;
+
+		dev_priv->head->next = entry;
+
+		if ( dev_priv->head->next )
+			dev_priv->head->next->prev = entry;
+	}
+
+	return 0;
+
+}
+#endif
+
+static drm_buf_t *r128_freelist_get( drm_device_t *dev )
+{
+	drm_device_dma_t *dma = dev->dma;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_buf_priv_t *buf_priv;
+	drm_buf_t *buf;
+	int i, t;
+
+	/* FIXME: Optimize -- use freelist code */
+
+	for ( i = 0 ; i < dma->buf_count ; i++ ) {
+		buf = dma->buflist[i];
+		buf_priv = buf->dev_private;
+		if ( buf->filp == 0 )
+			return buf;
+	}
+
+	for ( t = 0 ; t < dev_priv->usec_timeout ; t++ ) {
+		u32 done_age = R128_READ( R128_LAST_DISPATCH_REG );
+
+		for ( i = 0 ; i < dma->buf_count ; i++ ) {
+			buf = dma->buflist[i];
+			buf_priv = buf->dev_private;
+			if ( buf->pending && buf_priv->age <= done_age ) {
+				/* The buffer has been processed, so it
+				 * can now be used.
+				 */
+				buf->pending = 0;
+				return buf;
+			}
+		}
+		DRM_UDELAY( 1 );
+	}
+
+	DRM_DEBUG( "returning NULL!\n" );
+	return NULL;
+}
+
+void r128_freelist_reset( drm_device_t *dev )
+{
+	drm_device_dma_t *dma = dev->dma;
+	int i;
+
+	for ( i = 0 ; i < dma->buf_count ; i++ ) {
+		drm_buf_t *buf = dma->buflist[i];
+		drm_r128_buf_priv_t *buf_priv = buf->dev_private;
+		buf_priv->age = 0;
+	}
+}
+
+
+/* ================================================================
+ * CCE command submission
+ */
+
+int r128_wait_ring( drm_r128_private_t *dev_priv, int n )
+{
+	drm_r128_ring_buffer_t *ring = &dev_priv->ring;
+	int i;
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		r128_update_ring_snapshot( dev_priv );
+		if ( ring->space >= n )
+			return 0;
+		DRM_UDELAY( 1 );
+	}
+
+	/* FIXME: This is being ignored... */
+	DRM_ERROR( "failed!\n" );
+	return DRM_ERR(EBUSY);
+}
+
+static int r128_cce_get_buffers( DRMFILE filp, drm_device_t *dev, drm_dma_t *d )
+{
+	int i;
+	drm_buf_t *buf;
+
+	for ( i = d->granted_count ; i < d->request_count ; i++ ) {
+		buf = r128_freelist_get( dev );
+		if ( !buf ) return DRM_ERR(EAGAIN);
+
+		buf->filp = filp;
+
+		if ( DRM_COPY_TO_USER( &d->request_indices[i], &buf->idx,
+				   sizeof(buf->idx) ) )
+			return DRM_ERR(EFAULT);
+		if ( DRM_COPY_TO_USER( &d->request_sizes[i], &buf->total,
+				   sizeof(buf->total) ) )
+			return DRM_ERR(EFAULT);
+
+		d->granted_count++;
+	}
+	return 0;
+}
+
+int r128_cce_buffers( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_device_dma_t *dma = dev->dma;
+	int ret = 0;
+	drm_dma_t __user *argp = (void __user *)data;
+	drm_dma_t d;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( d, argp, sizeof(d) );
+
+	/* Please don't send us buffers.
+	 */
+	if ( d.send_count != 0 ) {
+		DRM_ERROR( "Process %d trying to send %d buffers via drmDMA\n",
+			   DRM_CURRENTPID, d.send_count );
+		return DRM_ERR(EINVAL);
+	}
+
+	/* We'll send you buffers.
+	 */
+	if ( d.request_count < 0 || d.request_count > dma->buf_count ) {
+		DRM_ERROR( "Process %d trying to get %d buffers (of %d max)\n",
+			   DRM_CURRENTPID, d.request_count, dma->buf_count );
+		return DRM_ERR(EINVAL);
+	}
+
+	d.granted_count = 0;
+
+	if ( d.request_count ) {
+		ret = r128_cce_get_buffers( filp, dev, &d );
+	}
+
+	DRM_COPY_TO_USER_IOCTL(argp, d, sizeof(d) );
+
+	return ret;
+}
diff --git a/drivers/char/drm/r128_drm.h b/drivers/char/drm/r128_drm.h
new file mode 100644
index 0000000..0cba17d
--- /dev/null
+++ b/drivers/char/drm/r128_drm.h
@@ -0,0 +1,345 @@
+/* r128_drm.h -- Public header for the r128 driver -*- linux-c -*-
+ * Created: Wed Apr  5 19:24:19 2000 by kevin@precisioninsight.com
+ *
+ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Gareth Hughes <gareth@valinux.com>
+ *    Kevin E. Martin <martin@valinux.com>
+ */
+
+#ifndef __R128_DRM_H__
+#define __R128_DRM_H__
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the X server file (r128_sarea.h)
+ */
+#ifndef __R128_SAREA_DEFINES__
+#define __R128_SAREA_DEFINES__
+
+/* What needs to be changed for the current vertex buffer?
+ */
+#define R128_UPLOAD_CONTEXT		0x001
+#define R128_UPLOAD_SETUP		0x002
+#define R128_UPLOAD_TEX0		0x004
+#define R128_UPLOAD_TEX1		0x008
+#define R128_UPLOAD_TEX0IMAGES		0x010
+#define R128_UPLOAD_TEX1IMAGES		0x020
+#define R128_UPLOAD_CORE		0x040
+#define R128_UPLOAD_MASKS		0x080
+#define R128_UPLOAD_WINDOW		0x100
+#define R128_UPLOAD_CLIPRECTS		0x200	/* handled client-side */
+#define R128_REQUIRE_QUIESCENCE		0x400
+#define R128_UPLOAD_ALL			0x7ff
+
+#define R128_FRONT			0x1
+#define R128_BACK			0x2
+#define R128_DEPTH			0x4
+
+/* Primitive types
+ */
+#define R128_POINTS			0x1
+#define R128_LINES			0x2
+#define R128_LINE_STRIP			0x3
+#define R128_TRIANGLES			0x4
+#define R128_TRIANGLE_FAN		0x5
+#define R128_TRIANGLE_STRIP		0x6
+
+/* Vertex/indirect buffer size
+ */
+#define R128_BUFFER_SIZE		16384
+
+/* Byte offsets for indirect buffer data
+ */
+#define R128_INDEX_PRIM_OFFSET		20
+#define R128_HOSTDATA_BLIT_OFFSET	32
+
+/* Keep these small for testing.
+ */
+#define R128_NR_SAREA_CLIPRECTS		12
+
+/* There are 2 heaps (local/AGP).  Each region within a heap is a
+ *  minimum of 64k, and there are at most 64 of them per heap.
+ */
+#define R128_LOCAL_TEX_HEAP		0
+#define R128_AGP_TEX_HEAP		1
+#define R128_NR_TEX_HEAPS		2
+#define R128_NR_TEX_REGIONS		64
+#define R128_LOG_TEX_GRANULARITY	16
+
+#define R128_NR_CONTEXT_REGS		12
+
+#define R128_MAX_TEXTURE_LEVELS		11
+#define R128_MAX_TEXTURE_UNITS		2
+
+#endif /* __R128_SAREA_DEFINES__ */
+
+typedef struct {
+	/* Context state - can be written in one large chunk */
+	unsigned int dst_pitch_offset_c;
+	unsigned int dp_gui_master_cntl_c;
+	unsigned int sc_top_left_c;
+	unsigned int sc_bottom_right_c;
+	unsigned int z_offset_c;
+	unsigned int z_pitch_c;
+	unsigned int z_sten_cntl_c;
+	unsigned int tex_cntl_c;
+	unsigned int misc_3d_state_cntl_reg;
+	unsigned int texture_clr_cmp_clr_c;
+	unsigned int texture_clr_cmp_msk_c;
+	unsigned int fog_color_c;
+
+	/* Texture state */
+	unsigned int tex_size_pitch_c;
+	unsigned int constant_color_c;
+
+	/* Setup state */
+	unsigned int pm4_vc_fpu_setup;
+	unsigned int setup_cntl;
+
+	/* Mask state */
+	unsigned int dp_write_mask;
+	unsigned int sten_ref_mask_c;
+	unsigned int plane_3d_mask_c;
+
+	/* Window state */
+	unsigned int window_xy_offset;
+
+	/* Core state */
+	unsigned int scale_3d_cntl;
+} drm_r128_context_regs_t;
+
+/* Setup registers for each texture unit
+ */
+typedef struct {
+	unsigned int tex_cntl;
+	unsigned int tex_combine_cntl;
+	unsigned int tex_size_pitch;
+	unsigned int tex_offset[R128_MAX_TEXTURE_LEVELS];
+	unsigned int tex_border_color;
+} drm_r128_texture_regs_t;
+
+
+typedef struct drm_r128_sarea {
+	/* The channel for communication of state information to the kernel
+	 * on firing a vertex buffer.
+	 */
+	drm_r128_context_regs_t context_state;
+	drm_r128_texture_regs_t tex_state[R128_MAX_TEXTURE_UNITS];
+	unsigned int dirty;
+	unsigned int vertsize;
+	unsigned int vc_format;
+
+	/* The current cliprects, or a subset thereof.
+	 */
+	drm_clip_rect_t boxes[R128_NR_SAREA_CLIPRECTS];
+	unsigned int nbox;
+
+	/* Counters for client-side throttling of rendering clients.
+	 */
+	unsigned int last_frame;
+	unsigned int last_dispatch;
+
+	drm_tex_region_t tex_list[R128_NR_TEX_HEAPS][R128_NR_TEX_REGIONS+1];
+	unsigned int tex_age[R128_NR_TEX_HEAPS];
+	int ctx_owner;
+	int pfAllowPageFlip;        /* number of 3d windows (0,1,2 or more) */
+	int pfCurrentPage;	    /* which buffer is being displayed? */
+} drm_r128_sarea_t;
+
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmR128.h)
+ */
+
+/* Rage 128 specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_R128_INIT       0x00
+#define DRM_R128_CCE_START  0x01
+#define DRM_R128_CCE_STOP   0x02
+#define DRM_R128_CCE_RESET  0x03
+#define DRM_R128_CCE_IDLE   0x04
+/* 0x05 not used */
+#define DRM_R128_RESET      0x06
+#define DRM_R128_SWAP       0x07
+#define DRM_R128_CLEAR      0x08
+#define DRM_R128_VERTEX     0x09
+#define DRM_R128_INDICES    0x0a
+#define DRM_R128_BLIT       0x0b
+#define DRM_R128_DEPTH      0x0c
+#define DRM_R128_STIPPLE    0x0d
+/* 0x0e not used */
+#define DRM_R128_INDIRECT   0x0f
+#define DRM_R128_FULLSCREEN 0x10
+#define DRM_R128_CLEAR2     0x11
+#define DRM_R128_GETPARAM   0x12
+#define DRM_R128_FLIP       0x13
+
+#define DRM_IOCTL_R128_INIT       DRM_IOW( DRM_COMMAND_BASE + DRM_R128_INIT, drm_r128_init_t)
+#define DRM_IOCTL_R128_CCE_START  DRM_IO(  DRM_COMMAND_BASE + DRM_R128_CCE_START)
+#define DRM_IOCTL_R128_CCE_STOP   DRM_IOW( DRM_COMMAND_BASE + DRM_R128_CCE_STOP, drm_r128_cce_stop_t)
+#define DRM_IOCTL_R128_CCE_RESET  DRM_IO(  DRM_COMMAND_BASE + DRM_R128_CCE_RESET)
+#define DRM_IOCTL_R128_CCE_IDLE   DRM_IO(  DRM_COMMAND_BASE + DRM_R128_CCE_IDLE)
+/* 0x05 not used */
+#define DRM_IOCTL_R128_RESET      DRM_IO(  DRM_COMMAND_BASE + DRM_R128_RESET)
+#define DRM_IOCTL_R128_SWAP       DRM_IO(  DRM_COMMAND_BASE + DRM_R128_SWAP)
+#define DRM_IOCTL_R128_CLEAR      DRM_IOW( DRM_COMMAND_BASE + DRM_R128_CLEAR, drm_r128_clear_t)
+#define DRM_IOCTL_R128_VERTEX     DRM_IOW( DRM_COMMAND_BASE + DRM_R128_VERTEX, drm_r128_vertex_t)
+#define DRM_IOCTL_R128_INDICES    DRM_IOW( DRM_COMMAND_BASE + DRM_R128_INDICES, drm_r128_indices_t)
+#define DRM_IOCTL_R128_BLIT       DRM_IOW( DRM_COMMAND_BASE + DRM_R128_BLIT, drm_r128_blit_t)
+#define DRM_IOCTL_R128_DEPTH      DRM_IOW( DRM_COMMAND_BASE + DRM_R128_DEPTH, drm_r128_depth_t)
+#define DRM_IOCTL_R128_STIPPLE    DRM_IOW( DRM_COMMAND_BASE + DRM_R128_STIPPLE, drm_r128_stipple_t)
+/* 0x0e not used */
+#define DRM_IOCTL_R128_INDIRECT   DRM_IOWR(DRM_COMMAND_BASE + DRM_R128_INDIRECT, drm_r128_indirect_t)
+#define DRM_IOCTL_R128_FULLSCREEN DRM_IOW( DRM_COMMAND_BASE + DRM_R128_FULLSCREEN, drm_r128_fullscreen_t)
+#define DRM_IOCTL_R128_CLEAR2     DRM_IOW( DRM_COMMAND_BASE + DRM_R128_CLEAR2, drm_r128_clear2_t)
+#define DRM_IOCTL_R128_GETPARAM   DRM_IOW( DRM_COMMAND_BASE + DRM_R128_GETPARAM, drm_r128_getparam_t)
+#define DRM_IOCTL_R128_FLIP       DRM_IO(  DRM_COMMAND_BASE + DRM_R128_FLIP)
+
+typedef struct drm_r128_init {
+	enum {
+		R128_INIT_CCE    = 0x01,
+		R128_CLEANUP_CCE = 0x02
+	} func;
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+	int sarea_priv_offset;
+#else
+	unsigned long sarea_priv_offset;
+#endif
+	int is_pci;
+	int cce_mode;
+	int cce_secure;
+	int ring_size;
+	int usec_timeout;
+
+	unsigned int fb_bpp;
+	unsigned int front_offset, front_pitch;
+	unsigned int back_offset, back_pitch;
+	unsigned int depth_bpp;
+	unsigned int depth_offset, depth_pitch;
+	unsigned int span_offset;
+
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+	unsigned int fb_offset;
+	unsigned int mmio_offset;
+	unsigned int ring_offset;
+	unsigned int ring_rptr_offset;
+	unsigned int buffers_offset;
+	unsigned int agp_textures_offset;
+#else
+	unsigned long fb_offset;
+	unsigned long mmio_offset;
+	unsigned long ring_offset;
+	unsigned long ring_rptr_offset;
+	unsigned long buffers_offset;
+	unsigned long agp_textures_offset;
+#endif
+} drm_r128_init_t;
+
+typedef struct drm_r128_cce_stop {
+	int flush;
+	int idle;
+} drm_r128_cce_stop_t;
+
+typedef struct drm_r128_clear {
+	unsigned int flags;
+#if CONFIG_XFREE86_VERSION < XFREE86_VERSION(4,1,0,0)
+	int x, y, w, h;
+#endif
+	unsigned int clear_color;
+	unsigned int clear_depth;
+#if CONFIG_XFREE86_VERSION >= XFREE86_VERSION(4,1,0,0)
+	unsigned int color_mask;
+	unsigned int depth_mask;
+#endif
+} drm_r128_clear_t;
+
+typedef struct drm_r128_vertex {
+	int prim;
+	int idx;			/* Index of vertex buffer */
+	int count;			/* Number of vertices in buffer */
+	int discard;			/* Client finished with buffer? */
+} drm_r128_vertex_t;
+
+typedef struct drm_r128_indices {
+	int prim;
+	int idx;
+	int start;
+	int end;
+	int discard;			/* Client finished with buffer? */
+} drm_r128_indices_t;
+
+typedef struct drm_r128_blit {
+	int idx;
+	int pitch;
+	int offset;
+	int format;
+	unsigned short x, y;
+	unsigned short width, height;
+} drm_r128_blit_t;
+
+typedef struct drm_r128_depth {
+	enum {
+		R128_WRITE_SPAN		= 0x01,
+		R128_WRITE_PIXELS	= 0x02,
+		R128_READ_SPAN		= 0x03,
+		R128_READ_PIXELS	= 0x04
+	} func;
+	int n;
+	int __user *x;
+	int __user *y;
+	unsigned int __user *buffer;
+	unsigned char __user *mask;
+} drm_r128_depth_t;
+
+typedef struct drm_r128_stipple {
+	unsigned int __user *mask;
+} drm_r128_stipple_t;
+
+typedef struct drm_r128_indirect {
+	int idx;
+	int start;
+	int end;
+	int discard;
+} drm_r128_indirect_t;
+
+typedef struct drm_r128_fullscreen {
+	enum {
+		R128_INIT_FULLSCREEN    = 0x01,
+		R128_CLEANUP_FULLSCREEN = 0x02
+	} func;
+} drm_r128_fullscreen_t;
+
+/* 2.3: An ioctl to get parameters that aren't available to the 3d
+ * client any other way.  
+ */
+#define R128_PARAM_IRQ_NR            1
+
+typedef struct drm_r128_getparam {
+	int param;
+	void __user *value;
+} drm_r128_getparam_t;
+
+#endif
diff --git a/drivers/char/drm/r128_drv.c b/drivers/char/drm/r128_drv.c
new file mode 100644
index 0000000..ced6381
--- /dev/null
+++ b/drivers/char/drm/r128_drv.c
@@ -0,0 +1,122 @@
+/* r128_drv.c -- ATI Rage 128 driver -*- linux-c -*-
+ * Created: Mon Dec 13 09:47:27 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "r128_drm.h"
+#include "r128_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->primary.minor,
+		pci_pretty_name(dev->pdev)
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+	r128_PCI_IDS
+};
+
+extern drm_ioctl_desc_t r128_ioctls[];
+extern int r128_max_ioctl;
+
+static struct drm_driver driver = {
+	.driver_features = DRIVER_USE_AGP | DRIVER_USE_MTRR | DRIVER_PCI_DMA | DRIVER_SG | DRIVER_HAVE_DMA | DRIVER_HAVE_IRQ | DRIVER_IRQ_SHARED | DRIVER_IRQ_VBL,
+	.dev_priv_size = sizeof(drm_r128_buf_priv_t),
+	.prerelease = r128_driver_prerelease,
+	.pretakedown = r128_driver_pretakedown,
+	.vblank_wait = r128_driver_vblank_wait,
+	.irq_preinstall = r128_driver_irq_preinstall,
+	.irq_postinstall = r128_driver_irq_postinstall,
+	.irq_uninstall = r128_driver_irq_uninstall,
+	.irq_handler = r128_driver_irq_handler,
+	.reclaim_buffers = drm_core_reclaim_buffers,
+	.get_map_ofs = drm_core_get_map_ofs,
+	.get_reg_ofs = drm_core_get_reg_ofs,
+	.postinit = postinit,
+	.version = version,
+	.ioctls = r128_ioctls,
+	.dma_ioctl = r128_cce_buffers,
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+	.pci_driver = {
+		.name          = DRIVER_NAME,
+		.id_table      = pciidlist,
+	}
+};
+
+static int __init r128_init(void)
+{
+	driver.num_ioctls = r128_max_ioctl;
+	return drm_init(&driver);
+}
+
+static void __exit r128_exit(void)
+{
+	drm_exit(&driver);
+}
+
+module_init(r128_init);
+module_exit(r128_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/r128_drv.h b/drivers/char/drm/r128_drv.h
new file mode 100644
index 0000000..cf1aa5d
--- /dev/null
+++ b/drivers/char/drm/r128_drv.h
@@ -0,0 +1,521 @@
+/* r128_drv.h -- Private header for r128 driver -*- linux-c -*-
+ * Created: Mon Dec 13 09:51:11 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Kevin E. Martin <martin@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ *    Michel D�zer <daenzerm@student.ethz.ch>
+ */
+
+#ifndef __R128_DRV_H__
+#define __R128_DRV_H__
+
+/* General customization:
+ */
+#define DRIVER_AUTHOR		"Gareth Hughes, VA Linux Systems Inc."
+
+#define DRIVER_NAME		"r128"
+#define DRIVER_DESC		"ATI Rage 128"
+#define DRIVER_DATE		"20030725"
+
+/* Interface history:
+ *
+ * ??  - ??
+ * 2.4 - Add support for ycbcr textures (no new ioctls)
+ * 2.5 - Add FLIP ioctl, disable FULLSCREEN.
+ */
+#define DRIVER_MAJOR		2
+#define DRIVER_MINOR		5
+#define DRIVER_PATCHLEVEL	0
+
+
+#define GET_RING_HEAD(dev_priv)		R128_READ( R128_PM4_BUFFER_DL_RPTR )
+
+typedef struct drm_r128_freelist {
+   	unsigned int age;
+   	drm_buf_t *buf;
+   	struct drm_r128_freelist *next;
+   	struct drm_r128_freelist *prev;
+} drm_r128_freelist_t;
+
+typedef struct drm_r128_ring_buffer {
+	u32 *start;
+	u32 *end;
+	int size;
+	int size_l2qw;
+
+	u32 tail;
+	u32 tail_mask;
+	int space;
+
+	int high_mark;
+} drm_r128_ring_buffer_t;
+
+typedef struct drm_r128_private {
+	drm_r128_ring_buffer_t ring;
+	drm_r128_sarea_t *sarea_priv;
+
+	int cce_mode;
+	int cce_fifo_size;
+	int cce_running;
+
+   	drm_r128_freelist_t *head;
+   	drm_r128_freelist_t *tail;
+
+	int usec_timeout;
+	int is_pci;
+	unsigned long phys_pci_gart;
+	dma_addr_t bus_pci_gart;
+	unsigned long cce_buffers_offset;
+
+	atomic_t idle_count;
+
+	int page_flipping;
+	int current_page;
+	u32 crtc_offset;
+	u32 crtc_offset_cntl;
+
+	u32 color_fmt;
+	unsigned int front_offset;
+	unsigned int front_pitch;
+	unsigned int back_offset;
+	unsigned int back_pitch;
+
+	u32 depth_fmt;
+	unsigned int depth_offset;
+	unsigned int depth_pitch;
+	unsigned int span_offset;
+
+	u32 front_pitch_offset_c;
+	u32 back_pitch_offset_c;
+	u32 depth_pitch_offset_c;
+	u32 span_pitch_offset_c;
+
+	drm_local_map_t *sarea;
+	drm_local_map_t *mmio;
+	drm_local_map_t *cce_ring;
+	drm_local_map_t *ring_rptr;
+	drm_local_map_t *agp_textures;
+} drm_r128_private_t;
+
+typedef struct drm_r128_buf_priv {
+	u32 age;
+	int prim;
+	int discard;
+	int dispatched;
+   	drm_r128_freelist_t *list_entry;
+} drm_r128_buf_priv_t;
+
+				/* r128_cce.c */
+extern int r128_cce_init( DRM_IOCTL_ARGS );
+extern int r128_cce_start( DRM_IOCTL_ARGS );
+extern int r128_cce_stop( DRM_IOCTL_ARGS );
+extern int r128_cce_reset( DRM_IOCTL_ARGS );
+extern int r128_cce_idle( DRM_IOCTL_ARGS );
+extern int r128_engine_reset( DRM_IOCTL_ARGS );
+extern int r128_fullscreen( DRM_IOCTL_ARGS );
+extern int r128_cce_buffers( DRM_IOCTL_ARGS );
+
+extern void r128_freelist_reset( drm_device_t *dev );
+
+extern int r128_wait_ring( drm_r128_private_t *dev_priv, int n );
+
+extern int r128_do_cce_idle( drm_r128_private_t *dev_priv );
+extern int r128_do_cleanup_cce( drm_device_t *dev );
+
+extern int r128_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence);
+
+extern irqreturn_t r128_driver_irq_handler( DRM_IRQ_ARGS );
+extern void r128_driver_irq_preinstall( drm_device_t *dev );
+extern void r128_driver_irq_postinstall( drm_device_t *dev );
+extern void r128_driver_irq_uninstall( drm_device_t *dev );
+extern void r128_driver_pretakedown(drm_device_t *dev);
+extern void r128_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+
+/* Register definitions, register access macros and drmAddMap constants
+ * for Rage 128 kernel driver.
+ */
+
+#define R128_AUX_SC_CNTL		0x1660
+#	define R128_AUX1_SC_EN			(1 << 0)
+#	define R128_AUX1_SC_MODE_OR		(0 << 1)
+#	define R128_AUX1_SC_MODE_NAND		(1 << 1)
+#	define R128_AUX2_SC_EN			(1 << 2)
+#	define R128_AUX2_SC_MODE_OR		(0 << 3)
+#	define R128_AUX2_SC_MODE_NAND		(1 << 3)
+#	define R128_AUX3_SC_EN			(1 << 4)
+#	define R128_AUX3_SC_MODE_OR		(0 << 5)
+#	define R128_AUX3_SC_MODE_NAND		(1 << 5)
+#define R128_AUX1_SC_LEFT		0x1664
+#define R128_AUX1_SC_RIGHT		0x1668
+#define R128_AUX1_SC_TOP		0x166c
+#define R128_AUX1_SC_BOTTOM		0x1670
+#define R128_AUX2_SC_LEFT		0x1674
+#define R128_AUX2_SC_RIGHT		0x1678
+#define R128_AUX2_SC_TOP		0x167c
+#define R128_AUX2_SC_BOTTOM		0x1680
+#define R128_AUX3_SC_LEFT		0x1684
+#define R128_AUX3_SC_RIGHT		0x1688
+#define R128_AUX3_SC_TOP		0x168c
+#define R128_AUX3_SC_BOTTOM		0x1690
+
+#define R128_BRUSH_DATA0		0x1480
+#define R128_BUS_CNTL			0x0030
+#	define R128_BUS_MASTER_DIS		(1 << 6)
+
+#define R128_CLOCK_CNTL_INDEX		0x0008
+#define R128_CLOCK_CNTL_DATA		0x000c
+#	define R128_PLL_WR_EN			(1 << 7)
+#define R128_CONSTANT_COLOR_C		0x1d34
+#define R128_CRTC_OFFSET		0x0224
+#define R128_CRTC_OFFSET_CNTL		0x0228
+#	define R128_CRTC_OFFSET_FLIP_CNTL	(1 << 16)
+
+#define R128_DP_GUI_MASTER_CNTL		0x146c
+#       define R128_GMC_SRC_PITCH_OFFSET_CNTL	(1    <<  0)
+#       define R128_GMC_DST_PITCH_OFFSET_CNTL	(1    <<  1)
+#	define R128_GMC_BRUSH_SOLID_COLOR	(13   <<  4)
+#	define R128_GMC_BRUSH_NONE		(15   <<  4)
+#	define R128_GMC_DST_16BPP		(4    <<  8)
+#	define R128_GMC_DST_24BPP		(5    <<  8)
+#	define R128_GMC_DST_32BPP		(6    <<  8)
+#       define R128_GMC_DST_DATATYPE_SHIFT	8
+#	define R128_GMC_SRC_DATATYPE_COLOR	(3    << 12)
+#	define R128_DP_SRC_SOURCE_MEMORY	(2    << 24)
+#	define R128_DP_SRC_SOURCE_HOST_DATA	(3    << 24)
+#	define R128_GMC_CLR_CMP_CNTL_DIS	(1    << 28)
+#	define R128_GMC_AUX_CLIP_DIS		(1    << 29)
+#	define R128_GMC_WR_MSK_DIS		(1    << 30)
+#	define R128_ROP3_S			0x00cc0000
+#	define R128_ROP3_P			0x00f00000
+#define R128_DP_WRITE_MASK		0x16cc
+#define R128_DST_PITCH_OFFSET_C		0x1c80
+#	define R128_DST_TILE			(1 << 31)
+
+#define R128_GEN_INT_CNTL		0x0040
+#	define R128_CRTC_VBLANK_INT_EN		(1 <<  0)
+#define R128_GEN_INT_STATUS		0x0044
+#	define R128_CRTC_VBLANK_INT		(1 <<  0)
+#	define R128_CRTC_VBLANK_INT_AK		(1 <<  0)
+#define R128_GEN_RESET_CNTL		0x00f0
+#	define R128_SOFT_RESET_GUI		(1 <<  0)
+
+#define R128_GUI_SCRATCH_REG0		0x15e0
+#define R128_GUI_SCRATCH_REG1		0x15e4
+#define R128_GUI_SCRATCH_REG2		0x15e8
+#define R128_GUI_SCRATCH_REG3		0x15ec
+#define R128_GUI_SCRATCH_REG4		0x15f0
+#define R128_GUI_SCRATCH_REG5		0x15f4
+
+#define R128_GUI_STAT			0x1740
+#	define R128_GUI_FIFOCNT_MASK		0x0fff
+#	define R128_GUI_ACTIVE			(1 << 31)
+
+#define R128_MCLK_CNTL			0x000f
+#	define R128_FORCE_GCP			(1 << 16)
+#	define R128_FORCE_PIPE3D_CP		(1 << 17)
+#	define R128_FORCE_RCP			(1 << 18)
+
+#define R128_PC_GUI_CTLSTAT		0x1748
+#define R128_PC_NGUI_CTLSTAT		0x0184
+#	define R128_PC_FLUSH_GUI		(3 << 0)
+#	define R128_PC_RI_GUI			(1 << 2)
+#	define R128_PC_FLUSH_ALL		0x00ff
+#	define R128_PC_BUSY			(1 << 31)
+
+#define R128_PCI_GART_PAGE		0x017c
+#define R128_PRIM_TEX_CNTL_C		0x1cb0
+
+#define R128_SCALE_3D_CNTL		0x1a00
+#define R128_SEC_TEX_CNTL_C		0x1d00
+#define R128_SEC_TEXTURE_BORDER_COLOR_C	0x1d3c
+#define R128_SETUP_CNTL			0x1bc4
+#define R128_STEN_REF_MASK_C		0x1d40
+
+#define R128_TEX_CNTL_C			0x1c9c
+#	define R128_TEX_CACHE_FLUSH		(1 << 23)
+
+#define R128_WAIT_UNTIL			0x1720
+#	define R128_EVENT_CRTC_OFFSET		(1 << 0)
+#define R128_WINDOW_XY_OFFSET		0x1bcc
+
+
+/* CCE registers
+ */
+#define R128_PM4_BUFFER_OFFSET		0x0700
+#define R128_PM4_BUFFER_CNTL		0x0704
+#	define R128_PM4_MASK			(15 << 28)
+#	define R128_PM4_NONPM4			(0  << 28)
+#	define R128_PM4_192PIO			(1  << 28)
+#	define R128_PM4_192BM			(2  << 28)
+#	define R128_PM4_128PIO_64INDBM		(3  << 28)
+#	define R128_PM4_128BM_64INDBM		(4  << 28)
+#	define R128_PM4_64PIO_128INDBM		(5  << 28)
+#	define R128_PM4_64BM_128INDBM		(6  << 28)
+#	define R128_PM4_64PIO_64VCBM_64INDBM	(7  << 28)
+#	define R128_PM4_64BM_64VCBM_64INDBM	(8  << 28)
+#	define R128_PM4_64PIO_64VCPIO_64INDPIO	(15 << 28)
+#	define R128_PM4_BUFFER_CNTL_NOUPDATE	(1  << 27)
+
+#define R128_PM4_BUFFER_WM_CNTL		0x0708
+#	define R128_WMA_SHIFT			0
+#	define R128_WMB_SHIFT			8
+#	define R128_WMC_SHIFT			16
+#	define R128_WB_WM_SHIFT			24
+
+#define R128_PM4_BUFFER_DL_RPTR_ADDR	0x070c
+#define R128_PM4_BUFFER_DL_RPTR		0x0710
+#define R128_PM4_BUFFER_DL_WPTR		0x0714
+#	define R128_PM4_BUFFER_DL_DONE		(1 << 31)
+
+#define R128_PM4_VC_FPU_SETUP		0x071c
+
+#define R128_PM4_IW_INDOFF		0x0738
+#define R128_PM4_IW_INDSIZE		0x073c
+
+#define R128_PM4_STAT			0x07b8
+#	define R128_PM4_FIFOCNT_MASK		0x0fff
+#	define R128_PM4_BUSY			(1 << 16)
+#	define R128_PM4_GUI_ACTIVE		(1 << 31)
+
+#define R128_PM4_MICROCODE_ADDR		0x07d4
+#define R128_PM4_MICROCODE_RADDR	0x07d8
+#define R128_PM4_MICROCODE_DATAH	0x07dc
+#define R128_PM4_MICROCODE_DATAL	0x07e0
+
+#define R128_PM4_BUFFER_ADDR		0x07f0
+#define R128_PM4_MICRO_CNTL		0x07fc
+#	define R128_PM4_MICRO_FREERUN		(1 << 30)
+
+#define R128_PM4_FIFO_DATA_EVEN		0x1000
+#define R128_PM4_FIFO_DATA_ODD		0x1004
+
+
+/* CCE command packets
+ */
+#define R128_CCE_PACKET0		0x00000000
+#define R128_CCE_PACKET1		0x40000000
+#define R128_CCE_PACKET2		0x80000000
+#define R128_CCE_PACKET3		0xC0000000
+#	define R128_CNTL_HOSTDATA_BLT		0x00009400
+#	define R128_CNTL_PAINT_MULTI		0x00009A00
+#	define R128_CNTL_BITBLT_MULTI		0x00009B00
+#	define R128_3D_RNDR_GEN_INDX_PRIM	0x00002300
+
+#define R128_CCE_PACKET_MASK		0xC0000000
+#define R128_CCE_PACKET_COUNT_MASK	0x3fff0000
+#define R128_CCE_PACKET0_REG_MASK	0x000007ff
+#define R128_CCE_PACKET1_REG0_MASK	0x000007ff
+#define R128_CCE_PACKET1_REG1_MASK	0x003ff800
+
+#define R128_CCE_VC_CNTL_PRIM_TYPE_NONE		0x00000000
+#define R128_CCE_VC_CNTL_PRIM_TYPE_POINT	0x00000001
+#define R128_CCE_VC_CNTL_PRIM_TYPE_LINE		0x00000002
+#define R128_CCE_VC_CNTL_PRIM_TYPE_POLY_LINE	0x00000003
+#define R128_CCE_VC_CNTL_PRIM_TYPE_TRI_LIST	0x00000004
+#define R128_CCE_VC_CNTL_PRIM_TYPE_TRI_FAN	0x00000005
+#define R128_CCE_VC_CNTL_PRIM_TYPE_TRI_STRIP	0x00000006
+#define R128_CCE_VC_CNTL_PRIM_TYPE_TRI_TYPE2	0x00000007
+#define R128_CCE_VC_CNTL_PRIM_WALK_IND		0x00000010
+#define R128_CCE_VC_CNTL_PRIM_WALK_LIST		0x00000020
+#define R128_CCE_VC_CNTL_PRIM_WALK_RING		0x00000030
+#define R128_CCE_VC_CNTL_NUM_SHIFT		16
+
+#define R128_DATATYPE_VQ		0
+#define R128_DATATYPE_CI4		1
+#define R128_DATATYPE_CI8		2
+#define R128_DATATYPE_ARGB1555		3
+#define R128_DATATYPE_RGB565		4
+#define R128_DATATYPE_RGB888		5
+#define R128_DATATYPE_ARGB8888		6
+#define R128_DATATYPE_RGB332		7
+#define R128_DATATYPE_Y8		8
+#define R128_DATATYPE_RGB8		9
+#define R128_DATATYPE_CI16		10
+#define R128_DATATYPE_YVYU422		11
+#define R128_DATATYPE_VYUY422		12
+#define R128_DATATYPE_AYUV444		14
+#define R128_DATATYPE_ARGB4444		15
+
+/* Constants */
+#define R128_AGP_OFFSET			0x02000000
+
+#define R128_WATERMARK_L		16
+#define R128_WATERMARK_M		8
+#define R128_WATERMARK_N		8
+#define R128_WATERMARK_K		128
+
+#define R128_MAX_USEC_TIMEOUT		100000	/* 100 ms */
+
+#define R128_LAST_FRAME_REG		R128_GUI_SCRATCH_REG0
+#define R128_LAST_DISPATCH_REG		R128_GUI_SCRATCH_REG1
+#define R128_MAX_VB_AGE			0x7fffffff
+#define R128_MAX_VB_VERTS		(0xffff)
+
+#define R128_RING_HIGH_MARK		128
+
+#define R128_PERFORMANCE_BOXES		0
+
+#define R128_READ(reg)		DRM_READ32(  dev_priv->mmio, (reg) )
+#define R128_WRITE(reg,val)	DRM_WRITE32( dev_priv->mmio, (reg), (val) )
+#define R128_READ8(reg)		DRM_READ8(   dev_priv->mmio, (reg) )
+#define R128_WRITE8(reg,val)	DRM_WRITE8(  dev_priv->mmio, (reg), (val) )
+
+#define R128_WRITE_PLL(addr,val)					\
+do {									\
+	R128_WRITE8(R128_CLOCK_CNTL_INDEX,				\
+		    ((addr) & 0x1f) | R128_PLL_WR_EN);			\
+	R128_WRITE(R128_CLOCK_CNTL_DATA, (val));			\
+} while (0)
+
+
+#define CCE_PACKET0( reg, n )		(R128_CCE_PACKET0 |		\
+					 ((n) << 16) | ((reg) >> 2))
+#define CCE_PACKET1( reg0, reg1 )	(R128_CCE_PACKET1 |		\
+					 (((reg1) >> 2) << 11) | ((reg0) >> 2))
+#define CCE_PACKET2()			(R128_CCE_PACKET2)
+#define CCE_PACKET3( pkt, n )		(R128_CCE_PACKET3 |		\
+					 (pkt) | ((n) << 16))
+
+
+static __inline__ void
+r128_update_ring_snapshot( drm_r128_private_t *dev_priv )
+{
+	drm_r128_ring_buffer_t *ring = &dev_priv->ring;
+	ring->space = (GET_RING_HEAD( dev_priv ) - ring->tail) * sizeof(u32);
+	if ( ring->space <= 0 )
+		ring->space += ring->size;
+}
+
+/* ================================================================
+ * Misc helper macros
+ */
+
+#define RING_SPACE_TEST_WITH_RETURN( dev_priv )				\
+do {									\
+	drm_r128_ring_buffer_t *ring = &dev_priv->ring; int i;		\
+	if ( ring->space < ring->high_mark ) {				\
+		for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {	\
+			r128_update_ring_snapshot( dev_priv );		\
+			if ( ring->space >= ring->high_mark )		\
+				goto __ring_space_done;			\
+			DRM_UDELAY(1);				\
+		}							\
+		DRM_ERROR( "ring space check failed!\n" );		\
+		return DRM_ERR(EBUSY);				\
+	}								\
+ __ring_space_done:							\
+	;								\
+} while (0)
+
+#define VB_AGE_TEST_WITH_RETURN( dev_priv )				\
+do {									\
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;		\
+	if ( sarea_priv->last_dispatch >= R128_MAX_VB_AGE ) {		\
+		int __ret = r128_do_cce_idle( dev_priv );		\
+		if ( __ret ) return __ret;				\
+		sarea_priv->last_dispatch = 0;				\
+		r128_freelist_reset( dev );				\
+	}								\
+} while (0)
+
+#define R128_WAIT_UNTIL_PAGE_FLIPPED() do {				\
+	OUT_RING( CCE_PACKET0( R128_WAIT_UNTIL, 0 ) );			\
+	OUT_RING( R128_EVENT_CRTC_OFFSET );				\
+} while (0)
+
+
+/* ================================================================
+ * Ring control
+ */
+
+#define R128_VERBOSE	0
+
+#define RING_LOCALS							\
+	int write, _nr; unsigned int tail_mask; volatile u32 *ring;
+
+#define BEGIN_RING( n ) do {						\
+	if ( R128_VERBOSE ) {						\
+		DRM_INFO( "BEGIN_RING( %d ) in %s\n",			\
+			   (n), __FUNCTION__ );				\
+	}								\
+	if ( dev_priv->ring.space <= (n) * sizeof(u32) ) {		\
+		COMMIT_RING();						\
+		r128_wait_ring( dev_priv, (n) * sizeof(u32) );		\
+	}								\
+	_nr = n; dev_priv->ring.space -= (n) * sizeof(u32);		\
+	ring = dev_priv->ring.start;					\
+	write = dev_priv->ring.tail;					\
+	tail_mask = dev_priv->ring.tail_mask;				\
+} while (0)
+
+/* You can set this to zero if you want.  If the card locks up, you'll
+ * need to keep this set.  It works around a bug in early revs of the
+ * Rage 128 chipset, where the CCE would read 32 dwords past the end of
+ * the ring buffer before wrapping around.
+ */
+#define R128_BROKEN_CCE	1
+
+#define ADVANCE_RING() do {						\
+	if ( R128_VERBOSE ) {						\
+		DRM_INFO( "ADVANCE_RING() wr=0x%06x tail=0x%06x\n",	\
+			  write, dev_priv->ring.tail );			\
+	}								\
+	if ( R128_BROKEN_CCE && write < 32 ) {				\
+		memcpy( dev_priv->ring.end,				\
+			dev_priv->ring.start,				\
+			write * sizeof(u32) );				\
+	}								\
+	if (((dev_priv->ring.tail + _nr) & tail_mask) != write) {	\
+		DRM_ERROR( 						\
+			"ADVANCE_RING(): mismatch: nr: %x write: %x line: %d\n",	\
+			((dev_priv->ring.tail + _nr) & tail_mask),	\
+			write, __LINE__);				\
+	} else								\
+		dev_priv->ring.tail = write;				\
+} while (0)
+
+#define COMMIT_RING() do {						\
+	if ( R128_VERBOSE ) {						\
+		DRM_INFO( "COMMIT_RING() tail=0x%06x\n",		\
+			dev_priv->ring.tail );				\
+	}								\
+	DRM_MEMORYBARRIER();						\
+	R128_WRITE( R128_PM4_BUFFER_DL_WPTR, dev_priv->ring.tail );	\
+	R128_READ( R128_PM4_BUFFER_DL_WPTR );				\
+} while (0)
+
+#define OUT_RING( x ) do {						\
+	if ( R128_VERBOSE ) {						\
+		DRM_INFO( "   OUT_RING( 0x%08x ) at 0x%x\n",		\
+			   (unsigned int)(x), write );			\
+	}								\
+	ring[write++] = cpu_to_le32( x );				\
+	write &= tail_mask;						\
+} while (0)
+
+#endif /* __R128_DRV_H__ */
diff --git a/drivers/char/drm/r128_irq.c b/drivers/char/drm/r128_irq.c
new file mode 100644
index 0000000..643a307
--- /dev/null
+++ b/drivers/char/drm/r128_irq.c
@@ -0,0 +1,102 @@
+/* r128_irq.c -- IRQ handling for radeon -*- linux-c -*-
+ *
+ * Copyright (C) The Weather Channel, Inc.  2002.  All Rights Reserved.
+ * 
+ * The Weather Channel (TM) funded Tungsten Graphics to develop the
+ * initial release of the Radeon 8500 driver under the XFree86 license.
+ * This notice must be preserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ *    Eric Anholt <anholt@FreeBSD.org>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "r128_drm.h"
+#include "r128_drv.h"
+
+irqreturn_t r128_driver_irq_handler( DRM_IRQ_ARGS )
+{
+	drm_device_t *dev = (drm_device_t *) arg;
+	drm_r128_private_t *dev_priv = 
+	   (drm_r128_private_t *)dev->dev_private;
+	int status;
+
+	status = R128_READ( R128_GEN_INT_STATUS );
+	
+	/* VBLANK interrupt */
+	if ( status & R128_CRTC_VBLANK_INT ) {
+		R128_WRITE( R128_GEN_INT_STATUS, R128_CRTC_VBLANK_INT_AK );
+		atomic_inc(&dev->vbl_received);
+		DRM_WAKEUP(&dev->vbl_queue);
+		drm_vbl_send_signals( dev );
+		return IRQ_HANDLED;
+	}
+	return IRQ_NONE;
+}
+
+int r128_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence)
+{
+	unsigned int cur_vblank;
+	int ret = 0;
+
+	/* Assume that the user has missed the current sequence number
+	 * by about a day rather than she wants to wait for years
+	 * using vertical blanks... 
+	 */
+	DRM_WAIT_ON( ret, dev->vbl_queue, 3*DRM_HZ, 
+		     ( ( ( cur_vblank = atomic_read(&dev->vbl_received ) )
+			 - *sequence ) <= (1<<23) ) );
+
+	*sequence = cur_vblank;
+
+	return ret;
+}
+
+void r128_driver_irq_preinstall( drm_device_t *dev ) {
+  	drm_r128_private_t *dev_priv = 
+	   (drm_r128_private_t *)dev->dev_private;
+
+	/* Disable *all* interrupts */
+      	R128_WRITE( R128_GEN_INT_CNTL, 0 );
+	/* Clear vblank bit if it's already high */
+   	R128_WRITE( R128_GEN_INT_STATUS, R128_CRTC_VBLANK_INT_AK );
+}
+
+void r128_driver_irq_postinstall( drm_device_t *dev ) {
+  	drm_r128_private_t *dev_priv = 
+	   (drm_r128_private_t *)dev->dev_private;
+
+	/* Turn on VBL interrupt */
+   	R128_WRITE( R128_GEN_INT_CNTL, R128_CRTC_VBLANK_INT_EN );
+}
+
+void r128_driver_irq_uninstall( drm_device_t *dev ) {
+  	drm_r128_private_t *dev_priv = 
+	   (drm_r128_private_t *)dev->dev_private;
+	if (!dev_priv)
+		return;
+
+	/* Disable *all* interrupts */
+	R128_WRITE( R128_GEN_INT_CNTL, 0 );
+}
diff --git a/drivers/char/drm/r128_state.c b/drivers/char/drm/r128_state.c
new file mode 100644
index 0000000..38b3cbd
--- /dev/null
+++ b/drivers/char/drm/r128_state.c
@@ -0,0 +1,1732 @@
+/* r128_state.c -- State support for r128 -*- linux-c -*-
+ * Created: Thu Jan 27 02:53:43 2000 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "r128_drm.h"
+#include "r128_drv.h"
+
+
+/* ================================================================
+ * CCE hardware state programming functions
+ */
+
+static void r128_emit_clip_rects( drm_r128_private_t *dev_priv,
+				  drm_clip_rect_t *boxes, int count )
+{
+	u32 aux_sc_cntl = 0x00000000;
+	RING_LOCALS;
+	DRM_DEBUG( "    %s\n", __FUNCTION__ );
+
+	BEGIN_RING( (count < 3? count: 3) * 5 + 2 );
+
+	if ( count >= 1 ) {
+		OUT_RING( CCE_PACKET0( R128_AUX1_SC_LEFT, 3 ) );
+		OUT_RING( boxes[0].x1 );
+		OUT_RING( boxes[0].x2 - 1 );
+		OUT_RING( boxes[0].y1 );
+		OUT_RING( boxes[0].y2 - 1 );
+
+		aux_sc_cntl |= (R128_AUX1_SC_EN | R128_AUX1_SC_MODE_OR);
+	}
+	if ( count >= 2 ) {
+		OUT_RING( CCE_PACKET0( R128_AUX2_SC_LEFT, 3 ) );
+		OUT_RING( boxes[1].x1 );
+		OUT_RING( boxes[1].x2 - 1 );
+		OUT_RING( boxes[1].y1 );
+		OUT_RING( boxes[1].y2 - 1 );
+
+		aux_sc_cntl |= (R128_AUX2_SC_EN | R128_AUX2_SC_MODE_OR);
+	}
+	if ( count >= 3 ) {
+		OUT_RING( CCE_PACKET0( R128_AUX3_SC_LEFT, 3 ) );
+		OUT_RING( boxes[2].x1 );
+		OUT_RING( boxes[2].x2 - 1 );
+		OUT_RING( boxes[2].y1 );
+		OUT_RING( boxes[2].y2 - 1 );
+
+		aux_sc_cntl |= (R128_AUX3_SC_EN | R128_AUX3_SC_MODE_OR);
+	}
+
+	OUT_RING( CCE_PACKET0( R128_AUX_SC_CNTL, 0 ) );
+	OUT_RING( aux_sc_cntl );
+
+	ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_core( drm_r128_private_t *dev_priv )
+{
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+	RING_LOCALS;
+	DRM_DEBUG( "    %s\n", __FUNCTION__ );
+
+	BEGIN_RING( 2 );
+
+	OUT_RING( CCE_PACKET0( R128_SCALE_3D_CNTL, 0 ) );
+	OUT_RING( ctx->scale_3d_cntl );
+
+	ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_context( drm_r128_private_t *dev_priv )
+{
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+	RING_LOCALS;
+	DRM_DEBUG( "    %s\n", __FUNCTION__ );
+
+	BEGIN_RING( 13 );
+
+	OUT_RING( CCE_PACKET0( R128_DST_PITCH_OFFSET_C, 11 ) );
+	OUT_RING( ctx->dst_pitch_offset_c );
+	OUT_RING( ctx->dp_gui_master_cntl_c );
+	OUT_RING( ctx->sc_top_left_c );
+	OUT_RING( ctx->sc_bottom_right_c );
+	OUT_RING( ctx->z_offset_c );
+	OUT_RING( ctx->z_pitch_c );
+	OUT_RING( ctx->z_sten_cntl_c );
+	OUT_RING( ctx->tex_cntl_c );
+	OUT_RING( ctx->misc_3d_state_cntl_reg );
+	OUT_RING( ctx->texture_clr_cmp_clr_c );
+	OUT_RING( ctx->texture_clr_cmp_msk_c );
+	OUT_RING( ctx->fog_color_c );
+
+	ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_setup( drm_r128_private_t *dev_priv )
+{
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+	RING_LOCALS;
+	DRM_DEBUG( "    %s\n", __FUNCTION__ );
+
+	BEGIN_RING( 3 );
+
+	OUT_RING( CCE_PACKET1( R128_SETUP_CNTL, R128_PM4_VC_FPU_SETUP ) );
+	OUT_RING( ctx->setup_cntl );
+	OUT_RING( ctx->pm4_vc_fpu_setup );
+
+	ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_masks( drm_r128_private_t *dev_priv )
+{
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+	RING_LOCALS;
+	DRM_DEBUG( "    %s\n", __FUNCTION__ );
+
+	BEGIN_RING( 5 );
+
+	OUT_RING( CCE_PACKET0( R128_DP_WRITE_MASK, 0 ) );
+	OUT_RING( ctx->dp_write_mask );
+
+	OUT_RING( CCE_PACKET0( R128_STEN_REF_MASK_C, 1 ) );
+	OUT_RING( ctx->sten_ref_mask_c );
+	OUT_RING( ctx->plane_3d_mask_c );
+
+	ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_window( drm_r128_private_t *dev_priv )
+{
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+	RING_LOCALS;
+	DRM_DEBUG( "    %s\n", __FUNCTION__ );
+
+	BEGIN_RING( 2 );
+
+	OUT_RING( CCE_PACKET0( R128_WINDOW_XY_OFFSET, 0 ) );
+	OUT_RING( ctx->window_xy_offset );
+
+	ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_tex0( drm_r128_private_t *dev_priv )
+{
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_r128_context_regs_t *ctx = &sarea_priv->context_state;
+	drm_r128_texture_regs_t *tex = &sarea_priv->tex_state[0];
+	int i;
+	RING_LOCALS;
+	DRM_DEBUG( "    %s\n", __FUNCTION__ );
+
+	BEGIN_RING( 7 + R128_MAX_TEXTURE_LEVELS );
+
+	OUT_RING( CCE_PACKET0( R128_PRIM_TEX_CNTL_C,
+			       2 + R128_MAX_TEXTURE_LEVELS ) );
+	OUT_RING( tex->tex_cntl );
+	OUT_RING( tex->tex_combine_cntl );
+	OUT_RING( ctx->tex_size_pitch_c );
+	for ( i = 0 ; i < R128_MAX_TEXTURE_LEVELS ; i++ ) {
+		OUT_RING( tex->tex_offset[i] );
+	}
+
+	OUT_RING( CCE_PACKET0( R128_CONSTANT_COLOR_C, 1 ) );
+	OUT_RING( ctx->constant_color_c );
+	OUT_RING( tex->tex_border_color );
+
+	ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_tex1( drm_r128_private_t *dev_priv )
+{
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_r128_texture_regs_t *tex = &sarea_priv->tex_state[1];
+	int i;
+	RING_LOCALS;
+	DRM_DEBUG( "    %s\n", __FUNCTION__ );
+
+	BEGIN_RING( 5 + R128_MAX_TEXTURE_LEVELS );
+
+	OUT_RING( CCE_PACKET0( R128_SEC_TEX_CNTL_C,
+			       1 + R128_MAX_TEXTURE_LEVELS ) );
+	OUT_RING( tex->tex_cntl );
+	OUT_RING( tex->tex_combine_cntl );
+	for ( i = 0 ; i < R128_MAX_TEXTURE_LEVELS ; i++ ) {
+		OUT_RING( tex->tex_offset[i] );
+	}
+
+	OUT_RING( CCE_PACKET0( R128_SEC_TEXTURE_BORDER_COLOR_C, 0 ) );
+	OUT_RING( tex->tex_border_color );
+
+	ADVANCE_RING();
+}
+
+static __inline__ void r128_emit_state( drm_r128_private_t *dev_priv )
+{
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	unsigned int dirty = sarea_priv->dirty;
+
+	DRM_DEBUG( "%s: dirty=0x%08x\n", __FUNCTION__, dirty );
+
+	if ( dirty & R128_UPLOAD_CORE ) {
+		r128_emit_core( dev_priv );
+		sarea_priv->dirty &= ~R128_UPLOAD_CORE;
+	}
+
+	if ( dirty & R128_UPLOAD_CONTEXT ) {
+		r128_emit_context( dev_priv );
+		sarea_priv->dirty &= ~R128_UPLOAD_CONTEXT;
+	}
+
+	if ( dirty & R128_UPLOAD_SETUP ) {
+		r128_emit_setup( dev_priv );
+		sarea_priv->dirty &= ~R128_UPLOAD_SETUP;
+	}
+
+	if ( dirty & R128_UPLOAD_MASKS ) {
+		r128_emit_masks( dev_priv );
+		sarea_priv->dirty &= ~R128_UPLOAD_MASKS;
+	}
+
+	if ( dirty & R128_UPLOAD_WINDOW ) {
+		r128_emit_window( dev_priv );
+		sarea_priv->dirty &= ~R128_UPLOAD_WINDOW;
+	}
+
+	if ( dirty & R128_UPLOAD_TEX0 ) {
+		r128_emit_tex0( dev_priv );
+		sarea_priv->dirty &= ~R128_UPLOAD_TEX0;
+	}
+
+	if ( dirty & R128_UPLOAD_TEX1 ) {
+		r128_emit_tex1( dev_priv );
+		sarea_priv->dirty &= ~R128_UPLOAD_TEX1;
+	}
+
+	/* Turn off the texture cache flushing */
+	sarea_priv->context_state.tex_cntl_c &= ~R128_TEX_CACHE_FLUSH;
+
+	sarea_priv->dirty &= ~R128_REQUIRE_QUIESCENCE;
+}
+
+
+#if R128_PERFORMANCE_BOXES
+/* ================================================================
+ * Performance monitoring functions
+ */
+
+static void r128_clear_box( drm_r128_private_t *dev_priv,
+			    int x, int y, int w, int h,
+			    int r, int g, int b )
+{
+	u32 pitch, offset;
+	u32 fb_bpp, color;
+	RING_LOCALS;
+
+	switch ( dev_priv->fb_bpp ) {
+	case 16:
+		fb_bpp = R128_GMC_DST_16BPP;
+		color = (((r & 0xf8) << 8) |
+			 ((g & 0xfc) << 3) |
+			 ((b & 0xf8) >> 3));
+		break;
+	case 24:
+		fb_bpp = R128_GMC_DST_24BPP;
+		color = ((r << 16) | (g << 8) | b);
+		break;
+	case 32:
+		fb_bpp = R128_GMC_DST_32BPP;
+		color = (((0xff) << 24) | (r << 16) | (g <<  8) | b);
+		break;
+	default:
+		return;
+	}
+
+	offset = dev_priv->back_offset;
+	pitch = dev_priv->back_pitch >> 3;
+
+	BEGIN_RING( 6 );
+
+	OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+	OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+		  R128_GMC_BRUSH_SOLID_COLOR |
+		  fb_bpp |
+		  R128_GMC_SRC_DATATYPE_COLOR |
+		  R128_ROP3_P |
+		  R128_GMC_CLR_CMP_CNTL_DIS |
+		  R128_GMC_AUX_CLIP_DIS );
+
+	OUT_RING( (pitch << 21) | (offset >> 5) );
+	OUT_RING( color );
+
+	OUT_RING( (x << 16) | y );
+	OUT_RING( (w << 16) | h );
+
+	ADVANCE_RING();
+}
+
+static void r128_cce_performance_boxes( drm_r128_private_t *dev_priv )
+{
+	if ( atomic_read( &dev_priv->idle_count ) == 0 ) {
+		r128_clear_box( dev_priv, 64, 4, 8, 8, 0, 255, 0 );
+	} else {
+		atomic_set( &dev_priv->idle_count, 0 );
+	}
+}
+
+#endif
+
+
+/* ================================================================
+ * CCE command dispatch functions
+ */
+
+static void r128_print_dirty( const char *msg, unsigned int flags )
+{
+	DRM_INFO( "%s: (0x%x) %s%s%s%s%s%s%s%s%s\n",
+		  msg,
+		  flags,
+		  (flags & R128_UPLOAD_CORE)        ? "core, " : "",
+		  (flags & R128_UPLOAD_CONTEXT)     ? "context, " : "",
+		  (flags & R128_UPLOAD_SETUP)       ? "setup, " : "",
+		  (flags & R128_UPLOAD_TEX0)        ? "tex0, " : "",
+		  (flags & R128_UPLOAD_TEX1)        ? "tex1, " : "",
+		  (flags & R128_UPLOAD_MASKS)       ? "masks, " : "",
+		  (flags & R128_UPLOAD_WINDOW)      ? "window, " : "",
+		  (flags & R128_UPLOAD_CLIPRECTS)   ? "cliprects, " : "",
+		  (flags & R128_REQUIRE_QUIESCENCE) ? "quiescence, " : "" );
+}
+
+static void r128_cce_dispatch_clear( drm_device_t *dev,
+				     drm_r128_clear_t *clear )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int nbox = sarea_priv->nbox;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	unsigned int flags = clear->flags;
+	int i;
+	RING_LOCALS;
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	if ( dev_priv->page_flipping && dev_priv->current_page == 1 ) {
+		unsigned int tmp = flags;
+
+		flags &= ~(R128_FRONT | R128_BACK);
+		if ( tmp & R128_FRONT ) flags |= R128_BACK;
+		if ( tmp & R128_BACK )  flags |= R128_FRONT;
+	}
+
+	for ( i = 0 ; i < nbox ; i++ ) {
+		int x = pbox[i].x1;
+		int y = pbox[i].y1;
+		int w = pbox[i].x2 - x;
+		int h = pbox[i].y2 - y;
+
+		DRM_DEBUG( "dispatch clear %d,%d-%d,%d flags 0x%x\n",
+			   pbox[i].x1, pbox[i].y1, pbox[i].x2,
+			   pbox[i].y2, flags );
+
+		if ( flags & (R128_FRONT | R128_BACK) ) {
+			BEGIN_RING( 2 );
+
+			OUT_RING( CCE_PACKET0( R128_DP_WRITE_MASK, 0 ) );
+			OUT_RING( clear->color_mask );
+
+			ADVANCE_RING();
+		}
+
+		if ( flags & R128_FRONT ) {
+			BEGIN_RING( 6 );
+
+			OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+			OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+				  R128_GMC_BRUSH_SOLID_COLOR |
+				  (dev_priv->color_fmt << 8) |
+				  R128_GMC_SRC_DATATYPE_COLOR |
+				  R128_ROP3_P |
+				  R128_GMC_CLR_CMP_CNTL_DIS |
+				  R128_GMC_AUX_CLIP_DIS );
+
+			OUT_RING( dev_priv->front_pitch_offset_c );
+			OUT_RING( clear->clear_color );
+
+			OUT_RING( (x << 16) | y );
+			OUT_RING( (w << 16) | h );
+
+			ADVANCE_RING();
+		}
+
+		if ( flags & R128_BACK ) {
+			BEGIN_RING( 6 );
+
+			OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+			OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+				  R128_GMC_BRUSH_SOLID_COLOR |
+				  (dev_priv->color_fmt << 8) |
+				  R128_GMC_SRC_DATATYPE_COLOR |
+				  R128_ROP3_P |
+				  R128_GMC_CLR_CMP_CNTL_DIS |
+				  R128_GMC_AUX_CLIP_DIS );
+
+			OUT_RING( dev_priv->back_pitch_offset_c );
+			OUT_RING( clear->clear_color );
+
+			OUT_RING( (x << 16) | y );
+			OUT_RING( (w << 16) | h );
+
+			ADVANCE_RING();
+		}
+
+		if ( flags & R128_DEPTH ) {
+			BEGIN_RING( 6 );
+
+			OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+			OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+				  R128_GMC_BRUSH_SOLID_COLOR |
+				  (dev_priv->depth_fmt << 8) |
+				  R128_GMC_SRC_DATATYPE_COLOR |
+				  R128_ROP3_P |
+				  R128_GMC_CLR_CMP_CNTL_DIS |
+				  R128_GMC_AUX_CLIP_DIS |
+				  R128_GMC_WR_MSK_DIS );
+
+			OUT_RING( dev_priv->depth_pitch_offset_c );
+			OUT_RING( clear->clear_depth );
+
+			OUT_RING( (x << 16) | y );
+			OUT_RING( (w << 16) | h );
+
+			ADVANCE_RING();
+		}
+	}
+}
+
+static void r128_cce_dispatch_swap( drm_device_t *dev )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int nbox = sarea_priv->nbox;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int i;
+	RING_LOCALS;
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+#if R128_PERFORMANCE_BOXES
+	/* Do some trivial performance monitoring...
+	 */
+	r128_cce_performance_boxes( dev_priv );
+#endif
+
+	for ( i = 0 ; i < nbox ; i++ ) {
+		int x = pbox[i].x1;
+		int y = pbox[i].y1;
+		int w = pbox[i].x2 - x;
+		int h = pbox[i].y2 - y;
+
+		BEGIN_RING( 7 );
+
+		OUT_RING( CCE_PACKET3( R128_CNTL_BITBLT_MULTI, 5 ) );
+		OUT_RING( R128_GMC_SRC_PITCH_OFFSET_CNTL |
+			  R128_GMC_DST_PITCH_OFFSET_CNTL |
+			  R128_GMC_BRUSH_NONE |
+			  (dev_priv->color_fmt << 8) |
+			  R128_GMC_SRC_DATATYPE_COLOR |
+			  R128_ROP3_S |
+			  R128_DP_SRC_SOURCE_MEMORY |
+			  R128_GMC_CLR_CMP_CNTL_DIS |
+			  R128_GMC_AUX_CLIP_DIS |
+			  R128_GMC_WR_MSK_DIS );
+
+		/* Make this work even if front & back are flipped:
+		 */
+		if (dev_priv->current_page == 0) {
+			OUT_RING( dev_priv->back_pitch_offset_c );
+			OUT_RING( dev_priv->front_pitch_offset_c );
+		} 
+		else {
+			OUT_RING( dev_priv->front_pitch_offset_c );
+			OUT_RING( dev_priv->back_pitch_offset_c );
+		}
+
+		OUT_RING( (x << 16) | y );
+		OUT_RING( (x << 16) | y );
+		OUT_RING( (w << 16) | h );
+
+		ADVANCE_RING();
+	}
+
+	/* Increment the frame counter.  The client-side 3D driver must
+	 * throttle the framerate by waiting for this value before
+	 * performing the swapbuffer ioctl.
+	 */
+	dev_priv->sarea_priv->last_frame++;
+
+	BEGIN_RING( 2 );
+
+	OUT_RING( CCE_PACKET0( R128_LAST_FRAME_REG, 0 ) );
+	OUT_RING( dev_priv->sarea_priv->last_frame );
+
+	ADVANCE_RING();
+}
+
+static void r128_cce_dispatch_flip( drm_device_t *dev )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	RING_LOCALS;
+	DRM_DEBUG("%s: page=%d pfCurrentPage=%d\n", 
+		__FUNCTION__,
+		dev_priv->current_page,
+		dev_priv->sarea_priv->pfCurrentPage);
+
+#if R128_PERFORMANCE_BOXES
+	/* Do some trivial performance monitoring...
+	 */
+	r128_cce_performance_boxes( dev_priv );
+#endif
+
+	BEGIN_RING( 4 );
+
+	R128_WAIT_UNTIL_PAGE_FLIPPED();
+	OUT_RING( CCE_PACKET0( R128_CRTC_OFFSET, 0 ) );
+
+	if ( dev_priv->current_page == 0 ) {
+		OUT_RING( dev_priv->back_offset );
+	} else {
+		OUT_RING( dev_priv->front_offset );
+	}
+
+	ADVANCE_RING();
+
+	/* Increment the frame counter.  The client-side 3D driver must
+	 * throttle the framerate by waiting for this value before
+	 * performing the swapbuffer ioctl.
+	 */
+	dev_priv->sarea_priv->last_frame++;
+	dev_priv->sarea_priv->pfCurrentPage = dev_priv->current_page =
+					      1 - dev_priv->current_page;
+
+	BEGIN_RING( 2 );
+
+	OUT_RING( CCE_PACKET0( R128_LAST_FRAME_REG, 0 ) );
+	OUT_RING( dev_priv->sarea_priv->last_frame );
+
+	ADVANCE_RING();
+}
+
+static void r128_cce_dispatch_vertex( drm_device_t *dev,
+				      drm_buf_t *buf )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_buf_priv_t *buf_priv = buf->dev_private;
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int format = sarea_priv->vc_format;
+	int offset = buf->bus_address;
+	int size = buf->used;
+	int prim = buf_priv->prim;
+	int i = 0;
+	RING_LOCALS;
+	DRM_DEBUG( "buf=%d nbox=%d\n", buf->idx, sarea_priv->nbox );
+
+	if ( 0 )
+		r128_print_dirty( "dispatch_vertex", sarea_priv->dirty );
+
+	if ( buf->used ) {
+		buf_priv->dispatched = 1;
+
+		if ( sarea_priv->dirty & ~R128_UPLOAD_CLIPRECTS ) {
+			r128_emit_state( dev_priv );
+		}
+
+		do {
+			/* Emit the next set of up to three cliprects */
+			if ( i < sarea_priv->nbox ) {
+				r128_emit_clip_rects( dev_priv,
+						      &sarea_priv->boxes[i],
+						      sarea_priv->nbox - i );
+			}
+
+			/* Emit the vertex buffer rendering commands */
+			BEGIN_RING( 5 );
+
+			OUT_RING( CCE_PACKET3( R128_3D_RNDR_GEN_INDX_PRIM, 3 ) );
+			OUT_RING( offset );
+			OUT_RING( size );
+			OUT_RING( format );
+			OUT_RING( prim | R128_CCE_VC_CNTL_PRIM_WALK_LIST |
+				  (size << R128_CCE_VC_CNTL_NUM_SHIFT) );
+
+			ADVANCE_RING();
+
+			i += 3;
+		} while ( i < sarea_priv->nbox );
+	}
+
+	if ( buf_priv->discard ) {
+		buf_priv->age = dev_priv->sarea_priv->last_dispatch;
+
+		/* Emit the vertex buffer age */
+		BEGIN_RING( 2 );
+
+		OUT_RING( CCE_PACKET0( R128_LAST_DISPATCH_REG, 0 ) );
+		OUT_RING( buf_priv->age );
+
+		ADVANCE_RING();
+
+		buf->pending = 1;
+		buf->used = 0;
+		/* FIXME: Check dispatched field */
+		buf_priv->dispatched = 0;
+	}
+
+	dev_priv->sarea_priv->last_dispatch++;
+
+	sarea_priv->dirty &= ~R128_UPLOAD_CLIPRECTS;
+	sarea_priv->nbox = 0;
+}
+
+static void r128_cce_dispatch_indirect( drm_device_t *dev,
+					drm_buf_t *buf,
+					int start, int end )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_buf_priv_t *buf_priv = buf->dev_private;
+	RING_LOCALS;
+	DRM_DEBUG( "indirect: buf=%d s=0x%x e=0x%x\n",
+		   buf->idx, start, end );
+
+	if ( start != end ) {
+		int offset = buf->bus_address + start;
+		int dwords = (end - start + 3) / sizeof(u32);
+
+		/* Indirect buffer data must be an even number of
+		 * dwords, so if we've been given an odd number we must
+		 * pad the data with a Type-2 CCE packet.
+		 */
+		if ( dwords & 1 ) {
+			u32 *data = (u32 *)
+				((char *)dev->agp_buffer_map->handle
+				 + buf->offset + start);
+			data[dwords++] = cpu_to_le32( R128_CCE_PACKET2 );
+		}
+
+		buf_priv->dispatched = 1;
+
+		/* Fire off the indirect buffer */
+		BEGIN_RING( 3 );
+
+		OUT_RING( CCE_PACKET0( R128_PM4_IW_INDOFF, 1 ) );
+		OUT_RING( offset );
+		OUT_RING( dwords );
+
+		ADVANCE_RING();
+	}
+
+	if ( buf_priv->discard ) {
+		buf_priv->age = dev_priv->sarea_priv->last_dispatch;
+
+		/* Emit the indirect buffer age */
+		BEGIN_RING( 2 );
+
+		OUT_RING( CCE_PACKET0( R128_LAST_DISPATCH_REG, 0 ) );
+		OUT_RING( buf_priv->age );
+
+		ADVANCE_RING();
+
+		buf->pending = 1;
+		buf->used = 0;
+		/* FIXME: Check dispatched field */
+		buf_priv->dispatched = 0;
+	}
+
+	dev_priv->sarea_priv->last_dispatch++;
+}
+
+static void r128_cce_dispatch_indices( drm_device_t *dev,
+				       drm_buf_t *buf,
+				       int start, int end,
+				       int count )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_buf_priv_t *buf_priv = buf->dev_private;
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int format = sarea_priv->vc_format;
+	int offset = dev->agp_buffer_map->offset - dev_priv->cce_buffers_offset;
+	int prim = buf_priv->prim;
+	u32 *data;
+	int dwords;
+	int i = 0;
+	RING_LOCALS;
+	DRM_DEBUG( "indices: s=%d e=%d c=%d\n", start, end, count );
+
+	if ( 0 )
+		r128_print_dirty( "dispatch_indices", sarea_priv->dirty );
+
+	if ( start != end ) {
+		buf_priv->dispatched = 1;
+
+		if ( sarea_priv->dirty & ~R128_UPLOAD_CLIPRECTS ) {
+			r128_emit_state( dev_priv );
+		}
+
+		dwords = (end - start + 3) / sizeof(u32);
+
+		data = (u32 *)((char *)dev->agp_buffer_map->handle
+			       + buf->offset + start);
+
+		data[0] = cpu_to_le32( CCE_PACKET3( R128_3D_RNDR_GEN_INDX_PRIM,
+						    dwords-2 ) );
+
+		data[1] = cpu_to_le32( offset );
+		data[2] = cpu_to_le32( R128_MAX_VB_VERTS );
+		data[3] = cpu_to_le32( format );
+		data[4] = cpu_to_le32( (prim | R128_CCE_VC_CNTL_PRIM_WALK_IND |
+					(count << 16)) );
+
+		if ( count & 0x1 ) {
+#ifdef __LITTLE_ENDIAN
+			data[dwords-1] &= 0x0000ffff;
+#else
+			data[dwords-1] &= 0xffff0000;
+#endif
+		}
+
+		do {
+			/* Emit the next set of up to three cliprects */
+			if ( i < sarea_priv->nbox ) {
+				r128_emit_clip_rects( dev_priv,
+						      &sarea_priv->boxes[i],
+						      sarea_priv->nbox - i );
+			}
+
+			r128_cce_dispatch_indirect( dev, buf, start, end );
+
+			i += 3;
+		} while ( i < sarea_priv->nbox );
+	}
+
+	if ( buf_priv->discard ) {
+		buf_priv->age = dev_priv->sarea_priv->last_dispatch;
+
+		/* Emit the vertex buffer age */
+		BEGIN_RING( 2 );
+
+		OUT_RING( CCE_PACKET0( R128_LAST_DISPATCH_REG, 0 ) );
+		OUT_RING( buf_priv->age );
+
+		ADVANCE_RING();
+
+		buf->pending = 1;
+		/* FIXME: Check dispatched field */
+		buf_priv->dispatched = 0;
+	}
+
+	dev_priv->sarea_priv->last_dispatch++;
+
+	sarea_priv->dirty &= ~R128_UPLOAD_CLIPRECTS;
+	sarea_priv->nbox = 0;
+}
+
+static int r128_cce_dispatch_blit( DRMFILE filp,
+				   drm_device_t *dev,
+				   drm_r128_blit_t *blit )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_r128_buf_priv_t *buf_priv;
+	u32 *data;
+	int dword_shift, dwords;
+	RING_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	/* The compiler won't optimize away a division by a variable,
+	 * even if the only legal values are powers of two.  Thus, we'll
+	 * use a shift instead.
+	 */
+	switch ( blit->format ) {
+	case R128_DATATYPE_ARGB8888:
+		dword_shift = 0;
+		break;
+	case R128_DATATYPE_ARGB1555:
+	case R128_DATATYPE_RGB565:
+	case R128_DATATYPE_ARGB4444:
+	case R128_DATATYPE_YVYU422:
+	case R128_DATATYPE_VYUY422:
+		dword_shift = 1;
+		break;
+	case R128_DATATYPE_CI8:
+	case R128_DATATYPE_RGB8:
+		dword_shift = 2;
+		break;
+	default:
+		DRM_ERROR( "invalid blit format %d\n", blit->format );
+		return DRM_ERR(EINVAL);
+	}
+
+	/* Flush the pixel cache, and mark the contents as Read Invalid.
+	 * This ensures no pixel data gets mixed up with the texture
+	 * data from the host data blit, otherwise part of the texture
+	 * image may be corrupted.
+	 */
+	BEGIN_RING( 2 );
+
+	OUT_RING( CCE_PACKET0( R128_PC_GUI_CTLSTAT, 0 ) );
+	OUT_RING( R128_PC_RI_GUI | R128_PC_FLUSH_GUI );
+
+	ADVANCE_RING();
+
+	/* Dispatch the indirect buffer.
+	 */
+	buf = dma->buflist[blit->idx];
+	buf_priv = buf->dev_private;
+
+	if ( buf->filp != filp ) {
+		DRM_ERROR( "process %d using buffer owned by %p\n",
+			   DRM_CURRENTPID, buf->filp );
+		return DRM_ERR(EINVAL);
+	}
+	if ( buf->pending ) {
+		DRM_ERROR( "sending pending buffer %d\n", blit->idx );
+		return DRM_ERR(EINVAL);
+	}
+
+	buf_priv->discard = 1;
+
+	dwords = (blit->width * blit->height) >> dword_shift;
+
+	data = (u32 *)((char *)dev->agp_buffer_map->handle + buf->offset);
+
+	data[0] = cpu_to_le32( CCE_PACKET3( R128_CNTL_HOSTDATA_BLT, dwords + 6 ) );
+	data[1] = cpu_to_le32( (R128_GMC_DST_PITCH_OFFSET_CNTL |
+				R128_GMC_BRUSH_NONE |
+				(blit->format << 8) |
+				R128_GMC_SRC_DATATYPE_COLOR |
+				R128_ROP3_S |
+				R128_DP_SRC_SOURCE_HOST_DATA |
+				R128_GMC_CLR_CMP_CNTL_DIS |
+				R128_GMC_AUX_CLIP_DIS |
+				R128_GMC_WR_MSK_DIS) );
+
+	data[2] = cpu_to_le32( (blit->pitch << 21) | (blit->offset >> 5) );
+	data[3] = cpu_to_le32( 0xffffffff );
+	data[4] = cpu_to_le32( 0xffffffff );
+	data[5] = cpu_to_le32( (blit->y << 16) | blit->x );
+	data[6] = cpu_to_le32( (blit->height << 16) | blit->width );
+	data[7] = cpu_to_le32( dwords );
+
+	buf->used = (dwords + 8) * sizeof(u32);
+
+	r128_cce_dispatch_indirect( dev, buf, 0, buf->used );
+
+	/* Flush the pixel cache after the blit completes.  This ensures
+	 * the texture data is written out to memory before rendering
+	 * continues.
+	 */
+	BEGIN_RING( 2 );
+
+	OUT_RING( CCE_PACKET0( R128_PC_GUI_CTLSTAT, 0 ) );
+	OUT_RING( R128_PC_FLUSH_GUI );
+
+	ADVANCE_RING();
+
+	return 0;
+}
+
+
+/* ================================================================
+ * Tiled depth buffer management
+ *
+ * FIXME: These should all set the destination write mask for when we
+ * have hardware stencil support.
+ */
+
+static int r128_cce_dispatch_write_span( drm_device_t *dev,
+					 drm_r128_depth_t *depth )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	int count, x, y;
+	u32 *buffer;
+	u8 *mask;
+	int i, buffer_size, mask_size;
+	RING_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	count = depth->n;
+	if (count > 4096 || count <= 0)
+		return DRM_ERR(EMSGSIZE);
+
+	if ( DRM_COPY_FROM_USER( &x, depth->x, sizeof(x) ) ) {
+		return DRM_ERR(EFAULT);
+	}
+	if ( DRM_COPY_FROM_USER( &y, depth->y, sizeof(y) ) ) {
+		return DRM_ERR(EFAULT);
+	}
+
+	buffer_size = depth->n * sizeof(u32);
+	buffer = drm_alloc( buffer_size, DRM_MEM_BUFS );
+	if ( buffer == NULL )
+		return DRM_ERR(ENOMEM);
+	if ( DRM_COPY_FROM_USER( buffer, depth->buffer, buffer_size ) ) {
+		drm_free( buffer, buffer_size, DRM_MEM_BUFS);
+		return DRM_ERR(EFAULT);
+	}
+
+	mask_size = depth->n * sizeof(u8);
+	if ( depth->mask ) {
+		mask = drm_alloc( mask_size, DRM_MEM_BUFS );
+		if ( mask == NULL ) {
+			drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+			return DRM_ERR(ENOMEM);
+		}
+		if ( DRM_COPY_FROM_USER( mask, depth->mask, mask_size ) ) {
+			drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+			drm_free( mask, mask_size, DRM_MEM_BUFS );
+			return DRM_ERR(EFAULT);
+		}
+
+		for ( i = 0 ; i < count ; i++, x++ ) {
+			if ( mask[i] ) {
+				BEGIN_RING( 6 );
+
+				OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+				OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+					  R128_GMC_BRUSH_SOLID_COLOR |
+					  (dev_priv->depth_fmt << 8) |
+					  R128_GMC_SRC_DATATYPE_COLOR |
+					  R128_ROP3_P |
+					  R128_GMC_CLR_CMP_CNTL_DIS |
+					  R128_GMC_WR_MSK_DIS );
+
+				OUT_RING( dev_priv->depth_pitch_offset_c );
+				OUT_RING( buffer[i] );
+
+				OUT_RING( (x << 16) | y );
+				OUT_RING( (1 << 16) | 1 );
+
+				ADVANCE_RING();
+			}
+		}
+
+		drm_free( mask, mask_size, DRM_MEM_BUFS );
+	} else {
+		for ( i = 0 ; i < count ; i++, x++ ) {
+			BEGIN_RING( 6 );
+
+			OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+			OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+				  R128_GMC_BRUSH_SOLID_COLOR |
+				  (dev_priv->depth_fmt << 8) |
+				  R128_GMC_SRC_DATATYPE_COLOR |
+				  R128_ROP3_P |
+				  R128_GMC_CLR_CMP_CNTL_DIS |
+				  R128_GMC_WR_MSK_DIS );
+
+			OUT_RING( dev_priv->depth_pitch_offset_c );
+			OUT_RING( buffer[i] );
+
+			OUT_RING( (x << 16) | y );
+			OUT_RING( (1 << 16) | 1 );
+
+			ADVANCE_RING();
+		}
+	}
+
+	drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+
+	return 0;
+}
+
+static int r128_cce_dispatch_write_pixels( drm_device_t *dev,
+					   drm_r128_depth_t *depth )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	int count, *x, *y;
+	u32 *buffer;
+	u8 *mask;
+	int i, xbuf_size, ybuf_size, buffer_size, mask_size;
+	RING_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	count = depth->n;
+	if (count > 4096 || count <= 0)
+		return DRM_ERR(EMSGSIZE);
+
+	xbuf_size = count * sizeof(*x);
+	ybuf_size = count * sizeof(*y);
+	x = drm_alloc( xbuf_size, DRM_MEM_BUFS );
+	if ( x == NULL ) {
+		return DRM_ERR(ENOMEM);
+	}
+	y = drm_alloc( ybuf_size, DRM_MEM_BUFS );
+	if ( y == NULL ) {
+		drm_free( x, xbuf_size, DRM_MEM_BUFS );
+		return DRM_ERR(ENOMEM);
+	}
+	if ( DRM_COPY_FROM_USER( x, depth->x, xbuf_size ) ) {
+		drm_free( x, xbuf_size, DRM_MEM_BUFS );
+		drm_free( y, ybuf_size, DRM_MEM_BUFS );
+		return DRM_ERR(EFAULT);
+	}
+	if ( DRM_COPY_FROM_USER( y, depth->y, xbuf_size ) ) {
+		drm_free( x, xbuf_size, DRM_MEM_BUFS );
+		drm_free( y, ybuf_size, DRM_MEM_BUFS );
+		return DRM_ERR(EFAULT);
+	}
+
+	buffer_size = depth->n * sizeof(u32);
+	buffer = drm_alloc( buffer_size, DRM_MEM_BUFS );
+	if ( buffer == NULL ) {
+		drm_free( x, xbuf_size, DRM_MEM_BUFS );
+		drm_free( y, ybuf_size, DRM_MEM_BUFS );
+		return DRM_ERR(ENOMEM);
+	}
+	if ( DRM_COPY_FROM_USER( buffer, depth->buffer, buffer_size ) ) {
+		drm_free( x, xbuf_size, DRM_MEM_BUFS );
+		drm_free( y, ybuf_size, DRM_MEM_BUFS );
+		drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+		return DRM_ERR(EFAULT);
+	}
+
+	if ( depth->mask ) {
+		mask_size = depth->n * sizeof(u8);
+		mask = drm_alloc( mask_size, DRM_MEM_BUFS );
+		if ( mask == NULL ) {
+			drm_free( x, xbuf_size, DRM_MEM_BUFS );
+			drm_free( y, ybuf_size, DRM_MEM_BUFS );
+			drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+			return DRM_ERR(ENOMEM);
+		}
+		if ( DRM_COPY_FROM_USER( mask, depth->mask, mask_size ) ) {
+			drm_free( x, xbuf_size, DRM_MEM_BUFS  );
+			drm_free( y, ybuf_size, DRM_MEM_BUFS  );
+			drm_free( buffer, buffer_size, DRM_MEM_BUFS  );
+			drm_free( mask, mask_size, DRM_MEM_BUFS  );
+			return DRM_ERR(EFAULT);
+		}
+
+		for ( i = 0 ; i < count ; i++ ) {
+			if ( mask[i] ) {
+				BEGIN_RING( 6 );
+
+				OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+				OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+					  R128_GMC_BRUSH_SOLID_COLOR |
+					  (dev_priv->depth_fmt << 8) |
+					  R128_GMC_SRC_DATATYPE_COLOR |
+					  R128_ROP3_P |
+					  R128_GMC_CLR_CMP_CNTL_DIS |
+					  R128_GMC_WR_MSK_DIS );
+
+				OUT_RING( dev_priv->depth_pitch_offset_c );
+				OUT_RING( buffer[i] );
+
+				OUT_RING( (x[i] << 16) | y[i] );
+				OUT_RING( (1 << 16) | 1 );
+
+				ADVANCE_RING();
+			}
+		}
+
+		drm_free( mask, mask_size, DRM_MEM_BUFS );
+	} else {
+		for ( i = 0 ; i < count ; i++ ) {
+			BEGIN_RING( 6 );
+
+			OUT_RING( CCE_PACKET3( R128_CNTL_PAINT_MULTI, 4 ) );
+			OUT_RING( R128_GMC_DST_PITCH_OFFSET_CNTL |
+				  R128_GMC_BRUSH_SOLID_COLOR |
+				  (dev_priv->depth_fmt << 8) |
+				  R128_GMC_SRC_DATATYPE_COLOR |
+				  R128_ROP3_P |
+				  R128_GMC_CLR_CMP_CNTL_DIS |
+				  R128_GMC_WR_MSK_DIS );
+
+			OUT_RING( dev_priv->depth_pitch_offset_c );
+			OUT_RING( buffer[i] );
+
+			OUT_RING( (x[i] << 16) | y[i] );
+			OUT_RING( (1 << 16) | 1 );
+
+			ADVANCE_RING();
+		}
+	}
+
+	drm_free( x, xbuf_size, DRM_MEM_BUFS );
+	drm_free( y, ybuf_size, DRM_MEM_BUFS );
+	drm_free( buffer, buffer_size, DRM_MEM_BUFS );
+
+	return 0;
+}
+
+static int r128_cce_dispatch_read_span( drm_device_t *dev,
+					drm_r128_depth_t *depth )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	int count, x, y;
+	RING_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	count = depth->n;
+	if (count > 4096 || count <= 0)
+		return DRM_ERR(EMSGSIZE);
+
+	if ( DRM_COPY_FROM_USER( &x, depth->x, sizeof(x) ) ) {
+		return DRM_ERR(EFAULT);
+	}
+	if ( DRM_COPY_FROM_USER( &y, depth->y, sizeof(y) ) ) {
+		return DRM_ERR(EFAULT);
+	}
+
+	BEGIN_RING( 7 );
+
+	OUT_RING( CCE_PACKET3( R128_CNTL_BITBLT_MULTI, 5 ) );
+	OUT_RING( R128_GMC_SRC_PITCH_OFFSET_CNTL |
+		  R128_GMC_DST_PITCH_OFFSET_CNTL |
+		  R128_GMC_BRUSH_NONE |
+		  (dev_priv->depth_fmt << 8) |
+		  R128_GMC_SRC_DATATYPE_COLOR |
+		  R128_ROP3_S |
+		  R128_DP_SRC_SOURCE_MEMORY |
+		  R128_GMC_CLR_CMP_CNTL_DIS |
+		  R128_GMC_WR_MSK_DIS );
+
+	OUT_RING( dev_priv->depth_pitch_offset_c );
+	OUT_RING( dev_priv->span_pitch_offset_c );
+
+	OUT_RING( (x << 16) | y );
+	OUT_RING( (0 << 16) | 0 );
+	OUT_RING( (count << 16) | 1 );
+
+	ADVANCE_RING();
+
+	return 0;
+}
+
+static int r128_cce_dispatch_read_pixels( drm_device_t *dev,
+					  drm_r128_depth_t *depth )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	int count, *x, *y;
+	int i, xbuf_size, ybuf_size;
+	RING_LOCALS;
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	count = depth->n;
+	if (count > 4096 || count <= 0)
+		return DRM_ERR(EMSGSIZE);
+
+	if ( count > dev_priv->depth_pitch ) {
+		count = dev_priv->depth_pitch;
+	}
+
+	xbuf_size = count * sizeof(*x);
+	ybuf_size = count * sizeof(*y);
+	x = drm_alloc( xbuf_size, DRM_MEM_BUFS );
+	if ( x == NULL ) {
+		return DRM_ERR(ENOMEM);
+	}
+	y = drm_alloc( ybuf_size, DRM_MEM_BUFS );
+	if ( y == NULL ) {
+		drm_free( x, xbuf_size, DRM_MEM_BUFS );
+		return DRM_ERR(ENOMEM);
+	}
+	if ( DRM_COPY_FROM_USER( x, depth->x, xbuf_size ) ) {
+		drm_free( x, xbuf_size, DRM_MEM_BUFS );
+		drm_free( y, ybuf_size, DRM_MEM_BUFS );
+		return DRM_ERR(EFAULT);
+	}
+	if ( DRM_COPY_FROM_USER( y, depth->y, ybuf_size ) ) {
+		drm_free( x, xbuf_size, DRM_MEM_BUFS );
+		drm_free( y, ybuf_size, DRM_MEM_BUFS );
+		return DRM_ERR(EFAULT);
+	}
+
+	for ( i = 0 ; i < count ; i++ ) {
+		BEGIN_RING( 7 );
+
+		OUT_RING( CCE_PACKET3( R128_CNTL_BITBLT_MULTI, 5 ) );
+		OUT_RING( R128_GMC_SRC_PITCH_OFFSET_CNTL |
+			  R128_GMC_DST_PITCH_OFFSET_CNTL |
+			  R128_GMC_BRUSH_NONE |
+			  (dev_priv->depth_fmt << 8) |
+			  R128_GMC_SRC_DATATYPE_COLOR |
+			  R128_ROP3_S |
+			  R128_DP_SRC_SOURCE_MEMORY |
+			  R128_GMC_CLR_CMP_CNTL_DIS |
+			  R128_GMC_WR_MSK_DIS );
+
+		OUT_RING( dev_priv->depth_pitch_offset_c );
+		OUT_RING( dev_priv->span_pitch_offset_c );
+
+		OUT_RING( (x[i] << 16) | y[i] );
+		OUT_RING( (i << 16) | 0 );
+		OUT_RING( (1 << 16) | 1 );
+
+		ADVANCE_RING();
+	}
+
+	drm_free( x, xbuf_size, DRM_MEM_BUFS );
+	drm_free( y, ybuf_size, DRM_MEM_BUFS );
+
+	return 0;
+}
+
+
+/* ================================================================
+ * Polygon stipple
+ */
+
+static void r128_cce_dispatch_stipple( drm_device_t *dev, u32 *stipple )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	int i;
+	RING_LOCALS;
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	BEGIN_RING( 33 );
+
+	OUT_RING( CCE_PACKET0( R128_BRUSH_DATA0, 31 ) );
+	for ( i = 0 ; i < 32 ; i++ ) {
+		OUT_RING( stipple[i] );
+	}
+
+	ADVANCE_RING();
+}
+
+
+/* ================================================================
+ * IOCTL functions
+ */
+
+static int r128_cce_clear( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_r128_clear_t clear;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( clear, (drm_r128_clear_t __user *) data,
+			     sizeof(clear) );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	if ( sarea_priv->nbox > R128_NR_SAREA_CLIPRECTS )
+		sarea_priv->nbox = R128_NR_SAREA_CLIPRECTS;
+
+	r128_cce_dispatch_clear( dev, &clear );
+	COMMIT_RING();
+
+	/* Make sure we restore the 3D state next time.
+	 */
+	dev_priv->sarea_priv->dirty |= R128_UPLOAD_CONTEXT | R128_UPLOAD_MASKS;
+
+	return 0;
+}
+
+static int r128_do_init_pageflip( drm_device_t *dev )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	dev_priv->crtc_offset =      R128_READ( R128_CRTC_OFFSET );
+	dev_priv->crtc_offset_cntl = R128_READ( R128_CRTC_OFFSET_CNTL );
+
+	R128_WRITE( R128_CRTC_OFFSET, dev_priv->front_offset );
+	R128_WRITE( R128_CRTC_OFFSET_CNTL,
+		    dev_priv->crtc_offset_cntl | R128_CRTC_OFFSET_FLIP_CNTL );
+
+	dev_priv->page_flipping = 1;
+	dev_priv->current_page = 0;
+	dev_priv->sarea_priv->pfCurrentPage = dev_priv->current_page;
+
+	return 0;
+}
+
+int r128_do_cleanup_pageflip( drm_device_t *dev )
+{
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	R128_WRITE( R128_CRTC_OFFSET,      dev_priv->crtc_offset );
+	R128_WRITE( R128_CRTC_OFFSET_CNTL, dev_priv->crtc_offset_cntl );
+
+	if (dev_priv->current_page != 0) {
+		r128_cce_dispatch_flip( dev );
+		COMMIT_RING();
+	}
+
+	dev_priv->page_flipping = 0;
+	return 0;
+}
+
+/* Swapping and flipping are different operations, need different ioctls.
+ * They can & should be intermixed to support multiple 3d windows.  
+ */
+
+static int r128_cce_flip( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	if (!dev_priv->page_flipping) 
+		r128_do_init_pageflip( dev );
+
+	r128_cce_dispatch_flip( dev );
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int r128_cce_swap( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	DRM_DEBUG( "%s\n", __FUNCTION__ );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	if ( sarea_priv->nbox > R128_NR_SAREA_CLIPRECTS )
+		sarea_priv->nbox = R128_NR_SAREA_CLIPRECTS;
+
+	r128_cce_dispatch_swap( dev );
+	dev_priv->sarea_priv->dirty |= (R128_UPLOAD_CONTEXT |
+					R128_UPLOAD_MASKS);
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int r128_cce_vertex( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_r128_buf_priv_t *buf_priv;
+	drm_r128_vertex_t vertex;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( vertex, (drm_r128_vertex_t __user *) data,
+			     sizeof(vertex) );
+
+	DRM_DEBUG( "pid=%d index=%d count=%d discard=%d\n",
+		   DRM_CURRENTPID,
+		   vertex.idx, vertex.count, vertex.discard );
+
+	if ( vertex.idx < 0 || vertex.idx >= dma->buf_count ) {
+		DRM_ERROR( "buffer index %d (of %d max)\n",
+			   vertex.idx, dma->buf_count - 1 );
+		return DRM_ERR(EINVAL);
+	}
+	if ( vertex.prim < 0 ||
+	     vertex.prim > R128_CCE_VC_CNTL_PRIM_TYPE_TRI_TYPE2 ) {
+		DRM_ERROR( "buffer prim %d\n", vertex.prim );
+		return DRM_ERR(EINVAL);
+	}
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	buf = dma->buflist[vertex.idx];
+	buf_priv = buf->dev_private;
+
+	if ( buf->filp != filp ) {
+		DRM_ERROR( "process %d using buffer owned by %p\n",
+			   DRM_CURRENTPID, buf->filp );
+		return DRM_ERR(EINVAL);
+	}
+	if ( buf->pending ) {
+		DRM_ERROR( "sending pending buffer %d\n", vertex.idx );
+		return DRM_ERR(EINVAL);
+	}
+
+	buf->used = vertex.count;
+	buf_priv->prim = vertex.prim;
+	buf_priv->discard = vertex.discard;
+
+	r128_cce_dispatch_vertex( dev, buf );
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int r128_cce_indices( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_r128_buf_priv_t *buf_priv;
+	drm_r128_indices_t elts;
+	int count;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( elts, (drm_r128_indices_t __user *) data,
+			     sizeof(elts) );
+
+	DRM_DEBUG( "pid=%d buf=%d s=%d e=%d d=%d\n", DRM_CURRENTPID,
+		   elts.idx, elts.start, elts.end, elts.discard );
+
+	if ( elts.idx < 0 || elts.idx >= dma->buf_count ) {
+		DRM_ERROR( "buffer index %d (of %d max)\n",
+			   elts.idx, dma->buf_count - 1 );
+		return DRM_ERR(EINVAL);
+	}
+	if ( elts.prim < 0 ||
+	     elts.prim > R128_CCE_VC_CNTL_PRIM_TYPE_TRI_TYPE2 ) {
+		DRM_ERROR( "buffer prim %d\n", elts.prim );
+		return DRM_ERR(EINVAL);
+	}
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	buf = dma->buflist[elts.idx];
+	buf_priv = buf->dev_private;
+
+	if ( buf->filp != filp ) {
+		DRM_ERROR( "process %d using buffer owned by %p\n",
+			   DRM_CURRENTPID, buf->filp );
+		return DRM_ERR(EINVAL);
+	}
+	if ( buf->pending ) {
+		DRM_ERROR( "sending pending buffer %d\n", elts.idx );
+		return DRM_ERR(EINVAL);
+	}
+
+	count = (elts.end - elts.start) / sizeof(u16);
+	elts.start -= R128_INDEX_PRIM_OFFSET;
+
+	if ( elts.start & 0x7 ) {
+		DRM_ERROR( "misaligned buffer 0x%x\n", elts.start );
+		return DRM_ERR(EINVAL);
+	}
+	if ( elts.start < buf->used ) {
+		DRM_ERROR( "no header 0x%x - 0x%x\n", elts.start, buf->used );
+		return DRM_ERR(EINVAL);
+	}
+
+	buf->used = elts.end;
+	buf_priv->prim = elts.prim;
+	buf_priv->discard = elts.discard;
+
+	r128_cce_dispatch_indices( dev, buf, elts.start, elts.end, count );
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int r128_cce_blit( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_device_dma_t *dma = dev->dma;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_blit_t blit;
+	int ret;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( blit, (drm_r128_blit_t __user *) data,
+			     sizeof(blit) );
+
+	DRM_DEBUG( "pid=%d index=%d\n", DRM_CURRENTPID, blit.idx );
+
+	if ( blit.idx < 0 || blit.idx >= dma->buf_count ) {
+		DRM_ERROR( "buffer index %d (of %d max)\n",
+			   blit.idx, dma->buf_count - 1 );
+		return DRM_ERR(EINVAL);
+	}
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	ret = r128_cce_dispatch_blit( filp, dev, &blit );
+
+	COMMIT_RING();
+	return ret;
+}
+
+static int r128_cce_depth( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_depth_t depth;
+	int ret;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( depth, (drm_r128_depth_t __user *) data,
+			     sizeof(depth) );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	ret = DRM_ERR(EINVAL);
+	switch ( depth.func ) {
+	case R128_WRITE_SPAN:
+		ret = r128_cce_dispatch_write_span( dev, &depth );
+	case R128_WRITE_PIXELS:
+		ret = r128_cce_dispatch_write_pixels( dev, &depth );
+	case R128_READ_SPAN:
+		ret = r128_cce_dispatch_read_span( dev, &depth );
+	case R128_READ_PIXELS:
+		ret = r128_cce_dispatch_read_pixels( dev, &depth );
+	}
+
+	COMMIT_RING();
+	return ret;
+}
+
+static int r128_cce_stipple( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_stipple_t stipple;
+	u32 mask[32];
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( stipple, (drm_r128_stipple_t __user *) data,
+			     sizeof(stipple) );
+
+	if ( DRM_COPY_FROM_USER( &mask, stipple.mask,
+			     32 * sizeof(u32) ) )
+		return DRM_ERR( EFAULT );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	r128_cce_dispatch_stipple( dev, mask );
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int r128_cce_indirect( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_r128_buf_priv_t *buf_priv;
+	drm_r128_indirect_t indirect;
+#if 0
+	RING_LOCALS;
+#endif
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( indirect, (drm_r128_indirect_t __user *) data,
+			     sizeof(indirect) );
+
+	DRM_DEBUG( "indirect: idx=%d s=%d e=%d d=%d\n",
+		   indirect.idx, indirect.start,
+		   indirect.end, indirect.discard );
+
+	if ( indirect.idx < 0 || indirect.idx >= dma->buf_count ) {
+		DRM_ERROR( "buffer index %d (of %d max)\n",
+			   indirect.idx, dma->buf_count - 1 );
+		return DRM_ERR(EINVAL);
+	}
+
+	buf = dma->buflist[indirect.idx];
+	buf_priv = buf->dev_private;
+
+	if ( buf->filp != filp ) {
+		DRM_ERROR( "process %d using buffer owned by %p\n",
+			   DRM_CURRENTPID, buf->filp );
+		return DRM_ERR(EINVAL);
+	}
+	if ( buf->pending ) {
+		DRM_ERROR( "sending pending buffer %d\n", indirect.idx );
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( indirect.start < buf->used ) {
+		DRM_ERROR( "reusing indirect: start=0x%x actual=0x%x\n",
+			   indirect.start, buf->used );
+		return DRM_ERR(EINVAL);
+	}
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	buf->used = indirect.end;
+	buf_priv->discard = indirect.discard;
+
+#if 0
+	/* Wait for the 3D stream to idle before the indirect buffer
+	 * containing 2D acceleration commands is processed.
+	 */
+	BEGIN_RING( 2 );
+	RADEON_WAIT_UNTIL_3D_IDLE();
+	ADVANCE_RING();
+#endif
+
+	/* Dispatch the indirect buffer full of commands from the
+	 * X server.  This is insecure and is thus only available to
+	 * privileged clients.
+	 */
+	r128_cce_dispatch_indirect( dev, buf, indirect.start, indirect.end );
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int r128_getparam( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_r128_private_t *dev_priv = dev->dev_private;
+	drm_r128_getparam_t param;
+	int value;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( param, (drm_r128_getparam_t __user *)data,
+			     sizeof(param) );
+
+	DRM_DEBUG( "pid=%d\n", DRM_CURRENTPID );
+
+	switch( param.param ) {
+	case R128_PARAM_IRQ_NR:
+		value = dev->irq;
+		break;
+	default:
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( DRM_COPY_TO_USER( param.value, &value, sizeof(int) ) ) {
+		DRM_ERROR( "copy_to_user\n" );
+		return DRM_ERR(EFAULT);
+	}
+	
+	return 0;
+}
+
+void r128_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+	if ( dev->dev_private ) {
+		drm_r128_private_t *dev_priv = dev->dev_private;
+		if ( dev_priv->page_flipping ) {
+			r128_do_cleanup_pageflip( dev );
+		}
+	}			
+}
+
+void r128_driver_pretakedown(drm_device_t *dev)
+{
+	r128_do_cleanup_cce( dev );
+}
+
+drm_ioctl_desc_t r128_ioctls[] = {
+	[DRM_IOCTL_NR(DRM_R128_INIT)]       = { r128_cce_init,     1, 1 },
+	[DRM_IOCTL_NR(DRM_R128_CCE_START)]  = { r128_cce_start,    1, 1 },
+	[DRM_IOCTL_NR(DRM_R128_CCE_STOP)]   = { r128_cce_stop,     1, 1 },
+	[DRM_IOCTL_NR(DRM_R128_CCE_RESET)]  = { r128_cce_reset,    1, 1 },
+	[DRM_IOCTL_NR(DRM_R128_CCE_IDLE)]   = { r128_cce_idle,     1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_RESET)]      = { r128_engine_reset, 1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_FULLSCREEN)] = { r128_fullscreen,   1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_SWAP)]       = { r128_cce_swap,     1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_FLIP)]       = { r128_cce_flip,     1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_CLEAR)]      = { r128_cce_clear,    1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_VERTEX)]     = { r128_cce_vertex,   1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_INDICES)]    = { r128_cce_indices,  1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_BLIT)]       = { r128_cce_blit,     1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_DEPTH)]      = { r128_cce_depth,    1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_STIPPLE)]    = { r128_cce_stipple,  1, 0 },
+	[DRM_IOCTL_NR(DRM_R128_INDIRECT)]   = { r128_cce_indirect, 1, 1 },
+	[DRM_IOCTL_NR(DRM_R128_GETPARAM)]   = { r128_getparam, 1, 0 },
+};
+
+int r128_max_ioctl = DRM_ARRAY_SIZE(r128_ioctls);
diff --git a/drivers/char/drm/radeon_cp.c b/drivers/char/drm/radeon_cp.c
new file mode 100644
index 0000000..20bcf87
--- /dev/null
+++ b/drivers/char/drm/radeon_cp.c
@@ -0,0 +1,2061 @@
+/* radeon_cp.c -- CP support for Radeon -*- linux-c -*-
+ *
+ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Fremont, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Kevin E. Martin <martin@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+#define RADEON_FIFO_DEBUG	0
+
+static int radeon_do_cleanup_cp( drm_device_t *dev );
+
+/* CP microcode (from ATI) */
+static u32 R200_cp_microcode[][2] = {
+	{ 0x21007000, 0000000000 },        
+	{ 0x20007000, 0000000000 }, 
+	{ 0x000000ab, 0x00000004 },
+	{ 0x000000af, 0x00000004 },
+	{ 0x66544a49, 0000000000 },
+	{ 0x49494174, 0000000000 },
+	{ 0x54517d83, 0000000000 },
+	{ 0x498d8b64, 0000000000 },
+	{ 0x49494949, 0000000000 },
+	{ 0x49da493c, 0000000000 },
+	{ 0x49989898, 0000000000 },
+	{ 0xd34949d5, 0000000000 },
+	{ 0x9dc90e11, 0000000000 },
+	{ 0xce9b9b9b, 0000000000 },
+	{ 0x000f0000, 0x00000016 },
+	{ 0x352e232c, 0000000000 },
+	{ 0x00000013, 0x00000004 },
+	{ 0x000f0000, 0x00000016 },
+	{ 0x352e272c, 0000000000 },
+	{ 0x000f0001, 0x00000016 },
+	{ 0x3239362f, 0000000000 },
+	{ 0x000077ef, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x00000020, 0x0000001a },
+	{ 0x00004000, 0x0000001e },
+	{ 0x00061000, 0x00000002 },
+	{ 0x00000020, 0x0000001a },
+	{ 0x00004000, 0x0000001e },
+	{ 0x00061000, 0x00000002 },
+	{ 0x00000020, 0x0000001a },
+	{ 0x00004000, 0x0000001e },
+	{ 0x00000016, 0x00000004 },
+	{ 0x0003802a, 0x00000002 },
+	{ 0x040067e0, 0x00000002 },
+	{ 0x00000016, 0x00000004 },
+	{ 0x000077e0, 0x00000002 },
+	{ 0x00065000, 0x00000002 },
+	{ 0x000037e1, 0x00000002 },
+	{ 0x040067e1, 0x00000006 },
+	{ 0x000077e0, 0x00000002 },
+	{ 0x000077e1, 0x00000002 },
+	{ 0x000077e1, 0x00000006 },
+	{ 0xffffffff, 0000000000 },
+	{ 0x10000000, 0000000000 },
+	{ 0x0003802a, 0x00000002 },
+	{ 0x040067e0, 0x00000006 },
+	{ 0x00007675, 0x00000002 },
+	{ 0x00007676, 0x00000002 },
+	{ 0x00007677, 0x00000002 },
+	{ 0x00007678, 0x00000006 },
+	{ 0x0003802b, 0x00000002 },
+	{ 0x04002676, 0x00000002 },
+	{ 0x00007677, 0x00000002 },
+	{ 0x00007678, 0x00000006 },
+	{ 0x0000002e, 0x00000018 },
+	{ 0x0000002e, 0x00000018 },
+	{ 0000000000, 0x00000006 },
+	{ 0x0000002f, 0x00000018 },
+	{ 0x0000002f, 0x00000018 },
+	{ 0000000000, 0x00000006 },
+	{ 0x01605000, 0x00000002 },
+	{ 0x00065000, 0x00000002 },
+	{ 0x00098000, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x64c0603d, 0x00000004 },
+	{ 0x00080000, 0x00000016 },
+	{ 0000000000, 0000000000 },
+	{ 0x0400251d, 0x00000002 },
+	{ 0x00007580, 0x00000002 },
+	{ 0x00067581, 0x00000002 },
+	{ 0x04002580, 0x00000002 },
+	{ 0x00067581, 0x00000002 },
+	{ 0x00000046, 0x00000004 },
+	{ 0x00005000, 0000000000 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x0000750e, 0x00000002 },
+	{ 0x00019000, 0x00000002 },
+	{ 0x00011055, 0x00000014 },
+	{ 0x00000055, 0x00000012 },
+	{ 0x0400250f, 0x00000002 },
+	{ 0x0000504a, 0x00000004 },
+	{ 0x00007565, 0x00000002 },
+	{ 0x00007566, 0x00000002 },
+	{ 0x00000051, 0x00000004 },
+	{ 0x01e655b4, 0x00000002 },
+	{ 0x4401b0dc, 0x00000002 },
+	{ 0x01c110dc, 0x00000002 },
+	{ 0x2666705d, 0x00000018 },
+	{ 0x040c2565, 0x00000002 },
+	{ 0x0000005d, 0x00000018 },
+	{ 0x04002564, 0x00000002 },
+	{ 0x00007566, 0x00000002 },
+	{ 0x00000054, 0x00000004 },
+	{ 0x00401060, 0x00000008 },
+	{ 0x00101000, 0x00000002 },
+	{ 0x000d80ff, 0x00000002 },
+	{ 0x00800063, 0x00000008 },
+	{ 0x000f9000, 0x00000002 },
+	{ 0x000e00ff, 0x00000002 },
+	{ 0000000000, 0x00000006 },
+	{ 0x00000080, 0x00000018 },
+	{ 0x00000054, 0x00000004 },
+	{ 0x00007576, 0x00000002 },
+	{ 0x00065000, 0x00000002 },
+	{ 0x00009000, 0x00000002 },
+	{ 0x00041000, 0x00000002 },
+	{ 0x0c00350e, 0x00000002 },
+	{ 0x00049000, 0x00000002 },
+	{ 0x00051000, 0x00000002 },
+	{ 0x01e785f8, 0x00000002 },
+	{ 0x00200000, 0x00000002 },
+	{ 0x00600073, 0x0000000c },
+	{ 0x00007563, 0x00000002 },
+	{ 0x006075f0, 0x00000021 },
+	{ 0x20007068, 0x00000004 },
+	{ 0x00005068, 0x00000004 },
+	{ 0x00007576, 0x00000002 },
+	{ 0x00007577, 0x00000002 },
+	{ 0x0000750e, 0x00000002 },
+	{ 0x0000750f, 0x00000002 },
+	{ 0x00a05000, 0x00000002 },
+	{ 0x00600076, 0x0000000c },
+	{ 0x006075f0, 0x00000021 },
+	{ 0x000075f8, 0x00000002 },
+	{ 0x00000076, 0x00000004 },
+	{ 0x000a750e, 0x00000002 },
+	{ 0x0020750f, 0x00000002 },
+	{ 0x00600079, 0x00000004 },
+	{ 0x00007570, 0x00000002 },
+	{ 0x00007571, 0x00000002 },
+	{ 0x00007572, 0x00000006 },
+	{ 0x00005000, 0x00000002 },
+	{ 0x00a05000, 0x00000002 },
+	{ 0x00007568, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x00000084, 0x0000000c },
+	{ 0x00058000, 0x00000002 },
+	{ 0x0c607562, 0x00000002 },
+	{ 0x00000086, 0x00000004 },
+	{ 0x00600085, 0x00000004 },
+	{ 0x400070dd, 0000000000 },
+	{ 0x000380dd, 0x00000002 },
+	{ 0x00000093, 0x0000001c },
+	{ 0x00065095, 0x00000018 },
+	{ 0x040025bb, 0x00000002 },
+	{ 0x00061096, 0x00000018 },
+	{ 0x040075bc, 0000000000 },
+	{ 0x000075bb, 0x00000002 },
+	{ 0x000075bc, 0000000000 },
+	{ 0x00090000, 0x00000006 },
+	{ 0x00090000, 0x00000002 },
+	{ 0x000d8002, 0x00000006 },
+	{ 0x00005000, 0x00000002 },
+	{ 0x00007821, 0x00000002 },
+	{ 0x00007800, 0000000000 },
+	{ 0x00007821, 0x00000002 },
+	{ 0x00007800, 0000000000 },
+	{ 0x01665000, 0x00000002 },
+	{ 0x000a0000, 0x00000002 },
+	{ 0x000671cc, 0x00000002 },
+	{ 0x0286f1cd, 0x00000002 },
+	{ 0x000000a3, 0x00000010 },
+	{ 0x21007000, 0000000000 },
+	{ 0x000000aa, 0x0000001c },
+	{ 0x00065000, 0x00000002 },
+	{ 0x000a0000, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x000b0000, 0x00000002 },
+	{ 0x38067000, 0x00000002 },
+	{ 0x000a00a6, 0x00000004 },
+	{ 0x20007000, 0000000000 },
+	{ 0x01200000, 0x00000002 },
+	{ 0x20077000, 0x00000002 },
+	{ 0x01200000, 0x00000002 },
+	{ 0x20007000, 0000000000 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x0120751b, 0x00000002 },
+	{ 0x8040750a, 0x00000002 },
+	{ 0x8040750b, 0x00000002 },
+	{ 0x00110000, 0x00000002 },
+	{ 0x000380dd, 0x00000002 },
+	{ 0x000000bd, 0x0000001c },
+	{ 0x00061096, 0x00000018 },
+	{ 0x844075bd, 0x00000002 },
+	{ 0x00061095, 0x00000018 },
+	{ 0x840075bb, 0x00000002 },
+	{ 0x00061096, 0x00000018 },
+	{ 0x844075bc, 0x00000002 },
+	{ 0x000000c0, 0x00000004 },
+	{ 0x804075bd, 0x00000002 },
+	{ 0x800075bb, 0x00000002 },
+	{ 0x804075bc, 0x00000002 },
+	{ 0x00108000, 0x00000002 },
+	{ 0x01400000, 0x00000002 },
+	{ 0x006000c4, 0x0000000c },
+	{ 0x20c07000, 0x00000020 },
+	{ 0x000000c6, 0x00000012 },
+	{ 0x00800000, 0x00000006 },
+	{ 0x0080751d, 0x00000006 },
+	{ 0x000025bb, 0x00000002 },
+	{ 0x000040c0, 0x00000004 },
+	{ 0x0000775c, 0x00000002 },
+	{ 0x00a05000, 0x00000002 },
+	{ 0x00661000, 0x00000002 },
+	{ 0x0460275d, 0x00000020 },
+	{ 0x00004000, 0000000000 },
+	{ 0x00007999, 0x00000002 },
+	{ 0x00a05000, 0x00000002 },
+	{ 0x00661000, 0x00000002 },
+	{ 0x0460299b, 0x00000020 },
+	{ 0x00004000, 0000000000 },
+	{ 0x01e00830, 0x00000002 },
+	{ 0x21007000, 0000000000 },
+	{ 0x00005000, 0x00000002 },
+	{ 0x00038042, 0x00000002 },
+	{ 0x040025e0, 0x00000002 },
+	{ 0x000075e1, 0000000000 },
+	{ 0x00000001, 0000000000 },
+	{ 0x000380d9, 0x00000002 },
+	{ 0x04007394, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+};
+
+
+static u32 radeon_cp_microcode[][2] = {
+	{ 0x21007000, 0000000000 },
+	{ 0x20007000, 0000000000 },
+	{ 0x000000b4, 0x00000004 },
+	{ 0x000000b8, 0x00000004 },
+	{ 0x6f5b4d4c, 0000000000 },
+	{ 0x4c4c427f, 0000000000 },
+	{ 0x5b568a92, 0000000000 },
+	{ 0x4ca09c6d, 0000000000 },
+	{ 0xad4c4c4c, 0000000000 },
+	{ 0x4ce1af3d, 0000000000 },
+	{ 0xd8afafaf, 0000000000 },
+	{ 0xd64c4cdc, 0000000000 },
+	{ 0x4cd10d10, 0000000000 },
+	{ 0x000f0000, 0x00000016 },
+	{ 0x362f242d, 0000000000 },
+	{ 0x00000012, 0x00000004 },
+	{ 0x000f0000, 0x00000016 },
+	{ 0x362f282d, 0000000000 },
+	{ 0x000380e7, 0x00000002 },
+	{ 0x04002c97, 0x00000002 },
+	{ 0x000f0001, 0x00000016 },
+	{ 0x333a3730, 0000000000 },
+	{ 0x000077ef, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x00000021, 0x0000001a },
+	{ 0x00004000, 0x0000001e },
+	{ 0x00061000, 0x00000002 },
+	{ 0x00000021, 0x0000001a },
+	{ 0x00004000, 0x0000001e },
+	{ 0x00061000, 0x00000002 },
+	{ 0x00000021, 0x0000001a },
+	{ 0x00004000, 0x0000001e },
+	{ 0x00000017, 0x00000004 },
+	{ 0x0003802b, 0x00000002 },
+	{ 0x040067e0, 0x00000002 },
+	{ 0x00000017, 0x00000004 },
+	{ 0x000077e0, 0x00000002 },
+	{ 0x00065000, 0x00000002 },
+	{ 0x000037e1, 0x00000002 },
+	{ 0x040067e1, 0x00000006 },
+	{ 0x000077e0, 0x00000002 },
+	{ 0x000077e1, 0x00000002 },
+	{ 0x000077e1, 0x00000006 },
+	{ 0xffffffff, 0000000000 },
+	{ 0x10000000, 0000000000 },
+	{ 0x0003802b, 0x00000002 },
+	{ 0x040067e0, 0x00000006 },
+	{ 0x00007675, 0x00000002 },
+	{ 0x00007676, 0x00000002 },
+	{ 0x00007677, 0x00000002 },
+	{ 0x00007678, 0x00000006 },
+	{ 0x0003802c, 0x00000002 },
+	{ 0x04002676, 0x00000002 },
+	{ 0x00007677, 0x00000002 },
+	{ 0x00007678, 0x00000006 },
+	{ 0x0000002f, 0x00000018 },
+	{ 0x0000002f, 0x00000018 },
+	{ 0000000000, 0x00000006 },
+	{ 0x00000030, 0x00000018 },
+	{ 0x00000030, 0x00000018 },
+	{ 0000000000, 0x00000006 },
+	{ 0x01605000, 0x00000002 },
+	{ 0x00065000, 0x00000002 },
+	{ 0x00098000, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x64c0603e, 0x00000004 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x00080000, 0x00000016 },
+	{ 0000000000, 0000000000 },
+	{ 0x0400251d, 0x00000002 },
+	{ 0x00007580, 0x00000002 },
+	{ 0x00067581, 0x00000002 },
+	{ 0x04002580, 0x00000002 },
+	{ 0x00067581, 0x00000002 },
+	{ 0x00000049, 0x00000004 },
+	{ 0x00005000, 0000000000 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x0000750e, 0x00000002 },
+	{ 0x00019000, 0x00000002 },
+	{ 0x00011055, 0x00000014 },
+	{ 0x00000055, 0x00000012 },
+	{ 0x0400250f, 0x00000002 },
+	{ 0x0000504f, 0x00000004 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x00007565, 0x00000002 },
+	{ 0x00007566, 0x00000002 },
+	{ 0x00000058, 0x00000004 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x01e655b4, 0x00000002 },
+	{ 0x4401b0e4, 0x00000002 },
+	{ 0x01c110e4, 0x00000002 },
+	{ 0x26667066, 0x00000018 },
+	{ 0x040c2565, 0x00000002 },
+	{ 0x00000066, 0x00000018 },
+	{ 0x04002564, 0x00000002 },
+	{ 0x00007566, 0x00000002 },
+	{ 0x0000005d, 0x00000004 },
+	{ 0x00401069, 0x00000008 },
+	{ 0x00101000, 0x00000002 },
+	{ 0x000d80ff, 0x00000002 },
+	{ 0x0080006c, 0x00000008 },
+	{ 0x000f9000, 0x00000002 },
+	{ 0x000e00ff, 0x00000002 },
+	{ 0000000000, 0x00000006 },
+	{ 0x0000008f, 0x00000018 },
+	{ 0x0000005b, 0x00000004 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x00007576, 0x00000002 },
+	{ 0x00065000, 0x00000002 },
+	{ 0x00009000, 0x00000002 },
+	{ 0x00041000, 0x00000002 },
+	{ 0x0c00350e, 0x00000002 },
+	{ 0x00049000, 0x00000002 },
+	{ 0x00051000, 0x00000002 },
+	{ 0x01e785f8, 0x00000002 },
+	{ 0x00200000, 0x00000002 },
+	{ 0x0060007e, 0x0000000c },
+	{ 0x00007563, 0x00000002 },
+	{ 0x006075f0, 0x00000021 },
+	{ 0x20007073, 0x00000004 },
+	{ 0x00005073, 0x00000004 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x00007576, 0x00000002 },
+	{ 0x00007577, 0x00000002 },
+	{ 0x0000750e, 0x00000002 },
+	{ 0x0000750f, 0x00000002 },
+	{ 0x00a05000, 0x00000002 },
+	{ 0x00600083, 0x0000000c },
+	{ 0x006075f0, 0x00000021 },
+	{ 0x000075f8, 0x00000002 },
+	{ 0x00000083, 0x00000004 },
+	{ 0x000a750e, 0x00000002 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x0020750f, 0x00000002 },
+	{ 0x00600086, 0x00000004 },
+	{ 0x00007570, 0x00000002 },
+	{ 0x00007571, 0x00000002 },
+	{ 0x00007572, 0x00000006 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x00005000, 0x00000002 },
+	{ 0x00a05000, 0x00000002 },
+	{ 0x00007568, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x00000095, 0x0000000c },
+	{ 0x00058000, 0x00000002 },
+	{ 0x0c607562, 0x00000002 },
+	{ 0x00000097, 0x00000004 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x00600096, 0x00000004 },
+	{ 0x400070e5, 0000000000 },
+	{ 0x000380e6, 0x00000002 },
+	{ 0x040025c5, 0x00000002 },
+	{ 0x000380e5, 0x00000002 },
+	{ 0x000000a8, 0x0000001c },
+	{ 0x000650aa, 0x00000018 },
+	{ 0x040025bb, 0x00000002 },
+	{ 0x000610ab, 0x00000018 },
+	{ 0x040075bc, 0000000000 },
+	{ 0x000075bb, 0x00000002 },
+	{ 0x000075bc, 0000000000 },
+	{ 0x00090000, 0x00000006 },
+	{ 0x00090000, 0x00000002 },
+	{ 0x000d8002, 0x00000006 },
+	{ 0x00007832, 0x00000002 },
+	{ 0x00005000, 0x00000002 },
+	{ 0x000380e7, 0x00000002 },
+	{ 0x04002c97, 0x00000002 },
+	{ 0x00007820, 0x00000002 },
+	{ 0x00007821, 0x00000002 },
+	{ 0x00007800, 0000000000 },
+	{ 0x01200000, 0x00000002 },
+	{ 0x20077000, 0x00000002 },
+	{ 0x01200000, 0x00000002 },
+	{ 0x20007000, 0x00000002 },
+	{ 0x00061000, 0x00000002 },
+	{ 0x0120751b, 0x00000002 },
+	{ 0x8040750a, 0x00000002 },
+	{ 0x8040750b, 0x00000002 },
+	{ 0x00110000, 0x00000002 },
+	{ 0x000380e5, 0x00000002 },
+	{ 0x000000c6, 0x0000001c },
+	{ 0x000610ab, 0x00000018 },
+	{ 0x844075bd, 0x00000002 },
+	{ 0x000610aa, 0x00000018 },
+	{ 0x840075bb, 0x00000002 },
+	{ 0x000610ab, 0x00000018 },
+	{ 0x844075bc, 0x00000002 },
+	{ 0x000000c9, 0x00000004 },
+	{ 0x804075bd, 0x00000002 },
+	{ 0x800075bb, 0x00000002 },
+	{ 0x804075bc, 0x00000002 },
+	{ 0x00108000, 0x00000002 },
+	{ 0x01400000, 0x00000002 },
+	{ 0x006000cd, 0x0000000c },
+	{ 0x20c07000, 0x00000020 },
+	{ 0x000000cf, 0x00000012 },
+	{ 0x00800000, 0x00000006 },
+	{ 0x0080751d, 0x00000006 },
+	{ 0000000000, 0000000000 },
+	{ 0x0000775c, 0x00000002 },
+	{ 0x00a05000, 0x00000002 },
+	{ 0x00661000, 0x00000002 },
+	{ 0x0460275d, 0x00000020 },
+	{ 0x00004000, 0000000000 },
+	{ 0x01e00830, 0x00000002 },
+	{ 0x21007000, 0000000000 },
+	{ 0x6464614d, 0000000000 },
+	{ 0x69687420, 0000000000 },
+	{ 0x00000073, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0x00005000, 0x00000002 },
+	{ 0x000380d0, 0x00000002 },
+	{ 0x040025e0, 0x00000002 },
+	{ 0x000075e1, 0000000000 },
+	{ 0x00000001, 0000000000 },
+	{ 0x000380e0, 0x00000002 },
+	{ 0x04002394, 0x00000002 },
+	{ 0x00005000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0x00000008, 0000000000 },
+	{ 0x00000004, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+};
+
+static u32 R300_cp_microcode[][2] = {
+	{ 0x4200e000, 0000000000 },
+	{ 0x4000e000, 0000000000 },
+	{ 0x000000af, 0x00000008 },
+	{ 0x000000b3, 0x00000008 },
+	{ 0x6c5a504f, 0000000000 },
+	{ 0x4f4f497a, 0000000000 },
+	{ 0x5a578288, 0000000000 },
+	{ 0x4f91906a, 0000000000 },
+	{ 0x4f4f4f4f, 0000000000 },
+	{ 0x4fe24f44, 0000000000 },
+	{ 0x4f9c9c9c, 0000000000 },
+	{ 0xdc4f4fde, 0000000000 },
+	{ 0xa1cd4f4f, 0000000000 },
+	{ 0xd29d9d9d, 0000000000 },
+	{ 0x4f0f9fd7, 0000000000 },
+	{ 0x000ca000, 0x00000004 },
+	{ 0x000d0012, 0x00000038 },
+	{ 0x0000e8b4, 0x00000004 },
+	{ 0x000d0014, 0x00000038 },
+	{ 0x0000e8b6, 0x00000004 },
+	{ 0x000d0016, 0x00000038 },
+	{ 0x0000e854, 0x00000004 },
+	{ 0x000d0018, 0x00000038 },
+	{ 0x0000e855, 0x00000004 },
+	{ 0x000d001a, 0x00000038 },
+	{ 0x0000e856, 0x00000004 },
+	{ 0x000d001c, 0x00000038 },
+	{ 0x0000e857, 0x00000004 },
+	{ 0x000d001e, 0x00000038 },
+	{ 0x0000e824, 0x00000004 },
+	{ 0x000d0020, 0x00000038 },
+	{ 0x0000e825, 0x00000004 },
+	{ 0x000d0022, 0x00000038 },
+	{ 0x0000e830, 0x00000004 },
+	{ 0x000d0024, 0x00000038 },
+	{ 0x0000f0c0, 0x00000004 },
+	{ 0x000d0026, 0x00000038 },
+	{ 0x0000f0c1, 0x00000004 },
+	{ 0x000d0028, 0x00000038 },
+	{ 0x0000f041, 0x00000004 },
+	{ 0x000d002a, 0x00000038 },
+	{ 0x0000f184, 0x00000004 },
+	{ 0x000d002c, 0x00000038 },
+	{ 0x0000f185, 0x00000004 },
+	{ 0x000d002e, 0x00000038 },
+	{ 0x0000f186, 0x00000004 },
+	{ 0x000d0030, 0x00000038 },
+	{ 0x0000f187, 0x00000004 },
+	{ 0x000d0032, 0x00000038 },
+	{ 0x0000f180, 0x00000004 },
+	{ 0x000d0034, 0x00000038 },
+	{ 0x0000f393, 0x00000004 },
+	{ 0x000d0036, 0x00000038 },
+	{ 0x0000f38a, 0x00000004 },
+	{ 0x000d0038, 0x00000038 },
+	{ 0x0000f38e, 0x00000004 },
+	{ 0x0000e821, 0x00000004 },
+	{ 0x0140a000, 0x00000004 },
+	{ 0x00000043, 0x00000018 },
+	{ 0x00cce800, 0x00000004 },
+	{ 0x001b0001, 0x00000004 },
+	{ 0x08004800, 0x00000004 },
+	{ 0x001b0001, 0x00000004 },
+	{ 0x08004800, 0x00000004 },
+	{ 0x001b0001, 0x00000004 },
+	{ 0x08004800, 0x00000004 },
+	{ 0x0000003a, 0x00000008 },
+	{ 0x0000a000, 0000000000 },
+	{ 0x02c0a000, 0x00000004 },
+	{ 0x000ca000, 0x00000004 },
+	{ 0x00130000, 0x00000004 },
+	{ 0x000c2000, 0x00000004 },
+	{ 0xc980c045, 0x00000008 },
+	{ 0x2000451d, 0x00000004 },
+	{ 0x0000e580, 0x00000004 },
+	{ 0x000ce581, 0x00000004 },
+	{ 0x08004580, 0x00000004 },
+	{ 0x000ce581, 0x00000004 },
+	{ 0x0000004c, 0x00000008 },
+	{ 0x0000a000, 0000000000 },
+	{ 0x000c2000, 0x00000004 },
+	{ 0x0000e50e, 0x00000004 },
+	{ 0x00032000, 0x00000004 },
+	{ 0x00022056, 0x00000028 },
+	{ 0x00000056, 0x00000024 },
+	{ 0x0800450f, 0x00000004 },
+	{ 0x0000a050, 0x00000008 },
+	{ 0x0000e565, 0x00000004 },
+	{ 0x0000e566, 0x00000004 },
+	{ 0x00000057, 0x00000008 },
+	{ 0x03cca5b4, 0x00000004 },
+	{ 0x05432000, 0x00000004 },
+	{ 0x00022000, 0x00000004 },
+	{ 0x4ccce063, 0x00000030 },
+	{ 0x08274565, 0x00000004 },
+	{ 0x00000063, 0x00000030 },
+	{ 0x08004564, 0x00000004 },
+	{ 0x0000e566, 0x00000004 },
+	{ 0x0000005a, 0x00000008 },
+	{ 0x00802066, 0x00000010 },
+	{ 0x00202000, 0x00000004 },
+	{ 0x001b00ff, 0x00000004 },
+	{ 0x01000069, 0x00000010 },
+	{ 0x001f2000, 0x00000004 },
+	{ 0x001c00ff, 0x00000004 },
+	{ 0000000000, 0x0000000c },
+	{ 0x00000085, 0x00000030 },
+	{ 0x0000005a, 0x00000008 },
+	{ 0x0000e576, 0x00000004 },
+	{ 0x000ca000, 0x00000004 },
+	{ 0x00012000, 0x00000004 },
+	{ 0x00082000, 0x00000004 },
+	{ 0x1800650e, 0x00000004 },
+	{ 0x00092000, 0x00000004 },
+	{ 0x000a2000, 0x00000004 },
+	{ 0x000f0000, 0x00000004 },
+	{ 0x00400000, 0x00000004 },
+	{ 0x00000079, 0x00000018 },
+	{ 0x0000e563, 0x00000004 },
+	{ 0x00c0e5f9, 0x000000c2 },
+	{ 0x0000006e, 0x00000008 },
+	{ 0x0000a06e, 0x00000008 },
+	{ 0x0000e576, 0x00000004 },
+	{ 0x0000e577, 0x00000004 },
+	{ 0x0000e50e, 0x00000004 },
+	{ 0x0000e50f, 0x00000004 },
+	{ 0x0140a000, 0x00000004 },
+	{ 0x0000007c, 0x00000018 },
+	{ 0x00c0e5f9, 0x000000c2 },
+	{ 0x0000007c, 0x00000008 },
+	{ 0x0014e50e, 0x00000004 },
+	{ 0x0040e50f, 0x00000004 },
+	{ 0x00c0007f, 0x00000008 },
+	{ 0x0000e570, 0x00000004 },
+	{ 0x0000e571, 0x00000004 },
+	{ 0x0000e572, 0x0000000c },
+	{ 0x0000a000, 0x00000004 },
+	{ 0x0140a000, 0x00000004 },
+	{ 0x0000e568, 0x00000004 },
+	{ 0x000c2000, 0x00000004 },
+	{ 0x00000089, 0x00000018 },
+	{ 0x000b0000, 0x00000004 },
+	{ 0x18c0e562, 0x00000004 },
+	{ 0x0000008b, 0x00000008 },
+	{ 0x00c0008a, 0x00000008 },
+	{ 0x000700e4, 0x00000004 },
+	{ 0x00000097, 0x00000038 },
+	{ 0x000ca099, 0x00000030 },
+	{ 0x080045bb, 0x00000004 },
+	{ 0x000c209a, 0x00000030 },
+	{ 0x0800e5bc, 0000000000 },
+	{ 0x0000e5bb, 0x00000004 },
+	{ 0x0000e5bc, 0000000000 },
+	{ 0x00120000, 0x0000000c },
+	{ 0x00120000, 0x00000004 },
+	{ 0x001b0002, 0x0000000c },
+	{ 0x0000a000, 0x00000004 },
+	{ 0x0000e821, 0x00000004 },
+	{ 0x0000e800, 0000000000 },
+	{ 0x0000e821, 0x00000004 },
+	{ 0x0000e82e, 0000000000 },
+	{ 0x02cca000, 0x00000004 },
+	{ 0x00140000, 0x00000004 },
+	{ 0x000ce1cc, 0x00000004 },
+	{ 0x050de1cd, 0x00000004 },
+	{ 0x000000a7, 0x00000020 },
+	{ 0x4200e000, 0000000000 },
+	{ 0x000000ae, 0x00000038 },
+	{ 0x000ca000, 0x00000004 },
+	{ 0x00140000, 0x00000004 },
+	{ 0x000c2000, 0x00000004 },
+	{ 0x00160000, 0x00000004 },
+	{ 0x700ce000, 0x00000004 },
+	{ 0x001400aa, 0x00000008 },
+	{ 0x4000e000, 0000000000 },
+	{ 0x02400000, 0x00000004 },
+	{ 0x400ee000, 0x00000004 },
+	{ 0x02400000, 0x00000004 },
+	{ 0x4000e000, 0000000000 },
+	{ 0x000c2000, 0x00000004 },
+	{ 0x0240e51b, 0x00000004 },
+	{ 0x0080e50a, 0x00000005 },
+	{ 0x0080e50b, 0x00000005 },
+	{ 0x00220000, 0x00000004 },
+	{ 0x000700e4, 0x00000004 },
+	{ 0x000000c1, 0x00000038 },
+	{ 0x000c209a, 0x00000030 },
+	{ 0x0880e5bd, 0x00000005 },
+	{ 0x000c2099, 0x00000030 },
+	{ 0x0800e5bb, 0x00000005 },
+	{ 0x000c209a, 0x00000030 },
+	{ 0x0880e5bc, 0x00000005 },
+	{ 0x000000c4, 0x00000008 },
+	{ 0x0080e5bd, 0x00000005 },
+	{ 0x0000e5bb, 0x00000005 },
+	{ 0x0080e5bc, 0x00000005 },
+	{ 0x00210000, 0x00000004 },
+	{ 0x02800000, 0x00000004 },
+	{ 0x00c000c8, 0x00000018 },
+	{ 0x4180e000, 0x00000040 },
+	{ 0x000000ca, 0x00000024 },
+	{ 0x01000000, 0x0000000c },
+	{ 0x0100e51d, 0x0000000c },
+	{ 0x000045bb, 0x00000004 },
+	{ 0x000080c4, 0x00000008 },
+	{ 0x0000f3ce, 0x00000004 },
+	{ 0x0140a000, 0x00000004 },
+	{ 0x00cc2000, 0x00000004 },
+	{ 0x08c053cf, 0x00000040 },
+	{ 0x00008000, 0000000000 },
+	{ 0x0000f3d2, 0x00000004 },
+	{ 0x0140a000, 0x00000004 },
+	{ 0x00cc2000, 0x00000004 },
+	{ 0x08c053d3, 0x00000040 },
+	{ 0x00008000, 0000000000 },
+	{ 0x0000f39d, 0x00000004 },
+	{ 0x0140a000, 0x00000004 },
+	{ 0x00cc2000, 0x00000004 },
+	{ 0x08c0539e, 0x00000040 },
+	{ 0x00008000, 0000000000 },
+	{ 0x03c00830, 0x00000004 },
+	{ 0x4200e000, 0000000000 },
+	{ 0x0000a000, 0x00000004 },
+	{ 0x200045e0, 0x00000004 },
+	{ 0x0000e5e1, 0000000000 },
+	{ 0x00000001, 0000000000 },
+	{ 0x000700e1, 0x00000004 },
+	{ 0x0800e394, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+	{ 0000000000, 0000000000 },
+};
+
+static int RADEON_READ_PLL(drm_device_t *dev, int addr)
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+
+	RADEON_WRITE8(RADEON_CLOCK_CNTL_INDEX, addr & 0x1f);
+	return RADEON_READ(RADEON_CLOCK_CNTL_DATA);
+}
+
+#if RADEON_FIFO_DEBUG
+static void radeon_status( drm_radeon_private_t *dev_priv )
+{
+	printk( "%s:\n", __FUNCTION__ );
+	printk( "RBBM_STATUS = 0x%08x\n",
+		(unsigned int)RADEON_READ( RADEON_RBBM_STATUS ) );
+	printk( "CP_RB_RTPR = 0x%08x\n",
+		(unsigned int)RADEON_READ( RADEON_CP_RB_RPTR ) );
+	printk( "CP_RB_WTPR = 0x%08x\n",
+		(unsigned int)RADEON_READ( RADEON_CP_RB_WPTR ) );
+	printk( "AIC_CNTL = 0x%08x\n",
+		(unsigned int)RADEON_READ( RADEON_AIC_CNTL ) );
+	printk( "AIC_STAT = 0x%08x\n",
+		(unsigned int)RADEON_READ( RADEON_AIC_STAT ) );
+	printk( "AIC_PT_BASE = 0x%08x\n",
+		(unsigned int)RADEON_READ( RADEON_AIC_PT_BASE ) );
+	printk( "TLB_ADDR = 0x%08x\n",
+		(unsigned int)RADEON_READ( RADEON_AIC_TLB_ADDR ) );
+	printk( "TLB_DATA = 0x%08x\n",
+		(unsigned int)RADEON_READ( RADEON_AIC_TLB_DATA ) );
+}
+#endif
+
+
+/* ================================================================
+ * Engine, FIFO control
+ */
+
+static int radeon_do_pixcache_flush( drm_radeon_private_t *dev_priv )
+{
+	u32 tmp;
+	int i;
+
+	dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+	tmp  = RADEON_READ( RADEON_RB2D_DSTCACHE_CTLSTAT );
+	tmp |= RADEON_RB2D_DC_FLUSH_ALL;
+	RADEON_WRITE( RADEON_RB2D_DSTCACHE_CTLSTAT, tmp );
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		if ( !(RADEON_READ( RADEON_RB2D_DSTCACHE_CTLSTAT )
+		       & RADEON_RB2D_DC_BUSY) ) {
+			return 0;
+		}
+		DRM_UDELAY( 1 );
+	}
+
+#if RADEON_FIFO_DEBUG
+	DRM_ERROR( "failed!\n" );
+	radeon_status( dev_priv );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+static int radeon_do_wait_for_fifo( drm_radeon_private_t *dev_priv,
+				    int entries )
+{
+	int i;
+
+	dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		int slots = ( RADEON_READ( RADEON_RBBM_STATUS )
+			      & RADEON_RBBM_FIFOCNT_MASK );
+		if ( slots >= entries ) return 0;
+		DRM_UDELAY( 1 );
+	}
+
+#if RADEON_FIFO_DEBUG
+	DRM_ERROR( "failed!\n" );
+	radeon_status( dev_priv );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+static int radeon_do_wait_for_idle( drm_radeon_private_t *dev_priv )
+{
+	int i, ret;
+
+	dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+	ret = radeon_do_wait_for_fifo( dev_priv, 64 );
+	if ( ret ) return ret;
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		if ( !(RADEON_READ( RADEON_RBBM_STATUS )
+		       & RADEON_RBBM_ACTIVE) ) {
+			radeon_do_pixcache_flush( dev_priv );
+			return 0;
+		}
+		DRM_UDELAY( 1 );
+	}
+
+#if RADEON_FIFO_DEBUG
+	DRM_ERROR( "failed!\n" );
+	radeon_status( dev_priv );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+
+/* ================================================================
+ * CP control, initialization
+ */
+
+/* Load the microcode for the CP */
+static void radeon_cp_load_microcode( drm_radeon_private_t *dev_priv )
+{
+	int i;
+	DRM_DEBUG( "\n" );
+
+	radeon_do_wait_for_idle( dev_priv );
+
+	RADEON_WRITE( RADEON_CP_ME_RAM_ADDR, 0 );
+
+	if (dev_priv->microcode_version==UCODE_R200) {
+		DRM_INFO("Loading R200 Microcode\n");
+		for ( i = 0 ; i < 256 ; i++ ) 
+		{
+			RADEON_WRITE( RADEON_CP_ME_RAM_DATAH,
+				      R200_cp_microcode[i][1] );
+			RADEON_WRITE( RADEON_CP_ME_RAM_DATAL,
+				      R200_cp_microcode[i][0] );
+		}
+	} else if (dev_priv->microcode_version==UCODE_R300) {
+		DRM_INFO("Loading R300 Microcode\n");
+		for ( i = 0 ; i < 256 ; i++ ) 
+		{
+			RADEON_WRITE( RADEON_CP_ME_RAM_DATAH,
+				      R300_cp_microcode[i][1] );
+			RADEON_WRITE( RADEON_CP_ME_RAM_DATAL,
+				      R300_cp_microcode[i][0] );
+		}
+	} else {
+		for ( i = 0 ; i < 256 ; i++ ) {
+			RADEON_WRITE( RADEON_CP_ME_RAM_DATAH,
+				      radeon_cp_microcode[i][1] );
+			RADEON_WRITE( RADEON_CP_ME_RAM_DATAL,
+				      radeon_cp_microcode[i][0] );
+		}
+	}
+}
+
+/* Flush any pending commands to the CP.  This should only be used just
+ * prior to a wait for idle, as it informs the engine that the command
+ * stream is ending.
+ */
+static void radeon_do_cp_flush( drm_radeon_private_t *dev_priv )
+{
+	DRM_DEBUG( "\n" );
+#if 0
+	u32 tmp;
+
+	tmp = RADEON_READ( RADEON_CP_RB_WPTR ) | (1 << 31);
+	RADEON_WRITE( RADEON_CP_RB_WPTR, tmp );
+#endif
+}
+
+/* Wait for the CP to go idle.
+ */
+int radeon_do_cp_idle( drm_radeon_private_t *dev_priv )
+{
+	RING_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	BEGIN_RING( 6 );
+
+	RADEON_PURGE_CACHE();
+	RADEON_PURGE_ZCACHE();
+	RADEON_WAIT_UNTIL_IDLE();
+
+	ADVANCE_RING();
+	COMMIT_RING();
+
+	return radeon_do_wait_for_idle( dev_priv );
+}
+
+/* Start the Command Processor.
+ */
+static void radeon_do_cp_start( drm_radeon_private_t *dev_priv )
+{
+	RING_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	radeon_do_wait_for_idle( dev_priv );
+
+	RADEON_WRITE( RADEON_CP_CSQ_CNTL, dev_priv->cp_mode );
+
+	dev_priv->cp_running = 1;
+
+	BEGIN_RING( 6 );
+
+	RADEON_PURGE_CACHE();
+	RADEON_PURGE_ZCACHE();
+	RADEON_WAIT_UNTIL_IDLE();
+
+	ADVANCE_RING();
+	COMMIT_RING();
+}
+
+/* Reset the Command Processor.  This will not flush any pending
+ * commands, so you must wait for the CP command stream to complete
+ * before calling this routine.
+ */
+static void radeon_do_cp_reset( drm_radeon_private_t *dev_priv )
+{
+	u32 cur_read_ptr;
+	DRM_DEBUG( "\n" );
+
+	cur_read_ptr = RADEON_READ( RADEON_CP_RB_RPTR );
+	RADEON_WRITE( RADEON_CP_RB_WPTR, cur_read_ptr );
+	SET_RING_HEAD( dev_priv, cur_read_ptr );
+	dev_priv->ring.tail = cur_read_ptr;
+}
+
+/* Stop the Command Processor.  This will not flush any pending
+ * commands, so you must flush the command stream and wait for the CP
+ * to go idle before calling this routine.
+ */
+static void radeon_do_cp_stop( drm_radeon_private_t *dev_priv )
+{
+	DRM_DEBUG( "\n" );
+
+	RADEON_WRITE( RADEON_CP_CSQ_CNTL, RADEON_CSQ_PRIDIS_INDDIS );
+
+	dev_priv->cp_running = 0;
+}
+
+/* Reset the engine.  This will stop the CP if it is running.
+ */
+static int radeon_do_engine_reset( drm_device_t *dev )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	u32 clock_cntl_index, mclk_cntl, rbbm_soft_reset;
+	DRM_DEBUG( "\n" );
+
+	radeon_do_pixcache_flush( dev_priv );
+
+	clock_cntl_index = RADEON_READ( RADEON_CLOCK_CNTL_INDEX );
+	mclk_cntl = RADEON_READ_PLL( dev, RADEON_MCLK_CNTL );
+
+	RADEON_WRITE_PLL( RADEON_MCLK_CNTL, ( mclk_cntl |
+					      RADEON_FORCEON_MCLKA |
+					      RADEON_FORCEON_MCLKB |
+ 					      RADEON_FORCEON_YCLKA |
+					      RADEON_FORCEON_YCLKB |
+					      RADEON_FORCEON_MC |
+					      RADEON_FORCEON_AIC ) );
+
+	rbbm_soft_reset = RADEON_READ( RADEON_RBBM_SOFT_RESET );
+
+	RADEON_WRITE( RADEON_RBBM_SOFT_RESET, ( rbbm_soft_reset |
+						RADEON_SOFT_RESET_CP |
+						RADEON_SOFT_RESET_HI |
+						RADEON_SOFT_RESET_SE |
+						RADEON_SOFT_RESET_RE |
+						RADEON_SOFT_RESET_PP |
+						RADEON_SOFT_RESET_E2 |
+						RADEON_SOFT_RESET_RB ) );
+	RADEON_READ( RADEON_RBBM_SOFT_RESET );
+	RADEON_WRITE( RADEON_RBBM_SOFT_RESET, ( rbbm_soft_reset &
+						~( RADEON_SOFT_RESET_CP |
+						   RADEON_SOFT_RESET_HI |
+						   RADEON_SOFT_RESET_SE |
+						   RADEON_SOFT_RESET_RE |
+						   RADEON_SOFT_RESET_PP |
+						   RADEON_SOFT_RESET_E2 |
+						   RADEON_SOFT_RESET_RB ) ) );
+	RADEON_READ( RADEON_RBBM_SOFT_RESET );
+
+
+	RADEON_WRITE_PLL( RADEON_MCLK_CNTL, mclk_cntl );
+	RADEON_WRITE( RADEON_CLOCK_CNTL_INDEX, clock_cntl_index );
+	RADEON_WRITE( RADEON_RBBM_SOFT_RESET,  rbbm_soft_reset );
+
+	/* Reset the CP ring */
+	radeon_do_cp_reset( dev_priv );
+
+	/* The CP is no longer running after an engine reset */
+	dev_priv->cp_running = 0;
+
+	/* Reset any pending vertex, indirect buffers */
+	radeon_freelist_reset( dev );
+
+	return 0;
+}
+
+static void radeon_cp_init_ring_buffer( drm_device_t *dev,
+				        drm_radeon_private_t *dev_priv )
+{
+	u32 ring_start, cur_read_ptr;
+	u32 tmp;
+
+	/* Initialize the memory controller */
+	RADEON_WRITE( RADEON_MC_FB_LOCATION,
+		      ( ( dev_priv->gart_vm_start - 1 ) & 0xffff0000 )
+		    | ( dev_priv->fb_location >> 16 ) );
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci ) {
+		RADEON_WRITE( RADEON_MC_AGP_LOCATION,
+			      (((dev_priv->gart_vm_start - 1 +
+				 dev_priv->gart_size) & 0xffff0000) |
+			       (dev_priv->gart_vm_start >> 16)) );
+
+		ring_start = (dev_priv->cp_ring->offset
+			      - dev->agp->base
+			      + dev_priv->gart_vm_start);
+       } else
+#endif
+		ring_start = (dev_priv->cp_ring->offset
+			      - dev->sg->handle
+			      + dev_priv->gart_vm_start);
+
+	RADEON_WRITE( RADEON_CP_RB_BASE, ring_start );
+
+	/* Set the write pointer delay */
+	RADEON_WRITE( RADEON_CP_RB_WPTR_DELAY, 0 );
+
+	/* Initialize the ring buffer's read and write pointers */
+	cur_read_ptr = RADEON_READ( RADEON_CP_RB_RPTR );
+	RADEON_WRITE( RADEON_CP_RB_WPTR, cur_read_ptr );
+	SET_RING_HEAD( dev_priv, cur_read_ptr );
+	dev_priv->ring.tail = cur_read_ptr;
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci ) {
+		RADEON_WRITE( RADEON_CP_RB_RPTR_ADDR,
+			      dev_priv->ring_rptr->offset
+			      - dev->agp->base
+			      + dev_priv->gart_vm_start);
+	} else
+#endif
+	{
+		drm_sg_mem_t *entry = dev->sg;
+		unsigned long tmp_ofs, page_ofs;
+
+		tmp_ofs = dev_priv->ring_rptr->offset - dev->sg->handle;
+		page_ofs = tmp_ofs >> PAGE_SHIFT;
+
+		RADEON_WRITE( RADEON_CP_RB_RPTR_ADDR,
+			     entry->busaddr[page_ofs]);
+		DRM_DEBUG( "ring rptr: offset=0x%08lx handle=0x%08lx\n",
+			   (unsigned long) entry->busaddr[page_ofs],
+			   entry->handle + tmp_ofs );
+	}
+
+	/* Initialize the scratch register pointer.  This will cause
+	 * the scratch register values to be written out to memory
+	 * whenever they are updated.
+	 *
+	 * We simply put this behind the ring read pointer, this works
+	 * with PCI GART as well as (whatever kind of) AGP GART
+	 */
+	RADEON_WRITE( RADEON_SCRATCH_ADDR, RADEON_READ( RADEON_CP_RB_RPTR_ADDR )
+					 + RADEON_SCRATCH_REG_OFFSET );
+
+	dev_priv->scratch = ((__volatile__ u32 *)
+			     dev_priv->ring_rptr->handle +
+			     (RADEON_SCRATCH_REG_OFFSET / sizeof(u32)));
+
+	RADEON_WRITE( RADEON_SCRATCH_UMSK, 0x7 );
+
+	/* Writeback doesn't seem to work everywhere, test it first */
+	DRM_WRITE32( dev_priv->ring_rptr, RADEON_SCRATCHOFF(1), 0 );
+	RADEON_WRITE( RADEON_SCRATCH_REG1, 0xdeadbeef );
+
+	for ( tmp = 0 ; tmp < dev_priv->usec_timeout ; tmp++ ) {
+		if ( DRM_READ32( dev_priv->ring_rptr, RADEON_SCRATCHOFF(1) ) == 0xdeadbeef )
+			break;
+		DRM_UDELAY( 1 );
+	}
+
+	if ( tmp < dev_priv->usec_timeout ) {
+		dev_priv->writeback_works = 1;
+		DRM_DEBUG( "writeback test succeeded, tmp=%d\n", tmp );
+	} else {
+		dev_priv->writeback_works = 0;
+		DRM_DEBUG( "writeback test failed\n" );
+	}
+
+	dev_priv->sarea_priv->last_frame = dev_priv->scratch[0] = 0;
+	RADEON_WRITE( RADEON_LAST_FRAME_REG,
+		      dev_priv->sarea_priv->last_frame );
+
+	dev_priv->sarea_priv->last_dispatch = dev_priv->scratch[1] = 0;
+	RADEON_WRITE( RADEON_LAST_DISPATCH_REG,
+		      dev_priv->sarea_priv->last_dispatch );
+
+	dev_priv->sarea_priv->last_clear = dev_priv->scratch[2] = 0;
+	RADEON_WRITE( RADEON_LAST_CLEAR_REG,
+		      dev_priv->sarea_priv->last_clear );
+
+	/* Set ring buffer size */
+#ifdef __BIG_ENDIAN
+	RADEON_WRITE( RADEON_CP_RB_CNTL, dev_priv->ring.size_l2qw | RADEON_BUF_SWAP_32BIT );
+#else
+	RADEON_WRITE( RADEON_CP_RB_CNTL, dev_priv->ring.size_l2qw );
+#endif
+
+	radeon_do_wait_for_idle( dev_priv );
+
+	/* Turn on bus mastering */
+	tmp = RADEON_READ( RADEON_BUS_CNTL ) & ~RADEON_BUS_MASTER_DIS;
+	RADEON_WRITE( RADEON_BUS_CNTL, tmp );
+
+	/* Sync everything up */
+	RADEON_WRITE( RADEON_ISYNC_CNTL,
+		      (RADEON_ISYNC_ANY2D_IDLE3D |
+		       RADEON_ISYNC_ANY3D_IDLE2D |
+		       RADEON_ISYNC_WAIT_IDLEGUI |
+		       RADEON_ISYNC_CPSCRATCH_IDLEGUI) );
+}
+
+/* Enable or disable PCI GART on the chip */
+static void radeon_set_pcigart( drm_radeon_private_t *dev_priv, int on )
+{
+	u32 tmp	= RADEON_READ( RADEON_AIC_CNTL );
+
+	if ( on ) {
+		RADEON_WRITE( RADEON_AIC_CNTL, tmp | RADEON_PCIGART_TRANSLATE_EN );
+
+		/* set PCI GART page-table base address
+		 */
+		RADEON_WRITE( RADEON_AIC_PT_BASE, dev_priv->bus_pci_gart );
+
+		/* set address range for PCI address translate
+		 */
+		RADEON_WRITE( RADEON_AIC_LO_ADDR, dev_priv->gart_vm_start );
+		RADEON_WRITE( RADEON_AIC_HI_ADDR, dev_priv->gart_vm_start
+						  + dev_priv->gart_size - 1);
+
+		/* Turn off AGP aperture -- is this required for PCI GART?
+		 */
+		RADEON_WRITE( RADEON_MC_AGP_LOCATION, 0xffffffc0 ); /* ?? */
+		RADEON_WRITE( RADEON_AGP_COMMAND, 0 ); /* clear AGP_COMMAND */
+	} else {
+		RADEON_WRITE( RADEON_AIC_CNTL, tmp & ~RADEON_PCIGART_TRANSLATE_EN );
+	}
+}
+
+static int radeon_do_init_cp( drm_device_t *dev, drm_radeon_init_t *init )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;;
+	DRM_DEBUG( "\n" );
+
+	dev_priv->is_pci = init->is_pci;
+
+	if ( dev_priv->is_pci && !dev->sg ) {
+		DRM_ERROR( "PCI GART memory not allocated!\n" );
+		dev->dev_private = (void *)dev_priv;
+		radeon_do_cleanup_cp(dev);
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->usec_timeout = init->usec_timeout;
+	if ( dev_priv->usec_timeout < 1 ||
+	     dev_priv->usec_timeout > RADEON_MAX_USEC_TIMEOUT ) {
+		DRM_DEBUG( "TIMEOUT problem!\n" );
+		dev->dev_private = (void *)dev_priv;
+		radeon_do_cleanup_cp(dev);
+		return DRM_ERR(EINVAL);
+	}
+
+	switch(init->func) {
+	case RADEON_INIT_R200_CP:
+		dev_priv->microcode_version=UCODE_R200;
+		break;
+	case RADEON_INIT_R300_CP:
+		dev_priv->microcode_version=UCODE_R300;
+		break;
+	default:
+		dev_priv->microcode_version=UCODE_R100;
+	}
+	
+	dev_priv->do_boxes = 0;
+	dev_priv->cp_mode = init->cp_mode;
+
+	/* We don't support anything other than bus-mastering ring mode,
+	 * but the ring can be in either AGP or PCI space for the ring
+	 * read pointer.
+	 */
+	if ( ( init->cp_mode != RADEON_CSQ_PRIBM_INDDIS ) &&
+	     ( init->cp_mode != RADEON_CSQ_PRIBM_INDBM ) ) {
+		DRM_DEBUG( "BAD cp_mode (%x)!\n", init->cp_mode );
+		dev->dev_private = (void *)dev_priv;
+		radeon_do_cleanup_cp(dev);
+		return DRM_ERR(EINVAL);
+	}
+
+	switch ( init->fb_bpp ) {
+	case 16:
+		dev_priv->color_fmt = RADEON_COLOR_FORMAT_RGB565;
+		break;
+	case 32:
+	default:
+		dev_priv->color_fmt = RADEON_COLOR_FORMAT_ARGB8888;
+		break;
+	}
+	dev_priv->front_offset	= init->front_offset;
+	dev_priv->front_pitch	= init->front_pitch;
+	dev_priv->back_offset	= init->back_offset;
+	dev_priv->back_pitch	= init->back_pitch;
+
+	switch ( init->depth_bpp ) {
+	case 16:
+		dev_priv->depth_fmt = RADEON_DEPTH_FORMAT_16BIT_INT_Z;
+		break;
+	case 32:
+	default:
+		dev_priv->depth_fmt = RADEON_DEPTH_FORMAT_24BIT_INT_Z;
+		break;
+	}
+	dev_priv->depth_offset	= init->depth_offset;
+	dev_priv->depth_pitch	= init->depth_pitch;
+
+	/* Hardware state for depth clears.  Remove this if/when we no
+	 * longer clear the depth buffer with a 3D rectangle.  Hard-code
+	 * all values to prevent unwanted 3D state from slipping through
+	 * and screwing with the clear operation.
+	 */
+	dev_priv->depth_clear.rb3d_cntl = (RADEON_PLANE_MASK_ENABLE |
+					   (dev_priv->color_fmt << 10) |
+					   (dev_priv->microcode_version == UCODE_R100 ? RADEON_ZBLOCK16 : 0));
+
+	dev_priv->depth_clear.rb3d_zstencilcntl = 
+		(dev_priv->depth_fmt |
+		 RADEON_Z_TEST_ALWAYS |
+		 RADEON_STENCIL_TEST_ALWAYS |
+		 RADEON_STENCIL_S_FAIL_REPLACE |
+		 RADEON_STENCIL_ZPASS_REPLACE |
+		 RADEON_STENCIL_ZFAIL_REPLACE |
+		 RADEON_Z_WRITE_ENABLE);
+
+	dev_priv->depth_clear.se_cntl = (RADEON_FFACE_CULL_CW |
+					 RADEON_BFACE_SOLID |
+					 RADEON_FFACE_SOLID |
+					 RADEON_FLAT_SHADE_VTX_LAST |
+					 RADEON_DIFFUSE_SHADE_FLAT |
+					 RADEON_ALPHA_SHADE_FLAT |
+					 RADEON_SPECULAR_SHADE_FLAT |
+					 RADEON_FOG_SHADE_FLAT |
+					 RADEON_VTX_PIX_CENTER_OGL |
+					 RADEON_ROUND_MODE_TRUNC |
+					 RADEON_ROUND_PREC_8TH_PIX);
+
+	DRM_GETSAREA();
+
+	dev_priv->fb_offset = init->fb_offset;
+	dev_priv->mmio_offset = init->mmio_offset;
+	dev_priv->ring_offset = init->ring_offset;
+	dev_priv->ring_rptr_offset = init->ring_rptr_offset;
+	dev_priv->buffers_offset = init->buffers_offset;
+	dev_priv->gart_textures_offset = init->gart_textures_offset;
+	
+	if(!dev_priv->sarea) {
+		DRM_ERROR("could not find sarea!\n");
+		dev->dev_private = (void *)dev_priv;
+		radeon_do_cleanup_cp(dev);
+		return DRM_ERR(EINVAL);
+	}
+
+	dev_priv->mmio = drm_core_findmap(dev, init->mmio_offset);
+	if(!dev_priv->mmio) {
+		DRM_ERROR("could not find mmio region!\n");
+		dev->dev_private = (void *)dev_priv;
+		radeon_do_cleanup_cp(dev);
+		return DRM_ERR(EINVAL);
+	}
+	dev_priv->cp_ring = drm_core_findmap(dev, init->ring_offset);
+	if(!dev_priv->cp_ring) {
+		DRM_ERROR("could not find cp ring region!\n");
+		dev->dev_private = (void *)dev_priv;
+		radeon_do_cleanup_cp(dev);
+		return DRM_ERR(EINVAL);
+	}
+	dev_priv->ring_rptr = drm_core_findmap(dev, init->ring_rptr_offset);
+	if(!dev_priv->ring_rptr) {
+		DRM_ERROR("could not find ring read pointer!\n");
+		dev->dev_private = (void *)dev_priv;
+		radeon_do_cleanup_cp(dev);
+		return DRM_ERR(EINVAL);
+	}
+	dev->agp_buffer_map = drm_core_findmap(dev, init->buffers_offset);
+	if(!dev->agp_buffer_map) {
+		DRM_ERROR("could not find dma buffer region!\n");
+		dev->dev_private = (void *)dev_priv;
+		radeon_do_cleanup_cp(dev);
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( init->gart_textures_offset ) {
+		dev_priv->gart_textures = drm_core_findmap(dev, init->gart_textures_offset);
+		if ( !dev_priv->gart_textures ) {
+			DRM_ERROR("could not find GART texture region!\n");
+			dev->dev_private = (void *)dev_priv;
+			radeon_do_cleanup_cp(dev);
+			return DRM_ERR(EINVAL);
+		}
+	}
+
+	dev_priv->sarea_priv =
+		(drm_radeon_sarea_t *)((u8 *)dev_priv->sarea->handle +
+				       init->sarea_priv_offset);
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci ) {
+		drm_core_ioremap( dev_priv->cp_ring, dev );
+		drm_core_ioremap( dev_priv->ring_rptr, dev );
+		drm_core_ioremap( dev->agp_buffer_map, dev );
+		if(!dev_priv->cp_ring->handle ||
+		   !dev_priv->ring_rptr->handle ||
+		   !dev->agp_buffer_map->handle) {
+			DRM_ERROR("could not find ioremap agp regions!\n");
+			dev->dev_private = (void *)dev_priv;
+			radeon_do_cleanup_cp(dev);
+			return DRM_ERR(EINVAL);
+		}
+	} else
+#endif
+	{
+		dev_priv->cp_ring->handle =
+			(void *)dev_priv->cp_ring->offset;
+		dev_priv->ring_rptr->handle =
+			(void *)dev_priv->ring_rptr->offset;
+		dev->agp_buffer_map->handle = (void *)dev->agp_buffer_map->offset;
+
+		DRM_DEBUG( "dev_priv->cp_ring->handle %p\n",
+			   dev_priv->cp_ring->handle );
+		DRM_DEBUG( "dev_priv->ring_rptr->handle %p\n",
+			   dev_priv->ring_rptr->handle );
+		DRM_DEBUG( "dev->agp_buffer_map->handle %p\n",
+			   dev->agp_buffer_map->handle );
+	}
+
+	dev_priv->fb_location = ( RADEON_READ( RADEON_MC_FB_LOCATION )
+				& 0xffff ) << 16;
+
+	dev_priv->front_pitch_offset = (((dev_priv->front_pitch/64) << 22) |
+					( ( dev_priv->front_offset
+					  + dev_priv->fb_location ) >> 10 ) );
+
+	dev_priv->back_pitch_offset = (((dev_priv->back_pitch/64) << 22) |
+				       ( ( dev_priv->back_offset
+					 + dev_priv->fb_location ) >> 10 ) );
+
+	dev_priv->depth_pitch_offset = (((dev_priv->depth_pitch/64) << 22) |
+					( ( dev_priv->depth_offset
+					  + dev_priv->fb_location ) >> 10 ) );
+
+
+	dev_priv->gart_size = init->gart_size;
+	dev_priv->gart_vm_start = dev_priv->fb_location
+				+ RADEON_READ( RADEON_CONFIG_APER_SIZE );
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci )
+		dev_priv->gart_buffers_offset = (dev->agp_buffer_map->offset
+						- dev->agp->base
+						+ dev_priv->gart_vm_start);
+	else
+#endif
+		dev_priv->gart_buffers_offset = (dev->agp_buffer_map->offset
+						- dev->sg->handle
+						+ dev_priv->gart_vm_start);
+
+	DRM_DEBUG( "dev_priv->gart_size %d\n",
+		   dev_priv->gart_size );
+	DRM_DEBUG( "dev_priv->gart_vm_start 0x%x\n",
+		   dev_priv->gart_vm_start );
+	DRM_DEBUG( "dev_priv->gart_buffers_offset 0x%lx\n",
+		   dev_priv->gart_buffers_offset );
+
+	dev_priv->ring.start = (u32 *)dev_priv->cp_ring->handle;
+	dev_priv->ring.end = ((u32 *)dev_priv->cp_ring->handle
+			      + init->ring_size / sizeof(u32));
+	dev_priv->ring.size = init->ring_size;
+	dev_priv->ring.size_l2qw = drm_order( init->ring_size / 8 );
+
+	dev_priv->ring.tail_mask =
+		(dev_priv->ring.size / sizeof(u32)) - 1;
+
+	dev_priv->ring.high_mark = RADEON_RING_HIGH_MARK;
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci ) {
+		/* Turn off PCI GART */
+		radeon_set_pcigart( dev_priv, 0 );
+	} else
+#endif
+	{
+		if (!drm_ati_pcigart_init( dev, &dev_priv->phys_pci_gart,
+					    &dev_priv->bus_pci_gart)) {
+			DRM_ERROR( "failed to init PCI GART!\n" );
+			dev->dev_private = (void *)dev_priv;
+			radeon_do_cleanup_cp(dev);
+			return DRM_ERR(ENOMEM);
+		}
+
+		/* Turn on PCI GART */
+		radeon_set_pcigart( dev_priv, 1 );
+	}
+
+	radeon_cp_load_microcode( dev_priv );
+	radeon_cp_init_ring_buffer( dev, dev_priv );
+
+	dev_priv->last_buf = 0;
+
+	dev->dev_private = (void *)dev_priv;
+
+	radeon_do_engine_reset( dev );
+
+	return 0;
+}
+
+static int radeon_do_cleanup_cp( drm_device_t *dev )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	/* Make sure interrupts are disabled here because the uninstall ioctl
+	 * may not have been called from userspace and after dev_private
+	 * is freed, it's too late.
+	 */
+	if ( dev->irq_enabled ) drm_irq_uninstall(dev);
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci ) {
+		if ( dev_priv->cp_ring != NULL )
+			drm_core_ioremapfree( dev_priv->cp_ring, dev );
+		if ( dev_priv->ring_rptr != NULL )
+			drm_core_ioremapfree( dev_priv->ring_rptr, dev );
+		if ( dev->agp_buffer_map != NULL )
+		{
+			drm_core_ioremapfree( dev->agp_buffer_map, dev );
+			dev->agp_buffer_map = NULL;
+		}
+	} else
+#endif
+	{
+		if (!drm_ati_pcigart_cleanup( dev,
+					      dev_priv->phys_pci_gart,
+					      dev_priv->bus_pci_gart ))
+			DRM_ERROR( "failed to cleanup PCI GART!\n" );
+	}
+	
+	/* only clear to the start of flags */
+	memset(dev_priv, 0, offsetof(drm_radeon_private_t, flags));
+
+	return 0;
+}
+
+/* This code will reinit the Radeon CP hardware after a resume from disc.  
+ * AFAIK, it would be very difficult to pickle the state at suspend time, so 
+ * here we make sure that all Radeon hardware initialisation is re-done without
+ * affecting running applications.
+ *
+ * Charl P. Botha <http://cpbotha.net>
+ */
+static int radeon_do_resume_cp( drm_device_t *dev )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "Called with no initialization\n" );
+		return DRM_ERR( EINVAL );
+	}
+
+	DRM_DEBUG("Starting radeon_do_resume_cp()\n");
+
+#if __OS_HAS_AGP
+	if ( !dev_priv->is_pci ) {
+		/* Turn off PCI GART */
+		radeon_set_pcigart( dev_priv, 0 );
+	} else
+#endif
+	{
+		/* Turn on PCI GART */
+		radeon_set_pcigart( dev_priv, 1 );
+	}
+
+	radeon_cp_load_microcode( dev_priv );
+	radeon_cp_init_ring_buffer( dev, dev_priv );
+
+	radeon_do_engine_reset( dev );
+
+	DRM_DEBUG("radeon_do_resume_cp() complete\n");
+
+	return 0;
+}
+
+
+int radeon_cp_init( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_init_t init;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( init, (drm_radeon_init_t __user *)data, sizeof(init) );
+
+	switch ( init.func ) {
+	case RADEON_INIT_CP:
+	case RADEON_INIT_R200_CP:
+	case RADEON_INIT_R300_CP:
+		return radeon_do_init_cp( dev, &init );
+	case RADEON_CLEANUP_CP:
+		return radeon_do_cleanup_cp( dev );
+	}
+
+	return DRM_ERR(EINVAL);
+}
+
+int radeon_cp_start( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( dev_priv->cp_running ) {
+		DRM_DEBUG( "%s while CP running\n", __FUNCTION__ );
+		return 0;
+	}
+	if ( dev_priv->cp_mode == RADEON_CSQ_PRIDIS_INDDIS ) {
+		DRM_DEBUG( "%s called with bogus CP mode (%d)\n",
+			   __FUNCTION__, dev_priv->cp_mode );
+		return 0;
+	}
+
+	radeon_do_cp_start( dev_priv );
+
+	return 0;
+}
+
+/* Stop the CP.  The engine must have been idled before calling this
+ * routine.
+ */
+int radeon_cp_stop( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_cp_stop_t stop;
+	int ret;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( stop, (drm_radeon_cp_stop_t __user *)data, sizeof(stop) );
+
+	if (!dev_priv->cp_running)
+		return 0;
+
+	/* Flush any pending CP commands.  This ensures any outstanding
+	 * commands are exectuted by the engine before we turn it off.
+	 */
+	if ( stop.flush ) {
+		radeon_do_cp_flush( dev_priv );
+	}
+
+	/* If we fail to make the engine go idle, we return an error
+	 * code so that the DRM ioctl wrapper can try again.
+	 */
+	if ( stop.idle ) {
+		ret = radeon_do_cp_idle( dev_priv );
+		if ( ret ) return ret;
+	}
+
+	/* Finally, we can turn off the CP.  If the engine isn't idle,
+	 * we will get some dropped triangles as they won't be fully
+	 * rendered before the CP is shut down.
+	 */
+	radeon_do_cp_stop( dev_priv );
+
+	/* Reset the engine */
+	radeon_do_engine_reset( dev );
+
+	return 0;
+}
+
+
+void radeon_do_release( drm_device_t *dev )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	int i, ret;
+
+	if (dev_priv) {
+		if (dev_priv->cp_running) {
+			/* Stop the cp */
+			while ((ret = radeon_do_cp_idle( dev_priv )) != 0) {
+				DRM_DEBUG("radeon_do_cp_idle %d\n", ret);
+#ifdef __linux__
+				schedule();
+#else
+				tsleep(&ret, PZERO, "rdnrel", 1);
+#endif
+			}
+			radeon_do_cp_stop( dev_priv );
+			radeon_do_engine_reset( dev );
+		}
+
+		/* Disable *all* interrupts */
+		if (dev_priv->mmio)	/* remove this after permanent addmaps */
+			RADEON_WRITE( RADEON_GEN_INT_CNTL, 0 );
+
+		if (dev_priv->mmio) {/* remove all surfaces */
+			for (i = 0; i < RADEON_MAX_SURFACES; i++) {
+				RADEON_WRITE(RADEON_SURFACE0_INFO + 16*i, 0);
+				RADEON_WRITE(RADEON_SURFACE0_LOWER_BOUND + 16*i, 0);
+				RADEON_WRITE(RADEON_SURFACE0_UPPER_BOUND + 16*i, 0);
+			}
+		}
+
+		/* Free memory heap structures */
+		radeon_mem_takedown( &(dev_priv->gart_heap) );
+		radeon_mem_takedown( &(dev_priv->fb_heap) );
+
+		/* deallocate kernel resources */
+		radeon_do_cleanup_cp( dev );
+	}
+}
+
+/* Just reset the CP ring.  Called as part of an X Server engine reset.
+ */
+int radeon_cp_reset( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_DEBUG( "%s called before init done\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	radeon_do_cp_reset( dev_priv );
+
+	/* The CP is no longer running after an engine reset */
+	dev_priv->cp_running = 0;
+
+	return 0;
+}
+
+int radeon_cp_idle( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	return radeon_do_cp_idle( dev_priv );
+}
+
+/* Added by Charl P. Botha to call radeon_do_resume_cp().
+ */
+int radeon_cp_resume( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+
+	return radeon_do_resume_cp(dev);
+}
+
+
+int radeon_engine_reset( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	return radeon_do_engine_reset( dev );
+}
+
+
+/* ================================================================
+ * Fullscreen mode
+ */
+
+/* KW: Deprecated to say the least:
+ */
+int radeon_fullscreen( DRM_IOCTL_ARGS )
+{
+	return 0;
+}
+
+
+/* ================================================================
+ * Freelist management
+ */
+
+/* Original comment: FIXME: ROTATE_BUFS is a hack to cycle through
+ *   bufs until freelist code is used.  Note this hides a problem with
+ *   the scratch register * (used to keep track of last buffer
+ *   completed) being written to before * the last buffer has actually
+ *   completed rendering.  
+ *
+ * KW:  It's also a good way to find free buffers quickly.
+ *
+ * KW: Ideally this loop wouldn't exist, and freelist_get wouldn't
+ * sleep.  However, bugs in older versions of radeon_accel.c mean that
+ * we essentially have to do this, else old clients will break.
+ * 
+ * However, it does leave open a potential deadlock where all the
+ * buffers are held by other clients, which can't release them because
+ * they can't get the lock.  
+ */
+
+drm_buf_t *radeon_freelist_get( drm_device_t *dev )
+{
+	drm_device_dma_t *dma = dev->dma;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_buf_priv_t *buf_priv;
+	drm_buf_t *buf;
+	int i, t;
+	int start;
+
+	if ( ++dev_priv->last_buf >= dma->buf_count )
+		dev_priv->last_buf = 0;
+
+	start = dev_priv->last_buf;
+
+	for ( t = 0 ; t < dev_priv->usec_timeout ; t++ ) {
+		u32 done_age = GET_SCRATCH( 1 );
+		DRM_DEBUG("done_age = %d\n",done_age);
+		for ( i = start ; i < dma->buf_count ; i++ ) {
+			buf = dma->buflist[i];
+			buf_priv = buf->dev_private;
+			if ( buf->filp == 0 || (buf->pending && 
+					       buf_priv->age <= done_age) ) {
+				dev_priv->stats.requested_bufs++;
+				buf->pending = 0;
+				return buf;
+			}
+			start = 0;
+		}
+
+		if (t) {
+			DRM_UDELAY( 1 );
+			dev_priv->stats.freelist_loops++;
+		}
+	}
+
+	DRM_DEBUG( "returning NULL!\n" );
+	return NULL;
+}
+#if 0
+drm_buf_t *radeon_freelist_get( drm_device_t *dev )
+{
+	drm_device_dma_t *dma = dev->dma;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_buf_priv_t *buf_priv;
+	drm_buf_t *buf;
+	int i, t;
+	int start;
+	u32 done_age = DRM_READ32(dev_priv->ring_rptr, RADEON_SCRATCHOFF(1));
+
+	if ( ++dev_priv->last_buf >= dma->buf_count )
+		dev_priv->last_buf = 0;
+
+	start = dev_priv->last_buf;
+	dev_priv->stats.freelist_loops++;
+	
+	for ( t = 0 ; t < 2 ; t++ ) {
+		for ( i = start ; i < dma->buf_count ; i++ ) {
+			buf = dma->buflist[i];
+			buf_priv = buf->dev_private;
+			if ( buf->filp == 0 || (buf->pending && 
+					       buf_priv->age <= done_age) ) {
+				dev_priv->stats.requested_bufs++;
+				buf->pending = 0;
+				return buf;
+			}
+		}
+		start = 0;
+	}
+
+	return NULL;
+}
+#endif
+
+void radeon_freelist_reset( drm_device_t *dev )
+{
+	drm_device_dma_t *dma = dev->dma;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	int i;
+
+	dev_priv->last_buf = 0;
+	for ( i = 0 ; i < dma->buf_count ; i++ ) {
+		drm_buf_t *buf = dma->buflist[i];
+		drm_radeon_buf_priv_t *buf_priv = buf->dev_private;
+		buf_priv->age = 0;
+	}
+}
+
+
+/* ================================================================
+ * CP command submission
+ */
+
+int radeon_wait_ring( drm_radeon_private_t *dev_priv, int n )
+{
+	drm_radeon_ring_buffer_t *ring = &dev_priv->ring;
+	int i;
+	u32 last_head = GET_RING_HEAD( dev_priv );
+
+	for ( i = 0 ; i < dev_priv->usec_timeout ; i++ ) {
+		u32 head = GET_RING_HEAD( dev_priv );
+
+		ring->space = (head - ring->tail) * sizeof(u32);
+		if ( ring->space <= 0 )
+			ring->space += ring->size;
+		if ( ring->space > n )
+			return 0;
+		
+		dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+		if (head != last_head)
+			i = 0;
+		last_head = head;
+
+		DRM_UDELAY( 1 );
+	}
+
+	/* FIXME: This return value is ignored in the BEGIN_RING macro! */
+#if RADEON_FIFO_DEBUG
+	radeon_status( dev_priv );
+	DRM_ERROR( "failed!\n" );
+#endif
+	return DRM_ERR(EBUSY);
+}
+
+static int radeon_cp_get_buffers( DRMFILE filp, drm_device_t *dev, drm_dma_t *d )
+{
+	int i;
+	drm_buf_t *buf;
+
+	for ( i = d->granted_count ; i < d->request_count ; i++ ) {
+		buf = radeon_freelist_get( dev );
+		if ( !buf ) return DRM_ERR(EBUSY); /* NOTE: broken client */
+
+		buf->filp = filp;
+
+		if ( DRM_COPY_TO_USER( &d->request_indices[i], &buf->idx,
+				   sizeof(buf->idx) ) )
+			return DRM_ERR(EFAULT);
+		if ( DRM_COPY_TO_USER( &d->request_sizes[i], &buf->total,
+				   sizeof(buf->total) ) )
+			return DRM_ERR(EFAULT);
+
+		d->granted_count++;
+	}
+	return 0;
+}
+
+int radeon_cp_buffers( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_device_dma_t *dma = dev->dma;
+	int ret = 0;
+	drm_dma_t __user *argp = (void __user *)data;
+	drm_dma_t d;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( d, argp, sizeof(d) );
+
+	/* Please don't send us buffers.
+	 */
+	if ( d.send_count != 0 ) {
+		DRM_ERROR( "Process %d trying to send %d buffers via drmDMA\n",
+			   DRM_CURRENTPID, d.send_count );
+		return DRM_ERR(EINVAL);
+	}
+
+	/* We'll send you buffers.
+	 */
+	if ( d.request_count < 0 || d.request_count > dma->buf_count ) {
+		DRM_ERROR( "Process %d trying to get %d buffers (of %d max)\n",
+			   DRM_CURRENTPID, d.request_count, dma->buf_count );
+		return DRM_ERR(EINVAL);
+	}
+
+	d.granted_count = 0;
+
+	if ( d.request_count ) {
+		ret = radeon_cp_get_buffers( filp, dev, &d );
+	}
+
+	DRM_COPY_TO_USER_IOCTL( argp, d, sizeof(d) );
+
+	return ret;
+}
+
+int radeon_driver_preinit(struct drm_device *dev, unsigned long flags)
+{
+	drm_radeon_private_t *dev_priv;
+	int ret = 0;
+
+	dev_priv = drm_alloc(sizeof(drm_radeon_private_t), DRM_MEM_DRIVER);
+	if (dev_priv == NULL)
+		return DRM_ERR(ENOMEM);
+
+	memset(dev_priv, 0, sizeof(drm_radeon_private_t));
+	dev->dev_private = (void *)dev_priv;
+	dev_priv->flags = flags;
+
+	switch (flags & CHIP_FAMILY_MASK) {
+	case CHIP_R100:
+	case CHIP_RV200:
+	case CHIP_R200:
+	case CHIP_R300:
+		dev_priv->flags |= CHIP_HAS_HIERZ;
+		break;
+	default:
+	/* all other chips have no hierarchical z buffer */
+		break;
+	}
+	return ret;
+}
+
+int radeon_driver_postcleanup(struct drm_device *dev)
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+
+	DRM_DEBUG("\n");
+
+	drm_free(dev_priv, sizeof(*dev_priv), DRM_MEM_DRIVER);
+
+	dev->dev_private = NULL;
+	return 0;
+}
diff --git a/drivers/char/drm/radeon_drm.h b/drivers/char/drm/radeon_drm.h
new file mode 100644
index 0000000..c1e62d0
--- /dev/null
+++ b/drivers/char/drm/radeon_drm.h
@@ -0,0 +1,659 @@
+/* radeon_drm.h -- Public header for the radeon driver -*- linux-c -*-
+ *
+ * Copyright 2000 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Fremont, California.
+ * Copyright 2002 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Kevin E. Martin <martin@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ */
+
+#ifndef __RADEON_DRM_H__
+#define __RADEON_DRM_H__
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the X server file (radeon_sarea.h)
+ */
+#ifndef __RADEON_SAREA_DEFINES__
+#define __RADEON_SAREA_DEFINES__
+
+/* Old style state flags, required for sarea interface (1.1 and 1.2
+ * clears) and 1.2 drm_vertex2 ioctl.
+ */
+#define RADEON_UPLOAD_CONTEXT		0x00000001
+#define RADEON_UPLOAD_VERTFMT		0x00000002
+#define RADEON_UPLOAD_LINE		0x00000004
+#define RADEON_UPLOAD_BUMPMAP		0x00000008
+#define RADEON_UPLOAD_MASKS		0x00000010
+#define RADEON_UPLOAD_VIEWPORT		0x00000020
+#define RADEON_UPLOAD_SETUP		0x00000040
+#define RADEON_UPLOAD_TCL		0x00000080
+#define RADEON_UPLOAD_MISC		0x00000100
+#define RADEON_UPLOAD_TEX0		0x00000200
+#define RADEON_UPLOAD_TEX1		0x00000400
+#define RADEON_UPLOAD_TEX2		0x00000800
+#define RADEON_UPLOAD_TEX0IMAGES	0x00001000
+#define RADEON_UPLOAD_TEX1IMAGES	0x00002000
+#define RADEON_UPLOAD_TEX2IMAGES	0x00004000
+#define RADEON_UPLOAD_CLIPRECTS		0x00008000 /* handled client-side */
+#define RADEON_REQUIRE_QUIESCENCE	0x00010000
+#define RADEON_UPLOAD_ZBIAS		0x00020000 /* version 1.2 and newer */
+#define RADEON_UPLOAD_ALL		0x003effff
+#define RADEON_UPLOAD_CONTEXT_ALL       0x003e01ff
+
+
+/* New style per-packet identifiers for use in cmd_buffer ioctl with
+ * the RADEON_EMIT_PACKET command.  Comments relate new packets to old
+ * state bits and the packet size:
+ */
+#define RADEON_EMIT_PP_MISC                         0 /* context/7 */
+#define RADEON_EMIT_PP_CNTL                         1 /* context/3 */
+#define RADEON_EMIT_RB3D_COLORPITCH                 2 /* context/1 */
+#define RADEON_EMIT_RE_LINE_PATTERN                 3 /* line/2 */
+#define RADEON_EMIT_SE_LINE_WIDTH                   4 /* line/1 */
+#define RADEON_EMIT_PP_LUM_MATRIX                   5 /* bumpmap/1 */
+#define RADEON_EMIT_PP_ROT_MATRIX_0                 6 /* bumpmap/2 */
+#define RADEON_EMIT_RB3D_STENCILREFMASK             7 /* masks/3 */
+#define RADEON_EMIT_SE_VPORT_XSCALE                 8 /* viewport/6 */
+#define RADEON_EMIT_SE_CNTL                         9 /* setup/2 */
+#define RADEON_EMIT_SE_CNTL_STATUS                  10 /* setup/1 */
+#define RADEON_EMIT_RE_MISC                         11 /* misc/1 */
+#define RADEON_EMIT_PP_TXFILTER_0                   12 /* tex0/6 */
+#define RADEON_EMIT_PP_BORDER_COLOR_0               13 /* tex0/1 */
+#define RADEON_EMIT_PP_TXFILTER_1                   14 /* tex1/6 */
+#define RADEON_EMIT_PP_BORDER_COLOR_1               15 /* tex1/1 */
+#define RADEON_EMIT_PP_TXFILTER_2                   16 /* tex2/6 */
+#define RADEON_EMIT_PP_BORDER_COLOR_2               17 /* tex2/1 */
+#define RADEON_EMIT_SE_ZBIAS_FACTOR                 18 /* zbias/2 */
+#define RADEON_EMIT_SE_TCL_OUTPUT_VTX_FMT           19 /* tcl/11 */
+#define RADEON_EMIT_SE_TCL_MATERIAL_EMMISSIVE_RED   20 /* material/17 */
+#define R200_EMIT_PP_TXCBLEND_0                     21 /* tex0/4 */
+#define R200_EMIT_PP_TXCBLEND_1                     22 /* tex1/4 */
+#define R200_EMIT_PP_TXCBLEND_2                     23 /* tex2/4 */
+#define R200_EMIT_PP_TXCBLEND_3                     24 /* tex3/4 */
+#define R200_EMIT_PP_TXCBLEND_4                     25 /* tex4/4 */
+#define R200_EMIT_PP_TXCBLEND_5                     26 /* tex5/4 */
+#define R200_EMIT_PP_TXCBLEND_6                     27 /* /4 */
+#define R200_EMIT_PP_TXCBLEND_7                     28 /* /4 */
+#define R200_EMIT_TCL_LIGHT_MODEL_CTL_0             29 /* tcl/7 */
+#define R200_EMIT_TFACTOR_0                         30 /* tf/7 */
+#define R200_EMIT_VTX_FMT_0                         31 /* vtx/5 */
+#define R200_EMIT_VAP_CTL                           32 /* vap/1 */
+#define R200_EMIT_MATRIX_SELECT_0                   33 /* msl/5 */
+#define R200_EMIT_TEX_PROC_CTL_2                    34 /* tcg/5 */
+#define R200_EMIT_TCL_UCP_VERT_BLEND_CTL            35 /* tcl/1 */
+#define R200_EMIT_PP_TXFILTER_0                     36 /* tex0/6 */
+#define R200_EMIT_PP_TXFILTER_1                     37 /* tex1/6 */
+#define R200_EMIT_PP_TXFILTER_2                     38 /* tex2/6 */
+#define R200_EMIT_PP_TXFILTER_3                     39 /* tex3/6 */
+#define R200_EMIT_PP_TXFILTER_4                     40 /* tex4/6 */
+#define R200_EMIT_PP_TXFILTER_5                     41 /* tex5/6 */
+#define R200_EMIT_PP_TXOFFSET_0                     42 /* tex0/1 */
+#define R200_EMIT_PP_TXOFFSET_1                     43 /* tex1/1 */
+#define R200_EMIT_PP_TXOFFSET_2                     44 /* tex2/1 */
+#define R200_EMIT_PP_TXOFFSET_3                     45 /* tex3/1 */
+#define R200_EMIT_PP_TXOFFSET_4                     46 /* tex4/1 */
+#define R200_EMIT_PP_TXOFFSET_5                     47 /* tex5/1 */
+#define R200_EMIT_VTE_CNTL                          48 /* vte/1 */
+#define R200_EMIT_OUTPUT_VTX_COMP_SEL               49 /* vtx/1 */
+#define R200_EMIT_PP_TAM_DEBUG3                     50 /* tam/1 */
+#define R200_EMIT_PP_CNTL_X                         51 /* cst/1 */
+#define R200_EMIT_RB3D_DEPTHXY_OFFSET               52 /* cst/1 */
+#define R200_EMIT_RE_AUX_SCISSOR_CNTL               53 /* cst/1 */
+#define R200_EMIT_RE_SCISSOR_TL_0                   54 /* cst/2 */
+#define R200_EMIT_RE_SCISSOR_TL_1                   55 /* cst/2 */
+#define R200_EMIT_RE_SCISSOR_TL_2                   56 /* cst/2 */
+#define R200_EMIT_SE_VAP_CNTL_STATUS                57 /* cst/1 */
+#define R200_EMIT_SE_VTX_STATE_CNTL                 58 /* cst/1 */
+#define R200_EMIT_RE_POINTSIZE                      59 /* cst/1 */
+#define R200_EMIT_TCL_INPUT_VTX_VECTOR_ADDR_0       60 /* cst/4 */
+#define R200_EMIT_PP_CUBIC_FACES_0                  61
+#define R200_EMIT_PP_CUBIC_OFFSETS_0                62
+#define R200_EMIT_PP_CUBIC_FACES_1                  63
+#define R200_EMIT_PP_CUBIC_OFFSETS_1                64
+#define R200_EMIT_PP_CUBIC_FACES_2                  65
+#define R200_EMIT_PP_CUBIC_OFFSETS_2                66
+#define R200_EMIT_PP_CUBIC_FACES_3                  67
+#define R200_EMIT_PP_CUBIC_OFFSETS_3                68
+#define R200_EMIT_PP_CUBIC_FACES_4                  69
+#define R200_EMIT_PP_CUBIC_OFFSETS_4                70
+#define R200_EMIT_PP_CUBIC_FACES_5                  71
+#define R200_EMIT_PP_CUBIC_OFFSETS_5                72
+#define RADEON_EMIT_PP_TEX_SIZE_0                   73
+#define RADEON_EMIT_PP_TEX_SIZE_1                   74
+#define RADEON_EMIT_PP_TEX_SIZE_2                   75
+#define R200_EMIT_RB3D_BLENDCOLOR                   76
+#define R200_EMIT_TCL_POINT_SPRITE_CNTL             77
+#define RADEON_EMIT_PP_CUBIC_FACES_0                78
+#define RADEON_EMIT_PP_CUBIC_OFFSETS_T0             79
+#define RADEON_EMIT_PP_CUBIC_FACES_1                80
+#define RADEON_EMIT_PP_CUBIC_OFFSETS_T1             81
+#define RADEON_EMIT_PP_CUBIC_FACES_2                82
+#define RADEON_EMIT_PP_CUBIC_OFFSETS_T2             83
+#define R200_EMIT_PP_TRI_PERF_CNTL                  84
+#define RADEON_MAX_STATE_PACKETS                    85
+
+/* Commands understood by cmd_buffer ioctl.  More can be added but
+ * obviously these can't be removed or changed:
+ */
+#define RADEON_CMD_PACKET      1 /* emit one of the register packets above */
+#define RADEON_CMD_SCALARS     2 /* emit scalar data */
+#define RADEON_CMD_VECTORS     3 /* emit vector data */
+#define RADEON_CMD_DMA_DISCARD 4 /* discard current dma buf */
+#define RADEON_CMD_PACKET3     5 /* emit hw packet */
+#define RADEON_CMD_PACKET3_CLIP 6 /* emit hw packet wrapped in cliprects */
+#define RADEON_CMD_SCALARS2     7 /* r200 stopgap */
+#define RADEON_CMD_WAIT         8 /* emit hw wait commands -- note:
+				   *  doesn't make the cpu wait, just
+				   *  the graphics hardware */
+
+
+typedef union {
+	int i;
+	struct { 
+		unsigned char cmd_type, pad0, pad1, pad2;
+	} header;
+	struct { 
+		unsigned char cmd_type, packet_id, pad0, pad1;
+	} packet;
+	struct { 
+		unsigned char cmd_type, offset, stride, count; 
+	} scalars;
+	struct { 
+		unsigned char cmd_type, offset, stride, count; 
+	} vectors;
+	struct { 
+		unsigned char cmd_type, buf_idx, pad0, pad1; 
+	} dma;
+	struct { 
+		unsigned char cmd_type, flags, pad0, pad1; 
+	} wait;
+} drm_radeon_cmd_header_t;
+
+#define RADEON_WAIT_2D  0x1
+#define RADEON_WAIT_3D  0x2
+
+
+#define RADEON_FRONT			0x1
+#define RADEON_BACK			0x2
+#define RADEON_DEPTH			0x4
+#define RADEON_STENCIL			0x8
+#define RADEON_CLEAR_FASTZ		0x80000000
+#define RADEON_USE_HIERZ		0x40000000
+#define RADEON_USE_COMP_ZBUF		0x20000000
+
+/* Primitive types
+ */
+#define RADEON_POINTS			0x1
+#define RADEON_LINES			0x2
+#define RADEON_LINE_STRIP		0x3
+#define RADEON_TRIANGLES		0x4
+#define RADEON_TRIANGLE_FAN		0x5
+#define RADEON_TRIANGLE_STRIP		0x6
+
+/* Vertex/indirect buffer size
+ */
+#define RADEON_BUFFER_SIZE		65536
+
+/* Byte offsets for indirect buffer data
+ */
+#define RADEON_INDEX_PRIM_OFFSET	20
+
+#define RADEON_SCRATCH_REG_OFFSET	32
+
+#define RADEON_NR_SAREA_CLIPRECTS	12
+
+/* There are 2 heaps (local/GART).  Each region within a heap is a
+ * minimum of 64k, and there are at most 64 of them per heap.
+ */
+#define RADEON_LOCAL_TEX_HEAP		0
+#define RADEON_GART_TEX_HEAP		1
+#define RADEON_NR_TEX_HEAPS		2
+#define RADEON_NR_TEX_REGIONS		64
+#define RADEON_LOG_TEX_GRANULARITY	16
+
+#define RADEON_MAX_TEXTURE_LEVELS	12
+#define RADEON_MAX_TEXTURE_UNITS	3
+
+#define RADEON_MAX_SURFACES		8
+
+/* Blits have strict offset rules.  All blit offset must be aligned on
+ * a 1K-byte boundary.
+ */
+#define RADEON_OFFSET_SHIFT             10
+#define RADEON_OFFSET_ALIGN             (1 << RADEON_OFFSET_SHIFT)
+#define RADEON_OFFSET_MASK              (RADEON_OFFSET_ALIGN - 1)
+
+#endif /* __RADEON_SAREA_DEFINES__ */
+
+typedef struct {
+	unsigned int red;
+	unsigned int green;
+	unsigned int blue;
+	unsigned int alpha;
+} radeon_color_regs_t;
+
+typedef struct {
+	/* Context state */
+	unsigned int pp_misc;				/* 0x1c14 */
+	unsigned int pp_fog_color;
+	unsigned int re_solid_color;
+	unsigned int rb3d_blendcntl;
+	unsigned int rb3d_depthoffset;
+	unsigned int rb3d_depthpitch;
+	unsigned int rb3d_zstencilcntl;
+
+	unsigned int pp_cntl;				/* 0x1c38 */
+	unsigned int rb3d_cntl;
+	unsigned int rb3d_coloroffset;
+	unsigned int re_width_height;
+	unsigned int rb3d_colorpitch;
+	unsigned int se_cntl;
+
+	/* Vertex format state */
+	unsigned int se_coord_fmt;			/* 0x1c50 */
+
+	/* Line state */
+	unsigned int re_line_pattern;			/* 0x1cd0 */
+	unsigned int re_line_state;
+
+	unsigned int se_line_width;			/* 0x1db8 */
+
+	/* Bumpmap state */
+	unsigned int pp_lum_matrix;			/* 0x1d00 */
+
+	unsigned int pp_rot_matrix_0;			/* 0x1d58 */
+	unsigned int pp_rot_matrix_1;
+
+	/* Mask state */
+	unsigned int rb3d_stencilrefmask;		/* 0x1d7c */
+	unsigned int rb3d_ropcntl;
+	unsigned int rb3d_planemask;
+
+	/* Viewport state */
+	unsigned int se_vport_xscale;			/* 0x1d98 */
+	unsigned int se_vport_xoffset;
+	unsigned int se_vport_yscale;
+	unsigned int se_vport_yoffset;
+	unsigned int se_vport_zscale;
+	unsigned int se_vport_zoffset;
+
+	/* Setup state */
+	unsigned int se_cntl_status;			/* 0x2140 */
+
+	/* Misc state */
+	unsigned int re_top_left;			/* 0x26c0 */
+	unsigned int re_misc;
+} drm_radeon_context_regs_t;
+
+typedef struct {
+	/* Zbias state */
+	unsigned int se_zbias_factor;			/* 0x1dac */
+	unsigned int se_zbias_constant;
+} drm_radeon_context2_regs_t;
+
+
+/* Setup registers for each texture unit
+ */
+typedef struct {
+	unsigned int pp_txfilter;
+	unsigned int pp_txformat;
+	unsigned int pp_txoffset;
+	unsigned int pp_txcblend;
+	unsigned int pp_txablend;
+	unsigned int pp_tfactor;
+	unsigned int pp_border_color;
+} drm_radeon_texture_regs_t;
+
+typedef struct {
+	unsigned int start;
+	unsigned int finish;
+	unsigned int prim:8;
+	unsigned int stateidx:8;
+	unsigned int numverts:16; /* overloaded as offset/64 for elt prims */
+        unsigned int vc_format;   /* vertex format */
+} drm_radeon_prim_t;
+
+
+typedef struct {
+	drm_radeon_context_regs_t context;
+	drm_radeon_texture_regs_t tex[RADEON_MAX_TEXTURE_UNITS];
+	drm_radeon_context2_regs_t context2;
+	unsigned int dirty;
+} drm_radeon_state_t;
+
+
+typedef struct {
+	/* The channel for communication of state information to the
+	 * kernel on firing a vertex buffer with either of the
+	 * obsoleted vertex/index ioctls.
+	 */
+	drm_radeon_context_regs_t context_state;
+	drm_radeon_texture_regs_t tex_state[RADEON_MAX_TEXTURE_UNITS];
+	unsigned int dirty;
+	unsigned int vertsize;
+	unsigned int vc_format;
+
+	/* The current cliprects, or a subset thereof.
+	 */
+	drm_clip_rect_t boxes[RADEON_NR_SAREA_CLIPRECTS];
+	unsigned int nbox;
+
+	/* Counters for client-side throttling of rendering clients.
+	 */
+	unsigned int last_frame;
+	unsigned int last_dispatch;
+	unsigned int last_clear;
+
+	drm_tex_region_t tex_list[RADEON_NR_TEX_HEAPS][RADEON_NR_TEX_REGIONS+1];
+	unsigned int tex_age[RADEON_NR_TEX_HEAPS];
+	int ctx_owner;
+        int pfState;                /* number of 3d windows (0,1,2ormore) */
+        int pfCurrentPage;	    /* which buffer is being displayed? */
+	int crtc2_base;		    /* CRTC2 frame offset */
+	int tiling_enabled;	/* set by drm, read by 2d + 3d clients */
+} drm_radeon_sarea_t;
+
+
+/* WARNING: If you change any of these defines, make sure to change the
+ * defines in the Xserver file (xf86drmRadeon.h)
+ *
+ * KW: actually it's illegal to change any of this (backwards compatibility).
+ */
+
+/* Radeon specific ioctls
+ * The device specific ioctl range is 0x40 to 0x79.
+ */
+#define DRM_RADEON_CP_INIT    0x00 
+#define DRM_RADEON_CP_START   0x01 
+#define DRM_RADEON_CP_STOP    0x02
+#define DRM_RADEON_CP_RESET   0x03
+#define DRM_RADEON_CP_IDLE    0x04
+#define DRM_RADEON_RESET      0x05 
+#define DRM_RADEON_FULLSCREEN 0x06
+#define DRM_RADEON_SWAP       0x07 
+#define DRM_RADEON_CLEAR      0x08 
+#define DRM_RADEON_VERTEX     0x09
+#define DRM_RADEON_INDICES    0x0A
+#define DRM_RADEON_NOT_USED
+#define DRM_RADEON_STIPPLE    0x0C
+#define DRM_RADEON_INDIRECT   0x0D
+#define DRM_RADEON_TEXTURE    0x0E
+#define DRM_RADEON_VERTEX2    0x0F
+#define DRM_RADEON_CMDBUF     0x10
+#define DRM_RADEON_GETPARAM   0x11
+#define DRM_RADEON_FLIP       0x12
+#define DRM_RADEON_ALLOC      0x13
+#define DRM_RADEON_FREE       0x14
+#define DRM_RADEON_INIT_HEAP  0x15
+#define DRM_RADEON_IRQ_EMIT   0x16
+#define DRM_RADEON_IRQ_WAIT   0x17
+#define DRM_RADEON_CP_RESUME  0x18
+#define DRM_RADEON_SETPARAM   0x19
+#define DRM_RADEON_SURF_ALLOC 0x1a
+#define DRM_RADEON_SURF_FREE  0x1b
+
+#define DRM_IOCTL_RADEON_CP_INIT    DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CP_INIT, drm_radeon_init_t)
+#define DRM_IOCTL_RADEON_CP_START   DRM_IO(  DRM_COMMAND_BASE + DRM_RADEON_CP_START)
+#define DRM_IOCTL_RADEON_CP_STOP    DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CP_STOP, drm_radeon_cp_stop_t)
+#define DRM_IOCTL_RADEON_CP_RESET   DRM_IO(  DRM_COMMAND_BASE + DRM_RADEON_CP_RESET)
+#define DRM_IOCTL_RADEON_CP_IDLE    DRM_IO(  DRM_COMMAND_BASE + DRM_RADEON_CP_IDLE)
+#define DRM_IOCTL_RADEON_RESET      DRM_IO(  DRM_COMMAND_BASE + DRM_RADEON_RESET)
+#define DRM_IOCTL_RADEON_FULLSCREEN DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_FULLSCREEN, drm_radeon_fullscreen_t)
+#define DRM_IOCTL_RADEON_SWAP       DRM_IO(  DRM_COMMAND_BASE + DRM_RADEON_SWAP)
+#define DRM_IOCTL_RADEON_CLEAR      DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CLEAR, drm_radeon_clear_t)
+#define DRM_IOCTL_RADEON_VERTEX     DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_VERTEX, drm_radeon_vertex_t)
+#define DRM_IOCTL_RADEON_INDICES    DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_INDICES, drm_radeon_indices_t)
+#define DRM_IOCTL_RADEON_STIPPLE    DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_STIPPLE, drm_radeon_stipple_t)
+#define DRM_IOCTL_RADEON_INDIRECT   DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_INDIRECT, drm_radeon_indirect_t)
+#define DRM_IOCTL_RADEON_TEXTURE    DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_TEXTURE, drm_radeon_texture_t)
+#define DRM_IOCTL_RADEON_VERTEX2    DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_VERTEX2, drm_radeon_vertex2_t)
+#define DRM_IOCTL_RADEON_CMDBUF     DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_CMDBUF, drm_radeon_cmd_buffer_t)
+#define DRM_IOCTL_RADEON_GETPARAM   DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_GETPARAM, drm_radeon_getparam_t)
+#define DRM_IOCTL_RADEON_FLIP       DRM_IO(  DRM_COMMAND_BASE + DRM_RADEON_FLIP)
+#define DRM_IOCTL_RADEON_ALLOC      DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_ALLOC, drm_radeon_mem_alloc_t)
+#define DRM_IOCTL_RADEON_FREE       DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_FREE, drm_radeon_mem_free_t)
+#define DRM_IOCTL_RADEON_INIT_HEAP  DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_INIT_HEAP, drm_radeon_mem_init_heap_t)
+#define DRM_IOCTL_RADEON_IRQ_EMIT   DRM_IOWR(DRM_COMMAND_BASE + DRM_RADEON_IRQ_EMIT, drm_radeon_irq_emit_t)
+#define DRM_IOCTL_RADEON_IRQ_WAIT   DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_IRQ_WAIT, drm_radeon_irq_wait_t)
+#define DRM_IOCTL_RADEON_CP_RESUME  DRM_IO(  DRM_COMMAND_BASE + DRM_RADEON_CP_RESUME)
+#define DRM_IOCTL_RADEON_SETPARAM   DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SETPARAM, drm_radeon_setparam_t)
+#define DRM_IOCTL_RADEON_SURF_ALLOC DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SURF_ALLOC, drm_radeon_surface_alloc_t)
+#define DRM_IOCTL_RADEON_SURF_FREE  DRM_IOW( DRM_COMMAND_BASE + DRM_RADEON_SURF_FREE, drm_radeon_surface_free_t)
+
+typedef struct drm_radeon_init {
+	enum {
+		RADEON_INIT_CP    = 0x01,
+		RADEON_CLEANUP_CP = 0x02,
+		RADEON_INIT_R200_CP = 0x03,
+		RADEON_INIT_R300_CP = 0x04
+	} func;
+	unsigned long sarea_priv_offset;
+	int is_pci;
+	int cp_mode;
+	int gart_size;
+	int ring_size;
+	int usec_timeout;
+
+	unsigned int fb_bpp;
+	unsigned int front_offset, front_pitch;
+	unsigned int back_offset, back_pitch;
+	unsigned int depth_bpp;
+	unsigned int depth_offset, depth_pitch;
+
+	unsigned long fb_offset;
+	unsigned long mmio_offset;
+	unsigned long ring_offset;
+	unsigned long ring_rptr_offset;
+	unsigned long buffers_offset;
+	unsigned long gart_textures_offset;
+} drm_radeon_init_t;
+
+typedef struct drm_radeon_cp_stop {
+	int flush;
+	int idle;
+} drm_radeon_cp_stop_t;
+
+typedef struct drm_radeon_fullscreen {
+	enum {
+		RADEON_INIT_FULLSCREEN    = 0x01,
+		RADEON_CLEANUP_FULLSCREEN = 0x02
+	} func;
+} drm_radeon_fullscreen_t;
+
+#define CLEAR_X1	0
+#define CLEAR_Y1	1
+#define CLEAR_X2	2
+#define CLEAR_Y2	3
+#define CLEAR_DEPTH	4
+
+typedef union drm_radeon_clear_rect {
+	float f[5];
+	unsigned int ui[5];
+} drm_radeon_clear_rect_t;
+
+typedef struct drm_radeon_clear {
+	unsigned int flags;
+	unsigned int clear_color;
+	unsigned int clear_depth;
+	unsigned int color_mask;
+	unsigned int depth_mask;   /* misnamed field:  should be stencil */
+	drm_radeon_clear_rect_t __user *depth_boxes;
+} drm_radeon_clear_t;
+
+typedef struct drm_radeon_vertex {
+	int prim;
+	int idx;			/* Index of vertex buffer */
+	int count;			/* Number of vertices in buffer */
+	int discard;			/* Client finished with buffer? */
+} drm_radeon_vertex_t;
+
+typedef struct drm_radeon_indices {
+	int prim;
+	int idx;
+	int start;
+	int end;
+	int discard;			/* Client finished with buffer? */
+} drm_radeon_indices_t;
+
+/* v1.2 - obsoletes drm_radeon_vertex and drm_radeon_indices
+ *      - allows multiple primitives and state changes in a single ioctl
+ *      - supports driver change to emit native primitives
+ */
+typedef struct drm_radeon_vertex2 {
+	int idx;			/* Index of vertex buffer */
+	int discard;			/* Client finished with buffer? */
+	int nr_states;
+	drm_radeon_state_t __user *state;
+	int nr_prims;
+	drm_radeon_prim_t __user *prim;
+} drm_radeon_vertex2_t;
+
+/* v1.3 - obsoletes drm_radeon_vertex2
+ *      - allows arbitarily large cliprect list 
+ *      - allows updating of tcl packet, vector and scalar state
+ *      - allows memory-efficient description of state updates
+ *      - allows state to be emitted without a primitive 
+ *           (for clears, ctx switches)
+ *      - allows more than one dma buffer to be referenced per ioctl
+ *      - supports tcl driver
+ *      - may be extended in future versions with new cmd types, packets
+ */
+typedef struct drm_radeon_cmd_buffer {
+	int bufsz;
+	char __user *buf;
+	int nbox;
+	drm_clip_rect_t __user *boxes;
+} drm_radeon_cmd_buffer_t;
+
+typedef struct drm_radeon_tex_image {
+	unsigned int x, y;		/* Blit coordinates */
+	unsigned int width, height;
+	const void __user *data;
+} drm_radeon_tex_image_t;
+
+typedef struct drm_radeon_texture {
+	unsigned int offset;
+	int pitch;
+	int format;
+	int width;			/* Texture image coordinates */
+	int height;
+	drm_radeon_tex_image_t __user *image;
+} drm_radeon_texture_t;
+
+typedef struct drm_radeon_stipple {
+	unsigned int __user *mask;
+} drm_radeon_stipple_t;
+
+typedef struct drm_radeon_indirect {
+	int idx;
+	int start;
+	int end;
+	int discard;
+} drm_radeon_indirect_t;
+
+
+/* 1.3: An ioctl to get parameters that aren't available to the 3d
+ * client any other way.  
+ */
+#define RADEON_PARAM_GART_BUFFER_OFFSET    1 /* card offset of 1st GART buffer */
+#define RADEON_PARAM_LAST_FRAME            2
+#define RADEON_PARAM_LAST_DISPATCH         3
+#define RADEON_PARAM_LAST_CLEAR            4
+/* Added with DRM version 1.6. */
+#define RADEON_PARAM_IRQ_NR                5
+#define RADEON_PARAM_GART_BASE             6 /* card offset of GART base */
+/* Added with DRM version 1.8. */
+#define RADEON_PARAM_REGISTER_HANDLE       7 /* for drmMap() */
+#define RADEON_PARAM_STATUS_HANDLE         8
+#define RADEON_PARAM_SAREA_HANDLE          9
+#define RADEON_PARAM_GART_TEX_HANDLE       10
+#define RADEON_PARAM_SCRATCH_OFFSET        11
+
+typedef struct drm_radeon_getparam {
+	int param;
+	void __user *value;
+} drm_radeon_getparam_t;
+
+/* 1.6: Set up a memory manager for regions of shared memory:
+ */
+#define RADEON_MEM_REGION_GART 1
+#define RADEON_MEM_REGION_FB   2
+
+typedef struct drm_radeon_mem_alloc {
+	int region;
+	int alignment;
+	int size;
+	int __user *region_offset;	/* offset from start of fb or GART */
+} drm_radeon_mem_alloc_t;
+
+typedef struct drm_radeon_mem_free {
+	int region;
+	int region_offset;
+} drm_radeon_mem_free_t;
+
+typedef struct drm_radeon_mem_init_heap {
+	int region;
+	int size;
+	int start;	
+} drm_radeon_mem_init_heap_t;
+
+
+/* 1.6: Userspace can request & wait on irq's:
+ */
+typedef struct drm_radeon_irq_emit {
+	int __user *irq_seq;
+} drm_radeon_irq_emit_t;
+
+typedef struct drm_radeon_irq_wait {
+	int irq_seq;
+} drm_radeon_irq_wait_t;
+
+
+/* 1.10: Clients tell the DRM where they think the framebuffer is located in
+ * the card's address space, via a new generic ioctl to set parameters
+ */
+
+typedef struct drm_radeon_setparam {
+	unsigned int param;
+	int64_t      value;
+} drm_radeon_setparam_t;
+
+#define RADEON_SETPARAM_FB_LOCATION    1	/* determined framebuffer location */
+#define RADEON_SETPARAM_SWITCH_TILING  2	/* enable/disable color tiling */
+
+/* 1.14: Clients can allocate/free a surface
+ */
+typedef struct drm_radeon_surface_alloc {
+	unsigned int address;
+	unsigned int size;
+	unsigned int flags;
+} drm_radeon_surface_alloc_t;
+
+typedef struct drm_radeon_surface_free {
+	unsigned int address;
+} drm_radeon_surface_free_t;
+
+#endif
diff --git a/drivers/char/drm/radeon_drv.c b/drivers/char/drm/radeon_drv.c
new file mode 100644
index 0000000..7b983d9
--- /dev/null
+++ b/drivers/char/drm/radeon_drv.c
@@ -0,0 +1,127 @@
+/**
+ * \file radeon_drv.c
+ * ATI Radeon driver
+ *
+ * \author Gareth Hughes <gareth@valinux.com>
+ */
+
+/*
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "drm.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->primary.minor,
+		pci_pretty_name(dev->pdev)
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+	radeon_PCI_IDS
+};
+
+extern drm_ioctl_desc_t radeon_ioctls[];
+extern int radeon_max_ioctl;
+
+static struct drm_driver driver = {
+	.driver_features = DRIVER_USE_AGP | DRIVER_USE_MTRR | DRIVER_PCI_DMA | DRIVER_SG | DRIVER_HAVE_IRQ | DRIVER_HAVE_DMA | DRIVER_IRQ_SHARED | DRIVER_IRQ_VBL,
+	.dev_priv_size = sizeof(drm_radeon_buf_priv_t),
+	.preinit = radeon_driver_preinit,
+	.postcleanup = radeon_driver_postcleanup,
+	.prerelease = radeon_driver_prerelease,
+	.pretakedown = radeon_driver_pretakedown,
+	.open_helper = radeon_driver_open_helper,
+	.vblank_wait = radeon_driver_vblank_wait,
+	.irq_preinstall = radeon_driver_irq_preinstall,
+	.irq_postinstall = radeon_driver_irq_postinstall,
+	.irq_uninstall = radeon_driver_irq_uninstall,
+	.irq_handler = radeon_driver_irq_handler,
+	.free_filp_priv = radeon_driver_free_filp_priv,
+	.reclaim_buffers = drm_core_reclaim_buffers,
+	.get_map_ofs = drm_core_get_map_ofs,
+	.get_reg_ofs = drm_core_get_reg_ofs,
+	.postinit = postinit,
+	.version = version,
+	.ioctls = radeon_ioctls,
+	.dma_ioctl = radeon_cp_buffers,
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+	.pci_driver = {
+		.name          = DRIVER_NAME,
+		.id_table      = pciidlist,
+	}
+};
+
+static int __init radeon_init(void)
+{
+	driver.num_ioctls = radeon_max_ioctl;
+	return drm_init(&driver);
+}
+
+static void __exit radeon_exit(void)
+{
+	drm_exit(&driver);
+}
+
+module_init(radeon_init);
+module_exit(radeon_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/radeon_drv.h b/drivers/char/drm/radeon_drv.h
new file mode 100644
index 0000000..5837098
--- /dev/null
+++ b/drivers/char/drm/radeon_drv.h
@@ -0,0 +1,1044 @@
+/* radeon_drv.h -- Private header for radeon driver -*- linux-c -*-
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Fremont, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Kevin E. Martin <martin@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#ifndef __RADEON_DRV_H__
+#define __RADEON_DRV_H__
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR		"Gareth Hughes, Keith Whitwell, others."
+
+#define DRIVER_NAME		"radeon"
+#define DRIVER_DESC		"ATI Radeon"
+#define DRIVER_DATE		"20050311"
+
+/* Interface history:
+ *
+ * 1.1 - ??
+ * 1.2 - Add vertex2 ioctl (keith)
+ *     - Add stencil capability to clear ioctl (gareth, keith)
+ *     - Increase MAX_TEXTURE_LEVELS (brian)
+ * 1.3 - Add cmdbuf ioctl (keith)
+ *     - Add support for new radeon packets (keith)
+ *     - Add getparam ioctl (keith)
+ *     - Add flip-buffers ioctl, deprecate fullscreen foo (keith).
+ * 1.4 - Add scratch registers to get_param ioctl.
+ * 1.5 - Add r200 packets to cmdbuf ioctl
+ *     - Add r200 function to init ioctl
+ *     - Add 'scalar2' instruction to cmdbuf
+ * 1.6 - Add static GART memory manager
+ *       Add irq handler (won't be turned on unless X server knows to)
+ *       Add irq ioctls and irq_active getparam.
+ *       Add wait command for cmdbuf ioctl
+ *       Add GART offset query for getparam
+ * 1.7 - Add support for cube map registers: R200_PP_CUBIC_FACES_[0..5]
+ *       and R200_PP_CUBIC_OFFSET_F1_[0..5].
+ *       Added packets R200_EMIT_PP_CUBIC_FACES_[0..5] and
+ *       R200_EMIT_PP_CUBIC_OFFSETS_[0..5].  (brian)
+ * 1.8 - Remove need to call cleanup ioctls on last client exit (keith)
+ *       Add 'GET' queries for starting additional clients on different VT's.
+ * 1.9 - Add DRM_IOCTL_RADEON_CP_RESUME ioctl.
+ *       Add texture rectangle support for r100.
+ * 1.10- Add SETPARAM ioctl; first parameter to set is FB_LOCATION, which
+ *       clients use to tell the DRM where they think the framebuffer is 
+ *       located in the card's address space
+ * 1.11- Add packet R200_EMIT_RB3D_BLENDCOLOR to support GL_EXT_blend_color
+ *       and GL_EXT_blend_[func|equation]_separate on r200
+ * 1.12- Add R300 CP microcode support - this just loads the CP on r300
+ *       (No 3D support yet - just microcode loading)
+ * 1.13- Add packet R200_EMIT_TCL_POINT_SPRITE_CNTL for ARB_point_parameters
+ *     - Add hyperz support, add hyperz flags to clear ioctl.
+ * 1.14- Add support for color tiling
+ *     - Add R100/R200 surface allocation/free support
+ * 1.15- Add support for texture micro tiling
+ *     - Add support for r100 cube maps
+ * 1.16- Add R200_EMIT_PP_TRI_PERF_CNTL packet to support brilinear
+ *       texture filtering on r200
+ */
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		16
+#define DRIVER_PATCHLEVEL	0
+
+#define GET_RING_HEAD(dev_priv)		DRM_READ32(  (dev_priv)->ring_rptr, 0 )
+#define SET_RING_HEAD(dev_priv,val)	DRM_WRITE32( (dev_priv)->ring_rptr, 0, (val) )
+
+/*
+ * Radeon chip families
+ */
+enum radeon_family {
+	CHIP_R100,
+	CHIP_RS100,
+	CHIP_RV100,
+	CHIP_R200,
+	CHIP_RV200,
+	CHIP_RS200,
+	CHIP_R250,
+	CHIP_RS250,
+	CHIP_RV250,
+	CHIP_RV280,
+	CHIP_R300,
+	CHIP_RS300,
+	CHIP_RV350,
+	CHIP_LAST,
+};
+
+enum radeon_cp_microcode_version {
+	UCODE_R100,
+	UCODE_R200,
+	UCODE_R300,
+};
+
+/*
+ * Chip flags
+ */
+enum radeon_chip_flags {
+	CHIP_FAMILY_MASK = 0x0000ffffUL,
+	CHIP_FLAGS_MASK = 0xffff0000UL,
+	CHIP_IS_MOBILITY = 0x00010000UL,
+	CHIP_IS_IGP = 0x00020000UL,
+	CHIP_SINGLE_CRTC = 0x00040000UL,
+	CHIP_IS_AGP = 0x00080000UL,
+	CHIP_HAS_HIERZ = 0x00100000UL, 
+};
+
+typedef struct drm_radeon_freelist {
+   	unsigned int age;
+   	drm_buf_t *buf;
+   	struct drm_radeon_freelist *next;
+   	struct drm_radeon_freelist *prev;
+} drm_radeon_freelist_t;
+
+typedef struct drm_radeon_ring_buffer {
+	u32 *start;
+	u32 *end;
+	int size;
+	int size_l2qw;
+
+	u32 tail;
+	u32 tail_mask;
+	int space;
+
+	int high_mark;
+} drm_radeon_ring_buffer_t;
+
+typedef struct drm_radeon_depth_clear_t {
+	u32 rb3d_cntl;
+	u32 rb3d_zstencilcntl;
+	u32 se_cntl;
+} drm_radeon_depth_clear_t;
+
+struct drm_radeon_driver_file_fields {
+	int64_t radeon_fb_delta;
+};
+
+struct mem_block {
+	struct mem_block *next;
+	struct mem_block *prev;
+	int start;
+	int size;
+	DRMFILE filp;		/* 0: free, -1: heap, other: real files */
+};
+
+struct radeon_surface {
+	int refcount;
+	u32 lower;
+	u32 upper;
+	u32 flags;
+};
+
+struct radeon_virt_surface {
+	int surface_index;
+	u32 lower;
+	u32 upper;
+	u32 flags;
+	DRMFILE filp;
+};
+
+typedef struct drm_radeon_private {
+	drm_radeon_ring_buffer_t ring;
+	drm_radeon_sarea_t *sarea_priv;
+
+	u32 fb_location;
+
+	int gart_size;
+	u32 gart_vm_start;
+	unsigned long gart_buffers_offset;
+
+	int cp_mode;
+	int cp_running;
+
+   	drm_radeon_freelist_t *head;
+   	drm_radeon_freelist_t *tail;
+	int last_buf;
+	volatile u32 *scratch;
+	int writeback_works;
+
+	int usec_timeout;
+
+	int microcode_version;
+
+	int is_pci;
+	unsigned long phys_pci_gart;
+	dma_addr_t bus_pci_gart;
+
+	struct {
+		u32 boxes;
+		int freelist_timeouts;
+		int freelist_loops;
+		int requested_bufs;
+		int last_frame_reads;
+		int last_clear_reads;
+		int clears;
+		int texture_uploads;
+	} stats;
+
+	int do_boxes;
+	int page_flipping;
+	int current_page;
+
+	u32 color_fmt;
+	unsigned int front_offset;
+	unsigned int front_pitch;
+	unsigned int back_offset;
+	unsigned int back_pitch;
+
+	u32 depth_fmt;
+	unsigned int depth_offset;
+	unsigned int depth_pitch;
+
+	u32 front_pitch_offset;
+	u32 back_pitch_offset;
+	u32 depth_pitch_offset;
+
+	drm_radeon_depth_clear_t depth_clear;
+	
+	unsigned long fb_offset;
+	unsigned long mmio_offset;
+	unsigned long ring_offset;
+	unsigned long ring_rptr_offset;
+	unsigned long buffers_offset;
+	unsigned long gart_textures_offset;
+
+	drm_local_map_t *sarea;
+	drm_local_map_t *mmio;
+	drm_local_map_t *cp_ring;
+	drm_local_map_t *ring_rptr;
+	drm_local_map_t *gart_textures;
+
+	struct mem_block *gart_heap;
+	struct mem_block *fb_heap;
+
+	/* SW interrupt */
+   	wait_queue_head_t swi_queue;
+   	atomic_t swi_emitted;
+
+	struct radeon_surface surfaces[RADEON_MAX_SURFACES];
+	struct radeon_virt_surface virt_surfaces[2*RADEON_MAX_SURFACES];
+
+	/* starting from here on, data is preserved accross an open */
+	uint32_t flags;		/* see radeon_chip_flags */
+} drm_radeon_private_t;
+
+typedef struct drm_radeon_buf_priv {
+	u32 age;
+} drm_radeon_buf_priv_t;
+
+				/* radeon_cp.c */
+extern int radeon_cp_init( DRM_IOCTL_ARGS );
+extern int radeon_cp_start( DRM_IOCTL_ARGS );
+extern int radeon_cp_stop( DRM_IOCTL_ARGS );
+extern int radeon_cp_reset( DRM_IOCTL_ARGS );
+extern int radeon_cp_idle( DRM_IOCTL_ARGS );
+extern int radeon_cp_resume( DRM_IOCTL_ARGS );
+extern int radeon_engine_reset( DRM_IOCTL_ARGS );
+extern int radeon_fullscreen( DRM_IOCTL_ARGS );
+extern int radeon_cp_buffers( DRM_IOCTL_ARGS );
+
+extern void radeon_freelist_reset( drm_device_t *dev );
+extern drm_buf_t *radeon_freelist_get( drm_device_t *dev );
+
+extern int radeon_wait_ring( drm_radeon_private_t *dev_priv, int n );
+
+extern int radeon_do_cp_idle( drm_radeon_private_t *dev_priv );
+
+extern int radeon_driver_preinit(struct drm_device *dev, unsigned long flags);
+extern int radeon_driver_postcleanup(struct drm_device *dev);
+
+extern int radeon_mem_alloc( DRM_IOCTL_ARGS );
+extern int radeon_mem_free( DRM_IOCTL_ARGS );
+extern int radeon_mem_init_heap( DRM_IOCTL_ARGS );
+extern void radeon_mem_takedown( struct mem_block **heap );
+extern void radeon_mem_release( DRMFILE filp, struct mem_block *heap );
+
+				/* radeon_irq.c */
+extern int radeon_irq_emit( DRM_IOCTL_ARGS );
+extern int radeon_irq_wait( DRM_IOCTL_ARGS );
+
+extern void radeon_do_release(drm_device_t *dev);
+extern int radeon_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence);
+extern irqreturn_t radeon_driver_irq_handler( DRM_IRQ_ARGS );
+extern void radeon_driver_irq_preinstall( drm_device_t *dev );
+extern void radeon_driver_irq_postinstall( drm_device_t *dev );
+extern void radeon_driver_irq_uninstall( drm_device_t *dev );
+extern void radeon_driver_prerelease(drm_device_t *dev, DRMFILE filp);
+extern void radeon_driver_pretakedown(drm_device_t *dev);
+extern int radeon_driver_open_helper(drm_device_t *dev, drm_file_t *filp_priv);
+extern void radeon_driver_free_filp_priv(drm_device_t *dev, drm_file_t *filp_priv);
+
+extern int radeon_preinit( struct drm_device *dev, unsigned long flags );
+extern int radeon_postinit( struct drm_device *dev, unsigned long flags );
+extern int radeon_postcleanup( struct drm_device *dev );
+
+/* Flags for stats.boxes
+ */
+#define RADEON_BOX_DMA_IDLE      0x1
+#define RADEON_BOX_RING_FULL     0x2
+#define RADEON_BOX_FLIP          0x4
+#define RADEON_BOX_WAIT_IDLE     0x8
+#define RADEON_BOX_TEXTURE_LOAD  0x10
+
+
+
+/* Register definitions, register access macros and drmAddMap constants
+ * for Radeon kernel driver.
+ */
+
+#define RADEON_AGP_COMMAND		0x0f60
+#define RADEON_AUX_SCISSOR_CNTL		0x26f0
+#	define RADEON_EXCLUSIVE_SCISSOR_0	(1 << 24)
+#	define RADEON_EXCLUSIVE_SCISSOR_1	(1 << 25)
+#	define RADEON_EXCLUSIVE_SCISSOR_2	(1 << 26)
+#	define RADEON_SCISSOR_0_ENABLE		(1 << 28)
+#	define RADEON_SCISSOR_1_ENABLE		(1 << 29)
+#	define RADEON_SCISSOR_2_ENABLE		(1 << 30)
+
+#define RADEON_BUS_CNTL			0x0030
+#	define RADEON_BUS_MASTER_DIS		(1 << 6)
+
+#define RADEON_CLOCK_CNTL_DATA		0x000c
+#	define RADEON_PLL_WR_EN			(1 << 7)
+#define RADEON_CLOCK_CNTL_INDEX		0x0008
+#define RADEON_CONFIG_APER_SIZE		0x0108
+#define RADEON_CRTC_OFFSET		0x0224
+#define RADEON_CRTC_OFFSET_CNTL		0x0228
+#	define RADEON_CRTC_TILE_EN		(1 << 15)
+#	define RADEON_CRTC_OFFSET_FLIP_CNTL	(1 << 16)
+#define RADEON_CRTC2_OFFSET		0x0324
+#define RADEON_CRTC2_OFFSET_CNTL	0x0328
+
+#define RADEON_RB3D_COLOROFFSET		0x1c40
+#define RADEON_RB3D_COLORPITCH		0x1c48
+
+#define RADEON_DP_GUI_MASTER_CNTL	0x146c
+#	define RADEON_GMC_SRC_PITCH_OFFSET_CNTL	(1 << 0)
+#	define RADEON_GMC_DST_PITCH_OFFSET_CNTL	(1 << 1)
+#	define RADEON_GMC_BRUSH_SOLID_COLOR	(13 << 4)
+#	define RADEON_GMC_BRUSH_NONE		(15 << 4)
+#	define RADEON_GMC_DST_16BPP		(4 << 8)
+#	define RADEON_GMC_DST_24BPP		(5 << 8)
+#	define RADEON_GMC_DST_32BPP		(6 << 8)
+#	define RADEON_GMC_DST_DATATYPE_SHIFT	8
+#	define RADEON_GMC_SRC_DATATYPE_COLOR	(3 << 12)
+#	define RADEON_DP_SRC_SOURCE_MEMORY	(2 << 24)
+#	define RADEON_DP_SRC_SOURCE_HOST_DATA	(3 << 24)
+#	define RADEON_GMC_CLR_CMP_CNTL_DIS	(1 << 28)
+#	define RADEON_GMC_WR_MSK_DIS		(1 << 30)
+#	define RADEON_ROP3_S			0x00cc0000
+#	define RADEON_ROP3_P			0x00f00000
+#define RADEON_DP_WRITE_MASK		0x16cc
+#define RADEON_DST_PITCH_OFFSET		0x142c
+#define RADEON_DST_PITCH_OFFSET_C	0x1c80
+#	define RADEON_DST_TILE_LINEAR		(0 << 30)
+#	define RADEON_DST_TILE_MACRO		(1 << 30)
+#	define RADEON_DST_TILE_MICRO		(2 << 30)
+#	define RADEON_DST_TILE_BOTH		(3 << 30)
+
+#define RADEON_SCRATCH_REG0		0x15e0
+#define RADEON_SCRATCH_REG1		0x15e4
+#define RADEON_SCRATCH_REG2		0x15e8
+#define RADEON_SCRATCH_REG3		0x15ec
+#define RADEON_SCRATCH_REG4		0x15f0
+#define RADEON_SCRATCH_REG5		0x15f4
+#define RADEON_SCRATCH_UMSK		0x0770
+#define RADEON_SCRATCH_ADDR		0x0774
+
+#define RADEON_SCRATCHOFF( x )		(RADEON_SCRATCH_REG_OFFSET + 4*(x))
+
+#define GET_SCRATCH( x )	(dev_priv->writeback_works			\
+				? DRM_READ32( dev_priv->ring_rptr, RADEON_SCRATCHOFF(x) ) \
+				: RADEON_READ( RADEON_SCRATCH_REG0 + 4*(x) ) )
+
+
+#define RADEON_GEN_INT_CNTL		0x0040
+#	define RADEON_CRTC_VBLANK_MASK		(1 << 0)
+#	define RADEON_GUI_IDLE_INT_ENABLE	(1 << 19)
+#	define RADEON_SW_INT_ENABLE		(1 << 25)
+
+#define RADEON_GEN_INT_STATUS		0x0044
+#	define RADEON_CRTC_VBLANK_STAT		(1 << 0)
+#	define RADEON_CRTC_VBLANK_STAT_ACK   	(1 << 0)
+#	define RADEON_GUI_IDLE_INT_TEST_ACK     (1 << 19)
+#	define RADEON_SW_INT_TEST		(1 << 25)
+#	define RADEON_SW_INT_TEST_ACK   	(1 << 25)
+#	define RADEON_SW_INT_FIRE		(1 << 26)
+
+#define RADEON_HOST_PATH_CNTL		0x0130
+#	define RADEON_HDP_SOFT_RESET		(1 << 26)
+#	define RADEON_HDP_WC_TIMEOUT_MASK	(7 << 28)
+#	define RADEON_HDP_WC_TIMEOUT_28BCLK	(7 << 28)
+
+#define RADEON_ISYNC_CNTL		0x1724
+#	define RADEON_ISYNC_ANY2D_IDLE3D	(1 << 0)
+#	define RADEON_ISYNC_ANY3D_IDLE2D	(1 << 1)
+#	define RADEON_ISYNC_TRIG2D_IDLE3D	(1 << 2)
+#	define RADEON_ISYNC_TRIG3D_IDLE2D	(1 << 3)
+#	define RADEON_ISYNC_WAIT_IDLEGUI	(1 << 4)
+#	define RADEON_ISYNC_CPSCRATCH_IDLEGUI	(1 << 5)
+
+#define RADEON_RBBM_GUICNTL		0x172c
+#	define RADEON_HOST_DATA_SWAP_NONE	(0 << 0)
+#	define RADEON_HOST_DATA_SWAP_16BIT	(1 << 0)
+#	define RADEON_HOST_DATA_SWAP_32BIT	(2 << 0)
+#	define RADEON_HOST_DATA_SWAP_HDW	(3 << 0)
+
+#define RADEON_MC_AGP_LOCATION		0x014c
+#define RADEON_MC_FB_LOCATION		0x0148
+#define RADEON_MCLK_CNTL		0x0012
+#	define RADEON_FORCEON_MCLKA		(1 << 16)
+#	define RADEON_FORCEON_MCLKB		(1 << 17)
+#	define RADEON_FORCEON_YCLKA		(1 << 18)
+#	define RADEON_FORCEON_YCLKB		(1 << 19)
+#	define RADEON_FORCEON_MC		(1 << 20)
+#	define RADEON_FORCEON_AIC		(1 << 21)
+
+#define RADEON_PP_BORDER_COLOR_0	0x1d40
+#define RADEON_PP_BORDER_COLOR_1	0x1d44
+#define RADEON_PP_BORDER_COLOR_2	0x1d48
+#define RADEON_PP_CNTL			0x1c38
+#	define RADEON_SCISSOR_ENABLE		(1 <<  1)
+#define RADEON_PP_LUM_MATRIX		0x1d00
+#define RADEON_PP_MISC			0x1c14
+#define RADEON_PP_ROT_MATRIX_0		0x1d58
+#define RADEON_PP_TXFILTER_0		0x1c54
+#define RADEON_PP_TXOFFSET_0		0x1c5c
+#define RADEON_PP_TXFILTER_1		0x1c6c
+#define RADEON_PP_TXFILTER_2		0x1c84
+
+#define RADEON_RB2D_DSTCACHE_CTLSTAT	0x342c
+#	define RADEON_RB2D_DC_FLUSH		(3 << 0)
+#	define RADEON_RB2D_DC_FREE		(3 << 2)
+#	define RADEON_RB2D_DC_FLUSH_ALL		0xf
+#	define RADEON_RB2D_DC_BUSY		(1 << 31)
+#define RADEON_RB3D_CNTL		0x1c3c
+#	define RADEON_ALPHA_BLEND_ENABLE	(1 << 0)
+#	define RADEON_PLANE_MASK_ENABLE		(1 << 1)
+#	define RADEON_DITHER_ENABLE		(1 << 2)
+#	define RADEON_ROUND_ENABLE		(1 << 3)
+#	define RADEON_SCALE_DITHER_ENABLE	(1 << 4)
+#	define RADEON_DITHER_INIT		(1 << 5)
+#	define RADEON_ROP_ENABLE		(1 << 6)
+#	define RADEON_STENCIL_ENABLE		(1 << 7)
+#	define RADEON_Z_ENABLE			(1 << 8)
+#	define RADEON_ZBLOCK16			(1 << 15)
+#define RADEON_RB3D_DEPTHOFFSET		0x1c24
+#define RADEON_RB3D_DEPTHCLEARVALUE	0x3230
+#define RADEON_RB3D_DEPTHPITCH		0x1c28
+#define RADEON_RB3D_PLANEMASK		0x1d84
+#define RADEON_RB3D_STENCILREFMASK	0x1d7c
+#define RADEON_RB3D_ZCACHE_MODE		0x3250
+#define RADEON_RB3D_ZCACHE_CTLSTAT	0x3254
+#	define RADEON_RB3D_ZC_FLUSH		(1 << 0)
+#	define RADEON_RB3D_ZC_FREE		(1 << 2)
+#	define RADEON_RB3D_ZC_FLUSH_ALL		0x5
+#	define RADEON_RB3D_ZC_BUSY		(1 << 31)
+#define RADEON_RB3D_ZSTENCILCNTL	0x1c2c
+#	define RADEON_Z_TEST_MASK		(7 << 4)
+#	define RADEON_Z_TEST_ALWAYS		(7 << 4)
+#	define RADEON_Z_HIERARCHY_ENABLE	(1 << 8)
+#	define RADEON_STENCIL_TEST_ALWAYS	(7 << 12)
+#	define RADEON_STENCIL_S_FAIL_REPLACE	(2 << 16)
+#	define RADEON_STENCIL_ZPASS_REPLACE	(2 << 20)
+#	define RADEON_STENCIL_ZFAIL_REPLACE	(2 << 24)
+#	define RADEON_Z_COMPRESSION_ENABLE	(1 << 28)
+#	define RADEON_FORCE_Z_DIRTY		(1 << 29)
+#	define RADEON_Z_WRITE_ENABLE		(1 << 30)
+#	define RADEON_Z_DECOMPRESSION_ENABLE	(1 << 31)
+#define RADEON_RBBM_SOFT_RESET		0x00f0
+#	define RADEON_SOFT_RESET_CP		(1 <<  0)
+#	define RADEON_SOFT_RESET_HI		(1 <<  1)
+#	define RADEON_SOFT_RESET_SE		(1 <<  2)
+#	define RADEON_SOFT_RESET_RE		(1 <<  3)
+#	define RADEON_SOFT_RESET_PP		(1 <<  4)
+#	define RADEON_SOFT_RESET_E2		(1 <<  5)
+#	define RADEON_SOFT_RESET_RB		(1 <<  6)
+#	define RADEON_SOFT_RESET_HDP		(1 <<  7)
+#define RADEON_RBBM_STATUS		0x0e40
+#	define RADEON_RBBM_FIFOCNT_MASK		0x007f
+#	define RADEON_RBBM_ACTIVE		(1 << 31)
+#define RADEON_RE_LINE_PATTERN		0x1cd0
+#define RADEON_RE_MISC			0x26c4
+#define RADEON_RE_TOP_LEFT		0x26c0
+#define RADEON_RE_WIDTH_HEIGHT		0x1c44
+#define RADEON_RE_STIPPLE_ADDR		0x1cc8
+#define RADEON_RE_STIPPLE_DATA		0x1ccc
+
+#define RADEON_SCISSOR_TL_0		0x1cd8
+#define RADEON_SCISSOR_BR_0		0x1cdc
+#define RADEON_SCISSOR_TL_1		0x1ce0
+#define RADEON_SCISSOR_BR_1		0x1ce4
+#define RADEON_SCISSOR_TL_2		0x1ce8
+#define RADEON_SCISSOR_BR_2		0x1cec
+#define RADEON_SE_COORD_FMT		0x1c50
+#define RADEON_SE_CNTL			0x1c4c
+#	define RADEON_FFACE_CULL_CW		(0 << 0)
+#	define RADEON_BFACE_SOLID		(3 << 1)
+#	define RADEON_FFACE_SOLID		(3 << 3)
+#	define RADEON_FLAT_SHADE_VTX_LAST	(3 << 6)
+#	define RADEON_DIFFUSE_SHADE_FLAT	(1 << 8)
+#	define RADEON_DIFFUSE_SHADE_GOURAUD	(2 << 8)
+#	define RADEON_ALPHA_SHADE_FLAT		(1 << 10)
+#	define RADEON_ALPHA_SHADE_GOURAUD	(2 << 10)
+#	define RADEON_SPECULAR_SHADE_FLAT	(1 << 12)
+#	define RADEON_SPECULAR_SHADE_GOURAUD	(2 << 12)
+#	define RADEON_FOG_SHADE_FLAT		(1 << 14)
+#	define RADEON_FOG_SHADE_GOURAUD		(2 << 14)
+#	define RADEON_VPORT_XY_XFORM_ENABLE	(1 << 24)
+#	define RADEON_VPORT_Z_XFORM_ENABLE	(1 << 25)
+#	define RADEON_VTX_PIX_CENTER_OGL	(1 << 27)
+#	define RADEON_ROUND_MODE_TRUNC		(0 << 28)
+#	define RADEON_ROUND_PREC_8TH_PIX	(1 << 30)
+#define RADEON_SE_CNTL_STATUS		0x2140
+#define RADEON_SE_LINE_WIDTH		0x1db8
+#define RADEON_SE_VPORT_XSCALE		0x1d98
+#define RADEON_SE_ZBIAS_FACTOR		0x1db0
+#define RADEON_SE_TCL_MATERIAL_EMMISSIVE_RED 0x2210
+#define RADEON_SE_TCL_OUTPUT_VTX_FMT         0x2254
+#define RADEON_SE_TCL_VECTOR_INDX_REG        0x2200
+#       define RADEON_VEC_INDX_OCTWORD_STRIDE_SHIFT  16
+#       define RADEON_VEC_INDX_DWORD_COUNT_SHIFT     28
+#define RADEON_SE_TCL_VECTOR_DATA_REG       0x2204
+#define RADEON_SE_TCL_SCALAR_INDX_REG       0x2208
+#       define RADEON_SCAL_INDX_DWORD_STRIDE_SHIFT  16
+#define RADEON_SE_TCL_SCALAR_DATA_REG       0x220C
+#define RADEON_SURFACE_ACCESS_FLAGS	0x0bf8
+#define RADEON_SURFACE_ACCESS_CLR	0x0bfc
+#define RADEON_SURFACE_CNTL		0x0b00
+#	define RADEON_SURF_TRANSLATION_DIS	(1 << 8)
+#	define RADEON_NONSURF_AP0_SWP_MASK	(3 << 20)
+#	define RADEON_NONSURF_AP0_SWP_LITTLE	(0 << 20)
+#	define RADEON_NONSURF_AP0_SWP_BIG16	(1 << 20)
+#	define RADEON_NONSURF_AP0_SWP_BIG32	(2 << 20)
+#	define RADEON_NONSURF_AP1_SWP_MASK	(3 << 22)
+#	define RADEON_NONSURF_AP1_SWP_LITTLE	(0 << 22)
+#	define RADEON_NONSURF_AP1_SWP_BIG16	(1 << 22)
+#	define RADEON_NONSURF_AP1_SWP_BIG32	(2 << 22)
+#define RADEON_SURFACE0_INFO		0x0b0c
+#	define RADEON_SURF_PITCHSEL_MASK	(0x1ff << 0)
+#	define RADEON_SURF_TILE_MODE_MASK	(3 << 16)
+#	define RADEON_SURF_TILE_MODE_MACRO	(0 << 16)
+#	define RADEON_SURF_TILE_MODE_MICRO	(1 << 16)
+#	define RADEON_SURF_TILE_MODE_32BIT_Z	(2 << 16)
+#	define RADEON_SURF_TILE_MODE_16BIT_Z	(3 << 16)
+#define RADEON_SURFACE0_LOWER_BOUND	0x0b04
+#define RADEON_SURFACE0_UPPER_BOUND	0x0b08
+#	define RADEON_SURF_ADDRESS_FIXED_MASK	(0x3ff << 0)
+#define RADEON_SURFACE1_INFO		0x0b1c
+#define RADEON_SURFACE1_LOWER_BOUND	0x0b14
+#define RADEON_SURFACE1_UPPER_BOUND	0x0b18
+#define RADEON_SURFACE2_INFO		0x0b2c
+#define RADEON_SURFACE2_LOWER_BOUND	0x0b24
+#define RADEON_SURFACE2_UPPER_BOUND	0x0b28
+#define RADEON_SURFACE3_INFO		0x0b3c
+#define RADEON_SURFACE3_LOWER_BOUND	0x0b34
+#define RADEON_SURFACE3_UPPER_BOUND	0x0b38
+#define RADEON_SURFACE4_INFO		0x0b4c
+#define RADEON_SURFACE4_LOWER_BOUND	0x0b44
+#define RADEON_SURFACE4_UPPER_BOUND	0x0b48
+#define RADEON_SURFACE5_INFO		0x0b5c
+#define RADEON_SURFACE5_LOWER_BOUND	0x0b54
+#define RADEON_SURFACE5_UPPER_BOUND	0x0b58
+#define RADEON_SURFACE6_INFO		0x0b6c
+#define RADEON_SURFACE6_LOWER_BOUND	0x0b64
+#define RADEON_SURFACE6_UPPER_BOUND	0x0b68
+#define RADEON_SURFACE7_INFO		0x0b7c
+#define RADEON_SURFACE7_LOWER_BOUND	0x0b74
+#define RADEON_SURFACE7_UPPER_BOUND	0x0b78
+#define RADEON_SW_SEMAPHORE		0x013c
+
+#define RADEON_WAIT_UNTIL		0x1720
+#	define RADEON_WAIT_CRTC_PFLIP		(1 << 0)
+#	define RADEON_WAIT_2D_IDLECLEAN		(1 << 16)
+#	define RADEON_WAIT_3D_IDLECLEAN		(1 << 17)
+#	define RADEON_WAIT_HOST_IDLECLEAN	(1 << 18)
+
+#define RADEON_RB3D_ZMASKOFFSET		0x3234
+#define RADEON_RB3D_ZSTENCILCNTL	0x1c2c
+#	define RADEON_DEPTH_FORMAT_16BIT_INT_Z	(0 << 0)
+#	define RADEON_DEPTH_FORMAT_24BIT_INT_Z	(2 << 0)
+
+
+/* CP registers */
+#define RADEON_CP_ME_RAM_ADDR		0x07d4
+#define RADEON_CP_ME_RAM_RADDR		0x07d8
+#define RADEON_CP_ME_RAM_DATAH		0x07dc
+#define RADEON_CP_ME_RAM_DATAL		0x07e0
+
+#define RADEON_CP_RB_BASE		0x0700
+#define RADEON_CP_RB_CNTL		0x0704
+#	define RADEON_BUF_SWAP_32BIT		(2 << 16)
+#define RADEON_CP_RB_RPTR_ADDR		0x070c
+#define RADEON_CP_RB_RPTR		0x0710
+#define RADEON_CP_RB_WPTR		0x0714
+
+#define RADEON_CP_RB_WPTR_DELAY		0x0718
+#	define RADEON_PRE_WRITE_TIMER_SHIFT	0
+#	define RADEON_PRE_WRITE_LIMIT_SHIFT	23
+
+#define RADEON_CP_IB_BASE		0x0738
+
+#define RADEON_CP_CSQ_CNTL		0x0740
+#	define RADEON_CSQ_CNT_PRIMARY_MASK	(0xff << 0)
+#	define RADEON_CSQ_PRIDIS_INDDIS		(0 << 28)
+#	define RADEON_CSQ_PRIPIO_INDDIS		(1 << 28)
+#	define RADEON_CSQ_PRIBM_INDDIS		(2 << 28)
+#	define RADEON_CSQ_PRIPIO_INDBM		(3 << 28)
+#	define RADEON_CSQ_PRIBM_INDBM		(4 << 28)
+#	define RADEON_CSQ_PRIPIO_INDPIO		(15 << 28)
+
+#define RADEON_AIC_CNTL			0x01d0
+#	define RADEON_PCIGART_TRANSLATE_EN	(1 << 0)
+#define RADEON_AIC_STAT			0x01d4
+#define RADEON_AIC_PT_BASE		0x01d8
+#define RADEON_AIC_LO_ADDR		0x01dc
+#define RADEON_AIC_HI_ADDR		0x01e0
+#define RADEON_AIC_TLB_ADDR		0x01e4
+#define RADEON_AIC_TLB_DATA		0x01e8
+
+/* CP command packets */
+#define RADEON_CP_PACKET0		0x00000000
+#	define RADEON_ONE_REG_WR		(1 << 15)
+#define RADEON_CP_PACKET1		0x40000000
+#define RADEON_CP_PACKET2		0x80000000
+#define RADEON_CP_PACKET3		0xC0000000
+#	define RADEON_3D_RNDR_GEN_INDX_PRIM	0x00002300
+#	define RADEON_WAIT_FOR_IDLE		0x00002600
+#	define RADEON_3D_DRAW_VBUF		0x00002800
+#	define RADEON_3D_DRAW_IMMD		0x00002900
+#	define RADEON_3D_DRAW_INDX		0x00002A00
+#	define RADEON_3D_LOAD_VBPNTR		0x00002F00
+#	define RADEON_MPEG_IDCT_MACROBLOCK	0x00003000
+#	define RADEON_MPEG_IDCT_MACROBLOCK_REV	0x00003100
+#	define RADEON_3D_CLEAR_ZMASK		0x00003200
+#	define RADEON_3D_CLEAR_HIZ		0x00003700
+#	define RADEON_CNTL_HOSTDATA_BLT		0x00009400
+#	define RADEON_CNTL_PAINT_MULTI		0x00009A00
+#	define RADEON_CNTL_BITBLT_MULTI		0x00009B00
+#	define RADEON_CNTL_SET_SCISSORS		0xC0001E00
+
+#define RADEON_CP_PACKET_MASK		0xC0000000
+#define RADEON_CP_PACKET_COUNT_MASK	0x3fff0000
+#define RADEON_CP_PACKET0_REG_MASK	0x000007ff
+#define RADEON_CP_PACKET1_REG0_MASK	0x000007ff
+#define RADEON_CP_PACKET1_REG1_MASK	0x003ff800
+
+#define RADEON_VTX_Z_PRESENT			(1 << 31)
+#define RADEON_VTX_PKCOLOR_PRESENT		(1 << 3)
+
+#define RADEON_PRIM_TYPE_NONE			(0 << 0)
+#define RADEON_PRIM_TYPE_POINT			(1 << 0)
+#define RADEON_PRIM_TYPE_LINE			(2 << 0)
+#define RADEON_PRIM_TYPE_LINE_STRIP		(3 << 0)
+#define RADEON_PRIM_TYPE_TRI_LIST		(4 << 0)
+#define RADEON_PRIM_TYPE_TRI_FAN		(5 << 0)
+#define RADEON_PRIM_TYPE_TRI_STRIP		(6 << 0)
+#define RADEON_PRIM_TYPE_TRI_TYPE2		(7 << 0)
+#define RADEON_PRIM_TYPE_RECT_LIST		(8 << 0)
+#define RADEON_PRIM_TYPE_3VRT_POINT_LIST	(9 << 0)
+#define RADEON_PRIM_TYPE_3VRT_LINE_LIST		(10 << 0)
+#define RADEON_PRIM_TYPE_MASK                   0xf
+#define RADEON_PRIM_WALK_IND			(1 << 4)
+#define RADEON_PRIM_WALK_LIST			(2 << 4)
+#define RADEON_PRIM_WALK_RING			(3 << 4)
+#define RADEON_COLOR_ORDER_BGRA			(0 << 6)
+#define RADEON_COLOR_ORDER_RGBA			(1 << 6)
+#define RADEON_MAOS_ENABLE			(1 << 7)
+#define RADEON_VTX_FMT_R128_MODE		(0 << 8)
+#define RADEON_VTX_FMT_RADEON_MODE		(1 << 8)
+#define RADEON_NUM_VERTICES_SHIFT		16
+
+#define RADEON_COLOR_FORMAT_CI8		2
+#define RADEON_COLOR_FORMAT_ARGB1555	3
+#define RADEON_COLOR_FORMAT_RGB565	4
+#define RADEON_COLOR_FORMAT_ARGB8888	6
+#define RADEON_COLOR_FORMAT_RGB332	7
+#define RADEON_COLOR_FORMAT_RGB8	9
+#define RADEON_COLOR_FORMAT_ARGB4444	15
+
+#define RADEON_TXFORMAT_I8		0
+#define RADEON_TXFORMAT_AI88		1
+#define RADEON_TXFORMAT_RGB332		2
+#define RADEON_TXFORMAT_ARGB1555	3
+#define RADEON_TXFORMAT_RGB565		4
+#define RADEON_TXFORMAT_ARGB4444	5
+#define RADEON_TXFORMAT_ARGB8888	6
+#define RADEON_TXFORMAT_RGBA8888	7
+#define RADEON_TXFORMAT_Y8		8
+#define RADEON_TXFORMAT_VYUY422         10
+#define RADEON_TXFORMAT_YVYU422         11
+#define RADEON_TXFORMAT_DXT1            12
+#define RADEON_TXFORMAT_DXT23           14
+#define RADEON_TXFORMAT_DXT45           15
+
+#define R200_PP_TXCBLEND_0                0x2f00
+#define R200_PP_TXCBLEND_1                0x2f10
+#define R200_PP_TXCBLEND_2                0x2f20
+#define R200_PP_TXCBLEND_3                0x2f30
+#define R200_PP_TXCBLEND_4                0x2f40
+#define R200_PP_TXCBLEND_5                0x2f50
+#define R200_PP_TXCBLEND_6                0x2f60
+#define R200_PP_TXCBLEND_7                0x2f70
+#define R200_SE_TCL_LIGHT_MODEL_CTL_0     0x2268 
+#define R200_PP_TFACTOR_0                 0x2ee0
+#define R200_SE_VTX_FMT_0                 0x2088
+#define R200_SE_VAP_CNTL                  0x2080
+#define R200_SE_TCL_MATRIX_SEL_0          0x2230
+#define R200_SE_TCL_TEX_PROC_CTL_2        0x22a8 
+#define R200_SE_TCL_UCP_VERT_BLEND_CTL    0x22c0 
+#define R200_PP_TXFILTER_5                0x2ca0 
+#define R200_PP_TXFILTER_4                0x2c80 
+#define R200_PP_TXFILTER_3                0x2c60 
+#define R200_PP_TXFILTER_2                0x2c40 
+#define R200_PP_TXFILTER_1                0x2c20 
+#define R200_PP_TXFILTER_0                0x2c00 
+#define R200_PP_TXOFFSET_5                0x2d78
+#define R200_PP_TXOFFSET_4                0x2d60
+#define R200_PP_TXOFFSET_3                0x2d48
+#define R200_PP_TXOFFSET_2                0x2d30
+#define R200_PP_TXOFFSET_1                0x2d18
+#define R200_PP_TXOFFSET_0                0x2d00
+
+#define R200_PP_CUBIC_FACES_0             0x2c18
+#define R200_PP_CUBIC_FACES_1             0x2c38
+#define R200_PP_CUBIC_FACES_2             0x2c58
+#define R200_PP_CUBIC_FACES_3             0x2c78
+#define R200_PP_CUBIC_FACES_4             0x2c98
+#define R200_PP_CUBIC_FACES_5             0x2cb8
+#define R200_PP_CUBIC_OFFSET_F1_0         0x2d04
+#define R200_PP_CUBIC_OFFSET_F2_0         0x2d08
+#define R200_PP_CUBIC_OFFSET_F3_0         0x2d0c
+#define R200_PP_CUBIC_OFFSET_F4_0         0x2d10
+#define R200_PP_CUBIC_OFFSET_F5_0         0x2d14
+#define R200_PP_CUBIC_OFFSET_F1_1         0x2d1c
+#define R200_PP_CUBIC_OFFSET_F2_1         0x2d20
+#define R200_PP_CUBIC_OFFSET_F3_1         0x2d24
+#define R200_PP_CUBIC_OFFSET_F4_1         0x2d28
+#define R200_PP_CUBIC_OFFSET_F5_1         0x2d2c
+#define R200_PP_CUBIC_OFFSET_F1_2         0x2d34
+#define R200_PP_CUBIC_OFFSET_F2_2         0x2d38
+#define R200_PP_CUBIC_OFFSET_F3_2         0x2d3c
+#define R200_PP_CUBIC_OFFSET_F4_2         0x2d40
+#define R200_PP_CUBIC_OFFSET_F5_2         0x2d44
+#define R200_PP_CUBIC_OFFSET_F1_3         0x2d4c
+#define R200_PP_CUBIC_OFFSET_F2_3         0x2d50
+#define R200_PP_CUBIC_OFFSET_F3_3         0x2d54
+#define R200_PP_CUBIC_OFFSET_F4_3         0x2d58
+#define R200_PP_CUBIC_OFFSET_F5_3         0x2d5c
+#define R200_PP_CUBIC_OFFSET_F1_4         0x2d64
+#define R200_PP_CUBIC_OFFSET_F2_4         0x2d68
+#define R200_PP_CUBIC_OFFSET_F3_4         0x2d6c
+#define R200_PP_CUBIC_OFFSET_F4_4         0x2d70
+#define R200_PP_CUBIC_OFFSET_F5_4         0x2d74
+#define R200_PP_CUBIC_OFFSET_F1_5         0x2d7c
+#define R200_PP_CUBIC_OFFSET_F2_5         0x2d80
+#define R200_PP_CUBIC_OFFSET_F3_5         0x2d84
+#define R200_PP_CUBIC_OFFSET_F4_5         0x2d88
+#define R200_PP_CUBIC_OFFSET_F5_5         0x2d8c
+
+#define R200_RE_AUX_SCISSOR_CNTL          0x26f0
+#define R200_SE_VTE_CNTL                  0x20b0
+#define R200_SE_TCL_OUTPUT_VTX_COMP_SEL   0x2250
+#define R200_PP_TAM_DEBUG3                0x2d9c
+#define R200_PP_CNTL_X                    0x2cc4
+#define R200_SE_VAP_CNTL_STATUS           0x2140
+#define R200_RE_SCISSOR_TL_0              0x1cd8
+#define R200_RE_SCISSOR_TL_1              0x1ce0
+#define R200_RE_SCISSOR_TL_2              0x1ce8
+#define R200_RB3D_DEPTHXY_OFFSET          0x1d60 
+#define R200_RE_AUX_SCISSOR_CNTL          0x26f0
+#define R200_SE_VTX_STATE_CNTL            0x2180
+#define R200_RE_POINTSIZE                 0x2648
+#define R200_SE_TCL_INPUT_VTX_VECTOR_ADDR_0 0x2254
+
+#define RADEON_PP_TEX_SIZE_0                0x1d04  /* NPOT */
+#define RADEON_PP_TEX_SIZE_1                0x1d0c
+#define RADEON_PP_TEX_SIZE_2                0x1d14
+
+#define RADEON_PP_CUBIC_FACES_0             0x1d24
+#define RADEON_PP_CUBIC_FACES_1             0x1d28
+#define RADEON_PP_CUBIC_FACES_2             0x1d2c
+#define RADEON_PP_CUBIC_OFFSET_T0_0         0x1dd0	/* bits [31:5] */
+#define RADEON_PP_CUBIC_OFFSET_T1_0         0x1e00
+#define RADEON_PP_CUBIC_OFFSET_T2_0         0x1e14
+
+#define SE_VAP_CNTL__TCL_ENA_MASK                          0x00000001
+#define SE_VAP_CNTL__FORCE_W_TO_ONE_MASK                   0x00010000
+#define SE_VAP_CNTL__VF_MAX_VTX_NUM__SHIFT                 0x00000012
+#define SE_VTE_CNTL__VTX_XY_FMT_MASK                       0x00000100
+#define SE_VTE_CNTL__VTX_Z_FMT_MASK                        0x00000200
+#define SE_VTX_FMT_0__VTX_Z0_PRESENT_MASK                  0x00000001
+#define SE_VTX_FMT_0__VTX_W0_PRESENT_MASK                  0x00000002
+#define SE_VTX_FMT_0__VTX_COLOR_0_FMT__SHIFT               0x0000000b
+#define R200_3D_DRAW_IMMD_2      0xC0003500
+#define R200_SE_VTX_FMT_1                 0x208c
+#define R200_RE_CNTL                      0x1c50 
+
+#define R200_RB3D_BLENDCOLOR              0x3218
+
+#define R200_SE_TCL_POINT_SPRITE_CNTL     0x22c4
+
+#define R200_PP_TRI_PERF 0x2cf8
+
+/* Constants */
+#define RADEON_MAX_USEC_TIMEOUT		100000	/* 100 ms */
+
+#define RADEON_LAST_FRAME_REG		RADEON_SCRATCH_REG0
+#define RADEON_LAST_DISPATCH_REG	RADEON_SCRATCH_REG1
+#define RADEON_LAST_CLEAR_REG		RADEON_SCRATCH_REG2
+#define RADEON_LAST_SWI_REG		RADEON_SCRATCH_REG3
+#define RADEON_LAST_DISPATCH		1
+
+#define RADEON_MAX_VB_AGE		0x7fffffff
+#define RADEON_MAX_VB_VERTS		(0xffff)
+
+#define RADEON_RING_HIGH_MARK		128
+
+#define RADEON_READ(reg)	DRM_READ32(  dev_priv->mmio, (reg) )
+#define RADEON_WRITE(reg,val)	DRM_WRITE32( dev_priv->mmio, (reg), (val) )
+#define RADEON_READ8(reg)	DRM_READ8(  dev_priv->mmio, (reg) )
+#define RADEON_WRITE8(reg,val)	DRM_WRITE8( dev_priv->mmio, (reg), (val) )
+
+#define RADEON_WRITE_PLL( addr, val )					\
+do {									\
+	RADEON_WRITE8( RADEON_CLOCK_CNTL_INDEX,				\
+		       ((addr) & 0x1f) | RADEON_PLL_WR_EN );		\
+	RADEON_WRITE( RADEON_CLOCK_CNTL_DATA, (val) );			\
+} while (0)
+
+#define CP_PACKET0( reg, n )						\
+	(RADEON_CP_PACKET0 | ((n) << 16) | ((reg) >> 2))
+#define CP_PACKET0_TABLE( reg, n )					\
+	(RADEON_CP_PACKET0 | RADEON_ONE_REG_WR | ((n) << 16) | ((reg) >> 2))
+#define CP_PACKET1( reg0, reg1 )					\
+	(RADEON_CP_PACKET1 | (((reg1) >> 2) << 15) | ((reg0) >> 2))
+#define CP_PACKET2()							\
+	(RADEON_CP_PACKET2)
+#define CP_PACKET3( pkt, n )						\
+	(RADEON_CP_PACKET3 | (pkt) | ((n) << 16))
+
+
+/* ================================================================
+ * Engine control helper macros
+ */
+
+#define RADEON_WAIT_UNTIL_2D_IDLE() do {				\
+	OUT_RING( CP_PACKET0( RADEON_WAIT_UNTIL, 0 ) );			\
+	OUT_RING( (RADEON_WAIT_2D_IDLECLEAN |				\
+		   RADEON_WAIT_HOST_IDLECLEAN) );			\
+} while (0)
+
+#define RADEON_WAIT_UNTIL_3D_IDLE() do {				\
+	OUT_RING( CP_PACKET0( RADEON_WAIT_UNTIL, 0 ) );			\
+	OUT_RING( (RADEON_WAIT_3D_IDLECLEAN |				\
+		   RADEON_WAIT_HOST_IDLECLEAN) );			\
+} while (0)
+
+#define RADEON_WAIT_UNTIL_IDLE() do {					\
+	OUT_RING( CP_PACKET0( RADEON_WAIT_UNTIL, 0 ) );			\
+	OUT_RING( (RADEON_WAIT_2D_IDLECLEAN |				\
+		   RADEON_WAIT_3D_IDLECLEAN |				\
+		   RADEON_WAIT_HOST_IDLECLEAN) );			\
+} while (0)
+
+#define RADEON_WAIT_UNTIL_PAGE_FLIPPED() do {				\
+	OUT_RING( CP_PACKET0( RADEON_WAIT_UNTIL, 0 ) );			\
+	OUT_RING( RADEON_WAIT_CRTC_PFLIP );				\
+} while (0)
+
+#define RADEON_FLUSH_CACHE() do {					\
+	OUT_RING( CP_PACKET0( RADEON_RB2D_DSTCACHE_CTLSTAT, 0 ) );	\
+	OUT_RING( RADEON_RB2D_DC_FLUSH );				\
+} while (0)
+
+#define RADEON_PURGE_CACHE() do {					\
+	OUT_RING( CP_PACKET0( RADEON_RB2D_DSTCACHE_CTLSTAT, 0 ) );	\
+	OUT_RING( RADEON_RB2D_DC_FLUSH_ALL );				\
+} while (0)
+
+#define RADEON_FLUSH_ZCACHE() do {					\
+	OUT_RING( CP_PACKET0( RADEON_RB3D_ZCACHE_CTLSTAT, 0 ) );	\
+	OUT_RING( RADEON_RB3D_ZC_FLUSH );				\
+} while (0)
+
+#define RADEON_PURGE_ZCACHE() do {					\
+	OUT_RING( CP_PACKET0( RADEON_RB3D_ZCACHE_CTLSTAT, 0 ) );	\
+	OUT_RING( RADEON_RB3D_ZC_FLUSH_ALL );				\
+} while (0)
+
+
+/* ================================================================
+ * Misc helper macros
+ */
+
+/* Perfbox functionality only.  
+ */
+#define RING_SPACE_TEST_WITH_RETURN( dev_priv )				\
+do {									\
+	if (!(dev_priv->stats.boxes & RADEON_BOX_DMA_IDLE)) {		\
+		u32 head = GET_RING_HEAD( dev_priv );			\
+		if (head == dev_priv->ring.tail)			\
+			dev_priv->stats.boxes |= RADEON_BOX_DMA_IDLE;	\
+	}								\
+} while (0)
+
+#define VB_AGE_TEST_WITH_RETURN( dev_priv )				\
+do {									\
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;		\
+	if ( sarea_priv->last_dispatch >= RADEON_MAX_VB_AGE ) {		\
+		int __ret = radeon_do_cp_idle( dev_priv );		\
+		if ( __ret ) return __ret;				\
+		sarea_priv->last_dispatch = 0;				\
+		radeon_freelist_reset( dev );				\
+	}								\
+} while (0)
+
+#define RADEON_DISPATCH_AGE( age ) do {					\
+	OUT_RING( CP_PACKET0( RADEON_LAST_DISPATCH_REG, 0 ) );		\
+	OUT_RING( age );						\
+} while (0)
+
+#define RADEON_FRAME_AGE( age ) do {					\
+	OUT_RING( CP_PACKET0( RADEON_LAST_FRAME_REG, 0 ) );		\
+	OUT_RING( age );						\
+} while (0)
+
+#define RADEON_CLEAR_AGE( age ) do {					\
+	OUT_RING( CP_PACKET0( RADEON_LAST_CLEAR_REG, 0 ) );		\
+	OUT_RING( age );						\
+} while (0)
+
+
+/* ================================================================
+ * Ring control
+ */
+
+#define RADEON_VERBOSE	0
+
+#define RING_LOCALS	int write, _nr; unsigned int mask; u32 *ring;
+
+#define BEGIN_RING( n ) do {						\
+	if ( RADEON_VERBOSE ) {						\
+		DRM_INFO( "BEGIN_RING( %d ) in %s\n",			\
+			   n, __FUNCTION__ );				\
+	}								\
+	if ( dev_priv->ring.space <= (n) * sizeof(u32) ) {		\
+                COMMIT_RING();						\
+		radeon_wait_ring( dev_priv, (n) * sizeof(u32) );	\
+	}								\
+	_nr = n; dev_priv->ring.space -= (n) * sizeof(u32);		\
+	ring = dev_priv->ring.start;					\
+	write = dev_priv->ring.tail;					\
+	mask = dev_priv->ring.tail_mask;				\
+} while (0)
+
+#define ADVANCE_RING() do {						\
+	if ( RADEON_VERBOSE ) {						\
+		DRM_INFO( "ADVANCE_RING() wr=0x%06x tail=0x%06x\n",	\
+			  write, dev_priv->ring.tail );			\
+	}								\
+	if (((dev_priv->ring.tail + _nr) & mask) != write) {		\
+		DRM_ERROR( 						\
+			"ADVANCE_RING(): mismatch: nr: %x write: %x line: %d\n",	\
+			((dev_priv->ring.tail + _nr) & mask),		\
+			write, __LINE__);						\
+	} else								\
+		dev_priv->ring.tail = write;				\
+} while (0)
+
+#define COMMIT_RING() do {						\
+	/* Flush writes to ring */					\
+	DRM_MEMORYBARRIER();						\
+	GET_RING_HEAD( dev_priv );					\
+	RADEON_WRITE( RADEON_CP_RB_WPTR, dev_priv->ring.tail );		\
+	/* read from PCI bus to ensure correct posting */		\
+	RADEON_READ( RADEON_CP_RB_RPTR );				\
+} while (0)
+
+#define OUT_RING( x ) do {						\
+	if ( RADEON_VERBOSE ) {						\
+		DRM_INFO( "   OUT_RING( 0x%08x ) at 0x%x\n",		\
+			   (unsigned int)(x), write );			\
+	}								\
+	ring[write++] = (x);						\
+	write &= mask;							\
+} while (0)
+
+#define OUT_RING_REG( reg, val ) do {					\
+	OUT_RING( CP_PACKET0( reg, 0 ) );				\
+	OUT_RING( val );						\
+} while (0)
+
+
+#define OUT_RING_TABLE( tab, sz ) do {					\
+	int _size = (sz);					\
+	int *_tab = (int *)(tab);				\
+								\
+	if (write + _size > mask) {				\
+		int _i = (mask+1) - write;			\
+		_size -= _i;					\
+		while (_i > 0 ) {				\
+			*(int *)(ring + write) = *_tab++;	\
+			write++;				\
+			_i--;					\
+		}						\
+		write = 0;					\
+		_tab += _i;					\
+	}							\
+								\
+	while (_size > 0) {					\
+		*(ring + write) = *_tab++;			\
+		write++;					\
+		_size--;					\
+	}							\
+	write &= mask;						\
+} while (0)
+
+
+#endif /* __RADEON_DRV_H__ */
diff --git a/drivers/char/drm/radeon_irq.c b/drivers/char/drm/radeon_irq.c
new file mode 100644
index 0000000..5b18bee
--- /dev/null
+++ b/drivers/char/drm/radeon_irq.c
@@ -0,0 +1,251 @@
+/* radeon_irq.c -- IRQ handling for radeon -*- linux-c -*-
+ *
+ * Copyright (C) The Weather Channel, Inc.  2002.  All Rights Reserved.
+ * 
+ * The Weather Channel (TM) funded Tungsten Graphics to develop the
+ * initial release of the Radeon 8500 driver under the XFree86 license.
+ * This notice must be preserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ *    Michel D�zer <michel@daenzer.net>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+/* Interrupts - Used for device synchronization and flushing in the
+ * following circumstances:
+ *
+ * - Exclusive FB access with hw idle:
+ *    - Wait for GUI Idle (?) interrupt, then do normal flush.
+ *
+ * - Frame throttling, NV_fence:
+ *    - Drop marker irq's into command stream ahead of time.
+ *    - Wait on irq's with lock *not held*
+ *    - Check each for termination condition
+ *
+ * - Internally in cp_getbuffer, etc:
+ *    - as above, but wait with lock held???
+ *
+ * NOTE: These functions are misleadingly named -- the irq's aren't
+ * tied to dma at all, this is just a hangover from dri prehistory.
+ */
+
+irqreturn_t radeon_driver_irq_handler( DRM_IRQ_ARGS )
+{
+	drm_device_t *dev = (drm_device_t *) arg;
+	drm_radeon_private_t *dev_priv = 
+	   (drm_radeon_private_t *)dev->dev_private;
+   	u32 stat;
+
+	/* Only consider the bits we're interested in - others could be used
+	 * outside the DRM
+	 */
+	stat = RADEON_READ(RADEON_GEN_INT_STATUS)
+	     & (RADEON_SW_INT_TEST | RADEON_CRTC_VBLANK_STAT);
+	if (!stat)
+		return IRQ_NONE;
+
+	/* SW interrupt */
+	if (stat & RADEON_SW_INT_TEST) {
+		DRM_WAKEUP( &dev_priv->swi_queue );
+	}
+
+	/* VBLANK interrupt */
+	if (stat & RADEON_CRTC_VBLANK_STAT) {
+		atomic_inc(&dev->vbl_received);
+		DRM_WAKEUP(&dev->vbl_queue);
+		drm_vbl_send_signals( dev );
+	}
+
+	/* Acknowledge interrupts we handle */
+	RADEON_WRITE(RADEON_GEN_INT_STATUS, stat);
+	return IRQ_HANDLED;
+}
+
+static __inline__ void radeon_acknowledge_irqs(drm_radeon_private_t *dev_priv)
+{
+	u32 tmp = RADEON_READ( RADEON_GEN_INT_STATUS )
+		& (RADEON_SW_INT_TEST_ACK | RADEON_CRTC_VBLANK_STAT);
+	if (tmp)
+		RADEON_WRITE( RADEON_GEN_INT_STATUS, tmp );
+}
+
+static int radeon_emit_irq(drm_device_t *dev)
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	unsigned int ret;
+	RING_LOCALS;
+
+	atomic_inc(&dev_priv->swi_emitted);
+	ret = atomic_read(&dev_priv->swi_emitted);
+
+	BEGIN_RING( 4 );
+	OUT_RING_REG( RADEON_LAST_SWI_REG, ret );
+	OUT_RING_REG( RADEON_GEN_INT_STATUS, RADEON_SW_INT_FIRE );
+	ADVANCE_RING(); 
+ 	COMMIT_RING();
+
+	return ret;
+}
+
+
+static int radeon_wait_irq(drm_device_t *dev, int swi_nr)
+{
+  	drm_radeon_private_t *dev_priv = 
+	   (drm_radeon_private_t *)dev->dev_private;
+	int ret = 0;
+
+ 	if (RADEON_READ( RADEON_LAST_SWI_REG ) >= swi_nr)  
+ 		return 0; 
+
+	dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+	/* This is a hack to work around mysterious freezes on certain
+	 * systems:
+	 */ 
+	radeon_acknowledge_irqs( dev_priv );
+
+	DRM_WAIT_ON( ret, dev_priv->swi_queue, 3 * DRM_HZ, 
+		     RADEON_READ( RADEON_LAST_SWI_REG ) >= swi_nr );
+
+	return ret;
+}
+
+int radeon_driver_vblank_wait(drm_device_t *dev, unsigned int *sequence)
+{
+  	drm_radeon_private_t *dev_priv = 
+	   (drm_radeon_private_t *)dev->dev_private;
+	unsigned int cur_vblank;
+	int ret = 0;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	radeon_acknowledge_irqs( dev_priv );
+
+	dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+
+	/* Assume that the user has missed the current sequence number
+	 * by about a day rather than she wants to wait for years
+	 * using vertical blanks... 
+	 */
+	DRM_WAIT_ON( ret, dev->vbl_queue, 3*DRM_HZ, 
+		     ( ( ( cur_vblank = atomic_read(&dev->vbl_received ) )
+			 - *sequence ) <= (1<<23) ) );
+
+	*sequence = cur_vblank;
+
+	return ret;
+}
+
+
+/* Needs the lock as it touches the ring.
+ */
+int radeon_irq_emit( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_irq_emit_t emit;
+	int result;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( emit, (drm_radeon_irq_emit_t __user *)data,
+				  sizeof(emit) );
+
+	result = radeon_emit_irq( dev );
+
+	if ( DRM_COPY_TO_USER( emit.irq_seq, &result, sizeof(int) ) ) {
+		DRM_ERROR( "copy_to_user\n" );
+		return DRM_ERR(EFAULT);
+	}
+
+	return 0;
+}
+
+
+/* Doesn't need the hardware lock.
+ */
+int radeon_irq_wait( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_irq_wait_t irqwait;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( irqwait, (drm_radeon_irq_wait_t __user*)data,
+				  sizeof(irqwait) );
+
+	return radeon_wait_irq( dev, irqwait.irq_seq );
+}
+
+
+/* drm_dma.h hooks
+*/
+void radeon_driver_irq_preinstall( drm_device_t *dev ) {
+	drm_radeon_private_t *dev_priv =
+		(drm_radeon_private_t *)dev->dev_private;
+
+ 	/* Disable *all* interrupts */
+      	RADEON_WRITE( RADEON_GEN_INT_CNTL, 0 );
+
+	/* Clear bits if they're already high */
+	radeon_acknowledge_irqs( dev_priv );
+}
+
+void radeon_driver_irq_postinstall( drm_device_t *dev ) {
+	drm_radeon_private_t *dev_priv =
+		(drm_radeon_private_t *)dev->dev_private;
+
+   	atomic_set(&dev_priv->swi_emitted, 0);
+	DRM_INIT_WAITQUEUE( &dev_priv->swi_queue );
+
+	/* Turn on SW and VBL ints */
+   	RADEON_WRITE( RADEON_GEN_INT_CNTL,
+		      RADEON_CRTC_VBLANK_MASK |	
+		      RADEON_SW_INT_ENABLE );
+}
+
+void radeon_driver_irq_uninstall( drm_device_t *dev ) {
+	drm_radeon_private_t *dev_priv =
+		(drm_radeon_private_t *)dev->dev_private;
+	if (!dev_priv)
+		return;
+
+	/* Disable *all* interrupts */
+	RADEON_WRITE( RADEON_GEN_INT_CNTL, 0 );
+}
diff --git a/drivers/char/drm/radeon_mem.c b/drivers/char/drm/radeon_mem.c
new file mode 100644
index 0000000..134f894
--- /dev/null
+++ b/drivers/char/drm/radeon_mem.c
@@ -0,0 +1,322 @@
+/* radeon_mem.c -- Simple GART/fb memory manager for radeon -*- linux-c -*-
+ *
+ * Copyright (C) The Weather Channel, Inc.  2002.  All Rights Reserved.
+ * 
+ * The Weather Channel (TM) funded Tungsten Graphics to develop the
+ * initial release of the Radeon 8500 driver under the XFree86 license.
+ * This notice must be preserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Keith Whitwell <keith@tungstengraphics.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+/* Very simple allocator for GART memory, working on a static range
+ * already mapped into each client's address space.  
+ */
+
+static struct mem_block *split_block(struct mem_block *p, int start, int size,
+				     DRMFILE filp )
+{
+	/* Maybe cut off the start of an existing block */
+	if (start > p->start) {
+		struct mem_block *newblock = drm_alloc(sizeof(*newblock), DRM_MEM_BUFS );
+		if (!newblock) 
+			goto out;
+		newblock->start = start;
+		newblock->size = p->size - (start - p->start);
+		newblock->filp = NULL;
+		newblock->next = p->next;
+		newblock->prev = p;
+		p->next->prev = newblock;
+		p->next = newblock;
+		p->size -= newblock->size;
+		p = newblock;
+	}
+   
+	/* Maybe cut off the end of an existing block */
+	if (size < p->size) {
+		struct mem_block *newblock = drm_alloc(sizeof(*newblock), DRM_MEM_BUFS );
+		if (!newblock)
+			goto out;
+		newblock->start = start + size;
+		newblock->size = p->size - size;
+		newblock->filp = NULL;
+		newblock->next = p->next;
+		newblock->prev = p;
+		p->next->prev = newblock;
+		p->next = newblock;
+		p->size = size;
+	}
+
+ out:
+	/* Our block is in the middle */
+	p->filp = filp;
+	return p;
+}
+
+static struct mem_block *alloc_block( struct mem_block *heap, int size, 
+				      int align2, DRMFILE filp )
+{
+	struct mem_block *p;
+	int mask = (1 << align2)-1;
+
+	list_for_each(p, heap) {
+		int start = (p->start + mask) & ~mask;
+		if (p->filp == 0 && start + size <= p->start + p->size)
+			return split_block( p, start, size, filp );
+	}
+
+	return NULL;
+}
+
+static struct mem_block *find_block( struct mem_block *heap, int start )
+{
+	struct mem_block *p;
+
+	list_for_each(p, heap)
+		if (p->start == start)
+			return p;
+
+	return NULL;
+}
+
+
+static void free_block( struct mem_block *p )
+{
+	p->filp = NULL;
+
+	/* Assumes a single contiguous range.  Needs a special filp in
+	 * 'heap' to stop it being subsumed.
+	 */
+	if (p->next->filp == 0) {
+		struct mem_block *q = p->next;
+		p->size += q->size;
+		p->next = q->next;
+		p->next->prev = p;
+		drm_free(q, sizeof(*q), DRM_MEM_BUFS );
+	}
+
+	if (p->prev->filp == 0) {
+		struct mem_block *q = p->prev;
+		q->size += p->size;
+		q->next = p->next;
+		q->next->prev = q;
+		drm_free(p, sizeof(*q), DRM_MEM_BUFS );
+	}
+}
+
+/* Initialize.  How to check for an uninitialized heap?
+ */
+static int init_heap(struct mem_block **heap, int start, int size)
+{
+	struct mem_block *blocks = drm_alloc(sizeof(*blocks), DRM_MEM_BUFS );
+
+	if (!blocks) 
+		return DRM_ERR(ENOMEM);
+	
+	*heap = drm_alloc(sizeof(**heap), DRM_MEM_BUFS );
+	if (!*heap) {
+		drm_free( blocks, sizeof(*blocks), DRM_MEM_BUFS );
+		return DRM_ERR(ENOMEM);
+	}
+
+	blocks->start = start;
+	blocks->size = size;
+	blocks->filp = NULL;
+	blocks->next = blocks->prev = *heap;
+
+	memset( *heap, 0, sizeof(**heap) );
+	(*heap)->filp = (DRMFILE) -1;
+	(*heap)->next = (*heap)->prev = blocks;
+	return 0;
+}
+
+
+/* Free all blocks associated with the releasing file.
+ */
+void radeon_mem_release( DRMFILE filp, struct mem_block *heap )
+{
+	struct mem_block *p;
+
+	if (!heap || !heap->next)
+		return;
+
+	list_for_each(p, heap) {
+		if (p->filp == filp) 
+			p->filp = NULL;
+	}
+
+	/* Assumes a single contiguous range.  Needs a special filp in
+	 * 'heap' to stop it being subsumed.
+	 */
+	list_for_each(p, heap) {
+		while (p->filp == 0 && p->next->filp == 0) {
+			struct mem_block *q = p->next;
+			p->size += q->size;
+			p->next = q->next;
+			p->next->prev = p;
+			drm_free(q, sizeof(*q),DRM_MEM_DRIVER);
+		}
+	}
+}
+
+/* Shutdown.
+ */
+void radeon_mem_takedown( struct mem_block **heap )
+{
+	struct mem_block *p;
+	
+	if (!*heap)
+		return;
+
+	for (p = (*heap)->next ; p != *heap ; ) {
+		struct mem_block *q = p;
+		p = p->next;
+		drm_free(q, sizeof(*q),DRM_MEM_DRIVER);
+	}
+
+	drm_free( *heap, sizeof(**heap),DRM_MEM_DRIVER );
+	*heap = NULL;
+}
+
+
+
+/* IOCTL HANDLERS */
+
+static struct mem_block **get_heap( drm_radeon_private_t *dev_priv,
+				   int region )
+{
+	switch( region ) {
+	case RADEON_MEM_REGION_GART:
+ 		return &dev_priv->gart_heap; 
+	case RADEON_MEM_REGION_FB:
+		return &dev_priv->fb_heap;
+	default:
+		return NULL;
+	}
+}
+
+int radeon_mem_alloc( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_mem_alloc_t alloc;
+	struct mem_block *block, **heap;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( alloc, (drm_radeon_mem_alloc_t __user *)data,
+				  sizeof(alloc) );
+
+	heap = get_heap( dev_priv, alloc.region );
+	if (!heap || !*heap)
+		return DRM_ERR(EFAULT);
+	
+	/* Make things easier on ourselves: all allocations at least
+	 * 4k aligned.
+	 */
+	if (alloc.alignment < 12)
+		alloc.alignment = 12;
+
+	block = alloc_block( *heap, alloc.size, alloc.alignment,
+			     filp );
+
+	if (!block) 
+		return DRM_ERR(ENOMEM);
+
+	if ( DRM_COPY_TO_USER( alloc.region_offset, &block->start, 
+			       sizeof(int) ) ) {
+		DRM_ERROR( "copy_to_user\n" );
+		return DRM_ERR(EFAULT);
+	}
+	
+	return 0;
+}
+
+
+
+int radeon_mem_free( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_mem_free_t memfree;
+	struct mem_block *block, **heap;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( memfree, (drm_radeon_mem_free_t __user *)data,
+				  sizeof(memfree) );
+
+	heap = get_heap( dev_priv, memfree.region );
+	if (!heap || !*heap)
+		return DRM_ERR(EFAULT);
+	
+	block = find_block( *heap, memfree.region_offset );
+	if (!block)
+		return DRM_ERR(EFAULT);
+
+	if (block->filp != filp)
+		return DRM_ERR(EPERM);
+
+	free_block( block );	
+	return 0;
+}
+
+int radeon_mem_init_heap( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_mem_init_heap_t initheap;
+	struct mem_block **heap;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( initheap, (drm_radeon_mem_init_heap_t __user *)data,
+				  sizeof(initheap) );
+
+	heap = get_heap( dev_priv, initheap.region );
+	if (!heap) 
+		return DRM_ERR(EFAULT);
+	
+	if (*heap) {
+		DRM_ERROR("heap already initialized?");
+		return DRM_ERR(EFAULT);
+	}
+		
+	return init_heap( heap, initheap.start, initheap.size );
+}
+
+
diff --git a/drivers/char/drm/radeon_state.c b/drivers/char/drm/radeon_state.c
new file mode 100644
index 0000000..1f79e24
--- /dev/null
+++ b/drivers/char/drm/radeon_state.c
@@ -0,0 +1,3102 @@
+/* radeon_state.c -- State support for Radeon -*- linux-c -*-
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Fremont, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Gareth Hughes <gareth@valinux.com>
+ *    Kevin E. Martin <martin@valinux.com>
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "drm_sarea.h"
+#include "radeon_drm.h"
+#include "radeon_drv.h"
+
+/* ================================================================
+ * Helper functions for client state checking and fixup
+ */
+
+static __inline__ int radeon_check_and_fixup_offset( drm_radeon_private_t *dev_priv,
+						     drm_file_t *filp_priv,
+						     u32 *offset ) {
+	u32 off = *offset;
+	struct drm_radeon_driver_file_fields *radeon_priv;
+
+	if ( off >= dev_priv->fb_location &&
+	     off < ( dev_priv->gart_vm_start + dev_priv->gart_size ) )
+		return 0;
+
+	radeon_priv = filp_priv->driver_priv;
+	off += radeon_priv->radeon_fb_delta;
+
+	DRM_DEBUG( "offset fixed up to 0x%x\n", off );
+
+	if ( off < dev_priv->fb_location ||
+	     off >= ( dev_priv->gart_vm_start + dev_priv->gart_size ) )
+		return DRM_ERR( EINVAL );
+
+	*offset = off;
+
+	return 0;
+}
+
+static __inline__ int radeon_check_and_fixup_packets( drm_radeon_private_t *dev_priv,
+						      drm_file_t *filp_priv,
+						      int id,
+						      u32 __user *data ) {
+	switch ( id ) {
+
+	case RADEON_EMIT_PP_MISC:
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &data[( RADEON_RB3D_DEPTHOFFSET
+							    - RADEON_PP_MISC ) / 4] ) ) {
+			DRM_ERROR( "Invalid depth buffer offset\n" );
+			return DRM_ERR( EINVAL );
+		}
+		break;
+
+	case RADEON_EMIT_PP_CNTL:
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &data[( RADEON_RB3D_COLOROFFSET
+							    - RADEON_PP_CNTL ) / 4] ) ) {
+			DRM_ERROR( "Invalid colour buffer offset\n" );
+			return DRM_ERR( EINVAL );
+		}
+		break;
+
+	case R200_EMIT_PP_TXOFFSET_0:
+	case R200_EMIT_PP_TXOFFSET_1:
+	case R200_EMIT_PP_TXOFFSET_2:
+	case R200_EMIT_PP_TXOFFSET_3:
+	case R200_EMIT_PP_TXOFFSET_4:
+	case R200_EMIT_PP_TXOFFSET_5:
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &data[0] ) ) {
+			DRM_ERROR( "Invalid R200 texture offset\n" );
+			return DRM_ERR( EINVAL );
+		}
+		break;
+
+	case RADEON_EMIT_PP_TXFILTER_0:
+	case RADEON_EMIT_PP_TXFILTER_1:
+	case RADEON_EMIT_PP_TXFILTER_2:
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &data[( RADEON_PP_TXOFFSET_0
+							    - RADEON_PP_TXFILTER_0 ) / 4] ) ) {
+			DRM_ERROR( "Invalid R100 texture offset\n" );
+			return DRM_ERR( EINVAL );
+		}
+		break;
+
+	case R200_EMIT_PP_CUBIC_OFFSETS_0:
+	case R200_EMIT_PP_CUBIC_OFFSETS_1:
+	case R200_EMIT_PP_CUBIC_OFFSETS_2:
+	case R200_EMIT_PP_CUBIC_OFFSETS_3:
+	case R200_EMIT_PP_CUBIC_OFFSETS_4:
+	case R200_EMIT_PP_CUBIC_OFFSETS_5: {
+		int i;
+		for ( i = 0; i < 5; i++ ) {
+			if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+							    &data[i] ) ) {
+				DRM_ERROR( "Invalid R200 cubic texture offset\n" );
+				return DRM_ERR( EINVAL );
+			}
+		}
+		break;
+	}
+
+	case RADEON_EMIT_PP_CUBIC_OFFSETS_T0:
+	case RADEON_EMIT_PP_CUBIC_OFFSETS_T1:
+	case RADEON_EMIT_PP_CUBIC_OFFSETS_T2:{
+			int i;
+			for (i = 0; i < 5; i++) {
+				if (radeon_check_and_fixup_offset(dev_priv,
+								  filp_priv,
+								  &data[i])) {
+					DRM_ERROR
+					    ("Invalid R100 cubic texture offset\n");
+					return DRM_ERR(EINVAL);
+				}
+			}
+		}
+		break;
+
+	case RADEON_EMIT_RB3D_COLORPITCH:
+	case RADEON_EMIT_RE_LINE_PATTERN:
+	case RADEON_EMIT_SE_LINE_WIDTH:
+	case RADEON_EMIT_PP_LUM_MATRIX:
+	case RADEON_EMIT_PP_ROT_MATRIX_0:
+	case RADEON_EMIT_RB3D_STENCILREFMASK:
+	case RADEON_EMIT_SE_VPORT_XSCALE:
+	case RADEON_EMIT_SE_CNTL:
+	case RADEON_EMIT_SE_CNTL_STATUS:
+	case RADEON_EMIT_RE_MISC:
+	case RADEON_EMIT_PP_BORDER_COLOR_0:
+	case RADEON_EMIT_PP_BORDER_COLOR_1:
+	case RADEON_EMIT_PP_BORDER_COLOR_2:
+	case RADEON_EMIT_SE_ZBIAS_FACTOR:
+	case RADEON_EMIT_SE_TCL_OUTPUT_VTX_FMT:
+	case RADEON_EMIT_SE_TCL_MATERIAL_EMMISSIVE_RED:
+	case R200_EMIT_PP_TXCBLEND_0:
+	case R200_EMIT_PP_TXCBLEND_1:
+	case R200_EMIT_PP_TXCBLEND_2:
+	case R200_EMIT_PP_TXCBLEND_3:
+	case R200_EMIT_PP_TXCBLEND_4:
+	case R200_EMIT_PP_TXCBLEND_5:
+	case R200_EMIT_PP_TXCBLEND_6:
+	case R200_EMIT_PP_TXCBLEND_7:
+	case R200_EMIT_TCL_LIGHT_MODEL_CTL_0:
+	case R200_EMIT_TFACTOR_0:
+	case R200_EMIT_VTX_FMT_0:
+	case R200_EMIT_VAP_CTL:
+	case R200_EMIT_MATRIX_SELECT_0:
+	case R200_EMIT_TEX_PROC_CTL_2:
+	case R200_EMIT_TCL_UCP_VERT_BLEND_CTL:
+	case R200_EMIT_PP_TXFILTER_0:
+	case R200_EMIT_PP_TXFILTER_1:
+	case R200_EMIT_PP_TXFILTER_2:
+	case R200_EMIT_PP_TXFILTER_3:
+	case R200_EMIT_PP_TXFILTER_4:
+	case R200_EMIT_PP_TXFILTER_5:
+	case R200_EMIT_VTE_CNTL:
+	case R200_EMIT_OUTPUT_VTX_COMP_SEL:
+	case R200_EMIT_PP_TAM_DEBUG3:
+	case R200_EMIT_PP_CNTL_X:
+	case R200_EMIT_RB3D_DEPTHXY_OFFSET:
+	case R200_EMIT_RE_AUX_SCISSOR_CNTL:
+	case R200_EMIT_RE_SCISSOR_TL_0:
+	case R200_EMIT_RE_SCISSOR_TL_1:
+	case R200_EMIT_RE_SCISSOR_TL_2:
+	case R200_EMIT_SE_VAP_CNTL_STATUS:
+	case R200_EMIT_SE_VTX_STATE_CNTL:
+	case R200_EMIT_RE_POINTSIZE:
+	case R200_EMIT_TCL_INPUT_VTX_VECTOR_ADDR_0:
+	case R200_EMIT_PP_CUBIC_FACES_0:
+	case R200_EMIT_PP_CUBIC_FACES_1:
+	case R200_EMIT_PP_CUBIC_FACES_2:
+	case R200_EMIT_PP_CUBIC_FACES_3:
+	case R200_EMIT_PP_CUBIC_FACES_4:
+	case R200_EMIT_PP_CUBIC_FACES_5:
+	case RADEON_EMIT_PP_TEX_SIZE_0:
+	case RADEON_EMIT_PP_TEX_SIZE_1:
+	case RADEON_EMIT_PP_TEX_SIZE_2:
+	case R200_EMIT_RB3D_BLENDCOLOR:
+	case R200_EMIT_TCL_POINT_SPRITE_CNTL:
+	case RADEON_EMIT_PP_CUBIC_FACES_0:
+	case RADEON_EMIT_PP_CUBIC_FACES_1:
+	case RADEON_EMIT_PP_CUBIC_FACES_2:
+	case R200_EMIT_PP_TRI_PERF_CNTL:
+		/* These packets don't contain memory offsets */
+		break;
+
+	default:
+		DRM_ERROR( "Unknown state packet ID %d\n", id );
+		return DRM_ERR( EINVAL );
+	}
+
+	return 0;
+}
+
+static __inline__ int radeon_check_and_fixup_packet3( drm_radeon_private_t *dev_priv,
+						      drm_file_t *filp_priv,
+						      drm_radeon_cmd_buffer_t *cmdbuf,
+						      unsigned int *cmdsz ) {
+	u32 *cmd = (u32 *) cmdbuf->buf;
+
+	*cmdsz = 2 + ( ( cmd[0] & RADEON_CP_PACKET_COUNT_MASK ) >> 16 );
+
+	if ( ( cmd[0] & 0xc0000000 ) != RADEON_CP_PACKET3 ) {
+		DRM_ERROR( "Not a type 3 packet\n" );
+		return DRM_ERR( EINVAL );
+	}
+
+	if ( 4 * *cmdsz > cmdbuf->bufsz ) {
+		DRM_ERROR( "Packet size larger than size of data provided\n" );
+		return DRM_ERR( EINVAL );
+	}
+
+	/* Check client state and fix it up if necessary */
+	if ( cmd[0] & 0x8000 ) { /* MSB of opcode: next DWORD GUI_CNTL */
+		u32 offset;
+
+		if ( cmd[1] & ( RADEON_GMC_SRC_PITCH_OFFSET_CNTL
+			      | RADEON_GMC_DST_PITCH_OFFSET_CNTL ) ) {
+			offset = cmd[2] << 10;
+			if ( radeon_check_and_fixup_offset( dev_priv, filp_priv, &offset ) ) {
+				DRM_ERROR( "Invalid first packet offset\n" );
+				return DRM_ERR( EINVAL );
+			}
+			cmd[2] = ( cmd[2] & 0xffc00000 ) | offset >> 10;
+		}
+
+		if ( ( cmd[1] & RADEON_GMC_SRC_PITCH_OFFSET_CNTL ) &&
+		     ( cmd[1] & RADEON_GMC_DST_PITCH_OFFSET_CNTL ) ) {
+			offset = cmd[3] << 10;
+			if ( radeon_check_and_fixup_offset( dev_priv, filp_priv, &offset ) ) {
+				DRM_ERROR( "Invalid second packet offset\n" );
+				return DRM_ERR( EINVAL );
+			}
+			cmd[3] = ( cmd[3] & 0xffc00000 ) | offset >> 10;
+		}
+	}
+
+	return 0;
+}
+
+
+/* ================================================================
+ * CP hardware state programming functions
+ */
+
+static __inline__ void radeon_emit_clip_rect( drm_radeon_private_t *dev_priv,
+					  drm_clip_rect_t *box )
+{
+	RING_LOCALS;
+
+	DRM_DEBUG( "   box:  x1=%d y1=%d  x2=%d y2=%d\n",
+		   box->x1, box->y1, box->x2, box->y2 );
+
+	BEGIN_RING( 4 );
+	OUT_RING( CP_PACKET0( RADEON_RE_TOP_LEFT, 0 ) );
+	OUT_RING( (box->y1 << 16) | box->x1 );
+	OUT_RING( CP_PACKET0( RADEON_RE_WIDTH_HEIGHT, 0 ) );
+	OUT_RING( ((box->y2 - 1) << 16) | (box->x2 - 1) );
+	ADVANCE_RING();
+}
+
+/* Emit 1.1 state
+ */
+static int radeon_emit_state( drm_radeon_private_t *dev_priv,
+			      drm_file_t *filp_priv,
+			      drm_radeon_context_regs_t *ctx,
+			      drm_radeon_texture_regs_t *tex,
+			      unsigned int dirty )
+{
+	RING_LOCALS;
+	DRM_DEBUG( "dirty=0x%08x\n", dirty );
+
+	if ( dirty & RADEON_UPLOAD_CONTEXT ) {
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &ctx->rb3d_depthoffset ) ) {
+			DRM_ERROR( "Invalid depth buffer offset\n" );
+			return DRM_ERR( EINVAL );
+		}
+
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &ctx->rb3d_coloroffset ) ) {
+			DRM_ERROR( "Invalid depth buffer offset\n" );
+			return DRM_ERR( EINVAL );
+		}
+
+		BEGIN_RING( 14 );
+		OUT_RING( CP_PACKET0( RADEON_PP_MISC, 6 ) );
+		OUT_RING( ctx->pp_misc );
+		OUT_RING( ctx->pp_fog_color );
+		OUT_RING( ctx->re_solid_color );
+		OUT_RING( ctx->rb3d_blendcntl );
+		OUT_RING( ctx->rb3d_depthoffset );
+		OUT_RING( ctx->rb3d_depthpitch );
+		OUT_RING( ctx->rb3d_zstencilcntl );
+		OUT_RING( CP_PACKET0( RADEON_PP_CNTL, 2 ) );
+		OUT_RING( ctx->pp_cntl );
+		OUT_RING( ctx->rb3d_cntl );
+		OUT_RING( ctx->rb3d_coloroffset );
+		OUT_RING( CP_PACKET0( RADEON_RB3D_COLORPITCH, 0 ) );
+		OUT_RING( ctx->rb3d_colorpitch );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_VERTFMT ) {
+		BEGIN_RING( 2 );
+		OUT_RING( CP_PACKET0( RADEON_SE_COORD_FMT, 0 ) );
+		OUT_RING( ctx->se_coord_fmt );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_LINE ) {
+		BEGIN_RING( 5 );
+		OUT_RING( CP_PACKET0( RADEON_RE_LINE_PATTERN, 1 ) );
+		OUT_RING( ctx->re_line_pattern );
+		OUT_RING( ctx->re_line_state );
+		OUT_RING( CP_PACKET0( RADEON_SE_LINE_WIDTH, 0 ) );
+		OUT_RING( ctx->se_line_width );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_BUMPMAP ) {
+		BEGIN_RING( 5 );
+		OUT_RING( CP_PACKET0( RADEON_PP_LUM_MATRIX, 0 ) );
+		OUT_RING( ctx->pp_lum_matrix );
+		OUT_RING( CP_PACKET0( RADEON_PP_ROT_MATRIX_0, 1 ) );
+		OUT_RING( ctx->pp_rot_matrix_0 );
+		OUT_RING( ctx->pp_rot_matrix_1 );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_MASKS ) {
+		BEGIN_RING( 4 );
+		OUT_RING( CP_PACKET0( RADEON_RB3D_STENCILREFMASK, 2 ) );
+		OUT_RING( ctx->rb3d_stencilrefmask );
+		OUT_RING( ctx->rb3d_ropcntl );
+		OUT_RING( ctx->rb3d_planemask );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_VIEWPORT ) {
+		BEGIN_RING( 7 );
+		OUT_RING( CP_PACKET0( RADEON_SE_VPORT_XSCALE, 5 ) );
+		OUT_RING( ctx->se_vport_xscale );
+		OUT_RING( ctx->se_vport_xoffset );
+		OUT_RING( ctx->se_vport_yscale );
+		OUT_RING( ctx->se_vport_yoffset );
+		OUT_RING( ctx->se_vport_zscale );
+		OUT_RING( ctx->se_vport_zoffset );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_SETUP ) {
+		BEGIN_RING( 4 );
+		OUT_RING( CP_PACKET0( RADEON_SE_CNTL, 0 ) );
+		OUT_RING( ctx->se_cntl );
+		OUT_RING( CP_PACKET0( RADEON_SE_CNTL_STATUS, 0 ) );
+		OUT_RING( ctx->se_cntl_status );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_MISC ) {
+		BEGIN_RING( 2 );
+		OUT_RING( CP_PACKET0( RADEON_RE_MISC, 0 ) );
+		OUT_RING( ctx->re_misc );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_TEX0 ) {
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &tex[0].pp_txoffset ) ) {
+			DRM_ERROR( "Invalid texture offset for unit 0\n" );
+			return DRM_ERR( EINVAL );
+		}
+
+		BEGIN_RING( 9 );
+		OUT_RING( CP_PACKET0( RADEON_PP_TXFILTER_0, 5 ) );
+		OUT_RING( tex[0].pp_txfilter );
+		OUT_RING( tex[0].pp_txformat );
+		OUT_RING( tex[0].pp_txoffset );
+		OUT_RING( tex[0].pp_txcblend );
+		OUT_RING( tex[0].pp_txablend );
+		OUT_RING( tex[0].pp_tfactor );
+		OUT_RING( CP_PACKET0( RADEON_PP_BORDER_COLOR_0, 0 ) );
+		OUT_RING( tex[0].pp_border_color );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_TEX1 ) {
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &tex[1].pp_txoffset ) ) {
+			DRM_ERROR( "Invalid texture offset for unit 1\n" );
+			return DRM_ERR( EINVAL );
+		}
+
+		BEGIN_RING( 9 );
+		OUT_RING( CP_PACKET0( RADEON_PP_TXFILTER_1, 5 ) );
+		OUT_RING( tex[1].pp_txfilter );
+		OUT_RING( tex[1].pp_txformat );
+		OUT_RING( tex[1].pp_txoffset );
+		OUT_RING( tex[1].pp_txcblend );
+		OUT_RING( tex[1].pp_txablend );
+		OUT_RING( tex[1].pp_tfactor );
+		OUT_RING( CP_PACKET0( RADEON_PP_BORDER_COLOR_1, 0 ) );
+		OUT_RING( tex[1].pp_border_color );
+		ADVANCE_RING();
+	}
+
+	if ( dirty & RADEON_UPLOAD_TEX2 ) {
+		if ( radeon_check_and_fixup_offset( dev_priv, filp_priv,
+						    &tex[2].pp_txoffset ) ) {
+			DRM_ERROR( "Invalid texture offset for unit 2\n" );
+			return DRM_ERR( EINVAL );
+		}
+
+		BEGIN_RING( 9 );
+		OUT_RING( CP_PACKET0( RADEON_PP_TXFILTER_2, 5 ) );
+		OUT_RING( tex[2].pp_txfilter );
+		OUT_RING( tex[2].pp_txformat );
+		OUT_RING( tex[2].pp_txoffset );
+		OUT_RING( tex[2].pp_txcblend );
+		OUT_RING( tex[2].pp_txablend );
+		OUT_RING( tex[2].pp_tfactor );
+		OUT_RING( CP_PACKET0( RADEON_PP_BORDER_COLOR_2, 0 ) );
+		OUT_RING( tex[2].pp_border_color );
+		ADVANCE_RING();
+	}
+
+	return 0;
+}
+
+/* Emit 1.2 state
+ */
+static int radeon_emit_state2( drm_radeon_private_t *dev_priv,
+			       drm_file_t *filp_priv,
+			       drm_radeon_state_t *state )
+{
+	RING_LOCALS;
+
+	if (state->dirty & RADEON_UPLOAD_ZBIAS) {
+		BEGIN_RING( 3 );
+		OUT_RING( CP_PACKET0( RADEON_SE_ZBIAS_FACTOR, 1 ) );
+		OUT_RING( state->context2.se_zbias_factor ); 
+		OUT_RING( state->context2.se_zbias_constant ); 
+		ADVANCE_RING();
+	}
+
+	return radeon_emit_state( dev_priv, filp_priv, &state->context,
+			   state->tex, state->dirty );
+}
+
+/* New (1.3) state mechanism.  3 commands (packet, scalar, vector) in
+ * 1.3 cmdbuffers allow all previous state to be updated as well as
+ * the tcl scalar and vector areas.  
+ */
+static struct { 
+	int start; 
+	int len; 
+	const char *name;
+} packet[RADEON_MAX_STATE_PACKETS] = {
+	{ RADEON_PP_MISC,7,"RADEON_PP_MISC" },
+	{ RADEON_PP_CNTL,3,"RADEON_PP_CNTL" },
+	{ RADEON_RB3D_COLORPITCH,1,"RADEON_RB3D_COLORPITCH" },
+	{ RADEON_RE_LINE_PATTERN,2,"RADEON_RE_LINE_PATTERN" },
+	{ RADEON_SE_LINE_WIDTH,1,"RADEON_SE_LINE_WIDTH" },
+	{ RADEON_PP_LUM_MATRIX,1,"RADEON_PP_LUM_MATRIX" },
+	{ RADEON_PP_ROT_MATRIX_0,2,"RADEON_PP_ROT_MATRIX_0" },
+	{ RADEON_RB3D_STENCILREFMASK,3,"RADEON_RB3D_STENCILREFMASK" },
+	{ RADEON_SE_VPORT_XSCALE,6,"RADEON_SE_VPORT_XSCALE" },
+	{ RADEON_SE_CNTL,2,"RADEON_SE_CNTL" },
+	{ RADEON_SE_CNTL_STATUS,1,"RADEON_SE_CNTL_STATUS" },
+	{ RADEON_RE_MISC,1,"RADEON_RE_MISC" },
+	{ RADEON_PP_TXFILTER_0,6,"RADEON_PP_TXFILTER_0" },
+	{ RADEON_PP_BORDER_COLOR_0,1,"RADEON_PP_BORDER_COLOR_0" },
+	{ RADEON_PP_TXFILTER_1,6,"RADEON_PP_TXFILTER_1" },
+	{ RADEON_PP_BORDER_COLOR_1,1,"RADEON_PP_BORDER_COLOR_1" },
+	{ RADEON_PP_TXFILTER_2,6,"RADEON_PP_TXFILTER_2" },
+	{ RADEON_PP_BORDER_COLOR_2,1,"RADEON_PP_BORDER_COLOR_2" },
+	{ RADEON_SE_ZBIAS_FACTOR,2,"RADEON_SE_ZBIAS_FACTOR" },
+	{ RADEON_SE_TCL_OUTPUT_VTX_FMT,11,"RADEON_SE_TCL_OUTPUT_VTX_FMT" },
+	{ RADEON_SE_TCL_MATERIAL_EMMISSIVE_RED,17,"RADEON_SE_TCL_MATERIAL_EMMISSIVE_RED" },
+	{ R200_PP_TXCBLEND_0, 4, "R200_PP_TXCBLEND_0" },
+	{ R200_PP_TXCBLEND_1, 4, "R200_PP_TXCBLEND_1" },
+	{ R200_PP_TXCBLEND_2, 4, "R200_PP_TXCBLEND_2" },
+	{ R200_PP_TXCBLEND_3, 4, "R200_PP_TXCBLEND_3" },
+	{ R200_PP_TXCBLEND_4, 4, "R200_PP_TXCBLEND_4" },
+	{ R200_PP_TXCBLEND_5, 4, "R200_PP_TXCBLEND_5" },
+	{ R200_PP_TXCBLEND_6, 4, "R200_PP_TXCBLEND_6" },
+	{ R200_PP_TXCBLEND_7, 4, "R200_PP_TXCBLEND_7" },
+	{ R200_SE_TCL_LIGHT_MODEL_CTL_0, 6, "R200_SE_TCL_LIGHT_MODEL_CTL_0" },
+	{ R200_PP_TFACTOR_0, 6, "R200_PP_TFACTOR_0" },
+	{ R200_SE_VTX_FMT_0, 4, "R200_SE_VTX_FMT_0" },
+	{ R200_SE_VAP_CNTL, 1, "R200_SE_VAP_CNTL" },
+	{ R200_SE_TCL_MATRIX_SEL_0, 5, "R200_SE_TCL_MATRIX_SEL_0" },
+	{ R200_SE_TCL_TEX_PROC_CTL_2, 5, "R200_SE_TCL_TEX_PROC_CTL_2" },
+	{ R200_SE_TCL_UCP_VERT_BLEND_CTL, 1, "R200_SE_TCL_UCP_VERT_BLEND_CTL" },
+	{ R200_PP_TXFILTER_0, 6, "R200_PP_TXFILTER_0" },
+	{ R200_PP_TXFILTER_1, 6, "R200_PP_TXFILTER_1" },
+	{ R200_PP_TXFILTER_2, 6, "R200_PP_TXFILTER_2" },
+	{ R200_PP_TXFILTER_3, 6, "R200_PP_TXFILTER_3" },
+	{ R200_PP_TXFILTER_4, 6, "R200_PP_TXFILTER_4" },
+	{ R200_PP_TXFILTER_5, 6, "R200_PP_TXFILTER_5" },
+	{ R200_PP_TXOFFSET_0, 1, "R200_PP_TXOFFSET_0" },
+	{ R200_PP_TXOFFSET_1, 1, "R200_PP_TXOFFSET_1" },
+	{ R200_PP_TXOFFSET_2, 1, "R200_PP_TXOFFSET_2" },
+	{ R200_PP_TXOFFSET_3, 1, "R200_PP_TXOFFSET_3" },
+	{ R200_PP_TXOFFSET_4, 1, "R200_PP_TXOFFSET_4" },
+	{ R200_PP_TXOFFSET_5, 1, "R200_PP_TXOFFSET_5" },
+	{ R200_SE_VTE_CNTL, 1, "R200_SE_VTE_CNTL" },
+	{ R200_SE_TCL_OUTPUT_VTX_COMP_SEL, 1, "R200_SE_TCL_OUTPUT_VTX_COMP_SEL" },
+	{ R200_PP_TAM_DEBUG3, 1, "R200_PP_TAM_DEBUG3" },
+	{ R200_PP_CNTL_X, 1, "R200_PP_CNTL_X" }, 
+	{ R200_RB3D_DEPTHXY_OFFSET, 1, "R200_RB3D_DEPTHXY_OFFSET" }, 
+	{ R200_RE_AUX_SCISSOR_CNTL, 1, "R200_RE_AUX_SCISSOR_CNTL" }, 
+	{ R200_RE_SCISSOR_TL_0, 2, "R200_RE_SCISSOR_TL_0" }, 
+	{ R200_RE_SCISSOR_TL_1, 2, "R200_RE_SCISSOR_TL_1" }, 
+	{ R200_RE_SCISSOR_TL_2, 2, "R200_RE_SCISSOR_TL_2" }, 
+	{ R200_SE_VAP_CNTL_STATUS, 1, "R200_SE_VAP_CNTL_STATUS" }, 
+	{ R200_SE_VTX_STATE_CNTL, 1, "R200_SE_VTX_STATE_CNTL" }, 
+	{ R200_RE_POINTSIZE, 1, "R200_RE_POINTSIZE" }, 
+	{ R200_SE_TCL_INPUT_VTX_VECTOR_ADDR_0, 4, "R200_SE_TCL_INPUT_VTX_VECTOR_ADDR_0" },
+	{ R200_PP_CUBIC_FACES_0, 1, "R200_PP_CUBIC_FACES_0" }, /* 61 */
+	{ R200_PP_CUBIC_OFFSET_F1_0, 5, "R200_PP_CUBIC_OFFSET_F1_0" }, /* 62 */
+	{ R200_PP_CUBIC_FACES_1, 1, "R200_PP_CUBIC_FACES_1" },
+	{ R200_PP_CUBIC_OFFSET_F1_1, 5, "R200_PP_CUBIC_OFFSET_F1_1" },
+	{ R200_PP_CUBIC_FACES_2, 1, "R200_PP_CUBIC_FACES_2" },
+	{ R200_PP_CUBIC_OFFSET_F1_2, 5, "R200_PP_CUBIC_OFFSET_F1_2" },
+	{ R200_PP_CUBIC_FACES_3, 1, "R200_PP_CUBIC_FACES_3" },
+	{ R200_PP_CUBIC_OFFSET_F1_3, 5, "R200_PP_CUBIC_OFFSET_F1_3" },
+	{ R200_PP_CUBIC_FACES_4, 1, "R200_PP_CUBIC_FACES_4" },
+	{ R200_PP_CUBIC_OFFSET_F1_4, 5, "R200_PP_CUBIC_OFFSET_F1_4" },
+	{ R200_PP_CUBIC_FACES_5, 1, "R200_PP_CUBIC_FACES_5" },
+	{ R200_PP_CUBIC_OFFSET_F1_5, 5, "R200_PP_CUBIC_OFFSET_F1_5" },
+	{ RADEON_PP_TEX_SIZE_0, 2, "RADEON_PP_TEX_SIZE_0" },
+	{ RADEON_PP_TEX_SIZE_1, 2, "RADEON_PP_TEX_SIZE_1" },
+	{ RADEON_PP_TEX_SIZE_2, 2, "RADEON_PP_TEX_SIZE_2" },
+	{ R200_RB3D_BLENDCOLOR, 3, "R200_RB3D_BLENDCOLOR" },
+	{ R200_SE_TCL_POINT_SPRITE_CNTL, 1, "R200_SE_TCL_POINT_SPRITE_CNTL" },
+	{ RADEON_PP_CUBIC_FACES_0, 1, "RADEON_PP_CUBIC_FACES_0"},
+	{ RADEON_PP_CUBIC_OFFSET_T0_0, 5, "RADEON_PP_CUBIC_OFFSET_T0_0"},
+	{ RADEON_PP_CUBIC_FACES_1, 1, "RADEON_PP_CUBIC_FACES_1"},
+	{ RADEON_PP_CUBIC_OFFSET_T1_0, 5, "RADEON_PP_CUBIC_OFFSET_T1_0"},
+	{ RADEON_PP_CUBIC_FACES_2, 1, "RADEON_PP_CUBIC_FACES_2"},
+	{ RADEON_PP_CUBIC_OFFSET_T2_0, 5, "RADEON_PP_CUBIC_OFFSET_T2_0"},
+	{ R200_PP_TRI_PERF, 2, "R200_PP_TRI_PERF"},
+};
+
+
+
+/* ================================================================
+ * Performance monitoring functions
+ */
+
+static void radeon_clear_box( drm_radeon_private_t *dev_priv,
+			      int x, int y, int w, int h,
+			      int r, int g, int b )
+{
+	u32 color;
+	RING_LOCALS;
+
+	x += dev_priv->sarea_priv->boxes[0].x1;
+	y += dev_priv->sarea_priv->boxes[0].y1;
+
+	switch ( dev_priv->color_fmt ) {
+	case RADEON_COLOR_FORMAT_RGB565:
+		color = (((r & 0xf8) << 8) |
+			 ((g & 0xfc) << 3) |
+			 ((b & 0xf8) >> 3));
+		break;
+	case RADEON_COLOR_FORMAT_ARGB8888:
+	default:
+		color = (((0xff) << 24) | (r << 16) | (g <<  8) | b);
+		break;
+	}
+
+	BEGIN_RING( 4 );
+	RADEON_WAIT_UNTIL_3D_IDLE();		
+	OUT_RING( CP_PACKET0( RADEON_DP_WRITE_MASK, 0 ) );
+	OUT_RING( 0xffffffff );
+	ADVANCE_RING();
+
+	BEGIN_RING( 6 );
+
+	OUT_RING( CP_PACKET3( RADEON_CNTL_PAINT_MULTI, 4 ) );
+	OUT_RING( RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+		  RADEON_GMC_BRUSH_SOLID_COLOR |
+		  (dev_priv->color_fmt << 8) |
+		  RADEON_GMC_SRC_DATATYPE_COLOR |
+		  RADEON_ROP3_P |
+		  RADEON_GMC_CLR_CMP_CNTL_DIS );
+
+ 	if ( dev_priv->page_flipping && dev_priv->current_page == 1 ) { 
+		OUT_RING( dev_priv->front_pitch_offset );
+ 	} else {	 
+		OUT_RING( dev_priv->back_pitch_offset );
+ 	} 
+
+	OUT_RING( color );
+
+	OUT_RING( (x << 16) | y );
+	OUT_RING( (w << 16) | h );
+
+	ADVANCE_RING();
+}
+
+static void radeon_cp_performance_boxes( drm_radeon_private_t *dev_priv )
+{
+	/* Collapse various things into a wait flag -- trying to
+	 * guess if userspase slept -- better just to have them tell us.
+	 */
+	if (dev_priv->stats.last_frame_reads > 1 ||
+	    dev_priv->stats.last_clear_reads > dev_priv->stats.clears) {
+		dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+	}
+
+	if (dev_priv->stats.freelist_loops) {
+		dev_priv->stats.boxes |= RADEON_BOX_WAIT_IDLE;
+	}
+
+	/* Purple box for page flipping
+	 */
+	if ( dev_priv->stats.boxes & RADEON_BOX_FLIP ) 
+		radeon_clear_box( dev_priv, 4, 4, 8, 8, 255, 0, 255 );
+
+	/* Red box if we have to wait for idle at any point
+	 */
+	if ( dev_priv->stats.boxes & RADEON_BOX_WAIT_IDLE ) 
+		radeon_clear_box( dev_priv, 16, 4, 8, 8, 255, 0, 0 );
+
+	/* Blue box: lost context?
+	 */
+
+	/* Yellow box for texture swaps
+	 */
+	if ( dev_priv->stats.boxes & RADEON_BOX_TEXTURE_LOAD ) 
+		radeon_clear_box( dev_priv, 40, 4, 8, 8, 255, 255, 0 );
+
+	/* Green box if hardware never idles (as far as we can tell)
+	 */
+	if ( !(dev_priv->stats.boxes & RADEON_BOX_DMA_IDLE) ) 
+		radeon_clear_box( dev_priv, 64, 4, 8, 8, 0, 255, 0 );
+
+
+	/* Draw bars indicating number of buffers allocated 
+	 * (not a great measure, easily confused)
+	 */
+	if (dev_priv->stats.requested_bufs) {
+		if (dev_priv->stats.requested_bufs > 100)
+			dev_priv->stats.requested_bufs = 100;
+
+		radeon_clear_box( dev_priv, 4, 16,  
+				  dev_priv->stats.requested_bufs, 4,
+				  196, 128, 128 );
+	}
+
+	memset( &dev_priv->stats, 0, sizeof(dev_priv->stats) );
+
+}
+/* ================================================================
+ * CP command dispatch functions
+ */
+
+static void radeon_cp_dispatch_clear( drm_device_t *dev,
+				      drm_radeon_clear_t *clear,
+				      drm_radeon_clear_rect_t *depth_boxes )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_radeon_depth_clear_t *depth_clear = &dev_priv->depth_clear;
+	int nbox = sarea_priv->nbox;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	unsigned int flags = clear->flags;
+	u32 rb3d_cntl = 0, rb3d_stencilrefmask= 0;
+	int i;
+	RING_LOCALS;
+	DRM_DEBUG( "flags = 0x%x\n", flags );
+
+	dev_priv->stats.clears++;
+
+	if ( dev_priv->page_flipping && dev_priv->current_page == 1 ) {
+		unsigned int tmp = flags;
+
+		flags &= ~(RADEON_FRONT | RADEON_BACK);
+		if ( tmp & RADEON_FRONT ) flags |= RADEON_BACK;
+		if ( tmp & RADEON_BACK )  flags |= RADEON_FRONT;
+	}
+
+	if ( flags & (RADEON_FRONT | RADEON_BACK) ) {
+
+		BEGIN_RING( 4 );
+
+		/* Ensure the 3D stream is idle before doing a
+		 * 2D fill to clear the front or back buffer.
+		 */
+		RADEON_WAIT_UNTIL_3D_IDLE();
+		
+		OUT_RING( CP_PACKET0( RADEON_DP_WRITE_MASK, 0 ) );
+		OUT_RING( clear->color_mask );
+
+		ADVANCE_RING();
+
+		/* Make sure we restore the 3D state next time.
+		 */
+		dev_priv->sarea_priv->ctx_owner = 0;
+
+		for ( i = 0 ; i < nbox ; i++ ) {
+			int x = pbox[i].x1;
+			int y = pbox[i].y1;
+			int w = pbox[i].x2 - x;
+			int h = pbox[i].y2 - y;
+
+			DRM_DEBUG( "dispatch clear %d,%d-%d,%d flags 0x%x\n",
+				   x, y, w, h, flags );
+
+			if ( flags & RADEON_FRONT ) {
+				BEGIN_RING( 6 );
+				
+				OUT_RING( CP_PACKET3( RADEON_CNTL_PAINT_MULTI, 4 ) );
+				OUT_RING( RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+					  RADEON_GMC_BRUSH_SOLID_COLOR |
+					  (dev_priv->color_fmt << 8) |
+					  RADEON_GMC_SRC_DATATYPE_COLOR |
+					  RADEON_ROP3_P |
+					  RADEON_GMC_CLR_CMP_CNTL_DIS );
+
+				OUT_RING( dev_priv->front_pitch_offset );
+				OUT_RING( clear->clear_color );
+				
+				OUT_RING( (x << 16) | y );
+				OUT_RING( (w << 16) | h );
+				
+				ADVANCE_RING();
+			}
+			
+			if ( flags & RADEON_BACK ) {
+				BEGIN_RING( 6 );
+				
+				OUT_RING( CP_PACKET3( RADEON_CNTL_PAINT_MULTI, 4 ) );
+				OUT_RING( RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+					  RADEON_GMC_BRUSH_SOLID_COLOR |
+					  (dev_priv->color_fmt << 8) |
+					  RADEON_GMC_SRC_DATATYPE_COLOR |
+					  RADEON_ROP3_P |
+					  RADEON_GMC_CLR_CMP_CNTL_DIS );
+				
+				OUT_RING( dev_priv->back_pitch_offset );
+				OUT_RING( clear->clear_color );
+
+				OUT_RING( (x << 16) | y );
+				OUT_RING( (w << 16) | h );
+
+				ADVANCE_RING();
+			}
+		}
+	}
+	
+	/* hyper z clear */
+	/* no docs available, based on reverse engeneering by Stephane Marchesin */
+	if ((flags & (RADEON_DEPTH | RADEON_STENCIL)) && (flags & RADEON_CLEAR_FASTZ)) {
+
+		int i;
+		int depthpixperline = dev_priv->depth_fmt==RADEON_DEPTH_FORMAT_16BIT_INT_Z? 
+			(dev_priv->depth_pitch / 2): (dev_priv->depth_pitch / 4);
+		
+		u32 clearmask;
+
+		u32 tempRB3D_DEPTHCLEARVALUE = clear->clear_depth |
+			((clear->depth_mask & 0xff) << 24);
+	
+		
+		/* Make sure we restore the 3D state next time.
+		 * we haven't touched any "normal" state - still need this?
+		 */
+		dev_priv->sarea_priv->ctx_owner = 0;
+
+		if ((dev_priv->flags & CHIP_HAS_HIERZ) && (flags & RADEON_USE_HIERZ)) {
+		/* FIXME : reverse engineer that for Rx00 cards */
+		/* FIXME : the mask supposedly contains low-res z values. So can't set
+		   just to the max (0xff? or actually 0x3fff?), need to take z clear
+		   value into account? */
+		/* pattern seems to work for r100, though get slight
+		   rendering errors with glxgears. If hierz is not enabled for r100,
+		   only 4 bits which indicate clear (15,16,31,32, all zero) matter, the
+		   other ones are ignored, and the same clear mask can be used. That's
+		   very different behaviour than R200 which needs different clear mask
+		   and different number of tiles to clear if hierz is enabled or not !?!
+		*/
+			clearmask = (0xff<<22)|(0xff<<6)| 0x003f003f;
+		}
+		else {
+		/* clear mask : chooses the clearing pattern.
+		   rv250: could be used to clear only parts of macrotiles
+		   (but that would get really complicated...)?
+		   bit 0 and 1 (either or both of them ?!?!) are used to
+		   not clear tile (or maybe one of the bits indicates if the tile is
+		   compressed or not), bit 2 and 3 to not clear tile 1,...,.
+		   Pattern is as follows:
+		        | 0,1 | 4,5 | 8,9 |12,13|16,17|20,21|24,25|28,29|
+		   bits -------------------------------------------------
+		        | 2,3 | 6,7 |10,11|14,15|18,19|22,23|26,27|30,31|
+		   rv100: clearmask covers 2x8 4x1 tiles, but one clear still
+		   covers 256 pixels ?!?
+		*/
+			clearmask = 0x0;
+		}
+
+		BEGIN_RING( 8 );
+		RADEON_WAIT_UNTIL_2D_IDLE();
+		OUT_RING_REG( RADEON_RB3D_DEPTHCLEARVALUE,
+			tempRB3D_DEPTHCLEARVALUE);
+		/* what offset is this exactly ? */
+		OUT_RING_REG( RADEON_RB3D_ZMASKOFFSET, 0 );
+		/* need ctlstat, otherwise get some strange black flickering */
+		OUT_RING_REG( RADEON_RB3D_ZCACHE_CTLSTAT, RADEON_RB3D_ZC_FLUSH_ALL );
+		ADVANCE_RING();
+
+		for (i = 0; i < nbox; i++) {
+			int tileoffset, nrtilesx, nrtilesy, j;
+			/* it looks like r200 needs rv-style clears, at least if hierz is not enabled? */
+			if ((dev_priv->flags&CHIP_HAS_HIERZ) && !(dev_priv->microcode_version==UCODE_R200)) {
+				/* FIXME : figure this out for r200 (when hierz is enabled). Or
+				   maybe r200 actually doesn't need to put the low-res z value into
+				   the tile cache like r100, but just needs to clear the hi-level z-buffer?
+				   Works for R100, both with hierz and without.
+				   R100 seems to operate on 2x1 8x8 tiles, but...
+				   odd: offset/nrtiles need to be 64 pix (4 block) aligned? Potentially
+				   problematic with resolutions which are not 64 pix aligned? */
+				tileoffset = ((pbox[i].y1 >> 3) * depthpixperline + pbox[i].x1) >> 6;
+				nrtilesx = ((pbox[i].x2 & ~63) - (pbox[i].x1 & ~63)) >> 4;
+				nrtilesy = (pbox[i].y2 >> 3) - (pbox[i].y1 >> 3);
+				for (j = 0; j <= nrtilesy; j++) {
+					BEGIN_RING( 4 );
+					OUT_RING( CP_PACKET3( RADEON_3D_CLEAR_ZMASK, 2 ) );
+					/* first tile */
+					OUT_RING( tileoffset * 8 );
+					/* the number of tiles to clear */
+					OUT_RING( nrtilesx + 4 );
+					/* clear mask : chooses the clearing pattern. */
+					OUT_RING( clearmask );
+					ADVANCE_RING();
+					tileoffset += depthpixperline >> 6;
+				}
+			}
+			else if (dev_priv->microcode_version==UCODE_R200) {
+				/* works for rv250. */
+				/* find first macro tile (8x2 4x4 z-pixels on rv250) */
+				tileoffset = ((pbox[i].y1 >> 3) * depthpixperline + pbox[i].x1) >> 5;
+				nrtilesx = (pbox[i].x2 >> 5) - (pbox[i].x1 >> 5);
+				nrtilesy = (pbox[i].y2 >> 3) - (pbox[i].y1 >> 3);
+				for (j = 0; j <= nrtilesy; j++) {
+					BEGIN_RING( 4 );
+					OUT_RING( CP_PACKET3( RADEON_3D_CLEAR_ZMASK, 2 ) );
+					/* first tile */
+					/* judging by the first tile offset needed, could possibly
+					   directly address/clear 4x4 tiles instead of 8x2 * 4x4
+					   macro tiles, though would still need clear mask for
+					   right/bottom if truely 4x4 granularity is desired ? */
+					OUT_RING( tileoffset * 16 );
+					/* the number of tiles to clear */
+					OUT_RING( nrtilesx + 1 );
+					/* clear mask : chooses the clearing pattern. */
+					OUT_RING( clearmask );
+					ADVANCE_RING();
+					tileoffset += depthpixperline >> 5;
+				}
+			}
+			else { /* rv 100 */
+				/* rv100 might not need 64 pix alignment, who knows */
+				/* offsets are, hmm, weird */
+				tileoffset = ((pbox[i].y1 >> 4) * depthpixperline + pbox[i].x1) >> 6;
+				nrtilesx = ((pbox[i].x2 & ~63) - (pbox[i].x1 & ~63)) >> 4;
+				nrtilesy = (pbox[i].y2 >> 4) - (pbox[i].y1 >> 4);
+				for (j = 0; j <= nrtilesy; j++) {
+					BEGIN_RING( 4 );
+					OUT_RING( CP_PACKET3( RADEON_3D_CLEAR_ZMASK, 2 ) );
+					OUT_RING( tileoffset * 128 );
+					/* the number of tiles to clear */
+					OUT_RING( nrtilesx + 4 );
+					/* clear mask : chooses the clearing pattern. */
+					OUT_RING( clearmask );
+					ADVANCE_RING();
+					tileoffset += depthpixperline >> 6;
+				}
+			}
+		}
+
+		/* TODO don't always clear all hi-level z tiles */
+		if ((dev_priv->flags & CHIP_HAS_HIERZ) && (dev_priv->microcode_version==UCODE_R200)
+			&& (flags & RADEON_USE_HIERZ))
+		/* r100 and cards without hierarchical z-buffer have no high-level z-buffer */
+		/* FIXME : the mask supposedly contains low-res z values. So can't set
+		   just to the max (0xff? or actually 0x3fff?), need to take z clear
+		   value into account? */
+		{
+			BEGIN_RING( 4 );
+			OUT_RING( CP_PACKET3( RADEON_3D_CLEAR_HIZ, 2 ) );
+			OUT_RING( 0x0 ); /* First tile */
+			OUT_RING( 0x3cc0 );
+			OUT_RING( (0xff<<22)|(0xff<<6)| 0x003f003f);
+			ADVANCE_RING();
+		}
+	}
+
+	/* We have to clear the depth and/or stencil buffers by
+	 * rendering a quad into just those buffers.  Thus, we have to
+	 * make sure the 3D engine is configured correctly.
+	 */
+	if ((dev_priv->microcode_version == UCODE_R200) &&
+	    (flags & (RADEON_DEPTH | RADEON_STENCIL))) {
+
+		int tempPP_CNTL;
+		int tempRE_CNTL;
+		int tempRB3D_CNTL;
+		int tempRB3D_ZSTENCILCNTL;
+		int tempRB3D_STENCILREFMASK;
+		int tempRB3D_PLANEMASK;
+		int tempSE_CNTL;
+		int tempSE_VTE_CNTL;
+		int tempSE_VTX_FMT_0;
+		int tempSE_VTX_FMT_1;
+		int tempSE_VAP_CNTL;
+		int tempRE_AUX_SCISSOR_CNTL;
+
+		tempPP_CNTL = 0;
+		tempRE_CNTL = 0;
+
+		tempRB3D_CNTL = depth_clear->rb3d_cntl;
+
+		tempRB3D_ZSTENCILCNTL = depth_clear->rb3d_zstencilcntl;
+		tempRB3D_STENCILREFMASK = 0x0;
+
+		tempSE_CNTL = depth_clear->se_cntl;
+
+
+
+		/* Disable TCL */
+
+		tempSE_VAP_CNTL = (/* SE_VAP_CNTL__FORCE_W_TO_ONE_MASK |  */
+				   (0x9 << SE_VAP_CNTL__VF_MAX_VTX_NUM__SHIFT));
+
+		tempRB3D_PLANEMASK = 0x0;
+
+		tempRE_AUX_SCISSOR_CNTL = 0x0;
+
+		tempSE_VTE_CNTL =
+			SE_VTE_CNTL__VTX_XY_FMT_MASK |
+			SE_VTE_CNTL__VTX_Z_FMT_MASK;
+
+		/* Vertex format (X, Y, Z, W)*/
+		tempSE_VTX_FMT_0 =
+			SE_VTX_FMT_0__VTX_Z0_PRESENT_MASK |
+			SE_VTX_FMT_0__VTX_W0_PRESENT_MASK;
+		tempSE_VTX_FMT_1 = 0x0;
+
+
+		/* 
+		 * Depth buffer specific enables 
+		 */
+		if (flags & RADEON_DEPTH) {
+			/* Enable depth buffer */
+			tempRB3D_CNTL |= RADEON_Z_ENABLE;
+		} else {
+			/* Disable depth buffer */
+			tempRB3D_CNTL &= ~RADEON_Z_ENABLE;
+		}
+
+		/* 
+		 * Stencil buffer specific enables
+		 */
+		if ( flags & RADEON_STENCIL ) {
+			tempRB3D_CNTL |=  RADEON_STENCIL_ENABLE;
+			tempRB3D_STENCILREFMASK = clear->depth_mask; 
+		} else {
+			tempRB3D_CNTL &= ~RADEON_STENCIL_ENABLE;
+			tempRB3D_STENCILREFMASK = 0x00000000;
+		}
+
+		if (flags & RADEON_USE_COMP_ZBUF) {
+			tempRB3D_ZSTENCILCNTL |= RADEON_Z_COMPRESSION_ENABLE |
+				RADEON_Z_DECOMPRESSION_ENABLE;
+		}
+		if (flags & RADEON_USE_HIERZ) {
+			tempRB3D_ZSTENCILCNTL |= RADEON_Z_HIERARCHY_ENABLE;
+		}
+
+		BEGIN_RING( 26 );
+		RADEON_WAIT_UNTIL_2D_IDLE();
+
+		OUT_RING_REG( RADEON_PP_CNTL, tempPP_CNTL );
+		OUT_RING_REG( R200_RE_CNTL, tempRE_CNTL );
+		OUT_RING_REG( RADEON_RB3D_CNTL, tempRB3D_CNTL );
+		OUT_RING_REG( RADEON_RB3D_ZSTENCILCNTL,
+			      tempRB3D_ZSTENCILCNTL );
+		OUT_RING_REG( RADEON_RB3D_STENCILREFMASK, 
+			      tempRB3D_STENCILREFMASK );
+		OUT_RING_REG( RADEON_RB3D_PLANEMASK, tempRB3D_PLANEMASK );
+		OUT_RING_REG( RADEON_SE_CNTL, tempSE_CNTL );
+		OUT_RING_REG( R200_SE_VTE_CNTL, tempSE_VTE_CNTL );
+		OUT_RING_REG( R200_SE_VTX_FMT_0, tempSE_VTX_FMT_0 );
+		OUT_RING_REG( R200_SE_VTX_FMT_1, tempSE_VTX_FMT_1 );
+		OUT_RING_REG( R200_SE_VAP_CNTL, tempSE_VAP_CNTL );
+		OUT_RING_REG( R200_RE_AUX_SCISSOR_CNTL, 
+			      tempRE_AUX_SCISSOR_CNTL );
+		ADVANCE_RING();
+
+		/* Make sure we restore the 3D state next time.
+		 */
+		dev_priv->sarea_priv->ctx_owner = 0;
+
+		for ( i = 0 ; i < nbox ; i++ ) {
+			
+			/* Funny that this should be required -- 
+			 *  sets top-left?
+			 */
+			radeon_emit_clip_rect( dev_priv,
+					       &sarea_priv->boxes[i] );
+
+			BEGIN_RING( 14 );
+			OUT_RING( CP_PACKET3( R200_3D_DRAW_IMMD_2, 12 ) );
+			OUT_RING( (RADEON_PRIM_TYPE_RECT_LIST |
+				   RADEON_PRIM_WALK_RING |
+				   (3 << RADEON_NUM_VERTICES_SHIFT)) );
+			OUT_RING( depth_boxes[i].ui[CLEAR_X1] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_Y1] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+			OUT_RING( 0x3f800000 );
+			OUT_RING( depth_boxes[i].ui[CLEAR_X1] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_Y2] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+			OUT_RING( 0x3f800000 );
+			OUT_RING( depth_boxes[i].ui[CLEAR_X2] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_Y2] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+			OUT_RING( 0x3f800000 );
+			ADVANCE_RING();
+		}
+	} 
+	else if ( (flags & (RADEON_DEPTH | RADEON_STENCIL)) ) {
+
+		int tempRB3D_ZSTENCILCNTL = depth_clear->rb3d_zstencilcntl;
+
+		rb3d_cntl = depth_clear->rb3d_cntl;
+
+		if ( flags & RADEON_DEPTH ) {
+			rb3d_cntl |=  RADEON_Z_ENABLE;
+		} else {
+			rb3d_cntl &= ~RADEON_Z_ENABLE;
+		}
+
+		if ( flags & RADEON_STENCIL ) {
+			rb3d_cntl |=  RADEON_STENCIL_ENABLE;
+			rb3d_stencilrefmask = clear->depth_mask; /* misnamed field */
+		} else {
+			rb3d_cntl &= ~RADEON_STENCIL_ENABLE;
+			rb3d_stencilrefmask = 0x00000000;
+		}
+
+		if (flags & RADEON_USE_COMP_ZBUF) {
+			tempRB3D_ZSTENCILCNTL |= RADEON_Z_COMPRESSION_ENABLE |
+				RADEON_Z_DECOMPRESSION_ENABLE;
+		}
+		if (flags & RADEON_USE_HIERZ) {
+			tempRB3D_ZSTENCILCNTL |= RADEON_Z_HIERARCHY_ENABLE;
+		}
+
+		BEGIN_RING( 13 );
+		RADEON_WAIT_UNTIL_2D_IDLE();
+
+		OUT_RING( CP_PACKET0( RADEON_PP_CNTL, 1 ) );
+		OUT_RING( 0x00000000 );
+		OUT_RING( rb3d_cntl );
+		
+		OUT_RING_REG( RADEON_RB3D_ZSTENCILCNTL, tempRB3D_ZSTENCILCNTL );
+		OUT_RING_REG( RADEON_RB3D_STENCILREFMASK,
+			      rb3d_stencilrefmask );
+		OUT_RING_REG( RADEON_RB3D_PLANEMASK,
+			      0x00000000 );
+		OUT_RING_REG( RADEON_SE_CNTL,
+			      depth_clear->se_cntl );
+		ADVANCE_RING();
+
+		/* Make sure we restore the 3D state next time.
+		 */
+		dev_priv->sarea_priv->ctx_owner = 0;
+
+		for ( i = 0 ; i < nbox ; i++ ) {
+			
+			/* Funny that this should be required -- 
+			 *  sets top-left?
+			 */
+			radeon_emit_clip_rect( dev_priv,
+					       &sarea_priv->boxes[i] );
+
+			BEGIN_RING( 15 );
+
+			OUT_RING( CP_PACKET3( RADEON_3D_DRAW_IMMD, 13 ) );
+			OUT_RING( RADEON_VTX_Z_PRESENT |
+				  RADEON_VTX_PKCOLOR_PRESENT);
+			OUT_RING( (RADEON_PRIM_TYPE_RECT_LIST |
+				   RADEON_PRIM_WALK_RING |
+				   RADEON_MAOS_ENABLE |
+				   RADEON_VTX_FMT_RADEON_MODE |
+				   (3 << RADEON_NUM_VERTICES_SHIFT)) );
+
+
+			OUT_RING( depth_boxes[i].ui[CLEAR_X1] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_Y1] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+			OUT_RING( 0x0 );
+
+			OUT_RING( depth_boxes[i].ui[CLEAR_X1] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_Y2] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+			OUT_RING( 0x0 );
+
+			OUT_RING( depth_boxes[i].ui[CLEAR_X2] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_Y2] );
+			OUT_RING( depth_boxes[i].ui[CLEAR_DEPTH] );
+			OUT_RING( 0x0 );
+
+			ADVANCE_RING();
+		}
+	}
+
+	/* Increment the clear counter.  The client-side 3D driver must
+	 * wait on this value before performing the clear ioctl.  We
+	 * need this because the card's so damned fast...
+	 */
+	dev_priv->sarea_priv->last_clear++;
+
+	BEGIN_RING( 4 );
+
+	RADEON_CLEAR_AGE( dev_priv->sarea_priv->last_clear );
+	RADEON_WAIT_UNTIL_IDLE();
+
+	ADVANCE_RING();
+}
+
+static void radeon_cp_dispatch_swap( drm_device_t *dev )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int nbox = sarea_priv->nbox;
+	drm_clip_rect_t *pbox = sarea_priv->boxes;
+	int i;
+	RING_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	/* Do some trivial performance monitoring...
+	 */
+	if (dev_priv->do_boxes)
+		radeon_cp_performance_boxes( dev_priv );
+
+
+	/* Wait for the 3D stream to idle before dispatching the bitblt.
+	 * This will prevent data corruption between the two streams.
+	 */
+	BEGIN_RING( 2 );
+
+	RADEON_WAIT_UNTIL_3D_IDLE();
+
+	ADVANCE_RING();
+
+	for ( i = 0 ; i < nbox ; i++ ) {
+		int x = pbox[i].x1;
+		int y = pbox[i].y1;
+		int w = pbox[i].x2 - x;
+		int h = pbox[i].y2 - y;
+
+		DRM_DEBUG( "dispatch swap %d,%d-%d,%d\n",
+			   x, y, w, h );
+
+		BEGIN_RING( 7 );
+
+		OUT_RING( CP_PACKET3( RADEON_CNTL_BITBLT_MULTI, 5 ) );
+		OUT_RING( RADEON_GMC_SRC_PITCH_OFFSET_CNTL |
+			  RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+			  RADEON_GMC_BRUSH_NONE |
+			  (dev_priv->color_fmt << 8) |
+			  RADEON_GMC_SRC_DATATYPE_COLOR |
+			  RADEON_ROP3_S |
+			  RADEON_DP_SRC_SOURCE_MEMORY |
+			  RADEON_GMC_CLR_CMP_CNTL_DIS |
+			  RADEON_GMC_WR_MSK_DIS );
+		
+		/* Make this work even if front & back are flipped:
+		 */
+		if (dev_priv->current_page == 0) {
+			OUT_RING( dev_priv->back_pitch_offset );
+			OUT_RING( dev_priv->front_pitch_offset );
+		} 
+		else {
+			OUT_RING( dev_priv->front_pitch_offset );
+			OUT_RING( dev_priv->back_pitch_offset );
+		}
+
+		OUT_RING( (x << 16) | y );
+		OUT_RING( (x << 16) | y );
+		OUT_RING( (w << 16) | h );
+
+		ADVANCE_RING();
+	}
+
+	/* Increment the frame counter.  The client-side 3D driver must
+	 * throttle the framerate by waiting for this value before
+	 * performing the swapbuffer ioctl.
+	 */
+	dev_priv->sarea_priv->last_frame++;
+
+	BEGIN_RING( 4 );
+
+	RADEON_FRAME_AGE( dev_priv->sarea_priv->last_frame );
+	RADEON_WAIT_UNTIL_2D_IDLE();
+
+	ADVANCE_RING();
+}
+
+static void radeon_cp_dispatch_flip( drm_device_t *dev )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_sarea_t *sarea = (drm_sarea_t *)dev_priv->sarea->handle;
+	int offset = (dev_priv->current_page == 1)
+		   ? dev_priv->front_offset : dev_priv->back_offset;
+	RING_LOCALS;
+	DRM_DEBUG( "%s: page=%d pfCurrentPage=%d\n", 
+		__FUNCTION__, 
+		dev_priv->current_page,
+		dev_priv->sarea_priv->pfCurrentPage);
+
+	/* Do some trivial performance monitoring...
+	 */
+	if (dev_priv->do_boxes) {
+		dev_priv->stats.boxes |= RADEON_BOX_FLIP;
+		radeon_cp_performance_boxes( dev_priv );
+	}
+
+	/* Update the frame offsets for both CRTCs
+	 */
+	BEGIN_RING( 6 );
+
+	RADEON_WAIT_UNTIL_3D_IDLE();
+	OUT_RING_REG( RADEON_CRTC_OFFSET, ( ( sarea->frame.y * dev_priv->front_pitch
+					      + sarea->frame.x 
+					      * ( dev_priv->color_fmt - 2 ) ) & ~7 )
+					  + offset );
+	OUT_RING_REG( RADEON_CRTC2_OFFSET, dev_priv->sarea_priv->crtc2_base
+					   + offset );
+
+	ADVANCE_RING();
+
+	/* Increment the frame counter.  The client-side 3D driver must
+	 * throttle the framerate by waiting for this value before
+	 * performing the swapbuffer ioctl.
+	 */
+	dev_priv->sarea_priv->last_frame++;
+	dev_priv->sarea_priv->pfCurrentPage = dev_priv->current_page =
+					      1 - dev_priv->current_page;
+
+	BEGIN_RING( 2 );
+
+	RADEON_FRAME_AGE( dev_priv->sarea_priv->last_frame );
+
+	ADVANCE_RING();
+}
+
+static int bad_prim_vertex_nr( int primitive, int nr )
+{
+	switch (primitive & RADEON_PRIM_TYPE_MASK) {
+	case RADEON_PRIM_TYPE_NONE:
+	case RADEON_PRIM_TYPE_POINT:
+		return nr < 1;
+	case RADEON_PRIM_TYPE_LINE:
+		return (nr & 1) || nr == 0;
+	case RADEON_PRIM_TYPE_LINE_STRIP:
+		return nr < 2;
+	case RADEON_PRIM_TYPE_TRI_LIST:
+	case RADEON_PRIM_TYPE_3VRT_POINT_LIST:
+	case RADEON_PRIM_TYPE_3VRT_LINE_LIST:
+	case RADEON_PRIM_TYPE_RECT_LIST:
+		return nr % 3 || nr == 0;
+	case RADEON_PRIM_TYPE_TRI_FAN:
+	case RADEON_PRIM_TYPE_TRI_STRIP:
+		return nr < 3;
+	default:
+		return 1;
+	}	
+}
+
+
+
+typedef struct {
+	unsigned int start;
+	unsigned int finish;
+	unsigned int prim;
+	unsigned int numverts;
+	unsigned int offset;   
+        unsigned int vc_format;
+} drm_radeon_tcl_prim_t;
+
+static void radeon_cp_dispatch_vertex( drm_device_t *dev,
+				       drm_buf_t *buf,
+				       drm_radeon_tcl_prim_t *prim )
+
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int offset = dev_priv->gart_buffers_offset + buf->offset + prim->start;
+	int numverts = (int)prim->numverts;
+	int nbox = sarea_priv->nbox;
+	int i = 0;
+	RING_LOCALS;
+
+	DRM_DEBUG("hwprim 0x%x vfmt 0x%x %d..%d %d verts\n",
+		  prim->prim,
+		  prim->vc_format,
+		  prim->start,
+		  prim->finish,
+		  prim->numverts);
+
+	if (bad_prim_vertex_nr( prim->prim, prim->numverts )) {
+		DRM_ERROR( "bad prim %x numverts %d\n", 
+			   prim->prim, prim->numverts );
+		return;
+	}
+
+	do {
+		/* Emit the next cliprect */
+		if ( i < nbox ) {
+			radeon_emit_clip_rect( dev_priv, 
+					       &sarea_priv->boxes[i] );
+		}
+
+		/* Emit the vertex buffer rendering commands */
+		BEGIN_RING( 5 );
+
+		OUT_RING( CP_PACKET3( RADEON_3D_RNDR_GEN_INDX_PRIM, 3 ) );
+		OUT_RING( offset );
+		OUT_RING( numverts );
+		OUT_RING( prim->vc_format );
+		OUT_RING( prim->prim | RADEON_PRIM_WALK_LIST |
+			  RADEON_COLOR_ORDER_RGBA |
+			  RADEON_VTX_FMT_RADEON_MODE |
+			  (numverts << RADEON_NUM_VERTICES_SHIFT) );
+
+		ADVANCE_RING();
+
+		i++;
+	} while ( i < nbox );
+}
+
+
+
+static void radeon_cp_discard_buffer( drm_device_t *dev, drm_buf_t *buf )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_buf_priv_t *buf_priv = buf->dev_private;
+	RING_LOCALS;
+
+	buf_priv->age = ++dev_priv->sarea_priv->last_dispatch;
+
+	/* Emit the vertex buffer age */
+	BEGIN_RING( 2 );
+	RADEON_DISPATCH_AGE( buf_priv->age );
+	ADVANCE_RING();
+
+	buf->pending = 1;
+	buf->used = 0;
+}
+
+static void radeon_cp_dispatch_indirect( drm_device_t *dev,
+					 drm_buf_t *buf,
+					 int start, int end )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	RING_LOCALS;
+	DRM_DEBUG( "indirect: buf=%d s=0x%x e=0x%x\n",
+		   buf->idx, start, end );
+
+	if ( start != end ) {
+		int offset = (dev_priv->gart_buffers_offset
+			      + buf->offset + start);
+		int dwords = (end - start + 3) / sizeof(u32);
+
+		/* Indirect buffer data must be an even number of
+		 * dwords, so if we've been given an odd number we must
+		 * pad the data with a Type-2 CP packet.
+		 */
+		if ( dwords & 1 ) {
+			u32 *data = (u32 *)
+				((char *)dev->agp_buffer_map->handle
+				 + buf->offset + start);
+			data[dwords++] = RADEON_CP_PACKET2;
+		}
+
+		/* Fire off the indirect buffer */
+		BEGIN_RING( 3 );
+
+		OUT_RING( CP_PACKET0( RADEON_CP_IB_BASE, 1 ) );
+		OUT_RING( offset );
+		OUT_RING( dwords );
+
+		ADVANCE_RING();
+	}
+}
+
+
+static void radeon_cp_dispatch_indices( drm_device_t *dev,
+					drm_buf_t *elt_buf,
+					drm_radeon_tcl_prim_t *prim )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	int offset = dev_priv->gart_buffers_offset + prim->offset;
+	u32 *data;
+	int dwords;
+	int i = 0;
+	int start = prim->start + RADEON_INDEX_PRIM_OFFSET;
+	int count = (prim->finish - start) / sizeof(u16);
+	int nbox = sarea_priv->nbox;
+
+	DRM_DEBUG("hwprim 0x%x vfmt 0x%x %d..%d offset: %x nr %d\n",
+		  prim->prim,
+		  prim->vc_format,
+		  prim->start,
+		  prim->finish,
+		  prim->offset,
+		  prim->numverts);
+
+	if (bad_prim_vertex_nr( prim->prim, count )) {
+		DRM_ERROR( "bad prim %x count %d\n", 
+			   prim->prim, count );
+		return;
+	}
+
+
+	if ( start >= prim->finish ||
+	     (prim->start & 0x7) ) {
+		DRM_ERROR( "buffer prim %d\n", prim->prim );
+		return;
+	}
+
+	dwords = (prim->finish - prim->start + 3) / sizeof(u32);
+
+	data = (u32 *)((char *)dev->agp_buffer_map->handle +
+		       elt_buf->offset + prim->start);
+
+	data[0] = CP_PACKET3( RADEON_3D_RNDR_GEN_INDX_PRIM, dwords-2 );
+	data[1] = offset;
+	data[2] = prim->numverts;
+	data[3] = prim->vc_format;
+	data[4] = (prim->prim |
+		   RADEON_PRIM_WALK_IND |
+		   RADEON_COLOR_ORDER_RGBA |
+		   RADEON_VTX_FMT_RADEON_MODE |
+		   (count << RADEON_NUM_VERTICES_SHIFT) );
+
+	do {
+		if ( i < nbox ) 
+			radeon_emit_clip_rect( dev_priv, 
+					       &sarea_priv->boxes[i] );
+
+		radeon_cp_dispatch_indirect( dev, elt_buf,
+					     prim->start,
+					     prim->finish );
+
+		i++;
+	} while ( i < nbox );
+
+}
+
+#define RADEON_MAX_TEXTURE_SIZE (RADEON_BUFFER_SIZE - 8 * sizeof(u32))
+
+static int radeon_cp_dispatch_texture( DRMFILE filp,
+				       drm_device_t *dev,
+				       drm_radeon_texture_t *tex,
+				       drm_radeon_tex_image_t *image )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_file_t *filp_priv;
+	drm_buf_t *buf;
+	u32 format;
+	u32 *buffer;
+	const u8 __user *data;
+	int size, dwords, tex_width, blit_width;
+	u32 height;
+	int i;
+	u32 texpitch, microtile;
+	RING_LOCALS;
+
+	DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+	if ( radeon_check_and_fixup_offset( dev_priv, filp_priv, &tex->offset ) ) {
+		DRM_ERROR( "Invalid destination offset\n" );
+		return DRM_ERR( EINVAL );
+	}
+
+	dev_priv->stats.boxes |= RADEON_BOX_TEXTURE_LOAD;
+
+	/* Flush the pixel cache.  This ensures no pixel data gets mixed
+	 * up with the texture data from the host data blit, otherwise
+	 * part of the texture image may be corrupted.
+	 */
+	BEGIN_RING( 4 );
+	RADEON_FLUSH_CACHE();
+	RADEON_WAIT_UNTIL_IDLE();
+	ADVANCE_RING();
+
+#ifdef __BIG_ENDIAN
+	/* The Mesa texture functions provide the data in little endian as the
+	 * chip wants it, but we need to compensate for the fact that the CP
+	 * ring gets byte-swapped
+	 */
+	BEGIN_RING( 2 );
+	OUT_RING_REG( RADEON_RBBM_GUICNTL, RADEON_HOST_DATA_SWAP_32BIT );
+	ADVANCE_RING();
+#endif
+
+
+	/* The compiler won't optimize away a division by a variable,
+	 * even if the only legal values are powers of two.  Thus, we'll
+	 * use a shift instead.
+	 */
+	switch ( tex->format ) {
+	case RADEON_TXFORMAT_ARGB8888:
+	case RADEON_TXFORMAT_RGBA8888:
+		format = RADEON_COLOR_FORMAT_ARGB8888;
+		tex_width = tex->width * 4;
+		blit_width = image->width * 4;
+		break;
+	case RADEON_TXFORMAT_AI88:
+	case RADEON_TXFORMAT_ARGB1555:
+	case RADEON_TXFORMAT_RGB565:
+	case RADEON_TXFORMAT_ARGB4444:
+	case RADEON_TXFORMAT_VYUY422:
+	case RADEON_TXFORMAT_YVYU422:
+		format = RADEON_COLOR_FORMAT_RGB565;
+		tex_width = tex->width * 2;
+		blit_width = image->width * 2;
+		break;
+	case RADEON_TXFORMAT_I8:
+	case RADEON_TXFORMAT_RGB332:
+		format = RADEON_COLOR_FORMAT_CI8;
+		tex_width = tex->width * 1;
+		blit_width = image->width * 1;
+		break;
+	default:
+		DRM_ERROR( "invalid texture format %d\n", tex->format );
+		return DRM_ERR(EINVAL);
+	}
+	texpitch = tex->pitch;
+	if ((texpitch << 22) & RADEON_DST_TILE_MICRO) {
+		microtile = 1;
+		if (tex_width < 64) {
+			texpitch &= ~(RADEON_DST_TILE_MICRO >> 22);
+			/* we got tiled coordinates, untile them */
+			image->x *= 2;
+		}
+	}
+	else microtile = 0;
+
+	DRM_DEBUG("tex=%dx%d blit=%d\n", tex_width, tex->height, blit_width );
+
+	do {
+		DRM_DEBUG( "tex: ofs=0x%x p=%d f=%d x=%hd y=%hd w=%hd h=%hd\n",
+			   tex->offset >> 10, tex->pitch, tex->format,
+			   image->x, image->y, image->width, image->height );
+
+		/* Make a copy of some parameters in case we have to
+		 * update them for a multi-pass texture blit.
+		 */
+		height = image->height;
+		data = (const u8 __user *)image->data;
+		
+		size = height * blit_width;
+
+		if ( size > RADEON_MAX_TEXTURE_SIZE ) {
+			height = RADEON_MAX_TEXTURE_SIZE / blit_width;
+			size = height * blit_width;
+		} else if ( size < 4 && size > 0 ) {
+			size = 4;
+		} else if ( size == 0 ) {
+			return 0;
+		}
+
+		buf = radeon_freelist_get( dev );
+		if ( 0 && !buf ) {
+			radeon_do_cp_idle( dev_priv );
+			buf = radeon_freelist_get( dev );
+		}
+		if ( !buf ) {
+			DRM_DEBUG("radeon_cp_dispatch_texture: EAGAIN\n");
+			if (DRM_COPY_TO_USER( tex->image, image, sizeof(*image) ))
+				return DRM_ERR(EFAULT);
+			return DRM_ERR(EAGAIN);
+		}
+
+
+		/* Dispatch the indirect buffer.
+		 */
+		buffer = (u32*)((char*)dev->agp_buffer_map->handle + buf->offset);
+		dwords = size / 4;
+		buffer[0] = CP_PACKET3( RADEON_CNTL_HOSTDATA_BLT, dwords + 6 );
+		buffer[1] = (RADEON_GMC_DST_PITCH_OFFSET_CNTL |
+			     RADEON_GMC_BRUSH_NONE |
+			     (format << 8) |
+			     RADEON_GMC_SRC_DATATYPE_COLOR |
+			     RADEON_ROP3_S |
+			     RADEON_DP_SRC_SOURCE_HOST_DATA |
+			     RADEON_GMC_CLR_CMP_CNTL_DIS |
+			     RADEON_GMC_WR_MSK_DIS);
+		
+		buffer[2] = (texpitch << 22) | (tex->offset >> 10);
+		buffer[3] = 0xffffffff;
+		buffer[4] = 0xffffffff;
+		buffer[5] = (image->y << 16) | image->x;
+		buffer[6] = (height << 16) | image->width;
+		buffer[7] = dwords;
+		buffer += 8;
+
+		
+
+		if (microtile) {
+			/* texture micro tiling in use, minimum texture width is thus 16 bytes.
+			   however, we cannot use blitter directly for texture width < 64 bytes,
+			   since minimum tex pitch is 64 bytes and we need this to match
+			   the texture width, otherwise the blitter will tile it wrong.
+			   Thus, tiling manually in this case. Additionally, need to special
+			   case tex height = 1, since our actual image will have height 2
+			   and we need to ensure we don't read beyond the texture size
+			   from user space. */
+			if (tex->height == 1) {
+				if (tex_width >= 64 || tex_width <= 16) {
+					if (DRM_COPY_FROM_USER(buffer, data,
+							       tex_width * sizeof(u32))) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n",
+							  tex_width);
+						return DRM_ERR(EFAULT);
+					}
+				} else if (tex_width == 32) {
+					if (DRM_COPY_FROM_USER(buffer, data, 16)) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n",
+							  tex_width);
+						return DRM_ERR(EFAULT);
+					}
+					if (DRM_COPY_FROM_USER(buffer + 8, data + 16, 16)) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n",
+							  tex_width);
+						return DRM_ERR(EFAULT);
+					}
+				}
+			} else if (tex_width >= 64 || tex_width == 16) {
+				if (DRM_COPY_FROM_USER(buffer, data,
+						       dwords * sizeof(u32))) {
+					DRM_ERROR("EFAULT on data, %d dwords\n",
+						  dwords);
+					return DRM_ERR(EFAULT);
+				}
+			} else if (tex_width < 16) {
+				for (i = 0; i < tex->height; i++) {
+					if (DRM_COPY_FROM_USER(buffer, data, tex_width)) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n",
+							  tex_width);
+						return DRM_ERR(EFAULT);
+					}
+					buffer += 4;
+					data += tex_width;
+				}
+			} else if (tex_width == 32) {
+				/* TODO: make sure this works when not fitting in one buffer
+				   (i.e. 32bytes x 2048...) */
+				for (i = 0; i < tex->height; i += 2) {
+					if (DRM_COPY_FROM_USER(buffer, data, 16)) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n",
+							  tex_width);
+						return DRM_ERR(EFAULT);
+					}
+					data += 16;
+					if (DRM_COPY_FROM_USER(buffer + 8, data, 16)) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n",
+							  tex_width);
+						return DRM_ERR(EFAULT);
+					}
+					data += 16;
+					if (DRM_COPY_FROM_USER(buffer + 4, data, 16)) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n",
+							  tex_width);
+						return DRM_ERR(EFAULT);
+					}
+					data += 16;
+					if (DRM_COPY_FROM_USER(buffer + 12, data, 16)) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n",
+							  tex_width);
+						return DRM_ERR(EFAULT);
+					}
+					data += 16;
+					buffer += 16;
+				}
+			}
+		}
+		else {
+			if (tex_width >= 32) {
+				/* Texture image width is larger than the minimum, so we
+				 * can upload it directly.
+				 */
+				if (DRM_COPY_FROM_USER(buffer, data,
+						       dwords * sizeof(u32))) {
+					DRM_ERROR("EFAULT on data, %d dwords\n",
+						  dwords);
+					return DRM_ERR(EFAULT);
+				}
+			} else {
+				/* Texture image width is less than the minimum, so we
+				 * need to pad out each image scanline to the minimum
+				 * width.
+				 */
+				for (i = 0 ; i < tex->height ; i++) {
+					if (DRM_COPY_FROM_USER(buffer, data, tex_width )) {
+						DRM_ERROR("EFAULT on pad, %d bytes\n", tex_width);
+						return DRM_ERR(EFAULT);
+					}
+					buffer += 8;
+					data += tex_width;
+				}
+			}
+		}
+
+		buf->filp = filp;
+		buf->used = (dwords + 8) * sizeof(u32);
+		radeon_cp_dispatch_indirect( dev, buf, 0, buf->used );
+		radeon_cp_discard_buffer( dev, buf );
+
+		/* Update the input parameters for next time */
+		image->y += height;
+		image->height -= height;
+		image->data = (const u8 __user *)image->data + size;
+	} while (image->height > 0);
+
+	/* Flush the pixel cache after the blit completes.  This ensures
+	 * the texture data is written out to memory before rendering
+	 * continues.
+	 */
+	BEGIN_RING( 4 );
+	RADEON_FLUSH_CACHE();
+	RADEON_WAIT_UNTIL_2D_IDLE();
+	ADVANCE_RING();
+	return 0;
+}
+
+
+static void radeon_cp_dispatch_stipple( drm_device_t *dev, u32 *stipple )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	int i;
+	RING_LOCALS;
+	DRM_DEBUG( "\n" );
+
+	BEGIN_RING( 35 );
+
+	OUT_RING( CP_PACKET0( RADEON_RE_STIPPLE_ADDR, 0 ) );
+	OUT_RING( 0x00000000 );
+
+	OUT_RING( CP_PACKET0_TABLE( RADEON_RE_STIPPLE_DATA, 31 ) );
+	for ( i = 0 ; i < 32 ; i++ ) {
+		OUT_RING( stipple[i] );
+	}
+
+	ADVANCE_RING();
+}
+
+static void radeon_apply_surface_regs(int surf_index, drm_radeon_private_t *dev_priv)
+{
+	if (!dev_priv->mmio)
+		return;
+
+	radeon_do_cp_idle(dev_priv);
+
+	RADEON_WRITE(RADEON_SURFACE0_INFO + 16*surf_index,
+		dev_priv->surfaces[surf_index].flags);
+	RADEON_WRITE(RADEON_SURFACE0_LOWER_BOUND + 16*surf_index,
+		dev_priv->surfaces[surf_index].lower);
+	RADEON_WRITE(RADEON_SURFACE0_UPPER_BOUND + 16*surf_index,
+		dev_priv->surfaces[surf_index].upper);
+}
+
+
+/* Allocates a virtual surface
+ * doesn't always allocate a real surface, will stretch an existing 
+ * surface when possible.
+ *
+ * Note that refcount can be at most 2, since during a free refcount=3
+ * might mean we have to allocate a new surface which might not always
+ * be available.
+ * For example : we allocate three contigous surfaces ABC. If B is 
+ * freed, we suddenly need two surfaces to store A and C, which might
+ * not always be available.
+ */
+static int alloc_surface(drm_radeon_surface_alloc_t* new, drm_radeon_private_t *dev_priv, DRMFILE filp)
+{
+	struct radeon_virt_surface *s;
+	int i;
+	int virt_surface_index;
+	uint32_t new_upper, new_lower;
+
+	new_lower = new->address;
+	new_upper = new_lower + new->size - 1;
+
+	/* sanity check */
+	if ((new_lower >= new_upper) || (new->flags == 0) || (new->size == 0) ||
+		((new_upper & RADEON_SURF_ADDRESS_FIXED_MASK) != RADEON_SURF_ADDRESS_FIXED_MASK) ||
+		((new_lower & RADEON_SURF_ADDRESS_FIXED_MASK) != 0))
+		return -1;
+
+	/* make sure there is no overlap with existing surfaces */
+	for (i = 0; i < RADEON_MAX_SURFACES; i++) {
+		if ((dev_priv->surfaces[i].refcount != 0) &&
+		(( (new_lower >= dev_priv->surfaces[i].lower) &&
+			(new_lower < dev_priv->surfaces[i].upper) ) ||
+		 ( (new_lower < dev_priv->surfaces[i].lower) &&
+			(new_upper > dev_priv->surfaces[i].lower) )) ){
+		return -1;}
+	}
+
+	/* find a virtual surface */
+	for (i = 0; i < 2*RADEON_MAX_SURFACES; i++)
+		if (dev_priv->virt_surfaces[i].filp == 0)
+			break;
+	if (i == 2*RADEON_MAX_SURFACES) {
+		return -1;}
+	virt_surface_index = i;
+
+	/* try to reuse an existing surface */
+	for (i = 0; i < RADEON_MAX_SURFACES; i++) {
+		/* extend before */
+		if ((dev_priv->surfaces[i].refcount == 1) &&
+		  (new->flags == dev_priv->surfaces[i].flags) &&
+		  (new_upper + 1 == dev_priv->surfaces[i].lower)) {
+			s = &(dev_priv->virt_surfaces[virt_surface_index]);
+			s->surface_index = i;
+			s->lower = new_lower;
+			s->upper = new_upper;
+			s->flags = new->flags;
+			s->filp = filp;
+			dev_priv->surfaces[i].refcount++;
+			dev_priv->surfaces[i].lower = s->lower;
+			radeon_apply_surface_regs(s->surface_index, dev_priv);
+			return virt_surface_index;
+		}
+
+		/* extend after */
+		if ((dev_priv->surfaces[i].refcount == 1) &&
+		  (new->flags == dev_priv->surfaces[i].flags) &&
+		  (new_lower == dev_priv->surfaces[i].upper + 1)) {
+			s = &(dev_priv->virt_surfaces[virt_surface_index]);
+			s->surface_index = i;
+			s->lower = new_lower;
+			s->upper = new_upper;
+			s->flags = new->flags;
+			s->filp = filp;
+			dev_priv->surfaces[i].refcount++;
+			dev_priv->surfaces[i].upper = s->upper;
+			radeon_apply_surface_regs(s->surface_index, dev_priv);
+			return virt_surface_index;
+		}
+	}
+
+	/* okay, we need a new one */
+	for (i = 0; i < RADEON_MAX_SURFACES; i++) {
+		if (dev_priv->surfaces[i].refcount == 0) {
+			s = &(dev_priv->virt_surfaces[virt_surface_index]);
+			s->surface_index = i;
+			s->lower = new_lower;
+			s->upper = new_upper;
+			s->flags = new->flags;
+			s->filp = filp;
+			dev_priv->surfaces[i].refcount = 1;
+			dev_priv->surfaces[i].lower = s->lower;
+			dev_priv->surfaces[i].upper = s->upper;
+			dev_priv->surfaces[i].flags = s->flags;
+			radeon_apply_surface_regs(s->surface_index, dev_priv);
+			return virt_surface_index;
+		}
+	}
+
+	/* we didn't find anything */
+	return -1;
+}
+
+static int free_surface(DRMFILE filp, drm_radeon_private_t *dev_priv, int lower)
+{
+	struct radeon_virt_surface *s;
+	int i;
+	/* find the virtual surface */
+	for(i = 0; i < 2*RADEON_MAX_SURFACES; i++) {
+		s = &(dev_priv->virt_surfaces[i]);
+		if (s->filp) {
+			if ((lower == s->lower) && (filp == s->filp)) {
+				if (dev_priv->surfaces[s->surface_index].lower == s->lower)
+					dev_priv->surfaces[s->surface_index].lower = s->upper;
+
+				if (dev_priv->surfaces[s->surface_index].upper == s->upper)
+					dev_priv->surfaces[s->surface_index].upper = s->lower;
+
+				dev_priv->surfaces[s->surface_index].refcount--;
+				if (dev_priv->surfaces[s->surface_index].refcount == 0)
+					dev_priv->surfaces[s->surface_index].flags = 0;
+				s->filp = NULL;
+				radeon_apply_surface_regs(s->surface_index, dev_priv);
+				return 0;
+			}
+		}
+	}
+	return 1;
+}
+
+static void radeon_surfaces_release(DRMFILE filp, drm_radeon_private_t *dev_priv)
+{
+	int i;
+	for( i = 0; i < 2*RADEON_MAX_SURFACES; i++)
+	{
+		if (dev_priv->virt_surfaces[i].filp == filp)
+			free_surface(filp, dev_priv, dev_priv->virt_surfaces[i].lower);
+	}
+}
+
+/* ================================================================
+ * IOCTL functions
+ */
+static int radeon_surface_alloc(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_surface_alloc_t alloc;
+
+	if (!dev_priv) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(alloc, (drm_radeon_surface_alloc_t __user *)data,
+				  sizeof(alloc));
+
+	if (alloc_surface(&alloc, dev_priv, filp) == -1)
+		return DRM_ERR(EINVAL);
+	else
+		return 0;
+}
+
+static int radeon_surface_free(DRM_IOCTL_ARGS)
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_surface_free_t memfree;
+
+	if (!dev_priv) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL(memfree, (drm_radeon_mem_free_t __user *)data,
+				  sizeof(memfree) );
+
+	if (free_surface(filp, dev_priv, memfree.address))
+		return DRM_ERR(EINVAL);
+	else
+		return 0;
+}
+
+static int radeon_cp_clear( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_radeon_clear_t clear;
+	drm_radeon_clear_rect_t depth_boxes[RADEON_NR_SAREA_CLIPRECTS];
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( clear, (drm_radeon_clear_t __user *)data,
+			     sizeof(clear) );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	if ( sarea_priv->nbox > RADEON_NR_SAREA_CLIPRECTS )
+		sarea_priv->nbox = RADEON_NR_SAREA_CLIPRECTS;
+
+	if ( DRM_COPY_FROM_USER( &depth_boxes, clear.depth_boxes,
+			     sarea_priv->nbox * sizeof(depth_boxes[0]) ) )
+		return DRM_ERR(EFAULT);
+
+	radeon_cp_dispatch_clear( dev, &clear, depth_boxes );
+
+	COMMIT_RING();
+	return 0;
+}
+
+
+/* Not sure why this isn't set all the time:
+ */ 
+static int radeon_do_init_pageflip( drm_device_t *dev )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	RING_LOCALS;
+
+	DRM_DEBUG( "\n" );
+
+	BEGIN_RING( 6 );
+	RADEON_WAIT_UNTIL_3D_IDLE();
+	OUT_RING( CP_PACKET0( RADEON_CRTC_OFFSET_CNTL, 0 ) );
+	OUT_RING( RADEON_READ( RADEON_CRTC_OFFSET_CNTL ) | RADEON_CRTC_OFFSET_FLIP_CNTL );
+	OUT_RING( CP_PACKET0( RADEON_CRTC2_OFFSET_CNTL, 0 ) );
+	OUT_RING( RADEON_READ( RADEON_CRTC2_OFFSET_CNTL ) | RADEON_CRTC_OFFSET_FLIP_CNTL );
+	ADVANCE_RING();
+
+	dev_priv->page_flipping = 1;
+	dev_priv->current_page = 0;
+	dev_priv->sarea_priv->pfCurrentPage = dev_priv->current_page;
+
+	return 0;
+}
+
+/* Called whenever a client dies, from drm_release.
+ * NOTE:  Lock isn't necessarily held when this is called!
+ */
+static int radeon_do_cleanup_pageflip( drm_device_t *dev )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	if (dev_priv->current_page != 0)
+		radeon_cp_dispatch_flip( dev );
+
+	dev_priv->page_flipping = 0;
+	return 0;
+}
+
+/* Swapping and flipping are different operations, need different ioctls.
+ * They can & should be intermixed to support multiple 3d windows.  
+ */
+static int radeon_cp_flip( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	if (!dev_priv->page_flipping) 
+		radeon_do_init_pageflip( dev );
+		
+	radeon_cp_dispatch_flip( dev );
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int radeon_cp_swap( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	DRM_DEBUG( "\n" );
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	if ( sarea_priv->nbox > RADEON_NR_SAREA_CLIPRECTS )
+		sarea_priv->nbox = RADEON_NR_SAREA_CLIPRECTS;
+
+	radeon_cp_dispatch_swap( dev );
+	dev_priv->sarea_priv->ctx_owner = 0;
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int radeon_cp_vertex( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_file_t *filp_priv;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_radeon_vertex_t vertex;
+	drm_radeon_tcl_prim_t prim;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( vertex, (drm_radeon_vertex_t __user *)data,
+			     sizeof(vertex) );
+
+	DRM_DEBUG( "pid=%d index=%d count=%d discard=%d\n",
+		   DRM_CURRENTPID,
+		   vertex.idx, vertex.count, vertex.discard );
+
+	if ( vertex.idx < 0 || vertex.idx >= dma->buf_count ) {
+		DRM_ERROR( "buffer index %d (of %d max)\n",
+			   vertex.idx, dma->buf_count - 1 );
+		return DRM_ERR(EINVAL);
+	}
+	if ( vertex.prim < 0 ||
+	     vertex.prim > RADEON_PRIM_TYPE_3VRT_LINE_LIST ) {
+		DRM_ERROR( "buffer prim %d\n", vertex.prim );
+		return DRM_ERR(EINVAL);
+	}
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	buf = dma->buflist[vertex.idx];
+
+	if ( buf->filp != filp ) {
+		DRM_ERROR( "process %d using buffer owned by %p\n",
+			   DRM_CURRENTPID, buf->filp );
+		return DRM_ERR(EINVAL);
+	}
+	if ( buf->pending ) {
+		DRM_ERROR( "sending pending buffer %d\n", vertex.idx );
+		return DRM_ERR(EINVAL);
+	}
+
+	/* Build up a prim_t record:
+	 */
+	if (vertex.count) {
+		buf->used = vertex.count; /* not used? */
+
+		if ( sarea_priv->dirty & ~RADEON_UPLOAD_CLIPRECTS ) {
+			if ( radeon_emit_state( dev_priv, filp_priv,
+						&sarea_priv->context_state,
+						sarea_priv->tex_state,
+						sarea_priv->dirty ) ) {
+				DRM_ERROR( "radeon_emit_state failed\n" );
+				return DRM_ERR( EINVAL );
+			}
+
+			sarea_priv->dirty &= ~(RADEON_UPLOAD_TEX0IMAGES |
+					       RADEON_UPLOAD_TEX1IMAGES |
+					       RADEON_UPLOAD_TEX2IMAGES |
+					       RADEON_REQUIRE_QUIESCENCE);
+		}
+
+		prim.start = 0;
+		prim.finish = vertex.count; /* unused */
+		prim.prim = vertex.prim;
+		prim.numverts = vertex.count;
+		prim.vc_format = dev_priv->sarea_priv->vc_format;
+		
+		radeon_cp_dispatch_vertex( dev, buf, &prim );
+	}
+
+	if (vertex.discard) {
+		radeon_cp_discard_buffer( dev, buf );
+	}
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int radeon_cp_indices( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_file_t *filp_priv;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_radeon_indices_t elts;
+	drm_radeon_tcl_prim_t prim;
+	int count;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( elts, (drm_radeon_indices_t __user *)data,
+			     sizeof(elts) );
+
+	DRM_DEBUG( "pid=%d index=%d start=%d end=%d discard=%d\n",
+		   DRM_CURRENTPID,
+		   elts.idx, elts.start, elts.end, elts.discard );
+
+	if ( elts.idx < 0 || elts.idx >= dma->buf_count ) {
+		DRM_ERROR( "buffer index %d (of %d max)\n",
+			   elts.idx, dma->buf_count - 1 );
+		return DRM_ERR(EINVAL);
+	}
+	if ( elts.prim < 0 ||
+	     elts.prim > RADEON_PRIM_TYPE_3VRT_LINE_LIST ) {
+		DRM_ERROR( "buffer prim %d\n", elts.prim );
+		return DRM_ERR(EINVAL);
+	}
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	buf = dma->buflist[elts.idx];
+
+	if ( buf->filp != filp ) {
+		DRM_ERROR( "process %d using buffer owned by %p\n",
+			   DRM_CURRENTPID, buf->filp );
+		return DRM_ERR(EINVAL);
+	}
+	if ( buf->pending ) {
+		DRM_ERROR( "sending pending buffer %d\n", elts.idx );
+		return DRM_ERR(EINVAL);
+	}
+
+	count = (elts.end - elts.start) / sizeof(u16);
+	elts.start -= RADEON_INDEX_PRIM_OFFSET;
+
+	if ( elts.start & 0x7 ) {
+		DRM_ERROR( "misaligned buffer 0x%x\n", elts.start );
+		return DRM_ERR(EINVAL);
+	}
+	if ( elts.start < buf->used ) {
+		DRM_ERROR( "no header 0x%x - 0x%x\n", elts.start, buf->used );
+		return DRM_ERR(EINVAL);
+	}
+
+	buf->used = elts.end;
+
+	if ( sarea_priv->dirty & ~RADEON_UPLOAD_CLIPRECTS ) {
+		if ( radeon_emit_state( dev_priv, filp_priv,
+					&sarea_priv->context_state,
+					sarea_priv->tex_state,
+					sarea_priv->dirty ) ) {
+			DRM_ERROR( "radeon_emit_state failed\n" );
+			return DRM_ERR( EINVAL );
+		}
+
+		sarea_priv->dirty &= ~(RADEON_UPLOAD_TEX0IMAGES |
+				       RADEON_UPLOAD_TEX1IMAGES |
+				       RADEON_UPLOAD_TEX2IMAGES |
+				       RADEON_REQUIRE_QUIESCENCE);
+	}
+
+
+	/* Build up a prim_t record:
+	 */
+	prim.start = elts.start;
+	prim.finish = elts.end; 
+	prim.prim = elts.prim;
+	prim.offset = 0;	/* offset from start of dma buffers */
+	prim.numverts = RADEON_MAX_VB_VERTS; /* duh */
+	prim.vc_format = dev_priv->sarea_priv->vc_format;
+	
+	radeon_cp_dispatch_indices( dev, buf, &prim );
+	if (elts.discard) {
+		radeon_cp_discard_buffer( dev, buf );
+	}
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int radeon_cp_texture( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_texture_t tex;
+	drm_radeon_tex_image_t image;
+	int ret;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( tex, (drm_radeon_texture_t __user *)data, sizeof(tex) );
+
+	if ( tex.image == NULL ) {
+		DRM_ERROR( "null texture image!\n" );
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( DRM_COPY_FROM_USER( &image,
+			     (drm_radeon_tex_image_t __user *)tex.image,
+			     sizeof(image) ) )
+		return DRM_ERR(EFAULT);
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	ret = radeon_cp_dispatch_texture( filp, dev, &tex, &image );
+
+	COMMIT_RING();
+	return ret;
+}
+
+static int radeon_cp_stipple( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_stipple_t stipple;
+	u32 mask[32];
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( stipple, (drm_radeon_stipple_t __user *)data,
+			     sizeof(stipple) );
+
+	if ( DRM_COPY_FROM_USER( &mask, stipple.mask, 32 * sizeof(u32) ) )
+		return DRM_ERR(EFAULT);
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+
+	radeon_cp_dispatch_stipple( dev, mask );
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int radeon_cp_indirect( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_radeon_indirect_t indirect;
+	RING_LOCALS;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( indirect, (drm_radeon_indirect_t __user *)data,
+			     sizeof(indirect) );
+
+	DRM_DEBUG( "indirect: idx=%d s=%d e=%d d=%d\n",
+		   indirect.idx, indirect.start,
+		   indirect.end, indirect.discard );
+
+	if ( indirect.idx < 0 || indirect.idx >= dma->buf_count ) {
+		DRM_ERROR( "buffer index %d (of %d max)\n",
+			   indirect.idx, dma->buf_count - 1 );
+		return DRM_ERR(EINVAL);
+	}
+
+	buf = dma->buflist[indirect.idx];
+
+	if ( buf->filp != filp ) {
+		DRM_ERROR( "process %d using buffer owned by %p\n",
+			   DRM_CURRENTPID, buf->filp );
+		return DRM_ERR(EINVAL);
+	}
+	if ( buf->pending ) {
+		DRM_ERROR( "sending pending buffer %d\n", indirect.idx );
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( indirect.start < buf->used ) {
+		DRM_ERROR( "reusing indirect: start=0x%x actual=0x%x\n",
+			   indirect.start, buf->used );
+		return DRM_ERR(EINVAL);
+	}
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	buf->used = indirect.end;
+
+	/* Wait for the 3D stream to idle before the indirect buffer
+	 * containing 2D acceleration commands is processed.
+	 */
+	BEGIN_RING( 2 );
+
+	RADEON_WAIT_UNTIL_3D_IDLE();
+
+	ADVANCE_RING();
+
+	/* Dispatch the indirect buffer full of commands from the
+	 * X server.  This is insecure and is thus only available to
+	 * privileged clients.
+	 */
+	radeon_cp_dispatch_indirect( dev, buf, indirect.start, indirect.end );
+	if (indirect.discard) {
+		radeon_cp_discard_buffer( dev, buf );
+	}
+
+
+	COMMIT_RING();
+	return 0;
+}
+
+static int radeon_cp_vertex2( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_file_t *filp_priv;
+	drm_radeon_sarea_t *sarea_priv = dev_priv->sarea_priv;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf;
+	drm_radeon_vertex2_t vertex;
+	int i;
+	unsigned char laststate;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( vertex, (drm_radeon_vertex2_t __user *)data,
+			     sizeof(vertex) );
+
+	DRM_DEBUG( "pid=%d index=%d discard=%d\n",
+		   DRM_CURRENTPID,
+		   vertex.idx, vertex.discard );
+
+	if ( vertex.idx < 0 || vertex.idx >= dma->buf_count ) {
+		DRM_ERROR( "buffer index %d (of %d max)\n",
+			   vertex.idx, dma->buf_count - 1 );
+		return DRM_ERR(EINVAL);
+	}
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	buf = dma->buflist[vertex.idx];
+
+	if ( buf->filp != filp ) {
+		DRM_ERROR( "process %d using buffer owned by %p\n",
+			   DRM_CURRENTPID, buf->filp );
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( buf->pending ) {
+		DRM_ERROR( "sending pending buffer %d\n", vertex.idx );
+		return DRM_ERR(EINVAL);
+	}
+	
+	if (sarea_priv->nbox > RADEON_NR_SAREA_CLIPRECTS)
+		return DRM_ERR(EINVAL);
+
+	for (laststate = 0xff, i = 0 ; i < vertex.nr_prims ; i++) {
+		drm_radeon_prim_t prim;
+		drm_radeon_tcl_prim_t tclprim;
+		
+		if ( DRM_COPY_FROM_USER( &prim, &vertex.prim[i], sizeof(prim) ) )
+			return DRM_ERR(EFAULT);
+		
+		if ( prim.stateidx != laststate ) {
+			drm_radeon_state_t state;			       
+				
+			if ( DRM_COPY_FROM_USER( &state, 
+					     &vertex.state[prim.stateidx], 
+					     sizeof(state) ) )
+				return DRM_ERR(EFAULT);
+
+			if ( radeon_emit_state2( dev_priv, filp_priv, &state ) ) {
+				DRM_ERROR( "radeon_emit_state2 failed\n" );
+				return DRM_ERR( EINVAL );
+			}
+
+			laststate = prim.stateidx;
+		}
+
+		tclprim.start = prim.start;
+		tclprim.finish = prim.finish;
+		tclprim.prim = prim.prim;
+		tclprim.vc_format = prim.vc_format;
+
+		if ( prim.prim & RADEON_PRIM_WALK_IND ) {
+			tclprim.offset = prim.numverts * 64;
+			tclprim.numverts = RADEON_MAX_VB_VERTS; /* duh */
+
+			radeon_cp_dispatch_indices( dev, buf, &tclprim );
+		} else {
+			tclprim.numverts = prim.numverts;
+			tclprim.offset = 0; /* not used */
+
+			radeon_cp_dispatch_vertex( dev, buf, &tclprim );
+		}
+		
+		if (sarea_priv->nbox == 1)
+			sarea_priv->nbox = 0;
+	}
+
+	if ( vertex.discard ) {
+		radeon_cp_discard_buffer( dev, buf );
+	}
+
+	COMMIT_RING();
+	return 0;
+}
+
+
+static int radeon_emit_packets( 
+	drm_radeon_private_t *dev_priv,
+	drm_file_t *filp_priv,
+	drm_radeon_cmd_header_t header,
+	drm_radeon_cmd_buffer_t *cmdbuf )
+{
+	int id = (int)header.packet.packet_id;
+	int sz, reg;
+	int *data = (int *)cmdbuf->buf;
+	RING_LOCALS;
+   
+	if (id >= RADEON_MAX_STATE_PACKETS)
+		return DRM_ERR(EINVAL);
+
+	sz = packet[id].len;
+	reg = packet[id].start;
+
+	if (sz * sizeof(int) > cmdbuf->bufsz) {
+		DRM_ERROR( "Packet size provided larger than data provided\n" );
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( radeon_check_and_fixup_packets( dev_priv, filp_priv, id, data ) ) {
+		DRM_ERROR( "Packet verification failed\n" );
+		return DRM_ERR( EINVAL );
+	}
+
+	BEGIN_RING(sz+1);
+	OUT_RING( CP_PACKET0( reg, (sz-1) ) );
+	OUT_RING_TABLE( data, sz );
+	ADVANCE_RING();
+
+	cmdbuf->buf += sz * sizeof(int);
+	cmdbuf->bufsz -= sz * sizeof(int);
+	return 0;
+}
+
+static __inline__ int radeon_emit_scalars( 
+	drm_radeon_private_t *dev_priv,
+	drm_radeon_cmd_header_t header,
+	drm_radeon_cmd_buffer_t *cmdbuf )
+{
+	int sz = header.scalars.count;
+	int start = header.scalars.offset;
+	int stride = header.scalars.stride;
+	RING_LOCALS;
+
+	BEGIN_RING( 3+sz );
+	OUT_RING( CP_PACKET0( RADEON_SE_TCL_SCALAR_INDX_REG, 0 ) );
+	OUT_RING( start | (stride << RADEON_SCAL_INDX_DWORD_STRIDE_SHIFT));
+	OUT_RING( CP_PACKET0_TABLE( RADEON_SE_TCL_SCALAR_DATA_REG, sz-1 ) );
+	OUT_RING_TABLE( cmdbuf->buf, sz );
+	ADVANCE_RING();
+	cmdbuf->buf += sz * sizeof(int);
+	cmdbuf->bufsz -= sz * sizeof(int);
+	return 0;
+}
+
+/* God this is ugly
+ */
+static __inline__ int radeon_emit_scalars2( 
+	drm_radeon_private_t *dev_priv,
+	drm_radeon_cmd_header_t header,
+	drm_radeon_cmd_buffer_t *cmdbuf )
+{
+	int sz = header.scalars.count;
+	int start = ((unsigned int)header.scalars.offset) + 0x100;
+	int stride = header.scalars.stride;
+	RING_LOCALS;
+
+	BEGIN_RING( 3+sz );
+	OUT_RING( CP_PACKET0( RADEON_SE_TCL_SCALAR_INDX_REG, 0 ) );
+	OUT_RING( start | (stride << RADEON_SCAL_INDX_DWORD_STRIDE_SHIFT));
+	OUT_RING( CP_PACKET0_TABLE( RADEON_SE_TCL_SCALAR_DATA_REG, sz-1 ) );
+	OUT_RING_TABLE( cmdbuf->buf, sz );
+	ADVANCE_RING();
+	cmdbuf->buf += sz * sizeof(int);
+	cmdbuf->bufsz -= sz * sizeof(int);
+	return 0;
+}
+
+static __inline__ int radeon_emit_vectors( 
+	drm_radeon_private_t *dev_priv,
+	drm_radeon_cmd_header_t header,
+	drm_radeon_cmd_buffer_t *cmdbuf )
+{
+	int sz = header.vectors.count;
+	int start = header.vectors.offset;
+	int stride = header.vectors.stride;
+	RING_LOCALS;
+
+	BEGIN_RING( 3+sz );
+	OUT_RING( CP_PACKET0( RADEON_SE_TCL_VECTOR_INDX_REG, 0 ) );
+	OUT_RING( start | (stride << RADEON_VEC_INDX_OCTWORD_STRIDE_SHIFT));
+	OUT_RING( CP_PACKET0_TABLE( RADEON_SE_TCL_VECTOR_DATA_REG, (sz-1) ) );
+	OUT_RING_TABLE( cmdbuf->buf, sz );
+	ADVANCE_RING();
+
+	cmdbuf->buf += sz * sizeof(int);
+	cmdbuf->bufsz -= sz * sizeof(int);
+	return 0;
+}
+
+
+static int radeon_emit_packet3( drm_device_t *dev,
+				drm_file_t *filp_priv,
+				drm_radeon_cmd_buffer_t *cmdbuf )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	unsigned int cmdsz;
+	int ret;
+	RING_LOCALS;
+
+	DRM_DEBUG("\n");
+
+	if ( ( ret = radeon_check_and_fixup_packet3( dev_priv, filp_priv,
+						     cmdbuf, &cmdsz ) ) ) {
+		DRM_ERROR( "Packet verification failed\n" );
+		return ret;
+	}
+
+	BEGIN_RING( cmdsz );
+	OUT_RING_TABLE( cmdbuf->buf, cmdsz );
+	ADVANCE_RING();
+
+	cmdbuf->buf += cmdsz * 4;
+	cmdbuf->bufsz -= cmdsz * 4;
+	return 0;
+}
+
+
+static int radeon_emit_packet3_cliprect( drm_device_t *dev,
+					 drm_file_t *filp_priv,
+					 drm_radeon_cmd_buffer_t *cmdbuf,
+					 int orig_nbox )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_clip_rect_t box;
+	unsigned int cmdsz;
+	int ret;
+	drm_clip_rect_t __user *boxes = cmdbuf->boxes;
+	int i = 0;
+	RING_LOCALS;
+
+	DRM_DEBUG("\n");
+
+	if ( ( ret = radeon_check_and_fixup_packet3( dev_priv, filp_priv,
+						     cmdbuf, &cmdsz ) ) ) {
+		DRM_ERROR( "Packet verification failed\n" );
+		return ret;
+	}
+
+	if (!orig_nbox)
+		goto out;
+
+	do {
+		if ( i < cmdbuf->nbox ) {
+			if (DRM_COPY_FROM_USER( &box, &boxes[i], sizeof(box) ))
+				return DRM_ERR(EFAULT);
+			/* FIXME The second and subsequent times round
+			 * this loop, send a WAIT_UNTIL_3D_IDLE before
+			 * calling emit_clip_rect(). This fixes a
+			 * lockup on fast machines when sending
+			 * several cliprects with a cmdbuf, as when
+			 * waving a 2D window over a 3D
+			 * window. Something in the commands from user
+			 * space seems to hang the card when they're
+			 * sent several times in a row. That would be
+			 * the correct place to fix it but this works
+			 * around it until I can figure that out - Tim
+			 * Smith */
+			if ( i ) {
+				BEGIN_RING( 2 );
+				RADEON_WAIT_UNTIL_3D_IDLE();
+				ADVANCE_RING();
+			}
+			radeon_emit_clip_rect( dev_priv, &box );
+		}
+		
+		BEGIN_RING( cmdsz );
+		OUT_RING_TABLE( cmdbuf->buf, cmdsz );
+		ADVANCE_RING();
+
+	} while ( ++i < cmdbuf->nbox );
+ 	if (cmdbuf->nbox == 1)
+		cmdbuf->nbox = 0;
+
+ out:
+	cmdbuf->buf += cmdsz * 4;
+	cmdbuf->bufsz -= cmdsz * 4;
+	return 0;
+}
+
+
+static int radeon_emit_wait( drm_device_t *dev, int flags )
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	RING_LOCALS;
+
+	DRM_DEBUG("%s: %x\n", __FUNCTION__, flags);
+	switch (flags) {
+	case RADEON_WAIT_2D:
+		BEGIN_RING( 2 );
+		RADEON_WAIT_UNTIL_2D_IDLE(); 
+		ADVANCE_RING();
+		break;
+	case RADEON_WAIT_3D:
+		BEGIN_RING( 2 );
+		RADEON_WAIT_UNTIL_3D_IDLE(); 
+		ADVANCE_RING();
+		break;
+	case RADEON_WAIT_2D|RADEON_WAIT_3D:
+		BEGIN_RING( 2 );
+		RADEON_WAIT_UNTIL_IDLE(); 
+		ADVANCE_RING();
+		break;
+	default:
+		return DRM_ERR(EINVAL);
+	}
+
+	return 0;
+}
+
+static int radeon_cp_cmdbuf( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_file_t *filp_priv;
+	drm_device_dma_t *dma = dev->dma;
+	drm_buf_t *buf = NULL;
+	int idx;
+	drm_radeon_cmd_buffer_t cmdbuf;
+	drm_radeon_cmd_header_t header;
+	int orig_nbox, orig_bufsz;
+	char *kbuf=NULL;
+
+	LOCK_TEST_WITH_RETURN( dev, filp );
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( cmdbuf, (drm_radeon_cmd_buffer_t __user *)data,
+			     sizeof(cmdbuf) );
+
+	RING_SPACE_TEST_WITH_RETURN( dev_priv );
+	VB_AGE_TEST_WITH_RETURN( dev_priv );
+
+	if (cmdbuf.bufsz > 64*1024 || cmdbuf.bufsz<0) {
+		return DRM_ERR(EINVAL);
+	}
+
+	/* Allocate an in-kernel area and copy in the cmdbuf.  Do this to avoid
+	 * races between checking values and using those values in other code,
+	 * and simply to avoid a lot of function calls to copy in data.
+	 */
+	orig_bufsz = cmdbuf.bufsz;
+	if (orig_bufsz != 0) {
+		kbuf = drm_alloc(cmdbuf.bufsz, DRM_MEM_DRIVER);
+		if (kbuf == NULL)
+			return DRM_ERR(ENOMEM);
+		if (DRM_COPY_FROM_USER(kbuf, cmdbuf.buf, cmdbuf.bufsz)) {
+			drm_free(kbuf, orig_bufsz, DRM_MEM_DRIVER);
+			return DRM_ERR(EFAULT);
+		}
+		cmdbuf.buf = kbuf;
+	}
+
+	orig_nbox = cmdbuf.nbox;
+
+	while ( cmdbuf.bufsz >= sizeof(header) ) {
+
+		header.i = *(int *)cmdbuf.buf;
+		cmdbuf.buf += sizeof(header);
+		cmdbuf.bufsz -= sizeof(header);
+
+		switch (header.header.cmd_type) {
+		case RADEON_CMD_PACKET: 
+			DRM_DEBUG("RADEON_CMD_PACKET\n");
+			if (radeon_emit_packets( dev_priv, filp_priv, header, &cmdbuf )) {
+				DRM_ERROR("radeon_emit_packets failed\n");
+				goto err;
+			}
+			break;
+
+		case RADEON_CMD_SCALARS:
+			DRM_DEBUG("RADEON_CMD_SCALARS\n");
+			if (radeon_emit_scalars( dev_priv, header, &cmdbuf )) {
+				DRM_ERROR("radeon_emit_scalars failed\n");
+				goto err;
+			}
+			break;
+
+		case RADEON_CMD_VECTORS:
+			DRM_DEBUG("RADEON_CMD_VECTORS\n");
+			if (radeon_emit_vectors( dev_priv, header, &cmdbuf )) {
+				DRM_ERROR("radeon_emit_vectors failed\n");
+				goto err;
+			}
+			break;
+
+		case RADEON_CMD_DMA_DISCARD:
+			DRM_DEBUG("RADEON_CMD_DMA_DISCARD\n");
+			idx = header.dma.buf_idx;
+			if ( idx < 0 || idx >= dma->buf_count ) {
+				DRM_ERROR( "buffer index %d (of %d max)\n",
+					   idx, dma->buf_count - 1 );
+				goto err;
+			}
+
+			buf = dma->buflist[idx];
+			if ( buf->filp != filp || buf->pending ) {
+				DRM_ERROR( "bad buffer %p %p %d\n",
+					   buf->filp, filp, buf->pending);
+				goto err;
+			}
+
+			radeon_cp_discard_buffer( dev, buf );
+			break;
+
+		case RADEON_CMD_PACKET3:
+			DRM_DEBUG("RADEON_CMD_PACKET3\n");
+			if (radeon_emit_packet3( dev, filp_priv, &cmdbuf )) {
+				DRM_ERROR("radeon_emit_packet3 failed\n");
+				goto err;
+			}
+			break;
+
+		case RADEON_CMD_PACKET3_CLIP:
+			DRM_DEBUG("RADEON_CMD_PACKET3_CLIP\n");
+			if (radeon_emit_packet3_cliprect( dev, filp_priv, &cmdbuf, orig_nbox )) {
+				DRM_ERROR("radeon_emit_packet3_clip failed\n");
+				goto err;
+			}
+			break;
+
+		case RADEON_CMD_SCALARS2:
+			DRM_DEBUG("RADEON_CMD_SCALARS2\n");
+			if (radeon_emit_scalars2( dev_priv, header, &cmdbuf )) {
+				DRM_ERROR("radeon_emit_scalars2 failed\n");
+				goto err;
+			}
+			break;
+
+		case RADEON_CMD_WAIT:
+			DRM_DEBUG("RADEON_CMD_WAIT\n");
+			if (radeon_emit_wait( dev, header.wait.flags )) {
+				DRM_ERROR("radeon_emit_wait failed\n");
+				goto err;
+			}
+			break;
+		default:
+			DRM_ERROR("bad cmd_type %d at %p\n", 
+				  header.header.cmd_type,
+				  cmdbuf.buf - sizeof(header));
+			goto err;
+		}
+	}
+
+	if (orig_bufsz != 0)
+		drm_free(kbuf, orig_bufsz, DRM_MEM_DRIVER);
+
+	DRM_DEBUG("DONE\n");
+	COMMIT_RING();
+	return 0;
+
+err:
+	if (orig_bufsz != 0)
+		drm_free(kbuf, orig_bufsz, DRM_MEM_DRIVER);
+	return DRM_ERR(EINVAL);
+}
+
+
+
+static int radeon_cp_getparam( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_radeon_getparam_t param;
+	int value;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR(EINVAL);
+	}
+
+	DRM_COPY_FROM_USER_IOCTL( param, (drm_radeon_getparam_t __user *)data,
+			     sizeof(param) );
+
+	DRM_DEBUG( "pid=%d\n", DRM_CURRENTPID );
+
+	switch( param.param ) {
+	case RADEON_PARAM_GART_BUFFER_OFFSET:
+		value = dev_priv->gart_buffers_offset;
+		break;
+	case RADEON_PARAM_LAST_FRAME:
+		dev_priv->stats.last_frame_reads++;
+		value = GET_SCRATCH( 0 );
+		break;
+	case RADEON_PARAM_LAST_DISPATCH:
+		value = GET_SCRATCH( 1 );
+		break;
+	case RADEON_PARAM_LAST_CLEAR:
+		dev_priv->stats.last_clear_reads++;
+		value = GET_SCRATCH( 2 );
+		break;
+	case RADEON_PARAM_IRQ_NR:
+		value = dev->irq;
+		break;
+	case RADEON_PARAM_GART_BASE:
+		value = dev_priv->gart_vm_start;
+		break;
+	case RADEON_PARAM_REGISTER_HANDLE:
+		value = dev_priv->mmio_offset;
+		break;
+	case RADEON_PARAM_STATUS_HANDLE:
+		value = dev_priv->ring_rptr_offset;
+		break;
+#if BITS_PER_LONG == 32
+	/*
+	 * This ioctl() doesn't work on 64-bit platforms because hw_lock is a
+	 * pointer which can't fit into an int-sized variable.  According to
+	 * Michel Dänzer, the ioctl() is only used on embedded platforms, so
+	 * not supporting it shouldn't be a problem.  If the same functionality
+	 * is needed on 64-bit platforms, a new ioctl() would have to be added,
+	 * so backwards-compatibility for the embedded platforms can be
+	 * maintained.  --davidm 4-Feb-2004.
+	 */
+	case RADEON_PARAM_SAREA_HANDLE:
+		/* The lock is the first dword in the sarea. */
+		value = (long)dev->lock.hw_lock;
+		break;
+#endif
+	case RADEON_PARAM_GART_TEX_HANDLE:
+		value = dev_priv->gart_textures_offset;
+		break;
+	default:
+		return DRM_ERR(EINVAL);
+	}
+
+	if ( DRM_COPY_TO_USER( param.value, &value, sizeof(int) ) ) {
+		DRM_ERROR( "copy_to_user\n" );
+		return DRM_ERR(EFAULT);
+	}
+	
+	return 0;
+}
+
+static int radeon_cp_setparam( DRM_IOCTL_ARGS ) {
+	DRM_DEVICE;
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	drm_file_t *filp_priv;
+	drm_radeon_setparam_t sp;
+	struct drm_radeon_driver_file_fields *radeon_priv;
+
+	if ( !dev_priv ) {
+		DRM_ERROR( "%s called with no initialization\n", __FUNCTION__ );
+		return DRM_ERR( EINVAL );
+	}
+
+	DRM_GET_PRIV_WITH_RETURN( filp_priv, filp );
+
+	DRM_COPY_FROM_USER_IOCTL( sp, ( drm_radeon_setparam_t __user * )data,
+				  sizeof( sp ) );
+
+	switch( sp.param ) {
+	case RADEON_SETPARAM_FB_LOCATION:
+		radeon_priv = filp_priv->driver_priv;
+		radeon_priv->radeon_fb_delta = dev_priv->fb_location - sp.value;
+		break;
+	case RADEON_SETPARAM_SWITCH_TILING:
+		if (sp.value == 0) {
+			DRM_DEBUG( "color tiling disabled\n" );
+			dev_priv->front_pitch_offset &= ~RADEON_DST_TILE_MACRO;
+			dev_priv->back_pitch_offset &= ~RADEON_DST_TILE_MACRO;
+			dev_priv->sarea_priv->tiling_enabled = 0;
+		}
+		else if (sp.value == 1) {
+			DRM_DEBUG( "color tiling enabled\n" );
+			dev_priv->front_pitch_offset |= RADEON_DST_TILE_MACRO;
+			dev_priv->back_pitch_offset |= RADEON_DST_TILE_MACRO;
+			dev_priv->sarea_priv->tiling_enabled = 1;
+		}
+		break;	
+	default:
+		DRM_DEBUG( "Invalid parameter %d\n", sp.param );
+		return DRM_ERR( EINVAL );
+	}
+
+	return 0;
+}
+
+/* When a client dies:
+ *    - Check for and clean up flipped page state
+ *    - Free any alloced GART memory.
+ *
+ * DRM infrastructure takes care of reclaiming dma buffers.
+ */
+void radeon_driver_prerelease(drm_device_t *dev, DRMFILE filp)
+{
+	if ( dev->dev_private ) {				
+		drm_radeon_private_t *dev_priv = dev->dev_private; 
+		if ( dev_priv->page_flipping ) {		
+			radeon_do_cleanup_pageflip( dev );	
+		}						
+		radeon_mem_release( filp, dev_priv->gart_heap ); 
+		radeon_mem_release( filp, dev_priv->fb_heap );	
+		radeon_surfaces_release(filp, dev_priv);
+	}				
+}
+
+void radeon_driver_pretakedown(drm_device_t *dev)
+{
+	radeon_do_release(dev);
+}
+
+int radeon_driver_open_helper(drm_device_t *dev, drm_file_t *filp_priv)
+{
+	drm_radeon_private_t *dev_priv = dev->dev_private;
+	struct drm_radeon_driver_file_fields *radeon_priv;
+	
+	radeon_priv = (struct drm_radeon_driver_file_fields *)drm_alloc(sizeof(*radeon_priv), DRM_MEM_FILES);
+	
+	if (!radeon_priv)
+		return -ENOMEM;
+
+	filp_priv->driver_priv = radeon_priv;
+	if ( dev_priv )
+		radeon_priv->radeon_fb_delta = dev_priv->fb_location;
+	else
+		radeon_priv->radeon_fb_delta = 0;
+	return 0;
+}
+
+
+void radeon_driver_free_filp_priv(drm_device_t *dev, drm_file_t *filp_priv)
+{
+	 struct drm_radeon_driver_file_fields *radeon_priv = filp_priv->driver_priv;
+	 
+	 drm_free(radeon_priv, sizeof(*radeon_priv), DRM_MEM_FILES);
+}
+
+drm_ioctl_desc_t radeon_ioctls[] = {
+	[DRM_IOCTL_NR(DRM_RADEON_CP_INIT)]    = { radeon_cp_init,      1, 1 },
+	[DRM_IOCTL_NR(DRM_RADEON_CP_START)]   = { radeon_cp_start,     1, 1 },
+	[DRM_IOCTL_NR(DRM_RADEON_CP_STOP)]    = { radeon_cp_stop,      1, 1 },
+	[DRM_IOCTL_NR(DRM_RADEON_CP_RESET)]   = { radeon_cp_reset,     1, 1 },
+	[DRM_IOCTL_NR(DRM_RADEON_CP_IDLE)]    = { radeon_cp_idle,      1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_CP_RESUME)]  = { radeon_cp_resume,    1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_RESET)]      = { radeon_engine_reset, 1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_FULLSCREEN)] = { radeon_fullscreen,   1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_SWAP)]       = { radeon_cp_swap,      1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_CLEAR)]      = { radeon_cp_clear,     1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_VERTEX)]     = { radeon_cp_vertex,    1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_INDICES)]    = { radeon_cp_indices,   1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_TEXTURE)]    = { radeon_cp_texture,   1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_STIPPLE)]    = { radeon_cp_stipple,   1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_INDIRECT)]   = { radeon_cp_indirect,  1, 1 },
+	[DRM_IOCTL_NR(DRM_RADEON_VERTEX2)]    = { radeon_cp_vertex2,   1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_CMDBUF)]     = { radeon_cp_cmdbuf,    1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_GETPARAM)]   = { radeon_cp_getparam,  1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_FLIP)]       = { radeon_cp_flip,      1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_ALLOC)]      = { radeon_mem_alloc,    1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_FREE)]       = { radeon_mem_free,     1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_INIT_HEAP)]  = { radeon_mem_init_heap,1, 1 },
+	[DRM_IOCTL_NR(DRM_RADEON_IRQ_EMIT)]   = { radeon_irq_emit,     1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_IRQ_WAIT)]   = { radeon_irq_wait,     1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_SETPARAM)]   = { radeon_cp_setparam,  1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_SURF_ALLOC)] = { radeon_surface_alloc,1, 0 },
+	[DRM_IOCTL_NR(DRM_RADEON_SURF_FREE)]  = { radeon_surface_free, 1, 0 }
+};
+
+int radeon_max_ioctl = DRM_ARRAY_SIZE(radeon_ioctls);
diff --git a/drivers/char/drm/sis_drm.h b/drivers/char/drm/sis_drm.h
new file mode 100644
index 0000000..e99c3a4
--- /dev/null
+++ b/drivers/char/drm/sis_drm.h
@@ -0,0 +1,42 @@
+
+#ifndef __SIS_DRM_H__
+#define __SIS_DRM_H__
+
+/* SiS specific ioctls */
+#define NOT_USED_0_3
+#define DRM_SIS_FB_ALLOC	0x04
+#define DRM_SIS_FB_FREE	        0x05
+#define NOT_USED_6_12
+#define DRM_SIS_AGP_INIT	0x13
+#define DRM_SIS_AGP_ALLOC	0x14
+#define DRM_SIS_AGP_FREE	0x15
+#define DRM_SIS_FB_INIT	        0x16
+
+#define DRM_IOCTL_SIS_FB_ALLOC		DRM_IOWR(DRM_COMMAND_BASE + DRM_SIS_FB_ALLOC, drm_sis_mem_t)
+#define DRM_IOCTL_SIS_FB_FREE		DRM_IOW( DRM_COMMAND_BASE + DRM_SIS_FB_FREE, drm_sis_mem_t)
+#define DRM_IOCTL_SIS_AGP_INIT		DRM_IOWR(DRM_COMMAND_BASE + DRM_SIS_AGP_INIT, drm_sis_agp_t)
+#define DRM_IOCTL_SIS_AGP_ALLOC		DRM_IOWR(DRM_COMMAND_BASE + DRM_SIS_AGP_ALLOC, drm_sis_mem_t)
+#define DRM_IOCTL_SIS_AGP_FREE		DRM_IOW( DRM_COMMAND_BASE + DRM_SIS_AGP_FREE, drm_sis_mem_t)
+#define DRM_IOCTL_SIS_FB_INIT		DRM_IOW( DRM_COMMAND_BASE + DRM_SIS_FB_INIT, drm_sis_fb_t)
+/*
+#define DRM_IOCTL_SIS_FLIP		DRM_IOW( 0x48, drm_sis_flip_t)
+#define DRM_IOCTL_SIS_FLIP_INIT		DRM_IO(  0x49)
+#define DRM_IOCTL_SIS_FLIP_FINAL	DRM_IO(  0x50)
+*/
+
+typedef struct {
+	int context;
+	unsigned int offset;
+	unsigned int size;
+	unsigned long free;
+} drm_sis_mem_t;
+
+typedef struct {
+	unsigned int offset, size;
+} drm_sis_agp_t;
+
+typedef struct {
+	unsigned int offset, size;
+} drm_sis_fb_t;
+
+#endif /* __SIS_DRM_H__ */
diff --git a/drivers/char/drm/sis_drv.c b/drivers/char/drm/sis_drv.c
new file mode 100644
index 0000000..f441714
--- /dev/null
+++ b/drivers/char/drm/sis_drv.c
@@ -0,0 +1,110 @@
+/* sis.c -- sis driver -*- linux-c -*-
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "sis_drm.h"
+#include "sis_drv.h"
+
+#include "drm_pciids.h"
+  
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->primary.minor,
+		pci_pretty_name(dev->pdev)
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+	sisdrv_PCI_IDS
+};
+
+extern drm_ioctl_desc_t sis_ioctls[];
+extern int sis_max_ioctl;
+
+static struct drm_driver driver = {
+	.driver_features = DRIVER_USE_AGP | DRIVER_USE_MTRR,
+	.context_ctor = sis_init_context,
+	.context_dtor = sis_final_context,
+	.reclaim_buffers = drm_core_reclaim_buffers,
+	.get_map_ofs = drm_core_get_map_ofs,
+	.get_reg_ofs = drm_core_get_reg_ofs,
+	.postinit = postinit,
+	.version = version,
+	.ioctls = sis_ioctls,
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+	.pci_driver = {
+		.name          = DRIVER_NAME,
+		.id_table      = pciidlist,
+	}
+};
+
+static int __init sis_init(void)
+{
+	driver.num_ioctls = sis_max_ioctl;
+	return drm_init(&driver);
+}
+
+static void __exit sis_exit(void)
+{
+	drm_exit(&driver);
+}
+
+module_init(sis_init);
+module_exit(sis_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/sis_drv.h b/drivers/char/drm/sis_drv.h
new file mode 100644
index 0000000..5be36b5
--- /dev/null
+++ b/drivers/char/drm/sis_drv.h
@@ -0,0 +1,52 @@
+/* sis_drv.h -- Private header for sis driver -*- linux-c -*-
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */
+
+#ifndef _SIS_DRV_H_
+#define _SIS_DRV_H_
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR		"SIS"
+#define DRIVER_NAME		"sis"
+#define DRIVER_DESC		"SIS 300/630/540"
+#define DRIVER_DATE		"20030826"
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		1
+#define DRIVER_PATCHLEVEL	0
+
+#include "sis_ds.h"
+
+typedef struct drm_sis_private {
+	memHeap_t *AGPHeap;
+	memHeap_t *FBHeap;
+} drm_sis_private_t;
+
+extern int sis_init_context(drm_device_t *dev, int context);
+extern int sis_final_context(drm_device_t *dev, int context);
+
+#endif
diff --git a/drivers/char/drm/sis_ds.c b/drivers/char/drm/sis_ds.c
new file mode 100644
index 0000000..e37ed8c
--- /dev/null
+++ b/drivers/char/drm/sis_ds.c
@@ -0,0 +1,301 @@
+/* sis_ds.c -- Private header for Direct Rendering Manager -*- linux-c -*-
+ * Created: Mon Jan  4 10:05:05 1999 by sclin@sis.com.tw
+ *
+ * Copyright 2000 Silicon Integrated Systems Corp, Inc., HsinChu, Taiwan.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ * Authors:
+ *    Sung-Ching Lin <sclin@sis.com.tw>
+ * 
+ */
+
+#include "drmP.h"
+#include "drm.h"
+#include "sis_ds.h"
+
+/* Set Data Structure, not check repeated value
+ * temporarily used
+ */
+
+set_t *setInit(void)
+{
+	int i;
+	set_t *set;
+
+	set = (set_t *)drm_alloc(sizeof(set_t), DRM_MEM_DRIVER);
+	if (set != NULL) {
+		for (i = 0; i < SET_SIZE; i++) {
+			set->list[i].free_next = i + 1;    
+			set->list[i].alloc_next = -1;
+		}
+		set->list[SET_SIZE-1].free_next = -1;
+		set->free = 0;
+		set->alloc = -1;
+		set->trace = -1;
+	}
+	return set;
+}
+
+int setAdd(set_t *set, ITEM_TYPE item)
+{
+	int free = set->free;
+  
+	if (free != -1) {
+		set->list[free].val = item;
+		set->free = set->list[free].free_next;
+	} else {
+		return 0;
+	}
+
+	set->list[free].alloc_next = set->alloc;
+	set->alloc = free;  
+	set->list[free].free_next = -1;    
+
+	return 1;
+}
+
+int setDel(set_t *set, ITEM_TYPE item)
+{
+	int alloc = set->alloc;
+	int prev = -1;  
+
+	while (alloc != -1) {
+		if (set->list[alloc].val == item) {
+			if (prev != -1)
+				set->list[prev].alloc_next =
+				    set->list[alloc].alloc_next;
+			else
+				set->alloc = set->list[alloc].alloc_next;
+			break;
+		}
+		prev = alloc;
+		alloc = set->list[alloc].alloc_next;
+	}
+
+	if (alloc == -1)
+		return 0;
+
+	set->list[alloc].free_next = set->free;
+	set->free = alloc;
+	set->list[alloc].alloc_next = -1;
+
+	return 1;
+}
+
+/* setFirst -> setAdd -> setNext is wrong */
+
+int setFirst(set_t *set, ITEM_TYPE *item)
+{
+	if (set->alloc == -1)
+		return 0;
+
+	*item = set->list[set->alloc].val;
+	set->trace = set->list[set->alloc].alloc_next;
+
+	return 1;
+}
+
+int setNext(set_t *set, ITEM_TYPE *item)
+{
+	if (set->trace == -1)
+		return 0;
+
+	*item = set->list[set->trace].val;
+	set->trace = set->list[set->trace].alloc_next;
+
+	return 1;
+}
+
+int setDestroy(set_t *set)
+{
+	drm_free(set, sizeof(set_t), DRM_MEM_DRIVER);
+
+	return 1;
+}
+
+/*
+ * GLX Hardware Device Driver common code
+ * Copyright (C) 1999 Wittawat Yamwong
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * WITTAWAT YAMWONG, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM, 
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+#define ISFREE(bptr) ((bptr)->free)
+
+memHeap_t *mmInit(int ofs,
+		  int size)
+{
+	PMemBlock blocks;
+
+	if (size <= 0)
+		return NULL;
+
+	blocks = (TMemBlock *)drm_calloc(1, sizeof(TMemBlock), DRM_MEM_DRIVER);
+	if (blocks != NULL) {
+		blocks->ofs = ofs;
+		blocks->size = size;
+		blocks->free = 1;
+		return (memHeap_t *)blocks;
+	} else
+		return NULL;
+}
+
+/* Checks if a pointer 'b' is part of the heap 'heap' */
+int mmBlockInHeap(memHeap_t *heap, PMemBlock b)
+{
+	TMemBlock *p;
+
+	if (heap == NULL || b == NULL)
+		return 0;
+
+	p = heap;
+	while (p != NULL && p != b) {
+		p = p->next;
+	}
+	if (p == b)
+		return 1;
+	else
+		return 0;
+}
+
+static TMemBlock* SliceBlock(TMemBlock *p, 
+			     int startofs, int size, 
+			     int reserved, int alignment)
+{
+	TMemBlock *newblock;
+
+	/* break left */
+	if (startofs > p->ofs) {
+		newblock = (TMemBlock*) drm_calloc(1, sizeof(TMemBlock),
+		    DRM_MEM_DRIVER);
+		newblock->ofs = startofs;
+		newblock->size = p->size - (startofs - p->ofs);
+		newblock->free = 1;
+		newblock->next = p->next;
+		p->size -= newblock->size;
+		p->next = newblock;
+		p = newblock;
+	}
+
+	/* break right */
+	if (size < p->size) {
+		newblock = (TMemBlock*) drm_calloc(1, sizeof(TMemBlock),
+		    DRM_MEM_DRIVER);
+		newblock->ofs = startofs + size;
+		newblock->size = p->size - size;
+		newblock->free = 1;
+		newblock->next = p->next;
+		p->size = size;
+		p->next = newblock;
+	}
+
+	/* p = middle block */
+	p->align = alignment;
+	p->free = 0;
+	p->reserved = reserved;
+	return p;
+}
+
+PMemBlock mmAllocMem( memHeap_t *heap, int size, int align2, int startSearch)
+{
+	int mask,startofs, endofs;
+	TMemBlock *p;
+	
+	if (heap == NULL || align2 < 0 || size <= 0)
+		return NULL;
+
+	mask = (1 << align2)-1;
+	startofs = 0;
+	p = (TMemBlock *)heap;
+	while (p != NULL) {
+		if (ISFREE(p)) {
+			startofs = (p->ofs + mask) & ~mask;
+			if ( startofs < startSearch ) {
+				startofs = startSearch;
+			}
+			endofs = startofs+size;
+			if (endofs <= (p->ofs+p->size))
+				break;
+		}
+		p = p->next;
+	}
+	if (p == NULL)
+		return NULL;
+	p = SliceBlock(p,startofs,size,0,mask+1);
+	p->heap = heap;
+	return p;
+}
+
+static __inline__ int Join2Blocks(TMemBlock *p)
+{
+	if (p->free && p->next && p->next->free) {
+		TMemBlock *q = p->next;
+		p->size += q->size;
+		p->next = q->next;
+		drm_free(q, sizeof(TMemBlock), DRM_MEM_DRIVER);
+		return 1;
+	}
+	return 0;
+}
+
+int mmFreeMem(PMemBlock b)
+{
+	TMemBlock *p, *prev;
+
+	if (b == NULL)
+		return 0;
+	if (b->heap == NULL)
+		return -1;
+
+	p = b->heap;
+	prev = NULL;
+	while (p != NULL && p != b) {
+		prev = p;
+		p = p->next;
+	}
+	if (p == NULL || p->free || p->reserved)
+		return -1;
+
+	p->free = 1;
+	Join2Blocks(p);
+	if (prev)
+	Join2Blocks(prev);
+	return 0;
+}
+
diff --git a/drivers/char/drm/sis_ds.h b/drivers/char/drm/sis_ds.h
new file mode 100644
index 0000000..171ee75
--- /dev/null
+++ b/drivers/char/drm/sis_ds.h
@@ -0,0 +1,145 @@
+/* sis_ds.h -- Private header for Direct Rendering Manager -*- linux-c -*-
+ * Created: Mon Jan  4 10:05:05 1999 by sclin@sis.com.tw
+ *
+ * Copyright 2000 Silicon Integrated Systems Corp, Inc., HsinChu, Taiwan.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ * Authors:
+ *    Sung-Ching Lin <sclin@sis.com.tw>
+ * 
+ */
+
+#ifndef __SIS_DS_H__
+#define __SIS_DS_H__
+
+/* Set Data Structure */
+
+#define SET_SIZE 5000
+
+typedef unsigned int ITEM_TYPE;
+
+typedef struct {
+	ITEM_TYPE val;
+	int alloc_next, free_next;
+} list_item_t;
+
+typedef struct {
+	int alloc;
+	int free;
+	int trace;
+	list_item_t list[SET_SIZE];
+} set_t;
+
+set_t *setInit(void);
+int setAdd(set_t *set, ITEM_TYPE item);
+int setDel(set_t *set, ITEM_TYPE item);
+int setFirst(set_t *set, ITEM_TYPE *item);
+int setNext(set_t *set, ITEM_TYPE *item);
+int setDestroy(set_t *set);
+
+/*
+ * GLX Hardware Device Driver common code
+ * Copyright (C) 1999 Wittawat Yamwong
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * WITTAWAT YAMWONG, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM, 
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+struct mem_block_t {
+	struct mem_block_t *next;
+	struct mem_block_t *heap;
+	int ofs,size;
+	int align;
+	unsigned int free:1;
+	unsigned int reserved:1;
+};
+typedef struct mem_block_t TMemBlock;
+typedef struct mem_block_t *PMemBlock;
+
+/* a heap is just the first block in a chain */
+typedef struct mem_block_t memHeap_t;
+
+static __inline__ int mmBlockSize(PMemBlock b)
+{
+	return b->size;
+}
+
+static __inline__ int mmOffset(PMemBlock b)
+{
+	return b->ofs;
+}
+
+static __inline__ void mmMarkReserved(PMemBlock b)
+{
+	b->reserved = 1;
+}
+
+/* 
+ * input: total size in bytes
+ * return: a heap pointer if OK, NULL if error
+ */
+memHeap_t *mmInit( int ofs, int size );
+
+/*
+ * Allocate 'size' bytes with 2^align2 bytes alignment,
+ * restrict the search to free memory after 'startSearch'
+ * depth and back buffers should be in different 4mb banks
+ * to get better page hits if possible
+ * input:	size = size of block
+ *       	align2 = 2^align2 bytes alignment
+ *		startSearch = linear offset from start of heap to begin search
+ * return: pointer to the allocated block, 0 if error
+ */
+PMemBlock mmAllocMem( memHeap_t *heap, int size, int align2, int startSearch );
+
+/*
+ * Returns 1 if the block 'b' is part of the heap 'heap'
+ */
+int mmBlockInHeap( PMemBlock heap, PMemBlock b );
+
+/*
+ * Free block starts at offset
+ * input: pointer to a block
+ * return: 0 if OK, -1 if error
+ */
+int mmFreeMem( PMemBlock b );
+
+/* For debuging purpose. */
+void mmDumpMemInfo( memHeap_t *mmInit );
+
+#endif /* __SIS_DS_H__ */
diff --git a/drivers/char/drm/sis_mm.c b/drivers/char/drm/sis_mm.c
new file mode 100644
index 0000000..6610c55
--- /dev/null
+++ b/drivers/char/drm/sis_mm.c
@@ -0,0 +1,417 @@
+/* sis_mm.c -- Private header for Direct Rendering Manager -*- linux-c -*-
+ * Created: Mon Jan  4 10:05:05 1999 by sclin@sis.com.tw
+ *
+ * Copyright 2000 Silicon Integrated Systems Corp, Inc., HsinChu, Taiwan.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ * Authors:
+ *    Sung-Ching Lin <sclin@sis.com.tw>
+ * 
+ */
+
+#include "drmP.h"
+#include "sis_drm.h"
+#include "sis_drv.h"
+#include "sis_ds.h"
+#if defined(__linux__) && defined(CONFIG_FB_SIS)
+#include <video/sisfb.h>
+#endif
+
+#define MAX_CONTEXT 100
+#define VIDEO_TYPE 0 
+#define AGP_TYPE 1
+
+typedef struct {
+	int used;
+	int context;
+	set_t *sets[2]; /* 0 for video, 1 for AGP */
+} sis_context_t;
+
+static sis_context_t global_ppriv[MAX_CONTEXT];
+
+
+static int add_alloc_set(int context, int type, unsigned int val)
+{
+	int i, retval = 0;
+	
+	for (i = 0; i < MAX_CONTEXT; i++) {
+		if (global_ppriv[i].used && global_ppriv[i].context == context)
+		{
+			retval = setAdd(global_ppriv[i].sets[type], val);
+			break;
+		}
+	}
+	return retval;
+}
+
+static int del_alloc_set(int context, int type, unsigned int val)
+{  
+	int i, retval = 0;
+
+	for (i = 0; i < MAX_CONTEXT; i++) {
+		if (global_ppriv[i].used && global_ppriv[i].context == context)
+		{
+			retval = setDel(global_ppriv[i].sets[type], val);
+			break;
+		}
+	}
+	return retval;
+}
+
+/* fb management via fb device */ 
+#if defined(__linux__) && defined(CONFIG_FB_SIS)
+
+static int sis_fb_init( DRM_IOCTL_ARGS )
+{
+	return 0;
+}
+
+static int sis_fb_alloc( DRM_IOCTL_ARGS )
+{
+	drm_sis_mem_t fb;
+	struct sis_memreq req;
+	drm_sis_mem_t __user *argp = (void __user *)data;
+	int retval = 0;
+
+	DRM_COPY_FROM_USER_IOCTL(fb, argp, sizeof(fb));
+
+	req.size = fb.size;
+	sis_malloc(&req);
+	if (req.offset) {
+		/* TODO */
+		fb.offset = req.offset;
+		fb.free = req.offset;
+		if (!add_alloc_set(fb.context, VIDEO_TYPE, fb.free)) {
+			DRM_DEBUG("adding to allocation set fails\n");
+			sis_free(req.offset);
+			retval = DRM_ERR(EINVAL);
+		}
+	} else {  
+		fb.offset = 0;
+		fb.size = 0;
+		fb.free = 0;
+	}
+
+	DRM_COPY_TO_USER_IOCTL(argp, fb, sizeof(fb));
+
+	DRM_DEBUG("alloc fb, size = %d, offset = %d\n", fb.size, req.offset);
+
+	return retval;
+}
+
+static int sis_fb_free( DRM_IOCTL_ARGS )
+{
+	drm_sis_mem_t fb;
+	int retval = 0;
+
+	DRM_COPY_FROM_USER_IOCTL(fb, (drm_sis_mem_t __user *)data, sizeof(fb));
+
+	if (!fb.free)
+		return DRM_ERR(EINVAL);
+
+	if (!del_alloc_set(fb.context, VIDEO_TYPE, fb.free))
+		retval = DRM_ERR(EINVAL);
+	sis_free((u32)fb.free);
+
+	DRM_DEBUG("free fb, offset = %lu\n", fb.free);
+
+	return retval;
+}
+
+#else
+
+/* Called by the X Server to initialize the FB heap.  Allocations will fail
+ * unless this is called.  Offset is the beginning of the heap from the
+ * framebuffer offset (MaxXFBMem in XFree86).
+ *
+ * Memory layout according to Thomas Winischofer:
+ * |------------------|DDDDDDDDDDDDDDDDDDDDDDDDDDDDD|HHHH|CCCCCCCCCCC|
+ *
+ *    X driver/sisfb                                  HW-   Command-
+ *  framebuffer memory           DRI heap           Cursor   queue
+ */
+static int sis_fb_init( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_sis_private_t *dev_priv = dev->dev_private;
+	drm_sis_fb_t fb;
+
+	DRM_COPY_FROM_USER_IOCTL(fb, (drm_sis_fb_t __user *)data, sizeof(fb));
+
+	if (dev_priv == NULL) {
+		dev->dev_private = drm_calloc(1, sizeof(drm_sis_private_t),
+		    DRM_MEM_DRIVER);
+		dev_priv = dev->dev_private;
+		if (dev_priv == NULL)
+			return ENOMEM;
+	}
+
+	if (dev_priv->FBHeap != NULL)
+		return DRM_ERR(EINVAL);
+
+	dev_priv->FBHeap = mmInit(fb.offset, fb.size);
+
+	DRM_DEBUG("offset = %u, size = %u", fb.offset, fb.size);
+
+	return 0;
+}
+
+static int sis_fb_alloc( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_sis_private_t *dev_priv = dev->dev_private;
+	drm_sis_mem_t __user *argp = (void __user *)data;
+	drm_sis_mem_t fb;
+	PMemBlock block;
+	int retval = 0;
+
+	if (dev_priv == NULL || dev_priv->FBHeap == NULL)
+		return DRM_ERR(EINVAL);
+  
+	DRM_COPY_FROM_USER_IOCTL(fb, argp, sizeof(fb));
+  
+	block = mmAllocMem(dev_priv->FBHeap, fb.size, 0, 0);
+	if (block) {
+		/* TODO */
+		fb.offset = block->ofs;
+		fb.free = (unsigned long)block;
+		if (!add_alloc_set(fb.context, VIDEO_TYPE, fb.free)) {
+			DRM_DEBUG("adding to allocation set fails\n");
+			mmFreeMem((PMemBlock)fb.free);
+			retval = DRM_ERR(EINVAL);
+		}
+	} else {
+		fb.offset = 0;
+		fb.size = 0;
+		fb.free = 0;
+	}
+
+	DRM_COPY_TO_USER_IOCTL(argp, fb, sizeof(fb));
+
+	DRM_DEBUG("alloc fb, size = %d, offset = %d\n", fb.size, fb.offset);
+
+	return retval;
+}
+
+static int sis_fb_free( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_sis_private_t *dev_priv = dev->dev_private;
+	drm_sis_mem_t fb;
+
+	if (dev_priv == NULL || dev_priv->FBHeap == NULL)
+		return DRM_ERR(EINVAL);
+
+	DRM_COPY_FROM_USER_IOCTL(fb, (drm_sis_mem_t __user *)data, sizeof(fb));
+
+	if (!mmBlockInHeap(dev_priv->FBHeap, (PMemBlock)fb.free))
+		return DRM_ERR(EINVAL);
+
+	if (!del_alloc_set(fb.context, VIDEO_TYPE, fb.free))
+		return DRM_ERR(EINVAL);
+	mmFreeMem((PMemBlock)fb.free);
+
+	DRM_DEBUG("free fb, free = 0x%lx\n", fb.free);
+
+	return 0;
+}
+
+#endif
+
+/* agp memory management */ 
+
+static int sis_ioctl_agp_init( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_sis_private_t *dev_priv = dev->dev_private;
+	drm_sis_agp_t agp;
+
+	if (dev_priv == NULL) {
+		dev->dev_private = drm_calloc(1, sizeof(drm_sis_private_t),
+		    DRM_MEM_DRIVER);
+		dev_priv = dev->dev_private;
+		if (dev_priv == NULL)
+			return ENOMEM;
+	}
+
+	if (dev_priv->AGPHeap != NULL)
+		return DRM_ERR(EINVAL);
+
+	DRM_COPY_FROM_USER_IOCTL(agp, (drm_sis_agp_t __user *)data, sizeof(agp));
+
+	dev_priv->AGPHeap = mmInit(agp.offset, agp.size);
+
+	DRM_DEBUG("offset = %u, size = %u", agp.offset, agp.size);
+  
+	return 0;
+}
+
+static int sis_ioctl_agp_alloc( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_sis_private_t *dev_priv = dev->dev_private;
+	drm_sis_mem_t __user *argp = (void __user *)data;
+	drm_sis_mem_t agp;
+	PMemBlock block;
+	int retval = 0;
+   
+	if (dev_priv == NULL || dev_priv->AGPHeap == NULL)
+		return DRM_ERR(EINVAL);
+  
+	DRM_COPY_FROM_USER_IOCTL(agp, argp, sizeof(agp));
+  
+	block = mmAllocMem(dev_priv->AGPHeap, agp.size, 0, 0);
+	if (block) {
+		/* TODO */
+		agp.offset = block->ofs;
+		agp.free = (unsigned long)block;
+		if (!add_alloc_set(agp.context, AGP_TYPE, agp.free)) {
+			DRM_DEBUG("adding to allocation set fails\n");
+			mmFreeMem((PMemBlock)agp.free);
+			retval = -1;
+		}
+	} else {  
+		agp.offset = 0;
+		agp.size = 0;
+		agp.free = 0;
+	}
+
+	DRM_COPY_TO_USER_IOCTL(argp, agp, sizeof(agp));
+
+	DRM_DEBUG("alloc agp, size = %d, offset = %d\n", agp.size, agp.offset);
+
+	return retval;
+}
+
+static int sis_ioctl_agp_free( DRM_IOCTL_ARGS )
+{
+	DRM_DEVICE;
+	drm_sis_private_t *dev_priv = dev->dev_private;
+	drm_sis_mem_t agp;
+
+	if (dev_priv == NULL || dev_priv->AGPHeap == NULL)
+		return DRM_ERR(EINVAL);
+
+	DRM_COPY_FROM_USER_IOCTL(agp, (drm_sis_mem_t __user *)data, sizeof(agp));
+
+	if (!mmBlockInHeap(dev_priv->AGPHeap, (PMemBlock)agp.free))
+		return DRM_ERR(EINVAL);
+
+	mmFreeMem((PMemBlock)agp.free);
+	if (!del_alloc_set(agp.context, AGP_TYPE, agp.free))
+		return DRM_ERR(EINVAL);
+
+	DRM_DEBUG("free agp, free = 0x%lx\n", agp.free);
+
+	return 0;
+}
+
+int sis_init_context(struct drm_device *dev, int context)
+{
+	int i;
+
+	for (i = 0; i < MAX_CONTEXT ; i++) {
+		if (global_ppriv[i].used &&
+		    (global_ppriv[i].context == context))
+			break;
+	}
+
+	if (i >= MAX_CONTEXT) {
+		for (i = 0; i < MAX_CONTEXT ; i++) {
+			if (!global_ppriv[i].used) {
+				global_ppriv[i].context = context;
+				global_ppriv[i].used = 1;
+				global_ppriv[i].sets[0] = setInit();
+				global_ppriv[i].sets[1] = setInit();
+				DRM_DEBUG("init allocation set, socket=%d, "
+				    "context = %d\n", i, context);
+				break;
+			}
+		}
+		if ((i >= MAX_CONTEXT) || (global_ppriv[i].sets[0] == NULL) ||
+		    (global_ppriv[i].sets[1] == NULL))
+		{
+			return 0;
+		}
+	}
+	
+	return 1;
+}
+
+int sis_final_context(struct drm_device *dev, int context)
+{
+	int i;
+
+	for (i=0; i<MAX_CONTEXT; i++) {
+		if (global_ppriv[i].used &&
+		    (global_ppriv[i].context == context))
+			break;
+	}
+
+	if (i < MAX_CONTEXT) {
+		set_t *set;
+		unsigned int item;
+		int retval;
+
+		DRM_DEBUG("find socket %d, context = %d\n", i, context);
+
+		/* Video Memory */
+		set = global_ppriv[i].sets[0];
+		retval = setFirst(set, &item);
+		while (retval) {
+			DRM_DEBUG("free video memory 0x%x\n", item);
+#if defined(__linux__) && defined(CONFIG_FB_SIS)
+			sis_free(item);
+#else
+			mmFreeMem((PMemBlock)item);
+#endif
+			retval = setNext(set, &item);
+		}
+		setDestroy(set);
+
+		/* AGP Memory */
+		set = global_ppriv[i].sets[1];
+		retval = setFirst(set, &item);
+		while (retval) {
+			DRM_DEBUG("free agp memory 0x%x\n", item);
+			mmFreeMem((PMemBlock)item);
+			retval = setNext(set, &item);
+		}
+		setDestroy(set);
+
+		global_ppriv[i].used = 0;	  
+        }
+	
+	return 1;
+}
+
+drm_ioctl_desc_t sis_ioctls[] = {
+	[DRM_IOCTL_NR(DRM_SIS_FB_ALLOC)]  = { sis_fb_alloc,        1, 0 },
+	[DRM_IOCTL_NR(DRM_SIS_FB_FREE)]   = { sis_fb_free,         1, 0 },
+	[DRM_IOCTL_NR(DRM_SIS_AGP_INIT)]  = { sis_ioctl_agp_init,  1, 1 },
+	[DRM_IOCTL_NR(DRM_SIS_AGP_ALLOC)] = { sis_ioctl_agp_alloc, 1, 0 },
+	[DRM_IOCTL_NR(DRM_SIS_AGP_FREE)]  = { sis_ioctl_agp_free,  1, 0 },
+	[DRM_IOCTL_NR(DRM_SIS_FB_INIT)]   = { sis_fb_init,         1, 1 }
+};
+
+int sis_max_ioctl = DRM_ARRAY_SIZE(sis_ioctls);
+
diff --git a/drivers/char/drm/tdfx_drv.c b/drivers/char/drm/tdfx_drv.c
new file mode 100644
index 0000000..0e7943e
--- /dev/null
+++ b/drivers/char/drm/tdfx_drv.c
@@ -0,0 +1,107 @@
+/* tdfx_drv.c -- tdfx driver -*- linux-c -*-
+ * Created: Thu Oct  7 10:38:32 1999 by faith@precisioninsight.com
+ *
+ * Copyright 1999 Precision Insight, Inc., Cedar Park, Texas.
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Rickard E. (Rik) Faith <faith@valinux.com>
+ *    Daryll Strauss <daryll@valinux.com>
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#include <linux/config.h>
+#include "drmP.h"
+#include "tdfx_drv.h"
+
+#include "drm_pciids.h"
+
+static int postinit( struct drm_device *dev, unsigned long flags )
+{
+	DRM_INFO( "Initialized %s %d.%d.%d %s on minor %d: %s\n",
+		DRIVER_NAME,
+		DRIVER_MAJOR,
+		DRIVER_MINOR,
+		DRIVER_PATCHLEVEL,
+		DRIVER_DATE,
+		dev->primary.minor,
+		pci_pretty_name(dev->pdev)
+		);
+	return 0;
+}
+
+static int version( drm_version_t *version )
+{
+	int len;
+
+	version->version_major = DRIVER_MAJOR;
+	version->version_minor = DRIVER_MINOR;
+	version->version_patchlevel = DRIVER_PATCHLEVEL;
+	DRM_COPY( version->name, DRIVER_NAME );
+	DRM_COPY( version->date, DRIVER_DATE );
+	DRM_COPY( version->desc, DRIVER_DESC );
+	return 0;
+}
+
+static struct pci_device_id pciidlist[] = {
+	tdfx_PCI_IDS
+};
+
+static struct drm_driver driver = {
+	.driver_features = DRIVER_USE_MTRR,
+	.reclaim_buffers = drm_core_reclaim_buffers,
+	.get_map_ofs = drm_core_get_map_ofs,
+	.get_reg_ofs = drm_core_get_reg_ofs,
+	.postinit = postinit,
+	.version = version,
+	.fops = {
+		.owner = THIS_MODULE,
+		.open = drm_open,
+		.release = drm_release,
+		.ioctl = drm_ioctl,
+		.mmap = drm_mmap,
+		.poll = drm_poll,
+		.fasync = drm_fasync,
+	},
+	.pci_driver = {
+		.name          = DRIVER_NAME,
+		.id_table      = pciidlist,
+	}
+};
+
+static int __init tdfx_init(void)
+{
+	return drm_init(&driver);
+}
+
+static void __exit tdfx_exit(void)
+{
+	drm_exit(&driver);
+}
+
+module_init(tdfx_init);
+module_exit(tdfx_exit);
+
+MODULE_AUTHOR( DRIVER_AUTHOR );
+MODULE_DESCRIPTION( DRIVER_DESC );
+MODULE_LICENSE("GPL and additional rights");
diff --git a/drivers/char/drm/tdfx_drv.h b/drivers/char/drm/tdfx_drv.h
new file mode 100644
index 0000000..a582a3d
--- /dev/null
+++ b/drivers/char/drm/tdfx_drv.h
@@ -0,0 +1,50 @@
+/* tdfx.h -- 3dfx DRM template customization -*- linux-c -*-
+ * Created: Wed Feb 14 12:32:32 2001 by gareth@valinux.com
+ *
+ * Copyright 2000 VA Linux Systems, Inc., Sunnyvale, California.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ *    Gareth Hughes <gareth@valinux.com>
+ */
+
+#ifndef __TDFX_H__
+#define __TDFX_H__
+
+/* This remains constant for all DRM template files.
+ */
+#define DRM(x) tdfx_##x
+
+/* General customization:
+ */
+
+#define DRIVER_AUTHOR		"VA Linux Systems Inc."
+
+#define DRIVER_NAME		"tdfx"
+#define DRIVER_DESC		"3dfx Banshee/Voodoo3+"
+#define DRIVER_DATE		"20010216"
+
+#define DRIVER_MAJOR		1
+#define DRIVER_MINOR		0
+#define DRIVER_PATCHLEVEL	0
+
+#endif
diff --git a/drivers/char/ds1286.c b/drivers/char/ds1286.c
new file mode 100644
index 0000000..d755cac
--- /dev/null
+++ b/drivers/char/ds1286.c
@@ -0,0 +1,578 @@
+/*
+ * DS1286 Real Time Clock interface for Linux
+ *
+ * Copyright (C) 1998, 1999, 2000 Ralf Baechle
+ *
+ * Based on code written by Paul Gortmaker.
+ *
+ * This driver allows use of the real time clock (built into nearly all
+ * computers) from user space. It exports the /dev/rtc interface supporting
+ * various ioctl() and also the /proc/rtc pseudo-file for status
+ * information.
+ *
+ * The ioctls can be used to set the interrupt behaviour and generation rate
+ * from the RTC via IRQ 8. Then the /dev/rtc interface can be used to make
+ * use of these timer interrupts, be they interval or alarm based.
+ *
+ * The /dev/rtc interface will block on reads until an interrupt has been
+ * received. If a RTC interrupt has already happened, it will output an
+ * unsigned long and then block. The output value contains the interrupt
+ * status in the low byte and the number of interrupts since the last read
+ * in the remaining high bytes. The /dev/rtc interface can also be used with
+ * the select(2) call.
+ *
+ * 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.
+ */
+#include <linux/ds1286.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/rtc.h>
+#include <linux/spinlock.h>
+#include <linux/bcd.h>
+#include <linux/proc_fs.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define DS1286_VERSION		"1.0"
+
+/*
+ *	We sponge a minor off of the misc major. No need slurping
+ *	up another valuable major dev number for this. If you add
+ *	an ioctl, make sure you don't conflict with SPARC's RTC
+ *	ioctls.
+ */
+
+static DECLARE_WAIT_QUEUE_HEAD(ds1286_wait);
+
+static ssize_t ds1286_read(struct file *file, char *buf,
+			size_t count, loff_t *ppos);
+
+static int ds1286_ioctl(struct inode *inode, struct file *file,
+                        unsigned int cmd, unsigned long arg);
+
+static unsigned int ds1286_poll(struct file *file, poll_table *wait);
+
+static void ds1286_get_alm_time (struct rtc_time *alm_tm);
+static void ds1286_get_time(struct rtc_time *rtc_tm);
+static int ds1286_set_time(struct rtc_time *rtc_tm);
+
+static inline unsigned char ds1286_is_updating(void);
+
+static DEFINE_SPINLOCK(ds1286_lock);
+
+static int ds1286_read_proc(char *page, char **start, off_t off,
+                            int count, int *eof, void *data);
+
+/*
+ *	Bits in rtc_status. (7 bits of room for future expansion)
+ */
+
+#define RTC_IS_OPEN		0x01	/* means /dev/rtc is in use	*/
+#define RTC_TIMER_ON		0x02	/* missed irq timer active	*/
+
+static unsigned char ds1286_status;	/* bitmapped status byte.	*/
+
+static unsigned char days_in_mo[] = {
+	0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ *	Now all the various file operations that we export.
+ */
+
+static ssize_t ds1286_read(struct file *file, char *buf,
+                           size_t count, loff_t *ppos)
+{
+	return -EIO;
+}
+
+static int ds1286_ioctl(struct inode *inode, struct file *file,
+                        unsigned int cmd, unsigned long arg)
+{
+	struct rtc_time wtime;
+
+	switch (cmd) {
+	case RTC_AIE_OFF:	/* Mask alarm int. enab. bit	*/
+	{
+		unsigned int flags;
+		unsigned char val;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		spin_lock_irqsave(&ds1286_lock, flags);
+		val = rtc_read(RTC_CMD);
+		val |=  RTC_TDM;
+		rtc_write(val, RTC_CMD);
+		spin_unlock_irqrestore(&ds1286_lock, flags);
+
+		return 0;
+	}
+	case RTC_AIE_ON:	/* Allow alarm interrupts.	*/
+	{
+		unsigned int flags;
+		unsigned char val;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		spin_lock_irqsave(&ds1286_lock, flags);
+		val = rtc_read(RTC_CMD);
+		val &=  ~RTC_TDM;
+		rtc_write(val, RTC_CMD);
+		spin_unlock_irqrestore(&ds1286_lock, flags);
+
+		return 0;
+	}
+	case RTC_WIE_OFF:	/* Mask watchdog int. enab. bit	*/
+	{
+		unsigned int flags;
+		unsigned char val;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		spin_lock_irqsave(&ds1286_lock, flags);
+		val = rtc_read(RTC_CMD);
+		val |= RTC_WAM;
+		rtc_write(val, RTC_CMD);
+		spin_unlock_irqrestore(&ds1286_lock, flags);
+
+		return 0;
+	}
+	case RTC_WIE_ON:	/* Allow watchdog interrupts.	*/
+	{
+		unsigned int flags;
+		unsigned char val;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		spin_lock_irqsave(&ds1286_lock, flags);
+		val = rtc_read(RTC_CMD);
+		val &= ~RTC_WAM;
+		rtc_write(val, RTC_CMD);
+		spin_unlock_irqrestore(&ds1286_lock, flags);
+
+		return 0;
+	}
+	case RTC_ALM_READ:	/* Read the present alarm time */
+	{
+		/*
+		 * This returns a struct rtc_time. Reading >= 0xc0
+		 * means "don't care" or "match all". Only the tm_hour,
+		 * tm_min, and tm_sec values are filled in.
+		 */
+
+		memset(&wtime, 0, sizeof(wtime));
+		ds1286_get_alm_time(&wtime);
+		break;
+	}
+	case RTC_ALM_SET:	/* Store a time into the alarm */
+	{
+		/*
+		 * This expects a struct rtc_time. Writing 0xff means
+		 * "don't care" or "match all". Only the tm_hour,
+		 * tm_min and tm_sec are used.
+		 */
+		unsigned char hrs, min, sec;
+		struct rtc_time alm_tm;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		if (copy_from_user(&alm_tm, (struct rtc_time*)arg,
+				   sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		hrs = alm_tm.tm_hour;
+		min = alm_tm.tm_min;
+
+		if (hrs >= 24)
+			hrs = 0xff;
+
+		if (min >= 60)
+			min = 0xff;
+
+		BIN_TO_BCD(sec);
+		BIN_TO_BCD(min);
+		BIN_TO_BCD(hrs);
+
+		spin_lock(&ds1286_lock);
+		rtc_write(hrs, RTC_HOURS_ALARM);
+		rtc_write(min, RTC_MINUTES_ALARM);
+		spin_unlock(&ds1286_lock);
+
+		return 0;
+	}
+	case RTC_RD_TIME:	/* Read the time/date from RTC	*/
+	{
+		memset(&wtime, 0, sizeof(wtime));
+		ds1286_get_time(&wtime);
+		break;
+	}
+	case RTC_SET_TIME:	/* Set the RTC */
+	{
+		struct rtc_time rtc_tm;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		if (copy_from_user(&rtc_tm, (struct rtc_time*)arg,
+				   sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		return ds1286_set_time(&rtc_tm);
+	}
+	default:
+		return -EINVAL;
+	}
+	return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0;
+}
+
+/*
+ *	We enforce only one user at a time here with the open/close.
+ *	Also clear the previous interrupt data on an open, and clean
+ *	up things on a close.
+ */
+
+static int ds1286_open(struct inode *inode, struct file *file)
+{
+	spin_lock_irq(&ds1286_lock);
+
+	if (ds1286_status & RTC_IS_OPEN)
+		goto out_busy;
+
+	ds1286_status |= RTC_IS_OPEN;
+
+	spin_unlock_irq(&ds1286_lock);
+	return 0;
+
+out_busy:
+	spin_lock_irq(&ds1286_lock);
+	return -EBUSY;
+}
+
+static int ds1286_release(struct inode *inode, struct file *file)
+{
+	ds1286_status &= ~RTC_IS_OPEN;
+
+	return 0;
+}
+
+static unsigned int ds1286_poll(struct file *file, poll_table *wait)
+{
+	poll_wait(file, &ds1286_wait, wait);
+
+	return 0;
+}
+
+/*
+ *	The various file operations we support.
+ */
+
+static struct file_operations ds1286_fops = {
+	.llseek		= no_llseek,
+	.read		= ds1286_read,
+	.poll		= ds1286_poll,
+	.ioctl		= ds1286_ioctl,
+	.open		= ds1286_open,
+	.release	= ds1286_release,
+};
+
+static struct miscdevice ds1286_dev=
+{
+	.minor	= RTC_MINOR,
+	.name	= "rtc",
+	.fops	= &ds1286_fops,
+};
+
+static int __init ds1286_init(void)
+{
+	int err;
+
+	printk(KERN_INFO "DS1286 Real Time Clock Driver v%s\n", DS1286_VERSION);
+
+	err = misc_register(&ds1286_dev);
+	if (err)
+		goto out;
+
+	if (!create_proc_read_entry("driver/rtc", 0, 0, ds1286_read_proc, NULL)) {
+		err = -ENOMEM;
+
+		goto out_deregister;
+	}
+
+	return 0;
+
+out_deregister:
+	misc_deregister(&ds1286_dev);
+
+out:
+	return err;
+}
+
+static void __exit ds1286_exit(void)
+{
+	remove_proc_entry("driver/rtc", NULL);
+	misc_deregister(&ds1286_dev);
+}
+
+static char *days[] = {
+	"***", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+/*
+ *	Info exported via "/proc/rtc".
+ */
+static int ds1286_proc_output(char *buf)
+{
+	char *p, *s;
+	struct rtc_time tm;
+	unsigned char hundredth, month, cmd, amode;
+
+	p = buf;
+
+	ds1286_get_time(&tm);
+	hundredth = rtc_read(RTC_HUNDREDTH_SECOND);
+	BCD_TO_BIN(hundredth);
+
+	p += sprintf(p,
+	             "rtc_time\t: %02d:%02d:%02d.%02d\n"
+	             "rtc_date\t: %04d-%02d-%02d\n",
+		     tm.tm_hour, tm.tm_min, tm.tm_sec, hundredth,
+		     tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+
+	/*
+	 * We implicitly assume 24hr mode here. Alarm values >= 0xc0 will
+	 * match any value for that particular field. Values that are
+	 * greater than a valid time, but less than 0xc0 shouldn't appear.
+	 */
+	ds1286_get_alm_time(&tm);
+	p += sprintf(p, "alarm\t\t: %s ", days[tm.tm_wday]);
+	if (tm.tm_hour <= 24)
+		p += sprintf(p, "%02d:", tm.tm_hour);
+	else
+		p += sprintf(p, "**:");
+
+	if (tm.tm_min <= 59)
+		p += sprintf(p, "%02d\n", tm.tm_min);
+	else
+		p += sprintf(p, "**\n");
+
+	month = rtc_read(RTC_MONTH);
+	p += sprintf(p,
+	             "oscillator\t: %s\n"
+	             "square_wave\t: %s\n",
+	             (month & RTC_EOSC) ? "disabled" : "enabled",
+	             (month & RTC_ESQW) ? "disabled" : "enabled");
+
+	amode = ((rtc_read(RTC_MINUTES_ALARM) & 0x80) >> 5) |
+	        ((rtc_read(RTC_HOURS_ALARM) & 0x80) >> 6) |
+	        ((rtc_read(RTC_DAY_ALARM) & 0x80) >> 7);
+	if (amode == 7)      s = "each minute";
+	else if (amode == 3) s = "minutes match";
+	else if (amode == 1) s = "hours and minutes match";
+	else if (amode == 0) s = "days, hours and minutes match";
+	else                 s = "invalid";
+	p += sprintf(p, "alarm_mode\t: %s\n", s);
+
+	cmd = rtc_read(RTC_CMD);
+	p += sprintf(p,
+	             "alarm_enable\t: %s\n"
+	             "wdog_alarm\t: %s\n"
+	             "alarm_mask\t: %s\n"
+	             "wdog_alarm_mask\t: %s\n"
+	             "interrupt_mode\t: %s\n"
+	             "INTB_mode\t: %s_active\n"
+	             "interrupt_pins\t: %s\n",
+		     (cmd & RTC_TDF) ? "yes" : "no",
+		     (cmd & RTC_WAF) ? "yes" : "no",
+		     (cmd & RTC_TDM) ? "disabled" : "enabled",
+		     (cmd & RTC_WAM) ? "disabled" : "enabled",
+		     (cmd & RTC_PU_LVL) ? "pulse" : "level",
+		     (cmd & RTC_IBH_LO) ? "low" : "high",
+	             (cmd & RTC_IPSW) ? "unswapped" : "swapped");
+
+	return  p - buf;
+}
+
+static int ds1286_read_proc(char *page, char **start, off_t off,
+                         int count, int *eof, void *data)
+{
+	int len = ds1286_proc_output (page);
+	if (len <= off+count) *eof = 1;
+	*start = page + off;
+	len -= off;
+	if (len>count)
+		len = count;
+	if (len<0)
+		len = 0;
+
+	return len;
+}
+
+/*
+ * Returns true if a clock update is in progress
+ */
+static inline unsigned char ds1286_is_updating(void)
+{
+	return rtc_read(RTC_CMD) & RTC_TE;
+}
+
+
+static void ds1286_get_time(struct rtc_time *rtc_tm)
+{
+	unsigned char save_control;
+	unsigned int flags;
+	unsigned long uip_watchdog = jiffies;
+
+	/*
+	 * read RTC once any update in progress is done. The update
+	 * can take just over 2ms. We wait 10 to 20ms. There is no need to
+	 * to poll-wait (up to 1s - eeccch) for the falling edge of RTC_UIP.
+	 * If you need to know *exactly* when a second has started, enable
+	 * periodic update complete interrupts, (via ioctl) and then
+	 * immediately read /dev/rtc which will block until you get the IRQ.
+	 * Once the read clears, read the RTC time (again via ioctl). Easy.
+	 */
+
+	if (ds1286_is_updating() != 0)
+		while (jiffies - uip_watchdog < 2*HZ/100)
+			barrier();
+
+	/*
+	 * Only the values that we read from the RTC are set. We leave
+	 * tm_wday, tm_yday and tm_isdst untouched. Even though the
+	 * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated
+	 * by the RTC when initially set to a non-zero value.
+	 */
+	spin_lock_irqsave(&ds1286_lock, flags);
+	save_control = rtc_read(RTC_CMD);
+	rtc_write((save_control|RTC_TE), RTC_CMD);
+
+	rtc_tm->tm_sec = rtc_read(RTC_SECONDS);
+	rtc_tm->tm_min = rtc_read(RTC_MINUTES);
+	rtc_tm->tm_hour = rtc_read(RTC_HOURS) & 0x3f;
+	rtc_tm->tm_mday = rtc_read(RTC_DATE);
+	rtc_tm->tm_mon = rtc_read(RTC_MONTH) & 0x1f;
+	rtc_tm->tm_year = rtc_read(RTC_YEAR);
+
+	rtc_write(save_control, RTC_CMD);
+	spin_unlock_irqrestore(&ds1286_lock, flags);
+
+	BCD_TO_BIN(rtc_tm->tm_sec);
+	BCD_TO_BIN(rtc_tm->tm_min);
+	BCD_TO_BIN(rtc_tm->tm_hour);
+	BCD_TO_BIN(rtc_tm->tm_mday);
+	BCD_TO_BIN(rtc_tm->tm_mon);
+	BCD_TO_BIN(rtc_tm->tm_year);
+
+	/*
+	 * Account for differences between how the RTC uses the values
+	 * and how they are defined in a struct rtc_time;
+	 */
+	if (rtc_tm->tm_year < 45)
+		rtc_tm->tm_year += 30;
+	if ((rtc_tm->tm_year += 40) < 70)
+		rtc_tm->tm_year += 100;
+
+	rtc_tm->tm_mon--;
+}
+
+static int ds1286_set_time(struct rtc_time *rtc_tm)
+{
+	unsigned char mon, day, hrs, min, sec, leap_yr;
+	unsigned char save_control;
+	unsigned int yrs, flags;
+
+
+	yrs = rtc_tm->tm_year + 1900;
+	mon = rtc_tm->tm_mon + 1;   /* tm_mon starts at zero */
+	day = rtc_tm->tm_mday;
+	hrs = rtc_tm->tm_hour;
+	min = rtc_tm->tm_min;
+	sec = rtc_tm->tm_sec;
+
+	if (yrs < 1970)
+		return -EINVAL;
+
+	leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+	if ((mon > 12) || (day == 0))
+		return -EINVAL;
+
+	if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+		return -EINVAL;
+
+	if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+		return -EINVAL;
+
+	if ((yrs -= 1940) > 255)    /* They are unsigned */
+		return -EINVAL;
+
+	if (yrs >= 100)
+		yrs -= 100;
+
+	BIN_TO_BCD(sec);
+	BIN_TO_BCD(min);
+	BIN_TO_BCD(hrs);
+	BIN_TO_BCD(day);
+	BIN_TO_BCD(mon);
+	BIN_TO_BCD(yrs);
+
+	spin_lock_irqsave(&ds1286_lock, flags);
+	save_control = rtc_read(RTC_CMD);
+	rtc_write((save_control|RTC_TE), RTC_CMD);
+
+	rtc_write(yrs, RTC_YEAR);
+	rtc_write(mon, RTC_MONTH);
+	rtc_write(day, RTC_DATE);
+	rtc_write(hrs, RTC_HOURS);
+	rtc_write(min, RTC_MINUTES);
+	rtc_write(sec, RTC_SECONDS);
+	rtc_write(0, RTC_HUNDREDTH_SECOND);
+
+	rtc_write(save_control, RTC_CMD);
+	spin_unlock_irqrestore(&ds1286_lock, flags);
+
+	return 0;
+}
+
+static void ds1286_get_alm_time(struct rtc_time *alm_tm)
+{
+	unsigned char cmd;
+	unsigned int flags;
+
+	/*
+	 * Only the values that we read from the RTC are set. That
+	 * means only tm_wday, tm_hour, tm_min.
+	 */
+	spin_lock_irqsave(&ds1286_lock, flags);
+	alm_tm->tm_min = rtc_read(RTC_MINUTES_ALARM) & 0x7f;
+	alm_tm->tm_hour = rtc_read(RTC_HOURS_ALARM)  & 0x1f;
+	alm_tm->tm_wday = rtc_read(RTC_DAY_ALARM)    & 0x07;
+	cmd = rtc_read(RTC_CMD);
+	spin_unlock_irqrestore(&ds1286_lock, flags);
+
+	BCD_TO_BIN(alm_tm->tm_min);
+	BCD_TO_BIN(alm_tm->tm_hour);
+	alm_tm->tm_sec = 0;
+}
+
+module_init(ds1286_init);
+module_exit(ds1286_exit);
+
+MODULE_AUTHOR("Ralf Baechle");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(RTC_MINOR);
diff --git a/drivers/char/ds1302.c b/drivers/char/ds1302.c
new file mode 100644
index 0000000..a75e860
--- /dev/null
+++ b/drivers/char/ds1302.c
@@ -0,0 +1,354 @@
+/*!***************************************************************************
+*!
+*! FILE NAME  : ds1302.c
+*!
+*! DESCRIPTION: Implements an interface for the DS1302 RTC
+*!
+*! Functions exported: ds1302_readreg, ds1302_writereg, ds1302_init, get_rtc_status
+*!
+*! ---------------------------------------------------------------------------
+*!
+*! (C) Copyright 1999, 2000, 2001  Axis Communications AB, LUND, SWEDEN
+*!
+*!***************************************************************************/
+
+#include <linux/config.h>
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/delay.h>
+#include <linux/bcd.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/rtc.h>
+#if defined(CONFIG_M32R)
+#include <asm/m32r.h>
+#endif
+
+#define RTC_MAJOR_NR 121 /* local major, change later */
+
+static const char ds1302_name[] = "ds1302";
+
+/* Send 8 bits. */
+static void
+out_byte_rtc(unsigned int reg_addr, unsigned char x)
+{
+	//RST H
+	outw(0x0001,(unsigned long)PLD_RTCRSTODT);
+	//write data
+	outw(((x<<8)|(reg_addr&0xff)),(unsigned long)PLD_RTCWRDATA);
+	//WE
+	outw(0x0002,(unsigned long)PLD_RTCCR);
+	//wait
+	while(inw((unsigned long)PLD_RTCCR));
+
+	//RST L
+	outw(0x0000,(unsigned long)PLD_RTCRSTODT);
+
+}
+
+static unsigned char
+in_byte_rtc(unsigned int reg_addr)
+{
+	unsigned char retval;
+
+	//RST H
+	outw(0x0001,(unsigned long)PLD_RTCRSTODT);
+	//write data
+	outw((reg_addr&0xff),(unsigned long)PLD_RTCRDDATA);
+	//RE
+	outw(0x0001,(unsigned long)PLD_RTCCR);
+	//wait
+	while(inw((unsigned long)PLD_RTCCR));
+
+	//read data
+	retval=(inw((unsigned long)PLD_RTCRDDATA) & 0xff00)>>8;
+
+	//RST L
+	outw(0x0000,(unsigned long)PLD_RTCRSTODT);
+
+	return retval;
+}
+
+/* Enable writing. */
+
+static void
+ds1302_wenable(void)
+{
+	out_byte_rtc(0x8e,0x00);
+}
+
+/* Disable writing. */
+
+static void
+ds1302_wdisable(void)
+{
+	out_byte_rtc(0x8e,0x80);
+}
+
+
+
+/* Read a byte from the selected register in the DS1302. */
+
+unsigned char
+ds1302_readreg(int reg)
+{
+	unsigned char x;
+
+	x=in_byte_rtc((0x81 | (reg << 1))); /* read register */
+
+	return x;
+}
+
+/* Write a byte to the selected register. */
+
+void
+ds1302_writereg(int reg, unsigned char val)
+{
+	ds1302_wenable();
+	out_byte_rtc((0x80 | (reg << 1)),val);
+	ds1302_wdisable();
+}
+
+void
+get_rtc_time(struct rtc_time *rtc_tm)
+{
+	unsigned long flags;
+
+	local_irq_save(flags);
+	local_irq_disable();
+
+	rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS);
+	rtc_tm->tm_min = CMOS_READ(RTC_MINUTES);
+	rtc_tm->tm_hour = CMOS_READ(RTC_HOURS);
+	rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH);
+	rtc_tm->tm_mon = CMOS_READ(RTC_MONTH);
+	rtc_tm->tm_year = CMOS_READ(RTC_YEAR);
+
+	local_irq_restore(flags);
+
+	BCD_TO_BIN(rtc_tm->tm_sec);
+	BCD_TO_BIN(rtc_tm->tm_min);
+	BCD_TO_BIN(rtc_tm->tm_hour);
+	BCD_TO_BIN(rtc_tm->tm_mday);
+	BCD_TO_BIN(rtc_tm->tm_mon);
+	BCD_TO_BIN(rtc_tm->tm_year);
+
+	/*
+	 * Account for differences between how the RTC uses the values
+	 * and how they are defined in a struct rtc_time;
+	 */
+
+	if (rtc_tm->tm_year <= 69)
+		rtc_tm->tm_year += 100;
+
+	rtc_tm->tm_mon--;
+}
+
+static unsigned char days_in_mo[] =
+    {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+/* ioctl that supports RTC_RD_TIME and RTC_SET_TIME (read and set time/date). */
+
+static int
+rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	  unsigned long arg)
+{
+	unsigned long flags;
+
+	switch(cmd) {
+		case RTC_RD_TIME:	/* read the time/date from RTC	*/
+		{
+			struct rtc_time rtc_tm;
+
+			memset(&rtc_tm, 0, sizeof (struct rtc_time));
+			get_rtc_time(&rtc_tm);
+			if (copy_to_user((struct rtc_time*)arg, &rtc_tm, sizeof(struct rtc_time)))
+				return -EFAULT;
+			return 0;
+		}
+
+		case RTC_SET_TIME:	/* set the RTC */
+		{
+			struct rtc_time rtc_tm;
+			unsigned char mon, day, hrs, min, sec, leap_yr;
+			unsigned int yrs;
+
+			if (!capable(CAP_SYS_TIME))
+				return -EPERM;
+
+			if (copy_from_user(&rtc_tm, (struct rtc_time*)arg, sizeof(struct rtc_time)))
+				return -EFAULT;
+
+			yrs = rtc_tm.tm_year + 1900;
+			mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */
+			day = rtc_tm.tm_mday;
+			hrs = rtc_tm.tm_hour;
+			min = rtc_tm.tm_min;
+			sec = rtc_tm.tm_sec;
+
+
+			if ((yrs < 1970) || (yrs > 2069))
+				return -EINVAL;
+
+			leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+			if ((mon > 12) || (day == 0))
+				return -EINVAL;
+
+			if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+				return -EINVAL;
+
+			if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+				return -EINVAL;
+
+			if (yrs >= 2000)
+				yrs -= 2000;	/* RTC (0, 1, ... 69) */
+			else
+				yrs -= 1900;	/* RTC (70, 71, ... 99) */
+
+			BIN_TO_BCD(sec);
+			BIN_TO_BCD(min);
+			BIN_TO_BCD(hrs);
+			BIN_TO_BCD(day);
+			BIN_TO_BCD(mon);
+			BIN_TO_BCD(yrs);
+
+			local_irq_save(flags);
+			local_irq_disable();
+			CMOS_WRITE(yrs, RTC_YEAR);
+			CMOS_WRITE(mon, RTC_MONTH);
+			CMOS_WRITE(day, RTC_DAY_OF_MONTH);
+			CMOS_WRITE(hrs, RTC_HOURS);
+			CMOS_WRITE(min, RTC_MINUTES);
+			CMOS_WRITE(sec, RTC_SECONDS);
+			local_irq_restore(flags);
+
+			/* Notice that at this point, the RTC is updated but
+			 * the kernel is still running with the old time.
+			 * You need to set that separately with settimeofday
+			 * or adjtimex.
+			 */
+			return 0;
+		}
+
+		case RTC_SET_CHARGE: /* set the RTC TRICKLE CHARGE register */
+		{
+			int tcs_val;
+
+			if (!capable(CAP_SYS_TIME))
+				return -EPERM;
+
+			if(copy_from_user(&tcs_val, (int*)arg, sizeof(int)))
+				return -EFAULT;
+
+			tcs_val = RTC_TCR_PATTERN | (tcs_val & 0x0F);
+			ds1302_writereg(RTC_TRICKLECHARGER, tcs_val);
+			return 0;
+		}
+		default:
+			return -EINVAL;
+	}
+}
+
+int
+get_rtc_status(char *buf)
+{
+	char *p;
+	struct rtc_time tm;
+
+	p = buf;
+
+	get_rtc_time(&tm);
+
+	/*
+	 * There is no way to tell if the luser has the RTC set for local
+	 * time or for Universal Standard Time (GMT). Probably local though.
+	 */
+
+	p += sprintf(p,
+		"rtc_time\t: %02d:%02d:%02d\n"
+		"rtc_date\t: %04d-%02d-%02d\n",
+		tm.tm_hour, tm.tm_min, tm.tm_sec,
+		tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+
+	return  p - buf;
+}
+
+
+/* The various file operations we support. */
+
+static struct file_operations rtc_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= rtc_ioctl,
+};
+
+/* Probe for the chip by writing something to its RAM and try reading it back. */
+
+#define MAGIC_PATTERN 0x42
+
+static int __init
+ds1302_probe(void)
+{
+	int retval, res, baur;
+
+	baur=(boot_cpu_data.bus_clock/(2*1000*1000));
+
+	printk("%s: Set PLD_RTCBAUR = %d\n", ds1302_name,baur);
+
+	outw(0x0000,(unsigned long)PLD_RTCCR);
+	outw(0x0000,(unsigned long)PLD_RTCRSTODT);
+	outw(baur,(unsigned long)PLD_RTCBAUR);
+
+	/* Try to talk to timekeeper. */
+
+	ds1302_wenable();
+	/* write RAM byte 0 */
+	/* write something magic */
+	out_byte_rtc(0xc0,MAGIC_PATTERN);
+
+	/* read RAM byte 0 */
+	if((res = in_byte_rtc(0xc1)) == MAGIC_PATTERN) {
+		char buf[100];
+		ds1302_wdisable();
+		printk("%s: RTC found.\n", ds1302_name);
+		get_rtc_status(buf);
+		printk(buf);
+		retval = 1;
+	} else {
+		printk("%s: RTC not found.\n", ds1302_name);
+		retval = 0;
+	}
+
+	return retval;
+}
+
+
+/* Just probe for the RTC and register the device to handle the ioctl needed. */
+
+int __init
+ds1302_init(void)
+{
+	if (!ds1302_probe()) {
+		return -1;
+  	}
+	return 0;
+}
+
+static int __init ds1302_register(void)
+{
+	ds1302_init();
+	if (register_chrdev(RTC_MAJOR_NR, ds1302_name, &rtc_fops)) {
+		printk(KERN_INFO "%s: unable to get major %d for rtc\n",
+		       ds1302_name, RTC_MAJOR_NR);
+		return -1;
+	}
+	return 0;
+}
+
+module_init(ds1302_register);
diff --git a/drivers/char/ds1620.c b/drivers/char/ds1620.c
new file mode 100644
index 0000000..7def6ad
--- /dev/null
+++ b/drivers/char/ds1620.c
@@ -0,0 +1,416 @@
+/*
+ * linux/drivers/char/ds1620.c: Dallas Semiconductors DS1620
+ *   thermometer driver (as used in the Rebel.com NetWinder)
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/miscdevice.h>
+#include <linux/smp_lock.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/capability.h>
+#include <linux/init.h>
+
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/uaccess.h>
+#include <asm/therm.h>
+
+#ifdef CONFIG_PROC_FS
+/* define for /proc interface */
+#define THERM_USE_PROC
+#endif
+
+/* Definitions for DS1620 chip */
+#define THERM_START_CONVERT	0xee
+#define THERM_RESET		0xaf
+#define THERM_READ_CONFIG	0xac
+#define THERM_READ_TEMP		0xaa
+#define THERM_READ_TL		0xa2
+#define THERM_READ_TH		0xa1
+#define THERM_WRITE_CONFIG	0x0c
+#define THERM_WRITE_TL		0x02
+#define THERM_WRITE_TH		0x01
+
+#define CFG_CPU			2
+#define CFG_1SHOT		1
+
+static const char *fan_state[] = { "off", "on", "on (hardwired)" };
+
+/*
+ * Start of NetWinder specifics
+ *  Note!  We have to hold the gpio lock with IRQs disabled over the
+ *  whole of our transaction to the Dallas chip, since there is a
+ *  chance that the WaveArtist driver could touch these bits to
+ *  enable or disable the speaker.
+ */
+extern spinlock_t gpio_lock;
+extern unsigned int system_rev;
+
+static inline void netwinder_ds1620_set_clk(int clk)
+{
+	gpio_modify_op(GPIO_DSCLK, clk ? GPIO_DSCLK : 0);
+}
+
+static inline void netwinder_ds1620_set_data(int dat)
+{
+	gpio_modify_op(GPIO_DATA, dat ? GPIO_DATA : 0);
+}
+
+static inline int netwinder_ds1620_get_data(void)
+{
+	return gpio_read() & GPIO_DATA;
+}
+
+static inline void netwinder_ds1620_set_data_dir(int dir)
+{
+	gpio_modify_io(GPIO_DATA, dir ? GPIO_DATA : 0);
+}
+
+static inline void netwinder_ds1620_reset(void)
+{
+	cpld_modify(CPLD_DS_ENABLE, 0);
+	cpld_modify(CPLD_DS_ENABLE, CPLD_DS_ENABLE);
+}
+
+static inline void netwinder_lock(unsigned long *flags)
+{
+	spin_lock_irqsave(&gpio_lock, *flags);
+}
+
+static inline void netwinder_unlock(unsigned long *flags)
+{
+	spin_unlock_irqrestore(&gpio_lock, *flags);
+}
+
+static inline void netwinder_set_fan(int i)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gpio_lock, flags);
+	gpio_modify_op(GPIO_FAN, i ? GPIO_FAN : 0);
+	spin_unlock_irqrestore(&gpio_lock, flags);
+}
+
+static inline int netwinder_get_fan(void)
+{
+	if ((system_rev & 0xf000) == 0x4000)
+		return FAN_ALWAYS_ON;
+
+	return (gpio_read() & GPIO_FAN) ? FAN_ON : FAN_OFF;
+}
+
+/*
+ * End of NetWinder specifics
+ */
+
+static void ds1620_send_bits(int nr, int value)
+{
+	int i;
+
+	for (i = 0; i < nr; i++) {
+		netwinder_ds1620_set_data(value & 1);
+		netwinder_ds1620_set_clk(0);
+		udelay(1);
+		netwinder_ds1620_set_clk(1);
+		udelay(1);
+
+		value >>= 1;
+	}
+}
+
+static unsigned int ds1620_recv_bits(int nr)
+{
+	unsigned int value = 0, mask = 1;
+	int i;
+
+	netwinder_ds1620_set_data(0);
+
+	for (i = 0; i < nr; i++) {
+		netwinder_ds1620_set_clk(0);
+		udelay(1);
+
+		if (netwinder_ds1620_get_data())
+			value |= mask;
+
+		mask <<= 1;
+
+		netwinder_ds1620_set_clk(1);
+		udelay(1);
+	}
+
+	return value;
+}
+
+static void ds1620_out(int cmd, int bits, int value)
+{
+	unsigned long flags;
+
+	netwinder_lock(&flags);
+	netwinder_ds1620_set_clk(1);
+	netwinder_ds1620_set_data_dir(0);
+	netwinder_ds1620_reset();
+
+	udelay(1);
+
+	ds1620_send_bits(8, cmd);
+	if (bits)
+		ds1620_send_bits(bits, value);
+
+	udelay(1);
+
+	netwinder_ds1620_reset();
+	netwinder_unlock(&flags);
+
+	set_current_state(TASK_INTERRUPTIBLE);
+	schedule_timeout(2);
+}
+
+static unsigned int ds1620_in(int cmd, int bits)
+{
+	unsigned long flags;
+	unsigned int value;
+
+	netwinder_lock(&flags);
+	netwinder_ds1620_set_clk(1);
+	netwinder_ds1620_set_data_dir(0);
+	netwinder_ds1620_reset();
+
+	udelay(1);
+
+	ds1620_send_bits(8, cmd);
+
+	netwinder_ds1620_set_data_dir(1);
+	value = ds1620_recv_bits(bits);
+
+	netwinder_ds1620_reset();
+	netwinder_unlock(&flags);
+
+	return value;
+}
+
+static int cvt_9_to_int(unsigned int val)
+{
+	if (val & 0x100)
+		val |= 0xfffffe00;
+
+	return val;
+}
+
+static void ds1620_write_state(struct therm *therm)
+{
+	ds1620_out(THERM_WRITE_CONFIG, 8, CFG_CPU);
+	ds1620_out(THERM_WRITE_TL, 9, therm->lo);
+	ds1620_out(THERM_WRITE_TH, 9, therm->hi);
+	ds1620_out(THERM_START_CONVERT, 0, 0);
+}
+
+static void ds1620_read_state(struct therm *therm)
+{
+	therm->lo = cvt_9_to_int(ds1620_in(THERM_READ_TL, 9));
+	therm->hi = cvt_9_to_int(ds1620_in(THERM_READ_TH, 9));
+}
+
+static ssize_t
+ds1620_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
+{
+	signed int cur_temp;
+	signed char cur_temp_degF;
+
+	cur_temp = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9)) >> 1;
+
+	/* convert to Fahrenheit, as per wdt.c */
+	cur_temp_degF = (cur_temp * 9) / 5 + 32;
+
+	if (copy_to_user(buf, &cur_temp_degF, 1))
+		return -EFAULT;
+
+	return 1;
+}
+
+static int
+ds1620_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct therm therm;
+	union {
+		struct therm __user *therm;
+		int __user *i;
+	} uarg;
+	int i;
+
+	uarg.i = (int __user *)arg;
+
+	switch(cmd) {
+	case CMD_SET_THERMOSTATE:
+	case CMD_SET_THERMOSTATE2:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+
+		if (cmd == CMD_SET_THERMOSTATE) {
+			if (get_user(therm.hi, uarg.i))
+				return -EFAULT;
+			therm.lo = therm.hi - 3;
+		} else {
+			if (copy_from_user(&therm, uarg.therm, sizeof(therm)))
+				return -EFAULT;
+		}
+
+		therm.lo <<= 1;
+		therm.hi <<= 1;
+
+		ds1620_write_state(&therm);
+		break;
+
+	case CMD_GET_THERMOSTATE:
+	case CMD_GET_THERMOSTATE2:
+		ds1620_read_state(&therm);
+
+		therm.lo >>= 1;
+		therm.hi >>= 1;
+
+		if (cmd == CMD_GET_THERMOSTATE) {
+			if (put_user(therm.hi, uarg.i))
+				return -EFAULT;
+		} else {
+			if (copy_to_user(uarg.therm, &therm, sizeof(therm)))
+				return -EFAULT;
+		}
+		break;
+
+	case CMD_GET_TEMPERATURE:
+	case CMD_GET_TEMPERATURE2:
+		i = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9));
+
+		if (cmd == CMD_GET_TEMPERATURE)
+			i >>= 1;
+
+		return put_user(i, uarg.i) ? -EFAULT : 0;
+
+	case CMD_GET_STATUS:
+		i = ds1620_in(THERM_READ_CONFIG, 8) & 0xe3;
+
+		return put_user(i, uarg.i) ? -EFAULT : 0;
+
+	case CMD_GET_FAN:
+		i = netwinder_get_fan();
+
+		return put_user(i, uarg.i) ? -EFAULT : 0;
+
+	case CMD_SET_FAN:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+
+		if (get_user(i, uarg.i))
+			return -EFAULT;
+
+		netwinder_set_fan(i);
+		break;
+		
+	default:
+		return -ENOIOCTLCMD;
+	}
+
+	return 0;
+}
+
+#ifdef THERM_USE_PROC
+static int
+proc_therm_ds1620_read(char *buf, char **start, off_t offset,
+		       int len, int *eof, void *unused)
+{
+	struct therm th;
+	int temp;
+
+	ds1620_read_state(&th);
+	temp =  cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9));
+
+	len = sprintf(buf, "Thermostat: HI %i.%i, LOW %i.%i; "
+		      "temperature: %i.%i C, fan %s\n",
+		      th.hi >> 1, th.hi & 1 ? 5 : 0,
+		      th.lo >> 1, th.lo & 1 ? 5 : 0,
+		      temp  >> 1, temp  & 1 ? 5 : 0,
+		      fan_state[netwinder_get_fan()]);
+
+	return len;
+}
+
+static struct proc_dir_entry *proc_therm_ds1620;
+#endif
+
+static struct file_operations ds1620_fops = {
+	.owner		= THIS_MODULE,
+	.open		= nonseekable_open,
+	.read		= ds1620_read,
+	.ioctl		= ds1620_ioctl,
+};
+
+static struct miscdevice ds1620_miscdev = {
+	TEMP_MINOR,
+	"temp",
+	&ds1620_fops
+};
+
+static int __init ds1620_init(void)
+{
+	int ret;
+	struct therm th, th_start;
+
+	if (!machine_is_netwinder())
+		return -ENODEV;
+
+	ds1620_out(THERM_RESET, 0, 0);
+	ds1620_out(THERM_WRITE_CONFIG, 8, CFG_CPU);
+	ds1620_out(THERM_START_CONVERT, 0, 0);
+
+	/*
+	 * Trigger the fan to start by setting
+	 * temperature high point low.  This kicks
+	 * the fan into action.
+	 */
+	ds1620_read_state(&th);
+	th_start.lo = 0;
+	th_start.hi = 1;
+	ds1620_write_state(&th_start);
+
+	msleep(2000);
+
+	ds1620_write_state(&th);
+
+	ret = misc_register(&ds1620_miscdev);
+	if (ret < 0)
+		return ret;
+
+#ifdef THERM_USE_PROC
+	proc_therm_ds1620 = create_proc_entry("therm", 0, NULL);
+	if (proc_therm_ds1620)
+		proc_therm_ds1620->read_proc = proc_therm_ds1620_read;
+	else
+		printk(KERN_ERR "therm: unable to register /proc/therm\n");
+#endif
+
+	ds1620_read_state(&th);
+	ret = cvt_9_to_int(ds1620_in(THERM_READ_TEMP, 9));
+
+	printk(KERN_INFO "Thermostat: high %i.%i, low %i.%i, "
+	       "current %i.%i C, fan %s.\n",
+	       th.hi >> 1, th.hi & 1 ? 5 : 0,
+	       th.lo >> 1, th.lo & 1 ? 5 : 0,
+	       ret   >> 1, ret   & 1 ? 5 : 0,
+	       fan_state[netwinder_get_fan()]);
+
+	return 0;
+}
+
+static void __exit ds1620_exit(void)
+{
+#ifdef THERM_USE_PROC
+	remove_proc_entry("therm", NULL);
+#endif
+	misc_deregister(&ds1620_miscdev);
+}
+
+module_init(ds1620_init);
+module_exit(ds1620_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/dsp56k.c b/drivers/char/dsp56k.c
new file mode 100644
index 0000000..37d6649
--- /dev/null
+++ b/drivers/char/dsp56k.c
@@ -0,0 +1,547 @@
+/*
+ * The DSP56001 Device Driver, saviour of the Free World(tm)
+ *
+ * Authors: Fredrik Noring   <noring@nocrew.org>
+ *          lars brinkhoff   <lars@nocrew.org>
+ *          Tomas Berndtsson <tomas@nocrew.org>
+ *
+ * First version May 1996
+ *
+ * History:
+ *  97-01-29   Tomas Berndtsson,
+ *               Integrated with Linux 2.1.21 kernel sources.
+ *  97-02-15   Tomas Berndtsson,
+ *               Fixed for kernel 2.1.26
+ *
+ * BUGS:
+ *  Hmm... there must be something here :)
+ *
+ * Copyright (C) 1996,1997 Fredrik Noring, lars brinkhoff & Tomas Berndtsson
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>	/* for kmalloc() and kfree() */
+#include <linux/sched.h>	/* for struct wait_queue etc */
+#include <linux/major.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/delay.h>	/* guess what */
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+
+#include <asm/atarihw.h>
+#include <asm/traps.h>
+#include <asm/uaccess.h>	/* For put_user and get_user */
+
+#include <asm/dsp56k.h>
+
+/* minor devices */
+#define DSP56K_DEV_56001        0    /* The only device so far */
+
+#define TIMEOUT    10   /* Host port timeout in number of tries */
+#define MAXIO    2048   /* Maximum number of words before sleep */
+#define DSP56K_MAX_BINARY_LENGTH (3*64*1024)
+
+#define DSP56K_TX_INT_ON	dsp56k_host_interface.icr |=  DSP56K_ICR_TREQ
+#define DSP56K_RX_INT_ON	dsp56k_host_interface.icr |=  DSP56K_ICR_RREQ
+#define DSP56K_TX_INT_OFF	dsp56k_host_interface.icr &= ~DSP56K_ICR_TREQ
+#define DSP56K_RX_INT_OFF	dsp56k_host_interface.icr &= ~DSP56K_ICR_RREQ
+
+#define DSP56K_TRANSMIT		(dsp56k_host_interface.isr & DSP56K_ISR_TXDE)
+#define DSP56K_RECEIVE		(dsp56k_host_interface.isr & DSP56K_ISR_RXDF)
+
+#define handshake(count, maxio, timeout, ENABLE, f) \
+{ \
+	long i, t, m; \
+	while (count > 0) { \
+		m = min_t(unsigned long, count, maxio); \
+		for (i = 0; i < m; i++) { \
+			for (t = 0; t < timeout && !ENABLE; t++) \
+				msleep(20); \
+			if(!ENABLE) \
+				return -EIO; \
+			f; \
+		} \
+		count -= m; \
+		if (m == maxio) msleep(20); \
+	} \
+}
+
+#define tx_wait(n) \
+{ \
+	int t; \
+	for(t = 0; t < n && !DSP56K_TRANSMIT; t++) \
+		msleep(10); \
+	if(!DSP56K_TRANSMIT) { \
+		return -EIO; \
+	} \
+}
+
+#define rx_wait(n) \
+{ \
+	int t; \
+	for(t = 0; t < n && !DSP56K_RECEIVE; t++) \
+		msleep(10); \
+	if(!DSP56K_RECEIVE) { \
+		return -EIO; \
+	} \
+}
+
+/* DSP56001 bootstrap code */
+static char bootstrap[] = {
+	0x0c, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x60, 0xf4, 0x00, 0x00, 0x00, 0x4f, 0x61, 0xf4,
+	0x00, 0x00, 0x7e, 0xa9, 0x06, 0x2e, 0x80, 0x00, 0x00, 0x47,
+	0x07, 0xd8, 0x84, 0x07, 0x59, 0x84, 0x08, 0xf4, 0xa8, 0x00,
+	0x00, 0x04, 0x08, 0xf4, 0xbf, 0x00, 0x0c, 0x00, 0x00, 0xfe,
+	0xb8, 0x0a, 0xf0, 0x80, 0x00, 0x7e, 0xa9, 0x08, 0xf4, 0xa0,
+	0x00, 0x00, 0x01, 0x08, 0xf4, 0xbe, 0x00, 0x00, 0x00, 0x0a,
+	0xa9, 0x80, 0x00, 0x7e, 0xad, 0x08, 0x4e, 0x2b, 0x44, 0xf4,
+	0x00, 0x00, 0x00, 0x03, 0x44, 0xf4, 0x45, 0x00, 0x00, 0x01,
+	0x0e, 0xa0, 0x00, 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xb5, 0x08,
+	0x50, 0x2b, 0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xb8, 0x08, 0x46,
+	0x2b, 0x44, 0xf4, 0x45, 0x00, 0x00, 0x02, 0x0a, 0xf0, 0xaa,
+	0x00, 0x7e, 0xc9, 0x20, 0x00, 0x45, 0x0a, 0xf0, 0xaa, 0x00,
+	0x7e, 0xd0, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xc6, 0x0a, 0xa9,
+	0x80, 0x00, 0x7e, 0xc4, 0x08, 0x58, 0x6b, 0x0a, 0xf0, 0x80,
+	0x00, 0x7e, 0xad, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xcd, 0x0a,
+	0xa9, 0x80, 0x00, 0x7e, 0xcb, 0x08, 0x58, 0xab, 0x0a, 0xf0,
+	0x80, 0x00, 0x7e, 0xad, 0x06, 0xc6, 0x00, 0x00, 0x7e, 0xd4,
+	0x0a, 0xa9, 0x80, 0x00, 0x7e, 0xd2, 0x08, 0x58, 0xeb, 0x0a,
+	0xf0, 0x80, 0x00, 0x7e, 0xad};
+static int sizeof_bootstrap = 375;
+
+
+static struct dsp56k_device {
+	long in_use;
+	long maxio, timeout;
+	int tx_wsize, rx_wsize;
+} dsp56k;
+
+static struct class_simple *dsp56k_class;
+
+static int dsp56k_reset(void)
+{
+	u_char status;
+	
+	/* Power down the DSP */
+	sound_ym.rd_data_reg_sel = 14;
+	status = sound_ym.rd_data_reg_sel & 0xef;
+	sound_ym.wd_data = status;
+	sound_ym.wd_data = status | 0x10;
+  
+	udelay(10);
+  
+	/* Power up the DSP */
+	sound_ym.rd_data_reg_sel = 14;
+	sound_ym.wd_data = sound_ym.rd_data_reg_sel & 0xef;
+
+	return 0;
+}
+
+static int dsp56k_upload(u_char *bin, int len)
+{
+	int i;
+	u_char *p;
+	
+	dsp56k_reset();
+  
+	p = bootstrap;
+	for (i = 0; i < sizeof_bootstrap/3; i++) {
+		/* tx_wait(10); */
+		dsp56k_host_interface.data.b[1] = *p++;
+		dsp56k_host_interface.data.b[2] = *p++;
+		dsp56k_host_interface.data.b[3] = *p++;
+	}
+	for (; i < 512; i++) {
+		/* tx_wait(10); */
+		dsp56k_host_interface.data.b[1] = 0;
+		dsp56k_host_interface.data.b[2] = 0;
+		dsp56k_host_interface.data.b[3] = 0;
+	}
+  
+	for (i = 0; i < len; i++) {
+		tx_wait(10);
+		get_user(dsp56k_host_interface.data.b[1], bin++);
+		get_user(dsp56k_host_interface.data.b[2], bin++);
+		get_user(dsp56k_host_interface.data.b[3], bin++);
+	}
+
+	tx_wait(10);
+	dsp56k_host_interface.data.l = 3;    /* Magic execute */
+
+	return 0;
+}
+
+static ssize_t dsp56k_read(struct file *file, char *buf, size_t count,
+			   loff_t *ppos)
+{
+	struct inode *inode = file->f_dentry->d_inode;
+	int dev = iminor(inode) & 0x0f;
+
+	switch(dev)
+	{
+	case DSP56K_DEV_56001:
+	{
+
+		long n;
+
+		/* Don't do anything if nothing is to be done */
+		if (!count) return 0;
+
+		n = 0;
+		switch (dsp56k.rx_wsize) {
+		case 1:  /* 8 bit */
+		{
+			handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
+				  put_user(dsp56k_host_interface.data.b[3], buf+n++));
+			return n;
+		}
+		case 2:  /* 16 bit */
+		{
+			short *data;
+
+			count /= 2;
+			data = (short*) buf;
+			handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
+				  put_user(dsp56k_host_interface.data.w[1], data+n++));
+			return 2*n;
+		}
+		case 3:  /* 24 bit */
+		{
+			count /= 3;
+			handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
+				  put_user(dsp56k_host_interface.data.b[1], buf+n++);
+				  put_user(dsp56k_host_interface.data.b[2], buf+n++);
+				  put_user(dsp56k_host_interface.data.b[3], buf+n++));
+			return 3*n;
+		}
+		case 4:  /* 32 bit */
+		{
+			long *data;
+
+			count /= 4;
+			data = (long*) buf;
+			handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_RECEIVE,
+				  put_user(dsp56k_host_interface.data.l, data+n++));
+			return 4*n;
+		}
+		}
+		return -EFAULT;
+	}
+
+	default:
+		printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
+		return -ENXIO;
+	}
+}
+
+static ssize_t dsp56k_write(struct file *file, const char *buf, size_t count,
+			    loff_t *ppos)
+{
+	struct inode *inode = file->f_dentry->d_inode;
+	int dev = iminor(inode) & 0x0f;
+
+	switch(dev)
+	{
+	case DSP56K_DEV_56001:
+	{
+		long n;
+
+		/* Don't do anything if nothing is to be done */
+		if (!count) return 0;
+
+		n = 0;
+		switch (dsp56k.tx_wsize) {
+		case 1:  /* 8 bit */
+		{
+			handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
+				  get_user(dsp56k_host_interface.data.b[3], buf+n++));
+			return n;
+		}
+		case 2:  /* 16 bit */
+		{
+			const short *data;
+
+			count /= 2;
+			data = (const short *)buf;
+			handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
+				  get_user(dsp56k_host_interface.data.w[1], data+n++));
+			return 2*n;
+		}
+		case 3:  /* 24 bit */
+		{
+			count /= 3;
+			handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
+				  get_user(dsp56k_host_interface.data.b[1], buf+n++);
+				  get_user(dsp56k_host_interface.data.b[2], buf+n++);
+				  get_user(dsp56k_host_interface.data.b[3], buf+n++));
+			return 3*n;
+		}
+		case 4:  /* 32 bit */
+		{
+			const long *data;
+
+			count /= 4;
+			data = (const long *)buf;
+			handshake(count, dsp56k.maxio, dsp56k.timeout, DSP56K_TRANSMIT,
+				  get_user(dsp56k_host_interface.data.l, data+n++));
+			return 4*n;
+		}
+		}
+
+		return -EFAULT;
+	}
+	default:
+		printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
+		return -ENXIO;
+	}
+}
+
+static int dsp56k_ioctl(struct inode *inode, struct file *file,
+			unsigned int cmd, unsigned long arg)
+{
+	int dev = iminor(inode) & 0x0f;
+
+	switch(dev)
+	{
+	case DSP56K_DEV_56001:
+
+		switch(cmd) {
+		case DSP56K_UPLOAD:
+		{
+			char *bin;
+			int r, len;
+			struct dsp56k_upload *binary = (struct dsp56k_upload *) arg;
+    
+			if(get_user(len, &binary->len) < 0)
+				return -EFAULT;
+			if(get_user(bin, &binary->bin) < 0)
+				return -EFAULT;
+		
+			if (len == 0) {
+				return -EINVAL;      /* nothing to upload?!? */
+			}
+			if (len > DSP56K_MAX_BINARY_LENGTH) {
+				return -EINVAL;
+			}
+    
+			r = dsp56k_upload(bin, len);
+			if (r < 0) {
+				return r;
+			}
+    
+			break;
+		}
+		case DSP56K_SET_TX_WSIZE:
+			if (arg > 4 || arg < 1)
+				return -EINVAL;
+			dsp56k.tx_wsize = (int) arg;
+			break;
+		case DSP56K_SET_RX_WSIZE:
+			if (arg > 4 || arg < 1)
+				return -EINVAL;
+			dsp56k.rx_wsize = (int) arg;
+			break;
+		case DSP56K_HOST_FLAGS:
+		{
+			int dir, out, status;
+			struct dsp56k_host_flags *hf = (struct dsp56k_host_flags*) arg;
+    
+			if(get_user(dir, &hf->dir) < 0)
+				return -EFAULT;
+			if(get_user(out, &hf->out) < 0)
+				return -EFAULT;
+
+			if ((dir & 0x1) && (out & 0x1))
+				dsp56k_host_interface.icr |= DSP56K_ICR_HF0;
+			else if (dir & 0x1)
+				dsp56k_host_interface.icr &= ~DSP56K_ICR_HF0;
+			if ((dir & 0x2) && (out & 0x2))
+				dsp56k_host_interface.icr |= DSP56K_ICR_HF1;
+			else if (dir & 0x2)
+				dsp56k_host_interface.icr &= ~DSP56K_ICR_HF1;
+
+			status = 0;
+			if (dsp56k_host_interface.icr & DSP56K_ICR_HF0) status |= 0x1;
+			if (dsp56k_host_interface.icr & DSP56K_ICR_HF1) status |= 0x2;
+			if (dsp56k_host_interface.isr & DSP56K_ISR_HF2) status |= 0x4;
+			if (dsp56k_host_interface.isr & DSP56K_ISR_HF3) status |= 0x8;
+
+			return put_user(status, &hf->status);
+		}
+		case DSP56K_HOST_CMD:
+			if (arg > 31 || arg < 0)
+				return -EINVAL;
+			dsp56k_host_interface.cvr = (u_char)((arg & DSP56K_CVR_HV_MASK) |
+							     DSP56K_CVR_HC);
+			break;
+		default:
+			return -EINVAL;
+		}
+		return 0;
+
+	default:
+		printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
+		return -ENXIO;
+	}
+}
+
+/* As of 2.1.26 this should be dsp56k_poll,
+ * but how do I then check device minor number?
+ * Do I need this function at all???
+ */
+#if 0
+static unsigned int dsp56k_poll(struct file *file, poll_table *wait)
+{
+	int dev = iminor(file->f_dentry->d_inode) & 0x0f;
+
+	switch(dev)
+	{
+	case DSP56K_DEV_56001:
+		/* poll_wait(file, ???, wait); */
+		return POLLIN | POLLRDNORM | POLLOUT;
+
+	default:
+		printk("DSP56k driver: Unknown minor device: %d\n", dev);
+		return 0;
+	}
+}
+#endif
+
+static int dsp56k_open(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode) & 0x0f;
+
+	switch(dev)
+	{
+	case DSP56K_DEV_56001:
+
+		if (test_and_set_bit(0, &dsp56k.in_use))
+			return -EBUSY;
+
+		dsp56k.timeout = TIMEOUT;
+		dsp56k.maxio = MAXIO;
+		dsp56k.rx_wsize = dsp56k.tx_wsize = 4; 
+
+		DSP56K_TX_INT_OFF;
+		DSP56K_RX_INT_OFF;
+
+		/* Zero host flags */
+		dsp56k_host_interface.icr &= ~DSP56K_ICR_HF0;
+		dsp56k_host_interface.icr &= ~DSP56K_ICR_HF1;
+
+		break;
+
+	default:
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int dsp56k_release(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode) & 0x0f;
+
+	switch(dev)
+	{
+	case DSP56K_DEV_56001:
+		clear_bit(0, &dsp56k.in_use);
+		break;
+	default:
+		printk(KERN_ERR "DSP56k driver: Unknown minor device: %d\n", dev);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static struct file_operations dsp56k_fops = {
+	.owner		= THIS_MODULE,
+	.read		= dsp56k_read,
+	.write		= dsp56k_write,
+	.ioctl		= dsp56k_ioctl,
+	.open		= dsp56k_open,
+	.release	= dsp56k_release,
+};
+
+
+/****** Init and module functions ******/
+
+static char banner[] __initdata = KERN_INFO "DSP56k driver installed\n";
+
+static int __init dsp56k_init_driver(void)
+{
+	int err = 0;
+
+	if(!MACH_IS_ATARI || !ATARIHW_PRESENT(DSP56K)) {
+		printk("DSP56k driver: Hardware not present\n");
+		return -ENODEV;
+	}
+
+	if(register_chrdev(DSP56K_MAJOR, "dsp56k", &dsp56k_fops)) {
+		printk("DSP56k driver: Unable to register driver\n");
+		return -ENODEV;
+	}
+	dsp56k_class = class_simple_create(THIS_MODULE, "dsp56k");
+	if (IS_ERR(dsp56k_class)) {
+		err = PTR_ERR(dsp56k_class);
+		goto out_chrdev;
+	}
+	class_simple_device_add(dsp56k_class, MKDEV(DSP56K_MAJOR, 0), NULL, "dsp56k");
+
+	err = devfs_mk_cdev(MKDEV(DSP56K_MAJOR, 0),
+		      S_IFCHR | S_IRUSR | S_IWUSR, "dsp56k");
+	if(err)
+		goto out_class;
+
+	printk(banner);
+	goto out;
+
+out_class:
+	class_simple_device_remove(MKDEV(DSP56K_MAJOR, 0));
+	class_simple_destroy(dsp56k_class);
+out_chrdev:
+	unregister_chrdev(DSP56K_MAJOR, "dsp56k");
+out:
+	return err;
+}
+module_init(dsp56k_init_driver);
+
+static void __exit dsp56k_cleanup_driver(void)
+{
+	class_simple_device_remove(MKDEV(DSP56K_MAJOR, 0));
+	class_simple_destroy(dsp56k_class);
+	unregister_chrdev(DSP56K_MAJOR, "dsp56k");
+	devfs_remove("dsp56k");
+}
+module_exit(dsp56k_cleanup_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/dtlk.c b/drivers/char/dtlk.c
new file mode 100644
index 0000000..903e4c3
--- /dev/null
+++ b/drivers/char/dtlk.c
@@ -0,0 +1,659 @@
+/*                                              -*- linux-c -*-
+ * dtlk.c - DoubleTalk PC driver for Linux
+ *
+ * Original author: Chris Pallotta <chris@allmedia.com>
+ * Current maintainer: Jim Van Zandt <jrv@vanzandt.mv.com>
+ * 
+ * 2000-03-18 Jim Van Zandt: Fix polling.
+ *  Eliminate dtlk_timer_active flag and separate dtlk_stop_timer
+ *  function.  Don't restart timer in dtlk_timer_tick.  Restart timer
+ *  in dtlk_poll after every poll.  dtlk_poll returns mask (duh).
+ *  Eliminate unused function dtlk_write_byte.  Misc. code cleanups.
+ */
+
+/* This driver is for the DoubleTalk PC, a speech synthesizer
+   manufactured by RC Systems (http://www.rcsys.com/).  It was written
+   based on documentation in their User's Manual file and Developer's
+   Tools disk.
+
+   The DoubleTalk PC contains four voice synthesizers: text-to-speech
+   (TTS), linear predictive coding (LPC), PCM/ADPCM, and CVSD.  It
+   also has a tone generator.  Output data for LPC are written to the
+   LPC port, and output data for the other modes are written to the
+   TTS port.
+
+   Two kinds of data can be read from the DoubleTalk: status
+   information (in response to the "\001?" interrogation command) is
+   read from the TTS port, and index markers (which mark the progress
+   of the speech) are read from the LPC port.  Not all models of the
+   DoubleTalk PC implement index markers.  Both the TTS and LPC ports
+   can also display status flags.
+
+   The DoubleTalk PC generates no interrupts.
+
+   These characteristics are mapped into the Unix stream I/O model as
+   follows:
+
+   "write" sends bytes to the TTS port.  It is the responsibility of
+   the user program to switch modes among TTS, PCM/ADPCM, and CVSD.
+   This driver was written for use with the text-to-speech
+   synthesizer.  If LPC output is needed some day, other minor device
+   numbers can be used to select among output modes.
+
+   "read" gets index markers from the LPC port.  If the device does
+   not implement index markers, the read will fail with error EINVAL.
+
+   Status information is available using the DTLK_INTERROGATE ioctl.
+
+ */
+
+#include <linux/module.h>
+
+#define KERNEL
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/mm.h>		/* for verify_area */
+#include <linux/errno.h>	/* for -EBUSY */
+#include <linux/ioport.h>	/* for request_region */
+#include <linux/delay.h>	/* for loops_per_jiffy */
+#include <asm/io.h>		/* for inb_p, outb_p, inb, outb, etc. */
+#include <asm/uaccess.h>	/* for get_user, etc. */
+#include <linux/wait.h>		/* for wait_queue */
+#include <linux/init.h>		/* for __init, module_{init,exit} */
+#include <linux/poll.h>		/* for POLLIN, etc. */
+#include <linux/dtlk.h>		/* local header file for DoubleTalk values */
+#include <linux/devfs_fs_kernel.h>
+#include <linux/smp_lock.h>
+
+#ifdef TRACING
+#define TRACE_TEXT(str) printk(str);
+#define TRACE_RET printk(")")
+#else				/* !TRACING */
+#define TRACE_TEXT(str) ((void) 0)
+#define TRACE_RET ((void) 0)
+#endif				/* TRACING */
+
+
+static int dtlk_major;
+static int dtlk_port_lpc;
+static int dtlk_port_tts;
+static int dtlk_busy;
+static int dtlk_has_indexing;
+static unsigned int dtlk_portlist[] =
+{0x25e, 0x29e, 0x2de, 0x31e, 0x35e, 0x39e, 0};
+static wait_queue_head_t dtlk_process_list;
+static struct timer_list dtlk_timer;
+
+/* prototypes for file_operations struct */
+static ssize_t dtlk_read(struct file *, char __user *,
+			 size_t nbytes, loff_t * ppos);
+static ssize_t dtlk_write(struct file *, const char __user *,
+			  size_t nbytes, loff_t * ppos);
+static unsigned int dtlk_poll(struct file *, poll_table *);
+static int dtlk_open(struct inode *, struct file *);
+static int dtlk_release(struct inode *, struct file *);
+static int dtlk_ioctl(struct inode *inode, struct file *file,
+		      unsigned int cmd, unsigned long arg);
+
+static struct file_operations dtlk_fops =
+{
+	.owner		= THIS_MODULE,
+	.read		= dtlk_read,
+	.write		= dtlk_write,
+	.poll		= dtlk_poll,
+	.ioctl		= dtlk_ioctl,
+	.open		= dtlk_open,
+	.release	= dtlk_release,
+};
+
+/* local prototypes */
+static int dtlk_dev_probe(void);
+static struct dtlk_settings *dtlk_interrogate(void);
+static int dtlk_readable(void);
+static char dtlk_read_lpc(void);
+static char dtlk_read_tts(void);
+static int dtlk_writeable(void);
+static char dtlk_write_bytes(const char *buf, int n);
+static char dtlk_write_tts(char);
+/*
+   static void dtlk_handle_error(char, char, unsigned int);
+ */
+static void dtlk_timer_tick(unsigned long data);
+
+static ssize_t dtlk_read(struct file *file, char __user *buf,
+			 size_t count, loff_t * ppos)
+{
+	unsigned int minor = iminor(file->f_dentry->d_inode);
+	char ch;
+	int i = 0, retries;
+
+	TRACE_TEXT("(dtlk_read");
+	/*  printk("DoubleTalk PC - dtlk_read()\n"); */
+
+	if (minor != DTLK_MINOR || !dtlk_has_indexing)
+		return -EINVAL;
+
+	for (retries = 0; retries < loops_per_jiffy; retries++) {
+		while (i < count && dtlk_readable()) {
+			ch = dtlk_read_lpc();
+			/*        printk("dtlk_read() reads 0x%02x\n", ch); */
+			if (put_user(ch, buf++))
+				return -EFAULT;
+			i++;
+		}
+		if (i)
+			return i;
+		if (file->f_flags & O_NONBLOCK)
+			break;
+		msleep_interruptible(100);
+	}
+	if (retries == loops_per_jiffy)
+		printk(KERN_ERR "dtlk_read times out\n");
+	TRACE_RET;
+	return -EAGAIN;
+}
+
+static ssize_t dtlk_write(struct file *file, const char __user *buf,
+			  size_t count, loff_t * ppos)
+{
+	int i = 0, retries = 0, ch;
+
+	TRACE_TEXT("(dtlk_write");
+#ifdef TRACING
+	printk(" \"");
+	{
+		int i, ch;
+		for (i = 0; i < count; i++) {
+			if (get_user(ch, buf + i))
+				return -EFAULT;
+			if (' ' <= ch && ch <= '~')
+				printk("%c", ch);
+			else
+				printk("\\%03o", ch);
+		}
+		printk("\"");
+	}
+#endif
+
+	if (iminor(file->f_dentry->d_inode) != DTLK_MINOR)
+		return -EINVAL;
+
+	while (1) {
+		while (i < count && !get_user(ch, buf) &&
+		       (ch == DTLK_CLEAR || dtlk_writeable())) {
+			dtlk_write_tts(ch);
+			buf++;
+			i++;
+			if (i % 5 == 0)
+				/* We yield our time until scheduled
+				   again.  This reduces the transfer
+				   rate to 500 bytes/sec, but that's
+				   still enough to keep up with the
+				   speech synthesizer. */
+				msleep_interruptible(1);
+			else {
+				/* the RDY bit goes zero 2-3 usec
+				   after writing, and goes 1 again
+				   180-190 usec later.  Here, we wait
+				   up to 250 usec for the RDY bit to
+				   go nonzero. */
+				for (retries = 0;
+				     retries < loops_per_jiffy / (4000/HZ);
+				     retries++)
+					if (inb_p(dtlk_port_tts) &
+					    TTS_WRITABLE)
+						break;
+			}
+			retries = 0;
+		}
+		if (i == count)
+			return i;
+		if (file->f_flags & O_NONBLOCK)
+			break;
+
+		msleep_interruptible(1);
+
+		if (++retries > 10 * HZ) { /* wait no more than 10 sec
+					      from last write */
+			printk("dtlk: write timeout.  "
+			       "inb_p(dtlk_port_tts) = 0x%02x\n",
+			       inb_p(dtlk_port_tts));
+			TRACE_RET;
+			return -EBUSY;
+		}
+	}
+	TRACE_RET;
+	return -EAGAIN;
+}
+
+static unsigned int dtlk_poll(struct file *file, poll_table * wait)
+{
+	int mask = 0;
+	unsigned long expires;
+
+	TRACE_TEXT(" dtlk_poll");
+	/*
+	   static long int j;
+	   printk(".");
+	   printk("<%ld>", jiffies-j);
+	   j=jiffies;
+	 */
+	poll_wait(file, &dtlk_process_list, wait);
+
+	if (dtlk_has_indexing && dtlk_readable()) {
+	        del_timer(&dtlk_timer);
+		mask = POLLIN | POLLRDNORM;
+	}
+	if (dtlk_writeable()) {
+	        del_timer(&dtlk_timer);
+		mask |= POLLOUT | POLLWRNORM;
+	}
+	/* there are no exception conditions */
+
+	/* There won't be any interrupts, so we set a timer instead. */
+	expires = jiffies + 3*HZ / 100;
+	mod_timer(&dtlk_timer, expires);
+
+	return mask;
+}
+
+static void dtlk_timer_tick(unsigned long data)
+{
+	TRACE_TEXT(" dtlk_timer_tick");
+	wake_up_interruptible(&dtlk_process_list);
+}
+
+static int dtlk_ioctl(struct inode *inode,
+		      struct file *file,
+		      unsigned int cmd,
+		      unsigned long arg)
+{
+	char __user *argp = (char __user *)arg;
+	struct dtlk_settings *sp;
+	char portval;
+	TRACE_TEXT(" dtlk_ioctl");
+
+	switch (cmd) {
+
+	case DTLK_INTERROGATE:
+		sp = dtlk_interrogate();
+		if (copy_to_user(argp, sp, sizeof(struct dtlk_settings)))
+			return -EINVAL;
+		return 0;
+
+	case DTLK_STATUS:
+		portval = inb_p(dtlk_port_tts);
+		return put_user(portval, argp);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dtlk_open(struct inode *inode, struct file *file)
+{
+	TRACE_TEXT("(dtlk_open");
+
+	nonseekable_open(inode, file);
+	switch (iminor(inode)) {
+	case DTLK_MINOR:
+		if (dtlk_busy)
+			return -EBUSY;
+		return nonseekable_open(inode, file);
+
+	default:
+		return -ENXIO;
+	}
+}
+
+static int dtlk_release(struct inode *inode, struct file *file)
+{
+	TRACE_TEXT("(dtlk_release");
+
+	switch (iminor(inode)) {
+	case DTLK_MINOR:
+		break;
+
+	default:
+		break;
+	}
+	TRACE_RET;
+	
+	del_timer(&dtlk_timer);
+
+	return 0;
+}
+
+static int __init dtlk_init(void)
+{
+	dtlk_port_lpc = 0;
+	dtlk_port_tts = 0;
+	dtlk_busy = 0;
+	dtlk_major = register_chrdev(0, "dtlk", &dtlk_fops);
+	if (dtlk_major == 0) {
+		printk(KERN_ERR "DoubleTalk PC - cannot register device\n");
+		return 0;
+	}
+	if (dtlk_dev_probe() == 0)
+		printk(", MAJOR %d\n", dtlk_major);
+
+	devfs_mk_cdev(MKDEV(dtlk_major, DTLK_MINOR),
+		       S_IFCHR | S_IRUSR | S_IWUSR, "dtlk");
+
+	init_timer(&dtlk_timer);
+	dtlk_timer.function = dtlk_timer_tick;
+	init_waitqueue_head(&dtlk_process_list);
+
+	return 0;
+}
+
+static void __exit dtlk_cleanup (void)
+{
+	dtlk_write_bytes("goodbye", 8);
+	msleep_interruptible(500);		/* nap 0.50 sec but
+						   could be awakened
+						   earlier by
+						   signals... */
+
+	dtlk_write_tts(DTLK_CLEAR);
+	unregister_chrdev(dtlk_major, "dtlk");
+	devfs_remove("dtlk");
+	release_region(dtlk_port_lpc, DTLK_IO_EXTENT);
+}
+
+module_init(dtlk_init);
+module_exit(dtlk_cleanup);
+
+/* ------------------------------------------------------------------------ */
+
+static int dtlk_readable(void)
+{
+#ifdef TRACING
+	printk(" dtlk_readable=%u@%u", inb_p(dtlk_port_lpc) != 0x7f, jiffies);
+#endif
+	return inb_p(dtlk_port_lpc) != 0x7f;
+}
+
+static int dtlk_writeable(void)
+{
+	/* TRACE_TEXT(" dtlk_writeable"); */
+#ifdef TRACINGMORE
+	printk(" dtlk_writeable=%u", (inb_p(dtlk_port_tts) & TTS_WRITABLE)!=0);
+#endif
+	return inb_p(dtlk_port_tts) & TTS_WRITABLE;
+}
+
+static int __init dtlk_dev_probe(void)
+{
+	unsigned int testval = 0;
+	int i = 0;
+	struct dtlk_settings *sp;
+
+	if (dtlk_port_lpc | dtlk_port_tts)
+		return -EBUSY;
+
+	for (i = 0; dtlk_portlist[i]; i++) {
+#if 0
+		printk("DoubleTalk PC - Port %03x = %04x\n",
+		       dtlk_portlist[i], (testval = inw_p(dtlk_portlist[i])));
+#endif
+
+		if (!request_region(dtlk_portlist[i], DTLK_IO_EXTENT, 
+			       "dtlk"))
+			continue;
+		testval = inw_p(dtlk_portlist[i]);
+		if ((testval &= 0xfbff) == 0x107f) {
+			dtlk_port_lpc = dtlk_portlist[i];
+			dtlk_port_tts = dtlk_port_lpc + 1;
+
+			sp = dtlk_interrogate();
+			printk("DoubleTalk PC at %03x-%03x, "
+			       "ROM version %s, serial number %u",
+			       dtlk_portlist[i], dtlk_portlist[i] +
+			       DTLK_IO_EXTENT - 1,
+			       sp->rom_version, sp->serial_number);
+
+                        /* put LPC port into known state, so
+			   dtlk_readable() gives valid result */
+			outb_p(0xff, dtlk_port_lpc); 
+
+                        /* INIT string and index marker */
+			dtlk_write_bytes("\036\1@\0\0012I\r", 8);
+			/* posting an index takes 18 msec.  Here, we
+			   wait up to 100 msec to see whether it
+			   appears. */
+			msleep_interruptible(100);
+			dtlk_has_indexing = dtlk_readable();
+#ifdef TRACING
+			printk(", indexing %d\n", dtlk_has_indexing);
+#endif
+#ifdef INSCOPE
+			{
+/* This macro records ten samples read from the LPC port, for later display */
+#define LOOK					\
+for (i = 0; i < 10; i++)			\
+  {						\
+    buffer[b++] = inb_p(dtlk_port_lpc);		\
+    __delay(loops_per_jiffy/(1000000/HZ));             \
+  }
+				char buffer[1000];
+				int b = 0, i, j;
+
+				LOOK
+				outb_p(0xff, dtlk_port_lpc);
+				buffer[b++] = 0;
+				LOOK
+				dtlk_write_bytes("\0012I\r", 4);
+				buffer[b++] = 0;
+				__delay(50 * loops_per_jiffy / (1000/HZ));
+				outb_p(0xff, dtlk_port_lpc);
+				buffer[b++] = 0;
+				LOOK
+
+				printk("\n");
+				for (j = 0; j < b; j++)
+					printk(" %02x", buffer[j]);
+				printk("\n");
+			}
+#endif				/* INSCOPE */
+
+#ifdef OUTSCOPE
+			{
+/* This macro records ten samples read from the TTS port, for later display */
+#define LOOK					\
+for (i = 0; i < 10; i++)			\
+  {						\
+    buffer[b++] = inb_p(dtlk_port_tts);		\
+    __delay(loops_per_jiffy/(1000000/HZ));  /* 1 us */ \
+  }
+				char buffer[1000];
+				int b = 0, i, j;
+
+				mdelay(10);	/* 10 ms */
+				LOOK
+				outb_p(0x03, dtlk_port_tts);
+				buffer[b++] = 0;
+				LOOK
+				LOOK
+
+				printk("\n");
+				for (j = 0; j < b; j++)
+					printk(" %02x", buffer[j]);
+				printk("\n");
+			}
+#endif				/* OUTSCOPE */
+
+			dtlk_write_bytes("Double Talk found", 18);
+
+			return 0;
+		}
+		release_region(dtlk_portlist[i], DTLK_IO_EXTENT);
+	}
+
+	printk(KERN_INFO "\nDoubleTalk PC - not found\n");
+	return -ENODEV;
+}
+
+/*
+   static void dtlk_handle_error(char op, char rc, unsigned int minor)
+   {
+   printk(KERN_INFO"\nDoubleTalk PC - MINOR: %d, OPCODE: %d, ERROR: %d\n", 
+   minor, op, rc);
+   return;
+   }
+ */
+
+/* interrogate the DoubleTalk PC and return its settings */
+static struct dtlk_settings *dtlk_interrogate(void)
+{
+	unsigned char *t;
+	static char buf[sizeof(struct dtlk_settings) + 1];
+	int total, i;
+	static struct dtlk_settings status;
+	TRACE_TEXT("(dtlk_interrogate");
+	dtlk_write_bytes("\030\001?", 3);
+	for (total = 0, i = 0; i < 50; i++) {
+		buf[total] = dtlk_read_tts();
+		if (total > 2 && buf[total] == 0x7f)
+			break;
+		if (total < sizeof(struct dtlk_settings))
+			total++;
+	}
+	/*
+	   if (i==50) printk("interrogate() read overrun\n");
+	   for (i=0; i<sizeof(buf); i++)
+	   printk(" %02x", buf[i]);
+	   printk("\n");
+	 */
+	t = buf;
+	status.serial_number = t[0] + t[1] * 256; /* serial number is
+						     little endian */
+	t += 2;
+
+	i = 0;
+	while (*t != '\r') {
+		status.rom_version[i] = *t;
+		if (i < sizeof(status.rom_version) - 1)
+			i++;
+		t++;
+	}
+	status.rom_version[i] = 0;
+	t++;
+
+	status.mode = *t++;
+	status.punc_level = *t++;
+	status.formant_freq = *t++;
+	status.pitch = *t++;
+	status.speed = *t++;
+	status.volume = *t++;
+	status.tone = *t++;
+	status.expression = *t++;
+	status.ext_dict_loaded = *t++;
+	status.ext_dict_status = *t++;
+	status.free_ram = *t++;
+	status.articulation = *t++;
+	status.reverb = *t++;
+	status.eob = *t++;
+	status.has_indexing = dtlk_has_indexing;
+	TRACE_RET;
+	return &status;
+}
+
+static char dtlk_read_tts(void)
+{
+	int portval, retries = 0;
+	char ch;
+	TRACE_TEXT("(dtlk_read_tts");
+
+	/* verify DT is ready, read char, wait for ACK */
+	do {
+		portval = inb_p(dtlk_port_tts);
+	} while ((portval & TTS_READABLE) == 0 &&
+		 retries++ < DTLK_MAX_RETRIES);
+	if (retries == DTLK_MAX_RETRIES)
+		printk(KERN_ERR "dtlk_read_tts() timeout\n");
+
+	ch = inb_p(dtlk_port_tts);	/* input from TTS port */
+	ch &= 0x7f;
+	outb_p(ch, dtlk_port_tts);
+
+	retries = 0;
+	do {
+		portval = inb_p(dtlk_port_tts);
+	} while ((portval & TTS_READABLE) != 0 &&
+		 retries++ < DTLK_MAX_RETRIES);
+	if (retries == DTLK_MAX_RETRIES)
+		printk(KERN_ERR "dtlk_read_tts() timeout\n");
+
+	TRACE_RET;
+	return ch;
+}
+
+static char dtlk_read_lpc(void)
+{
+	int retries = 0;
+	char ch;
+	TRACE_TEXT("(dtlk_read_lpc");
+
+	/* no need to test -- this is only called when the port is readable */
+
+	ch = inb_p(dtlk_port_lpc);	/* input from LPC port */
+
+	outb_p(0xff, dtlk_port_lpc);
+
+	/* acknowledging a read takes 3-4
+	   usec.  Here, we wait up to 20 usec
+	   for the acknowledgement */
+	retries = (loops_per_jiffy * 20) / (1000000/HZ);
+	while (inb_p(dtlk_port_lpc) != 0x7f && --retries > 0);
+	if (retries == 0)
+		printk(KERN_ERR "dtlk_read_lpc() timeout\n");
+
+	TRACE_RET;
+	return ch;
+}
+
+/* write n bytes to tts port */
+static char dtlk_write_bytes(const char *buf, int n)
+{
+	char val = 0;
+	/*  printk("dtlk_write_bytes(\"%-*s\", %d)\n", n, buf, n); */
+	TRACE_TEXT("(dtlk_write_bytes");
+	while (n-- > 0)
+		val = dtlk_write_tts(*buf++);
+	TRACE_RET;
+	return val;
+}
+
+static char dtlk_write_tts(char ch)
+{
+	int retries = 0;
+#ifdef TRACINGMORE
+	printk("  dtlk_write_tts(");
+	if (' ' <= ch && ch <= '~')
+		printk("'%c'", ch);
+	else
+		printk("0x%02x", ch);
+#endif
+	if (ch != DTLK_CLEAR)	/* no flow control for CLEAR command */
+		while ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0 &&
+		       retries++ < DTLK_MAX_RETRIES)	/* DT ready? */
+			;
+	if (retries == DTLK_MAX_RETRIES)
+		printk(KERN_ERR "dtlk_write_tts() timeout\n");
+
+	outb_p(ch, dtlk_port_tts);	/* output to TTS port */
+	/* the RDY bit goes zero 2-3 usec after writing, and goes
+	   1 again 180-190 usec later.  Here, we wait up to 10
+	   usec for the RDY bit to go zero. */
+	for (retries = 0; retries < loops_per_jiffy / (100000/HZ); retries++)
+		if ((inb_p(dtlk_port_tts) & TTS_WRITABLE) == 0)
+			break;
+
+#ifdef TRACINGMORE
+	printk(")\n");
+#endif
+	return 0;
+}
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ec3104_keyb.c b/drivers/char/ec3104_keyb.c
new file mode 100644
index 0000000..4aed669
--- /dev/null
+++ b/drivers/char/ec3104_keyb.c
@@ -0,0 +1,459 @@
+/*
+ * linux/drivers/char/ec3104_keyb.c
+ * 
+ * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org>
+ *
+ * based on linux/drivers/char/pc_keyb.c, which had the following comments:
+ *
+ * Separation of the PC low-level part by Geert Uytterhoeven, May 1997
+ * See keyboard.c for the whole history.
+ *
+ * Major cleanup by Martin Mares, May 1997
+ *
+ * Combined the keyboard and PS/2 mouse handling into one file,
+ * because they share the same hardware.
+ * Johan Myreen <jem@iki.fi> 1998-10-08.
+ *
+ * Code fixes to handle mouse ACKs properly.
+ * C. Scott Ananian <cananian@alumni.princeton.edu> 1999-01-29.
+ */
+/* EC3104 note:
+ * This code was written without any documentation about the EC3104 chip.  While
+ * I hope I got most of the basic functionality right, the register names I use
+ * are most likely completely different from those in the chip documentation.
+ *
+ * If you have any further information about the EC3104, please tell me
+ * (prumpf@tux.org).
+ */
+
+#include <linux/config.h>
+
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/kbd_ll.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/kbd_kern.h>
+#include <linux/smp_lock.h>
+#include <linux/bitops.h>
+
+#include <asm/keyboard.h>
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/ec3104.h>
+
+#include <asm/io.h>
+
+/* Some configuration switches are present in the include file... */
+
+#include <linux/pc_keyb.h>
+
+#define MSR_CTS 0x10
+#define MCR_RTS 0x02
+#define LSR_DR 0x01
+#define LSR_BOTH_EMPTY 0x60
+
+static struct e5_struct {
+	u8 packet[8];
+	int pos;
+	int length;
+
+	u8 cached_mcr;
+	u8 last_msr;
+} ec3104_keyb;
+	
+/* Simple translation table for the SysRq keys */
+
+
+#ifdef CONFIG_MAGIC_SYSRQ
+unsigned char ec3104_kbd_sysrq_xlate[128] =
+	"\000\0331234567890-=\177\t"			/* 0x00 - 0x0f */
+	"qwertyuiop[]\r\000as"				/* 0x10 - 0x1f */
+	"dfghjkl;'`\000\\zxcv"				/* 0x20 - 0x2f */
+	"bnm,./\000*\000 \000\201\202\203\204\205"	/* 0x30 - 0x3f */
+	"\206\207\210\211\212\000\000789-456+1"		/* 0x40 - 0x4f */
+	"230\177\000\000\213\214\000\000\000\000\000\000\000\000\000\000" /* 0x50 - 0x5f */
+	"\r\000/";					/* 0x60 - 0x6f */
+#endif
+
+static void kbd_write_command_w(int data);
+static void kbd_write_output_w(int data);
+#ifdef CONFIG_PSMOUSE
+static void aux_write_ack(int val);
+static void __aux_write_ack(int val);
+#endif
+
+static DEFINE_SPINLOCK(kbd_controller_lock);
+static unsigned char handle_kbd_event(void);
+
+/* used only by send_data - set by keyboard_interrupt */
+static volatile unsigned char reply_expected;
+static volatile unsigned char acknowledge;
+static volatile unsigned char resend;
+
+
+int ec3104_kbd_setkeycode(unsigned int scancode, unsigned int keycode)
+{
+	return 0;
+}
+
+int ec3104_kbd_getkeycode(unsigned int scancode)
+{
+	return 0;
+}
+
+
+/* yes, it probably would be faster to use an array.  I don't care. */
+
+static inline unsigned char ec3104_scan2key(unsigned char scancode)
+{
+	switch (scancode) {
+	case  1: /* '`' */
+		return 41;
+		
+	case  2 ... 27:
+		return scancode;
+		
+	case 28: /* '\\' */
+		return 43;
+
+	case 29 ... 39:
+		return scancode + 1;
+
+	case 40: /* '\r' */
+		return 28;
+
+	case 41 ... 50:
+		return scancode + 3;
+
+	case 51: /* ' ' */
+		return 57;
+		
+	case 52: /* escape */
+		return 1;
+
+	case 54: /* insert/delete (labelled delete) */
+		/* this should arguably be 110, but I'd like to have ctrl-alt-del
+		 * working with a standard keymap */
+		return 111;
+
+	case 55: /* left */
+		return 105;
+	case 56: /* home */
+		return 102;
+	case 57: /* end */
+		return 107;
+	case 58: /* up */
+		return 103;
+	case 59: /* down */
+		return 108;
+	case 60: /* pgup */
+		return 104;
+	case 61: /* pgdown */
+		return 109;
+	case 62: /* right */
+		return 106;
+
+	case 79 ... 88: /* f1 - f10 */
+		return scancode - 20;
+
+	case 89 ... 90: /* f11 - f12 */
+		return scancode - 2;
+
+	case 91: /* left shift */
+		return 42;
+
+	case 92: /* right shift */
+		return 54;
+
+	case 93: /* left alt */
+		return 56;
+	case 94: /* right alt */
+		return 100;
+	case 95: /* left ctrl */
+		return 29;
+	case 96: /* right ctrl */
+		return 97;
+
+	case 97: /* caps lock */
+		return 58;
+	case 102: /* left windows */
+		return 125;
+	case 103: /* right windows */
+		return 126;
+
+	case 106: /* Fn */
+		/* this is wrong. */
+		return 84;
+
+	default:
+		return 0;
+	}
+}
+		
+int ec3104_kbd_translate(unsigned char scancode, unsigned char *keycode,
+		    char raw_mode)
+{
+	scancode &= 0x7f;
+
+	*keycode = ec3104_scan2key(scancode);
+
+ 	return 1;
+}
+
+char ec3104_kbd_unexpected_up(unsigned char keycode)
+{
+	return 0200;
+}
+
+static inline void handle_keyboard_event(unsigned char scancode)
+{
+#ifdef CONFIG_VT
+	handle_scancode(scancode, !(scancode & 0x80));
+#endif				
+	tasklet_schedule(&keyboard_tasklet);
+}	
+
+void ec3104_kbd_leds(unsigned char leds)
+{
+}
+
+static u8 e5_checksum(u8 *packet, int count)
+{
+	int i;
+	u8 sum = 0;
+
+	for (i=0; i<count; i++)
+		sum ^= packet[i];
+		
+	if (sum & 0x80)
+		sum ^= 0xc0;
+
+	return sum;
+}
+
+static void e5_wait_for_cts(struct e5_struct *k)
+{
+	u8 msr;
+		
+	do {
+		msr = ctrl_inb(EC3104_SER4_MSR);
+	} while (!(msr & MSR_CTS));
+}
+
+
+static void e5_send_byte(u8 byte, struct e5_struct *k)
+{
+	u8 status;
+		
+	do {
+		status = ctrl_inb(EC3104_SER4_LSR);
+	} while ((status & LSR_BOTH_EMPTY) != LSR_BOTH_EMPTY);
+	
+	printk("<%02x>", byte);
+
+	ctrl_outb(byte, EC3104_SER4_DATA);
+
+	do {
+		status = ctrl_inb(EC3104_SER4_LSR);
+	} while ((status & LSR_BOTH_EMPTY) != LSR_BOTH_EMPTY);
+	
+}
+
+static int e5_send_packet(u8 *packet, int count, struct e5_struct *k)
+{
+	int i;
+
+	disable_irq(EC3104_IRQ_SER4);
+	
+	if (k->cached_mcr & MCR_RTS) {
+		printk("e5_send_packet: too slow\n");
+		enable_irq(EC3104_IRQ_SER4);
+		return -EAGAIN;
+	}
+
+	k->cached_mcr |= MCR_RTS;
+	ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+
+	e5_wait_for_cts(k);
+
+	printk("p: ");
+
+	for(i=0; i<count; i++)
+		e5_send_byte(packet[i], k);
+
+	e5_send_byte(e5_checksum(packet, count), k);
+
+	printk("\n");
+
+	udelay(1500);
+
+	k->cached_mcr &= ~MCR_RTS;
+	ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+
+	set_current_state(TASK_UNINTERRUPTIBLE);
+	
+	
+
+	enable_irq(EC3104_IRQ_SER4);
+
+	
+
+	return 0;
+}
+
+/*
+ * E5 packets we know about:
+ * E5->host 0x80 0x05 <checksum> - resend packet
+ * host->E5 0x83 0x43 <contrast> - set LCD contrast
+ * host->E5 0x85 0x41 0x02 <brightness> 0x02 - set LCD backlight
+ * E5->host 0x87 <ps2 packet> 0x00 <checksum> - external PS2 
+ * E5->host 0x88 <scancode> <checksum> - key press
+ */
+
+static void e5_receive(struct e5_struct *k)
+{
+	k->packet[k->pos++] = ctrl_inb(EC3104_SER4_DATA);
+
+	if (k->pos == 1) {
+		switch(k->packet[0]) {
+		case 0x80:
+			k->length = 3;
+			break;
+			
+		case 0x87: /* PS2 ext */
+			k->length = 6;
+			break;
+
+		case 0x88: /* keyboard */
+			k->length = 3;
+			break;
+
+		default:
+			k->length = 1;
+			printk(KERN_WARNING "unknown E5 packet %02x\n",
+			       k->packet[0]);
+		}
+	}
+
+	if (k->pos == k->length) {
+		int i;
+
+		if (e5_checksum(k->packet, k->length) != 0)
+			printk(KERN_WARNING "E5: wrong checksum\n");
+
+#if 0
+		printk("E5 packet [");
+		for(i=0; i<k->length; i++) {
+			printk("%02x ", k->packet[i]);
+		}
+
+		printk("(%02x)]\n", e5_checksum(k->packet, k->length-1));
+#endif
+
+		switch(k->packet[0]) {
+		case 0x80:
+		case 0x88:
+			handle_keyboard_event(k->packet[1]);
+			break;
+		}
+
+		k->pos = k->length = 0;
+	}
+}
+
+static void ec3104_keyb_interrupt(int irq, void *data, struct pt_regs *regs)
+{
+	struct e5_struct *k = &ec3104_keyb;
+	u8 msr, lsr;
+
+	msr = ctrl_inb(EC3104_SER4_MSR);
+	
+	if ((msr & MSR_CTS) && !(k->last_msr & MSR_CTS)) {
+		if (k->cached_mcr & MCR_RTS)
+			printk("confused: RTS already high\n");
+		/* CTS went high.  Send RTS. */
+		k->cached_mcr |= MCR_RTS;
+		
+		ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+	} else if ((!(msr & MSR_CTS)) && (k->last_msr & MSR_CTS)) {
+		/* CTS went low. */
+		if (!(k->cached_mcr & MCR_RTS))
+			printk("confused: RTS already low\n");
+
+		k->cached_mcr &= ~MCR_RTS;
+
+		ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+	}
+
+	k->last_msr = msr;
+
+	lsr = ctrl_inb(EC3104_SER4_LSR);
+
+	if (lsr & LSR_DR)
+		e5_receive(k);
+}
+
+static void ec3104_keyb_clear_state(void)
+{
+	struct e5_struct *k = &ec3104_keyb;
+	u8 msr, lsr;
+	
+	/* we want CTS to be low */
+	k->last_msr = 0;
+
+	for (;;) {
+		msleep(100);
+
+		msr = ctrl_inb(EC3104_SER4_MSR);
+	
+		lsr = ctrl_inb(EC3104_SER4_LSR);
+		
+		if (lsr & LSR_DR) {
+			e5_receive(k);
+			continue;
+		}
+
+		if ((msr & MSR_CTS) && !(k->last_msr & MSR_CTS)) {
+			if (k->cached_mcr & MCR_RTS)
+				printk("confused: RTS already high\n");
+			/* CTS went high.  Send RTS. */
+			k->cached_mcr |= MCR_RTS;
+		
+			ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+		} else if ((!(msr & MSR_CTS)) && (k->last_msr & MSR_CTS)) {
+			/* CTS went low. */
+			if (!(k->cached_mcr & MCR_RTS))
+				printk("confused: RTS already low\n");
+			
+			k->cached_mcr &= ~MCR_RTS;
+			
+			ctrl_outb(k->cached_mcr, EC3104_SER4_MCR);
+		} else
+			break;
+
+		k->last_msr = msr;
+
+		continue;
+	}
+}
+
+void __init ec3104_kbd_init_hw(void)
+{
+	ec3104_keyb.last_msr = ctrl_inb(EC3104_SER4_MSR);
+	ec3104_keyb.cached_mcr = ctrl_inb(EC3104_SER4_MCR);
+
+	ec3104_keyb_clear_state();
+
+	/* Ok, finally allocate the IRQ, and off we go.. */
+	request_irq(EC3104_IRQ_SER4, ec3104_keyb_interrupt, 0, "keyboard", NULL);
+}
diff --git a/drivers/char/efirtc.c b/drivers/char/efirtc.c
new file mode 100644
index 0000000..0090e7a
--- /dev/null
+++ b/drivers/char/efirtc.c
@@ -0,0 +1,417 @@
+/*
+ * EFI Time Services Driver for Linux
+ *
+ * Copyright (C) 1999 Hewlett-Packard Co
+ * Copyright (C) 1999 Stephane Eranian <eranian@hpl.hp.com>
+ *
+ * Based on skeleton from the drivers/char/rtc.c driver by P. Gortmaker
+ *
+ * This code provides an architected & portable interface to the real time
+ * clock by using EFI instead of direct bit fiddling. The functionalities are 
+ * quite different from the rtc.c driver. The only way to talk to the device 
+ * is by using ioctl(). There is a /proc interface which provides the raw 
+ * information.
+ *
+ * Please note that we have kept the API as close as possible to the
+ * legacy RTC. The standard /sbin/hwclock program should work normally 
+ * when used to get/set the time.
+ *
+ * NOTES:
+ *	- Locking is required for safe execution of EFI calls with regards
+ *	  to interrrupts and SMP.
+ *
+ * TODO (December 1999):
+ * 	- provide the API to set/get the WakeUp Alarm (different from the
+ *	  rtc.c alarm).
+ *	- SMP testing
+ * 	- Add module support
+ */
+
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rtc.h>
+#include <linux/proc_fs.h>
+#include <linux/efi.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define EFI_RTC_VERSION		"0.4"
+
+#define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT)
+/*
+ * EFI Epoch is 1/1/1998
+ */
+#define EFI_RTC_EPOCH		1998
+
+static DEFINE_SPINLOCK(efi_rtc_lock);
+
+static int efi_rtc_ioctl(struct inode *inode, struct file *file,
+		     unsigned int cmd, unsigned long arg);
+
+#define is_leap(year) \
+          ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
+
+static const unsigned short int __mon_yday[2][13] =
+{
+	/* Normal years.  */
+	{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+	/* Leap years.  */  
+	{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+};
+
+/*
+ * returns day of the year [0-365]
+ */
+static inline int
+compute_yday(efi_time_t *eft)
+{
+	/* efi_time_t.month is in the [1-12] so, we need -1 */
+	return  __mon_yday[is_leap(eft->year)][eft->month-1]+ eft->day -1;
+}
+/*
+ * returns day of the week [0-6] 0=Sunday
+ *
+ * Don't try to provide a year that's before 1998, please !
+ */
+static int
+compute_wday(efi_time_t *eft)
+{
+	int y;
+	int ndays = 0;
+
+	if ( eft->year < 1998 ) {
+		printk(KERN_ERR "efirtc: EFI year < 1998, invalid date\n");
+		return -1;
+	}
+
+	for(y=EFI_RTC_EPOCH; y < eft->year; y++ ) {
+		ndays += 365 + (is_leap(y) ? 1 : 0);
+	}
+	ndays += compute_yday(eft);
+
+	/*
+	 * 4=1/1/1998 was a Thursday
+	 */
+	return (ndays + 4) % 7;
+}
+
+static void
+convert_to_efi_time(struct rtc_time *wtime, efi_time_t *eft)
+{
+
+	eft->year	= wtime->tm_year + 1900;
+	eft->month	= wtime->tm_mon + 1; 
+	eft->day	= wtime->tm_mday;
+	eft->hour	= wtime->tm_hour;
+	eft->minute	= wtime->tm_min;
+	eft->second 	= wtime->tm_sec;
+	eft->nanosecond = 0; 
+	eft->daylight	= wtime->tm_isdst ? EFI_ISDST: 0;
+	eft->timezone	= EFI_UNSPECIFIED_TIMEZONE;
+}
+
+static void
+convert_from_efi_time(efi_time_t *eft, struct rtc_time *wtime)
+{
+	memset(wtime, 0, sizeof(*wtime));
+	wtime->tm_sec  = eft->second;
+	wtime->tm_min  = eft->minute;
+	wtime->tm_hour = eft->hour;
+	wtime->tm_mday = eft->day;
+	wtime->tm_mon  = eft->month - 1;
+	wtime->tm_year = eft->year - 1900;
+
+	/* day of the week [0-6], Sunday=0 */
+	wtime->tm_wday = compute_wday(eft);
+
+	/* day in the year [1-365]*/
+	wtime->tm_yday = compute_yday(eft);
+
+
+	switch (eft->daylight & EFI_ISDST) {
+		case EFI_ISDST:
+			wtime->tm_isdst = 1;
+			break;
+		case EFI_TIME_ADJUST_DAYLIGHT:
+			wtime->tm_isdst = 0;
+			break;
+		default:
+			wtime->tm_isdst = -1;
+	}
+}
+
+static int
+efi_rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+		     unsigned long arg)
+{
+
+	efi_status_t	status;
+	unsigned long	flags;
+	efi_time_t	eft;
+	efi_time_cap_t	cap;
+	struct rtc_time	wtime;
+	struct rtc_wkalrm __user *ewp;
+	unsigned char	enabled, pending;
+
+	switch (cmd) {
+		case RTC_UIE_ON:
+		case RTC_UIE_OFF:
+		case RTC_PIE_ON:
+		case RTC_PIE_OFF:
+		case RTC_AIE_ON:
+		case RTC_AIE_OFF:
+		case RTC_ALM_SET:
+		case RTC_ALM_READ:
+		case RTC_IRQP_READ:
+		case RTC_IRQP_SET:
+		case RTC_EPOCH_READ:
+		case RTC_EPOCH_SET:
+			return -EINVAL;
+
+		case RTC_RD_TIME:
+
+			spin_lock_irqsave(&efi_rtc_lock, flags);
+
+			status = efi.get_time(&eft, &cap);
+
+			spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+			if (status != EFI_SUCCESS) {
+				/* should never happen */
+				printk(KERN_ERR "efitime: can't read time\n");
+				return -EINVAL;
+			}
+
+			convert_from_efi_time(&eft, &wtime);
+
+ 			return copy_to_user((void __user *)arg, &wtime,
+					    sizeof (struct rtc_time)) ? - EFAULT : 0;
+
+		case RTC_SET_TIME:
+
+			if (!capable(CAP_SYS_TIME)) return -EACCES;
+
+			if (copy_from_user(&wtime, (struct rtc_time __user *)arg,
+					   sizeof(struct rtc_time)) )
+				return -EFAULT;
+
+			convert_to_efi_time(&wtime, &eft);
+
+			spin_lock_irqsave(&efi_rtc_lock, flags);
+
+			status = efi.set_time(&eft);
+
+			spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+			return status == EFI_SUCCESS ? 0 : -EINVAL;
+
+		case RTC_WKALM_SET:
+
+			if (!capable(CAP_SYS_TIME)) return -EACCES;
+
+			ewp = (struct rtc_wkalrm __user *)arg;
+
+			if (  get_user(enabled, &ewp->enabled)
+			   || copy_from_user(&wtime, &ewp->time, sizeof(struct rtc_time)) )
+				return -EFAULT;
+
+			convert_to_efi_time(&wtime, &eft);
+
+			spin_lock_irqsave(&efi_rtc_lock, flags);
+			/*
+			 * XXX Fixme:
+			 * As of EFI 0.92 with the firmware I have on my
+			 * machine this call does not seem to work quite
+			 * right
+			 */
+			status = efi.set_wakeup_time((efi_bool_t)enabled, &eft);
+
+			spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+			return status == EFI_SUCCESS ? 0 : -EINVAL;
+
+		case RTC_WKALM_RD:
+
+			spin_lock_irqsave(&efi_rtc_lock, flags);
+
+			status = efi.get_wakeup_time((efi_bool_t *)&enabled, (efi_bool_t *)&pending, &eft);
+
+			spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+			if (status != EFI_SUCCESS) return -EINVAL;
+
+			ewp = (struct rtc_wkalrm __user *)arg;
+
+			if (  put_user(enabled, &ewp->enabled)
+			   || put_user(pending, &ewp->pending)) return -EFAULT;
+
+			convert_from_efi_time(&eft, &wtime);
+
+			return copy_to_user(&ewp->time, &wtime,
+					    sizeof(struct rtc_time)) ? -EFAULT : 0;
+	}
+	return -EINVAL;
+}
+
+/*
+ *	We enforce only one user at a time here with the open/close.
+ *	Also clear the previous interrupt data on an open, and clean
+ *	up things on a close.
+ */
+
+static int
+efi_rtc_open(struct inode *inode, struct file *file)
+{
+	/*
+	 * nothing special to do here
+	 * We do accept multiple open files at the same time as we
+	 * synchronize on the per call operation.
+	 */
+	return 0;
+}
+
+static int
+efi_rtc_close(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+/*
+ *	The various file operations we support.
+ */
+
+static struct file_operations efi_rtc_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= efi_rtc_ioctl,
+	.open		= efi_rtc_open,
+	.release	= efi_rtc_close,
+};
+
+static struct miscdevice efi_rtc_dev=
+{
+	EFI_RTC_MINOR,
+	"efirtc",
+	&efi_rtc_fops
+};
+
+/*
+ *	We export RAW EFI information to /proc/driver/efirtc
+ */
+static int
+efi_rtc_get_status(char *buf)
+{
+	efi_time_t 	eft, alm;
+	efi_time_cap_t	cap;
+	char		*p = buf;
+	efi_bool_t	enabled, pending;	
+	unsigned long	flags;
+
+	memset(&eft, 0, sizeof(eft));
+	memset(&alm, 0, sizeof(alm));
+	memset(&cap, 0, sizeof(cap));
+
+	spin_lock_irqsave(&efi_rtc_lock, flags);
+
+	efi.get_time(&eft, &cap);
+	efi.get_wakeup_time(&enabled, &pending, &alm);
+
+	spin_unlock_irqrestore(&efi_rtc_lock,flags);
+
+	p += sprintf(p,
+		     "Time           : %u:%u:%u.%09u\n"
+		     "Date           : %u-%u-%u\n"
+		     "Daylight       : %u\n",
+		     eft.hour, eft.minute, eft.second, eft.nanosecond, 
+		     eft.year, eft.month, eft.day,
+		     eft.daylight);
+
+	if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE)
+		p += sprintf(p, "Timezone       : unspecified\n");
+	else
+		/* XXX fixme: convert to string? */
+		p += sprintf(p, "Timezone       : %u\n", eft.timezone);
+		
+
+	p += sprintf(p,
+		     "Alarm Time     : %u:%u:%u.%09u\n"
+		     "Alarm Date     : %u-%u-%u\n"
+		     "Alarm Daylight : %u\n"
+		     "Enabled        : %s\n"
+		     "Pending        : %s\n",
+		     alm.hour, alm.minute, alm.second, alm.nanosecond, 
+		     alm.year, alm.month, alm.day, 
+		     alm.daylight,
+		     enabled == 1 ? "yes" : "no",
+		     pending == 1 ? "yes" : "no");
+
+	if (eft.timezone == EFI_UNSPECIFIED_TIMEZONE)
+		p += sprintf(p, "Timezone       : unspecified\n");
+	else
+		/* XXX fixme: convert to string? */
+		p += sprintf(p, "Timezone       : %u\n", alm.timezone);
+
+	/*
+	 * now prints the capabilities
+	 */
+	p += sprintf(p,
+		     "Resolution     : %u\n"
+		     "Accuracy       : %u\n"
+		     "SetstoZero     : %u\n",
+		      cap.resolution, cap.accuracy, cap.sets_to_zero);
+
+	return  p - buf;
+}
+
+static int
+efi_rtc_read_proc(char *page, char **start, off_t off,
+                                 int count, int *eof, void *data)
+{
+        int len = efi_rtc_get_status(page);
+        if (len <= off+count) *eof = 1;
+        *start = page + off;
+        len -= off;
+        if (len>count) len = count;
+        if (len<0) len = 0;
+        return len;
+}
+
+static int __init 
+efi_rtc_init(void)
+{
+	int ret;
+	struct proc_dir_entry *dir;
+
+	printk(KERN_INFO "EFI Time Services Driver v%s\n", EFI_RTC_VERSION);
+
+	ret = misc_register(&efi_rtc_dev);
+	if (ret) {
+		printk(KERN_ERR "efirtc: can't misc_register on minor=%d\n",
+				EFI_RTC_MINOR);
+		return ret;
+	}
+
+	dir = create_proc_read_entry ("driver/efirtc", 0, NULL,
+			              efi_rtc_read_proc, NULL);
+	if (dir == NULL) {
+		printk(KERN_ERR "efirtc: can't create /proc/driver/efirtc.\n");
+		misc_deregister(&efi_rtc_dev);
+		return -1;
+	}
+	return 0;
+}
+
+static void __exit
+efi_rtc_exit(void)
+{
+	/* not yet used */
+}
+
+module_init(efi_rtc_init);
+module_exit(efi_rtc_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/epca.c b/drivers/char/epca.c
new file mode 100644
index 0000000..6025e18
--- /dev/null
+++ b/drivers/char/epca.c
@@ -0,0 +1,3789 @@
+/*
+
+ 
+	Copyright (C) 1996  Digi International.
+ 
+	For technical support please email digiLinux@dgii.com or
+	call Digi tech support at (612) 912-3456
+
+	Much of this design and code came from epca.c which was 
+	copyright (C) 1994, 1995 Troy De Jongh, and subsquently 
+	modified by David Nugent, Christoph Lameter, Mike McLagan. 
+ 
+ 	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.
+
+--------------------------------------------------------------------------- */
+/* See README.epca for change history --DAT*/
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/serial.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#ifdef CONFIG_PCI
+#define ENABLE_PCI
+#endif /* CONFIG_PCI */
+
+#define putUser(arg1, arg2) put_user(arg1, (unsigned long __user *)arg2)
+#define getUser(arg1, arg2) get_user(arg1, (unsigned __user *)arg2)
+
+#ifdef ENABLE_PCI
+#include <linux/pci.h>
+#include "digiPCI.h"
+#endif /* ENABLE_PCI */
+
+#include "digi1.h"
+#include "digiFep1.h"
+#include "epca.h"
+#include "epcaconfig.h"
+
+#if BITS_PER_LONG != 32
+#  error FIXME: this driver only works on 32-bit platforms
+#endif
+
+/* ---------------------- Begin defines ------------------------ */
+
+#define VERSION            "1.3.0.1-LK"
+
+/* This major needs to be submitted to Linux to join the majors list */
+
+#define DIGIINFOMAJOR       35  /* For Digi specific ioctl */ 
+
+
+#define MAXCARDS 7
+#define epcaassert(x, msg)  if (!(x)) epca_error(__LINE__, msg)
+
+#define PFX "epca: "
+
+/* ----------------- Begin global definitions ------------------- */
+
+static char mesg[100];
+static int nbdevs, num_cards, liloconfig;
+static int digi_poller_inhibited = 1 ;
+
+static int setup_error_code;
+static int invalid_lilo_config;
+
+/* -----------------------------------------------------------------------
+	MAXBOARDS is typically 12, but ISA and EISA cards are restricted to 
+	7 below.
+--------------------------------------------------------------------------*/
+static struct board_info boards[MAXBOARDS];
+
+
+/* ------------- Begin structures used for driver registeration ---------- */
+
+static struct tty_driver *pc_driver;
+static struct tty_driver *pc_info;
+
+/* ------------------ Begin Digi specific structures -------------------- */
+
+/* ------------------------------------------------------------------------
+	digi_channels represents an array of structures that keep track of
+	each channel of the Digi product.  Information such as transmit and
+	receive pointers, termio data, and signal definitions (DTR, CTS, etc ...)
+	are stored here.  This structure is NOT used to overlay the cards 
+	physical channel structure.
+-------------------------------------------------------------------------- */
+  
+static struct channel digi_channels[MAX_ALLOC];
+
+/* ------------------------------------------------------------------------
+	card_ptr is an array used to hold the address of the
+	first channel structure of each card.  This array will hold
+	the addresses of various channels located in digi_channels.
+-------------------------------------------------------------------------- */
+static struct channel *card_ptr[MAXCARDS];
+
+static struct timer_list epca_timer;
+
+/* ---------------------- Begin function prototypes --------------------- */
+
+/* ----------------------------------------------------------------------
+	Begin generic memory functions.  These functions will be alias
+	(point at) more specific functions dependent on the board being
+	configured.
+----------------------------------------------------------------------- */
+	
+static inline void memwinon(struct board_info *b, unsigned int win);
+static inline void memwinoff(struct board_info *b, unsigned int win);
+static inline void globalwinon(struct channel *ch);
+static inline void rxwinon(struct channel *ch);
+static inline void txwinon(struct channel *ch);
+static inline void memoff(struct channel *ch);
+static inline void assertgwinon(struct channel *ch);
+static inline void assertmemoff(struct channel *ch);
+
+/* ---- Begin more 'specific' memory functions for cx_like products --- */
+
+static inline void pcxem_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxem_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxem_globalwinon(struct channel *ch);
+static inline void pcxem_rxwinon(struct channel *ch);
+static inline void pcxem_txwinon(struct channel *ch);
+static inline void pcxem_memoff(struct channel *ch);
+
+/* ------ Begin more 'specific' memory functions for the pcxe ------- */
+
+static inline void pcxe_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxe_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxe_globalwinon(struct channel *ch);
+static inline void pcxe_rxwinon(struct channel *ch);
+static inline void pcxe_txwinon(struct channel *ch);
+static inline void pcxe_memoff(struct channel *ch);
+
+/* ---- Begin more 'specific' memory functions for the pc64xe and pcxi ---- */
+/* Note : pc64xe and pcxi share the same windowing routines */
+
+static inline void pcxi_memwinon(struct board_info *b, unsigned int win);
+static inline void pcxi_memwinoff(struct board_info *b, unsigned int win);
+static inline void pcxi_globalwinon(struct channel *ch);
+static inline void pcxi_rxwinon(struct channel *ch);
+static inline void pcxi_txwinon(struct channel *ch);
+static inline void pcxi_memoff(struct channel *ch);
+
+/* - Begin 'specific' do nothing memory functions needed for some cards - */
+
+static inline void dummy_memwinon(struct board_info *b, unsigned int win);
+static inline void dummy_memwinoff(struct board_info *b, unsigned int win);
+static inline void dummy_globalwinon(struct channel *ch);
+static inline void dummy_rxwinon(struct channel *ch);
+static inline void dummy_txwinon(struct channel *ch);
+static inline void dummy_memoff(struct channel *ch);
+static inline void dummy_assertgwinon(struct channel *ch);
+static inline void dummy_assertmemoff(struct channel *ch);
+
+/* ------------------- Begin declare functions ----------------------- */
+
+static inline struct channel *verifyChannel(register struct tty_struct *);
+static inline void pc_sched_event(struct channel *, int);
+static void epca_error(int, char *);
+static void pc_close(struct tty_struct *, struct file *);
+static void shutdown(struct channel *);
+static void pc_hangup(struct tty_struct *);
+static void pc_put_char(struct tty_struct *, unsigned char);
+static int pc_write_room(struct tty_struct *);
+static int pc_chars_in_buffer(struct tty_struct *);
+static void pc_flush_buffer(struct tty_struct *);
+static void pc_flush_chars(struct tty_struct *);
+static int block_til_ready(struct tty_struct *, struct file *,
+                           struct channel *);
+static int pc_open(struct tty_struct *, struct file *);
+static void post_fep_init(unsigned int crd);
+static void epcapoll(unsigned long);
+static void doevent(int);
+static void fepcmd(struct channel *, int, int, int, int, int);
+static unsigned termios2digi_h(struct channel *ch, unsigned);
+static unsigned termios2digi_i(struct channel *ch, unsigned);
+static unsigned termios2digi_c(struct channel *ch, unsigned);
+static void epcaparam(struct tty_struct *, struct channel *);
+static void receive_data(struct channel *);
+static int pc_ioctl(struct tty_struct *, struct file *,
+                    unsigned int, unsigned long);
+static int info_ioctl(struct tty_struct *, struct file *,
+                    unsigned int, unsigned long);
+static void pc_set_termios(struct tty_struct *, struct termios *);
+static void do_softint(void *);
+static void pc_stop(struct tty_struct *);
+static void pc_start(struct tty_struct *);
+static void pc_throttle(struct tty_struct * tty);
+static void pc_unthrottle(struct tty_struct *tty);
+static void digi_send_break(struct channel *ch, int msec);
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch);
+void epca_setup(char *, int *);
+void console_print(const char *);
+
+static int get_termio(struct tty_struct *, struct termio __user *);
+static int pc_write(struct tty_struct *, const unsigned char *, int);
+int pc_init(void);
+
+#ifdef ENABLE_PCI
+static int init_PCI(void);
+#endif /* ENABLE_PCI */
+
+
+/* ------------------------------------------------------------------
+	Table of functions for each board to handle memory.  Mantaining 
+	parallelism is a *very* good idea here.  The idea is for the 
+	runtime code to blindly call these functions, not knowing/caring    
+	about the underlying hardware.  This stuff should contain no
+	conditionals; if more functionality is needed a different entry
+	should be established.  These calls are the interface calls and 
+	are the only functions that should be accessed.  Anyone caught
+	making direct calls deserves what they get.
+-------------------------------------------------------------------- */
+
+static inline void memwinon(struct board_info *b, unsigned int win)
+{
+	(b->memwinon)(b, win);
+}
+
+static inline void memwinoff(struct board_info *b, unsigned int win)
+{
+	(b->memwinoff)(b, win);
+}
+
+static inline void globalwinon(struct channel *ch)
+{
+	(ch->board->globalwinon)(ch);
+}
+
+static inline void rxwinon(struct channel *ch)
+{
+	(ch->board->rxwinon)(ch);
+}
+
+static inline void txwinon(struct channel *ch)
+{
+	(ch->board->txwinon)(ch);
+}
+
+static inline void memoff(struct channel *ch)
+{
+	(ch->board->memoff)(ch);
+}
+static inline void assertgwinon(struct channel *ch)
+{
+	(ch->board->assertgwinon)(ch);
+}
+
+static inline void assertmemoff(struct channel *ch)
+{
+	(ch->board->assertmemoff)(ch);
+}
+
+/* ---------------------------------------------------------
+	PCXEM windowing is the same as that used in the PCXR 
+	and CX series cards.
+------------------------------------------------------------ */
+
+static inline void pcxem_memwinon(struct board_info *b, unsigned int win)
+{
+        outb_p(FEPWIN|win, (int)b->port + 1);
+}
+
+static inline void pcxem_memwinoff(struct board_info *b, unsigned int win)
+{
+	outb_p(0, (int)b->port + 1);
+}
+
+static inline void pcxem_globalwinon(struct channel *ch)
+{
+	outb_p( FEPWIN, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_rxwinon(struct channel *ch)
+{
+	outb_p(ch->rxwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_txwinon(struct channel *ch)
+{
+	outb_p(ch->txwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxem_memoff(struct channel *ch)
+{
+	outb_p(0, (int)ch->board->port + 1);
+}
+
+/* ----------------- Begin pcxe memory window stuff ------------------ */
+
+static inline void pcxe_memwinon(struct board_info *b, unsigned int win)
+{
+               outb_p(FEPWIN | win, (int)b->port + 1);
+}
+
+static inline void pcxe_memwinoff(struct board_info *b, unsigned int win)
+{
+	outb_p(inb((int)b->port) & ~FEPMEM,
+	           (int)b->port + 1);
+	outb_p(0, (int)b->port + 1);
+}
+
+static inline void pcxe_globalwinon(struct channel *ch)
+{
+	outb_p( FEPWIN, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_rxwinon(struct channel *ch)
+{
+		outb_p(ch->rxwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_txwinon(struct channel *ch)
+{
+		outb_p(ch->txwin, (int)ch->board->port + 1);
+}
+
+static inline void pcxe_memoff(struct channel *ch)
+{
+	outb_p(0, (int)ch->board->port);
+	outb_p(0, (int)ch->board->port + 1);
+}
+
+/* ------------- Begin pc64xe and pcxi memory window stuff -------------- */
+
+static inline void pcxi_memwinon(struct board_info *b, unsigned int win)
+{
+               outb_p(inb((int)b->port) | FEPMEM, (int)b->port);
+}
+
+static inline void pcxi_memwinoff(struct board_info *b, unsigned int win)
+{
+	outb_p(inb((int)b->port) & ~FEPMEM, (int)b->port);
+}
+
+static inline void pcxi_globalwinon(struct channel *ch)
+{
+	outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_rxwinon(struct channel *ch)
+{
+		outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_txwinon(struct channel *ch)
+{
+		outb_p(FEPMEM, (int)ch->board->port);
+}
+
+static inline void pcxi_memoff(struct channel *ch)
+{
+	outb_p(0, (int)ch->board->port);
+}
+
+static inline void pcxi_assertgwinon(struct channel *ch)
+{
+	epcaassert(inb((int)ch->board->port) & FEPMEM, "Global memory off");
+}
+
+static inline void pcxi_assertmemoff(struct channel *ch)
+{
+	epcaassert(!(inb((int)ch->board->port) & FEPMEM), "Memory on");
+}
+
+
+/* ----------------------------------------------------------------------
+	Not all of the cards need specific memory windowing routines.  Some
+	cards (Such as PCI) needs no windowing routines at all.  We provide
+	these do nothing routines so that the same code base can be used.
+	The driver will ALWAYS call a windowing routine if it thinks it needs
+	to; regardless of the card.  However, dependent on the card the routine
+	may or may not do anything.
+---------------------------------------------------------------------------*/
+
+static inline void dummy_memwinon(struct board_info *b, unsigned int win)
+{
+}
+
+static inline void dummy_memwinoff(struct board_info *b, unsigned int win)
+{
+}
+
+static inline void dummy_globalwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_rxwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_txwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_memoff(struct channel *ch)
+{
+}
+
+static inline void dummy_assertgwinon(struct channel *ch)
+{
+}
+
+static inline void dummy_assertmemoff(struct channel *ch)
+{
+}
+
+/* ----------------- Begin verifyChannel function ----------------------- */
+static inline struct channel *verifyChannel(register struct tty_struct *tty)
+{ /* Begin verifyChannel */
+
+	/* --------------------------------------------------------------------
+		This routine basically provides a sanity check.  It insures that
+		the channel returned is within the proper range of addresses as
+		well as properly initialized.  If some bogus info gets passed in
+		through tty->driver_data this should catch it.
+	--------------------------------------------------------------------- */
+
+	if (tty) 
+	{ /* Begin if tty */
+
+		register struct channel *ch = (struct channel *)tty->driver_data;
+
+		if ((ch >= &digi_channels[0]) && (ch < &digi_channels[nbdevs])) 
+		{
+			if (ch->magic == EPCA_MAGIC)
+				return ch;
+		}
+
+	} /* End if tty */
+
+	/* Else return a NULL for invalid */
+	return NULL;
+
+} /* End verifyChannel */
+
+/* ------------------ Begin pc_sched_event ------------------------- */
+
+static inline void pc_sched_event(struct channel *ch, int event)
+{ /* Begin pc_sched_event */
+
+
+	/* ----------------------------------------------------------------------
+		We call this to schedule interrupt processing on some event.  The 
+		kernel sees our request and calls the related routine in OUR driver.
+	-------------------------------------------------------------------------*/
+
+	ch->event |= 1 << event;
+	schedule_work(&ch->tqueue);
+
+
+} /* End pc_sched_event */
+
+/* ------------------ Begin epca_error ------------------------- */
+
+static void epca_error(int line, char *msg)
+{ /* Begin epca_error */
+
+	printk(KERN_ERR "epca_error (Digi): line = %d %s\n",line,msg);
+	return;
+
+} /* End epca_error */
+
+/* ------------------ Begin pc_close ------------------------- */
+static void pc_close(struct tty_struct * tty, struct file * filp)
+{ /* Begin pc_close */
+
+	struct channel *ch;
+	unsigned long flags;
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{ /* Begin if ch != NULL */
+
+		save_flags(flags);
+		cli();
+
+		if (tty_hung_up_p(filp)) 
+		{
+			restore_flags(flags);
+			return;
+		}
+
+		/* Check to see if the channel is open more than once */
+		if (ch->count-- > 1) 
+		{ /* Begin channel is open more than once */
+
+			/* -------------------------------------------------------------
+				Return without doing anything.  Someone might still be using
+				the channel.
+			---------------------------------------------------------------- */
+
+			restore_flags(flags);
+			return;
+		} /* End channel is open more than once */
+
+		/* Port open only once go ahead with shutdown & reset */
+
+		if (ch->count < 0) 
+		{
+			ch->count = 0;
+		}
+
+		/* ---------------------------------------------------------------
+			Let the rest of the driver know the channel is being closed.
+			This becomes important if an open is attempted before close 
+			is finished.
+		------------------------------------------------------------------ */
+
+		ch->asyncflags |= ASYNC_CLOSING;
+	
+		tty->closing = 1;
+
+		if (ch->asyncflags & ASYNC_INITIALIZED) 
+		{
+			/* Setup an event to indicate when the transmit buffer empties */
+			setup_empty_event(tty, ch);		
+			tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */
+		}
+	
+		if (tty->driver->flush_buffer)
+			tty->driver->flush_buffer(tty);
+
+		tty_ldisc_flush(tty);
+		shutdown(ch);
+		tty->closing = 0;
+		ch->event = 0;
+		ch->tty = NULL;
+
+		if (ch->blocked_open) 
+		{ /* Begin if blocked_open */
+
+			if (ch->close_delay) 
+			{
+				msleep_interruptible(jiffies_to_msecs(ch->close_delay));
+			}
+
+			wake_up_interruptible(&ch->open_wait);
+
+		} /* End if blocked_open */
+
+		ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_INITIALIZED | 
+		                      ASYNC_CLOSING);
+		wake_up_interruptible(&ch->close_wait);
+
+
+		restore_flags(flags);
+
+	} /* End if ch != NULL */
+
+} /* End pc_close */ 
+
+/* ------------------ Begin shutdown  ------------------------- */
+
+static void shutdown(struct channel *ch)
+{ /* Begin shutdown */
+
+	unsigned long flags;
+	struct tty_struct *tty;
+	volatile struct board_chan *bc;
+
+	if (!(ch->asyncflags & ASYNC_INITIALIZED)) 
+		return;
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+
+	bc = ch->brdchan;
+
+	/* ------------------------------------------------------------------
+		In order for an event to be generated on the receipt of data the
+		idata flag must be set. Since we are shutting down, this is not 
+		necessary clear this flag.
+	--------------------------------------------------------------------- */ 
+
+	if (bc)
+		bc->idata = 0;
+
+	tty = ch->tty;
+
+	/* ----------------------------------------------------------------
+	   If we're a modem control device and HUPCL is on, drop RTS & DTR.
+ 	------------------------------------------------------------------ */
+
+	if (tty->termios->c_cflag & HUPCL) 
+	{
+		ch->omodem &= ~(ch->m_rts | ch->m_dtr);
+		fepcmd(ch, SETMODEM, 0, ch->m_dtr | ch->m_rts, 10, 1);
+	}
+
+	memoff(ch);
+
+	/* ------------------------------------------------------------------
+		The channel has officialy been closed.  The next time it is opened
+		it will have to reinitialized.  Set a flag to indicate this.
+	---------------------------------------------------------------------- */
+
+	/* Prevent future Digi programmed interrupts from coming active */
+
+	ch->asyncflags &= ~ASYNC_INITIALIZED;
+	restore_flags(flags);
+
+} /* End shutdown */
+
+/* ------------------ Begin pc_hangup  ------------------------- */
+
+static void pc_hangup(struct tty_struct *tty)
+{ /* Begin pc_hangup */
+
+	struct channel *ch;
+	
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{ /* Begin if ch != NULL */
+
+		unsigned long flags;
+
+		save_flags(flags);
+		cli();
+		if (tty->driver->flush_buffer)
+			tty->driver->flush_buffer(tty);
+		tty_ldisc_flush(tty);
+		shutdown(ch);
+
+		ch->tty   = NULL;
+		ch->event = 0;
+		ch->count = 0;
+		restore_flags(flags);
+		ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_INITIALIZED);
+		wake_up_interruptible(&ch->open_wait);
+
+	} /* End if ch != NULL */
+
+} /* End pc_hangup */
+
+/* ------------------ Begin pc_write  ------------------------- */
+
+static int pc_write(struct tty_struct * tty,
+                    const unsigned char *buf, int bytesAvailable)
+{ /* Begin pc_write */
+
+	register unsigned int head, tail;
+	register int dataLen;
+	register int size;
+	register int amountCopied;
+
+
+	struct channel *ch;
+	unsigned long flags;
+	int remain;
+	volatile struct board_chan *bc;
+
+
+	/* ----------------------------------------------------------------
+		pc_write is primarily called directly by the kernel routine
+		tty_write (Though it can also be called by put_char) found in
+		tty_io.c.  pc_write is passed a line discipline buffer where 
+		the data to be written out is stored.  The line discipline 
+		implementation itself is done at the kernel level and is not 
+		brought into the driver.  
+	------------------------------------------------------------------- */
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) == NULL)
+		return 0;
+
+	/* Make a pointer to the channel data structure found on the board. */
+
+	bc   = ch->brdchan;
+	size = ch->txbufsize;
+
+	amountCopied = 0;
+	save_flags(flags);
+	cli();
+
+	globalwinon(ch);
+
+	head = bc->tin & (size - 1);
+	tail = bc->tout;
+
+	if (tail != bc->tout)
+		tail = bc->tout;
+	tail &= (size - 1);
+
+	/*	If head >= tail, head has not wrapped around. */ 
+	if (head >= tail) 
+	{ /* Begin head has not wrapped */
+
+		/* ---------------------------------------------------------------
+			remain (much like dataLen above) represents the total amount of
+			space available on the card for data.  Here dataLen represents
+			the space existing between the head pointer and the end of 
+			buffer.  This is important because a memcpy cannot be told to
+			automatically wrap around when it hits the buffer end.
+		------------------------------------------------------------------ */ 
+
+		dataLen = size - head;
+		remain = size - (head - tail) - 1;
+
+	} /* End head has not wrapped */
+	else 
+	{ /* Begin head has wrapped around */
+
+		remain = tail - head - 1;
+		dataLen = remain;
+
+	} /* End head has wrapped around */
+
+	/* -------------------------------------------------------------------
+			Check the space on the card.  If we have more data than 
+			space; reduce the amount of data to fit the space.
+	---------------------------------------------------------------------- */
+
+	bytesAvailable = min(remain, bytesAvailable);
+
+	txwinon(ch);
+	while (bytesAvailable > 0) 
+	{ /* Begin while there is data to copy onto card */
+
+		/* -----------------------------------------------------------------
+			If head is not wrapped, the below will make sure the first 
+			data copy fills to the end of card buffer.
+		------------------------------------------------------------------- */
+
+		dataLen = min(bytesAvailable, dataLen);
+		memcpy(ch->txptr + head, buf, dataLen);
+		buf += dataLen;
+		head += dataLen;
+		amountCopied += dataLen;
+		bytesAvailable -= dataLen;
+
+		if (head >= size) 
+		{
+			head = 0;
+			dataLen = tail;
+		}
+
+	} /* End while there is data to copy onto card */
+
+	ch->statusflags |= TXBUSY;
+	globalwinon(ch);
+	bc->tin = head;
+
+	if ((ch->statusflags & LOWWAIT) == 0) 
+	{
+		ch->statusflags |= LOWWAIT;
+		bc->ilow = 1;
+	}
+	memoff(ch);
+	restore_flags(flags);
+
+	return(amountCopied);
+
+} /* End pc_write */
+
+/* ------------------ Begin pc_put_char  ------------------------- */
+
+static void pc_put_char(struct tty_struct *tty, unsigned char c)
+{ /* Begin pc_put_char */
+
+   
+	pc_write(tty, &c, 1);
+	return;
+
+} /* End pc_put_char */
+
+/* ------------------ Begin pc_write_room  ------------------------- */
+
+static int pc_write_room(struct tty_struct *tty)
+{ /* Begin pc_write_room */
+
+	int remain;
+	struct channel *ch;
+	unsigned long flags;
+	unsigned int head, tail;
+	volatile struct board_chan *bc;
+
+	remain = 0;
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{
+		save_flags(flags);
+		cli();
+		globalwinon(ch);
+
+		bc   = ch->brdchan;
+		head = bc->tin & (ch->txbufsize - 1);
+		tail = bc->tout;
+
+		if (tail != bc->tout)
+			tail = bc->tout;
+		/* Wrap tail if necessary */
+		tail &= (ch->txbufsize - 1);
+
+		if ((remain = tail - head - 1) < 0 )
+			remain += ch->txbufsize;
+
+		if (remain && (ch->statusflags & LOWWAIT) == 0) 
+		{
+			ch->statusflags |= LOWWAIT;
+			bc->ilow = 1;
+		}
+		memoff(ch);
+		restore_flags(flags);
+	}
+
+	/* Return how much room is left on card */
+	return remain;
+
+} /* End pc_write_room */
+
+/* ------------------ Begin pc_chars_in_buffer  ---------------------- */
+
+static int pc_chars_in_buffer(struct tty_struct *tty)
+{ /* Begin pc_chars_in_buffer */
+
+	int chars;
+	unsigned int ctail, head, tail;
+	int remain;
+	unsigned long flags;
+	struct channel *ch;
+	volatile struct board_chan *bc;
+
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) == NULL)
+		return(0);
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+
+	bc = ch->brdchan;
+	tail = bc->tout;
+	head = bc->tin;
+	ctail = ch->mailbox->cout;
+
+	if (tail == head && ch->mailbox->cin == ctail && bc->tbusy == 0)
+		chars = 0;
+	else 
+	{ /* Begin if some space on the card has been used */
+
+		head = bc->tin & (ch->txbufsize - 1);
+		tail &= (ch->txbufsize - 1);
+
+		/*  --------------------------------------------------------------
+			The logic here is basically opposite of the above pc_write_room
+			here we are finding the amount of bytes in the buffer filled.
+			Not the amount of bytes empty.
+		------------------------------------------------------------------- */
+
+		if ((remain = tail - head - 1) < 0 )
+			remain += ch->txbufsize;
+
+		chars = (int)(ch->txbufsize - remain);
+
+		/* -------------------------------------------------------------  
+			Make it possible to wakeup anything waiting for output
+			in tty_ioctl.c, etc.
+
+			If not already set.  Setup an event to indicate when the
+			transmit buffer empties 
+		----------------------------------------------------------------- */
+
+		if (!(ch->statusflags & EMPTYWAIT))
+			setup_empty_event(tty,ch);
+
+	} /* End if some space on the card has been used */
+
+	memoff(ch);
+	restore_flags(flags);
+
+	/* Return number of characters residing on card. */
+	return(chars);
+
+} /* End pc_chars_in_buffer */
+
+/* ------------------ Begin pc_flush_buffer  ---------------------- */
+
+static void pc_flush_buffer(struct tty_struct *tty)
+{ /* Begin pc_flush_buffer */
+
+	unsigned int tail;
+	unsigned long flags;
+	struct channel *ch;
+	volatile struct board_chan *bc;
+
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) == NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+
+	globalwinon(ch);
+
+	bc   = ch->brdchan;
+	tail = bc->tout;
+
+	/* Have FEP move tout pointer; effectively flushing transmit buffer */
+
+	fepcmd(ch, STOUT, (unsigned) tail, 0, 0, 0);
+
+	memoff(ch);
+	restore_flags(flags);
+
+	wake_up_interruptible(&tty->write_wait);
+	tty_wakeup(tty);
+
+} /* End pc_flush_buffer */
+
+/* ------------------ Begin pc_flush_chars  ---------------------- */
+
+static void pc_flush_chars(struct tty_struct *tty)
+{ /* Begin pc_flush_chars */
+
+	struct channel * ch;
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{
+		unsigned long flags;
+
+		save_flags(flags);
+		cli();
+
+		/* ----------------------------------------------------------------
+			If not already set and the transmitter is busy setup an event
+			to indicate when the transmit empties.
+		------------------------------------------------------------------- */
+
+		if ((ch->statusflags & TXBUSY) && !(ch->statusflags & EMPTYWAIT))
+			setup_empty_event(tty,ch);
+
+		restore_flags(flags);
+	}
+
+} /* End pc_flush_chars */
+
+/* ------------------ Begin block_til_ready  ---------------------- */
+
+static int block_til_ready(struct tty_struct *tty, 
+                           struct file *filp, struct channel *ch)
+{ /* Begin block_til_ready */
+
+	DECLARE_WAITQUEUE(wait,current);
+	int	retval, do_clocal = 0;
+	unsigned long flags;
+
+
+	if (tty_hung_up_p(filp))
+	{
+		if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+			retval = -EAGAIN;
+		else
+			retval = -ERESTARTSYS;	
+		return(retval);
+	}
+
+	/* ----------------------------------------------------------------- 
+		If the device is in the middle of being closed, then block
+		until it's done, and then try again.
+	-------------------------------------------------------------------- */
+	if (ch->asyncflags & ASYNC_CLOSING) 
+	{
+		interruptible_sleep_on(&ch->close_wait);
+
+		if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+	}
+
+	if (filp->f_flags & O_NONBLOCK) 
+	{
+		/* ----------------------------------------------------------------- 
+	  	 If non-blocking mode is set, then make the check up front
+	  	 and then exit.
+		-------------------------------------------------------------------- */
+
+		ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+
+		return 0;
+	}
+
+
+	if (tty->termios->c_cflag & CLOCAL)
+		do_clocal = 1;
+	
+   /* Block waiting for the carrier detect and the line to become free */
+	
+	retval = 0;
+	add_wait_queue(&ch->open_wait, &wait);
+	save_flags(flags);
+	cli();
+
+
+	/* We dec count so that pc_close will know when to free things */
+	if (!tty_hung_up_p(filp))
+		ch->count--;
+
+	restore_flags(flags);
+
+	ch->blocked_open++;
+
+	while(1) 
+	{ /* Begin forever while  */
+
+		set_current_state(TASK_INTERRUPTIBLE);
+
+		if (tty_hung_up_p(filp) ||
+		    !(ch->asyncflags & ASYNC_INITIALIZED)) 
+		{
+			if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;	
+			break;
+		}
+
+		if (!(ch->asyncflags & ASYNC_CLOSING) && 
+			  (do_clocal || (ch->imodem & ch->dcd)))
+			break;
+
+		if (signal_pending(current)) 
+		{
+			retval = -ERESTARTSYS;
+			break;
+		}
+
+		/* ---------------------------------------------------------------
+			Allow someone else to be scheduled.  We will occasionally go
+			through this loop until one of the above conditions change.
+			The below schedule call will allow other processes to enter and
+			prevent this loop from hogging the cpu.
+		------------------------------------------------------------------ */
+		schedule();
+
+	} /* End forever while  */
+
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&ch->open_wait, &wait);
+	cli();
+	if (!tty_hung_up_p(filp))
+		ch->count++;
+	restore_flags(flags);
+
+	ch->blocked_open--;
+
+	if (retval)
+		return retval;
+
+	ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+
+	return 0;
+
+} /* End block_til_ready */	
+
+/* ------------------ Begin pc_open  ---------------------- */
+
+static int pc_open(struct tty_struct *tty, struct file * filp)
+{ /* Begin pc_open */
+
+	struct channel *ch;
+	unsigned long flags;
+	int line, retval, boardnum;
+	volatile struct board_chan *bc;
+	volatile unsigned int head;
+
+	line = tty->index;
+	if (line < 0 || line >= nbdevs) 
+	{
+		printk(KERN_ERR "<Error> - pc_open : line out of range in pc_open\n");
+		tty->driver_data = NULL;
+		return(-ENODEV);
+	}
+
+
+	ch = &digi_channels[line];
+	boardnum = ch->boardnum;
+
+	/* Check status of board configured in system.  */
+
+	/* -----------------------------------------------------------------
+		I check to see if the epca_setup routine detected an user error.  
+		It might be better to put this in pc_init, but for the moment it
+		goes here.
+	---------------------------------------------------------------------- */
+
+	if (invalid_lilo_config)
+	{
+		if (setup_error_code & INVALID_BOARD_TYPE)
+			printk(KERN_ERR "<Error> - pc_open: Invalid board type specified in LILO command\n");
+
+		if (setup_error_code & INVALID_NUM_PORTS)
+			printk(KERN_ERR "<Error> - pc_open: Invalid number of ports specified in LILO command\n");
+
+		if (setup_error_code & INVALID_MEM_BASE)
+			printk(KERN_ERR "<Error> - pc_open: Invalid board memory address specified in LILO command\n");
+
+		if (setup_error_code & INVALID_PORT_BASE)
+			printk(KERN_ERR "<Error> - pc_open: Invalid board port address specified in LILO command\n");
+
+		if (setup_error_code & INVALID_BOARD_STATUS)
+			printk(KERN_ERR "<Error> - pc_open: Invalid board status specified in LILO command\n");
+
+		if (setup_error_code & INVALID_ALTPIN)
+			printk(KERN_ERR "<Error> - pc_open: Invalid board altpin specified in LILO command\n");
+
+		tty->driver_data = NULL;   /* Mark this device as 'down' */
+		return(-ENODEV);
+	}
+
+	if ((boardnum >= num_cards) || (boards[boardnum].status == DISABLED)) 
+	{
+		tty->driver_data = NULL;   /* Mark this device as 'down' */
+		return(-ENODEV);
+	}
+	
+	if (( bc = ch->brdchan) == 0) 
+	{
+		tty->driver_data = NULL;
+		return(-ENODEV);
+	}
+
+	/* ------------------------------------------------------------------
+		Every time a channel is opened, increment a counter.  This is 
+		necessary because we do not wish to flush and shutdown the channel
+		until the last app holding the channel open, closes it.	 	
+	--------------------------------------------------------------------- */
+
+	ch->count++;
+
+	/* ----------------------------------------------------------------
+		Set a kernel structures pointer to our local channel 
+		structure.  This way we can get to it when passed only
+		a tty struct.
+	------------------------------------------------------------------ */
+
+	tty->driver_data = ch;
+	
+	/* ----------------------------------------------------------------
+		If this is the first time the channel has been opened, initialize
+		the tty->termios struct otherwise let pc_close handle it.
+	-------------------------------------------------------------------- */
+
+	save_flags(flags);
+	cli();
+
+	globalwinon(ch);
+	ch->statusflags = 0;
+
+	/* Save boards current modem status */
+	ch->imodem = bc->mstat;
+
+	/* ----------------------------------------------------------------
+	   Set receive head and tail ptrs to each other.  This indicates
+	   no data available to read.
+	----------------------------------------------------------------- */
+	head = bc->rin;
+	bc->rout = head;
+
+	/* Set the channels associated tty structure */
+	ch->tty = tty;
+
+	/* -----------------------------------------------------------------
+		The below routine generally sets up parity, baud, flow control 
+		issues, etc.... It effect both control flags and input flags.
+	-------------------------------------------------------------------- */
+	epcaparam(tty,ch);
+
+	ch->asyncflags |= ASYNC_INITIALIZED;
+	memoff(ch);
+
+	restore_flags(flags);
+
+	retval = block_til_ready(tty, filp, ch);
+	if (retval)
+	{
+		return retval;
+	}
+
+	/* -------------------------------------------------------------
+		Set this again in case a hangup set it to zero while this 
+		open() was waiting for the line...
+	--------------------------------------------------------------- */
+	ch->tty = tty;
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+
+	/* Enable Digi Data events */
+	bc->idata = 1;
+
+	memoff(ch);
+	restore_flags(flags);
+
+	return 0;
+
+} /* End pc_open */
+
+#ifdef MODULE
+static int __init epca_module_init(void)
+{ /* Begin init_module */
+
+	unsigned long	flags;
+
+	save_flags(flags);
+	cli();
+
+	pc_init();
+
+	restore_flags(flags);
+
+	return(0);
+}
+
+module_init(epca_module_init);
+#endif
+
+#ifdef ENABLE_PCI
+static struct pci_driver epca_driver;
+#endif
+
+#ifdef MODULE
+/* -------------------- Begin cleanup_module  ---------------------- */
+
+static void __exit epca_module_exit(void)
+{
+
+	int               count, crd;
+	struct board_info *bd;
+	struct channel    *ch;
+	unsigned long     flags;
+
+	del_timer_sync(&epca_timer);
+
+	save_flags(flags);
+	cli();
+
+	if ((tty_unregister_driver(pc_driver)) ||  
+	    (tty_unregister_driver(pc_info)))
+	{
+		printk(KERN_WARNING "<Error> - DIGI : cleanup_module failed to un-register tty driver\n");
+		restore_flags(flags);
+		return;
+	}
+	put_tty_driver(pc_driver);
+	put_tty_driver(pc_info);
+
+	for (crd = 0; crd < num_cards; crd++) 
+	{ /* Begin for each card */
+
+		bd = &boards[crd];
+
+		if (!bd)
+		{ /* Begin sanity check */
+			printk(KERN_ERR "<Error> - Digi : cleanup_module failed\n");
+			return;
+		} /* End sanity check */
+
+		ch = card_ptr[crd]; 
+
+		for (count = 0; count < bd->numports; count++, ch++) 
+		{ /* Begin for each port */
+
+			if (ch) 
+			{
+				if (ch->tty)
+					tty_hangup(ch->tty);
+				kfree(ch->tmp_buf);
+			}
+
+		} /* End for each port */
+	} /* End for each card */
+
+#ifdef ENABLE_PCI
+	pci_unregister_driver (&epca_driver);
+#endif
+
+	restore_flags(flags);
+
+}
+module_exit(epca_module_exit);
+#endif /* MODULE */
+
+static struct tty_operations pc_ops = {
+	.open = pc_open,
+	.close = pc_close,
+	.write = pc_write,
+	.write_room = pc_write_room,
+	.flush_buffer = pc_flush_buffer,
+	.chars_in_buffer = pc_chars_in_buffer,
+	.flush_chars = pc_flush_chars,
+	.put_char = pc_put_char,
+	.ioctl = pc_ioctl,
+	.set_termios = pc_set_termios,
+	.stop = pc_stop,
+	.start = pc_start,
+	.throttle = pc_throttle,
+	.unthrottle = pc_unthrottle,
+	.hangup = pc_hangup,
+};
+
+static int info_open(struct tty_struct *tty, struct file * filp)
+{
+	return 0;
+}
+
+static struct tty_operations info_ops = {
+	.open = info_open,
+	.ioctl = info_ioctl,
+};
+
+/* ------------------ Begin pc_init  ---------------------- */
+
+int __init pc_init(void)
+{ /* Begin pc_init */
+
+	/* ----------------------------------------------------------------
+		pc_init is called by the operating system during boot up prior to
+		any open calls being made.  In the older versions of Linux (Prior
+		to 2.0.0) an entry is made into tty_io.c.  A pointer to the last
+		memory location (from kernel space) used (kmem_start) is passed
+		to pc_init.  It is pc_inits responsibility to modify this value 
+		for any memory that the Digi driver might need and then return
+		this value to the operating system.  For example if the driver
+		wishes to allocate 1K of kernel memory, pc_init would return 
+		(kmem_start + 1024).  This memory (Between kmem_start and kmem_start
+		+ 1024) would then be available for use exclusively by the driver.  
+		In this case our driver does not allocate any of this kernel 
+		memory.
+	------------------------------------------------------------------*/
+
+	ulong flags;
+	int crd;
+	struct board_info *bd;
+	unsigned char board_id = 0;
+
+#ifdef ENABLE_PCI
+	int pci_boards_found, pci_count;
+
+	pci_count = 0;
+#endif /* ENABLE_PCI */
+
+	pc_driver = alloc_tty_driver(MAX_ALLOC);
+	if (!pc_driver)
+		return -ENOMEM;
+
+	pc_info = alloc_tty_driver(MAX_ALLOC);
+	if (!pc_info) {
+		put_tty_driver(pc_driver);
+		return -ENOMEM;
+	}
+
+	/* -----------------------------------------------------------------------
+		If epca_setup has not been ran by LILO set num_cards to defaults; copy
+		board structure defined by digiConfig into drivers board structure.
+		Note : If LILO has ran epca_setup then epca_setup will handle defining
+		num_cards as well as copying the data into the board structure.
+	-------------------------------------------------------------------------- */
+	if (!liloconfig)
+	{ /* Begin driver has been configured via. epcaconfig */
+
+		nbdevs = NBDEVS;
+		num_cards = NUMCARDS;
+		memcpy((void *)&boards, (void *)&static_boards,
+		       (sizeof(struct board_info) * NUMCARDS));
+	} /* End driver has been configured via. epcaconfig */
+
+	/* -----------------------------------------------------------------
+		Note : If lilo was used to configure the driver and the 
+		ignore epcaconfig option was choosen (digiepca=2) then 
+		nbdevs and num_cards will equal 0 at this point.  This is
+		okay; PCI cards will still be picked up if detected.
+	--------------------------------------------------------------------- */
+
+	/*  -----------------------------------------------------------
+		Set up interrupt, we will worry about memory allocation in
+		post_fep_init. 
+	--------------------------------------------------------------- */
+
+
+	printk(KERN_INFO "DIGI epca driver version %s loaded.\n",VERSION);
+
+#ifdef ENABLE_PCI
+
+	/* ------------------------------------------------------------------
+		NOTE : This code assumes that the number of ports found in 
+		       the boards array is correct.  This could be wrong if
+		       the card in question is PCI (And therefore has no ports 
+		       entry in the boards structure.)  The rest of the 
+		       information will be valid for PCI because the beginning
+		       of pc_init scans for PCI and determines i/o and base
+		       memory addresses.  I am not sure if it is possible to 
+		       read the number of ports supported by the card prior to
+		       it being booted (Since that is the state it is in when 
+		       pc_init is run).  Because it is not possible to query the
+		       number of supported ports until after the card has booted;
+		       we are required to calculate the card_ptrs as the card is	 
+		       is initialized (Inside post_fep_init).  The negative thing
+		       about this approach is that digiDload's call to GET_INFO
+		       will have a bad port value.  (Since this is called prior
+		       to post_fep_init.)
+
+	--------------------------------------------------------------------- */
+  
+	pci_boards_found = 0;
+	if(num_cards < MAXBOARDS)
+		pci_boards_found += init_PCI();
+	num_cards += pci_boards_found;
+
+#endif /* ENABLE_PCI */
+
+	pc_driver->owner = THIS_MODULE;
+	pc_driver->name = "ttyD"; 
+	pc_driver->devfs_name = "tts/D";
+	pc_driver->major = DIGI_MAJOR; 
+	pc_driver->minor_start = 0;
+	pc_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	pc_driver->subtype = SERIAL_TYPE_NORMAL;
+	pc_driver->init_termios = tty_std_termios;
+	pc_driver->init_termios.c_iflag = 0;
+	pc_driver->init_termios.c_oflag = 0;
+	pc_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+	pc_driver->init_termios.c_lflag = 0;
+	pc_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(pc_driver, &pc_ops);
+
+	pc_info->owner = THIS_MODULE;
+	pc_info->name = "digi_ctl";
+	pc_info->major = DIGIINFOMAJOR;
+	pc_info->minor_start = 0;
+	pc_info->type = TTY_DRIVER_TYPE_SERIAL;
+	pc_info->subtype = SERIAL_TYPE_INFO;
+	pc_info->init_termios = tty_std_termios;
+	pc_info->init_termios.c_iflag = 0;
+	pc_info->init_termios.c_oflag = 0;
+	pc_info->init_termios.c_lflag = 0;
+	pc_info->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
+	pc_info->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(pc_info, &info_ops);
+
+
+	save_flags(flags);
+	cli();
+
+	for (crd = 0; crd < num_cards; crd++) 
+	{ /* Begin for each card */
+
+		/*  ------------------------------------------------------------------
+			This is where the appropriate memory handlers for the hardware is
+			set.  Everything at runtime blindly jumps through these vectors.
+		---------------------------------------------------------------------- */
+
+		/* defined in epcaconfig.h */
+		bd = &boards[crd];
+
+		switch (bd->type)
+		{ /* Begin switch on bd->type {board type} */
+			case PCXEM:
+			case EISAXEM:
+				bd->memwinon     = pcxem_memwinon ;
+				bd->memwinoff    = pcxem_memwinoff ;
+				bd->globalwinon  = pcxem_globalwinon ;
+				bd->txwinon      = pcxem_txwinon ;
+				bd->rxwinon      = pcxem_rxwinon ;
+				bd->memoff       = pcxem_memoff ;
+				bd->assertgwinon = dummy_assertgwinon;
+				bd->assertmemoff = dummy_assertmemoff;
+				break;
+
+			case PCIXEM:
+			case PCIXRJ:
+			case PCIXR:
+				bd->memwinon     = dummy_memwinon;
+				bd->memwinoff    = dummy_memwinoff;
+				bd->globalwinon  = dummy_globalwinon;
+				bd->txwinon      = dummy_txwinon;
+				bd->rxwinon      = dummy_rxwinon;
+				bd->memoff       = dummy_memoff;
+				bd->assertgwinon = dummy_assertgwinon;
+				bd->assertmemoff = dummy_assertmemoff;
+				break;
+
+			case PCXE:
+			case PCXEVE:
+
+				bd->memwinon     = pcxe_memwinon;
+				bd->memwinoff    = pcxe_memwinoff;
+				bd->globalwinon  = pcxe_globalwinon;
+				bd->txwinon      = pcxe_txwinon;
+				bd->rxwinon      = pcxe_rxwinon;
+				bd->memoff       = pcxe_memoff;
+				bd->assertgwinon = dummy_assertgwinon;
+				bd->assertmemoff = dummy_assertmemoff;
+				break;
+
+			case PCXI:
+			case PC64XE:
+
+				bd->memwinon     = pcxi_memwinon;
+				bd->memwinoff    = pcxi_memwinoff;
+				bd->globalwinon  = pcxi_globalwinon;
+				bd->txwinon      = pcxi_txwinon;
+				bd->rxwinon      = pcxi_rxwinon;
+				bd->memoff       = pcxi_memoff;
+				bd->assertgwinon = pcxi_assertgwinon;
+				bd->assertmemoff = pcxi_assertmemoff;
+				break;
+
+			default:
+				break;
+
+		} /* End switch on bd->type */
+
+		/* ---------------------------------------------------------------
+			Some cards need a memory segment to be defined for use in 
+			transmit and receive windowing operations.  These boards
+			are listed in the below switch.  In the case of the XI the
+			amount of memory on the board is variable so the memory_seg
+			is also variable.  This code determines what they segment 
+			should be.
+		----------------------------------------------------------------- */
+
+		switch (bd->type)
+		{ /* Begin switch on bd->type {board type} */
+
+			case PCXE:
+			case PCXEVE:
+			case PC64XE:
+				bd->memory_seg = 0xf000;
+			break;
+
+			case PCXI:
+				board_id = inb((int)bd->port);
+				if ((board_id & 0x1) == 0x1) 
+				{ /* Begin it's an XI card */ 
+
+					/* Is it a 64K board */
+					if ((board_id & 0x30) == 0) 
+						bd->memory_seg = 0xf000;
+
+					/* Is it a 128K board */
+					if ((board_id & 0x30) == 0x10) 
+						bd->memory_seg = 0xe000;
+
+					/* Is is a 256K board */	
+					if ((board_id & 0x30) == 0x20) 
+						bd->memory_seg = 0xc000;
+
+					/* Is it a 512K board */
+					if ((board_id & 0x30) == 0x30) 
+						bd->memory_seg = 0x8000;
+
+				} /* End it is an XI card */
+				else
+				{
+					printk(KERN_ERR "<Error> - Board at 0x%x doesn't appear to be an XI\n",(int)bd->port);
+				}
+			break;
+
+		} /* End switch on bd->type */
+
+	} /* End for each card */
+
+	if (tty_register_driver(pc_driver))
+		panic("Couldn't register Digi PC/ driver");
+
+	if (tty_register_driver(pc_info))
+		panic("Couldn't register Digi PC/ info ");
+
+	/* -------------------------------------------------------------------
+	   Start up the poller to check for events on all enabled boards
+	---------------------------------------------------------------------- */
+
+	init_timer(&epca_timer);
+	epca_timer.function = epcapoll;
+	mod_timer(&epca_timer, jiffies + HZ/25);
+
+	restore_flags(flags);
+
+	return 0;
+
+} /* End pc_init */
+
+/* ------------------ Begin post_fep_init  ---------------------- */
+
+static void post_fep_init(unsigned int crd)
+{ /* Begin post_fep_init */
+
+	int i;
+	unchar *memaddr;
+	volatile struct global_data *gd;
+	struct board_info *bd;
+	volatile struct board_chan *bc;
+	struct channel *ch; 
+	int shrinkmem = 0, lowwater ; 
+ 
+	/*  -------------------------------------------------------------
+		This call is made by the user via. the ioctl call DIGI_INIT.
+		It is responsible for setting up all the card specific stuff.
+	---------------------------------------------------------------- */
+	bd = &boards[crd];
+
+	/* -----------------------------------------------------------------
+		If this is a PCI board, get the port info.  Remember PCI cards
+		do not have entries into the epcaconfig.h file, so we can't get 
+		the number of ports from it.  Unfortunetly, this means that anyone
+		doing a DIGI_GETINFO before the board has booted will get an invalid
+		number of ports returned (It should return 0).  Calls to DIGI_GETINFO
+		after DIGI_INIT has been called will return the proper values. 
+	------------------------------------------------------------------- */
+
+	if (bd->type >= PCIXEM) /* If the board in question is PCI */
+	{ /* Begin get PCI number of ports */
+
+		/* --------------------------------------------------------------------
+			Below we use XEMPORTS as a memory offset regardless of which PCI
+			card it is.  This is because all of the supported PCI cards have
+			the same memory offset for the channel data.  This will have to be
+			changed if we ever develop a PCI/XE card.  NOTE : The FEP manual
+			states that the port offset is 0xC22 as opposed to 0xC02.  This is
+			only true for PC/XE, and PC/XI cards; not for the XEM, or CX series.
+			On the PCI cards the number of ports is determined by reading a 
+			ID PROM located in the box attached to the card.  The card can then
+			determine the index the id to determine the number of ports available.
+			(FYI - The id should be located at 0x1ac (And may use up to 4 bytes
+			if the box in question is a XEM or CX)).  
+		------------------------------------------------------------------------ */ 
+
+		bd->numports = (unsigned short)*(unsigned char *)bus_to_virt((unsigned long)
+                                                       (bd->re_map_membase + XEMPORTS));
+
+		
+		epcaassert(bd->numports <= 64,"PCI returned a invalid number of ports");
+		nbdevs += (bd->numports);
+
+	} /* End get PCI number of ports */
+
+	if (crd != 0)
+		card_ptr[crd] = card_ptr[crd-1] + boards[crd-1].numports;
+	else
+		card_ptr[crd] = &digi_channels[crd]; /* <- For card 0 only */
+
+	ch = card_ptr[crd];
+
+
+	epcaassert(ch <= &digi_channels[nbdevs - 1], "ch out of range");
+
+	memaddr = (unchar *)bd->re_map_membase;
+
+	/* 
+	   The below command is necessary because newer kernels (2.1.x and
+	   up) do not have a 1:1 virtual to physical mapping.  The below
+	   call adjust for that.
+	*/
+
+	memaddr = (unsigned char *)bus_to_virt((unsigned long)memaddr);
+
+	/* -----------------------------------------------------------------
+		The below assignment will set bc to point at the BEGINING of
+		the cards channel structures.  For 1 card there will be between
+		8 and 64 of these structures.
+	-------------------------------------------------------------------- */
+
+	bc = (volatile struct board_chan *)((ulong)memaddr + CHANSTRUCT);
+
+	/* -------------------------------------------------------------------
+		The below assignment will set gd to point at the BEGINING of
+		global memory address 0xc00.  The first data in that global
+		memory actually starts at address 0xc1a.  The command in 
+		pointer begins at 0xd10.
+	---------------------------------------------------------------------- */
+
+	gd = (volatile struct global_data *)((ulong)memaddr + GLOBAL);
+
+	/* --------------------------------------------------------------------
+		XEPORTS (address 0xc22) points at the number of channels the
+		card supports. (For 64XE, XI, XEM, and XR use 0xc02)
+	----------------------------------------------------------------------- */
+
+	if (((bd->type == PCXEVE) | (bd->type == PCXE)) &&
+	    (*(ushort *)((ulong)memaddr + XEPORTS) < 3))
+		shrinkmem = 1;
+	if (bd->type < PCIXEM)
+		if (!request_region((int)bd->port, 4, board_desc[bd->type]))
+			return;		
+
+	memwinon(bd, 0);
+
+	/*  --------------------------------------------------------------------
+		Remember ch is the main drivers channels structure, while bc is 
+	   the cards channel structure.
+	------------------------------------------------------------------------ */
+
+	/* For every port on the card do ..... */
+
+	for (i = 0; i < bd->numports; i++, ch++, bc++) 
+	{ /* Begin for each port */
+
+		ch->brdchan        = bc;
+		ch->mailbox        = gd; 
+		INIT_WORK(&ch->tqueue, do_softint, ch);
+		ch->board          = &boards[crd];
+
+		switch (bd->type)
+		{ /* Begin switch bd->type */
+
+			/* ----------------------------------------------------------------
+				Since some of the boards use different bitmaps for their
+				control signals we cannot hard code these values and retain
+				portability.  We virtualize this data here.
+			------------------------------------------------------------------- */
+			case EISAXEM:
+			case PCXEM:
+			case PCIXEM:
+			case PCIXRJ:
+			case PCIXR:
+				ch->m_rts = 0x02 ;
+				ch->m_dcd = 0x80 ; 
+				ch->m_dsr = 0x20 ;
+				ch->m_cts = 0x10 ;
+				ch->m_ri  = 0x40 ;
+				ch->m_dtr = 0x01 ;
+				break;
+
+			case PCXE:
+			case PCXEVE:
+			case PCXI:
+			case PC64XE:
+				ch->m_rts = 0x02 ;
+				ch->m_dcd = 0x08 ; 
+				ch->m_dsr = 0x10 ;
+				ch->m_cts = 0x20 ;
+				ch->m_ri  = 0x40 ;
+				ch->m_dtr = 0x80 ;
+				break;
+	
+		} /* End switch bd->type */
+
+		if (boards[crd].altpin) 
+		{
+			ch->dsr = ch->m_dcd;
+			ch->dcd = ch->m_dsr;
+			ch->digiext.digi_flags |= DIGI_ALTPIN;
+		}
+		else 
+		{ 
+			ch->dcd = ch->m_dcd;
+			ch->dsr = ch->m_dsr;
+		}
+	
+		ch->boardnum   = crd;
+		ch->channelnum = i;
+		ch->magic      = EPCA_MAGIC;
+		ch->tty        = NULL;
+
+		if (shrinkmem) 
+		{
+			fepcmd(ch, SETBUFFER, 32, 0, 0, 0);
+			shrinkmem = 0;
+		}
+
+		switch (bd->type)
+		{ /* Begin switch bd->type */
+
+			case PCIXEM:
+			case PCIXRJ:
+			case PCIXR:
+				/* Cover all the 2MEG cards */
+				ch->txptr = memaddr + (((bc->tseg) << 4) & 0x1fffff);
+				ch->rxptr = memaddr + (((bc->rseg) << 4) & 0x1fffff);
+				ch->txwin = FEPWIN | ((bc->tseg) >> 11);
+				ch->rxwin = FEPWIN | ((bc->rseg) >> 11);
+				break;
+
+			case PCXEM:
+			case EISAXEM:
+				/* Cover all the 32K windowed cards */
+				/* Mask equal to window size - 1 */
+				ch->txptr = memaddr + (((bc->tseg) << 4) & 0x7fff);
+				ch->rxptr = memaddr + (((bc->rseg) << 4) & 0x7fff);
+				ch->txwin = FEPWIN | ((bc->tseg) >> 11);
+				ch->rxwin = FEPWIN | ((bc->rseg) >> 11);
+				break;
+
+			case PCXEVE:
+			case PCXE:
+				ch->txptr = memaddr + (((bc->tseg - bd->memory_seg) << 4) & 0x1fff);
+				ch->txwin = FEPWIN | ((bc->tseg - bd->memory_seg) >> 9);
+				ch->rxptr = memaddr + (((bc->rseg - bd->memory_seg) << 4) & 0x1fff);
+				ch->rxwin = FEPWIN | ((bc->rseg - bd->memory_seg) >>9 );
+				break;
+
+			case PCXI:
+			case PC64XE:
+				ch->txptr = memaddr + ((bc->tseg - bd->memory_seg) << 4);
+				ch->rxptr = memaddr + ((bc->rseg - bd->memory_seg) << 4);
+				ch->txwin = ch->rxwin = 0;
+				break;
+
+		} /* End switch bd->type */
+
+		ch->txbufhead = 0;
+		ch->txbufsize = bc->tmax + 1;
+	
+		ch->rxbufhead = 0;
+		ch->rxbufsize = bc->rmax + 1;
+	
+		lowwater = ch->txbufsize >= 2000 ? 1024 : (ch->txbufsize / 2);
+
+		/* Set transmitter low water mark */
+		fepcmd(ch, STXLWATER, lowwater, 0, 10, 0);
+
+		/* Set receiver low water mark */
+
+		fepcmd(ch, SRXLWATER, (ch->rxbufsize / 4), 0, 10, 0);
+
+		/* Set receiver high water mark */
+
+		fepcmd(ch, SRXHWATER, (3 * ch->rxbufsize / 4), 0, 10, 0);
+
+		bc->edelay = 100;
+		bc->idata = 1;
+	
+		ch->startc  = bc->startc;
+		ch->stopc   = bc->stopc;
+		ch->startca = bc->startca;
+		ch->stopca  = bc->stopca;
+	
+		ch->fepcflag = 0;
+		ch->fepiflag = 0;
+		ch->fepoflag = 0;
+		ch->fepstartc = 0;
+		ch->fepstopc = 0;
+		ch->fepstartca = 0;
+		ch->fepstopca = 0;
+	
+		ch->close_delay = 50;
+		ch->count = 0;
+		ch->blocked_open = 0;
+		init_waitqueue_head(&ch->open_wait);
+		init_waitqueue_head(&ch->close_wait);
+		ch->tmp_buf = kmalloc(ch->txbufsize,GFP_KERNEL);
+		if (!(ch->tmp_buf))
+		{
+			printk(KERN_ERR "POST FEP INIT : kmalloc failed for port 0x%x\n",i);
+			release_region((int)bd->port, 4);
+			while(i-- > 0)
+				kfree((ch--)->tmp_buf);
+			return;
+		}
+		else 
+			memset((void *)ch->tmp_buf,0,ch->txbufsize);
+	} /* End for each port */
+
+	printk(KERN_INFO 
+	        "Digi PC/Xx Driver V%s:  %s I/O = 0x%lx Mem = 0x%lx Ports = %d\n", 
+	        VERSION, board_desc[bd->type], (long)bd->port, (long)bd->membase, bd->numports);
+	sprintf(mesg, 
+	        "Digi PC/Xx Driver V%s:  %s I/O = 0x%lx Mem = 0x%lx Ports = %d\n", 
+	        VERSION, board_desc[bd->type], (long)bd->port, (long)bd->membase, bd->numports);
+	console_print(mesg);
+
+	memwinoff(bd, 0);
+
+} /* End post_fep_init */
+
+/* --------------------- Begin epcapoll  ------------------------ */
+
+static void epcapoll(unsigned long ignored)
+{ /* Begin epcapoll */
+
+	unsigned long flags;
+	int crd;
+	volatile unsigned int head, tail;
+	struct channel *ch;
+	struct board_info *bd;
+
+	/* -------------------------------------------------------------------
+		This routine is called upon every timer interrupt.  Even though
+		the Digi series cards are capable of generating interrupts this 
+		method of non-looping polling is more efficient.  This routine
+		checks for card generated events (Such as receive data, are transmit
+		buffer empty) and acts on those events.
+	----------------------------------------------------------------------- */
+	
+	save_flags(flags);
+	cli();
+
+	for (crd = 0; crd < num_cards; crd++) 
+	{ /* Begin for each card */
+
+		bd = &boards[crd];
+		ch = card_ptr[crd];
+
+		if ((bd->status == DISABLED) || digi_poller_inhibited)
+			continue; /* Begin loop next interation */
+
+		/* -----------------------------------------------------------
+			assertmemoff is not needed here; indeed it is an empty subroutine.
+			It is being kept because future boards may need this as well as
+			some legacy boards.
+		---------------------------------------------------------------- */
+
+		assertmemoff(ch);
+
+		globalwinon(ch);
+
+		/* ---------------------------------------------------------------
+			In this case head and tail actually refer to the event queue not
+			the transmit or receive queue.
+		------------------------------------------------------------------- */
+
+		head = ch->mailbox->ein;
+		tail = ch->mailbox->eout;
+		
+		/* If head isn't equal to tail we have an event */
+
+		if (head != tail)
+			doevent(crd);
+
+		memoff(ch);
+
+	} /* End for each card */
+
+	mod_timer(&epca_timer, jiffies + (HZ / 25));
+
+	restore_flags(flags);
+} /* End epcapoll */
+
+/* --------------------- Begin doevent  ------------------------ */
+
+static void doevent(int crd)
+{ /* Begin doevent */
+
+	volatile unchar *eventbuf;
+	struct channel *ch, *chan0;
+	static struct tty_struct *tty;
+	volatile struct board_info *bd;
+	volatile struct board_chan *bc;
+	register volatile unsigned int tail, head;
+	register int event, channel;
+	register int mstat, lstat;
+
+	/* -------------------------------------------------------------------
+		This subroutine is called by epcapoll when an event is detected 
+		in the event queue.  This routine responds to those events.
+	--------------------------------------------------------------------- */
+
+	bd = &boards[crd];
+
+	chan0 = card_ptr[crd];
+	epcaassert(chan0 <= &digi_channels[nbdevs - 1], "ch out of range");
+
+	assertgwinon(chan0);
+
+	while ((tail = chan0->mailbox->eout) != (head = chan0->mailbox->ein)) 
+	{ /* Begin while something in event queue */
+
+		assertgwinon(chan0);
+
+		eventbuf = (volatile unchar *)bus_to_virt((ulong)(bd->re_map_membase + tail + ISTART));
+
+		/* Get the channel the event occurred on */
+		channel = eventbuf[0];
+
+		/* Get the actual event code that occurred */
+		event = eventbuf[1];
+
+		/*  ----------------------------------------------------------------
+			The two assignments below get the current modem status (mstat)
+			and the previous modem status (lstat).  These are useful becuase
+			an event could signal a change in modem signals itself.
+		------------------------------------------------------------------- */
+
+		mstat = eventbuf[2];
+		lstat = eventbuf[3];
+
+		ch = chan0 + channel;
+
+		if ((unsigned)channel >= bd->numports || !ch) 
+		{ 
+			if (channel >= bd->numports)
+				ch = chan0;
+			bc = ch->brdchan;
+			goto next;
+		}
+
+		if ((bc = ch->brdchan) == NULL)
+			goto next;
+
+		if (event & DATA_IND) 
+		{ /* Begin DATA_IND */
+
+			receive_data(ch);
+			assertgwinon(ch);
+
+		} /* End DATA_IND */
+		/* else *//* Fix for DCD transition missed bug */
+		if (event & MODEMCHG_IND) 
+		{ /* Begin MODEMCHG_IND */
+
+			/* A modem signal change has been indicated */
+
+			ch->imodem = mstat;
+
+			if (ch->asyncflags & ASYNC_CHECK_CD) 
+			{
+				if (mstat & ch->dcd)  /* We are now receiving dcd */
+					wake_up_interruptible(&ch->open_wait);
+				else
+					pc_sched_event(ch, EPCA_EVENT_HANGUP); /* No dcd; hangup */
+			}
+
+		} /* End MODEMCHG_IND */
+
+		tty = ch->tty;
+		if (tty) 
+		{ /* Begin if valid tty */
+
+			if (event & BREAK_IND) 
+			{ /* Begin if BREAK_IND */
+
+				/* A break has been indicated */
+
+				tty->flip.count++;
+				*tty->flip.flag_buf_ptr++ = TTY_BREAK;
+
+				*tty->flip.char_buf_ptr++ = 0;
+
+				tty_schedule_flip(tty); 
+
+			} /* End if BREAK_IND */
+			else
+			if (event & LOWTX_IND) 
+			{ /* Begin LOWTX_IND */
+
+				if (ch->statusflags & LOWWAIT) 
+				{ /* Begin if LOWWAIT */
+
+					ch->statusflags &= ~LOWWAIT;
+					tty_wakeup(tty);
+					wake_up_interruptible(&tty->write_wait);
+
+				} /* End if LOWWAIT */
+
+			} /* End LOWTX_IND */
+			else
+			if (event & EMPTYTX_IND) 
+			{ /* Begin EMPTYTX_IND */
+
+				/* This event is generated by setup_empty_event */
+
+				ch->statusflags &= ~TXBUSY;
+				if (ch->statusflags & EMPTYWAIT) 
+				{ /* Begin if EMPTYWAIT */
+
+					ch->statusflags &= ~EMPTYWAIT;
+					tty_wakeup(tty);
+
+					wake_up_interruptible(&tty->write_wait);
+
+				} /* End if EMPTYWAIT */
+
+			} /* End EMPTYTX_IND */
+
+		} /* End if valid tty */
+
+
+	next:
+		globalwinon(ch);
+
+		if (!bc)
+			printk(KERN_ERR "<Error> - bc == NULL in doevent!\n");
+		else 
+			bc->idata = 1;
+
+		chan0->mailbox->eout = (tail + 4) & (IMAX - ISTART - 4);
+		globalwinon(chan0);
+
+	} /* End while something in event queue */
+
+} /* End doevent */
+
+/* --------------------- Begin fepcmd  ------------------------ */
+
+static void fepcmd(struct channel *ch, int cmd, int word_or_byte,
+                   int byte2, int ncmds, int bytecmd)
+{ /* Begin fepcmd */
+
+	unchar *memaddr;
+	unsigned int head, cmdTail, cmdStart, cmdMax;
+	long count;
+	int n;
+
+	/* This is the routine in which commands may be passed to the card. */
+
+	if (ch->board->status == DISABLED)
+	{
+		return;
+	}
+
+	assertgwinon(ch);
+
+	/* Remember head (As well as max) is just an offset not a base addr */
+	head = ch->mailbox->cin;
+
+	/* cmdStart is a base address */
+	cmdStart = ch->mailbox->cstart;
+
+	/* ------------------------------------------------------------------
+		We do the addition below because we do not want a max pointer 
+		relative to cmdStart.  We want a max pointer that points at the 
+		physical end of the command queue.
+	-------------------------------------------------------------------- */
+
+	cmdMax = (cmdStart + 4 + (ch->mailbox->cmax));
+
+	memaddr = ch->board->re_map_membase;
+
+	/* 
+	   The below command is necessary because newer kernels (2.1.x and
+	   up) do not have a 1:1 virtual to physical mapping.  The below
+	   call adjust for that.
+	*/
+
+	memaddr = (unsigned char *)bus_to_virt((unsigned long)memaddr);
+
+	if (head >= (cmdMax - cmdStart) || (head & 03)) 
+	{
+		printk(KERN_ERR "line %d: Out of range, cmd = %x, head = %x\n", __LINE__, 
+              cmd, head);
+		printk(KERN_ERR "line %d: Out of range, cmdMax = %x, cmdStart = %x\n", __LINE__, 
+              cmdMax, cmdStart);
+		return;
+	}
+
+	if (bytecmd) 
+	{
+		*(volatile unchar *)(memaddr + head + cmdStart + 0) = (unchar)cmd;
+
+		*(volatile unchar *)(memaddr + head + cmdStart + 1) = (unchar)ch->channelnum;
+		/* Below word_or_byte is bits to set */
+		*(volatile unchar *)(memaddr + head + cmdStart + 2) = (unchar)word_or_byte;
+		/* Below byte2 is bits to reset */
+		*(volatile unchar *)(memaddr + head + cmdStart + 3) = (unchar)byte2;
+
+	} 
+	else 
+	{
+		*(volatile unchar *)(memaddr + head + cmdStart + 0) = (unchar)cmd;
+		*(volatile unchar *)(memaddr + head + cmdStart + 1) = (unchar)ch->channelnum;
+		*(volatile ushort*)(memaddr + head + cmdStart + 2) = (ushort)word_or_byte;
+	}
+
+	head = (head + 4) & (cmdMax - cmdStart - 4);
+	ch->mailbox->cin = head;
+
+	count = FEPTIMEOUT;
+
+	for (;;) 
+	{ /* Begin forever loop */
+
+		count--;
+		if (count == 0) 
+		{
+			printk(KERN_ERR "<Error> - Fep not responding in fepcmd()\n");
+			return;
+		}
+
+		head = ch->mailbox->cin;
+		cmdTail = ch->mailbox->cout;
+
+		n = (head - cmdTail) & (cmdMax - cmdStart - 4);
+
+		/* ----------------------------------------------------------
+			Basically this will break when the FEP acknowledges the 
+			command by incrementing cmdTail (Making it equal to head).
+		------------------------------------------------------------- */
+
+		if (n <= ncmds * (sizeof(short) * 4))
+			break; /* Well nearly forever :-) */
+
+	} /* End forever loop */
+
+} /* End fepcmd */
+
+/* ---------------------------------------------------------------------
+	Digi products use fields in their channels structures that are very
+	similar to the c_cflag and c_iflag fields typically found in UNIX
+	termios structures.  The below three routines allow mappings 
+	between these hardware "flags" and their respective Linux flags.
+------------------------------------------------------------------------- */
+ 
+/* --------------------- Begin termios2digi_h -------------------- */
+
+static unsigned termios2digi_h(struct channel *ch, unsigned cflag)
+{ /* Begin termios2digi_h */
+
+	unsigned res = 0;
+
+	if (cflag & CRTSCTS) 
+	{
+		ch->digiext.digi_flags |= (RTSPACE | CTSPACE);
+		res |= ((ch->m_cts) | (ch->m_rts));
+	}
+
+	if (ch->digiext.digi_flags & RTSPACE)
+		res |= ch->m_rts;
+
+	if (ch->digiext.digi_flags & DTRPACE)
+		res |= ch->m_dtr;
+
+	if (ch->digiext.digi_flags & CTSPACE)
+		res |= ch->m_cts;
+
+	if (ch->digiext.digi_flags & DSRPACE)
+		res |= ch->dsr;
+
+	if (ch->digiext.digi_flags & DCDPACE)
+		res |= ch->dcd;
+
+	if (res & (ch->m_rts))
+		ch->digiext.digi_flags |= RTSPACE;
+
+	if (res & (ch->m_cts))
+		ch->digiext.digi_flags |= CTSPACE;
+
+	return res;
+
+} /* End termios2digi_h */
+
+/* --------------------- Begin termios2digi_i -------------------- */
+static unsigned termios2digi_i(struct channel *ch, unsigned iflag)
+{ /* Begin termios2digi_i */
+
+	unsigned res = iflag & (IGNBRK | BRKINT | IGNPAR | PARMRK | 
+	                        INPCK | ISTRIP|IXON|IXANY|IXOFF);
+	
+	if (ch->digiext.digi_flags & DIGI_AIXON)
+		res |= IAIXON;
+	return res;
+
+} /* End termios2digi_i */
+
+/* --------------------- Begin termios2digi_c -------------------- */
+
+static unsigned termios2digi_c(struct channel *ch, unsigned cflag)
+{ /* Begin termios2digi_c */
+
+	unsigned res = 0;
+
+#ifdef SPEED_HACK
+	/* CL: HACK to force 115200 at 38400 and 57600 at 19200 Baud */
+	if ((cflag & CBAUD)== B38400) cflag=cflag - B38400 + B115200;
+	if ((cflag & CBAUD)== B19200) cflag=cflag - B19200 + B57600;
+#endif /* SPEED_HACK */
+
+	if (cflag & CBAUDEX)
+	{ /* Begin detected CBAUDEX */
+
+		ch->digiext.digi_flags |= DIGI_FAST;
+
+		/* -------------------------------------------------------------
+		   HUPCL bit is used by FEP to indicate fast baud
+		   table is to be used.
+		----------------------------------------------------------------- */
+
+		res |= FEP_HUPCL;
+
+	} /* End detected CBAUDEX */
+	else ch->digiext.digi_flags &= ~DIGI_FAST; 
+
+	/* -------------------------------------------------------------------
+		CBAUD has bit position 0x1000 set these days to indicate Linux
+		baud rate remap.  Digi hardware can't handle the bit assignment.
+		(We use a different bit assignment for high speed.).  Clear this
+		bit out.
+	---------------------------------------------------------------------- */
+	res |= cflag & ((CBAUD ^ CBAUDEX) | PARODD | PARENB | CSTOPB | CSIZE);
+
+	/* -------------------------------------------------------------
+		This gets a little confusing.  The Digi cards have their own
+		representation of c_cflags controling baud rate.  For the most
+		part this is identical to the Linux implementation.  However;
+		Digi supports one rate (76800) that Linux doesn't.  This means 
+		that the c_cflag entry that would normally mean 76800 for Digi
+		actually means 115200 under Linux.  Without the below mapping,
+		a stty 115200 would only drive the board at 76800.  Since 
+		the rate 230400 is also found after 76800, the same problem afflicts	
+		us when we choose a rate of 230400.  Without the below modificiation
+		stty 230400 would actually give us 115200.
+
+		There are two additional differences.  The Linux value for CLOCAL
+		(0x800; 0004000) has no meaning to the Digi hardware.  Also in 
+		later releases of Linux; the CBAUD define has CBAUDEX (0x1000;
+		0010000) ored into it (CBAUD = 0x100f as opposed to 0xf). CBAUDEX
+		should be checked for a screened out prior to termios2digi_c 
+		returning.  Since CLOCAL isn't used by the board this can be
+		ignored as long as the returned value is used only by Digi hardware. 
+	----------------------------------------------------------------- */
+
+	if (cflag & CBAUDEX)
+	{
+		/* -------------------------------------------------------------
+			The below code is trying to guarantee that only baud rates
+			115200 and 230400 are remapped.  We use exclusive or because
+			the various baud rates share common bit positions and therefore
+			can't be tested for easily.
+		----------------------------------------------------------------- */
+
+				
+		if ((!((cflag & 0x7) ^ (B115200 & ~CBAUDEX))) || 
+		    (!((cflag & 0x7) ^ (B230400 & ~CBAUDEX))))
+		{
+			res += 1;
+		}
+	}
+
+	return res;
+
+} /* End termios2digi_c */
+
+/* --------------------- Begin epcaparam  ----------------------- */
+
+static void epcaparam(struct tty_struct *tty, struct channel *ch)
+{ /* Begin epcaparam */
+
+	unsigned int cmdHead;
+	struct termios *ts;
+	volatile struct board_chan *bc;
+	unsigned mval, hflow, cflag, iflag;
+
+	bc = ch->brdchan;
+	epcaassert(bc !=0, "bc out of range");
+
+	assertgwinon(ch);
+
+	ts = tty->termios;
+
+	if ((ts->c_cflag & CBAUD) == 0) 
+	{ /* Begin CBAUD detected */
+
+		cmdHead = bc->rin;
+		bc->rout = cmdHead;
+		cmdHead = bc->tin;
+
+		/* Changing baud in mid-stream transmission can be wonderful */
+		/* ---------------------------------------------------------------
+			Flush current transmit buffer by setting cmdTail pointer (tout)
+			to cmdHead pointer (tin).  Hopefully the transmit buffer is empty.
+		----------------------------------------------------------------- */
+
+		fepcmd(ch, STOUT, (unsigned) cmdHead, 0, 0, 0);
+		mval = 0;
+
+	} /* End CBAUD detected */
+	else 
+	{ /* Begin CBAUD not detected */
+
+		/* -------------------------------------------------------------------
+			c_cflags have changed but that change had nothing to do with BAUD.
+			Propagate the change to the card.
+		---------------------------------------------------------------------- */ 
+
+		cflag = termios2digi_c(ch, ts->c_cflag);
+
+		if (cflag != ch->fepcflag) 
+		{
+			ch->fepcflag = cflag;
+			/* Set baud rate, char size, stop bits, parity */
+			fepcmd(ch, SETCTRLFLAGS, (unsigned) cflag, 0, 0, 0);
+		}
+
+
+		/* ----------------------------------------------------------------
+			If the user has not forced CLOCAL and if the device is not a 
+			CALLOUT device (Which is always CLOCAL) we set flags such that
+			the driver will wait on carrier detect.
+		------------------------------------------------------------------- */
+
+		if (ts->c_cflag & CLOCAL)
+		{ /* Begin it is a cud device or a ttyD device with CLOCAL on */
+			ch->asyncflags &= ~ASYNC_CHECK_CD;
+		} /* End it is a cud device or a ttyD device with CLOCAL on */
+		else
+		{ /* Begin it is a ttyD device */
+			ch->asyncflags |= ASYNC_CHECK_CD;
+		} /* End it is a ttyD device */
+
+		mval = ch->m_dtr | ch->m_rts;
+
+	} /* End CBAUD not detected */
+
+	iflag = termios2digi_i(ch, ts->c_iflag);
+
+	/* Check input mode flags */
+
+	if (iflag != ch->fepiflag) 
+	{
+		ch->fepiflag = iflag;
+
+		/* ---------------------------------------------------------------
+			Command sets channels iflag structure on the board. Such things 
+			as input soft flow control, handling of parity errors, and
+			break handling are all set here.
+		------------------------------------------------------------------- */
+
+		/* break handling, parity handling, input stripping, flow control chars */
+		fepcmd(ch, SETIFLAGS, (unsigned int) ch->fepiflag, 0, 0, 0);
+	}
+
+	/* ---------------------------------------------------------------
+		Set the board mint value for this channel.  This will cause hardware
+		events to be generated each time the DCD signal (Described in mint) 
+		changes.	
+	------------------------------------------------------------------- */
+	bc->mint = ch->dcd;
+
+	if ((ts->c_cflag & CLOCAL) || (ch->digiext.digi_flags & DIGI_FORCEDCD))
+		if (ch->digiext.digi_flags & DIGI_FORCEDCD)
+			bc->mint = 0;
+
+	ch->imodem = bc->mstat;
+
+	hflow = termios2digi_h(ch, ts->c_cflag);
+
+	if (hflow != ch->hflow) 
+	{
+		ch->hflow = hflow;
+
+		/* --------------------------------------------------------------
+			Hard flow control has been selected but the board is not
+			using it.  Activate hard flow control now.
+		----------------------------------------------------------------- */
+
+		fepcmd(ch, SETHFLOW, hflow, 0xff, 0, 1);
+	}
+	
+
+	mval ^= ch->modemfake & (mval ^ ch->modem);
+
+	if (ch->omodem ^ mval) 
+	{
+		ch->omodem = mval;
+
+		/* --------------------------------------------------------------
+			The below command sets the DTR and RTS mstat structure.  If
+			hard flow control is NOT active these changes will drive the
+			output of the actual DTR and RTS lines.  If hard flow control 
+			is active, the changes will be saved in the mstat structure and
+			only asserted when hard flow control is turned off. 
+		----------------------------------------------------------------- */
+
+		/* First reset DTR & RTS; then set them */
+		fepcmd(ch, SETMODEM, 0, ((ch->m_dtr)|(ch->m_rts)), 0, 1);
+		fepcmd(ch, SETMODEM, mval, 0, 0, 1);
+
+	}
+
+	if (ch->startc != ch->fepstartc || ch->stopc != ch->fepstopc) 
+	{
+		ch->fepstartc = ch->startc;
+		ch->fepstopc = ch->stopc;
+
+		/* ------------------------------------------------------------
+			The XON / XOFF characters have changed; propagate these
+			changes to the card.	
+		--------------------------------------------------------------- */
+
+		fepcmd(ch, SONOFFC, ch->fepstartc, ch->fepstopc, 0, 1);
+	}
+
+	if (ch->startca != ch->fepstartca || ch->stopca != ch->fepstopca) 
+	{
+		ch->fepstartca = ch->startca;
+		ch->fepstopca = ch->stopca;
+
+		/* ---------------------------------------------------------------
+			Similar to the above, this time the auxilarly XON / XOFF 
+			characters have changed; propagate these changes to the card.
+		------------------------------------------------------------------ */
+
+		fepcmd(ch, SAUXONOFFC, ch->fepstartca, ch->fepstopca, 0, 1);
+	}
+
+} /* End epcaparam */
+
+/* --------------------- Begin receive_data  ----------------------- */
+
+static void receive_data(struct channel *ch)
+{ /* Begin receive_data */
+
+	unchar *rptr;
+	struct termios *ts = NULL;
+	struct tty_struct *tty;
+	volatile struct board_chan *bc;
+	register int dataToRead, wrapgap, bytesAvailable;
+	register unsigned int tail, head;
+	unsigned int wrapmask;
+	int rc;
+
+
+	/* ---------------------------------------------------------------
+		This routine is called by doint when a receive data event 
+		has taken place.
+	------------------------------------------------------------------- */
+
+	globalwinon(ch);
+
+	if (ch->statusflags & RXSTOPPED)
+		return;
+
+	tty = ch->tty;
+	if (tty)
+		ts = tty->termios;
+
+	bc = ch->brdchan;
+
+	if (!bc) 
+	{
+		printk(KERN_ERR "<Error> - bc is NULL in receive_data!\n");
+		return;
+	}
+
+	wrapmask = ch->rxbufsize - 1;
+
+	/* --------------------------------------------------------------------- 
+		Get the head and tail pointers to the receiver queue.  Wrap the 
+		head pointer if it has reached the end of the buffer.
+	------------------------------------------------------------------------ */
+
+	head = bc->rin;
+	head &= wrapmask;
+	tail = bc->rout & wrapmask;
+
+	bytesAvailable = (head - tail) & wrapmask;
+
+	if (bytesAvailable == 0)
+		return;
+
+	/* ------------------------------------------------------------------
+	   If CREAD bit is off or device not open, set TX tail to head
+	--------------------------------------------------------------------- */
+
+	if (!tty || !ts || !(ts->c_cflag & CREAD)) 
+	{
+		bc->rout = head;
+		return;
+	}
+
+	if (tty->flip.count == TTY_FLIPBUF_SIZE) 
+		return;
+
+	if (bc->orun) 
+	{
+		bc->orun = 0;
+		printk(KERN_WARNING "overrun! DigiBoard device %s\n",tty->name);
+	}
+
+	rxwinon(ch);
+	rptr = tty->flip.char_buf_ptr;
+	rc = tty->flip.count;
+
+	while (bytesAvailable > 0) 
+	{ /* Begin while there is data on the card */
+
+		wrapgap = (head >= tail) ? head - tail : ch->rxbufsize - tail;
+
+		/* ---------------------------------------------------------------
+			Even if head has wrapped around only report the amount of
+			data to be equal to the size - tail.  Remember memcpy can't
+			automaticly wrap around the receive buffer.
+		----------------------------------------------------------------- */
+
+		dataToRead = (wrapgap < bytesAvailable) ? wrapgap : bytesAvailable;
+
+		/* --------------------------------------------------------------
+		   Make sure we don't overflow the buffer
+		----------------------------------------------------------------- */
+
+		if ((rc + dataToRead) > TTY_FLIPBUF_SIZE)
+			dataToRead = TTY_FLIPBUF_SIZE - rc;
+
+		if (dataToRead == 0)
+			break;
+
+		/* ---------------------------------------------------------------
+			Move data read from our card into the line disciplines buffer
+			for translation if necessary.
+		------------------------------------------------------------------ */
+
+		if ((memcpy(rptr, ch->rxptr + tail, dataToRead)) != rptr)
+			printk(KERN_ERR "<Error> - receive_data : memcpy failed\n");
+			
+		rc   += dataToRead;
+		rptr += dataToRead;
+		tail = (tail + dataToRead) & wrapmask;
+		bytesAvailable -= dataToRead;
+
+	} /* End while there is data on the card */
+
+
+	tty->flip.count = rc;
+	tty->flip.char_buf_ptr = rptr;
+	globalwinon(ch);
+	bc->rout = tail;
+
+	/* Must be called with global data */
+	tty_schedule_flip(ch->tty); 
+	return;
+
+} /* End receive_data */
+
+static int info_ioctl(struct tty_struct *tty, struct file * file,
+		    unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) 
+	{ /* Begin switch cmd */
+
+		case DIGI_GETINFO:
+		{ /* Begin case DIGI_GETINFO */
+
+			struct digi_info di ;
+			int brd;
+
+			getUser(brd, (unsigned int __user *)arg);
+
+			if ((brd < 0) || (brd >= num_cards) || (num_cards == 0))
+				return (-ENODEV);
+
+			memset(&di, 0, sizeof(di));
+
+			di.board = brd ; 
+			di.status = boards[brd].status;
+			di.type = boards[brd].type ;
+			di.numports = boards[brd].numports ;
+			di.port = boards[brd].port ;
+			di.membase = boards[brd].membase ;
+
+			if (copy_to_user((void __user *)arg, &di, sizeof (di)))
+				return -EFAULT;
+			break;
+
+		} /* End case DIGI_GETINFO */
+
+		case DIGI_POLLER:
+		{ /* Begin case DIGI_POLLER */
+
+			int brd = arg & 0xff000000 >> 16 ; 
+			unsigned char state = arg & 0xff ; 
+
+			if ((brd < 0) || (brd >= num_cards))
+			{
+				printk(KERN_ERR "<Error> - DIGI POLLER : brd not valid!\n");
+				return (-ENODEV);
+			}
+
+			digi_poller_inhibited = state ;
+			break ; 
+
+		} /* End case DIGI_POLLER */
+
+		case DIGI_INIT:
+		{ /* Begin case DIGI_INIT */
+
+			/* ------------------------------------------------------------
+				This call is made by the apps to complete the initilization
+				of the board(s).  This routine is responsible for setting
+				the card to its initial state and setting the drivers control
+				fields to the sutianle settings for the card in question.
+			---------------------------------------------------------------- */
+		
+			int crd ; 
+			for (crd = 0; crd < num_cards; crd++) 
+				post_fep_init (crd);
+
+			break ; 
+
+		} /* End case DIGI_INIT */
+
+
+		default:
+			return -ENOIOCTLCMD;
+
+	} /* End switch cmd */
+	return (0) ;
+}
+/* --------------------- Begin pc_ioctl  ----------------------- */
+
+static int pc_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct channel *ch = (struct channel *) tty->driver_data;
+	volatile struct board_chan *bc;
+	unsigned int mstat, mflag = 0;
+	unsigned long flags;
+
+	if (ch)
+		bc = ch->brdchan;
+	else
+	{
+		printk(KERN_ERR "<Error> - ch is NULL in pc_tiocmget!\n");
+		return(-EINVAL);
+	}
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+	mstat = bc->mstat;
+	memoff(ch);
+	restore_flags(flags);
+
+	if (mstat & ch->m_dtr)
+		mflag |= TIOCM_DTR;
+
+	if (mstat & ch->m_rts)
+		mflag |= TIOCM_RTS;
+
+	if (mstat & ch->m_cts)
+		mflag |= TIOCM_CTS;
+
+	if (mstat & ch->dsr)
+		mflag |= TIOCM_DSR;
+
+	if (mstat & ch->m_ri)
+		mflag |= TIOCM_RI;
+
+	if (mstat & ch->dcd)
+		mflag |= TIOCM_CD;
+
+	return mflag;
+}
+
+static int pc_tiocmset(struct tty_struct *tty, struct file *file,
+		       unsigned int set, unsigned int clear)
+{
+	struct channel *ch = (struct channel *) tty->driver_data;
+	unsigned long flags;
+
+	if (!ch) {
+		printk(KERN_ERR "<Error> - ch is NULL in pc_tiocmset!\n");
+		return(-EINVAL);
+	}
+
+	save_flags(flags);
+	cli();
+	/*
+	 * I think this modemfake stuff is broken.  It doesn't
+	 * correctly reflect the behaviour desired by the TIOCM*
+	 * ioctls.  Therefore this is probably broken.
+	 */
+	if (set & TIOCM_RTS) {
+		ch->modemfake |= ch->m_rts;
+		ch->modem |= ch->m_rts;
+	}
+	if (set & TIOCM_DTR) {
+		ch->modemfake |= ch->m_dtr;
+		ch->modem |= ch->m_dtr;
+	}
+	if (clear & TIOCM_RTS) {
+		ch->modemfake |= ch->m_rts;
+		ch->modem &= ~ch->m_rts;
+	}
+	if (clear & TIOCM_DTR) {
+		ch->modemfake |= ch->m_dtr;
+		ch->modem &= ~ch->m_dtr;
+	}
+
+	globalwinon(ch);
+
+	/*  --------------------------------------------------------------
+		The below routine generally sets up parity, baud, flow control
+		issues, etc.... It effect both control flags and input flags.
+	------------------------------------------------------------------ */
+
+	epcaparam(tty,ch);
+	memoff(ch);
+	restore_flags(flags);
+	return 0;
+}
+
+static int pc_ioctl(struct tty_struct *tty, struct file * file,
+		    unsigned int cmd, unsigned long arg)
+{ /* Begin pc_ioctl */
+
+	digiflow_t dflow;
+	int retval;
+	unsigned long flags;
+	unsigned int mflag, mstat;
+	unsigned char startc, stopc;
+	volatile struct board_chan *bc;
+	struct channel *ch = (struct channel *) tty->driver_data;
+	void __user *argp = (void __user *)arg;
+	
+	if (ch)
+		bc = ch->brdchan;
+	else 
+	{
+		printk(KERN_ERR "<Error> - ch is NULL in pc_ioctl!\n");
+		return(-EINVAL);
+	}
+
+	save_flags(flags);
+
+	/* -------------------------------------------------------------------
+		For POSIX compliance we need to add more ioctls.  See tty_ioctl.c
+		in /usr/src/linux/drivers/char for a good example.  In particular 
+		think about adding TCSETAF, TCSETAW, TCSETA, TCSETSF, TCSETSW, TCSETS.
+	---------------------------------------------------------------------- */
+
+	switch (cmd) 
+	{ /* Begin switch cmd */
+
+		case TCGETS:
+			if (copy_to_user(argp, 
+					 tty->termios, sizeof(struct termios)))
+				return -EFAULT;
+			return(0);
+
+		case TCGETA:
+			return get_termio(tty, argp);
+
+		case TCSBRK:	/* SVID version: non-zero arg --> no break */
+
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+
+			/* Setup an event to indicate when the transmit buffer empties */
+
+			setup_empty_event(tty,ch);		
+			tty_wait_until_sent(tty, 0);
+			if (!arg)
+				digi_send_break(ch, HZ/4);    /* 1/4 second */
+			return 0;
+
+		case TCSBRKP:	/* support for POSIX tcsendbreak() */
+
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+
+			/* Setup an event to indicate when the transmit buffer empties */
+
+			setup_empty_event(tty,ch);		
+			tty_wait_until_sent(tty, 0);
+			digi_send_break(ch, arg ? arg*(HZ/10) : HZ/4);
+			return 0;
+
+		case TIOCGSOFTCAR:
+			if (put_user(C_CLOCAL(tty)?1:0, (unsigned long __user *)arg))
+				return -EFAULT;
+			return 0;
+
+		case TIOCSSOFTCAR:
+		{
+			unsigned int value;
+
+			if (get_user(value, (unsigned __user *)argp))
+				return -EFAULT;
+			tty->termios->c_cflag =
+				((tty->termios->c_cflag & ~CLOCAL) |
+				 (value ? CLOCAL : 0));
+			return 0;
+		}
+
+		case TIOCMODG:
+			mflag = pc_tiocmget(tty, file);
+			if (put_user(mflag, (unsigned long __user *)argp))
+				return -EFAULT;
+			break;
+
+		case TIOCMODS:
+			if (get_user(mstat, (unsigned __user *)argp))
+				return -EFAULT;
+			return pc_tiocmset(tty, file, mstat, ~mstat);
+
+		case TIOCSDTR:
+			ch->omodem |= ch->m_dtr;
+			cli();
+			globalwinon(ch);
+			fepcmd(ch, SETMODEM, ch->m_dtr, 0, 10, 1);
+			memoff(ch);
+			restore_flags(flags);
+			break;
+
+		case TIOCCDTR:
+			ch->omodem &= ~ch->m_dtr;
+			cli();
+			globalwinon(ch);
+			fepcmd(ch, SETMODEM, 0, ch->m_dtr, 10, 1);
+			memoff(ch);
+			restore_flags(flags);
+			break;
+
+		case DIGI_GETA:
+			if (copy_to_user(argp, &ch->digiext, sizeof(digi_t)))
+				return -EFAULT;
+			break;
+
+		case DIGI_SETAW:
+		case DIGI_SETAF:
+			if ((cmd) == (DIGI_SETAW)) 
+			{
+				/* Setup an event to indicate when the transmit buffer empties */
+
+				setup_empty_event(tty,ch);		
+				tty_wait_until_sent(tty, 0);
+			}
+			else 
+			{
+				/* ldisc lock already held in ioctl */
+				if (tty->ldisc.flush_buffer)
+					tty->ldisc.flush_buffer(tty);
+			}
+
+			/* Fall Thru */
+
+		case DIGI_SETA:
+			if (copy_from_user(&ch->digiext, argp, sizeof(digi_t)))
+				return -EFAULT;
+			
+			if (ch->digiext.digi_flags & DIGI_ALTPIN) 
+			{
+				ch->dcd = ch->m_dsr;
+				ch->dsr = ch->m_dcd;
+			} 
+			else 
+			{
+				ch->dcd = ch->m_dcd;
+				ch->dsr = ch->m_dsr;
+			}
+		
+			cli();
+			globalwinon(ch);
+
+			/* -----------------------------------------------------------------
+				The below routine generally sets up parity, baud, flow control 
+				issues, etc.... It effect both control flags and input flags.
+			------------------------------------------------------------------- */
+
+			epcaparam(tty,ch);
+			memoff(ch);
+			restore_flags(flags);
+			break;
+
+		case DIGI_GETFLOW:
+		case DIGI_GETAFLOW:
+			cli();	
+			globalwinon(ch);
+			if ((cmd) == (DIGI_GETFLOW)) 
+			{
+				dflow.startc = bc->startc;
+				dflow.stopc = bc->stopc;
+			}
+			else 
+			{
+				dflow.startc = bc->startca;
+				dflow.stopc = bc->stopca;
+			}
+			memoff(ch);
+			restore_flags(flags);
+
+			if (copy_to_user(argp, &dflow, sizeof(dflow)))
+				return -EFAULT;
+			break;
+
+		case DIGI_SETAFLOW:
+		case DIGI_SETFLOW:
+			if ((cmd) == (DIGI_SETFLOW)) 
+			{
+				startc = ch->startc;
+				stopc = ch->stopc;
+			} 
+			else 
+			{
+				startc = ch->startca;
+				stopc = ch->stopca;
+			}
+
+			if (copy_from_user(&dflow, argp, sizeof(dflow)))
+				return -EFAULT;
+
+			if (dflow.startc != startc || dflow.stopc != stopc) 
+			{ /* Begin  if setflow toggled */
+				cli();
+				globalwinon(ch);
+
+				if ((cmd) == (DIGI_SETFLOW)) 
+				{
+					ch->fepstartc = ch->startc = dflow.startc;
+					ch->fepstopc = ch->stopc = dflow.stopc;
+					fepcmd(ch, SONOFFC, ch->fepstartc, ch->fepstopc, 0, 1);
+				} 
+				else 
+				{
+					ch->fepstartca = ch->startca = dflow.startc;
+					ch->fepstopca  = ch->stopca = dflow.stopc;
+					fepcmd(ch, SAUXONOFFC, ch->fepstartca, ch->fepstopca, 0, 1);
+				}
+
+				if	(ch->statusflags & TXSTOPPED)
+					pc_start(tty);
+
+				memoff(ch);
+				restore_flags(flags);
+
+			} /* End if setflow toggled */
+			break;
+
+		default:
+			return -ENOIOCTLCMD;
+
+	} /* End switch cmd */
+
+	return 0;
+
+} /* End pc_ioctl */
+
+/* --------------------- Begin pc_set_termios  ----------------------- */
+
+static void pc_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{ /* Begin pc_set_termios */
+
+	struct channel *ch;
+	unsigned long flags;
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{ /* Begin if channel valid */
+
+		save_flags(flags);
+		cli();
+		globalwinon(ch);
+		epcaparam(tty, ch);
+		memoff(ch);
+
+		if ((old_termios->c_cflag & CRTSCTS) &&
+			 ((tty->termios->c_cflag & CRTSCTS) == 0))
+			tty->hw_stopped = 0;
+
+		if (!(old_termios->c_cflag & CLOCAL) &&
+			 (tty->termios->c_cflag & CLOCAL))
+			wake_up_interruptible(&ch->open_wait);
+
+		restore_flags(flags);
+
+	} /* End if channel valid */
+
+} /* End pc_set_termios */
+
+/* --------------------- Begin do_softint  ----------------------- */
+
+static void do_softint(void *private_)
+{ /* Begin do_softint */
+
+	struct channel *ch = (struct channel *) private_;
+	
+
+	/* Called in response to a modem change event */
+
+	if (ch && ch->magic == EPCA_MAGIC) 
+	{ /* Begin EPCA_MAGIC */
+
+		struct tty_struct *tty = ch->tty;
+
+		if (tty && tty->driver_data) 
+		{ 
+			if (test_and_clear_bit(EPCA_EVENT_HANGUP, &ch->event)) 
+			{ /* Begin if clear_bit */
+
+				tty_hangup(tty);	/* FIXME: module removal race here - AKPM */
+				wake_up_interruptible(&ch->open_wait);
+				ch->asyncflags &= ~ASYNC_NORMAL_ACTIVE;
+
+			} /* End if clear_bit */
+		}
+
+	} /* End EPCA_MAGIC */
+} /* End do_softint */
+
+/* ------------------------------------------------------------
+	pc_stop and pc_start provide software flow control to the 
+	routine and the pc_ioctl routine.
+---------------------------------------------------------------- */
+
+/* --------------------- Begin pc_stop  ----------------------- */
+
+static void pc_stop(struct tty_struct *tty)
+{ /* Begin pc_stop */
+
+	struct channel *ch;
+	unsigned long flags;
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{ /* Begin if valid channel */
+
+		save_flags(flags); 
+		cli();
+
+		if ((ch->statusflags & TXSTOPPED) == 0) 
+		{ /* Begin if transmit stop requested */
+
+			globalwinon(ch);
+
+			/* STOP transmitting now !! */
+
+			fepcmd(ch, PAUSETX, 0, 0, 0, 0);
+
+			ch->statusflags |= TXSTOPPED;
+			memoff(ch);
+
+		} /* End if transmit stop requested */
+
+		restore_flags(flags);
+
+	} /* End if valid channel */
+
+} /* End pc_stop */
+
+/* --------------------- Begin pc_start  ----------------------- */
+
+static void pc_start(struct tty_struct *tty)
+{ /* Begin pc_start */
+
+	struct channel *ch;
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{ /* Begin if channel valid */
+
+		unsigned long flags;
+
+		save_flags(flags);
+		cli();
+
+		/* Just in case output was resumed because of a change in Digi-flow */
+		if (ch->statusflags & TXSTOPPED) 
+		{ /* Begin transmit resume requested */
+
+			volatile struct board_chan *bc;
+
+			globalwinon(ch);
+			bc = ch->brdchan;
+			if (ch->statusflags & LOWWAIT)
+				bc->ilow = 1;
+
+			/* Okay, you can start transmitting again... */
+
+			fepcmd(ch, RESUMETX, 0, 0, 0, 0);
+
+			ch->statusflags &= ~TXSTOPPED;
+			memoff(ch);
+
+		} /* End transmit resume requested */
+
+		restore_flags(flags);
+
+	} /* End if channel valid */
+
+} /* End pc_start */
+
+/* ------------------------------------------------------------------
+	The below routines pc_throttle and pc_unthrottle are used 
+	to slow (And resume) the receipt of data into the kernels
+	receive buffers.  The exact occurrence of this depends on the
+	size of the kernels receive buffer and what the 'watermarks'
+	are set to for that buffer.  See the n_ttys.c file for more
+	details. 
+______________________________________________________________________ */
+/* --------------------- Begin throttle  ----------------------- */
+
+static void pc_throttle(struct tty_struct * tty)
+{ /* Begin pc_throttle */
+
+	struct channel *ch;
+	unsigned long flags;
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{ /* Begin if channel valid */
+
+
+		save_flags(flags);
+		cli();
+
+		if ((ch->statusflags & RXSTOPPED) == 0)
+		{
+			globalwinon(ch);
+			fepcmd(ch, PAUSERX, 0, 0, 0, 0);
+
+			ch->statusflags |= RXSTOPPED;
+			memoff(ch);
+		}
+		restore_flags(flags);
+
+	} /* End if channel valid */
+
+} /* End pc_throttle */
+
+/* --------------------- Begin unthrottle  ----------------------- */
+
+static void pc_unthrottle(struct tty_struct *tty)
+{ /* Begin pc_unthrottle */
+
+	struct channel *ch;
+	unsigned long flags;
+	volatile struct board_chan *bc;
+
+
+	/* ---------------------------------------------------------
+		verifyChannel returns the channel from the tty struct
+		if it is valid.  This serves as a sanity check.
+	------------------------------------------------------------- */
+
+	if ((ch = verifyChannel(tty)) != NULL) 
+	{ /* Begin if channel valid */
+
+
+		/* Just in case output was resumed because of a change in Digi-flow */
+		save_flags(flags);
+		cli();
+
+		if (ch->statusflags & RXSTOPPED) 
+		{
+
+			globalwinon(ch);
+			bc = ch->brdchan;
+			fepcmd(ch, RESUMERX, 0, 0, 0, 0);
+
+			ch->statusflags &= ~RXSTOPPED;
+			memoff(ch);
+		}
+		restore_flags(flags);
+
+	} /* End if channel valid */
+
+} /* End pc_unthrottle */
+
+/* --------------------- Begin digi_send_break  ----------------------- */
+
+void digi_send_break(struct channel *ch, int msec)
+{ /* Begin digi_send_break */
+
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+
+	/* -------------------------------------------------------------------- 
+	   Maybe I should send an infinite break here, schedule() for
+	   msec amount of time, and then stop the break.  This way,
+	   the user can't screw up the FEP by causing digi_send_break()
+	   to be called (i.e. via an ioctl()) more than once in msec amount 
+	   of time.  Try this for now...
+	------------------------------------------------------------------------ */
+
+	fepcmd(ch, SENDBREAK, msec, 0, 10, 0);
+	memoff(ch);
+
+	restore_flags(flags);
+
+} /* End digi_send_break */
+
+/* --------------------- Begin setup_empty_event  ----------------------- */
+
+static void setup_empty_event(struct tty_struct *tty, struct channel *ch)
+{ /* Begin setup_empty_event */
+
+	volatile struct board_chan *bc = ch->brdchan;
+	unsigned long int flags;
+
+	save_flags(flags);
+	cli();
+	globalwinon(ch);
+	ch->statusflags |= EMPTYWAIT;
+	
+	/* ------------------------------------------------------------------
+		When set the iempty flag request a event to be generated when the 
+		transmit buffer is empty (If there is no BREAK in progress).
+	--------------------------------------------------------------------- */
+
+	bc->iempty = 1;
+	memoff(ch);
+	restore_flags(flags);
+
+} /* End setup_empty_event */
+
+/* --------------------- Begin get_termio ----------------------- */
+
+static int get_termio(struct tty_struct * tty, struct termio __user * termio)
+{ /* Begin get_termio */
+	return kernel_termios_to_user_termio(termio, tty->termios);
+} /* End get_termio */
+/* ---------------------- Begin epca_setup  -------------------------- */
+void epca_setup(char *str, int *ints)
+{ /* Begin epca_setup */
+
+	struct board_info board;
+	int               index, loop, last;
+	char              *temp, *t2;
+	unsigned          len;
+
+	/* ----------------------------------------------------------------------
+		If this routine looks a little strange it is because it is only called
+		if a LILO append command is given to boot the kernel with parameters.  
+		In this way, we can provide the user a method of changing his board
+		configuration without rebuilding the kernel.
+	----------------------------------------------------------------------- */
+	if (!liloconfig) 
+		liloconfig = 1; 
+
+	memset(&board, 0, sizeof(board));
+
+	/* Assume the data is int first, later we can change it */
+	/* I think that array position 0 of ints holds the number of args */
+	for (last = 0, index = 1; index <= ints[0]; index++)
+		switch(index)
+		{ /* Begin parse switch */
+
+			case 1:
+				board.status = ints[index];
+				
+				/* ---------------------------------------------------------
+					We check for 2 (As opposed to 1; because 2 is a flag
+					instructing the driver to ignore epcaconfig.)  For this
+					reason we check for 2.
+				------------------------------------------------------------ */ 
+				if (board.status == 2)
+				{ /* Begin ignore epcaconfig as well as lilo cmd line */
+					nbdevs = 0;
+					num_cards = 0;
+					return;
+				} /* End ignore epcaconfig as well as lilo cmd line */
+	
+				if (board.status > 2)
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid board status 0x%x\n", board.status);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_BOARD_STATUS;
+					return;
+				}
+				last = index;
+				break;
+
+			case 2:
+				board.type = ints[index];
+				if (board.type >= PCIXEM) 
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid board type 0x%x\n", board.type);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_BOARD_TYPE;
+					return;
+				}
+				last = index;
+				break;
+
+			case 3:
+				board.altpin = ints[index];
+				if (board.altpin > 1)
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid board altpin 0x%x\n", board.altpin);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_ALTPIN;
+					return;
+				}
+				last = index;
+				break;
+
+			case 4:
+				board.numports = ints[index];
+				if ((board.numports < 2) || (board.numports > 256))
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid board numports 0x%x\n", board.numports);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_NUM_PORTS;
+					return;
+				}
+				nbdevs += board.numports;
+				last = index;
+				break;
+
+			case 5:
+				board.port = (unsigned char *)ints[index];
+				if (ints[index] <= 0)
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid io port 0x%x\n", (unsigned int)board.port);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_PORT_BASE;
+					return;
+				}
+				last = index;
+				break;
+
+			case 6:
+				board.membase = (unsigned char *)ints[index];
+				if (ints[index] <= 0)
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid memory base 0x%x\n",(unsigned int)board.membase);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_MEM_BASE;
+					return;
+				}
+				last = index;
+				break;
+
+			default:
+				printk(KERN_ERR "<Error> - epca_setup: Too many integer parms\n");
+				return;
+
+		} /* End parse switch */
+
+	while (str && *str) 
+	{ /* Begin while there is a string arg */
+
+		/* find the next comma or terminator */
+		temp = str;
+
+		/* While string is not null, and a comma hasn't been found */
+		while (*temp && (*temp != ','))
+			temp++;
+
+		if (!*temp)
+			temp = NULL;
+		else
+			*temp++ = 0;
+
+		/* Set index to the number of args + 1 */
+		index = last + 1;
+
+		switch(index)
+		{
+			case 1:
+				len = strlen(str);
+				if (strncmp("Disable", str, len) == 0) 
+					board.status = 0;
+				else
+				if (strncmp("Enable", str, len) == 0)
+					board.status = 1;
+				else
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid status %s\n", str);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_BOARD_STATUS;
+					return;
+				}
+				last = index;
+				break;
+
+			case 2:
+
+				for(loop = 0; loop < EPCA_NUM_TYPES; loop++)
+					if (strcmp(board_desc[loop], str) == 0)
+						break;
+
+
+				/* ---------------------------------------------------------------
+					If the index incremented above refers to a legitamate board 
+					type set it here. 
+				------------------------------------------------------------------*/
+
+				if (index < EPCA_NUM_TYPES) 
+					board.type = loop;
+				else
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid board type: %s\n", str);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_BOARD_TYPE;
+					return;
+				}
+				last = index;
+				break;
+
+			case 3:
+				len = strlen(str);
+				if (strncmp("Disable", str, len) == 0) 
+					board.altpin = 0;
+				else
+				if (strncmp("Enable", str, len) == 0)
+					board.altpin = 1;
+				else
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid altpin %s\n", str);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_ALTPIN;
+					return;
+				}
+				last = index;
+				break;
+
+			case 4:
+				t2 = str;
+				while (isdigit(*t2))
+					t2++;
+
+				if (*t2)
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid port count %s\n", str);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_NUM_PORTS;
+					return;
+				}
+
+				/* ------------------------------------------------------------
+					There is not a man page for simple_strtoul but the code can be 
+					found in vsprintf.c.  The first argument is the string to 
+					translate (To an unsigned long obviously),  the second argument
+					can be the address of any character variable or a NULL.  If a
+					variable is given, the end pointer of the string will be stored 
+					in that variable; if a NULL is given the end pointer will 
+					not be returned.  The last argument is the base to use.  If 
+					a 0 is indicated, the routine will attempt to determine the 
+					proper base by looking at the values prefix (A '0' for octal,
+					a 'x' for hex, etc ...  If a value is given it will use that 
+					value as the base. 
+				---------------------------------------------------------------- */ 
+				board.numports = simple_strtoul(str, NULL, 0);
+				nbdevs += board.numports;
+				last = index;
+				break;
+
+			case 5:
+				t2 = str;
+				while (isxdigit(*t2))
+					t2++;
+
+				if (*t2)
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid i/o address %s\n", str);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_PORT_BASE;
+					return;
+				}
+
+				board.port = (unsigned char *)simple_strtoul(str, NULL, 16);
+				last = index;
+				break;
+
+			case 6:
+				t2 = str;
+				while (isxdigit(*t2))
+					t2++;
+
+				if (*t2)
+				{
+					printk(KERN_ERR "<Error> - epca_setup: Invalid memory base %s\n",str);
+					invalid_lilo_config = 1;
+					setup_error_code |= INVALID_MEM_BASE;
+					return;
+				}
+
+				board.membase = (unsigned char *)simple_strtoul(str, NULL, 16);
+				last = index;
+				break;
+
+			default:
+				printk(KERN_ERR "PC/Xx: Too many string parms\n");
+				return;
+		}
+		str = temp;
+
+	} /* End while there is a string arg */
+
+
+	if (last < 6)  
+	{
+		printk(KERN_ERR "PC/Xx: Insufficient parms specified\n");
+		return;
+	}
+ 
+	/* I should REALLY validate the stuff here */
+
+	/* Copies our local copy of board into boards */
+	memcpy((void *)&boards[num_cards],(void *)&board, sizeof(board));
+
+
+	/* Does this get called once per lilo arg are what ? */
+
+	printk(KERN_INFO "PC/Xx: Added board %i, %s %i ports at 0x%4.4X base 0x%6.6X\n", 
+		num_cards, board_desc[board.type], 
+		board.numports, (int)board.port, (unsigned int) board.membase);
+
+	num_cards++;
+
+} /* End epca_setup */
+
+
+
+#ifdef ENABLE_PCI
+/* ------------------------ Begin init_PCI  --------------------------- */
+
+enum epic_board_types {
+	brd_xr = 0,
+	brd_xem,
+	brd_cx,
+	brd_xrj,
+};
+
+
+/* indexed directly by epic_board_types enum */
+static struct {
+	unsigned char board_type;
+	unsigned bar_idx;		/* PCI base address region */
+} epca_info_tbl[] = {
+	{ PCIXR, 0, },
+	{ PCIXEM, 0, },
+	{ PCICX, 0, },
+	{ PCIXRJ, 2, },
+};
+
+
+static int __devinit epca_init_one (struct pci_dev *pdev,
+				 const struct pci_device_id *ent)
+{
+	static int board_num = -1;
+	int board_idx, info_idx = ent->driver_data;
+	unsigned long addr;
+
+	if (pci_enable_device(pdev))
+		return -EIO;
+
+	board_num++;
+	board_idx = board_num + num_cards;
+	if (board_idx >= MAXBOARDS)
+		goto err_out;
+	
+	addr = pci_resource_start (pdev, epca_info_tbl[info_idx].bar_idx);
+	if (!addr) {
+		printk (KERN_ERR PFX "PCI region #%d not available (size 0)\n",
+			epca_info_tbl[info_idx].bar_idx);
+		goto err_out;
+	}
+
+	boards[board_idx].status = ENABLED;
+	boards[board_idx].type = epca_info_tbl[info_idx].board_type;
+	boards[board_idx].numports = 0x0;
+	boards[board_idx].port =
+		(unsigned char *)((char *) addr + PCI_IO_OFFSET);
+	boards[board_idx].membase =
+		(unsigned char *)((char *) addr);
+
+	if (!request_mem_region (addr + PCI_IO_OFFSET, 0x200000, "epca")) {
+		printk (KERN_ERR PFX "resource 0x%x @ 0x%lx unavailable\n",
+			0x200000, addr + PCI_IO_OFFSET);
+		goto err_out;
+	}
+
+	boards[board_idx].re_map_port = ioremap(addr + PCI_IO_OFFSET, 0x200000);
+	if (!boards[board_idx].re_map_port) {
+		printk (KERN_ERR PFX "cannot map 0x%x @ 0x%lx\n",
+			0x200000, addr + PCI_IO_OFFSET);
+		goto err_out_free_pciio;
+	}
+
+	if (!request_mem_region (addr, 0x200000, "epca")) {
+		printk (KERN_ERR PFX "resource 0x%x @ 0x%lx unavailable\n",
+			0x200000, addr);
+		goto err_out_free_iounmap;
+	}
+
+	boards[board_idx].re_map_membase = ioremap(addr, 0x200000);
+	if (!boards[board_idx].re_map_membase) {
+		printk (KERN_ERR PFX "cannot map 0x%x @ 0x%lx\n",
+			0x200000, addr + PCI_IO_OFFSET);
+		goto err_out_free_memregion;
+	}
+
+	/* --------------------------------------------------------------
+		I don't know what the below does, but the hardware guys say
+		its required on everything except PLX (In this case XRJ).
+	---------------------------------------------------------------- */
+	if (info_idx != brd_xrj) {
+		pci_write_config_byte(pdev, 0x40, 0);  
+		pci_write_config_byte(pdev, 0x46, 0);
+	}
+	
+	return 0;
+
+err_out_free_memregion:
+	release_mem_region (addr, 0x200000);
+err_out_free_iounmap:
+	iounmap (boards[board_idx].re_map_port);
+err_out_free_pciio:
+	release_mem_region (addr + PCI_IO_OFFSET, 0x200000);
+err_out:
+	return -ENODEV;
+}
+
+
+static struct pci_device_id epca_pci_tbl[] = {
+	{ PCI_VENDOR_DIGI, PCI_DEVICE_XR, PCI_ANY_ID, PCI_ANY_ID, 0, 0, brd_xr },
+	{ PCI_VENDOR_DIGI, PCI_DEVICE_XEM, PCI_ANY_ID, PCI_ANY_ID, 0, 0, brd_xem },
+	{ PCI_VENDOR_DIGI, PCI_DEVICE_CX, PCI_ANY_ID, PCI_ANY_ID, 0, 0, brd_cx },
+	{ PCI_VENDOR_DIGI, PCI_DEVICE_XRJ, PCI_ANY_ID, PCI_ANY_ID, 0, 0, brd_xrj },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, epca_pci_tbl);
+
+int __init init_PCI (void)
+{ /* Begin init_PCI */
+	memset (&epca_driver, 0, sizeof (epca_driver));
+	epca_driver.name = "epca";
+	epca_driver.id_table = epca_pci_tbl;
+	epca_driver.probe = epca_init_one;
+
+	return pci_register_driver(&epca_driver);
+} /* End init_PCI */
+
+#endif /* ENABLE_PCI */
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/epca.h b/drivers/char/epca.h
new file mode 100644
index 0000000..52205ef
--- /dev/null
+++ b/drivers/char/epca.h
@@ -0,0 +1,165 @@
+#define XEMPORTS    0xC02
+#define XEPORTS     0xC22
+
+#define MAX_ALLOC   0x100
+
+#define MAXBOARDS   12
+#define FEPCODESEG  0x0200L
+#define FEPCODE     0x2000L
+#define BIOSCODE    0xf800L
+
+#define MISCGLOBAL  0x0C00L
+#define NPORT       0x0C22L
+#define MBOX        0x0C40L
+#define PORTBASE    0x0C90L
+
+/* Begin code defines used for epca_setup */
+
+#define INVALID_BOARD_TYPE   0x1
+#define INVALID_NUM_PORTS    0x2
+#define INVALID_MEM_BASE     0x4
+#define INVALID_PORT_BASE    0x8
+#define INVALID_BOARD_STATUS 0x10
+#define INVALID_ALTPIN       0x20
+
+/* End code defines used for epca_setup */
+
+
+#define FEPCLR      0x00
+#define FEPMEM      0x02
+#define FEPRST      0x04
+#define FEPINT      0x08
+#define	FEPMASK     0x0e
+#define	FEPWIN      0x80
+
+#define PCXE    0
+#define PCXEVE  1
+#define PCXEM   2   
+#define EISAXEM 3
+#define PC64XE  4
+#define PCXI    5
+#define PCIXEM  7
+#define PCICX   8
+#define PCIXR   9
+#define PCIXRJ  10
+#define EPCA_NUM_TYPES 6
+
+
+static char *board_desc[] = 
+{
+	"PC/Xe",
+	"PC/Xeve",
+	"PC/Xem",
+	"EISA/Xem",
+	"PC/64Xe",
+	"PC/Xi",
+	"unknown",
+	"PCI/Xem",
+	"PCI/CX",
+	"PCI/Xr",
+	"PCI/Xrj",
+};
+
+#define STARTC      021
+#define STOPC       023
+#define IAIXON      0x2000
+
+
+#define TXSTOPPED  0x1
+#define LOWWAIT    0x2
+#define EMPTYWAIT  0x4
+#define RXSTOPPED  0x8
+#define TXBUSY     0x10
+
+#define DISABLED   0
+#define ENABLED    1
+#define OFF        0
+#define ON         1
+
+#define FEPTIMEOUT 200000  
+#define SERIAL_TYPE_NORMAL  1
+#define SERIAL_TYPE_INFO    3
+#define EPCA_EVENT_HANGUP   1
+#define EPCA_MAGIC          0x5c6df104L
+
+struct channel 
+{
+	long   magic;
+	unchar boardnum;
+	unchar channelnum;
+	unchar omodem;         /* FEP output modem status     */
+	unchar imodem;         /* FEP input modem status      */
+	unchar modemfake;      /* Modem values to be forced   */
+	unchar modem;          /* Force values                */
+	unchar hflow;
+	unchar dsr;
+	unchar dcd;
+	unchar m_rts ; 		/* The bits used in whatever FEP */
+	unchar m_dcd ;		/* is indiginous to this board to */
+	unchar m_dsr ;		/* represent each of the physical */
+	unchar m_cts ;		/* handshake lines */
+	unchar m_ri ;
+	unchar m_dtr ;
+	unchar stopc;
+	unchar startc;
+	unchar stopca;
+	unchar startca;
+	unchar fepstopc;
+	unchar fepstartc;
+	unchar fepstopca;
+	unchar fepstartca;
+	unchar txwin;
+	unchar rxwin;
+	ushort fepiflag;
+	ushort fepcflag;
+	ushort fepoflag;
+	ushort txbufhead;
+	ushort txbufsize;
+	ushort rxbufhead;
+	ushort rxbufsize;
+	int    close_delay;
+	int    count;
+	int    blocked_open;
+	ulong  event;
+	int    asyncflags;
+	uint   dev;
+	ulong  statusflags;
+	ulong  c_iflag;
+	ulong  c_cflag;
+	ulong  c_lflag;
+	ulong  c_oflag;
+	unchar *txptr;
+	unchar *rxptr;
+	unchar *tmp_buf;
+	struct board_info           *board;
+	volatile struct board_chan  *brdchan;
+	struct digi_struct          digiext;
+	struct tty_struct           *tty;
+	wait_queue_head_t           open_wait;
+	wait_queue_head_t           close_wait;
+	struct work_struct            tqueue;
+	volatile struct global_data *mailbox;
+};
+
+struct board_info	
+{
+	unchar status;
+	unchar type;
+	unchar altpin;
+	ushort numports;
+	unchar *port;
+	unchar *membase;
+	unchar __iomem *re_map_port;
+	unchar *re_map_membase;
+	ulong  memory_seg;
+	void ( * memwinon )	(struct board_info *, unsigned int) ;
+	void ( * memwinoff ) 	(struct board_info *, unsigned int) ;
+	void ( * globalwinon )	(struct channel *) ;
+	void ( * txwinon ) 	(struct channel *) ;
+	void ( * rxwinon )	(struct channel *) ;
+	void ( * memoff )	(struct channel *) ;
+	void ( * assertgwinon )	(struct channel *) ;
+	void ( * assertmemoff )	(struct channel *) ;
+	unchar poller_inhibited ;
+};
+
diff --git a/drivers/char/epcaconfig.h b/drivers/char/epcaconfig.h
new file mode 100644
index 0000000..55dec06
--- /dev/null
+++ b/drivers/char/epcaconfig.h
@@ -0,0 +1,7 @@
+#define NUMCARDS 0
+#define NBDEVS 0
+
+struct board_info static_boards[NUMCARDS]={
+};
+
+/* DO NOT HAND EDIT THIS FILE! */
diff --git a/drivers/char/esp.c b/drivers/char/esp.c
new file mode 100644
index 0000000..9f53d2fc
--- /dev/null
+++ b/drivers/char/esp.c
@@ -0,0 +1,2630 @@
+/*
+ *  esp.c - driver for Hayes ESP serial cards
+ *
+ *  --- Notices from serial.c, upon which this driver is based ---
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ *
+ *  Extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92.  Now
+ *  much more extensible to support other serial cards based on the
+ *  16450/16550A UART's.  Added support for the AST FourPort and the
+ *  Accent Async board.  
+ *
+ *  set_serial_info fixed to set the flags, custom divisor, and uart
+ * 	type fields.  Fix suggested by Michael K. Johnson 12/12/92.
+ *
+ *  11/95: TIOCMIWAIT, TIOCGICOUNT by Angelo Haritsis <ah@doc.ic.ac.uk>
+ *
+ *  03/96: Modularised by Angelo Haritsis <ah@doc.ic.ac.uk>
+ *
+ *  rs_set_termios fixed to look also for changes of the input
+ *      flags INPCK, BRKINT, PARMRK, IGNPAR and IGNBRK.
+ *                                            Bernd Anh�pl 05/17/96.
+ *
+ * --- End of notices from serial.c ---
+ *
+ * Support for the ESP serial card by Andrew J. Robinson
+ *     <arobinso@nyx.net> (Card detection routine taken from a patch
+ *     by Dennis J. Boylan).  Patches to allow use with 2.1.x contributed
+ *     by Chris Faylor.
+ *
+ * Most recent changes: (Andrew J. Robinson)
+ *   Support for PIO mode.  This allows the driver to work properly with
+ *     multiport cards.
+ *
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> -
+ * several cleanups, use module_init/module_exit, etc
+ *
+ * This module exports the following rs232 io functions:
+ *
+ *	int espserial_init(void);
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/serialP.h>
+#include <linux/serial_reg.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/bitops.h>
+
+#include <asm/dma.h>
+#include <linux/slab.h>
+#include <asm/uaccess.h>
+
+#include <linux/hayesesp.h>
+
+#define NR_PORTS 64	/* maximum number of ports */
+#define NR_PRIMARY 8	/* maximum number of primary ports */
+#define REGION_SIZE 8   /* size of io region to request */
+
+/* The following variables can be set by giving module options */
+static int irq[NR_PRIMARY];	/* IRQ for each base port */
+static unsigned int divisor[NR_PRIMARY]; /* custom divisor for each port */
+static unsigned int dma = ESP_DMA_CHANNEL; /* DMA channel */
+static unsigned int rx_trigger = ESP_RX_TRIGGER;
+static unsigned int tx_trigger = ESP_TX_TRIGGER;
+static unsigned int flow_off = ESP_FLOW_OFF;
+static unsigned int flow_on = ESP_FLOW_ON;
+static unsigned int rx_timeout = ESP_RX_TMOUT;
+static unsigned int pio_threshold = ESP_PIO_THRESHOLD;
+
+MODULE_LICENSE("GPL");
+
+module_param_array(irq, int, NULL, 0);
+module_param_array(divisor, uint, NULL, 0);
+module_param(dma, uint, 0);
+module_param(rx_trigger, uint, 0);
+module_param(tx_trigger, uint, 0);
+module_param(flow_off, uint, 0);
+module_param(flow_on, uint, 0);
+module_param(rx_timeout, uint, 0);
+module_param(pio_threshold, uint, 0);
+
+/* END */
+
+static char *dma_buffer;
+static int dma_bytes;
+static struct esp_pio_buffer *free_pio_buf;
+
+#define DMA_BUFFER_SZ 1024
+
+#define WAKEUP_CHARS 1024
+
+static char serial_name[] __initdata = "ESP serial driver";
+static char serial_version[] __initdata = "2.2";
+
+static struct tty_driver *esp_driver;
+
+/* serial subtype definitions */
+#define SERIAL_TYPE_NORMAL	1
+
+/*
+ * Serial driver configuration section.  Here are the various options:
+ *
+ * SERIAL_PARANOIA_CHECK
+ * 		Check the magic number for the esp_structure where
+ * 		ever possible.
+ */
+
+#undef SERIAL_PARANOIA_CHECK
+#define SERIAL_DO_RESTART
+
+#undef SERIAL_DEBUG_INTR
+#undef SERIAL_DEBUG_OPEN
+#undef SERIAL_DEBUG_FLOW
+
+#if defined(MODULE) && defined(SERIAL_DEBUG_MCOUNT)
+#define DBG_CNT(s) printk("(%s): [%x] refc=%d, serc=%d, ttyc=%d -> %s\n", \
+ tty->name, (info->flags), serial_driver.refcount,info->count,tty->count,s)
+#else
+#define DBG_CNT(s)
+#endif
+
+static struct esp_struct *ports;
+
+static void change_speed(struct esp_struct *info);
+static void rs_wait_until_sent(struct tty_struct *, int);
+
+/*
+ * The ESP card has a clock rate of 14.7456 MHz (that is, 2**ESPC_SCALE
+ * times the normal 1.8432 Mhz clock of most serial boards).
+ */
+#define BASE_BAUD ((1843200 / 16) * (1 << ESPC_SCALE))
+
+/* Standard COM flags (except for COM4, because of the 8514 problem) */
+#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST)
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write.  We need to
+ * lock it in case the memcpy_fromfs blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char *tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static inline int serial_paranoia_check(struct esp_struct *info,
+					char *name, const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+	static const char badmagic[] = KERN_WARNING
+		"Warning: bad magic number for serial struct (%s) in %s\n";
+	static const char badinfo[] = KERN_WARNING
+		"Warning: null esp_struct for (%s) in %s\n";
+
+	if (!info) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (info->magic != ESP_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+#endif
+	return 0;
+}
+
+static inline unsigned int serial_in(struct esp_struct *info, int offset)
+{
+	return inb(info->port + offset);
+}
+
+static inline void serial_out(struct esp_struct *info, int offset,
+			      unsigned char value)
+{
+	outb(value, info->port+offset);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_stop() and rs_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * They enable or disable transmitter interrupts, as necessary.
+ * ------------------------------------------------------------
+ */
+static void rs_stop(struct tty_struct *tty)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_stop"))
+		return;
+
+	spin_lock_irqsave(&info->lock, flags);
+	if (info->IER & UART_IER_THRI) {
+		info->IER &= ~UART_IER_THRI;
+		serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+		serial_out(info, UART_ESI_CMD2, info->IER);
+	}
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_start(struct tty_struct *tty)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (serial_paranoia_check(info, tty->name, "rs_start"))
+		return;
+	
+	spin_lock_irqsave(&info->lock, flags);
+	if (info->xmit_cnt && info->xmit_buf && !(info->IER & UART_IER_THRI)) {
+		info->IER |= UART_IER_THRI;
+		serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+		serial_out(info, UART_ESI_CMD2, info->IER);
+	}
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+/*
+ * ----------------------------------------------------------------------
+ *
+ * Here starts the interrupt handling routines.  All of the following
+ * subroutines are declared as inline and are folded into
+ * rs_interrupt().  They were separated out for readability's sake.
+ *
+ * Note: rs_interrupt() is a "fast" interrupt, which means that it
+ * runs with interrupts turned off.  People who may want to modify
+ * rs_interrupt() should try to keep the interrupt handler as fast as
+ * possible.  After you are done making modifications, it is not a bad
+ * idea to do:
+ * 
+ * gcc -S -DKERNEL -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer serial.c
+ *
+ * and look at the resulting assemble code in serial.s.
+ *
+ * 				- Ted Ts'o (tytso@mit.edu), 7-Mar-93
+ * -----------------------------------------------------------------------
+ */
+
+/*
+ * This routine is used by the interrupt handler to schedule
+ * processing in the software interrupt portion of the driver.
+ */
+static inline void rs_sched_event(struct esp_struct *info,
+				  int event)
+{
+	info->event |= 1 << event;
+	schedule_work(&info->tqueue);
+}
+
+static DEFINE_SPINLOCK(pio_lock);
+
+static inline struct esp_pio_buffer *get_pio_buffer(void)
+{
+	struct esp_pio_buffer *buf;
+	unsigned long flags;
+
+	spin_lock_irqsave(&pio_lock, flags);
+	if (free_pio_buf) {
+		buf = free_pio_buf;
+		free_pio_buf = buf->next;
+	} else {
+		buf = kmalloc(sizeof(struct esp_pio_buffer), GFP_ATOMIC);
+	}
+	spin_unlock_irqrestore(&pio_lock, flags);
+	return buf;
+}
+
+static inline void release_pio_buffer(struct esp_pio_buffer *buf)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&pio_lock, flags);
+	buf->next = free_pio_buf;
+	free_pio_buf = buf;
+	spin_unlock_irqrestore(&pio_lock, flags);
+}
+
+static inline void receive_chars_pio(struct esp_struct *info, int num_bytes)
+{
+	struct tty_struct *tty = info->tty;
+	int i;
+	struct esp_pio_buffer *pio_buf;
+	struct esp_pio_buffer *err_buf;
+	unsigned char status_mask;
+
+	pio_buf = get_pio_buffer();
+
+	if (!pio_buf)
+		return;
+
+	err_buf = get_pio_buffer();
+
+	if (!err_buf) {
+		release_pio_buffer(pio_buf);
+		return;
+	}
+
+	status_mask = (info->read_status_mask >> 2) & 0x07;
+		
+	for (i = 0; i < num_bytes - 1; i += 2) {
+		*((unsigned short *)(pio_buf->data + i)) =
+			inw(info->port + UART_ESI_RX);
+		err_buf->data[i] = serial_in(info, UART_ESI_RWS);
+		err_buf->data[i + 1] = (err_buf->data[i] >> 3) & status_mask;
+		err_buf->data[i] &= status_mask;
+	}
+
+	if (num_bytes & 0x0001) {
+		pio_buf->data[num_bytes - 1] = serial_in(info, UART_ESI_RX);
+		err_buf->data[num_bytes - 1] =
+			(serial_in(info, UART_ESI_RWS) >> 3) & status_mask;
+	}
+
+	/* make sure everything is still ok since interrupts were enabled */
+	tty = info->tty;
+
+	if (!tty) {
+		release_pio_buffer(pio_buf);
+		release_pio_buffer(err_buf);
+		info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
+		return;
+	}
+
+	status_mask = (info->ignore_status_mask >> 2) & 0x07;
+
+	for (i = 0; i < num_bytes; i++) {
+		if (!(err_buf->data[i] & status_mask)) {
+			*(tty->flip.char_buf_ptr++) = pio_buf->data[i];
+
+			if (err_buf->data[i] & 0x04) {
+				*(tty->flip.flag_buf_ptr++) = TTY_BREAK;
+
+				if (info->flags & ASYNC_SAK)
+					do_SAK(tty);
+			}
+			else if (err_buf->data[i] & 0x02)
+				*(tty->flip.flag_buf_ptr++) = TTY_FRAME;
+			else if (err_buf->data[i] & 0x01)
+				*(tty->flip.flag_buf_ptr++) = TTY_PARITY;
+			else
+				*(tty->flip.flag_buf_ptr++) = 0;
+		
+			tty->flip.count++;
+		}
+	}
+
+	schedule_delayed_work(&tty->flip.work, 1);
+
+	info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
+	release_pio_buffer(pio_buf);
+	release_pio_buffer(err_buf);
+}
+
+static inline void receive_chars_dma(struct esp_struct *info, int num_bytes)
+{
+	unsigned long flags;
+	info->stat_flags &= ~ESP_STAT_RX_TIMEOUT;
+	dma_bytes = num_bytes;
+	info->stat_flags |= ESP_STAT_DMA_RX;
+	
+	flags=claim_dma_lock();
+        disable_dma(dma);
+        clear_dma_ff(dma);
+        set_dma_mode(dma, DMA_MODE_READ);
+        set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
+        set_dma_count(dma, dma_bytes);
+        enable_dma(dma);
+        release_dma_lock(flags);
+        
+        serial_out(info, UART_ESI_CMD1, ESI_START_DMA_RX);
+}
+
+static inline void receive_chars_dma_done(struct esp_struct *info,
+					    int status)
+{
+	struct tty_struct *tty = info->tty;
+	int num_bytes;
+	unsigned long flags;
+	
+	
+	flags=claim_dma_lock();
+	disable_dma(dma);
+	clear_dma_ff(dma);
+
+	info->stat_flags &= ~ESP_STAT_DMA_RX;
+	num_bytes = dma_bytes - get_dma_residue(dma);
+	release_dma_lock(flags);
+	
+	info->icount.rx += num_bytes;
+
+	memcpy(tty->flip.char_buf_ptr, dma_buffer, num_bytes);
+	tty->flip.char_buf_ptr += num_bytes;
+	tty->flip.count += num_bytes;
+	memset(tty->flip.flag_buf_ptr, 0, num_bytes);
+	tty->flip.flag_buf_ptr += num_bytes;
+
+	if (num_bytes > 0) {
+		tty->flip.flag_buf_ptr--;
+
+		status &= (0x1c & info->read_status_mask);
+
+		if (status & info->ignore_status_mask) {
+			tty->flip.count--;
+			tty->flip.char_buf_ptr--;
+			tty->flip.flag_buf_ptr--;
+		} else if (status & 0x10) {
+			*tty->flip.flag_buf_ptr = TTY_BREAK;
+			(info->icount.brk)++;
+			if (info->flags & ASYNC_SAK)
+				do_SAK(tty);
+		} else if (status & 0x08) {
+			*tty->flip.flag_buf_ptr = TTY_FRAME;
+			(info->icount.frame)++;
+		}
+		else if (status & 0x04) {
+			*tty->flip.flag_buf_ptr = TTY_PARITY;
+			(info->icount.parity)++;
+		}
+
+		tty->flip.flag_buf_ptr++;
+		
+		schedule_delayed_work(&tty->flip.work, 1);
+	}
+
+	if (dma_bytes != num_bytes) {
+		num_bytes = dma_bytes - num_bytes;
+		dma_bytes = 0;
+		receive_chars_dma(info, num_bytes);
+	} else
+		dma_bytes = 0;
+}
+
+/* Caller must hold info->lock */
+
+static inline void transmit_chars_pio(struct esp_struct *info,
+					int space_avail)
+{
+	int i;
+	struct esp_pio_buffer *pio_buf;
+
+	pio_buf = get_pio_buffer();
+
+	if (!pio_buf)
+		return;
+
+	while (space_avail && info->xmit_cnt) {
+		if (info->xmit_tail + space_avail <= ESP_XMIT_SIZE) {
+			memcpy(pio_buf->data,
+			       &(info->xmit_buf[info->xmit_tail]),
+			       space_avail);
+		} else {
+			i = ESP_XMIT_SIZE - info->xmit_tail;
+			memcpy(pio_buf->data,
+			       &(info->xmit_buf[info->xmit_tail]), i);
+			memcpy(&(pio_buf->data[i]), info->xmit_buf,
+			       space_avail - i);
+		}
+
+		info->xmit_cnt -= space_avail;
+		info->xmit_tail = (info->xmit_tail + space_avail) &
+			(ESP_XMIT_SIZE - 1);
+
+		for (i = 0; i < space_avail - 1; i += 2) {
+			outw(*((unsigned short *)(pio_buf->data + i)),
+			     info->port + UART_ESI_TX);
+		}
+
+		if (space_avail & 0x0001)
+			serial_out(info, UART_ESI_TX,
+				   pio_buf->data[space_avail - 1]);
+
+		if (info->xmit_cnt) {
+			serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+			serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
+			space_avail = serial_in(info, UART_ESI_STAT1) << 8;
+			space_avail |= serial_in(info, UART_ESI_STAT2);
+
+			if (space_avail > info->xmit_cnt)
+				space_avail = info->xmit_cnt;
+		}
+	}
+
+	if (info->xmit_cnt < WAKEUP_CHARS) {
+		rs_sched_event(info, ESP_EVENT_WRITE_WAKEUP);
+
+#ifdef SERIAL_DEBUG_INTR
+		printk("THRE...");
+#endif
+
+		if (info->xmit_cnt <= 0) {
+			info->IER &= ~UART_IER_THRI;
+			serial_out(info, UART_ESI_CMD1,
+				   ESI_SET_SRV_MASK);
+			serial_out(info, UART_ESI_CMD2, info->IER);
+		}
+	}
+
+	release_pio_buffer(pio_buf);
+}
+
+/* Caller must hold info->lock */
+static inline void transmit_chars_dma(struct esp_struct *info, int num_bytes)
+{
+	unsigned long flags;
+	
+	dma_bytes = num_bytes;
+
+	if (info->xmit_tail + dma_bytes <= ESP_XMIT_SIZE) {
+		memcpy(dma_buffer, &(info->xmit_buf[info->xmit_tail]),
+		       dma_bytes);
+	} else {
+		int i = ESP_XMIT_SIZE - info->xmit_tail;
+		memcpy(dma_buffer, &(info->xmit_buf[info->xmit_tail]),
+			i);
+		memcpy(&(dma_buffer[i]), info->xmit_buf, dma_bytes - i);
+	}
+
+	info->xmit_cnt -= dma_bytes;
+	info->xmit_tail = (info->xmit_tail + dma_bytes) & (ESP_XMIT_SIZE - 1);
+
+	if (info->xmit_cnt < WAKEUP_CHARS) {
+		rs_sched_event(info, ESP_EVENT_WRITE_WAKEUP);
+
+#ifdef SERIAL_DEBUG_INTR
+		printk("THRE...");
+#endif
+
+		if (info->xmit_cnt <= 0) {
+			info->IER &= ~UART_IER_THRI;
+			serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+			serial_out(info, UART_ESI_CMD2, info->IER);
+		}
+	}
+
+	info->stat_flags |= ESP_STAT_DMA_TX;
+	
+	flags=claim_dma_lock();
+        disable_dma(dma);
+        clear_dma_ff(dma);
+        set_dma_mode(dma, DMA_MODE_WRITE);
+        set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
+        set_dma_count(dma, dma_bytes);
+        enable_dma(dma);
+        release_dma_lock(flags);
+        
+        serial_out(info, UART_ESI_CMD1, ESI_START_DMA_TX);
+}
+
+static inline void transmit_chars_dma_done(struct esp_struct *info)
+{
+	int num_bytes;
+	unsigned long flags;
+	
+
+	flags=claim_dma_lock();
+	disable_dma(dma);
+	clear_dma_ff(dma);
+
+	num_bytes = dma_bytes - get_dma_residue(dma);
+	info->icount.tx += dma_bytes;
+	release_dma_lock(flags);
+
+	if (dma_bytes != num_bytes) {
+		dma_bytes -= num_bytes;
+		memmove(dma_buffer, dma_buffer + num_bytes, dma_bytes);
+		
+		flags=claim_dma_lock();
+        	disable_dma(dma);
+        	clear_dma_ff(dma);
+        	set_dma_mode(dma, DMA_MODE_WRITE);
+        	set_dma_addr(dma, isa_virt_to_bus(dma_buffer));
+        	set_dma_count(dma, dma_bytes);
+        	enable_dma(dma);
+        	release_dma_lock(flags);
+        	
+        	serial_out(info, UART_ESI_CMD1, ESI_START_DMA_TX);
+	} else {
+		dma_bytes = 0;
+		info->stat_flags &= ~ESP_STAT_DMA_TX;
+	}
+}
+
+static inline void check_modem_status(struct esp_struct *info)
+{
+	int	status;
+	
+	serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
+	status = serial_in(info, UART_ESI_STAT2);
+
+	if (status & UART_MSR_ANY_DELTA) {
+		/* update input line counters */
+		if (status & UART_MSR_TERI)
+			info->icount.rng++;
+		if (status & UART_MSR_DDSR)
+			info->icount.dsr++;
+		if (status & UART_MSR_DDCD)
+			info->icount.dcd++;
+		if (status & UART_MSR_DCTS)
+			info->icount.cts++;
+		wake_up_interruptible(&info->delta_msr_wait);
+	}
+
+	if ((info->flags & ASYNC_CHECK_CD) && (status & UART_MSR_DDCD)) {
+#if (defined(SERIAL_DEBUG_OPEN) || defined(SERIAL_DEBUG_INTR))
+		printk("ttys%d CD now %s...", info->line,
+		       (status & UART_MSR_DCD) ? "on" : "off");
+#endif		
+		if (status & UART_MSR_DCD)
+			wake_up_interruptible(&info->open_wait);
+		else {
+#ifdef SERIAL_DEBUG_OPEN
+			printk("scheduling hangup...");
+#endif
+			schedule_work(&info->tqueue_hangup);
+		}
+	}
+}
+
+/*
+ * This is the serial driver's interrupt routine
+ */
+static irqreturn_t rs_interrupt_single(int irq, void *dev_id,
+					struct pt_regs *regs)
+{
+	struct esp_struct * info;
+	unsigned err_status;
+	unsigned int scratch;
+
+#ifdef SERIAL_DEBUG_INTR
+	printk("rs_interrupt_single(%d)...", irq);
+#endif
+	info = (struct esp_struct *)dev_id;
+	err_status = 0;
+	scratch = serial_in(info, UART_ESI_SID);
+
+	spin_lock(&info->lock);
+	
+	if (!info->tty) {
+		spin_unlock(&info->lock);
+		return IRQ_NONE;
+	}
+
+	if (scratch & 0x04) { /* error */
+		serial_out(info, UART_ESI_CMD1, ESI_GET_ERR_STAT);
+		err_status = serial_in(info, UART_ESI_STAT1);
+		serial_in(info, UART_ESI_STAT2);
+
+		if (err_status & 0x01)
+			info->stat_flags |= ESP_STAT_RX_TIMEOUT;
+
+		if (err_status & 0x20) /* UART status */
+			check_modem_status(info);
+
+		if (err_status & 0x80) /* Start break */
+			wake_up_interruptible(&info->break_wait);
+	}
+		
+	if ((scratch & 0x88) || /* DMA completed or timed out */
+	    (err_status & 0x1c) /* receive error */) {
+		if (info->stat_flags & ESP_STAT_DMA_RX)
+			receive_chars_dma_done(info, err_status);
+		else if (info->stat_flags & ESP_STAT_DMA_TX)
+			transmit_chars_dma_done(info);
+	}
+
+	if (!(info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) &&
+	    ((scratch & 0x01) || (info->stat_flags & ESP_STAT_RX_TIMEOUT)) &&
+	    (info->IER & UART_IER_RDI)) {
+		int num_bytes;
+
+		serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+		serial_out(info, UART_ESI_CMD1, ESI_GET_RX_AVAIL);
+		num_bytes = serial_in(info, UART_ESI_STAT1) << 8;
+		num_bytes |= serial_in(info, UART_ESI_STAT2);
+
+		if (num_bytes > (TTY_FLIPBUF_SIZE - info->tty->flip.count))
+		  num_bytes = TTY_FLIPBUF_SIZE - info->tty->flip.count;
+
+		if (num_bytes) {
+			if (dma_bytes ||
+			    (info->stat_flags & ESP_STAT_USE_PIO) ||
+			    (num_bytes <= info->config.pio_threshold))
+				receive_chars_pio(info, num_bytes);
+			else
+				receive_chars_dma(info, num_bytes);
+		}
+	}
+	
+	if (!(info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) &&
+	    (scratch & 0x02) && (info->IER & UART_IER_THRI)) {
+		if ((info->xmit_cnt <= 0) || info->tty->stopped) {
+			info->IER &= ~UART_IER_THRI;
+			serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+			serial_out(info, UART_ESI_CMD2, info->IER);
+		} else {
+			int num_bytes;
+
+			serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+			serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
+			num_bytes = serial_in(info, UART_ESI_STAT1) << 8;
+			num_bytes |= serial_in(info, UART_ESI_STAT2);
+
+			if (num_bytes > info->xmit_cnt)
+				num_bytes = info->xmit_cnt;
+
+			if (num_bytes) {
+				if (dma_bytes ||
+				    (info->stat_flags & ESP_STAT_USE_PIO) ||
+				    (num_bytes <= info->config.pio_threshold))
+					transmit_chars_pio(info, num_bytes);
+				else
+					transmit_chars_dma(info, num_bytes);
+			}
+		}
+	}
+
+	info->last_active = jiffies;
+
+#ifdef SERIAL_DEBUG_INTR
+	printk("end.\n");
+#endif
+	spin_unlock(&info->lock);
+	return IRQ_HANDLED;
+}
+
+/*
+ * -------------------------------------------------------------------
+ * Here ends the serial interrupt routines.
+ * -------------------------------------------------------------------
+ */
+
+static void do_softint(void *private_)
+{
+	struct esp_struct	*info = (struct esp_struct *) private_;
+	struct tty_struct	*tty;
+	
+	tty = info->tty;
+	if (!tty)
+		return;
+
+	if (test_and_clear_bit(ESP_EVENT_WRITE_WAKEUP, &info->event)) {
+		tty_wakeup(tty);
+	}
+}
+
+/*
+ * This routine is called from the scheduler tqueue when the interrupt
+ * routine has signalled that a hangup has occurred.  The path of
+ * hangup processing is:
+ *
+ * 	serial interrupt routine -> (scheduler tqueue) ->
+ * 	do_serial_hangup() -> tty->hangup() -> esp_hangup()
+ * 
+ */
+static void do_serial_hangup(void *private_)
+{
+	struct esp_struct	*info = (struct esp_struct *) private_;
+	struct tty_struct	*tty;
+	
+	tty = info->tty;
+	if (tty)
+		tty_hangup(tty);
+}
+
+/*
+ * ---------------------------------------------------------------
+ * Low level utility subroutines for the serial driver:  routines to
+ * figure out the appropriate timeout for an interrupt chain, routines
+ * to initialize and startup a serial port, and routines to shutdown a
+ * serial port.  Useful stuff like that.
+ *
+ * Caller should hold lock
+ * ---------------------------------------------------------------
+ */
+
+static inline void esp_basic_init(struct esp_struct * info)
+{
+	/* put ESPC in enhanced mode */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_MODE);
+	
+	if (info->stat_flags & ESP_STAT_NEVER_DMA)
+		serial_out(info, UART_ESI_CMD2, 0x01);
+	else
+		serial_out(info, UART_ESI_CMD2, 0x31);
+
+	/* disable interrupts for now */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+	serial_out(info, UART_ESI_CMD2, 0x00);
+
+	/* set interrupt and DMA channel */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_IRQ);
+
+	if (info->stat_flags & ESP_STAT_NEVER_DMA)
+		serial_out(info, UART_ESI_CMD2, 0x01);
+	else
+		serial_out(info, UART_ESI_CMD2, (dma << 4) | 0x01);
+
+	serial_out(info, UART_ESI_CMD1, ESI_SET_ENH_IRQ);
+
+	if (info->line % 8)	/* secondary port */
+		serial_out(info, UART_ESI_CMD2, 0x0d);	/* shared */
+	else if (info->irq == 9)
+		serial_out(info, UART_ESI_CMD2, 0x02);
+	else
+		serial_out(info, UART_ESI_CMD2, info->irq);
+
+	/* set error status mask (check this) */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_ERR_MASK);
+
+	if (info->stat_flags & ESP_STAT_NEVER_DMA)
+		serial_out(info, UART_ESI_CMD2, 0xa1);
+	else
+		serial_out(info, UART_ESI_CMD2, 0xbd);
+
+	serial_out(info, UART_ESI_CMD2, 0x00);
+
+	/* set DMA timeout */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_DMA_TMOUT);
+	serial_out(info, UART_ESI_CMD2, 0xff);
+
+	/* set FIFO trigger levels */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_TRIGGER);
+	serial_out(info, UART_ESI_CMD2, info->config.rx_trigger >> 8);
+	serial_out(info, UART_ESI_CMD2, info->config.rx_trigger);
+	serial_out(info, UART_ESI_CMD2, info->config.tx_trigger >> 8);
+	serial_out(info, UART_ESI_CMD2, info->config.tx_trigger);
+
+	/* Set clock scaling and wait states */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_PRESCALAR);
+	serial_out(info, UART_ESI_CMD2, 0x04 | ESPC_SCALE);
+
+	/* set reinterrupt pacing */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_REINTR);
+	serial_out(info, UART_ESI_CMD2, 0xff);
+}
+
+static int startup(struct esp_struct * info)
+{
+	unsigned long flags;
+	int	retval=0;
+        unsigned int num_chars;
+
+        spin_lock_irqsave(&info->lock, flags);
+
+	if (info->flags & ASYNC_INITIALIZED)
+		goto out;
+
+	if (!info->xmit_buf) {
+		info->xmit_buf = (unsigned char *)get_zeroed_page(GFP_ATOMIC);
+		retval = -ENOMEM;
+		if (!info->xmit_buf)
+			goto out;
+	}
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("starting up ttys%d (irq %d)...", info->line, info->irq);
+#endif
+
+	/* Flush the RX buffer.  Using the ESI flush command may cause */
+	/* wild interrupts, so read all the data instead. */
+
+	serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+	serial_out(info, UART_ESI_CMD1, ESI_GET_RX_AVAIL);
+	num_chars = serial_in(info, UART_ESI_STAT1) << 8;
+	num_chars |= serial_in(info, UART_ESI_STAT2);
+
+	while (num_chars > 1) {
+		inw(info->port + UART_ESI_RX);
+		num_chars -= 2;
+	}
+
+	if (num_chars)
+		serial_in(info, UART_ESI_RX);
+
+	/* set receive character timeout */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
+	serial_out(info, UART_ESI_CMD2, info->config.rx_timeout);
+
+	/* clear all flags except the "never DMA" flag */
+	info->stat_flags &= ESP_STAT_NEVER_DMA;
+
+	if (info->stat_flags & ESP_STAT_NEVER_DMA)
+		info->stat_flags |= ESP_STAT_USE_PIO;
+
+	spin_unlock_irqrestore(&info->lock, flags);
+
+	/*
+	 * Allocate the IRQ
+	 */
+
+	retval = request_irq(info->irq, rs_interrupt_single, SA_SHIRQ,
+			     "esp serial", info);
+
+	if (retval) {
+		if (capable(CAP_SYS_ADMIN)) {
+			if (info->tty)
+				set_bit(TTY_IO_ERROR,
+					&info->tty->flags);
+			retval = 0;
+		}
+		goto out_unlocked;
+	}
+
+	if (!(info->stat_flags & ESP_STAT_USE_PIO) && !dma_buffer) {
+		dma_buffer = (char *)__get_dma_pages(
+			GFP_KERNEL, get_order(DMA_BUFFER_SZ));
+
+		/* use PIO mode if DMA buf/chan cannot be allocated */
+		if (!dma_buffer)
+			info->stat_flags |= ESP_STAT_USE_PIO;
+		else if (request_dma(dma, "esp serial")) {
+			free_pages((unsigned long)dma_buffer,
+				   get_order(DMA_BUFFER_SZ));
+			dma_buffer = NULL;
+			info->stat_flags |= ESP_STAT_USE_PIO;
+		}
+			
+	}
+
+	info->MCR = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2;
+
+	spin_lock_irqsave(&info->lock, flags);
+	serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+	serial_out(info, UART_ESI_CMD2, UART_MCR);
+	serial_out(info, UART_ESI_CMD2, info->MCR);
+	
+	/*
+	 * Finally, enable interrupts
+	 */
+	/* info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI; */
+	info->IER = UART_IER_RLSI | UART_IER_RDI | UART_IER_DMA_TMOUT |
+			UART_IER_DMA_TC;
+	serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+	serial_out(info, UART_ESI_CMD2, info->IER);
+	
+	if (info->tty)
+		clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+	spin_unlock_irqrestore(&info->lock, flags);
+
+	/*
+	 * Set up the tty->alt_speed kludge
+	 */
+	if (info->tty) {
+		if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			info->tty->alt_speed = 57600;
+		if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			info->tty->alt_speed = 115200;
+		if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+			info->tty->alt_speed = 230400;
+		if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+			info->tty->alt_speed = 460800;
+	}
+	
+	/*
+	 * set the speed of the serial port
+	 */
+	change_speed(info);
+	info->flags |= ASYNC_INITIALIZED;
+	return 0;
+
+out:
+	spin_unlock_irqrestore(&info->lock, flags);
+out_unlocked:
+	return retval;
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts are disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void shutdown(struct esp_struct * info)
+{
+	unsigned long	flags, f;
+
+	if (!(info->flags & ASYNC_INITIALIZED))
+		return;
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("Shutting down serial port %d (irq %d)....", info->line,
+	       info->irq);
+#endif
+	
+	spin_lock_irqsave(&info->lock, flags);
+	/*
+	 * 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(&info->delta_msr_wait);
+	wake_up_interruptible(&info->break_wait);
+
+	/* stop a DMA transfer on the port being closed */
+	/* DMA lock is higher priority always */
+	if (info->stat_flags & (ESP_STAT_DMA_RX | ESP_STAT_DMA_TX)) {
+		f=claim_dma_lock();
+		disable_dma(dma);
+		clear_dma_ff(dma);
+		release_dma_lock(f);
+		
+		dma_bytes = 0;
+	}
+	
+	/*
+	 * Free the IRQ
+	 */
+	free_irq(info->irq, info);
+
+	if (dma_buffer) {
+		struct esp_struct *current_port = ports;
+
+		while (current_port) {
+			if ((current_port != info) &&
+			    (current_port->flags & ASYNC_INITIALIZED))
+				break;
+
+			current_port = current_port->next_port;
+		}
+
+		if (!current_port) {
+			free_dma(dma);
+			free_pages((unsigned long)dma_buffer,
+				   get_order(DMA_BUFFER_SZ));
+			dma_buffer = NULL;
+		}		
+	}
+
+	if (info->xmit_buf) {
+		free_page((unsigned long) info->xmit_buf);
+		info->xmit_buf = NULL;
+	}
+
+	info->IER = 0;
+	serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+	serial_out(info, UART_ESI_CMD2, 0x00);
+
+	if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
+		info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
+
+	info->MCR &= ~UART_MCR_OUT2;
+	serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+	serial_out(info, UART_ESI_CMD2, UART_MCR);
+	serial_out(info, UART_ESI_CMD2, info->MCR);
+
+	if (info->tty)
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+	
+	info->flags &= ~ASYNC_INITIALIZED;
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+static void change_speed(struct esp_struct *info)
+{
+	unsigned short port;
+	int	quot = 0;
+	unsigned cflag,cval;
+	int	baud, bits;
+	unsigned char flow1 = 0, flow2 = 0;
+	unsigned long flags;
+
+	if (!info->tty || !info->tty->termios)
+		return;
+	cflag = info->tty->termios->c_cflag;
+	port = info->port;
+	
+	/* byte size and parity */
+	switch (cflag & CSIZE) {
+	      case CS5: cval = 0x00; bits = 7; break;
+	      case CS6: cval = 0x01; bits = 8; break;
+	      case CS7: cval = 0x02; bits = 9; break;
+	      case CS8: cval = 0x03; bits = 10; break;
+	      default:  cval = 0x00; bits = 7; break;
+	}
+	if (cflag & CSTOPB) {
+		cval |= 0x04;
+		bits++;
+	}
+	if (cflag & PARENB) {
+		cval |= UART_LCR_PARITY;
+		bits++;
+	}
+	if (!(cflag & PARODD))
+		cval |= UART_LCR_EPAR;
+#ifdef CMSPAR
+	if (cflag & CMSPAR)
+		cval |= UART_LCR_SPAR;
+#endif
+
+	baud = tty_get_baud_rate(info->tty);
+	if (baud == 38400 &&
+	    ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST))
+		quot = info->custom_divisor;
+	else {
+		if (baud == 134)
+			/* Special case since 134 is really 134.5 */
+			quot = (2*BASE_BAUD / 269);
+		else if (baud)
+			quot = BASE_BAUD / baud;
+	}
+	/* If the quotient is ever zero, default to 9600 bps */
+	if (!quot)
+		quot = BASE_BAUD / 9600;
+	
+	info->timeout = ((1024 * HZ * bits * quot) / BASE_BAUD) + (HZ / 50);
+
+	/* CTS flow control flag and modem status interrupts */
+	/* info->IER &= ~UART_IER_MSI; */
+	if (cflag & CRTSCTS) {
+		info->flags |= ASYNC_CTS_FLOW;
+		/* info->IER |= UART_IER_MSI; */
+		flow1 = 0x04;
+		flow2 = 0x10;
+	} else
+		info->flags &= ~ASYNC_CTS_FLOW;
+	if (cflag & CLOCAL)
+		info->flags &= ~ASYNC_CHECK_CD;
+	else {
+		info->flags |= ASYNC_CHECK_CD;
+		/* info->IER |= UART_IER_MSI; */
+	}
+
+	/*
+	 * Set up parity check flag
+	 */
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+	info->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+	if (I_INPCK(info->tty))
+		info->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+	if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+		info->read_status_mask |= UART_LSR_BI;
+	
+	info->ignore_status_mask = 0;
+#if 0
+	/* This should be safe, but for some broken bits of hardware... */
+	if (I_IGNPAR(info->tty)) {
+		info->ignore_status_mask |= UART_LSR_PE | UART_LSR_FE;
+		info->read_status_mask |= UART_LSR_PE | UART_LSR_FE;
+	}
+#endif
+	if (I_IGNBRK(info->tty)) {
+		info->ignore_status_mask |= UART_LSR_BI;
+		info->read_status_mask |= UART_LSR_BI;
+		/*
+		 * If we're ignore parity and break indicators, ignore 
+		 * overruns too.  (For real raw support).
+		 */
+		if (I_IGNPAR(info->tty)) {
+			info->ignore_status_mask |= UART_LSR_OE | \
+				UART_LSR_PE | UART_LSR_FE;
+			info->read_status_mask |= UART_LSR_OE | \
+				UART_LSR_PE | UART_LSR_FE;
+		}
+	}
+
+	if (I_IXOFF(info->tty))
+		flow1 |= 0x81;
+
+	spin_lock_irqsave(&info->lock, flags);
+	/* set baud */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_BAUD);
+	serial_out(info, UART_ESI_CMD2, quot >> 8);
+	serial_out(info, UART_ESI_CMD2, quot & 0xff);
+
+	/* set data bits, parity, etc. */
+	serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+	serial_out(info, UART_ESI_CMD2, UART_LCR);
+	serial_out(info, UART_ESI_CMD2, cval);
+
+	/* Enable flow control */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_CNTL);
+	serial_out(info, UART_ESI_CMD2, flow1);
+	serial_out(info, UART_ESI_CMD2, flow2);
+
+	/* set flow control characters (XON/XOFF only) */
+	if (I_IXOFF(info->tty)) {
+		serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_CHARS);
+		serial_out(info, UART_ESI_CMD2, START_CHAR(info->tty));
+		serial_out(info, UART_ESI_CMD2, STOP_CHAR(info->tty));
+		serial_out(info, UART_ESI_CMD2, 0x10);
+		serial_out(info, UART_ESI_CMD2, 0x21);
+		switch (cflag & CSIZE) {
+			case CS5:
+				serial_out(info, UART_ESI_CMD2, 0x1f);
+				break;
+			case CS6:
+				serial_out(info, UART_ESI_CMD2, 0x3f);
+				break;
+			case CS7:
+			case CS8:
+				serial_out(info, UART_ESI_CMD2, 0x7f);
+				break;
+			default:
+				serial_out(info, UART_ESI_CMD2, 0xff);
+				break;
+		}
+	}
+
+	/* Set high/low water */
+	serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_LVL);
+	serial_out(info, UART_ESI_CMD2, info->config.flow_off >> 8);
+	serial_out(info, UART_ESI_CMD2, info->config.flow_off);
+	serial_out(info, UART_ESI_CMD2, info->config.flow_on >> 8);
+	serial_out(info, UART_ESI_CMD2, info->config.flow_on);
+
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_put_char"))
+		return;
+
+	if (!tty || !info->xmit_buf)
+		return;
+
+	spin_lock_irqsave(&info->lock, flags);
+	if (info->xmit_cnt < ESP_XMIT_SIZE - 1) {
+		info->xmit_buf[info->xmit_head++] = ch;
+		info->xmit_head &= ESP_XMIT_SIZE-1;
+		info->xmit_cnt++;
+	}
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_flush_chars(struct tty_struct *tty)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+				
+	if (serial_paranoia_check(info, tty->name, "rs_flush_chars"))
+		return;
+
+	spin_lock_irqsave(&info->lock, flags);
+
+	if (info->xmit_cnt <= 0 || tty->stopped || !info->xmit_buf)
+		goto out;
+
+	if (!(info->IER & UART_IER_THRI)) {
+		info->IER |= UART_IER_THRI;
+		serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+		serial_out(info, UART_ESI_CMD2, info->IER);
+	}
+out:
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static int rs_write(struct tty_struct * tty,
+		    const unsigned char *buf, int count)
+{
+	int	c, t, ret = 0;
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_write"))
+		return 0;
+
+	if (!tty || !info->xmit_buf || !tmp_buf)
+		return 0;
+	    
+	while (1) {
+		/* Thanks to R. Wolff for suggesting how to do this with */
+		/* interrupts enabled */
+
+		c = count;
+		t = ESP_XMIT_SIZE - info->xmit_cnt - 1;
+		
+		if (t < c)
+			c = t;
+
+		t = ESP_XMIT_SIZE - info->xmit_head;
+		
+		if (t < c)
+			c = t;
+
+		if (c <= 0)
+			break;
+
+		memcpy(info->xmit_buf + info->xmit_head, buf, c);
+
+		info->xmit_head = (info->xmit_head + c) & (ESP_XMIT_SIZE-1);
+		info->xmit_cnt += c;
+		buf += c;
+		count -= c;
+		ret += c;
+	}
+
+	spin_lock_irqsave(&info->lock, flags);
+
+	if (info->xmit_cnt && !tty->stopped && !(info->IER & UART_IER_THRI)) {
+		info->IER |= UART_IER_THRI;
+		serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+		serial_out(info, UART_ESI_CMD2, info->IER);
+	}
+
+	spin_unlock_irqrestore(&info->lock, flags);
+	return ret;
+}
+
+static int rs_write_room(struct tty_struct *tty)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	int	ret;
+	unsigned long flags;
+				
+	if (serial_paranoia_check(info, tty->name, "rs_write_room"))
+		return 0;
+
+	spin_lock_irqsave(&info->lock, flags);
+
+	ret = ESP_XMIT_SIZE - info->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+	spin_unlock_irqrestore(&info->lock, flags);
+	return ret;
+}
+
+static int rs_chars_in_buffer(struct tty_struct *tty)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+				
+	if (serial_paranoia_check(info, tty->name, "rs_chars_in_buffer"))
+		return 0;
+	return info->xmit_cnt;
+}
+
+static void rs_flush_buffer(struct tty_struct *tty)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+				
+	if (serial_paranoia_check(info, tty->name, "rs_flush_buffer"))
+		return;
+	spin_lock_irqsave(&info->lock, flags);
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+	spin_unlock_irqrestore(&info->lock, flags);
+	tty_wakeup(tty);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_throttle()
+ * 
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled.
+ * ------------------------------------------------------------
+ */
+static void rs_throttle(struct tty_struct * tty)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+#ifdef SERIAL_DEBUG_THROTTLE
+	char	buf[64];
+	
+	printk("throttle %s: %d....\n", tty_name(tty, buf),
+	       tty->ldisc.chars_in_buffer(tty));
+#endif
+
+	if (serial_paranoia_check(info, tty->name, "rs_throttle"))
+		return;
+
+	spin_lock_irqsave(&info->lock, flags);
+	info->IER &= ~UART_IER_RDI;
+	serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+	serial_out(info, UART_ESI_CMD2, info->IER);
+	serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
+	serial_out(info, UART_ESI_CMD2, 0x00);
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_unthrottle(struct tty_struct * tty)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+#ifdef SERIAL_DEBUG_THROTTLE
+	char	buf[64];
+	
+	printk("unthrottle %s: %d....\n", tty_name(tty, buf),
+	       tty->ldisc.chars_in_buffer(tty));
+#endif
+
+	if (serial_paranoia_check(info, tty->name, "rs_unthrottle"))
+		return;
+	
+	spin_lock_irqsave(&info->lock, flags);
+	info->IER |= UART_IER_RDI;
+	serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+	serial_out(info, UART_ESI_CMD2, info->IER);
+	serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
+	serial_out(info, UART_ESI_CMD2, info->config.rx_timeout);
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_ioctl() and friends
+ * ------------------------------------------------------------
+ */
+
+static int get_serial_info(struct esp_struct * info,
+			   struct serial_struct __user *retinfo)
+{
+	struct serial_struct tmp;
+  
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.type = PORT_16550A;
+	tmp.line = info->line;
+	tmp.port = info->port;
+	tmp.irq = info->irq;
+	tmp.flags = info->flags;
+	tmp.xmit_fifo_size = 1024;
+	tmp.baud_base = BASE_BAUD;
+	tmp.close_delay = info->close_delay;
+	tmp.closing_wait = info->closing_wait;
+	tmp.custom_divisor = info->custom_divisor;
+	tmp.hub6 = 0;
+	if (copy_to_user(retinfo,&tmp,sizeof(*retinfo)))
+		return -EFAULT;
+	return 0;
+}
+
+static int get_esp_config(struct esp_struct * info,
+			  struct hayes_esp_config __user *retinfo)
+{
+	struct hayes_esp_config tmp;
+  
+	if (!retinfo)
+		return -EFAULT;
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.rx_timeout = info->config.rx_timeout;
+	tmp.rx_trigger = info->config.rx_trigger;
+	tmp.tx_trigger = info->config.tx_trigger;
+	tmp.flow_off = info->config.flow_off;
+	tmp.flow_on = info->config.flow_on;
+	tmp.pio_threshold = info->config.pio_threshold;
+	tmp.dma_channel = (info->stat_flags & ESP_STAT_NEVER_DMA ? 0 : dma);
+
+	return copy_to_user(retinfo, &tmp, sizeof(*retinfo)) ? -EFAULT : 0;
+}
+
+static int set_serial_info(struct esp_struct * info,
+			   struct serial_struct __user *new_info)
+{
+	struct serial_struct new_serial;
+	struct esp_struct old_info;
+	unsigned int change_irq;
+	int retval = 0;
+	struct esp_struct *current_async;
+
+	if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+		return -EFAULT;
+	old_info = *info;
+
+	if ((new_serial.type != PORT_16550A) ||
+	    (new_serial.hub6) ||
+	    (info->port != new_serial.port) ||
+	    (new_serial.baud_base != BASE_BAUD) ||
+	    (new_serial.irq > 15) ||
+	    (new_serial.irq < 2) ||
+	    (new_serial.irq == 6) ||
+	    (new_serial.irq == 8) ||
+	    (new_serial.irq == 13))
+		return -EINVAL;
+
+	change_irq = new_serial.irq != info->irq;
+
+	if (change_irq && (info->line % 8))
+		return -EINVAL;
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if (change_irq || 
+		    (new_serial.close_delay != info->close_delay) ||
+		    ((new_serial.flags & ~ASYNC_USR_MASK) !=
+		     (info->flags & ~ASYNC_USR_MASK)))
+			return -EPERM;
+		info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+			       (new_serial.flags & ASYNC_USR_MASK));
+		info->custom_divisor = new_serial.custom_divisor;
+	} else {
+		if (new_serial.irq == 2)
+			new_serial.irq = 9;
+
+		if (change_irq) {
+			current_async = ports;
+
+			while (current_async) {
+				if ((current_async->line >= info->line) &&
+				    (current_async->line < (info->line + 8))) {
+					if (current_async == info) {
+						if (current_async->count > 1)
+							return -EBUSY;
+					} else if (current_async->count)
+						return -EBUSY;
+				}
+
+				current_async = current_async->next_port;
+			}
+		}
+
+		/*
+		 * OK, past this point, all the error checking has been done.
+		 * At this point, we start making changes.....
+		 */
+
+		info->flags = ((info->flags & ~ASYNC_FLAGS) |
+			       (new_serial.flags & ASYNC_FLAGS));
+		info->custom_divisor = new_serial.custom_divisor;
+		info->close_delay = new_serial.close_delay * HZ/100;
+		info->closing_wait = new_serial.closing_wait * HZ/100;
+
+		if (change_irq) {
+			/*
+			 * We need to shutdown the serial port at the old
+			 * port/irq combination.
+			 */
+			shutdown(info);
+
+			current_async = ports;
+
+			while (current_async) {
+				if ((current_async->line >= info->line) &&
+				    (current_async->line < (info->line + 8)))
+					current_async->irq = new_serial.irq;
+
+				current_async = current_async->next_port;
+			}
+
+			serial_out(info, UART_ESI_CMD1, ESI_SET_ENH_IRQ);
+			if (info->irq == 9)
+				serial_out(info, UART_ESI_CMD2, 0x02);
+			else
+				serial_out(info, UART_ESI_CMD2, info->irq);
+		}
+	}
+
+	if (info->flags & ASYNC_INITIALIZED) {
+		if (((old_info.flags & ASYNC_SPD_MASK) !=
+		     (info->flags & ASYNC_SPD_MASK)) ||
+		    (old_info.custom_divisor != info->custom_divisor)) {
+			if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+				info->tty->alt_speed = 57600;
+			if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+				info->tty->alt_speed = 115200;
+			if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+				info->tty->alt_speed = 230400;
+			if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+				info->tty->alt_speed = 460800;
+			change_speed(info);
+		}
+	} else
+		retval = startup(info);
+
+	return retval;
+}
+
+static int set_esp_config(struct esp_struct * info,
+			  struct hayes_esp_config __user * new_info)
+{
+	struct hayes_esp_config new_config;
+	unsigned int change_dma;
+	int retval = 0;
+	struct esp_struct *current_async;
+	unsigned long flags;
+
+	/* Perhaps a non-sysadmin user should be able to do some of these */
+	/* operations.  I haven't decided yet. */
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	if (copy_from_user(&new_config, new_info, sizeof(new_config)))
+		return -EFAULT;
+
+	if ((new_config.flow_on >= new_config.flow_off) ||
+	    (new_config.rx_trigger < 1) ||
+	    (new_config.tx_trigger < 1) ||
+	    (new_config.flow_off < 1) ||
+	    (new_config.flow_on < 1) ||
+	    (new_config.rx_trigger > 1023) ||
+	    (new_config.tx_trigger > 1023) ||
+	    (new_config.flow_off > 1023) ||
+	    (new_config.flow_on > 1023) ||
+	    (new_config.pio_threshold < 0) ||
+	    (new_config.pio_threshold > 1024))
+		return -EINVAL;
+
+	if ((new_config.dma_channel != 1) && (new_config.dma_channel != 3))
+		new_config.dma_channel = 0;
+
+	if (info->stat_flags & ESP_STAT_NEVER_DMA)
+		change_dma = new_config.dma_channel;
+	else
+		change_dma = (new_config.dma_channel != dma);
+
+	if (change_dma) {
+		if (new_config.dma_channel) {
+			/* PIO mode to DMA mode transition OR */
+			/* change current DMA channel */
+			
+			current_async = ports;
+
+			while (current_async) {
+				if (current_async == info) {
+					if (current_async->count > 1)
+						return -EBUSY;
+				} else if (current_async->count)
+					return -EBUSY;
+					
+				current_async =
+					current_async->next_port;
+			}
+
+			shutdown(info);
+			dma = new_config.dma_channel;
+			info->stat_flags &= ~ESP_STAT_NEVER_DMA;
+			
+                        /* all ports must use the same DMA channel */
+
+			spin_lock_irqsave(&info->lock, flags);
+			current_async = ports;
+
+			while (current_async) {
+				esp_basic_init(current_async);
+				current_async = current_async->next_port;
+			}
+			spin_unlock_irqrestore(&info->lock, flags);
+		} else {
+			/* DMA mode to PIO mode only */
+			
+			if (info->count > 1)
+				return -EBUSY;
+
+			shutdown(info);
+			spin_lock_irqsave(&info->lock, flags);
+			info->stat_flags |= ESP_STAT_NEVER_DMA;
+			esp_basic_init(info);
+			spin_unlock_irqrestore(&info->lock, flags);
+		}
+	}
+
+	info->config.pio_threshold = new_config.pio_threshold;
+
+	if ((new_config.flow_off != info->config.flow_off) ||
+	    (new_config.flow_on != info->config.flow_on)) {
+		unsigned long flags;
+
+		info->config.flow_off = new_config.flow_off;
+		info->config.flow_on = new_config.flow_on;
+
+		spin_lock_irqsave(&info->lock, flags);
+		serial_out(info, UART_ESI_CMD1, ESI_SET_FLOW_LVL);
+		serial_out(info, UART_ESI_CMD2, new_config.flow_off >> 8);
+		serial_out(info, UART_ESI_CMD2, new_config.flow_off);
+		serial_out(info, UART_ESI_CMD2, new_config.flow_on >> 8);
+		serial_out(info, UART_ESI_CMD2, new_config.flow_on);
+		spin_unlock_irqrestore(&info->lock, flags);
+	}
+
+	if ((new_config.rx_trigger != info->config.rx_trigger) ||
+	    (new_config.tx_trigger != info->config.tx_trigger)) {
+		unsigned long flags;
+
+		info->config.rx_trigger = new_config.rx_trigger;
+		info->config.tx_trigger = new_config.tx_trigger;
+		spin_lock_irqsave(&info->lock, flags);
+		serial_out(info, UART_ESI_CMD1, ESI_SET_TRIGGER);
+		serial_out(info, UART_ESI_CMD2,
+			   new_config.rx_trigger >> 8);
+		serial_out(info, UART_ESI_CMD2, new_config.rx_trigger);
+		serial_out(info, UART_ESI_CMD2,
+			   new_config.tx_trigger >> 8);
+		serial_out(info, UART_ESI_CMD2, new_config.tx_trigger);
+		spin_unlock_irqrestore(&info->lock, flags);
+	}
+
+	if (new_config.rx_timeout != info->config.rx_timeout) {
+		unsigned long flags;
+
+		info->config.rx_timeout = new_config.rx_timeout;
+		spin_lock_irqsave(&info->lock, flags);
+
+		if (info->IER & UART_IER_RDI) {
+			serial_out(info, UART_ESI_CMD1,
+				   ESI_SET_RX_TIMEOUT);
+			serial_out(info, UART_ESI_CMD2,
+				   new_config.rx_timeout);
+		}
+
+		spin_unlock_irqrestore(&info->lock, flags);
+	}
+
+	if (!(info->flags & ASYNC_INITIALIZED))
+		retval = startup(info);
+
+	return retval;
+}
+
+/*
+ * get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ * 	    is emptied.  On bus types like RS485, the transmitter must
+ * 	    release the bus after transmitting. This must be done when
+ * 	    the transmit shift register is empty, not be done when the
+ * 	    transmit holding register is empty.  This functionality
+ * 	    allows an RS485 driver to be written in user space. 
+ */
+static int get_lsr_info(struct esp_struct * info, unsigned int __user *value)
+{
+	unsigned char status;
+	unsigned int result;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->lock, flags);
+	serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
+	status = serial_in(info, UART_ESI_STAT1);
+	spin_unlock_irqrestore(&info->lock, flags);
+	result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
+	return put_user(result,value);
+}
+
+
+static int esp_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+	unsigned char control, status;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, __FUNCTION__))
+		return -ENODEV;
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return -EIO;
+
+	control = info->MCR;
+
+	spin_lock_irqsave(&info->lock, flags);
+	serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
+	status = serial_in(info, UART_ESI_STAT2);
+	spin_unlock_irqrestore(&info->lock, flags);
+
+	return    ((control & UART_MCR_RTS) ? TIOCM_RTS : 0)
+		| ((control & UART_MCR_DTR) ? TIOCM_DTR : 0)
+		| ((status  & UART_MSR_DCD) ? TIOCM_CAR : 0)
+		| ((status  & UART_MSR_RI) ? TIOCM_RNG : 0)
+		| ((status  & UART_MSR_DSR) ? TIOCM_DSR : 0)
+		| ((status  & UART_MSR_CTS) ? TIOCM_CTS : 0);
+}
+
+static int esp_tiocmset(struct tty_struct *tty, struct file *file,
+			unsigned int set, unsigned int clear)
+{
+	struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, __FUNCTION__))
+		return -ENODEV;
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return -EIO;
+
+	spin_lock_irqsave(&info->lock, flags);
+
+	if (set & TIOCM_RTS)
+		info->MCR |= UART_MCR_RTS;
+	if (set & TIOCM_DTR)
+		info->MCR |= UART_MCR_DTR;
+
+	if (clear & TIOCM_RTS)
+		info->MCR &= ~UART_MCR_RTS;
+	if (clear & TIOCM_DTR)
+		info->MCR &= ~UART_MCR_DTR;
+
+	serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+	serial_out(info, UART_ESI_CMD2, UART_MCR);
+	serial_out(info, UART_ESI_CMD2, info->MCR);
+
+	spin_unlock_irqrestore(&info->lock, flags);
+	return 0;
+}
+
+/*
+ * rs_break() --- routine which turns the break handling on or off
+ */
+static void esp_break(struct tty_struct *tty, int break_state)
+{
+	struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (serial_paranoia_check(info, tty->name, "esp_break"))
+		return;
+
+	if (break_state == -1) {
+		spin_lock_irqsave(&info->lock, flags);
+		serial_out(info, UART_ESI_CMD1, ESI_ISSUE_BREAK);
+		serial_out(info, UART_ESI_CMD2, 0x01);
+		spin_unlock_irqrestore(&info->lock, flags);
+
+		/* FIXME - new style wait needed here */
+		interruptible_sleep_on(&info->break_wait);
+	} else {
+		spin_lock_irqsave(&info->lock, flags);
+		serial_out(info, UART_ESI_CMD1, ESI_ISSUE_BREAK);
+		serial_out(info, UART_ESI_CMD2, 0x00);
+		spin_unlock_irqrestore(&info->lock, flags);
+	}
+}
+
+static int rs_ioctl(struct tty_struct *tty, struct file * file,
+		    unsigned int cmd, unsigned long arg)
+{
+	struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+	struct async_icount cprev, cnow;	/* kernel counter temps */
+	struct serial_icounter_struct __user *p_cuser;	/* user space */
+	void __user *argp = (void __user *)arg;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_ioctl"))
+		return -ENODEV;
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+	    (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGWILD)  &&
+	    (cmd != TIOCSERSWILD) && (cmd != TIOCSERGSTRUCT) &&
+	    (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT) &&
+	    (cmd != TIOCGHAYESESP) && (cmd != TIOCSHAYESESP)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+		    return -EIO;
+	}
+	
+	switch (cmd) {
+		case TIOCGSERIAL:
+			return get_serial_info(info, argp);
+		case TIOCSSERIAL:
+			return set_serial_info(info, argp);
+		case TIOCSERCONFIG:
+			/* do not reconfigure after initial configuration */
+			return 0;
+
+		case TIOCSERGWILD:
+			return put_user(0L, (unsigned long __user *)argp);
+
+		case TIOCSERGETLSR: /* Get line status register */
+			    return get_lsr_info(info, argp);
+
+		case TIOCSERSWILD:
+			if (!capable(CAP_SYS_ADMIN))
+				return -EPERM;
+			return 0;
+
+		/*
+		 * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+		 * - mask passed in arg for lines of interest
+ 		 *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+		 * Caller should use TIOCGICOUNT to see which one it was
+		 */
+		 case TIOCMIWAIT:
+			spin_lock_irqsave(&info->lock, flags);
+			cprev = info->icount;	/* note the counters on entry */
+			spin_unlock_irqrestore(&info->lock, flags);
+			while (1) {
+				/* FIXME: convert to new style wakeup */
+				interruptible_sleep_on(&info->delta_msr_wait);
+				/* see if a signal did it */
+				if (signal_pending(current))
+					return -ERESTARTSYS;
+				spin_lock_irqsave(&info->lock, flags);
+				cnow = info->icount;	/* atomic copy */
+				spin_unlock_irqrestore(&info->lock, flags);
+				if (cnow.rng == cprev.rng &&
+				    cnow.dsr == cprev.dsr && 
+				    cnow.dcd == cprev.dcd &&
+				    cnow.cts == cprev.cts)
+					return -EIO; /* no change => error */
+				if (((arg & TIOCM_RNG) &&
+				     (cnow.rng != cprev.rng)) ||
+				     ((arg & TIOCM_DSR) &&
+				      (cnow.dsr != cprev.dsr)) ||
+				     ((arg & TIOCM_CD) &&
+				      (cnow.dcd != cprev.dcd)) ||
+				     ((arg & TIOCM_CTS) &&
+				      (cnow.cts != cprev.cts)) ) {
+					return 0;
+				}
+				cprev = cnow;
+			}
+			/* NOTREACHED */
+
+		/* 
+		 * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+		 * Return: write counters to the user passed counter struct
+		 * NB: both 1->0 and 0->1 transitions are counted except for
+		 *     RI where only 0->1 is counted.
+		 */
+		case TIOCGICOUNT:
+			spin_lock_irqsave(&info->lock, flags);
+			cnow = info->icount;
+			spin_unlock_irqrestore(&info->lock, flags);
+			p_cuser = argp;
+			if (put_user(cnow.cts, &p_cuser->cts) ||
+			    put_user(cnow.dsr, &p_cuser->dsr) ||
+			    put_user(cnow.rng, &p_cuser->rng) ||
+			    put_user(cnow.dcd, &p_cuser->dcd))
+				return -EFAULT;
+
+			return 0;
+	case TIOCGHAYESESP:
+		return get_esp_config(info, argp);
+	case TIOCSHAYESESP:
+		return set_esp_config(info, argp);
+
+		default:
+			return -ENOIOCTLCMD;
+		}
+	return 0;
+}
+
+static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (   (tty->termios->c_cflag == old_termios->c_cflag)
+	    && (   RELEVANT_IFLAG(tty->termios->c_iflag) 
+		== RELEVANT_IFLAG(old_termios->c_iflag)))
+		return;
+
+	change_speed(info);
+
+	spin_lock_irqsave(&info->lock, flags);
+
+	/* Handle transition to B0 status */
+	if ((old_termios->c_cflag & CBAUD) &&
+		!(tty->termios->c_cflag & CBAUD)) {
+		info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS);
+		serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+		serial_out(info, UART_ESI_CMD2, UART_MCR);
+		serial_out(info, UART_ESI_CMD2, info->MCR);
+	}
+
+	/* Handle transition away from B0 status */
+	if (!(old_termios->c_cflag & CBAUD) &&
+		(tty->termios->c_cflag & CBAUD)) {
+		info->MCR |= (UART_MCR_DTR | UART_MCR_RTS);
+		serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+		serial_out(info, UART_ESI_CMD2, UART_MCR);
+		serial_out(info, UART_ESI_CMD2, info->MCR);
+	}
+
+	spin_unlock_irqrestore(&info->lock, flags);
+
+	/* Handle turning of CRTSCTS */
+	if ((old_termios->c_cflag & CRTSCTS) &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {
+		rs_start(tty);
+	}
+}
+
+/*
+ * ------------------------------------------------------------
+ * rs_close()
+ * 
+ * This routine is called when the serial port gets closed.  First, we
+ * wait for the last remaining data to be sent.  Then, we unlink its
+ * async structure from the interrupt chain if necessary, and we free
+ * that IRQ if nothing is left in the chain.
+ * ------------------------------------------------------------
+ */
+static void rs_close(struct tty_struct *tty, struct file * filp)
+{
+	struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (!info || serial_paranoia_check(info, tty->name, "rs_close"))
+		return;
+	
+	spin_lock_irqsave(&info->lock, flags);
+	
+	if (tty_hung_up_p(filp)) {
+		DBG_CNT("before DEC-hung");
+		goto out;
+	}
+	
+#ifdef SERIAL_DEBUG_OPEN
+	printk("rs_close ttys%d, count = %d\n", info->line, info->count);
+#endif
+	if ((tty->count == 1) && (info->count != 1)) {
+		/*
+		 * Uh, oh.  tty->count is 1, which means that the tty
+		 * structure will be freed.  Info->count should always
+		 * be one in these conditions.  If it's greater than
+		 * one, we've got real problems, since it means the
+		 * serial port won't be shutdown.
+		 */
+		printk("rs_close: bad serial port count; tty->count is 1, "
+		       "info->count is %d\n", info->count);
+		info->count = 1;
+	}
+	if (--info->count < 0) {
+		printk("rs_close: bad serial port count for ttys%d: %d\n",
+		       info->line, info->count);
+		info->count = 0;
+	}
+	if (info->count) {
+		DBG_CNT("before DEC-2");
+		goto out;
+	}
+	info->flags |= ASYNC_CLOSING;
+
+	spin_unlock_irqrestore(&info->lock, flags);
+	/*
+	 * Now we wait for the transmit buffer to clear; and we notify 
+	 * the line discipline to only process XON/XOFF characters.
+	 */
+	tty->closing = 1;
+	if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, info->closing_wait);
+	/*
+	 * At this point we stop accepting input.  To do this, we
+	 * disable the receive line status interrupts, and tell the
+	 * interrupt driver to stop checking the data ready bit in the
+	 * line status register.
+	 */
+	/* info->IER &= ~UART_IER_RLSI; */
+	info->IER &= ~UART_IER_RDI;
+	info->read_status_mask &= ~UART_LSR_DR;
+	if (info->flags & ASYNC_INITIALIZED) {
+
+		spin_lock_irqsave(&info->lock, flags);
+		serial_out(info, UART_ESI_CMD1, ESI_SET_SRV_MASK);
+		serial_out(info, UART_ESI_CMD2, info->IER);
+
+		/* disable receive timeout */
+		serial_out(info, UART_ESI_CMD1, ESI_SET_RX_TIMEOUT);
+		serial_out(info, UART_ESI_CMD2, 0x00);
+
+		spin_unlock_irqrestore(&info->lock, flags);
+
+		/*
+		 * Before we drop DTR, make sure the UART transmitter
+		 * has completely drained; this is especially
+		 * important if there is a transmit FIFO!
+		 */
+		rs_wait_until_sent(tty, info->timeout);
+	}
+	shutdown(info);
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+	tty_ldisc_flush(tty);
+	tty->closing = 0;
+	info->event = 0;
+	info->tty = NULL;
+
+	if (info->blocked_open) {
+		if (info->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(info->close_delay));
+		}
+		wake_up_interruptible(&info->open_wait);
+	}
+	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+	wake_up_interruptible(&info->close_wait);
+	return;
+
+out:
+	spin_unlock_irqrestore(&info->lock, flags);
+}
+
+static void rs_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct esp_struct *info = (struct esp_struct *)tty->driver_data;
+	unsigned long orig_jiffies, char_time;
+	unsigned long flags;
+
+	if (serial_paranoia_check(info, tty->name, "rs_wait_until_sent"))
+		return;
+
+	orig_jiffies = jiffies;
+	char_time = ((info->timeout - HZ / 50) / 1024) / 5;
+
+	if (!char_time)
+		char_time = 1;
+
+	spin_lock_irqsave(&info->lock, flags);
+	serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+	serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
+
+	while ((serial_in(info, UART_ESI_STAT1) != 0x03) ||
+		(serial_in(info, UART_ESI_STAT2) != 0xff)) {
+
+		spin_unlock_irqrestore(&info->lock, flags);
+		msleep_interruptible(jiffies_to_msecs(char_time));
+
+		if (signal_pending(current))
+			break;
+
+		if (timeout && time_after(jiffies, orig_jiffies + timeout))
+			break;
+
+		spin_lock_irqsave(&info->lock, flags);
+		serial_out(info, UART_ESI_CMD1, ESI_NO_COMMAND);
+		serial_out(info, UART_ESI_CMD1, ESI_GET_TX_AVAIL);
+	}
+	spin_unlock_irqrestore(&info->lock, flags);
+	set_current_state(TASK_RUNNING);
+}
+
+/*
+ * esp_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void esp_hangup(struct tty_struct *tty)
+{
+	struct esp_struct * info = (struct esp_struct *)tty->driver_data;
+	
+	if (serial_paranoia_check(info, tty->name, "esp_hangup"))
+		return;
+	
+	rs_flush_buffer(tty);
+	shutdown(info);
+	info->event = 0;
+	info->count = 0;
+	info->flags &= ~ASYNC_NORMAL_ACTIVE;
+	info->tty = NULL;
+	wake_up_interruptible(&info->open_wait);
+}
+
+/*
+ * ------------------------------------------------------------
+ * esp_open() and friends
+ * ------------------------------------------------------------
+ */
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+			   struct esp_struct *info)
+{
+	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) ||
+	    (info->flags & ASYNC_CLOSING)) {
+		if (info->flags & ASYNC_CLOSING)
+			interruptible_sleep_on(&info->close_wait);
+#ifdef SERIAL_DO_RESTART
+		if (info->flags & ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+#else
+		return -EAGAIN;
+#endif
+	}
+
+	/*
+	 * 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))) {
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	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, info->count is dropped by one, so that
+	 * rs_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	retval = 0;
+	add_wait_queue(&info->open_wait, &wait);
+#ifdef SERIAL_DEBUG_OPEN
+	printk("block_til_ready before block: ttys%d, count = %d\n",
+	       info->line, info->count);
+#endif
+	spin_lock_irqsave(&info->lock, flags);
+	if (!tty_hung_up_p(filp)) 
+		info->count--;
+	info->blocked_open++;
+	while (1) {
+		if ((tty->termios->c_cflag & CBAUD)) {
+			unsigned int scratch;
+
+			serial_out(info, UART_ESI_CMD1, ESI_READ_UART);
+			serial_out(info, UART_ESI_CMD2, UART_MCR);
+			scratch = serial_in(info, UART_ESI_STAT1);
+			serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+			serial_out(info, UART_ESI_CMD2, UART_MCR);
+			serial_out(info, UART_ESI_CMD2,
+				scratch | UART_MCR_DTR | UART_MCR_RTS);
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) ||
+		    !(info->flags & ASYNC_INITIALIZED)) {
+#ifdef SERIAL_DO_RESTART
+			if (info->flags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;	
+#else
+			retval = -EAGAIN;
+#endif
+			break;
+		}
+
+		serial_out(info, UART_ESI_CMD1, ESI_GET_UART_STAT);
+		if (serial_in(info, UART_ESI_STAT2) & UART_MSR_DCD)
+			do_clocal = 1;
+
+		if (!(info->flags & ASYNC_CLOSING) &&
+		    (do_clocal))
+			break;
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+#ifdef SERIAL_DEBUG_OPEN
+		printk("block_til_ready blocking: ttys%d, count = %d\n",
+		       info->line, info->count);
+#endif
+		spin_unlock_irqrestore(&info->lock, flags);
+		schedule();
+		spin_lock_irqsave(&info->lock, flags);
+	}
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&info->open_wait, &wait);
+	if (!tty_hung_up_p(filp))
+		info->count++;
+	info->blocked_open--;
+	spin_unlock_irqrestore(&info->lock, flags);
+#ifdef SERIAL_DEBUG_OPEN
+	printk("block_til_ready after blocking: ttys%d, count = %d\n",
+	       info->line, info->count);
+#endif
+	if (retval)
+		return retval;
+	info->flags |= ASYNC_NORMAL_ACTIVE;
+	return 0;
+}	
+
+/*
+ * This routine is called whenever a serial port is opened.  It
+ * enables interrupts for a serial port, linking in its async structure into
+ * the IRQ chain.   It also performs the serial-specific
+ * initialization for the tty structure.
+ */
+static int esp_open(struct tty_struct *tty, struct file * filp)
+{
+	struct esp_struct	*info;
+	int 			retval, line;
+	unsigned long		flags;
+
+	line = tty->index;
+	if ((line < 0) || (line >= NR_PORTS))
+		return -ENODEV;
+
+	/* find the port in the chain */
+
+	info = ports;
+
+	while (info && (info->line != line))
+		info = info->next_port;
+
+	if (!info) {
+		serial_paranoia_check(info, tty->name, "esp_open");
+		return -ENODEV;
+	}
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("esp_open %s, count = %d\n", tty->name, info->count);
+#endif
+	spin_lock_irqsave(&info->lock, flags);
+	info->count++;
+	tty->driver_data = info;
+	info->tty = tty;
+
+	if (!tmp_buf) {
+		tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL);
+		if (!tmp_buf)
+			return -ENOMEM;
+	}
+	
+	/*
+	 * Start up serial port
+	 */
+	retval = startup(info);
+	if (retval)
+		return retval;
+
+	retval = block_til_ready(tty, filp, info);
+	if (retval) {
+#ifdef SERIAL_DEBUG_OPEN
+		printk("esp_open returning after block_til_ready with %d\n",
+		       retval);
+#endif
+		return retval;
+	}
+
+#ifdef SERIAL_DEBUG_OPEN
+	printk("esp_open %s successful...", tty->name);
+#endif
+	return 0;
+}
+
+/*
+ * ---------------------------------------------------------------------
+ * espserial_init() and friends
+ *
+ * espserial_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+/*
+ * This routine prints out the appropriate serial driver version
+ * number, and identifies which options were configured into this
+ * driver.
+ */
+ 
+static inline void show_serial_version(void)
+{
+ 	printk(KERN_INFO "%s version %s (DMA %u)\n",
+		serial_name, serial_version, dma);
+}
+
+/*
+ * This routine is called by espserial_init() to initialize a specific serial
+ * port.
+ */
+static inline int autoconfig(struct esp_struct * info)
+{
+	int port_detected = 0;
+	unsigned long flags;
+
+	if (!request_region(info->port, REGION_SIZE, "esp serial"))
+		return -EIO;
+
+	spin_lock_irqsave(&info->lock, flags);
+	/*
+	 * Check for ESP card
+	 */
+
+	if (serial_in(info, UART_ESI_BASE) == 0xf3) {
+		serial_out(info, UART_ESI_CMD1, 0x00);
+		serial_out(info, UART_ESI_CMD1, 0x01);
+
+		if ((serial_in(info, UART_ESI_STAT2) & 0x70) == 0x20) {
+			port_detected = 1;
+
+			if (!(info->irq)) {
+				serial_out(info, UART_ESI_CMD1, 0x02);
+
+				if (serial_in(info, UART_ESI_STAT1) & 0x01)
+					info->irq = 3;
+				else
+					info->irq = 4;
+			}
+
+
+			/* put card in enhanced mode */
+			/* this prevents access through */
+			/* the "old" IO ports */
+			esp_basic_init(info);
+
+			/* clear out MCR */
+			serial_out(info, UART_ESI_CMD1, ESI_WRITE_UART);
+			serial_out(info, UART_ESI_CMD2, UART_MCR);
+			serial_out(info, UART_ESI_CMD2, 0x00);
+		}
+	}
+	if (!port_detected)
+		release_region(info->port, REGION_SIZE);
+
+	spin_unlock_irqrestore(&info->lock, flags);
+	return (port_detected);
+}
+
+static struct tty_operations esp_ops = {
+	.open = esp_open,
+	.close = rs_close,
+	.write = rs_write,
+	.put_char = rs_put_char,
+	.flush_chars = rs_flush_chars,
+	.write_room = rs_write_room,
+	.chars_in_buffer = rs_chars_in_buffer,
+	.flush_buffer = rs_flush_buffer,
+	.ioctl = rs_ioctl,
+	.throttle = rs_throttle,
+	.unthrottle = rs_unthrottle,
+	.set_termios = rs_set_termios,
+	.stop = rs_stop,
+	.start = rs_start,
+	.hangup = esp_hangup,
+	.break_ctl = esp_break,
+	.wait_until_sent = rs_wait_until_sent,
+	.tiocmget = esp_tiocmget,
+	.tiocmset = esp_tiocmset,
+};
+
+/*
+ * The serial driver boot-time initialization code!
+ */
+static int __init espserial_init(void)
+{
+	int i, offset;
+	struct esp_struct * info;
+	struct esp_struct *last_primary = NULL;
+	int esp[] = {0x100,0x140,0x180,0x200,0x240,0x280,0x300,0x380};
+
+	esp_driver = alloc_tty_driver(NR_PORTS);
+	if (!esp_driver)
+		return -ENOMEM;
+	
+	for (i = 0; i < NR_PRIMARY; i++) {
+		if (irq[i] != 0) {
+			if ((irq[i] < 2) || (irq[i] > 15) || (irq[i] == 6) ||
+			    (irq[i] == 8) || (irq[i] == 13))
+				irq[i] = 0;
+			else if (irq[i] == 2)
+				irq[i] = 9;
+		}
+	}
+
+	if ((dma != 1) && (dma != 3))
+		dma = 0;
+
+	if ((rx_trigger < 1) || (rx_trigger > 1023))
+		rx_trigger = 768;
+
+	if ((tx_trigger < 1) || (tx_trigger > 1023))
+		tx_trigger = 768;
+
+	if ((flow_off < 1) || (flow_off > 1023))
+		flow_off = 1016;
+	
+	if ((flow_on < 1) || (flow_on > 1023))
+		flow_on = 944;
+
+	if ((rx_timeout < 0) || (rx_timeout > 255))
+		rx_timeout = 128;
+	
+	if (flow_on >= flow_off)
+		flow_on = flow_off - 1;
+
+	show_serial_version();
+
+	/* Initialize the tty_driver structure */
+	
+	esp_driver->owner = THIS_MODULE;
+	esp_driver->name = "ttyP";
+	esp_driver->devfs_name = "tts/P";
+	esp_driver->major = ESP_IN_MAJOR;
+	esp_driver->minor_start = 0;
+	esp_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	esp_driver->subtype = SERIAL_TYPE_NORMAL;
+	esp_driver->init_termios = tty_std_termios;
+	esp_driver->init_termios.c_cflag =
+		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	esp_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(esp_driver, &esp_ops);
+	if (tty_register_driver(esp_driver))
+	{
+		printk(KERN_ERR "Couldn't register esp serial driver");
+		put_tty_driver(esp_driver);
+		return 1;
+	}
+
+	info = kmalloc(sizeof(struct esp_struct), GFP_KERNEL);
+
+	if (!info)
+	{
+		printk(KERN_ERR "Couldn't allocate memory for esp serial device information\n");
+		tty_unregister_driver(esp_driver);
+		put_tty_driver(esp_driver);
+		return 1;
+	}
+
+	memset((void *)info, 0, sizeof(struct esp_struct));
+	/* rx_trigger, tx_trigger are needed by autoconfig */
+	info->config.rx_trigger = rx_trigger;
+	info->config.tx_trigger = tx_trigger;
+
+	i = 0;
+	offset = 0;
+
+	do {
+		info->port = esp[i] + offset;
+		info->irq = irq[i];
+		info->line = (i * 8) + (offset / 8);
+
+		if (!autoconfig(info)) {
+			i++;
+			offset = 0;
+			continue;
+		}
+
+		info->custom_divisor = (divisor[i] >> (offset / 2)) & 0xf;
+		info->flags = STD_COM_FLAGS;
+		if (info->custom_divisor)
+			info->flags |= ASYNC_SPD_CUST;
+		info->magic = ESP_MAGIC;
+		info->close_delay = 5*HZ/10;
+		info->closing_wait = 30*HZ;
+		INIT_WORK(&info->tqueue, do_softint, info);
+		INIT_WORK(&info->tqueue_hangup, do_serial_hangup, info);
+		info->config.rx_timeout = rx_timeout;
+		info->config.flow_on = flow_on;
+		info->config.flow_off = flow_off;
+		info->config.pio_threshold = pio_threshold;
+		info->next_port = ports;
+		init_waitqueue_head(&info->open_wait);
+		init_waitqueue_head(&info->close_wait);
+		init_waitqueue_head(&info->delta_msr_wait);
+		init_waitqueue_head(&info->break_wait);
+		spin_lock_init(&info->lock);
+		ports = info;
+		printk(KERN_INFO "ttyP%d at 0x%04x (irq = %d) is an ESP ",
+			info->line, info->port, info->irq);
+
+		if (info->line % 8) {
+			printk("secondary port\n");
+			/* 8 port cards can't do DMA */
+			info->stat_flags |= ESP_STAT_NEVER_DMA;
+
+			if (last_primary)
+				last_primary->stat_flags |= ESP_STAT_NEVER_DMA;
+		} else {
+			printk("primary port\n");
+			last_primary = info;
+			irq[i] = info->irq;
+		}
+
+		if (!dma)
+			info->stat_flags |= ESP_STAT_NEVER_DMA;
+
+		info = kmalloc(sizeof(struct esp_struct), GFP_KERNEL);
+		if (!info)
+		{
+			printk(KERN_ERR "Couldn't allocate memory for esp serial device information\n"); 
+
+			/* allow use of the already detected ports */
+			return 0;
+		}
+
+		memset((void *)info, 0, sizeof(struct esp_struct));
+		/* rx_trigger, tx_trigger are needed by autoconfig */
+		info->config.rx_trigger = rx_trigger;
+		info->config.tx_trigger = tx_trigger;
+
+		if (offset == 56) {
+			i++;
+			offset = 0;
+		} else {
+			offset += 8;
+		}
+	} while (i < NR_PRIMARY);
+
+	/* free the last port memory allocation */
+	kfree(info);
+
+	return 0;
+}
+
+static void __exit espserial_exit(void) 
+{
+	int e1;
+	struct esp_struct *temp_async;
+	struct esp_pio_buffer *pio_buf;
+
+	/* printk("Unloading %s: version %s\n", serial_name, serial_version); */
+	if ((e1 = tty_unregister_driver(esp_driver)))
+		printk("SERIAL: failed to unregister serial driver (%d)\n",
+		       e1);
+	put_tty_driver(esp_driver);
+
+	while (ports) {
+		if (ports->port) {
+			release_region(ports->port, REGION_SIZE);
+		}
+		temp_async = ports->next_port;
+		kfree(ports);
+		ports = temp_async;
+	}
+
+	if (dma_buffer)
+		free_pages((unsigned long)dma_buffer,
+			get_order(DMA_BUFFER_SZ));
+
+	if (tmp_buf)
+		free_page((unsigned long)tmp_buf);
+
+	while (free_pio_buf) {
+		pio_buf = free_pio_buf->next;
+		kfree(free_pio_buf);
+		free_pio_buf = pio_buf;
+	}
+}
+
+module_init(espserial_init);
+module_exit(espserial_exit);
diff --git a/drivers/char/ftape/Kconfig b/drivers/char/ftape/Kconfig
new file mode 100644
index 0000000..7d3ecb5
--- /dev/null
+++ b/drivers/char/ftape/Kconfig
@@ -0,0 +1,340 @@
+#
+# Ftape configuration
+#
+config ZFTAPE
+	tristate "Zftape, the VFS interface"
+	depends on FTAPE
+	---help---
+	  Normally, you want to say Y or M. DON'T say N here or you
+	  WON'T BE ABLE TO USE YOUR FLOPPY TAPE DRIVE.
+
+	  The ftape module itself no longer contains the routines necessary
+	  to interface with the kernel VFS layer (i.e. to actually write data
+	  to and read data from the tape drive).  Instead the file system
+	  interface (i.e. the hardware independent part of the driver) has
+	  been moved to a separate module.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called zftape.
+
+	  Regardless of whether you say Y or M here, an additional runtime
+	  loadable module called `zft-compressor' which contains code to
+	  support user transparent on-the-fly compression based on Ross
+	  William's lzrw3 algorithm will be produced.  If you have enabled the
+	  kernel module loader (i.e. have said Y to "Kernel module loader
+	  support", above) then `zft-compressor' will be loaded
+	  automatically by zftape when needed.
+
+	  Despite its name, zftape does NOT use compression by default.  The
+	  file <file:Documentation/ftape.txt> contains a short description of
+	  the most important changes in the file system interface compared to
+	  previous versions of ftape.  The ftape home page
+	  <http://www.instmath.rwth-aachen.de/~heine/ftape/> contains
+	  further information.
+
+	  IMPORTANT NOTE: zftape can read archives created by previous
+	  versions of ftape and provide file mark support (i.e. fast skipping
+	  between tape archives) but previous version of ftape will lack file
+	  mark support when reading archives produced by zftape.
+
+config ZFT_DFLT_BLK_SZ
+	int "Default block size"
+	depends on ZFTAPE
+	default "10240"
+	---help---
+	  If unsure leave this at its default value, i.e. 10240. Note that
+	  you specify only the default block size here. The block size can be
+	  changed at run time using the MTSETBLK tape operation with the
+	  MTIOCTOP ioctl (i.e. with "mt -f /dev/qft0 setblk #BLKSZ" from the
+	  shell command line).
+
+	  The probably most striking difference between zftape and previous
+	  versions of ftape is the fact that all data must be written or read
+	  in multiples of a fixed block size. The block size defaults to
+	  10240 which is what GNU tar uses. The values for the block size
+	  should be either 1 or multiples of 1024 up to a maximum value of
+	  63488 (i.e. 62 K). If you specify `1' then zftape's builtin
+	  compression will be disabled.
+
+	  Reasonable values are `10240' (GNU tar's default block size),
+	  `5120' (afio's default block size), `32768' (default block size some
+	  backup programs assume for SCSI tape drives) or `1' (no restriction
+	  on block size, but disables builtin compression).
+
+comment "The compressor will be built as a module only!"
+	depends on FTAPE && ZFTAPE
+
+config ZFT_COMPRESSOR
+	tristate
+	depends on FTAPE!=n && ZFTAPE!=n
+	default m
+
+config FT_NR_BUFFERS
+	int "Number of ftape buffers (EXPERIMENTAL)"
+	depends on FTAPE && EXPERIMENTAL
+	default "3"
+	help
+	  Please leave this at `3' unless you REALLY know what you are doing.
+	  It is not necessary to change this value. Values below 3 make the
+	  proper use of ftape impossible, values greater than 3 are a waste of
+	  memory. You can change the amount of DMA memory used by ftape at
+	  runtime with "mt -f /dev/qft0 setdrvbuffer #NUMBUFFERS". Each buffer
+	  wastes 32 KB of memory. Please note that this memory cannot be
+	  swapped out.
+
+config FT_PROC_FS
+	bool "Enable procfs status report (+2kb)"
+	depends on FTAPE && PROC_FS
+	---help---
+	  Optional. Saying Y will result in creation of a directory
+	  `/proc/ftape' under the /proc file system. The files can be viewed
+	  with your favorite pager (i.e. use "more /proc/ftape/history" or
+	  "less /proc/ftape/history" or simply "cat /proc/ftape/history"). The
+	  file will contain some status information about the inserted
+	  cartridge, the kernel driver, your tape drive, the floppy disk
+	  controller and the error history for the most recent use of the
+	  kernel driver. Saying Y will enlarge the size of the ftape driver
+	  by approximately 2 KB.
+
+	  WARNING: When compiling ftape as a module (i.e. saying M to "Floppy
+	  tape drive") it is dangerous to use ftape's /proc file system
+	  interface. Accessing `/proc/ftape' while the module is unloaded will
+	  result in a kernel Oops. This cannot be fixed from inside ftape.
+
+choice
+	prompt "Debugging output"
+	depends on FTAPE
+	default FT_NORMAL_DEBUG
+
+config FT_NORMAL_DEBUG
+	bool "Normal"
+	---help---
+	  This option controls the amount of debugging output the ftape driver
+	  is ABLE to produce; it does not increase or diminish the debugging
+	  level itself. If unsure, leave this at its default setting,
+	  i.e. choose "Normal".
+
+	  Ftape can print lots of debugging messages to the system console
+	  resp. kernel log files. Reducing the amount of possible debugging
+	  output reduces the size of the kernel module by some KB, so it might
+	  be a good idea to use "None" for emergency boot floppies.
+
+	  If you want to save memory then the following strategy is
+	  recommended: leave this option at its default setting "Normal" until
+	  you know that the driver works as expected, afterwards reconfigure
+	  the kernel, this time specifying "Reduced" or "None" and recompile
+	  and install the kernel as usual. Note that choosing "Excessive"
+	  debugging output does not increase the amount of debugging output
+	  printed to the console but only makes it possible to produce
+	  "Excessive" debugging output.
+
+	  Please read <file:Documentation/ftape.txt> for a short description
+	  how to control the amount of debugging output.
+
+config FT_FULL_DEBUG
+	bool "Excessive"
+	help
+	  Extremely verbose output for driver debugging purposes.
+
+config FT_NO_TRACE
+	bool "Reduced"
+	help
+	  Reduced tape driver debugging output.
+
+config FT_NO_TRACE_AT_ALL
+	bool "None"
+	help
+	  Suppress all debugging output from the tape drive.
+
+endchoice
+
+comment "Hardware configuration"
+	depends on FTAPE
+
+choice
+	prompt "Floppy tape controllers"
+	depends on FTAPE
+	default FT_STD_FDC
+
+config FT_STD_FDC
+	bool "Standard"
+	---help---
+	  Only change this setting if you have a special controller. If you
+	  didn't plug any add-on card into your computer system but just
+	  plugged the floppy tape cable into the already existing floppy drive
+	  controller then you don't want to change the default setting,
+	  i.e. choose "Standard".
+
+	  Choose "MACH-2" if you have a Mountain Mach-2 controller.
+	  Choose "FC-10/FC-20" if you have a Colorado FC-10 or FC-20
+	  controller.
+	  Choose "Alt/82078" if you have another controller that is located at
+	  an IO base address different from the standard floppy drive
+	  controller's base address of `0x3f0', or uses an IRQ (interrupt)
+	  channel different from `6', or a DMA channel different from
+	  `2'. This is necessary for any controller card that is based on
+	  Intel's 82078 FDC such as Seagate's, Exabyte's and Iomega's "high
+	  speed" controllers.
+
+	  If you choose something other than "Standard" then please make
+	  sure that the settings for the IO base address and the IRQ and DMA
+	  channel in the configuration menus below are correct. Use the manual
+	  of your tape drive to determine the correct settings!
+
+	  If you are already successfully using your tape drive with another
+	  operating system then you definitely should use the same settings
+	  for the IO base, the IRQ and DMA channel that have proven to work
+	  with that other OS.
+
+	  Note that this menu lets you specify only the default setting for
+	  the hardware setup. The hardware configuration can be changed at
+	  boot time (when ftape is compiled into the kernel, i.e. if you
+	  have said Y to "Floppy tape drive") or module load time (i.e. if you
+	  have said M to "Floppy tape drive").
+
+	  Please read also the file <file:Documentation/ftape.txt> which
+	  contains a short description of the parameters that can be set at
+	  boot or load time. If you want to use your floppy tape drive on a
+	  PCI-bus based system, please read the file
+	  <file:drivers/char/ftape/README.PCI>.
+
+config FT_MACH2
+	bool "MACH-2"
+
+config FT_PROBE_FC10
+	bool "FC-10/FC-20"
+
+config FT_ALT_FDC
+	bool "Alt/82078"
+
+endchoice
+
+comment "Consult the manuals of your tape drive for the correct settings!"
+	depends on FTAPE && !FT_STD_FDC
+
+config FT_FDC_BASE
+	hex "IO base of the floppy disk controller"
+	depends on FTAPE && !FT_STD_FDC
+	default "0"
+	---help---
+	  You don't need to specify a value if the following default
+	  settings for the base IO address are correct:
+	  <<< MACH-2     : 0x1E0 >>>
+	  <<< FC-10/FC-20: 0x180 >>>
+	  <<< Secondary  : 0x370 >>>
+	  Secondary refers to a secondary FDC controller like the "high speed"
+	  controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash.
+	  Please make sure that the setting for the IO base address
+	  specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR
+	  CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already
+	  successfully using the tape drive with another operating system then
+	  you definitely should use the same settings for the IO base that has
+	  proven to work with that other OS.
+
+	  Note that this menu lets you specify only the default setting for
+	  the IO base. The hardware configuration can be changed at boot time
+	  (when ftape is compiled into the kernel, i.e. if you specified Y to
+	  "Floppy tape drive") or module load time (i.e. if you have said M to
+	  "Floppy tape drive").
+
+	  Please read also the file <file:Documentation/ftape.txt> which
+	  contains a short description of the parameters that can be set at
+	  boot or load time.
+
+config FT_FDC_IRQ
+	int "IRQ channel of the floppy disk controller"
+	depends on FTAPE && !FT_STD_FDC
+	default "0"
+	---help---
+	  You don't need to specify a value if the following default
+	  settings for the interrupt channel are correct:
+	  <<< MACH-2     : 6 >>>
+	  <<< FC-10/FC-20: 9 >>>
+	  <<< Secondary  : 6 >>>
+	  Secondary refers to secondary a FDC controller like the "high speed"
+	  controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash.
+	  Please make sure that the setting for the IO base address
+	  specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR
+	  CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already
+	  successfully using the tape drive with another operating system then
+	  you definitely should use the same settings for the IO base that has
+	  proven to work with that other OS.
+
+	  Note that this menu lets you specify only the default setting for
+	  the IRQ channel. The hardware configuration can be changed at boot
+	  time (when ftape is compiled into the kernel, i.e. if you said Y to
+	  "Floppy tape drive") or module load time (i.e. if you said M to
+	  "Floppy tape drive").
+
+	  Please read also the file <file:Documentation/ftape.txt> which
+	  contains a short description of the parameters that can be set at
+	  boot or load time.
+
+config FT_FDC_DMA
+	int "DMA channel of the floppy disk controller"
+	depends on FTAPE && !FT_STD_FDC
+	default "0"
+	---help---
+	  You don't need to specify a value if the following default
+	  settings for the DMA channel are correct:
+	  <<< MACH-2     : 2 >>>
+	  <<< FC-10/FC-20: 3 >>>
+	  <<< Secondary  : 2 >>>
+	  Secondary refers to a secondary FDC controller like the "high speed"
+	  controllers delivered by Seagate or Exabyte or Iomega's Ditto Dash.
+	  Please make sure that the setting for the IO base address
+	  specified here is correct. USE THE MANUAL OF YOUR TAPE DRIVE OR
+	  CONTROLLER CARD TO DETERMINE THE CORRECT SETTING. If you are already
+	  successfully using the tape drive with another operating system then
+	  you definitely should use the same settings for the IO base that has
+	  proven to work with that other OS.
+
+	  Note that this menu lets you specify only the default setting for
+	  the DMA channel. The hardware configuration can be changed at boot
+	  time (when ftape is compiled into the kernel, i.e. if you said Y to
+	  "Floppy tape drive") or module load time (i.e. if you said M to
+	  "Floppy tape drive").
+
+	  Please read also the file <file:Documentation/ftape.txt> which
+	  contains a short description of the parameters that can be set at
+	  boot or load time.
+
+config FT_FDC_THR
+	int "Default FIFO threshold (EXPERIMENTAL)"
+	depends on FTAPE && EXPERIMENTAL
+	default "8"
+	help
+	  Set the FIFO threshold of the FDC. If this is higher the DMA
+	  controller may serve the FDC after a higher latency time. If this is
+	  lower, fewer DMA transfers occur leading to less bus contention.
+	  You may try to tune this if ftape annoys you with "reduced data
+	  rate because of excessive overrun errors" messages. However, this
+	  doesn't seem to have too much effect.
+
+	  If unsure, don't touch the initial value, i.e. leave it at "8".
+
+config FT_FDC_MAX_RATE
+	int "Maximal data rate to use (EXPERIMENTAL)"
+	depends on FTAPE && EXPERIMENTAL
+	default "2000"
+	---help---
+	  With some motherboard/FDC combinations ftape will not be able to
+	  run your FDC/tape drive combination at the highest available
+	  speed. If this is the case you'll encounter "reduced data rate
+	  because of excessive overrun errors" messages and lots of retries
+	  before ftape finally decides to reduce the data rate.
+
+	  In this case it might be desirable to tell ftape beforehand that
+	  it need not try to run the tape drive at the highest available
+	  speed. If unsure, leave this disabled, i.e. leave it at 2000
+	  bits/sec.
+
+config FT_ALPHA_CLOCK
+	int "CPU clock frequency of your DEC Alpha" if ALPHA
+	depends on FTAPE
+	default "0"
+	help
+	  On some DEC Alpha machines the CPU clock frequency cannot be
+	  determined automatically, so you need to specify it here ONLY if
+	  running a DEC Alpha, otherwise this setting has no effect.
+
diff --git a/drivers/char/ftape/Makefile b/drivers/char/ftape/Makefile
new file mode 100644
index 0000000..0e67d2f
--- /dev/null
+++ b/drivers/char/ftape/Makefile
@@ -0,0 +1,28 @@
+#
+#       Copyright (C) 1997 Claus Heine.
+#
+# 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, 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; see the file COPYING.  If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Source: /homes/cvs/ftape-stacked/ftape/Makefile,v $
+# $Revision: 1.4 $
+# $Date: 1997/10/05 19:17:56 $
+#
+#      Makefile for the QIC-40/80/3010/3020 floppy-tape driver for
+#      Linux.
+#
+
+obj-$(CONFIG_FTAPE)		+= lowlevel/
+obj-$(CONFIG_ZFTAPE)		+= zftape/
+obj-$(CONFIG_ZFT_COMPRESSOR)	+= compressor/
diff --git a/drivers/char/ftape/README.PCI b/drivers/char/ftape/README.PCI
new file mode 100644
index 0000000..18de159
--- /dev/null
+++ b/drivers/char/ftape/README.PCI
@@ -0,0 +1,81 @@
+Some notes for ftape users with PCI motherboards:
+=================================================
+
+The problem:
+------------
+
+There have been some problem reports from people using PCI-bus based
+systems getting overrun errors.
+I wasn't able to reproduce these until I ran ftape on a Intel Plato
+(Premiere PCI II) motherboard with bios version 1.00.08AX1.
+It turned out that if GAT (Guaranteed Access Timing) is enabled (?)
+ftape gets a lot of overrun errors.
+The problem disappears when disabling GAT in the bios.
+Note that Intel removed this setting (permanently disabled) from the
+1.00.10AX1 bios !
+
+It looks like that if GAT is enabled there are often large periods
+(greater than 120 us !??) on the ISA bus that the DMA controller cannot
+service the floppy disk controller.
+I cannot imagine this being acceptable in a decent PCI implementation.
+Maybe this is a `feature' of the chipset. I can only speculate why
+Intel choose to remove the option from the latest Bios...
+
+The lesson of this all is that there may be other motherboard
+implementations having the same of similar problems.
+If you experience a lot of overrun errors during a backup to tape,
+see if there is some setting in the Bios that may influence the
+bus timing.
+
+I judge this a hardware problem and not a limitation of ftape ;-)
+My DOS backup software seems to be suffering from the same problems
+and even refuses to run at 1 Mbps !
+Ftape will reduce the data-rate from 1 Mbps to 500 Kbps if the number
+of overrun errors on a track exceeds a threshold.
+
+
+Possible solutions:
+-------------------
+
+Some of the problems were solved by upgrading the (flash) bios.
+Other suggest that it has to do with the FDC being on the PCI
+bus, but that is not the case with the Intel Premiere II boards.
+[If upgrading the bios doesn't solve the problem you could try
+a floppy disk controller on the isa-bus].
+
+Here is a list of systems and recommended BIOS settings:
+
+
+        Intel Premiere PCI (Revenge):
+
+Bios version 1.00.09.AF2 is reported to work.
+
+
+
+        Intel Premiere PCI II (Plato):
+
+Bios version 1.00.10.AX1 and version 11 beta are ok.
+If using version 1.00.08.AX1, GAT must be disabled !
+
+
+
+        ASUS PCI/I-SP3G:
+
+Preferred settings:     ISA-GAT-mode : disabled
+                        DMA-linebuffer-mode : standard
+                        ISA-masterbuffer-mode : standard
+
+
+        DELL Dimension XPS P90
+
+Bios version A2 is reported to be broken, while bios version A5 works.
+You can get a flash bios upgrade from http://www.dell.com
+
+
+To see if you're having the GAT problem, try making a backup
+under DOS. If it's very slow and often repositions you're
+probably having this problem.
+
+                        --//--
+ LocalWords:  ftape PCI bios GAT ISA DMA chipset Mbps Kbps FDC isa AF ok ASUS
+ LocalWords:  SP linebuffer masterbuffer XPS http www com
diff --git a/drivers/char/ftape/RELEASE-NOTES b/drivers/char/ftape/RELEASE-NOTES
new file mode 100644
index 0000000..03799db
--- /dev/null
+++ b/drivers/char/ftape/RELEASE-NOTES
@@ -0,0 +1,966 @@
+Hey, Emacs, we're -*-Text-*- mode!
+
+===== Release notes for ftape-3.04d 25/11/97 =====
+- The correct pre-processor statement for "else if" is "#elif" not
+  "elsif".
+- Need to call zft_reset_position() when overwriting cartridges
+  previously written with ftape-2.x, sftape, or ancient
+  (pre-ftape-3.x) versions of zftape.
+
+===== Release notes for ftape-3.04c 16/11/97 =====
+- fdc_probe() was calling DUMPREGS with a result length of "1" which
+  was just fine. Undo previous change.
+
+===== Release notes for ftape-3.04b 14/11/97 =====
+
+- patches/2.x.x/floppy.c.diff was somewhat broken, releasing i/o
+  regions it never had allocated.
+- fdc_probe() was calling DUMPREGS with a result length of "1" instead
+  of "10"
+- Writing deleted data marks if the first segents on track zero are
+  should work now.
+- ftformat should now be able to handle those cases where the tape
+  drive sets the read only status bit (QIC-40/80 cartridges with
+  QIC-3010/3020 tape drives) because the header segment is damaged.
+- the MTIOCFTCMD ioctl may now be issued by the superuser ONLY.
+
+===== Release notes for ftape-3.04a 12/11/97 =====
+- Fix an "infinite loop can't be killed by signal" bug in
+  ftape_get_drive_status(). Only relevant when trying to access
+  buggy/misconfigured hardware
+- Try to compensate a bug in the HP Colorado T3000's firmware: it
+  doesn't set the write protect bit for QIC80/QIC40 cartridges.
+
+===== Release notes for ftape-3.04 06/11/97 =====
+- If positioning with fast seeking fails fall back to a slow seek
+  before giving up.
+- (nearly) no retries on "no data errors" when verifying after
+  formatting. Improved tuning of the bad sector map after formatting.
+- the directory layout has changed again to allow for easier kernel
+  integration
+- Module parameter "ftape_tracing" now is called "ft_tracing" because
+  the "ftape_tracing" variable has the version checksum attached to it.
+- `/proc/ftape' interface for 2.0.* kernels. `/proc/ftape' no longer
+  is a directory but a file that contains all the information formerly
+  provided in separate files under the `/proc/ftape/' directory.
+- Most of the configuration options have been prefixed by "CONFIG_FT_"
+  in preparation of the kernel inclusion. The Makefiles under
+  "./ftape/" should be directly usable by the kernel.
+- The MODVERSIONS stuff is now auto-detected.
+- Broke backslashed multi line options in MCONFIG into separate lines
+  using GNU-make's "+=" feature.
+- The html and dvi version of the manual is now installed under
+  '/usr/doc/ftape` with 'make install`
+- New SMP define in MCONFIG. ftape works with SMP if this is defined.
+- attempt to cope with "excessive overrun errors" by gradually
+  increasing FDC FIFO threshold. But this doesn't seem to have too
+  much an effect.
+- New load time configuration parameter "ft_fdc_rate_limit". If you
+  encounter too many overrun errors with a 2Mb controller then you
+  might want to set this to 1000.
+- overrun errors on the last sector in a segment sometimes result in
+  a zero DMA residue. Dunno why, but compensate for it.
+- there were still fdc_read() timeout errors. I think I have fixed it
+  now, please FIXME.
+- Sometimes ftape_write() failed to re-start the tape drive when a
+  segment without a good sector was reached ("wait for empty segment
+  failed"). This is fixed. Especially important for > QIC-3010.
+- sftape (aka ftape-2.x) has vanished. I didn't work on it for
+  ages. It is probably still possible to use the old code with
+  ftape-3.04, if one really needs it (BUT RECOMPILE IT)
+- zftape no longer alters the contents of already existing volume
+  table entries, which makes it possible to fill in missing fields,
+  like time stamps using some user space program.
+- ./contrib/vtblc/ contains such a program.
+- new perl script ./contrib/scripts/listtape that list the contents of a
+  floppy tape cartridge parsing the output of "mt volinfo" + "mt fsf"
+- the MTWEOF implementation has changed a little bit (after I had a
+  look at amanda). Calling MTWEOF while the tape is still held open
+  after writing something to the tape now will terminate the current
+  volume, and start a new one at the current position.
+- the volume table maintained by zftape now is a doubly linked list
+  that grows dynamically as needed.
+
+  formatting floppy tape cartridges
+  ---------------------------------
+  * there is a new user space formatting program that does most of the
+    dirty work in user space (auto-detect, computing the sector
+    coordinates, adjusting time stamps and statistics). It has a
+    simple command line interface.
+  * ftape-format.o has vanished, it has been folded into the low level
+    ftape.o module, and the ioctl interface into zftape.o. Most of the
+    complicated stuff has been moved to user space, so there was no
+    need for a separate module anymore.
+  * there is a new ioctl MTIOCFTCMD that sends a bare QIC-117 command
+    to the tape drive.
+  * there is a new mmap() feature to map the dma buffers into user
+    space to be used by the user level formatting program.
+  * Formatting of yet unformatted or totally degaussed cartridges
+    should be possible now. FIXME.
+
+===== Release notes for ftape-3.03b, <forgot the exact date> ====
+
+ftape-3.03b was released as a beta release only. Its main new feature
+was support of the DITTO-2GB drive. This was made possible by reverse
+engineering done by <fill in his name> after Iomega failed to support
+ftape. Although they had promised to do so (this makes me feel a bit
+sad and uncomfortable about Iomega).
+
+===== Release notes for ftape-3.03a, 22/05/97 ====
+
+- Finally fixed auto-un-loading of modules for kernels > 2.1.18
+- Add an "uninstall" target to the Makefile
+- removed the kdtime hack
+- texi2www didn't properly set the back-reference from a footnote back
+  to the regular text.
+
+  zftape specific
+  ---------------
+  * hide the old compression map volume. Taper doesn't accept the
+    presence of non-Taper volumes and Taper-written volume on the same
+    tape.
+  * EOD (End Of Data) handling was still broken: the expected behavior
+    is to return a zero byte count at the first attempt to read past
+    EOD, return a zero byte count at the second attempt to read past
+    EOD and THEN return -EIO.
+  
+  ftape-format specific
+  ---------------------
+  * Detection of QIC-40 cartridges in select_tape_format() was broken
+    and made it impossible to format QIC-3010/3020 cartridges.
+  * There are strange "TR-1 Extra" cartridges out there which weren't
+    detected properly because the don't strictly conform to the
+    QIC-80, Rev. N, spec.
+
+===== Release notes for ftape-3.03, 30/04/97 =====
+
+- Removed kernel integration code from the package. I plan to provide
+  a package that can be integrated into the stock kernel separately
+  (hopefully soon).
+  As a result, a simple `make' command now will build everything.
+- ALL compile time configuration options have been moved to the file
+  `MCONFIG'.
+- Quite a few `low level' changes to allow formatting of cartridges.
+- formatting is implemented as a separate module `ftape-format.o'. The
+  modified `mt' program contains sample code that shows how to use it.
+- The VFS interface has been moved from the `ftape.o' module to the
+  high level modules `zftape.o' resp. `sftape.o'. `ftape.o' contains
+  the hardware support only.
+- A bit of /proc support for kernels > 2.1.28
+- Moved documentation to Doc subdir. INSTALL now contains some real
+  installation notes.
+- `install' target in Makefile.
+
+zftape specific:
+----------------
+
+- zftape works for large cartridges now ( > 2^31 bytes)
+- MTIOCVOLINFO and MTIOCGETSIZE now return the size in KILOBYTES,
+  NO LONGER in bytes.
+
+- permissions for write access to a cartridge have changed:
+  * zftape now also takes the file access mode into account
+  * zftape no longer allows writing in the middle of the recorded
+    media. The tape has to be positioned at BOT or EOD for write
+    access.
+
+- MTBSF has changed. It used to position at the beginning of the
+  previous file when called with count 1. This was different from the
+  expected behavior for other Un*x tape drivers (i.e. SCSI). MTBSF
+  with count 1 should merely position at the beginning of the current
+  volume. Fixed. As a result, `tar --verify' now produces the desired
+  result: it verifies the last written volume, not the pre-last
+  written volume.
+
+- The compression map has vanished --> no need for `mt erase' any
+  more. Fast seeking in a compressed volume is still be possible, but
+  takes slightly longer. As a side effect, you may experience an
+  additional volume showing up in front of all others for old
+  cartridges. This is the tape volume that holds the compression map.
+
+- The compression support for zftape has been moved to a separate
+  module `zft-compressor'. DON'T forget to load it before trying to
+  read back compressed volumes. The stock `zftape.o' module probes for
+  the module `zft-compressor' using the kerneld message channel; you
+  have to install `zft-compressor.o' in a place where modprobe can
+  find it if you want to use this.
+
+- New experimental feature that tries to get the broken down GMT time
+  from user space via a kernel daemon message channel. You need to
+  compile and start the `kdtime' daemon contained in the contrib
+  directory to use it. Needed (?) for time stamps in the header
+  segments and the volume table.
+
+- variable block size mode via MTSETBLK 0
+
+- keep modules locked in memory after the block size has been changed
+
+sftape specific:
+----------------
+
+- end of tape handling should be fixed, i.e. multi volume archives
+  written with `afio' can be read back now.
+
+
+===== Release notes for ftape-3.02a, 09/01/97 =====
+
+No big news:
+- call zft_init() resp. sft_init() when compiling the entire stuff
+  into the kernel image.
+- fix bug in ftape-setup.c when NO_TRACE_AT_ALL was defined.
+- fix bug in sftape-eof.c/zftape-eof.c for old kernels (1.2.*)
+- add support for new module interface for recent kernels
+
+===== Release notes for ftape-3.02, 16/12/96 =====
+- Fixed the `FDC unlock command failed' bug in fdc-io.c. When the FIFO
+  was already locked when ftape was loaded, ftape failed to unlock it.
+- Fixed compilation of `contrib/gnumt'. It now finds `mtio.h' even if
+  ftape is NOT included into the kernel source tree.
+- fc-10.c: include <asm/io.h> for inb() and outb().
+- ftape/sftape/zftape: all global variable now have either a `ftape_',
+  a `ft_', `sft_', `zft_' or `qic_' prefix to prevent name clashes
+  with other parts of the kernel when including ftape into the kernel
+  source tree.
+- Kerneld support has changed. `ftape' now searches for a module
+  `ftape-frontend' when none of the frontend (`sftape' or `zftape') is
+  loaded. Please refer to the `Installation/Loading ftape' section of
+  the TeXinfo manual.
+- Add load resp. boot-time configuration of ftape. There are now
+  variables ft_fdc_base, ft_fdc_dma and ft_fdc_irq corresponding to
+  the former FDC_BASE etc. compile time definitions. One can also use
+  the kernel command line parameters to configure the driver if it is
+  compiled into the kernel. Also, the FC-10/FC-20 support is load-time
+  configurable now as well as the MACH-II hack (ft_probe_fc10,
+  resp. ft_mach2). Please refer to the section `Installation/Configure
+  ftape' of the TeXinfo manual.
+- I removed the MODVERSIONS option from `Makefile.module'. Let me alone
+  with ftape and MODVERSIONS unless you include the ftape sources into
+  the kernel source tree.
+- new vendors in `vendors.h':
+  * HP Colorado T3000 
+  * ComByte DoublePlay (including a bug fix for their broken
+    formatting software, thanks to whraven@njackn.com)
+  * Iomega DITTO 2GIG. NOTE: this drive cannot work with ftape because
+    the logical data layout of the cartridges used by this drive does
+    NOT conform to the QIC standards, it is a special Iomega specific
+    format. I've sent mail to Iomega but didn't receive an answer
+    yet. If you want this drive to be supported by ftape, ask Iomega
+    to give me information about it.
+- zftape:
+  * re-introduced the MTIOC_ZFTAPE_GETBLKSZ ioctl for compatibility
+    with zftape 1.06a and earlier. Please don't use it when writing
+    new software, use the MTIOCVOLINFO ioctl instead.
+  * Major overhaul of the code that updates the header segments. Never
+    change the tape label unless erasing the tape. Thus we almost
+    never need to write the header segments, unless we would modify
+    the bad sector map which isn't done yet. Updating of volume table
+    and compression map more secure now although it takes a bit
+    longer.
+  * Fixed bug when aborting a write operation with a signal: zftape
+    now finishes the current volume (i.e. writes an eof marker) at the
+    current position. It didn't before which led to somehow *strange*
+    behavior in this cases.
+  * Keep module locked in memory when using it with  the non-rewinding
+    devices and the tape is not logical at BOT. Needed for kerneld
+    support.
+- sftape:
+  * Keep module locked in memory when using it with  the non-rewinding
+    devices and the tape is not logical at BOT. Needed for kerneld
+    support.
+
+===== Release notes for ftape-3.01, 14/11/96 =====
+
+- Fixed silly bugs in ftape-3.00:
+  * MAKEDEV.ftape: major device number must be 27, not 23 
+  * sftape/sftape-read.c: sftape_read_header_segments() called 
+    itself recursively instead of calling ftape_read_header_segment()
+  * zftape/qic-vtbl.h: conversion of ftape's file marks to zftape's
+    internal volume table was broken.
+  * patches/2.x.x/linux-2.0.21.dif: my RCS (resp. CVS) system replaced
+    the `$Revison:' etc. macros in the `ftape.h' concerning part of the
+    patch :-( Fixed.
+  * info/ftape.info: Fixed misspellings (`cp' <-> `cp -r' etc.)
+  * when ftape/sftape or ftape/zftape was compiled into the kernel the
+    variable ftape_status was declared twice. Fixed.
+  * removed reference to undeclared variable kernel_version when not
+    compiling as module
+  * fixed a bug introduced by the use of bit-fields for some flags
+    (i.e. write_protected, no_cartridge, formatted)
+  * flag `header_read' is now reset correctly to zero when tape is
+    removed.
+- fixed a bug in sftape/sftape-eof.c that was already in the original
+  ftape code. MTFSF/BSF was not handled correctly when positioned
+  right before the file mark (think of tar)
+- Changed TRACE macros (following a suggestion of Marcin Dalecki) to use
+  the predefined __FUNCTION__ macro of GCC. Spares about 4k of code.
+- added new vendor id for Iomega DITTO 2GIG
+- fixed a bug already present in zftape-1.06 when aborting a write
+  with a signal: we now finish the current volume at that
+  position. Header segments remain NOT up to date until an explicit call
+  to MTREW or MTOFFL is done.  
+
+===== Release notes for ftape-3.00, 14/10/96 =====
+
+- Merged ftape with zftape. There are three modules now:
+  ftape for the hardware support, sftape for the implementation of the
+  original ftape eof mark stuff and zftape that implements zftape's way
+  of handling things (compression, volume table, tape blocks of
+  constant length)
+- Documentation in TeXinfo format in the `info' subdirectory.
+- New ioctls for zftape. See zftape/zftape.h
+- Dummy formatting ioctl for ftape. See ftape.h
+- Kernel patch files for the 2.*.* series to include ftape-3.00 in the
+  kernel source tree. These includes a kernel compatible Config.in
+  script and fairly large online information for the kernel configure
+  script.
+- Support for compiling with Linux-1.2.13. 
+- Modified GNU mt from their cpio package that can handle the new
+  ioctls.
+- ftape/sftape/zftape is kerneld save now!
+
+Notes on sftape:
+- sftape implements the eof handling code of the original ftape. If
+  you like to stick with the original ftape stuff, you have to use
+  this module, not zftape.
+- sftape is kerneld save, unlike the original ftape.
+- we keep the entire header segment now in memory, so no need to read
+  it before updating the header segments. Additional memory
+  consumption: 256 bytes. 
+
+Notes for zftape:
+- zftape has support for tapes with format code 6 now, which use a
+  slightly different volume table format compared with other floppy
+  tapes.
+- new ioctls for zftape. Have a look at zftape/zftape.h
+- The internal volume table representation has changed for zftape. Old
+  cartridges are converted automatically.
+- zftape no longer uses compression map segments, which have vanished
+  from the QIC specs, but creates volume table entry that reserves
+  enough space for the compression map. 
+- zftape is kerneld save now.
+- we keep the entire header segment now in memory, so no need to read
+  it before updating the header segments. Additional memory
+  consumption: 256 bytes. 
+
+Notes for contrib/gnumt:
+- modified mt from the GNU cpio package that supports all the new
+  ioctls of zftape.
+Notes for contrib/swapout:
+- This contains the swapout.c program that was written by Kai
+  Harrekilde-Pederson. I simply added a Makefile.
+
+===== Release notes for ftape-2.10, 14/10/96 =====
+
+The ftape maintainer has changed. 
+Kai Harrekilde-Petersen <khp@dolphinics.no>
+has resigned from maintaining ftape, and I,
+Claus-Justus Heine <claus@momo.math.rwth-aachen.de>,
+have taken over.
+
+- Added support for tapes with `format code 6', i.e. QIC-3020 tapes
+  with more than 2^16 segments.
+- merged changes made by Bas Laarhoven with ftape-2.09. Refer
+  to his release notes below. I've included them into this
+  file unchanged for your reference.
+- disabled call stack back trace for now. This new feature
+  introduced by the interim release 2.0.x still seems to
+  be buggy.
+- Tried to minimize differences between the ftape version
+  to be included into the kernel source tree and the standalone
+  module version.
+- Reintroduced support for Linux-1.2.13. Please refer to the
+  Install-guide. 
+
+===== Release notes for ftape-2.09, 16/06/96 =====
+
+There aren't any really big news in this release, mostly just that I
+(the maintainer) have changed my email address (due to a new job).  My
+new address is <khp@dolphinics.no>
+
+- The CLK_48MHZ and FDC_82078SL options has gone (all 2Mbps cards seem
+  to use a 48MHz oscillator anyway and I haven't heard of an 'SL
+  chip out there).
+- The S82078B has been `downgraded' to i82077AA compability.
+- TESTING option revived.  Right now, it'll enable the (seriously broken)
+  2Mbps code.  If you enable it, you'll experience a tape drive that's
+  *really* out to lunch!
+- Some (bold) changes in the init code.  Please notify me if they
+  break things for you.
+
+===== Release notes for ftape-2.08, 14/03/96 =====
+
+If you correct a problem with ftape, please send your patch to
+khp@dolphinics.no too.
+
+- Updated to reflect that NR_MEM_LISTS is gone in 1.3.74
+- Teac 700 added to list of known drives.
+- The registered device name is now "ft" rather than "ftape".
+
+===== Release notes for ftape-2.07a, 14/03/96 =====
+
+Bugfixes by Marcin Dalecki <dalecki@namu03.gwdg.de>:
+- In the last release it just compiled against 1.3.70;
+  now the params to request_irq() and free_irq are() are fixed, so it also 
+  works in 1.3.73 :-)
+- Support for modules is now correct for newer kernels.
+
+===== Release notes for ftape-2.07, 04/03/96 =====
+
+
+- ftape updated to compile against 1.3.70.
+- Iomega 700 and Wangtek 3200 recognised.
+
+
+===== Release notes for ftape-2.06b, 13/02/96 =====
+
+Another simple bugfix version.
+
+- Jumbo 700 recognised.
+- Typo in vendors.h fixed.
+
+
+===== Release notes for ftape-2.06a, 10/02/96 =====
+
+This release is a simple bugfix version.
+
+- Linux/SMP: ftape *should* work.
+- FC-10/20: Only accepts IRQs 3-7, or 9.  If IRQ 9, properly tell the card
+  to use IRQ 2.  Thanks to Greg Crider (gcrider@iclnet.org) for finding and
+  locating this bug and testing the patch.
+- Insight drive recognised correctly again.
+- Motor-on wakeup version of the Iomega 250 drive added
+
+
+===== Release notes for ftape-2.06, 28/01/96 =====
+
+Special thanks go to Neal Friedman and Steven Sorbom for their
+help in producing and testing this release.
+
+I have continued to clean up the code, with an eye towards inclusion
+of ftape in Linus' official kernel (In fact, as I type this, I am
+running on a kernel with ftape support statically linked).  I have
+test-compiled ftape against my 1.2.13 tree without problems.
+Hopefully, everything should be OK for the v1.2.x people.
+
+WARNING! Alan Cox has mailed me that ftape does *NOT* work with
+Linux/SMP.  If you try to run ftape under Linux/SMP, it will cause a
+kernel deadlock (which is worse than a panic).
+
+- QIC-3020/TR-3: 1Mbps support works.  Neal is capable of reading and
+  writing data to a tape.  ftape will automatically detect the type of
+  tape (e.g. TR-3 vs QIC-80) and move the fdc in and out of
+  "perpendicular mode" as necessary.
+- 2Mbps support is disabled by default, since it is not fully
+  debugged.  If you are adventurous, remove -DFDC_82078SL in the
+  Makefile and see what happens :-)
+- fdc detection: silly bugs removed (Only 2Mbps fdcs were affected)
+  and added detection of the National Semiconductors PC8744 fdc chip
+  (used in the PC873xx "super-IO" chips).
+- Removed warning about incompatible types when compiling with Linux
+  1.2.x.
+- README.PCI updated with info about the DELL Dimension XPS P90.
+- Connor TST3200R added to detected drives.
+- `swapout' utility added to distribution.  It will dirty 5Meg of
+  memory, trying to swap out other programs.  Just say `make swapout'
+  to build it.  ftape will do this automatically Real Soon Now (ie:
+  when I have found out which kernel memory alloc function to call).
+
+
+===== Release notes for ftape-2.05, 08/01/96 =====
+
+- For v1.2.x Kernels, you must apply the patch linux-1.2/ksyms.patch to
+  the kernel and rebuild it (it adds the __get_dma_pages symbol to
+  ksyms.c).
+- Included new asm-i386/io.h file from v1.3.x kernel series, to enable
+  gcc v.2.7.[12] to compile v1.2.x kernels (linux-1.2/io.h).
+- Module versions: If you wish to compile ftape as a versioned module,
+  you must first compile your kernel with CONFIG_MODVERSIONS=y.
+  Otherwise, you will get complaints that <linux/modversions.h> does not
+  exist (if that happens, a `touch modversions.h' will help you out).
+- CLK_48MHZ: new define in the Makefile (default: non-zero).  If you have
+  a tape controller card that uses the i82078(-1) chip, but cannot get
+  it to work with ftape, try set it to 0 (and please report this).
+- QIC-3010/3020: Complete support is still missing, but will hopefully
+  come soon.  Steven Sorbom has kindly provided me with hints about
+  this.  Writing of QIC-3020 tapes definitely does NOT work (do not try
+  it! - the drive will not be in "perpendicular mode" and this will ruin
+  the formatting info on the tape).
+- ftape_num_buffers is out of fashion: use NR_BUFFERS instead (and
+  recompile if you want to change it :-).
+
+
+===== Release notes for ftape-2.04, 01/01/96 =====
+
+This version by Kai Harrekilde-Petersen <khp@dolphinics.no>
+
+- ALERT! Support for Kernels earlier then v1.1.85 is about to go away.
+  I intend to clean up some of the code (getting rid of an annoyingly
+  large numbers of #ifdef mostly), which means that support for
+  pre-1.1.85 kernels must go as well.
+- NR_FTAPE_BUFFERS is gone; You can instead select the number of dma
+  buffers by saying `insmod ftape.o ftape_num_buffer=<n>' instead.
+- Configure script gone.  ftape will now automagically determine your
+  kernel version by /usr/include/linux/version.h instead.
+- CONFIG_MODVERSIONS now work.  All combinations of versioned /
+  unversioned kernel and ftape module works (at least with my 1.3.52
+  kernel).
+- If you have problems with inserting ftape into an old (1.2.x)
+  kernel (e.g. insmod says "1.2.8 does not match 1.2.8), recompile
+  your modules utilities with your new compiler.
+- Reveal TB1400 drive added to vendors.h
+- Support for the i82078-1 (2Mbps) chip is coming along.  The
+  biggest problem is that I don't have such a card, which makes
+  testing / debugging somewhat problematic.  The second biggest
+  problem is that I do not have the QIC-3010/3020 standards either.
+  Status right now is that the chip is detected, and it should be
+  possible to put it into 2Mbps mode.  However, I do not know what
+  "extras" are needed to complete the support.  Although putting the
+  i82078 into 1Mbps mode ought to work out of the box, it doesn't
+  (right now, ftape complains about id am errors).
+
+
+===== Release notes for ftape-2.04beta5, 29/12/95 =====
+
+Bas offline linux-tape
+----------------------
+For reasons only known to the majordomo mail list processor, Bas was
+kicked off the linux-tape list sometime during the summer.  Being
+overworked at his for-pay job, he didn't notice it much.  Instead I
+(Kai, khp@dolphinics.no) has worked on ftape to produce the 2.04(beta)
+version.
+
+zftape
+------
+Note that there exists a much improved version of ftape, written by
+Claus-Justus Heine <claus@willi.math.rwth-aachen.de> which is named
+zftape, which conforms to the QIC-80 specs on how to mark backups, and
+is capable of doing automatic compression.  However, zftape makes
+substantial changes to ftape, and I (Kai) have therefore declined to
+integrate zftape into ftape.  Hopefully, this will happen soon.
+
+CONFIG_QIC117 removed from the kernel
+-------------------------------------
+The biggest change of all is that ftape now will allocate its dma
+buffers when it is inserted.  The means that the CONFIG_QIC117 option
+has disappeared from the Linux kernel as of v1.3.34.  If you have an
+earlier kernel, simply answer 'no' to the question will do the trick
+(if you get complains about __get_free_pages() missing, contact the
+linux-tape mailing list).
+
+Note that ftape-2.04beta will work equally well on kernels with and
+without `ftape support'.  The only catch is, that you will waste
+around 96-128Kb of precious DMA'able memory on a box that has ftape
+support compiled in.
+
+Now for the real changes:
+
+- FC-20 can now use DMA channels 1, 2, and 3. Thanks to Daniel
+  Cohen, catman@wpi.edu.
+- ftape no longer requires a (gigantic) 96Kb buffer to be statically
+  allocated by the kernel.
+- Added new Iomega drive (8882) to vendors.h
+- -fno-strength-reduce added to Makefile, since GCC is broken.
+- i82078-1 (2Mbps) FDC support started.
+
+
+===== Release notes for ftape-2.03b, 27/05/95 =====
+
+- Prevented verify_area to return error if called with zero length.
+- Fixed a bug in flush_buffers that caused too much padding to be
+  written when a final segment had bad sectors.
+- Increased maximum fast-seek overshoot value from 5 to 10 segments.
+- Breaking loop after 5 retries when positioning fails.
+- Fixed wrong calculation of tape length for QIC-3010 and QIC-3020
+  tapes (densities were swapped).
+- Fixed wrong calculation of overshoot on seek_forward: Wrong sign
+  of error.
+- Suppress (false) error message due to new tape loaded.
+- Added two new CMS drives (11c3 and 11c5) to vendors.h.
+
+
+===== Release notes for ftape-2.03a, 09/05/95 =====
+
+- Fixed display of old error (even if already cleared) in ftape_open.
+- Improved tape length detection, ioctls would fail for 425 ft tapes.
+  Until the tape length is calculated with data from the header
+  segment, we'll use worst-case values.
+- Clear eof_mark after rewinding ioctls.
+- Fixed wrong version message (2.03 had 2.02g id).
+- Fixed bug that caused the fdc to be reset very frequently.
+  This shouldn't affect normal operation but the timing of the
+  report routines has changed again and that may cause problems.
+  We'll just have to find out....
+- Implemented correct write precompensation setting for QIC-3010/3020.
+- Cleaned up fdc_interrupt_wait routine. Hope it still works :-)
+- Finally removed (already disabled) special eof mark handling for
+  gnu tar.
+- Changed order of get_dma_residue and disable_dma in fdc-isr.c
+  because the current order would fail on at least one system.
+  We're back to the original order again, hope (and expect) this
+  doesn't break any other system.
+
+
+===== Release notes for ftape-2.03, 07/05/95 =====
+
+(Changes refer to the first ftape-2.02 release)
+
+Support for wide and extended length tapes
+------------------------------------------
+The Conner TSM 420 and 850 drives are reported to be working.
+I haven't received any reports about other brands; the TSM 420
+and 850 seem to be the most widely used wide drives.
+Extended length tapes (425 ft) with normal QIC-80 drives
+are operating too (At least I've had no reports stating otherwise).
+_Not_ yet completely supported (although they may work) are
+QIC-3020 drives and 2 Mbps floppy disk controllers won't work at
+the highest speed.
+If someone is kind enough to send me one of these, I'll include
+support for it too ;-)
+
+Easier configuration
+--------------------
+Problems due to wrong settings in the Makefile are prevented
+by using a configuration script that sets the necessary (kernel
+version dependent) compile time options.
+This kernel version is now determined from the sources found
+at /usr/src/linux, or if not found, the old way using
+/proc/version.
+Versioned modules will be used automatically when supported
+by- and configured in- the kernel.
+Note that the current modules code (1.1.87) is still broken
+and _needs_ the fix included in the insmod directory.
+Please don't send me any more Oops reports caused by insmod :-(
+
+Reduced module size
+-------------------
+The standard module size is much reduced and some compile time
+options can even reduce it further. (I don't recommend this
+for normal use but it can be handy for rescue diskettes)
+
+Option:           Approx. module size:
+
+<standard>             150 Kb
+NO_TRACE               125 Kb
+NO_TRACE_AT_ALL         67 Kb
+
+
+Much improved driver interruption
+---------------------------------
+Most possible loops have been broken and signal detection
+has been improved.
+In most cases the driver can be aborted by ^C (SIGINT) and
+SIGKILL (kill -9) will generate be a sure kill.
+(Note that aborting a tape operation may damage the last
+data written to tape)
+
+Improved error recovery
+-----------------------
+Ftape now returns an error (ENODATA) to the application if
+a segment proves to be unrecoverable and then skips the
+bad segment.
+This causes most applications to continue to work (tar
+and afio) loosing only a small amount (up to 29 Kb) of data.
+Retried read operations will now be done slightly off-track
+to improve the chance of success. Serious head off-track
+errors will be detected.
+
+FC-10 and FC-20 controllers
+---------------------------
+Ftape now supports both the old CMS FC-10 and the newer FC-20
+controllers.
+Because the operation of these cards is still undocumented,
+thus far they will only work with the default settings (See
+Makefile). Any feed-back on how to use them with other settings
+will be welcome !
+Compilation will fail if one changes the settings to illegal
+values.
+
+Kernels and compilers
+---------------------
+Ftape is currently being developed using the 2.5.8 compiler.
+The older 2.4.5 probably works too (Set option in Makefile!).
+I have no experience with any later compilers nor Elf support.
+Any information on this is welcome.
+The latest kernel I have tested ftape with is 1.2.6.
+
+Compression
+-----------
+An impressive collection of changes for ftape including
+on-the-fly compression is still lying on my desk.
+If 2.03 proves to be reliable I might start integrating these
+but as usual, I'm short in time :-(
+
+Formatting
+----------
+There is still no way to format tapes under Linux. As far as
+I know all attempts to write such a program have died now.
+Since formatted tapes are rather common now, I think all we
+need is a utility that writes a worst case pattern and verifies
+that with the drive put in verify mode, reducing margins.
+Any takers ?
+
+Furthermore
+-----------
+Cleaned up messages.
+Prepared to support multiple tape drives on one fdc.
+Thanks to all the people who sent bug reports and helped me
+improve the driver. Without trying to be complete I'll mention
+Gary Anderson (without his accurate reports and unreliable
+hardware there wouldn't be a 2.03), Stefan Kneifel (FC-20),
+Robert Broughton (FC-20, you were almost there ;-), Bjorn
+Ekwall (for the versioned modules and buggy insmod ;-), Peter
+Fox, Christopher Oliver, Ralph Whittaker and not the least
+Linus Torvalds (for Linux and keeping me busy because of
+changes to the kernel ;-)
+Thanks to anyone I forgot, for the bug reports, the ftape
+bashing and the mental support...
+
+
+That's it for now. Have Fun,
+
+Bas.
+
+
+===== Release notes for ftape-2.02g, 06/05/95 =====
+
+- Added extra test to break read-id loop with signal.
+- Changed rewind code to handle negative overshoot for drives
+  that take very long to start or stop.
+- Let use of get/set i/o-regions depend on kernel version.
+- Changed code to use a more general test for conditional
+  compilations depending on kernel version.
+- Improved micro-step functionality to go off-track only
+  while reading (id & data).
+- Added failure on tape-not-referenced bit in ftape_command.
+- Added FOREVER option to read-wait routine.
+- Changed read-id to use shorter timeout causing smaller
+  rewinds on timeout.
+- Made kernel-interface functions static.
+
+
+===== Release notes for ftape-2.02f, 03/05/95 =====
+
+- Added support for dual tape drives on my system, extended Configure
+  script to detect host 'dodo'.
+- Log media defect in history if ecc failed and no data was returned.
+- Fixed Configure script that was failing for kernel versions with
+  double digit version or revision numbers.
+
+
+===== Release notes for ftape-2.02e, 01/05/95 =====
+
+- Fixed reposition loop at logical eot (failing read_id).
+- Fixed 34 segment offset when rewinding.
+- Added fast seek capability for more than 255 segments.
+- Fixed wrong busy result from ftape_command causing reverse
+  seek to fail.
+- Added breakout from infinite rewind loop (if something fails).
+
+
+===== Release notes for ftape-2.02d, 30/04/95 =====
+
+- Improved abortion on signals: Interrupt will make a graceful
+  exit, Kill will be less nice and should be used if everything
+  else fails.
+- Included check for tape-head off track.
+- Implemented exit from tape-start loop.
+- Added kernel io-port registration.
+- Implemented skip of failing segment (ENODATA) on ecc failure.
+  This allows afio and tar to continue when the tape is damaged.
+- Made distinction between drive names with different codes.
+
+
+===== Release notes for ftape-2.02c, 22/04/95 =====
+
+- Fixed too tight command queueing after tape stop/pause command
+  issued from within interrupt service routine (Showed as timeout
+  on Acknowledge errors during retries on some systems)
+- Tried to fix timeouts when using 425 ft tape because the extended
+  length doesn't seem to be detected by the hardware.
+  We now use the format code from the header segment so adjust the
+  timing after reading the header segment.
+- Fixed some messages stating 'unexpected something...' being not
+  unexpected anymore.
+- Started preparations for merge of dynamic buffer allocation and
+  compression code.
+- Changed some debug messages to include relevant segment information
+  at level 4.
+- Included early bail-out when drive offline, preventing a lot of
+  false messages.
+- Moved ftape_parameter_xxx() offsets into function instead of in calls.
+- Removed 'weird, drive busy but no data' error when caused by
+  an error during a read-id.
+- Improved 'timeout on acknowledge' diagnostics.
+- Moved MODULE option into Configure.
+- Reduced code size when no tracing at all was set (Claus Heine).
+- No longer log error code 0 (no error) as an error.
+
+
+===== Release notes for ftape-2.02b, 09/04/95 =====
+
+- Relaxed timing for status operation and displaying
+  abnormal results. Hopefully this shows what's going
+  wrong with the Conner TSM850R drives.
+- Created script for configuration, using version number
+  of kernel source if available, otherwise /proc/version.
+- Fixed conditionals in kernel-interface.c.
+- Removed unavoidable TRACE output.
+
+
+===== Release notes for ftape-2.02a, 01/04/95 =====
+
+- Implemented `new-style' (versioned) modules support for new
+  kernels.
+- Reduced size of module by moving static data to bss.
+- Now using version number of kernel source instead of running
+  kernel for kernel versions >= 1.1.82
+- Added feedback on drive speeds to vendor information.
+- Included fixed insmod sources to distribution (Let's hope
+  the modules distribution get fixed soon :-/).
+
+Note that I haven't yet implemented any of the code extension I
+received. I hope to find some time to do this soon.
+
+
+===== Release notes for ftape-2.02, 15/01/95 =====
+
+
+- Fixed failing repositioning when overshoot was incremented.
+- Fixed rate selection: Because of a deficiency in the QIC-117
+  specification one cannot distinguish between a not implemented
+  and a failing command. Therefor we now try to find out if the
+  drive does support this command before usage.
+- Fixed error retry using wrong offset in fdc-isr.
+- Improved retry code to retry only once on a single no-data
+  error in a segment.
+- Validate sector number extracted from eof mark because an
+  invalid file mark (due to ???) could cause kernel panic.
+- Split ftape-io.c into ftape-io.c and ftape-ctl.c files.
+- Corrected too high media error count after writing to
+  a bad tape.
+- Added #include <asm/segment.h> again because old kernel versions
+  need it.
+- Fixed fdc not being disabled when open failed because no tape
+  drive was found.
+- Fixed problem with soft error in sector 32 (shift operator with
+  shiftcount 32 is not defined).
+
+
+===== Release notes for ftape-2.01, 08/01/95 =====
+
+
+- Removed TESTING setting from distributed Makefile.
+- Fixed `mt asf' failure: Rewind was deferred to close which
+  overruled the fsf ioctl.
+- Prevented non-interruptible commands being interrupted.
+- Added missing timeout.pause setting.
+- Maximum tape speed read from drive type information table.
+  If the information is not in the table (0) the drive will
+  determine the speed itself and put a message in the logfile.
+  This information should then be added to the table in the
+  vendors.h file (and reported to me).
+- Added call to ftape_init_drive after soft reset for those
+  (antique) drives that don't do an implicit seek_load_point
+  after a reset or power up.
+- Don't try to set data rate if reset failed.
+- Prevent update of seek variables when starting from the
+  beginning or the end of the tape.
+- Fixed wrong adjustment of overshoot in seek_forward().
+- Added sync to Makefile (again).
+- Added code to diagnose timer problems (calibr.c).
+- Replaced time differences by timediff calls.
+- Removed reference to do_floppy from object for recent kernels.
+- Fixed wrong display of 'failing dma controller' message.
+- Removed various no longer used #include statements.
+- Added max. tape speed value to vendor-struct.
+- Changed ftape-command to check pre-conditions and wait
+  if needed.
+- Further updated qic117.h to rev G.
+- Combined command name table and restrictions table to one.
+  Extended this table with some new fields.
+- Increased timeout on Ack timer value and included code to
+  report out of spec behaviour.
+- Increased rewind timeout margin to calculated + 20%.
+- Improved data rate selection so it won't fail on some
+  older (pre standard) drives.
+- Changed initialisation code so drive will be rewound if the
+  driver is reloaded and the tape is not at bot.
+- Moved some of the flush operations from close to the ioctls.
+- Added exit code value to failing verify area message.
+- Loop until tape halted in smart-stop.
+- Fast seek handled specially if located at bot or eot.
+- Being more conservative on overshoot value.
+
+
+===== Release notes for ftape-2.00, 31/12/94 =====
+
+  The Install-guide is completely rewritten and now also includes
+some information on how to use the driver. If you're either new
+to ftape or new to Unix tape devices make sure to read it !
+
+  If you own a pci system and experience problems with the
+ftape driver make sure to read the README.PCI file. It contains
+some hints on how to fix your hardware.
+
+  For anybody who hasn't noticed: The version number of the
+driver has been incremented (The latest released version has
+been version 1.14d).
+  This has been done for two major reasons:
+
+  o  A new (better) error recovery scheme is implemented.
+  o  Support for new drive types has been added.
+
+  All these improvements/changes will probably include a couple
+of new (and old?) bugs. If you encounter any problems that you think
+I'm not yet aware of, feel free to send a report to <bas@vimec.nl>.
+  I recommend keeping a version of ftape-1.14d available, just
+in case ;-)
+
+  This version should work with all kernel versions from 1.0.9 up
+to 1.1.72 (and probably earlier and later versions too).
+
+
+Major new features:
+
+- Better handling of tapes with defects: When a sector repeatedly
+  (SOFT_RETRIES in ftape.h) cannot be written to or read from it is
+  marked as an hard error and gets skipped.
+  The error correction code can handle up to three of these hard
+  errors provided there are no other errors in that segment (32 Kb).
+  
+- Allows writing to tapes with defects (although the risk of loosing
+  data increases !)
+  Look for the media-defects entry printed with the statistics when
+  the tape is closed. A non-zero value here shows a bad tape.
+  [the actual count is wrong (too high), this is a known bug].
+
+- Use of backup header segment if first one is failing.
+
+- Support for extended length tapes with QIC-80: both 425 and 1100 ft.
+  0.25 inch tapes are now recognized and handled.
+
+- Support for new QIC-80 drives with 8 mm `wide' tapes (e.g. Conner
+  TSM 420).
+
+- Support for new QIC-3010 and QIC-3020 drives (experimental) with
+  both 0.25 inch and 8 mm tapes.
+
+Some minor features were added, a couple of small bugs were fixed and
+probably some new ones introduced ;-).
+
+[lseek() didn't make it into this version]
+
+Have fun,
+
+Bas.
+----
+ LocalWords:  ftape MCONFIG mt VFS zftape resp sftape proc subdir MTIOCVOLINFO
+ LocalWords:  MTIOCGETSIZE BOT EOD MTBSF zft kerneld modprobe kdtime contrib TR
+ LocalWords:  MTSETBLK afio uninstall texi www EIO QIC init sft eof aka dma GB
+ LocalWords:  SIGKILL MTIOCFTCMD mmap Iomega FDC fdc io gnumt mtio fc asm inb
+ LocalWords:  outb ft qic frontend TeXinfo irq mach MODVERSIONS CONFIG html dvi
+ LocalWords:  usr doc SMP Mb Dunno FIXME vtblc perl listtape volinfo fsf MTWEOF
+ LocalWords:  amanda degaussed ComByte DoublePlay whraven njackn com MTIOC vtbl
+ LocalWords:  GETBLKSZ MAKEDEV zftape's linux dif CVS Revison cp MTREW MTOFFL
+ LocalWords:  MTFSF BSF Marcin Dalecki GCC Config cpio swapout Kai Harrekilde
+ LocalWords:  Pederson khp dolphinics Justus claus momo rwth aachen Laarhoven
diff --git a/drivers/char/ftape/compressor/Makefile b/drivers/char/ftape/compressor/Makefile
new file mode 100644
index 0000000..1fbd6c4
--- /dev/null
+++ b/drivers/char/ftape/compressor/Makefile
@@ -0,0 +1,31 @@
+#
+#       Copyright (C) 1997 Claus-Justus Heine.
+#
+# 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, 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; see the file COPYING.  If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Source: /homes/cvs/ftape-stacked/ftape/compressor/Makefile,v $
+# $Revision: 1.1 $
+# $Date: 1997/10/05 19:12:28 $
+#
+#      Makefile for the optional compressor for th zftape VFS
+#      interface to the QIC-40/80/3010/3020 floppy-tape driver for
+#      Linux.
+#
+
+obj-$(CONFIG_ZFT_COMPRESSOR) += zft-compressor.o
+
+zft-compressor-objs := zftape-compress.o lzrw3.o
+
+CFLAGS_lzrw3.o	:= -O6 -funroll-all-loops
diff --git a/drivers/char/ftape/compressor/lzrw3.c b/drivers/char/ftape/compressor/lzrw3.c
new file mode 100644
index 0000000..a032a0e
--- /dev/null
+++ b/drivers/char/ftape/compressor/lzrw3.c
@@ -0,0 +1,743 @@
+/*
+ * $Source: /homes/cvs/ftape-stacked/ftape/compressor/lzrw3.c,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/10/05 19:12:29 $
+ *
+ * Implementation of Ross Williams lzrw3 algorithm. Adaption for zftape.
+ *
+ */
+
+#include "../compressor/lzrw3.h"       /* Defines single exported function "compress".   */
+
+/******************************************************************************/
+/*                                                                            */
+/*                                    LZRW3.C                                 */
+/*                                                                            */
+/******************************************************************************/
+/*                                                                            */
+/* Author  : Ross Williams.                                                   */
+/* Date    : 30-Jun-1991.                                                     */
+/* Release : 1.                                                               */
+/*                                                                            */
+/******************************************************************************/
+/*                                                                            */
+/* This file contains an implementation of the LZRW3 data compression         */
+/* algorithm in C.                                                            */
+/*                                                                            */
+/* The algorithm is a general purpose compression algorithm that runs fast    */
+/* and gives reasonable compression. The algorithm is a member of the Lempel  */
+/* Ziv family of algorithms and bases its compression on the presence in the  */
+/* data of repeated substrings.                                               */
+/*                                                                            */
+/* This algorithm is unpatented and the code is public domain. As the         */
+/* algorithm is based on the LZ77 class of algorithms, it is unlikely to be   */
+/* the subject of a patent challenge.                                         */
+/*                                                                            */
+/* Unlike the LZRW1 and LZRW1-A algorithms, the LZRW3 algorithm is            */
+/* deterministic and is guaranteed to yield the same compressed               */
+/* representation for a given file each time it is run.                       */
+/*                                                                            */
+/* The LZRW3 algorithm was originally designed and implemented                */
+/* by Ross Williams on 31-Dec-1990.                                           */
+/*                                                                            */
+/* Here are the results of applying this code, compiled under THINK C 4.0     */
+/* and running on a Mac-SE (8MHz 68000), to the standard calgary corpus.      */
+/*                                                                            */
+/*    +----------------------------------------------------------------+      */
+/*    | DATA COMPRESSION TEST                                          |      */
+/*    | =====================                                          |      */
+/*    | Time of run     : Sun 30-Jun-1991 09:31PM                      |      */
+/*    | Timing accuracy : One part in 100                              |      */
+/*    | Context length  : 262144 bytes (= 256.0000K)                   |      */
+/*    | Test suite      : Calgary Corpus Suite                         |      */
+/*    | Files in suite  : 14                                           |      */
+/*    | Algorithm       : LZRW3                                        |      */
+/*    | Note: All averages are calculated from the un-rounded values.  |      */
+/*    +----------------------------------------------------------------+      */
+/*    | File Name   Length  CxB  ComLen  %Remn  Bits  Com K/s  Dec K/s |      */
+/*    | ----------  ------  ---  ------  -----  ----  -------  ------- |      */
+/*    | rpus:Bib.D  111261    1   55033   49.5  3.96    19.46    32.27 |      */
+/*    | us:Book1.D  768771    3  467962   60.9  4.87    17.03    31.07 |      */
+/*    | us:Book2.D  610856    3  317102   51.9  4.15    19.39    34.15 |      */
+/*    | rpus:Geo.D  102400    1   82424   80.5  6.44    11.65    18.18 |      */
+/*    | pus:News.D  377109    2  205670   54.5  4.36    17.14    27.47 |      */
+/*    | pus:Obj1.D   21504    1   13027   60.6  4.85    13.40    18.95 |      */
+/*    | pus:Obj2.D  246814    1  116286   47.1  3.77    19.31    30.10 |      */
+/*    | s:Paper1.D   53161    1   27522   51.8  4.14    18.60    31.15 |      */
+/*    | s:Paper2.D   82199    1   45160   54.9  4.40    18.45    32.84 |      */
+/*    | rpus:Pic.D  513216    2  122388   23.8  1.91    35.29    51.05 |      */
+/*    | us:Progc.D   39611    1   19669   49.7  3.97    18.87    30.64 |      */
+/*    | us:Progl.D   71646    1   28247   39.4  3.15    24.34    40.66 |      */
+/*    | us:Progp.D   49379    1   19377   39.2  3.14    23.91    39.23 |      */
+/*    | us:Trans.D   93695    1   33481   35.7  2.86    25.48    40.37 |      */
+/*    +----------------------------------------------------------------+      */
+/*    | Average     224401    1  110953   50.0  4.00    20.17    32.72 |      */
+/*    +----------------------------------------------------------------+      */
+/*                                                                            */
+/******************************************************************************/
+
+/******************************************************************************/
+
+/* The following structure is returned by the "compress" function below when  */
+/* the user asks the function to return identifying information.              */
+/* The most important field in the record is the working memory field which   */
+/* tells the calling program how much working memory should be passed to      */
+/* "compress" when it is called to perform a compression or decompression.    */
+/* LZRW3 uses the same amount of memory during compression and decompression. */
+/* For more information on this structure see "compress.h".                   */
+  
+#define U(X)            ((ULONG) X)
+#define SIZE_P_BYTE     (U(sizeof(UBYTE *)))
+#define SIZE_WORD       (U(sizeof(UWORD  )))
+#define ALIGNMENT_FUDGE (U(16))
+#define MEM_REQ ( U(4096)*(SIZE_P_BYTE) + ALIGNMENT_FUDGE )
+
+static struct compress_identity identity =
+{
+ U(0x032DDEA8),                           /* Algorithm identification number. */
+ MEM_REQ,                                 /* Working memory (bytes) required. */
+ "LZRW3",                                 /* Name of algorithm.               */
+ "1.0",                                   /* Version number of algorithm.     */
+ "31-Dec-1990",                           /* Date of algorithm.               */
+ "Public Domain",                         /* Copyright notice.                */
+ "Ross N. Williams",                      /* Author of algorithm.             */
+ "Renaissance Software",                  /* Affiliation of author.           */
+ "Public Domain"                          /* Vendor of algorithm.             */
+};
+ 
+LOCAL void compress_compress  (UBYTE *,UBYTE *,ULONG,UBYTE *, LONG *);
+LOCAL void compress_decompress(UBYTE *,UBYTE *,LONG, UBYTE *, ULONG *);
+
+/******************************************************************************/
+
+/* This function is the only function exported by this module.                */
+/* Depending on its first parameter, the function can be requested to         */
+/* compress a block of memory, decompress a block of memory, or to identify   */
+/* itself. For more information, see the specification file "compress.h".     */
+
+EXPORT void lzrw3_compress(
+	UWORD     action,      /* Action to be performed.		*/
+	UBYTE   *wrk_mem,	/* Address of working memory we can use.*/
+	UBYTE   *src_adr,	/* Address of input data.		*/
+	LONG     src_len,	/* Length  of input data.		*/
+	UBYTE   *dst_adr,	/* Address to put output data.		*/
+	void  *p_dst_len	/* Address of longword for length of output data.*/
+)
+{
+ switch (action)
+   {
+    case COMPRESS_ACTION_IDENTITY:
+       *((struct compress_identity **)p_dst_len)= &identity;
+       break;
+    case COMPRESS_ACTION_COMPRESS:
+       compress_compress(wrk_mem,src_adr,src_len,dst_adr,(LONG *)p_dst_len);
+       break;
+    case COMPRESS_ACTION_DECOMPRESS:
+       compress_decompress(wrk_mem,src_adr,src_len,dst_adr,(LONG *)p_dst_len);
+       break;
+   }
+}
+
+/******************************************************************************/
+/*                                                                            */
+/* BRIEF DESCRIPTION OF THE LZRW3 ALGORITHM                                   */
+/* ========================================                                   */
+/* The LZRW3 algorithm is identical to the LZRW1-A algorithm except that      */
+/* instead of transmitting history offsets, it transmits hash table indexes.  */
+/* In order to decode the indexes, the decompressor must maintain an          */
+/* identical hash table. Copy items are straightforward:when the decompressor */
+/* receives a copy item, it simply looks up the hash table to translate the   */
+/* index into a pointer into the data already decompressed. To update the     */
+/* hash table, it replaces the same table entry with a pointer to the start   */
+/* of the newly decoded phrase. The tricky part is with literal items, for at */
+/* the time that the decompressor receives a literal item the decompressor    */
+/* does not have the three bytes in the Ziv (that the compressor has) to      */
+/* perform the three-byte hash. To solve this problem, in LZRW3, both the     */
+/* compressor and decompressor are wired up so that they "buffer" these       */
+/* literals and update their hash tables only when three bytes are available. */
+/* This makes the maximum buffering 2 bytes.                                  */
+/*                                                                            */
+/* Replacement of offsets by hash table indexes yields a few percent extra    */
+/* compression at the cost of some speed. LZRW3 is slower than LZRW1, LZRW1-A */
+/* and LZRW2, but yields better compression.                                  */
+/*                                                                            */
+/* Extra compression could be obtained by using a hash table of depth two.    */
+/* However, increasing the depth above one incurs a significant decrease in   */
+/* compression speed which was not considered worthwhile. Another reason for  */
+/* keeping the depth down to one was to allow easy comparison with the        */
+/* LZRW1-A and LZRW2 algorithms so as to demonstrate the exact effect of the  */
+/* use of direct hash indexes.                                                */
+/*                                                                            */
+/*                                  +---+                                     */
+/*                                  |___|4095                                 */
+/*                                  |___|                                     */
+/*              +---------------------*_|<---+   /----+---\                   */
+/*              |                   |___|    +---|Hash    |                   */
+/*              |                   |___|        |Function|                   */
+/*              |                   |___|        \--------/                   */
+/*              |                   |___|0            ^                       */
+/*              |                   +---+             |                       */
+/*              |                   Hash        +-----+                       */
+/*              |                   Table       |                             */
+/*              |                              ---                            */
+/*              v                              ^^^                            */
+/*      +-------------------------------------|----------------+              */
+/*      ||||||||||||||||||||||||||||||||||||||||||||||||||||||||              */
+/*      +-------------------------------------|----------------+              */
+/*      |                                     |1......18|      |              */
+/*      |<------- Lempel=History ------------>|<--Ziv-->|      |              */
+/*      |     (=bytes already processed)      |<-Still to go-->|              */
+/*      |<-------------------- INPUT BLOCK ------------------->|              */
+/*                                                                            */
+/* The diagram above for LZRW3 looks almost identical to the diagram for      */
+/* LZRW1. The difference is that in LZRW3, the compressor transmits hash      */
+/* table indices instead of Lempel offsets. For this to work, the             */
+/* decompressor must maintain a hash table as well as the compressor and both */
+/* compressor and decompressor must "buffer" literals, as the decompressor    */
+/* cannot hash phrases commencing with a literal until another two bytes have */
+/* arrived.                                                                   */
+/*                                                                            */
+/*  LZRW3 Algorithm Execution Summary                                         */
+/*  ---------------------------------                                         */
+/*  1. Hash the first three bytes of the Ziv to yield a hash table index h.   */
+/*  2. Look up the hash table yielding history pointer p.                     */
+/*  3. Match where p points with the Ziv. If there is a match of three or     */
+/*     more bytes, code those bytes (in the Ziv) as a copy item, otherwise    */
+/*     code the next byte in the Ziv as a literal item.                       */
+/*  4. Update the hash table as possible subject to the constraint that only  */
+/*     phrases commencing three bytes back from the Ziv can be hashed and     */
+/*     entered into the hash table. (This enables the decompressor to keep    */
+/*     pace). See the description and code for more details.                  */
+/*                                                                            */
+/******************************************************************************/
+/*                                                                            */
+/*                     DEFINITION OF COMPRESSED FILE FORMAT                   */
+/*                     ====================================                   */
+/*  * A compressed file consists of a COPY FLAG followed by a REMAINDER.      */
+/*  * The copy flag CF uses up four bytes with the first byte being the       */
+/*    least significant.                                                      */
+/*  * If CF=1, then the compressed file represents the remainder of the file  */
+/*    exactly. Otherwise CF=0 and the remainder of the file consists of zero  */
+/*    or more GROUPS, each of which represents one or more bytes.             */
+/*  * Each group consists of two bytes of CONTROL information followed by     */
+/*    sixteen ITEMs except for the last group which can contain from one      */
+/*    to sixteen items.                                                       */
+/*  * An item can be either a LITERAL item or a COPY item.                    */
+/*  * Each item corresponds to a bit in the control bytes.                    */
+/*  * The first control byte corresponds to the first 8 items in the group    */
+/*    with bit 0 corresponding to the first item in the group and bit 7 to    */
+/*    the eighth item in the group.                                           */
+/*  * The second control byte corresponds to the second 8 items in the group  */
+/*    with bit 0 corresponding to the ninth item in the group and bit 7 to    */
+/*    the sixteenth item in the group.                                        */
+/*  * A zero bit in a control word means that the corresponding item is a     */
+/*    literal item. A one bit corresponds to a copy item.                     */
+/*  * A literal item consists of a single byte which represents itself.       */
+/*  * A copy item consists of two bytes that represent from 3 to 18 bytes.    */
+/*  * The first  byte in a copy item will be denoted C1.                      */
+/*  * The second byte in a copy item will be denoted C2.                      */
+/*  * Bits will be selected using square brackets.                            */
+/*    For example: C1[0..3] is the low nibble of the first control byte.      */
+/*    of copy item C1.                                                        */
+/*  * The LENGTH of a copy item is defined to be C1[0..3]+3 which is a number */
+/*    in the range [3,18].                                                    */
+/*  * The INDEX of a copy item is defined to be C1[4..7]*256+C2[0..8] which   */
+/*    is a number in the range [0,4095].                                      */
+/*  * A copy item represents the sequence of bytes                            */
+/*       text[POS-OFFSET..POS-OFFSET+LENGTH-1] where                          */
+/*          text   is the entire text of the uncompressed string.             */
+/*          POS    is the index in the text of the character following the    */
+/*                   string represented by all the items preceeding the item  */
+/*                   being defined.                                           */
+/*          OFFSET is obtained from INDEX by looking up the hash table.       */
+/*                                                                            */
+/******************************************************************************/
+
+/* The following #define defines the length of the copy flag that appears at  */
+/* the start of the compressed file. The value of four bytes was chosen       */
+/* because the fast_copy routine on my Macintosh runs faster if the source    */
+/* and destination blocks are relatively longword aligned.                    */
+/* The actual flag data appears in the first byte. The rest are zeroed so as  */
+/* to normalize the compressed representation (i.e. not non-deterministic).   */
+#define FLAG_BYTES 4
+
+/* The following #defines define the meaning of the values of the copy        */
+/* flag at the start of the compressed file.                                  */
+#define FLAG_COMPRESS 0     /* Signals that output was result of compression. */
+#define FLAG_COPY     1     /* Signals that output was simply copied over.    */
+
+/* The 68000 microprocessor (on which this algorithm was originally developed */
+/* is fussy about non-aligned arrays of words. To avoid these problems the    */
+/* following macro can be used to "waste" from 0 to 3 bytes so as to align    */
+/* the argument pointer.                                                      */
+#define ULONG_ALIGN_UP(X) ((((ULONG)X)+sizeof(ULONG)-1)&~(sizeof(ULONG)-1))
+
+
+/* The following constant defines the maximum length of an uncompressed item. */
+/* This definition must not be changed; its value is hardwired into the code. */
+/* The longest number of bytes that can be spanned by a single item is 18     */
+/* for the longest copy item.                                                 */
+#define MAX_RAW_ITEM (18)
+
+/* The following constant defines the maximum length of an uncompressed group.*/
+/* This definition must not be changed; its value is hardwired into the code. */
+/* A group contains at most 16 items which explains this definition.          */
+#define MAX_RAW_GROUP (16*MAX_RAW_ITEM)
+
+/* The following constant defines the maximum length of a compressed group.   */
+/* This definition must not be changed; its value is hardwired into the code. */
+/* A compressed group consists of two control bytes followed by up to 16      */
+/* compressed items each of which can have a maximum length of two bytes.     */
+#define MAX_CMP_GROUP (2+16*2)
+
+/* The following constant defines the number of entries in the hash table.    */
+/* This definition must not be changed; its value is hardwired into the code. */
+#define HASH_TABLE_LENGTH (4096)
+
+/* LZRW3, unlike LZRW1(-A), must initialize its hash table so as to enable    */
+/* the compressor and decompressor to stay in step maintaining identical hash */
+/* tables. In an early version of the algorithm, the tables were simply       */
+/* initialized to zero and a check for zero was included just before the      */
+/* matching code. However, this test costs time. A better solution is to      */
+/* initialize all the entries in the hash table to point to a constant        */
+/* string. The decompressor does the same. This solution requires no extra    */
+/* test. The contents of the string do not matter so long as the string is    */
+/* the same for the compressor and decompressor and contains at least         */
+/* MAX_RAW_ITEM bytes. I chose consecutive decimal digits because they do not */
+/* have white space problems (e.g. there is no chance that the compiler will  */
+/* replace more than one space by a TAB) and because they make the length of  */
+/* the string obvious by inspection.                                          */
+#define START_STRING_18 ((UBYTE *) "123456789012345678")
+
+/* In this algorithm, hash values have to be calculated at more than one      */
+/* point. The following macro neatens the code up for this.                   */
+#define HASH(PTR) \
+   (((40543*(((*(PTR))<<8)^((*((PTR)+1))<<4)^(*((PTR)+2))))>>4) & 0xFFF)
+
+/******************************************************************************/
+
+/* Input  : Hand over the required amount of working memory in p_wrk_mem.     */
+/* Input  : Specify input block using p_src_first and src_len.                */
+/* Input  : Point p_dst_first to the start of the output zone (OZ).           */
+/* Input  : Point p_dst_len to a ULONG to receive the output length.          */
+/* Input  : Input block and output zone must not overlap.                     */
+/* Output : Length of output block written to *p_dst_len.                     */
+/* Output : Output block in Mem[p_dst_first..p_dst_first+*p_dst_len-1]. May   */
+/* Output : write in OZ=Mem[p_dst_first..p_dst_first+src_len+MAX_CMP_GROUP-1].*/
+/* Output : Upon completion guaranteed *p_dst_len<=src_len+FLAG_BYTES.        */
+LOCAL void compress_compress(UBYTE *p_wrk_mem,
+			     UBYTE *p_src_first, ULONG  src_len,
+			     UBYTE *p_dst_first, LONG  *p_dst_len)
+{
+ /* p_src and p_dst step through the source and destination blocks.           */
+ register UBYTE *p_src = p_src_first;
+ register UBYTE *p_dst = p_dst_first;
+ 
+ /* The following variables are never modified and are used in the            */
+ /* calculations that determine when the main loop terminates.                */
+ UBYTE *p_src_post  = p_src_first+src_len;
+ UBYTE *p_dst_post  = p_dst_first+src_len;
+ UBYTE *p_src_max1  = p_src_first+src_len-MAX_RAW_ITEM;
+ UBYTE *p_src_max16 = p_src_first+src_len-MAX_RAW_ITEM*16;
+ 
+ /* The variables 'p_control' and 'control' are used to buffer control bits.  */
+ /* Before each group is processed, the next two bytes of the output block    */
+ /* are set aside for the control word for the group about to be processed.   */
+ /* 'p_control' is set to point to the first byte of that word. Meanwhile,    */
+ /* 'control' buffers the control bits being generated during the processing  */
+ /* of the group. Instead of having a counter to keep track of how many items */
+ /* have been processed (=the number of bits in the control word), at the     */
+ /* start of each group, the top word of 'control' is filled with 1 bits.     */
+ /* As 'control' is shifted for each item, the 1 bits in the top word are     */
+ /* absorbed or destroyed. When they all run out (i.e. when the top word is   */
+ /* all zero bits, we know that we are at the end of a group.                 */
+# define TOPWORD 0xFFFF0000
+ UBYTE *p_control;
+ register ULONG control=TOPWORD;
+ 
+ /* THe variable 'hash' always points to the first element of the hash table. */
+ UBYTE **hash= (UBYTE **)  ULONG_ALIGN_UP(p_wrk_mem);
+ 
+ /* The following two variables represent the literal buffer. p_h1 points to  */
+ /* the hash table entry corresponding to the youngest literal. p_h2 points   */
+ /* to the hash table entry corresponding to the second youngest literal.     */
+ /* Note: p_h1=0=>p_h2=0 because zero values denote absence of a pending      */
+ /* literal. The variables are initialized to zero meaning an empty "buffer". */
+ UBYTE **p_h1=NULL;
+ UBYTE **p_h2=NULL;
+  
+ /* To start, we write the flag bytes. Being optimistic, we set the flag to   */
+ /* FLAG_COMPRESS. The remaining flag bytes are zeroed so as to keep the      */
+ /* algorithm deterministic.                                                  */
+ *p_dst++=FLAG_COMPRESS;
+ {UWORD i; for (i=2;i<=FLAG_BYTES;i++) *p_dst++=0;}
+
+ /* Reserve the first word of output as the control word for the first group. */
+ /* Note: This is undone at the end if the input block is empty.              */
+ p_control=p_dst; p_dst+=2;
+ 
+ /* Initialize all elements of the hash table to point to a constant string.  */
+ /* Use of an unrolled loop speeds this up considerably.                      */
+ {UWORD i; UBYTE **p_h=hash;
+#  define ZH *p_h++=START_STRING_18
+  for (i=0;i<256;i++)     /* 256=HASH_TABLE_LENGTH/16. */
+    {ZH;ZH;ZH;ZH;
+     ZH;ZH;ZH;ZH;
+     ZH;ZH;ZH;ZH;
+     ZH;ZH;ZH;ZH;}
+ }
+
+ /* The main loop processes either 1 or 16 items per iteration. As its        */
+ /* termination logic is complicated, I have opted for an infinite loop       */
+ /* structure containing 'break' and 'goto' statements.                       */
+ while (TRUE)
+   {/* Begin main processing loop. */
+   
+    /* Note: All the variables here except unroll should be defined within    */
+    /*       the inner loop. Unfortunately the loop hasn't got a block.       */
+     register UBYTE *p;         /* Scans through targ phrase during matching. */
+     register UBYTE *p_ziv= NULL ;     /* Points to first byte of current Ziv.       */
+     register UWORD unroll;     /* Loop counter for unrolled inner loop.      */
+     register UWORD index;      /* Index of current hash table entry.         */
+     register UBYTE **p_h0 = NULL ;     /* Pointer to current hash table entry.       */
+     
+    /* Test for overrun and jump to overrun code if necessary.                */
+    if (p_dst>p_dst_post)
+       goto overrun;
+       
+    /* The following cascade of if statements efficiently catches and deals   */
+    /* with varying degrees of closeness to the end of the input block.       */
+    /* When we get very close to the end, we stop updating the table and      */
+    /* code the remaining bytes as literals. This makes the code simpler.     */
+    unroll=16;
+    if (p_src>p_src_max16)
+      {
+       unroll=1;
+       if (p_src>p_src_max1)
+         {
+          if (p_src==p_src_post)
+             break;
+          else
+             goto literal;
+         }
+      }
+         
+    /* This inner unrolled loop processes 'unroll' (whose value is either 1   */
+    /* or 16) items. I have chosen to implement this loop with labels and     */
+    /* gotos to heighten the ease with which the loop may be implemented with */
+    /* a single decrement and branch instruction in assembly language and     */
+    /* also because the labels act as highly readable place markers.          */
+    /* (Also because we jump into the loop for endgame literals (see above)). */
+    
+    begin_unrolled_loop:
+    
+       /* To process the next phrase, we hash the next three bytes and use    */
+       /* the resultant hash table index to look up the hash table. A pointer */
+       /* to the entry is stored in p_h0 so as to avoid an array lookup. The  */
+       /* hash table entry *p_h0 is looked up yielding a pointer p to a       */
+       /* potential match of the Ziv in the history.                          */
+       index=HASH(p_src);
+       p_h0=&hash[index];
+       p=*p_h0;
+       
+       /* Having looked up the candidate position, we are in a position to    */
+       /* attempt a match. The match loop has been unrolled using the PS      */
+       /* macro so that failure within the first three bytes automatically    */
+       /* results in the literal branch being taken. The coding is simple.    */
+       /* p_ziv saves p_src so we can let p_src wander.                       */
+#       define PS *p++!=*p_src++
+       p_ziv=p_src;
+       if (PS || PS || PS)
+         {
+          /* Literal. */
+          
+          /* Code the literal byte as itself and a zero control bit.          */
+          p_src=p_ziv; literal: *p_dst++=*p_src++; control&=0xFFFEFFFF;
+          
+          /* We have just coded a literal. If we had two pending ones, that   */
+          /* makes three and we can update the hash table.                    */
+          if (p_h2!=0)
+             {*p_h2=p_ziv-2;}
+             
+          /* In any case, rotate the hash table pointers for next time. */
+          p_h2=p_h1; p_h1=p_h0;
+          
+         }
+       else
+         {
+          /* Copy */
+          
+          /* Match up to 15 remaining bytes using an unrolled loop and code. */
+#if 0
+          PS || PS || PS || PS || PS || PS || PS || PS ||
+          PS || PS || PS || PS || PS || PS || PS || p_src++;
+#else     
+          if (
+               !( PS || PS || PS || PS || PS || PS || PS || PS ||
+                  PS || PS || PS || PS || PS || PS || PS ) 
+             ) p_src++;
+#endif
+          *p_dst++=((index&0xF00)>>4)|(--p_src-p_ziv-3);
+          *p_dst++=index&0xFF;
+          
+          /* As we have just coded three bytes, we are now in a position to   */
+          /* update the hash table with the literal bytes that were pending   */
+          /* upon the arrival of extra context bytes.                         */
+          if (p_h1!=0)
+            {
+             if (p_h2)
+               {*p_h2=p_ziv-2; p_h2=NULL;}
+             *p_h1=p_ziv-1; p_h1=NULL;
+            }
+            
+          /* In any case, we can update the hash table based on the current   */
+          /* position as we just coded at least three bytes in a copy items.  */
+          *p_h0=p_ziv;
+          
+         }
+       control>>=1;
+                
+       /* This loop is all set up for a decrement and jump instruction! */
+#ifndef linux
+`    end_unrolled_loop: if (--unroll) goto begin_unrolled_loop;
+#else
+    /* end_unrolled_loop: */ if (--unroll) goto begin_unrolled_loop;
+#endif
+
+    /* At this point it will nearly always be the end of a group in which     */
+    /* case, we have to do some control-word processing. However, near the    */
+    /* end of the input block, the inner unrolled loop is only executed once. */
+    /* This necessitates the 'if' test.                                       */
+    if ((control&TOPWORD)==0)
+      {
+       /* Write the control word to the place we saved for it in the output. */
+       *p_control++=  control     &0xFF;
+       *p_control  = (control>>8) &0xFF;
+
+       /* Reserve the next word in the output block for the control word */
+       /* for the group about to be processed.                           */
+       p_control=p_dst; p_dst+=2;
+       
+       /* Reset the control bits buffer. */
+       control=TOPWORD;
+      }
+          
+   } /* End main processing loop. */
+   
+ /* After the main processing loop has executed, all the input bytes have     */
+ /* been processed. However, the control word has still to be written to the  */
+ /* word reserved for it in the output at the start of the most recent group. */
+ /* Before writing, the control word has to be shifted so that all the bits   */
+ /* are in the right place. The "empty" bit positions are filled with 1s      */
+ /* which partially fill the top word.                                        */
+ while(control&TOPWORD) control>>=1;
+ *p_control++= control     &0xFF;
+ *p_control++=(control>>8) &0xFF;
+ 
+ /* If the last group contained no items, delete the control word too.        */
+ if (p_control==p_dst) p_dst-=2;
+ 
+ /* Write the length of the output block to the dst_len parameter and return. */
+ *p_dst_len=p_dst-p_dst_first;                           
+ return;
+ 
+ /* Jump here as soon as an overrun is detected. An overrun is defined to     */
+ /* have occurred if p_dst>p_dst_first+src_len. That is, the moment the       */
+ /* length of the output written so far exceeds the length of the input block.*/
+ /* The algorithm checks for overruns at least at the end of each group       */
+ /* which means that the maximum overrun is MAX_CMP_GROUP bytes.              */
+ /* Once an overrun occurs, the only thing to do is to set the copy flag and  */
+ /* copy the input over.                                                      */
+ overrun:
+#if 0
+ *p_dst_first=FLAG_COPY;
+ fast_copy(p_src_first,p_dst_first+FLAG_BYTES,src_len);
+ *p_dst_len=src_len+FLAG_BYTES;
+#else
+ fast_copy(p_src_first,p_dst_first,src_len);
+ *p_dst_len= -src_len; /* return a negative number to indicate uncompressed data */
+#endif
+}
+
+/******************************************************************************/
+
+/* Input  : Hand over the required amount of working memory in p_wrk_mem.     */
+/* Input  : Specify input block using p_src_first and src_len.                */
+/* Input  : Point p_dst_first to the start of the output zone.                */
+/* Input  : Point p_dst_len to a ULONG to receive the output length.          */
+/* Input  : Input block and output zone must not overlap. User knows          */
+/* Input  : upperbound on output block length from earlier compression.       */
+/* Input  : In any case, maximum expansion possible is nine times.            */
+/* Output : Length of output block written to *p_dst_len.                     */
+/* Output : Output block in Mem[p_dst_first..p_dst_first+*p_dst_len-1].       */
+/* Output : Writes only  in Mem[p_dst_first..p_dst_first+*p_dst_len-1].       */
+LOCAL void compress_decompress( UBYTE *p_wrk_mem,
+				UBYTE *p_src_first, LONG   src_len,
+				UBYTE *p_dst_first, ULONG *p_dst_len)
+{
+ /* Byte pointers p_src and p_dst scan through the input and output blocks.   */
+ register UBYTE *p_src = p_src_first+FLAG_BYTES;
+ register UBYTE *p_dst = p_dst_first;
+ /* we need to avoid a SEGV when trying to uncompress corrupt data */
+ register UBYTE *p_dst_post = p_dst_first + *p_dst_len;
+
+ /* The following two variables are never modified and are used to control    */
+ /* the main loop.                                                            */
+ UBYTE *p_src_post  = p_src_first+src_len;
+ UBYTE *p_src_max16 = p_src_first+src_len-(MAX_CMP_GROUP-2);
+ 
+ /* The hash table is the only resident of the working memory. The hash table */
+ /* contains HASH_TABLE_LENGTH=4096 pointers to positions in the history. To  */
+ /* keep Macintoshes happy, it is longword aligned.                           */
+ UBYTE **hash = (UBYTE **) ULONG_ALIGN_UP(p_wrk_mem);
+
+ /* The variable 'control' is used to buffer the control bits which appear in */
+ /* groups of 16 bits (control words) at the start of each compressed group.  */
+ /* When each group is read, bit 16 of the register is set to one. Whenever   */
+ /* a new bit is needed, the register is shifted right. When the value of the */
+ /* register becomes 1, we know that we have reached the end of a group.      */
+ /* Initializing the register to 1 thus instructs the code to follow that it  */
+ /* should read a new control word immediately.                               */
+ register ULONG control=1;
+ 
+ /* The value of 'literals' is always in the range 0..3. It is the number of  */
+ /* consecutive literal items just seen. We have to record this number so as  */
+ /* to know when to update the hash table. When literals gets to 3, there     */
+ /* have been three consecutive literals and we can update at the position of */
+ /* the oldest of the three.                                                  */
+ register UWORD literals=0;
+ 
+ /* Check the leading copy flag to see if the compressor chose to use a copy  */
+ /* operation instead of a compression operation. If a copy operation was     */
+ /* used, then all we need to do is copy the data over, set the output length */
+ /* and return.                                                               */
+#if 0
+ if (*p_src_first==FLAG_COPY)
+   {
+    fast_copy(p_src_first+FLAG_BYTES,p_dst_first,src_len-FLAG_BYTES);
+    *p_dst_len=src_len-FLAG_BYTES;
+    return;
+   }
+#else
+  if ( src_len < 0 )
+  {                                            
+   fast_copy(p_src_first,p_dst_first,-src_len );
+   *p_dst_len = (ULONG)-src_len;
+   return;
+  }
+#endif
+   
+ /* Initialize all elements of the hash table to point to a constant string.  */
+ /* Use of an unrolled loop speeds this up considerably.                      */
+ {UWORD i; UBYTE **p_h=hash;
+#  define ZJ *p_h++=START_STRING_18
+  for (i=0;i<256;i++)     /* 256=HASH_TABLE_LENGTH/16. */
+    {ZJ;ZJ;ZJ;ZJ;
+     ZJ;ZJ;ZJ;ZJ;
+     ZJ;ZJ;ZJ;ZJ;
+     ZJ;ZJ;ZJ;ZJ;}
+ }
+
+ /* The outer loop processes either 1 or 16 items per iteration depending on  */
+ /* how close p_src is to the end of the input block.                         */
+ while (p_src!=p_src_post)
+   {/* Start of outer loop */
+   
+    register UWORD unroll;   /* Counts unrolled loop executions.              */
+    
+    /* When 'control' has the value 1, it means that the 16 buffered control  */
+    /* bits that were read in at the start of the current group have all been */
+    /* shifted out and that all that is left is the 1 bit that was injected   */
+    /* into bit 16 at the start of the current group. When we reach the end   */
+    /* of a group, we have to load a new control word and inject a new 1 bit. */
+    if (control==1)
+      {
+       control=0x10000|*p_src++;
+       control|=(*p_src++)<<8;
+      }
+
+    /* If it is possible that we are within 16 groups from the end of the     */
+    /* input, execute the unrolled loop only once, else process a whole group */
+    /* of 16 items by looping 16 times.                                       */
+    unroll= p_src<=p_src_max16 ? 16 : 1;
+
+    /* This inner loop processes one phrase (item) per iteration. */
+    while (unroll--)
+      { /* Begin unrolled inner loop. */
+      
+       /* Process a literal or copy item depending on the next control bit. */
+       if (control&1)
+         {
+          /* Copy item. */
+          
+          register UBYTE *p;           /* Points to place from which to copy. */
+          register UWORD lenmt;        /* Length of copy item minus three.    */
+          register UBYTE **p_hte;      /* Pointer to current hash table entry.*/
+          register UBYTE *p_ziv=p_dst; /* Pointer to start of current Ziv.    */
+          
+          /* Read and dismantle the copy word. Work out from where to copy.   */
+          lenmt=*p_src++;
+          p_hte=&hash[((lenmt&0xF0)<<4)|*p_src++];
+          p=*p_hte;
+          lenmt&=0xF;
+          
+          /* Now perform the copy using a half unrolled loop. */
+          *p_dst++=*p++;
+          *p_dst++=*p++;
+          *p_dst++=*p++;
+          while (lenmt--)
+             *p_dst++=*p++;
+                 
+          /* Because we have just received 3 or more bytes in a copy item     */
+          /* (whose bytes we have just installed in the output), we are now   */
+          /* in a position to flush all the pending literal hashings that had */
+          /* been postponed for lack of bytes.                                */
+          if (literals>0)
+            {
+             register UBYTE *r=p_ziv-literals;
+             hash[HASH(r)]=r;
+             if (literals==2)
+                {r++; hash[HASH(r)]=r;}
+             literals=0;
+            }
+            
+          /* In any case, we can immediately update the hash table with the   */
+          /* current position. We don't need to do a HASH(...) to work out    */
+          /* where to put the pointer, as the compressor just told us!!!      */
+          *p_hte=p_ziv;
+          
+         }
+       else
+         {
+          /* Literal item. */
+          
+          /* Copy over the literal byte. */
+          *p_dst++=*p_src++;
+          
+          /* If we now have three literals waiting to be hashed into the hash */
+          /* table, we can do one of them now (because there are three).      */
+          if (++literals == 3)
+             {register UBYTE *p=p_dst-3; hash[HASH(p)]=p; literals=2;}
+         }
+          
+       /* Shift the control buffer so the next control bit is in bit 0. */
+       control>>=1;
+#if 1
+       if (p_dst > p_dst_post) 
+       {
+	       /* Shit: we tried to decompress corrupt data */
+	       *p_dst_len = 0;
+	       return;
+       }
+#endif
+      } /* End unrolled inner loop. */
+               
+   } /* End of outer loop */
+   
+ /* Write the length of the decompressed data before returning. */
+  *p_dst_len=p_dst-p_dst_first;
+}
+
+/******************************************************************************/
+/*                               End of LZRW3.C                               */
+/******************************************************************************/
diff --git a/drivers/char/ftape/compressor/lzrw3.h b/drivers/char/ftape/compressor/lzrw3.h
new file mode 100644
index 0000000..533feba
--- /dev/null
+++ b/drivers/char/ftape/compressor/lzrw3.h
@@ -0,0 +1,253 @@
+#ifndef _LZRW3_H
+#define _LZRW3_H
+/*
+ * $Source: /homes/cvs/ftape-stacked/ftape/compressor/lzrw3.h,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/10/05 19:12:30 $
+ *
+ *  include files for lzrw3. Only slighty modified from the original
+ *  version. Assembles the three include files compress.h, port.h and
+ *  fastcopy.h from the original lzrw3 package.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/string.h>
+
+/******************************************************************************/
+/*                                                                            */
+/*                                 COMPRESS.H                                 */
+/*                                                                            */
+/******************************************************************************/
+/*                                                                            */
+/* Author : Ross Williams.                                                    */
+/* Date   : December 1989.                                                    */
+/*                                                                            */
+/* This header file defines the interface to a set of functions called        */
+/* 'compress', each member of which implements a particular data compression  */
+/* algorithm.                                                                 */
+/*                                                                            */
+/* Normally in C programming, for each .H file, there is a corresponding .C   */
+/* file that implements the functions promised in the .H file.                */
+/* Here, there are many .C files corresponding to this header file.           */
+/* Each comforming implementation file contains a single function             */
+/* called 'compress' that implements a single data compression                */
+/* algorithm that conforms with the interface specified in this header file.  */
+/* Only one algorithm can be linked in at a time in this organization.        */
+/*                                                                            */
+/******************************************************************************/
+/*                                                                            */
+/*                    DEFINITION OF FUNCTION COMPRESS                         */
+/*                    ===============================                         */
+/*                                                                            */
+/* Summary of Function Compress                                               */
+/* ----------------------------                                               */
+/* The action that 'compress' takes depends on its first argument called      */
+/* 'action'.  The function provides three actions:                            */
+/*                                                                            */
+/*    - Return information about the algorithm.                               */
+/*    - Compress   a block of memory.                                         */
+/*    - Decompress a block of memory.                                         */
+/*                                                                            */
+/* Parameters                                                                 */
+/* ----------                                                                 */
+/* See the formal C definition later for a description of the parameters.     */
+/*                                                                            */
+/* Constants                                                                  */
+/* ---------                                                                  */
+/* COMPRESS_OVERRUN: The constant COMPRESS_OVERRUN defines by how many bytes  */
+/* an algorithm is allowed to expand a block during a compression operation.  */
+/*                                                                            */
+/* Although compression algorithms usually compress data, there will always   */
+/* be data that a given compressor will expand (this can be proven).          */
+/* Fortunately, the degree of expansion can be limited to a single bit, by    */
+/* copying over the input data if the data gets bigger during compression.    */
+/* To allow for this possibility, the first bit of a compressed               */
+/* representation can be used as a flag indicating whether the                */
+/* input data was copied over, or truly compressed. In practice, the first    */
+/* byte would be used to store this bit so as to maintain byte alignment.     */
+/*                                                                            */
+/* Unfortunately, in general, the only way to tell if an algorithm will       */
+/* expand a particular block of data is to run the algorithm on the data.     */
+/* If the algorithm does not continuously monitor how many output bytes it    */
+/* has written, it might write an output block far larger than the input      */
+/* block before realizing that it has done so.                                */
+/* On the other hand, continuous checks on output length are inefficient.     */
+/*                                                                            */
+/* To cater for all these problems, this interface definition:                */
+/* > Allows a compression algorithm to return an output block that is up to   */
+/*   COMPRESS_OVERRUN bytes longer than the input block.                      */
+/* > Allows a compression algorithm to write up to COMPRESS_OVERRUN bytes     */
+/*   more than the length of the input block to the memory of the output      */
+/*   block regardless of the length of the output block eventually returned.  */
+/*   This allows an algorithm to overrun the length of the input block in the */
+/*   output block by up to COMPRESS_OVERRUN bytes between expansion checks.   */
+/*                                                                            */
+/* The problem does not arise for decompression.                              */
+/*                                                                            */
+/* Identity Action                                                            */
+/* ---------------                                                            */
+/* > action must be COMPRESS_ACTION_IDENTITY.                                 */
+/* > p_dst_len must point to a longword to receive a longword address.        */
+/* > The value of the other parameters does not matter.                       */
+/* > After execution, the longword that p_dst_len points to will be a pointer */
+/*   to a structure of type compress_identity.                                */
+/*   Thus, for example, after the call, (*p_dst_len)->memory will return the  */
+/*   number of bytes of working memory that the algorithm requires to run.    */
+/* > The values of the identity structure returned are fixed constant         */
+/*   attributes of the algorithm and must not vary from call to call.         */
+/*                                                                            */
+/* Common Requirements for Compression and Decompression Actions              */
+/* -------------------------------------------------------------              */
+/* > wrk_mem must point to an unused block of memory of a length specified in */
+/*   the algorithm's identity block. The identity block can be obtained by    */
+/*   making a separate call to compress, specifying the identity action.      */
+/* > The INPUT BLOCK is defined to be Memory[src_addr,src_addr+src_len-1].    */
+/* > dst_len will be used to denote *p_dst_len.                               */
+/* > dst_len is not read by compress, only written.                           */
+/* > The value of dst_len is defined only upon termination.                   */
+/* > The OUTPUT BLOCK is defined to be Memory[dst_addr,dst_addr+dst_len-1].   */
+/*                                                                            */
+/* Compression Action                                                         */
+/* ------------------                                                         */
+/* > action must be COMPRESS_ACTION_COMPRESS.                                 */
+/* > src_len must be in the range [0,COMPRESS_MAX_ORG].                       */
+/* > The OUTPUT ZONE is defined to be                                         */
+/*      Memory[dst_addr,dst_addr+src_len-1+COMPRESS_OVERRUN].                 */
+/* > The function can modify any part of the output zone regardless of the    */
+/*   final length of the output block.                                        */
+/* > The input block and the output zone must not overlap.                    */
+/* > dst_len will be in the range [0,src_len+COMPRESS_OVERRUN].               */
+/* > dst_len will be in the range [0,COMPRESS_MAX_COM] (from prev fact).      */
+/* > The output block will consist of a representation of the input block.    */
+/*                                                                            */
+/* Decompression Action                                                       */
+/* --------------------                                                       */
+/* > action must be COMPRESS_ACTION_DECOMPRESS.                               */
+/* > The input block must be the result of an earlier compression operation.  */
+/* > If the previous fact is true, the following facts must also be true:     */
+/*   > src_len will be in the range [0,COMPRESS_MAX_COM].                     */
+/*   > dst_len will be in the range [0,COMPRESS_MAX_ORG].                     */
+/* > The input and output blocks must not overlap.                            */
+/* > Only the output block is modified.                                       */
+/* > Upon termination, the output block will consist of the bytes contained   */
+/*   in the input block passed to the earlier compression operation.          */
+/*                                                                            */
+/******************************************************************************/
+
+/******************************************************************************/
+/*                                                                            */
+/*                                    PORT.H                                  */
+/*                                                                            */
+/******************************************************************************/
+/*                                                                            */
+/* This module contains macro definitions and types that are likely to        */
+/* change between computers.                                                  */
+/*                                                                            */
+/******************************************************************************/
+
+#ifndef DONE_PORT       /* Only do this if not previously done.               */
+
+   #ifdef THINK_C
+      #define UBYTE unsigned char      /* Unsigned byte                       */
+      #define UWORD unsigned int       /* Unsigned word (2 bytes)             */
+      #define ULONG unsigned long      /* Unsigned word (4 bytes)             */
+      #define BOOL  unsigned char      /* Boolean                             */
+      #define FOPEN_BINARY_READ  "rb"  /* Mode string for binary reading.     */
+      #define FOPEN_BINARY_WRITE "wb"  /* Mode string for binary writing.     */
+      #define FOPEN_TEXT_APPEND  "a"   /* Mode string for text appending.     */
+      #define REAL double              /* USed for floating point stuff.      */
+   #endif
+   #if defined(LINUX) || defined(linux)
+      #define UBYTE __u8               /* Unsigned byte                       */
+      #define UWORD __u16              /* Unsigned word (2 bytes)             */
+      #define ULONG __u32              /* Unsigned word (4 bytes)             */
+      #define LONG  __s32              /* Signed   word (4 bytes)             */
+      #define BOOL  is not used here   /* Boolean                             */
+      #define FOPEN_BINARY_READ  not used  /* Mode string for binary reading. */
+      #define FOPEN_BINARY_WRITE not used  /* Mode string for binary writing. */
+      #define FOPEN_TEXT_APPEND  not used  /* Mode string for text appending. */
+      #define REAL not used                /* USed for floating point stuff.  */
+      #ifndef TRUE
+      #define TRUE 1
+      #endif
+   #endif
+
+   #define DONE_PORT                   /* Don't do all this again.            */
+   #define MALLOC_FAIL NULL            /* Failure status from malloc()        */
+   #define LOCAL static                /* For non-exported routines.          */
+   #define EXPORT                      /* Signals exported function.          */
+   #define then                        /* Useful for aligning ifs.            */
+
+#endif
+
+/******************************************************************************/
+/*                              End of PORT.H                                 */
+/******************************************************************************/
+
+#define COMPRESS_ACTION_IDENTITY   0
+#define COMPRESS_ACTION_COMPRESS   1
+#define COMPRESS_ACTION_DECOMPRESS 2
+
+#define COMPRESS_OVERRUN 1024
+#define COMPRESS_MAX_COM 0x70000000
+#define COMPRESS_MAX_ORG (COMPRESS_MAX_COM-COMPRESS_OVERRUN)
+
+#define COMPRESS_MAX_STRLEN 255
+
+/* The following structure provides information about the algorithm.         */
+/* > The top bit of id must be zero. The remaining bits must be chosen by    */
+/*   the author of the algorithm by tossing a coin 31 times.                 */
+/* > The amount of memory requested by the algorithm is specified in bytes   */
+/*   and must be in the range [0,0x70000000].                                */
+/* > All strings s must be such that strlen(s)<=COMPRESS_MAX_STRLEN.         */
+struct compress_identity
+  {
+   ULONG id;           /* Identifying number of algorithm.            */
+   ULONG memory;       /* Number of bytes of working memory required. */
+
+   char  *name;        /* Name of algorithm.                          */
+   char  *version;     /* Version number.                             */
+   char  *date;        /* Date of release of this version.            */
+   char  *copyright;   /* Copyright message.                          */
+
+   char  *author;      /* Author of algorithm.                        */
+   char  *affiliation; /* Affiliation of author.                      */
+   char  *vendor;      /* Where the algorithm can be obtained.        */
+  };
+
+void  lzrw3_compress(        /* Single function interface to compression algorithm. */
+UWORD     action,      /* Action to be performed.                             */
+UBYTE   *wrk_mem,      /* Working memory temporarily given to routine to use. */
+UBYTE   *src_adr,      /* Address of input  data.                             */
+LONG     src_len,      /* Length  of input  data.                             */
+UBYTE   *dst_adr,      /* Address of output data.                             */
+void  *p_dst_len       /* Pointer to a longword where routine will write:     */
+                       /*    If action=..IDENTITY   => Adr of id structure.   */
+                       /*    If action=..COMPRESS   => Length of output data. */
+                       /*    If action=..DECOMPRESS => Length of output data. */
+);
+
+/******************************************************************************/
+/*                             End of COMPRESS.H                              */
+/******************************************************************************/
+
+
+/******************************************************************************/
+/*                                  fast_copy.h                               */
+/******************************************************************************/
+
+/* This function copies a block of memory very quickly.                       */
+/* The exact speed depends on the relative alignment of the blocks of memory. */
+/* PRE  : 0<=src_len<=(2^32)-1 .                                              */
+/* PRE  : Source and destination blocks must not overlap.                     */
+/* POST : MEM[dst_adr,dst_adr+src_len-1]=MEM[src_adr,src_adr+src_len-1].      */
+/* POST : MEM[dst_adr,dst_adr+src_len-1] is the only memory changed.          */
+
+#define fast_copy(src,dst,len) memcpy(dst,src,len)
+
+/******************************************************************************/
+/*                               End of fast_copy.h                           */
+/******************************************************************************/
+
+#endif
diff --git a/drivers/char/ftape/compressor/zftape-compress.c b/drivers/char/ftape/compressor/zftape-compress.c
new file mode 100644
index 0000000..220a227
--- /dev/null
+++ b/drivers/char/ftape/compressor/zftape-compress.c
@@ -0,0 +1,1203 @@
+/*
+ *      Copyright (C) 1994-1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+ 
+ *
+ *     This file implements a "generic" interface between the *
+ *     zftape-driver and a compression-algorithm. The *
+ *     compression-algorithm currently used is a LZ77. I use the *
+ *     implementation lzrw3 by Ross N. Williams (Renaissance *
+ *     Software). The compression program itself is in the file
+ *     lzrw3.c * and lzrw3.h.  To adopt another compression algorithm
+ *     the functions * zft_compress() and zft_uncompress() must be
+ *     changed * appropriately. See below.
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+
+#include <linux/zftape.h>
+
+#include <asm/uaccess.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../compressor/zftape-compress.h"
+#include "../zftape/zftape-vtbl.h"
+#include "../compressor/lzrw3.h"
+
+/*
+ *   global variables
+ */
+
+/* I handle the allocation of this buffer as a special case, because
+ * it's size varies depending on the tape length inserted.
+ */
+
+/* local variables 
+ */
+static void *zftc_wrk_mem = NULL;
+static __u8 *zftc_buf     = NULL;
+static void *zftc_scratch_buf  = NULL;
+
+/* compression statistics 
+ */
+static unsigned int zftc_wr_uncompressed = 0;
+static unsigned int zftc_wr_compressed   = 0;
+static unsigned int zftc_rd_uncompressed = 0;
+static unsigned int zftc_rd_compressed   = 0;
+
+/* forward */
+static int  zftc_write(int *write_cnt,
+		       __u8 *dst_buf, const int seg_sz,
+		       const __u8 __user *src_buf, const int req_len,
+		       const zft_position *pos, const zft_volinfo *volume);
+static int  zftc_read(int *read_cnt,
+		      __u8  __user *dst_buf, const int to_do,
+		      const __u8 *src_buf, const int seg_sz,
+		      const zft_position *pos, const zft_volinfo *volume);
+static int  zftc_seek(unsigned int new_block_pos, 
+		      zft_position *pos, const zft_volinfo *volume,
+		      __u8 *buffer);
+static void zftc_lock   (void);
+static void zftc_reset  (void);
+static void zftc_cleanup(void);
+static void zftc_stats      (void);
+
+/* compressed segment. This conforms to QIC-80-MC, Revision K.
+ * 
+ * Rev. K applies to tapes with `fixed length format' which is
+ * indicated by format code 2,3 and 5. See below for format code 4 and 6
+ *
+ * 2 bytes: offset of compression segment structure
+ *          29k > offset >= 29k-18: data from previous segment ens in this
+ *                                  segment and no compressed block starts
+ *                                  in this segment
+ *                     offset == 0: data from previous segment occupies entire
+ *                                  segment and continues in next segment
+ * n bytes: remainder from previous segment
+ * 
+ * Rev. K:  
+ * 4 bytes: 4 bytes: files set byte offset
+ * Post Rev. K and QIC-3020/3020:
+ * 8 bytes: 8 bytes: files set byte offset
+ * 2 bytes: byte count N (amount of data following)
+ *          bit 15 is set if data is compressed, bit 15 is not
+ *          set if data is uncompressed
+ * N bytes: data (as much as specified in the byte count)
+ * 2 bytes: byte count N_1 of next cluster
+ * N_1 bytes: data of next cluset
+ * 2 bytes: byte count N_2 of next cluster
+ * N_2 bytes: ...  
+ *
+ * Note that the `N' byte count accounts only for the bytes that in the
+ * current segment if the cluster spans to the next segment.
+ */
+
+typedef struct
+{
+	int cmpr_pos;             /* actual position in compression buffer */
+	int cmpr_sz;              /* what is left in the compression buffer
+				   * when copying the compressed data to the
+				   * deblock buffer
+				   */
+	unsigned int first_block; /* location of header information in
+				   * this segment
+				   */
+	unsigned int count;       /* amount of data of current block
+				   * contained in current segment 
+				   */
+	unsigned int offset;      /* offset in current segment */
+	unsigned int spans:1;     /* might continue in next segment */
+	unsigned int uncmpr;      /* 0x8000 if this block contains
+				   * uncompressed data 
+				   */
+	__s64 foffs;              /* file set byte offset, same as in 
+				   * compression map segment
+				   */
+} cmpr_info;
+
+static cmpr_info cseg; /* static data. Must be kept uptodate and shared by 
+			* read, write and seek functions
+			*/
+
+#define DUMP_CMPR_INFO(level, msg, info)				\
+	TRACE(level, msg "\n"						\
+	      KERN_INFO "cmpr_pos   : %d\n"				\
+	      KERN_INFO "cmpr_sz    : %d\n"				\
+	      KERN_INFO "first_block: %d\n"				\
+	      KERN_INFO "count      : %d\n"				\
+	      KERN_INFO "offset     : %d\n"				\
+	      KERN_INFO "spans      : %d\n"				\
+	      KERN_INFO "uncmpr     : 0x%04x\n"				\
+	      KERN_INFO "foffs      : " LL_X,				\
+	      (info)->cmpr_pos, (info)->cmpr_sz, (info)->first_block,	\
+	      (info)->count, (info)->offset, (info)->spans == 1,	\
+	      (info)->uncmpr, LL((info)->foffs))
+
+/*   dispatch compression segment info, return error code
+ *  
+ *   afterwards, cseg->offset points to start of data of the NEXT
+ *   compressed block, and cseg->count contains the amount of data
+ *   left in the actual compressed block. cseg->spans is set to 1 if
+ *   the block is continued in the following segment. Otherwise it is
+ *   set to 0. 
+ */
+static int get_cseg (cmpr_info *cinfo, const __u8 *buff, 
+		     const unsigned int seg_sz,
+		     const zft_volinfo *volume)
+{
+	TRACE_FUN(ft_t_flow);
+
+ 	cinfo->first_block = GET2(buff, 0);
+	if (cinfo->first_block == 0) { /* data spans to next segment */
+		cinfo->count  = seg_sz - sizeof(__u16);
+		cinfo->offset = seg_sz;
+		cinfo->spans = 1;
+	} else { /* cluster definetely ends in this segment */
+		if (cinfo->first_block > seg_sz) {
+			/* data corrupted */
+			TRACE_ABORT(-EIO, ft_t_err, "corrupted data:\n"
+				    KERN_INFO "segment size: %d\n"
+				    KERN_INFO "first block : %d",
+				    seg_sz, cinfo->first_block);
+		}
+	        cinfo->count  = cinfo->first_block - sizeof(__u16);
+		cinfo->offset = cinfo->first_block;
+		cinfo->spans = 0;
+	}
+	/* now get the offset the first block should have in the
+	 * uncompressed data stream.
+	 *
+	 * For this magic `18' refer to CRF-3 standard or QIC-80MC,
+	 * Rev. K.  
+	 */
+	if ((seg_sz - cinfo->offset) > 18) {
+		if (volume->qic113) { /* > revision K */
+			TRACE(ft_t_data_flow, "New QIC-113 compliance");
+			cinfo->foffs = GET8(buff, cinfo->offset);
+			cinfo->offset += sizeof(__s64); 
+		} else {
+			TRACE(/* ft_t_data_flow */ ft_t_noise, "pre QIC-113 version");
+			cinfo->foffs   = (__s64)GET4(buff, cinfo->offset);
+			cinfo->offset += sizeof(__u32); 
+		}
+	}
+	if (cinfo->foffs > volume->size) {
+		TRACE_ABORT(-EIO, ft_t_err, "Inconsistency:\n"
+			    KERN_INFO "offset in current volume: %d\n"
+			    KERN_INFO "size of current volume  : %d",
+			    (int)(cinfo->foffs>>10), (int)(volume->size>>10));
+	}
+	if (cinfo->cmpr_pos + cinfo->count > volume->blk_sz) {
+		TRACE_ABORT(-EIO, ft_t_err, "Inconsistency:\n"
+			    KERN_INFO "block size : %d\n"
+			    KERN_INFO "data record: %d",
+			    volume->blk_sz, cinfo->cmpr_pos + cinfo->count);
+	}
+	DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */, "", cinfo);
+	TRACE_EXIT 0;
+}
+
+/*  This one is called, when a new cluster starts in same segment.
+ *  
+ *  Note: if this is the first cluster in the current segment, we must
+ *  not check whether there are more than 18 bytes available because
+ *  this have already been done in get_cseg() and there may be less
+ *  than 18 bytes available due to header information.
+ * 
+ */
+static void get_next_cluster(cmpr_info *cluster, const __u8 *buff, 
+			     const int seg_sz, const int finish)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (seg_sz - cluster->offset > 18 || cluster->foffs != 0) {
+		cluster->count   = GET2(buff, cluster->offset);
+		cluster->uncmpr  = cluster->count & 0x8000;
+		cluster->count  -= cluster->uncmpr;
+		cluster->offset += sizeof(__u16);
+		cluster->foffs   = 0;
+		if ((cluster->offset + cluster->count) < seg_sz) {
+			cluster->spans = 0;
+		} else if (cluster->offset + cluster->count == seg_sz) {
+			cluster->spans = !finish;
+		} else {
+			/* either an error or a volume written by an 
+			 * old version. If this is a data error, then we'll
+			 * catch it later.
+			 */
+			TRACE(ft_t_data_flow, "Either error or old volume");
+			cluster->spans = 1;
+			cluster->count = seg_sz - cluster->offset;
+		}
+	} else {
+		cluster->count = 0;
+		cluster->spans = 0;
+		cluster->foffs = 0;
+	}
+	DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */ , "", cluster);
+	TRACE_EXIT;
+}
+
+static void zftc_lock(void)
+{
+}
+
+/*  this function is needed for zftape_reset_position in zftape-io.c 
+ */
+static void zftc_reset(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	memset((void *)&cseg, '\0', sizeof(cseg));
+	zftc_stats();
+	TRACE_EXIT;
+}
+
+static int cmpr_mem_initialized = 0;
+static unsigned int alloc_blksz = 0;
+
+static int zft_allocate_cmpr_mem(unsigned int blksz)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (cmpr_mem_initialized && blksz == alloc_blksz) {
+		TRACE_EXIT 0;
+	}
+	TRACE_CATCH(zft_vmalloc_once(&zftc_wrk_mem, CMPR_WRK_MEM_SIZE),
+		    zftc_cleanup());
+	TRACE_CATCH(zft_vmalloc_always(&zftc_buf, blksz + CMPR_OVERRUN),
+		    zftc_cleanup());
+	alloc_blksz = blksz;
+	TRACE_CATCH(zft_vmalloc_always(&zftc_scratch_buf, blksz+CMPR_OVERRUN),
+		    zftc_cleanup());
+	cmpr_mem_initialized = 1;
+	TRACE_EXIT 0;
+}
+
+static void zftc_cleanup(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	zft_vfree(&zftc_wrk_mem, CMPR_WRK_MEM_SIZE);
+	zft_vfree(&zftc_buf, alloc_blksz + CMPR_OVERRUN);
+	zft_vfree(&zftc_scratch_buf, alloc_blksz + CMPR_OVERRUN);
+	cmpr_mem_initialized = alloc_blksz = 0;
+	TRACE_EXIT;
+}
+
+/*****************************************************************************
+ *                                                                           *
+ *  The following two functions "ftape_compress()" and                       *
+ *  "ftape_uncompress()" are the interface to the actual compression         *
+ *  algorithm (i.e. they are calling the "compress()" function from          *
+ *  the lzrw3 package for now). These routines could quite easily be         *
+ *  changed to adopt another compression algorithm instead of lzrw3,         *
+ *  which currently is used.                                                 *
+ *                                                                           *
+ *****************************************************************************/
+
+/* called by zft_compress_write() to perform the compression. Must
+ * return the size of the compressed data.
+ *
+ * NOTE: The size of the compressed data should not exceed the size of
+ *       the uncompressed data. Most compression algorithms have means
+ *       to store data unchanged if the "compressed" data amount would
+ *       exceed the original one. Mostly this is done by storing some
+ *       flag-bytes in front of the compressed data to indicate if it
+ *       is compressed or not. Thus the worst compression result
+ *       length is the original length plus those flag-bytes.
+ *
+ *       We don't want that, as the QIC-80 standard provides a means
+ *       of marking uncompressed blocks by simply setting bit 15 of
+ *       the compressed block's length. Thus a compessed block can
+ *       have at most a length of 2^15-1 bytes. The QIC-80 standard
+ *       restricts the block-length even further, allowing only 29k -
+ *       6 bytes.
+ *
+ *       Currently, the maximum blocksize used by zftape is 28k.
+ *
+ *       In short: don't exceed the length of the input-package, set
+ *       bit 15 of the compressed size to 1 if you have copied data
+ *       instead of compressing it.
+ */
+static int zft_compress(__u8 *in_buffer, unsigned int in_sz, __u8 *out_buffer)
+{ 
+	__s32 compressed_sz;
+	TRACE_FUN(ft_t_flow);
+	
+
+	lzrw3_compress(COMPRESS_ACTION_COMPRESS, zftc_wrk_mem,
+		       in_buffer, in_sz, out_buffer, &compressed_sz);
+	if (TRACE_LEVEL >= ft_t_info) {
+		/*  the compiler will optimize this away when
+		 *  compiled with NO_TRACE_AT_ALL option
+		 */
+		TRACE(ft_t_data_flow, "\n"
+		      KERN_INFO "before compression: %d bytes\n"
+		      KERN_INFO "after compresison : %d bytes", 
+		      in_sz, 
+		      (int)(compressed_sz < 0 
+		      ? -compressed_sz : compressed_sz));
+		/*  for statistical purposes
+		 */
+		zftc_wr_compressed   += (compressed_sz < 0 
+					   ? -compressed_sz : compressed_sz);
+		zftc_wr_uncompressed += in_sz;
+	}
+	TRACE_EXIT (int)compressed_sz;
+}
+
+/* called by zft_compress_read() to decompress the data. Must
+ * return the size of the decompressed data for sanity checks
+ * (compared with zft_blk_sz)
+ *
+ * NOTE: Read the note for zft_compress() above!  If bit 15 of the
+ *       parameter in_sz is set, then the data in in_buffer isn't
+ *       compressed, which must be handled by the un-compression
+ *       algorithm. (I changed lzrw3 to handle this.)
+ *
+ *  The parameter max_out_sz is needed to prevent buffer overruns when 
+ *  uncompressing corrupt data.
+ */
+static unsigned int zft_uncompress(__u8 *in_buffer, 
+				   int in_sz, 
+				   __u8 *out_buffer,
+				   unsigned int max_out_sz)
+{ 
+	TRACE_FUN(ft_t_flow);
+	
+	lzrw3_compress(COMPRESS_ACTION_DECOMPRESS, zftc_wrk_mem,
+		       in_buffer, (__s32)in_sz,
+		       out_buffer, (__u32 *)&max_out_sz);
+	
+	if (TRACE_LEVEL >= ft_t_info) {
+		TRACE(ft_t_data_flow, "\n"
+		      KERN_INFO "before decompression: %d bytes\n"
+		      KERN_INFO "after decompression : %d bytes", 
+		      in_sz < 0 ? -in_sz : in_sz,(int)max_out_sz);
+		/*  for statistical purposes
+		 */
+		zftc_rd_compressed   += in_sz < 0 ? -in_sz : in_sz;
+		zftc_rd_uncompressed += max_out_sz;
+	}
+	TRACE_EXIT (unsigned int)max_out_sz;
+}
+
+/* print some statistics about the efficiency of the compression to
+ * the kernel log 
+ */
+static void zftc_stats(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (TRACE_LEVEL < ft_t_info) {
+		TRACE_EXIT;
+	}
+	if (zftc_wr_uncompressed != 0) {
+		if (zftc_wr_compressed > (1<<14)) {
+			TRACE(ft_t_info, "compression statistics (writing):\n"
+			      KERN_INFO " compr./uncmpr.   : %3d %%",
+			      (((zftc_wr_compressed>>10) * 100)
+			       / (zftc_wr_uncompressed>>10)));
+		} else {
+			TRACE(ft_t_info, "compression statistics (writing):\n"
+			      KERN_INFO " compr./uncmpr.   : %3d %%",
+			      ((zftc_wr_compressed * 100)
+			       / zftc_wr_uncompressed));
+		}
+	}
+	if (zftc_rd_uncompressed != 0) {
+		if (zftc_rd_compressed > (1<<14)) {
+			TRACE(ft_t_info, "compression statistics (reading):\n"
+			      KERN_INFO " compr./uncmpr.   : %3d %%",
+			      (((zftc_rd_compressed>>10) * 100)
+			       / (zftc_rd_uncompressed>>10)));
+		} else {
+			TRACE(ft_t_info, "compression statistics (reading):\n"
+			      KERN_INFO " compr./uncmpr.   : %3d %%",
+			      ((zftc_rd_compressed * 100)
+			       / zftc_rd_uncompressed));
+		}
+	}
+	/* only print it once: */
+	zftc_wr_uncompressed = 
+		zftc_wr_compressed  =
+		zftc_rd_uncompressed =
+		zftc_rd_compressed   = 0;
+	TRACE_EXIT;
+}
+
+/* start new compressed block 
+ */
+static int start_new_cseg(cmpr_info *cluster, 
+			  char *dst_buf, 
+			  const zft_position *pos,
+			  const unsigned int blk_sz,
+			  const char *src_buf,
+			  const int this_segs_sz,
+			  const int qic113)
+{
+	int size_left;
+	int cp_cnt;
+	int buf_pos;
+	TRACE_FUN(ft_t_flow);
+
+	size_left = this_segs_sz - sizeof(__u16) - cluster->cmpr_sz;
+	TRACE(ft_t_data_flow,"\n" 
+	      KERN_INFO "segment size   : %d\n"
+	      KERN_INFO "compressed_sz: %d\n"
+	      KERN_INFO "size_left      : %d",
+	      this_segs_sz, cluster->cmpr_sz, size_left);
+	if (size_left > 18) { /* start a new cluseter */
+		cp_cnt = cluster->cmpr_sz;
+		cluster->cmpr_sz = 0;
+		buf_pos = cp_cnt + sizeof(__u16);
+		PUT2(dst_buf, 0, buf_pos);
+
+		if (qic113) {
+			__s64 foffs = pos->volume_pos;
+			if (cp_cnt) foffs += (__s64)blk_sz;
+
+			TRACE(ft_t_data_flow, "new style QIC-113 header");
+			PUT8(dst_buf, buf_pos, foffs);
+			buf_pos += sizeof(__s64);
+		} else {
+			__u32 foffs = (__u32)pos->volume_pos;
+			if (cp_cnt) foffs += (__u32)blk_sz;
+			
+			TRACE(ft_t_data_flow, "old style QIC-80MC header");
+			PUT4(dst_buf, buf_pos, foffs);
+			buf_pos += sizeof(__u32);
+		}
+	} else if (size_left >= 0) {
+		cp_cnt = cluster->cmpr_sz;
+		cluster->cmpr_sz = 0;
+		buf_pos = cp_cnt + sizeof(__u16);
+		PUT2(dst_buf, 0, buf_pos);  
+		/* zero unused part of segment. */
+		memset(dst_buf + buf_pos, '\0', size_left);
+		buf_pos = this_segs_sz;
+	} else { /* need entire segment and more space */
+		PUT2(dst_buf, 0, 0); 
+		cp_cnt = this_segs_sz - sizeof(__u16);
+		cluster->cmpr_sz  -= cp_cnt;
+		buf_pos = this_segs_sz;
+	}
+	memcpy(dst_buf + sizeof(__u16), src_buf + cluster->cmpr_pos, cp_cnt);
+	cluster->cmpr_pos += cp_cnt;
+	TRACE_EXIT buf_pos;
+}
+
+/* return-value: the number of bytes removed from the user-buffer
+ *               `src_buf' or error code
+ *
+ *  int *write_cnt           : how much actually has been moved to the
+ *                             dst_buf. Need not be initialized when
+ *                             function returns with an error code
+ *                             (negativ return value) 
+ *  __u8 *dst_buf            : kernel space buffer where the has to be
+ *                             copied to. The contents of this buffers
+ *                             goes to a specific segment.
+ *  const int seg_sz         : the size of the segment dst_buf will be
+ *                             copied to.
+ *  const zft_position *pos  : struct containing the coordinates in
+ *                             the current volume (byte position,
+ *                             segment id of current segment etc)
+ *  const zft_volinfo *volume: information about the current volume,
+ *                             size etc.
+ *  const __u8 *src_buf      : user space buffer that contains the
+ *                             data the user wants to be written to
+ *                             tape.
+ *  const int req_len        : the amount of data the user wants to be
+ *                             written to tape.
+ */
+static int zftc_write(int *write_cnt,
+		      __u8 *dst_buf, const int seg_sz,
+		      const __u8 __user *src_buf, const int req_len,
+		      const zft_position *pos, const zft_volinfo *volume)
+{
+	int req_len_left = req_len;
+	int result;
+	int len_left;
+	int buf_pos_write = pos->seg_byte_pos;
+	TRACE_FUN(ft_t_flow);
+	
+	/* Note: we do not unlock the module because
+	 * there are some values cached in that `cseg' variable.  We
+	 * don't don't want to use this information when being
+	 * unloaded by kerneld even when the tape is full or when we
+	 * cannot allocate enough memory.
+	 */
+	if (pos->tape_pos > (volume->size-volume->blk_sz-ZFT_CMPR_OVERHEAD)) {
+		TRACE_EXIT -ENOSPC;
+	}    
+	if (zft_allocate_cmpr_mem(volume->blk_sz) < 0) {
+		/* should we unlock the module? But it shouldn't 
+		 * be locked anyway ...
+		 */
+		TRACE_EXIT -ENOMEM;
+	}
+	if (buf_pos_write == 0) { /* fill a new segment */
+		*write_cnt = buf_pos_write = start_new_cseg(&cseg,
+							    dst_buf,
+							    pos,
+							    volume->blk_sz,
+							    zftc_buf, 
+							    seg_sz,
+							    volume->qic113);
+		if (cseg.cmpr_sz == 0 && cseg.cmpr_pos != 0) {
+			req_len_left -= result = volume->blk_sz;
+			cseg.cmpr_pos  = 0;
+		} else {
+			result = 0;
+		}
+	} else {
+		*write_cnt = result = 0;
+	}
+	
+	len_left = seg_sz - buf_pos_write;
+	while ((req_len_left > 0) && (len_left > 18)) {
+		/* now we have some size left for a new compressed
+		 * block.  We know, that the compression buffer is
+		 * empty (else there wouldn't be any space left).  
+		 */
+		if (copy_from_user(zftc_scratch_buf, src_buf + result, 
+				   volume->blk_sz) != 0) {
+			TRACE_EXIT -EFAULT;
+		}
+		req_len_left -= volume->blk_sz;
+		cseg.cmpr_sz = zft_compress(zftc_scratch_buf, volume->blk_sz, 
+					    zftc_buf);
+		if (cseg.cmpr_sz < 0) {
+			cseg.uncmpr = 0x8000;
+			cseg.cmpr_sz = -cseg.cmpr_sz;
+		} else {
+			cseg.uncmpr = 0;
+		}
+		/* increment "result" iff we copied the entire
+		 * compressed block to the zft_deblock_buf 
+		 */
+		len_left -= sizeof(__u16);
+		if (len_left >= cseg.cmpr_sz) {
+			len_left -= cseg.count = cseg.cmpr_sz;
+			cseg.cmpr_pos = cseg.cmpr_sz = 0;
+			result += volume->blk_sz;
+		} else {
+			cseg.cmpr_sz       -= 
+				cseg.cmpr_pos =
+				cseg.count    = len_left;
+			len_left = 0;
+		}
+		PUT2(dst_buf, buf_pos_write, cseg.uncmpr | cseg.count);
+		buf_pos_write += sizeof(__u16);
+		memcpy(dst_buf + buf_pos_write, zftc_buf, cseg.count);
+		buf_pos_write += cseg.count;
+		*write_cnt    += cseg.count + sizeof(__u16);
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+	}
+	/* erase the remainder of the segment if less than 18 bytes
+	 * left (18 bytes is due to the QIC-80 standard) 
+	 */
+	if (len_left <= 18) {
+		memset(dst_buf + buf_pos_write, '\0', len_left);
+		(*write_cnt) += len_left;
+	}
+	TRACE(ft_t_data_flow, "returning %d", result);
+	TRACE_EXIT result;
+}   
+
+/* out:
+ *
+ * int *read_cnt: the number of bytes we removed from the zft_deblock_buf
+ *                (result)
+ * int *to_do   : the remaining size of the read-request.
+ *
+ * in:
+ *
+ * char *buff          : buff is the address of the upper part of the user
+ *                       buffer, that hasn't been filled with data yet.
+
+ * int buf_pos_read    : copy of from _ftape_read()
+ * int buf_len_read    : copy of buf_len_rd from _ftape_read()
+ * char *zft_deblock_buf: zft_deblock_buf
+ * unsigned short blk_sz: the block size valid for this volume, may differ
+ *                            from zft_blk_sz.
+ * int finish: if != 0 means that this is the last segment belonging
+ *  to this volume
+ * returns the amount of data actually copied to the user-buffer
+ *
+ * to_do MUST NOT SHRINK except to indicate an EOF. In this case *to_do has to
+ * be set to 0 
+ */
+static int zftc_read (int *read_cnt, 
+		      __u8  __user *dst_buf, const int to_do, 
+		      const __u8 *src_buf, const int seg_sz, 
+		      const zft_position *pos, const zft_volinfo *volume)
+{          
+	int uncompressed_sz;         
+	int result = 0;
+	int remaining = to_do;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE_CATCH(zft_allocate_cmpr_mem(volume->blk_sz),);
+	if (pos->seg_byte_pos == 0) {
+		/* new segment just read
+		 */
+		TRACE_CATCH(get_cseg(&cseg, src_buf, seg_sz, volume),
+			    *read_cnt = 0);
+		memcpy(zftc_buf + cseg.cmpr_pos, src_buf + sizeof(__u16), 
+		       cseg.count);
+		cseg.cmpr_pos += cseg.count;
+		*read_cnt      = cseg.offset;
+		DUMP_CMPR_INFO(ft_t_noise /* ft_t_any */, "", &cseg);
+	} else {
+		*read_cnt = 0;
+	}
+	/* loop and uncompress until user buffer full or
+	 * deblock-buffer empty 
+	 */
+	TRACE(ft_t_data_flow, "compressed_sz: %d, compos : %d, *read_cnt: %d",
+	      cseg.cmpr_sz, cseg.cmpr_pos, *read_cnt);
+	while ((cseg.spans == 0) && (remaining > 0)) {
+		if (cseg.cmpr_pos  != 0) { /* cmpr buf is not empty */
+			uncompressed_sz = 
+				zft_uncompress(zftc_buf,
+					       cseg.uncmpr == 0x8000 ?
+					       -cseg.cmpr_pos : cseg.cmpr_pos,
+					       zftc_scratch_buf,
+					       volume->blk_sz);
+			if (uncompressed_sz != volume->blk_sz) {
+				*read_cnt = 0;
+				TRACE_ABORT(-EIO, ft_t_warn,
+				      "Uncompressed blk (%d) != blk size (%d)",
+				      uncompressed_sz, volume->blk_sz);
+			}       
+			if (copy_to_user(dst_buf + result, 
+					 zftc_scratch_buf, 
+					 uncompressed_sz) != 0 ) {
+				TRACE_EXIT -EFAULT;
+			}
+			remaining      -= uncompressed_sz;
+			result     += uncompressed_sz;
+			cseg.cmpr_pos  = 0;
+		}                                              
+		if (remaining > 0) {
+			get_next_cluster(&cseg, src_buf, seg_sz, 
+					 volume->end_seg == pos->seg_pos);
+			if (cseg.count != 0) {
+				memcpy(zftc_buf, src_buf + cseg.offset,
+				       cseg.count);
+				cseg.cmpr_pos = cseg.count;
+				cseg.offset  += cseg.count;
+				*read_cnt += cseg.count + sizeof(__u16);
+			} else {
+				remaining = 0;
+			}
+		}
+		TRACE(ft_t_data_flow, "\n" 
+		      KERN_INFO "compressed_sz: %d\n"
+		      KERN_INFO "compos       : %d\n"
+		      KERN_INFO "*read_cnt    : %d",
+		      cseg.cmpr_sz, cseg.cmpr_pos, *read_cnt);
+	}
+	if (seg_sz - cseg.offset <= 18) {
+		*read_cnt += seg_sz - cseg.offset;
+		TRACE(ft_t_data_flow, "expanding read cnt to: %d", *read_cnt);
+	}
+	TRACE(ft_t_data_flow, "\n"
+	      KERN_INFO "segment size   : %d\n"
+	      KERN_INFO "read count     : %d\n"
+	      KERN_INFO "buf_pos_read   : %d\n"
+	      KERN_INFO "remaining      : %d",
+		seg_sz, *read_cnt, pos->seg_byte_pos, 
+		seg_sz - *read_cnt - pos->seg_byte_pos);
+	TRACE(ft_t_data_flow, "returning: %d", result);
+	TRACE_EXIT result;
+}                
+
+/* seeks to the new data-position. Reads sometimes a segment.
+ *  
+ * start_seg and end_seg give the boundaries of the current volume
+ * blk_sz is the blk_sz of the current volume as stored in the
+ * volume label
+ *
+ * We don't allow blocksizes less than 1024 bytes, therefore we don't need
+ * a 64 bit argument for new_block_pos.
+ */
+
+static int seek_in_segment(const unsigned int to_do, cmpr_info  *c_info,
+			   const char *src_buf, const int seg_sz, 
+			   const int seg_pos, const zft_volinfo *volume);
+static int slow_seek_forward_until_error(const unsigned int distance,
+					 cmpr_info *c_info, zft_position *pos, 
+					 const zft_volinfo *volume, __u8 *buf);
+static int search_valid_segment(unsigned int segment,
+				const unsigned int end_seg,
+				const unsigned int max_foffs,
+				zft_position *pos, cmpr_info *c_info,
+				const zft_volinfo *volume, __u8 *buf);
+static int slow_seek_forward(unsigned int dest, cmpr_info *c_info,
+			     zft_position *pos, const zft_volinfo *volume,
+			     __u8 *buf);
+static int compute_seg_pos(unsigned int dest, zft_position *pos,
+			   const zft_volinfo *volume);
+
+#define ZFT_SLOW_SEEK_THRESHOLD  10 /* segments */
+#define ZFT_FAST_SEEK_MAX_TRIALS 10 /* times */
+#define ZFT_FAST_SEEK_BACKUP     10 /* segments */
+
+static int zftc_seek(unsigned int new_block_pos,
+		     zft_position *pos, const zft_volinfo *volume, __u8 *buf)
+{
+	unsigned int dest;
+	int limit;
+	int distance;
+	int result = 0;
+	int seg_dist;
+	int new_seg;
+	int old_seg = 0;
+	int fast_seek_trials = 0;
+	TRACE_FUN(ft_t_flow);
+
+	if (new_block_pos == 0) {
+		pos->seg_pos      = volume->start_seg;
+		pos->seg_byte_pos = 0;
+		pos->volume_pos   = 0;
+		zftc_reset();
+		TRACE_EXIT 0;
+	}
+	dest = new_block_pos * (volume->blk_sz >> 10);
+	distance = dest - (pos->volume_pos >> 10);
+	while (distance != 0) {
+		seg_dist = compute_seg_pos(dest, pos, volume);
+		TRACE(ft_t_noise, "\n"
+		      KERN_INFO "seg_dist: %d\n"
+		      KERN_INFO "distance: %d\n"
+		      KERN_INFO "dest    : %d\n"
+		      KERN_INFO "vpos    : %d\n"
+		      KERN_INFO "seg_pos : %d\n"
+		      KERN_INFO "trials  : %d",
+		      seg_dist, distance, dest,
+		      (unsigned int)(pos->volume_pos>>10), pos->seg_pos,
+		      fast_seek_trials);
+		if (distance > 0) {
+			if (seg_dist < 0) {
+				TRACE(ft_t_bug, "BUG: distance %d > 0, "
+				      "segment difference %d < 0",
+				      distance, seg_dist);
+				result = -EIO;
+				break;
+			}
+			new_seg = pos->seg_pos + seg_dist;
+			if (new_seg > volume->end_seg) {
+				new_seg = volume->end_seg;
+			}
+			if (old_seg == new_seg || /* loop */
+			    seg_dist <= ZFT_SLOW_SEEK_THRESHOLD ||
+			    fast_seek_trials >= ZFT_FAST_SEEK_MAX_TRIALS) {
+				TRACE(ft_t_noise, "starting slow seek:\n"
+				   KERN_INFO "fast seek failed too often: %s\n"
+				   KERN_INFO "near target position      : %s\n"
+				   KERN_INFO "looping between two segs  : %s",
+				      (fast_seek_trials >= 
+				       ZFT_FAST_SEEK_MAX_TRIALS)
+				      ? "yes" : "no",
+				      (seg_dist <= ZFT_SLOW_SEEK_THRESHOLD) 
+				      ? "yes" : "no",
+				      (old_seg == new_seg)
+				      ? "yes" : "no");
+				result = slow_seek_forward(dest, &cseg, 
+							   pos, volume, buf);
+				break;
+			}
+			old_seg = new_seg;
+			limit = volume->end_seg;
+			fast_seek_trials ++;
+			for (;;) {
+				result = search_valid_segment(new_seg, limit,
+							      volume->size,
+							      pos, &cseg,
+							      volume, buf);
+				if (result == 0 || result == -EINTR) {
+					break;
+				}
+				if (new_seg == volume->start_seg) {
+					result = -EIO; /* set errror 
+							* condition
+							*/
+					break;
+				}
+				limit    = new_seg;
+				new_seg -= ZFT_FAST_SEEK_BACKUP;
+				if (new_seg < volume->start_seg) {
+					new_seg = volume->start_seg;
+				}
+			}
+			if (result < 0) {
+				TRACE(ft_t_warn,
+				      "Couldn't find a readable segment");
+				break;
+			}
+		} else /* if (distance < 0) */ {
+			if (seg_dist > 0) {
+				TRACE(ft_t_bug, "BUG: distance %d < 0, "
+				      "segment difference %d >0",
+				      distance, seg_dist);
+				result = -EIO;
+				break;
+			}
+			new_seg = pos->seg_pos + seg_dist;
+			if (fast_seek_trials > 0 && seg_dist == 0) {
+				/* this avoids sticking to the same
+				 * segment all the time. On the other hand:
+				 * if we got here for the first time, and the
+				 * deblock_buffer still contains a valid
+				 * segment, then there is no need to skip to 
+				 * the previous segment if the desired position
+				 * is inside this segment.
+				 */
+				new_seg --;
+			}
+			if (new_seg < volume->start_seg) {
+				new_seg = volume->start_seg;
+			}
+			limit   = pos->seg_pos;
+			fast_seek_trials ++;
+			for (;;) {
+				result = search_valid_segment(new_seg, limit,
+							      pos->volume_pos,
+							      pos, &cseg,
+							      volume, buf);
+				if (result == 0 || result == -EINTR) {
+					break;
+				}
+				if (new_seg == volume->start_seg) {
+					result = -EIO; /* set errror 
+							* condition
+							*/
+					break;
+				}
+				limit    = new_seg;
+				new_seg -= ZFT_FAST_SEEK_BACKUP;
+				if (new_seg < volume->start_seg) {
+					new_seg = volume->start_seg;
+				}
+			}
+			if (result < 0) {
+				TRACE(ft_t_warn,
+				      "Couldn't find a readable segment");
+				break;
+			}
+		}
+		distance = dest - (pos->volume_pos >> 10);
+	}
+	TRACE_EXIT result;
+}
+
+
+/*  advance inside the given segment at most to_do bytes.
+ *  of kilobytes moved
+ */
+
+static int seek_in_segment(const unsigned int to_do,
+			   cmpr_info  *c_info,
+			   const char *src_buf, 
+			   const int seg_sz, 
+			   const int seg_pos,
+			   const zft_volinfo *volume)
+{
+	int result = 0;
+	int blk_sz = volume->blk_sz >> 10;
+	int remaining = to_do;
+	TRACE_FUN(ft_t_flow);
+
+	if (c_info->offset == 0) {
+		/* new segment just read
+		 */
+		TRACE_CATCH(get_cseg(c_info, src_buf, seg_sz, volume),);
+		c_info->cmpr_pos += c_info->count;
+		DUMP_CMPR_INFO(ft_t_noise, "", c_info);
+	}
+	/* loop and uncompress until user buffer full or
+	 * deblock-buffer empty 
+	 */
+	TRACE(ft_t_noise, "compressed_sz: %d, compos : %d",
+	      c_info->cmpr_sz, c_info->cmpr_pos);
+	while (c_info->spans == 0 && remaining > 0) {
+		if (c_info->cmpr_pos  != 0) { /* cmpr buf is not empty */
+			result       += blk_sz;
+			remaining    -= blk_sz;
+			c_info->cmpr_pos = 0;
+		}
+		if (remaining > 0) {
+			get_next_cluster(c_info, src_buf, seg_sz, 
+					 volume->end_seg == seg_pos);
+			if (c_info->count != 0) {
+				c_info->cmpr_pos = c_info->count;
+				c_info->offset  += c_info->count;
+			} else {
+				break;
+			}
+		}
+		/*  Allow escape from this loop on signal!
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		DUMP_CMPR_INFO(ft_t_noise, "", c_info);
+		TRACE(ft_t_noise, "to_do: %d", remaining);
+	}
+	if (seg_sz - c_info->offset <= 18) {
+		c_info->offset = seg_sz;
+	}
+	TRACE(ft_t_noise, "\n"
+	      KERN_INFO "segment size   : %d\n"
+	      KERN_INFO "buf_pos_read   : %d\n"
+	      KERN_INFO "remaining      : %d",
+	      seg_sz, c_info->offset,
+	      seg_sz - c_info->offset);
+	TRACE_EXIT result;
+}                
+
+static int slow_seek_forward_until_error(const unsigned int distance,
+					 cmpr_info *c_info,
+					 zft_position *pos, 
+					 const zft_volinfo *volume,
+					 __u8 *buf)
+{
+	unsigned int remaining = distance;
+	int seg_sz;
+	int seg_pos;
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	seg_pos = pos->seg_pos;
+	do {
+		TRACE_CATCH(seg_sz = zft_fetch_segment(seg_pos, buf, 
+						       FT_RD_AHEAD),);
+		/* now we have the contents of the actual segment in
+		 * the deblock buffer
+		 */
+		TRACE_CATCH(result = seek_in_segment(remaining, c_info, buf,
+						     seg_sz, seg_pos,volume),);
+		remaining        -= result;
+		pos->volume_pos  += result<<10;
+		pos->seg_pos      = seg_pos;
+		pos->seg_byte_pos = c_info->offset;
+		seg_pos ++;
+		if (seg_pos <= volume->end_seg && c_info->offset == seg_sz) {
+			pos->seg_pos ++;
+			pos->seg_byte_pos = 0;
+			c_info->offset = 0;
+		}
+		/*  Allow escape from this loop on signal!
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		TRACE(ft_t_noise, "\n"
+		      KERN_INFO "remaining:  %d\n"
+		      KERN_INFO "seg_pos:    %d\n"
+		      KERN_INFO "end_seg:    %d\n"
+		      KERN_INFO "result:     %d",
+		      remaining, seg_pos, volume->end_seg, result);  
+	} while (remaining > 0 && seg_pos <= volume->end_seg);
+	TRACE_EXIT 0;
+}
+
+/* return segment id of next segment containing valid data, -EIO otherwise
+ */
+static int search_valid_segment(unsigned int segment,
+				const unsigned int end_seg,
+				const unsigned int max_foffs,
+				zft_position *pos,
+				cmpr_info *c_info,
+				const zft_volinfo *volume,
+				__u8 *buf)
+{
+	cmpr_info tmp_info;
+	int seg_sz;
+	TRACE_FUN(ft_t_flow);
+	
+	memset(&tmp_info, 0, sizeof(cmpr_info));
+	while (segment <= end_seg) {
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		TRACE(ft_t_noise,
+		      "Searching readable segment between %d and %d",
+		      segment, end_seg);
+		seg_sz = zft_fetch_segment(segment, buf, FT_RD_AHEAD);
+		if ((seg_sz > 0) &&
+		    (get_cseg (&tmp_info, buf, seg_sz, volume) >= 0) &&
+		    (tmp_info.foffs != 0 || segment == volume->start_seg)) {
+			if ((tmp_info.foffs>>10) > max_foffs) {
+				TRACE_ABORT(-EIO, ft_t_noise, "\n"
+					    KERN_INFO "cseg.foff: %d\n"
+					    KERN_INFO "dest     : %d",
+					    (int)(tmp_info.foffs >> 10),
+					    max_foffs);
+			}
+			DUMP_CMPR_INFO(ft_t_noise, "", &tmp_info);
+			*c_info           = tmp_info;
+			pos->seg_pos      = segment;
+			pos->volume_pos   = c_info->foffs;
+			pos->seg_byte_pos = c_info->offset;
+			TRACE(ft_t_noise, "found segment at %d", segment);
+			TRACE_EXIT 0;
+		}
+		segment++;
+	}
+	TRACE_EXIT -EIO;
+}
+
+static int slow_seek_forward(unsigned int dest,
+			     cmpr_info *c_info,
+			     zft_position *pos,
+			     const zft_volinfo *volume,
+			     __u8 *buf)
+{
+	unsigned int distance;
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+		
+	distance = dest - (pos->volume_pos >> 10);
+	while ((distance > 0) &&
+	       (result = slow_seek_forward_until_error(distance,
+						       c_info,
+						       pos,
+						       volume,
+						       buf)) < 0) {
+		if (result == -EINTR) {
+			break;
+		}
+		TRACE(ft_t_noise, "seg_pos: %d", pos->seg_pos);
+		/* the failing segment is either pos->seg_pos or
+		 * pos->seg_pos + 1. There is no need to further try
+		 * that segment, because ftape_read_segment() already
+		 * has tried very much to read it. So we start with
+		 * following segment, which is pos->seg_pos + 1
+		 */
+		if(search_valid_segment(pos->seg_pos+1, volume->end_seg, dest,
+					pos, c_info,
+					volume, buf) < 0) {
+			TRACE(ft_t_noise, "search_valid_segment() failed");
+			result = -EIO;
+			break;
+		}
+		distance = dest - (pos->volume_pos >> 10);
+		result = 0;
+		TRACE(ft_t_noise, "segment: %d", pos->seg_pos);
+		/* found valid segment, retry the seek */
+	}
+	TRACE_EXIT result;
+}
+
+static int compute_seg_pos(const unsigned int dest,
+			   zft_position *pos,
+			   const zft_volinfo *volume)
+{
+	int segment;
+	int distance = dest - (pos->volume_pos >> 10);
+	unsigned int raw_size;
+	unsigned int virt_size;
+	unsigned int factor;
+	TRACE_FUN(ft_t_flow);
+
+	if (distance >= 0) {
+		raw_size  = volume->end_seg - pos->seg_pos + 1;
+		virt_size = ((unsigned int)(volume->size>>10) 
+			     - (unsigned int)(pos->volume_pos>>10)
+			     + FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS - 1);
+		virt_size /= FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS;
+		if (virt_size == 0 || raw_size == 0) {
+			TRACE_EXIT 0;
+		}
+		if (raw_size >= (1<<25)) {
+			factor = raw_size/(virt_size>>7);
+		} else {
+			factor = (raw_size<<7)/virt_size;
+		}
+		segment = distance/(FT_SECTORS_PER_SEGMENT-FT_ECC_SECTORS);
+		segment = (segment * factor)>>7;
+	} else {
+		raw_size  = pos->seg_pos - volume->start_seg + 1;
+		virt_size = ((unsigned int)(pos->volume_pos>>10)
+			     + FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS - 1);
+		virt_size /= FT_SECTORS_PER_SEGMENT - FT_ECC_SECTORS;
+		if (virt_size == 0 || raw_size == 0) {
+			TRACE_EXIT 0;
+		}
+		if (raw_size >= (1<<25)) {
+			factor = raw_size/(virt_size>>7);
+		} else {
+			factor = (raw_size<<7)/virt_size;
+		}
+		segment = distance/(FT_SECTORS_PER_SEGMENT-FT_ECC_SECTORS);
+	}
+	TRACE(ft_t_noise, "factor: %d/%d", factor, 1<<7);
+	TRACE_EXIT segment;
+}
+
+static struct zft_cmpr_ops cmpr_ops = {
+	zftc_write,
+	zftc_read,
+	zftc_seek,
+	zftc_lock,
+	zftc_reset,
+	zftc_cleanup
+};
+
+int zft_compressor_init(void)
+{
+	TRACE_FUN(ft_t_flow);
+	
+#ifdef MODULE
+	printk(KERN_INFO "zftape compressor v1.00a 970514 for " FTAPE_VERSION "\n");
+        if (TRACE_LEVEL >= ft_t_info) {
+		printk(
+KERN_INFO "(c) 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n"
+KERN_INFO "Compressor for zftape (lzrw3 algorithm)\n");
+        }
+#else /* !MODULE */
+	/* print a short no-nonsense boot message */
+	printk("zftape compressor v1.00a 970514\n");
+	printk("For use with " FTAPE_VERSION "\n");
+#endif /* MODULE */
+	TRACE(ft_t_info, "zft_compressor_init @ 0x%p", zft_compressor_init);
+	TRACE(ft_t_info, "installing compressor for zftape ...");
+	TRACE_CATCH(zft_cmpr_register(&cmpr_ops),);
+	TRACE_EXIT 0;
+}
+
+#ifdef MODULE
+
+MODULE_AUTHOR(
+	"(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de");
+MODULE_DESCRIPTION(
+"Compression routines for zftape. Uses the lzrw3 algorithm by Ross Williams");
+MODULE_LICENSE("GPL");
+
+/* Called by modules package when installing the driver
+ */
+int init_module(void)
+{
+	return zft_compressor_init();
+}
+
+#endif /* MODULE */
diff --git a/drivers/char/ftape/compressor/zftape-compress.h b/drivers/char/ftape/compressor/zftape-compress.h
new file mode 100644
index 0000000..f200741
--- /dev/null
+++ b/drivers/char/ftape/compressor/zftape-compress.h
@@ -0,0 +1,83 @@
+#ifndef _ZFTAPE_COMPRESS_H
+#define _ZFTAPE_COMPRESS_H
+/*
+ *      Copyright (c) 1994-1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/compressor/zftape-compress.h,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/10/05 19:12:32 $
+ *
+ * This file contains macros and definitions for zftape's
+ * builtin compression code.
+ *
+ */
+
+#include "../zftape/zftape-buffers.h"
+#include "../zftape/zftape-vtbl.h"
+#include "../compressor/lzrw3.h"
+
+/* CMPR_WRK_MEM_SIZE gives the size of the compression wrk_mem */
+/* I got these out of lzrw3.c */
+#define U(X)            ((__u32) X)
+#define SIZE_P_BYTE     (U(sizeof(__u8 *)))
+#define ALIGNMENT_FUDGE (U(16))
+
+#define CMPR_WRK_MEM_SIZE (U(4096)*(SIZE_P_BYTE) + ALIGNMENT_FUDGE)
+
+/* the maximum number of bytes the size of the "compressed" data can
+ * exceed the uncompressed data. As it is quite useless to compress
+ * data twice it is sometimes the case that it is more efficient to
+ * copy a block of data but to feed it to the "compression"
+ * algorithm. In this case there are some flag bytes or the like
+ * proceding the "compressed" data.  THAT MUST NOT BE THE CASE for the
+ * algorithm we use for this driver. Instead, the high bit 15 of
+ * compressed_size:
+ *
+ * compressed_size = ftape_compress()
+ *
+ * must be set in such a case.
+ *
+ * Nevertheless, it might also be as for lzrw3 that there is an
+ * "intermediate" overrun that exceeds the amount of the compressed
+ * data that is actually produced. During the algorithm we need in the
+ * worst case MAX_CMP_GROUP bytes more than the input-size.
+ */
+#define MAX_CMP_GROUP (2+16*2) /* from lzrw3.c */
+
+#define CMPR_OVERRUN      MAX_CMP_GROUP /* during compression */
+
+/****************************************************/
+
+#define     CMPR_BUFFER_SIZE (MAX_BLOCK_SIZE + CMPR_OVERRUN)
+
+/* the compression map stores the byte offset compressed blocks within
+ * the current volume for catridges with format code 2,3 and 5
+ * (and old versions of zftape) and the offset measured in kilobytes for
+ * format code 4 and 6. This gives us a possible max. size of a 
+ * compressed volume of 1024*4GIG which should be enough.
+ */
+typedef __u32 CmprMap;
+
+/* globals 
+ */
+
+/* exported functions
+ */
+
+#endif /* _ZFTAPE_COMPRESS_H */
diff --git a/drivers/char/ftape/lowlevel/Makefile b/drivers/char/ftape/lowlevel/Makefile
new file mode 100644
index 0000000..febab07
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/Makefile
@@ -0,0 +1,43 @@
+#
+#       Copyright (C) 1996, 1997 Clau-Justus Heine.
+#
+# 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, 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; see the file COPYING.  If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/Makefile,v $
+# $Revision: 1.4 $
+# $Date: 1997/10/07 09:26:02 $
+#
+#      Makefile for the lowlevel part QIC-40/80/3010/3020 floppy-tape
+#      driver for Linux.
+#
+
+obj-$(CONFIG_FTAPE) += ftape.o
+
+ftape-objs := ftape-init.o fdc-io.o fdc-isr.o \
+	      ftape-bsm.o ftape-ctl.o ftape-read.o ftape-rw.o \
+	      ftape-write.o ftape-io.o ftape-calibr.o ftape-ecc.o fc-10.o \
+	      ftape-buffer.o ftape-format.o ftape_syms.o
+
+ifeq ($(CONFIG_FTAPE),y)
+ftape-objs += ftape-setup.o
+endif
+
+ifndef CONFIG_FT_NO_TRACE_AT_ALL
+ftape-objs += ftape-tracing.o
+endif
+
+ifeq ($(CONFIG_FT_PROC_FS),y)
+ftape-objs += ftape-proc.o
+endif
diff --git a/drivers/char/ftape/lowlevel/fc-10.c b/drivers/char/ftape/lowlevel/fc-10.c
new file mode 100644
index 0000000..9bc1cdd
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fc-10.c
@@ -0,0 +1,175 @@
+/*
+ *
+
+   Copyright (C) 1993,1994 Jon Tombs.
+
+   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.
+
+   The entire guts of this program was written by dosemu, modified to
+   record reads and writes to the ports in the 0x180-0x188 address space,
+   while running the CMS program TAPE.EXE V2.0.5 supplied with the drive.
+
+   Modified to use an array of addresses and generally cleaned up (made
+   much shorter) 4 June 94, dosemu isn't that good at writing short code it
+   would seem :-). Made independent of 0x180, but I doubt it will work
+   at any other address.
+
+   Modified for distribution with ftape source. 21 June 94, SJL.
+
+   Modifications on 20 October 95, by Daniel Cohen (catman@wpi.edu):
+   Modified to support different DMA, IRQ, and IO Ports.  Borland's
+   Turbo Debugger in virtual 8086 mode (TD386.EXE with hardware breakpoints
+   provided by the TDH386.SYS Device Driver) was used on the CMS program
+   TAPE V4.0.5.  I set breakpoints on I/O to ports 0x180-0x187.  Note that
+   CMS's program will not successfully configure the tape drive if you set
+   breakpoints on IO Reads, but you can set them on IO Writes without problems.
+   Known problems:
+   - You can not use DMA Channels 5 or 7.
+
+   Modification on 29 January 96, by Daniel Cohen (catman@wpi.edu):
+   Modified to only accept IRQs 3 - 7, or 9.  Since we can only send a 3 bit
+   number representing the IRQ to the card, special handling is required when
+   IRQ 9 is selected.  IRQ 2 and 9 are the same, and we should request IRQ 9
+   from the kernel while telling the card to use IRQ 2.  Thanks to Greg
+   Crider (gcrider@iclnet.org) for finding and locating this bug, as well as
+   testing the patch.
+
+   Modification on 11 December 96, by Claus Heine (claus@momo.math.rwth-aachen.de):
+   Modified a little to use variahle ft_fdc_base, ft_fdc_irq, ft_fdc_dma 
+   instead of preprocessor symbols. Thus we can compile this into the module
+   or kernel and let the user specify the options as command line arguments.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fc-10.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:04 $
+ *
+ *      This file contains code for the CMS FC-10/FC-20 card.
+ */
+
+#include <asm/io.h>
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/fc-10.h"
+
+static __u16 inbs_magic[] = {
+	0x3, 0x3, 0x0, 0x4, 0x7, 0x2, 0x5, 0x3, 0x1, 0x4,
+	0x3, 0x5, 0x2, 0x0, 0x3, 0x7, 0x4, 0x2,
+	0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7
+};
+
+static __u16 fc10_ports[] = {
+	0x180, 0x210, 0x2A0, 0x300, 0x330, 0x340, 0x370
+};
+
+int fc10_enable(void)
+{
+	int i;
+	__u8 cardConfig = 0x00;
+	__u8 x;
+	TRACE_FUN(ft_t_flow);
+
+/*  This code will only work if the FC-10 (or FC-20) is set to
+ *  use DMA channels 1, 2, or 3.  DMA channels 5 and 7 seem to be 
+ *  initialized by the same command as channels 1 and 3, respectively.
+ */
+	if (ft_fdc_dma > 3) {
+		TRACE_ABORT(0, ft_t_err,
+"Error: The FC-10/20 must be set to use DMA channels 1, 2, or 3!");
+	}
+/*  Only allow the FC-10/20 to use IRQ 3-7, or 9.  Note that CMS's program
+ *  only accepts IRQ's 2-7, but in linux, IRQ 2 is the same as IRQ 9.
+ */
+	if (ft_fdc_irq < 3 || ft_fdc_irq == 8 || ft_fdc_irq > 9) {
+		TRACE_ABORT(0, ft_t_err, 
+"Error: The FC-10/20 must be set to use IRQ levels 3 - 7, or 9!\n"
+KERN_INFO "Note: IRQ 9 is the same as IRQ 2");
+	}
+	/*  Clear state machine ???
+	 */
+	for (i = 0; i < NR_ITEMS(inbs_magic); i++) {
+		inb(ft_fdc_base + inbs_magic[i]);
+	}
+	outb(0x0, ft_fdc_base);
+
+	x = inb(ft_fdc_base);
+	if (x == 0x13 || x == 0x93) {
+		for (i = 1; i < 8; i++) {
+			if (inb(ft_fdc_base + i) != x) {
+				TRACE_EXIT 0;
+			}
+		}
+	} else {
+		TRACE_EXIT 0;
+	}
+
+	outb(0x8, ft_fdc_base);
+
+	for (i = 0; i < 8; i++) {
+		if (inb(ft_fdc_base + i) != 0x0) {
+			TRACE_EXIT 0;
+		}
+	}
+	outb(0x10, ft_fdc_base);
+
+	for (i = 0; i < 8; i++) {
+		if (inb(ft_fdc_base + i) != 0xff) {
+			TRACE_EXIT 0;
+		}
+	}
+
+	/*  Okay, we found a FC-10 card ! ???
+	 */
+	outb(0x0, fdc.ccr);
+
+	/*  Clear state machine again ???
+	 */
+	for (i = 0; i < NR_ITEMS(inbs_magic); i++) {
+		inb(ft_fdc_base + inbs_magic[i]);
+	}
+	/* Send io port */
+	for (i = 0; i < NR_ITEMS(fc10_ports); i++)
+		if (ft_fdc_base == fc10_ports[i])
+			cardConfig = i + 1;
+	if (cardConfig == 0) {
+		TRACE_EXIT 0;	/* Invalid I/O Port */
+	}
+	/* and IRQ - If using IRQ 9, tell the FC card it is actually IRQ 2 */
+	if (ft_fdc_irq != 9)
+		cardConfig |= ft_fdc_irq << 3;
+	else
+		cardConfig |= 2 << 3;
+
+	/* and finally DMA Channel */
+	cardConfig |= ft_fdc_dma << 6;
+	outb(cardConfig, ft_fdc_base);	/* DMA [2 bits]/IRQ [3 bits]/BASE [3 bits] */
+
+	/*  Enable FC-10 ???
+	 */
+	outb(0, fdc.ccr);
+	outb(0, fdc.dor2);
+	outb(FDC_DMA_MODE /* 8 */, fdc.dor);
+	outb(FDC_DMA_MODE /* 8 */, fdc.dor);
+	outb(1, fdc.dor2);
+
+	/*************************************
+	 *
+	 * cH: why the hell should this be necessary? This is done 
+	 *     by fdc_reset()!!!
+	 *
+	 *************************************/
+	/*  Initialize fdc, select drive B:
+	 */
+	outb(FDC_DMA_MODE, fdc.dor);	/* assert reset, dma & irq enabled */
+	/*       0x08    */
+	outb(FDC_DMA_MODE|FDC_RESET_NOT, fdc.dor);	/* release reset */
+	/*       0x08    |   0x04   = 0x0c */
+	outb(FDC_DMA_MODE|FDC_RESET_NOT|FDC_MOTOR_1|FTAPE_SEL_B, fdc.dor);
+	/*       0x08    |   0x04      |  0x20     |  0x01  = 0x2d */    
+	/* select drive 1 */ /* why not drive 0 ???? */
+	TRACE_EXIT (x == 0x93) ? 2 : 1;
+}
diff --git a/drivers/char/ftape/lowlevel/fc-10.h b/drivers/char/ftape/lowlevel/fc-10.h
new file mode 100644
index 0000000..da7b88b
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fc-10.h
@@ -0,0 +1,39 @@
+#ifndef _FC_10_H
+#define _FC_10_H
+
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fc-10.h,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/09/19 09:05:22 $
+ *
+ *      This file contains definitions for the FC-10 code
+ *      of the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+/*
+ *      fc-10.c defined global vars.
+ */
+
+/*
+ *      fc-10.c defined global functions.
+ */
+extern int fc10_enable(void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/fdc-io.c b/drivers/char/ftape/lowlevel/fdc-io.c
new file mode 100644
index 0000000..1704a2a
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fdc-io.c
@@ -0,0 +1,1352 @@
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-io.c,v $
+ * $Revision: 1.7.4.2 $
+ * $Date: 1997/11/16 14:48:17 $
+ *
+ *      This file contains the low-level floppy disk interface code
+ *      for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for
+ *      Linux.
+ */
+
+#include <linux/config.h> /* for CONFIG_FT_* */
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/fdc-isr.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-calibr.h"
+#include "../lowlevel/fc-10.h"
+
+/*      Global vars.
+ */
+static int ftape_motor;
+volatile int ftape_current_cylinder = -1;
+volatile fdc_mode_enum fdc_mode = fdc_idle;
+fdc_config_info fdc;
+DECLARE_WAIT_QUEUE_HEAD(ftape_wait_intr);
+
+unsigned int ft_fdc_base       = CONFIG_FT_FDC_BASE;
+unsigned int ft_fdc_irq        = CONFIG_FT_FDC_IRQ;
+unsigned int ft_fdc_dma        = CONFIG_FT_FDC_DMA;
+unsigned int ft_fdc_threshold  = CONFIG_FT_FDC_THR;  /* bytes */
+unsigned int ft_fdc_rate_limit = CONFIG_FT_FDC_MAX_RATE; /* bits/sec */
+int ft_probe_fc10        = CONFIG_FT_PROBE_FC10;
+int ft_mach2             = CONFIG_FT_MACH2;
+
+/*      Local vars.
+ */
+static spinlock_t fdc_io_lock; 
+static unsigned int fdc_calibr_count;
+static unsigned int fdc_calibr_time;
+static int fdc_status;
+volatile __u8 fdc_head;		/* FDC head from sector id */
+volatile __u8 fdc_cyl;		/* FDC track from sector id */
+volatile __u8 fdc_sect;		/* FDC sector from sector id */
+static int fdc_data_rate = 500;	/* data rate (Kbps) */
+static int fdc_rate_code;	/* data rate code (0 == 500 Kbps) */
+static int fdc_seek_rate = 2;	/* step rate (msec) */
+static void (*do_ftape) (void);
+static int fdc_fifo_state;	/* original fifo setting - fifo enabled */
+static int fdc_fifo_thr;	/* original fifo setting - threshold */
+static int fdc_lock_state;	/* original lock setting - locked */
+static int fdc_fifo_locked;	/* has fifo && lock set ? */
+static __u8 fdc_precomp;	/* default precomp. value (nsec) */
+static __u8 fdc_prec_code;	/* fdc precomp. select code */
+
+static char ftape_id[] = "ftape";  /* used by request irq and free irq */
+
+static int fdc_set_seek_rate(int seek_rate);
+
+void fdc_catch_stray_interrupts(int count)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&fdc_io_lock, flags);
+	if (count == 0) {
+		ft_expected_stray_interrupts = 0;
+	} else {
+		ft_expected_stray_interrupts += count;
+	}
+	spin_unlock_irqrestore(&fdc_io_lock, flags);
+}
+
+/*  Wait during a timeout period for a given FDC status.
+ *  If usecs == 0 then just test status, else wait at least for usecs.
+ *  Returns -ETIME on timeout. Function must be calibrated first !
+ */
+static int fdc_wait(unsigned int usecs, __u8 mask, __u8 state)
+{
+	int count_1 = (fdc_calibr_count * usecs +
+                       fdc_calibr_count - 1) / fdc_calibr_time;
+
+	do {
+		fdc_status = inb_p(fdc.msr);
+		if ((fdc_status & mask) == state) {
+			return 0;
+		}
+	} while (count_1-- >= 0);
+	return -ETIME;
+}
+
+int fdc_ready_wait(unsigned int usecs)
+{
+	return fdc_wait(usecs, FDC_DATA_READY | FDC_BUSY, FDC_DATA_READY);
+}
+
+/* Why can't we just use udelay()?
+ */
+static void fdc_usec_wait(unsigned int usecs)
+{
+	fdc_wait(usecs, 0, 1);	/* will always timeout ! */
+}
+
+static int fdc_ready_out_wait(unsigned int usecs)
+{
+	fdc_usec_wait(FT_RQM_DELAY);	/* wait for valid RQM status */
+	return fdc_wait(usecs, FDC_DATA_OUT_READY, FDC_DATA_OUT_READY);
+}
+
+void fdc_wait_calibrate(void)
+{
+	ftape_calibrate("fdc_wait",
+			fdc_usec_wait, &fdc_calibr_count, &fdc_calibr_time); 
+}
+
+/*  Wait for a (short) while for the FDC to become ready
+ *  and transfer the next command byte.
+ *  Return -ETIME on timeout on getting ready (depends on hardware!).
+ */
+static int fdc_write(const __u8 data)
+{
+	fdc_usec_wait(FT_RQM_DELAY);	/* wait for valid RQM status */
+	if (fdc_wait(150, FDC_DATA_READY_MASK, FDC_DATA_IN_READY) < 0) {
+		return -ETIME;
+	} else {
+		outb(data, fdc.fifo);
+		return 0;
+	}
+}
+
+/*  Wait for a (short) while for the FDC to become ready
+ *  and transfer the next result byte.
+ *  Return -ETIME if timeout on getting ready (depends on hardware!).
+ */
+static int fdc_read(__u8 * data)
+{
+	fdc_usec_wait(FT_RQM_DELAY);	/* wait for valid RQM status */
+	if (fdc_wait(150, FDC_DATA_READY_MASK, FDC_DATA_OUT_READY) < 0) {
+		return -ETIME;
+	} else {
+		*data = inb(fdc.fifo);
+		return 0;
+	}
+}
+
+/*  Output a cmd_len long command string to the FDC.
+ *  The FDC should be ready to receive a new command or
+ *  an error (EBUSY or ETIME) will occur.
+ */
+int fdc_command(const __u8 * cmd_data, int cmd_len)
+{
+	int result = 0;
+	unsigned long flags;
+	int count = cmd_len;
+	int retry = 0;
+#ifdef TESTING
+	static unsigned int last_time;
+	unsigned int time;
+#endif
+	TRACE_FUN(ft_t_any);
+
+	fdc_usec_wait(FT_RQM_DELAY);	/* wait for valid RQM status */
+	spin_lock_irqsave(&fdc_io_lock, flags);
+	if (!in_interrupt())
+		/* Yes, I know, too much comments inside this function
+		 * ...
+		 * 
+		 * Yet another bug in the original driver. All that
+		 * havoc is caused by the fact that the isr() sends
+		 * itself a command to the floppy tape driver (pause,
+		 * micro step pause).  Now, the problem is that
+		 * commands are transmitted via the fdc_seek
+		 * command. But: the fdc performs seeks in the
+		 * background i.e. it doesn't signal busy while
+		 * sending the step pulses to the drive. Therefore the
+		 * non-interrupt level driver has no chance to tell
+		 * whether the isr() just has issued a seek. Therefore
+		 * we HAVE TO have a look at the ft_hide_interrupt
+		 * flag: it signals the non-interrupt level part of
+		 * the driver that it has to wait for the fdc until it
+		 * has completet seeking.
+		 *
+		 * THIS WAS PRESUMABLY THE REASON FOR ALL THAT
+		 * "fdc_read timeout" errors, I HOPE :-)
+		 */
+		if (ft_hide_interrupt) {
+			restore_flags(flags);
+			TRACE(ft_t_info,
+			      "Waiting for the isr() completing fdc_seek()");
+			if (fdc_interrupt_wait(2 * FT_SECOND) < 0) {
+				TRACE(ft_t_warn,
+		      "Warning: timeout waiting for isr() seek to complete");
+			}
+			if (ft_hide_interrupt || !ft_seek_completed) {
+				/* There cannot be another
+				 * interrupt. The isr() only stops
+				 * the tape and the next interrupt
+				 * won't come until we have send our
+				 * command to the drive.
+				 */
+				TRACE_ABORT(-EIO, ft_t_bug,
+					    "BUG? isr() is still seeking?\n"
+					    KERN_INFO "hide: %d\n"
+					    KERN_INFO "seek: %d",
+					    ft_hide_interrupt,
+					    ft_seek_completed);
+
+			}
+			fdc_usec_wait(FT_RQM_DELAY);	/* wait for valid RQM status */
+			spin_lock_irqsave(&fdc_io_lock, flags);
+		}
+	fdc_status = inb(fdc.msr);
+	if ((fdc_status & FDC_DATA_READY_MASK) != FDC_DATA_IN_READY) {
+		spin_unlock_irqrestore(&fdc_io_lock, flags);
+		TRACE_ABORT(-EBUSY, ft_t_err, "fdc not ready");
+	} 
+	fdc_mode = *cmd_data;	/* used by isr */
+#ifdef TESTING
+	if (fdc_mode == FDC_SEEK) {
+		time = ftape_timediff(last_time, ftape_timestamp());
+		if (time < 6000) {
+	TRACE(ft_t_bug,"Warning: short timeout between seek commands: %d",
+	      time);
+		}
+	}
+#endif
+	if (!in_interrupt()) {
+		/* shouldn't be cleared if called from isr
+		 */
+		ft_interrupt_seen = 0;
+	}
+	while (count) {
+		result = fdc_write(*cmd_data);
+		if (result < 0) {
+			TRACE(ft_t_fdc_dma,
+			      "fdc_mode = %02x, status = %02x at index %d",
+			      (int) fdc_mode, (int) fdc_status,
+			      cmd_len - count);
+			if (++retry <= 3) {
+				TRACE(ft_t_warn, "fdc_write timeout, retry");
+			} else {
+				TRACE(ft_t_err, "fdc_write timeout, fatal");
+				/* recover ??? */
+				break;
+			}
+		} else {
+			--count;
+			++cmd_data;
+		}
+        }
+#ifdef TESTING
+	if (fdc_mode == FDC_SEEK) {
+		last_time = ftape_timestamp();
+	}
+#endif
+	spin_unlock_irqrestore(&fdc_io_lock, flags);
+	TRACE_EXIT result;
+}
+
+/*  Input a res_len long result string from the FDC.
+ *  The FDC should be ready to send the result or an error
+ *  (EBUSY or ETIME) will occur.
+ */
+int fdc_result(__u8 * res_data, int res_len)
+{
+	int result = 0;
+	unsigned long flags;
+	int count = res_len;
+	int retry = 0;
+	TRACE_FUN(ft_t_any);
+
+	spin_lock_irqsave(&fdc_io_lock, flags);
+	fdc_status = inb(fdc.msr);
+	if ((fdc_status & FDC_DATA_READY_MASK) != FDC_DATA_OUT_READY) {
+		TRACE(ft_t_err, "fdc not ready");
+		result = -EBUSY;
+	} else while (count) {
+		if (!(fdc_status & FDC_BUSY)) {
+			spin_unlock_irqrestore(&fdc_io_lock, flags);
+			TRACE_ABORT(-EIO, ft_t_err, "premature end of result phase");
+		}
+		result = fdc_read(res_data);
+		if (result < 0) {
+			TRACE(ft_t_fdc_dma,
+			      "fdc_mode = %02x, status = %02x at index %d",
+			      (int) fdc_mode,
+			      (int) fdc_status,
+			      res_len - count);
+			if (++retry <= 3) {
+				TRACE(ft_t_warn, "fdc_read timeout, retry");
+			} else {
+				TRACE(ft_t_err, "fdc_read timeout, fatal");
+				/* recover ??? */
+				break;
+				++retry;
+			}
+		} else {
+			--count;
+			++res_data;
+		}
+	}
+	spin_unlock_irqrestore(&fdc_io_lock, flags);
+	fdc_usec_wait(FT_RQM_DELAY);	/* allow FDC to negate BSY */
+	TRACE_EXIT result;
+}
+
+/*      Handle command and result phases for
+ *      commands without data phase.
+ */
+static int fdc_issue_command(const __u8 * out_data, int out_count,
+		      __u8 * in_data, int in_count)
+{
+	TRACE_FUN(ft_t_any);
+
+	if (out_count > 0) {
+		TRACE_CATCH(fdc_command(out_data, out_count),);
+	}
+	/* will take 24 - 30 usec for fdc_sense_drive_status and
+	 * fdc_sense_interrupt_status commands.
+	 *    35 fails sometimes (5/9/93 SJL)
+	 * On a loaded system it incidentally takes longer than
+	 * this for the fdc to get ready ! ?????? WHY ??????
+	 * So until we know what's going on use a very long timeout.
+	 */
+	TRACE_CATCH(fdc_ready_out_wait(500 /* usec */),);
+	if (in_count > 0) {
+		TRACE_CATCH(fdc_result(in_data, in_count),
+			    TRACE(ft_t_err, "result phase aborted"));
+	}
+	TRACE_EXIT 0;
+}
+
+/*      Wait for FDC interrupt with timeout (in milliseconds).
+ *      Signals are blocked so the wait will not be aborted.
+ *      Note: interrupts must be enabled ! (23/05/93 SJL)
+ */
+int fdc_interrupt_wait(unsigned int time)
+{
+	DECLARE_WAITQUEUE(wait,current);
+	sigset_t old_sigmask;	
+	static int resetting;
+	long timeout;
+
+	TRACE_FUN(ft_t_fdc_dma);
+
+ 	if (waitqueue_active(&ftape_wait_intr)) {
+		TRACE_ABORT(-EIO, ft_t_err, "error: nested call");
+	}
+	/* timeout time will be up to USPT microseconds too long ! */
+	timeout = (1000 * time + FT_USPT - 1) / FT_USPT;
+
+	spin_lock_irq(&current->sighand->siglock);
+	old_sigmask = current->blocked;
+	sigfillset(&current->blocked);
+	recalc_sigpending();
+	spin_unlock_irq(&current->sighand->siglock);
+
+	set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&ftape_wait_intr, &wait);
+	while (!ft_interrupt_seen && timeout) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		timeout = schedule_timeout(timeout);
+        }
+
+	spin_lock_irq(&current->sighand->siglock);
+	current->blocked = old_sigmask;
+	recalc_sigpending();
+	spin_unlock_irq(&current->sighand->siglock);
+	
+	remove_wait_queue(&ftape_wait_intr, &wait);
+	/*  the following IS necessary. True: as well
+	 *  wake_up_interruptible() as the schedule() set TASK_RUNNING
+	 *  when they wakeup a task, BUT: it may very well be that
+	 *  ft_interrupt_seen is already set to 1 when we enter here
+	 *  in which case schedule() gets never called, and
+	 *  TASK_RUNNING never set. This has the funny effect that we
+	 *  execute all the code until we leave kernel space, but then
+	 *  the task is stopped (a task CANNOT be preempted while in
+	 *  kernel mode. Sending a pair of SIGSTOP/SIGCONT to the
+	 *  tasks wakes it up again. Funny! :-)
+	 */
+	current->state = TASK_RUNNING; 
+	if (ft_interrupt_seen) { /* woken up by interrupt */
+		ft_interrupt_seen = 0;
+		TRACE_EXIT 0;
+	}
+	/*  Original comment:
+	 *  In first instance, next statement seems unnecessary since
+	 *  it will be cleared in fdc_command. However, a small part of
+	 *  the software seems to rely on this being cleared here
+	 *  (ftape_close might fail) so stick to it until things get fixed !
+	 */
+	/*  My deeply sought of knowledge:
+	 *  Behold NO! It is obvious. fdc_reset() doesn't call fdc_command()
+	 *  but nevertheless uses fdc_interrupt_wait(). OF COURSE this needs to
+	 *  be reset here.
+	 */
+	ft_interrupt_seen = 0;	/* clear for next call */
+	if (!resetting) {
+		resetting = 1;	/* break infinite recursion if reset fails */
+		TRACE(ft_t_any, "cleanup reset");
+		fdc_reset();
+		resetting = 0;
+	}
+	TRACE_EXIT (signal_pending(current)) ? -EINTR : -ETIME;
+}
+
+/*      Start/stop drive motor. Enable DMA mode.
+ */
+void fdc_motor(int motor)
+{
+	int unit = ft_drive_sel;
+	int data = unit | FDC_RESET_NOT | FDC_DMA_MODE;
+	TRACE_FUN(ft_t_any);
+
+	ftape_motor = motor;
+	if (ftape_motor) {
+		data |= FDC_MOTOR_0 << unit;
+		TRACE(ft_t_noise, "turning motor %d on", unit);
+	} else {
+		TRACE(ft_t_noise, "turning motor %d off", unit);
+	}
+	if (ft_mach2) {
+		outb_p(data, fdc.dor2);
+	} else {
+		outb_p(data, fdc.dor);
+	}
+	ftape_sleep(10 * FT_MILLISECOND);
+	TRACE_EXIT;
+}
+
+static void fdc_update_dsr(void)
+{
+	TRACE_FUN(ft_t_any);
+
+	TRACE(ft_t_flow, "rate = %d Kbps, precomp = %d ns",
+	      fdc_data_rate, fdc_precomp);
+	if (fdc.type >= i82077) {
+		outb_p((fdc_rate_code & 0x03) | fdc_prec_code, fdc.dsr);
+	} else {
+		outb_p(fdc_rate_code & 0x03, fdc.ccr);
+	}
+	TRACE_EXIT;
+}
+
+void fdc_set_write_precomp(int precomp)
+{
+	TRACE_FUN(ft_t_any);
+
+	TRACE(ft_t_noise, "New precomp: %d nsec", precomp);
+	fdc_precomp = precomp;
+	/*  write precompensation can be set in multiples of 41.67 nsec.
+	 *  round the parameter to the nearest multiple and convert it
+	 *  into a fdc setting. Note that 0 means default to the fdc,
+	 *  7 is used instead of that.
+	 */
+	fdc_prec_code = ((fdc_precomp + 21) / 42) << 2;
+	if (fdc_prec_code == 0 || fdc_prec_code > (6 << 2)) {
+		fdc_prec_code = 7 << 2;
+	}
+	fdc_update_dsr();
+	TRACE_EXIT;
+}
+
+/*  Reprogram the 82078 registers to use Data Rate Table 1 on all drives.
+ */
+static void fdc_set_drive_specs(void)
+{
+	__u8 cmd[] = { FDC_DRIVE_SPEC, 0x00, 0x00, 0x00, 0x00, 0xc0};
+	int result;
+	TRACE_FUN(ft_t_any);
+
+	TRACE(ft_t_flow, "Setting of drive specs called");
+	if (fdc.type >= i82078_1) {
+		cmd[1] = (0 << 5) | (2 << 2);
+		cmd[2] = (1 << 5) | (2 << 2);
+		cmd[3] = (2 << 5) | (2 << 2);
+		cmd[4] = (3 << 5) | (2 << 2);
+		result = fdc_command(cmd, NR_ITEMS(cmd));
+		if (result < 0) {
+			TRACE(ft_t_err, "Setting of drive specs failed");
+		}
+	}
+	TRACE_EXIT;
+}
+
+/* Select clock for fdc, must correspond with tape drive setting !
+ * This also influences the fdc timing so we must adjust some values.
+ */
+int fdc_set_data_rate(int rate)
+{
+	int bad_rate = 0;
+	TRACE_FUN(ft_t_any);
+
+	/* Select clock for fdc, must correspond with tape drive setting !
+	 * This also influences the fdc timing so we must adjust some values.
+	 */
+	TRACE(ft_t_fdc_dma, "new rate = %d", rate);
+	switch (rate) {
+	case 250:
+		fdc_rate_code = fdc_data_rate_250;
+		break;
+	case 500:
+		fdc_rate_code = fdc_data_rate_500;
+		break;
+	case 1000:
+		if (fdc.type < i82077) {
+			bad_rate = 1;
+                } else {
+			fdc_rate_code = fdc_data_rate_1000;
+		}
+		break;
+	case 2000:
+		if (fdc.type < i82078_1) {
+			bad_rate = 1;
+                } else {
+			fdc_rate_code = fdc_data_rate_2000;
+		}
+		break;
+	default:
+		bad_rate = 1;
+        }
+	if (bad_rate) {
+		TRACE_ABORT(-EIO,
+			    ft_t_fdc_dma, "%d is not a valid data rate", rate);
+	}
+	fdc_data_rate = rate;
+	fdc_update_dsr();
+	fdc_set_seek_rate(fdc_seek_rate);  /* clock changed! */
+	ftape_udelay(1000);
+	TRACE_EXIT 0;
+}
+
+/*  keep the unit select if keep_select is != 0,
+ */
+static void fdc_dor_reset(int keep_select)
+{
+	__u8 fdc_ctl = ft_drive_sel;
+
+	if (keep_select != 0) {
+		fdc_ctl |= FDC_DMA_MODE;
+		if (ftape_motor) {
+			fdc_ctl |= FDC_MOTOR_0 << ft_drive_sel;
+		}
+	}
+	ftape_udelay(10); /* ??? but seems to be necessary */
+	if (ft_mach2) {
+		outb_p(fdc_ctl & 0x0f, fdc.dor);
+		outb_p(fdc_ctl, fdc.dor2);
+	} else {
+		outb_p(fdc_ctl, fdc.dor);
+	}
+	fdc_usec_wait(10); /* delay >= 14 fdc clocks */
+	if (keep_select == 0) {
+		fdc_ctl = 0;
+	}
+	fdc_ctl |= FDC_RESET_NOT;
+	if (ft_mach2) {
+		outb_p(fdc_ctl & 0x0f, fdc.dor);
+		outb_p(fdc_ctl, fdc.dor2);
+	} else {
+		outb_p(fdc_ctl, fdc.dor);
+	}
+}
+
+/*      Reset the floppy disk controller. Leave the ftape_unit selected.
+ */
+void fdc_reset(void)
+{
+	int st0;
+	int i;
+	int dummy;
+	unsigned long flags;
+	TRACE_FUN(ft_t_any);
+
+	spin_lock_irqsave(&fdc_io_lock, flags);
+
+	fdc_dor_reset(1); /* keep unit selected */
+
+	fdc_mode = fdc_idle;
+
+	/*  maybe the cli()/sti() pair is not necessary, BUT:
+	 *  the following line MUST be here. Otherwise fdc_interrupt_wait()
+	 *  won't wait. Note that fdc_reset() is called from 
+	 *  ftape_dumb_stop() when the fdc is busy transferring data. In this
+	 *  case fdc_isr() MOST PROBABLY sets ft_interrupt_seen, and tries
+	 *  to get the result bytes from the fdc etc. CLASH.
+	 */
+	ft_interrupt_seen = 0;
+	
+	/*  Program data rate
+	 */
+	fdc_update_dsr();               /* restore data rate and precomp */
+
+	spin_unlock_irqrestore(&fdc_io_lock, flags);
+
+        /*
+         *	Wait for first polling cycle to complete
+	 */
+	if (fdc_interrupt_wait(1 * FT_SECOND) < 0) {
+		TRACE(ft_t_err, "no drive polling interrupt!");
+	} else {	/* clear all disk-changed statuses */
+		for (i = 0; i < 4; ++i) {
+			if(fdc_sense_interrupt_status(&st0, &dummy) != 0) {
+				TRACE(ft_t_err, "sense failed for %d", i);
+			}
+			if (i == ft_drive_sel) {
+				ftape_current_cylinder = dummy;
+			}
+		}
+		TRACE(ft_t_noise, "drive polling completed");
+	}
+	/*
+         *	SPECIFY COMMAND
+	 */
+	fdc_set_seek_rate(fdc_seek_rate);
+	/*
+	 *	DRIVE SPECIFICATION COMMAND (if fdc type known)
+	 */
+	if (fdc.type >= i82078_1) {
+		fdc_set_drive_specs();
+	}
+	TRACE_EXIT;
+}
+
+#if !defined(CLK_48MHZ)
+# define CLK_48MHZ 1
+#endif
+
+/*  When we're done, put the fdc into reset mode so that the regular
+ *  floppy disk driver will figure out that something is wrong and
+ *  initialize the controller the way it wants.
+ */
+void fdc_disable(void)
+{
+	__u8 cmd1[] = {FDC_CONFIGURE, 0x00, 0x00, 0x00};
+	__u8 cmd2[] = {FDC_LOCK};
+	__u8 cmd3[] = {FDC_UNLOCK};
+	__u8 stat[1];
+	TRACE_FUN(ft_t_flow);
+
+	if (!fdc_fifo_locked) {
+		fdc_reset();
+		TRACE_EXIT;
+	}
+	if (fdc_issue_command(cmd3, 1, stat, 1) < 0 || stat[0] != 0x00) {
+		fdc_dor_reset(0);
+		TRACE_ABORT(/**/, ft_t_bug, 
+		"couldn't unlock fifo, configuration remains changed");
+	}
+	fdc_fifo_locked = 0;
+	if (CLK_48MHZ && fdc.type >= i82078) {
+		cmd1[0] |= FDC_CLK48_BIT;
+	}
+	cmd1[2] = ((fdc_fifo_state) ? 0 : 0x20) + (fdc_fifo_thr - 1);
+	if (fdc_command(cmd1, NR_ITEMS(cmd1)) < 0) {
+		fdc_dor_reset(0);
+		TRACE_ABORT(/**/, ft_t_bug,
+		"couldn't reconfigure fifo to old state");
+	}
+	if (fdc_lock_state &&
+	    fdc_issue_command(cmd2, 1, stat, 1) < 0) {
+		fdc_dor_reset(0);
+		TRACE_ABORT(/**/, ft_t_bug, "couldn't lock old state again");
+	}
+	TRACE(ft_t_noise, "fifo restored: %sabled, thr. %d, %slocked",
+	      fdc_fifo_state ? "en" : "dis",
+	      fdc_fifo_thr, (fdc_lock_state) ? "" : "not ");
+	fdc_dor_reset(0);
+	TRACE_EXIT;
+}
+
+/*      Specify FDC seek-rate (milliseconds)
+ */
+static int fdc_set_seek_rate(int seek_rate)
+{
+	/* set step rate, dma mode, and minimal head load and unload times
+	 */
+	__u8 in[3] = { FDC_SPECIFY, 1, (1 << 1)};
+ 
+	fdc_seek_rate = seek_rate;
+	in[1] |= (16 - (fdc_data_rate * fdc_seek_rate) / 500) << 4;
+
+	return fdc_command(in, 3);
+}
+
+/*      Sense drive status: get unit's drive status (ST3)
+ */
+int fdc_sense_drive_status(int *st3)
+{
+	__u8 out[2];
+	__u8 in[1];
+	TRACE_FUN(ft_t_any);
+
+	out[0] = FDC_SENSED;
+	out[1] = ft_drive_sel;
+	TRACE_CATCH(fdc_issue_command(out, 2, in, 1),);
+	*st3 = in[0];
+	TRACE_EXIT 0;
+}
+
+/*      Sense Interrupt Status command:
+ *      should be issued at the end of each seek.
+ *      get ST0 and current cylinder.
+ */
+int fdc_sense_interrupt_status(int *st0, int *current_cylinder)
+{
+	__u8 out[1];
+	__u8 in[2];
+	TRACE_FUN(ft_t_any);
+
+	out[0] = FDC_SENSEI;
+	TRACE_CATCH(fdc_issue_command(out, 1, in, 2),);
+	*st0 = in[0];
+	*current_cylinder = in[1];
+	TRACE_EXIT 0;
+}
+
+/*      step to track
+ */
+int fdc_seek(int track)
+{
+	__u8 out[3];
+	int st0, pcn;
+#ifdef TESTING
+	unsigned int time;
+#endif
+	TRACE_FUN(ft_t_any);
+
+	out[0] = FDC_SEEK;
+	out[1] = ft_drive_sel;
+	out[2] = track;
+#ifdef TESTING
+	time = ftape_timestamp();
+#endif
+	/*  We really need this command to work !
+	 */
+	ft_seek_completed = 0;
+	TRACE_CATCH(fdc_command(out, 3),
+		    fdc_reset();
+		    TRACE(ft_t_noise, "destination was: %d, resetting FDC...",
+			  track));
+	/*    Handle interrupts until ft_seek_completed or timeout.
+	 */
+	for (;;) {
+		TRACE_CATCH(fdc_interrupt_wait(2 * FT_SECOND),);
+		if (ft_seek_completed) {
+			TRACE_CATCH(fdc_sense_interrupt_status(&st0, &pcn),);
+			if ((st0 & ST0_SEEK_END) == 0) {
+				TRACE_ABORT(-EIO, ft_t_err,
+				      "no seek-end after seek completion !??");
+			}
+			break;
+		}
+	}
+#ifdef TESTING
+	time = ftape_timediff(time, ftape_timestamp()) / abs(track - ftape_current_cylinder);
+	if ((time < 900 || time > 3100) && abs(track - ftape_current_cylinder) > 5) {
+		TRACE(ft_t_warn, "Wrong FDC STEP interval: %d usecs (%d)",
+                         time, track - ftape_current_cylinder);
+	}
+#endif
+	/*    Verify whether we issued the right tape command.
+	 */
+	/* Verify that we seek to the proper track. */
+	if (pcn != track) {
+		TRACE_ABORT(-EIO, ft_t_err, "bad seek..");
+	}
+	ftape_current_cylinder = track;
+	TRACE_EXIT 0;
+}
+
+static int perpend_mode; /* set if fdc is in perpendicular mode */
+
+static int perpend_off(void)
+{
+ 	__u8 perpend[] = {FDC_PERPEND, 0x00};
+	TRACE_FUN(ft_t_any);
+	
+	if (perpend_mode) {
+		/* Turn off perpendicular mode */
+		perpend[1] = 0x80;
+		TRACE_CATCH(fdc_command(perpend, 2),
+			    TRACE(ft_t_err,"Perpendicular mode exit failed!"));
+		perpend_mode = 0;
+	}
+	TRACE_EXIT 0;
+}
+
+static int handle_perpend(int segment_id)
+{
+ 	__u8 perpend[] = {FDC_PERPEND, 0x00};
+	TRACE_FUN(ft_t_any);
+
+	/* When writing QIC-3020 tapes, turn on perpendicular mode
+	 * if tape is moving in forward direction (even tracks).
+	 */
+	if (ft_qic_std == QIC_TAPE_QIC3020 &&
+	    ((segment_id / ft_segments_per_track) & 1) == 0) {
+/*  FIXME: some i82077 seem to support perpendicular mode as
+ *  well. 
+ */
+#if 0
+		if (fdc.type < i82077AA) {}
+#else
+		if (fdc.type < i82077 && ft_data_rate < 1000) {
+#endif
+			/*  fdc does not support perpendicular mode: complain 
+			 */
+			TRACE_ABORT(-EIO, ft_t_err,
+				    "Your FDC does not support QIC-3020.");
+		}
+		perpend[1] = 0x03 /* 0x83 + (0x4 << ft_drive_sel) */ ;
+		TRACE_CATCH(fdc_command(perpend, 2),
+			   TRACE(ft_t_err,"Perpendicular mode entry failed!"));
+		TRACE(ft_t_flow, "Perpendicular mode set");
+		perpend_mode = 1;
+		TRACE_EXIT 0;
+	}
+	TRACE_EXIT perpend_off();
+}
+
+static inline void fdc_setup_dma(char mode,
+				 volatile void *addr, unsigned int count)
+{
+	/* Program the DMA controller.
+	 */
+	disable_dma(fdc.dma);
+	clear_dma_ff(fdc.dma);
+	set_dma_mode(fdc.dma, mode);
+	set_dma_addr(fdc.dma, virt_to_bus((void*)addr));
+	set_dma_count(fdc.dma, count);
+	enable_dma(fdc.dma);
+}
+
+/*  Setup fdc and dma for formatting the next segment
+ */
+int fdc_setup_formatting(buffer_struct * buff)
+{
+	unsigned long flags;
+	__u8 out[6] = {
+		FDC_FORMAT, 0x00, 3, 4 * FT_SECTORS_PER_SEGMENT, 0x00, 0x6b
+	};
+	TRACE_FUN(ft_t_any);
+	
+	TRACE_CATCH(handle_perpend(buff->segment_id),);
+	/* Program the DMA controller.
+	 */
+        TRACE(ft_t_fdc_dma,
+	      "phys. addr. = %lx", virt_to_bus((void*) buff->ptr));
+	spin_lock_irqsave(&fdc_io_lock, flags);
+	fdc_setup_dma(DMA_MODE_WRITE, buff->ptr, FT_SECTORS_PER_SEGMENT * 4);
+	/* Issue FDC command to start reading/writing.
+	 */
+	out[1] = ft_drive_sel;
+	out[4] = buff->gap3;
+	TRACE_CATCH(fdc_setup_error = fdc_command(out, sizeof(out)),
+		    restore_flags(flags); fdc_mode = fdc_idle);
+	spin_unlock_irqrestore(&fdc_io_lock, flags);
+	TRACE_EXIT 0;
+}
+
+
+/*      Setup Floppy Disk Controller and DMA to read or write the next cluster
+ *      of good sectors from or to the current segment.
+ */
+int fdc_setup_read_write(buffer_struct * buff, __u8 operation)
+{
+	unsigned long flags;
+	__u8 out[9];
+	int dma_mode;
+	TRACE_FUN(ft_t_any);
+
+	switch(operation) {
+	case FDC_VERIFY:
+		if (fdc.type < i82077) {
+			operation = FDC_READ;
+		}
+	case FDC_READ:
+	case FDC_READ_DELETED:
+		dma_mode = DMA_MODE_READ;
+		TRACE(ft_t_fdc_dma, "xfer %d sectors to 0x%p",
+		      buff->sector_count, buff->ptr);
+		TRACE_CATCH(perpend_off(),);
+		break;
+	case FDC_WRITE_DELETED:
+		TRACE(ft_t_noise, "deleting segment %d", buff->segment_id);
+	case FDC_WRITE:
+		dma_mode = DMA_MODE_WRITE;
+		/* When writing QIC-3020 tapes, turn on perpendicular mode
+		 * if tape is moving in forward direction (even tracks).
+		 */
+		TRACE_CATCH(handle_perpend(buff->segment_id),);
+		TRACE(ft_t_fdc_dma, "xfer %d sectors from 0x%p",
+		      buff->sector_count, buff->ptr);
+		break;
+	default:
+		TRACE_ABORT(-EIO,
+			    ft_t_bug, "bug: invalid operation parameter");
+	}
+	TRACE(ft_t_fdc_dma, "phys. addr. = %lx",virt_to_bus((void*)buff->ptr));
+	spin_lock_irqsave(&fdc_io_lock, flags);
+	if (operation != FDC_VERIFY) {
+		fdc_setup_dma(dma_mode, buff->ptr,
+			      FT_SECTOR_SIZE * buff->sector_count);
+	}
+	/* Issue FDC command to start reading/writing.
+	 */
+	out[0] = operation;
+	out[1] = ft_drive_sel;
+	out[2] = buff->cyl;
+	out[3] = buff->head;
+	out[4] = buff->sect + buff->sector_offset;
+	out[5] = 3;		/* Sector size of 1K. */
+	out[6] = out[4] + buff->sector_count - 1;	/* last sector */
+	out[7] = 109;		/* Gap length. */
+	out[8] = 0xff;		/* No limit to transfer size. */
+	TRACE(ft_t_fdc_dma, "C: 0x%02x, H: 0x%02x, R: 0x%02x, cnt: 0x%02x",
+		out[2], out[3], out[4], out[6] - out[4] + 1);
+	spin_unlock_irqrestore(&fdc_io_lock, flags);
+	TRACE_CATCH(fdc_setup_error = fdc_command(out, 9),fdc_mode = fdc_idle);
+	TRACE_EXIT 0;
+}
+
+int fdc_fifo_threshold(__u8 threshold,
+		       int *fifo_state, int *lock_state, int *fifo_thr)
+{
+	const __u8 cmd0[] = {FDC_DUMPREGS};
+	__u8 cmd1[] = {FDC_CONFIGURE, 0, (0x0f & (threshold - 1)), 0};
+	const __u8 cmd2[] = {FDC_LOCK};
+	const __u8 cmd3[] = {FDC_UNLOCK};
+	__u8 reg[10];
+	__u8 stat;
+	int i;
+	int result;
+	TRACE_FUN(ft_t_any);
+
+	if (CLK_48MHZ && fdc.type >= i82078) {
+		cmd1[0] |= FDC_CLK48_BIT;
+	}
+	/*  Dump fdc internal registers for examination
+	 */
+	TRACE_CATCH(fdc_command(cmd0, NR_ITEMS(cmd0)),
+		    TRACE(ft_t_warn, "dumpreg cmd failed, fifo unchanged"));
+	/*  Now read fdc internal registers from fifo
+	 */
+	for (i = 0; i < (int)NR_ITEMS(reg); ++i) {
+		fdc_read(&reg[i]);
+		TRACE(ft_t_fdc_dma, "Register %d = 0x%02x", i, reg[i]);
+	}
+	if (fifo_state && lock_state && fifo_thr) {
+		*fifo_state = (reg[8] & 0x20) == 0;
+		*lock_state = reg[7] & 0x80;
+		*fifo_thr = 1 + (reg[8] & 0x0f);
+	}
+	TRACE(ft_t_noise,
+	      "original fifo state: %sabled, threshold %d, %slocked",
+	      ((reg[8] & 0x20) == 0) ? "en" : "dis",
+	      1 + (reg[8] & 0x0f), (reg[7] & 0x80) ? "" : "not ");
+	/*  If fdc is already locked, unlock it first ! */
+	if (reg[7] & 0x80) {
+		fdc_ready_wait(100);
+		TRACE_CATCH(fdc_issue_command(cmd3, NR_ITEMS(cmd3), &stat, 1),
+			    TRACE(ft_t_bug, "FDC unlock command failed, "
+				  "configuration unchanged"));
+	}
+	fdc_fifo_locked = 0;
+	/*  Enable fifo and set threshold at xx bytes to allow a
+	 *  reasonably large latency and reduce number of dma bursts.
+	 */
+	fdc_ready_wait(100);
+	if ((result = fdc_command(cmd1, NR_ITEMS(cmd1))) < 0) {
+		TRACE(ft_t_bug, "configure cmd failed, fifo unchanged");
+	}
+	/*  Now lock configuration so reset will not change it
+	 */
+        if(fdc_issue_command(cmd2, NR_ITEMS(cmd2), &stat, 1) < 0 ||
+	   stat != 0x10) {
+		TRACE_ABORT(-EIO, ft_t_bug,
+			    "FDC lock command failed, stat = 0x%02x", stat);
+	}
+	fdc_fifo_locked = 1;
+	TRACE_EXIT result;
+}
+
+static int fdc_fifo_enable(void)
+{
+	TRACE_FUN(ft_t_any);
+
+	if (fdc_fifo_locked) {
+		TRACE_ABORT(0, ft_t_warn, "Fifo not enabled because locked");
+	}
+	TRACE_CATCH(fdc_fifo_threshold(ft_fdc_threshold /* bytes */,
+				       &fdc_fifo_state,
+				       &fdc_lock_state,
+				       &fdc_fifo_thr),);
+	TRACE_CATCH(fdc_fifo_threshold(ft_fdc_threshold /* bytes */,
+				       NULL, NULL, NULL),);
+	TRACE_EXIT 0;
+}
+
+/*   Determine fd controller type 
+ */
+static __u8 fdc_save_state[2];
+
+static int fdc_probe(void)
+{
+	__u8 cmd[1];
+	__u8 stat[16]; /* must be able to hold dumpregs & save results */
+	int i;
+	TRACE_FUN(ft_t_any);
+
+	/*  Try to find out what kind of fd controller we have to deal with
+	 *  Scheme borrowed from floppy driver:
+	 *  first try if FDC_DUMPREGS command works
+	 *  (this indicates that we have a 82072 or better)
+	 *  then try the FDC_VERSION command (82072 doesn't support this)
+	 *  then try the FDC_UNLOCK command (some older 82077's don't support this)
+	 *  then try the FDC_PARTID command (82078's support this)
+	 */
+	cmd[0] = FDC_DUMPREGS;
+	if (fdc_issue_command(cmd, 1, stat, 1) != 0) {
+		TRACE_ABORT(no_fdc, ft_t_bug, "No FDC found");
+	}
+	if (stat[0] == 0x80) {
+		/* invalid command: must be pre 82072 */
+		TRACE_ABORT(i8272,
+			    ft_t_warn, "Type 8272A/765A compatible FDC found");
+	}
+	fdc_result(&stat[1], 9);
+	fdc_save_state[0] = stat[7];
+	fdc_save_state[1] = stat[8];
+	cmd[0] = FDC_VERSION;
+	if (fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] == 0x80) {
+		TRACE_ABORT(i8272, ft_t_warn, "Type 82072 FDC found");
+	}
+	if (*stat != 0x90) {
+		TRACE_ABORT(i8272, ft_t_warn, "Unknown FDC found");
+	}
+	cmd[0] = FDC_UNLOCK;
+	if(fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] != 0x00) {
+		TRACE_ABORT(i8272, ft_t_warn,
+			    "Type pre-1991 82077 FDC found, "
+			    "treating it like a 82072");
+	}
+	if (fdc_save_state[0] & 0x80) { /* was locked */
+		cmd[0] = FDC_LOCK; /* restore lock */
+		(void)fdc_issue_command(cmd, 1, stat, 1);
+		TRACE(ft_t_warn, "FDC is already locked");
+	}
+	/* Test for a i82078 FDC */
+	cmd[0] = FDC_PARTID;
+	if (fdc_issue_command(cmd, 1, stat, 1) < 0 || stat[0] == 0x80) {
+		/* invalid command: not a i82078xx type FDC */
+		for (i = 0; i < 4; ++i) {
+			outb_p(i, fdc.tdr);
+			if ((inb_p(fdc.tdr) & 0x03) != i) {
+				TRACE_ABORT(i82077,
+					    ft_t_warn, "Type 82077 FDC found");
+			}
+		}
+		TRACE_ABORT(i82077AA, ft_t_warn, "Type 82077AA FDC found");
+	}
+	/* FDC_PARTID cmd succeeded */
+	switch (stat[0] >> 5) {
+	case 0x0:
+		/* i82078SL or i82078-1.  The SL part cannot run at
+		 * 2Mbps (the SL and -1 dies are identical; they are
+		 * speed graded after production, according to Intel).
+		 * Some SL's can be detected by doing a SAVE cmd and
+		 * look at bit 7 of the first byte (the SEL3V# bit).
+		 * If it is 0, the part runs off 3Volts, and hence it
+		 * is a SL.
+		 */
+		cmd[0] = FDC_SAVE;
+		if(fdc_issue_command(cmd, 1, stat, 16) < 0) {
+			TRACE(ft_t_err, "FDC_SAVE failed. Dunno why");
+			/* guess we better claim the fdc to be a i82078 */
+			TRACE_ABORT(i82078,
+				    ft_t_warn,
+				    "Type i82078 FDC (i suppose) found");
+		}
+		if ((stat[0] & FDC_SEL3V_BIT)) {
+			/* fdc running off 5Volts; Pray that it's a i82078-1
+			 */
+			TRACE_ABORT(i82078_1, ft_t_warn,
+				  "Type i82078-1 or 5Volt i82078SL FDC found");
+		}
+		TRACE_ABORT(i82078, ft_t_warn,
+			    "Type 3Volt i82078SL FDC (1Mbps) found");
+	case 0x1:
+	case 0x2: /* S82078B  */
+		/* The '78B  isn't '78 compatible.  Detect it as a '77AA */
+		TRACE_ABORT(i82077AA, ft_t_warn, "Type i82077AA FDC found");
+	case 0x3: /* NSC PC8744 core; used in several super-IO chips */
+		TRACE_ABORT(i82077AA,
+			    ft_t_warn, "Type 82077AA compatible FDC found");
+	default:
+		TRACE(ft_t_warn, "A previously undetected FDC found");
+		TRACE_ABORT(i82077AA, ft_t_warn,
+			  "Treating it as a 82077AA. Please report partid= %d",
+			    stat[0]);
+	} /* switch(stat[ 0] >> 5) */
+	TRACE_EXIT no_fdc;
+}
+
+static int fdc_request_regions(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (ft_mach2 || ft_probe_fc10) {
+		if (!request_region(fdc.sra, 8, "fdc (ft)")) {
+#ifndef BROKEN_FLOPPY_DRIVER
+			TRACE_EXIT -EBUSY;
+#else
+			TRACE(ft_t_warn,
+"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra);
+#endif
+		}
+	} else {
+		if (!request_region(fdc.sra, 6, "fdc (ft)")) {
+#ifndef BROKEN_FLOPPY_DRIVER
+			TRACE_EXIT -EBUSY;
+#else
+			TRACE(ft_t_warn,
+"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra);
+#endif
+		}
+		if (!request_region(fdc.sra + 7, 1, "fdc (ft)")) {
+#ifndef BROKEN_FLOPPY_DRIVER
+			release_region(fdc.sra, 6);
+			TRACE_EXIT -EBUSY;
+#else
+			TRACE(ft_t_warn,
+"address 0x%03x occupied (by floppy driver?), using it anyway", fdc.sra + 7);
+#endif
+		}
+	}
+	TRACE_EXIT 0;
+}
+
+void fdc_release_regions(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (fdc.sra != 0) {
+		if (fdc.dor2 != 0) {
+			release_region(fdc.sra, 8);
+		} else {
+			release_region(fdc.sra, 6);
+			release_region(fdc.dir, 1);
+		}
+	}
+	TRACE_EXIT;
+}
+
+static int fdc_config_regs(unsigned int fdc_base, 
+			   unsigned int fdc_irq, 
+			   unsigned int fdc_dma)
+{
+	TRACE_FUN(ft_t_flow);
+
+	fdc.irq = fdc_irq;
+	fdc.dma = fdc_dma;
+	fdc.sra = fdc_base;
+	fdc.srb = fdc_base + 1;
+	fdc.dor = fdc_base + 2;
+	fdc.tdr = fdc_base + 3;
+	fdc.msr = fdc.dsr = fdc_base + 4;
+	fdc.fifo = fdc_base + 5;
+	fdc.dir = fdc.ccr = fdc_base + 7;
+	fdc.dor2 = (ft_mach2 || ft_probe_fc10) ? fdc_base + 6 : 0;
+	TRACE_CATCH(fdc_request_regions(), fdc.sra = 0);
+	TRACE_EXIT 0;
+}
+
+static int fdc_config(void)
+{
+	static int already_done;
+	TRACE_FUN(ft_t_any);
+
+	if (already_done) {
+		TRACE_CATCH(fdc_request_regions(),);
+		*(fdc.hook) = fdc_isr;	/* hook our handler in */
+		TRACE_EXIT 0;
+	}
+	if (ft_probe_fc10) {
+		int fc_type;
+		
+		TRACE_CATCH(fdc_config_regs(ft_fdc_base,
+					    ft_fdc_irq, ft_fdc_dma),);
+		fc_type = fc10_enable();
+		if (fc_type != 0) {
+			TRACE(ft_t_warn, "FC-%c0 controller found", '0' + fc_type);
+			fdc.type = fc10;
+			fdc.hook = &do_ftape;
+			*(fdc.hook) = fdc_isr;	/* hook our handler in */
+			already_done = 1;
+			TRACE_EXIT 0;
+		} else {
+			TRACE(ft_t_warn, "FC-10/20 controller not found");
+			fdc_release_regions();
+			fdc.type = no_fdc;
+			ft_probe_fc10 = 0;
+			ft_fdc_base   = 0x3f0;
+			ft_fdc_irq    = 6;
+			ft_fdc_dma    = 2;
+		}
+	}
+	TRACE(ft_t_warn, "fdc base: 0x%x, irq: %d, dma: %d", 
+	      ft_fdc_base, ft_fdc_irq, ft_fdc_dma);
+	TRACE_CATCH(fdc_config_regs(ft_fdc_base, ft_fdc_irq, ft_fdc_dma),);
+	fdc.hook = &do_ftape;
+	*(fdc.hook) = fdc_isr;	/* hook our handler in */
+	already_done = 1;
+	TRACE_EXIT 0;
+}
+
+static irqreturn_t ftape_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	void (*handler) (void) = *fdc.hook;
+	int handled = 0;
+	TRACE_FUN(ft_t_any);
+
+	*fdc.hook = NULL;
+	if (handler) {
+		handled = 1;
+		handler();
+	} else {
+		TRACE(ft_t_bug, "Unexpected ftape interrupt");
+	}
+	TRACE_EXIT IRQ_RETVAL(handled);
+}
+
+static int fdc_grab_irq_and_dma(void)
+{
+	TRACE_FUN(ft_t_any);
+
+	if (fdc.hook == &do_ftape) {
+		/*  Get fast interrupt handler.
+		 */
+		if (request_irq(fdc.irq, ftape_interrupt,
+				SA_INTERRUPT, "ft", ftape_id)) {
+			TRACE_ABORT(-EIO, ft_t_bug,
+				    "Unable to grab IRQ%d for ftape driver",
+				    fdc.irq);
+		}
+		if (request_dma(fdc.dma, ftape_id)) {
+			free_irq(fdc.irq, ftape_id);
+			TRACE_ABORT(-EIO, ft_t_bug,
+			      "Unable to grab DMA%d for ftape driver",
+			      fdc.dma);
+		}
+	}
+	if (ft_fdc_base != 0x3f0 && (ft_fdc_dma == 2 || ft_fdc_irq == 6)) {
+		/* Using same dma channel or irq as standard fdc, need
+		 * to disable the dma-gate on the std fdc. This
+		 * couldn't be done in the floppy driver as some
+		 * laptops are using the dma-gate to enter a low power
+		 * or even suspended state :-(
+		 */
+		outb_p(FDC_RESET_NOT, 0x3f2);
+		TRACE(ft_t_noise, "DMA-gate on standard fdc disabled");
+	}
+	TRACE_EXIT 0;
+}
+
+int fdc_release_irq_and_dma(void)
+{
+	TRACE_FUN(ft_t_any);
+
+	if (fdc.hook == &do_ftape) {
+		disable_dma(fdc.dma);	/* just in case... */
+		free_dma(fdc.dma);
+		free_irq(fdc.irq, ftape_id);
+	}
+	if (ft_fdc_base != 0x3f0 && (ft_fdc_dma == 2 || ft_fdc_irq == 6)) {
+		/* Using same dma channel as standard fdc, need to
+		 * disable the dma-gate on the std fdc. This couldn't
+		 * be done in the floppy driver as some laptops are
+		 * using the dma-gate to enter a low power or even
+		 * suspended state :-(
+		 */
+		outb_p(FDC_RESET_NOT | FDC_DMA_MODE, 0x3f2);
+		TRACE(ft_t_noise, "DMA-gate on standard fdc enabled again");
+	}
+	TRACE_EXIT 0;
+}
+
+int fdc_init(void)
+{
+	TRACE_FUN(ft_t_any);
+
+	/* find a FDC to use */
+	TRACE_CATCH(fdc_config(),);
+	TRACE_CATCH(fdc_grab_irq_and_dma(), fdc_release_regions());
+	ftape_motor = 0;
+	fdc_catch_stray_interrupts(0);	/* clear number of awainted
+					 * stray interrupte 
+					 */
+	fdc_catch_stray_interrupts(1);	/* one always comes (?) */
+	TRACE(ft_t_flow, "resetting fdc");
+	fdc_set_seek_rate(2);		/* use nominal QIC step rate */
+	fdc_reset();			/* init fdc & clear track counters */
+	if (fdc.type == no_fdc) {	/* no FC-10 or FC-20 found */
+		fdc.type = fdc_probe();
+		fdc_reset();		/* update with new knowledge */
+	}
+	if (fdc.type == no_fdc) {
+		fdc_release_irq_and_dma();
+		fdc_release_regions();
+		TRACE_EXIT -ENXIO;
+	}
+	if (fdc.type >= i82077) {
+		if (fdc_fifo_enable() < 0) {
+			TRACE(ft_t_warn, "couldn't enable fdc fifo !");
+		} else {
+			TRACE(ft_t_flow, "fdc fifo enabled and locked");
+		}
+	}
+	TRACE_EXIT 0;
+}
diff --git a/drivers/char/ftape/lowlevel/fdc-io.h b/drivers/char/ftape/lowlevel/fdc-io.h
new file mode 100644
index 0000000..7ec3c72
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fdc-io.h
@@ -0,0 +1,252 @@
+#ifndef _FDC_IO_H
+#define _FDC_IO_H
+
+/*
+ *    Copyright (C) 1993-1996 Bas Laarhoven,
+ *              (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-io.h,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/05 19:18:06 $
+ *
+ *      This file contains the declarations for the low level
+ *      functions that communicate with the floppy disk controller,
+ *      for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for
+ *      Linux.
+ */
+
+#include <linux/fdreg.h>
+
+#include "../lowlevel/ftape-bsm.h"
+
+#define FDC_SK_BIT      (0x20)
+#define FDC_MT_BIT      (0x80)
+
+#define FDC_READ        (FD_READ & ~(FDC_SK_BIT | FDC_MT_BIT))
+#define FDC_WRITE       (FD_WRITE & ~FDC_MT_BIT)
+#define FDC_READ_DELETED  (0x4c)
+#define FDC_WRITE_DELETED (0x49)
+#define FDC_VERIFY        (0x56)
+#define FDC_READID      (0x4a)
+#define FDC_SENSED      (0x04)
+#define FDC_SENSEI      (FD_SENSEI)
+#define FDC_FORMAT      (FD_FORMAT)
+#define FDC_RECAL       (FD_RECALIBRATE)
+#define FDC_SEEK        (FD_SEEK)
+#define FDC_SPECIFY     (FD_SPECIFY)
+#define FDC_RECALIBR    (FD_RECALIBRATE)
+#define FDC_VERSION     (FD_VERSION)
+#define FDC_PERPEND     (FD_PERPENDICULAR)
+#define FDC_DUMPREGS    (FD_DUMPREGS)
+#define FDC_LOCK        (FD_LOCK)
+#define FDC_UNLOCK      (FD_UNLOCK)
+#define FDC_CONFIGURE   (FD_CONFIGURE)
+#define FDC_DRIVE_SPEC  (0x8e)	/* i82078 has this (any others?) */
+#define FDC_PARTID      (0x18)	/* i82078 has this */
+#define FDC_SAVE        (0x2e)	/* i82078 has this (any others?) */
+#define FDC_RESTORE     (0x4e)	/* i82078 has this (any others?) */
+
+#define FDC_STATUS_MASK (STATUS_BUSY | STATUS_DMA | STATUS_DIR | STATUS_READY)
+#define FDC_DATA_READY  (STATUS_READY)
+#define FDC_DATA_OUTPUT (STATUS_DIR)
+#define FDC_DATA_READY_MASK (STATUS_READY | STATUS_DIR)
+#define FDC_DATA_OUT_READY  (STATUS_READY | STATUS_DIR)
+#define FDC_DATA_IN_READY   (STATUS_READY)
+#define FDC_BUSY        (STATUS_BUSY)
+#define FDC_CLK48_BIT   (0x80)
+#define FDC_SEL3V_BIT   (0x40)
+
+#define ST0_INT_MASK    (ST0_INTR)
+#define FDC_INT_NORMAL  (ST0_INTR & 0x00)
+#define FDC_INT_ABNORMAL (ST0_INTR & 0x40)
+#define FDC_INT_INVALID (ST0_INTR & 0x80)
+#define FDC_INT_READYCH (ST0_INTR & 0xC0)
+#define ST0_SEEK_END    (ST0_SE)
+#define ST3_TRACK_0     (ST3_TZ)
+
+#define FDC_RESET_NOT   (0x04)
+#define FDC_DMA_MODE    (0x08)
+#define FDC_MOTOR_0     (0x10)
+#define FDC_MOTOR_1     (0x20)
+
+typedef struct {
+	void (**hook) (void);	/* our wedge into the isr */
+	enum {
+		no_fdc, i8272, i82077, i82077AA, fc10,
+		i82078, i82078_1
+	} type;			/* FDC type */
+	unsigned int irq; /* FDC irq nr */
+	unsigned int dma; /* FDC dma channel nr */
+	__u16 sra;	  /* Status register A (PS/2 only) */
+	__u16 srb;	  /* Status register B (PS/2 only) */
+	__u16 dor;	  /* Digital output register */
+	__u16 tdr;	  /* Tape Drive Register (82077SL-1 &
+			     82078 only) */
+	__u16 msr;	  /* Main Status Register */
+	__u16 dsr;	  /* Datarate Select Register (8207x only) */
+	__u16 fifo;	  /* Data register / Fifo on 8207x */
+	__u16 dir;	  /* Digital Input Register */
+	__u16 ccr;	  /* Configuration Control Register */
+	__u16 dor2;	  /* Alternate dor on MACH-2 controller,
+			     also used with FC-10, meaning unknown */
+} fdc_config_info;
+
+typedef enum {
+	fdc_data_rate_250  = 2,
+	fdc_data_rate_300  = 1,	/* any fdc in default configuration */
+	fdc_data_rate_500  = 0,
+	fdc_data_rate_1000 = 3,
+	fdc_data_rate_2000 = 1,	/* i82078-1: when using Data Rate Table #2 */
+} fdc_data_rate_type;
+
+typedef enum {
+	fdc_idle          = 0,
+	fdc_reading_data  = FDC_READ,
+	fdc_seeking       = FDC_SEEK,
+	fdc_writing_data  = FDC_WRITE,
+	fdc_deleting      = FDC_WRITE_DELETED,
+	fdc_reading_id    = FDC_READID,
+	fdc_recalibrating = FDC_RECAL,
+	fdc_formatting    = FDC_FORMAT,
+	fdc_verifying     = FDC_VERIFY
+} fdc_mode_enum;
+
+typedef enum {
+	waiting = 0,
+	reading,
+	writing,
+	formatting,
+	verifying,
+	deleting,
+	done,
+	error,
+	mmapped,
+} buffer_state_enum;
+
+typedef struct {
+	__u8 *address;
+	volatile buffer_state_enum status;
+	volatile __u8 *ptr;
+	volatile unsigned int bytes;
+	volatile unsigned int segment_id;
+
+	/* bitmap for remainder of segment not yet handled.
+	 * one bit set for each bad sector that must be skipped.
+	 */
+	volatile SectorMap bad_sector_map;
+
+	/* bitmap with bad data blocks in data buffer.
+	 * the errors in this map may be retried.
+	 */
+	volatile SectorMap soft_error_map;
+
+	/* bitmap with bad data blocks in data buffer
+	 * the errors in this map may not be retried.
+	 */
+	volatile SectorMap hard_error_map;
+
+	/* retry counter for soft errors.
+	 */
+	volatile int retry;
+
+	/* sectors to skip on retry ???
+	 */
+	volatile unsigned int skip;
+
+	/* nr of data blocks in data buffer
+	 */
+	volatile unsigned int data_offset;
+
+	/* offset in segment for first sector to be handled.
+	 */
+	volatile unsigned int sector_offset;
+
+	/* size of cluster of good sectors to be handled.
+	 */
+	volatile unsigned int sector_count;
+
+	/* size of remaining part of segment to be handled.
+	 */
+	volatile unsigned int remaining;
+
+	/* points to next segment (contiguous) to be handled,
+	 * or is zero if no read-ahead is allowed.
+	 */
+	volatile unsigned int next_segment;
+
+	/* flag being set if deleted data was read.
+	 */
+	volatile int deleted;
+
+	/* floppy coordinates of first sector in segment */
+	volatile __u8 head;
+	volatile __u8 cyl;
+	volatile __u8 sect;
+
+	/* gap to use when formatting */
+	__u8 gap3;
+	/* flag set when buffer is mmaped */
+	int mmapped;
+} buffer_struct;
+
+/*
+ *      fdc-io.c defined public variables
+ */
+extern volatile fdc_mode_enum fdc_mode;
+extern int fdc_setup_error;	/* outdated ??? */
+extern wait_queue_head_t ftape_wait_intr;
+extern volatile int ftape_current_cylinder; /* track nr FDC thinks we're on */
+extern volatile __u8 fdc_head;	/* FDC head */
+extern volatile __u8 fdc_cyl;	/* FDC track */
+extern volatile __u8 fdc_sect;	/* FDC sector */
+extern fdc_config_info fdc;	/* FDC hardware configuration */
+
+extern unsigned int ft_fdc_base;
+extern unsigned int ft_fdc_irq;
+extern unsigned int ft_fdc_dma;
+extern unsigned int ft_fdc_threshold;
+extern unsigned int ft_fdc_rate_limit;
+extern int ft_probe_fc10;
+extern int ft_mach2;
+/*
+ *      fdc-io.c defined public functions
+ */
+extern void fdc_catch_stray_interrupts(int count);
+extern int fdc_ready_wait(unsigned int timeout);
+extern int fdc_command(const __u8 * cmd_data, int cmd_len);
+extern int fdc_result(__u8 * res_data, int res_len);
+extern int fdc_interrupt_wait(unsigned int time);
+extern int fdc_seek(int track);
+extern int fdc_sense_drive_status(int *st3);
+extern void fdc_motor(int motor);
+extern void fdc_reset(void);
+extern void fdc_disable(void);
+extern int fdc_fifo_threshold(__u8 threshold,
+			      int *fifo_state, int *lock_state, int *fifo_thr);
+extern void fdc_wait_calibrate(void);
+extern int fdc_sense_interrupt_status(int *st0, int *current_cylinder);
+extern void fdc_save_drive_specs(void);
+extern void fdc_restore_drive_specs(void);
+extern int fdc_set_data_rate(int rate);
+extern void fdc_set_write_precomp(int precomp);
+extern int fdc_release_irq_and_dma(void);
+extern void fdc_release_regions(void);
+extern int fdc_init(void);
+extern int fdc_setup_read_write(buffer_struct * buff, __u8 operation);
+extern int fdc_setup_formatting(buffer_struct * buff);
+#endif
diff --git a/drivers/char/ftape/lowlevel/fdc-isr.c b/drivers/char/ftape/lowlevel/fdc-isr.c
new file mode 100644
index 0000000..ad2bc73
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fdc-isr.c
@@ -0,0 +1,1170 @@
+/*
+ *      Copyright (C) 1994-1996 Bas Laarhoven,
+ *                (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-isr.c,v $
+ * $Revision: 1.9 $
+ * $Date: 1997/10/17 23:01:53 $
+ *
+ *      This file contains the interrupt service routine and
+ *      associated code for the QIC-40/80/3010/3020 floppy-tape driver
+ *      "ftape" for Linux.
+ */
+
+#include <asm/io.h>
+#include <asm/dma.h>
+
+#define volatile		/* */
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-isr.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-calibr.h"
+#include "../lowlevel/ftape-bsm.h"
+
+/*      Global vars.
+ */
+volatile int ft_expected_stray_interrupts;
+volatile int ft_interrupt_seen;
+volatile int ft_seek_completed;
+volatile int ft_hide_interrupt;
+/*      Local vars.
+ */
+typedef enum {
+	no_error = 0, id_am_error = 0x01, id_crc_error = 0x02,
+	data_am_error = 0x04, data_crc_error = 0x08,
+	no_data_error = 0x10, overrun_error = 0x20,
+} error_cause;
+static int stop_read_ahead;
+
+
+static void print_error_cause(int cause)
+{
+	TRACE_FUN(ft_t_any);
+
+	switch (cause) {
+	case no_data_error:
+		TRACE(ft_t_noise, "no data error");
+		break;
+	case id_am_error:
+		TRACE(ft_t_noise, "id am error");
+		break;
+	case id_crc_error:
+		TRACE(ft_t_noise, "id crc error");
+		break;
+	case data_am_error:
+		TRACE(ft_t_noise, "data am error");
+		break;
+	case data_crc_error:
+		TRACE(ft_t_noise, "data crc error");
+		break;
+	case overrun_error:
+		TRACE(ft_t_noise, "overrun error");
+		break;
+	default:;
+	}
+	TRACE_EXIT;
+}
+
+static char *fdc_mode_txt(fdc_mode_enum mode)
+{
+	switch (mode) {
+	case fdc_idle:
+		return "fdc_idle";
+	case fdc_reading_data:
+		return "fdc_reading_data";
+	case fdc_seeking:
+		return "fdc_seeking";
+	case fdc_writing_data:
+		return "fdc_writing_data";
+	case fdc_reading_id:
+		return "fdc_reading_id";
+	case fdc_recalibrating:
+		return "fdc_recalibrating";
+	case fdc_formatting:
+		return "fdc_formatting";
+	case fdc_verifying:
+		return "fdc_verifying";
+	default:
+		return "unknown";
+	}
+}
+
+static inline error_cause decode_irq_cause(fdc_mode_enum mode, __u8 st[])
+{
+	error_cause cause = no_error;
+	TRACE_FUN(ft_t_any);
+
+	/*  Valid st[], decode cause of interrupt.
+	 */
+	switch (st[0] & ST0_INT_MASK) {
+	case FDC_INT_NORMAL:
+		TRACE(ft_t_fdc_dma,"normal completion: %s",fdc_mode_txt(mode));
+		break;
+	case FDC_INT_ABNORMAL:
+		TRACE(ft_t_flow, "abnormal completion %s", fdc_mode_txt(mode));
+		TRACE(ft_t_fdc_dma, "ST0: 0x%02x, ST1: 0x%02x, ST2: 0x%02x",
+		      st[0], st[1], st[2]);
+		TRACE(ft_t_fdc_dma,
+		      "C: 0x%02x, H: 0x%02x, R: 0x%02x, N: 0x%02x",
+		      st[3], st[4], st[5], st[6]);
+		if (st[1] & 0x01) {
+			if (st[2] & 0x01) {
+				cause = data_am_error;
+			} else {
+				cause = id_am_error;
+			}
+		} else if (st[1] & 0x20) {
+			if (st[2] & 0x20) {
+				cause = data_crc_error;
+			} else {
+				cause = id_crc_error;
+			}
+		} else if (st[1] & 0x04) {
+			cause = no_data_error;
+		} else if (st[1] & 0x10) {
+			cause = overrun_error;
+		}
+		print_error_cause(cause);
+		break;
+	case FDC_INT_INVALID:
+		TRACE(ft_t_flow, "invalid completion %s", fdc_mode_txt(mode));
+		break;
+	case FDC_INT_READYCH:
+		if (st[0] & ST0_SEEK_END) {
+			TRACE(ft_t_flow, "drive poll completed");
+		} else {
+			TRACE(ft_t_flow, "ready change %s",fdc_mode_txt(mode));
+		}
+		break;
+	default:
+		break;
+	}
+	TRACE_EXIT cause;
+}
+
+static void update_history(error_cause cause)
+{
+	switch (cause) {
+	case id_am_error:
+		ft_history.id_am_errors++;
+		break;
+	case id_crc_error:
+		ft_history.id_crc_errors++;
+		break;
+	case data_am_error:
+		ft_history.data_am_errors++;
+		break;
+	case data_crc_error:
+		ft_history.data_crc_errors++;
+		break;
+	case overrun_error:
+		ft_history.overrun_errors++;
+		break;
+	case no_data_error:
+		ft_history.no_data_errors++;
+		break;
+	default:;
+	}
+}
+
+static void skip_bad_sector(buffer_struct * buff)
+{
+	TRACE_FUN(ft_t_any);
+
+	/*  Mark sector as soft error and skip it
+	 */
+	if (buff->remaining > 0) {
+		++buff->sector_offset;
+		++buff->data_offset;
+		--buff->remaining;
+		buff->ptr += FT_SECTOR_SIZE;
+		buff->bad_sector_map >>= 1;
+	} else {
+		/*  Hey, what is this????????????? C code: if we shift 
+		 *  more than 31 bits, we get no shift. That's bad!!!!!!
+		 */
+		++buff->sector_offset;	/* hack for error maps */
+		TRACE(ft_t_warn, "skipping last sector in segment");
+	}
+	TRACE_EXIT;
+}
+
+static void update_error_maps(buffer_struct * buff, unsigned int error_offset)
+{
+	int hard = 0;
+	TRACE_FUN(ft_t_any);
+
+	if (buff->retry < FT_SOFT_RETRIES) {
+		buff->soft_error_map |= (1 << error_offset);
+	} else {
+		buff->hard_error_map |= (1 << error_offset);
+		buff->soft_error_map &= ~buff->hard_error_map;
+		buff->retry = -1;  /* will be set to 0 in setup_segment */
+		hard = 1;
+	}
+	TRACE(ft_t_noise, "sector %d : %s error\n"
+	      KERN_INFO "hard map: 0x%08lx\n"
+	      KERN_INFO "soft map: 0x%08lx",
+	      FT_SECTOR(error_offset), hard ? "hard" : "soft",
+	      (long) buff->hard_error_map, (long) buff->soft_error_map);
+	TRACE_EXIT;
+}
+
+static void print_progress(buffer_struct *buff, error_cause cause)
+{
+	TRACE_FUN(ft_t_any);
+
+	switch (cause) {
+	case no_error: 
+		TRACE(ft_t_flow,"%d Sector(s) transferred", buff->sector_count);
+		break;
+	case no_data_error:
+		TRACE(ft_t_flow, "Sector %d not found",
+		      FT_SECTOR(buff->sector_offset));
+		break;
+	case overrun_error:
+		/*  got an overrun error on the first byte, must be a
+		 *  hardware problem
+		 */
+		TRACE(ft_t_bug,
+		      "Unexpected error: failing DMA or FDC controller ?");
+		break;
+	case data_crc_error:
+		TRACE(ft_t_flow, "Error in sector %d",
+		      FT_SECTOR(buff->sector_offset - 1));
+		break;
+	case id_crc_error:
+	case id_am_error:
+	case data_am_error:
+		TRACE(ft_t_flow, "Error in sector %d",
+		      FT_SECTOR(buff->sector_offset));
+		break;
+	default:
+		TRACE(ft_t_flow, "Unexpected error at sector %d",
+		      FT_SECTOR(buff->sector_offset));
+		break;
+	}
+	TRACE_EXIT;
+}
+
+/*
+ *  Error cause:   Amount xferred:  Action:
+ *
+ *  id_am_error         0           mark bad and skip
+ *  id_crc_error        0           mark bad and skip
+ *  data_am_error       0           mark bad and skip
+ *  data_crc_error    % 1024        mark bad and skip
+ *  no_data_error       0           retry on write
+ *                                  mark bad and skip on read
+ *  overrun_error  [ 0..all-1 ]     mark bad and skip
+ *  no_error           all          continue
+ */
+
+/*  the arg `sector' is returned by the fdc and tells us at which sector we
+ *  are positioned at (relative to starting sector of segment)
+ */
+static void determine_verify_progress(buffer_struct *buff,
+				      error_cause cause,
+				      __u8 sector)
+{
+	TRACE_FUN(ft_t_any);
+
+	if (cause == no_error && sector == 1) {
+		buff->sector_offset = FT_SECTORS_PER_SEGMENT;
+		buff->remaining     = 0;
+		if (TRACE_LEVEL >= ft_t_flow) {
+			print_progress(buff, cause);
+		}
+	} else {
+		buff->sector_offset = sector - buff->sect;
+		buff->remaining = FT_SECTORS_PER_SEGMENT - buff->sector_offset;
+		TRACE(ft_t_noise, "%ssector offset: 0x%04x", 
+		      (cause == no_error) ? "unexpected " : "",
+		      buff->sector_offset);
+		switch (cause) {
+		case overrun_error:
+			break;
+#if 0
+		case no_data_error:
+			buff->retry = FT_SOFT_RETRIES;
+			if (buff->hard_error_map    &&
+			    buff->sector_offset > 1 &&
+			    (buff->hard_error_map & 
+			     (1 << (buff->sector_offset-2)))) {
+				buff->retry --;
+			}
+			break;
+#endif
+		default:
+			buff->retry = FT_SOFT_RETRIES;
+			break;
+		}
+		if (TRACE_LEVEL >= ft_t_flow) {
+			print_progress(buff, cause);
+		}
+		/*  Sector_offset points to the problem area Now adjust
+		 *  sector_offset so it always points one past he failing
+		 *  sector. I.e. skip the bad sector.
+		 */
+		++buff->sector_offset;
+		--buff->remaining;
+		update_error_maps(buff, buff->sector_offset - 1);
+	}
+	TRACE_EXIT;
+}
+
+static void determine_progress(buffer_struct *buff,
+			       error_cause cause,
+			       __u8 sector)
+{
+	unsigned int dma_residue;
+	TRACE_FUN(ft_t_any);
+
+	/*  Using less preferred order of disable_dma and
+	 *  get_dma_residue because this seems to fail on at least one
+	 *  system if reversed!
+	 */
+	dma_residue = get_dma_residue(fdc.dma);
+	disable_dma(fdc.dma);
+	if (cause != no_error || dma_residue != 0) {
+		TRACE(ft_t_noise, "%sDMA residue: 0x%04x", 
+		      (cause == no_error) ? "unexpected " : "",
+		      dma_residue);
+		/* adjust to actual value: */
+		if (dma_residue == 0) {
+			/* this happens sometimes with overrun errors.
+			 * I don't know whether we could ignore the
+			 * overrun error. Play save.
+			 */
+			buff->sector_count --;
+		} else {
+			buff->sector_count -= ((dma_residue + 
+						(FT_SECTOR_SIZE - 1)) /
+					       FT_SECTOR_SIZE);
+		}
+	}
+	/*  Update var's influenced by the DMA operation.
+	 */
+	if (buff->sector_count > 0) {
+		buff->sector_offset   += buff->sector_count;
+		buff->data_offset     += buff->sector_count;
+		buff->ptr             += (buff->sector_count *
+					  FT_SECTOR_SIZE);
+		buff->remaining       -= buff->sector_count;
+		buff->bad_sector_map >>= buff->sector_count;
+	}
+	if (TRACE_LEVEL >= ft_t_flow) {
+		print_progress(buff, cause);
+	}
+	if (cause != no_error) {
+		if (buff->remaining == 0) {
+			TRACE(ft_t_warn, "foo?\n"
+			      KERN_INFO "count : %d\n"
+			      KERN_INFO "offset: %d\n"
+			      KERN_INFO "soft  : %08x\n"
+			      KERN_INFO "hard  : %08x",
+			      buff->sector_count,
+			      buff->sector_offset,
+			      buff->soft_error_map,
+			      buff->hard_error_map);
+		}
+		/*  Sector_offset points to the problem area, except if we got
+		 *  a data_crc_error. In that case it points one past the
+		 *  failing sector.
+		 *
+		 *  Now adjust sector_offset so it always points one past he
+		 *  failing sector. I.e. skip the bad sector.  
+		 */
+		if (cause != data_crc_error) {
+			skip_bad_sector(buff);
+		}
+		update_error_maps(buff, buff->sector_offset - 1);
+	}
+	TRACE_EXIT;
+}
+
+static int calc_steps(int cmd)
+{
+	if (ftape_current_cylinder > cmd) {
+		return ftape_current_cylinder - cmd;
+	} else {
+		return ftape_current_cylinder + cmd;
+	}
+}
+
+static void pause_tape(int retry, int mode)
+{
+	int result;
+	__u8 out[3] = {FDC_SEEK, ft_drive_sel, 0};
+	TRACE_FUN(ft_t_any);
+
+	/*  We'll use a raw seek command to get the tape to rewind and
+	 *  stop for a retry.
+	 */
+	++ft_history.rewinds;
+	if (qic117_cmds[ftape_current_command].non_intr) {
+		TRACE(ft_t_warn, "motion command may be issued too soon");
+	}
+	if (retry && (mode == fdc_reading_data ||
+		      mode == fdc_reading_id   ||
+		      mode == fdc_verifying)) {
+		ftape_current_command = QIC_MICRO_STEP_PAUSE;
+		ftape_might_be_off_track = 1;
+	} else {
+		ftape_current_command = QIC_PAUSE;
+	}
+	out[2] = calc_steps(ftape_current_command);
+	result = fdc_command(out, 3); /* issue QIC_117 command */
+	ftape_current_cylinder = out[ 2];
+	if (result < 0) {
+		TRACE(ft_t_noise, "qic-pause failed, status = %d", result);
+	} else {
+		ft_location.known  = 0;
+		ft_runner_status   = idle;
+		ft_hide_interrupt     = 1;
+		ftape_tape_running = 0;
+	}
+	TRACE_EXIT;
+}
+
+static void continue_xfer(buffer_struct *buff,
+			  fdc_mode_enum mode, 
+			  unsigned int skip)
+{
+	int write = 0;
+ 	TRACE_FUN(ft_t_any);
+
+	if (mode == fdc_writing_data || mode == fdc_deleting) {
+		write = 1;
+	}
+	/*  This part can be removed if it never happens
+	 */
+	if (skip > 0 &&
+	    (ft_runner_status != running ||
+	     (write && (buff->status != writing)) ||
+	     (!write && (buff->status != reading && 
+			 buff->status != verifying)))) {
+		TRACE(ft_t_err, "unexpected runner/buffer state %d/%d",
+		      ft_runner_status, buff->status);
+		buff->status = error;
+		/* finish this buffer: */
+		(void)ftape_next_buffer(ft_queue_head);
+		ft_runner_status = aborting;
+		fdc_mode         = fdc_idle;
+	} else if (buff->remaining > 0 && ftape_calc_next_cluster(buff) > 0) {
+		/*  still sectors left in current segment, continue
+		 *  with this segment
+		 */
+		if (fdc_setup_read_write(buff, mode) < 0) {
+			/* failed, abort operation
+			 */
+			buff->bytes = buff->ptr - buff->address;
+			buff->status = error;
+			/* finish this buffer: */
+			(void)ftape_next_buffer(ft_queue_head);
+			ft_runner_status = aborting;
+			fdc_mode         = fdc_idle;
+		}
+	} else {
+		/* current segment completed
+		 */
+		unsigned int last_segment = buff->segment_id;
+		int eot = ((last_segment + 1) % ft_segments_per_track) == 0;
+		unsigned int next = buff->next_segment;	/* 0 means stop ! */
+
+		buff->bytes = buff->ptr - buff->address;
+		buff->status = done;
+		buff = ftape_next_buffer(ft_queue_head);
+		if (eot) {
+			/*  finished last segment on current track,
+			 *  can't continue
+			 */
+			ft_runner_status = logical_eot;
+			fdc_mode         = fdc_idle;
+			TRACE_EXIT;
+		}
+		if (next <= 0) {
+			/*  don't continue with next segment
+			 */
+			TRACE(ft_t_noise, "no %s allowed, stopping tape",
+			      (write) ? "write next" : "read ahead");
+			pause_tape(0, mode);
+			ft_runner_status = idle;  /*  not quite true until
+						   *  next irq 
+						   */
+			TRACE_EXIT;
+		}
+		/*  continue with next segment
+		 */
+		if (buff->status != waiting) {
+			TRACE(ft_t_noise, "all input buffers %s, pausing tape",
+			      (write) ? "empty" : "full");
+			pause_tape(0, mode);
+			ft_runner_status = idle;  /*  not quite true until
+						   *  next irq 
+						   */
+			TRACE_EXIT;
+		}
+		if (write && next != buff->segment_id) {
+			TRACE(ft_t_noise, 
+			      "segments out of order, aborting write");
+			ft_runner_status = do_abort;
+			fdc_mode         = fdc_idle;
+			TRACE_EXIT;
+		}
+		ftape_setup_new_segment(buff, next, 0);
+		if (stop_read_ahead) {
+			buff->next_segment = 0;
+			stop_read_ahead = 0;
+		}
+		if (ftape_calc_next_cluster(buff) == 0 ||
+		    fdc_setup_read_write(buff, mode) != 0) {
+			TRACE(ft_t_err, "couldn't start %s-ahead",
+			      write ? "write" : "read");
+			ft_runner_status = do_abort;
+			fdc_mode         = fdc_idle;
+		} else {
+			/* keep on going */
+			switch (ft_driver_state) {
+			case   reading: buff->status = reading;   break;
+			case verifying: buff->status = verifying; break;
+			case   writing: buff->status = writing;   break;
+			case  deleting: buff->status = deleting;  break;
+			default:
+				TRACE(ft_t_err, 
+		      "BUG: ft_driver_state %d should be one out of "
+		      "{reading, writing, verifying, deleting}",
+				      ft_driver_state);
+				buff->status = write ? writing : reading;
+				break;
+			}
+		}
+	}
+	TRACE_EXIT;
+}
+
+static void retry_sector(buffer_struct *buff, 
+			 int mode,
+			 unsigned int skip)
+{
+	TRACE_FUN(ft_t_any);
+
+	TRACE(ft_t_noise, "%s error, will retry",
+	      (mode == fdc_writing_data || mode == fdc_deleting) ? "write" : "read");
+	pause_tape(1, mode);
+	ft_runner_status = aborting;
+	buff->status     = error;
+	buff->skip       = skip;
+	TRACE_EXIT;
+}
+
+static unsigned int find_resume_point(buffer_struct *buff)
+{
+	int i = 0;
+	SectorMap mask;
+	SectorMap map;
+	TRACE_FUN(ft_t_any);
+
+	/*  This function is to be called after all variables have been
+	 *  updated to point past the failing sector.
+	 *  If there are any soft errors before the failing sector,
+	 *  find the first soft error and return the sector offset.
+	 *  Otherwise find the last hard error.
+	 *  Note: there should always be at least one hard or soft error !
+	 */
+	if (buff->sector_offset < 1 || buff->sector_offset > 32) {
+		TRACE(ft_t_bug, "BUG: sector_offset = %d",
+		      buff->sector_offset);
+		TRACE_EXIT 0;
+	}
+	if (buff->sector_offset >= 32) { /* C-limitation on shift ! */
+		mask = 0xffffffff;
+	} else {
+		mask = (1 << buff->sector_offset) - 1;
+	}
+	map = buff->soft_error_map & mask;
+	if (map) {
+		while ((map & (1 << i)) == 0) {
+			++i;
+		}
+		TRACE(ft_t_noise, "at sector %d", FT_SECTOR(i));
+	} else {
+		map = buff->hard_error_map & mask;
+		i = buff->sector_offset - 1;
+		if (map) {
+			while ((map & (1 << i)) == 0) {
+				--i;
+			}
+			TRACE(ft_t_noise, "after sector %d", FT_SECTOR(i));
+			++i; /* first sector after last hard error */
+		} else {
+			TRACE(ft_t_bug, "BUG: no soft or hard errors");
+		}
+	}
+	TRACE_EXIT i;
+}
+
+/*  check possible dma residue when formatting, update position record in
+ *  buffer struct. This is, of course, modelled after determine_progress(), but
+ *  we don't need to set up for retries because the format process cannot be
+ *  interrupted (except at the end of the tape track).
+ */
+static int determine_fmt_progress(buffer_struct *buff, error_cause cause)
+{
+	unsigned int dma_residue;
+	TRACE_FUN(ft_t_any);
+
+	/*  Using less preferred order of disable_dma and
+	 *  get_dma_residue because this seems to fail on at least one
+	 *  system if reversed!
+	 */
+	dma_residue = get_dma_residue(fdc.dma);
+	disable_dma(fdc.dma);
+	if (cause != no_error || dma_residue != 0) {
+		TRACE(ft_t_info, "DMA residue = 0x%04x", dma_residue);
+		fdc_mode = fdc_idle;
+		switch(cause) {
+		case no_error:
+			ft_runner_status = aborting;
+			buff->status = idle;
+			break;
+		case overrun_error:
+			/*  got an overrun error on the first byte, must be a
+			 *  hardware problem 
+			 */
+			TRACE(ft_t_bug, 
+			      "Unexpected error: failing DMA controller ?");
+			ft_runner_status = do_abort;
+			buff->status = error;
+			break;
+		default:
+			TRACE(ft_t_noise, "Unexpected error at segment %d",
+			      buff->segment_id);
+			ft_runner_status = do_abort;
+			buff->status = error;
+			break;
+		}
+		TRACE_EXIT -EIO; /* can only retry entire track in format mode
+				  */
+	}
+	/*  Update var's influenced by the DMA operation.
+	 */
+	buff->ptr             += FT_SECTORS_PER_SEGMENT * 4;
+	buff->bytes           -= FT_SECTORS_PER_SEGMENT * 4;
+	buff->remaining       -= FT_SECTORS_PER_SEGMENT;
+	buff->segment_id ++; /* done with segment */
+	TRACE_EXIT 0;
+}
+
+/*
+ *  Continue formatting, switch buffers if there is no data left in
+ *  current buffer. This is, of course, modelled after
+ *  continue_xfer(), but we don't need to set up for retries because
+ *  the format process cannot be interrupted (except at the end of the
+ *  tape track).
+ */
+static void continue_formatting(buffer_struct *buff)
+{
+	TRACE_FUN(ft_t_any);
+
+	if (buff->remaining <= 0) { /*  no space left in dma buffer */
+		unsigned int next = buff->next_segment; 
+
+		if (next == 0) { /* end of tape track */
+			buff->status     = done;
+			ft_runner_status = logical_eot;
+			fdc_mode         = fdc_idle;
+			TRACE(ft_t_noise, "Done formatting track %d",
+			      ft_location.track);
+			TRACE_EXIT;
+		}
+		/*
+		 *  switch to next buffer!
+		 */
+		buff->status   = done;
+		buff = ftape_next_buffer(ft_queue_head);
+
+		if (buff->status != waiting  || next != buff->segment_id) {
+			goto format_setup_error;
+		}
+	}
+	if (fdc_setup_formatting(buff) < 0) {
+		goto format_setup_error;
+	}
+	buff->status = formatting;
+	TRACE(ft_t_fdc_dma, "Formatting segment %d on track %d",
+	      buff->segment_id, ft_location.track);
+	TRACE_EXIT;
+ format_setup_error:
+	ft_runner_status = do_abort;
+	fdc_mode         = fdc_idle;
+	buff->status     = error;
+	TRACE(ft_t_err, "Error setting up for segment %d on track %d",
+	      buff->segment_id, ft_location.track);
+	TRACE_EXIT;
+
+}
+
+/*  this handles writing, read id, reading and formatting
+ */
+static void handle_fdc_busy(buffer_struct *buff)
+{
+	static int no_data_error_count;
+	int retry = 0;
+	error_cause cause;
+	__u8 in[7];
+	int skip;
+	fdc_mode_enum fmode = fdc_mode;
+	TRACE_FUN(ft_t_any);
+
+	if (fdc_result(in, 7) < 0) { /* better get it fast ! */
+		TRACE(ft_t_err, 
+		      "Probably fatal error during FDC Result Phase\n"
+		      KERN_INFO
+		      "drive may hang until (power on) reset :-(");
+		/*  what to do next ????
+		 */
+		TRACE_EXIT;
+	}
+	cause = decode_irq_cause(fdc_mode, in);
+#ifdef TESTING
+	{ int i;
+	for (i = 0; i < (int)ft_nr_buffers; ++i)
+		TRACE(ft_t_any, "buffer[%d] status: %d, segment_id: %d",
+		      i, ft_buffer[i]->status, ft_buffer[i]->segment_id);
+	}
+#endif
+	if (fmode == fdc_reading_data && ft_driver_state == verifying) {
+		fmode = fdc_verifying;
+	}
+	switch (fmode) {
+	case fdc_verifying:
+		if (ft_runner_status == aborting ||
+		    ft_runner_status == do_abort) {
+			TRACE(ft_t_noise,"aborting %s",fdc_mode_txt(fdc_mode));
+			break;
+		}
+		if (buff->retry > 0) {
+			TRACE(ft_t_flow, "this is retry nr %d", buff->retry);
+		}
+		switch (cause) {
+		case no_error:
+			no_data_error_count = 0;
+			determine_verify_progress(buff, cause, in[5]);
+			if (in[2] & 0x40) {
+				/*  This should not happen when verifying
+				 */
+				TRACE(ft_t_warn,
+				      "deleted data in segment %d/%d",
+				      buff->segment_id,
+				      FT_SECTOR(buff->sector_offset - 1));
+				buff->remaining = 0; /* abort transfer */
+				buff->hard_error_map = EMPTY_SEGMENT;
+				skip = 1;
+			} else {
+				skip = 0;
+			}
+			continue_xfer(buff, fdc_mode, skip);
+			break;
+		case no_data_error:
+			no_data_error_count ++;
+		case overrun_error:
+			retry ++;
+		case id_am_error:
+		case id_crc_error:
+		case data_am_error:
+		case data_crc_error:
+			determine_verify_progress(buff, cause, in[5]); 
+			if (cause == no_data_error) {
+				if (no_data_error_count >= 2) {
+					TRACE(ft_t_warn,
+					      "retrying because of successive "
+					      "no data errors");
+					no_data_error_count = 0;
+				} else {
+					retry --;
+				}
+			} else {
+				no_data_error_count = 0;
+			}
+			if (retry) {
+				skip = find_resume_point(buff);
+			} else {
+				skip = buff->sector_offset;
+			}
+			if (retry && skip < 32) {
+				retry_sector(buff, fdc_mode, skip);
+			} else {
+				continue_xfer(buff, fdc_mode, skip);
+			}
+			update_history(cause);
+			break;
+		default:
+			/*  Don't know why this could happen 
+			 *  but find out.
+			 */
+			determine_verify_progress(buff, cause, in[5]);
+			retry_sector(buff, fdc_mode, 0);
+			TRACE(ft_t_err, "Error: unexpected error");
+			break;
+		}
+		break;
+	case fdc_reading_data:
+#ifdef TESTING
+		/* I'm sorry, but: NOBODY ever used this trace
+		 * messages for ages. I guess that Bas was the last person
+		 * that ever really used this (thank you, between the lines)
+		 */
+		if (cause == no_error) {
+			TRACE(ft_t_flow,"reading segment %d",buff->segment_id);
+		} else {
+			TRACE(ft_t_noise, "error reading segment %d",
+			      buff->segment_id);
+			TRACE(ft_t_noise, "\n"
+			      KERN_INFO
+			     "IRQ:C: 0x%02x, H: 0x%02x, R: 0x%02x, N: 0x%02x\n"
+			      KERN_INFO
+			      "BUF:C: 0x%02x, H: 0x%02x, R: 0x%02x",
+			      in[3], in[4], in[5], in[6],
+			      buff->cyl, buff->head, buff->sect);
+		}
+#endif
+		if (ft_runner_status == aborting ||
+		    ft_runner_status == do_abort) {
+			TRACE(ft_t_noise,"aborting %s",fdc_mode_txt(fdc_mode));
+			break;
+		}
+		if (buff->bad_sector_map == FAKE_SEGMENT) {
+			/* This condition occurs when reading a `fake'
+			 * sector that's not accessible. Doesn't
+			 * really matter as we would have ignored it
+			 * anyway !
+			 *
+			 * Chance is that we're past the next segment
+			 * now, so the next operation may fail and
+			 * result in a retry.  
+			 */
+			buff->remaining = 0;	/* skip failing sector */
+			/* buff->ptr       = buff->address; */
+			/* fake success: */
+			continue_xfer(buff, fdc_mode, 1);
+			/*  trace calls are expensive: place them AFTER
+			 *  the real stuff has been done.
+			 *  
+			 */
+			TRACE(ft_t_noise, "skipping empty segment %d (read), size? %d",
+			      buff->segment_id, buff->ptr - buff->address);
+			TRACE_EXIT;
+		}
+		if (buff->retry > 0) {
+			TRACE(ft_t_flow, "this is retry nr %d", buff->retry);
+		}
+		switch (cause) {
+		case no_error:
+			determine_progress(buff, cause, in[5]);
+			if (in[2] & 0x40) {
+				/*  Handle deleted data in header segments.
+				 *  Skip segment and force read-ahead.
+				 */
+				TRACE(ft_t_warn,
+				      "deleted data in segment %d/%d",
+				      buff->segment_id,
+				      FT_SECTOR(buff->sector_offset - 1));
+				buff->deleted = 1;
+				buff->remaining = 0;/*abort transfer */
+				buff->soft_error_map |=
+						(-1L << buff->sector_offset);
+				if (buff->segment_id == 0) {
+					/* stop on next segment */
+					stop_read_ahead = 1;
+				}
+				/* force read-ahead: */
+				buff->next_segment = 
+					buff->segment_id + 1;
+				skip = (FT_SECTORS_PER_SEGMENT - 
+					buff->sector_offset);
+			} else {
+				skip = 0;
+			}
+			continue_xfer(buff, fdc_mode, skip);
+			break;
+		case no_data_error:
+			/* Tape started too far ahead of or behind the
+			 * right sector.  This may also happen in the
+			 * middle of a segment !
+			 *
+			 * Handle no-data as soft error. If next
+			 * sector fails too, a retry (with needed
+			 * reposition) will follow.
+			 */
+			retry ++;
+		case id_am_error:
+		case id_crc_error:
+		case data_am_error:
+		case data_crc_error:
+		case overrun_error:
+			retry += (buff->soft_error_map != 0 ||
+				  buff->hard_error_map != 0);
+			determine_progress(buff, cause, in[5]); 
+#if 1 || defined(TESTING)
+			if (cause == overrun_error) retry ++;
+#endif
+			if (retry) {
+				skip = find_resume_point(buff);
+			} else {
+				skip = buff->sector_offset;
+			}
+			/*  Try to resume with next sector on single
+			 *  errors (let ecc correct it), but retry on
+			 *  no_data (we'll be past the target when we
+			 *  get here so we cannot retry) or on
+			 *  multiple errors (reduce chance on ecc
+			 *  failure).
+			 */
+			/*  cH: 23/02/97: if the last sector in the 
+			 *  segment was a hard error, then there is 
+			 *  no sense in a retry. This occasion seldom
+			 *  occurs but ... @:³²¸`@%&§$
+			 */
+			if (retry && skip < 32) {
+				retry_sector(buff, fdc_mode, skip);
+			} else {
+				continue_xfer(buff, fdc_mode, skip);
+			}
+			update_history(cause);
+			break;
+		default:
+			/*  Don't know why this could happen 
+			 *  but find out.
+			 */
+			determine_progress(buff, cause, in[5]);
+			retry_sector(buff, fdc_mode, 0);
+			TRACE(ft_t_err, "Error: unexpected error");
+			break;
+		}
+		break;
+	case fdc_reading_id:
+		if (cause == no_error) {
+			fdc_cyl = in[3];
+			fdc_head = in[4];
+			fdc_sect = in[5];
+			TRACE(ft_t_fdc_dma,
+			      "id read: C: 0x%02x, H: 0x%02x, R: 0x%02x",
+			      fdc_cyl, fdc_head, fdc_sect);
+		} else {	/* no valid information, use invalid sector */
+			fdc_cyl = fdc_head = fdc_sect = 0;
+			TRACE(ft_t_flow, "Didn't find valid sector Id");
+		}
+		fdc_mode = fdc_idle;
+		break;
+	case fdc_deleting:
+	case fdc_writing_data:
+#ifdef TESTING
+		if (cause == no_error) {
+			TRACE(ft_t_flow, "writing segment %d", buff->segment_id);
+		} else {
+			TRACE(ft_t_noise, "error writing segment %d",
+			      buff->segment_id);
+		}
+#endif
+		if (ft_runner_status == aborting ||
+		    ft_runner_status == do_abort) {
+			TRACE(ft_t_flow, "aborting %s",fdc_mode_txt(fdc_mode));
+			break;
+		}
+		if (buff->retry > 0) {
+			TRACE(ft_t_flow, "this is retry nr %d", buff->retry);
+		}
+		if (buff->bad_sector_map == FAKE_SEGMENT) {
+			/* This condition occurs when trying to write to a
+			 * `fake' sector that's not accessible. Doesn't really
+			 * matter as it isn't used anyway ! Might be located
+			 * at wrong segment, then we'll fail on the next
+			 * segment.
+			 */
+			TRACE(ft_t_noise, "skipping empty segment (write)");
+			buff->remaining = 0;	/* skip failing sector */
+			/* fake success: */
+			continue_xfer(buff, fdc_mode, 1);
+			break;
+		}
+		switch (cause) {
+		case no_error:
+			determine_progress(buff, cause, in[5]);
+			continue_xfer(buff, fdc_mode, 0);
+			break;
+		case no_data_error:
+		case id_am_error:
+		case id_crc_error:
+		case data_am_error:
+		case overrun_error:
+			update_history(cause);
+			determine_progress(buff, cause, in[5]);
+			skip = find_resume_point(buff);
+			retry_sector(buff, fdc_mode, skip);
+			break;
+		default:
+			if (in[1] & 0x02) {
+				TRACE(ft_t_err, "media not writable");
+			} else {
+				TRACE(ft_t_bug, "unforeseen write error");
+			}
+			fdc_mode = fdc_idle;
+			break;
+		}
+		break; /* fdc_deleting || fdc_writing_data */
+	case fdc_formatting:
+		/*  The interrupt comes after formatting a segment. We then
+		 *  have to set up QUICKLY for the next segment. But
+		 *  afterwards, there is plenty of time.
+		 */
+		switch (cause) {
+		case no_error:
+			/*  would like to keep most of the formatting stuff
+			 *  outside the isr code, but timing is too critical
+			 */
+			if (determine_fmt_progress(buff, cause) >= 0) {
+				continue_formatting(buff);
+			}
+			break;
+		case no_data_error:
+		case id_am_error:
+		case id_crc_error:
+		case data_am_error:
+		case overrun_error:
+		default:
+			determine_fmt_progress(buff, cause);
+			update_history(cause);
+			if (in[1] & 0x02) {
+				TRACE(ft_t_err, "media not writable");
+			} else {
+				TRACE(ft_t_bug, "unforeseen write error");
+			}
+			break;
+		} /* cause */
+		break;
+	default:
+		TRACE(ft_t_warn, "Warning: unexpected irq during: %s",
+		      fdc_mode_txt(fdc_mode));
+		fdc_mode = fdc_idle;
+		break;
+	}
+	TRACE_EXIT;
+}
+
+/*      FDC interrupt service routine.
+ */
+void fdc_isr(void)
+{
+	static int isr_active;
+#ifdef TESTING
+	unsigned int t0 = ftape_timestamp();
+#endif
+	TRACE_FUN(ft_t_any);
+
+ 	if (isr_active++) {
+		--isr_active;
+		TRACE(ft_t_bug, "BUG: nested interrupt, not good !");
+		*fdc.hook = fdc_isr; /*  hook our handler into the fdc
+				      *  code again 
+				      */
+		TRACE_EXIT;
+	}
+	sti();
+	if (inb_p(fdc.msr) & FDC_BUSY) {	/*  Entering Result Phase */
+		ft_hide_interrupt = 0;
+		handle_fdc_busy(ftape_get_buffer(ft_queue_head));
+		if (ft_runner_status == do_abort) {
+			/*      cease operation, remember tape position
+			 */
+			TRACE(ft_t_flow, "runner aborting");
+			ft_runner_status = aborting;
+			++ft_expected_stray_interrupts;
+		}
+	} else { /* !FDC_BUSY */
+		/*  clear interrupt, cause should be gotten by issuing
+		 *  a Sense Interrupt Status command.
+		 */
+		if (fdc_mode == fdc_recalibrating || fdc_mode == fdc_seeking) {
+			if (ft_hide_interrupt) {
+				int st0;
+				int pcn;
+
+				if (fdc_sense_interrupt_status(&st0, &pcn) < 0)
+					TRACE(ft_t_err,
+					      "sense interrupt status failed");
+				ftape_current_cylinder = pcn;
+				TRACE(ft_t_flow, "handled hidden interrupt");
+			}
+			ft_seek_completed = 1;
+			fdc_mode = fdc_idle;
+		} else if (!waitqueue_active(&ftape_wait_intr)) {
+			if (ft_expected_stray_interrupts == 0) {
+				TRACE(ft_t_warn, "unexpected stray interrupt");
+			} else {
+				TRACE(ft_t_flow, "expected stray interrupt");
+				--ft_expected_stray_interrupts;
+			}
+		} else {
+			if (fdc_mode == fdc_reading_data ||
+			    fdc_mode == fdc_verifying    ||
+			    fdc_mode == fdc_writing_data ||
+			    fdc_mode == fdc_deleting     ||
+			    fdc_mode == fdc_formatting   ||
+			    fdc_mode == fdc_reading_id) {
+				if (inb_p(fdc.msr) & FDC_BUSY) {
+					TRACE(ft_t_bug,
+					"***** FDC failure, busy too late");
+				} else {
+					TRACE(ft_t_bug,
+					      "***** FDC failure, no busy");
+				}
+			} else {
+				TRACE(ft_t_fdc_dma, "awaited stray interrupt");
+			}
+		}
+		ft_hide_interrupt = 0;
+	}
+	/*    Handle sleep code.
+	 */
+	if (!ft_hide_interrupt) {
+		ft_interrupt_seen ++;
+		if (waitqueue_active(&ftape_wait_intr)) {
+			wake_up_interruptible(&ftape_wait_intr);
+		}
+	} else {
+		TRACE(ft_t_flow, "hiding interrupt while %s", 
+		      waitqueue_active(&ftape_wait_intr) ? "waiting":"active");
+	}
+#ifdef TESTING
+	t0 = ftape_timediff(t0, ftape_timestamp());
+	if (t0 >= 1000) {
+		/* only tell us about long calls */
+		TRACE(ft_t_noise, "isr() duration: %5d usec", t0);
+	}
+#endif
+	*fdc.hook = fdc_isr;	/* hook our handler into the fdc code again */
+	--isr_active;
+	TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/lowlevel/fdc-isr.h b/drivers/char/ftape/lowlevel/fdc-isr.h
new file mode 100644
index 0000000..065aa97
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/fdc-isr.h
@@ -0,0 +1,55 @@
+#ifndef _FDC_ISR_H
+#define _FDC_ISR_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/fdc-isr.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:07 $
+ *
+ *      This file declares the global variables necessary to
+ *      synchronize the interrupt service routine (isr) with the
+ *      remainder of the QIC-40/80/3010/3020 floppy-tape driver
+ *      "ftape" for Linux.
+ */
+
+/*
+ *      fdc-isr.c defined public variables
+ */
+extern volatile int ft_expected_stray_interrupts; /* masks stray interrupts */
+extern volatile int ft_seek_completed;	          /* flag set by isr */
+extern volatile int ft_interrupt_seen;	          /* flag set by isr */
+extern volatile int ft_hide_interrupt;            /* flag set by isr */
+
+/*
+ *      fdc-io.c defined public functions
+ */
+extern void fdc_isr(void);
+
+/*
+ *      A kernel hook that steals one interrupt from the floppy
+ *      driver (Should be fixed when the new fdc driver gets ready)
+ *      See the linux kernel source files:
+ *          drivers/block/floppy.c & drivers/block/blk.h
+ *      for the details.
+ */
+extern void (*do_floppy) (void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-bsm.c b/drivers/char/ftape/lowlevel/ftape-bsm.c
new file mode 100644
index 0000000..d1a301c
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-bsm.c
@@ -0,0 +1,491 @@
+/*
+ *      Copyright (C) 1994-1996 Bas Laarhoven,
+ *                (C) 1996-1997 Claus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-bsm.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/05 19:15:15 $
+ *
+ *      This file contains the bad-sector map handling code for
+ *      the QIC-117 floppy tape driver for Linux.
+ *      QIC-40, QIC-80, QIC-3010 and QIC-3020 maps are implemented.
+ */
+
+#include <linux/string.h>
+
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+
+/*      Global vars.
+ */
+
+/*      Local vars.
+ */
+static __u8 *bad_sector_map;
+static SectorCount *bsm_hash_ptr; 
+
+typedef enum {
+	forward, backward
+} mode_type;
+
+#if 0
+static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map);
+#endif
+
+#if 0
+/*  fix_tape converts a normal QIC-80 tape into a 'wide' tape.
+ *  For testing purposes only !
+ */
+void fix_tape(__u8 * buffer, ft_format_type new_code)
+{
+	static __u8 list[BAD_SECTOR_MAP_SIZE];
+	SectorMap *src_ptr = (SectorMap *) list;
+	__u8 *dst_ptr = bad_sector_map;
+	SectorMap map;
+	unsigned int sector = 1;
+	int i;
+
+	if (format_code != fmt_var && format_code != fmt_big) {
+		memcpy(list, bad_sector_map, sizeof(list));
+		memset(bad_sector_map, 0, sizeof(bad_sector_map));
+		while ((__u8 *) src_ptr - list < sizeof(list)) {
+			map = *src_ptr++;
+			if (map == EMPTY_SEGMENT) {
+				*(SectorMap *) dst_ptr = 0x800000 + sector;
+				dst_ptr += 3;
+				sector += SECTORS_PER_SEGMENT;
+			} else {
+				for (i = 0; i < SECTORS_PER_SEGMENT; ++i) {
+					if (map & 1) {
+						*(SewctorMap *) dst_ptr = sector;
+						dst_ptr += 3;
+					}
+					map >>= 1;
+					++sector;
+				}
+			}
+		}
+	}
+	bad_sector_map_changed = 1;
+	*(buffer + 4) = new_code;	/* put new format code */
+	if (format_code != fmt_var && new_code == fmt_big) {
+		PUT4(buffer, FT_6_HSEG_1,   (__u32)GET2(buffer, 6));
+		PUT4(buffer, FT_6_HSEG_2,   (__u32)GET2(buffer, 8));
+		PUT4(buffer, FT_6_FRST_SEG, (__u32)GET2(buffer, 10));
+		PUT4(buffer, FT_6_LAST_SEG, (__u32)GET2(buffer, 12));
+		memset(buffer+6, '\0', 8);
+	}
+	format_code = new_code;
+}
+
+#endif
+
+/*   given buffer that contains a header segment, find the end of
+ *   of the bsm list
+ */
+__u8 * ftape_find_end_of_bsm_list(__u8 * address)
+{
+	__u8 *ptr   = address + FT_HEADER_END; /* start of bsm list */
+	__u8 *limit = address + FT_SEGMENT_SIZE;
+	while (ptr + 2 < limit) {
+		if (ptr[0] || ptr[1] || ptr[2]) {
+			ptr += 3;
+		} else {
+			return ptr;
+		}
+	}
+	return NULL;
+}
+
+static inline void put_sector(SectorCount *ptr, unsigned int sector)
+{
+	ptr->bytes[0] = sector & 0xff;
+	sector >>= 8;
+	ptr->bytes[1] = sector & 0xff;
+	sector >>= 8;
+	ptr->bytes[2] = sector & 0xff;
+}
+
+static inline unsigned int get_sector(SectorCount *ptr)
+{
+#if 1
+	unsigned int sector;
+
+	sector  = ptr->bytes[0];
+	sector += ptr->bytes[1] <<  8;
+	sector += ptr->bytes[2] << 16;
+
+	return sector;
+#else
+	/*  GET4 gets the next four bytes in Intel little endian order
+	 *  and converts them to host byte order and handles unaligned
+	 *  access.
+	 */
+	return (GET4(ptr, 0) & 0x00ffffff); /* back to host byte order */
+#endif
+}
+
+static void bsm_debug_fake(void)
+{
+	/* for testing of bad sector handling at end of tape
+	 */
+#if 0
+	ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 3,
+				   0x000003e0;
+	ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 2,
+				   0xff3fffff;
+	ftape_put_bad_sector_entry(segments_per_track * tracks_per_tape - 1,
+				   0xffffe000;
+#endif
+	/*  Enable to test bad sector handling
+	 */
+#if 0
+	ftape_put_bad_sector_entry(30, 0xfffffffe)
+	ftape_put_bad_sector_entry(32, 0x7fffffff);
+	ftape_put_bad_sector_entry(34, 0xfffeffff);
+	ftape_put_bad_sector_entry(36, 0x55555555);
+	ftape_put_bad_sector_entry(38, 0xffffffff);
+	ftape_put_bad_sector_entry(50, 0xffff0000);
+	ftape_put_bad_sector_entry(51, 0xffffffff);
+	ftape_put_bad_sector_entry(52, 0xffffffff);
+	ftape_put_bad_sector_entry(53, 0x0000ffff);
+#endif
+	/*  Enable when testing multiple volume tar dumps.
+	 */
+#if 0
+	{
+		int i;
+
+		for (i = ft_first_data_segment;
+		     i <= ft_last_data_segment - 7; ++i) {
+			ftape_put_bad_sector_entry(i, EMPTY_SEGMENT);
+		}
+	}
+#endif
+	/*  Enable when testing bit positions in *_error_map
+	 */
+#if 0
+	{
+		int i;
+		
+		for (i = first_data_segment; i <= last_data_segment; ++i) {
+			ftape_put_bad_sector_entry(i,
+					   ftape_get_bad_sector_entry(i) 
+					   | 0x00ff00ff);
+		}
+	}
+#endif
+}
+
+static void print_bad_sector_map(void)
+{
+	unsigned int good_sectors;
+	unsigned int total_bad = 0;
+	int i;
+	TRACE_FUN(ft_t_flow);
+
+	if (ft_format_code == fmt_big || 
+	    ft_format_code == fmt_var || 
+	    ft_format_code == fmt_1100ft) {
+		SectorCount *ptr = (SectorCount *)bad_sector_map;
+		unsigned int sector;
+		__u16 *ptr16;
+
+		while((sector = get_sector(ptr++)) != 0) {
+			if ((ft_format_code == fmt_big || 
+			     ft_format_code == fmt_var) &&
+			    sector & 0x800000) {
+				total_bad += FT_SECTORS_PER_SEGMENT - 3;
+				TRACE(ft_t_noise, "bad segment at sector: %6d",
+				      sector & 0x7fffff);
+			} else {
+				++total_bad;
+				TRACE(ft_t_noise, "bad sector: %6d", sector);
+			}
+		}
+		/*  Display old ftape's end-of-file marks
+		 */
+		ptr16 = (__u16*)ptr;
+		while ((sector = get_unaligned(ptr16++)) != 0) {
+			TRACE(ft_t_noise, "Old ftape eof mark: %4d/%2d",
+			      sector, get_unaligned(ptr16++));
+		}
+	} else { /* fixed size format */
+		for (i = ft_first_data_segment;
+		     i < (int)(ft_segments_per_track * ft_tracks_per_tape); ++i) {
+			SectorMap map = ((SectorMap *) bad_sector_map)[i];
+
+			if (map) {
+				TRACE(ft_t_noise,
+				      "bsm for segment %4d: 0x%08x", i, (unsigned int)map);
+				total_bad += ((map == EMPTY_SEGMENT)
+					       ? FT_SECTORS_PER_SEGMENT - 3
+					       : count_ones(map));
+			}
+		}
+	}
+	good_sectors =
+		((ft_segments_per_track * ft_tracks_per_tape - ft_first_data_segment)
+		 * (FT_SECTORS_PER_SEGMENT - 3)) - total_bad;
+	TRACE(ft_t_info, "%d Kb usable on this tape", good_sectors);
+	if (total_bad == 0) {
+		TRACE(ft_t_info,
+		      "WARNING: this tape has no bad blocks registered !");
+	} else {
+		TRACE(ft_t_info, "%d bad sectors", total_bad);
+	}
+	TRACE_EXIT;
+}
+
+
+void ftape_extract_bad_sector_map(__u8 * buffer)
+{
+	TRACE_FUN(ft_t_any);
+
+	/*  Fill the bad sector map with the contents of buffer.
+	 */
+	if (ft_format_code == fmt_var || ft_format_code == fmt_big) {
+		/* QIC-3010/3020 and wide QIC-80 tapes no longer have a failed
+		 * sector log but use this area to extend the bad sector map.
+		 */
+		bad_sector_map = &buffer[FT_HEADER_END];
+	} else {
+		/* non-wide QIC-80 tapes have a failed sector log area that
+		 * mustn't be included in the bad sector map.
+		 */
+		bad_sector_map = &buffer[FT_FSL + FT_FSL_SIZE];
+	}
+	if (ft_format_code == fmt_1100ft || 
+	    ft_format_code == fmt_var    ||
+	    ft_format_code == fmt_big) {
+		bsm_hash_ptr = (SectorCount *)bad_sector_map;
+	} else {
+		bsm_hash_ptr = NULL;
+	}
+	bsm_debug_fake();
+	if (TRACE_LEVEL >= ft_t_info) {
+		print_bad_sector_map();
+	}
+	TRACE_EXIT;
+}
+
+static inline SectorMap cvt2map(unsigned int sector)
+{
+	return 1 << (((sector & 0x7fffff) - 1) % FT_SECTORS_PER_SEGMENT);
+}
+
+static inline int cvt2segment(unsigned int sector)
+{
+	return ((sector & 0x7fffff) - 1) / FT_SECTORS_PER_SEGMENT;
+}
+
+static int forward_seek_entry(int segment_id, 
+			      SectorCount **ptr, 
+			      SectorMap *map)
+{
+	unsigned int sector;
+	int segment;
+
+	do {
+		sector = get_sector((*ptr)++);
+		segment = cvt2segment(sector);
+	} while (sector != 0 && segment < segment_id);
+	(*ptr) --; /* point to first sector >= segment_id */
+	/*  Get all sectors in segment_id
+	 */
+	if (sector == 0 || segment != segment_id) {
+		*map = 0;
+		return 0;
+	} else if ((sector & 0x800000) &&
+		   (ft_format_code == fmt_var || ft_format_code == fmt_big)) {
+		*map = EMPTY_SEGMENT;
+		return FT_SECTORS_PER_SEGMENT;
+	} else {
+		int count = 1;
+		SectorCount *tmp_ptr = (*ptr) + 1;
+		
+		*map = cvt2map(sector);
+		while ((sector = get_sector(tmp_ptr++)) != 0 &&
+		       (segment = cvt2segment(sector)) == segment_id) {
+			*map |= cvt2map(sector);
+			++count;
+		}
+		return count;
+	}
+}
+
+static int backwards_seek_entry(int segment_id,
+				SectorCount **ptr,
+				SectorMap *map)
+{
+	unsigned int sector;
+	int segment; /* max unsigned int */
+
+	if (*ptr <= (SectorCount *)bad_sector_map) {
+		*map = 0;
+		return 0;
+	}
+	do {
+		sector  = get_sector(--(*ptr));
+		segment = cvt2segment(sector);
+	} while (*ptr > (SectorCount *)bad_sector_map && segment > segment_id);
+	if (segment > segment_id) { /*  at start of list, no entry found */
+		*map = 0;
+		return 0;
+	} else if (segment < segment_id) {
+		/*  before smaller entry, adjust for overshoot */
+		(*ptr) ++;
+		*map = 0;
+		return 0;
+	} else if ((sector & 0x800000) &&
+		   (ft_format_code == fmt_big || ft_format_code == fmt_var)) {
+		*map = EMPTY_SEGMENT;
+		return FT_SECTORS_PER_SEGMENT;
+	} else { /*  get all sectors in segment_id */
+		int count = 1;
+
+		*map = cvt2map(sector);
+		while(*ptr > (SectorCount *)bad_sector_map) {
+			sector = get_sector(--(*ptr));
+			segment = cvt2segment(sector);
+			if (segment != segment_id) {
+				break;
+			}
+			*map |= cvt2map(sector);
+			++count;
+		}
+		if (segment < segment_id) {
+			(*ptr) ++;
+		}
+		return count;
+	}
+}
+
+#if 0
+static void ftape_put_bad_sector_entry(int segment_id, SectorMap new_map)
+{
+	SectorCount *ptr = (SectorCount *)bad_sector_map;
+	int count;
+	int new_count;
+	SectorMap map;
+	TRACE_FUN(ft_t_any);
+
+	if (ft_format_code == fmt_1100ft || 
+	    ft_format_code == fmt_var || 
+	    ft_format_code == fmt_big) {
+		count = forward_seek_entry(segment_id, &ptr, &map);
+		new_count = count_ones(new_map);
+		/* If format code == 4 put empty segment instead of 32
+		 * bad sectors.
+		 */
+		if (ft_format_code == fmt_var || ft_format_code == fmt_big) {
+			if (new_count == FT_SECTORS_PER_SEGMENT) {
+				new_count = 1;
+			}
+			if (count == FT_SECTORS_PER_SEGMENT) {
+				count = 1;
+			}
+		}
+		if (count != new_count) {
+			/* insert (or delete if < 0) new_count - count
+			 * entries.  Move trailing part of list
+			 * including terminating 0.
+			 */
+			SectorCount *hi_ptr = ptr;
+
+			do {
+			} while (get_sector(hi_ptr++) != 0);
+			/*  Note: ptr is of type byte *, and each bad sector
+			 *  consumes 3 bytes.
+			 */
+			memmove(ptr + new_count, ptr + count,
+				(size_t)(hi_ptr - (ptr + count))*sizeof(SectorCount));
+		}
+		TRACE(ft_t_noise, "putting map 0x%08x at %p, segment %d",
+		      (unsigned int)new_map, ptr, segment_id);
+		if (new_count == 1 && new_map == EMPTY_SEGMENT) {
+			put_sector(ptr++, (0x800001 + 
+					  segment_id * 
+					  FT_SECTORS_PER_SEGMENT));
+		} else {
+			int i = 0;
+
+			while (new_map) {
+				if (new_map & 1) {
+					put_sector(ptr++, 
+						   1 + segment_id * 
+						   FT_SECTORS_PER_SEGMENT + i);
+				}
+				++i;
+				new_map >>= 1;
+			}
+		}
+	} else {
+		((SectorMap *) bad_sector_map)[segment_id] = new_map;
+	}
+	TRACE_EXIT;
+}
+#endif  /*  0  */
+
+SectorMap ftape_get_bad_sector_entry(int segment_id)
+{
+	if (ft_used_header_segment == -1) {
+		/*  When reading header segment we'll need a blank map.
+		 */
+		return 0;
+	} else if (bsm_hash_ptr != NULL) {
+		/*  Invariants:
+		 *    map - mask value returned on last call.
+		 *    bsm_hash_ptr - points to first sector greater or equal to
+		 *          first sector in last_referenced segment.
+		 *    last_referenced - segment id used in the last call,
+		 *                      sector and map belong to this id.
+		 *  This code is designed for sequential access and retries.
+		 *  For true random access it may have to be redesigned.
+		 */
+		static int last_reference = -1;
+		static SectorMap map;
+
+		if (segment_id > last_reference) {
+			/*  Skip all sectors before segment_id
+			 */
+			forward_seek_entry(segment_id, &bsm_hash_ptr, &map);
+		} else if (segment_id < last_reference) {
+			/* Skip backwards until begin of buffer or
+			 * first sector in segment_id 
+			 */
+			backwards_seek_entry(segment_id, &bsm_hash_ptr, &map);
+		}		/* segment_id == last_reference : keep map */
+		last_reference = segment_id;
+		return map;
+	} else {
+		return ((SectorMap *) bad_sector_map)[segment_id];
+	}
+}
+
+/*  This is simply here to prevent us from overwriting other kernel
+ *  data. Writes will result in NULL Pointer dereference.
+ */
+void ftape_init_bsm(void)
+{
+	bad_sector_map = NULL;
+	bsm_hash_ptr   = NULL;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-bsm.h b/drivers/char/ftape/lowlevel/ftape-bsm.h
new file mode 100644
index 0000000..ed45465
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-bsm.h
@@ -0,0 +1,66 @@
+#ifndef _FTAPE_BSM_H
+#define _FTAPE_BSM_H
+
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-bsm.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:07 $
+ *
+ *      This file contains definitions for the bad sector map handling
+ *      routines for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/ftape.h>
+#include <linux/ftape-header-segment.h>
+
+#define EMPTY_SEGMENT           (0xffffffff)
+#define FAKE_SEGMENT            (0xfffffffe)
+
+/*  maximum (format code 4) bad sector map size (bytes).
+ */
+#define BAD_SECTOR_MAP_SIZE     (29 * SECTOR_SIZE - 256)
+
+/*  format code 4 bad sector entry, ftape uses this
+ *  internally for all format codes
+ */
+typedef __u32 SectorMap;
+/*  variable and 1100 ft bad sector map entry. These three bytes represent
+ *  a single sector address measured from BOT. 
+ */
+typedef struct NewSectorMap {          
+	__u8 bytes[3];
+} SectorCount;
+
+
+/*
+ *      ftape-bsm.c defined global vars.
+ */
+
+/*
+ *      ftape-bsm.c defined global functions.
+ */
+extern void update_bad_sector_map(__u8 * buffer);
+extern void ftape_extract_bad_sector_map(__u8 * buffer);
+extern SectorMap ftape_get_bad_sector_entry(int segment_id);
+extern __u8 *ftape_find_end_of_bsm_list(__u8 * address);
+extern void ftape_init_bsm(void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-buffer.c b/drivers/char/ftape/lowlevel/ftape-buffer.c
new file mode 100644
index 0000000..54af20c
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-buffer.c
@@ -0,0 +1,129 @@
+/*
+ *      Copyright (C) 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-buffer.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/16 23:33:11 $
+ *
+ *  This file contains the allocator/dealloctor for ftape's dynamic dma
+ *  buffer.
+ */
+
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <asm/dma.h>
+
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-tracing.h"
+
+/*  DMA'able memory allocation stuff.
+ */
+
+static inline void *dmaalloc(size_t size)
+{
+	unsigned long addr;
+
+	if (size == 0) {
+		return NULL;
+	}
+	addr = __get_dma_pages(GFP_KERNEL, get_order(size));
+	if (addr) {
+		struct page *page;
+
+		for (page = virt_to_page(addr); page < virt_to_page(addr+size); page++)
+			SetPageReserved(page);
+	}
+	return (void *)addr;
+}
+
+static inline void dmafree(void *addr, size_t size)
+{
+	if (size > 0) {
+		struct page *page;
+
+		for (page = virt_to_page((unsigned long)addr);
+		     page < virt_to_page((unsigned long)addr+size); page++)
+			ClearPageReserved(page);
+		free_pages((unsigned long) addr, get_order(size));
+	}
+}
+
+static int add_one_buffer(void)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	if (ft_nr_buffers >= FT_MAX_NR_BUFFERS) {
+		TRACE_EXIT -ENOMEM;
+	}
+	ft_buffer[ft_nr_buffers] = kmalloc(sizeof(buffer_struct), GFP_KERNEL);
+	if (ft_buffer[ft_nr_buffers] == NULL) {
+		TRACE_EXIT -ENOMEM;
+	}
+	memset(ft_buffer[ft_nr_buffers], 0, sizeof(buffer_struct));
+	ft_buffer[ft_nr_buffers]->address = dmaalloc(FT_BUFF_SIZE);
+	if (ft_buffer[ft_nr_buffers]->address == NULL) {
+		kfree(ft_buffer[ft_nr_buffers]);
+		ft_buffer[ft_nr_buffers] = NULL;
+		TRACE_EXIT -ENOMEM;
+	}
+	ft_nr_buffers ++;
+	TRACE(ft_t_info, "buffer nr #%d @ %p, dma area @ %p",
+	      ft_nr_buffers,
+	      ft_buffer[ft_nr_buffers-1],
+	      ft_buffer[ft_nr_buffers-1]->address);
+	TRACE_EXIT 0;
+}
+
+static void del_one_buffer(void)
+{
+	TRACE_FUN(ft_t_flow);
+	if (ft_nr_buffers > 0) {
+		TRACE(ft_t_info, "releasing buffer nr #%d @ %p, dma area @ %p",
+		      ft_nr_buffers,
+		      ft_buffer[ft_nr_buffers-1],
+		      ft_buffer[ft_nr_buffers-1]->address);
+		ft_nr_buffers --;
+		dmafree(ft_buffer[ft_nr_buffers]->address, FT_BUFF_SIZE);
+		kfree(ft_buffer[ft_nr_buffers]);
+		ft_buffer[ft_nr_buffers] = NULL;
+	}
+	TRACE_EXIT;
+}
+
+int ftape_set_nr_buffers(int cnt)
+{
+	int delta = cnt - ft_nr_buffers;
+	TRACE_FUN(ft_t_flow);
+
+	if (delta > 0) {
+		while (delta--) {
+			if (add_one_buffer() < 0) {
+				TRACE_EXIT -ENOMEM;
+			}
+		}
+	} else if (delta < 0) {
+		while (delta++) {
+			del_one_buffer();
+		}
+	}
+	ftape_zap_read_buffers();
+	TRACE_EXIT 0;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-buffer.h b/drivers/char/ftape/lowlevel/ftape-buffer.h
new file mode 100644
index 0000000..eec99ce
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-buffer.h
@@ -0,0 +1,32 @@
+#ifndef _FTAPE_BUFFER_H
+#define _FTAPE_BUFFER_H
+
+/*
+ *      Copyright (C) 1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-buffer.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:08 $
+ *
+ *  This file contains the allocator/dealloctor for ftape's dynamic dma
+ *  buffer.
+ */
+
+extern int  ftape_set_nr_buffers(int cnt);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-calibr.c b/drivers/char/ftape/lowlevel/ftape-calibr.c
new file mode 100644
index 0000000..956b258
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-calibr.c
@@ -0,0 +1,276 @@
+/*
+ *      Copyright (C) 1993-1996 Bas Laarhoven.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-calibr.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:08 $
+ *
+ *      GP calibration routine for processor speed dependent
+ *      functions.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/jiffies.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#if defined(__alpha__)
+# include <asm/hwrpb.h>
+#elif defined(__x86_64__)
+# include <asm/msr.h>
+# include <asm/timex.h>
+#elif defined(__i386__)
+# include <linux/timex.h>
+#endif
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-calibr.h"
+#include "../lowlevel/fdc-io.h"
+
+#undef DEBUG
+
+#if !defined(__alpha__) && !defined(__i386__) && !defined(__x86_64__)
+# error Ftape is not implemented for this architecture!
+#endif
+
+#if defined(__alpha__) || defined(__x86_64__)
+static unsigned long ps_per_cycle = 0;
+#endif
+
+static spinlock_t calibr_lock;
+
+/*
+ * Note: On Intel PCs, the clock ticks at 100 Hz (HZ==100) which is
+ * too slow for certain timeouts (and that clock doesn't even tick
+ * when interrupts are disabled).  For that reason, the 8254 timer is
+ * used directly to implement fine-grained timeouts.  However, on
+ * Alpha PCs, the 8254 is *not* used to implement the clock tick
+ * (which is 1024 Hz, normally) and the 8254 timer runs at some
+ * "random" frequency (it seems to run at 18Hz, but it's not safe to
+ * rely on this value).  Instead, we use the Alpha's "rpcc"
+ * instruction to read cycle counts.  As this is a 32 bit counter,
+ * it will overflow only once per 30 seconds (on a 200MHz machine),
+ * which is plenty.
+ */
+
+unsigned int ftape_timestamp(void)
+{
+#if defined(__alpha__)
+	unsigned long r;
+
+	asm volatile ("rpcc %0" : "=r" (r));
+	return r;
+#elif defined(__x86_64__)
+	unsigned long r;
+	rdtscl(r);
+	return r;
+#elif defined(__i386__)
+
+/*
+ * Note that there is some time between counter underflowing and jiffies
+ * increasing, so the code below won't always give correct output.
+ * -Vojtech
+ */
+
+	unsigned long flags;
+	__u16 lo;
+	__u16 hi;
+
+	spin_lock_irqsave(&calibr_lock, flags);
+	outb_p(0x00, 0x43);	/* latch the count ASAP */
+	lo = inb_p(0x40);	/* read the latched count */
+	lo |= inb(0x40) << 8;
+	hi = jiffies;
+	spin_unlock_irqrestore(&calibr_lock, flags);
+	return ((hi + 1) * (unsigned int) LATCH) - lo;  /* downcounter ! */
+#endif
+}
+
+static unsigned int short_ftape_timestamp(void)
+{
+#if defined(__alpha__) || defined(__x86_64__)
+	return ftape_timestamp();
+#elif defined(__i386__)
+	unsigned int count;
+ 	unsigned long flags;
+ 
+	spin_lock_irqsave(&calibr_lock, flags);
+ 	outb_p(0x00, 0x43);	/* latch the count ASAP */
+	count = inb_p(0x40);	/* read the latched count */
+	count |= inb(0x40) << 8;
+	spin_unlock_irqrestore(&calibr_lock, flags);
+	return (LATCH - count);	/* normal: downcounter */
+#endif
+}
+
+static unsigned int diff(unsigned int t0, unsigned int t1)
+{
+#if defined(__alpha__) || defined(__x86_64__)
+	return (t1 - t0);
+#elif defined(__i386__)
+	/*
+	 * This is tricky: to work for both short and full ftape_timestamps
+	 * we'll have to discriminate between these.
+	 * If it _looks_ like short stamps with wrapping around we'll
+	 * asume it are. This will generate a small error if it really
+	 * was a (very large) delta from full ftape_timestamps.
+	 */
+	return (t1 <= t0 && t0 <= LATCH) ? t1 + LATCH - t0 : t1 - t0;
+#endif
+}
+
+static unsigned int usecs(unsigned int count)
+{
+#if defined(__alpha__) || defined(__x86_64__)
+	return (ps_per_cycle * count) / 1000000UL;
+#elif defined(__i386__)
+	return (10000 * count) / ((CLOCK_TICK_RATE + 50) / 100);
+#endif
+}
+
+unsigned int ftape_timediff(unsigned int t0, unsigned int t1)
+{
+	/*
+	 *  Calculate difference in usec for ftape_timestamp results t0 & t1.
+	 *  Note that on the i386 platform with short time-stamps, the
+	 *  maximum allowed timespan is 1/HZ or we'll lose ticks!
+	 */
+	return usecs(diff(t0, t1));
+}
+
+/*      To get an indication of the I/O performance,
+ *      measure the duration of the inb() function.
+ */
+static void time_inb(void)
+{
+	int i;
+	int t0, t1;
+	unsigned long flags;
+	int status;
+	TRACE_FUN(ft_t_any);
+
+	spin_lock_irqsave(&calibr_lock, flags);
+	t0 = short_ftape_timestamp();
+	for (i = 0; i < 1000; ++i) {
+		status = inb(fdc.msr);
+	}
+	t1 = short_ftape_timestamp();
+	spin_unlock_irqrestore(&calibr_lock, flags);
+	TRACE(ft_t_info, "inb() duration: %d nsec", ftape_timediff(t0, t1));
+	TRACE_EXIT;
+}
+
+static void init_clock(void)
+{
+	TRACE_FUN(ft_t_any);
+
+#if defined(__x86_64__)
+	ps_per_cycle = 1000000000UL / cpu_khz;
+#elif defined(__alpha__)
+	extern struct hwrpb_struct *hwrpb;
+	ps_per_cycle = (1000*1000*1000*1000UL) / hwrpb->cycle_freq;
+#endif
+	TRACE_EXIT;
+}
+
+/*
+ *      Input:  function taking int count as parameter.
+ *              pointers to calculated calibration variables.
+ */
+void ftape_calibrate(char *name,
+		    void (*fun) (unsigned int), 
+		    unsigned int *calibr_count, 
+		    unsigned int *calibr_time)
+{
+	static int first_time = 1;
+	int i;
+	unsigned int tc = 0;
+	unsigned int count;
+	unsigned int time;
+#if defined(__i386__)
+	unsigned int old_tc = 0;
+	unsigned int old_count = 1;
+	unsigned int old_time = 1;
+#endif
+	TRACE_FUN(ft_t_flow);
+
+	if (first_time) {             /* get idea of I/O performance */
+		init_clock();
+		time_inb();
+		first_time = 0;
+	}
+	/*    value of timeout must be set so that on very slow systems
+	 *    it will give a time less than one jiffy, and on
+	 *    very fast systems it'll give reasonable precision.
+	 */
+
+	count = 40;
+	for (i = 0; i < 15; ++i) {
+		unsigned int t0;
+		unsigned int t1;
+		unsigned int once;
+		unsigned int multiple;
+		unsigned long flags;
+
+		*calibr_count =
+		*calibr_time = count;	/* set TC to 1 */
+		spin_lock_irqsave(&calibr_lock, flags);
+		fun(0);		/* dummy, get code into cache */
+		t0 = short_ftape_timestamp();
+		fun(0);		/* overhead + one test */
+		t1 = short_ftape_timestamp();
+		once = diff(t0, t1);
+		t0 = short_ftape_timestamp();
+		fun(count);		/* overhead + count tests */
+		t1 = short_ftape_timestamp();
+		multiple = diff(t0, t1);
+		spin_unlock_irqrestore(&calibr_lock, flags);
+		time = ftape_timediff(0, multiple - once);
+		tc = (1000 * time) / (count - 1);
+		TRACE(ft_t_any, "once:%3d us,%6d times:%6d us, TC:%5d ns",
+			usecs(once), count - 1, usecs(multiple), tc);
+#if defined(__alpha__) || defined(__x86_64__)
+		/*
+		 * Increase the calibration count exponentially until the
+		 * calibration time exceeds 100 ms.
+		 */
+		if (time >= 100*1000) {
+			break;
+		}
+#elif defined(__i386__)
+		/*
+		 * increase the count until the resulting time nears 2/HZ,
+		 * then the tc will drop sharply because we lose LATCH counts.
+		 */
+		if (tc <= old_tc / 2) {
+			time = old_time;
+			count = old_count;
+			break;
+		}
+		old_tc = tc;
+		old_count = count;
+		old_time = time;
+#endif
+		count *= 2;
+	}
+	*calibr_count = count - 1;
+	*calibr_time  = time;
+	TRACE(ft_t_info, "TC for `%s()' = %d nsec (at %d counts)",
+	     name, (1000 * *calibr_time) / *calibr_count, *calibr_count);
+	TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-calibr.h b/drivers/char/ftape/lowlevel/ftape-calibr.h
new file mode 100644
index 0000000..0c7e752
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-calibr.h
@@ -0,0 +1,37 @@
+#ifndef _FTAPE_CALIBR_H
+#define _FTAPE_CALIBR_H
+
+/*
+ *      Copyright (C) 1993-1996 Bas Laarhoven.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-calibr.h,v $
+ * $Revision: 1.1 $
+ * $Date: 1997/09/19 09:05:26 $
+ *
+ *      This file contains a gp calibration routine for
+ *      hardware dependent timeout functions.
+ */
+
+extern void ftape_calibrate(char *name,
+			    void (*fun) (unsigned int),
+			    unsigned int *calibr_count,
+			    unsigned int *calibr_time);
+extern unsigned int ftape_timestamp(void);
+extern unsigned int ftape_timediff(unsigned int t0, unsigned int t1);
+
+#endif /* _FTAPE_CALIBR_H */
diff --git a/drivers/char/ftape/lowlevel/ftape-ctl.c b/drivers/char/ftape/lowlevel/ftape-ctl.c
new file mode 100644
index 0000000..32e0439
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-ctl.c
@@ -0,0 +1,897 @@
+/*
+ *      Copyright (C) 1993-1996 Bas Laarhoven,
+ *                    1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ctl.c,v $
+ * $Revision: 1.4 $
+ * $Date: 1997/11/11 14:37:44 $
+ *
+ *      This file contains the non-read/write ftape functions for the
+ *      QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+/* ease porting between pre-2.4.x and later kernels */
+#define vma_get_pgoff(v)      ((v)->vm_pgoff)
+
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-bsm.h"
+
+/*      Global vars.
+ */
+ftape_info ftape_status = {
+/*  vendor information */
+	{ 0, },     /* drive type */
+/*  data rates */
+	500,        /* used data rate */
+	500,        /* drive max rate */
+	500,        /* fdc max rate   */
+/*  drive selection, either FTAPE_SEL_A/B/C/D */
+	-1,     /* drive selection */
+/*  flags set after decode the drive and tape status   */
+	0,          /* formatted */
+	1,          /* no tape */
+	1,          /* write protected */
+	1,          /* new tape */
+/*  values of last queried drive/tape status and error */
+	{{0,}},     /* last error code */
+	{{0,}},     /* drive status, configuration, tape status */
+/*  cartridge geometry */
+        20,         /* tracks_per_tape */
+        102,        /* segments_per_track */
+/*  location of header segments, etc. */
+	-1,     /* used_header_segment */
+	-1,     /* header_segment_1 */
+	-1,     /* header_segment_2 */
+	-1,     /* first_data_segment */
+        -1,     /* last_data_segment */
+/*  the format code as stored in the header segment  */
+	fmt_normal, /* format code */
+/*  the default for the qic std: unknown */
+	-1,
+/*  is tape running? */
+	idle,       /* runner_state */
+/*  is tape reading/writing/verifying/formatting/deleting */
+	idle,       /* driver state */
+/*  flags fatal hardware error */
+	1,          /* failure */
+/*  history record */
+	{ 0, }      /* history record */
+};
+	
+int ftape_segments_per_head     = 1020;
+int ftape_segments_per_cylinder = 4;
+int ftape_init_drive_needed = 1; /* need to be global for ftape_reset_drive()
+				  * in ftape-io.c
+				  */
+
+/*      Local vars.
+ */
+static const vendor_struct vendors[] = QIC117_VENDORS;
+static const wakeup_method methods[] = WAKEUP_METHODS;
+
+const ftape_info *ftape_get_status(void)
+{
+#if defined(STATUS_PARANOYA)
+	static ftape_info get_status;
+
+	get_status = ftape_status;
+	return &get_status;
+#else
+	return &ftape_status; /*  maybe return only a copy of it to assure 
+			       *  read only access
+			       */
+#endif
+}
+
+static int ftape_not_operational(int status)
+{
+	/* return true if status indicates tape can not be used.
+	 */
+	return ((status ^ QIC_STATUS_CARTRIDGE_PRESENT) &
+		(QIC_STATUS_ERROR |
+		 QIC_STATUS_CARTRIDGE_PRESENT |
+		 QIC_STATUS_NEW_CARTRIDGE));
+}
+
+int ftape_seek_to_eot(void)
+{
+	int status;
+	TRACE_FUN(ft_t_any);
+
+	TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),);
+	while ((status & QIC_STATUS_AT_EOT) == 0) {
+		if (ftape_not_operational(status)) {
+			TRACE_EXIT -EIO;
+		}
+		TRACE_CATCH(ftape_command_wait(QIC_PHYSICAL_FORWARD,
+					       ftape_timeout.rewind,&status),);
+	}
+	TRACE_EXIT 0;
+}
+
+int ftape_seek_to_bot(void)
+{
+	int status;
+	TRACE_FUN(ft_t_any);
+
+	TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),);
+	while ((status & QIC_STATUS_AT_BOT) == 0) {
+		if (ftape_not_operational(status)) {
+			TRACE_EXIT -EIO;
+		}
+		TRACE_CATCH(ftape_command_wait(QIC_PHYSICAL_REVERSE,
+					       ftape_timeout.rewind,&status),);
+	}
+	TRACE_EXIT 0;
+}
+
+static int ftape_new_cartridge(void)
+{
+	ft_location.track = -1; /* force seek on first access */
+	ftape_zap_read_buffers();
+	ftape_zap_write_buffers();
+	return 0;
+}
+
+int ftape_abort_operation(void)
+{
+	int result = 0;
+	int status;
+	TRACE_FUN(ft_t_flow);
+
+	if (ft_runner_status == running) {
+		TRACE(ft_t_noise, "aborting runner, waiting");
+		
+		ft_runner_status = do_abort;
+		/* set timeout so that the tape will run to logical EOT
+		 * if we missed the last sector and there are no queue pulses.
+		 */
+		result = ftape_dumb_stop();
+	}
+	if (ft_runner_status != idle) {
+		if (ft_runner_status == do_abort) {
+			TRACE(ft_t_noise, "forcing runner abort");
+		}
+		TRACE(ft_t_noise, "stopping tape");
+		result = ftape_stop_tape(&status);
+		ft_location.known = 0;
+		ft_runner_status  = idle;
+	}
+	ftape_reset_buffer();
+	ftape_zap_read_buffers();
+	ftape_set_state(idle);
+	TRACE_EXIT result;
+}
+
+static int lookup_vendor_id(unsigned int vendor_id)
+{
+	int i = 0;
+
+	while (vendors[i].vendor_id != vendor_id) {
+		if (++i >= NR_ITEMS(vendors)) {
+			return -1;
+		}
+	}
+	return i;
+}
+
+static void ftape_detach_drive(void)
+{
+	TRACE_FUN(ft_t_any);
+
+	TRACE(ft_t_flow, "disabling tape drive and fdc");
+	ftape_put_drive_to_sleep(ft_drive_type.wake_up);
+	fdc_catch_stray_interrupts(1);	/* one always comes */
+	fdc_disable();
+	fdc_release_irq_and_dma();
+	fdc_release_regions();
+	TRACE_EXIT;
+}
+
+static void clear_history(void)
+{
+	ft_history.used = 0;
+	ft_history.id_am_errors =
+		ft_history.id_crc_errors =
+		ft_history.data_am_errors =
+		ft_history.data_crc_errors =
+		ft_history.overrun_errors =
+		ft_history.no_data_errors =
+		ft_history.retries =
+		ft_history.crc_errors =
+		ft_history.crc_failures =
+		ft_history.ecc_failures =
+		ft_history.corrected =
+		ft_history.defects =
+		ft_history.rewinds = 0;
+}
+
+static int ftape_activate_drive(vendor_struct * drive_type)
+{
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+
+	/* If we already know the drive type, wake it up.
+	 * Else try to find out what kind of drive is attached.
+	 */
+	if (drive_type->wake_up != unknown_wake_up) {
+		TRACE(ft_t_flow, "enabling tape drive and fdc");
+		result = ftape_wakeup_drive(drive_type->wake_up);
+		if (result < 0) {
+			TRACE(ft_t_err, "known wakeup method failed");
+		}
+	} else {
+		wake_up_types method;
+		const ft_trace_t old_tracing = TRACE_LEVEL;
+		if (TRACE_LEVEL < ft_t_flow) {
+			SET_TRACE_LEVEL(ft_t_bug);
+		}
+
+		/*  Try to awaken the drive using all known methods.
+		 *  Lower tracing for a while.
+		 */
+		for (method=no_wake_up; method < NR_ITEMS(methods); ++method) {
+			drive_type->wake_up = method;
+#ifdef CONFIG_FT_TWO_DRIVES
+			/*  Test setup for dual drive configuration.
+			 *  /dev/rft2 uses mountain wakeup
+			 *  /dev/rft3 uses colorado wakeup
+			 *  Other systems will use the normal scheme.
+			 */
+			if ((ft_drive_sel < 2)                            ||
+			    (ft_drive_sel == 2 && method == FT_WAKE_UP_1) ||
+			    (ft_drive_sel == 3 && method == FT_WAKE_UP_2)) {
+				result=ftape_wakeup_drive(drive_type->wake_up);
+			} else {
+				result = -EIO;
+			}
+#else
+			result = ftape_wakeup_drive(drive_type->wake_up);
+#endif
+			if (result >= 0) {
+				TRACE(ft_t_warn, "drive wakeup method: %s",
+				      methods[drive_type->wake_up].name);
+				break;
+			}
+		}
+		SET_TRACE_LEVEL(old_tracing);
+
+		if (method >= NR_ITEMS(methods)) {
+			/* no response at all, cannot open this drive */
+			drive_type->wake_up = unknown_wake_up;
+			TRACE(ft_t_err, "no tape drive found !");
+			result = -ENODEV;
+		}
+	}
+	TRACE_EXIT result;
+}
+
+static int ftape_get_drive_status(void)
+{
+	int result;
+	int status;
+	TRACE_FUN(ft_t_flow);
+
+	ft_no_tape = ft_write_protected = 0;
+	/*    Tape drive is activated now.
+	 *    First clear error status if present.
+	 */
+	do {
+		result = ftape_ready_wait(ftape_timeout.reset, &status);
+		if (result < 0) {
+			if (result == -ETIME) {
+				TRACE(ft_t_err, "ftape_ready_wait timeout");
+			} else if (result == -EINTR) {
+				TRACE(ft_t_err, "ftape_ready_wait aborted");
+			} else {
+				TRACE(ft_t_err, "ftape_ready_wait failed");
+			}
+			TRACE_EXIT -EIO;
+		}
+		/*  Clear error condition (drive is ready !)
+		 */
+		if (status & QIC_STATUS_ERROR) {
+			unsigned int error;
+			qic117_cmd_t command;
+
+			TRACE(ft_t_err, "error status set");
+			result = ftape_report_error(&error, &command, 1);
+			if (result < 0) {
+				TRACE(ft_t_err,
+				      "report_error_code failed: %d", result);
+				/* hope it's working next time */
+				ftape_reset_drive();
+				TRACE_EXIT -EIO;
+			} else if (error != 0) {
+				TRACE(ft_t_noise, "error code   : %d", error);
+				TRACE(ft_t_noise, "error command: %d", command);
+			}
+		}
+		if (status & QIC_STATUS_NEW_CARTRIDGE) {
+			unsigned int error;
+			qic117_cmd_t command;
+			const ft_trace_t old_tracing = TRACE_LEVEL;
+			SET_TRACE_LEVEL(ft_t_bug);
+
+			/*  Undocumented feature: Must clear (not present!)
+			 *  error here or we'll fail later.
+			 */
+			ftape_report_error(&error, &command, 1);
+
+			SET_TRACE_LEVEL(old_tracing);
+			TRACE(ft_t_info, "status: new cartridge");
+			ft_new_tape = 1;
+		} else {
+			ft_new_tape = 0;
+		}
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+	} while (status & QIC_STATUS_ERROR);
+	
+	ft_no_tape = !(status & QIC_STATUS_CARTRIDGE_PRESENT);
+	ft_write_protected = (status & QIC_STATUS_WRITE_PROTECT) != 0;
+	if (ft_no_tape) {
+		TRACE(ft_t_warn, "no cartridge present");
+	} else {
+		if (ft_write_protected) {
+			TRACE(ft_t_noise, "Write protected cartridge");
+		}
+	}
+	TRACE_EXIT 0;
+}
+
+static void ftape_log_vendor_id(void)
+{
+	int vendor_index;
+	TRACE_FUN(ft_t_flow);
+
+	ftape_report_vendor_id(&ft_drive_type.vendor_id);
+	vendor_index = lookup_vendor_id(ft_drive_type.vendor_id);
+	if (ft_drive_type.vendor_id == UNKNOWN_VENDOR &&
+	    ft_drive_type.wake_up == wake_up_colorado) {
+		vendor_index = 0;
+		/* hack to get rid of all this mail */
+		ft_drive_type.vendor_id = 0;
+	}
+	if (vendor_index < 0) {
+		/* Unknown vendor id, first time opening device.  The
+		 * drive_type remains set to type found at wakeup
+		 * time, this will probably keep the driver operating
+		 * for this new vendor.  
+		 */
+		TRACE(ft_t_warn, "\n"
+		      KERN_INFO "============ unknown vendor id ===========\n"
+		      KERN_INFO "A new, yet unsupported tape drive is found\n"
+		      KERN_INFO "Please report the following values:\n"
+		      KERN_INFO "   Vendor id     : 0x%04x\n"
+		      KERN_INFO "   Wakeup method : %s\n"
+		      KERN_INFO "And a description of your tape drive\n"
+		      KERN_INFO "to "THE_FTAPE_MAINTAINER"\n"
+		      KERN_INFO "==========================================",
+		      ft_drive_type.vendor_id,
+		      methods[ft_drive_type.wake_up].name);
+		ft_drive_type.speed = 0;		/* unknown */
+	} else {
+		ft_drive_type.name  = vendors[vendor_index].name;
+		ft_drive_type.speed = vendors[vendor_index].speed;
+		TRACE(ft_t_info, "tape drive type: %s", ft_drive_type.name);
+		/* scan all methods for this vendor_id in table */
+		while(ft_drive_type.wake_up != vendors[vendor_index].wake_up) {
+			if (vendor_index < NR_ITEMS(vendors) - 1 &&
+			    vendors[vendor_index + 1].vendor_id 
+			    == 
+			    ft_drive_type.vendor_id) {
+				++vendor_index;
+			} else {
+				break;
+			}
+		}
+		if (ft_drive_type.wake_up != vendors[vendor_index].wake_up) {
+			TRACE(ft_t_warn, "\n"
+		     KERN_INFO "==========================================\n"
+		     KERN_INFO "wakeup type mismatch:\n"
+		     KERN_INFO "found: %s, expected: %s\n"
+		     KERN_INFO "please report this to "THE_FTAPE_MAINTAINER"\n"
+		     KERN_INFO "==========================================",
+			      methods[ft_drive_type.wake_up].name,
+			      methods[vendors[vendor_index].wake_up].name);
+		}
+	}
+	TRACE_EXIT;
+}
+
+void ftape_calc_timeouts(unsigned int qic_std,
+			 unsigned int data_rate,
+			 unsigned int tape_len)
+{
+	int speed;		/* deci-ips ! */
+	int ff_speed;
+	int length;
+	TRACE_FUN(ft_t_any);
+
+	/*                           tape transport speed
+	 *  data rate:        QIC-40   QIC-80   QIC-3010 QIC-3020
+	 *
+	 *    250 Kbps        25 ips     n/a      n/a      n/a
+	 *    500 Kbps        50 ips   34 ips   22.6 ips   n/a
+	 *      1 Mbps          n/a    68 ips   45.2 ips 22.6 ips
+	 *      2 Mbps          n/a      n/a      n/a    45.2 ips
+	 *
+	 *  fast tape transport speed is at least 68 ips.
+	 */
+	switch (qic_std) {
+	case QIC_TAPE_QIC40:
+		speed = (data_rate == 250) ? 250 : 500;
+		break;
+	case QIC_TAPE_QIC80:
+		speed = (data_rate == 500) ? 340 : 680;
+		break;
+	case QIC_TAPE_QIC3010:
+		speed = (data_rate == 500) ? 226 : 452;
+		break;
+	case QIC_TAPE_QIC3020:
+		speed = (data_rate == 1000) ? 226 : 452;
+		break;
+	default:
+		TRACE(ft_t_bug, "Unknown qic_std (bug) ?");
+		speed = 500;
+		break;
+	}
+	if (ft_drive_type.speed == 0) {
+		unsigned long t0;
+		static int dt = 0;     /* keep gcc from complaining */
+		static int first_time = 1;
+
+		/*  Measure the time it takes to wind to EOT and back to BOT.
+		 *  If the tape length is known, calculate the rewind speed.
+		 *  Else keep the time value for calculation of the rewind
+		 *  speed later on, when the length _is_ known.
+		 *  Ask for a report only when length and speed are both known.
+		 */
+		if (first_time) {
+			ftape_seek_to_bot();
+			t0 = jiffies;
+			ftape_seek_to_eot();
+			ftape_seek_to_bot();
+			dt = (int) (((jiffies - t0) * FT_USPT) / 1000);
+			if (dt < 1) {
+				dt = 1;	/* prevent div by zero on failures */
+			}
+			first_time = 0;
+			TRACE(ft_t_info,
+			      "trying to determine seek timeout, got %d msec",
+			      dt);
+		}
+		if (tape_len != 0) {
+			ft_drive_type.speed = 
+				(2 * 12 * tape_len * 1000) / dt;
+			TRACE(ft_t_warn, "\n"
+		     KERN_INFO "==========================================\n"
+		     KERN_INFO "drive type: %s\n"
+		     KERN_INFO "delta time = %d ms, length = %d ft\n"
+		     KERN_INFO "has a maximum tape speed of %d ips\n"
+		     KERN_INFO "please report this to "THE_FTAPE_MAINTAINER"\n"
+		     KERN_INFO "==========================================",
+			      ft_drive_type.name, dt, tape_len, 
+			      ft_drive_type.speed);
+		}
+	}
+	/*  Handle unknown length tapes as very long ones. We'll
+	 *  determine the actual length from a header segment later.
+	 *  This is normal for all modern (Wide,TR1/2/3) formats.
+	 */
+	if (tape_len <= 0) {
+		TRACE(ft_t_noise,
+		      "Unknown tape length, using maximal timeouts");
+		length = QIC_TOP_TAPE_LEN;	/* use worst case values */
+	} else {
+		length = tape_len;		/* use actual values */
+	}
+	if (ft_drive_type.speed == 0) {
+		ff_speed = speed; 
+	} else {
+		ff_speed = ft_drive_type.speed;
+	}
+	/*  time to go from bot to eot at normal speed (data rate):
+	 *  time = (1+delta) * length (ft) * 12 (inch/ft) / speed (ips)
+	 *  delta = 10 % for seek speed, 20 % for rewind speed.
+	 */
+	ftape_timeout.seek = (length * 132 * FT_SECOND) / speed;
+	ftape_timeout.rewind = (length * 144 * FT_SECOND) / (10 * ff_speed);
+	ftape_timeout.reset = 20 * FT_SECOND + ftape_timeout.rewind;
+	TRACE(ft_t_noise, "timeouts for speed = %d, length = %d\n"
+	      KERN_INFO "seek timeout  : %d sec\n"
+	      KERN_INFO "rewind timeout: %d sec\n"
+	      KERN_INFO "reset timeout : %d sec",
+	      speed, length,
+	      (ftape_timeout.seek + 500) / 1000,
+	      (ftape_timeout.rewind + 500) / 1000,
+	      (ftape_timeout.reset + 500) / 1000);
+	TRACE_EXIT;
+}
+
+/* This function calibrates the datarate (i.e. determines the maximal
+ * usable data rate) and sets the global variable ft_qic_std to qic_std
+ *
+ */
+int ftape_calibrate_data_rate(unsigned int qic_std)
+{
+	int rate = ft_fdc_rate_limit;
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	ft_qic_std = qic_std;
+
+	if (ft_qic_std == -1) {
+		TRACE_ABORT(-EIO, ft_t_err,
+		"Unable to determine data rate if QIC standard is unknown");
+	}
+
+	/*  Select highest rate supported by both fdc and drive.
+	 *  Start with highest rate supported by the fdc.
+	 */
+	while (fdc_set_data_rate(rate) < 0 && rate > 250) {
+		rate /= 2;
+	}
+	TRACE(ft_t_info,
+	      "Highest FDC supported data rate: %d Kbps", rate);
+	ft_fdc_max_rate = rate;
+	do {
+		result = ftape_set_data_rate(rate, ft_qic_std);
+	} while (result == -EINVAL && (rate /= 2) > 250);
+	if (result < 0) {
+		TRACE_ABORT(-EIO, ft_t_err, "set datarate failed");
+	}
+	ft_data_rate = rate;
+	TRACE_EXIT 0;
+}
+
+static int ftape_init_drive(void)
+{
+	int status;
+	qic_model model;
+	unsigned int qic_std;
+	unsigned int data_rate;
+	TRACE_FUN(ft_t_flow);
+
+	ftape_init_drive_needed = 0; /* don't retry if this fails ? */
+	TRACE_CATCH(ftape_report_raw_drive_status(&status),);
+	if (status & QIC_STATUS_CARTRIDGE_PRESENT) {
+		if (!(status & QIC_STATUS_AT_BOT)) {
+			/*  Antique drives will get here after a soft reset,
+			 *  modern ones only if the driver is loaded when the
+			 *  tape wasn't rewound properly.
+			 */
+			/* Tape should be at bot if new cartridge ! */
+			ftape_seek_to_bot();
+		}
+		if (!(status & QIC_STATUS_REFERENCED)) {
+			TRACE(ft_t_flow, "starting seek_load_point");
+			TRACE_CATCH(ftape_command_wait(QIC_SEEK_LOAD_POINT,
+						       ftape_timeout.reset,
+						       &status),);
+		}
+	}
+	ft_formatted = (status & QIC_STATUS_REFERENCED) != 0;
+	if (!ft_formatted) {
+		TRACE(ft_t_warn, "Warning: tape is not formatted !");
+	}
+
+	/*  report configuration aborts when ftape_tape_len == -1
+	 *  unknown qic_std is okay if not formatted.
+	 */
+	TRACE_CATCH(ftape_report_configuration(&model,
+					       &data_rate,
+					       &qic_std,
+					       &ftape_tape_len),);
+
+	/*  Maybe add the following to the /proc entry
+	 */
+	TRACE(ft_t_info, "%s drive @ %d Kbps",
+	      (model == prehistoric) ? "prehistoric" :
+	      ((model == pre_qic117c) ? "pre QIC-117C" :
+	       ((model == post_qic117b) ? "post QIC-117B" :
+		"post QIC-117D")), data_rate);
+
+	if (ft_formatted) {
+		/*  initialize ft_used_data_rate to maximum value 
+		 *  and set ft_qic_std
+		 */
+		TRACE_CATCH(ftape_calibrate_data_rate(qic_std),);
+		if (ftape_tape_len == 0) {
+			TRACE(ft_t_info, "unknown length QIC-%s tape",
+			      (ft_qic_std == QIC_TAPE_QIC40) ? "40" :
+			      ((ft_qic_std == QIC_TAPE_QIC80) ? "80" :
+			       ((ft_qic_std == QIC_TAPE_QIC3010) 
+				? "3010" : "3020")));
+		} else {
+			TRACE(ft_t_info, "%d ft. QIC-%s tape", ftape_tape_len,
+			      (ft_qic_std == QIC_TAPE_QIC40) ? "40" :
+			      ((ft_qic_std == QIC_TAPE_QIC80) ? "80" :
+			       ((ft_qic_std == QIC_TAPE_QIC3010)
+				? "3010" : "3020")));
+		}
+		ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len);
+		/* soft write-protect QIC-40/QIC-80 cartridges used with a
+		 * Colorado T3000 drive. Buggy hardware!
+		 */
+		if ((ft_drive_type.vendor_id == 0x011c6) &&
+		    ((ft_qic_std == QIC_TAPE_QIC40 ||
+		      ft_qic_std == QIC_TAPE_QIC80) &&
+		     !ft_write_protected)) {
+			TRACE(ft_t_warn, "\n"
+	KERN_INFO "The famous Colorado T3000 bug:\n"
+	KERN_INFO "%s drives can't write QIC40 and QIC80\n"
+	KERN_INFO "cartridges but don't set the write-protect flag!",
+			      ft_drive_type.name);
+			ft_write_protected = 1;
+		}
+	} else {
+		/*  Doesn't make too much sense to set the data rate
+		 *  because we don't know what to use for the write
+		 *  precompensation.
+		 *  Need to do this again when formatting the cartridge.
+		 */
+		ft_data_rate = data_rate;
+		ftape_calc_timeouts(QIC_TAPE_QIC40,
+				    data_rate,
+				    ftape_tape_len);
+	}
+	ftape_new_cartridge();
+	TRACE_EXIT 0;
+}
+
+static void ftape_munmap(void)
+{
+	int i;
+	TRACE_FUN(ft_t_flow);
+	
+	for (i = 0; i < ft_nr_buffers; i++) {
+		ft_buffer[i]->mmapped = 0;
+	}
+	TRACE_EXIT;
+}
+
+/*   Map the dma buffers into the virtual address range given by vma.
+ *   We only check the caller doesn't map non-existent buffers. We
+ *   don't check for multiple mappings.
+ */
+int ftape_mmap(struct vm_area_struct *vma)
+{
+	int num_buffers;
+	int i;
+	TRACE_FUN(ft_t_flow);
+	
+	if (ft_failure) {
+		TRACE_EXIT -ENODEV;
+	}
+	if (!(vma->vm_flags & (VM_READ|VM_WRITE))) {
+		TRACE_ABORT(-EINVAL, ft_t_err, "Undefined mmap() access");
+	}
+	if (vma_get_pgoff(vma) != 0) {
+		TRACE_ABORT(-EINVAL, ft_t_err, "page offset must be 0");
+	}
+	if ((vma->vm_end - vma->vm_start) % FT_BUFF_SIZE != 0) {
+		TRACE_ABORT(-EINVAL, ft_t_err,
+			    "size = %ld, should be a multiple of %d",
+			    vma->vm_end - vma->vm_start,
+			    FT_BUFF_SIZE);
+	}
+	num_buffers = (vma->vm_end - vma->vm_start) / FT_BUFF_SIZE;
+	if (num_buffers > ft_nr_buffers) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_err, "size = %ld, should be less than %d",
+			    vma->vm_end - vma->vm_start,
+			    ft_nr_buffers * FT_BUFF_SIZE);
+	}
+	if (ft_driver_state != idle) {
+		/* this also clears the buffer states 
+		 */
+		ftape_abort_operation();
+	} else {
+		ftape_reset_buffer();
+	}
+	for (i = 0; i < num_buffers; i++) {
+		unsigned long pfn;
+
+		pfn = virt_to_phys(ft_buffer[i]->address) >> PAGE_SHIFT;
+		TRACE_CATCH(remap_pfn_range(vma, vma->vm_start +
+					     i * FT_BUFF_SIZE,
+					     pfn,
+					     FT_BUFF_SIZE,
+					     vma->vm_page_prot),
+			    _res = -EAGAIN);
+		TRACE(ft_t_noise, "remapped dma buffer @ %p to location @ %p",
+		      ft_buffer[i]->address,
+		      (void *)(vma->vm_start + i * FT_BUFF_SIZE));
+	}
+	for (i = 0; i < num_buffers; i++) {
+		memset(ft_buffer[i]->address, 0xAA, FT_BUFF_SIZE);
+		ft_buffer[i]->mmapped++;
+	}	
+	TRACE_EXIT 0;
+}
+
+static void ftape_init_driver(void); /* forward declaration */
+
+/*      OPEN routine called by kernel-interface code
+ */
+int ftape_enable(int drive_selection)
+{
+	TRACE_FUN(ft_t_any);
+
+	if (ft_drive_sel == -1 || ft_drive_sel != drive_selection) {
+		/* Other selection than last time
+		 */
+		ftape_init_driver();
+	}
+	ft_drive_sel = FTAPE_SEL(drive_selection);
+	ft_failure = 0;
+	TRACE_CATCH(fdc_init(),); /* init & detect fdc */
+	TRACE_CATCH(ftape_activate_drive(&ft_drive_type),
+		    fdc_disable();
+		    fdc_release_irq_and_dma();
+		    fdc_release_regions());
+	TRACE_CATCH(ftape_get_drive_status(), ftape_detach_drive());
+	if (ft_drive_type.vendor_id == UNKNOWN_VENDOR) {
+		ftape_log_vendor_id();
+	}
+	if (ft_new_tape) {
+		ftape_init_drive_needed = 1;
+	}
+	if (!ft_no_tape && ftape_init_drive_needed) {
+		TRACE_CATCH(ftape_init_drive(), ftape_detach_drive());
+	}
+	ftape_munmap(); /* clear the mmap flag */
+	clear_history();
+	TRACE_EXIT 0;
+}
+
+/*   release routine called by the high level interface modules
+ *   zftape or sftape.
+ */
+void ftape_disable(void)
+{
+	int i;
+	TRACE_FUN(ft_t_any);
+
+	for (i = 0; i < ft_nr_buffers; i++) {
+		if (ft_buffer[i]->mmapped) {
+			TRACE(ft_t_noise, "first byte of buffer %d: 0x%02x",
+			      i, *ft_buffer[i]->address);
+		}
+	}
+	if (sigtestsetmask(&current->pending.signal, _DONT_BLOCK) && 
+	    !(sigtestsetmask(&current->pending.signal, _NEVER_BLOCK)) &&
+	    ftape_tape_running) {
+		TRACE(ft_t_warn,
+		      "Interrupted by fatal signal and tape still running");
+		ftape_dumb_stop();
+		ftape_abort_operation(); /* it's annoying */
+	} else {
+		ftape_set_state(idle);
+	}
+	ftape_detach_drive();
+	if (ft_history.used) {
+		TRACE(ft_t_info, "== Non-fatal errors this run: ==");
+		TRACE(ft_t_info, "fdc isr statistics:\n"
+		      KERN_INFO " id_am_errors     : %3d\n"
+		      KERN_INFO " id_crc_errors    : %3d\n"
+		      KERN_INFO " data_am_errors   : %3d\n"
+		      KERN_INFO " data_crc_errors  : %3d\n"
+		      KERN_INFO " overrun_errors   : %3d\n"
+		      KERN_INFO " no_data_errors   : %3d\n"
+		      KERN_INFO " retries          : %3d",
+		      ft_history.id_am_errors,   ft_history.id_crc_errors,
+		      ft_history.data_am_errors, ft_history.data_crc_errors,
+		      ft_history.overrun_errors, ft_history.no_data_errors,
+		      ft_history.retries);
+		if (ft_history.used & 1) {
+			TRACE(ft_t_info, "ecc statistics:\n"
+			      KERN_INFO " crc_errors       : %3d\n"
+			      KERN_INFO " crc_failures     : %3d\n"
+			      KERN_INFO " ecc_failures     : %3d\n"
+			      KERN_INFO " sectors corrected: %3d",
+			      ft_history.crc_errors,   ft_history.crc_failures,
+			      ft_history.ecc_failures, ft_history.corrected);
+		}
+		if (ft_history.defects > 0) {
+			TRACE(ft_t_warn, "Warning: %d media defects!",
+			      ft_history.defects);
+		}
+		if (ft_history.rewinds > 0) {
+			TRACE(ft_t_info, "tape motion statistics:\n"
+			      KERN_INFO "repositions       : %3d",
+			      ft_history.rewinds);
+		}
+	}
+	ft_failure = 1;
+	TRACE_EXIT;
+}
+
+static void ftape_init_driver(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	ft_drive_type.vendor_id = UNKNOWN_VENDOR;
+	ft_drive_type.speed     = 0;
+	ft_drive_type.wake_up   = unknown_wake_up;
+	ft_drive_type.name      = "Unknown";
+
+	ftape_timeout.seek      = 650 * FT_SECOND;
+	ftape_timeout.reset     = 670 * FT_SECOND;
+	ftape_timeout.rewind    = 650 * FT_SECOND;
+	ftape_timeout.head_seek =  15 * FT_SECOND;
+	ftape_timeout.stop      =   5 * FT_SECOND;
+	ftape_timeout.pause     =  16 * FT_SECOND;
+
+	ft_qic_std             = -1;
+	ftape_tape_len         = 0;  /* unknown */
+	ftape_current_command  = 0;
+	ftape_current_cylinder = -1;
+
+	ft_segments_per_track       = 102;
+	ftape_segments_per_head     = 1020;
+	ftape_segments_per_cylinder = 4;
+	ft_tracks_per_tape          = 20;
+
+	ft_failure = 1;
+
+	ft_formatted       = 0;
+	ft_no_tape         = 1;
+	ft_write_protected = 1;
+	ft_new_tape        = 1;
+
+	ft_driver_state = idle;
+
+	ft_data_rate = 
+		ft_fdc_max_rate   = 500;
+	ft_drive_max_rate = 0; /* triggers set_rate_test() */
+
+	ftape_init_drive_needed = 1;
+
+	ft_header_segment_1    = -1;
+	ft_header_segment_2    = -1;
+	ft_used_header_segment = -1;
+	ft_first_data_segment  = -1;
+	ft_last_data_segment   = -1;
+
+	ft_location.track = -1;
+	ft_location.known = 0;
+
+	ftape_tape_running = 0;
+	ftape_might_be_off_track = 1;
+
+	ftape_new_cartridge();	/* init some tape related variables */
+	ftape_init_bsm();
+	TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-ctl.h b/drivers/char/ftape/lowlevel/ftape-ctl.h
new file mode 100644
index 0000000..5f5e30b
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-ctl.h
@@ -0,0 +1,162 @@
+#ifndef _FTAPE_CTL_H
+#define _FTAPE_CTL_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ctl.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:09 $
+ *
+ *      This file contains the non-standard IOCTL related definitions
+ *      for the QIC-40/80/3010/3020 floppy-tape driver "ftape" for
+ *      Linux.
+ */
+
+#include <linux/ioctl.h>
+#include <linux/mtio.h>
+#include <linux/ftape-vendors.h>
+
+#include "../lowlevel/ftape-rw.h"
+#include <linux/ftape-header-segment.h>
+
+typedef struct {
+	int used;		/* any reading or writing done */
+	/* isr statistics */
+	unsigned int id_am_errors;	/* id address mark not found */
+	unsigned int id_crc_errors;	/* crc error in id address mark */
+	unsigned int data_am_errors;	/* data address mark not found */
+	unsigned int data_crc_errors;	/* crc error in data field */
+	unsigned int overrun_errors;	/* fdc access timing problem */
+	unsigned int no_data_errors;	/* sector not found */
+	unsigned int retries;	/* number of tape retries */
+	/* ecc statistics */
+	unsigned int crc_errors;	/* crc error in data */
+	unsigned int crc_failures;	/* bad data without crc error */
+	unsigned int ecc_failures;	/* failed to correct */
+	unsigned int corrected;	/* total sectors corrected */
+	/* general statistics */
+	unsigned int rewinds;	/* number of tape rewinds */
+	unsigned int defects;	/* bad sectors due to media defects */
+} history_record;
+
+/* this structure contains * ALL * information that we want
+ * our child modules to know about, but don't want them to
+ * modify. 
+ */
+typedef struct {
+	/*  vendor information */
+	vendor_struct fti_drive_type;
+	/*  data rates */
+	unsigned int fti_used_data_rate;
+	unsigned int fti_drive_max_rate;
+	unsigned int fti_fdc_max_rate;
+	/*  drive selection, either FTAPE_SEL_A/B/C/D */
+	int fti_drive_sel;      
+	/*  flags set after decode the drive and tape status   */
+	unsigned int fti_formatted      :1;
+	unsigned int fti_no_tape        :1;
+	unsigned int fti_write_protected:1;
+	unsigned int fti_new_tape       :1;
+	/*  values of last queried drive/tape status and error */
+	ft_drive_error  fti_last_error;
+	ft_drive_status fti_last_status;
+	/*  cartridge geometry */
+	unsigned int fti_tracks_per_tape;
+	unsigned int fti_segments_per_track;
+	/*  location of header segments, etc. */
+	int fti_used_header_segment;
+	int fti_header_segment_1;
+	int fti_header_segment_2;
+	int fti_first_data_segment;
+	int fti_last_data_segment;
+	/*  the format code as stored in the header segment  */
+	ft_format_type  fti_format_code;
+	/*  the following is the sole reason for the ftape_set_status() call */
+	unsigned int fti_qic_std;
+	/*  is tape running? */
+	volatile enum runner_status_enum fti_runner_status;
+	/*  is tape reading/writing/verifying/formatting/deleting */
+	buffer_state_enum fti_state;
+	/*  flags fatal hardware error */
+	unsigned int fti_failure:1;
+	/*  history record */
+	history_record fti_history;
+} ftape_info;
+
+/* vendor information */
+#define ft_drive_type          ftape_status.fti_drive_type
+/*  data rates */
+#define ft_data_rate           ftape_status.fti_used_data_rate
+#define ft_drive_max_rate      ftape_status.fti_drive_max_rate
+#define ft_fdc_max_rate        ftape_status.fti_fdc_max_rate
+/*  drive selection, either FTAPE_SEL_A/B/C/D */
+#define ft_drive_sel           ftape_status.fti_drive_sel
+/*  flags set after decode the drive and tape status   */
+#define ft_formatted           ftape_status.fti_formatted
+#define ft_no_tape             ftape_status.fti_no_tape
+#define ft_write_protected     ftape_status.fti_write_protected
+#define ft_new_tape            ftape_status.fti_new_tape
+/*  values of last queried drive/tape status and error */
+#define ft_last_error          ftape_status.fti_last_error
+#define ft_last_status         ftape_status.fti_last_status
+/*  cartridge geometry */
+#define ft_tracks_per_tape     ftape_status.fti_tracks_per_tape
+#define ft_segments_per_track  ftape_status.fti_segments_per_track
+/*  the format code as stored in the header segment  */
+#define ft_format_code         ftape_status.fti_format_code
+/*  the qic status as returned by report drive configuration */
+#define ft_qic_std             ftape_status.fti_qic_std
+#define ft_used_header_segment ftape_status.fti_used_header_segment
+#define ft_header_segment_1    ftape_status.fti_header_segment_1
+#define ft_header_segment_2    ftape_status.fti_header_segment_2
+#define ft_first_data_segment  ftape_status.fti_first_data_segment
+#define ft_last_data_segment   ftape_status.fti_last_data_segment
+/*  is tape running? */
+#define ft_runner_status       ftape_status.fti_runner_status
+/*  is tape reading/writing/verifying/formatting/deleting */
+#define ft_driver_state        ftape_status.fti_state
+/*  flags fatal hardware error */
+#define ft_failure             ftape_status.fti_failure
+/*  history record */
+#define ft_history             ftape_status.fti_history
+
+/*
+ *      ftape-ctl.c defined global vars.
+ */
+extern ftape_info ftape_status;
+extern int ftape_segments_per_head;
+extern int ftape_segments_per_cylinder;
+extern int ftape_init_drive_needed;
+
+/*
+ *      ftape-ctl.c defined global functions.
+ */
+extern int  ftape_mmap(struct vm_area_struct *vma);
+extern int  ftape_enable(int drive_selection);
+extern void ftape_disable(void);
+extern int  ftape_seek_to_bot(void);
+extern int  ftape_seek_to_eot(void);
+extern int  ftape_abort_operation(void);
+extern void ftape_calc_timeouts(unsigned int qic_std,
+				 unsigned int data_rate,
+				 unsigned int tape_len);
+extern int  ftape_calibrate_data_rate(unsigned int qic_std);
+extern const ftape_info *ftape_get_status(void);
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-ecc.c b/drivers/char/ftape/lowlevel/ftape-ecc.c
new file mode 100644
index 0000000..e5632f6
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-ecc.c
@@ -0,0 +1,853 @@
+/*
+ *
+ *      Copyright (c) 1993 Ning and David Mosberger.
+ 
+ This is based on code originally written by Bas Laarhoven (bas@vimec.nl)
+ and David L. Brown, Jr., and incorporates improvements suggested by
+ Kai Harrekilde-Petersen.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ecc.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/05 19:18:10 $
+ *
+ *      This file contains the Reed-Solomon error correction code 
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/ftape.h>
+
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-ecc.h"
+
+/* Machines that are big-endian should define macro BIG_ENDIAN.
+ * Unfortunately, there doesn't appear to be a standard include file
+ * that works for all OSs.
+ */
+
+#if defined(__sparc__) || defined(__hppa)
+#define BIG_ENDIAN
+#endif				/* __sparc__ || __hppa */
+
+#if defined(__mips__)
+#error Find a smart way to determine the Endianness of the MIPS CPU
+#endif
+
+/* Notice: to minimize the potential for confusion, we use r to
+ *         denote the independent variable of the polynomials in the
+ *         Galois Field GF(2^8).  We reserve x for polynomials that
+ *         that have coefficients in GF(2^8).
+ *         
+ * The Galois Field in which coefficient arithmetic is performed are
+ * the polynomials over Z_2 (i.e., 0 and 1) modulo the irreducible
+ * polynomial f(r), where f(r)=r^8 + r^7 + r^2 + r + 1.  A polynomial
+ * is represented as a byte with the MSB as the coefficient of r^7 and
+ * the LSB as the coefficient of r^0.  For example, the binary
+ * representation of f(x) is 0x187 (of course, this doesn't fit into 8
+ * bits).  In this field, the polynomial r is a primitive element.
+ * That is, r^i with i in 0,...,255 enumerates all elements in the
+ * field.
+ *
+ * The generator polynomial for the QIC-80 ECC is
+ *
+ *      g(x) = x^3 + r^105*x^2 + r^105*x + 1
+ *
+ * which can be factored into:
+ *
+ *      g(x) = (x-r^-1)(x-r^0)(x-r^1)
+ *
+ * the byte representation of the coefficients are:
+ *
+ *      r^105 = 0xc0
+ *      r^-1  = 0xc3
+ *      r^0   = 0x01
+ *      r^1   = 0x02
+ *
+ * Notice that r^-1 = r^254 as exponent arithmetic is performed
+ * modulo 2^8-1 = 255.
+ *
+ * For more information on Galois Fields and Reed-Solomon codes, refer
+ * to any good book.  I found _An Introduction to Error Correcting
+ * Codes with Applications_ by S. A. Vanstone and P. C. van Oorschot
+ * to be a good introduction into the former.  _CODING THEORY: The
+ * Essentials_ I found very useful for its concise description of
+ * Reed-Solomon encoding/decoding.
+ *
+ */
+
+typedef __u8 Matrix[3][3];
+
+/*
+ * gfpow[] is defined such that gfpow[i] returns r^i if
+ * i is in the range [0..255].
+ */
+static const __u8 gfpow[] =
+{
+	0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
+	0x87, 0x89, 0x95, 0xad, 0xdd, 0x3d, 0x7a, 0xf4,
+	0x6f, 0xde, 0x3b, 0x76, 0xec, 0x5f, 0xbe, 0xfb,
+	0x71, 0xe2, 0x43, 0x86, 0x8b, 0x91, 0xa5, 0xcd,
+	0x1d, 0x3a, 0x74, 0xe8, 0x57, 0xae, 0xdb, 0x31,
+	0x62, 0xc4, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 0x67,
+	0xce, 0x1b, 0x36, 0x6c, 0xd8, 0x37, 0x6e, 0xdc,
+	0x3f, 0x7e, 0xfc, 0x7f, 0xfe, 0x7b, 0xf6, 0x6b,
+	0xd6, 0x2b, 0x56, 0xac, 0xdf, 0x39, 0x72, 0xe4,
+	0x4f, 0x9e, 0xbb, 0xf1, 0x65, 0xca, 0x13, 0x26,
+	0x4c, 0x98, 0xb7, 0xe9, 0x55, 0xaa, 0xd3, 0x21,
+	0x42, 0x84, 0x8f, 0x99, 0xb5, 0xed, 0x5d, 0xba,
+	0xf3, 0x61, 0xc2, 0x03, 0x06, 0x0c, 0x18, 0x30,
+	0x60, 0xc0, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0,
+	0x47, 0x8e, 0x9b, 0xb1, 0xe5, 0x4d, 0x9a, 0xb3,
+	0xe1, 0x45, 0x8a, 0x93, 0xa1, 0xc5, 0x0d, 0x1a,
+	0x34, 0x68, 0xd0, 0x27, 0x4e, 0x9c, 0xbf, 0xf9,
+	0x75, 0xea, 0x53, 0xa6, 0xcb, 0x11, 0x22, 0x44,
+	0x88, 0x97, 0xa9, 0xd5, 0x2d, 0x5a, 0xb4, 0xef,
+	0x59, 0xb2, 0xe3, 0x41, 0x82, 0x83, 0x81, 0x85,
+	0x8d, 0x9d, 0xbd, 0xfd, 0x7d, 0xfa, 0x73, 0xe6,
+	0x4b, 0x96, 0xab, 0xd1, 0x25, 0x4a, 0x94, 0xaf,
+	0xd9, 0x35, 0x6a, 0xd4, 0x2f, 0x5e, 0xbc, 0xff,
+	0x79, 0xf2, 0x63, 0xc6, 0x0b, 0x16, 0x2c, 0x58,
+	0xb0, 0xe7, 0x49, 0x92, 0xa3, 0xc1, 0x05, 0x0a,
+	0x14, 0x28, 0x50, 0xa0, 0xc7, 0x09, 0x12, 0x24,
+	0x48, 0x90, 0xa7, 0xc9, 0x15, 0x2a, 0x54, 0xa8,
+	0xd7, 0x29, 0x52, 0xa4, 0xcf, 0x19, 0x32, 0x64,
+	0xc8, 0x17, 0x2e, 0x5c, 0xb8, 0xf7, 0x69, 0xd2,
+	0x23, 0x46, 0x8c, 0x9f, 0xb9, 0xf5, 0x6d, 0xda,
+	0x33, 0x66, 0xcc, 0x1f, 0x3e, 0x7c, 0xf8, 0x77,
+	0xee, 0x5b, 0xb6, 0xeb, 0x51, 0xa2, 0xc3, 0x01
+};
+
+/*
+ * This is a log table.  That is, gflog[r^i] returns i (modulo f(r)).
+ * gflog[0] is undefined and the first element is therefore not valid.
+ */
+static const __u8 gflog[256] =
+{
+	0xff, 0x00, 0x01, 0x63, 0x02, 0xc6, 0x64, 0x6a,
+	0x03, 0xcd, 0xc7, 0xbc, 0x65, 0x7e, 0x6b, 0x2a,
+	0x04, 0x8d, 0xce, 0x4e, 0xc8, 0xd4, 0xbd, 0xe1,
+	0x66, 0xdd, 0x7f, 0x31, 0x6c, 0x20, 0x2b, 0xf3,
+	0x05, 0x57, 0x8e, 0xe8, 0xcf, 0xac, 0x4f, 0x83,
+	0xc9, 0xd9, 0xd5, 0x41, 0xbe, 0x94, 0xe2, 0xb4,
+	0x67, 0x27, 0xde, 0xf0, 0x80, 0xb1, 0x32, 0x35,
+	0x6d, 0x45, 0x21, 0x12, 0x2c, 0x0d, 0xf4, 0x38,
+	0x06, 0x9b, 0x58, 0x1a, 0x8f, 0x79, 0xe9, 0x70,
+	0xd0, 0xc2, 0xad, 0xa8, 0x50, 0x75, 0x84, 0x48,
+	0xca, 0xfc, 0xda, 0x8a, 0xd6, 0x54, 0x42, 0x24,
+	0xbf, 0x98, 0x95, 0xf9, 0xe3, 0x5e, 0xb5, 0x15,
+	0x68, 0x61, 0x28, 0xba, 0xdf, 0x4c, 0xf1, 0x2f,
+	0x81, 0xe6, 0xb2, 0x3f, 0x33, 0xee, 0x36, 0x10,
+	0x6e, 0x18, 0x46, 0xa6, 0x22, 0x88, 0x13, 0xf7,
+	0x2d, 0xb8, 0x0e, 0x3d, 0xf5, 0xa4, 0x39, 0x3b,
+	0x07, 0x9e, 0x9c, 0x9d, 0x59, 0x9f, 0x1b, 0x08,
+	0x90, 0x09, 0x7a, 0x1c, 0xea, 0xa0, 0x71, 0x5a,
+	0xd1, 0x1d, 0xc3, 0x7b, 0xae, 0x0a, 0xa9, 0x91,
+	0x51, 0x5b, 0x76, 0x72, 0x85, 0xa1, 0x49, 0xeb,
+	0xcb, 0x7c, 0xfd, 0xc4, 0xdb, 0x1e, 0x8b, 0xd2,
+	0xd7, 0x92, 0x55, 0xaa, 0x43, 0x0b, 0x25, 0xaf,
+	0xc0, 0x73, 0x99, 0x77, 0x96, 0x5c, 0xfa, 0x52,
+	0xe4, 0xec, 0x5f, 0x4a, 0xb6, 0xa2, 0x16, 0x86,
+	0x69, 0xc5, 0x62, 0xfe, 0x29, 0x7d, 0xbb, 0xcc,
+	0xe0, 0xd3, 0x4d, 0x8c, 0xf2, 0x1f, 0x30, 0xdc,
+	0x82, 0xab, 0xe7, 0x56, 0xb3, 0x93, 0x40, 0xd8,
+	0x34, 0xb0, 0xef, 0x26, 0x37, 0x0c, 0x11, 0x44,
+	0x6f, 0x78, 0x19, 0x9a, 0x47, 0x74, 0xa7, 0xc1,
+	0x23, 0x53, 0x89, 0xfb, 0x14, 0x5d, 0xf8, 0x97,
+	0x2e, 0x4b, 0xb9, 0x60, 0x0f, 0xed, 0x3e, 0xe5,
+	0xf6, 0x87, 0xa5, 0x17, 0x3a, 0xa3, 0x3c, 0xb7
+};
+
+/* This is a multiplication table for the factor 0xc0 (i.e., r^105 (mod f(r)).
+ * gfmul_c0[f] returns r^105 * f(r) (modulo f(r)).
+ */
+static const __u8 gfmul_c0[256] =
+{
+	0x00, 0xc0, 0x07, 0xc7, 0x0e, 0xce, 0x09, 0xc9,
+	0x1c, 0xdc, 0x1b, 0xdb, 0x12, 0xd2, 0x15, 0xd5,
+	0x38, 0xf8, 0x3f, 0xff, 0x36, 0xf6, 0x31, 0xf1,
+	0x24, 0xe4, 0x23, 0xe3, 0x2a, 0xea, 0x2d, 0xed,
+	0x70, 0xb0, 0x77, 0xb7, 0x7e, 0xbe, 0x79, 0xb9,
+	0x6c, 0xac, 0x6b, 0xab, 0x62, 0xa2, 0x65, 0xa5,
+	0x48, 0x88, 0x4f, 0x8f, 0x46, 0x86, 0x41, 0x81,
+	0x54, 0x94, 0x53, 0x93, 0x5a, 0x9a, 0x5d, 0x9d,
+	0xe0, 0x20, 0xe7, 0x27, 0xee, 0x2e, 0xe9, 0x29,
+	0xfc, 0x3c, 0xfb, 0x3b, 0xf2, 0x32, 0xf5, 0x35,
+	0xd8, 0x18, 0xdf, 0x1f, 0xd6, 0x16, 0xd1, 0x11,
+	0xc4, 0x04, 0xc3, 0x03, 0xca, 0x0a, 0xcd, 0x0d,
+	0x90, 0x50, 0x97, 0x57, 0x9e, 0x5e, 0x99, 0x59,
+	0x8c, 0x4c, 0x8b, 0x4b, 0x82, 0x42, 0x85, 0x45,
+	0xa8, 0x68, 0xaf, 0x6f, 0xa6, 0x66, 0xa1, 0x61,
+	0xb4, 0x74, 0xb3, 0x73, 0xba, 0x7a, 0xbd, 0x7d,
+	0x47, 0x87, 0x40, 0x80, 0x49, 0x89, 0x4e, 0x8e,
+	0x5b, 0x9b, 0x5c, 0x9c, 0x55, 0x95, 0x52, 0x92,
+	0x7f, 0xbf, 0x78, 0xb8, 0x71, 0xb1, 0x76, 0xb6,
+	0x63, 0xa3, 0x64, 0xa4, 0x6d, 0xad, 0x6a, 0xaa,
+	0x37, 0xf7, 0x30, 0xf0, 0x39, 0xf9, 0x3e, 0xfe,
+	0x2b, 0xeb, 0x2c, 0xec, 0x25, 0xe5, 0x22, 0xe2,
+	0x0f, 0xcf, 0x08, 0xc8, 0x01, 0xc1, 0x06, 0xc6,
+	0x13, 0xd3, 0x14, 0xd4, 0x1d, 0xdd, 0x1a, 0xda,
+	0xa7, 0x67, 0xa0, 0x60, 0xa9, 0x69, 0xae, 0x6e,
+	0xbb, 0x7b, 0xbc, 0x7c, 0xb5, 0x75, 0xb2, 0x72,
+	0x9f, 0x5f, 0x98, 0x58, 0x91, 0x51, 0x96, 0x56,
+	0x83, 0x43, 0x84, 0x44, 0x8d, 0x4d, 0x8a, 0x4a,
+	0xd7, 0x17, 0xd0, 0x10, 0xd9, 0x19, 0xde, 0x1e,
+	0xcb, 0x0b, 0xcc, 0x0c, 0xc5, 0x05, 0xc2, 0x02,
+	0xef, 0x2f, 0xe8, 0x28, 0xe1, 0x21, 0xe6, 0x26,
+	0xf3, 0x33, 0xf4, 0x34, 0xfd, 0x3d, 0xfa, 0x3a
+};
+
+
+/* Returns V modulo 255 provided V is in the range -255,-254,...,509.
+ */
+static inline __u8 mod255(int v)
+{
+	if (v > 0) {
+		if (v < 255) {
+			return v;
+		} else {
+			return v - 255;
+		}
+	} else {
+		return v + 255;
+	}
+}
+
+
+/* Add two numbers in the field.  Addition in this field is equivalent
+ * to a bit-wise exclusive OR operation---subtraction is therefore
+ * identical to addition.
+ */
+static inline __u8 gfadd(__u8 a, __u8 b)
+{
+	return a ^ b;
+}
+
+
+/* Add two vectors of numbers in the field.  Each byte in A and B gets
+ * added individually.
+ */
+static inline unsigned long gfadd_long(unsigned long a, unsigned long b)
+{
+	return a ^ b;
+}
+
+
+/* Multiply two numbers in the field:
+ */
+static inline __u8 gfmul(__u8 a, __u8 b)
+{
+	if (a && b) {
+		return gfpow[mod255(gflog[a] + gflog[b])];
+	} else {
+		return 0;
+	}
+}
+
+
+/* Just like gfmul, except we have already looked up the log of the
+ * second number.
+ */
+static inline __u8 gfmul_exp(__u8 a, int b)
+{
+	if (a) {
+		return gfpow[mod255(gflog[a] + b)];
+	} else {
+		return 0;
+	}
+}
+
+
+/* Just like gfmul_exp, except that A is a vector of numbers.  That
+ * is, each byte in A gets multiplied by gfpow[mod255(B)].
+ */
+static inline unsigned long gfmul_exp_long(unsigned long a, int b)
+{
+	__u8 t;
+
+	if (sizeof(long) == 4) {
+		return (
+		((t = (__u32)a >> 24 & 0xff) ?
+		 (((__u32) gfpow[mod255(gflog[t] + b)]) << 24) : 0) |
+		((t = (__u32)a >> 16 & 0xff) ?
+		 (((__u32) gfpow[mod255(gflog[t] + b)]) << 16) : 0) |
+		((t = (__u32)a >> 8 & 0xff) ?
+		 (((__u32) gfpow[mod255(gflog[t] + b)]) << 8) : 0) |
+		((t = (__u32)a >> 0 & 0xff) ?
+		 (((__u32) gfpow[mod255(gflog[t] + b)]) << 0) : 0));
+	} else if (sizeof(long) == 8) {
+		return (
+		((t = (__u64)a >> 56 & 0xff) ?
+		 (((__u64) gfpow[mod255(gflog[t] + b)]) << 56) : 0) |
+		((t = (__u64)a >> 48 & 0xff) ?
+		 (((__u64) gfpow[mod255(gflog[t] + b)]) << 48) : 0) |
+		((t = (__u64)a >> 40 & 0xff) ?
+		 (((__u64) gfpow[mod255(gflog[t] + b)]) << 40) : 0) |
+		((t = (__u64)a >> 32 & 0xff) ?
+		 (((__u64) gfpow[mod255(gflog[t] + b)]) << 32) : 0) |
+		((t = (__u64)a >> 24 & 0xff) ?
+		 (((__u64) gfpow[mod255(gflog[t] + b)]) << 24) : 0) |
+		((t = (__u64)a >> 16 & 0xff) ?
+		 (((__u64) gfpow[mod255(gflog[t] + b)]) << 16) : 0) |
+		((t = (__u64)a >> 8 & 0xff) ?
+		 (((__u64) gfpow[mod255(gflog[t] + b)]) << 8) : 0) |
+		((t = (__u64)a >> 0 & 0xff) ?
+		 (((__u64) gfpow[mod255(gflog[t] + b)]) << 0) : 0));
+	} else {
+		TRACE_FUN(ft_t_any);
+		TRACE_ABORT(-1, ft_t_err, "Error: size of long is %d bytes",
+			    (int)sizeof(long));
+	}
+}
+
+
+/* Divide two numbers in the field.  Returns a/b (modulo f(x)).
+ */
+static inline __u8 gfdiv(__u8 a, __u8 b)
+{
+	if (!b) {
+		TRACE_FUN(ft_t_any);
+		TRACE_ABORT(0xff, ft_t_bug, "Error: division by zero");
+	} else if (a == 0) {
+		return 0;
+	} else {
+		return gfpow[mod255(gflog[a] - gflog[b])];
+	}
+}
+
+
+/* The following functions return the inverse of the matrix of the
+ * linear system that needs to be solved to determine the error
+ * magnitudes.  The first deals with matrices of rank 3, while the
+ * second deals with matrices of rank 2.  The error indices are passed
+ * in arguments L0,..,L2 (0=first sector, 31=last sector).  The error
+ * indices must be sorted in ascending order, i.e., L0<L1<L2.
+ *
+ * The linear system that needs to be solved for the error magnitudes
+ * is A * b = s, where s is the known vector of syndromes, b is the
+ * vector of error magnitudes and A in the ORDER=3 case:
+ *
+ *    A_3 = {{1/r^L[0], 1/r^L[1], 1/r^L[2]},
+ *          {        1,        1,        1},
+ *          { r^L[0], r^L[1], r^L[2]}} 
+ */
+static inline int gfinv3(__u8 l0,
+			 __u8 l1, 
+			 __u8 l2, 
+			 Matrix Ainv)
+{
+	__u8 det;
+	__u8 t20, t10, t21, t12, t01, t02;
+	int log_det;
+
+	/* compute some intermediate results: */
+	t20 = gfpow[l2 - l0];	        /* t20 = r^l2/r^l0 */
+	t10 = gfpow[l1 - l0];	        /* t10 = r^l1/r^l0 */
+	t21 = gfpow[l2 - l1];	        /* t21 = r^l2/r^l1 */
+	t12 = gfpow[l1 - l2 + 255];	/* t12 = r^l1/r^l2 */
+	t01 = gfpow[l0 - l1 + 255];	/* t01 = r^l0/r^l1 */
+	t02 = gfpow[l0 - l2 + 255];	/* t02 = r^l0/r^l2 */
+	/* Calculate the determinant of matrix A_3^-1 (sometimes
+	 * called the Vandermonde determinant):
+	 */
+	det = gfadd(t20, gfadd(t10, gfadd(t21, gfadd(t12, gfadd(t01, t02)))));
+	if (!det) {
+		TRACE_FUN(ft_t_any);
+		TRACE_ABORT(0, ft_t_err,
+			   "Inversion failed (3 CRC errors, >0 CRC failures)");
+	}
+	log_det = 255 - gflog[det];
+
+	/* Now, calculate all of the coefficients:
+	 */
+	Ainv[0][0]= gfmul_exp(gfadd(gfpow[l1], gfpow[l2]), log_det);
+	Ainv[0][1]= gfmul_exp(gfadd(t21, t12), log_det);
+	Ainv[0][2]= gfmul_exp(gfadd(gfpow[255 - l1], gfpow[255 - l2]),log_det);
+
+	Ainv[1][0]= gfmul_exp(gfadd(gfpow[l0], gfpow[l2]), log_det);
+	Ainv[1][1]= gfmul_exp(gfadd(t20, t02), log_det);
+	Ainv[1][2]= gfmul_exp(gfadd(gfpow[255 - l0], gfpow[255 - l2]),log_det);
+
+	Ainv[2][0]= gfmul_exp(gfadd(gfpow[l0], gfpow[l1]), log_det);
+	Ainv[2][1]= gfmul_exp(gfadd(t10, t01), log_det);
+	Ainv[2][2]= gfmul_exp(gfadd(gfpow[255 - l0], gfpow[255 - l1]),log_det);
+
+	return 1;
+}
+
+
+static inline int gfinv2(__u8 l0, __u8 l1, Matrix Ainv)
+{
+	__u8 det;
+	__u8 t1, t2;
+	int log_det;
+
+	t1 = gfpow[255 - l0];
+	t2 = gfpow[255 - l1];
+	det = gfadd(t1, t2);
+	if (!det) {
+		TRACE_FUN(ft_t_any);
+		TRACE_ABORT(0, ft_t_err,
+			   "Inversion failed (2 CRC errors, >0 CRC failures)");
+	}
+	log_det = 255 - gflog[det];
+
+	/* Now, calculate all of the coefficients:
+	 */
+	Ainv[0][0] = Ainv[1][0] = gfpow[log_det];
+
+	Ainv[0][1] = gfmul_exp(t2, log_det);
+	Ainv[1][1] = gfmul_exp(t1, log_det);
+
+	return 1;
+}
+
+
+/* Multiply matrix A by vector S and return result in vector B.  M is
+ * assumed to be of order NxN, S and B of order Nx1.
+ */
+static inline void gfmat_mul(int n, Matrix A, 
+			     __u8 *s, __u8 *b)
+{
+	int i, j;
+	__u8 dot_prod;
+
+	for (i = 0; i < n; ++i) {
+		dot_prod = 0;
+		for (j = 0; j < n; ++j) {
+			dot_prod = gfadd(dot_prod, gfmul(A[i][j], s[j]));
+		}
+		b[i] = dot_prod;
+	}
+}
+
+
+
+/* The Reed Solomon ECC codes are computed over the N-th byte of each
+ * block, where N=SECTOR_SIZE.  There are up to 29 blocks of data, and
+ * 3 blocks of ECC.  The blocks are stored contiguously in memory.  A
+ * segment, consequently, is assumed to have at least 4 blocks: one or
+ * more data blocks plus three ECC blocks.
+ *
+ * Notice: In QIC-80 speak, a CRC error is a sector with an incorrect
+ *         CRC.  A CRC failure is a sector with incorrect data, but
+ *         a valid CRC.  In the error control literature, the former
+ *         is usually called "erasure", the latter "error."
+ */
+/* Compute the parity bytes for C columns of data, where C is the
+ * number of bytes that fit into a long integer.  We use a linear
+ * feed-back register to do this.  The parity bytes P[0], P[STRIDE],
+ * P[2*STRIDE] are computed such that:
+ *
+ *              x^k * p(x) + m(x) = 0 (modulo g(x))
+ *
+ * where k = NBLOCKS,
+ *       p(x) = P[0] + P[STRIDE]*x + P[2*STRIDE]*x^2, and
+ *       m(x) = sum_{i=0}^k m_i*x^i.
+ *       m_i = DATA[i*SECTOR_SIZE]
+ */
+static inline void set_parity(unsigned long *data,
+			      int nblocks, 
+			      unsigned long *p, 
+			      int stride)
+{
+	unsigned long p0, p1, p2, t1, t2, *end;
+
+	end = data + nblocks * (FT_SECTOR_SIZE / sizeof(long));
+	p0 = p1 = p2 = 0;
+	while (data < end) {
+		/* The new parity bytes p0_i, p1_i, p2_i are computed
+		 * from the old values p0_{i-1}, p1_{i-1}, p2_{i-1}
+		 * recursively as:
+		 *
+		 *        p0_i = p1_{i-1} + r^105 * (m_{i-1} - p0_{i-1})
+		 *        p1_i = p2_{i-1} + r^105 * (m_{i-1} - p0_{i-1})
+		 *        p2_i =                    (m_{i-1} - p0_{i-1})
+		 *
+		 * With the initial condition: p0_0 = p1_0 = p2_0 = 0.
+		 */
+		t1 = gfadd_long(*data, p0);
+		/*
+		 * Multiply each byte in t1 by 0xc0:
+		 */
+		if (sizeof(long) == 4) {
+			t2= (((__u32) gfmul_c0[(__u32)t1 >> 24 & 0xff]) << 24 |
+			     ((__u32) gfmul_c0[(__u32)t1 >> 16 & 0xff]) << 16 |
+			     ((__u32) gfmul_c0[(__u32)t1 >>  8 & 0xff]) <<  8 |
+			     ((__u32) gfmul_c0[(__u32)t1 >>  0 & 0xff]) <<  0);
+		} else if (sizeof(long) == 8) {
+			t2= (((__u64) gfmul_c0[(__u64)t1 >> 56 & 0xff]) << 56 |
+			     ((__u64) gfmul_c0[(__u64)t1 >> 48 & 0xff]) << 48 |
+			     ((__u64) gfmul_c0[(__u64)t1 >> 40 & 0xff]) << 40 |
+			     ((__u64) gfmul_c0[(__u64)t1 >> 32 & 0xff]) << 32 |
+			     ((__u64) gfmul_c0[(__u64)t1 >> 24 & 0xff]) << 24 |
+			     ((__u64) gfmul_c0[(__u64)t1 >> 16 & 0xff]) << 16 |
+			     ((__u64) gfmul_c0[(__u64)t1 >>  8 & 0xff]) <<  8 |
+			     ((__u64) gfmul_c0[(__u64)t1 >>  0 & 0xff]) <<  0);
+		} else {
+			TRACE_FUN(ft_t_any);
+			TRACE(ft_t_err, "Error: long is of size %d",
+			      (int) sizeof(long));
+			TRACE_EXIT;
+		}
+		p0 = gfadd_long(t2, p1);
+		p1 = gfadd_long(t2, p2);
+		p2 = t1;
+		data += FT_SECTOR_SIZE / sizeof(long);
+	}
+	*p = p0;
+	p += stride;
+	*p = p1;
+	p += stride;
+	*p = p2;
+	return;
+}
+
+
+/* Compute the 3 syndrome values.  DATA should point to the first byte
+ * of the column for which the syndromes are desired.  The syndromes
+ * are computed over the first NBLOCKS of rows.  The three bytes will
+ * be placed in S[0], S[1], and S[2].
+ *
+ * S[i] is the value of the "message" polynomial m(x) evaluated at the
+ * i-th root of the generator polynomial g(x).
+ *
+ * As g(x)=(x-r^-1)(x-1)(x-r^1) we evaluate the message polynomial at
+ * x=r^-1 to get S[0], at x=r^0=1 to get S[1], and at x=r to get S[2].
+ * This could be done directly and efficiently via the Horner scheme.
+ * However, it would require multiplication tables for the factors
+ * r^-1 (0xc3) and r (0x02).  The following scheme does not require
+ * any multiplication tables beyond what's needed for set_parity()
+ * anyway and is slightly faster if there are no errors and slightly
+ * slower if there are errors.  The latter is hopefully the infrequent
+ * case.
+ *
+ * To understand the alternative algorithm, notice that set_parity(m,
+ * k, p) computes parity bytes such that:
+ *
+ *      x^k * p(x) = m(x) (modulo g(x)).
+ *
+ * That is, to evaluate m(r^m), where r^m is a root of g(x), we can
+ * simply evaluate (r^m)^k*p(r^m).  Also, notice that p is 0 if and
+ * only if s is zero.  That is, if all parity bytes are 0, we know
+ * there is no error in the data and consequently there is no need to
+ * compute s(x) at all!  In all other cases, we compute s(x) from p(x)
+ * by evaluating (r^m)^k*p(r^m) for m=-1, m=0, and m=1.  The p(x)
+ * polynomial is evaluated via the Horner scheme.
+ */
+static int compute_syndromes(unsigned long *data, int nblocks, unsigned long *s)
+{
+	unsigned long p[3];
+
+	set_parity(data, nblocks, p, 1);
+	if (p[0] | p[1] | p[2]) {
+		/* Some of the checked columns do not have a zero
+		 * syndrome.  For simplicity, we compute the syndromes
+		 * for all columns that we have computed the
+		 * remainders for.
+		 */
+		s[0] = gfmul_exp_long(
+			gfadd_long(p[0], 
+				   gfmul_exp_long(
+					   gfadd_long(p[1], 
+						      gfmul_exp_long(p[2], -1)),
+					   -1)), 
+			-nblocks);
+		s[1] = gfadd_long(gfadd_long(p[2], p[1]), p[0]);
+		s[2] = gfmul_exp_long(
+			gfadd_long(p[0], 
+				   gfmul_exp_long(
+					   gfadd_long(p[1],
+						      gfmul_exp_long(p[2], 1)),
+					   1)),
+			nblocks);
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+
+/* Correct the block in the column pointed to by DATA.  There are NBAD
+ * CRC errors and their indices are in BAD_LOC[0], up to
+ * BAD_LOC[NBAD-1].  If NBAD>1, Ainv holds the inverse of the matrix
+ * of the linear system that needs to be solved to determine the error
+ * magnitudes.  S[0], S[1], and S[2] are the syndrome values.  If row
+ * j gets corrected, then bit j will be set in CORRECTION_MAP.
+ */
+static inline int correct_block(__u8 *data, int nblocks,
+				int nbad, int *bad_loc, Matrix Ainv,
+				__u8 *s,
+				SectorMap * correction_map)
+{
+	int ncorrected = 0;
+	int i;
+	__u8 t1, t2;
+	__u8 c0, c1, c2;	/* check bytes */
+	__u8 error_mag[3], log_error_mag;
+	__u8 *dp, l, e;
+	TRACE_FUN(ft_t_any);
+
+	switch (nbad) {
+	case 0:
+		/* might have a CRC failure: */
+		if (s[0] == 0) {
+			/* more than one error */
+			TRACE_ABORT(-1, ft_t_err,
+				 "ECC failed (0 CRC errors, >1 CRC failures)");
+		}
+		t1 = gfdiv(s[1], s[0]);
+		if ((bad_loc[nbad++] = gflog[t1]) >= nblocks) {
+			TRACE(ft_t_err,
+			      "ECC failed (0 CRC errors, >1 CRC failures)");
+			TRACE_ABORT(-1, ft_t_err,
+				  "attempt to correct data at %d", bad_loc[0]);
+		}
+		error_mag[0] = s[1];
+		break;
+	case 1:
+		t1 = gfadd(gfmul_exp(s[1], bad_loc[0]), s[2]);
+		t2 = gfadd(gfmul_exp(s[0], bad_loc[0]), s[1]);
+		if (t1 == 0 && t2 == 0) {
+			/* one erasure, no error: */
+			Ainv[0][0] = gfpow[bad_loc[0]];
+		} else if (t1 == 0 || t2 == 0) {
+			/* one erasure and more than one error: */
+			TRACE_ABORT(-1, ft_t_err,
+				    "ECC failed (1 erasure, >1 error)");
+		} else {
+			/* one erasure, one error: */
+			if ((bad_loc[nbad++] = gflog[gfdiv(t1, t2)]) 
+			    >= nblocks) {
+				TRACE(ft_t_err, "ECC failed "
+				      "(1 CRC errors, >1 CRC failures)");
+				TRACE_ABORT(-1, ft_t_err,
+					    "attempt to correct data at %d",
+					    bad_loc[1]);
+			}
+			if (!gfinv2(bad_loc[0], bad_loc[1], Ainv)) {
+				/* inversion failed---must have more
+                                 *  than one error 
+				 */
+				TRACE_EXIT -1;
+			}
+		}
+		/* FALL THROUGH TO ERROR MAGNITUDE COMPUTATION:
+		 */
+	case 2:
+	case 3:
+		/* compute error magnitudes: */
+		gfmat_mul(nbad, Ainv, s, error_mag);
+		break;
+
+	default:
+		TRACE_ABORT(-1, ft_t_err,
+			    "Internal Error: number of CRC errors > 3");
+	}
+
+	/* Perform correction by adding ERROR_MAG[i] to the byte at
+	 * offset BAD_LOC[i].  Also add the value of the computed
+	 * error polynomial to the syndrome values.  If the correction
+	 * was successful, the resulting check bytes should be zero
+	 * (i.e., the corrected data is a valid code word).
+	 */
+	c0 = s[0];
+	c1 = s[1];
+	c2 = s[2];
+	for (i = 0; i < nbad; ++i) {
+		e = error_mag[i];
+		if (e) {
+			/* correct the byte at offset L by magnitude E: */
+			l = bad_loc[i];
+			dp = &data[l * FT_SECTOR_SIZE];
+			*dp = gfadd(*dp, e);
+			*correction_map |= 1 << l;
+			++ncorrected;
+
+			log_error_mag = gflog[e];
+			c0 = gfadd(c0, gfpow[mod255(log_error_mag - l)]);
+			c1 = gfadd(c1, e);
+			c2 = gfadd(c2, gfpow[mod255(log_error_mag + l)]);
+		}
+	}
+	if (c0 || c1 || c2) {
+		TRACE_ABORT(-1, ft_t_err,
+			    "ECC self-check failed, too many errors");
+	}
+	TRACE_EXIT ncorrected;
+}
+
+
+#if defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID)
+
+/* Perform a sanity check on the computed parity bytes:
+ */
+static int sanity_check(unsigned long *data, int nblocks)
+{
+	TRACE_FUN(ft_t_any);
+	unsigned long s[3];
+
+	if (!compute_syndromes(data, nblocks, s)) {
+		TRACE_ABORT(0, ft_bug,
+			    "Internal Error: syndrome self-check failed");
+	}
+	TRACE_EXIT 1;
+}
+
+#endif /* defined(ECC_SANITY_CHECK) || defined(ECC_PARANOID) */
+
+/* Compute the parity for an entire segment of data.
+ */
+int ftape_ecc_set_segment_parity(struct memory_segment *mseg)
+{
+	int i;
+	__u8 *parity_bytes;
+
+	parity_bytes = &mseg->data[(mseg->blocks - 3) * FT_SECTOR_SIZE];
+	for (i = 0; i < FT_SECTOR_SIZE; i += sizeof(long)) {
+		set_parity((unsigned long *) &mseg->data[i], mseg->blocks - 3,
+			   (unsigned long *) &parity_bytes[i],
+			   FT_SECTOR_SIZE / sizeof(long));
+#ifdef ECC_PARANOID
+		if (!sanity_check((unsigned long *) &mseg->data[i],
+				   mseg->blocks)) {
+			return -1;
+		}
+#endif				/* ECC_PARANOID */
+	}
+	return 0;
+}
+
+
+/* Checks and corrects (if possible) the segment MSEG.  Returns one of
+ * ECC_OK, ECC_CORRECTED, and ECC_FAILED.
+ */
+int ftape_ecc_correct_data(struct memory_segment *mseg)
+{
+	int col, i, result;
+	int ncorrected = 0;
+	int nerasures = 0;	/* # of erasures (CRC errors) */
+	int erasure_loc[3];	/* erasure locations */
+	unsigned long ss[3];
+	__u8 s[3];
+	Matrix Ainv;
+	TRACE_FUN(ft_t_flow);
+
+	mseg->corrected = 0;
+
+	/* find first column that has non-zero syndromes: */
+	for (col = 0; col < FT_SECTOR_SIZE; col += sizeof(long)) {
+		if (!compute_syndromes((unsigned long *) &mseg->data[col],
+				       mseg->blocks, ss)) {
+			/* something is wrong---have to fix things */
+			break;
+		}
+	}
+	if (col >= FT_SECTOR_SIZE) {
+		/* all syndromes are ok, therefore nothing to correct */
+		TRACE_EXIT ECC_OK;
+	}
+	/* count the number of CRC errors if there were any: */
+	if (mseg->read_bad) {
+		for (i = 0; i < mseg->blocks; i++) {
+			if (BAD_CHECK(mseg->read_bad, i)) {
+				if (nerasures >= 3) {
+					/* this is too much for ECC */
+					TRACE_ABORT(ECC_FAILED, ft_t_err,
+						"ECC failed (>3 CRC errors)");
+				}	/* if */
+				erasure_loc[nerasures++] = i;
+			}
+		}
+	}
+	/*
+	 * If there are at least 2 CRC errors, determine inverse of matrix
+	 * of linear system to be solved:
+	 */
+	switch (nerasures) {
+	case 2:
+		if (!gfinv2(erasure_loc[0], erasure_loc[1], Ainv)) {
+			TRACE_EXIT ECC_FAILED;
+		}
+		break;
+	case 3:
+		if (!gfinv3(erasure_loc[0], erasure_loc[1],
+			    erasure_loc[2], Ainv)) {
+			TRACE_EXIT ECC_FAILED;
+		}
+		break;
+	default:
+		/* this is not an error condition... */
+		break;
+	}
+
+	do {
+		for (i = 0; i < sizeof(long); ++i) {
+			s[0] = ss[0];
+			s[1] = ss[1];
+			s[2] = ss[2];
+			if (s[0] | s[1] | s[2]) {
+#ifdef BIG_ENDIAN
+				result = correct_block(
+					&mseg->data[col + sizeof(long) - 1 - i],
+					mseg->blocks,
+					nerasures,
+					erasure_loc,
+					Ainv,
+					s,
+					&mseg->corrected);
+#else
+				result = correct_block(&mseg->data[col + i],
+						       mseg->blocks,
+						       nerasures,
+						       erasure_loc,
+						       Ainv,
+						       s,
+						       &mseg->corrected);
+#endif
+				if (result < 0) {
+					TRACE_EXIT ECC_FAILED;
+				}
+				ncorrected += result;
+			}
+			ss[0] >>= 8;
+			ss[1] >>= 8;
+			ss[2] >>= 8;
+		}
+
+#ifdef ECC_SANITY_CHECK
+		if (!sanity_check((unsigned long *) &mseg->data[col],
+				  mseg->blocks)) {
+			TRACE_EXIT ECC_FAILED;
+		}
+#endif				/* ECC_SANITY_CHECK */
+
+		/* find next column with non-zero syndromes: */
+		while ((col += sizeof(long)) < FT_SECTOR_SIZE) {
+			if (!compute_syndromes((unsigned long *)
+				    &mseg->data[col], mseg->blocks, ss)) {
+				/* something is wrong---have to fix things */
+				break;
+			}
+		}
+	} while (col < FT_SECTOR_SIZE);
+	if (ncorrected && nerasures == 0) {
+		TRACE(ft_t_warn, "block contained error not caught by CRC");
+	}
+	TRACE((ncorrected > 0) ? ft_t_noise : ft_t_any, "number of corrections: %d", ncorrected);
+	TRACE_EXIT ncorrected ? ECC_CORRECTED : ECC_OK;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-ecc.h b/drivers/char/ftape/lowlevel/ftape-ecc.h
new file mode 100644
index 0000000..4829146
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-ecc.h
@@ -0,0 +1,84 @@
+#ifndef _FTAPE_ECC_H_
+#define _FTAPE_ECC_H_
+
+/*
+ *      Copyright (C) 1993 Ning and David Mosberger.
+ *      Original:
+ *      Copyright (C) 1993 Bas Laarhoven.
+ *      Copyright (C) 1992 David L. Brown, Jr.
+ 
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+ 
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-ecc.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:11 $
+ *
+ *      This file contains the definitions for the
+ *      Reed-Solomon error correction code 
+ *      for the QIC-40/80 tape streamer device driver.
+ */
+
+#include "../lowlevel/ftape-bsm.h"
+
+#define BAD_CLEAR(entry) ((entry)=0)
+#define BAD_SET(entry,sector) ((entry)|=(1<<(sector)))
+#define BAD_CHECK(entry,sector) ((entry)&(1<<(sector)))
+
+/*
+ * Return values for ecc_correct_data:
+ */
+enum {
+	ECC_OK,			/* Data was correct. */
+	ECC_CORRECTED,		/* Correctable error in data. */
+	ECC_FAILED,		/* Could not correct data. */
+};
+
+/*
+ * Representation of an in memory segment.  MARKED_BAD lists the
+ * sectors that were marked bad during formatting.  If the N-th sector
+ * in a segment is marked bad, bit 1<<N will be set in MARKED_BAD.
+ * The sectors should be read in from the disk and packed, as if the
+ * bad sectors were not there, and the segment just contained fewer
+ * sectors.  READ_SECTORS is a bitmap of errors encountered while
+ * reading the data.  These offsets are relative to the packed data.
+ * BLOCKS is a count of the sectors not marked bad.  This is just to
+ * prevent having to count the zero bits in MARKED_BAD each time this
+ * is needed.  DATA is the actual sector packed data from (or to) the
+ * tape.
+ */
+ struct memory_segment {
+	SectorMap marked_bad;
+	SectorMap read_bad;
+ 	int blocks;
+ 	__u8 *data;
+	SectorMap corrected;
+ };
+
+/*
+ * ecc.c defined global variables:
+ */
+#ifdef TEST
+extern int ftape_ecc_tracing;
+#endif
+
+/*
+ * ecc.c defined global functions:
+ */
+extern int ftape_ecc_correct_data(struct memory_segment *data);
+extern int ftape_ecc_set_segment_parity(struct memory_segment *data);
+
+#endif	/* _FTAPE_ECC_H_ */
diff --git a/drivers/char/ftape/lowlevel/ftape-format.c b/drivers/char/ftape/lowlevel/ftape-format.c
new file mode 100644
index 0000000..5dd4c59
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-format.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-format.c,v $
+ * $Revision: 1.2.4.1 $
+ * $Date: 1997/11/14 16:05:39 $
+ *
+ *      This file contains the code to support formatting of floppy
+ *      tape cartridges with the QIC-40/80/3010/3020 floppy-tape
+ *      driver "ftape" for Linux.
+ */
+ 
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-ecc.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/ftape-format.h"
+
+#if defined(TESTING)
+#define FT_FMT_SEGS_PER_BUF 50
+#else
+#define FT_FMT_SEGS_PER_BUF (FT_BUFF_SIZE/(4*FT_SECTORS_PER_SEGMENT))
+#endif
+
+static spinlock_t ftape_format_lock;
+
+/*
+ *  first segment of the new buffer
+ */
+static int switch_segment;
+
+/*
+ *  at most 256 segments fit into one 32 kb buffer.  Even TR-1 cartridges have
+ *  more than this many segments per track, so better be careful.
+ *
+ *  buffer_struct *buff: buffer to store the formatting coordinates in
+ *  int  start: starting segment for this buffer.
+ *  int    spt: segments per track
+ *
+ *  Note: segment ids are relative to the start of the track here.
+ */
+static void setup_format_buffer(buffer_struct *buff, int start, int spt,
+				__u8 gap3)
+{
+	int to_do = spt - start;
+	TRACE_FUN(ft_t_flow);
+
+	if (to_do > FT_FMT_SEGS_PER_BUF) {
+		to_do = FT_FMT_SEGS_PER_BUF;
+	}
+	buff->ptr          = buff->address;
+	buff->remaining    = to_do * FT_SECTORS_PER_SEGMENT; /* # sectors */
+	buff->bytes        = buff->remaining * 4; /* need 4 bytes per sector */
+	buff->gap3         = gap3;
+	buff->segment_id   = start;
+	buff->next_segment = start + to_do;
+	if (buff->next_segment >= spt) {
+		buff->next_segment = 0; /* 0 means: stop runner */
+	}
+	buff->status       = waiting; /* tells the isr that it can use
+				       * this buffer
+				       */
+	TRACE_EXIT;
+}
+
+
+/*
+ *  start formatting a new track.
+ */
+int ftape_format_track(const unsigned int track, const __u8 gap3)
+{
+	unsigned long flags;
+	buffer_struct *tail, *head;
+	int status;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE_CATCH(ftape_ready_wait(ftape_timeout.pause, &status),);
+	if (track & 1) {
+		if (!(status & QIC_STATUS_AT_EOT)) {
+			TRACE_CATCH(ftape_seek_to_eot(),);
+		}
+	} else {
+		if (!(status & QIC_STATUS_AT_BOT)) {
+			TRACE_CATCH(ftape_seek_to_bot(),);
+		}
+	}
+	ftape_abort_operation(); /* this sets ft_head = ft_tail = 0 */
+	ftape_set_state(formatting);
+
+	TRACE(ft_t_noise,
+	      "Formatting track %d, logical: from segment %d to %d",
+	      track, track * ft_segments_per_track, 
+	      (track + 1) * ft_segments_per_track - 1);
+	
+	/*
+	 *  initialize the buffer switching protocol for this track
+	 */
+	head = ftape_get_buffer(ft_queue_head); /* tape isn't running yet */
+	tail = ftape_get_buffer(ft_queue_tail); /* tape isn't running yet */
+	switch_segment = 0;
+	do {
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		setup_format_buffer(tail, switch_segment,
+				    ft_segments_per_track, gap3);
+		switch_segment = tail->next_segment;
+	} while ((switch_segment != 0) &&
+		 ((tail = ftape_next_buffer(ft_queue_tail)) != head));
+	/* go */
+	head->status = formatting;
+	TRACE_CATCH(ftape_seek_head_to_track(track),);
+	TRACE_CATCH(ftape_command(QIC_LOGICAL_FORWARD),);
+	spin_lock_irqsave(&ftape_format_lock, flags);
+	TRACE_CATCH(fdc_setup_formatting(head), restore_flags(flags));
+	spin_unlock_irqrestore(&ftape_format_lock, flags);
+	TRACE_EXIT 0;
+}
+
+/*   return segment id of segment currently being formatted and do the
+ *   buffer switching stuff.
+ */
+int ftape_format_status(unsigned int *segment_id)
+{
+	buffer_struct *tail = ftape_get_buffer(ft_queue_tail);
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	while (switch_segment != 0 &&
+	       ftape_get_buffer(ft_queue_head) != tail) {
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		/*  need more buffers, first wait for empty buffer
+		 */
+		TRACE_CATCH(ftape_wait_segment(formatting),);
+		/*  don't worry for gap3. If we ever hit this piece of code,
+		 *  then all buffer already have the correct gap3 set!
+		 */
+		setup_format_buffer(tail, switch_segment,
+				    ft_segments_per_track, tail->gap3);
+		switch_segment = tail->next_segment;
+		if (switch_segment != 0) {
+			tail = ftape_next_buffer(ft_queue_tail);
+		}
+	}
+	/*    should runner stop ?
+	 */
+	if (ft_runner_status == aborting || ft_runner_status == do_abort) {
+		buffer_struct *head = ftape_get_buffer(ft_queue_head);
+		TRACE(ft_t_warn, "Error formatting segment %d",
+		      ftape_get_buffer(ft_queue_head)->segment_id);
+		(void)ftape_abort_operation();
+		TRACE_EXIT (head->status != error) ? -EAGAIN : -EIO;
+	}
+	/*
+	 *  don't care if the timer expires, this is just kind of a
+	 *  "select" operation that lets the calling process sleep
+	 *  until something has happened
+	 */
+	if (fdc_interrupt_wait(5 * FT_SECOND) < 0) {
+		TRACE(ft_t_noise, "End of track %d at segment %d",
+		      ft_location.track,
+		      ftape_get_buffer(ft_queue_head)->segment_id);
+		result = 1;  /* end of track, unlock module */
+	} else {
+		result = 0;
+	}
+	/*
+	 *  the calling process should use the seg id to determine
+	 *  which parts of the dma buffers can be safely overwritten
+	 *  with new data.
+	 */
+	*segment_id = ftape_get_buffer(ft_queue_head)->segment_id;
+	/*
+	 *  Internally we start counting segment ids from the start of
+	 *  each track when formatting, but externally we keep them
+	 *  relative to the start of the tape:
+	 */
+	*segment_id += ft_location.track * ft_segments_per_track;
+	TRACE_EXIT result;
+}
+
+/*
+ *  The segment id is relative to the start of the tape
+ */
+int ftape_verify_segment(const unsigned int segment_id, SectorMap *bsm)
+{
+	int result;
+	int verify_done = 0;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "Verifying segment %d", segment_id);
+
+	if (ft_driver_state != verifying) {
+		TRACE(ft_t_noise, "calling ftape_abort_operation");
+		if (ftape_abort_operation() < 0) {
+			TRACE(ft_t_err, "ftape_abort_operation failed");
+			TRACE_EXIT -EIO;
+		}
+	}
+	*bsm = 0x00000000;
+	ftape_set_state(verifying);
+	for (;;) {
+		buffer_struct *tail;
+		/*
+		 *  Allow escape from this loop on signal
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		/*
+		 *  Search all full buffers for the first matching the
+		 *  wanted segment.  Clear other buffers on the fly.
+		 */
+		tail = ftape_get_buffer(ft_queue_tail);
+		while (!verify_done && tail->status == done) {
+			/*
+			 *  Allow escape from this loop on signal !
+			 */
+			FT_SIGNAL_EXIT(_DONT_BLOCK);
+			if (tail->segment_id == segment_id) {
+				/*  If out buffer is already full,
+				 *  return its contents.  
+				 */
+				TRACE(ft_t_flow, "found segment in cache: %d",
+				      segment_id);
+				if ((tail->soft_error_map |
+				     tail->hard_error_map) != 0) {
+					TRACE(ft_t_info,"bsm[%d] = 0x%08lx",
+					      segment_id,
+					      (unsigned long)
+					      (tail->soft_error_map |
+					      tail->hard_error_map));
+					*bsm = (tail->soft_error_map |
+						tail->hard_error_map);
+				}
+				verify_done = 1;
+			} else {
+				TRACE(ft_t_flow,"zapping segment in cache: %d",
+				      tail->segment_id);
+			}
+			tail->status = waiting;
+			tail = ftape_next_buffer(ft_queue_tail);
+		}
+		if (!verify_done && tail->status == verifying) {
+			if (tail->segment_id == segment_id) {
+				switch(ftape_wait_segment(verifying)) {
+				case 0:
+					break;
+				case -EINTR:
+					TRACE_ABORT(-EINTR, ft_t_warn,
+						    "interrupted by "
+						    "non-blockable signal");
+					break;
+				default:
+					ftape_abort_operation();
+					ftape_set_state(verifying);
+					/* be picky */
+					TRACE_ABORT(-EIO, ft_t_warn,
+						    "wait_segment failed");
+				}
+			} else {
+				/*  We're reading the wrong segment,
+				 *  stop runner.
+				 */
+				TRACE(ft_t_noise, "verifying wrong segment");
+				ftape_abort_operation();
+				ftape_set_state(verifying);
+			}
+		}
+		/*    should runner stop ?
+		 */
+		if (ft_runner_status == aborting) {
+			buffer_struct *head = ftape_get_buffer(ft_queue_head);
+			if (head->status == error ||
+			    head->status == verifying) {
+				/* no data or overrun error */
+				head->status = waiting;
+			}
+			TRACE_CATCH(ftape_dumb_stop(),);
+		} else {
+			/*  If just passed last segment on tape: wait
+			 *  for BOT or EOT mark. Sets ft_runner_status to
+			 *  idle if at lEOT and successful 
+			 */
+			TRACE_CATCH(ftape_handle_logical_eot(),);
+		}
+		if (verify_done) {
+			TRACE_EXIT 0;
+		}
+		/*    Now at least one buffer is idle!
+		 *    Restart runner & tape if needed.
+		 */
+		/*  We could optimize the following a little bit. We know that 
+		 *  the bad sector map is empty.
+		 */
+		tail = ftape_get_buffer(ft_queue_tail);
+		if (tail->status == waiting) {
+			buffer_struct *head = ftape_get_buffer(ft_queue_head);
+
+			ftape_setup_new_segment(head, segment_id, -1);
+			ftape_calc_next_cluster(head);
+			if (ft_runner_status == idle) {
+				result = ftape_start_tape(segment_id,
+							  head->sector_offset);
+				switch(result) {
+				case 0:
+					break;
+				case -ETIME:
+				case -EINTR:
+					TRACE_ABORT(result, ft_t_err, "Error: "
+						    "segment %d unreachable",
+						    segment_id);
+					break;
+				default:
+					*bsm = EMPTY_SEGMENT;
+					TRACE_EXIT 0;
+					break;
+				}
+			}
+			head->status = verifying;
+			fdc_setup_read_write(head, FDC_VERIFY);
+		}
+	}
+	/* not reached */
+	TRACE_EXIT -EIO;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-format.h b/drivers/char/ftape/lowlevel/ftape-format.h
new file mode 100644
index 0000000..f151615
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-format.h
@@ -0,0 +1,37 @@
+#ifndef _FTAPE_FORMAT_H
+#define _FTAPE_FORMAT_H
+
+/*
+ * Copyright (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-format.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:13 $
+ *
+ *      This file contains the low level definitions for the
+ *      formatting support for the QIC-40/80/3010/3020 floppy-tape
+ *      driver "ftape" for Linux.
+ */
+
+#ifdef __KERNEL__
+extern int ftape_format_track(const unsigned int track, const __u8 gap3);
+extern int ftape_format_status(unsigned int *segment_id);
+extern int ftape_verify_segment(const unsigned int segment_id, SectorMap *bsm);
+#endif /* __KERNEL__ */
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-init.c b/drivers/char/ftape/lowlevel/ftape-init.c
new file mode 100644
index 0000000..b54260d
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-init.c
@@ -0,0 +1,161 @@
+/*
+ *      Copyright (C) 1993-1996 Bas Laarhoven,
+ *                (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ *      This file contains the code that interfaces the kernel
+ *      for the QIC-40/80/3010/3020 floppy-tape driver for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/major.h>
+
+#include <linux/ftape.h>
+#include <linux/init.h>
+#include <linux/qic117.h>
+#ifdef CONFIG_ZFTAPE
+#include <linux/zftape.h>
+#endif
+
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-buffer.h"
+#include "../lowlevel/ftape-proc.h"
+#include "../lowlevel/ftape-tracing.h"
+
+
+#if defined(MODULE) && !defined(CONFIG_FT_NO_TRACE_AT_ALL)
+static int ft_tracing = -1;
+#endif
+
+
+/*  Called by modules package when installing the driver
+ *  or by kernel during the initialization phase
+ */
+static int __init ftape_init(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+#ifdef MODULE
+#ifndef CONFIG_FT_NO_TRACE_AT_ALL
+	if (ft_tracing != -1) {
+		ftape_tracing = ft_tracing;
+	}
+#endif
+	printk(KERN_INFO FTAPE_VERSION "\n");
+        if (TRACE_LEVEL >= ft_t_info) {
+		printk(
+KERN_INFO "(c) 1993-1996 Bas Laarhoven (bas@vimec.nl)\n"
+KERN_INFO "(c) 1995-1996 Kai Harrekilde-Petersen (khp@dolphinics.no)\n"
+KERN_INFO "(c) 1996-1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n"
+KERN_INFO "QIC-117 driver for QIC-40/80/3010/3020 floppy tape drives\n");
+        }
+#else /* !MODULE */
+	/* print a short no-nonsense boot message */
+	printk(KERN_INFO FTAPE_VERSION "\n");
+#endif /* MODULE */
+	TRACE(ft_t_info, "installing QIC-117 floppy tape hardware drive ... ");
+	TRACE(ft_t_info, "ftape_init @ 0x%p", ftape_init);
+	/*  Allocate the DMA buffers. They are deallocated at cleanup() time.
+	 */
+#ifdef TESTING
+#ifdef MODULE
+	while (ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS) < 0) {
+		ftape_sleep(FT_SECOND/20);
+		if (signal_pending(current)) {
+			(void)ftape_set_nr_buffers(0);
+			TRACE(ft_t_bug,
+			      "Killed by signal while allocating buffers.");
+			TRACE_ABORT(-EINTR, 
+				    ft_t_bug, "Free up memory and retry");
+		}
+	}
+#else
+	TRACE_CATCH(ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS),
+		    (void)ftape_set_nr_buffers(0));
+#endif
+#else
+	TRACE_CATCH(ftape_set_nr_buffers(CONFIG_FT_NR_BUFFERS),
+		    (void)ftape_set_nr_buffers(0));
+#endif
+	ft_drive_sel = -1;
+	ft_failure   = 1;         /* inhibit any operation but open */
+	ftape_udelay_calibrate(); /* must be before fdc_wait_calibrate ! */
+	fdc_wait_calibrate();
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS)
+	(void)ftape_proc_init();
+#endif
+#ifdef CONFIG_ZFTAPE
+	(void)zft_init();
+#endif
+	TRACE_EXIT 0;
+}
+
+module_param(ft_fdc_base,       uint, 0);
+MODULE_PARM_DESC(ft_fdc_base,  "Base address of FDC controller.");
+module_param(ft_fdc_irq,        uint, 0);
+MODULE_PARM_DESC(ft_fdc_irq,   "IRQ (interrupt channel) to use.");
+module_param(ft_fdc_dma,        uint, 0);
+MODULE_PARM_DESC(ft_fdc_dma,   "DMA channel to use.");
+module_param(ft_fdc_threshold,  uint, 0);
+MODULE_PARM_DESC(ft_fdc_threshold,  "Threshold of the FDC Fifo.");
+module_param(ft_fdc_rate_limit, uint, 0);
+MODULE_PARM_DESC(ft_fdc_rate_limit, "Maximal data rate for FDC.");
+module_param(ft_probe_fc10,     bool, 0);
+MODULE_PARM_DESC(ft_probe_fc10,
+	    "If non-zero, probe for a Colorado FC-10/FC-20 controller.");
+module_param(ft_mach2,          bool, 0);
+MODULE_PARM_DESC(ft_mach2,
+	    "If non-zero, probe for a Mountain MACH-2 controller.");
+#if defined(MODULE) && !defined(CONFIG_FT_NO_TRACE_AT_ALL)
+module_param(ft_tracing,        int, 0644);
+MODULE_PARM_DESC(ft_tracing,
+	    "Amount of debugging output, 0 <= tracing <= 8, default 3.");
+#endif
+
+MODULE_AUTHOR(
+	"(c) 1993-1996 Bas Laarhoven (bas@vimec.nl), "
+	"(c) 1995-1996 Kai Harrekilde-Petersen (khp@dolphinics.no), "
+	"(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)");
+MODULE_DESCRIPTION(
+	"QIC-117 driver for QIC-40/80/3010/3020 floppy tape drives.");
+MODULE_LICENSE("GPL");
+
+static void __exit ftape_exit(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS)
+	ftape_proc_destroy();
+#endif
+	(void)ftape_set_nr_buffers(0);
+        printk(KERN_INFO "ftape: unloaded.\n");
+	TRACE_EXIT;
+}
+
+module_init(ftape_init);
+module_exit(ftape_exit);
diff --git a/drivers/char/ftape/lowlevel/ftape-init.h b/drivers/char/ftape/lowlevel/ftape-init.h
new file mode 100644
index 0000000..99a7b8a
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-init.h
@@ -0,0 +1,43 @@
+#ifndef _FTAPE_INIT_H
+#define _FTAPE_INIT_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-init.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:16 $
+ *
+ * This file contains the definitions for the interface to 
+ * the Linux kernel for floppy tape driver ftape.
+ *
+ */
+
+#include <linux/linkage.h>
+#include <linux/signal.h>
+
+#define _NEVER_BLOCK    (sigmask(SIGKILL) | sigmask(SIGSTOP))
+#define _DONT_BLOCK     (_NEVER_BLOCK | sigmask(SIGINT))
+#define _DO_BLOCK       (sigmask(SIGPIPE))
+
+#ifndef QIC117_TAPE_MAJOR
+#define QIC117_TAPE_MAJOR 27
+#endif
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-io.c b/drivers/char/ftape/lowlevel/ftape-io.c
new file mode 100644
index 0000000..259015a
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-io.c
@@ -0,0 +1,992 @@
+/*
+ *      Copyright (C) 1993-1996 Bas Laarhoven,
+ *                (C) 1996      Kai Harrekilde-Petersen,
+ *                (C) 1997      Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-io.c,v $
+ * $Revision: 1.4 $
+ * $Date: 1997/11/11 14:02:36 $
+ *
+ *      This file contains the general control functions for the
+ *      QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <asm/system.h>
+#include <linux/ioctl.h>
+#include <linux/mtio.h>
+#include <linux/delay.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/ftape-calibr.h"
+
+/*      Global vars.
+ */
+/* NOTE: sectors start numbering at 1, all others at 0 ! */
+ft_timeout_table ftape_timeout;
+unsigned int ftape_tape_len;
+volatile qic117_cmd_t ftape_current_command;
+const struct qic117_command_table qic117_cmds[] = QIC117_COMMANDS;
+int ftape_might_be_off_track;
+
+/*      Local vars.
+ */
+static int diagnostic_mode;
+static unsigned int ftape_udelay_count;
+static unsigned int ftape_udelay_time;
+
+void ftape_udelay(unsigned int usecs)
+{
+	volatile int count = (ftape_udelay_count * usecs +
+                              ftape_udelay_count - 1) / ftape_udelay_time;
+	volatile int i;
+
+	while (count-- > 0) {
+		for (i = 0; i < 20; ++i);
+	}
+}
+
+void ftape_udelay_calibrate(void)
+{
+	ftape_calibrate("ftape_udelay",
+			ftape_udelay, &ftape_udelay_count, &ftape_udelay_time);
+}
+
+/*      Delay (msec) routine.
+ */
+void ftape_sleep(unsigned int time)
+{
+	TRACE_FUN(ft_t_any);
+
+	time *= 1000;   /* msecs -> usecs */
+	if (time < FT_USPT) {
+		/*  Time too small for scheduler, do a busy wait ! */
+		ftape_udelay(time);
+	} else {
+		long timeout;
+		unsigned long flags;
+		unsigned int ticks = (time + FT_USPT - 1) / FT_USPT;
+
+		TRACE(ft_t_any, "%d msec, %d ticks", time/1000, ticks);
+		timeout = ticks;
+		save_flags(flags);
+		sti();
+		msleep_interruptible(jiffies_to_msecs(timeout));
+		/*  Mmm. Isn't current->blocked == 0xffffffff ?
+		 */
+		if (signal_pending(current)) {
+			TRACE(ft_t_err, "awoken by non-blocked signal :-(");
+		}
+		restore_flags(flags);
+	}
+	TRACE_EXIT;
+}
+
+/*  send a command or parameter to the drive
+ *  Generates # of step pulses.
+ */
+static inline int ft_send_to_drive(int arg)
+{
+	/*  Always wait for a command_timeout period to separate
+	 *  individuals commands and/or parameters.
+	 */
+	ftape_sleep(3 * FT_MILLISECOND);
+	/*  Keep cylinder nr within range, step towards home if possible.
+	 */
+	if (ftape_current_cylinder >= arg) {
+		return fdc_seek(ftape_current_cylinder - arg);
+	} else {
+		return fdc_seek(ftape_current_cylinder + arg);
+	}
+}
+
+/* forward */ int ftape_report_raw_drive_status(int *status);
+
+static int ft_check_cmd_restrictions(qic117_cmd_t command)
+{
+	int status = -1;
+	TRACE_FUN(ft_t_any);
+	
+	TRACE(ft_t_flow, "%s", qic117_cmds[command].name);
+	/* A new motion command during an uninterruptible (motion)
+	 *  command requires a ready status before the new command can
+	 *  be issued. Otherwise a new motion command needs to be
+	 *  checked against required status.
+	 */
+	if (qic117_cmds[command].cmd_type == motion &&
+	    qic117_cmds[ftape_current_command].non_intr) {
+		ftape_report_raw_drive_status(&status);
+		if ((status & QIC_STATUS_READY) == 0) {
+			TRACE(ft_t_noise,
+			      "motion cmd (%d) during non-intr cmd (%d)",
+			      command, ftape_current_command);
+			TRACE(ft_t_noise, "waiting until drive gets ready");
+			ftape_ready_wait(ftape_timeout.seek,
+					 &status);
+		}
+	}
+	if (qic117_cmds[command].mask != 0) {
+		__u8 difference;
+		/*  Some commands do require a certain status:
+		 */
+		if (status == -1) {	/* not yet set */
+			ftape_report_raw_drive_status(&status);
+		}
+		difference = ((status ^ qic117_cmds[command].state) &
+			      qic117_cmds[command].mask);
+		/*  Wait until the drive gets
+		 *  ready. This may last forever if
+		 *  the drive never gets ready... 
+		 */
+		while ((difference & QIC_STATUS_READY) != 0) {
+			TRACE(ft_t_noise, "command %d issued while not ready",
+			      command);
+			TRACE(ft_t_noise, "waiting until drive gets ready");
+			if (ftape_ready_wait(ftape_timeout.seek,
+					     &status) == -EINTR) {
+				/*  Bail out on signal !
+				 */
+				TRACE_ABORT(-EINTR, ft_t_warn,
+				      "interrupted by non-blockable signal");
+			}
+			difference = ((status ^ qic117_cmds[command].state) &
+				      qic117_cmds[command].mask);
+		}
+		while ((difference & QIC_STATUS_ERROR) != 0) {
+			int err;
+			qic117_cmd_t cmd;
+
+			TRACE(ft_t_noise,
+			      "command %d issued while error pending",
+			      command);
+			TRACE(ft_t_noise, "clearing error status");
+			ftape_report_error(&err, &cmd, 1);
+			ftape_report_raw_drive_status(&status);
+			difference = ((status ^ qic117_cmds[command].state) &
+				      qic117_cmds[command].mask);
+			if ((difference & QIC_STATUS_ERROR) != 0) {
+				/*  Bail out on fatal signal !
+				 */
+				FT_SIGNAL_EXIT(_NEVER_BLOCK);
+			}
+		}
+		if (difference) {
+			/*  Any remaining difference can't be solved
+			 *  here.  
+			 */
+			if (difference & (QIC_STATUS_CARTRIDGE_PRESENT |
+					  QIC_STATUS_NEW_CARTRIDGE |
+					  QIC_STATUS_REFERENCED)) {
+				TRACE(ft_t_warn,
+				      "Fatal: tape removed or reinserted !");
+				ft_failure = 1;
+			} else {
+				TRACE(ft_t_err, "wrong state: 0x%02x should be: 0x%02x",
+				      status & qic117_cmds[command].mask,
+				      qic117_cmds[command].state);
+			}
+			TRACE_EXIT -EIO;
+		}
+		if (~status & QIC_STATUS_READY & qic117_cmds[command].mask) {
+			TRACE_ABORT(-EBUSY, ft_t_err, "Bad: still busy!");
+		}
+	}
+	TRACE_EXIT 0;
+}
+
+/*      Issue a tape command:
+ */
+int ftape_command(qic117_cmd_t command)
+{
+	int result = 0;
+	static int level;
+	TRACE_FUN(ft_t_any);
+
+	if ((unsigned int)command > NR_ITEMS(qic117_cmds)) {
+		/*  This is a bug we'll want to know about too.
+		 */
+		TRACE_ABORT(-EIO, ft_t_bug, "bug - bad command: %d", command);
+	}
+	if (++level > 5) { /*  This is a bug we'll want to know about. */
+		--level;
+		TRACE_ABORT(-EIO, ft_t_bug, "bug - recursion for command: %d",
+			    command);
+	}
+	/*  disable logging and restriction check for some commands,
+	 *  check all other commands that have a prescribed starting
+	 *  status.
+	 */
+	if (diagnostic_mode) {
+		TRACE(ft_t_flow, "diagnostic command %d", command);
+	} else if (command == QIC_REPORT_DRIVE_STATUS ||
+		   command == QIC_REPORT_NEXT_BIT) {
+		TRACE(ft_t_any, "%s", qic117_cmds[command].name);
+	} else {
+		TRACE_CATCH(ft_check_cmd_restrictions(command), --level);
+	}
+	/*  Now all conditions are met or result was < 0.
+	 */
+	result = ft_send_to_drive((unsigned int)command);
+	if (qic117_cmds[command].cmd_type == motion &&
+	    command != QIC_LOGICAL_FORWARD && command != QIC_STOP_TAPE) {
+		ft_location.known = 0;
+	}
+	ftape_current_command = command;
+	--level;
+	TRACE_EXIT result;
+}
+
+/*      Send a tape command parameter:
+ *      Generates command # of step pulses.
+ *      Skips tape-status call !
+ */
+int ftape_parameter(unsigned int parameter)
+{
+	TRACE_FUN(ft_t_any);
+
+	TRACE(ft_t_flow, "called with parameter = %d", parameter);
+	TRACE_EXIT ft_send_to_drive(parameter + 2);
+}
+
+/*      Wait for the drive to get ready.
+ *      timeout time in milli-seconds
+ *      Returned status is valid if result != -EIO
+ *
+ *      Should we allow to be killed by SIGINT?  (^C)
+ *      Would be nice at least for large timeouts.
+ */
+int ftape_ready_wait(unsigned int timeout, int *status)
+{
+	unsigned long t0;
+	unsigned int poll_delay;
+	int signal_retries;
+	TRACE_FUN(ft_t_any);
+
+	/*  the following ** REALLY ** reduces the system load when
+	 *  e.g. one simply rewinds or retensions. The tape is slow 
+	 *  anyway. It is really not necessary to detect error 
+	 *  conditions with 1/10 seconds granularity
+	 *
+	 *  On my AMD 133MHZ 486: 100 ms: 23% system load
+	 *                        1  sec:  5%
+	 *                        5  sec:  0.6%, yeah
+	 */
+	if (timeout <= FT_SECOND) {
+		poll_delay = 100 * FT_MILLISECOND;
+		signal_retries = 20; /* two seconds */
+	} else if (timeout < 20 * FT_SECOND) {
+		TRACE(ft_t_flow, "setting poll delay to 1 second");
+		poll_delay = FT_SECOND;
+		signal_retries = 2; /* two seconds */
+	} else {
+		TRACE(ft_t_flow, "setting poll delay to 5 seconds");
+		poll_delay = 5 * FT_SECOND;
+		signal_retries = 1; /* five seconds */
+	}
+	for (;;) {
+		t0 = jiffies;
+		TRACE_CATCH(ftape_report_raw_drive_status(status),);
+		if (*status & QIC_STATUS_READY) {
+			TRACE_EXIT 0;
+		}
+		if (!signal_retries--) {
+			FT_SIGNAL_EXIT(_NEVER_BLOCK);
+		}
+		if ((int)timeout >= 0) {
+			/* this will fail when jiffies wraps around about
+			 * once every year :-)
+			 */
+			timeout -= ((jiffies - t0) * FT_SECOND) / HZ;
+			if (timeout <= 0) {
+				TRACE_ABORT(-ETIME, ft_t_err, "timeout");
+			}
+			ftape_sleep(poll_delay);
+			timeout -= poll_delay;
+		} else {
+			ftape_sleep(poll_delay);
+		}
+	}
+	TRACE_EXIT -ETIME;
+}
+
+/*      Issue command and wait up to timeout milli seconds for drive ready
+ */
+int ftape_command_wait(qic117_cmd_t command, unsigned int timeout, int *status)
+{
+	int result;
+
+	/* Drive should be ready, issue command
+	 */
+	result = ftape_command(command);
+	if (result >= 0) {
+		result = ftape_ready_wait(timeout, status);
+	}
+	return result;
+}
+
+static int ftape_parameter_wait(unsigned int parm, unsigned int timeout, int *status)
+{
+	int result;
+
+	/* Drive should be ready, issue command
+	 */
+	result = ftape_parameter(parm);
+	if (result >= 0) {
+		result = ftape_ready_wait(timeout, status);
+	}
+	return result;
+}
+
+/*--------------------------------------------------------------------------
+ *      Report operations
+ */
+
+/* Query the drive about its status.  The command is sent and
+   result_length bits of status are returned (2 extra bits are read
+   for start and stop). */
+
+int ftape_report_operation(int *status,
+			   qic117_cmd_t command,
+			   int result_length)
+{
+	int i, st3;
+	unsigned int t0;
+	unsigned int dt;
+	TRACE_FUN(ft_t_any);
+
+	TRACE_CATCH(ftape_command(command),);
+	t0 = ftape_timestamp();
+	i = 0;
+	do {
+		++i;
+		ftape_sleep(3 * FT_MILLISECOND);	/* see remark below */
+		TRACE_CATCH(fdc_sense_drive_status(&st3),);
+		dt = ftape_timediff(t0, ftape_timestamp());
+		/*  Ack should be asserted within Ttimout + Tack = 6 msec.
+		 *  Looks like some drives fail to do this so extend this
+		 *  period to 300 msec.
+		 */
+	} while (!(st3 & ST3_TRACK_0) && dt < 300000);
+	if (!(st3 & ST3_TRACK_0)) {
+		TRACE(ft_t_err,
+		      "No acknowledge after %u msec. (%i iter)", dt / 1000, i);
+		TRACE_ABORT(-EIO, ft_t_err, "timeout on Acknowledge");
+	}
+	/*  dt may be larger than expected because of other tasks
+	 *  scheduled while we were sleeping.
+	 */
+	if (i > 1 && dt > 6000) {
+		TRACE(ft_t_err, "Acknowledge after %u msec. (%i iter)",
+		      dt / 1000, i);
+	}
+	*status = 0;
+	for (i = 0; i < result_length + 1; i++) {
+		TRACE_CATCH(ftape_command(QIC_REPORT_NEXT_BIT),);
+		TRACE_CATCH(fdc_sense_drive_status(&st3),);
+		if (i < result_length) {
+			*status |= ((st3 & ST3_TRACK_0) ? 1 : 0) << i;
+		} else if ((st3 & ST3_TRACK_0) == 0) {
+			TRACE_ABORT(-EIO, ft_t_err, "missing status stop bit");
+		}
+	}
+	/* this command will put track zero and index back into normal state */
+	(void)ftape_command(QIC_REPORT_NEXT_BIT);
+	TRACE_EXIT 0;
+}
+
+/* Report the current drive status. */
+
+int ftape_report_raw_drive_status(int *status)
+{
+	int result;
+	int count = 0;
+	TRACE_FUN(ft_t_any);
+
+	do {
+		result = ftape_report_operation(status,
+						QIC_REPORT_DRIVE_STATUS, 8);
+	} while (result < 0 && ++count <= 3);
+	if (result < 0) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "report_operation failed after %d trials", count);
+	}
+	if ((*status & 0xff) == 0xff) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "impossible drive status 0xff");
+	}
+	if (*status & QIC_STATUS_READY) {
+		ftape_current_command = QIC_NO_COMMAND; /* completed */
+	}
+	ft_last_status.status.drive_status = (__u8)(*status & 0xff);
+	TRACE_EXIT 0;
+}
+
+int ftape_report_drive_status(int *status)
+{
+	TRACE_FUN(ft_t_any);
+
+	TRACE_CATCH(ftape_report_raw_drive_status(status),);
+	if (*status & QIC_STATUS_NEW_CARTRIDGE ||
+	    !(*status & QIC_STATUS_CARTRIDGE_PRESENT)) {
+		ft_failure = 1;	/* will inhibit further operations */
+		TRACE_EXIT -EIO;
+	}
+	if (*status & QIC_STATUS_READY && *status & QIC_STATUS_ERROR) {
+		/*  Let caller handle all errors */
+		TRACE_ABORT(1, ft_t_warn, "warning: error status set!");
+	}
+	TRACE_EXIT 0;
+}
+
+int ftape_report_error(unsigned int *error,
+		       qic117_cmd_t *command, int report)
+{
+	static const ftape_error ftape_errors[] = QIC117_ERRORS;
+	int code;
+	TRACE_FUN(ft_t_any);
+
+	TRACE_CATCH(ftape_report_operation(&code, QIC_REPORT_ERROR_CODE, 16),);
+	*error   = (unsigned int)(code & 0xff);
+	*command = (qic117_cmd_t)((code>>8)&0xff);
+	/*  remember hardware status, maybe useful for status ioctls
+	 */
+	ft_last_error.error.command = (__u8)*command;
+	ft_last_error.error.error   = (__u8)*error;
+	if (!report) {
+		TRACE_EXIT 0;
+	}
+	if (*error == 0) {
+		TRACE_ABORT(0, ft_t_info, "No error");
+	}
+	TRACE(ft_t_info, "errorcode: %d", *error);
+	if (*error < NR_ITEMS(ftape_errors)) {
+		TRACE(ft_t_noise, "%sFatal ERROR:",
+		      (ftape_errors[*error].fatal ? "" : "Non-"));
+		TRACE(ft_t_noise, "%s ...", ftape_errors[*error].message);
+	} else {
+		TRACE(ft_t_noise, "Unknown ERROR !");
+	}
+	if ((unsigned int)*command < NR_ITEMS(qic117_cmds) &&
+	    qic117_cmds[*command].name != NULL) {
+		TRACE(ft_t_noise, "... caused by command \'%s\'",
+		      qic117_cmds[*command].name);
+	} else {
+		TRACE(ft_t_noise, "... caused by unknown command %d",
+		      *command);
+	}
+	TRACE_EXIT 0;
+}
+
+int ftape_report_configuration(qic_model *model,
+			       unsigned int *rate,
+			       int *qic_std,
+			       int *tape_len)
+{
+	int result;
+	int config;
+	int status;
+	static const unsigned int qic_rates[ 4] = { 250, 2000, 500, 1000 };
+	TRACE_FUN(ft_t_any);
+
+	result = ftape_report_operation(&config,
+					QIC_REPORT_DRIVE_CONFIGURATION, 8);
+	if (result < 0) {
+		ft_last_status.status.drive_config = (__u8)0x00;
+		*model = prehistoric;
+		*rate = 500;
+		*qic_std = QIC_TAPE_QIC40;
+		*tape_len = 205;
+		TRACE_EXIT 0;
+	} else {
+		ft_last_status.status.drive_config = (__u8)(config & 0xff);
+	}
+	*rate = qic_rates[(config & QIC_CONFIG_RATE_MASK) >> QIC_CONFIG_RATE_SHIFT];
+	result = ftape_report_operation(&status, QIC_REPORT_TAPE_STATUS, 8);
+	if (result < 0) {
+		ft_last_status.status.tape_status = (__u8)0x00;
+		/* pre- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is valid.
+		 */
+		*qic_std = (config & QIC_CONFIG_80) ?
+			QIC_TAPE_QIC80 : QIC_TAPE_QIC40;
+		/* ?? how's about 425ft tapes? */
+		*tape_len = (config & QIC_CONFIG_LONG) ? 307 : 0;
+		*model = pre_qic117c;
+		result = 0;
+	} else {
+		ft_last_status.status.tape_status = (__u8)(status & 0xff);
+		*model = post_qic117b;
+		TRACE(ft_t_any, "report tape status result = %02x", status);
+		/* post- QIC117 rev C spec. drive, QIC_CONFIG_80 bit is
+		 * invalid. 
+		 */
+		switch (status & QIC_TAPE_STD_MASK) {
+		case QIC_TAPE_QIC40:
+		case QIC_TAPE_QIC80:
+		case QIC_TAPE_QIC3020:
+		case QIC_TAPE_QIC3010:
+			*qic_std = status & QIC_TAPE_STD_MASK;
+			break;
+		default:
+			*qic_std = -1;
+			break;
+		}
+		switch (status & QIC_TAPE_LEN_MASK) {
+		case QIC_TAPE_205FT:
+			/* 205 or 425+ ft 550 Oe tape */
+			*tape_len = 0;
+			break;
+		case QIC_TAPE_307FT:
+			/* 307.5 ft 550 Oe Extended Length (XL) tape */
+			*tape_len = 307;
+			break;
+		case QIC_TAPE_VARIABLE:
+			/* Variable length 550 Oe tape */
+			*tape_len = 0;
+			break;
+		case QIC_TAPE_1100FT:
+			/* 1100 ft 550 Oe tape */
+			*tape_len = 1100;
+			break;
+		case QIC_TAPE_FLEX:
+			/* Variable length 900 Oe tape */
+			*tape_len = 0;
+			break;
+		default:
+			*tape_len = -1;
+			break;
+		}
+		if (*qic_std == -1 || *tape_len == -1) {
+			TRACE(ft_t_any,
+			      "post qic-117b spec drive with unknown tape");
+		}
+		result = *tape_len == -1 ? -EIO : 0;
+		if (status & QIC_TAPE_WIDE) {
+			switch (*qic_std) {
+			case QIC_TAPE_QIC80:
+				TRACE(ft_t_info, "TR-1 tape detected");
+				break;
+			case QIC_TAPE_QIC3010:
+				TRACE(ft_t_info, "TR-2 tape detected");
+				break;
+			case QIC_TAPE_QIC3020:
+				TRACE(ft_t_info, "TR-3 tape detected");
+				break;
+			default:
+				TRACE(ft_t_warn,
+				      "Unknown Travan tape type detected");
+				break;
+			}
+		}
+	}
+	TRACE_EXIT (result < 0) ? -EIO : 0;
+}
+
+static int ftape_report_rom_version(int *version)
+{
+
+	if (ftape_report_operation(version, QIC_REPORT_ROM_VERSION, 8) < 0) {
+		return -EIO;
+	} else {
+		return 0;
+	}
+}
+
+void ftape_report_vendor_id(unsigned int *id)
+{
+	int result;
+	TRACE_FUN(ft_t_any);
+
+	/* We'll try to get a vendor id from the drive.  First
+	 * according to the QIC-117 spec, a 16-bit id is requested.
+	 * If that fails we'll try an 8-bit version, otherwise we'll
+	 * try an undocumented query.
+	 */
+	result = ftape_report_operation((int *) id, QIC_REPORT_VENDOR_ID, 16);
+	if (result < 0) {
+		result = ftape_report_operation((int *) id,
+						QIC_REPORT_VENDOR_ID, 8);
+		if (result < 0) {
+			/* The following is an undocumented call found
+			 * in the CMS code.
+			 */
+			result = ftape_report_operation((int *) id, 24, 8);
+			if (result < 0) {
+				*id = UNKNOWN_VENDOR;
+			} else {
+				TRACE(ft_t_noise, "got old 8 bit id: %04x",
+				      *id);
+				*id |= 0x20000;
+			}
+		} else {
+			TRACE(ft_t_noise, "got 8 bit id: %04x", *id);
+			*id |= 0x10000;
+		}
+	} else {
+		TRACE(ft_t_noise, "got 16 bit id: %04x", *id);
+	}
+	if (*id == 0x0047) {
+		int version;
+		int sign;
+
+		if (ftape_report_rom_version(&version) < 0) {
+			TRACE(ft_t_bug, "report rom version failed");
+			TRACE_EXIT;
+		}
+		TRACE(ft_t_noise, "CMS rom version: %d", version);
+		ftape_command(QIC_ENTER_DIAGNOSTIC_1);
+		ftape_command(QIC_ENTER_DIAGNOSTIC_1);
+		diagnostic_mode = 1;
+		if (ftape_report_operation(&sign, 9, 8) < 0) {
+			unsigned int error;
+			qic117_cmd_t command;
+
+			ftape_report_error(&error, &command, 1);
+			ftape_command(QIC_ENTER_PRIMARY_MODE);
+			diagnostic_mode = 0;
+			TRACE_EXIT;	/* failure ! */
+		} else {
+			TRACE(ft_t_noise, "CMS signature: %02x", sign);
+		}
+		if (sign == 0xa5) {
+			result = ftape_report_operation(&sign, 37, 8);
+			if (result < 0) {
+				if (version >= 63) {
+					*id = 0x8880;
+					TRACE(ft_t_noise,
+					      "This is an Iomega drive !");
+				} else {
+					*id = 0x0047;
+					TRACE(ft_t_noise,
+					      "This is a real CMS drive !");
+				}
+			} else {
+				*id = 0x0047;
+				TRACE(ft_t_noise, "CMS status: %d", sign);
+			}
+		} else {
+			*id = UNKNOWN_VENDOR;
+		}
+		ftape_command(QIC_ENTER_PRIMARY_MODE);
+		diagnostic_mode = 0;
+	}
+	TRACE_EXIT;
+}
+
+static int qic_rate_code(unsigned int rate)
+{
+	switch (rate) {
+	case 250:
+		return QIC_CONFIG_RATE_250;
+	case 500:
+		return QIC_CONFIG_RATE_500;
+	case 1000:
+		return QIC_CONFIG_RATE_1000;
+	case 2000:
+		return QIC_CONFIG_RATE_2000;
+	default:
+		return QIC_CONFIG_RATE_500;
+	}
+}
+
+static int ftape_set_rate_test(unsigned int *max_rate)
+{
+	unsigned int error;
+	qic117_cmd_t command;
+	int status;
+	int supported = 0;
+	TRACE_FUN(ft_t_any);
+
+	/*  Check if the drive does support the select rate command
+	 *  by testing all different settings. If any one is accepted
+	 *  we assume the command is supported, else not.
+	 */
+	for (*max_rate = 2000; *max_rate >= 250; *max_rate /= 2) {
+		if (ftape_command(QIC_SELECT_RATE) < 0) {
+			continue;
+		}		
+		if (ftape_parameter_wait(qic_rate_code(*max_rate),
+					 1 * FT_SECOND, &status) < 0) {
+			continue;
+		}
+		if (status & QIC_STATUS_ERROR) {
+			ftape_report_error(&error, &command, 0);
+			continue;
+		}
+		supported = 1; /* did accept a request */
+		break;
+	}
+	TRACE(ft_t_noise, "Select Rate command is%s supported", 
+	      supported ? "" : " not");
+	TRACE_EXIT supported;
+}
+
+int ftape_set_data_rate(unsigned int new_rate /* Kbps */, unsigned int qic_std)
+{
+	int status;
+	int result = 0;
+	unsigned int data_rate = new_rate;
+	static int supported;
+	int rate_changed = 0;
+	qic_model dummy_model;
+	unsigned int dummy_qic_std, dummy_tape_len;
+	TRACE_FUN(ft_t_any);
+
+	if (ft_drive_max_rate == 0) { /* first time */
+		supported = ftape_set_rate_test(&ft_drive_max_rate);
+	}
+	if (supported) {
+		ftape_command(QIC_SELECT_RATE);
+		result = ftape_parameter_wait(qic_rate_code(new_rate),
+					      1 * FT_SECOND, &status);
+		if (result >= 0 && !(status & QIC_STATUS_ERROR)) {
+			rate_changed = 1;
+		}
+	}
+	TRACE_CATCH(result = ftape_report_configuration(&dummy_model,
+							&data_rate, 
+							&dummy_qic_std,
+							&dummy_tape_len),);
+	if (data_rate != new_rate) {
+		if (!supported) {
+			TRACE(ft_t_warn, "Rate change not supported!");
+		} else if (rate_changed) {
+			TRACE(ft_t_warn, "Requested: %d, got %d",
+			      new_rate, data_rate);
+		} else {
+			TRACE(ft_t_warn, "Rate change failed!");
+		}
+		result = -EINVAL;
+	}
+	/*
+	 *  Set data rate and write precompensation as specified:
+	 *
+	 *            |  QIC-40/80  | QIC-3010/3020
+	 *   rate     |   precomp   |    precomp
+	 *  ----------+-------------+--------------
+	 *  250 Kbps. |   250 ns.   |     0 ns.
+	 *  500 Kbps. |   125 ns.   |     0 ns.
+	 *    1 Mbps. |    42 ns.   |     0 ns.
+	 *    2 Mbps  |      N/A    |     0 ns.
+	 */
+	if ((qic_std == QIC_TAPE_QIC40 && data_rate > 500) || 
+	    (qic_std == QIC_TAPE_QIC80 && data_rate > 1000)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_warn, "Datarate too high for QIC-mode");
+	}
+	TRACE_CATCH(fdc_set_data_rate(data_rate),_res = -EINVAL);
+	ft_data_rate = data_rate;
+	if (qic_std == QIC_TAPE_QIC40 || qic_std == QIC_TAPE_QIC80) {
+		switch (data_rate) {
+		case 250:
+			fdc_set_write_precomp(250);
+			break;
+		default:
+		case 500:
+			fdc_set_write_precomp(125);
+			break;
+		case 1000:
+			fdc_set_write_precomp(42);
+			break;
+		}
+	} else {
+		fdc_set_write_precomp(0);
+	}
+	TRACE_EXIT result;
+}
+
+/*  The next two functions are used to cope with excessive overrun errors
+ */
+int ftape_increase_threshold(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (fdc.type < i82077 || ft_fdc_threshold >= 12) {
+		TRACE_ABORT(-EIO, ft_t_err, "cannot increase fifo threshold");
+	}
+	if (fdc_fifo_threshold(++ft_fdc_threshold, NULL, NULL, NULL) < 0) {
+		TRACE(ft_t_err, "cannot increase fifo threshold");
+		ft_fdc_threshold --;
+		fdc_reset();
+	}
+	TRACE(ft_t_info, "New FIFO threshold: %d", ft_fdc_threshold);
+	TRACE_EXIT 0;
+}
+
+int ftape_half_data_rate(void)
+{
+	if (ft_data_rate < 500) {
+		return -1;
+	}
+	if (ftape_set_data_rate(ft_data_rate / 2, ft_qic_std) < 0) {
+		return -EIO;
+	}
+	ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len);
+	return 0;
+}
+
+/*      Seek the head to the specified track.
+ */
+int ftape_seek_head_to_track(unsigned int track)
+{
+	int status;
+	TRACE_FUN(ft_t_any);
+
+	ft_location.track = -1; /* remains set in case of error */
+	if (track >= ft_tracks_per_tape) {
+		TRACE_ABORT(-EINVAL, ft_t_bug, "track out of bounds");
+	}
+	TRACE(ft_t_flow, "seeking track %d", track);
+	TRACE_CATCH(ftape_command(QIC_SEEK_HEAD_TO_TRACK),);
+	TRACE_CATCH(ftape_parameter_wait(track, ftape_timeout.head_seek,
+					 &status),);
+	ft_location.track = track;
+	ftape_might_be_off_track = 0;
+	TRACE_EXIT 0;
+}
+
+int ftape_wakeup_drive(wake_up_types method)
+{
+	int status;
+	int motor_on = 0;
+	TRACE_FUN(ft_t_any);
+
+	switch (method) {
+	case wake_up_colorado:
+		TRACE_CATCH(ftape_command(QIC_PHANTOM_SELECT),);
+		TRACE_CATCH(ftape_parameter(0 /* ft_drive_sel ?? */),);
+		break;
+	case wake_up_mountain:
+		TRACE_CATCH(ftape_command(QIC_SOFT_SELECT),);
+		ftape_sleep(FT_MILLISECOND);	/* NEEDED */
+		TRACE_CATCH(ftape_parameter(18),);
+		break;
+	case wake_up_insight:
+		ftape_sleep(100 * FT_MILLISECOND);
+		motor_on = 1;
+		fdc_motor(motor_on);	/* enable is done by motor-on */
+	case no_wake_up:
+		break;
+	default:
+		TRACE_EXIT -ENODEV;	/* unknown wakeup method */
+		break;
+	}
+	/*  If wakeup succeeded we shouldn't get an error here..
+	 */
+	TRACE_CATCH(ftape_report_raw_drive_status(&status),
+		    if (motor_on) {
+			    fdc_motor(0);
+		    });
+	TRACE_EXIT 0;
+}
+
+int ftape_put_drive_to_sleep(wake_up_types method)
+{
+	TRACE_FUN(ft_t_any);
+
+	switch (method) {
+	case wake_up_colorado:
+		TRACE_CATCH(ftape_command(QIC_PHANTOM_DESELECT),);
+		break;
+	case wake_up_mountain:
+		TRACE_CATCH(ftape_command(QIC_SOFT_DESELECT),);
+		break;
+	case wake_up_insight:
+		fdc_motor(0);	/* enable is done by motor-on */
+	case no_wake_up:	/* no wakeup / no sleep ! */
+		break;
+	default:
+		TRACE_EXIT -ENODEV;	/* unknown wakeup method */
+	}
+	TRACE_EXIT 0;
+}
+
+int ftape_reset_drive(void)
+{
+	int result = 0;
+	int status;
+	unsigned int err_code;
+	qic117_cmd_t err_command;
+	int i;
+	TRACE_FUN(ft_t_any);
+
+	/*  We want to re-establish contact with our drive.  Fire a
+	 *  number of reset commands (single step pulses) and pray for
+	 *  success.
+	 */
+	for (i = 0; i < 2; ++i) {
+		TRACE(ft_t_flow, "Resetting fdc");
+		fdc_reset();
+		ftape_sleep(10 * FT_MILLISECOND);
+		TRACE(ft_t_flow, "Reset command to drive");
+		result = ftape_command(QIC_RESET);
+		if (result == 0) {
+			ftape_sleep(1 * FT_SECOND); /*  drive not
+						     *  accessible
+						     *  during 1 second
+						     */
+			TRACE(ft_t_flow, "Re-selecting drive");
+
+			/* Strange, the QIC-117 specs don't mention
+			 * this but the drive gets deselected after a
+			 * soft reset !  So we need to enable it
+			 * again.
+			 */
+			if (ftape_wakeup_drive(ft_drive_type.wake_up) < 0) {
+				TRACE(ft_t_err, "Wakeup failed !");
+			}
+			TRACE(ft_t_flow, "Waiting until drive gets ready");
+			result= ftape_ready_wait(ftape_timeout.reset, &status);
+			if (result == 0 && (status & QIC_STATUS_ERROR)) {
+				result = ftape_report_error(&err_code,
+							    &err_command, 1);
+				if (result == 0 && err_code == 27) {
+					/*  Okay, drive saw reset
+					 *  command and responded as it
+					 *  should
+					 */
+					break;
+				} else {
+					result = -EIO;
+				}
+			} else {
+				result = -EIO;
+			}
+		}
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+	}
+	if (result != 0) {
+		TRACE(ft_t_err, "General failure to reset tape drive");
+	} else {
+		/*  Restore correct settings: keep original rate 
+		 */
+		ftape_set_data_rate(ft_data_rate, ft_qic_std);
+	}
+	ftape_init_drive_needed = 1;
+	TRACE_EXIT result;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-io.h b/drivers/char/ftape/lowlevel/ftape-io.h
new file mode 100644
index 0000000..26a7baa
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-io.h
@@ -0,0 +1,90 @@
+#ifndef _FTAPE_IO_H
+#define _FTAPE_IO_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ *           (C) 1997      Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-io.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:18 $
+ *
+ *      This file contains definitions for the glue part of the
+ *      QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/qic117.h>
+#include <linux/ftape-vendors.h>
+
+typedef struct {
+	unsigned int seek;
+	unsigned int reset;
+	unsigned int rewind;
+	unsigned int head_seek;
+	unsigned int stop;
+	unsigned int pause;
+} ft_timeout_table;
+
+typedef enum {
+	prehistoric, pre_qic117c, post_qic117b, post_qic117d 
+} qic_model;
+
+/*
+ *      ftape-io.c defined global vars.
+ */
+extern ft_timeout_table ftape_timeout;
+extern unsigned int ftape_tape_len;
+extern volatile qic117_cmd_t ftape_current_command;
+extern const struct qic117_command_table qic117_cmds[];
+extern int ftape_might_be_off_track;
+
+/*
+ *      ftape-io.c defined global functions.
+ */
+extern void ftape_udelay(unsigned int usecs);
+extern void  ftape_udelay_calibrate(void);
+extern void ftape_sleep(unsigned int time);
+extern void ftape_report_vendor_id(unsigned int *id);
+extern int  ftape_command(qic117_cmd_t command);
+extern int  ftape_command_wait(qic117_cmd_t command,
+			       unsigned int timeout,
+			       int *status);
+extern int  ftape_parameter(unsigned int parameter);
+extern int ftape_report_operation(int *status,
+				  qic117_cmd_t  command,
+				  int result_length);
+extern int ftape_report_configuration(qic_model *model,
+				      unsigned int *rate,
+				      int *qic_std,
+				      int *tape_len);
+extern int ftape_report_drive_status(int *status);
+extern int ftape_report_raw_drive_status(int *status);
+extern int ftape_report_status(int *status);
+extern int ftape_ready_wait(unsigned int timeout, int *status);
+extern int ftape_seek_head_to_track(unsigned int track);
+extern int ftape_set_data_rate(unsigned int new_rate, unsigned int qic_std);
+extern int ftape_report_error(unsigned int *error,
+			      qic117_cmd_t *command,
+			      int report);
+extern int ftape_reset_drive(void);
+extern int ftape_put_drive_to_sleep(wake_up_types method);
+extern int ftape_wakeup_drive(wake_up_types method);
+extern int ftape_increase_threshold(void);
+extern int ftape_half_data_rate(void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-proc.c b/drivers/char/ftape/lowlevel/ftape-proc.c
new file mode 100644
index 0000000..c66251e
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-proc.c
@@ -0,0 +1,215 @@
+/*
+ *      Copyright (C) 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-proc.c,v $
+ * $Revision: 1.11 $
+ * $Date: 1997/10/24 14:47:37 $
+ *
+ *      This file contains the procfs interface for the
+ *      QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+
+ *	Old code removed, switched to dynamic proc entry.
+ */
+
+#include <linux/config.h>
+
+#if defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS)
+
+#include <linux/proc_fs.h>
+
+#include <linux/ftape.h>
+#include <linux/init.h>
+#include <linux/qic117.h>
+
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-proc.h"
+#include "../lowlevel/ftape-tracing.h"
+
+static size_t get_driver_info(char *buf)
+{
+	const char *debug_level[] = { "bugs"  ,
+				      "errors",
+				      "warnings",
+				      "informational",
+				      "noisy",
+				      "program flow",
+				      "fdc and dma",
+				      "data flow",
+				      "anything" };
+
+	return sprintf(buf,
+		       "version       : %s\n"
+		       "used data rate: %d kbit/sec\n"
+		       "dma memory    : %d kb\n"
+		       "debug messages: %s\n",
+		       FTAPE_VERSION,
+		       ft_data_rate,
+		       FT_BUFF_SIZE * ft_nr_buffers >> 10,
+		       debug_level[TRACE_LEVEL]);
+}
+
+static size_t get_tapedrive_info(char *buf)
+{ 
+	return sprintf(buf,
+		       "vendor id : 0x%04x\n"
+		       "drive name: %s\n"
+		       "wind speed: %d ips\n"
+		       "wakeup    : %s\n"
+		       "max. rate : %d kbit/sec\n",
+		       ft_drive_type.vendor_id,
+		       ft_drive_type.name,
+		       ft_drive_type.speed,
+		       ((ft_drive_type.wake_up == no_wake_up)
+			? "No wakeup needed" :
+			((ft_drive_type.wake_up == wake_up_colorado)
+			 ? "Colorado" :
+			 ((ft_drive_type.wake_up == wake_up_mountain)
+			  ? "Mountain" :
+			  ((ft_drive_type.wake_up == wake_up_insight)
+			   ? "Motor on" :
+			   "Unknown")))),
+		       ft_drive_max_rate);
+}
+
+static size_t get_cartridge_info(char *buf)
+{
+	if (ftape_init_drive_needed) {
+		return sprintf(buf, "uninitialized\n");
+	}
+	if (ft_no_tape) {
+		return sprintf(buf, "no cartridge inserted\n");
+	}
+	return sprintf(buf,
+		       "segments  : %5d\n"
+		       "tracks    : %5d\n"
+		       "length    : %5dft\n"
+		       "formatted : %3s\n"
+		       "writable  : %3s\n"
+		       "QIC spec. : QIC-%s\n"
+		       "fmt-code  : %1d\n",
+		       ft_segments_per_track,
+		       ft_tracks_per_tape,
+		       ftape_tape_len,
+		       (ft_formatted == 1) ? "yes" : "no",
+		       (ft_write_protected == 1) ? "no" : "yes",
+		       ((ft_qic_std == QIC_TAPE_QIC40) ? "40" :
+			((ft_qic_std == QIC_TAPE_QIC80) ? "80" :
+			 ((ft_qic_std == QIC_TAPE_QIC3010) ? "3010" :
+			  ((ft_qic_std == QIC_TAPE_QIC3020) ? "3020" :
+			   "???")))),
+		       ft_format_code);
+}
+
+static size_t get_controller_info(char *buf)
+{
+	const char  *fdc_name[] = { "no fdc",
+				    "i8272",
+				    "i82077",
+				    "i82077AA",
+				    "Colorado FC-10 or FC-20",
+				    "i82078",
+				    "i82078_1" };
+
+	return sprintf(buf,
+		       "FDC type  : %s\n"
+		       "FDC base  : 0x%03x\n"
+		       "FDC irq   : %d\n"
+		       "FDC dma   : %d\n"
+		       "FDC thr.  : %d\n"
+		       "max. rate : %d kbit/sec\n",
+		       ft_mach2 ? "Mountain MACH-2" : fdc_name[fdc.type],
+		       fdc.sra, fdc.irq, fdc.dma,
+		       ft_fdc_threshold, ft_fdc_max_rate);
+}
+
+static size_t get_history_info(char *buf)
+{
+        size_t len;
+
+	len  = sprintf(buf,
+		       "\nFDC isr statistics\n"
+		       " id_am_errors     : %3d\n"
+		       " id_crc_errors    : %3d\n"
+		       " data_am_errors   : %3d\n"
+		       " data_crc_errors  : %3d\n"
+		       " overrun_errors   : %3d\n"
+		       " no_data_errors   : %3d\n"
+		       " retries          : %3d\n",
+		       ft_history.id_am_errors,   ft_history.id_crc_errors,
+		       ft_history.data_am_errors, ft_history.data_crc_errors,
+		       ft_history.overrun_errors, ft_history.no_data_errors,
+		       ft_history.retries);
+	len += sprintf(buf + len,
+		       "\nECC statistics\n"
+		       " crc_errors       : %3d\n"
+		       " crc_failures     : %3d\n"
+		       " ecc_failures     : %3d\n"
+		       " sectors corrected: %3d\n",
+		       ft_history.crc_errors,   ft_history.crc_failures,
+		       ft_history.ecc_failures, ft_history.corrected);
+	len += sprintf(buf + len,
+		       "\ntape quality statistics\n"
+		       " media defects    : %3d\n",
+		       ft_history.defects);
+	len += sprintf(buf + len,
+		       "\ntape motion statistics\n"
+		       " repositions      : %3d\n",
+		       ft_history.rewinds);
+	return len;
+}
+
+static int ftape_read_proc(char *page, char **start, off_t off,
+			   int count, int *eof, void *data)
+{
+	char *ptr = page;
+	size_t len;
+	
+	ptr += sprintf(ptr, "Kernel Driver\n\n");
+	ptr += get_driver_info(ptr);
+	ptr += sprintf(ptr, "\nTape Drive\n\n");
+	ptr += get_tapedrive_info(ptr);
+	ptr += sprintf(ptr, "\nFDC Controller\n\n");
+	ptr += get_controller_info(ptr);
+	ptr += sprintf(ptr, "\nTape Cartridge\n\n");
+	ptr += get_cartridge_info(ptr);
+	ptr += sprintf(ptr, "\nHistory Record\n\n");
+	ptr += get_history_info(ptr);
+
+	len = strlen(page);
+	*start = NULL;
+	if (off+count >= len) {
+		*eof = 1;
+	} else {
+		*eof = 0;
+	}
+	return len;
+}
+
+int __init ftape_proc_init(void)
+{
+	return create_proc_read_entry("ftape", 0, &proc_root,
+		ftape_read_proc, NULL) != NULL;
+}
+
+void ftape_proc_destroy(void)
+{
+	remove_proc_entry("ftape", &proc_root);
+}
+
+#endif /* defined(CONFIG_PROC_FS) && defined(CONFIG_FT_PROC_FS) */
diff --git a/drivers/char/ftape/lowlevel/ftape-proc.h b/drivers/char/ftape/lowlevel/ftape-proc.h
new file mode 100644
index 0000000..264dfcc
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-proc.h
@@ -0,0 +1,35 @@
+#ifndef _FTAPE_PROC_H
+#define _FTAPE_PROC_H
+
+/*
+ *      Copyright (C) 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-proc.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:20 $
+ *
+ *      This file contains definitions for the procfs interface of the
+ *      QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/proc_fs.h>
+
+extern int  ftape_proc_init(void);
+extern void ftape_proc_destroy(void);
+
+#endif
diff --git a/drivers/char/ftape/lowlevel/ftape-read.c b/drivers/char/ftape/lowlevel/ftape-read.c
new file mode 100644
index 0000000..d967d8c
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-read.c
@@ -0,0 +1,621 @@
+/*
+ *      Copyright (C) 1993-1996 Bas Laarhoven,
+ *                (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-read.c,v $
+ * $Revision: 1.6 $
+ * $Date: 1997/10/21 14:39:22 $
+ *
+ *      This file contains the reading code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-ecc.h"
+#include "../lowlevel/ftape-bsm.h"
+
+/*      Global vars.
+ */
+
+/*      Local vars.
+ */
+
+void ftape_zap_read_buffers(void)
+{
+	int i;
+
+	for (i = 0; i < ft_nr_buffers; ++i) {
+/*  changed to "fit" with dynamic allocation of tape_buffer. --khp  */
+		ft_buffer[i]->status = waiting;
+		ft_buffer[i]->bytes = 0;
+		ft_buffer[i]->skip = 0;
+		ft_buffer[i]->retry = 0;
+	}
+/*	ftape_reset_buffer(); */
+}
+
+static SectorMap convert_sector_map(buffer_struct * buff)
+{
+	int i = 0;
+	SectorMap bad_map = ftape_get_bad_sector_entry(buff->segment_id);
+	SectorMap src_map = buff->soft_error_map | buff->hard_error_map;
+	SectorMap dst_map = 0;
+	TRACE_FUN(ft_t_any);
+
+	if (bad_map || src_map) {
+		TRACE(ft_t_flow, "bad_map = 0x%08lx", (long) bad_map);
+		TRACE(ft_t_flow, "src_map = 0x%08lx", (long) src_map);
+	}
+	while (bad_map) {
+		while ((bad_map & 1) == 0) {
+			if (src_map & 1) {
+				dst_map |= (1 << i);
+			}
+			src_map >>= 1;
+			bad_map >>= 1;
+			++i;
+		}
+		/* (bad_map & 1) == 1 */
+		src_map >>= 1;
+		bad_map >>= 1;
+	}
+	if (src_map) {
+		dst_map |= (src_map << i);
+	}
+	if (dst_map) {
+		TRACE(ft_t_flow, "dst_map = 0x%08lx", (long) dst_map);
+	}
+	TRACE_EXIT dst_map;
+}
+
+static int correct_and_copy_fraction(buffer_struct *buff, __u8 * destination,
+				     int start, int size)
+{
+	struct memory_segment mseg;
+	int result;
+	SectorMap read_bad;
+	TRACE_FUN(ft_t_any);
+
+	mseg.read_bad = convert_sector_map(buff);
+	mseg.marked_bad = 0;	/* not used... */
+	mseg.blocks = buff->bytes / FT_SECTOR_SIZE;
+	mseg.data = buff->address;
+	/*    If there are no data sectors we can skip this segment.
+	 */
+	if (mseg.blocks <= 3) {
+		TRACE_ABORT(0, ft_t_noise, "empty segment");
+	}
+	read_bad = mseg.read_bad;
+	ft_history.crc_errors += count_ones(read_bad);
+	result = ftape_ecc_correct_data(&mseg);
+	if (read_bad != 0 || mseg.corrected != 0) {
+		TRACE(ft_t_noise, "crc error map: 0x%08lx", (unsigned long)read_bad);
+		TRACE(ft_t_noise, "corrected map: 0x%08lx", (unsigned long)mseg.corrected);
+		ft_history.corrected += count_ones(mseg.corrected);
+	}
+	if (result == ECC_CORRECTED || result == ECC_OK) {
+		if (result == ECC_CORRECTED) {
+			TRACE(ft_t_info, "ecc corrected segment: %d", buff->segment_id);
+		}
+		if(start < 0) {
+			start= 0;
+		}
+		if((start+size) > ((mseg.blocks - 3) * FT_SECTOR_SIZE)) {
+			size = (mseg.blocks - 3) * FT_SECTOR_SIZE  - start;
+		} 
+		if (size < 0) {
+			size= 0;
+		}
+		if(size > 0) {
+			memcpy(destination + start, mseg.data + start, size);
+		}
+		if ((read_bad ^ mseg.corrected) & mseg.corrected) {
+			/* sectors corrected without crc errors set */
+			ft_history.crc_failures++;
+		}
+		TRACE_EXIT size; /* (mseg.blocks - 3) * FT_SECTOR_SIZE; */
+	} else {
+		ft_history.ecc_failures++;
+		TRACE_ABORT(-EAGAIN,
+			    ft_t_err, "ecc failure on segment %d",
+			    buff->segment_id);
+	}
+	TRACE_EXIT 0;
+}
+
+/*      Read given segment into buffer at address.
+ */
+int ftape_read_segment_fraction(const int segment_id,
+				void  *address, 
+				const ft_read_mode_t read_mode,
+				const int start,
+				const int size)
+{
+	int result = 0;
+	int retry  = 0;
+	int bytes_read = 0;
+	int read_done  = 0;
+	TRACE_FUN(ft_t_flow);
+
+	ft_history.used |= 1;
+	TRACE(ft_t_data_flow, "segment_id = %d", segment_id);
+	if (ft_driver_state != reading) {
+		TRACE(ft_t_noise, "calling ftape_abort_operation");
+		TRACE_CATCH(ftape_abort_operation(),);
+		ftape_set_state(reading);
+	}
+	for(;;) {
+		buffer_struct *tail;
+		/*  Allow escape from this loop on signal !
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		/*  Search all full buffers for the first matching the
+		 *  wanted segment.  Clear other buffers on the fly.
+		 */
+		tail = ftape_get_buffer(ft_queue_tail);
+		while (!read_done && tail->status == done) {
+			/*  Allow escape from this loop on signal !
+			 */
+			FT_SIGNAL_EXIT(_DONT_BLOCK);
+			if (tail->segment_id == segment_id) {
+				/*  If out buffer is already full,
+				 *  return its contents.  
+				 */
+				TRACE(ft_t_flow, "found segment in cache: %d",
+				      segment_id);
+				if (tail->deleted) {
+					/*  Return a value that
+					 *  read_header_segment
+					 *  understands.  As this
+					 *  should only occur when
+					 *  searching for the header
+					 *  segments it shouldn't be
+					 *  misinterpreted elsewhere.
+					 */
+					TRACE_EXIT 0;
+				}
+				result = correct_and_copy_fraction(
+					tail,
+					address,
+					start,
+					size);
+				TRACE(ft_t_flow, "segment contains (bytes): %d",
+				      result);
+				if (result < 0) {
+					if (result != -EAGAIN) {
+						TRACE_EXIT result;
+					}
+					/* keep read_done == 0, will
+					 * trigger
+					 * ftape_abort_operation
+					 * because reading wrong
+					 * segment.
+					 */
+					TRACE(ft_t_err, "ecc failed, retry");
+					++retry;
+				} else {
+					read_done = 1;
+					bytes_read = result;
+				}
+			} else {
+				TRACE(ft_t_flow,"zapping segment in cache: %d",
+				      tail->segment_id);
+			}
+			tail->status = waiting;
+			tail = ftape_next_buffer(ft_queue_tail);
+		}
+		if (!read_done && tail->status == reading) {
+			if (tail->segment_id == segment_id) {
+				switch(ftape_wait_segment(reading)) {
+				case 0:
+					break;
+				case -EINTR:
+					TRACE_ABORT(-EINTR, ft_t_warn,
+						    "interrupted by "
+						    "non-blockable signal");
+					break;
+				default:
+					TRACE(ft_t_noise,
+					      "wait_segment failed");
+					ftape_abort_operation();
+					ftape_set_state(reading);
+					break;
+				}
+			} else {
+				/*  We're reading the wrong segment,
+				 *  stop runner.
+				 */
+				TRACE(ft_t_noise, "reading wrong segment");
+				ftape_abort_operation();
+				ftape_set_state(reading);
+			}
+		}
+		/*    should runner stop ?
+		 */
+		if (ft_runner_status == aborting) {
+			buffer_struct *head = ftape_get_buffer(ft_queue_head);
+			switch(head->status) {
+			case error:
+				ft_history.defects += 
+					count_ones(head->hard_error_map);
+			case reading:
+				head->status = waiting;
+				break;
+			default:
+				break;
+			}
+			TRACE_CATCH(ftape_dumb_stop(),);
+		} else {
+			/*  If just passed last segment on tape: wait
+			 *  for BOT or EOT mark. Sets ft_runner_status to
+			 *  idle if at lEOT and successful 
+			 */
+			TRACE_CATCH(ftape_handle_logical_eot(),);
+		}
+		/*    If we got a segment: quit, or else retry up to limit.
+		 *
+		 *    If segment to read is empty, do not start runner for it,
+		 *    but wait for next read call.
+		 */
+		if (read_done ||
+		    ftape_get_bad_sector_entry(segment_id) == EMPTY_SEGMENT ) {
+			/* bytes_read = 0;  should still be zero */
+			TRACE_EXIT bytes_read;
+
+		}
+		if (retry > FT_RETRIES_ON_ECC_ERROR) {
+			ft_history.defects++;
+			TRACE_ABORT(-ENODATA, ft_t_err,
+				    "too many retries on ecc failure");
+		}
+		/*    Now at least one buffer is empty !
+		 *    Restart runner & tape if needed.
+		 */
+		TRACE(ft_t_any, "head: %d, tail: %d, ft_runner_status: %d",
+		      ftape_buffer_id(ft_queue_head),
+		      ftape_buffer_id(ft_queue_tail),
+		      ft_runner_status);
+		TRACE(ft_t_any, "buffer[].status, [head]: %d, [tail]: %d",
+		      ftape_get_buffer(ft_queue_head)->status,
+		      ftape_get_buffer(ft_queue_tail)->status);
+		tail = ftape_get_buffer(ft_queue_tail);
+		if (tail->status == waiting) {
+			buffer_struct *head = ftape_get_buffer(ft_queue_head);
+
+			ftape_setup_new_segment(head, segment_id, -1);
+			if (read_mode == FT_RD_SINGLE) {
+				/* disable read-ahead */
+				head->next_segment = 0;
+			}
+			ftape_calc_next_cluster(head);
+			if (ft_runner_status == idle) {
+				result = ftape_start_tape(segment_id,
+							  head->sector_offset);
+				if (result < 0) {
+					TRACE_ABORT(result, ft_t_err, "Error: "
+						    "segment %d unreachable",
+						    segment_id);
+				}
+			}
+			head->status = reading;
+			fdc_setup_read_write(head, FDC_READ);
+		}
+	}
+	/* not reached */
+	TRACE_EXIT -EIO;
+}
+
+int ftape_read_header_segment(__u8 *address)
+{
+	int result;
+	int header_segment;
+	int first_failed = 0;
+	int status;
+	TRACE_FUN(ft_t_flow);
+
+	ft_used_header_segment = -1;
+	TRACE_CATCH(ftape_report_drive_status(&status),);
+	TRACE(ft_t_flow, "reading...");
+	/*  We're looking for the first header segment.
+	 *  A header segment cannot contain bad sectors, therefor at the
+	 *  tape start, segments with bad sectors are (according to QIC-40/80)
+	 *  written with deleted data marks and must be skipped.
+	 */
+	memset(address, '\0', (FT_SECTORS_PER_SEGMENT - 3) * FT_SECTOR_SIZE); 
+	result = 0;
+#define HEADER_SEGMENT_BOUNDARY 68  /* why not 42? */
+	for (header_segment = 0;
+	     header_segment < HEADER_SEGMENT_BOUNDARY && result == 0;
+	     ++header_segment) {
+		/*  Set no read-ahead, the isr will force read-ahead whenever
+		 *  it encounters deleted data !
+		 */
+		result = ftape_read_segment(header_segment,
+					    address,
+					    FT_RD_SINGLE);
+		if (result < 0 && !first_failed) {
+			TRACE(ft_t_err, "header segment damaged, trying backup");
+			first_failed = 1;
+			result = 0;	/* force read of next (backup) segment */
+		}
+	}
+	if (result < 0 || header_segment >= HEADER_SEGMENT_BOUNDARY) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "no readable header segment found");
+	}
+	TRACE_CATCH(ftape_abort_operation(),);
+	ft_used_header_segment = header_segment;
+	result = ftape_decode_header_segment(address);
+ 	TRACE_EXIT result;
+}
+
+int ftape_decode_header_segment(__u8 *address)
+{
+	unsigned int max_floppy_side;
+	unsigned int max_floppy_track;
+	unsigned int max_floppy_sector;
+	unsigned int new_tape_len;
+	TRACE_FUN(ft_t_flow);
+
+	if (GET4(address, FT_SIGNATURE) == FT_D2G_MAGIC) {
+		/* Ditto 2GB header segment. They encrypt the bad sector map.
+		 * We decrypt it and store them in normal format.
+		 * I hope this is correct.
+		 */
+		int i;
+		TRACE(ft_t_warn,
+		      "Found Ditto 2GB tape, "
+		      "trying to decrypt bad sector map");
+		for (i=256; i < 29 * FT_SECTOR_SIZE; i++) {
+			address[i] = ~(address[i] - (i&0xff));
+		}
+		PUT4(address, 0,FT_HSEG_MAGIC);
+	} else if (GET4(address, FT_SIGNATURE) != FT_HSEG_MAGIC) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "wrong signature in header segment");
+	}
+	ft_format_code = (ft_format_type) address[FT_FMT_CODE];
+	if (ft_format_code != fmt_big) {
+		ft_header_segment_1   = GET2(address, FT_HSEG_1);
+		ft_header_segment_2   = GET2(address, FT_HSEG_2);
+		ft_first_data_segment = GET2(address, FT_FRST_SEG);
+		ft_last_data_segment  = GET2(address, FT_LAST_SEG);
+	} else {
+		ft_header_segment_1   = GET4(address, FT_6_HSEG_1);
+		ft_header_segment_2   = GET4(address, FT_6_HSEG_2);
+		ft_first_data_segment = GET4(address, FT_6_FRST_SEG);
+		ft_last_data_segment  = GET4(address, FT_6_LAST_SEG);
+	}
+	TRACE(ft_t_noise, "first data segment: %d", ft_first_data_segment);
+	TRACE(ft_t_noise, "last  data segment: %d", ft_last_data_segment);
+	TRACE(ft_t_noise, "header segments are %d and %d",
+	      ft_header_segment_1, ft_header_segment_2);
+
+	/*    Verify tape parameters...
+	 *    QIC-40/80 spec:                 tape_parameters:
+	 *
+	 *    segments-per-track              segments_per_track
+	 *    tracks-per-cartridge            tracks_per_tape
+	 *    max-floppy-side                 (segments_per_track *
+	 *                                    tracks_per_tape - 1) /
+	 *                                    ftape_segments_per_head
+	 *    max-floppy-track                ftape_segments_per_head /
+	 *                                    ftape_segments_per_cylinder - 1
+	 *    max-floppy-sector               ftape_segments_per_cylinder *
+	 *                                    FT_SECTORS_PER_SEGMENT
+	 */
+	ft_segments_per_track = GET2(address, FT_SPT);
+	ft_tracks_per_tape    = address[FT_TPC];
+	max_floppy_side       = address[FT_FHM];
+	max_floppy_track      = address[FT_FTM];
+	max_floppy_sector     = address[FT_FSM];
+	TRACE(ft_t_noise, "(fmt/spt/tpc/fhm/ftm/fsm) = %d/%d/%d/%d/%d/%d",
+	      ft_format_code, ft_segments_per_track, ft_tracks_per_tape,
+	      max_floppy_side, max_floppy_track, max_floppy_sector);
+	new_tape_len = ftape_tape_len;
+	switch (ft_format_code) {
+	case fmt_425ft:
+		new_tape_len = 425;
+		break;
+	case fmt_normal:
+		if (ftape_tape_len == 0) {	/* otherwise 307 ft */
+			new_tape_len = 205;
+		}
+		break;
+	case fmt_1100ft:
+		new_tape_len = 1100;
+		break;
+	case fmt_var:{
+			int segments_per_1000_inch = 1;		/* non-zero default for switch */
+			switch (ft_qic_std) {
+			case QIC_TAPE_QIC40:
+				segments_per_1000_inch = 332;
+				break;
+			case QIC_TAPE_QIC80:
+				segments_per_1000_inch = 488;
+				break;
+			case QIC_TAPE_QIC3010:
+				segments_per_1000_inch = 730;
+				break;
+			case QIC_TAPE_QIC3020:
+				segments_per_1000_inch = 1430;
+				break;
+			}
+			new_tape_len = (1000 * ft_segments_per_track +
+					(segments_per_1000_inch - 1)) / segments_per_1000_inch;
+			break;
+		}
+	case fmt_big:{
+			int segments_per_1000_inch = 1;		/* non-zero default for switch */
+			switch (ft_qic_std) {
+			case QIC_TAPE_QIC40:
+				segments_per_1000_inch = 332;
+				break;
+			case QIC_TAPE_QIC80:
+				segments_per_1000_inch = 488;
+				break;
+			case QIC_TAPE_QIC3010:
+				segments_per_1000_inch = 730;
+				break;
+			case QIC_TAPE_QIC3020:
+				segments_per_1000_inch = 1430;
+				break;
+			default:
+				TRACE_ABORT(-EIO, ft_t_bug,
+			"%x QIC-standard with fmt-code %d, please report",
+					    ft_qic_std, ft_format_code);
+			}
+			new_tape_len = ((1000 * ft_segments_per_track +
+					 (segments_per_1000_inch - 1)) / 
+					segments_per_1000_inch);
+			break;
+		}
+	default:
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "unknown tape format, please report !");
+	}
+	if (new_tape_len != ftape_tape_len) {
+		ftape_tape_len = new_tape_len;
+		TRACE(ft_t_info, "calculated tape length is %d ft",
+		      ftape_tape_len);
+		ftape_calc_timeouts(ft_qic_std, ft_data_rate, ftape_tape_len);
+	}
+	if (ft_segments_per_track == 0 && ft_tracks_per_tape == 0 &&
+	    max_floppy_side == 0 && max_floppy_track == 0 &&
+	    max_floppy_sector == 0) {
+		/*  QIC-40 Rev E and earlier has no values in the header.
+		 */
+		ft_segments_per_track = 68;
+		ft_tracks_per_tape = 20;
+		max_floppy_side = 1;
+		max_floppy_track = 169;
+		max_floppy_sector = 128;
+	}
+	/*  This test will compensate for the wrong parameter on tapes
+	 *  formatted by Conner software.
+	 */
+	if (ft_segments_per_track == 150 &&
+	    ft_tracks_per_tape == 28 &&
+	    max_floppy_side == 7 &&
+	    max_floppy_track == 149 &&
+	    max_floppy_sector == 128) {
+TRACE(ft_t_info, "the famous CONNER bug: max_floppy_side off by one !");
+		max_floppy_side = 6;
+	}
+	/*  These tests will compensate for the wrong parameter on tapes
+	 *  formatted by ComByte Windows software.
+	 *
+	 *  First, for 205 foot tapes
+	 */
+	if (ft_segments_per_track == 100 &&
+	    ft_tracks_per_tape == 28 &&
+	    max_floppy_side == 9 &&
+	    max_floppy_track == 149 &&
+	    max_floppy_sector == 128) {
+TRACE(ft_t_info, "the ComByte bug: max_floppy_side incorrect!");
+		max_floppy_side = 4;
+	}
+	/* Next, for 307 foot tapes. */
+	if (ft_segments_per_track == 150 &&
+	    ft_tracks_per_tape == 28 &&
+	    max_floppy_side == 9 &&
+	    max_floppy_track == 149 &&
+	    max_floppy_sector == 128) {
+TRACE(ft_t_info, "the ComByte bug: max_floppy_side incorrect!");
+		max_floppy_side = 6;
+	}
+	/*  This test will compensate for the wrong parameter on tapes
+	 *  formatted by Colorado Windows software.
+	 */
+	if (ft_segments_per_track == 150 &&
+	    ft_tracks_per_tape == 28 &&
+	    max_floppy_side == 6 &&
+	    max_floppy_track == 150 &&
+	    max_floppy_sector == 128) {
+TRACE(ft_t_info, "the famous Colorado bug: max_floppy_track off by one !");
+		max_floppy_track = 149;
+	}
+	ftape_segments_per_head = ((max_floppy_sector/FT_SECTORS_PER_SEGMENT) *
+				   (max_floppy_track + 1));
+	/*  This test will compensate for some bug reported by Dima
+	 *  Brodsky.  Seems to be a Colorado bug, either. (freebee
+	 *  Imation tape shipped together with Colorado T3000
+	 */
+	if ((ft_format_code == fmt_var || ft_format_code == fmt_big) &&
+	    ft_tracks_per_tape == 50 &&
+	    max_floppy_side == 54 &&
+	    max_floppy_track == 255 &&
+	    max_floppy_sector == 128) {
+TRACE(ft_t_info, "the famous ??? bug: max_floppy_track off by one !");
+		max_floppy_track = 254;
+	}
+	/*
+	 *    Verify drive_configuration with tape parameters
+	 */
+	if (ftape_segments_per_head == 0 || ftape_segments_per_cylinder == 0 ||
+	  ((ft_segments_per_track * ft_tracks_per_tape - 1) / ftape_segments_per_head
+	   != max_floppy_side) ||
+	    (ftape_segments_per_head / ftape_segments_per_cylinder - 1 != max_floppy_track) ||
+	(ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT != max_floppy_sector)
+#ifdef TESTING
+	    || ((ft_format_code == fmt_var || ft_format_code == fmt_big) && 
+		(max_floppy_track != 254 || max_floppy_sector != 128))
+#endif
+	   ) {
+		char segperheadz = ftape_segments_per_head ? ' ' : '?';
+		char segpercylz  = ftape_segments_per_cylinder ? ' ' : '?';
+		TRACE(ft_t_err,"Tape parameters inconsistency, please report");
+		TRACE(ft_t_err, "reported = %d/%d/%d/%d/%d/%d",
+		      ft_format_code,
+		      ft_segments_per_track,
+		      ft_tracks_per_tape,
+		      max_floppy_side,
+		      max_floppy_track,
+		      max_floppy_sector);
+		TRACE(ft_t_err, "required = %d/%d/%d/%d%c/%d%c/%d",
+		      ft_format_code,
+		      ft_segments_per_track,
+		      ft_tracks_per_tape,
+		      ftape_segments_per_head ?
+		      ((ft_segments_per_track * ft_tracks_per_tape -1) / 
+		       ftape_segments_per_head ) :
+			(ft_segments_per_track * ft_tracks_per_tape -1),
+			segperheadz,
+		      ftape_segments_per_cylinder ?
+		      (ftape_segments_per_head / 
+		       ftape_segments_per_cylinder - 1 ) :
+			ftape_segments_per_head - 1,
+			segpercylz,
+		      (ftape_segments_per_cylinder * FT_SECTORS_PER_SEGMENT));
+		TRACE_EXIT -EIO;
+	}
+	ftape_extract_bad_sector_map(address);
+ 	TRACE_EXIT 0;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-read.h b/drivers/char/ftape/lowlevel/ftape-read.h
new file mode 100644
index 0000000..069f99f
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-read.h
@@ -0,0 +1,51 @@
+#ifndef _FTAPE_READ_H
+#define _FTAPE_READ_H
+
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-read.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:22 $
+ *
+ *      This file contains the definitions for the read functions
+ *      for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+/*      ftape-read.c defined global functions.
+ */
+typedef enum {
+	FT_RD_SINGLE = 0,
+	FT_RD_AHEAD  = 1,
+} ft_read_mode_t;
+
+extern int ftape_read_header_segment(__u8 *address);
+extern int ftape_decode_header_segment(__u8 *address);
+extern int ftape_read_segment_fraction(const int segment,
+				       void  *address, 
+				       const ft_read_mode_t read_mode,
+				       const int start,
+				       const int size);
+#define ftape_read_segment(segment, address, read_mode)			\
+	ftape_read_segment_fraction(segment, address, read_mode,	\
+				    0, FT_SEGMENT_SIZE)
+extern void ftape_zap_read_buffers(void);
+
+#endif				/* _FTAPE_READ_H */
diff --git a/drivers/char/ftape/lowlevel/ftape-rw.c b/drivers/char/ftape/lowlevel/ftape-rw.c
new file mode 100644
index 0000000..c0d6dc2
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-rw.c
@@ -0,0 +1,1092 @@
+/*
+ *      Copyright (C) 1993-1996 Bas Laarhoven,
+ *                (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-rw.c,v $
+ * $Revision: 1.7 $
+ * $Date: 1997/10/28 14:26:49 $
+ *
+ *      This file contains some common code for the segment read and
+ *      segment write routines for the QIC-117 floppy-tape driver for
+ *      Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-ecc.h"
+#include "../lowlevel/ftape-bsm.h"
+
+/*      Global vars.
+ */
+int ft_nr_buffers;
+buffer_struct *ft_buffer[FT_MAX_NR_BUFFERS];
+static volatile int ft_head;
+static volatile int ft_tail;	/* not volatile but need same type as head */
+int fdc_setup_error;
+location_record ft_location = {-1, 0};
+volatile int ftape_tape_running;
+
+/*      Local vars.
+ */
+static int overrun_count_offset;
+static int inhibit_correction;
+
+/*  maxmimal allowed overshoot when fast seeking
+ */
+#define OVERSHOOT_LIMIT 10
+
+/*      Increment cyclic buffer nr.
+ */
+buffer_struct *ftape_next_buffer(ft_buffer_queue_t pos)
+{
+	switch (pos) {
+	case ft_queue_head:
+		if (++ft_head >= ft_nr_buffers) {
+			ft_head = 0;
+		}
+		return ft_buffer[ft_head];
+	case ft_queue_tail:
+		if (++ft_tail >= ft_nr_buffers) {
+			ft_tail = 0;
+		}
+		return ft_buffer[ft_tail];
+	default:
+		return NULL;
+	}
+}
+int ftape_buffer_id(ft_buffer_queue_t pos)
+{
+	switch(pos) {
+	case ft_queue_head: return ft_head;
+	case ft_queue_tail: return ft_tail;
+	default: return -1;
+	}
+}
+buffer_struct *ftape_get_buffer(ft_buffer_queue_t pos)
+{
+	switch(pos) {
+	case ft_queue_head: return ft_buffer[ft_head];
+	case ft_queue_tail: return ft_buffer[ft_tail];
+	default: return NULL;
+	}
+}
+void ftape_reset_buffer(void)
+{
+	ft_head = ft_tail = 0;
+}
+
+buffer_state_enum ftape_set_state(buffer_state_enum new_state)
+{
+	buffer_state_enum old_state = ft_driver_state;
+
+	ft_driver_state = new_state;
+	return old_state;
+}
+/*      Calculate Floppy Disk Controller and DMA parameters for a segment.
+ *      head:   selects buffer struct in array.
+ *      offset: number of physical sectors to skip (including bad ones).
+ *      count:  number of physical sectors to handle (including bad ones).
+ */
+static int setup_segment(buffer_struct * buff, 
+			 int segment_id,
+			 unsigned int sector_offset, 
+			 unsigned int sector_count, 
+			 int retry)
+{
+	SectorMap offset_mask;
+	SectorMap mask;
+	TRACE_FUN(ft_t_any);
+
+	buff->segment_id = segment_id;
+	buff->sector_offset = sector_offset;
+	buff->remaining = sector_count;
+	buff->head = segment_id / ftape_segments_per_head;
+	buff->cyl = (segment_id % ftape_segments_per_head) / ftape_segments_per_cylinder;
+	buff->sect = (segment_id % ftape_segments_per_cylinder) * FT_SECTORS_PER_SEGMENT + 1;
+	buff->deleted = 0;
+	offset_mask = (1 << buff->sector_offset) - 1;
+	mask = ftape_get_bad_sector_entry(segment_id) & offset_mask;
+	while (mask) {
+		if (mask & 1) {
+			offset_mask >>= 1;	/* don't count bad sector */
+		}
+		mask >>= 1;
+	}
+	buff->data_offset = count_ones(offset_mask);	/* good sectors to skip */
+	buff->ptr = buff->address + buff->data_offset * FT_SECTOR_SIZE;
+	TRACE(ft_t_flow, "data offset = %d sectors", buff->data_offset);
+	if (retry) {
+		buff->soft_error_map &= offset_mask;	/* keep skipped part */
+	} else {
+		buff->hard_error_map = buff->soft_error_map = 0;
+	}
+	buff->bad_sector_map = ftape_get_bad_sector_entry(buff->segment_id);
+	if (buff->bad_sector_map != 0) {
+		TRACE(ft_t_noise, "segment: %d, bad sector map: %08lx",
+			buff->segment_id, (long)buff->bad_sector_map);
+	} else {
+		TRACE(ft_t_flow, "segment: %d", buff->segment_id);
+	}
+	if (buff->sector_offset > 0) {
+		buff->bad_sector_map >>= buff->sector_offset;
+	}
+	if (buff->sector_offset != 0 || buff->remaining != FT_SECTORS_PER_SEGMENT) {
+		TRACE(ft_t_flow, "sector offset = %d, count = %d",
+			buff->sector_offset, buff->remaining);
+	}
+	/*    Segments with 3 or less sectors are not written with valid
+	 *    data because there is no space left for the ecc.  The
+	 *    data written is whatever happens to be in the buffer.
+	 *    Reading such a segment will return a zero byte-count.
+	 *    To allow us to read/write segments with all bad sectors
+	 *    we fake one readable sector in the segment. This
+	 *    prevents having to handle these segments in a very
+	 *    special way.  It is not important if the reading of this
+	 *    bad sector fails or not (the data is ignored). It is
+	 *    only read to keep the driver running.
+	 *
+	 *    The QIC-40/80 spec. has no information on how to handle
+	 *    this case, so this is my interpretation.  
+	 */
+	if (buff->bad_sector_map == EMPTY_SEGMENT) {
+		TRACE(ft_t_flow, "empty segment %d, fake first sector good",
+		      buff->segment_id);
+		if (buff->ptr != buff->address) {
+			TRACE(ft_t_bug, "This is a bug: %p/%p",
+			      buff->ptr, buff->address);
+		}
+		buff->bad_sector_map = FAKE_SEGMENT;
+	}
+	fdc_setup_error = 0;
+	buff->next_segment = segment_id + 1;
+	TRACE_EXIT 0;
+}
+
+/*      Calculate Floppy Disk Controller and DMA parameters for a new segment.
+ */
+int ftape_setup_new_segment(buffer_struct * buff, int segment_id, int skip)
+{
+	int result = 0;
+	static int old_segment_id = -1;
+	static buffer_state_enum old_ft_driver_state = idle;
+	int retry = 0;
+	unsigned offset = 0;
+	int count = FT_SECTORS_PER_SEGMENT;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_flow, "%s segment %d (old = %d)",
+	      (ft_driver_state == reading || ft_driver_state == verifying) 
+	      ? "reading" : "writing",
+	      segment_id, old_segment_id);
+	if (ft_driver_state != old_ft_driver_state) {	/* when verifying */
+		old_segment_id = -1;
+		old_ft_driver_state = ft_driver_state;
+	}
+	if (segment_id == old_segment_id) {
+		++buff->retry;
+		++ft_history.retries;
+		TRACE(ft_t_flow, "setting up for retry nr %d", buff->retry);
+		retry = 1;
+		if (skip && buff->skip > 0) {	/* allow skip on retry */
+			offset = buff->skip;
+			count -= offset;
+			TRACE(ft_t_flow, "skipping %d sectors", offset);
+		}
+	} else {
+		buff->retry = 0;
+		buff->skip = 0;
+		old_segment_id = segment_id;
+	}
+	result = setup_segment(buff, segment_id, offset, count, retry);
+	TRACE_EXIT result;
+}
+
+/*      Determine size of next cluster of good sectors.
+ */
+int ftape_calc_next_cluster(buffer_struct * buff)
+{
+	/* Skip bad sectors.
+	 */
+	while (buff->remaining > 0 && (buff->bad_sector_map & 1) != 0) {
+		buff->bad_sector_map >>= 1;
+		++buff->sector_offset;
+		--buff->remaining;
+	}
+	/* Find next cluster of good sectors
+	 */
+	if (buff->bad_sector_map == 0) {	/* speed up */
+		buff->sector_count = buff->remaining;
+	} else {
+		SectorMap map = buff->bad_sector_map;
+
+		buff->sector_count = 0;
+		while (buff->sector_count < buff->remaining && (map & 1) == 0) {
+			++buff->sector_count;
+			map >>= 1;
+		}
+	}
+	return buff->sector_count;
+}
+
+/*  if just passed the last segment on a track, wait for BOT
+ *  or EOT mark.
+ */
+int ftape_handle_logical_eot(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (ft_runner_status == logical_eot) {
+		int status;
+
+		TRACE(ft_t_noise, "tape at logical EOT");
+		TRACE_CATCH(ftape_ready_wait(ftape_timeout.seek, &status),);
+		if ((status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) == 0) {
+			TRACE_ABORT(-EIO, ft_t_err, "eot/bot not reached");
+		}
+		ft_runner_status = end_of_tape;
+	}
+	if (ft_runner_status == end_of_tape) {
+		TRACE(ft_t_noise, "runner stopped because of logical EOT");
+		ft_runner_status = idle;
+	}
+	TRACE_EXIT 0;
+}
+
+static int check_bot_eot(int status)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (status & (QIC_STATUS_AT_BOT | QIC_STATUS_AT_EOT)) {
+		ft_location.bot = ((ft_location.track & 1) == 0 ?
+				(status & QIC_STATUS_AT_BOT) != 0:
+				(status & QIC_STATUS_AT_EOT) != 0);
+		ft_location.eot = !ft_location.bot;
+		ft_location.segment = (ft_location.track +
+			(ft_location.bot ? 0 : 1)) * ft_segments_per_track - 1;
+		ft_location.sector = -1;
+		ft_location.known  = 1;
+		TRACE(ft_t_flow, "tape at logical %s",
+		      ft_location.bot ? "bot" : "eot");
+		TRACE(ft_t_flow, "segment = %d", ft_location.segment);
+	} else {
+		ft_location.known = 0;
+	}
+	TRACE_EXIT ft_location.known;
+}
+
+/*      Read Id of first sector passing tape head.
+ */
+static int ftape_read_id(void)
+{
+	int status;
+	__u8 out[2];
+	TRACE_FUN(ft_t_any);
+
+	/* Assume tape is running on entry, be able to handle
+	 * situation where it stopped or is stopping.
+	 */
+	ft_location.known = 0;	/* default is location not known */
+	out[0] = FDC_READID;
+	out[1] = ft_drive_sel;
+	TRACE_CATCH(fdc_command(out, 2),);
+	switch (fdc_interrupt_wait(20 * FT_SECOND)) {
+	case 0:
+		if (fdc_sect == 0) {
+			if (ftape_report_drive_status(&status) >= 0 &&
+			    (status & QIC_STATUS_READY)) {
+				ftape_tape_running = 0;
+				TRACE(ft_t_flow, "tape has stopped");
+				check_bot_eot(status);
+			}
+		} else {
+			ft_location.known = 1;
+			ft_location.segment = (ftape_segments_per_head
+					       * fdc_head
+					       + ftape_segments_per_cylinder
+					       * fdc_cyl
+					       + (fdc_sect - 1)
+					       / FT_SECTORS_PER_SEGMENT);
+			ft_location.sector = ((fdc_sect - 1)
+					      % FT_SECTORS_PER_SEGMENT);
+			ft_location.eot = ft_location.bot = 0;
+		}
+		break;
+	case -ETIME:
+		/*  Didn't find id on tape, must be near end: Wait
+		 *  until stopped.
+		 */
+		if (ftape_ready_wait(FT_FOREVER, &status) >= 0) {
+			ftape_tape_running = 0;
+			TRACE(ft_t_flow, "tape has stopped");
+			check_bot_eot(status);
+		}
+		break;
+	default:
+		/*  Interrupted or otherwise failing
+		 *  fdc_interrupt_wait() 
+		 */
+		TRACE(ft_t_err, "fdc_interrupt_wait failed");
+		break;
+	}
+	if (!ft_location.known) {
+		TRACE_ABORT(-EIO, ft_t_flow, "no id found");
+	}
+	if (ft_location.sector == 0) {
+		TRACE(ft_t_flow, "passing segment %d/%d",
+		      ft_location.segment, ft_location.sector);
+	} else {
+		TRACE(ft_t_fdc_dma, "passing segment %d/%d",
+		      ft_location.segment, ft_location.sector);
+	}
+	TRACE_EXIT 0;
+}
+
+static int logical_forward(void)
+{
+	ftape_tape_running = 1;
+	return ftape_command(QIC_LOGICAL_FORWARD);
+}
+
+int ftape_stop_tape(int *pstatus)
+{
+	int retry = 0;
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	do {
+		result = ftape_command_wait(QIC_STOP_TAPE,
+					    ftape_timeout.stop, pstatus);
+		if (result == 0) {
+			if ((*pstatus & QIC_STATUS_READY) == 0) {
+				result = -EIO;
+			} else {
+				ftape_tape_running = 0;
+			}
+		}
+	} while (result < 0 && ++retry <= 3);
+	if (result < 0) {
+		TRACE(ft_t_err, "failed ! (fatal)");
+	}
+	TRACE_EXIT result;
+}
+
+int ftape_dumb_stop(void)
+{
+	int result;
+	int status;
+	TRACE_FUN(ft_t_flow);
+
+	/*  Abort current fdc operation if it's busy (probably read
+	 *  or write operation pending) with a reset.
+	 */
+	if (fdc_ready_wait(100 /* usec */) < 0) {
+		TRACE(ft_t_noise, "aborting fdc operation");
+		fdc_reset();
+	}
+	/*  Reading id's after the last segment on a track may fail
+	 *  but eventually the drive will become ready (logical eot).
+	 */
+	result = ftape_report_drive_status(&status);
+	ft_location.known = 0;
+	do {
+		if (result == 0 && status & QIC_STATUS_READY) {
+			/* Tape is not running any more.
+			 */
+			TRACE(ft_t_noise, "tape already halted");
+			check_bot_eot(status);
+			ftape_tape_running = 0;
+		} else if (ftape_tape_running) {
+			/*  Tape is (was) still moving.
+			 */
+#ifdef TESTING
+			ftape_read_id();
+#endif
+			result = ftape_stop_tape(&status);
+		} else {
+			/*  Tape not yet ready but stopped.
+			 */
+			result = ftape_ready_wait(ftape_timeout.pause,&status);
+		}
+	} while (ftape_tape_running
+		 && !(sigtestsetmask(&current->pending.signal, _NEVER_BLOCK)));
+#ifndef TESTING
+	ft_location.known = 0;
+#endif
+	if (ft_runner_status == aborting || ft_runner_status == do_abort) {
+		ft_runner_status = idle;
+	}
+	TRACE_EXIT result;
+}
+
+/*      Wait until runner has finished tail buffer.
+ *
+ */
+int ftape_wait_segment(buffer_state_enum state)
+{
+	int status;
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+
+	while (ft_buffer[ft_tail]->status == state) {
+		TRACE(ft_t_flow, "state: %d", ft_buffer[ft_tail]->status);
+		/*  First buffer still being worked on, wait up to timeout.
+		 *
+		 *  Note: we check two times for being killed. 50
+		 *  seconds are quite long. Note that
+		 *  fdc_interrupt_wait() is not killable by any
+		 *  means. ftape_read_segment() wants us to return
+		 *  -EINTR in case of a signal.  
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		result = fdc_interrupt_wait(50 * FT_SECOND);
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		if (result < 0) {
+			TRACE_ABORT(result,
+				    ft_t_err, "fdc_interrupt_wait failed");
+		}
+		if (fdc_setup_error) {
+			/* recover... FIXME */
+			TRACE_ABORT(-EIO, ft_t_err, "setup error");
+		}
+	}
+	if (ft_buffer[ft_tail]->status != error) {
+		TRACE_EXIT 0;
+	}
+	TRACE_CATCH(ftape_report_drive_status(&status),);
+	TRACE(ft_t_noise, "ftape_report_drive_status: 0x%02x", status);
+	if ((status & QIC_STATUS_READY) && 
+	    (status & QIC_STATUS_ERROR)) {
+		unsigned int error;
+		qic117_cmd_t command;
+		
+		/*  Report and clear error state.
+		 *  In case the drive can't operate at the selected
+		 *  rate, select the next lower data rate.
+		 */
+		ftape_report_error(&error, &command, 1);
+		if (error == 31 && command == QIC_LOGICAL_FORWARD) {
+			/* drive does not accept this data rate */
+			if (ft_data_rate > 250) {
+				TRACE(ft_t_info,
+				      "Probable data rate conflict");
+				TRACE(ft_t_info,
+				      "Lowering data rate to %d Kbps",
+				      ft_data_rate / 2);
+				ftape_half_data_rate();
+				if (ft_buffer[ft_tail]->retry > 0) {
+					/* give it a chance */
+					--ft_buffer[ft_tail]->retry;
+				}
+			} else {
+				/* no rate is accepted... */
+				TRACE(ft_t_err, "We're dead :(");
+			}
+		} else {
+			TRACE(ft_t_err, "Unknown error");
+		}
+		TRACE_EXIT -EIO;   /* g.p. error */
+	}
+	TRACE_EXIT 0;
+}
+
+/* forward */ static int seek_forward(int segment_id, int fast);
+
+static int fast_seek(int count, int reverse)
+{
+	int result = 0;
+	int status;
+	TRACE_FUN(ft_t_flow);
+
+	if (count > 0) {
+		/*  If positioned at begin or end of tape, fast seeking needs
+		 *  special treatment.
+		 *  Starting from logical bot needs a (slow) seek to the first
+		 *  segment before the high speed seek. Most drives do this
+		 *  automatically but some older don't, so we treat them
+		 *  all the same.
+		 *  Starting from logical eot is even more difficult because
+		 *  we cannot (slow) reverse seek to the last segment.
+		 *  TO BE IMPLEMENTED.
+		 */
+		inhibit_correction = 0;
+		if (ft_location.known &&
+		    ((ft_location.bot && !reverse) ||
+		     (ft_location.eot && reverse))) {
+			if (!reverse) {
+				/*  (slow) skip to first segment on a track
+				 */
+				seek_forward(ft_location.track * ft_segments_per_track, 0);
+				--count;
+			} else {
+				/*  When seeking backwards from
+				 *  end-of-tape the number of erased
+				 *  gaps found seems to be higher than
+				 *  expected.  Therefor the drive must
+				 *  skip some more segments than
+				 *  calculated, but we don't know how
+				 *  many.  Thus we will prevent the
+				 *  re-calculation of offset and
+				 *  overshoot when seeking backwards.
+				 */
+				inhibit_correction = 1;
+				count += 3;	/* best guess */
+			}
+		}
+	} else {
+		TRACE(ft_t_flow, "warning: zero or negative count: %d", count);
+	}
+	if (count > 0) {
+		int i;
+		int nibbles = count > 255 ? 3 : 2;
+
+		if (count > 4095) {
+			TRACE(ft_t_noise, "skipping clipped at 4095 segment");
+			count = 4095;
+		}
+		/* Issue this tape command first. */
+		if (!reverse) {
+			TRACE(ft_t_noise, "skipping %d segment(s)", count);
+			result = ftape_command(nibbles == 3 ?
+			   QIC_SKIP_EXTENDED_FORWARD : QIC_SKIP_FORWARD);
+		} else {
+			TRACE(ft_t_noise, "backing up %d segment(s)", count);
+			result = ftape_command(nibbles == 3 ?
+			   QIC_SKIP_EXTENDED_REVERSE : QIC_SKIP_REVERSE);
+		}
+		if (result < 0) {
+			TRACE(ft_t_noise, "Skip command failed");
+		} else {
+			--count;	/* 0 means one gap etc. */
+			for (i = 0; i < nibbles; ++i) {
+				if (result >= 0) {
+					result = ftape_parameter(count & 15);
+					count /= 16;
+				}
+			}
+			result = ftape_ready_wait(ftape_timeout.rewind, &status);
+			if (result >= 0) {
+				ftape_tape_running = 0;
+			}
+		}
+	}
+	TRACE_EXIT result;
+}
+
+static int validate(int id)
+{
+	/* Check to see if position found is off-track as reported
+	 *  once.  Because all tracks in one direction lie next to
+	 *  each other, if off-track the error will be approximately
+	 *  2 * ft_segments_per_track.
+	 */
+	if (ft_location.track == -1) {
+		return 1; /* unforseen situation, don't generate error */
+	} else {
+		/* Use margin of ft_segments_per_track on both sides
+		 * because ftape needs some margin and the error we're
+		 * looking for is much larger !
+		 */
+		int lo = (ft_location.track - 1) * ft_segments_per_track;
+		int hi = (ft_location.track + 2) * ft_segments_per_track;
+
+		return (id >= lo && id < hi);
+	}
+}
+
+static int seek_forward(int segment_id, int fast)
+{
+	int failures = 0;
+	int count;
+	static int margin = 1;	/* fixed: stop this before target */
+	static int overshoot = 1;
+	static int min_count = 8;
+	int expected = -1;
+	int target = segment_id - margin;
+	int fast_seeking;
+	int prev_segment = ft_location.segment;
+	TRACE_FUN(ft_t_flow);
+
+	if (!ft_location.known) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "fatal: cannot seek from unknown location");
+	}
+	if (!validate(segment_id)) {
+		ftape_sleep(1 * FT_SECOND);
+		ft_failure = 1;
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "fatal: head off track (bad hardware?)");
+	}
+	TRACE(ft_t_noise, "from %d/%d to %d/0 - %d",
+	      ft_location.segment, ft_location.sector,segment_id,margin);
+	count = target - ft_location.segment - overshoot;
+	fast_seeking = (fast &&
+			count > (min_count + (ft_location.bot ? 1 : 0)));
+	if (fast_seeking) {
+		TRACE(ft_t_noise, "fast skipping %d segments", count);
+		expected = segment_id - margin;
+		fast_seek(count, 0);
+	}
+	if (!ftape_tape_running) {
+		logical_forward();
+	}
+	while (ft_location.segment < segment_id) {
+		/*  This requires at least one sector in a (bad) segment to
+		 *  have a valid and readable sector id !
+		 *  It looks like this is not guaranteed, so we must try
+		 *  to find a way to skip an EMPTY_SEGMENT. !!! FIXME !!!
+		 */
+		if (ftape_read_id() < 0 || !ft_location.known ||
+		    sigtestsetmask(&current->pending.signal, _DONT_BLOCK)) {
+			ft_location.known = 0;
+			if (!ftape_tape_running ||
+			    ++failures > FT_SECTORS_PER_SEGMENT) {
+				TRACE_ABORT(-EIO, ft_t_err,
+					    "read_id failed completely");
+			}
+			FT_SIGNAL_EXIT(_DONT_BLOCK);
+			TRACE(ft_t_flow, "read_id failed, retry (%d)",
+			      failures);
+			continue;
+		}
+		if (fast_seeking) {
+			TRACE(ft_t_noise, "ended at %d/%d (%d,%d)",
+			      ft_location.segment, ft_location.sector,
+			      overshoot, inhibit_correction);
+			if (!inhibit_correction &&
+			    (ft_location.segment < expected ||
+			     ft_location.segment > expected + margin)) {
+				int error = ft_location.segment - expected;
+				TRACE(ft_t_noise,
+				      "adjusting overshoot from %d to %d",
+				      overshoot, overshoot + error);
+				overshoot += error;
+				/*  All overshoots have the same
+				 *  direction, so it should never
+				 *  become negative, but who knows.
+				 */
+				if (overshoot < -5 ||
+				    overshoot > OVERSHOOT_LIMIT) {
+					if (overshoot < 0) {
+						/* keep sane value */
+						overshoot = -5;
+					} else {
+						/* keep sane value */
+						overshoot = OVERSHOOT_LIMIT;
+					}
+					TRACE(ft_t_noise,
+					      "clipped overshoot to %d",
+					      overshoot);
+				}
+			}
+			fast_seeking = 0;
+		}
+		if (ft_location.known) {
+			if (ft_location.segment > prev_segment + 1) {
+				TRACE(ft_t_noise,
+				      "missed segment %d while skipping",
+				      prev_segment + 1);
+			}
+			prev_segment = ft_location.segment;
+		}
+	}
+	if (ft_location.segment > segment_id) {
+		TRACE_ABORT(-EIO,
+			    ft_t_noise, "failed: skip ended at segment %d/%d",
+			    ft_location.segment, ft_location.sector);
+	}
+	TRACE_EXIT 0;
+}
+
+static int skip_reverse(int segment_id, int *pstatus)
+{
+	int failures = 0;
+	static int overshoot = 1;
+	static int min_rewind = 2;	/* 1 + overshoot */
+	static const int margin = 1;	/* stop this before target */
+	int expected = 0;
+	int count = 1;
+	int short_seek;
+	int target = segment_id - margin;
+	TRACE_FUN(ft_t_flow);
+
+	if (ft_location.known && !validate(segment_id)) {
+		ftape_sleep(1 * FT_SECOND);
+		ft_failure = 1;
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "fatal: head off track (bad hardware?)");
+	}
+	do {
+		if (!ft_location.known) {
+			TRACE(ft_t_warn, "warning: location not known");
+		}
+		TRACE(ft_t_noise, "from %d/%d to %d/0 - %d",
+		      ft_location.segment, ft_location.sector,
+		      segment_id, margin);
+		/*  min_rewind == 1 + overshoot_when_doing_minimum_rewind
+		 *  overshoot  == overshoot_when_doing_larger_rewind
+		 *  Initially min_rewind == 1 + overshoot, optimization
+		 *  of both values will be done separately.
+		 *  overshoot and min_rewind can be negative as both are
+		 *  sums of three components:
+		 *  any_overshoot == rewind_overshoot - 
+		 *                   stop_overshoot   -
+		 *                   start_overshoot
+		 */
+		if (ft_location.segment - target - (min_rewind - 1) < 1) {
+			short_seek = 1;
+		} else {
+			count = ft_location.segment - target - overshoot;
+			short_seek = (count < 1);
+		}
+		if (short_seek) {
+			count = 1;	/* do shortest rewind */
+			expected = ft_location.segment - min_rewind;
+			if (expected/ft_segments_per_track != ft_location.track) {
+				expected = (ft_location.track * 
+					    ft_segments_per_track);
+			}
+		} else {
+			expected = target;
+		}
+		fast_seek(count, 1);
+		logical_forward();
+		if (ftape_read_id() < 0 || !ft_location.known ||
+		    (sigtestsetmask(&current->pending.signal, _DONT_BLOCK))) {
+			if ((!ftape_tape_running && !ft_location.known) ||
+			    ++failures > FT_SECTORS_PER_SEGMENT) {
+				TRACE_ABORT(-EIO, ft_t_err,
+					    "read_id failed completely");
+			}
+			FT_SIGNAL_EXIT(_DONT_BLOCK);
+			TRACE_CATCH(ftape_report_drive_status(pstatus),);
+			TRACE(ft_t_noise, "ftape_read_id failed, retry (%d)",
+			      failures);
+			continue;
+		}
+		TRACE(ft_t_noise, "ended at %d/%d (%d,%d,%d)", 
+		      ft_location.segment, ft_location.sector,
+		      min_rewind, overshoot, inhibit_correction);
+		if (!inhibit_correction &&
+		    (ft_location.segment < expected ||
+		     ft_location.segment > expected + margin)) {
+			int error = expected - ft_location.segment;
+			if (short_seek) {
+				TRACE(ft_t_noise,
+				      "adjusting min_rewind from %d to %d",
+				      min_rewind, min_rewind + error);
+				min_rewind += error;
+				if (min_rewind < -5) {
+					/* is this right ? FIXME ! */
+					/* keep sane value */
+					min_rewind = -5;
+					TRACE(ft_t_noise, 
+					      "clipped min_rewind to %d",
+					      min_rewind);
+				}
+			} else {
+				TRACE(ft_t_noise,
+				      "adjusting overshoot from %d to %d",
+				      overshoot, overshoot + error);
+				overshoot += error;
+				if (overshoot < -5 ||
+				    overshoot > OVERSHOOT_LIMIT) {
+					if (overshoot < 0) {
+						/* keep sane value */
+						overshoot = -5;
+					} else {
+						/* keep sane value */
+						overshoot = OVERSHOOT_LIMIT;
+					}
+					TRACE(ft_t_noise,
+					      "clipped overshoot to %d",
+					      overshoot);
+				}
+			}
+		}
+	} while (ft_location.segment > segment_id);
+	if (ft_location.known) {
+		TRACE(ft_t_noise, "current location: %d/%d",
+		      ft_location.segment, ft_location.sector);
+	}
+	TRACE_EXIT 0;
+}
+
+static int determine_position(void)
+{
+	int retry = 0;
+	int status;
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	if (!ftape_tape_running) {
+		/*  This should only happen if tape is stopped by isr.
+		 */
+		TRACE(ft_t_flow, "waiting for tape stop");
+		if (ftape_ready_wait(ftape_timeout.pause, &status) < 0) {
+			TRACE(ft_t_flow, "drive still running (fatal)");
+			ftape_tape_running = 1;	/* ? */
+		}
+	} else {
+		ftape_report_drive_status(&status);
+	}
+	if (status & QIC_STATUS_READY) {
+		/*  Drive must be ready to check error state !
+		 */
+		TRACE(ft_t_flow, "drive is ready");
+		if (status & QIC_STATUS_ERROR) {
+			unsigned int error;
+			qic117_cmd_t command;
+
+			/*  Report and clear error state, try to continue.
+			 */
+			TRACE(ft_t_flow, "error status set");
+			ftape_report_error(&error, &command, 1);
+			ftape_ready_wait(ftape_timeout.reset, &status);
+			ftape_tape_running = 0;	/* ? */
+		}
+		if (check_bot_eot(status)) {
+			if (ft_location.bot) {
+				if ((status & QIC_STATUS_READY) == 0) {
+					/* tape moving away from
+					 * bot/eot, let's see if we
+					 * can catch up with the first
+					 * segment on this track.
+					 */
+				} else {
+					TRACE(ft_t_flow,
+					      "start tape from logical bot");
+					logical_forward();	/* start moving */
+				}
+			} else {
+				if ((status & QIC_STATUS_READY) == 0) {
+					TRACE(ft_t_noise, "waiting for logical end of track");
+					result = ftape_ready_wait(ftape_timeout.reset, &status);
+					/* error handling needed ? */
+				} else {
+					TRACE(ft_t_noise,
+					      "tape at logical end of track");
+				}
+			}
+		} else {
+			TRACE(ft_t_flow, "start tape");
+			logical_forward();	/* start moving */
+			ft_location.known = 0;	/* not cleared by logical forward ! */
+		}
+	}
+	/* tape should be moving now, start reading id's
+	 */
+	while (!ft_location.known &&
+	       retry++ < FT_SECTORS_PER_SEGMENT &&
+	       (result = ftape_read_id()) < 0) {
+
+		TRACE(ft_t_flow, "location unknown");
+
+		/* exit on signal
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+
+		/*  read-id somehow failed, tape may
+		 *  have reached end or some other
+		 *  error happened.
+		 */
+		TRACE(ft_t_flow, "read-id failed");
+		TRACE_CATCH(ftape_report_drive_status(&status),);
+		TRACE(ft_t_err, "ftape_report_drive_status: 0x%02x", status);
+		if (status & QIC_STATUS_READY) {
+			ftape_tape_running = 0;
+			TRACE(ft_t_noise, "tape stopped for unknown reason! "
+			      "status = 0x%02x", status);
+			if (status & QIC_STATUS_ERROR ||
+			    !check_bot_eot(status)) {
+				/* oops, tape stopped but not at end!
+				 */
+				TRACE_EXIT -EIO;
+			}
+		}
+	}
+	TRACE(ft_t_flow,
+	      "tape is positioned at segment %d", ft_location.segment);
+	TRACE_EXIT ft_location.known ? 0 : -EIO;
+}
+
+/*      Get the tape running and position it just before the
+ *      requested segment.
+ *      Seek tape-track and reposition as needed.
+ */
+int ftape_start_tape(int segment_id, int sector_offset)
+{
+	int track = segment_id / ft_segments_per_track;
+	int result = -EIO;
+	int status;
+	static int last_segment = -1;
+	static int bad_bus_timing = 0;
+	/* number of segments passing the head between starting the tape
+	 * and being able to access the first sector.
+	 */
+	static int start_offset = 1;
+	int retry;
+	TRACE_FUN(ft_t_flow);
+
+	/* If sector_offset > 0, seek into wanted segment instead of
+	 * into previous.
+	 * This allows error recovery if a part of the segment is bad
+	 * (erased) causing the tape drive to generate an index pulse
+	 * thus causing a no-data error before the requested sector
+	 * is reached.
+	 */
+	ftape_tape_running = 0;
+	TRACE(ft_t_noise, "target segment: %d/%d%s", segment_id, sector_offset,
+		ft_buffer[ft_head]->retry > 0 ? " retry" : "");
+	if (ft_buffer[ft_head]->retry > 0) {	/* this is a retry */
+		int dist = segment_id - last_segment;
+
+		if ((int)ft_history.overrun_errors < overrun_count_offset) {
+			overrun_count_offset = ft_history.overrun_errors;
+		} else if (dist < 0 || dist > 50) {
+			overrun_count_offset = ft_history.overrun_errors;
+		} else if ((ft_history.overrun_errors -
+			    overrun_count_offset) >= 8) {
+			if (ftape_increase_threshold() >= 0) {
+				--ft_buffer[ft_head]->retry;
+				overrun_count_offset =
+					ft_history.overrun_errors;
+				TRACE(ft_t_warn, "increased threshold because "
+				      "of excessive overrun errors");
+			} else if (!bad_bus_timing && ft_data_rate >= 1000) {
+				ftape_half_data_rate();
+				--ft_buffer[ft_head]->retry;
+				bad_bus_timing = 1;
+				overrun_count_offset =
+					ft_history.overrun_errors;
+				TRACE(ft_t_warn, "reduced datarate because "
+				      "of excessive overrun errors");
+			}
+		}
+	}
+	last_segment = segment_id;
+	if (ft_location.track != track ||
+	    (ftape_might_be_off_track && ft_buffer[ft_head]->retry== 0)) {
+		/* current track unknown or not equal to destination
+		 */
+		ftape_ready_wait(ftape_timeout.seek, &status);
+		ftape_seek_head_to_track(track);
+		/* overrun_count_offset = ft_history.overrun_errors; */
+	}
+	result = -EIO;
+	retry = 0;
+	while (result < 0     &&
+	       retry++ <= 5   &&
+	       !ft_failure &&
+	       !(sigtestsetmask(&current->pending.signal, _DONT_BLOCK))) {
+		
+		if (retry && start_offset < 5) {
+			start_offset ++;
+		}
+		/*  Check if we are able to catch the requested
+		 *  segment in time.
+		 */
+		if ((ft_location.known || (determine_position() == 0)) &&
+		    ft_location.segment >=
+		    (segment_id -
+		     ((ftape_tape_running || ft_location.bot)
+		      ? 0 : start_offset))) {
+			/*  Too far ahead (in or past target segment).
+			 */
+			if (ftape_tape_running) {
+				if ((result = ftape_stop_tape(&status)) < 0) {
+					TRACE(ft_t_err,
+					      "stop tape failed with code %d",
+					      result);
+					break;
+				}
+				TRACE(ft_t_noise, "tape stopped");
+				ftape_tape_running = 0;
+			}
+			TRACE(ft_t_noise, "repositioning");
+			++ft_history.rewinds;
+			if (segment_id % ft_segments_per_track < start_offset){
+				TRACE(ft_t_noise, "end of track condition\n"
+				      KERN_INFO "segment_id        : %d\n"
+				      KERN_INFO "ft_segments_per_track: %d\n"
+				      KERN_INFO "start_offset      : %d",
+				      segment_id, ft_segments_per_track, 
+				      start_offset);
+				      
+				/*  If seeking to first segments on
+				 *  track better do a complete rewind
+				 *  to logical begin of track to get a
+				 *  more steady tape motion.  
+				 */
+				result = ftape_command_wait(
+					(ft_location.track & 1)
+					? QIC_PHYSICAL_FORWARD
+					: QIC_PHYSICAL_REVERSE,
+					ftape_timeout.rewind, &status);
+				check_bot_eot(status);	/* update location */
+			} else {
+				result= skip_reverse(segment_id - start_offset,
+						     &status);
+			}
+		}
+		if (!ft_location.known) {
+			TRACE(ft_t_bug, "panic: location not known");
+			result = -EIO;
+			continue; /* while() will check for failure */
+		}
+		TRACE(ft_t_noise, "current segment: %d/%d",
+		      ft_location.segment, ft_location.sector);
+		/*  We're on the right track somewhere before the
+		 *  wanted segment.  Start tape movement if needed and
+		 *  skip to just before or inside the requested
+		 *  segment. Keep tape running.  
+		 */
+		result = 0;
+		if (ft_location.segment < 
+		    (segment_id - ((ftape_tape_running || ft_location.bot)
+				   ? 0 : start_offset))) {
+			if (sector_offset > 0) {
+				result = seek_forward(segment_id,
+						      retry <= 3);
+			} else {
+				result = seek_forward(segment_id - 1,
+						      retry <= 3);
+			}
+		}
+		if (result == 0 &&
+		    ft_location.segment !=
+		    (segment_id - (sector_offset > 0 ? 0 : 1))) {
+			result = -EIO;
+		}
+	}
+	if (result < 0) {
+		TRACE(ft_t_err, "failed to reposition");
+	} else {
+		ft_runner_status = running;
+	}
+	TRACE_EXIT result;
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-rw.h b/drivers/char/ftape/lowlevel/ftape-rw.h
new file mode 100644
index 0000000..32f4fee
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-rw.h
@@ -0,0 +1,111 @@
+#ifndef _FTAPE_RW_H
+#define _FTAPE_RW_H
+
+/*
+ * Copyright (C) 1993-1996 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-rw.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:25 $
+ *
+ *      This file contains the definitions for the read and write
+ *      functions for the QIC-117 floppy-tape driver for Linux.
+ *
+ * Claus-Justus Heine (1996/09/20): Add definition of format code 6
+ * Claus-Justus Heine (1996/10/04): Changed GET/PUT macros to cast to (__u8 *)
+ *
+ */
+
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/ftape-bsm.h"
+
+#include <asm/unaligned.h>
+
+#define GET2(address, offset) get_unaligned((__u16*)((__u8 *)address + offset))
+#define GET4(address, offset) get_unaligned((__u32*)((__u8 *)address + offset))
+#define GET8(address, offset) get_unaligned((__u64*)((__u8 *)address + offset))
+#define PUT2(address, offset , value) put_unaligned((value), (__u16*)((__u8 *)address + offset))
+#define PUT4(address, offset , value) put_unaligned((value), (__u32*)((__u8 *)address + offset))
+#define PUT8(address, offset , value) put_unaligned((value), (__u64*)((__u8 *)address + offset))
+
+enum runner_status_enum {
+	idle = 0,
+	running,
+	do_abort,
+	aborting,
+	logical_eot,
+	end_of_tape,
+};
+
+typedef enum ft_buffer_queue {
+	ft_queue_head = 0,
+	ft_queue_tail = 1
+} ft_buffer_queue_t;
+
+
+typedef struct {
+	int track;		/* tape head position */
+	volatile int segment;	/* current segment */
+	volatile int sector;	/* sector offset within current segment */
+	volatile unsigned int bot;	/* logical begin of track */
+	volatile unsigned int eot;	/* logical end of track */
+	volatile unsigned int known;	/* validates bot, segment, sector */
+} location_record;
+
+/*      Count nr of 1's in pattern.
+ */
+static inline int count_ones(unsigned long mask)
+{
+	int bits;
+
+	for (bits = 0; mask != 0; mask >>= 1) {
+		if (mask & 1) {
+			++bits;
+		}
+	}
+	return bits;
+}
+
+#define FT_MAX_NR_BUFFERS 16 /* arbitrary value */
+/*      ftape-rw.c defined global vars.
+ */
+extern buffer_struct *ft_buffer[FT_MAX_NR_BUFFERS];
+extern int ft_nr_buffers;
+extern location_record ft_location;
+extern volatile int ftape_tape_running;
+
+/*      ftape-rw.c defined global functions.
+ */
+extern int  ftape_setup_new_segment(buffer_struct * buff,
+				    int segment_id,
+				    int offset);
+extern int  ftape_calc_next_cluster(buffer_struct * buff);
+extern buffer_struct *ftape_next_buffer (ft_buffer_queue_t pos);
+extern buffer_struct *ftape_get_buffer  (ft_buffer_queue_t pos);
+extern int            ftape_buffer_id   (ft_buffer_queue_t pos);
+extern void           ftape_reset_buffer(void);
+extern void ftape_tape_parameters(__u8 drive_configuration);
+extern int  ftape_wait_segment(buffer_state_enum state);
+extern int  ftape_dumb_stop(void);
+extern int  ftape_start_tape(int segment_id, int offset);
+extern int  ftape_stop_tape(int *pstatus);
+extern int  ftape_handle_logical_eot(void);
+extern buffer_state_enum ftape_set_state(buffer_state_enum new_state);
+#endif				/* _FTAPE_RW_H */
diff --git a/drivers/char/ftape/lowlevel/ftape-setup.c b/drivers/char/ftape/lowlevel/ftape-setup.c
new file mode 100644
index 0000000..280a1a5
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-setup.c
@@ -0,0 +1,105 @@
+/* 
+ * Copyright (C) 1996, 1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-setup.c,v $
+ * $Revision: 1.7 $
+ * $Date: 1997/10/10 09:57:06 $
+ *
+ *      This file contains the code for processing the kernel command
+ *      line options for the QIC-40/80/3010/3020 floppy-tape driver
+ *      "ftape" for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/ftape.h>
+#include <linux/init.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/fdc-io.h"
+
+static struct param_table {
+	const char *name;
+	int *var;
+	int def_param;
+	int min;
+	int max;
+} config_params[] __initdata = {
+#ifndef CONFIG_FT_NO_TRACE_AT_ALL
+	{ "tracing",   &ftape_tracing,     3,              ft_t_bug, ft_t_any},
+#endif
+	{ "ioport",    &ft_fdc_base,       CONFIG_FT_FDC_BASE,     0x0, 0xfff},
+	{ "irq",       &ft_fdc_irq,        CONFIG_FT_FDC_IRQ,        2,    15},
+	{ "dma",       &ft_fdc_dma,        CONFIG_FT_FDC_DMA,        0,     3},
+	{ "threshold", &ft_fdc_threshold,  CONFIG_FT_FDC_THR,         1,    16},
+	{ "datarate",  &ft_fdc_rate_limit, CONFIG_FT_FDC_MAX_RATE, 500,  2000},
+	{ "fc10",      &ft_probe_fc10,     CONFIG_FT_PROBE_FC10,     0,     1},
+	{ "mach2",     &ft_mach2,          CONFIG_FT_MACH2,          0,     1}
+};
+
+static int __init ftape_setup(char *str)
+{
+	int i;
+	int param;
+	int ints[2];
+
+	TRACE_FUN(ft_t_flow);
+
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+	if (str) {
+		for (i=0; i < NR_ITEMS(config_params); i++) {
+			if (strcmp(str,config_params[i].name) == 0){
+				if (ints[0]) {
+					param = ints[1];
+				} else {
+					param = config_params[i].def_param;
+				}
+				if (param < config_params[i].min ||
+				    param > config_params[i].max) {
+					TRACE(ft_t_err,
+					"parameter %s out of range %d ... %d",
+					      config_params[i].name,
+					      config_params[i].min,
+					      config_params[i].max);
+					goto out;
+				}
+				if(config_params[i].var) {
+					TRACE(ft_t_info, "%s=%d", str, param);
+					*config_params[i].var = param;
+				}
+				goto out;
+			}
+		}
+	}
+	if (str) {
+		TRACE(ft_t_err, "unknown ftape option [%s]", str);
+		
+		TRACE(ft_t_err, "allowed options are:");
+		for (i=0; i < NR_ITEMS(config_params); i++) {
+			TRACE(ft_t_err, " %s",config_params[i].name);
+		}
+	} else {
+		TRACE(ft_t_err, "botched ftape option");
+	}
+ out:
+	TRACE_EXIT 1;
+}
+
+__setup("ftape=", ftape_setup);
diff --git a/drivers/char/ftape/lowlevel/ftape-tracing.c b/drivers/char/ftape/lowlevel/ftape-tracing.c
new file mode 100644
index 0000000..7fdc656
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-tracing.c
@@ -0,0 +1,118 @@
+/*
+ *      Copyright (C) 1993-1996 Bas Laarhoven,
+ *                (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-tracing.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:27 $
+ *
+ *      This file contains the reading code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+
+/*      Global vars.
+ */
+/*      tracing
+ *      set it to:     to log :
+ *       0              bugs
+ *       1              + errors
+ *       2              + warnings
+ *       3              + information
+ *       4              + more information
+ *       5              + program flow
+ *       6              + fdc/dma info
+ *       7              + data flow
+ *       8              + everything else
+ */
+ft_trace_t ftape_tracing = ft_t_info; /* Default level: information and up */
+int  ftape_function_nest_level;
+
+/*      Local vars.
+ */
+static __u8 trace_id;
+static char spacing[] = "*                              ";
+
+void ftape_trace_call(const char *file, const char *name)
+{
+	char *indent;
+
+	/*    Since printk seems not to work with "%*s" format
+	 *    we'll use this work-around.
+	 */
+	if (ftape_function_nest_level < 0) {
+		printk(KERN_INFO "function nest level (%d) < 0\n",
+		       ftape_function_nest_level);
+		ftape_function_nest_level = 0;
+	}
+	if (ftape_function_nest_level < sizeof(spacing)) {
+		indent = (spacing +
+			  sizeof(spacing) - 1 -
+			  ftape_function_nest_level);
+	} else {
+		indent = spacing;
+	}
+	printk(KERN_INFO "[%03d]%s+%s (%s)\n",
+	       (int) trace_id++, indent, file, name);
+}
+
+void ftape_trace_exit(const char *file, const char *name)
+{
+	char *indent;
+
+	/*    Since printk seems not to work with "%*s" format
+	 *    we'll use this work-around.
+	 */
+	if (ftape_function_nest_level < 0) {
+		printk(KERN_INFO "function nest level (%d) < 0\n", ftape_function_nest_level);
+		ftape_function_nest_level = 0;
+	}
+	if (ftape_function_nest_level < sizeof(spacing)) {
+		indent = (spacing +
+			  sizeof(spacing) - 1 -
+			  ftape_function_nest_level);
+	} else {
+		indent = spacing;
+	}
+	printk(KERN_INFO "[%03d]%s-%s (%s)\n",
+	       (int) trace_id++, indent, file, name);
+}
+
+void ftape_trace_log(const char *file, const char *function)
+{
+	char *indent;
+
+	/*    Since printk seems not to work with "%*s" format
+	 *    we'll use this work-around.
+	 */
+	if (ftape_function_nest_level < 0) {
+		printk(KERN_INFO "function nest level (%d) < 0\n", ftape_function_nest_level);
+		ftape_function_nest_level = 0;
+	}
+	if (ftape_function_nest_level < sizeof(spacing)) {
+		indent = (spacing + 
+			  sizeof(spacing) - 1 - 
+			  ftape_function_nest_level);
+	} else {
+		indent = spacing;
+	}
+	printk(KERN_INFO "[%03d]%s%s (%s) - ", 
+	       (int) trace_id++, indent, file, function);
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-tracing.h b/drivers/char/ftape/lowlevel/ftape-tracing.h
new file mode 100644
index 0000000..fa7cd20
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-tracing.h
@@ -0,0 +1,180 @@
+#ifndef _FTAPE_TRACING_H
+#define _FTAPE_TRACING_H
+
+/*
+ * Copyright (C) 1994-1996 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-tracing.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:28 $
+ *
+ *      This file contains definitions that eases the debugging of the
+ *      QIC-40/80/3010/3020 floppy-tape driver "ftape" for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+
+/*
+ *  Be very careful with TRACE_EXIT and TRACE_ABORT.
+ *
+ *  if (something) TRACE_EXIT error;
+ *
+ *  will NOT work. Use
+ *
+ *  if (something) {
+ *    TRACE_EXIT error;
+ *  }
+ *
+ *  instead. Maybe a bit dangerous, but save lots of lines of code.
+ */
+
+#define LL_X "%d/%d KB"
+#define LL(x) (unsigned int)((__u64)(x)>>10), (unsigned int)((x)&1023)
+
+typedef enum {
+	ft_t_nil = -1,
+	ft_t_bug,
+	ft_t_err,
+	ft_t_warn,
+	ft_t_info,
+	ft_t_noise,
+	ft_t_flow,
+	ft_t_fdc_dma,
+	ft_t_data_flow,
+	ft_t_any
+} ft_trace_t;
+
+#ifdef  CONFIG_FT_NO_TRACE_AT_ALL
+/*  the compiler will optimize away most TRACE() macros
+ */
+#define FT_TRACE_TOP_LEVEL	ft_t_bug
+#define TRACE_FUN(level)	do {} while(0)
+#define TRACE_EXIT		return
+#define TRACE(l, m, i...)						\
+{									\
+	if ((ft_trace_t)(l) == FT_TRACE_TOP_LEVEL) {			\
+		printk(KERN_INFO"ftape%s(%s):\n"	                \
+		       KERN_INFO m".\n" ,__FILE__, __FUNCTION__ , ##i);	\
+	}								\
+}
+#define SET_TRACE_LEVEL(l)      if ((l) == (l)) do {} while(0)
+#define TRACE_LEVEL		FT_TRACE_TOP_LEVEL
+
+#else
+
+#ifdef CONFIG_FT_NO_TRACE
+/*  the compiler will optimize away many TRACE() macros
+ *  the ftape_simple_trace_call() function simply increments 
+ *  the function nest level.
+ */ 
+#define FT_TRACE_TOP_LEVEL	ft_t_warn
+#define TRACE_FUN(level)	ftape_function_nest_level++
+#define TRACE_EXIT		ftape_function_nest_level--; return
+
+#else
+#ifdef CONFIG_FT_FULL_DEBUG
+#define FT_TRACE_TOP_LEVEL ft_t_any
+#else
+#define FT_TRACE_TOP_LEVEL ft_t_flow
+#endif
+#define TRACE_FUN(level)					\
+	const ft_trace_t _tracing = level;			\
+	if (ftape_tracing >= (ft_trace_t)(level) &&		\
+	    (ft_trace_t)(level) <= FT_TRACE_TOP_LEVEL)		\
+		ftape_trace_call(__FILE__, __FUNCTION__);	\
+	ftape_function_nest_level ++;
+
+#define TRACE_EXIT						\
+	--ftape_function_nest_level;				\
+	if (ftape_tracing >= (ft_trace_t)(_tracing) &&		\
+	    (ft_trace_t)(_tracing) <= FT_TRACE_TOP_LEVEL)	\
+		ftape_trace_exit(__FILE__, __FUNCTION__);	\
+	return
+
+#endif
+
+#define TRACE(l, m, i...)					\
+{								\
+	if (ftape_tracing >= (ft_trace_t)(l) &&			\
+	    (ft_trace_t)(l) <= FT_TRACE_TOP_LEVEL) {		\
+		ftape_trace_log(__FILE__, __FUNCTION__);	\
+		printk(m".\n" ,##i);				\
+	}							\
+}
+
+#define SET_TRACE_LEVEL(l) 				\
+{							\
+	if ((ft_trace_t)(l) <= FT_TRACE_TOP_LEVEL) {	\
+		ftape_tracing = (ft_trace_t)(l);	\
+	} else {					\
+		ftape_tracing = FT_TRACE_TOP_LEVEL;	\
+	}						\
+}
+#define TRACE_LEVEL    							     \
+((ftape_tracing <= FT_TRACE_TOP_LEVEL) ? ftape_tracing : FT_TRACE_TOP_LEVEL)
+
+
+/*      Global variables declared in tracing.c
+ */
+extern ft_trace_t ftape_tracing;  /* sets default level */
+extern int ftape_function_nest_level;
+
+/*      Global functions declared in tracing.c
+ */
+extern void ftape_trace_call(const char *file, const char *name);
+extern void ftape_trace_exit(const char *file, const char *name);
+extern void ftape_trace_log (const char *file, const char *name);
+
+#endif /* !defined(CONFIG_FT_NO_TRACE_AT_ALL) */
+
+/*
+ *   Abort with a message.
+ */
+#define TRACE_ABORT(res, i...)			\
+{						\
+ 	TRACE(i);				\
+	TRACE_EXIT res;				\
+}
+
+/*   The following transforms the common "if(result < 0) ... " into a
+ *   one-liner.
+ */
+#define _TRACE_CATCH(level, fun, action)				\
+{									\
+	int _res = (fun);						\
+	if (_res < 0) {							\
+		do { action /* */ ; } while(0);				\
+		TRACE_ABORT(_res, level, "%s failed: %d", #fun,	_res);	\
+	}								\
+}
+
+#define TRACE_CATCH(fun, fail) _TRACE_CATCH(ft_t_err, fun, fail)
+
+/*  Abort the current function when signalled. This doesn't belong here,
+ *  but rather into ftape-rw.h (maybe)
+ */
+#define FT_SIGNAL_EXIT(sig_mask)					\
+	if (sigtestsetmask(&current->pending.signal, sig_mask)) {	\
+		TRACE_ABORT(-EINTR,					\
+			    ft_t_warn,					\
+			    "interrupted by non-blockable signal");	\
+	}
+
+#endif /* _FTAPE_TRACING_H */
diff --git a/drivers/char/ftape/lowlevel/ftape-write.c b/drivers/char/ftape/lowlevel/ftape-write.c
new file mode 100644
index 0000000..45601ec
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-write.c
@@ -0,0 +1,336 @@
+/*
+ *      Copyright (C) 1993-1995 Bas Laarhoven,
+ *                (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-write.c,v $
+ * $Revision: 1.3.4.1 $
+ * $Date: 1997/11/14 18:07:04 $
+ *
+ *      This file contains the writing code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/ftape.h>
+#include <linux/qic117.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-ecc.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/fdc-isr.h"
+
+/*      Global vars.
+ */
+
+/*      Local vars.
+ */
+static int last_write_failed;
+
+void ftape_zap_write_buffers(void)
+{
+	int i;
+
+	for (i = 0; i < ft_nr_buffers; ++i) {
+		ft_buffer[i]->status = done;
+	}
+	ftape_reset_buffer();
+}
+
+static int copy_and_gen_ecc(void *destination, 
+			    const void *source,
+			    const SectorMap bad_sector_map)
+{
+	int result;
+	struct memory_segment mseg;
+	int bads = count_ones(bad_sector_map);
+	TRACE_FUN(ft_t_any);
+
+	if (bads > 0) {
+		TRACE(ft_t_noise, "bad sectors in map: %d", bads);
+	}
+	if (bads + 3 >= FT_SECTORS_PER_SEGMENT) {
+		TRACE(ft_t_noise, "empty segment");
+		mseg.blocks = 0; /* skip entire segment */
+		result = 0;      /* nothing written */
+	} else {
+		mseg.blocks = FT_SECTORS_PER_SEGMENT - bads;
+		mseg.data = destination;
+		memcpy(mseg.data, source, (mseg.blocks - 3) * FT_SECTOR_SIZE);
+		result = ftape_ecc_set_segment_parity(&mseg);
+		if (result < 0) {
+			TRACE(ft_t_err, "ecc_set_segment_parity failed");
+		} else {
+			result = (mseg.blocks - 3) * FT_SECTOR_SIZE;
+		}
+	}
+	TRACE_EXIT result;
+}
+
+
+int ftape_start_writing(const ft_write_mode_t mode)
+{
+	buffer_struct *head = ftape_get_buffer(ft_queue_head);
+	int segment_id = head->segment_id;
+	int result;
+	buffer_state_enum wanted_state = (mode == FT_WR_DELETE
+					  ? deleting
+					  : writing);
+	TRACE_FUN(ft_t_flow);
+
+	if ((ft_driver_state != wanted_state) || head->status != waiting) {
+		TRACE_EXIT 0;
+	}
+	ftape_setup_new_segment(head, segment_id, 1);
+	if (mode == FT_WR_SINGLE) {
+		/* stop tape instead of pause */
+		head->next_segment = 0;
+	}
+	ftape_calc_next_cluster(head); /* prepare */
+	head->status = ft_driver_state; /* either writing or deleting */
+	if (ft_runner_status == idle) {
+		TRACE(ft_t_noise,
+		      "starting runner for segment %d", segment_id);
+		TRACE_CATCH(ftape_start_tape(segment_id,head->sector_offset),);
+	} else {
+		TRACE(ft_t_noise, "runner not idle, not starting tape");
+	}
+	/* go */
+	result = fdc_setup_read_write(head, (mode == FT_WR_DELETE
+					     ? FDC_WRITE_DELETED : FDC_WRITE));
+	ftape_set_state(wanted_state); /* should not be necessary */
+	TRACE_EXIT result;
+}
+
+/*  Wait until all data is actually written to tape.
+ *  
+ *  There is a problem: when the tape runs into logical EOT, then this
+ *  failes. We need to restart the runner in this case.
+ */
+int ftape_loop_until_writes_done(void)
+{
+	buffer_struct *head;
+	TRACE_FUN(ft_t_flow);
+
+	while ((ft_driver_state == writing || ft_driver_state == deleting) && 
+	       ftape_get_buffer(ft_queue_head)->status != done) {
+		/* set the runner status to idle if at lEOT */
+		TRACE_CATCH(ftape_handle_logical_eot(),	last_write_failed = 1);
+		/* restart the tape if necessary */
+		if (ft_runner_status == idle) {
+			TRACE(ft_t_noise, "runner is idle, restarting");
+			if (ft_driver_state == deleting) {
+				TRACE_CATCH(ftape_start_writing(FT_WR_DELETE),
+					    last_write_failed = 1);
+			} else {
+				TRACE_CATCH(ftape_start_writing(FT_WR_MULTI),
+					    last_write_failed = 1);
+			}
+		}
+		TRACE(ft_t_noise, "tail: %d, head: %d", 
+		      ftape_buffer_id(ft_queue_tail),
+		      ftape_buffer_id(ft_queue_head));
+		TRACE_CATCH(fdc_interrupt_wait(5 * FT_SECOND),
+			    last_write_failed = 1);
+		head = ftape_get_buffer(ft_queue_head);
+		if (head->status == error) {
+			/* Allow escape from loop when signaled !
+			 */
+			FT_SIGNAL_EXIT(_DONT_BLOCK);
+			if (head->hard_error_map != 0) {
+				/*  Implement hard write error recovery here
+				 */
+			}
+			/* retry this one */
+			head->status = waiting;
+			if (ft_runner_status == aborting) {
+				ftape_dumb_stop();
+			}
+			if (ft_runner_status != idle) {
+				TRACE_ABORT(-EIO, ft_t_err,
+					    "unexpected state: "
+					    "ft_runner_status != idle");
+			}
+			ftape_start_writing(ft_driver_state == deleting
+					    ? FT_WR_MULTI : FT_WR_DELETE);
+		}
+		TRACE(ft_t_noise, "looping until writes done");
+	}
+	ftape_set_state(idle);
+	TRACE_EXIT 0;
+}
+
+/*      Write given segment from buffer at address to tape.
+ */
+static int write_segment(const int segment_id,
+			 const void *address, 
+			 const ft_write_mode_t write_mode)
+{
+	int bytes_written = 0;
+	buffer_struct *tail;
+	buffer_state_enum wanted_state = (write_mode == FT_WR_DELETE
+					  ? deleting : writing);
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "segment_id = %d", segment_id);
+	if (ft_driver_state != wanted_state) {
+		if (ft_driver_state == deleting ||
+		    wanted_state == deleting) {
+			TRACE_CATCH(ftape_loop_until_writes_done(),);
+		}
+		TRACE(ft_t_noise, "calling ftape_abort_operation");
+		TRACE_CATCH(ftape_abort_operation(),);
+		ftape_zap_write_buffers();
+		ftape_set_state(wanted_state);
+	}
+	/*    if all buffers full we'll have to wait...
+	 */
+	ftape_wait_segment(wanted_state);
+	tail = ftape_get_buffer(ft_queue_tail);
+	switch(tail->status) {
+	case done:
+		ft_history.defects += count_ones(tail->hard_error_map);
+		break;
+	case waiting:
+		/* this could happen with multiple EMPTY_SEGMENTs, but
+		 * shouldn't happen any more as we re-start the runner even
+		 * with an empty segment.
+		 */
+		bytes_written = -EAGAIN;
+		break;
+	case error:
+		/*  setup for a retry
+		 */
+		tail->status = waiting;
+		bytes_written = -EAGAIN; /* force retry */
+		if (tail->hard_error_map != 0) {
+			TRACE(ft_t_warn, 
+			      "warning: %d hard error(s) in written segment",
+			      count_ones(tail->hard_error_map));
+			TRACE(ft_t_noise, "hard_error_map = 0x%08lx", 
+			      (long)tail->hard_error_map);
+			/*  Implement hard write error recovery here
+			 */
+		}
+		break;
+	default:
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "wait for empty segment failed, tail status: %d",
+			    tail->status);
+	}
+	/*    should runner stop ?
+	 */
+	if (ft_runner_status == aborting) {
+		buffer_struct *head = ftape_get_buffer(ft_queue_head);
+		if (head->status == wanted_state) {
+			head->status = done; /* ???? */
+		}
+		/*  don't call abort_operation(), we don't want to zap
+		 *  the dma buffers
+		 */
+		TRACE_CATCH(ftape_dumb_stop(),);
+	} else {
+		/*  If just passed last segment on tape: wait for BOT
+		 *  or EOT mark. Sets ft_runner_status to idle if at lEOT
+		 *  and successful 
+		 */
+		TRACE_CATCH(ftape_handle_logical_eot(),);
+	}
+	if (tail->status == done) {
+		/* now at least one buffer is empty, fill it with our
+		 * data.  skip bad sectors and generate ecc.
+		 * copy_and_gen_ecc return nr of bytes written, range
+		 * 0..29 Kb inclusive!  
+		 *
+		 * Empty segments are handled inside coyp_and_gen_ecc()
+		 */
+		if (write_mode != FT_WR_DELETE) {
+			TRACE_CATCH(bytes_written = copy_and_gen_ecc(
+				tail->address, address,
+				ftape_get_bad_sector_entry(segment_id)),);
+		}
+		tail->segment_id = segment_id;
+		tail->status = waiting;
+		tail = ftape_next_buffer(ft_queue_tail);
+	}
+	/*  Start tape only if all buffers full or flush mode.
+	 *  This will give higher probability of streaming.
+	 */
+	if (ft_runner_status != running && 
+	    ((tail->status == waiting &&
+	      ftape_get_buffer(ft_queue_head) == tail) ||
+	     write_mode != FT_WR_ASYNC)) {
+		TRACE_CATCH(ftape_start_writing(write_mode),);
+	}
+	TRACE_EXIT bytes_written;
+}
+
+/*  Write as much as fits from buffer to the given segment on tape
+ *  and handle retries.
+ *  Return the number of bytes written (>= 0), or:
+ *      -EIO          write failed
+ *      -EINTR        interrupted by signal
+ *      -ENOSPC       device full
+ */
+int ftape_write_segment(const int segment_id,
+			const void *buffer, 
+			const ft_write_mode_t flush)
+{
+	int retry = 0;
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	ft_history.used |= 2;
+	if (segment_id >= ft_tracks_per_tape*ft_segments_per_track) {
+		/* tape full */
+		TRACE_ABORT(-ENOSPC, ft_t_err,
+			    "invalid segment id: %d (max %d)", 
+			    segment_id, 
+			    ft_tracks_per_tape * ft_segments_per_track -1);
+	}
+	for (;;) {
+		if ((result = write_segment(segment_id, buffer, flush)) >= 0) {
+			if (result == 0) { /* empty segment */
+				TRACE(ft_t_noise,
+				      "empty segment, nothing written");
+			}
+			TRACE_EXIT result;
+		}
+		if (result == -EAGAIN) {
+			if (++retry > 100) { /* give up */
+				TRACE_ABORT(-EIO, ft_t_err,
+				      "write failed, >100 retries in segment");
+			}
+			TRACE(ft_t_warn, "write error, retry %d (%d)",
+			      retry,
+			      ftape_get_buffer(ft_queue_tail)->segment_id);
+		} else {
+			TRACE_ABORT(result, ft_t_err,
+				    "write_segment failed, error: %d", result);
+		}
+		/* Allow escape from loop when signaled !
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+	}
+}
diff --git a/drivers/char/ftape/lowlevel/ftape-write.h b/drivers/char/ftape/lowlevel/ftape-write.h
new file mode 100644
index 0000000..0e7f898
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape-write.h
@@ -0,0 +1,53 @@
+#ifndef _FTAPE_WRITE_H
+#define _FTAPE_WRITE_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven,
+ *           (C) 1996-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape-write.h,v $
+ $Author: claus $
+ *
+ $Revision: 1.2 $
+ $Date: 1997/10/05 19:18:30 $
+ $State: Exp $
+ *
+ *      This file contains the definitions for the write functions
+ *      for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+
+/*      ftape-write.c defined global functions.
+ */
+typedef enum {
+	FT_WR_ASYNC  = 0, /* start tape only when all buffers are full */
+	FT_WR_MULTI  = 1, /* start tape, but don't necessarily stop */
+	FT_WR_SINGLE = 2, /* write a single segment and stop afterwards */
+	FT_WR_DELETE = 3  /* write deleted data marks */
+} ft_write_mode_t;
+
+extern int  ftape_start_writing(const ft_write_mode_t mode);
+extern int  ftape_write_segment(const int segment,
+				const void *address, 
+				const ft_write_mode_t flushing);
+extern void ftape_zap_write_buffers(void);
+extern int  ftape_loop_until_writes_done(void);
+
+#endif				/* _FTAPE_WRITE_H */
+
diff --git a/drivers/char/ftape/lowlevel/ftape_syms.c b/drivers/char/ftape/lowlevel/ftape_syms.c
new file mode 100644
index 0000000..5dc3a38
--- /dev/null
+++ b/drivers/char/ftape/lowlevel/ftape_syms.c
@@ -0,0 +1,88 @@
+/*
+ *      Copyright (C) 1996-1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/lowlevel/ftape_syms.c,v $
+ * $Revision: 1.4 $
+ * $Date: 1997/10/17 00:03:51 $
+ *
+ *      This file contains the symbols that the ftape low level
+ *      part of the QIC-40/80/3010/3020 floppy-tape driver "ftape"
+ *      exports to its high level clients
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/ftape.h>
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-init.h"
+#include "../lowlevel/fdc-io.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-rw.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/ftape-buffer.h"
+#include "../lowlevel/ftape-format.h"
+
+/* bad sector handling from ftape-bsm.c */
+EXPORT_SYMBOL(ftape_get_bad_sector_entry);
+EXPORT_SYMBOL(ftape_find_end_of_bsm_list);
+/* from ftape-rw.c */
+EXPORT_SYMBOL(ftape_set_state);
+/* from ftape-ctl.c */
+EXPORT_SYMBOL(ftape_seek_to_bot);
+EXPORT_SYMBOL(ftape_seek_to_eot);
+EXPORT_SYMBOL(ftape_abort_operation);
+EXPORT_SYMBOL(ftape_get_status);
+EXPORT_SYMBOL(ftape_enable);
+EXPORT_SYMBOL(ftape_disable);
+EXPORT_SYMBOL(ftape_mmap);
+EXPORT_SYMBOL(ftape_calibrate_data_rate);
+/* from ftape-io.c */
+EXPORT_SYMBOL(ftape_reset_drive);
+EXPORT_SYMBOL(ftape_command);
+EXPORT_SYMBOL(ftape_parameter);
+EXPORT_SYMBOL(ftape_ready_wait);
+EXPORT_SYMBOL(ftape_report_operation);
+EXPORT_SYMBOL(ftape_report_error);
+/* from ftape-read.c */
+EXPORT_SYMBOL(ftape_read_segment_fraction);
+EXPORT_SYMBOL(ftape_zap_read_buffers);
+EXPORT_SYMBOL(ftape_read_header_segment);
+EXPORT_SYMBOL(ftape_decode_header_segment);
+/* from ftape-write.c */
+EXPORT_SYMBOL(ftape_write_segment);
+EXPORT_SYMBOL(ftape_start_writing);
+EXPORT_SYMBOL(ftape_loop_until_writes_done);
+/* from ftape-buffer.h */
+EXPORT_SYMBOL(ftape_set_nr_buffers);
+/* from ftape-format.h */
+EXPORT_SYMBOL(ftape_format_track);
+EXPORT_SYMBOL(ftape_format_status);
+EXPORT_SYMBOL(ftape_verify_segment);
+/* from tracing.c */
+#ifndef CONFIG_FT_NO_TRACE_AT_ALL
+EXPORT_SYMBOL(ftape_tracing);
+EXPORT_SYMBOL(ftape_function_nest_level);
+EXPORT_SYMBOL(ftape_trace_call);
+EXPORT_SYMBOL(ftape_trace_exit);
+EXPORT_SYMBOL(ftape_trace_log);
+#endif
+
diff --git a/drivers/char/ftape/zftape/Makefile b/drivers/char/ftape/zftape/Makefile
new file mode 100644
index 0000000..6d91c1f
--- /dev/null
+++ b/drivers/char/ftape/zftape/Makefile
@@ -0,0 +1,36 @@
+#
+#       Copyright (C) 1996, 1997 Claus-Justus Heine.
+#
+# 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, 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; see the file COPYING.  If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+# $Source: /homes/cvs/ftape-stacked/ftape/zftape/Makefile,v $
+# $Revision: 1.4 $
+# $Date: 1997/10/05 19:18:58 $
+#
+#      Makefile for the QIC-40/80/3010/3020 zftape interface VFS to
+#      ftape
+#
+
+
+# ZFT_OBSOLETE - enable the MTIOC_ZFTAPE_GETBLKSZ ioctl. You should
+#                leave this enabled for compatibility with taper.
+
+obj-$(CONFIG_ZFTAPE) += zftape.o
+
+zftape-objs := zftape-rw.o zftape-ctl.o zftape-read.o \
+	       zftape-write.o zftape-vtbl.o zftape-eof.o \
+	       zftape-init.o zftape-buffers.o zftape_syms.o
+
+EXTRA_CFLAGS := -DZFT_OBSOLETE
diff --git a/drivers/char/ftape/zftape/zftape-buffers.c b/drivers/char/ftape/zftape/zftape-buffers.c
new file mode 100644
index 0000000..da06f13
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-buffers.c
@@ -0,0 +1,149 @@
+/*
+ *      Copyright (C) 1995-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-buffers.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:59 $
+ *
+ *      This file contains the dynamic buffer allocation routines 
+ *      of zftape
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <linux/zftape.h>
+
+#include <linux/vmalloc.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/*  global variables
+ */
+
+/*  local varibales
+ */
+static unsigned int used_memory;
+static unsigned int peak_memory;
+
+void zft_memory_stats(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "Memory usage (vmalloc allocations):\n"
+	      KERN_INFO "total allocated: %d\n"
+	      KERN_INFO "peak allocation: %d",
+	      used_memory, peak_memory);
+	peak_memory = used_memory;
+	TRACE_EXIT;
+}
+
+int zft_vcalloc_once(void *new, size_t size)
+{
+	TRACE_FUN(ft_t_flow);
+	if (zft_vmalloc_once(new, size) < 0) {
+		TRACE_EXIT -ENOMEM;
+	}
+	memset(*(void **)new, '\0', size);
+	TRACE_EXIT 0;
+}
+int zft_vmalloc_once(void *new, size_t size)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (*(void **)new != NULL || size == 0) {
+		TRACE_EXIT 0;
+	}
+	if ((*(void **)new = vmalloc(size)) == NULL) {
+		TRACE_EXIT -ENOMEM;
+	}
+	used_memory += size;
+	if (peak_memory < used_memory) {
+		peak_memory = used_memory;
+	}
+	TRACE_ABORT(0, ft_t_noise,
+		    "allocated buffer @ %p, %d bytes", *(void **)new, size);
+}
+int zft_vmalloc_always(void *new, size_t size)
+{
+	TRACE_FUN(ft_t_flow);
+
+	zft_vfree(new, size);
+	TRACE_EXIT zft_vmalloc_once(new, size);
+}
+void zft_vfree(void *old, size_t size)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (*(void **)old) {
+		vfree(*(void **)old);
+		used_memory -= size;
+		TRACE(ft_t_noise, "released buffer @ %p, %d bytes",
+		      *(void **)old, size);
+		*(void **)old = NULL;
+	}
+	TRACE_EXIT;
+}
+
+void *zft_kmalloc(size_t size)
+{
+	void *new;
+
+	while ((new = kmalloc(size, GFP_KERNEL)) == NULL) {
+		msleep_interruptible(100);
+	}
+	memset(new, 0, size);
+	used_memory += size;
+	if (peak_memory < used_memory) {
+		peak_memory = used_memory;
+	}
+	return new;
+}
+
+void zft_kfree(void *old, size_t size)
+{
+	kfree(old);
+	used_memory -= size;
+}
+
+/* there are some more buffers that are allocated on demand.
+ * cleanup_module() calles this function to be sure to have released
+ * them 
+ */
+void zft_uninit_mem(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	zft_vfree(&zft_hseg_buf, FT_SEGMENT_SIZE);
+	zft_vfree(&zft_deblock_buf, FT_SEGMENT_SIZE); zft_deblock_segment = -1;
+	zft_free_vtbl();
+	if (zft_cmpr_lock(0 /* don't load */) == 0) {
+		(*zft_cmpr_ops->cleanup)();
+		(*zft_cmpr_ops->reset)(); /* unlock it again */
+	}
+	zft_memory_stats();
+	TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/zftape/zftape-buffers.h b/drivers/char/ftape/zftape/zftape-buffers.h
new file mode 100644
index 0000000..798e312
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-buffers.h
@@ -0,0 +1,55 @@
+#ifndef _FTAPE_DYNMEM_H
+#define _FTAPE_DYNMEM_H
+
+/*
+ *      Copyright (C) 1995-1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-buffers.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:18:59 $
+ *
+ *   memory allocation routines.
+ *
+ */
+
+/* we do not allocate all of the really large buffer memory before
+ * someone tries to open the drive. ftape_open() may fail with
+ * -ENOMEM, but that's better having 200k of vmalloced memory which
+ * cannot be swapped out.
+ */
+
+extern void  zft_memory_stats(void);
+extern int   zft_vmalloc_once(void *new, size_t size);
+extern int   zft_vcalloc_once(void *new, size_t size);
+extern int   zft_vmalloc_always(void *new, size_t size);
+extern void  zft_vfree(void *old, size_t size);
+extern void *zft_kmalloc(size_t size);
+extern void  zft_kfree(void *old, size_t size);
+
+/* called by cleanup_module() 
+ */
+extern void zft_uninit_mem(void);
+
+#endif
+
+
+
+
+
+
+
diff --git a/drivers/char/ftape/zftape/zftape-ctl.c b/drivers/char/ftape/zftape/zftape-ctl.c
new file mode 100644
index 0000000..6c7874e5
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-ctl.c
@@ -0,0 +1,1418 @@
+/* 
+ *      Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-ctl.c,v $
+ * $Revision: 1.2.6.2 $
+ * $Date: 1997/11/14 18:07:33 $
+ *
+ *      This file contains the non-read/write zftape functions
+ *      for the QIC-40/80/3010/3020 floppy-tape driver for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/fcntl.h>
+
+#include <linux/zftape.h>
+
+#include <asm/uaccess.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/*      Global vars.
+ */
+int zft_write_protected; /* this is when cartridge rdonly or O_RDONLY */
+int zft_header_read;
+int zft_offline;
+unsigned int zft_unit;
+int zft_resid;
+int zft_mt_compression;
+
+/*      Local vars.
+ */
+static int going_offline;
+
+typedef int (mt_fun)(int *argptr);
+typedef int (*mt_funp)(int *argptr);
+typedef struct
+{
+	mt_funp function;
+	unsigned offline         : 1; /* op permitted if offline or no_tape */
+	unsigned write_protected : 1; /* op permitted if write-protected    */
+	unsigned not_formatted   : 1; /* op permitted if tape not formatted */
+	unsigned raw_mode        : 1; /* op permitted if zft_mode == 0    */
+	unsigned need_idle_state : 1; /* need to call def_idle_state        */
+	char     *name;
+} fun_entry;
+
+static mt_fun mt_dummy, mt_reset, mt_fsr, mt_bsr, mt_rew, mt_offl, mt_nop,
+	mt_weof, mt_erase, mt_ras2, mt_setblk, mt_setdensity,
+	mt_seek, mt_tell, mt_reten, mt_eom, mt_fsf, mt_bsf,
+	mt_fsfm, mt_bsfm, mt_setdrvbuffer, mt_compression;
+
+static fun_entry mt_funs[]=
+{ 
+	{mt_reset       , 1, 1, 1, 1, 0, "MT_RESET" }, /*  0 */
+	{mt_fsf         , 0, 1, 0, 0, 1, "MT_FSF"   },
+	{mt_bsf         , 0, 1, 0, 0, 1, "MT_BSF"   },
+	{mt_fsr         , 0, 1, 0, 1, 1, "MT_FSR"   },
+	{mt_bsr         , 0, 1, 0, 1, 1, "MT_BSR"   },
+	{mt_weof        , 0, 0, 0, 0, 0, "MT_WEOF"  }, /*  5 */
+	{mt_rew         , 0, 1, 1, 1, 0, "MT_REW"   },
+	{mt_offl        , 0, 1, 1, 1, 0, "MT_OFFL"  },
+	{mt_nop         , 1, 1, 1, 1, 0, "MT_NOP"   },
+	{mt_reten       , 0, 1, 1, 1, 0, "MT_RETEN" },
+	{mt_bsfm        , 0, 1, 0, 0, 1, "MT_BSFM"  }, /* 10 */
+	{mt_fsfm        , 0, 1, 0, 0, 1, "MT_FSFM"  },
+	{mt_eom         , 0, 1, 0, 0, 1, "MT_EOM"   },
+	{mt_erase       , 0, 0, 0, 1, 0, "MT_ERASE" },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_RAS1"  },
+	{mt_ras2        , 0, 0, 0, 1, 0, "MT_RAS2"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_RAS3"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "UNKNOWN"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "UNKNOWN"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "UNKNOWN"  },
+	{mt_setblk      , 1, 1, 1, 1, 1, "MT_SETBLK"}, /* 20 */
+	{mt_setdensity  , 1, 1, 1, 1, 0, "MT_SETDENSITY"},
+	{mt_seek        , 0, 1, 0, 1, 1, "MT_SEEK"  },
+	{mt_dummy       , 0, 1, 0, 1, 1, "MT_TELL"  }, /* wr-only ?! */
+	{mt_setdrvbuffer, 1, 1, 1, 1, 0, "MT_SETDRVBUFFER" },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_FSS"   }, /* 25 */
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_BSS"   },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_WSM"   },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_LOCK"  },
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_UNLOCK"},
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_LOAD"  }, /* 30 */
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_UNLOAD"},
+	{mt_compression , 1, 1, 1, 0, 1, "MT_COMPRESSION"},
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_SETPART"},
+	{mt_dummy       , 1, 1, 1, 1, 0, "MT_MKPART"}
+};  
+
+#define NR_MT_CMDS NR_ITEMS(mt_funs)
+
+void zft_reset_position(zft_position *pos)
+{
+	TRACE_FUN(ft_t_flow);
+
+	pos->seg_byte_pos =
+		pos->volume_pos = 0;
+	if (zft_header_read) {
+		/* need to keep track of the volume table and
+		 * compression map. We therefor simply
+		 * position at the beginning of the first
+		 * volume. This covers old ftape archives as
+		 * well has various flavours of the
+		 * compression map segments. The worst case is
+		 * that the compression map shows up as a
+		 * additional volume in front of all others.
+		 */
+		pos->seg_pos  = zft_find_volume(0)->start_seg;
+		pos->tape_pos = zft_calc_tape_pos(pos->seg_pos);
+	} else {
+		pos->tape_pos =  0;
+		pos->seg_pos  = -1;
+	}
+	zft_just_before_eof =  0;
+	zft_deblock_segment = -1;
+	zft_io_state        = zft_idle;
+	zft_zap_read_buffers();
+	zft_prevent_flush();
+	/*  unlock the compresison module if it is loaded.
+	 *  The zero arg means not to try to load the module.
+	 */
+	if (zft_cmpr_lock(0) == 0) {
+		(*zft_cmpr_ops->reset)(); /* unlock */
+	}
+	TRACE_EXIT;
+}
+
+static void zft_init_driver(void)
+{
+	TRACE_FUN(ft_t_flow);
+
+	zft_resid =
+		zft_header_read          =
+		zft_old_ftape            =
+		zft_offline              =
+		zft_write_protected      =
+		going_offline            =
+		zft_mt_compression       =
+		zft_header_changed       =
+		zft_volume_table_changed =
+		zft_written_segments     = 0;
+	zft_blk_sz = CONFIG_ZFT_DFLT_BLK_SZ;
+	zft_reset_position(&zft_pos); /* does most of the stuff */
+	ftape_zap_read_buffers();
+	ftape_set_state(idle);
+	TRACE_EXIT;
+}
+
+int zft_def_idle_state(void)
+{ 
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+	
+	if (!zft_header_read) {
+		result = zft_read_header_segments();
+	} else if ((result = zft_flush_buffers()) >= 0 && zft_qic_mode) {
+		/*  don't move past eof
+		 */
+		(void)zft_close_volume(&zft_pos);
+	}
+	if (ftape_abort_operation() < 0) {
+		TRACE(ft_t_warn, "ftape_abort_operation() failed");
+		result = -EIO;
+	}
+	/* clear remaining read buffers */
+	zft_zap_read_buffers();
+	zft_io_state = zft_idle;
+	TRACE_EXIT result;
+}
+
+/*****************************************************************************
+ *                                                                           *
+ *  functions for the MTIOCTOP commands                                      *
+ *                                                                           *
+ *****************************************************************************/
+
+static int mt_dummy(int *dummy)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE_EXIT -ENOSYS;
+}
+
+static int mt_reset(int *dummy)
+{        
+	TRACE_FUN(ft_t_flow);
+	
+	(void)ftape_seek_to_bot();
+	TRACE_CATCH(ftape_reset_drive(),
+		    zft_init_driver(); zft_uninit_mem(); zft_offline = 1);
+	/*  fake a re-open of the device. This will set all flage and 
+	 *  allocate buffers as appropriate. The new tape condition will
+	 *  force the open routine to do anything we need.
+	 */
+	TRACE_CATCH(_zft_open(-1 /* fake reopen */, 0 /* dummy */),);
+	TRACE_EXIT 0;
+}
+
+static int mt_fsf(int *arg)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	result = zft_skip_volumes(*arg, &zft_pos);
+	zft_just_before_eof = 0;
+	TRACE_EXIT result;
+}
+
+static int mt_bsf(int *arg)
+{
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+	
+	if (*arg != 0) {
+		result = zft_skip_volumes(-*arg + 1, &zft_pos);
+	}
+	TRACE_EXIT result;
+}
+
+static int seek_block(__s64 data_offset,
+		      __s64 block_increment,
+		      zft_position *pos)
+{ 
+	int result      = 0;
+	__s64 new_block_pos;
+	__s64 vol_block_count;
+	const zft_volinfo *volume;
+	int exceed;
+	TRACE_FUN(ft_t_flow);
+	
+	volume = zft_find_volume(pos->seg_pos);
+	if (volume->start_seg == 0 || volume->end_seg == 0) {
+		TRACE_EXIT -EIO;
+	}
+	new_block_pos   = (zft_div_blksz(data_offset, volume->blk_sz)
+			   + block_increment);
+	vol_block_count = zft_div_blksz(volume->size, volume->blk_sz);
+	if (new_block_pos < 0) {
+		TRACE(ft_t_noise,
+		      "new_block_pos " LL_X " < 0", LL(new_block_pos));
+		zft_resid     = (int)new_block_pos;
+		new_block_pos = 0;
+		exceed = 1;
+	} else if (new_block_pos > vol_block_count) {
+		TRACE(ft_t_noise,
+		      "new_block_pos " LL_X " exceeds size of volume " LL_X,
+		      LL(new_block_pos), LL(vol_block_count));
+		zft_resid     = (int)(vol_block_count - new_block_pos);
+		new_block_pos = vol_block_count;
+		exceed = 1;
+	} else {
+		exceed = 0;
+	}
+	if (zft_use_compression && volume->use_compression) {
+		TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),);
+		result = (*zft_cmpr_ops->seek)(new_block_pos, pos, volume,
+					       zft_deblock_buf);
+		pos->tape_pos  = zft_calc_tape_pos(pos->seg_pos);
+		pos->tape_pos += pos->seg_byte_pos;
+	} else {
+		pos->volume_pos = zft_mul_blksz(new_block_pos, volume->blk_sz);
+		pos->tape_pos   = zft_calc_tape_pos(volume->start_seg);
+		pos->tape_pos  += pos->volume_pos;
+		pos->seg_pos    = zft_calc_seg_byte_coord(&pos->seg_byte_pos,
+							  pos->tape_pos);
+	}
+	zft_just_before_eof = volume->size == pos->volume_pos;
+	if (zft_just_before_eof) {
+		/* why this? because zft_file_no checks agains start
+		 * and end segment of a volume. We do not want to
+		 * advance to the next volume with this function.
+		 */
+		TRACE(ft_t_noise, "set zft_just_before_eof");
+		zft_position_before_eof(pos, volume);
+	}
+	TRACE(ft_t_noise, "\n"
+	      KERN_INFO "new_seg_pos : %d\n"
+	      KERN_INFO "new_tape_pos: " LL_X "\n"
+	      KERN_INFO "vol_size    : " LL_X "\n"
+	      KERN_INFO "seg_byte_pos: %d\n"
+	      KERN_INFO "blk_sz  : %d", 
+	      pos->seg_pos, LL(pos->tape_pos),
+	      LL(volume->size), pos->seg_byte_pos,
+	      volume->blk_sz);
+	if (!exceed) {
+		zft_resid = new_block_pos - zft_div_blksz(pos->volume_pos,
+							  volume->blk_sz);
+	}
+	if (zft_resid < 0) {
+		zft_resid = -zft_resid;
+	}
+	TRACE_EXIT ((exceed || zft_resid != 0) && result >= 0) ? -EINVAL : result;
+}     
+
+static int mt_fsr(int *arg)
+{ 
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = seek_block(zft_pos.volume_pos,  *arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_bsr(int *arg)
+{   
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = seek_block(zft_pos.volume_pos, -*arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_weof(int *arg)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE_CATCH(zft_flush_buffers(),);
+	result = zft_weof(*arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_rew(int *dummy)
+{          
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	if(zft_header_read) {
+		(void)zft_def_idle_state();
+	}
+	result = ftape_seek_to_bot();
+	zft_reset_position(&zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_offl(int *dummy)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	going_offline= 1;
+	result = mt_rew(NULL);
+	TRACE_EXIT result;
+}
+
+static int mt_nop(int *dummy)
+{
+	TRACE_FUN(ft_t_flow);
+	/*  should we set tape status?
+	 */
+	if (!zft_offline) { /* offline includes no_tape */
+		(void)zft_def_idle_state();
+	}
+	TRACE_EXIT 0; 
+}
+
+static int mt_reten(int *dummy)
+{  
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	if(zft_header_read) {
+		(void)zft_def_idle_state();
+	}
+	result = ftape_seek_to_eot();
+	if (result >= 0) {
+		result = ftape_seek_to_bot();
+	}
+	TRACE_EXIT(result);
+}
+
+static int fsfbsfm(int arg, zft_position *pos)
+{ 
+	const zft_volinfo *vtbl;
+	__s64 block_pos;
+	TRACE_FUN(ft_t_flow);
+	
+	/* What to do? This should seek to the next file-mark and
+	 * position BEFORE. That is, a next write would just extend
+	 * the current file.  Well. Let's just seek to the end of the
+	 * current file, if count == 1.  If count > 1, then do a
+	 * "mt_fsf(count - 1)", and then seek to the end of that file.
+	 * If count == 0, do nothing
+	 */
+	if (arg == 0) {
+		TRACE_EXIT 0;
+	}
+	zft_just_before_eof = 0;
+	TRACE_CATCH(zft_skip_volumes(arg < 0 ? arg : arg-1, pos),
+		    if (arg > 0) {
+			    zft_resid ++; 
+		    });
+	vtbl      = zft_find_volume(pos->seg_pos);
+	block_pos = zft_div_blksz(vtbl->size, vtbl->blk_sz);
+	(void)seek_block(0, block_pos, pos);
+	if (pos->volume_pos != vtbl->size) {
+		zft_just_before_eof = 0;
+		zft_resid = 1;
+		/* we didn't managed to go there */
+		TRACE_ABORT(-EIO, ft_t_err, 
+			    "wanted file position " LL_X ", arrived at " LL_X, 
+			    LL(vtbl->size), LL(pos->volume_pos));
+	}
+	zft_just_before_eof = 1;
+	TRACE_EXIT 0; 
+}
+
+static int mt_bsfm(int *arg)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = fsfbsfm(-*arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_fsfm(int *arg)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = fsfbsfm(*arg, &zft_pos);
+	TRACE_EXIT result;
+}
+
+static int mt_eom(int *dummy)
+{              
+	TRACE_FUN(ft_t_flow);
+	
+	zft_skip_to_eom(&zft_pos);
+	TRACE_EXIT 0;
+}
+
+static int mt_erase(int *dummy)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = zft_erase();
+	TRACE_EXIT result;
+}
+
+static int mt_ras2(int *dummy)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	result = -ENOSYS;
+	TRACE_EXIT result;
+} 
+
+/*  Sets the new blocksize in BYTES
+ *
+ */
+static int mt_setblk(int *new_size)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	if((unsigned int)(*new_size) > ZFT_MAX_BLK_SZ) {
+		TRACE_ABORT(-EINVAL, ft_t_info,
+			    "desired blk_sz (%d) should be <= %d bytes",
+			    *new_size, ZFT_MAX_BLK_SZ);
+	}
+	if ((*new_size & (FT_SECTOR_SIZE-1)) != 0) {
+		TRACE_ABORT(-EINVAL, ft_t_info,
+			"desired blk_sz (%d) must be a multiple of %d bytes",
+			    *new_size, FT_SECTOR_SIZE);
+	}
+	if (*new_size == 0) {
+		if (zft_use_compression) {
+			TRACE_ABORT(-EINVAL, ft_t_info,
+				    "Variable block size not yet "
+				    "supported with compression");
+		}
+		*new_size = 1;
+	}
+	zft_blk_sz = *new_size;
+	TRACE_EXIT 0;
+} 
+
+static int mt_setdensity(int *arg)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	SET_TRACE_LEVEL(*arg);
+	TRACE(TRACE_LEVEL, "tracing set to %d", TRACE_LEVEL);
+	if ((int)TRACE_LEVEL != *arg) {
+		TRACE_EXIT -EINVAL;
+	}
+	TRACE_EXIT 0;
+}          
+
+static int mt_seek(int *new_block_pos)
+{ 
+	int result= 0;        
+	TRACE_FUN(ft_t_any);
+	
+	result = seek_block(0, (__s64)*new_block_pos, &zft_pos);
+	TRACE_EXIT result;
+}
+
+/*  OK, this is totally different from SCSI, but the worst thing that can 
+ *  happen is that there is not enough defragmentated memory that can be 
+ *  allocated. Also, there is a hardwired limit of 16 dma buffers in the 
+ *  stock ftape module. This shouldn't bring the system down.
+ *
+ * NOTE: the argument specifies the total number of dma buffers to use.
+ *       The driver needs at least 3 buffers to function at all.
+ * 
+ */
+static int mt_setdrvbuffer(int *cnt)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (*cnt < 3) {
+		TRACE_EXIT -EINVAL;
+	}
+	TRACE_CATCH(ftape_set_nr_buffers(*cnt),);
+	TRACE_EXIT 0;
+}
+/* return the block position from start of volume 
+ */
+static int mt_tell(int *arg)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	*arg   = zft_div_blksz(zft_pos.volume_pos,
+			       zft_find_volume(zft_pos.seg_pos)->blk_sz);
+	TRACE_EXIT 0;
+}
+
+static int mt_compression(int *arg)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	/*  Ok. We could also check whether compression is available at
+	 *  all by trying to load the compression module.  We could
+	 *  also check for a block size of 1 byte which is illegal
+	 *  with compression.  Instead of doing it here we rely on
+	 *  zftape_write() to do the proper checks.
+	 */
+	if ((unsigned int)*arg > 1) {
+		TRACE_EXIT -EINVAL;
+	}
+	if (*arg != 0 && zft_blk_sz == 1) { /* variable block size */
+		TRACE_ABORT(-EINVAL, ft_t_info,
+			    "Compression not yet supported "
+			    "with variable block size");
+	}
+	zft_mt_compression  = *arg;
+	if ((zft_unit & ZFT_ZIP_MODE) == 0) {
+		zft_use_compression = zft_mt_compression;
+	}
+	TRACE_EXIT 0;
+}
+
+/*  check whether write access is allowed. Write access is denied when
+ *  + zft_write_protected == 1 -- this accounts for either hard write 
+ *                                protection of the cartridge or for 
+ *                                O_RDONLY access mode of the tape device
+ *  + zft_offline == 1         -- this meany that there is either no tape 
+ *                                or that the MTOFFLINE ioctl has been 
+ *                                previously issued (`soft eject')
+ *  + ft_formatted == 0        -- this means that the cartridge is not
+ *                                formatted
+ *  Then we distinuguish two cases. When zft_qic_mode is TRUE, then we try
+ *  to emulate a `traditional' (aka SCSI like) UN*X tape device. Therefore we
+ *  deny writes when
+ *  + zft_qic_mode ==1 && 
+ *       (!zft_tape_at_lbot() &&   -- tape no at logical BOT
+ *        !(zft_tape_at_eom() ||   -- tape not at logical EOM (or EOD)
+ *          (zft_tape_at_eom() &&
+ *           zft_old_ftape())))    -- we can't add new volume to tapes 
+ *                                    written by old ftape because ftape
+ *                                    don't use the volume table
+ *
+ *  when the drive is in true raw mode (aka /dev/rawft0) then we don't 
+ *  care about LBOT and EOM conditions. This device is intended for a 
+ *  user level program that wants to truly implement the QIC-80 compliance
+ *  at the logical data layout level of the cartridge, i.e. implement all
+ *  that volume table and volume directory stuff etc.<
+ */
+int zft_check_write_access(zft_position *pos)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (zft_offline) { /* offline includes no_tape */
+		TRACE_ABORT(-ENXIO,
+			    ft_t_info, "tape is offline or no cartridge");
+	}
+	if (!ft_formatted) {
+		TRACE_ABORT(-EACCES, ft_t_info, "tape is not formatted");
+	} 
+	if (zft_write_protected) {
+		TRACE_ABORT(-EACCES, ft_t_info, "cartridge write protected");
+	} 
+	if (zft_qic_mode) {
+		/*  check BOT condition */
+		if (!zft_tape_at_lbot(pos)) {
+			/*  protect cartridges written by old ftape if
+			 *  not at BOT because they use the vtbl
+			 *  segment for storing data
+			 */
+			if (zft_old_ftape) {
+				TRACE_ABORT(-EACCES, ft_t_warn, 
+      "Cannot write to cartridges written by old ftape when not at BOT");
+			}
+			/*  not at BOT, but allow writes at EOD, of course
+			 */
+			if (!zft_tape_at_eod(pos)) {
+				TRACE_ABORT(-EACCES, ft_t_info,
+					    "tape not at BOT and not at EOD");
+			}
+		}
+		/*  fine. Now the tape is either at BOT or at EOD. */
+	}
+	/* or in raw mode in which case we don't care about BOT and EOD */
+	TRACE_EXIT 0;
+}
+
+/*      OPEN routine called by kernel-interface code
+ *
+ *      NOTE: this is also called by mt_reset() with dev_minor == -1
+ *            to fake a reopen after a reset.
+ */
+int _zft_open(unsigned int dev_minor, unsigned int access_mode)
+{
+	static unsigned int tape_unit;
+	static unsigned int file_access_mode;
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	if ((int)dev_minor == -1) {
+		/* fake reopen */
+		zft_unit    = tape_unit;
+		access_mode = file_access_mode;
+		zft_init_driver(); /* reset all static data to defaults */
+	} else {
+		tape_unit        = dev_minor;
+		file_access_mode = access_mode;
+		if ((result = ftape_enable(FTAPE_SEL(dev_minor))) < 0) {
+			TRACE_ABORT(-ENXIO, ft_t_err,
+				    "ftape_enable failed: %d", result);
+		}
+		if (ft_new_tape || ft_no_tape || !ft_formatted ||
+		    (FTAPE_SEL(zft_unit) != FTAPE_SEL(dev_minor)) ||
+		    (zft_unit & ZFT_RAW_MODE) != (dev_minor & ZFT_RAW_MODE)) {
+			/* reset all static data to defaults,
+			 */
+			zft_init_driver(); 
+		}
+		zft_unit = dev_minor;
+	}
+	zft_set_flags(zft_unit); /* decode the minor bits */
+	if (zft_blk_sz == 1 && zft_use_compression) {
+		ftape_disable(); /* resets ft_no_tape */
+		TRACE_ABORT(-ENODEV, ft_t_warn, "Variable block size not yet "
+			    "supported with compression");
+	}
+	/*  no need for most of the buffers when no tape or not
+	 *  formatted.  for the read/write operations, it is the
+	 *  regardless whether there is no tape, a not-formatted tape
+	 *  or the whether the driver is soft offline.  
+	 *  Nevertheless we allow some ioctls with non-formatted tapes, 
+	 *  like rewind and reset.
+	 */
+	if (ft_no_tape || !ft_formatted) {
+		zft_uninit_mem();
+	}
+	if (ft_no_tape) {
+		zft_offline = 1; /* so we need not test two variables */
+	}
+	if ((access_mode == O_WRONLY || access_mode == O_RDWR) &&
+	    (ft_write_protected || ft_no_tape)) {
+		ftape_disable(); /* resets ft_no_tape */
+		TRACE_ABORT(ft_no_tape ? -ENXIO : -EROFS,
+			    ft_t_warn, "wrong access mode %s cartridge",
+			    ft_no_tape ? "without a" : "with write protected");
+	}
+	zft_write_protected = (access_mode == O_RDONLY || 
+			       ft_write_protected != 0);
+	if (zft_write_protected) {
+		TRACE(ft_t_noise,
+		      "read only access mode: %d, "
+		      "drive write protected: %d", 
+		      access_mode == O_RDONLY,
+		      ft_write_protected != 0);
+	}
+	if (!zft_offline) {
+		TRACE_CATCH(zft_vmalloc_once(&zft_deblock_buf,FT_SEGMENT_SIZE),
+			    ftape_disable());
+	}
+	/* zft_seg_pos should be greater than the vtbl segpos but not
+	 * if in compatibility mode and only after we read in the
+	 * header segments
+	 *
+	 * might also be a problem if the user makes a backup with a
+	 * *qft* device and rewinds it with a raw device.
+	 */
+	if (zft_qic_mode         &&
+	    !zft_old_ftape       &&
+	    zft_pos.seg_pos >= 0 &&
+	    zft_header_read      && 
+	    zft_pos.seg_pos <= ft_first_data_segment) {
+		TRACE(ft_t_noise, "you probably mixed up the zftape devices!");
+		zft_reset_position(&zft_pos); 
+	}
+	TRACE_EXIT 0;
+}
+
+/*      RELEASE routine called by kernel-interface code
+ */
+int _zft_close(void)
+{
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+	
+	if (zft_offline) {
+		/* call the hardware release routine. Puts the drive offline */
+		ftape_disable();
+		TRACE_EXIT 0;
+	}
+	if (!(ft_write_protected || zft_old_ftape)) {
+		result = zft_flush_buffers();
+		TRACE(ft_t_noise, "writing file mark at current position");
+		if (zft_qic_mode && zft_close_volume(&zft_pos) == 0) {
+			zft_move_past_eof(&zft_pos);
+		}
+		if ((zft_tape_at_lbot(&zft_pos) ||
+		     !(zft_unit & FTAPE_NO_REWIND))) {
+			if (result >= 0) {
+				result = zft_update_header_segments();
+			} else {
+				TRACE(ft_t_err,
+				"Error: unable to update header segments");
+			}
+		}
+	}
+	ftape_abort_operation();
+	if (!(zft_unit & FTAPE_NO_REWIND)) {
+		TRACE(ft_t_noise, "rewinding tape");
+		if (ftape_seek_to_bot() < 0 && result >= 0) {
+			result = -EIO; /* keep old value */
+		}
+		zft_reset_position(&zft_pos);
+	} 
+	zft_zap_read_buffers();
+	/*  now free up memory as much as possible. We don't destroy
+	 *  the deblock buffer if it containes a valid segment.
+	 */
+	if (zft_deblock_segment == -1) {
+		zft_vfree(&zft_deblock_buf, FT_SEGMENT_SIZE); 
+	}
+	/* high level driver status, forces creation of a new volume
+	 * when calling ftape_write again and not zft_just_before_eof
+	 */
+	zft_io_state = zft_idle;  
+	if (going_offline) {
+		zft_init_driver();
+		zft_uninit_mem();
+		going_offline = 0;
+		zft_offline   = 1;
+	} else if (zft_cmpr_lock(0 /* don't load */) == 0) {
+		(*zft_cmpr_ops->reset)(); /* unlock it again */
+	}
+	zft_memory_stats();
+	/* call the hardware release routine. Puts the drive offline */
+	ftape_disable();
+	TRACE_EXIT result;
+}
+
+/*
+ *  the wrapper function around the wrapper MTIOCTOP ioctl
+ */
+static int mtioctop(struct mtop *mtop, int arg_size)
+{
+	int result = 0;
+	fun_entry *mt_fun_entry;
+	TRACE_FUN(ft_t_flow);
+	
+	if (arg_size != sizeof(struct mtop) || mtop->mt_op >= NR_MT_CMDS) {
+		TRACE_EXIT -EINVAL;
+	}
+	TRACE(ft_t_noise, "calling MTIOCTOP command: %s",
+	      mt_funs[mtop->mt_op].name);
+	mt_fun_entry= &mt_funs[mtop->mt_op];
+	zft_resid = mtop->mt_count;
+	if (!mt_fun_entry->offline && zft_offline) {
+		if (ft_no_tape) {
+			TRACE_ABORT(-ENXIO, ft_t_info, "no tape present");
+		} else {
+			TRACE_ABORT(-ENXIO, ft_t_info, "drive is offline");
+		}
+	}
+	if (!mt_fun_entry->not_formatted && !ft_formatted) {
+		TRACE_ABORT(-EACCES, ft_t_info, "tape is not formatted");
+	}
+	if (!mt_fun_entry->write_protected) {
+		TRACE_CATCH(zft_check_write_access(&zft_pos),);
+	}
+	if (mt_fun_entry->need_idle_state && !(zft_offline || !ft_formatted)) {
+		TRACE_CATCH(zft_def_idle_state(),);
+	}
+	if (!zft_qic_mode && !mt_fun_entry->raw_mode) {
+		TRACE_ABORT(-EACCES, ft_t_info, 
+"Drive needs to be in QIC-80 compatibility mode for this command");
+	}
+	result = (mt_fun_entry->function)(&mtop->mt_count);
+	if (zft_tape_at_lbot(&zft_pos)) {
+		TRACE_CATCH(zft_update_header_segments(),);
+	}
+	if (result >= 0) {
+		zft_resid = 0;
+	}
+	TRACE_EXIT result;
+}
+
+/*
+ *  standard MTIOCGET ioctl
+ */
+static int mtiocget(struct mtget *mtget, int arg_size)
+{
+	const zft_volinfo *volume;
+	__s64 max_tape_pos;
+	TRACE_FUN(ft_t_flow);
+	
+	if (arg_size != sizeof(struct mtget)) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+			    arg_size);
+	}
+	mtget->mt_type  = ft_drive_type.vendor_id + 0x800000;
+	mtget->mt_dsreg = ft_last_status.space;
+	mtget->mt_erreg = ft_last_error.space; /* error register */
+	mtget->mt_resid = zft_resid; /* residuum of writes, reads and
+				      * MTIOCTOP commands 
+				      */
+	if (!zft_offline) { /* neither no_tape nor soft offline */
+		mtget->mt_gstat = GMT_ONLINE(~0UL);
+		/* should rather return the status of the cartridge
+		 * than the access mode of the file, therefor use
+		 * ft_write_protected, not zft_write_protected 
+		 */
+		if (ft_write_protected) {
+			mtget->mt_gstat |= GMT_WR_PROT(~0UL);
+		}
+		if(zft_header_read) { /* this catches non-formatted */
+			volume = zft_find_volume(zft_pos.seg_pos);
+			mtget->mt_fileno = volume->count;
+			max_tape_pos = zft_capacity - zft_blk_sz;
+			if (zft_use_compression) {
+				max_tape_pos -= ZFT_CMPR_OVERHEAD;
+			}
+			if (zft_tape_at_eod(&zft_pos)) {
+				mtget->mt_gstat |= GMT_EOD(~0UL);
+			}
+			if (zft_pos.tape_pos > max_tape_pos) {
+				mtget->mt_gstat |= GMT_EOT(~0UL);
+			}
+			mtget->mt_blkno = zft_div_blksz(zft_pos.volume_pos,
+							volume->blk_sz);
+			if (zft_just_before_eof) {
+				mtget->mt_gstat |= GMT_EOF(~0UL);
+			}
+			if (zft_tape_at_lbot(&zft_pos)) {
+				mtget->mt_gstat |= GMT_BOT(~0UL);
+			}
+		} else {
+			mtget->mt_fileno = mtget->mt_blkno = -1;
+			if (mtget->mt_dsreg & QIC_STATUS_AT_BOT) {
+				mtget->mt_gstat |= GMT_BOT(~0UL);
+			}
+		}
+	} else {
+		if (ft_no_tape) {
+			mtget->mt_gstat = GMT_DR_OPEN(~0UL);
+		} else {
+			mtget->mt_gstat = 0UL;
+		}
+ 		mtget->mt_fileno = mtget->mt_blkno = -1;
+	}
+	TRACE_EXIT 0;
+}
+
+#ifdef MTIOCRDFTSEG
+/*
+ *  Read a floppy tape segment. This is useful for manipulating the
+ *  volume table, and read the old header segment before re-formatting
+ *  the cartridge.
+ */
+static int mtiocrdftseg(struct mtftseg * mtftseg, int arg_size)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCRDFTSEG");
+	if (zft_qic_mode) {
+		TRACE_ABORT(-EACCES, ft_t_info,
+			    "driver needs to be in raw mode for this ioctl");
+	} 
+	if (arg_size != sizeof(struct mtftseg)) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+			    arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (mtftseg->mt_mode != FT_RD_SINGLE &&
+	    mtftseg->mt_mode != FT_RD_AHEAD) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "invalid read mode");
+	}
+	if (!ft_formatted) {
+		TRACE_EXIT -EACCES; /* -ENXIO ? */
+
+	}
+	if (!zft_header_read) {
+		TRACE_CATCH(zft_def_idle_state(),);
+	}
+	if (mtftseg->mt_segno > ft_last_data_segment) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "segment number is too large");
+	}
+	mtftseg->mt_result = ftape_read_segment(mtftseg->mt_segno,
+						zft_deblock_buf,
+						mtftseg->mt_mode);
+	if (mtftseg->mt_result < 0) {
+		/*  a negativ result is not an ioctl error. if
+		 *  the user wants to read damaged tapes,
+		 *  it's up to her/him
+		 */
+		TRACE_EXIT 0;
+	}
+	if (copy_to_user(mtftseg->mt_data,
+			 zft_deblock_buf,
+			 mtftseg->mt_result) != 0) {
+		TRACE_EXIT -EFAULT;
+	}
+	TRACE_EXIT 0;
+}
+#endif
+
+#ifdef MTIOCWRFTSEG
+/*
+ *  write a floppy tape segment. This version features writing of
+ *  deleted address marks, and gracefully ignores the (software)
+ *  ft_formatted flag to support writing of header segments after
+ *  formatting.
+ */
+static int mtiocwrftseg(struct mtftseg * mtftseg, int arg_size)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCWRFTSEG");
+	if (zft_write_protected || zft_qic_mode) {
+		TRACE_EXIT -EACCES;
+	} 
+	if (arg_size != sizeof(struct mtftseg)) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "bad argument size: %d",
+			    arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (mtftseg->mt_mode != FT_WR_ASYNC   && 
+	    mtftseg->mt_mode != FT_WR_MULTI   &&
+	    mtftseg->mt_mode != FT_WR_SINGLE  &&
+	    mtftseg->mt_mode != FT_WR_DELETE) {
+		TRACE_ABORT(-EINVAL, ft_t_info, "invalid write mode");
+	}
+	/*
+	 *  We don't check for ft_formatted, because this gives
+	 *  only the software status of the driver.
+	 *
+	 *  We assume that the user knows what it is
+	 *  doing. And rely on the low level stuff to fail
+	 *  when the tape isn't formatted. We only make sure
+	 *  that The header segment buffer is allocated,
+	 *  because it holds the bad sector map.
+	 */
+	if (zft_hseg_buf == NULL) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (mtftseg->mt_mode != FT_WR_DELETE) {
+		if (copy_from_user(zft_deblock_buf, 
+				   mtftseg->mt_data,
+				   FT_SEGMENT_SIZE) != 0) {
+			TRACE_EXIT -EFAULT;
+		}
+	}
+	mtftseg->mt_result = ftape_write_segment(mtftseg->mt_segno, 
+						 zft_deblock_buf,
+						 mtftseg->mt_mode);
+	if (mtftseg->mt_result >= 0 && mtftseg->mt_mode == FT_WR_SINGLE) {
+		/*  
+		 *  a negativ result is not an ioctl error. if
+		 *  the user wants to write damaged tapes,
+		 *  it's up to her/him
+		 */
+		if ((result = ftape_loop_until_writes_done()) < 0) {
+			mtftseg->mt_result = result;
+		}
+	}
+	TRACE_EXIT 0;
+}
+#endif
+  
+#ifdef MTIOCVOLINFO
+/*
+ *  get information about volume positioned at.
+ */
+static int mtiocvolinfo(struct mtvolinfo *volinfo, int arg_size)
+{
+	const zft_volinfo *volume;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCVOLINFO");
+	if (arg_size != sizeof(struct mtvolinfo)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (!ft_formatted) {
+		TRACE_EXIT -EACCES;
+	}
+	TRACE_CATCH(zft_def_idle_state(),);
+	volume = zft_find_volume(zft_pos.seg_pos);
+	volinfo->mt_volno   = volume->count;
+	volinfo->mt_blksz   = volume->blk_sz == 1 ? 0 : volume->blk_sz;
+	volinfo->mt_size    = volume->size >> 10;
+	volinfo->mt_rawsize = ((zft_calc_tape_pos(volume->end_seg + 1) >> 10) -
+			       (zft_calc_tape_pos(volume->start_seg) >> 10));
+	volinfo->mt_cmpr    = volume->use_compression;
+	TRACE_EXIT 0;
+}
+#endif
+
+#ifdef ZFT_OBSOLETE  
+static int mtioc_zftape_getblksz(struct mtblksz *blksz, int arg_size)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "\n"
+	      KERN_INFO "Mag tape ioctl command: MTIOC_ZTAPE_GETBLKSZ\n"
+	      KERN_INFO "This ioctl is here merely for compatibility.\n"
+	      KERN_INFO "Please use MTIOCVOLINFO instead");
+	if (arg_size != sizeof(struct mtblksz)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (!ft_formatted) {
+		TRACE_EXIT -EACCES;
+	}
+	TRACE_CATCH(zft_def_idle_state(),);
+	blksz->mt_blksz = zft_find_volume(zft_pos.seg_pos)->blk_sz;
+	TRACE_EXIT 0;
+}
+#endif
+
+#ifdef MTIOCGETSIZE
+/*
+ *  get the capacity of the tape cartridge.
+ */
+static int mtiocgetsize(struct mttapesize *size, int arg_size)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOC_ZFTAPE_GETSIZE");
+	if (arg_size != sizeof(struct mttapesize)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (zft_offline) {
+		TRACE_EXIT -ENXIO;
+	}
+	if (!ft_formatted) {
+		TRACE_EXIT -EACCES;
+	}
+	TRACE_CATCH(zft_def_idle_state(),);
+	size->mt_capacity = (unsigned int)(zft_capacity>>10);
+	size->mt_used     = (unsigned int)(zft_get_eom_pos()>>10);
+	TRACE_EXIT 0;
+}
+#endif
+
+static int mtiocpos(struct mtpos *mtpos, int arg_size)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCPOS");
+	if (arg_size != sizeof(struct mtpos)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	result = mt_tell((int *)&mtpos->mt_blkno);
+	TRACE_EXIT result;
+}
+
+#ifdef MTIOCFTFORMAT
+/*
+ * formatting of floppy tape cartridges. This is intended to be used
+ * together with the MTIOCFTCMD ioctl and the new mmap feature 
+ */
+
+/* 
+ *  This function uses ftape_decode_header_segment() to inform the low
+ *  level ftape module about the new parameters.
+ *
+ *  It erases the hseg_buf. The calling process must specify all
+ *  parameters to assure proper operation.
+ *
+ *  return values: -EINVAL - wrong argument size
+ *                 -EINVAL - if ftape_decode_header_segment() failed.
+ */
+static int set_format_parms(struct ftfmtparms *p, __u8 *hseg_buf)
+{
+	ft_trace_t old_level = TRACE_LEVEL;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "MTIOCFTFORMAT operation FTFMT_SETPARMS");
+	memset(hseg_buf, 0, FT_SEGMENT_SIZE);
+	PUT4(hseg_buf, FT_SIGNATURE, FT_HSEG_MAGIC);
+
+	/*  fill in user specified parameters
+	 */
+	hseg_buf[FT_FMT_CODE] = (__u8)p->ft_fmtcode;
+	PUT2(hseg_buf, FT_SPT, p->ft_spt);
+	hseg_buf[FT_TPC]      = (__u8)p->ft_tpc;
+	hseg_buf[FT_FHM]      = (__u8)p->ft_fhm;
+	hseg_buf[FT_FTM]      = (__u8)p->ft_ftm;
+
+	/*  fill in sane defaults to make ftape happy.
+	 */ 
+	hseg_buf[FT_FSM] = (__u8)128; /* 128 is hard wired all over ftape */
+	if (p->ft_fmtcode == fmt_big) {
+		PUT4(hseg_buf, FT_6_HSEG_1,   0);
+		PUT4(hseg_buf, FT_6_HSEG_2,   1);
+		PUT4(hseg_buf, FT_6_FRST_SEG, 2);
+		PUT4(hseg_buf, FT_6_LAST_SEG, p->ft_spt * p->ft_tpc - 1);
+	} else {
+		PUT2(hseg_buf, FT_HSEG_1,    0);
+		PUT2(hseg_buf, FT_HSEG_2,    1);
+		PUT2(hseg_buf, FT_FRST_SEG,  2);
+		PUT2(hseg_buf, FT_LAST_SEG, p->ft_spt * p->ft_tpc - 1);
+	}
+
+	/*  Synchronize with the low level module. This is particularly
+	 *  needed for unformatted cartridges as the QIC std was previously 
+	 *  unknown BUT is needed to set data rate and to calculate timeouts.
+	 */
+	TRACE_CATCH(ftape_calibrate_data_rate(p->ft_qicstd&QIC_TAPE_STD_MASK),
+		    _res = -EINVAL);
+
+	/*  The following will also recalcualte the timeouts for the tape
+	 *  length and QIC std we want to format to.
+	 *  abort with -EINVAL rather than -EIO
+	 */
+	SET_TRACE_LEVEL(ft_t_warn);
+	TRACE_CATCH(ftape_decode_header_segment(hseg_buf),
+		    SET_TRACE_LEVEL(old_level); _res = -EINVAL);
+	SET_TRACE_LEVEL(old_level);
+	TRACE_EXIT 0;
+}
+
+/*
+ *  Return the internal SOFTWARE status of the kernel driver. This does
+ *  NOT query the tape drive about its status.
+ */
+static int get_format_parms(struct ftfmtparms *p, __u8 *hseg_buffer)
+{
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "MTIOCFTFORMAT operation FTFMT_GETPARMS");
+	p->ft_qicstd  = ft_qic_std;
+	p->ft_fmtcode = ft_format_code;
+	p->ft_fhm     = hseg_buffer[FT_FHM];
+	p->ft_ftm     = hseg_buffer[FT_FTM];
+	p->ft_spt     = ft_segments_per_track;
+	p->ft_tpc     = ft_tracks_per_tape;
+	TRACE_EXIT 0;
+}
+
+static int mtiocftformat(struct mtftformat *mtftformat, int arg_size)
+{
+	int result;
+	union fmt_arg *arg = &mtftformat->fmt_arg;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCFTFORMAT");
+	if (zft_offline) {
+		if (ft_no_tape) {
+			TRACE_ABORT(-ENXIO, ft_t_info, "no tape present");
+		} else {
+			TRACE_ABORT(-ENXIO, ft_t_info, "drive is offline");
+		}
+	}
+	if (zft_qic_mode) {
+		TRACE_ABORT(-EACCES, ft_t_info,
+			    "driver needs to be in raw mode for this ioctl");
+	} 
+	if (zft_hseg_buf == NULL) {
+		TRACE_CATCH(zft_vcalloc_once(&zft_hseg_buf, FT_SEGMENT_SIZE),);
+	}
+	zft_header_read = 0;
+	switch(mtftformat->fmt_op) {
+	case FTFMT_SET_PARMS:
+		TRACE_CATCH(set_format_parms(&arg->fmt_parms, zft_hseg_buf),);
+		TRACE_EXIT 0;
+	case FTFMT_GET_PARMS:
+		TRACE_CATCH(get_format_parms(&arg->fmt_parms, zft_hseg_buf),);
+		TRACE_EXIT 0;
+	case FTFMT_FORMAT_TRACK:
+		if ((ft_formatted && zft_check_write_access(&zft_pos) < 0) ||
+		    (!ft_formatted && zft_write_protected)) {
+			TRACE_ABORT(-EACCES, ft_t_info, "Write access denied");
+		}
+		TRACE_CATCH(ftape_format_track(arg->fmt_track.ft_track,
+					       arg->fmt_track.ft_gap3),);
+		TRACE_EXIT 0;
+	case FTFMT_STATUS:
+		TRACE_CATCH(ftape_format_status(&arg->fmt_status.ft_segment),);
+		TRACE_EXIT 0;
+	case FTFMT_VERIFY:
+		TRACE_CATCH(ftape_verify_segment(arg->fmt_verify.ft_segment,
+				(SectorMap *)&arg->fmt_verify.ft_bsm),);
+		TRACE_EXIT 0;
+	default:
+		TRACE_ABORT(-EINVAL, ft_t_err, "Invalid format operation");
+	}
+	TRACE_EXIT result;
+}
+#endif
+
+#ifdef MTIOCFTCMD
+/*
+ *  send a QIC-117 command to the drive, with optional timeouts,
+ *  parameter and result bits. This is intended to be used together
+ *  with the formatting ioctl.
+ */
+static int mtiocftcmd(struct mtftcmd *ftcmd, int arg_size)
+{
+	int i;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "Mag tape ioctl command: MTIOCFTCMD");
+	if (!capable(CAP_SYS_ADMIN)) {
+		TRACE_ABORT(-EPERM, ft_t_info,
+			    "need CAP_SYS_ADMIN capability to send raw qic-117 commands");
+	}
+	if (zft_qic_mode) {
+		TRACE_ABORT(-EACCES, ft_t_info,
+			    "driver needs to be in raw mode for this ioctl");
+	} 
+	if (arg_size != sizeof(struct mtftcmd)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (ftcmd->ft_wait_before) {
+		TRACE_CATCH(ftape_ready_wait(ftcmd->ft_wait_before,
+					     &ftcmd->ft_status),);
+	}
+	if (ftcmd->ft_status & QIC_STATUS_ERROR)
+		goto ftmtcmd_error;
+	if (ftcmd->ft_result_bits != 0) {
+		TRACE_CATCH(ftape_report_operation(&ftcmd->ft_result,
+						   ftcmd->ft_cmd,
+						   ftcmd->ft_result_bits),);
+	} else {
+		TRACE_CATCH(ftape_command(ftcmd->ft_cmd),);
+		if (ftcmd->ft_status & QIC_STATUS_ERROR)
+			goto ftmtcmd_error;
+		for (i = 0; i < ftcmd->ft_parm_cnt; i++) {
+			TRACE_CATCH(ftape_parameter(ftcmd->ft_parms[i]&0x0f),);
+			if (ftcmd->ft_status & QIC_STATUS_ERROR)
+				goto ftmtcmd_error;
+		}
+	}
+	if (ftcmd->ft_wait_after != 0) {
+		TRACE_CATCH(ftape_ready_wait(ftcmd->ft_wait_after,
+					     &ftcmd->ft_status),);
+	}
+ftmtcmd_error:	       
+	if (ftcmd->ft_status & QIC_STATUS_ERROR) {
+		TRACE(ft_t_noise, "error status set");
+		TRACE_CATCH(ftape_report_error(&ftcmd->ft_error,
+					       &ftcmd->ft_cmd, 1),);
+	}
+	TRACE_EXIT 0; /* this is not an i/o error */
+}
+#endif
+
+/*  IOCTL routine called by kernel-interface code
+ */
+int _zft_ioctl(unsigned int command, void __user * arg)
+{
+	int result;
+	union { struct mtop       mtop;
+		struct mtget      mtget;
+		struct mtpos      mtpos;
+#ifdef MTIOCRDFTSEG
+		struct mtftseg    mtftseg;
+#endif
+#ifdef MTIOCVOLINFO
+		struct mtvolinfo  mtvolinfo;
+#endif
+#ifdef MTIOCGETSIZE
+		struct mttapesize mttapesize;
+#endif
+#ifdef MTIOCFTFORMAT
+		struct mtftformat mtftformat;
+#endif
+#ifdef ZFT_OBSOLETE
+		struct mtblksz mtblksz;
+#endif
+#ifdef MTIOCFTCMD
+		struct mtftcmd mtftcmd;
+#endif
+	} krnl_arg;
+	int arg_size = _IOC_SIZE(command);
+	int dir = _IOC_DIR(command);
+	TRACE_FUN(ft_t_flow);
+
+	/* This check will only catch arguments that are too large !
+	 */
+	if (dir & (_IOC_READ | _IOC_WRITE) && arg_size > sizeof(krnl_arg)) {
+		TRACE_ABORT(-EINVAL,
+			    ft_t_info, "bad argument size: %d", arg_size);
+	}
+	if (dir & _IOC_WRITE) {
+		if (copy_from_user(&krnl_arg, arg, arg_size) != 0) {
+			TRACE_EXIT -EFAULT;
+		}
+	}
+	TRACE(ft_t_flow, "called with ioctl command: 0x%08x", command);
+	switch (command) {
+	case MTIOCTOP:
+		result = mtioctop(&krnl_arg.mtop, arg_size);
+		break;
+	case MTIOCGET:
+		result = mtiocget(&krnl_arg.mtget, arg_size);
+		break;
+	case MTIOCPOS:
+		result = mtiocpos(&krnl_arg.mtpos, arg_size);
+		break;
+#ifdef MTIOCVOLINFO
+	case MTIOCVOLINFO:
+		result = mtiocvolinfo(&krnl_arg.mtvolinfo, arg_size);
+		break;
+#endif
+#ifdef ZFT_OBSOLETE
+	case MTIOC_ZFTAPE_GETBLKSZ:
+		result = mtioc_zftape_getblksz(&krnl_arg.mtblksz, arg_size);
+		break;
+#endif
+#ifdef MTIOCRDFTSEG
+	case MTIOCRDFTSEG: /* read a segment via ioctl */
+		result = mtiocrdftseg(&krnl_arg.mtftseg, arg_size);
+		break;
+#endif
+#ifdef MTIOCWRFTSEG
+	case MTIOCWRFTSEG: /* write a segment via ioctl */
+		result = mtiocwrftseg(&krnl_arg.mtftseg, arg_size);
+		break;
+#endif
+#ifdef MTIOCGETSIZE
+	case MTIOCGETSIZE:
+		result = mtiocgetsize(&krnl_arg.mttapesize, arg_size);
+		break;
+#endif
+#ifdef MTIOCFTFORMAT
+	case MTIOCFTFORMAT:
+		result = mtiocftformat(&krnl_arg.mtftformat, arg_size);
+		break;
+#endif
+#ifdef MTIOCFTCMD
+	case MTIOCFTCMD:
+		result = mtiocftcmd(&krnl_arg.mtftcmd, arg_size);
+		break;
+#endif
+	default:
+		result = -EINVAL;
+		break;
+	}
+	if ((result >= 0) && (dir & _IOC_READ)) {
+		if (copy_to_user(arg, &krnl_arg, arg_size) != 0) {
+			TRACE_EXIT -EFAULT;
+		}
+	}
+	TRACE_EXIT result;
+}
diff --git a/drivers/char/ftape/zftape/zftape-ctl.h b/drivers/char/ftape/zftape/zftape-ctl.h
new file mode 100644
index 0000000..4141598
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-ctl.h
@@ -0,0 +1,59 @@
+#ifndef _ZFTAPE_CTL_H
+#define _ZFTAPE_CTL_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-ctl.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:02 $
+ *
+ *      This file contains the non-standard IOCTL related definitions
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/config.h>
+#include <linux/ioctl.h>
+#include <linux/mtio.h>
+
+#include "../zftape/zftape-rw.h"
+
+#ifdef CONFIG_ZFTAPE_MODULE
+#define ftape_status (*zft_status)
+#endif
+
+extern int zft_offline;
+extern int zft_mt_compression;
+extern int zft_write_protected;
+extern int zft_header_read;
+extern unsigned int zft_unit;
+extern int zft_resid;
+
+extern void zft_reset_position(zft_position *pos);
+extern int  zft_check_write_access(zft_position *pos);
+extern int  zft_def_idle_state(void);
+
+/*  hooks for the VFS interface 
+ */
+extern int  _zft_open(unsigned int dev_minor, unsigned int access_mode);
+extern int  _zft_close(void);
+extern int  _zft_ioctl(unsigned int command, void __user *arg);
+#endif
+
+
+
diff --git a/drivers/char/ftape/zftape/zftape-eof.c b/drivers/char/ftape/zftape/zftape-eof.c
new file mode 100644
index 0000000..dcadcae
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-eof.c
@@ -0,0 +1,199 @@
+/*
+ *   I use these routines just to decide when I have to fake a 
+ *   volume-table to preserve compatibility to original ftape.
+ */
+/*
+ *      Copyright (C) 1994-1995 Bas Laarhoven.
+ *      
+ *      Modified for zftape 1996, 1997 Claus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-eof.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:02 $
+ *
+ *      This file contains the eof mark handling code
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include <linux/zftape.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-eof.h"
+
+/*      Global vars.
+ */
+
+/* a copy of the failed sector log from the header segment.
+ */
+eof_mark_union *zft_eof_map;
+
+/* number of eof marks (entries in bad sector log) on tape.
+ */
+int zft_nr_eof_marks = -1;
+
+
+/*      Local vars.
+ */
+
+static char linux_tape_label[] = "Linux raw format V";
+enum { 
+	min_fmt_version = 1, max_fmt_version = 2 
+};
+static unsigned ftape_fmt_version = 0;
+
+
+/* Ftape (mis)uses the bad sector log to record end-of-file marks.
+ * Initially (when the tape is erased) all entries in the bad sector
+ * log are added to the tape's bad sector map. The bad sector log then
+ * is cleared.
+ *
+ * The bad sector log normally contains entries of the form: 
+ * even 16-bit word: segment number of bad sector 
+ * odd 16-bit word: encoded date
+ * There can be a total of 448 entries (1792 bytes).
+ *
+ * My guess is that no program is using this bad sector log (the *
+ * format seems useless as there is no indication of the bad sector
+ * itself, only the segment) However, if any program does use the bad
+ * sector log, the format used by ftape will let the program think
+ * there are some bad sectors and no harm is done.
+ *  
+ * The eof mark entries that ftape stores in the bad sector log: even
+ * 16-bit word: segment number of eof mark odd 16-bit word: sector
+ * number of eof mark [1..32]
+ *  
+ * The zft_eof_map as maintained is a sorted list of eof mark entries.
+ *
+ *
+ * The tape name field in the header segments is used to store a linux
+ * tape identification string and a version number.  This way the tape
+ * can be recognized as a Linux raw format tape when using tools under
+ * other OS's.
+ *
+ * 'Wide' QIC tapes (format code 4) don't have a failed sector list
+ * anymore. That space is used for the (longer) bad sector map that
+ * now is a variable length list too.  We now store our end-of-file
+ * marker list after the bad-sector-map on tape. The list is delimited
+ * by a (__u32) 0 entry.
+ */
+
+int zft_ftape_validate_label(char *label)
+{
+	static char tmp_label[45];
+	int result = 0;
+	TRACE_FUN(ft_t_any);
+	
+	memcpy(tmp_label, label, FT_LABEL_SZ);
+	tmp_label[FT_LABEL_SZ] = '\0';
+	TRACE(ft_t_noise, "tape  label = `%s'", tmp_label);
+	ftape_fmt_version = 0;
+	if (memcmp(label, linux_tape_label, strlen(linux_tape_label)) == 0) {
+		int pos = strlen(linux_tape_label);
+		while (label[pos] >= '0' && label[pos] <= '9') {
+			ftape_fmt_version *= 10;
+			ftape_fmt_version = label[ pos++] - '0';
+		}
+		result = (ftape_fmt_version >= min_fmt_version &&
+			  ftape_fmt_version <= max_fmt_version);
+	}
+	TRACE(ft_t_noise, "format version = %d", ftape_fmt_version);
+	TRACE_EXIT result;
+}
+
+static __u8 * find_end_of_eof_list(__u8 * ptr, __u8 * limit)
+{
+	while (ptr + 3 < limit) {
+
+		if (get_unaligned((__u32*)ptr)) {
+			ptr += sizeof(__u32);
+		} else {
+			return ptr;
+		}
+	}
+	return NULL;
+}
+
+void zft_ftape_extract_file_marks(__u8* address)
+{
+	int i;
+	TRACE_FUN(ft_t_any);
+	
+	zft_eof_map = NULL;
+	if (ft_format_code == fmt_var || ft_format_code == fmt_big) {
+		__u8* end;
+		__u8* start = ftape_find_end_of_bsm_list(address);
+
+		zft_nr_eof_marks = 0;
+		if (start) {
+			start += 3; /* skip end of list mark */
+			end = find_end_of_eof_list(start, 
+						   address + FT_SEGMENT_SIZE);
+			if (end && end - start <= FT_FSL_SIZE) {
+				zft_nr_eof_marks = ((end - start) / 
+						    sizeof(eof_mark_union));
+				zft_eof_map = (eof_mark_union *)start;
+			} else {
+				TRACE(ft_t_err,
+				      "EOF Mark List is too long or damaged!");
+			}
+		} else {
+			TRACE(ft_t_err, 
+			      "Bad Sector List is too long or damaged !");
+		}
+	} else {
+		zft_eof_map = (eof_mark_union *)&address[FT_FSL];
+		zft_nr_eof_marks = GET2(address, FT_FSL_CNT);
+	}
+	TRACE(ft_t_noise, "number of file marks: %d", zft_nr_eof_marks);
+	if (ftape_fmt_version == 1) {
+		TRACE(ft_t_info, "swapping version 1 fields");
+		/* version 1 format uses swapped sector and segment
+		 * fields, correct that !  
+		 */
+		for (i = 0; i < zft_nr_eof_marks; ++i) {
+			__u16 tmp = GET2(&zft_eof_map[i].mark.segment,0);
+			PUT2(&zft_eof_map[i].mark.segment, 0, 
+			     GET2(&zft_eof_map[i].mark.date,0));
+			PUT2(&zft_eof_map[i].mark.date, 0, tmp);
+		}
+	}
+	for (i = 0; i < zft_nr_eof_marks; ++i) {
+		TRACE(ft_t_noise, "eof mark: %5d/%2d",
+			GET2(&zft_eof_map[i].mark.segment, 0), 
+			GET2(&zft_eof_map[i].mark.date,0));
+	}
+	TRACE_EXIT;
+}
+
+void zft_clear_ftape_file_marks(void)
+{
+	TRACE_FUN(ft_t_flow);
+	/*  Clear failed sector log: remove all tape marks. We
+	 *  don't use old ftape-style EOF-marks.
+	 */
+	TRACE(ft_t_info, "Clearing old ftape's eof map");
+	memset(zft_eof_map, 0, zft_nr_eof_marks * sizeof(__u32));
+	zft_nr_eof_marks = 0;
+	PUT2(zft_hseg_buf, FT_FSL_CNT, 0); /* nr of eof-marks */
+	zft_header_changed = 1;
+	zft_update_label(zft_hseg_buf);
+	TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/zftape/zftape-eof.h b/drivers/char/ftape/zftape/zftape-eof.h
new file mode 100644
index 0000000..26568c2
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-eof.h
@@ -0,0 +1,52 @@
+#ifndef _ZFTAPE_EOF_H
+#define _ZFTAPE_EOF_H
+
+/*
+ * Copyright (C) 1994-1995 Bas Laarhoven.
+ * adaptaed for zftape 1996, 1997 by Claus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-eof.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:03 $
+ *
+ *      Definitions and declarations for the end of file markers
+ *      for the QIC-40/80 floppy-tape driver for Linux.
+ */
+
+#include <linux/ftape-header-segment.h>
+#include "../zftape/zftape-buffers.h"
+/*  failed sector log size (only used if format code != 4).
+ */
+
+typedef union {
+	ft_fsl_entry mark;
+	__u32 entry;
+} eof_mark_union;
+ 
+/*      ftape-eof.c defined global vars.
+ */
+extern int zft_nr_eof_marks;
+extern eof_mark_union *zft_eof_map;
+
+/*      ftape-eof.c defined global functions.
+ */
+extern void zft_ftape_extract_file_marks(__u8* address);
+extern int  zft_ftape_validate_label(char* label);
+extern void zft_clear_ftape_file_marks(void);
+
+#endif
diff --git a/drivers/char/ftape/zftape/zftape-init.c b/drivers/char/ftape/zftape/zftape-init.c
new file mode 100644
index 0000000..dbac7e5
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-init.c
@@ -0,0 +1,403 @@
+/*
+ *      Copyright (C) 1996, 1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ *      This file contains the code that registers the zftape frontend 
+ *      to the ftape floppy tape driver for Linux
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#ifdef CONFIG_KMOD
+#include <linux/kmod.h>
+#endif
+#include <linux/fcntl.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <linux/zftape.h>
+#include <linux/init.h>
+#include <linux/device.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-buffers.h"
+
+MODULE_AUTHOR("(c) 1996, 1997 Claus-Justus Heine "
+	      "(claus@momo.math.rwth-aachen.de)");
+MODULE_DESCRIPTION(ZFTAPE_VERSION " - "
+		   "VFS interface for the Linux floppy tape driver. "
+		   "Support for QIC-113 compatible volume table "
+		   "and builtin compression (lzrw3 algorithm)");
+MODULE_SUPPORTED_DEVICE("char-major-27");
+MODULE_LICENSE("GPL");
+
+/*      Global vars.
+ */
+struct zft_cmpr_ops *zft_cmpr_ops = NULL;
+const ftape_info *zft_status;
+
+/*      Local vars.
+ */
+static unsigned long busy_flag;
+
+static sigset_t orig_sigmask;
+
+/*  the interface to the kernel vfs layer
+ */
+
+/* Note about llseek():
+ *
+ * st.c and tpqic.c update fp->f_pos but don't implment llseek() and
+ * initialize the llseek component of the file_ops struct with NULL.
+ * This means that the user will get the default seek, but the tape
+ * device will not respect the new position, but happily read from the
+ * old position. Think a zftape specific llseek() function would be
+ * better, returning -ESPIPE. TODO.
+ */
+
+static int  zft_open (struct inode *ino, struct file *filep);
+static int zft_close(struct inode *ino, struct file *filep);
+static int  zft_ioctl(struct inode *ino, struct file *filep,
+		      unsigned int command, unsigned long arg);
+static int  zft_mmap(struct file *filep, struct vm_area_struct *vma);
+static ssize_t zft_read (struct file *fp, char __user *buff,
+			 size_t req_len, loff_t *ppos);
+static ssize_t zft_write(struct file *fp, const char __user *buff,
+			 size_t req_len, loff_t *ppos);
+
+static struct file_operations zft_cdev =
+{
+	.owner		= THIS_MODULE,
+	.read		= zft_read,
+	.write		= zft_write,
+	.ioctl		= zft_ioctl,
+	.mmap		= zft_mmap,
+	.open		= zft_open,
+	.release	= zft_close,
+};
+
+static struct class_simple *zft_class;
+
+/*      Open floppy tape device
+ */
+static int zft_open(struct inode *ino, struct file *filep)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	nonseekable_open(ino, filep);
+	TRACE(ft_t_flow, "called for minor %d", iminor(ino));
+	if ( test_and_set_bit(0,&busy_flag) ) {
+		TRACE_ABORT(-EBUSY, ft_t_warn, "failed: already busy");
+	}
+	if ((iminor(ino) & ~(ZFT_MINOR_OP_MASK | FTAPE_NO_REWIND))
+	     > 
+	    FTAPE_SEL_D) {
+		clear_bit(0,&busy_flag);
+		TRACE_ABORT(-ENXIO, ft_t_err, "failed: invalid unit nr");
+	}
+	orig_sigmask = current->blocked;
+	sigfillset(&current->blocked);
+	result = _zft_open(iminor(ino), filep->f_flags & O_ACCMODE);
+	if (result < 0) {
+		current->blocked = orig_sigmask; /* restore mask */
+		clear_bit(0,&busy_flag);
+		TRACE_ABORT(result, ft_t_err, "_ftape_open failed");
+	} else {
+		/* Mask signals that will disturb proper operation of the
+		 * program that is calling.
+		 */
+		current->blocked = orig_sigmask;
+		sigaddsetmask (&current->blocked, _DO_BLOCK);
+		TRACE_EXIT 0;
+	}
+}
+
+/*      Close floppy tape device
+ */
+static int zft_close(struct inode *ino, struct file *filep)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	if ( !test_bit(0,&busy_flag) || iminor(ino) != zft_unit) {
+		TRACE(ft_t_err, "failed: not busy or wrong unit");
+		TRACE_EXIT 0;
+	}
+	sigfillset(&current->blocked);
+	result = _zft_close();
+	if (result < 0) {
+		TRACE(ft_t_err, "_zft_close failed");
+	}
+	current->blocked = orig_sigmask; /* restore before open state */
+	clear_bit(0,&busy_flag);
+	TRACE_EXIT 0;
+}
+
+/*      Ioctl for floppy tape device
+ */
+static int zft_ioctl(struct inode *ino, struct file *filep,
+		     unsigned int command, unsigned long arg)
+{
+	int result = -EIO;
+	sigset_t old_sigmask;
+	TRACE_FUN(ft_t_flow);
+
+	if ( !test_bit(0,&busy_flag) || iminor(ino) != zft_unit || ft_failure) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "failed: not busy, failure or wrong unit");
+	}
+	old_sigmask = current->blocked; /* save mask */
+	sigfillset(&current->blocked);
+	/* This will work as long as sizeof(void *) == sizeof(long) */
+	result = _zft_ioctl(command, (void __user *) arg);
+	current->blocked = old_sigmask; /* restore mask */
+	TRACE_EXIT result;
+}
+
+/*      Ioctl for floppy tape device
+ */
+static int  zft_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+	int result = -EIO;
+	sigset_t old_sigmask;
+	TRACE_FUN(ft_t_flow);
+
+	if ( !test_bit(0,&busy_flag) || 
+	    iminor(filep->f_dentry->d_inode) != zft_unit || 
+	    ft_failure)
+	{
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "failed: not busy, failure or wrong unit");
+	}
+	old_sigmask = current->blocked; /* save mask */
+	sigfillset(&current->blocked);
+	if ((result = ftape_mmap(vma)) >= 0) {
+#ifndef MSYNC_BUG_WAS_FIXED
+		static struct vm_operations_struct dummy = { NULL, };
+		vma->vm_ops = &dummy;
+#endif
+	}
+	current->blocked = old_sigmask; /* restore mask */
+	TRACE_EXIT result;
+}
+
+/*      Read from floppy tape device
+ */
+static ssize_t zft_read(struct file *fp, char __user *buff,
+			size_t req_len, loff_t *ppos)
+{
+	int result = -EIO;
+	sigset_t old_sigmask;
+	struct inode *ino = fp->f_dentry->d_inode;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_data_flow, "called with count: %ld", (unsigned long)req_len);
+	if (!test_bit(0,&busy_flag)  || iminor(ino) != zft_unit || ft_failure) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "failed: not busy, failure or wrong unit");
+	}
+	old_sigmask = current->blocked; /* save mask */
+	sigfillset(&current->blocked);
+	result = _zft_read(buff, req_len);
+	current->blocked = old_sigmask; /* restore mask */
+	TRACE(ft_t_data_flow, "return with count: %d", result);
+	TRACE_EXIT result;
+}
+
+/*      Write to tape device
+ */
+static ssize_t zft_write(struct file *fp, const char __user *buff,
+			 size_t req_len, loff_t *ppos)
+{
+	int result = -EIO;
+	sigset_t old_sigmask;
+	struct inode *ino = fp->f_dentry->d_inode;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_flow, "called with count: %ld", (unsigned long)req_len);
+	if (!test_bit(0,&busy_flag) || iminor(ino) != zft_unit || ft_failure) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "failed: not busy, failure or wrong unit");
+	}
+	old_sigmask = current->blocked; /* save mask */
+	sigfillset(&current->blocked);
+	result = _zft_write(buff, req_len);
+	current->blocked = old_sigmask; /* restore mask */
+	TRACE(ft_t_data_flow, "return with count: %d", result);
+	TRACE_EXIT result;
+}
+
+/*                    END OF VFS INTERFACE 
+ *          
+ *****************************************************************************/
+
+/*  driver/module initialization
+ */
+
+/*  the compression module has to call this function to hook into the zftape 
+ *  code
+ */
+int zft_cmpr_register(struct zft_cmpr_ops *new_ops)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	if (zft_cmpr_ops != NULL) {
+		TRACE_EXIT -EBUSY;
+	} else {
+		zft_cmpr_ops = new_ops;
+		TRACE_EXIT 0;
+	}
+}
+
+/*  lock the zft-compressor() module.
+ */
+int zft_cmpr_lock(int try_to_load)
+{
+	if (zft_cmpr_ops == NULL) {
+#ifdef CONFIG_KMOD
+		if (try_to_load) {
+			request_module("zft-compressor");
+			if (zft_cmpr_ops == NULL) {
+				return -ENOSYS;
+			}
+		} else {
+			return -ENOSYS;
+		}
+#else
+		return -ENOSYS;
+#endif
+	}
+	(*zft_cmpr_ops->lock)();
+	return 0;
+}
+
+#ifdef CONFIG_ZFT_COMPRESSOR
+extern int zft_compressor_init(void);
+#endif
+
+/*  Called by modules package when installing the driver or by kernel
+ *  during the initialization phase
+ */
+int __init zft_init(void)
+{
+	int i;
+	TRACE_FUN(ft_t_flow);
+
+#ifdef MODULE
+	printk(KERN_INFO ZFTAPE_VERSION "\n");
+        if (TRACE_LEVEL >= ft_t_info) {
+		printk(
+KERN_INFO
+"(c) 1996, 1997 Claus-Justus Heine (claus@momo.math.rwth-aachen.de)\n"
+KERN_INFO
+"vfs interface for ftape floppy tape driver.\n"
+KERN_INFO
+"Support for QIC-113 compatible volume table, dynamic memory allocation\n"
+KERN_INFO
+"and builtin compression (lzrw3 algorithm).\n");
+        }
+#else /* !MODULE */
+	/* print a short no-nonsense boot message */
+	printk(KERN_INFO ZFTAPE_VERSION "\n");
+#endif /* MODULE */
+	TRACE(ft_t_info, "zft_init @ 0x%p", zft_init);
+	TRACE(ft_t_info,
+	      "installing zftape VFS interface for ftape driver ...");
+	TRACE_CATCH(register_chrdev(QIC117_TAPE_MAJOR, "zft", &zft_cdev),);
+
+	zft_class = class_simple_create(THIS_MODULE, "zft");
+	for (i = 0; i < 4; i++) {
+		class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i), NULL, "qft%i", i);
+		devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i),
+				S_IFCHR | S_IRUSR | S_IWUSR,
+				"qft%i", i);
+		class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 4), NULL, "nqft%i", i);
+		devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 4),
+				S_IFCHR | S_IRUSR | S_IWUSR,
+				"nqft%i", i);
+		class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 16), NULL, "zqft%i", i);
+		devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 16),
+				S_IFCHR | S_IRUSR | S_IWUSR,
+				"zqft%i", i);
+		class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 20), NULL, "nzqft%i", i);
+		devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 20),
+				S_IFCHR | S_IRUSR | S_IWUSR,
+				"nzqft%i", i);
+		class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 32), NULL, "rawqft%i", i);
+		devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 32),
+				S_IFCHR | S_IRUSR | S_IWUSR,
+				"rawqft%i", i);
+		class_simple_device_add(zft_class, MKDEV(QIC117_TAPE_MAJOR, i + 36), NULL, "nrawrawqft%i", i);
+		devfs_mk_cdev(MKDEV(QIC117_TAPE_MAJOR, i + 36),
+				S_IFCHR | S_IRUSR | S_IWUSR,
+				"nrawqft%i", i);
+	}
+
+#ifdef CONFIG_ZFT_COMPRESSOR
+	(void)zft_compressor_init();
+#endif
+	zft_status = ftape_get_status(); /*  fetch global data of ftape 
+					  *  hardware driver 
+					  */
+	TRACE_EXIT 0;
+}
+
+
+/* Called by modules package when removing the driver 
+ */
+static void zft_exit(void)
+{
+	int i;
+	TRACE_FUN(ft_t_flow);
+
+	if (unregister_chrdev(QIC117_TAPE_MAJOR, "zft") != 0) {
+		TRACE(ft_t_warn, "failed");
+	} else {
+		TRACE(ft_t_info, "successful");
+	}
+        for (i = 0; i < 4; i++) {
+		devfs_remove("qft%i", i);
+		class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i));
+		devfs_remove("nqft%i", i);
+		class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 4));
+		devfs_remove("zqft%i", i);
+		class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 16));
+		devfs_remove("nzqft%i", i);
+		class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 20));
+		devfs_remove("rawqft%i", i);
+		class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 32));
+		devfs_remove("nrawqft%i", i);
+		class_simple_device_remove(MKDEV(QIC117_TAPE_MAJOR, i + 36));
+	}
+	class_simple_destroy(zft_class);
+	zft_uninit_mem(); /* release remaining memory, if any */
+        printk(KERN_INFO "zftape successfully unloaded.\n");
+	TRACE_EXIT;
+}
+
+module_init(zft_init);
+module_exit(zft_exit);
diff --git a/drivers/char/ftape/zftape/zftape-init.h b/drivers/char/ftape/zftape/zftape-init.h
new file mode 100644
index 0000000..937e5d4
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-init.h
@@ -0,0 +1,77 @@
+#ifndef _ZFTAPE_INIT_H
+#define _ZFTAPE_INIT_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-init.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:05 $
+ *
+ * This file contains definitions and macro for the vfs 
+ * interface defined by zftape
+ *
+ */
+
+#include <linux/ftape-header-segment.h>
+
+#include "../lowlevel/ftape-tracing.h"
+#include "../lowlevel/ftape-ctl.h"
+#include "../lowlevel/ftape-read.h"
+#include "../lowlevel/ftape-write.h"
+#include "../lowlevel/ftape-bsm.h"
+#include "../lowlevel/ftape-io.h"
+#include "../lowlevel/ftape-buffer.h"
+#include "../lowlevel/ftape-format.h"
+
+#include "../zftape/zftape-rw.h"
+
+#ifdef MODULE
+#define ftape_status (*zft_status)
+#endif
+
+extern const  ftape_info *zft_status; /* needed for zftape-vtbl.h */
+
+#include "../zftape/zftape-vtbl.h"
+
+struct zft_cmpr_ops {
+	int (*write)(int *write_cnt,
+		     __u8 *dst_buf, const int seg_sz,
+		     const __u8 __user *src_buf, const int req_len, 
+		     const zft_position *pos, const zft_volinfo *volume);
+	int (*read)(int *read_cnt,
+		    __u8 __user *dst_buf, const int req_len,
+		    const __u8 *src_buf, const int seg_sz,
+		    const zft_position *pos, const zft_volinfo *volume);
+	int (*seek)(unsigned int new_block_pos,
+		    zft_position *pos, const zft_volinfo *volume,
+		    __u8 *buffer);
+	void (*lock)   (void);
+	void (*reset)  (void);
+	void (*cleanup)(void);
+};
+
+extern struct zft_cmpr_ops *zft_cmpr_ops;
+/* zftape-init.c defined global functions.
+ */
+extern int                  zft_cmpr_register(struct zft_cmpr_ops *new_ops);
+extern int                  zft_cmpr_lock(int try_to_load);
+
+#endif
+
+
diff --git a/drivers/char/ftape/zftape/zftape-read.c b/drivers/char/ftape/zftape/zftape-read.c
new file mode 100644
index 0000000..214bf03
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-read.c
@@ -0,0 +1,377 @@
+/*
+ *      Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-read.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:06 $
+ *
+ *      This file contains the high level reading code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/zftape.h>
+
+#include <asm/uaccess.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/*      Global vars.
+ */
+int zft_just_before_eof;
+
+/*      Local vars.
+ */
+static int buf_len_rd;
+
+void zft_zap_read_buffers(void)
+{
+	buf_len_rd = 0;
+}
+
+int zft_read_header_segments(void)      
+{
+	TRACE_FUN(ft_t_flow);
+
+	zft_header_read = 0;
+	TRACE_CATCH(zft_vmalloc_once(&zft_hseg_buf, FT_SEGMENT_SIZE),);
+	TRACE_CATCH(ftape_read_header_segment(zft_hseg_buf),);
+	TRACE(ft_t_info, "Segments written since first format: %d",
+	      (int)GET4(zft_hseg_buf, FT_SEG_CNT));
+	zft_qic113 = (ft_format_code != fmt_normal &&
+		      ft_format_code != fmt_1100ft &&
+		      ft_format_code != fmt_425ft);
+	TRACE(ft_t_info, "ft_first_data_segment: %d, ft_last_data_segment: %d", 
+	      ft_first_data_segment, ft_last_data_segment);
+	zft_capacity = zft_get_capacity();
+	zft_old_ftape = zft_ftape_validate_label(&zft_hseg_buf[FT_LABEL]);
+	if (zft_old_ftape) {
+		TRACE(ft_t_info, 
+"Found old ftaped tape, emulating eof marks, entering read-only mode");
+		zft_ftape_extract_file_marks(zft_hseg_buf);
+		TRACE_CATCH(zft_fake_volume_headers(zft_eof_map, 
+						    zft_nr_eof_marks),);
+	} else {
+		/* the specs say that the volume table must be
+		 * initialized with zeroes during formatting, so it
+		 * MUST be readable, i.e. contain vaid ECC
+		 * information.  
+		 */
+		TRACE_CATCH(ftape_read_segment(ft_first_data_segment, 
+					       zft_deblock_buf, 
+					       FT_RD_SINGLE),);
+		TRACE_CATCH(zft_extract_volume_headers(zft_deblock_buf),);
+	}
+	zft_header_read = 1;
+	zft_set_flags(zft_unit);
+	zft_reset_position(&zft_pos);
+	TRACE_EXIT 0;
+}
+
+int zft_fetch_segment_fraction(const unsigned int segment, void *buffer,
+			       const ft_read_mode_t read_mode,
+			       const unsigned int start,
+			       const unsigned int size)
+{
+	int seg_sz;
+	TRACE_FUN(ft_t_flow);
+
+	if (segment == zft_deblock_segment) {
+		TRACE(ft_t_data_flow,
+		      "re-using segment %d already in deblock buffer",
+		      segment);
+		seg_sz = zft_get_seg_sz(segment);
+		if (start > seg_sz) {
+			TRACE_ABORT(-EINVAL, ft_t_bug,
+				    "trying to read beyond end of segment:\n"
+				    KERN_INFO "seg_sz : %d\n"
+				    KERN_INFO "start  : %d\n"
+				    KERN_INFO "segment: %d",
+				    seg_sz, start, segment);
+		}
+		if ((start + size) > seg_sz) {
+			TRACE_EXIT seg_sz - start;
+		}
+		TRACE_EXIT size;
+	}
+	seg_sz = ftape_read_segment_fraction(segment, buffer, read_mode,
+					     start, size);
+	TRACE(ft_t_data_flow, "segment %d, result %d", segment, seg_sz);
+	if ((int)seg_sz >= 0 && start == 0 && size == FT_SEGMENT_SIZE) {
+		/*  this implicitly assumes that we are always called with
+		 *  buffer == zft_deblock_buf 
+		 */
+		zft_deblock_segment = segment;
+	} else {
+		zft_deblock_segment = -1;
+	}
+	TRACE_EXIT seg_sz;
+}
+
+/*
+ * out:
+ *
+ * int *read_cnt: the number of bytes we removed from the
+ *                zft_deblock_buf (result)
+ *
+ * int *to_do   : the remaining size of the read-request. Is changed.
+ *
+ * in:
+ *
+ * char *buff      : buff is the address of the upper part of the user
+ *                   buffer, that hasn't been filled with data yet.
+ * int buf_pos_read: copy of buf_pos_rd
+ * int buf_len_read: copy of buf_len_rd
+ * char *zft_deblock_buf: ftape_zft_deblock_buf
+ *
+ * returns the amount of data actually copied to the user-buffer
+ *
+ * to_do MUST NOT SHRINK except to indicate an EOT. In this case to_do
+ * has to be set to 0. We cannot return -ENOSPC, because we return the
+ * amount of data actually * copied to the user-buffer
+ */
+static int zft_simple_read (int *read_cnt, 
+			    __u8  __user *dst_buf, 
+			    const int to_do, 
+			    const __u8 *src_buf, 
+			    const int seg_sz, 
+			    const zft_position *pos,
+			    const zft_volinfo *volume)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (seg_sz - pos->seg_byte_pos < to_do) {
+		*read_cnt = seg_sz - pos->seg_byte_pos;
+	} else {
+		*read_cnt = to_do;
+	}
+	if (copy_to_user(dst_buf, 
+			 src_buf + pos->seg_byte_pos, *read_cnt) != 0) {
+		TRACE_EXIT -EFAULT;
+	}
+	TRACE(ft_t_noise, "nr bytes just read: %d", *read_cnt);
+	TRACE_EXIT *read_cnt;
+}
+
+/* req_len: gets clipped due to EOT of EOF.
+ * req_clipped: is a flag indicating whether req_len was clipped or not
+ * volume: contains information on current volume (blk_sz etc.)
+ */
+static int check_read_access(int *req_len, 
+			     const zft_volinfo **volume,
+			     int *req_clipped, 
+			     const zft_position *pos)
+{
+	static __s64 remaining;
+	static int eod;
+	TRACE_FUN(ft_t_flow);
+	
+	if (zft_io_state != zft_reading) {
+		if (zft_offline) { /* offline includes no_tape */
+			TRACE_ABORT(-ENXIO, ft_t_warn,
+				    "tape is offline or no cartridge");
+		}
+		if (!ft_formatted) {
+			TRACE_ABORT(-EACCES,
+				    ft_t_warn, "tape is not formatted");
+		}
+		/*  now enter defined state, read header segment if not
+		 *  already done and flush write buffers
+		 */
+		TRACE_CATCH(zft_def_idle_state(),);
+		zft_io_state = zft_reading;
+		if (zft_tape_at_eod(pos)) {
+			eod = 1;
+			TRACE_EXIT 1;
+		}
+		eod = 0;
+		*volume = zft_find_volume(pos->seg_pos);
+		/* get the space left until EOF */
+		remaining = zft_check_for_eof(*volume, pos);
+		buf_len_rd = 0;
+		TRACE(ft_t_noise, "remaining: " LL_X ", vol_no: %d",
+		      LL(remaining), (*volume)->count);
+	} else if (zft_tape_at_eod(pos)) {
+		if (++eod > 2) {
+			TRACE_EXIT -EIO; /* st.c also returns -EIO */
+		} else {
+			TRACE_EXIT 1;
+		}
+	}
+	if ((*req_len % (*volume)->blk_sz) != 0) {
+		/*  this message is informational only. The user gets the
+		 *  proper return value
+		 */
+		TRACE_ABORT(-EINVAL, ft_t_info,
+			    "req_len %d not a multiple of block size %d",
+			    *req_len, (*volume)->blk_sz);
+	}
+	/* As GNU tar doesn't accept partial read counts when the
+	 * multiple volume flag is set, we make sure to return the
+	 * requested amount of data. Except, of course, at the end of
+	 * the tape or file mark.  
+	 */
+	remaining -= *req_len;
+	if (remaining <= 0) {
+		TRACE(ft_t_noise, 
+		      "clipped request from %d to %d.", 
+		      *req_len, (int)(*req_len + remaining));
+		*req_len += remaining;
+		*req_clipped = 1;
+	} else {
+		*req_clipped = 0;
+	}
+	TRACE_EXIT 0;
+}
+
+/* this_segs_size: the current segment's size.
+ * buff: the USER-SPACE buffer provided by the calling function.
+ * req_len: how much data should be read at most.
+ * volume: contains information on current volume (blk_sz etc.)
+ */  
+static int empty_deblock_buf(__u8 __user *usr_buf, const int req_len,
+			     const __u8 *src_buf, const int seg_sz,
+			     zft_position *pos,
+			     const zft_volinfo *volume)
+{
+	int cnt;
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_data_flow, "this_segs_size: %d", seg_sz);
+	if (zft_use_compression && volume->use_compression) {
+		TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),);
+		TRACE_CATCH(result= (*zft_cmpr_ops->read)(&cnt,
+							  usr_buf, req_len,
+							  src_buf, seg_sz,
+							  pos, volume),);
+	} else {                                  
+		TRACE_CATCH(result= zft_simple_read (&cnt,
+						     usr_buf, req_len,
+						     src_buf, seg_sz,
+						     pos, volume),);
+	}
+	pos->volume_pos   += result;
+        pos->tape_pos     += cnt;
+	pos->seg_byte_pos += cnt;
+	buf_len_rd        -= cnt; /* remaining bytes in buffer */
+	TRACE(ft_t_data_flow, "buf_len_rd: %d, cnt: %d", buf_len_rd, cnt);
+	if(pos->seg_byte_pos >= seg_sz) {
+		pos->seg_pos++;
+		pos->seg_byte_pos = 0;
+	}
+	TRACE(ft_t_data_flow, "bytes moved out of deblock-buffer: %d", cnt);
+	TRACE_EXIT result;
+}
+
+
+/* note: we store the segment id of the segment that is inside the
+ * deblock buffer. This spares a lot of ftape_read_segment()s when we
+ * use small block-sizes. The block-size may be 1kb (SECTOR_SIZE). In
+ * this case a MTFSR 28 maybe still inside the same segment.
+ */
+int _zft_read(char __user *buff, int req_len)
+{
+	int req_clipped;
+	int result     = 0;
+	int bytes_read = 0;
+	static unsigned int seg_sz = 0;
+	static const zft_volinfo *volume = NULL;
+	TRACE_FUN(ft_t_flow);
+	
+	zft_resid = req_len;
+	result = check_read_access(&req_len, &volume,
+				   &req_clipped, &zft_pos);
+	switch(result) {
+	case 0: 
+		break; /* nothing special */
+	case 1: 
+		TRACE(ft_t_noise, "EOD reached");
+		TRACE_EXIT 0;   /* EOD */
+	default:
+		TRACE_ABORT(result, ft_t_noise,
+			    "check_read_access() failed with result %d",
+			    result);
+		TRACE_EXIT result;
+	}
+	while (req_len > 0) { 
+		/*  Allow escape from this loop on signal !
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		/* buf_len_rd == 0 means that we need to read a new
+		 * segment.
+		 */
+		if (buf_len_rd == 0) {
+			while((result = zft_fetch_segment(zft_pos.seg_pos,
+							  zft_deblock_buf,
+							  FT_RD_AHEAD)) == 0) {
+				zft_pos.seg_pos ++;
+				zft_pos.seg_byte_pos = 0;
+			}
+			if (result < 0) {
+				zft_resid -= bytes_read;
+				TRACE_ABORT(result, ft_t_noise,
+					    "zft_fetch_segment(): %d",
+					    result);
+			}
+			seg_sz = result;
+			buf_len_rd = seg_sz - zft_pos.seg_byte_pos;
+		}
+		TRACE_CATCH(result = empty_deblock_buf(buff, 
+						       req_len,
+						       zft_deblock_buf, 
+						       seg_sz, 
+						       &zft_pos,
+						       volume),
+			    zft_resid -= bytes_read);
+		TRACE(ft_t_data_flow, "bytes just read: %d", result);
+		bytes_read += result; /* what we got so far       */
+		buff       += result; /* index in user-buffer     */
+		req_len    -= result; /* what's left from req_len */
+	} /* while (req_len  > 0) */
+	if (req_clipped) {
+		TRACE(ft_t_data_flow,
+		      "maybe partial count because of eof mark");
+		if (zft_just_before_eof && bytes_read == 0) {
+			/* req_len was > 0, but user didn't get
+			 * anything the user has read in the eof-mark 
+			 */
+			zft_move_past_eof(&zft_pos);
+			ftape_abort_operation();
+		} else {
+			/* don't skip to the next file before the user
+			 * tried to read a second time past EOF Just
+			 * mark that we are at EOF and maybe decrement
+			 * zft_seg_pos to stay in the same volume;
+			 */
+			zft_just_before_eof = 1;
+			zft_position_before_eof(&zft_pos, volume);
+			TRACE(ft_t_noise, "just before eof");
+		}
+	}
+	zft_resid -= result; /* for MTSTATUS       */
+	TRACE_EXIT bytes_read;
+}
diff --git a/drivers/char/ftape/zftape/zftape-read.h b/drivers/char/ftape/zftape/zftape-read.h
new file mode 100644
index 0000000..42941de
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-read.h
@@ -0,0 +1,53 @@
+#ifndef _ZFTAPE_READ_H
+#define _ZFTAPE_READ_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-read.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:07 $
+ *
+ *      This file contains the definitions for the read functions
+ *      for the zftape driver for Linux.
+ *
+ */
+
+#include "../lowlevel/ftape-read.h"
+
+/*      ftape-read.c defined global vars.
+ */
+extern int zft_just_before_eof;
+	
+/*      ftape-read.c defined global functions.
+ */
+extern void zft_zap_read_buffers(void);
+extern int  zft_read_header_segments(void);
+extern int  zft_fetch_segment_fraction(const unsigned int segment,
+				       void *buffer,
+				       const ft_read_mode_t read_mode,
+				       const unsigned int start,
+				       const unsigned int size);
+#define zft_fetch_segment(segment, address, read_mode)		\
+	zft_fetch_segment_fraction(segment, address, read_mode,	\
+				   0, FT_SEGMENT_SIZE)
+/*   hook for the VFS interface
+ */
+extern int  _zft_read(char __user *buff, int req_len);
+
+#endif /* _ZFTAPE_READ_H */
diff --git a/drivers/char/ftape/zftape/zftape-rw.c b/drivers/char/ftape/zftape/zftape-rw.c
new file mode 100644
index 0000000..a61ef50
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-rw.c
@@ -0,0 +1,376 @@
+/*
+ *      Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-rw.c,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:08 $
+ *
+ *      This file contains some common code for the r/w code for
+ *      zftape.
+ */
+
+#include <linux/config.h> /* for CONFIG_ZFT_DFLT_BLK_SZ */
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/zftape.h>
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/*      Global vars.
+ */
+
+__u8 *zft_deblock_buf;
+__u8 *zft_hseg_buf;
+int zft_deblock_segment = -1;
+zft_status_enum zft_io_state = zft_idle;
+int zft_header_changed;
+int zft_qic113; /* conform to old specs. and old zftape */
+int zft_use_compression;
+zft_position zft_pos = {
+	-1, /* seg_pos */
+	0,  /* seg_byte_pos */
+	0,  /* tape_pos */
+	0   /* volume_pos */
+};
+unsigned int zft_blk_sz = CONFIG_ZFT_DFLT_BLK_SZ;
+__s64 zft_capacity;
+
+unsigned int zft_written_segments;
+int zft_label_changed;
+
+/*      Local vars.
+ */
+
+unsigned int zft_get_seg_sz(unsigned int segment)
+{
+	int size;
+	TRACE_FUN(ft_t_any);
+	
+	size = FT_SEGMENT_SIZE - 
+		count_ones(ftape_get_bad_sector_entry(segment))*FT_SECTOR_SIZE;
+	if (size > 0) {
+		TRACE_EXIT (unsigned)size; 
+	} else {
+		TRACE_EXIT 0;
+	}
+}
+
+/* ftape_set_flags(). Claus-Justus Heine, 1994/1995
+ */
+void zft_set_flags(unsigned minor_unit)
+{     
+	TRACE_FUN(ft_t_flow);
+	
+	zft_use_compression = zft_qic_mode = 0;
+	switch (minor_unit & ZFT_MINOR_OP_MASK) {
+	case (ZFT_Q80_MODE | ZFT_ZIP_MODE):
+	case ZFT_ZIP_MODE:
+		zft_use_compression = 1;
+	case 0:
+	case ZFT_Q80_MODE:
+		zft_qic_mode = 1;
+		if (zft_mt_compression) { /* override the default */
+			zft_use_compression = 1;
+		}
+		break;
+	case ZFT_RAW_MODE:
+		TRACE(ft_t_noise, "switching to raw mode");
+		break;
+	default:
+		TRACE(ft_t_warn, "Warning:\n"
+		      KERN_INFO "Wrong combination of minor device bits.\n"
+		      KERN_INFO "Switching to raw read-only mode.");
+		zft_write_protected = 1;
+		break;
+	}
+	TRACE_EXIT;
+}
+
+/* computes the segment and byte offset inside the segment
+ * corresponding to tape_pos.
+ *
+ * tape_pos gives the offset in bytes from the beginning of the
+ * ft_first_data_segment *seg_byte_pos is the offset in the current
+ * segment in bytes
+ *
+ * Of, if this routine was called often one should cache the last data
+ * pos it was called with, but actually this is only needed in
+ * ftape_seek_block(), that is, almost never.
+ */
+int zft_calc_seg_byte_coord(int *seg_byte_pos, __s64 tape_pos)
+{
+	int segment;
+	int seg_sz;
+	TRACE_FUN(ft_t_flow);
+	
+	if (tape_pos == 0) {
+		*seg_byte_pos = 0;
+		segment = ft_first_data_segment;
+	} else {
+		seg_sz = 0;
+		
+		for (segment = ft_first_data_segment; 
+		     ((tape_pos > 0) && (segment <= ft_last_data_segment));
+		     segment++) {
+			seg_sz = zft_get_seg_sz(segment); 
+			tape_pos -= seg_sz;
+		}
+		if(tape_pos >= 0) {
+			/* the case tape_pos > != 0 means that the
+			 * argument tape_pos lies beyond the EOT.
+			 */
+			*seg_byte_pos= 0;
+		} else { /* tape_pos < 0 */
+			segment--;
+			*seg_byte_pos= tape_pos + seg_sz;
+		}
+	}
+	TRACE_EXIT(segment);
+}
+
+/* ftape_calc_tape_pos().
+ *
+ * computes the offset in bytes from the beginning of the
+ * ft_first_data_segment inverse to ftape_calc_seg_byte_coord
+ *
+ * We should do some caching. But how:
+ *
+ * Each time the header segments are read in, this routine is called
+ * with ft_tracks_per_tape*segments_per_track argumnet. So this should be
+ * the time to reset the cache.
+ *
+ * Also, it might be in the future that the bad sector map gets
+ * changed.  -> reset the cache
+ */
+static int seg_pos;
+static __s64 tape_pos;
+
+__s64 zft_get_capacity(void)
+{
+	seg_pos  = ft_first_data_segment;
+	tape_pos = 0;
+
+	while (seg_pos <= ft_last_data_segment) {
+		tape_pos += zft_get_seg_sz(seg_pos ++);
+	}
+	return tape_pos;
+}
+
+__s64 zft_calc_tape_pos(int segment)
+{
+	int d1, d2, d3;
+	TRACE_FUN(ft_t_any);
+	
+	if (segment > ft_last_data_segment) {
+	        TRACE_EXIT zft_capacity;
+	}
+	if (segment < ft_first_data_segment) {
+		TRACE_EXIT 0;
+	}
+	d2 = segment - seg_pos;
+	if (-d2 > 10) {
+		d1 = segment - ft_first_data_segment;
+		if (-d2 > d1) {
+			tape_pos = 0;
+			seg_pos = ft_first_data_segment;
+			d2 = d1;
+		}
+	}
+	if (d2 > 10) {
+		d3 = ft_last_data_segment - segment;
+		if (d2 > d3) {
+			tape_pos = zft_capacity;
+			seg_pos  = ft_last_data_segment + 1;
+			d2 = -d3;
+		}
+	}		
+	if (d2 > 0) {
+		while (seg_pos < segment) {
+			tape_pos +=  zft_get_seg_sz(seg_pos++);
+		}
+	} else {
+		while (seg_pos > segment) {
+			tape_pos -=  zft_get_seg_sz(--seg_pos);
+		}
+	}
+	TRACE(ft_t_noise, "new cached pos: %d", seg_pos);
+
+	TRACE_EXIT tape_pos;
+}
+
+/* copy Z-label string to buffer, keeps track of the correct offset in
+ * `buffer' 
+ */
+void zft_update_label(__u8 *buffer)
+{ 
+	TRACE_FUN(ft_t_flow);
+	
+	if (strncmp(&buffer[FT_LABEL], ZFTAPE_LABEL, 
+		    sizeof(ZFTAPE_LABEL)-1) != 0) {
+		TRACE(ft_t_info, "updating label from \"%s\" to \"%s\"",
+		      &buffer[FT_LABEL], ZFTAPE_LABEL);
+		strcpy(&buffer[FT_LABEL], ZFTAPE_LABEL);
+		memset(&buffer[FT_LABEL] + sizeof(ZFTAPE_LABEL) - 1, ' ', 
+		       FT_LABEL_SZ - sizeof(ZFTAPE_LABEL + 1));
+		PUT4(buffer, FT_LABEL_DATE, 0);
+		zft_label_changed = zft_header_changed = 1; /* changed */
+	}
+	TRACE_EXIT;
+}
+
+int zft_verify_write_segments(unsigned int segment, 
+			      __u8 *data, size_t size,
+			      __u8 *buffer)
+{
+	int result;
+	__u8 *write_buf;
+	__u8 *src_buf;
+	int single;
+	int seg_pos;
+	int seg_sz;
+	int remaining;
+	ft_write_mode_t write_mode;
+	TRACE_FUN(ft_t_flow);
+
+	seg_pos   = segment;
+	seg_sz    = zft_get_seg_sz(seg_pos);
+	src_buf   = data;
+	single    = size <= seg_sz;
+	remaining = size;
+	do {
+		TRACE(ft_t_noise, "\n"
+		      KERN_INFO "remaining: %d\n"
+		      KERN_INFO "seg_sz   : %d\n"
+		      KERN_INFO "segment  : %d",
+		      remaining, seg_sz, seg_pos);
+		if (remaining == seg_sz) {
+			write_buf = src_buf;
+			write_mode = single ? FT_WR_SINGLE : FT_WR_MULTI;
+			remaining = 0;
+		} else if (remaining > seg_sz) {
+			write_buf = src_buf;
+			write_mode = FT_WR_ASYNC; /* don't start tape */
+			remaining -= seg_sz;
+		} else { /* remaining < seg_sz */
+			write_buf = buffer;
+			memcpy(write_buf, src_buf, remaining);
+			memset(&write_buf[remaining],'\0',seg_sz-remaining);
+			write_mode = single ? FT_WR_SINGLE : FT_WR_MULTI;
+			remaining = 0;
+		}
+		if ((result = ftape_write_segment(seg_pos, 
+						  write_buf, 
+						  write_mode)) != seg_sz) {
+			TRACE(ft_t_err, "Error: "
+			      "Couldn't write segment %d", seg_pos);
+			TRACE_EXIT result < 0 ? result : -EIO; /* bail out */
+		}
+		zft_written_segments ++;
+		seg_sz = zft_get_seg_sz(++seg_pos);
+		src_buf += result;
+	} while (remaining > 0);
+	if (ftape_get_status()->fti_state == writing) {
+		TRACE_CATCH(ftape_loop_until_writes_done(),);
+		TRACE_CATCH(ftape_abort_operation(),);
+		zft_prevent_flush();
+	}
+	seg_pos = segment;
+	src_buf = data;
+	remaining = size;
+	do {
+		TRACE_CATCH(result = ftape_read_segment(seg_pos, buffer, 
+							single ? FT_RD_SINGLE
+							: FT_RD_AHEAD),);
+		if (memcmp(src_buf, buffer, 
+			   remaining > result ? result : remaining) != 0) {
+			TRACE_ABORT(-EIO, ft_t_err,
+				    "Failed to verify written segment %d",
+				    seg_pos);
+		}
+		remaining -= result;
+		TRACE(ft_t_noise, "verify successful:\n"
+		      KERN_INFO "segment  : %d\n"
+		      KERN_INFO "segsize  : %d\n"
+		      KERN_INFO "remaining: %d",
+		      seg_pos, result, remaining);
+		src_buf   += seg_sz;
+		seg_pos++;
+	} while (remaining > 0);
+	TRACE_EXIT size;
+}
+
+
+/* zft_erase().  implemented compression-handling
+ *
+ * calculate the first data-segment when using/not using compression.
+ *
+ * update header-segment and compression-map-segment.
+ */
+int zft_erase(void)
+{
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+	
+	if (!zft_header_read) {
+		TRACE_CATCH(zft_vmalloc_once((void **)&zft_hseg_buf,
+					     FT_SEGMENT_SIZE),);
+		/* no need to read the vtbl and compression map */
+		TRACE_CATCH(ftape_read_header_segment(zft_hseg_buf),);
+		if ((zft_old_ftape = 
+		     zft_ftape_validate_label(&zft_hseg_buf[FT_LABEL]))) {
+			zft_ftape_extract_file_marks(zft_hseg_buf);
+		}
+		TRACE(ft_t_noise,
+		      "ft_first_data_segment: %d, ft_last_data_segment: %d", 
+		      ft_first_data_segment, ft_last_data_segment);
+		zft_qic113 = (ft_format_code != fmt_normal &&
+			      ft_format_code != fmt_1100ft &&
+			      ft_format_code != fmt_425ft);
+	}
+	if (zft_old_ftape) {
+		zft_clear_ftape_file_marks();
+		zft_old_ftape = 0; /* no longer old ftape */
+	}
+	PUT2(zft_hseg_buf, FT_CMAP_START, 0);
+	zft_volume_table_changed = 1;
+	zft_capacity = zft_get_capacity();
+	zft_init_vtbl();
+	/* the rest must be done in ftape_update_header_segments 
+	 */
+	zft_header_read = 1;
+	zft_header_changed = 1; /* force update of timestamp */
+	result = zft_update_header_segments();
+
+	ftape_abort_operation();
+
+	zft_reset_position(&zft_pos);
+	zft_set_flags (zft_unit);
+	TRACE_EXIT result;
+}
+
+unsigned int zft_get_time(void) 
+{
+	unsigned int date = FT_TIME_STAMP(2097, 11, 30, 23, 59, 59); /* fun */
+	return date;
+}
diff --git a/drivers/char/ftape/zftape/zftape-rw.h b/drivers/char/ftape/zftape/zftape-rw.h
new file mode 100644
index 0000000..14c07f0
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-rw.h
@@ -0,0 +1,102 @@
+#ifndef _ZFTAPE_RW_H
+#define _ZFTAPE_RW_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine.
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-rw.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:09 $
+ *
+ *      This file contains the definitions for the read and write
+ *      functions for the QIC-117 floppy-tape driver for Linux.
+ *
+ */
+
+#include <linux/config.h> /* for CONFIG_ZFT_DFLT_BLK_SZ */
+#include "../zftape/zftape-buffers.h"
+
+#define SEGMENTS_PER_TAPE  (ft_segments_per_track * ft_tracks_per_tape)
+
+/*  QIC-113 Rev. G says that `a maximum of 63488 raw bytes may be
+ *  compressed into a single frame'.
+ *  Maybe we should stick to 32kb to make it more `beautiful'
+ */
+#define ZFT_MAX_BLK_SZ           (62*1024) /* bytes */
+#if !defined(CONFIG_ZFT_DFLT_BLK_SZ)
+# define CONFIG_ZFT_DFLT_BLK_SZ   (10*1024) /* bytes, default of gnu tar */
+#elif CONFIG_ZFT_DFLT_BLK_SZ == 0
+# undef  CONFIG_ZFT_DFLT_BLK_SZ
+# define CONFIG_ZFT_DFLT_BLK_SZ 1
+#elif (CONFIG_ZFT_DFLT_BLK_SZ % 1024) != 0
+# error CONFIG_ZFT_DFLT_BLK_SZ must be 1 or a multiple of 1024
+#endif
+/* The *optional* compression routines need some overhead per tape
+ *  block for their purposes. Instead of asking the actual compression
+ *  implementation how much it needs, we restrict this overhead to be
+ *  maximal of ZFT_CMPT_OVERHEAD size. We need this for EOT
+ *  conditions. The tape is assumed to be logical at EOT when the
+ *  distance from the physical EOT is less than 
+ *  one tape block + ZFT_CMPR_OVERHEAD 
+ */
+#define ZFT_CMPR_OVERHEAD 16        /* bytes */
+
+typedef enum
+{ 
+	zft_idle = 0,
+	zft_reading,
+	zft_writing,
+} zft_status_enum;
+
+typedef struct               /*  all values measured in bytes */
+{
+	int   seg_pos;       /*  segment currently positioned at */
+	int   seg_byte_pos;  /*  offset in current segment */ 
+	__s64 tape_pos;      /*  real offset from BOT */
+	__s64 volume_pos;    /*  pos. in uncompressed data stream in
+			      *  current volume 
+			      */
+} zft_position; 
+
+extern zft_position zft_pos;
+extern __u8 *zft_deblock_buf;
+extern __u8 *zft_hseg_buf;
+extern int zft_deblock_segment;
+extern zft_status_enum zft_io_state;
+extern int zft_header_changed;
+extern int zft_qic113; /* conform to old specs. and old zftape */
+extern int zft_use_compression;
+extern unsigned int zft_blk_sz;
+extern __s64 zft_capacity;
+extern unsigned int zft_written_segments;
+extern int zft_label_changed;
+
+/*  zftape-rw.c exported functions
+ */
+extern unsigned int zft_get_seg_sz(unsigned int segment);
+extern void  zft_set_flags(unsigned int minor_unit);
+extern int   zft_calc_seg_byte_coord(int *seg_byte_pos, __s64 tape_pos);
+extern __s64 zft_calc_tape_pos(int segment);
+extern __s64 zft_get_capacity(void);
+extern void  zft_update_label(__u8 *buffer);
+extern int   zft_erase(void);
+extern int   zft_verify_write_segments(unsigned int segment, 
+				       __u8 *data, size_t size, __u8 *buffer);
+extern unsigned int zft_get_time(void);
+#endif /* _ZFTAPE_RW_H */
+
diff --git a/drivers/char/ftape/zftape/zftape-vtbl.c b/drivers/char/ftape/zftape/zftape-vtbl.c
new file mode 100644
index 0000000..ad7f8be
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-vtbl.c
@@ -0,0 +1,757 @@
+/*
+ *      Copyright (c) 1995-1997 Claus-Justus Heine 
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-vtbl.c,v $
+ * $Revision: 1.7.6.1 $
+ * $Date: 1997/11/24 13:48:31 $
+ *
+ *      This file defines a volume table as defined in various QIC
+ *      standards.
+ * 
+ *      This is a minimal implementation, just allowing ordinary DOS
+ *      :( prgrams to identify the cartridge as used.
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+
+#include <linux/zftape.h>
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+#define ZFT_CMAP_HACK /* leave this defined to hide the compression map */
+
+/*
+ *  global variables 
+ */
+int zft_qic_mode   = 1; /* use the vtbl */
+int zft_old_ftape; /* prevents old ftaped tapes to be overwritten */
+int zft_volume_table_changed; /* for write_header_segments() */
+
+/*
+ *  private variables (only exported for inline functions)
+ */
+LIST_HEAD(zft_vtbl);
+
+/*  We could also allocate these dynamically when extracting the volume table
+ *  sizeof(zft_volinfo) is about 32 or something close to that
+ */
+static zft_volinfo  tape_vtbl;
+static zft_volinfo  eot_vtbl;
+static zft_volinfo *cur_vtbl;
+
+static inline void zft_new_vtbl_entry(void)
+{
+	struct list_head *tmp = &zft_last_vtbl->node;
+	zft_volinfo *new = zft_kmalloc(sizeof(zft_volinfo));
+
+	list_add(&new->node, tmp);
+	new->count = zft_eom_vtbl->count ++;
+}
+
+void zft_free_vtbl(void)
+{
+	for (;;) {
+		struct list_head *tmp = zft_vtbl.prev;
+		zft_volinfo *vtbl;
+
+		if (tmp == &zft_vtbl)
+			break;
+		list_del(tmp);
+		vtbl = list_entry(tmp, zft_volinfo, node);
+		zft_kfree(vtbl, sizeof(zft_volinfo));
+	}
+	INIT_LIST_HEAD(&zft_vtbl);
+	cur_vtbl = NULL;
+}
+
+/*  initialize vtbl, called by ftape_new_cartridge()
+ */
+void zft_init_vtbl(void)
+{ 
+	zft_volinfo *new;
+
+	zft_free_vtbl();
+	
+	/*  Create the two dummy vtbl entries
+	 */
+	new = zft_kmalloc(sizeof(zft_volinfo));
+	list_add(&new->node, &zft_vtbl);
+	new = zft_kmalloc(sizeof(zft_volinfo));
+	list_add(&new->node, &zft_vtbl);
+	zft_head_vtbl->end_seg   = ft_first_data_segment;
+	zft_head_vtbl->blk_sz    = zft_blk_sz;
+	zft_head_vtbl->count     = -1;
+	zft_eom_vtbl->start_seg  = ft_first_data_segment + 1;
+	zft_eom_vtbl->end_seg    = ft_last_data_segment + 1;
+	zft_eom_vtbl->blk_sz     = zft_blk_sz;
+	zft_eom_vtbl->count      = 0;
+
+	/*  Reset the pointer for zft_find_volume()
+	 */
+	cur_vtbl = zft_eom_vtbl;
+
+	/* initialize the dummy vtbl entries for zft_qic_mode == 0
+	 */
+	eot_vtbl.start_seg       = ft_last_data_segment + 1;
+	eot_vtbl.end_seg         = ft_last_data_segment + 1;
+	eot_vtbl.blk_sz          = zft_blk_sz;
+	eot_vtbl.count           = -1;
+	tape_vtbl.start_seg = ft_first_data_segment;
+	tape_vtbl.end_seg   = ft_last_data_segment;
+	tape_vtbl.blk_sz    = zft_blk_sz;
+	tape_vtbl.size      = zft_capacity;
+	tape_vtbl.count     = 0;
+}
+
+/* check for a valid VTBL signature. 
+ */
+static int vtbl_signature_valid(__u8 signature[4])
+{
+	const char *vtbl_ids[] = VTBL_IDS; /* valid signatures */
+	int j;
+	
+	for (j = 0; 
+	     (j < NR_ITEMS(vtbl_ids)) && (memcmp(signature, vtbl_ids[j], 4) != 0);
+	     j++);
+	return j < NR_ITEMS(vtbl_ids);
+}
+
+/* We used to store the block-size of the volume in the volume-label,
+ * using the keyword "blocksize". The blocksize written to the
+ * volume-label is in bytes.
+ *
+ * We use this now only for compatibility with old zftape version. We
+ * store the blocksize directly as binary number in the vendor
+ * extension part of the volume entry.
+ */
+static int check_volume_label(const char *label, int *blk_sz)
+{ 
+	int valid_format;
+	char *blocksize;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "called with \"%s\" / \"%s\"", label, ZFT_VOL_NAME);
+	if (strncmp(label, ZFT_VOL_NAME, strlen(ZFT_VOL_NAME)) != 0) {
+		*blk_sz = 1; /* smallest block size that we allow */
+		valid_format = 0;
+	} else {
+		TRACE(ft_t_noise, "got old style zftape vtbl entry");
+		/* get the default blocksize */
+		/* use the kernel strstr()   */
+		blocksize= strstr(label, " blocksize ");
+		if (blocksize) {
+			blocksize += strlen(" blocksize ");
+			for(*blk_sz= 0; 
+			    *blocksize >= '0' && *blocksize <= '9'; 
+			    blocksize++) {
+				*blk_sz *= 10;
+				*blk_sz += *blocksize - '0';
+			}
+			if (*blk_sz > ZFT_MAX_BLK_SZ) {
+				*blk_sz= 1;
+				valid_format= 0;
+			} else {
+				valid_format = 1;
+			}
+		} else {
+			*blk_sz= 1;
+			valid_format= 0;
+		}
+	}
+	TRACE_EXIT valid_format;
+}
+
+/*   check for a zftape volume
+ */
+static int check_volume(__u8 *entry, zft_volinfo *volume)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	if(strncmp(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG,
+		   strlen(ZFTAPE_SIG)) == 0) {
+		TRACE(ft_t_noise, "got new style zftape vtbl entry");
+		volume->blk_sz = GET2(entry, VTBL_EXT+EXT_ZFTAPE_BLKSZ);
+		volume->qic113 = entry[VTBL_EXT+EXT_ZFTAPE_QIC113];
+		TRACE_EXIT 1;
+	} else {
+		TRACE_EXIT check_volume_label(&entry[VTBL_DESC], &volume->blk_sz);
+	}
+}
+
+
+/* create zftape specific vtbl entry, the volume bounds are inserted
+ * in the calling function, zft_create_volume_headers()
+ */
+static void create_zft_volume(__u8 *entry, zft_volinfo *vtbl)
+{
+	TRACE_FUN(ft_t_flow);
+
+	memset(entry, 0, VTBL_SIZE);
+	memcpy(&entry[VTBL_SIG], VTBL_ID, 4);
+	sprintf(&entry[VTBL_DESC], ZFT_VOL_NAME" %03d", vtbl->count);
+	entry[VTBL_FLAGS] = (VTBL_FL_NOT_VERIFIED | VTBL_FL_SEG_SPANNING);
+	entry[VTBL_M_NO] = 1; /* multi_cartridge_count */
+	strcpy(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG);
+	PUT2(entry, VTBL_EXT+EXT_ZFTAPE_BLKSZ, vtbl->blk_sz);
+	if (zft_qic113) {
+		PUT8(entry, VTBL_DATA_SIZE, vtbl->size);
+		entry[VTBL_CMPR] = VTBL_CMPR_UNREG; 
+		if (vtbl->use_compression) { /* use compression: */
+			entry[VTBL_CMPR] |= VTBL_CMPR_USED;
+		}
+		entry[VTBL_EXT+EXT_ZFTAPE_QIC113] = 1;
+	} else {
+		PUT4(entry, VTBL_DATA_SIZE, vtbl->size);
+		entry[VTBL_K_CMPR] = VTBL_CMPR_UNREG; 
+		if (vtbl->use_compression) { /* use compression: */
+			entry[VTBL_K_CMPR] |= VTBL_CMPR_USED;
+		}
+	}
+	if (ft_format_code == fmt_big) {
+		/* SCSI like vtbl, store the number of used
+		 * segments as 4 byte value 
+		 */
+		PUT4(entry, VTBL_SCSI_SEGS, vtbl->end_seg-vtbl->start_seg + 1);
+	} else {
+		/* normal, QIC-80MC like vtbl 
+		 */
+		PUT2(entry, VTBL_START, vtbl->start_seg);
+		PUT2(entry, VTBL_END, vtbl->end_seg);
+	}
+	TRACE_EXIT;
+}
+
+/* this one creates the volume headers for each volume. It is assumed
+ * that buffer already contains the old volume-table, so that vtbl
+ * entries without the zft_volume flag set can savely be ignored.
+ */
+static void zft_create_volume_headers(__u8 *buffer)
+{   
+	__u8 *entry;
+	struct list_head *tmp;
+	zft_volinfo *vtbl;
+	TRACE_FUN(ft_t_flow);
+	
+#ifdef ZFT_CMAP_HACK
+	if((strncmp(&buffer[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG,
+		    strlen(ZFTAPE_SIG)) == 0) && 
+	   buffer[VTBL_EXT+EXT_ZFTAPE_CMAP] != 0) {
+		TRACE(ft_t_noise, "deleting cmap volume");
+		memmove(buffer, buffer + VTBL_SIZE,
+			FT_SEGMENT_SIZE - VTBL_SIZE);
+	}
+#endif
+	entry = buffer;
+	for (tmp = zft_head_vtbl->node.next;
+	     tmp != &zft_eom_vtbl->node;
+	     tmp = tmp->next) {
+		vtbl = list_entry(tmp, zft_volinfo, node);
+		/* we now fill in the values only for newly created volumes.
+		 */
+		if (vtbl->new_volume) {
+			create_zft_volume(entry, vtbl);
+			vtbl->new_volume = 0; /* clear the flag */
+		}
+		
+		DUMP_VOLINFO(ft_t_noise, &entry[VTBL_DESC], vtbl);
+		entry += VTBL_SIZE;
+	}
+	memset(entry, 0, FT_SEGMENT_SIZE - zft_eom_vtbl->count * VTBL_SIZE);
+	TRACE_EXIT;
+}
+
+/*  write volume table to tape. Calls zft_create_volume_headers()
+ */
+int zft_update_volume_table(unsigned int segment)
+{
+	int result = 0;
+	__u8 *verify_buf = NULL;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE_CATCH(result = ftape_read_segment(ft_first_data_segment, 
+						zft_deblock_buf,
+						FT_RD_SINGLE),);
+	zft_create_volume_headers(zft_deblock_buf);
+	TRACE(ft_t_noise, "writing volume table segment %d", segment);
+	if (zft_vmalloc_once(&verify_buf, FT_SEGMENT_SIZE) == 0) {
+		TRACE_CATCH(zft_verify_write_segments(segment, 
+						      zft_deblock_buf, result,
+						      verify_buf),
+			    zft_vfree(&verify_buf, FT_SEGMENT_SIZE));
+		zft_vfree(&verify_buf, FT_SEGMENT_SIZE);
+	} else {
+		TRACE_CATCH(ftape_write_segment(segment, zft_deblock_buf, 
+						FT_WR_SINGLE),);
+	}
+	TRACE_EXIT 0;
+}
+
+/* non zftape volumes are handled in raw mode. Thus we need to
+ * calculate the raw amount of data contained in those segments.  
+ */
+static void extract_alien_volume(__u8 *entry, zft_volinfo *vtbl)
+{
+	TRACE_FUN(ft_t_flow);
+
+	vtbl->size  = (zft_calc_tape_pos(zft_last_vtbl->end_seg+1) -
+		       zft_calc_tape_pos(zft_last_vtbl->start_seg));
+	vtbl->use_compression = 0;
+	vtbl->qic113 = zft_qic113;
+	if (vtbl->qic113) {
+		TRACE(ft_t_noise, 
+		      "Fake alien volume's size from " LL_X " to " LL_X, 
+		      LL(GET8(entry, VTBL_DATA_SIZE)), LL(vtbl->size));
+	} else {
+		TRACE(ft_t_noise,
+		      "Fake alien volume's size from %d to " LL_X, 
+		      (int)GET4(entry, VTBL_DATA_SIZE), LL(vtbl->size));
+	}
+	TRACE_EXIT;
+}
+
+
+/* extract an zftape specific volume
+ */
+static void extract_zft_volume(__u8 *entry, zft_volinfo *vtbl)
+{
+	TRACE_FUN(ft_t_flow);
+
+	if (vtbl->qic113) {
+		vtbl->size = GET8(entry, VTBL_DATA_SIZE);
+		vtbl->use_compression = 
+			(entry[VTBL_CMPR] & VTBL_CMPR_USED) != 0; 
+	} else {
+		vtbl->size = GET4(entry, VTBL_DATA_SIZE);
+		if (entry[VTBL_K_CMPR] & VTBL_CMPR_UNREG) {
+			vtbl->use_compression = 
+				(entry[VTBL_K_CMPR] & VTBL_CMPR_USED) != 0;
+		} else if (entry[VTBL_CMPR] & VTBL_CMPR_UNREG) {
+			vtbl->use_compression = 
+				(entry[VTBL_CMPR] & VTBL_CMPR_USED) != 0; 
+		} else {
+			TRACE(ft_t_warn, "Geeh! There is something wrong:\n"
+			      KERN_INFO "QIC compression (Rev = K): %x\n"
+			      KERN_INFO "QIC compression (Rev > K): %x",
+			      entry[VTBL_K_CMPR], entry[VTBL_CMPR]);
+		}
+	}
+	TRACE_EXIT;
+}
+
+/* extract the volume table from buffer. "buffer" must already contain
+ * the vtbl-segment 
+ */
+int zft_extract_volume_headers(__u8 *buffer)
+{                            
+        __u8 *entry;
+	TRACE_FUN(ft_t_flow);
+	
+	zft_init_vtbl();
+	entry = buffer;
+#ifdef ZFT_CMAP_HACK
+	if ((strncmp(&entry[VTBL_EXT+EXT_ZFTAPE_SIG], ZFTAPE_SIG,
+		     strlen(ZFTAPE_SIG)) == 0) &&
+	    entry[VTBL_EXT+EXT_ZFTAPE_CMAP] != 0) {
+		TRACE(ft_t_noise, "ignoring cmap volume");
+		entry += VTBL_SIZE;
+	} 
+#endif
+	/* the end of the vtbl is indicated by an invalid signature 
+	 */
+	while (vtbl_signature_valid(&entry[VTBL_SIG]) &&
+	       (entry - buffer) < FT_SEGMENT_SIZE) {
+		zft_new_vtbl_entry();
+		if (ft_format_code == fmt_big) {
+			/* SCSI like vtbl, stores only the number of
+			 * segments used 
+			 */
+			unsigned int num_segments= GET4(entry, VTBL_SCSI_SEGS);
+			zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg;
+			zft_last_vtbl->end_seg = 
+				zft_last_vtbl->start_seg + num_segments - 1;
+		} else {
+			/* `normal', QIC-80 like vtbl 
+			 */
+			zft_last_vtbl->start_seg = GET2(entry, VTBL_START);
+			zft_last_vtbl->end_seg   = GET2(entry, VTBL_END);
+		}
+		zft_eom_vtbl->start_seg  = zft_last_vtbl->end_seg + 1;
+		/* check if we created this volume and get the
+		 * blk_sz 
+		 */
+		zft_last_vtbl->zft_volume = check_volume(entry, zft_last_vtbl);
+		if (zft_last_vtbl->zft_volume == 0) {
+			extract_alien_volume(entry, zft_last_vtbl);
+		} else {
+			extract_zft_volume(entry, zft_last_vtbl);
+		}
+		DUMP_VOLINFO(ft_t_noise, &entry[VTBL_DESC], zft_last_vtbl);
+		entry +=VTBL_SIZE;
+	}
+#if 0
+/*
+ *  undefine to test end of tape handling
+ */
+	zft_new_vtbl_entry();
+	zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg;
+	zft_last_vtbl->end_seg   = ft_last_data_segment - 10;
+	zft_last_vtbl->blk_sz          = zft_blk_sz;
+	zft_last_vtbl->zft_volume      = 1;
+	zft_last_vtbl->qic113          = zft_qic113;
+	zft_last_vtbl->size = (zft_calc_tape_pos(zft_last_vtbl->end_seg+1)
+			       - zft_calc_tape_pos(zft_last_vtbl->start_seg));
+#endif
+	TRACE_EXIT 0;
+}
+
+/* this functions translates the failed_sector_log, misused as
+ * EOF-marker list, into a virtual volume table. The table mustn't be
+ * written to tape, because this would occupy the first data segment,
+ * which should be the volume table, but is actually the first segment
+ * that is filled with data (when using standard ftape).  We assume,
+ * that we get a non-empty failed_sector_log.
+ */
+int zft_fake_volume_headers (eof_mark_union *eof_map, int num_failed_sectors)
+{
+	unsigned int segment, sector;
+	int have_eom = 0;
+	int vol_no;
+	TRACE_FUN(ft_t_flow);
+
+	if ((num_failed_sectors >= 2) &&
+	    (GET2(&eof_map[num_failed_sectors - 1].mark.segment, 0) 
+	     == 
+	     GET2(&eof_map[num_failed_sectors - 2].mark.segment, 0) + 1) &&
+	    (GET2(&eof_map[num_failed_sectors - 1].mark.date, 0) == 1)) {
+		/* this should be eom. We keep the remainder of the
+		 * tape as another volume.
+		 */
+		have_eom = 1;
+	}
+	zft_init_vtbl();
+	zft_eom_vtbl->start_seg = ft_first_data_segment;
+	for(vol_no = 0; vol_no < num_failed_sectors - have_eom; vol_no ++) {
+		zft_new_vtbl_entry();
+
+		segment = GET2(&eof_map[vol_no].mark.segment, 0);
+		sector  = GET2(&eof_map[vol_no].mark.date, 0);
+
+		zft_last_vtbl->start_seg  = zft_eom_vtbl->start_seg;
+		zft_last_vtbl->end_seg    = segment;
+		zft_eom_vtbl->start_seg  = segment + 1;
+		zft_last_vtbl->blk_sz     = 1;
+		zft_last_vtbl->size       = 
+			(zft_calc_tape_pos(zft_last_vtbl->end_seg)
+			 - zft_calc_tape_pos(zft_last_vtbl->start_seg)
+			 + (sector-1) * FT_SECTOR_SIZE);
+		TRACE(ft_t_noise, 
+		      "failed sector log: segment: %d, sector: %d", 
+		      segment, sector);
+		DUMP_VOLINFO(ft_t_noise, "Faked volume", zft_last_vtbl);
+	}
+	if (!have_eom) {
+		zft_new_vtbl_entry();
+		zft_last_vtbl->start_seg = zft_eom_vtbl->start_seg;
+		zft_last_vtbl->end_seg   = ft_last_data_segment;
+		zft_eom_vtbl->start_seg  = ft_last_data_segment + 1;
+		zft_last_vtbl->size      = zft_capacity;
+		zft_last_vtbl->size     -= zft_calc_tape_pos(zft_last_vtbl->start_seg);
+		zft_last_vtbl->blk_sz    = 1;
+		DUMP_VOLINFO(ft_t_noise, "Faked volume",zft_last_vtbl);
+	}
+	TRACE_EXIT 0;
+}
+
+/* update the internal volume table
+ *
+ * if before start of last volume: erase all following volumes if
+ * inside a volume: set end of volume to infinity
+ *
+ * this function is intended to be called every time _ftape_write() is
+ * called
+ *
+ * return: 0 if no new volume was created, 1 if a new volume was
+ * created
+ *
+ * NOTE: we don't need to check for zft_mode as ftape_write() does
+ * that already. This function gets never called without accessing
+ * zftape via the *qft* devices 
+ */
+
+int zft_open_volume(zft_position *pos, int blk_sz, int use_compression)
+{ 
+	TRACE_FUN(ft_t_flow);
+	
+	if (!zft_qic_mode) {
+		TRACE_EXIT 0;
+	}
+	if (zft_tape_at_lbot(pos)) {
+		zft_init_vtbl();
+		if(zft_old_ftape) {
+			/* clear old ftape's eof marks */
+			zft_clear_ftape_file_marks();
+			zft_old_ftape = 0; /* no longer old ftape */
+		}
+		zft_reset_position(pos);
+	}
+	if (pos->seg_pos != zft_last_vtbl->end_seg + 1) {
+		TRACE_ABORT(-EIO, ft_t_bug, 
+		      "BUG: seg_pos: %d, zft_last_vtbl->end_seg: %d", 
+		      pos->seg_pos, zft_last_vtbl->end_seg);
+	}                            
+	TRACE(ft_t_noise, "create new volume");
+	if (zft_eom_vtbl->count >= ZFT_MAX_VOLUMES) {
+		TRACE_ABORT(-ENOSPC, ft_t_err,
+			    "Error: maxmimal number of volumes exhausted "
+			    "(maxmimum is %d)", ZFT_MAX_VOLUMES);
+	}
+	zft_new_vtbl_entry();
+	pos->volume_pos = pos->seg_byte_pos = 0;
+	zft_last_vtbl->start_seg       = pos->seg_pos;
+	zft_last_vtbl->end_seg         = ft_last_data_segment; /* infinity */
+	zft_last_vtbl->blk_sz          = blk_sz;
+	zft_last_vtbl->size            = zft_capacity;
+	zft_last_vtbl->zft_volume      = 1;
+	zft_last_vtbl->use_compression = use_compression;
+	zft_last_vtbl->qic113          = zft_qic113;
+	zft_last_vtbl->new_volume      = 1;
+	zft_last_vtbl->open            = 1;
+	zft_volume_table_changed = 1;
+	zft_eom_vtbl->start_seg  = ft_last_data_segment + 1;
+	TRACE_EXIT 0;
+}
+
+/*  perform mtfsf, mtbsf, not allowed without zft_qic_mode
+ */
+int zft_skip_volumes(int count, zft_position *pos)
+{ 
+	const zft_volinfo *vtbl;
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_noise, "count: %d", count);
+	
+	vtbl= zft_find_volume(pos->seg_pos);
+	while (count > 0 && vtbl != zft_eom_vtbl) {
+		vtbl = list_entry(vtbl->node.next, zft_volinfo, node);
+		count --;
+	}
+	while (count < 0 && vtbl != zft_first_vtbl) {
+		vtbl = list_entry(vtbl->node.prev, zft_volinfo, node);
+		count ++;
+	}
+	pos->seg_pos        = vtbl->start_seg;
+	pos->seg_byte_pos   = 0;
+	pos->volume_pos     = 0;
+	pos->tape_pos       = zft_calc_tape_pos(pos->seg_pos);
+	zft_just_before_eof = vtbl->size == 0;
+	if (zft_cmpr_ops) {
+		(*zft_cmpr_ops->reset)();
+	}
+	zft_deblock_segment = -1; /* no need to keep cache */
+	TRACE(ft_t_noise, "repositioning to:\n"
+	      KERN_INFO "zft_seg_pos        : %d\n"
+	      KERN_INFO "zft_seg_byte_pos   : %d\n"
+	      KERN_INFO "zft_tape_pos       : " LL_X "\n"
+	      KERN_INFO "zft_volume_pos     : " LL_X "\n"
+	      KERN_INFO "file number        : %d",
+	      pos->seg_pos, pos->seg_byte_pos, 
+	      LL(pos->tape_pos), LL(pos->volume_pos), vtbl->count);
+	zft_resid = count < 0 ? -count : count;
+	TRACE_EXIT zft_resid ? -EINVAL : 0;
+}
+
+/* the following simply returns the raw data position of the EOM
+ * marker, MTIOCSIZE ioctl 
+ */
+__s64 zft_get_eom_pos(void)
+{
+	if (zft_qic_mode) {
+		return zft_calc_tape_pos(zft_eom_vtbl->start_seg);
+	} else {
+		/* there is only one volume in raw mode */
+		return zft_capacity;
+	}
+}
+
+/* skip to eom, used for MTEOM
+ */
+void zft_skip_to_eom(zft_position *pos)
+{
+	TRACE_FUN(ft_t_flow);
+	pos->seg_pos      = zft_eom_vtbl->start_seg;
+	pos->seg_byte_pos = 
+		pos->volume_pos     = 
+		zft_just_before_eof = 0;
+	pos->tape_pos = zft_calc_tape_pos(pos->seg_pos);
+	TRACE(ft_t_noise, "ftape positioned to segment %d, data pos " LL_X, 
+	      pos->seg_pos, LL(pos->tape_pos));
+	TRACE_EXIT;
+}
+
+/*  write an EOF-marker by setting zft_last_vtbl->end_seg to seg_pos.
+ *  NOTE: this function assumes that zft_last_vtbl points to a valid
+ *  vtbl entry
+ *
+ *  NOTE: this routine always positions before the EOF marker
+ */
+int zft_close_volume(zft_position *pos)
+{
+	TRACE_FUN(ft_t_any);
+
+	if (zft_vtbl_empty || !zft_last_vtbl->open) { /* should not happen */
+		TRACE(ft_t_noise, "There are no volumes to finish");
+		TRACE_EXIT -EIO;
+	}
+	if (pos->seg_byte_pos == 0 && 
+	    pos->seg_pos != zft_last_vtbl->start_seg) {
+		pos->seg_pos --;
+		pos->seg_byte_pos      = zft_get_seg_sz(pos->seg_pos);
+	}
+	zft_last_vtbl->end_seg   = pos->seg_pos;
+	zft_last_vtbl->size      = pos->volume_pos;
+	zft_volume_table_changed = 1;
+	zft_just_before_eof      = 1;
+	zft_eom_vtbl->start_seg  = zft_last_vtbl->end_seg + 1;
+	zft_last_vtbl->open      = 0; /* closed */
+	TRACE_EXIT 0;
+}
+
+/* write count file-marks at current position. 
+ *
+ *  The tape is positioned after the eof-marker, that is at byte 0 of
+ *  the segment following the eof-marker
+ *
+ *  this function is only allowed in zft_qic_mode
+ *
+ *  Only allowed when tape is at BOT or EOD.
+ */
+int zft_weof(unsigned int count, zft_position *pos)
+{
+	
+	TRACE_FUN(ft_t_flow);
+
+	if (!count) { /* write zero EOF marks should be a real no-op */
+		TRACE_EXIT 0;
+	}
+	zft_volume_table_changed = 1;
+	if (zft_tape_at_lbot(pos)) {
+		zft_init_vtbl();
+		if(zft_old_ftape) {
+			/* clear old ftape's eof marks */
+			zft_clear_ftape_file_marks();
+			zft_old_ftape = 0;    /* no longer old ftape */
+		}
+	}
+	if (zft_last_vtbl->open) {
+		zft_close_volume(pos);
+		zft_move_past_eof(pos);
+		count --;
+	}
+	/* now it's easy, just append eof-marks, that is empty
+	 * volumes, to the end of the already recorded media.
+	 */
+	while (count > 0 && 
+	       pos->seg_pos <= ft_last_data_segment && 
+	       zft_eom_vtbl->count < ZFT_MAX_VOLUMES) {
+		TRACE(ft_t_noise,
+		      "Writing zero sized file at segment %d", pos->seg_pos);
+		zft_new_vtbl_entry();
+		zft_last_vtbl->start_seg       = pos->seg_pos;
+		zft_last_vtbl->end_seg         = pos->seg_pos;
+		zft_last_vtbl->size            = 0;
+		zft_last_vtbl->blk_sz          = zft_blk_sz;
+		zft_last_vtbl->zft_volume      = 1;
+		zft_last_vtbl->use_compression = 0;
+		pos->tape_pos += zft_get_seg_sz(pos->seg_pos);
+		zft_eom_vtbl->start_seg = ++ pos->seg_pos;
+		count --;
+	} 
+	if (count > 0) {
+		/*  there are two possibilities: end of tape, or the
+		 *  maximum number of files is exhausted.
+		 */
+		zft_resid = count;
+		TRACE(ft_t_noise,"Number of marks NOT written: %d", zft_resid);
+		if (zft_eom_vtbl->count == ZFT_MAX_VOLUMES) {
+			TRACE_ABORT(-EINVAL, ft_t_warn,
+				    "maximum allowed number of files "
+				    "exhausted: %d", ZFT_MAX_VOLUMES);
+		} else {
+			TRACE_ABORT(-ENOSPC,
+				    ft_t_noise, "reached end of tape");
+		}
+	}
+	TRACE_EXIT 0;
+}
+
+const zft_volinfo *zft_find_volume(unsigned int seg_pos)
+{
+	TRACE_FUN(ft_t_flow);
+	
+	TRACE(ft_t_any, "called with seg_pos %d",seg_pos);
+	if (!zft_qic_mode) {
+		if (seg_pos > ft_last_data_segment) {
+			TRACE_EXIT &eot_vtbl;
+		}
+		tape_vtbl.blk_sz =  zft_blk_sz;
+		TRACE_EXIT &tape_vtbl;
+	}
+	if (seg_pos < zft_first_vtbl->start_seg) {
+		TRACE_EXIT (cur_vtbl = zft_first_vtbl);
+	}
+	while (seg_pos > cur_vtbl->end_seg) {
+		cur_vtbl = list_entry(cur_vtbl->node.next, zft_volinfo, node);
+		TRACE(ft_t_noise, "%d - %d", cur_vtbl->start_seg, cur_vtbl->end_seg);
+	}
+	while (seg_pos < cur_vtbl->start_seg) {
+		cur_vtbl = list_entry(cur_vtbl->node.prev, zft_volinfo, node);
+		TRACE(ft_t_noise, "%d - %d", cur_vtbl->start_seg, cur_vtbl->end_seg);
+	}
+	if (seg_pos > cur_vtbl->end_seg || seg_pos < cur_vtbl->start_seg) {
+		TRACE(ft_t_bug, "This cannot happen");
+	}
+	DUMP_VOLINFO(ft_t_noise, "", cur_vtbl);
+	TRACE_EXIT cur_vtbl;
+}
+
+/* this function really assumes that we are just before eof
+ */
+void zft_move_past_eof(zft_position *pos)
+{ 
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_noise, "old seg. pos: %d", pos->seg_pos);
+	pos->tape_pos += zft_get_seg_sz(pos->seg_pos++) - pos->seg_byte_pos;
+	pos->seg_byte_pos = 0;
+	pos->volume_pos   = 0;
+	if (zft_cmpr_ops) {
+		(*zft_cmpr_ops->reset)();
+	}
+	zft_just_before_eof =  0;
+	zft_deblock_segment = -1; /* no need to cache it anymore */
+	TRACE(ft_t_noise, "new seg. pos: %d", pos->seg_pos);
+	TRACE_EXIT;
+}
diff --git a/drivers/char/ftape/zftape/zftape-vtbl.h b/drivers/char/ftape/zftape/zftape-vtbl.h
new file mode 100644
index 0000000..f31d196
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-vtbl.h
@@ -0,0 +1,227 @@
+#ifndef _ZFTAPE_VTBL_H
+#define _ZFTAPE_VTBL_H
+
+/*
+ *      Copyright (c) 1995-1997  Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-vtbl.h,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/28 14:30:09 $
+ *
+ *      This file defines a volume table as defined in the QIC-80
+ *      development standards.
+ */
+
+#include <linux/list.h>
+
+#include "../lowlevel/ftape-tracing.h"
+
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-rw.h"
+
+#define VTBL_SIZE 128 /* bytes */
+
+/* The following are offsets in the vtbl.  */
+#define VTBL_SIG   0
+#define VTBL_START 4
+#define VTBL_END   6
+#define VTBL_DESC  8
+#define VTBL_DATE  52
+#define VTBL_FLAGS 56
+#define VTBL_FL_VENDOR_SPECIFIC (1<<0)
+#define VTBL_FL_MUTLI_CARTRIDGE (1<<1)
+#define VTBL_FL_NOT_VERIFIED    (1<<2)
+#define VTBL_FL_REDIR_INHIBIT   (1<<3)
+#define VTBL_FL_SEG_SPANNING    (1<<4)
+#define VTBL_FL_DIRECTORY_LAST  (1<<5)
+#define VTBL_FL_RESERVED_6      (1<<6)
+#define VTBL_FL_RESERVED_7      (1<<7)
+#define VTBL_M_NO  57
+#define VTBL_EXT   58
+#define EXT_ZFTAPE_SIG     0
+#define EXT_ZFTAPE_BLKSZ  10
+#define EXT_ZFTAPE_CMAP   12
+#define EXT_ZFTAPE_QIC113 13
+#define VTBL_PWD   84
+#define VTBL_DIR_SIZE 92
+#define VTBL_DATA_SIZE 96
+#define VTBL_OS_VERSION 104
+#define VTBL_SRC_DRIVE  106
+#define VTBL_DEV        122
+#define VTBL_RESERVED_1 123
+#define VTBL_CMPR       124
+#define VTBL_CMPR_UNREG 0x3f
+#define VTBL_CMPR_USED  0x80
+#define VTBL_FMT        125
+#define VTBL_RESERVED_2 126
+#define VTBL_RESERVED_3 127
+/* compatibility with pre revision K */
+#define VTBL_K_CMPR     120 
+
+/*  the next is used by QIC-3020 tapes with format code 6 (>2^16
+ *  segments) It is specified in QIC-113, Rev. G, Section 5 (SCSI
+ *  volume table). The difference is simply, that we only store the
+ *  number of segments used, not the starting segment.
+ */
+#define VTBL_SCSI_SEGS  4 /* is a 4 byte value */
+
+/*  one vtbl is 128 bytes, that results in a maximum number of
+ *  29*1024/128 = 232 volumes.
+ */
+#define ZFT_MAX_VOLUMES (FT_SEGMENT_SIZE/VTBL_SIZE)
+#define VTBL_ID  "VTBL"
+#define VTBL_IDS { VTBL_ID, "XTBL", "UTID", "EXVT" } /* other valid ids */
+#define ZFT_VOL_NAME "zftape volume" /* volume label used by me */
+#define ZFTAPE_SIG "LINUX ZFT"
+
+/*  global variables
+ */
+typedef struct zft_internal_vtbl
+{
+	struct list_head node;
+	int          count;
+	unsigned int start_seg;         /* 32 bits are enough for now */
+	unsigned int end_seg;           /* 32 bits are enough for now */
+	__s64        size;              /* uncompressed size */
+        unsigned int blk_sz;            /* block size for this volume */
+	unsigned int zft_volume     :1; /* zftape created this volume */
+	unsigned int use_compression:1; /* compressed volume  */
+	unsigned int qic113         :1; /* layout of compressed block
+					 * info and vtbl conforms to
+					 * QIC-113, Rev. G 
+					 */
+	unsigned int new_volume     :1; /* it was created by us, this
+					 * run.  this allows the
+					 * fields that aren't really
+					 * used by zftape to be filled
+					 * in by some user level
+					 * program.
+					 */
+	unsigned int open           :1; /* just in progress of being 
+					 * written
+					 */
+} zft_volinfo;
+
+extern struct list_head zft_vtbl;
+#define zft_head_vtbl  list_entry(zft_vtbl.next, zft_volinfo, node)
+#define zft_eom_vtbl   list_entry(zft_vtbl.prev, zft_volinfo, node)
+#define zft_last_vtbl  list_entry(zft_eom_vtbl->node.prev, zft_volinfo, node)
+#define zft_first_vtbl list_entry(zft_head_vtbl->node.next, zft_volinfo, node)
+#define zft_vtbl_empty (zft_eom_vtbl->node.prev == &zft_head_vtbl->node)
+
+#define DUMP_VOLINFO(level, desc, info)					\
+{									\
+	char tmp[21];							\
+	strlcpy(tmp, desc, sizeof(tmp));				\
+	TRACE(level, "Volume %d:\n"					\
+	      KERN_INFO "description  : %s\n"				\
+	      KERN_INFO "first segment: %d\n"				\
+	      KERN_INFO "last  segment: %d\n"				\
+	      KERN_INFO "size         : " LL_X "\n"			\
+	      KERN_INFO "block size   : %d\n"				\
+	      KERN_INFO "compression  : %d\n"				\
+	      KERN_INFO "zftape volume: %d\n"				\
+	      KERN_INFO "QIC-113 conf.: %d",				\
+	      (info)->count, tmp, (info)->start_seg, (info)->end_seg,	\
+	      LL((info)->size), (info)->blk_sz,				\
+	      (info)->use_compression != 0, (info)->zft_volume != 0,	\
+	      (info)->qic113 != 0);					\
+}
+
+extern int zft_qic_mode;
+extern int zft_old_ftape;
+extern int zft_volume_table_changed;
+
+/* exported functions */
+extern void  zft_init_vtbl             (void);
+extern void  zft_free_vtbl             (void);
+extern int   zft_extract_volume_headers(__u8 *buffer);
+extern int   zft_update_volume_table   (unsigned int segment);
+extern int   zft_open_volume           (zft_position *pos,
+					int blk_sz, int use_compression);
+extern int   zft_close_volume          (zft_position *pos);
+extern const zft_volinfo *zft_find_volume(unsigned int seg_pos);
+extern int   zft_skip_volumes          (int count, zft_position *pos);
+extern __s64 zft_get_eom_pos           (void);
+extern void  zft_skip_to_eom           (zft_position *pos);
+extern int   zft_fake_volume_headers   (eof_mark_union *eof_map, 
+					int num_failed_sectors);
+extern int   zft_weof                  (unsigned int count, zft_position *pos);
+extern void  zft_move_past_eof         (zft_position *pos);
+
+static inline int   zft_tape_at_eod         (const zft_position *pos);
+static inline int   zft_tape_at_lbot        (const zft_position *pos);
+static inline void  zft_position_before_eof (zft_position *pos, 
+					     const zft_volinfo *volume);
+static inline __s64 zft_check_for_eof(const zft_volinfo *vtbl,
+				      const zft_position *pos);
+
+/* this function decrements the zft_seg_pos counter if we are right
+ * at the beginning of a segment. This is to handle fsfm/bsfm -- we
+ * need to position before the eof mark.  NOTE: zft_tape_pos is not
+ * changed 
+ */
+static inline void zft_position_before_eof(zft_position *pos, 
+					   const zft_volinfo *volume)
+{ 
+	TRACE_FUN(ft_t_flow);
+
+	if (pos->seg_pos == volume->end_seg + 1 &&  pos->seg_byte_pos == 0) {
+		pos->seg_pos --;
+		pos->seg_byte_pos = zft_get_seg_sz(pos->seg_pos);
+	}
+	TRACE_EXIT;
+}
+
+/*  Mmmh. Is the position at the end of the last volume, that is right
+ *  before the last EOF mark also logical an EOD condition?
+ */
+static inline int zft_tape_at_eod(const zft_position *pos)
+{ 
+	TRACE_FUN(ft_t_any);
+
+	if (zft_qic_mode) {
+		TRACE_EXIT (pos->seg_pos >= zft_eom_vtbl->start_seg ||
+			    zft_last_vtbl->open);
+	} else {
+		TRACE_EXIT pos->seg_pos > ft_last_data_segment;
+	}
+}
+
+static inline int zft_tape_at_lbot(const zft_position *pos)
+{
+	if (zft_qic_mode) {
+		return (pos->seg_pos <= zft_first_vtbl->start_seg &&
+			pos->volume_pos == 0);
+	} else {
+		return (pos->seg_pos <= ft_first_data_segment && 
+			pos->volume_pos == 0);
+	}
+}
+
+/* This one checks for EOF.  return remaing space (may be negative) 
+ */
+static inline __s64 zft_check_for_eof(const zft_volinfo *vtbl,
+				      const zft_position *pos)
+{     
+	return (__s64)(vtbl->size - pos->volume_pos);
+}
+
+#endif /* _ZFTAPE_VTBL_H */
diff --git a/drivers/char/ftape/zftape/zftape-write.c b/drivers/char/ftape/zftape/zftape-write.c
new file mode 100644
index 0000000..94327b8
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-write.c
@@ -0,0 +1,483 @@
+/*
+ *      Copyright (C) 1996, 1997 Claus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-write.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/11/06 00:50:29 $
+ *
+ *      This file contains the writing code
+ *      for the QIC-117 floppy-tape driver for Linux.
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+
+#include <linux/zftape.h>
+
+#include <asm/uaccess.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-eof.h"
+#include "../zftape/zftape-ctl.h"
+#include "../zftape/zftape-write.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-rw.h"
+#include "../zftape/zftape-vtbl.h"
+
+/*      Global vars.
+ */
+
+/*      Local vars.
+ */
+static int last_write_failed;
+static int need_flush;
+
+void zft_prevent_flush(void)
+{
+	need_flush = 0;
+}
+
+static int zft_write_header_segments(__u8* buffer)
+{
+	int header_1_ok = 0;
+	int header_2_ok = 0;
+	unsigned int time_stamp;
+	TRACE_FUN(ft_t_noise);
+	
+	TRACE_CATCH(ftape_abort_operation(),);
+	ftape_seek_to_bot();    /* prevents extra rewind */
+	if (GET4(buffer, 0) != FT_HSEG_MAGIC) {
+		TRACE_ABORT(-EIO, ft_t_err,
+			    "wrong header signature found, aborting");
+	} 
+	/*   Be optimistic: */
+	PUT4(buffer, FT_SEG_CNT,
+	     zft_written_segments + GET4(buffer, FT_SEG_CNT) + 2);
+	if ((time_stamp = zft_get_time()) != 0) {
+		PUT4(buffer, FT_WR_DATE, time_stamp);
+		if (zft_label_changed) {
+			PUT4(buffer, FT_LABEL_DATE, time_stamp);
+		}
+	}
+	TRACE(ft_t_noise,
+	      "writing first header segment %d", ft_header_segment_1);
+	header_1_ok = zft_verify_write_segments(ft_header_segment_1, 
+						buffer, FT_SEGMENT_SIZE,
+						zft_deblock_buf) >= 0;
+	TRACE(ft_t_noise,
+	      "writing second header segment %d", ft_header_segment_2);
+	header_2_ok = zft_verify_write_segments(ft_header_segment_2, 
+						buffer, FT_SEGMENT_SIZE,
+						zft_deblock_buf) >= 0;
+	if (!header_1_ok) {
+		TRACE(ft_t_warn, "Warning: "
+		      "update of first header segment failed");
+	}
+	if (!header_2_ok) {
+		TRACE(ft_t_warn, "Warning: "
+		      "update of second header segment failed");
+	}
+	if (!header_1_ok && !header_2_ok) {
+		TRACE_ABORT(-EIO, ft_t_err, "Error: "
+		      "update of both header segments failed.");
+	}
+	TRACE_EXIT 0;
+}
+
+int zft_update_header_segments(void)
+{
+	TRACE_FUN(ft_t_noise);
+	
+	/*  must NOT use zft_write_protected, as it also includes the
+	 *  file access mode. But we also want to update when soft
+	 *  write protection is enabled (O_RDONLY)
+	 */
+	if (ft_write_protected || zft_old_ftape) {
+		TRACE_ABORT(0, ft_t_noise, "Tape set read-only: no update");
+	} 
+	if (!zft_header_read) {
+		TRACE_ABORT(0, ft_t_noise, "Nothing to update");
+	}
+	if (!zft_header_changed) {
+		zft_header_changed = zft_written_segments > 0;
+	}
+	if (!zft_header_changed && !zft_volume_table_changed) {
+		TRACE_ABORT(0, ft_t_noise, "Nothing to update");
+	}
+	TRACE(ft_t_noise, "Updating header segments");
+	if (ftape_get_status()->fti_state == writing) {
+		TRACE_CATCH(ftape_loop_until_writes_done(),);
+	}
+	TRACE_CATCH(ftape_abort_operation(),);
+	
+	zft_deblock_segment = -1; /* invalidate the cache */
+	if (zft_header_changed) {
+		TRACE_CATCH(zft_write_header_segments(zft_hseg_buf),);
+	}
+	if (zft_volume_table_changed) {
+		TRACE_CATCH(zft_update_volume_table(ft_first_data_segment),);
+	}
+	zft_header_changed =
+		zft_volume_table_changed = 
+		zft_label_changed        =
+		zft_written_segments     = 0;
+	TRACE_CATCH(ftape_abort_operation(),);
+	ftape_seek_to_bot();
+	TRACE_EXIT 0;
+}
+
+static int read_merge_buffer(int seg_pos, __u8 *buffer, int offset, int seg_sz)
+{
+	int result = 0;
+	const ft_trace_t old_tracing = TRACE_LEVEL;
+	TRACE_FUN(ft_t_flow);
+	
+	if (zft_qic_mode) {
+		/*  writing in the middle of a volume is NOT allowed
+		 *
+		 */
+		TRACE(ft_t_noise, "No need to read a segment");
+		memset(buffer + offset, 0, seg_sz - offset);
+		TRACE_EXIT 0;
+	}
+	TRACE(ft_t_any, "waiting");
+	ftape_start_writing(FT_WR_MULTI);
+	TRACE_CATCH(ftape_loop_until_writes_done(),);
+	
+	TRACE(ft_t_noise, "trying to read segment %d from offset %d",
+	      seg_pos, offset);
+	SET_TRACE_LEVEL(ft_t_bug);
+	result = zft_fetch_segment_fraction(seg_pos, buffer, 
+					    FT_RD_SINGLE,
+					    offset, seg_sz - offset);
+	SET_TRACE_LEVEL(old_tracing);
+	if (result != (seg_sz - offset)) {
+		TRACE(ft_t_noise, "Ignore error: read_segment() result: %d",
+		      result);
+		memset(buffer + offset, 0, seg_sz - offset);
+	}
+	TRACE_EXIT 0;
+}
+
+/* flush the write buffer to tape and write an eof-marker at the
+ * current position if not in raw mode.  This function always
+ * positions the tape before the eof-marker.  _ftape_close() should
+ * then advance to the next segment.
+ *
+ * the parameter "finish_volume" describes whether to position before
+ * or after the possibly created file-mark. We always position after
+ * the file-mark when called from ftape_close() and a flush was needed
+ * (that is ftape_write() was the last tape operation before calling
+ * ftape_flush) But we always position before the file-mark when this
+ * function get's called from outside ftape_close() 
+ */
+int zft_flush_buffers(void)
+{
+	int result;
+	int data_remaining;
+	int this_segs_size;
+	TRACE_FUN(ft_t_flow);
+
+	TRACE(ft_t_data_flow,
+	      "entered, ftape_state = %d", ftape_get_status()->fti_state);
+	if (ftape_get_status()->fti_state != writing && !need_flush) {
+		TRACE_ABORT(0, ft_t_noise, "no need for flush");
+	}
+	zft_io_state = zft_idle; /*  triggers some initializations for the
+				  *  read and write routines 
+				  */
+	if (last_write_failed) {
+		ftape_abort_operation();
+		TRACE_EXIT -EIO;
+	}
+	TRACE(ft_t_noise, "flushing write buffers");
+	this_segs_size = zft_get_seg_sz(zft_pos.seg_pos);
+	if (this_segs_size == zft_pos.seg_byte_pos) {
+		zft_pos.seg_pos ++;
+		data_remaining = zft_pos.seg_byte_pos = 0;
+	} else {
+		data_remaining = zft_pos.seg_byte_pos;
+	}
+	/* If there is any data not written to tape yet, append zero's
+	 * up to the end of the sector (if using compression) or merge
+	 * it with the data existing on the tape Then write the
+	 * segment(s) to tape.
+	 */
+	TRACE(ft_t_noise, "Position:\n"
+	      KERN_INFO "seg_pos  : %d\n"
+	      KERN_INFO "byte pos : %d\n"
+	      KERN_INFO "remaining: %d",
+	      zft_pos.seg_pos, zft_pos.seg_byte_pos, data_remaining);
+	if (data_remaining > 0) {
+		do {
+			this_segs_size = zft_get_seg_sz(zft_pos.seg_pos);
+			if (this_segs_size > data_remaining) {
+				TRACE_CATCH(read_merge_buffer(zft_pos.seg_pos,
+							      zft_deblock_buf,
+							      data_remaining,
+							      this_segs_size),
+					    last_write_failed = 1);
+			}
+			result = ftape_write_segment(zft_pos.seg_pos, 
+						     zft_deblock_buf,
+						     FT_WR_MULTI);
+			if (result != this_segs_size) {
+				TRACE(ft_t_err, "flush buffers failed");
+				zft_pos.tape_pos    -= zft_pos.seg_byte_pos;
+				zft_pos.seg_byte_pos = 0;
+
+				last_write_failed = 1;
+				TRACE_EXIT result;
+			}
+			zft_written_segments ++;
+			TRACE(ft_t_data_flow,
+			      "flush, moved out buffer: %d", result);
+			/* need next segment for more data (empty segments?)
+			 */
+			if (result < data_remaining) { 
+				if (result > 0) {       
+					/* move remainder to buffer beginning 
+					 */
+					memmove(zft_deblock_buf, 
+						zft_deblock_buf + result,
+						FT_SEGMENT_SIZE - result);
+				}
+			} 
+			data_remaining -= result;
+			zft_pos.seg_pos ++;
+		} while (data_remaining > 0);
+		TRACE(ft_t_any, "result: %d", result);
+		zft_deblock_segment = --zft_pos.seg_pos;
+		if (data_remaining == 0) {  /* first byte next segment */
+			zft_pos.seg_byte_pos = this_segs_size;
+		} else { /* after data previous segment, data_remaining < 0 */
+			zft_pos.seg_byte_pos = data_remaining + result;
+		}
+	} else {
+		TRACE(ft_t_noise, "zft_deblock_buf empty");
+		zft_pos.seg_pos --;
+		zft_pos.seg_byte_pos = zft_get_seg_sz (zft_pos.seg_pos);
+		ftape_start_writing(FT_WR_MULTI);
+	}
+	TRACE(ft_t_any, "waiting");
+	if ((result = ftape_loop_until_writes_done()) < 0) {
+		/* that's really bad. What to to with zft_tape_pos? 
+		 */
+		TRACE(ft_t_err, "flush buffers failed");
+	}
+	TRACE(ft_t_any, "zft_seg_pos: %d, zft_seg_byte_pos: %d",
+	      zft_pos.seg_pos, zft_pos.seg_byte_pos);
+	last_write_failed  =
+		need_flush = 0;
+	TRACE_EXIT result;
+}
+
+/* return-value: the number of bytes removed from the user-buffer
+ *
+ * out: 
+ *      int *write_cnt: how much actually has been moved to the
+ *                      zft_deblock_buf
+ *      int req_len  : MUST NOT BE CHANGED, except at EOT, in 
+ *                      which case it may be adjusted
+ * in : 
+ *      char *buff        : the user buffer
+ *      int buf_pos_write : copy of buf_len_wr int
+ *      this_segs_size    : the size in bytes of the actual segment
+ *                          char
+ *      *zft_deblock_buf   : zft_deblock_buf
+ */
+static int zft_simple_write(int *cnt,
+			    __u8 *dst_buf, const int seg_sz,
+			    const __u8 __user *src_buf, const int req_len, 
+			    const zft_position *pos,const zft_volinfo *volume)
+{
+	int space_left;
+	TRACE_FUN(ft_t_flow);
+
+	/* volume->size holds the tape capacity while volume is open */
+	if (pos->tape_pos + volume->blk_sz > volume->size) {
+		TRACE_EXIT -ENOSPC;
+	}
+	/*  remaining space in this segment, NOT zft_deblock_buf
+	 */
+	space_left = seg_sz - pos->seg_byte_pos;
+	*cnt = req_len < space_left ? req_len : space_left;
+	if (copy_from_user(dst_buf + pos->seg_byte_pos, src_buf, *cnt) != 0) {
+		TRACE_EXIT -EFAULT;
+	}
+	TRACE_EXIT *cnt;
+}
+
+static int check_write_access(int req_len,
+			      const zft_volinfo **volume,
+			      zft_position *pos,
+			      const unsigned int blk_sz)
+{
+	int result;
+	TRACE_FUN(ft_t_flow);
+
+	if ((req_len % zft_blk_sz) != 0) {
+		TRACE_ABORT(-EINVAL, ft_t_info,
+			    "write-count %d must be multiple of block-size %d",
+			    req_len, blk_sz);
+	}
+	if (zft_io_state == zft_writing) {
+		/*  all other error conditions have been checked earlier
+		 */
+		TRACE_EXIT 0;
+	}
+	zft_io_state = zft_idle;
+	TRACE_CATCH(zft_check_write_access(pos),);
+	/*  If we haven't read the header segment yet, do it now.
+	 *  This will verify the configuration, get the bad sector
+	 *  table and read the volume table segment 
+	 */
+	if (!zft_header_read) {
+		TRACE_CATCH(zft_read_header_segments(),);
+	}
+	/*  fine. Now the tape is either at BOT or at EOD,
+	 *  Write start of volume now
+	 */
+	TRACE_CATCH(zft_open_volume(pos, blk_sz, zft_use_compression),);
+	*volume = zft_find_volume(pos->seg_pos);
+	DUMP_VOLINFO(ft_t_noise, "", *volume);
+	zft_just_before_eof = 0;
+	/* now merge with old data if necessary */
+	if (!zft_qic_mode && pos->seg_byte_pos != 0){
+		result = zft_fetch_segment(pos->seg_pos,
+					   zft_deblock_buf,
+					   FT_RD_SINGLE);
+		if (result < 0) {
+			if (result == -EINTR || result == -ENOSPC) {
+				TRACE_EXIT result;
+			}
+			TRACE(ft_t_noise, 
+			      "ftape_read_segment() result: %d. "
+			      "This might be normal when using "
+			      "a newly\nformatted tape", result);
+			memset(zft_deblock_buf, '\0', pos->seg_byte_pos);
+		}
+	}
+	zft_io_state = zft_writing;
+	TRACE_EXIT 0;
+}
+
+static int fill_deblock_buf(__u8 *dst_buf, const int seg_sz,
+			    zft_position *pos, const zft_volinfo *volume,
+			    const char __user *usr_buf, const int req_len)
+{
+	int cnt = 0;
+	int result = 0;
+	TRACE_FUN(ft_t_flow);
+
+	if (seg_sz == 0) {
+		TRACE_ABORT(0, ft_t_data_flow, "empty segment");
+	}
+	TRACE(ft_t_data_flow, "\n"
+	      KERN_INFO "remaining req_len: %d\n"
+	      KERN_INFO "          buf_pos: %d", 
+	      req_len, pos->seg_byte_pos);
+	/* zft_deblock_buf will not contain a valid segment any longer */
+	zft_deblock_segment = -1;
+	if (zft_use_compression) {
+		TRACE_CATCH(zft_cmpr_lock(1 /* try to load */),);
+		TRACE_CATCH(result= (*zft_cmpr_ops->write)(&cnt,
+							   dst_buf, seg_sz,
+							   usr_buf, req_len,
+							   pos, volume),);
+	} else {
+		TRACE_CATCH(result= zft_simple_write(&cnt,
+						     dst_buf, seg_sz,
+						     usr_buf, req_len,
+						     pos, volume),);
+	}
+	pos->volume_pos   += result;
+	pos->seg_byte_pos += cnt;
+	pos->tape_pos     += cnt;
+	TRACE(ft_t_data_flow, "\n"
+	      KERN_INFO "removed from user-buffer : %d bytes.\n"
+	      KERN_INFO "copied to zft_deblock_buf: %d bytes.\n"
+	      KERN_INFO "zft_tape_pos             : " LL_X " bytes.",
+	      result, cnt, LL(pos->tape_pos));
+	TRACE_EXIT result;
+}
+
+
+/*  called by the kernel-interface routine "zft_write()"
+ */
+int _zft_write(const char __user *buff, int req_len)
+{
+	int result = 0;
+	int written = 0;
+	int write_cnt;
+	int seg_sz;
+	static const zft_volinfo *volume = NULL;
+	TRACE_FUN(ft_t_flow);
+	
+	zft_resid         = req_len;	
+	last_write_failed = 1; /* reset to 0 when successful */
+	/* check if write is allowed 
+	 */
+	TRACE_CATCH(check_write_access(req_len, &volume,&zft_pos,zft_blk_sz),);
+	while (req_len > 0) {
+		/* Allow us to escape from this loop with a signal !
+		 */
+		FT_SIGNAL_EXIT(_DONT_BLOCK);
+		seg_sz = zft_get_seg_sz(zft_pos.seg_pos);
+		if ((write_cnt = fill_deblock_buf(zft_deblock_buf,
+						  seg_sz,
+						  &zft_pos,
+						  volume,
+						  buff,
+						  req_len)) < 0) {
+			zft_resid -= written;
+			if (write_cnt == -ENOSPC) {
+				/* leave the remainder to flush_buffers()
+				 */
+				TRACE(ft_t_info, "No space left on device");
+				last_write_failed = 0;
+				if (!need_flush) {
+					need_flush = written > 0;
+				}
+				TRACE_EXIT written > 0 ? written : -ENOSPC;
+			} else {
+				TRACE_EXIT result;
+			}
+		}
+		if (zft_pos.seg_byte_pos == seg_sz) {
+			TRACE_CATCH(ftape_write_segment(zft_pos.seg_pos, 
+							zft_deblock_buf,
+							FT_WR_ASYNC),
+				    zft_resid -= written);
+			zft_written_segments ++;
+			zft_pos.seg_byte_pos =  0;
+			zft_deblock_segment  = zft_pos.seg_pos;
+			++zft_pos.seg_pos;
+		}
+		written += write_cnt;
+		buff    += write_cnt;
+		req_len -= write_cnt;
+	} /* while (req_len > 0) */
+	TRACE(ft_t_data_flow, "remaining in blocking buffer: %d",
+	       zft_pos.seg_byte_pos);
+	TRACE(ft_t_data_flow, "just written bytes: %d", written);
+	last_write_failed = 0;
+	zft_resid -= written;
+	need_flush = need_flush || written > 0;
+	TRACE_EXIT written;               /* bytes written */
+}
diff --git a/drivers/char/ftape/zftape/zftape-write.h b/drivers/char/ftape/zftape/zftape-write.h
new file mode 100644
index 0000000..ea88701
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape-write.h
@@ -0,0 +1,38 @@
+#ifndef _ZFTAPE_WRITE_H
+#define _ZFTAPE_WRITE_H
+
+/*
+ * Copyright (C) 1996, 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape-write.h,v $
+ * $Revision: 1.2 $
+ * $Date: 1997/10/05 19:19:13 $
+ *
+ *      This file contains the definitions for the write functions
+ *      for the zftape driver for Linux.
+ *
+ */
+
+extern int  zft_flush_buffers(void);
+extern int  zft_update_header_segments(void);
+extern void zft_prevent_flush(void);
+
+/*  hook for the VFS interface 
+ */
+extern int _zft_write(const char __user *buff, int req_len);
+#endif /* _ZFTAPE_WRITE_H */
diff --git a/drivers/char/ftape/zftape/zftape_syms.c b/drivers/char/ftape/zftape/zftape_syms.c
new file mode 100644
index 0000000..2db1401
--- /dev/null
+++ b/drivers/char/ftape/zftape/zftape_syms.c
@@ -0,0 +1,43 @@
+/*
+ *      Copyright (C) 1997 Claus-Justus Heine
+
+ 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, 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; see the file COPYING.  If not, write to
+ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ *
+ * $Source: /homes/cvs/ftape-stacked/ftape/zftape/zftape_syms.c,v $
+ * $Revision: 1.3 $
+ * $Date: 1997/10/05 19:19:14 $
+ *
+ *      This file contains the symbols that the zftape frontend to 
+ *      the ftape floppy tape driver exports 
+ */		 
+
+#include <linux/module.h>
+
+#include <linux/zftape.h>
+
+#include "../zftape/zftape-init.h"
+#include "../zftape/zftape-read.h"
+#include "../zftape/zftape-buffers.h"
+#include "../zftape/zftape-ctl.h"
+
+/* zftape-init.c */
+EXPORT_SYMBOL(zft_cmpr_register);
+/* zftape-read.c */
+EXPORT_SYMBOL(zft_fetch_segment_fraction);
+/* zftape-buffers.c */
+EXPORT_SYMBOL(zft_vmalloc_once);
+EXPORT_SYMBOL(zft_vmalloc_always);
+EXPORT_SYMBOL(zft_vfree);
diff --git a/drivers/char/generic_nvram.c b/drivers/char/generic_nvram.c
new file mode 100644
index 0000000..1b5e01e
--- /dev/null
+++ b/drivers/char/generic_nvram.c
@@ -0,0 +1,145 @@
+/*
+ * Generic /dev/nvram driver for architectures providing some
+ * "generic" hooks, that is :
+ *
+ * nvram_read_byte, nvram_write_byte, nvram_sync
+ *
+ * Note that an additional hook is supported for PowerMac only
+ * for getting the nvram "partition" informations
+ *
+ */
+
+#define NVRAM_VERSION "1.1"
+
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <asm/uaccess.h>
+#include <asm/nvram.h>
+
+#define NVRAM_SIZE	8192
+
+static loff_t nvram_llseek(struct file *file, loff_t offset, int origin)
+{
+	lock_kernel();
+	switch (origin) {
+	case 1:
+		offset += file->f_pos;
+		break;
+	case 2:
+		offset += NVRAM_SIZE;
+		break;
+	}
+	if (offset < 0) {
+		unlock_kernel();
+		return -EINVAL;
+	}
+	file->f_pos = offset;
+	unlock_kernel();
+	return file->f_pos;
+}
+
+static ssize_t read_nvram(struct file *file, char __user *buf,
+			  size_t count, loff_t *ppos)
+{
+	unsigned int i;
+	char __user *p = buf;
+
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT;
+	if (*ppos >= NVRAM_SIZE)
+		return 0;
+	for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count)
+		if (__put_user(nvram_read_byte(i), p))
+			return -EFAULT;
+	*ppos = i;
+	return p - buf;
+}
+
+static ssize_t write_nvram(struct file *file, const char __user *buf,
+			   size_t count, loff_t *ppos)
+{
+	unsigned int i;
+	const char __user *p = buf;
+	char c;
+
+	if (!access_ok(VERIFY_READ, buf, count))
+		return -EFAULT;
+	if (*ppos >= NVRAM_SIZE)
+		return 0;
+	for (i = *ppos; count > 0 && i < NVRAM_SIZE; ++i, ++p, --count) {
+		if (__get_user(c, p))
+			return -EFAULT;
+		nvram_write_byte(c, i);
+	}
+	*ppos = i;
+	return p - buf;
+}
+
+static int nvram_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	switch(cmd) {
+#ifdef CONFIG_PPC_PMAC
+	case OBSOLETE_PMAC_NVRAM_GET_OFFSET:
+		printk(KERN_WARNING "nvram: Using obsolete PMAC_NVRAM_GET_OFFSET ioctl\n");
+	case IOC_NVRAM_GET_OFFSET: {
+		int part, offset;
+
+		if (_machine != _MACH_Pmac)
+			return -EINVAL;
+		if (copy_from_user(&part, (void __user*)arg, sizeof(part)) != 0)
+			return -EFAULT;
+		if (part < pmac_nvram_OF || part > pmac_nvram_NR)
+			return -EINVAL;
+		offset = pmac_get_partition(part);
+		if (copy_to_user((void __user*)arg, &offset, sizeof(offset)) != 0)
+			return -EFAULT;
+		break;
+	}
+#endif /* CONFIG_PPC_PMAC */
+	case IOC_NVRAM_SYNC:
+		nvram_sync();
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+struct file_operations nvram_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= nvram_llseek,
+	.read		= read_nvram,
+	.write		= write_nvram,
+	.ioctl		= nvram_ioctl,
+};
+
+static struct miscdevice nvram_dev = {
+	NVRAM_MINOR,
+	"nvram",
+	&nvram_fops
+};
+
+int __init nvram_init(void)
+{
+	printk(KERN_INFO "Macintosh non-volatile memory driver v%s\n",
+		NVRAM_VERSION);
+	return misc_register(&nvram_dev);
+}
+
+void __exit nvram_cleanup(void)
+{
+        misc_deregister( &nvram_dev );
+}
+
+module_init(nvram_init);
+module_exit(nvram_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/generic_serial.c b/drivers/char/generic_serial.c
new file mode 100644
index 0000000..204a730
--- /dev/null
+++ b/drivers/char/generic_serial.c
@@ -0,0 +1,1001 @@
+/*
+ *  generic_serial.c
+ *
+ *  Copyright (C) 1998/1999 R.E.Wolff@BitWizard.nl
+ *
+ *  written for the SX serial driver.
+ *     Contains the code that should be shared over all the serial drivers.
+ *
+ *  Credit for the idea to do it this way might go to Alan Cox. 
+ *
+ *
+ *  Version 0.1 -- December, 1998. Initial version.
+ *  Version 0.2 -- March, 1999.    Some more routines. Bugfixes. Etc.
+ *  Version 0.5 -- August, 1999.   Some more fixes. Reformat for Linus.
+ *
+ *  BitWizard is actively maintaining this file. We sometimes find
+ *  that someone submitted changes to this file. We really appreciate
+ *  your help, but please submit changes through us. We're doing our
+ *  best to be responsive.  -- REW
+ * */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/mm.h>
+#include <linux/generic_serial.h>
+#include <linux/interrupt.h>
+#include <linux/tty_flip.h>
+#include <linux/delay.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#define DEBUG 
+
+static char *                  tmp_buf; 
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static int gs_debug;
+
+#ifdef DEBUG
+#define gs_dprintk(f, str...) if (gs_debug & f) printk (str)
+#else
+#define gs_dprintk(f, str...) /* nothing */
+#endif
+
+#define func_enter() gs_dprintk (GS_DEBUG_FLOW, "gs: enter %s\n", __FUNCTION__)
+#define func_exit()  gs_dprintk (GS_DEBUG_FLOW, "gs: exit  %s\n", __FUNCTION__)
+#define NEW_WRITE_LOCKING 1
+#if NEW_WRITE_LOCKING
+#define DECL      /* Nothing */
+#define LOCKIT    down (& port->port_write_sem);
+#define RELEASEIT up (&port->port_write_sem);
+#else
+#define DECL      unsigned long flags;
+#define LOCKIT    save_flags (flags);cli ()
+#define RELEASEIT restore_flags (flags)
+#endif
+
+#define RS_EVENT_WRITE_WAKEUP	1
+
+module_param(gs_debug, int, 0644);
+
+
+void gs_put_char(struct tty_struct * tty, unsigned char ch)
+{
+	struct gs_port *port;
+	DECL
+
+	func_enter (); 
+
+	if (!tty) return;
+
+	port = tty->driver_data;
+
+	if (!port) return;
+
+	if (! (port->flags & ASYNC_INITIALIZED)) return;
+
+	/* Take a lock on the serial tranmit buffer! */
+	LOCKIT;
+
+	if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+		/* Sorry, buffer is full, drop character. Update statistics???? -- REW */
+		RELEASEIT;
+		return;
+	}
+
+	port->xmit_buf[port->xmit_head++] = ch;
+	port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+	port->xmit_cnt++;  /* Characters in buffer */
+
+	RELEASEIT;
+	func_exit ();
+}
+
+
+#ifdef NEW_WRITE_LOCKING
+
+/*
+> Problems to take into account are:
+>       -1- Interrupts that empty part of the buffer.
+>       -2- page faults on the access to userspace. 
+>       -3- Other processes that are also trying to do a "write". 
+*/
+
+int gs_write(struct tty_struct * tty, 
+                    const unsigned char *buf, int count)
+{
+	struct gs_port *port;
+	int c, total = 0;
+	int t;
+
+	func_enter ();
+
+	if (!tty) return 0;
+
+	port = tty->driver_data;
+
+	if (!port) return 0;
+
+	if (! (port->flags & ASYNC_INITIALIZED))
+		return 0;
+
+	/* get exclusive "write" access to this port (problem 3) */
+	/* This is not a spinlock because we can have a disk access (page 
+		 fault) in copy_from_user */
+	down (& port->port_write_sem);
+
+	while (1) {
+
+		c = count;
+ 
+		/* This is safe because we "OWN" the "head". Noone else can 
+		   change the "head": we own the port_write_sem. */
+		/* Don't overrun the end of the buffer */
+		t = SERIAL_XMIT_SIZE - port->xmit_head;
+		if (t < c) c = t;
+ 
+		/* This is safe because the xmit_cnt can only decrease. This 
+		   would increase "t", so we might copy too little chars. */
+		/* Don't copy past the "head" of the buffer */
+		t = SERIAL_XMIT_SIZE - 1 - port->xmit_cnt;
+		if (t < c) c = t;
+ 
+		/* Can't copy more? break out! */
+		if (c <= 0) break;
+
+		memcpy (port->xmit_buf + port->xmit_head, buf, c);
+
+		port -> xmit_cnt += c;
+		port -> xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE -1);
+		buf += c;
+		count -= c;
+		total += c;
+	}
+	up (& port->port_write_sem);
+
+	gs_dprintk (GS_DEBUG_WRITE, "write: interrupts are %s\n", 
+	            (port->flags & GS_TX_INTEN)?"enabled": "disabled"); 
+
+	if (port->xmit_cnt && 
+	    !tty->stopped && 
+	    !tty->hw_stopped &&
+	    !(port->flags & GS_TX_INTEN)) {
+		port->flags |= GS_TX_INTEN;
+		port->rd->enable_tx_interrupts (port);
+	}
+	func_exit ();
+	return total;
+}
+#else
+/*
+> Problems to take into account are:
+>       -1- Interrupts that empty part of the buffer.
+>       -2- page faults on the access to userspace. 
+>       -3- Other processes that are also trying to do a "write". 
+*/
+
+int gs_write(struct tty_struct * tty,
+                    const unsigned char *buf, int count)
+{
+	struct gs_port *port;
+	int c, total = 0;
+	int t;
+	unsigned long flags;
+
+	func_enter ();
+
+	/* The standard serial driver returns 0 in this case. 
+	   That sounds to me as "No error, I just didn't get to writing any
+	   bytes. Feel free to try again." 
+	   The "official" way to write n bytes from buf is:
+
+		 for (nwritten = 0;nwritten < n;nwritten += rv) {
+			 rv = write (fd, buf+nwritten, n-nwritten);
+			 if (rv < 0) break; // Error: bail out. //
+		 } 
+
+	   which will loop endlessly in this case. The manual page for write
+	   agrees with me. In practise almost everybody writes 
+	   "write (fd, buf,n);" but some people might have had to deal with 
+	   incomplete writes in the past and correctly implemented it by now... 
+	 */
+
+	if (!tty) return -EIO;
+
+	port = tty->driver_data;
+	if (!port || !port->xmit_buf || !tmp_buf)
+		return -EIO;
+
+	local_save_flags(flags);
+	while (1) {
+		cli();
+		c = count;
+
+		/* This is safe because we "OWN" the "head". Noone else can 
+		   change the "head": we own the port_write_sem. */
+		/* Don't overrun the end of the buffer */
+		t = SERIAL_XMIT_SIZE - port->xmit_head;
+		if (t < c) c = t;
+
+		/* This is safe because the xmit_cnt can only decrease. This 
+		   would increase "t", so we might copy too little chars. */
+		/* Don't copy past the "head" of the buffer */
+		t = SERIAL_XMIT_SIZE - 1 - port->xmit_cnt;
+		if (t < c) c = t;
+
+		/* Can't copy more? break out! */
+		if (c <= 0) {
+			local_restore_flags(flags);
+			break;
+		}
+		memcpy(port->xmit_buf + port->xmit_head, buf, c);
+		port->xmit_head = ((port->xmit_head + c) &
+		                   (SERIAL_XMIT_SIZE-1));
+		port->xmit_cnt += c;
+		local_restore_flags(flags);
+		buf += c;
+		count -= c;
+		total += c;
+	}
+
+	if (port->xmit_cnt && 
+	    !tty->stopped && 
+	    !tty->hw_stopped &&
+	    !(port->flags & GS_TX_INTEN)) {
+		port->flags |= GS_TX_INTEN;
+		port->rd->enable_tx_interrupts (port);
+	}
+	func_exit ();
+	return total;
+}
+
+#endif
+
+
+
+int gs_write_room(struct tty_struct * tty)
+{
+	struct gs_port *port = tty->driver_data;
+	int ret;
+
+	func_enter ();
+	ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+	func_exit ();
+	return ret;
+}
+
+
+int gs_chars_in_buffer(struct tty_struct *tty)
+{
+	struct gs_port *port = tty->driver_data;
+	func_enter ();
+
+	func_exit ();
+	return port->xmit_cnt;
+}
+
+
+static int gs_real_chars_in_buffer(struct tty_struct *tty)
+{
+	struct gs_port *port;
+	func_enter ();
+
+	if (!tty) return 0;
+	port = tty->driver_data;
+
+	if (!port->rd) return 0;
+	if (!port->rd->chars_in_buffer) return 0;
+
+	func_exit ();
+	return port->xmit_cnt + port->rd->chars_in_buffer (port);
+}
+
+
+static int gs_wait_tx_flushed (void * ptr, unsigned long timeout) 
+{
+	struct gs_port *port = ptr;
+	unsigned long end_jiffies;
+	int jiffies_to_transmit, charsleft = 0, rv = 0;
+	int rcib;
+
+	func_enter();
+
+	gs_dprintk (GS_DEBUG_FLUSH, "port=%p.\n", port);
+	if (port) {
+		gs_dprintk (GS_DEBUG_FLUSH, "xmit_cnt=%x, xmit_buf=%p, tty=%p.\n", 
+		port->xmit_cnt, port->xmit_buf, port->tty);
+	}
+
+	if (!port || port->xmit_cnt < 0 || !port->xmit_buf) {
+		gs_dprintk (GS_DEBUG_FLUSH, "ERROR: !port, !port->xmit_buf or prot->xmit_cnt < 0.\n");
+		func_exit();
+		return -EINVAL;  /* This is an error which we don't know how to handle. */
+	}
+
+	rcib = gs_real_chars_in_buffer(port->tty);
+
+	if(rcib <= 0) {
+		gs_dprintk (GS_DEBUG_FLUSH, "nothing to wait for.\n");
+		func_exit();
+		return rv;
+	}
+	/* stop trying: now + twice the time it would normally take +  seconds */
+	if (timeout == 0) timeout = MAX_SCHEDULE_TIMEOUT;
+	end_jiffies  = jiffies; 
+	if (timeout !=  MAX_SCHEDULE_TIMEOUT)
+		end_jiffies += port->baud?(2 * rcib * 10 * HZ / port->baud):0;
+	end_jiffies += timeout;
+
+	gs_dprintk (GS_DEBUG_FLUSH, "now=%lx, end=%lx (%ld).\n", 
+		    jiffies, end_jiffies, end_jiffies-jiffies); 
+
+	/* the expression is actually jiffies < end_jiffies, but that won't
+	   work around the wraparound. Tricky eh? */
+	while ((charsleft = gs_real_chars_in_buffer (port->tty)) &&
+	        time_after (end_jiffies, jiffies)) {
+		/* Units check: 
+		   chars * (bits/char) * (jiffies /sec) / (bits/sec) = jiffies!
+		   check! */
+
+		charsleft += 16; /* Allow 16 chars more to be transmitted ... */
+		jiffies_to_transmit = port->baud?(1 + charsleft * 10 * HZ / port->baud):0;
+		/*                                ^^^ Round up.... */
+		if (jiffies_to_transmit <= 0) jiffies_to_transmit = 1;
+
+		gs_dprintk (GS_DEBUG_FLUSH, "Expect to finish in %d jiffies "
+			    "(%d chars).\n", jiffies_to_transmit, charsleft); 
+
+		msleep_interruptible(jiffies_to_msecs(jiffies_to_transmit));
+		if (signal_pending (current)) {
+			gs_dprintk (GS_DEBUG_FLUSH, "Signal pending. Bombing out: "); 
+			rv = -EINTR;
+			break;
+		}
+	}
+
+	gs_dprintk (GS_DEBUG_FLUSH, "charsleft = %d.\n", charsleft); 
+	set_current_state (TASK_RUNNING);
+
+	func_exit();
+	return rv;
+}
+
+
+
+void gs_flush_buffer(struct tty_struct *tty)
+{
+	struct gs_port *port;
+	unsigned long flags;
+
+	func_enter ();
+
+	if (!tty) return;
+
+	port = tty->driver_data;
+
+	if (!port) return;
+
+	/* XXX Would the write semaphore do? */
+	spin_lock_irqsave (&port->driver_lock, flags);
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	spin_unlock_irqrestore (&port->driver_lock, flags);
+
+	wake_up_interruptible(&tty->write_wait);
+	tty_wakeup(tty);
+	func_exit ();
+}
+
+
+void gs_flush_chars(struct tty_struct * tty)
+{
+	struct gs_port *port;
+
+	func_enter ();
+
+	if (!tty) return;
+
+	port = tty->driver_data;
+
+	if (!port) return;
+
+	if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+	    !port->xmit_buf) {
+		func_exit ();
+		return;
+	}
+
+	/* Beats me -- REW */
+	port->flags |= GS_TX_INTEN;
+	port->rd->enable_tx_interrupts (port);
+	func_exit ();
+}
+
+
+void gs_stop(struct tty_struct * tty)
+{
+	struct gs_port *port;
+
+	func_enter ();
+
+	if (!tty) return;
+
+	port = tty->driver_data;
+
+	if (!port) return;
+
+	if (port->xmit_cnt && 
+	    port->xmit_buf && 
+	    (port->flags & GS_TX_INTEN) ) {
+		port->flags &= ~GS_TX_INTEN;
+		port->rd->disable_tx_interrupts (port);
+	}
+	func_exit ();
+}
+
+
+void gs_start(struct tty_struct * tty)
+{
+	struct gs_port *port;
+
+	if (!tty) return;
+
+	port = tty->driver_data;
+
+	if (!port) return;
+
+	if (port->xmit_cnt && 
+	    port->xmit_buf && 
+	    !(port->flags & GS_TX_INTEN) ) {
+		port->flags |= GS_TX_INTEN;
+		port->rd->enable_tx_interrupts (port);
+	}
+	func_exit ();
+}
+
+
+static void gs_shutdown_port (struct gs_port *port)
+{
+	unsigned long flags;
+
+	func_enter();
+	
+	if (!port) return;
+	
+	if (!(port->flags & ASYNC_INITIALIZED))
+		return;
+
+	spin_lock_irqsave(&port->driver_lock, flags);
+
+	if (port->xmit_buf) {
+		free_page((unsigned long) port->xmit_buf);
+		port->xmit_buf = NULL;
+	}
+
+	if (port->tty)
+		set_bit(TTY_IO_ERROR, &port->tty->flags);
+
+	port->rd->shutdown_port (port);
+
+	port->flags &= ~ASYNC_INITIALIZED;
+	spin_unlock_irqrestore(&port->driver_lock, flags);
+
+	func_exit();
+}
+
+
+void gs_hangup(struct tty_struct *tty)
+{
+	struct gs_port   *port;
+
+	func_enter ();
+
+	if (!tty) return;
+
+	port = tty->driver_data;
+	tty = port->tty;
+	if (!tty) 
+		return;
+
+	gs_shutdown_port (port);
+	port->flags &= ~(ASYNC_NORMAL_ACTIVE|GS_ACTIVE);
+	port->tty = NULL;
+	port->count = 0;
+
+	wake_up_interruptible(&port->open_wait);
+	func_exit ();
+}
+
+
+int gs_block_til_ready(void *port_, struct file * filp)
+{
+	struct gs_port *port = port_;
+	DECLARE_WAITQUEUE(wait, current);
+	int    retval;
+	int    do_clocal = 0;
+	int    CD;
+	struct tty_struct *tty;
+	unsigned long flags;
+
+	func_enter ();
+
+	if (!port) return 0;
+
+	tty = port->tty;
+
+	if (!tty) return 0;
+
+	gs_dprintk (GS_DEBUG_BTR, "Entering gs_block_till_ready.\n"); 
+	/*
+	 * 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 & ASYNC_CLOSING) {
+		interruptible_sleep_on(&port->close_wait);
+		if (port->flags & ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+	}
+
+	gs_dprintk (GS_DEBUG_BTR, "after hung up\n"); 
+
+	/*
+	 * 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))) {
+		port->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	gs_dprintk (GS_DEBUG_BTR, "after nonblock\n"); 
+ 
+	if (C_CLOCAL(tty))
+		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
+	 * rs_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	retval = 0;
+
+	add_wait_queue(&port->open_wait, &wait);
+
+	gs_dprintk (GS_DEBUG_BTR, "after add waitq.\n"); 
+	spin_lock_irqsave(&port->driver_lock, flags);
+	if (!tty_hung_up_p(filp)) {
+		port->count--;
+	}
+	spin_unlock_irqrestore(&port->driver_lock, flags);
+	port->blocked_open++;
+	while (1) {
+		CD = port->rd->get_CD (port);
+		gs_dprintk (GS_DEBUG_BTR, "CD is now %d.\n", CD);
+		set_current_state (TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) ||
+		    !(port->flags & ASYNC_INITIALIZED)) {
+			if (port->flags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;
+			break;
+		}
+		if (!(port->flags & ASYNC_CLOSING) &&
+		    (do_clocal || CD))
+			break;
+		gs_dprintk (GS_DEBUG_BTR, "signal_pending is now: %d (%lx)\n", 
+		(int)signal_pending (current), *(long*)(&current->blocked)); 
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();
+	}
+	gs_dprintk (GS_DEBUG_BTR, "Got out of the loop. (%d)\n",
+		    port->blocked_open);
+	set_current_state (TASK_RUNNING);
+	remove_wait_queue(&port->open_wait, &wait);
+	if (!tty_hung_up_p(filp)) {
+		port->count++;
+	}
+	port->blocked_open--;
+	if (retval)
+		return retval;
+
+	port->flags |= ASYNC_NORMAL_ACTIVE;
+	func_exit ();
+	return 0;
+}			 
+
+
+void gs_close(struct tty_struct * tty, struct file * filp)
+{
+	unsigned long flags;
+	struct gs_port *port;
+	
+	func_enter ();
+
+	if (!tty) return;
+
+	port = (struct gs_port *) tty->driver_data;
+
+	if (!port) return;
+
+	if (!port->tty) {
+		/* This seems to happen when this is called from vhangup. */
+		gs_dprintk (GS_DEBUG_CLOSE, "gs: Odd: port->tty is NULL\n");
+		port->tty = tty;
+	}
+
+	spin_lock_irqsave(&port->driver_lock, flags);
+
+	if (tty_hung_up_p(filp)) {
+		spin_unlock_irqrestore(&port->driver_lock, flags);
+		if (port->rd->hungup)
+			port->rd->hungup (port);
+		func_exit ();
+		return;
+	}
+
+	if ((tty->count == 1) && (port->count != 1)) {
+		printk(KERN_ERR "gs: gs_close port %p: bad port count;"
+		       " tty->count is 1, port count is %d\n", port, port->count);
+		port->count = 1;
+	}
+	if (--port->count < 0) {
+		printk(KERN_ERR "gs: gs_close port %p: bad port count: %d\n", port, port->count);
+		port->count = 0;
+	}
+
+	if (port->count) {
+		gs_dprintk(GS_DEBUG_CLOSE, "gs_close port %p: count: %d\n", port, port->count);
+		spin_unlock_irqrestore(&port->driver_lock, flags);
+		func_exit ();
+		return;
+	}
+	port->flags |= ASYNC_CLOSING;
+
+	/*
+	 * Now we wait for the transmit buffer to clear; and we notify 
+	 * the line discipline to only process XON/XOFF characters.
+	 */
+	tty->closing = 1;
+	/* if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+	   tty_wait_until_sent(tty, port->closing_wait); */
+
+	/*
+	 * At this point we stop accepting input.  To do this, we
+	 * disable the receive line status interrupts, and tell the
+	 * interrupt driver to stop checking the data ready bit in the
+	 * line status register.
+	 */
+
+	port->rd->disable_rx_interrupts (port);
+	spin_unlock_irqrestore(&port->driver_lock, flags);
+
+	/* close has no way of returning "EINTR", so discard return value */
+	if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		gs_wait_tx_flushed (port, port->closing_wait);
+
+	port->flags &= ~GS_ACTIVE;
+
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+
+	tty_ldisc_flush(tty);
+	tty->closing = 0;
+
+	port->event = 0;
+	port->rd->close (port);
+	port->rd->shutdown_port (port);
+	port->tty = NULL;
+
+	if (port->blocked_open) {
+		if (port->close_delay) {
+			spin_unlock_irqrestore(&port->driver_lock, flags);
+			msleep_interruptible(jiffies_to_msecs(port->close_delay));
+			spin_lock_irqsave(&port->driver_lock, flags);
+		}
+		wake_up_interruptible(&port->open_wait);
+	}
+	port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING | ASYNC_INITIALIZED);
+	wake_up_interruptible(&port->close_wait);
+
+	func_exit ();
+}
+
+
+static unsigned int     gs_baudrates[] = {
+  0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+  9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+};
+
+
+void gs_set_termios (struct tty_struct * tty, 
+                     struct termios * old_termios)
+{
+	struct gs_port *port;
+	int baudrate, tmp, rv;
+	struct termios *tiosp;
+
+	func_enter();
+
+	if (!tty) return;
+
+	port = tty->driver_data;
+
+	if (!port) return;
+	if (!port->tty) {
+		/* This seems to happen when this is called after gs_close. */
+		gs_dprintk (GS_DEBUG_TERMIOS, "gs: Odd: port->tty is NULL\n");
+		port->tty = tty;
+	}
+
+
+	tiosp = tty->termios;
+
+	if (gs_debug & GS_DEBUG_TERMIOS) {
+		gs_dprintk (GS_DEBUG_TERMIOS, "termios structure (%p):\n", tiosp);
+	}
+
+#if 0
+	/* This is an optimization that is only allowed for dumb cards */
+	/* Smart cards require knowledge of iflags and oflags too: that 
+	   might change hardware cooking mode.... */
+#endif
+	if (old_termios) {
+		if(   (tiosp->c_iflag == old_termios->c_iflag)
+		   && (tiosp->c_oflag == old_termios->c_oflag)
+		   && (tiosp->c_cflag == old_termios->c_cflag)
+		   && (tiosp->c_lflag == old_termios->c_lflag)
+		   && (tiosp->c_line  == old_termios->c_line)
+		   && (memcmp(tiosp->c_cc, old_termios->c_cc, NCC) == 0)) {
+			gs_dprintk(GS_DEBUG_TERMIOS, "gs_set_termios: optimized away\n");
+			return /* 0 */;
+		}
+	} else 
+		gs_dprintk(GS_DEBUG_TERMIOS, "gs_set_termios: no old_termios: "
+		           "no optimization\n");
+
+	if(old_termios && (gs_debug & GS_DEBUG_TERMIOS)) {
+		if(tiosp->c_iflag != old_termios->c_iflag)  printk("c_iflag changed\n");
+		if(tiosp->c_oflag != old_termios->c_oflag)  printk("c_oflag changed\n");
+		if(tiosp->c_cflag != old_termios->c_cflag)  printk("c_cflag changed\n");
+		if(tiosp->c_lflag != old_termios->c_lflag)  printk("c_lflag changed\n");
+		if(tiosp->c_line  != old_termios->c_line)   printk("c_line changed\n");
+		if(!memcmp(tiosp->c_cc, old_termios->c_cc, NCC)) printk("c_cc changed\n");
+	}
+
+	baudrate = tiosp->c_cflag & CBAUD;
+	if (baudrate & CBAUDEX) {
+		baudrate &= ~CBAUDEX;
+		if ((baudrate < 1) || (baudrate > 4))
+			tiosp->c_cflag &= ~CBAUDEX;
+		else
+			baudrate += 15;
+	}
+
+	baudrate = gs_baudrates[baudrate];
+	if ((tiosp->c_cflag & CBAUD) == B38400) {
+		if (     (port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			baudrate = 57600;
+		else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			baudrate = 115200;
+		else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+			baudrate = 230400;
+		else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+			baudrate = 460800;
+		else if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+			baudrate = (port->baud_base / port->custom_divisor);
+	}
+
+	/* I recommend using THIS instead of the mess in termios (and
+	   duplicating the above code). Next we should create a clean
+	   interface towards this variable. If your card supports arbitrary
+	   baud rates, (e.g. CD1400 or 16550 based cards) then everything
+	   will be very easy..... */
+	port->baud = baudrate;
+
+	/* Two timer ticks seems enough to wakeup something like SLIP driver */
+	/* Baudrate/10 is cps. Divide by HZ to get chars per tick. */
+	tmp = (baudrate / 10 / HZ) * 2;			 
+
+	if (tmp <                 0) tmp = 0;
+	if (tmp >= SERIAL_XMIT_SIZE) tmp = SERIAL_XMIT_SIZE-1;
+
+	port->wakeup_chars = tmp;
+
+	/* We should really wait for the characters to be all sent before
+	   changing the settings. -- CAL */
+	rv = gs_wait_tx_flushed (port, MAX_SCHEDULE_TIMEOUT);
+	if (rv < 0) return /* rv */;
+
+	rv = port->rd->set_real_termios(port);
+	if (rv < 0) return /* rv */;
+
+	if ((!old_termios || 
+	     (old_termios->c_cflag & CRTSCTS)) &&
+	    !(      tiosp->c_cflag & CRTSCTS)) {
+		tty->stopped = 0;
+		gs_start(tty);
+	}
+
+#ifdef tytso_patch_94Nov25_1726
+	/* This "makes sense", Why is it commented out? */
+
+	if (!(old_termios->c_cflag & CLOCAL) &&
+	    (tty->termios->c_cflag & CLOCAL))
+		wake_up_interruptible(&port->gs.open_wait);
+#endif
+
+	func_exit();
+	return /* 0 */;
+}
+
+
+
+/* Must be called with interrupts enabled */
+int gs_init_port(struct gs_port *port)
+{
+	unsigned long flags;
+	unsigned long page;
+
+	func_enter ();
+
+        if (!tmp_buf) {
+		page = get_zeroed_page(GFP_KERNEL);
+		spin_lock_irqsave (&port->driver_lock, flags); /* Don't expect this to make a difference. */
+		if (tmp_buf)
+			free_page(page);
+		else
+			tmp_buf = (unsigned char *) page;
+		spin_unlock_irqrestore (&port->driver_lock, flags);
+		if (!tmp_buf) {
+			func_exit ();
+			return -ENOMEM;
+		}
+	}
+
+	if (port->flags & ASYNC_INITIALIZED) {
+		func_exit ();
+		return 0;
+	}
+	if (!port->xmit_buf) {
+		/* We may sleep in get_zeroed_page() */
+		unsigned long tmp;
+
+		tmp = get_zeroed_page(GFP_KERNEL);
+		spin_lock_irqsave (&port->driver_lock, flags);
+		if (port->xmit_buf) 
+			free_page (tmp);
+		else
+			port->xmit_buf = (unsigned char *) tmp;
+		spin_unlock_irqrestore(&port->driver_lock, flags);
+		if (!port->xmit_buf) {
+			func_exit ();
+			return -ENOMEM;
+		}
+	}
+
+	spin_lock_irqsave (&port->driver_lock, flags);
+	if (port->tty) 
+		clear_bit(TTY_IO_ERROR, &port->tty->flags);
+	init_MUTEX(&port->port_write_sem);
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	spin_unlock_irqrestore(&port->driver_lock, flags);
+	gs_set_termios(port->tty, NULL);
+	spin_lock_irqsave (&port->driver_lock, flags);
+	port->flags |= ASYNC_INITIALIZED;
+	port->flags &= ~GS_TX_INTEN;
+
+	spin_unlock_irqrestore(&port->driver_lock, flags);
+	func_exit ();
+	return 0;
+}
+
+
+int gs_setserial(struct gs_port *port, struct serial_struct __user *sp)
+{
+	struct serial_struct sio;
+
+	if (copy_from_user(&sio, sp, sizeof(struct serial_struct)))
+		return(-EFAULT);
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((sio.baud_base != port->baud_base) ||
+		    (sio.close_delay != port->close_delay) ||
+		    ((sio.flags & ~ASYNC_USR_MASK) !=
+		     (port->flags & ~ASYNC_USR_MASK)))
+			return(-EPERM);
+	} 
+
+	port->flags = (port->flags & ~ASYNC_USR_MASK) |
+		(sio.flags & ASYNC_USR_MASK);
+  
+	port->baud_base = sio.baud_base;
+	port->close_delay = sio.close_delay;
+	port->closing_wait = sio.closing_wait;
+	port->custom_divisor = sio.custom_divisor;
+
+	gs_set_termios (port->tty, NULL);
+
+	return 0;
+}
+
+
+/*****************************************************************************/
+
+/*
+ *      Generate the serial struct info.
+ */
+
+int gs_getserial(struct gs_port *port, struct serial_struct __user *sp)
+{
+	struct serial_struct    sio;
+
+	memset(&sio, 0, sizeof(struct serial_struct));
+	sio.flags = port->flags;
+	sio.baud_base = port->baud_base;
+	sio.close_delay = port->close_delay;
+	sio.closing_wait = port->closing_wait;
+	sio.custom_divisor = port->custom_divisor;
+	sio.hub6 = 0;
+
+	/* If you want you can override these. */
+	sio.type = PORT_UNKNOWN;
+	sio.xmit_fifo_size = -1;
+	sio.line = -1;
+	sio.port = -1;
+	sio.irq = -1;
+
+	if (port->rd->getserial)
+		port->rd->getserial (port, &sio);
+
+	if (copy_to_user(sp, &sio, sizeof(struct serial_struct)))
+		return -EFAULT;
+	return 0;
+
+}
+
+
+void gs_got_break(struct gs_port *port)
+{
+	func_enter ();
+
+	tty_insert_flip_char(port->tty, 0, TTY_BREAK);
+	tty_schedule_flip(port->tty);
+	if (port->flags & ASYNC_SAK) {
+		do_SAK (port->tty);
+	}
+
+	func_exit ();
+}
+
+
+EXPORT_SYMBOL(gs_put_char);
+EXPORT_SYMBOL(gs_write);
+EXPORT_SYMBOL(gs_write_room);
+EXPORT_SYMBOL(gs_chars_in_buffer);
+EXPORT_SYMBOL(gs_flush_buffer);
+EXPORT_SYMBOL(gs_flush_chars);
+EXPORT_SYMBOL(gs_stop);
+EXPORT_SYMBOL(gs_start);
+EXPORT_SYMBOL(gs_hangup);
+EXPORT_SYMBOL(gs_block_til_ready);
+EXPORT_SYMBOL(gs_close);
+EXPORT_SYMBOL(gs_set_termios);
+EXPORT_SYMBOL(gs_init_port);
+EXPORT_SYMBOL(gs_setserial);
+EXPORT_SYMBOL(gs_getserial);
+EXPORT_SYMBOL(gs_got_break);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/genrtc.c b/drivers/char/genrtc.c
new file mode 100644
index 0000000..d3a2bc3
--- /dev/null
+++ b/drivers/char/genrtc.c
@@ -0,0 +1,535 @@
+/*
+ *	Real Time Clock interface for
+ *		- q40 and other m68k machines,
+ *		- HP PARISC machines
+ *		- PowerPC machines
+ *      emulate some RTC irq capabilities in software
+ *
+ *      Copyright (C) 1999 Richard Zidlicky
+ *
+ *	based on Paul Gortmaker's rtc.c device and
+ *           Sam Creasey Generic rtc driver
+ *
+ *	This driver allows use of the real time clock (built into
+ *	nearly all computers) from user space. It exports the /dev/rtc
+ *	interface supporting various ioctl() and also the /proc/dev/rtc
+ *	pseudo-file for status information.
+ *
+ *	The ioctls can be used to set the interrupt behaviour where
+ *	supported.
+ *
+ *	The /dev/rtc interface will block on reads until an interrupt
+ *	has been received. If a RTC interrupt has already happened,
+ *	it will output an unsigned long and then block. The output value
+ *	contains the interrupt status in the low byte and the number of
+ *	interrupts since the last read in the remaining high bytes. The
+ *	/dev/rtc interface can also be used with the select(2) call.
+ *
+ *	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.
+ *
+
+ *      1.01 fix for 2.3.X                    rz@linux-m68k.org
+ *      1.02 merged with code from genrtc.c   rz@linux-m68k.org
+ *      1.03 make it more portable            zippel@linux-m68k.org
+ *      1.04 removed useless timer code       rz@linux-m68k.org
+ *      1.05 portable RTC_UIE emulation       rz@linux-m68k.org
+ *      1.06 set_rtc_time can return an error trini@kernel.crashing.org
+ *      1.07 ported to HP PARISC (hppa)	      Helge Deller <deller@gmx.de>
+ */
+
+#define RTC_VERSION	"1.07"
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+
+#include <linux/rtc.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/workqueue.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/rtc.h>
+
+/*
+ *	We sponge a minor off of the misc major. No need slurping
+ *	up another valuable major dev number for this. If you add
+ *	an ioctl, make sure you don't conflict with SPARC's RTC
+ *	ioctls.
+ */
+
+static DECLARE_WAIT_QUEUE_HEAD(gen_rtc_wait);
+
+/*
+ *	Bits in gen_rtc_status.
+ */
+
+#define RTC_IS_OPEN		0x01	/* means /dev/rtc is in use	*/
+
+static unsigned char gen_rtc_status;	/* bitmapped status byte.	*/
+static unsigned long gen_rtc_irq_data;	/* our output to the world	*/
+
+/* months start at 0 now */
+static unsigned char days_in_mo[] =
+{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+static int irq_active;
+
+#ifdef CONFIG_GEN_RTC_X
+static struct work_struct genrtc_task;
+static struct timer_list timer_task;
+
+static unsigned int oldsecs;
+static int lostint;
+static unsigned long tt_exp;
+
+static void gen_rtc_timer(unsigned long data);
+
+static volatile int stask_active;              /* schedule_work */
+static volatile int ttask_active;              /* timer_task */
+static int stop_rtc_timers;                    /* don't requeue tasks */
+static DEFINE_SPINLOCK(gen_rtc_lock);
+
+static void gen_rtc_interrupt(unsigned long arg);
+
+/*
+ * Routine to poll RTC seconds field for change as often as possible,
+ * after first RTC_UIE use timer to reduce polling
+ */
+static void genrtc_troutine(void *data)
+{
+	unsigned int tmp = get_rtc_ss();
+	
+	if (stop_rtc_timers) {
+		stask_active = 0;
+		return;
+	}
+
+	if (oldsecs != tmp){
+		oldsecs = tmp;
+
+		timer_task.function = gen_rtc_timer;
+		timer_task.expires = jiffies + HZ - (HZ/10);
+		tt_exp=timer_task.expires;
+		ttask_active=1;
+		stask_active=0;
+		add_timer(&timer_task);
+
+		gen_rtc_interrupt(0);
+	} else if (schedule_work(&genrtc_task) == 0)
+		stask_active = 0;
+}
+
+static void gen_rtc_timer(unsigned long data)
+{
+	lostint = get_rtc_ss() - oldsecs ;
+	if (lostint<0) 
+		lostint = 60 - lostint;
+	if (time_after(jiffies, tt_exp))
+		printk(KERN_INFO "genrtc: timer task delayed by %ld jiffies\n",
+		       jiffies-tt_exp);
+	ttask_active=0;
+	stask_active=1;
+	if ((schedule_work(&genrtc_task) == 0))
+		stask_active = 0;
+}
+
+/* 
+ * call gen_rtc_interrupt function to signal an RTC_UIE,
+ * arg is unused.
+ * Could be invoked either from a real interrupt handler or
+ * from some routine that periodically (eg 100HZ) monitors
+ * whether RTC_SECS changed
+ */
+static void gen_rtc_interrupt(unsigned long arg)
+{
+	/*  We store the status in the low byte and the number of
+	 *	interrupts received since the last read in the remainder
+	 *	of rtc_irq_data.  */
+
+	gen_rtc_irq_data += 0x100;
+	gen_rtc_irq_data &= ~0xff;
+	gen_rtc_irq_data |= RTC_UIE;
+
+	if (lostint){
+		printk("genrtc: system delaying clock ticks?\n");
+		/* increment count so that userspace knows something is wrong */
+		gen_rtc_irq_data += ((lostint-1)<<8);
+		lostint = 0;
+	}
+
+	wake_up_interruptible(&gen_rtc_wait);
+}
+
+/*
+ *	Now all the various file operations that we export.
+ */
+static ssize_t gen_rtc_read(struct file *file, char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long data;
+	ssize_t retval;
+
+	if (count != sizeof (unsigned int) && count != sizeof (unsigned long))
+		return -EINVAL;
+
+	if (file->f_flags & O_NONBLOCK && !gen_rtc_irq_data)
+		return -EAGAIN;
+
+	add_wait_queue(&gen_rtc_wait, &wait);
+	retval = -ERESTARTSYS;
+
+	while (1) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		data = xchg(&gen_rtc_irq_data, 0);
+		if (data)
+			break;
+		if (signal_pending(current))
+			goto out;
+		schedule();
+	}
+
+	/* first test allows optimizer to nuke this case for 32-bit machines */
+	if (sizeof (int) != sizeof (long) && count == sizeof (unsigned int)) {
+		unsigned int uidata = data;
+		retval = put_user(uidata, (unsigned long __user *)buf);
+	}
+	else {
+		retval = put_user(data, (unsigned long __user *)buf);
+	}
+	if (!retval)
+		retval = sizeof(unsigned long);
+ out:
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&gen_rtc_wait, &wait);
+
+	return retval;
+}
+
+static unsigned int gen_rtc_poll(struct file *file,
+				 struct poll_table_struct *wait)
+{
+	poll_wait(file, &gen_rtc_wait, wait);
+	if (gen_rtc_irq_data != 0)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+#endif
+
+/*
+ * Used to disable/enable interrupts, only RTC_UIE supported
+ * We also clear out any old irq data after an ioctl() that
+ * meddles with the interrupt enable/disable bits.
+ */
+
+static inline void gen_clear_rtc_irq_bit(unsigned char bit)
+{
+#ifdef CONFIG_GEN_RTC_X
+	stop_rtc_timers = 1;
+	if (ttask_active){
+		del_timer_sync(&timer_task);
+		ttask_active = 0;
+	}
+	while (stask_active)
+		schedule();
+
+	spin_lock(&gen_rtc_lock);
+	irq_active = 0;
+	spin_unlock(&gen_rtc_lock);
+#endif
+}
+
+static inline int gen_set_rtc_irq_bit(unsigned char bit)
+{
+#ifdef CONFIG_GEN_RTC_X
+	spin_lock(&gen_rtc_lock);
+	if ( !irq_active ) {
+		irq_active = 1;
+		stop_rtc_timers = 0;
+		lostint = 0;
+		INIT_WORK(&genrtc_task, genrtc_troutine, NULL);
+		oldsecs = get_rtc_ss();
+		init_timer(&timer_task);
+
+		stask_active = 1;
+		if (schedule_work(&genrtc_task) == 0){
+			stask_active = 0;
+		}
+	}
+	spin_unlock(&gen_rtc_lock);
+	gen_rtc_irq_data = 0;
+	return 0;
+#else
+	return -EINVAL;
+#endif
+}
+
+static int gen_rtc_ioctl(struct inode *inode, struct file *file,
+			 unsigned int cmd, unsigned long arg)
+{
+	struct rtc_time wtime;
+	struct rtc_pll_info pll;
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd) {
+
+	case RTC_PLL_GET:
+	    if (get_rtc_pll(&pll))
+	 	    return -EINVAL;
+	    else
+		    return copy_to_user(argp, &pll, sizeof pll) ? -EFAULT : 0;
+
+	case RTC_PLL_SET:
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+		if (copy_from_user(&pll, argp, sizeof(pll)))
+			return -EFAULT;
+	    return set_rtc_pll(&pll);
+
+	case RTC_UIE_OFF:	/* disable ints from RTC updates.	*/
+		gen_clear_rtc_irq_bit(RTC_UIE);
+		return 0;
+
+	case RTC_UIE_ON:	/* enable ints for RTC updates.	*/
+	        return gen_set_rtc_irq_bit(RTC_UIE);
+
+	case RTC_RD_TIME:	/* Read the time/date from RTC	*/
+		/* this doesn't get week-day, who cares */
+		memset(&wtime, 0, sizeof(wtime));
+		get_rtc_time(&wtime);
+
+		return copy_to_user(argp, &wtime, sizeof(wtime)) ? -EFAULT : 0;
+
+	case RTC_SET_TIME:	/* Set the RTC */
+	    {
+		int year;
+		unsigned char leap_yr;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		if (copy_from_user(&wtime, argp, sizeof(wtime)))
+			return -EFAULT;
+
+		year = wtime.tm_year + 1900;
+		leap_yr = ((!(year % 4) && (year % 100)) ||
+			   !(year % 400));
+
+		if ((wtime.tm_mon < 0 || wtime.tm_mon > 11) || (wtime.tm_mday < 1))
+			return -EINVAL;
+
+		if (wtime.tm_mday < 0 || wtime.tm_mday >
+		    (days_in_mo[wtime.tm_mon] + ((wtime.tm_mon == 1) && leap_yr)))
+			return -EINVAL;
+
+		if (wtime.tm_hour < 0 || wtime.tm_hour >= 24 ||
+		    wtime.tm_min < 0 || wtime.tm_min >= 60 ||
+		    wtime.tm_sec < 0 || wtime.tm_sec >= 60)
+			return -EINVAL;
+
+		return set_rtc_time(&wtime);
+	    }
+	}
+
+	return -EINVAL;
+}
+
+/*
+ *	We enforce only one user at a time here with the open/close.
+ *	Also clear the previous interrupt data on an open, and clean
+ *	up things on a close.
+ */
+
+static int gen_rtc_open(struct inode *inode, struct file *file)
+{
+	if (gen_rtc_status & RTC_IS_OPEN)
+		return -EBUSY;
+
+	gen_rtc_status |= RTC_IS_OPEN;
+	gen_rtc_irq_data = 0;
+	irq_active = 0;
+
+	return 0;
+}
+
+static int gen_rtc_release(struct inode *inode, struct file *file)
+{
+	/*
+	 * Turn off all interrupts once the device is no longer
+	 * in use and clear the data.
+	 */
+
+	gen_clear_rtc_irq_bit(RTC_PIE|RTC_AIE|RTC_UIE);
+
+	gen_rtc_status &= ~RTC_IS_OPEN;
+	return 0;
+}
+
+
+#ifdef CONFIG_PROC_FS
+
+/*
+ *	Info exported via "/proc/rtc".
+ */
+
+static int gen_rtc_proc_output(char *buf)
+{
+	char *p;
+	struct rtc_time tm;
+	unsigned int flags;
+	struct rtc_pll_info pll;
+
+	p = buf;
+
+	flags = get_rtc_time(&tm);
+
+	p += sprintf(p,
+		     "rtc_time\t: %02d:%02d:%02d\n"
+		     "rtc_date\t: %04d-%02d-%02d\n"
+		     "rtc_epoch\t: %04u\n",
+		     tm.tm_hour, tm.tm_min, tm.tm_sec,
+		     tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, 1900);
+
+	tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+
+	p += sprintf(p, "alarm\t\t: ");
+	if (tm.tm_hour <= 24)
+		p += sprintf(p, "%02d:", tm.tm_hour);
+	else
+		p += sprintf(p, "**:");
+
+	if (tm.tm_min <= 59)
+		p += sprintf(p, "%02d:", tm.tm_min);
+	else
+		p += sprintf(p, "**:");
+
+	if (tm.tm_sec <= 59)
+		p += sprintf(p, "%02d\n", tm.tm_sec);
+	else
+		p += sprintf(p, "**\n");
+
+	p += sprintf(p,
+		     "DST_enable\t: %s\n"
+		     "BCD\t\t: %s\n"
+		     "24hr\t\t: %s\n"
+		     "square_wave\t: %s\n"
+		     "alarm_IRQ\t: %s\n"
+		     "update_IRQ\t: %s\n"
+		     "periodic_IRQ\t: %s\n"
+		     "periodic_freq\t: %ld\n"
+		     "batt_status\t: %s\n",
+		     (flags & RTC_DST_EN) ? "yes" : "no",
+		     (flags & RTC_DM_BINARY) ? "no" : "yes",
+		     (flags & RTC_24H) ? "yes" : "no",
+		     (flags & RTC_SQWE) ? "yes" : "no",
+		     (flags & RTC_AIE) ? "yes" : "no",
+		     irq_active ? "yes" : "no",
+		     (flags & RTC_PIE) ? "yes" : "no",
+		     0L /* freq */,
+		     (flags & RTC_BATT_BAD) ? "bad" : "okay");
+	if (!get_rtc_pll(&pll))
+	    p += sprintf(p,
+			 "PLL adjustment\t: %d\n"
+			 "PLL max +ve adjustment\t: %d\n"
+			 "PLL max -ve adjustment\t: %d\n"
+			 "PLL +ve adjustment factor\t: %d\n"
+			 "PLL -ve adjustment factor\t: %d\n"
+			 "PLL frequency\t: %ld\n",
+			 pll.pll_value,
+			 pll.pll_max,
+			 pll.pll_min,
+			 pll.pll_posmult,
+			 pll.pll_negmult,
+			 pll.pll_clock);
+	return p - buf;
+}
+
+static int gen_rtc_read_proc(char *page, char **start, off_t off,
+			     int count, int *eof, void *data)
+{
+	int len = gen_rtc_proc_output (page);
+        if (len <= off+count) *eof = 1;
+	*start = page + off;
+	len -= off;
+        if (len>count) len = count;
+        if (len<0) len = 0;
+	return len;
+}
+
+static int __init gen_rtc_proc_init(void)
+{
+	struct proc_dir_entry *r;
+
+	r = create_proc_read_entry("driver/rtc", 0, NULL, gen_rtc_read_proc, NULL);
+	if (!r)
+		return -ENOMEM;
+	return 0;
+}
+#else
+static inline int gen_rtc_proc_init(void) { return 0; }
+#endif /* CONFIG_PROC_FS */
+
+
+/*
+ *	The various file operations we support.
+ */
+
+static struct file_operations gen_rtc_fops = {
+	.owner		= THIS_MODULE,
+#ifdef CONFIG_GEN_RTC_X
+	.read		= gen_rtc_read,
+	.poll		= gen_rtc_poll,
+#endif
+	.ioctl		= gen_rtc_ioctl,
+	.open		= gen_rtc_open,
+	.release	= gen_rtc_release,
+};
+
+static struct miscdevice rtc_gen_dev =
+{
+	.minor		= RTC_MINOR,
+	.name		= "rtc",
+	.fops		= &gen_rtc_fops,
+};
+
+static int __init rtc_generic_init(void)
+{
+	int retval;
+
+	printk(KERN_INFO "Generic RTC Driver v%s\n", RTC_VERSION);
+
+	retval = misc_register(&rtc_gen_dev);
+	if (retval < 0)
+		return retval;
+
+	retval = gen_rtc_proc_init();
+	if (retval) {
+		misc_deregister(&rtc_gen_dev);
+		return retval;
+	}
+
+	return 0;
+}
+
+static void __exit rtc_generic_exit(void)
+{
+	remove_proc_entry ("driver/rtc", NULL);
+	misc_deregister(&rtc_gen_dev);
+}
+
+
+module_init(rtc_generic_init);
+module_exit(rtc_generic_exit);
+
+MODULE_AUTHOR("Richard Zidlicky");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(RTC_MINOR);
diff --git a/drivers/char/hangcheck-timer.c b/drivers/char/hangcheck-timer.c
new file mode 100644
index 0000000..83d6b37
--- /dev/null
+++ b/drivers/char/hangcheck-timer.c
@@ -0,0 +1,129 @@
+/*
+ * hangcheck-timer.c
+ *
+ * Driver for a little io fencing timer.
+ *
+ * Copyright (C) 2002 Oracle Corporation.  All rights reserved.
+ *
+ * Author: Joel Becker <joel.becker@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License version 2 as published by the Free Software Foundation.
+ * 
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 021110-1307, USA.
+ */
+
+/*
+ * The hangcheck-timer driver uses the TSC to catch delays that
+ * jiffies does not notice.  A timer is set.  When the timer fires, it
+ * checks whether it was delayed and if that delay exceeds a given
+ * margin of error.  The hangcheck_tick module paramter takes the timer
+ * duration in seconds.  The hangcheck_margin parameter defines the
+ * margin of error, in seconds.  The defaults are 60 seconds for the
+ * timer and 180 seconds for the margin of error.  IOW, a timer is set
+ * for 60 seconds.  When the timer fires, the callback checks the
+ * actual duration that the timer waited.  If the duration exceeds the
+ * alloted time and margin (here 60 + 180, or 240 seconds), the machine
+ * is restarted.  A healthy machine will have the duration match the
+ * expected timeout very closely.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+
+#define VERSION_STR "0.5.0"
+
+#define DEFAULT_IOFENCE_MARGIN 60	/* Default fudge factor, in seconds */
+#define DEFAULT_IOFENCE_TICK 180	/* Default timer timeout, in seconds */
+
+static int hangcheck_tick = DEFAULT_IOFENCE_TICK;
+static int hangcheck_margin = DEFAULT_IOFENCE_MARGIN;
+static int hangcheck_reboot;  /* Defaults to not reboot */
+
+/* Driver options */
+module_param(hangcheck_tick, int, 0);
+MODULE_PARM_DESC(hangcheck_tick, "Timer delay.");
+module_param(hangcheck_margin, int, 0);
+MODULE_PARM_DESC(hangcheck_margin, "If the hangcheck timer has been delayed more than hangcheck_margin seconds, the driver will fire.");
+module_param(hangcheck_reboot, int, 0);
+MODULE_PARM_DESC(hangcheck_reboot, "If nonzero, the machine will reboot when the timer margin is exceeded.");
+
+MODULE_AUTHOR("Joel Becker");
+MODULE_DESCRIPTION("Hangcheck-timer detects when the system has gone out to lunch past a certain margin.");
+MODULE_LICENSE("GPL");
+
+
+/* Last time scheduled */
+static unsigned long long hangcheck_tsc, hangcheck_tsc_margin;
+
+static void hangcheck_fire(unsigned long);
+
+static struct timer_list hangcheck_ticktock =
+		TIMER_INITIALIZER(hangcheck_fire, 0, 0);
+
+extern unsigned long long monotonic_clock(void);
+
+static void hangcheck_fire(unsigned long data)
+{
+	unsigned long long cur_tsc, tsc_diff;
+
+	cur_tsc = monotonic_clock();
+
+	if (cur_tsc > hangcheck_tsc)
+		tsc_diff = cur_tsc - hangcheck_tsc;
+	else
+		tsc_diff = (cur_tsc + (~0ULL - hangcheck_tsc)); /* or something */
+
+	if (tsc_diff > hangcheck_tsc_margin) {
+		if (hangcheck_reboot) {
+			printk(KERN_CRIT "Hangcheck: hangcheck is restarting the machine.\n");
+			machine_restart(NULL);
+		} else {
+			printk(KERN_CRIT "Hangcheck: hangcheck value past margin!\n");
+		}
+	}
+	mod_timer(&hangcheck_ticktock, jiffies + (hangcheck_tick*HZ));
+	hangcheck_tsc = monotonic_clock();
+}
+
+
+static int __init hangcheck_init(void)
+{
+	printk("Hangcheck: starting hangcheck timer %s (tick is %d seconds, margin is %d seconds).\n",
+	       VERSION_STR, hangcheck_tick, hangcheck_margin);
+
+	hangcheck_tsc_margin = hangcheck_margin + hangcheck_tick;
+	hangcheck_tsc_margin *= 1000000000;
+
+
+	hangcheck_tsc = monotonic_clock();
+	mod_timer(&hangcheck_ticktock, jiffies + (hangcheck_tick*HZ));
+
+	return 0;
+}
+
+
+static void __exit hangcheck_exit(void)
+{
+	del_timer_sync(&hangcheck_ticktock);
+}
+
+module_init(hangcheck_init);
+module_exit(hangcheck_exit);
diff --git a/drivers/char/hpet.c b/drivers/char/hpet.c
new file mode 100644
index 0000000..5ec732e
--- /dev/null
+++ b/drivers/char/hpet.c
@@ -0,0 +1,994 @@
+/*
+ * Intel & MS High Precision Event Timer Implementation.
+ *
+ * Copyright (C) 2003 Intel Corporation
+ *	Venki Pallipadi
+ * (c) Copyright 2004 Hewlett-Packard Development Company, L.P.
+ *	Bob Picco <robert.picco@hp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/major.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+#include <linux/sysctl.h>
+#include <linux/wait.h>
+#include <linux/bcd.h>
+#include <linux/seq_file.h>
+#include <linux/bitops.h>
+
+#include <asm/current.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/div64.h>
+
+#include <linux/acpi.h>
+#include <acpi/acpi_bus.h>
+#include <linux/hpet.h>
+
+/*
+ * The High Precision Event Timer driver.
+ * This driver is closely modelled after the rtc.c driver.
+ * http://www.intel.com/labs/platcomp/hpet/hpetspec.htm
+ */
+#define	HPET_USER_FREQ	(64)
+#define	HPET_DRIFT	(500)
+
+static u32 hpet_ntimer, hpet_nhpet, hpet_max_freq = HPET_USER_FREQ;
+
+/* A lock for concurrent access by app and isr hpet activity. */
+static DEFINE_SPINLOCK(hpet_lock);
+/* A lock for concurrent intermodule access to hpet and isr hpet activity. */
+static DEFINE_SPINLOCK(hpet_task_lock);
+
+#define	HPET_DEV_NAME	(7)
+
+struct hpet_dev {
+	struct hpets *hd_hpets;
+	struct hpet __iomem *hd_hpet;
+	struct hpet_timer __iomem *hd_timer;
+	unsigned long hd_ireqfreq;
+	unsigned long hd_irqdata;
+	wait_queue_head_t hd_waitqueue;
+	struct fasync_struct *hd_async_queue;
+	struct hpet_task *hd_task;
+	unsigned int hd_flags;
+	unsigned int hd_irq;
+	unsigned int hd_hdwirq;
+	char hd_name[HPET_DEV_NAME];
+};
+
+struct hpets {
+	struct hpets *hp_next;
+	struct hpet __iomem *hp_hpet;
+	unsigned long hp_hpet_phys;
+	struct time_interpolator *hp_interpolator;
+	unsigned long hp_period;
+	unsigned long hp_delta;
+	unsigned int hp_ntimer;
+	unsigned int hp_which;
+	struct hpet_dev hp_dev[1];
+};
+
+static struct hpets *hpets;
+
+#define	HPET_OPEN		0x0001
+#define	HPET_IE			0x0002	/* interrupt enabled */
+#define	HPET_PERIODIC		0x0004
+
+#if BITS_PER_LONG == 64
+#define	write_counter(V, MC)	writeq(V, MC)
+#define	read_counter(MC)	readq(MC)
+#else
+#define	write_counter(V, MC) 	writel(V, MC)
+#define	read_counter(MC)	readl(MC)
+#endif
+
+#ifndef readq
+static unsigned long long __inline readq(void __iomem *addr)
+{
+	return readl(addr) | (((unsigned long long)readl(addr + 4)) << 32LL);
+}
+#endif
+
+#ifndef writeq
+static void __inline writeq(unsigned long long v, void __iomem *addr)
+{
+	writel(v & 0xffffffff, addr);
+	writel(v >> 32, addr + 4);
+}
+#endif
+
+static irqreturn_t hpet_interrupt(int irq, void *data, struct pt_regs *regs)
+{
+	struct hpet_dev *devp;
+	unsigned long isr;
+
+	devp = data;
+
+	spin_lock(&hpet_lock);
+	devp->hd_irqdata++;
+
+	/*
+	 * For non-periodic timers, increment the accumulator.
+	 * This has the effect of treating non-periodic like periodic.
+	 */
+	if ((devp->hd_flags & (HPET_IE | HPET_PERIODIC)) == HPET_IE) {
+		unsigned long m, t;
+
+		t = devp->hd_ireqfreq;
+		m = read_counter(&devp->hd_hpet->hpet_mc);
+		write_counter(t + m + devp->hd_hpets->hp_delta,
+			      &devp->hd_timer->hpet_compare);
+	}
+
+	isr = (1 << (devp - devp->hd_hpets->hp_dev));
+	writeq(isr, &devp->hd_hpet->hpet_isr);
+	spin_unlock(&hpet_lock);
+
+	spin_lock(&hpet_task_lock);
+	if (devp->hd_task)
+		devp->hd_task->ht_func(devp->hd_task->ht_data);
+	spin_unlock(&hpet_task_lock);
+
+	wake_up_interruptible(&devp->hd_waitqueue);
+
+	kill_fasync(&devp->hd_async_queue, SIGIO, POLL_IN);
+
+	return IRQ_HANDLED;
+}
+
+static int hpet_open(struct inode *inode, struct file *file)
+{
+	struct hpet_dev *devp;
+	struct hpets *hpetp;
+	int i;
+
+	if (file->f_mode & FMODE_WRITE)
+		return -EINVAL;
+
+	spin_lock_irq(&hpet_lock);
+
+	for (devp = NULL, hpetp = hpets; hpetp && !devp; hpetp = hpetp->hp_next)
+		for (i = 0; i < hpetp->hp_ntimer; i++)
+			if (hpetp->hp_dev[i].hd_flags & HPET_OPEN
+			    || hpetp->hp_dev[i].hd_task)
+				continue;
+			else {
+				devp = &hpetp->hp_dev[i];
+				break;
+			}
+
+	if (!devp) {
+		spin_unlock_irq(&hpet_lock);
+		return -EBUSY;
+	}
+
+	file->private_data = devp;
+	devp->hd_irqdata = 0;
+	devp->hd_flags |= HPET_OPEN;
+	spin_unlock_irq(&hpet_lock);
+
+	return 0;
+}
+
+static ssize_t
+hpet_read(struct file *file, char __user *buf, size_t count, loff_t * ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long data;
+	ssize_t retval;
+	struct hpet_dev *devp;
+
+	devp = file->private_data;
+	if (!devp->hd_ireqfreq)
+		return -EIO;
+
+	if (count < sizeof(unsigned long))
+		return -EINVAL;
+
+	add_wait_queue(&devp->hd_waitqueue, &wait);
+
+	for ( ; ; ) {
+		set_current_state(TASK_INTERRUPTIBLE);
+
+		spin_lock_irq(&hpet_lock);
+		data = devp->hd_irqdata;
+		devp->hd_irqdata = 0;
+		spin_unlock_irq(&hpet_lock);
+
+		if (data)
+			break;
+		else if (file->f_flags & O_NONBLOCK) {
+			retval = -EAGAIN;
+			goto out;
+		} else if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			goto out;
+		}
+		schedule();
+	}
+
+	retval = put_user(data, (unsigned long __user *)buf);
+	if (!retval)
+		retval = sizeof(unsigned long);
+out:
+	__set_current_state(TASK_RUNNING);
+	remove_wait_queue(&devp->hd_waitqueue, &wait);
+
+	return retval;
+}
+
+static unsigned int hpet_poll(struct file *file, poll_table * wait)
+{
+	unsigned long v;
+	struct hpet_dev *devp;
+
+	devp = file->private_data;
+
+	if (!devp->hd_ireqfreq)
+		return 0;
+
+	poll_wait(file, &devp->hd_waitqueue, wait);
+
+	spin_lock_irq(&hpet_lock);
+	v = devp->hd_irqdata;
+	spin_unlock_irq(&hpet_lock);
+
+	if (v != 0)
+		return POLLIN | POLLRDNORM;
+
+	return 0;
+}
+
+static int hpet_mmap(struct file *file, struct vm_area_struct *vma)
+{
+#ifdef	CONFIG_HPET_MMAP
+	struct hpet_dev *devp;
+	unsigned long addr;
+
+	if (((vma->vm_end - vma->vm_start) != PAGE_SIZE) || vma->vm_pgoff)
+		return -EINVAL;
+
+	devp = file->private_data;
+	addr = devp->hd_hpets->hp_hpet_phys;
+
+	if (addr & (PAGE_SIZE - 1))
+		return -ENOSYS;
+
+	vma->vm_flags |= VM_IO;
+	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+	addr = __pa(addr);
+
+	if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT,
+					PAGE_SIZE, vma->vm_page_prot)) {
+		printk(KERN_ERR "remap_pfn_range failed in hpet.c\n");
+		return -EAGAIN;
+	}
+
+	return 0;
+#else
+	return -ENOSYS;
+#endif
+}
+
+static int hpet_fasync(int fd, struct file *file, int on)
+{
+	struct hpet_dev *devp;
+
+	devp = file->private_data;
+
+	if (fasync_helper(fd, file, on, &devp->hd_async_queue) >= 0)
+		return 0;
+	else
+		return -EIO;
+}
+
+static int hpet_release(struct inode *inode, struct file *file)
+{
+	struct hpet_dev *devp;
+	struct hpet_timer __iomem *timer;
+	int irq = 0;
+
+	devp = file->private_data;
+	timer = devp->hd_timer;
+
+	spin_lock_irq(&hpet_lock);
+
+	writeq((readq(&timer->hpet_config) & ~Tn_INT_ENB_CNF_MASK),
+	       &timer->hpet_config);
+
+	irq = devp->hd_irq;
+	devp->hd_irq = 0;
+
+	devp->hd_ireqfreq = 0;
+
+	if (devp->hd_flags & HPET_PERIODIC
+	    && readq(&timer->hpet_config) & Tn_TYPE_CNF_MASK) {
+		unsigned long v;
+
+		v = readq(&timer->hpet_config);
+		v ^= Tn_TYPE_CNF_MASK;
+		writeq(v, &timer->hpet_config);
+	}
+
+	devp->hd_flags &= ~(HPET_OPEN | HPET_IE | HPET_PERIODIC);
+	spin_unlock_irq(&hpet_lock);
+
+	if (irq)
+		free_irq(irq, devp);
+
+	if (file->f_flags & FASYNC)
+		hpet_fasync(-1, file, 0);
+
+	file->private_data = NULL;
+	return 0;
+}
+
+static int hpet_ioctl_common(struct hpet_dev *, int, unsigned long, int);
+
+static int
+hpet_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	   unsigned long arg)
+{
+	struct hpet_dev *devp;
+
+	devp = file->private_data;
+	return hpet_ioctl_common(devp, cmd, arg, 0);
+}
+
+static int hpet_ioctl_ieon(struct hpet_dev *devp)
+{
+	struct hpet_timer __iomem *timer;
+	struct hpet __iomem *hpet;
+	struct hpets *hpetp;
+	int irq;
+	unsigned long g, v, t, m;
+	unsigned long flags, isr;
+
+	timer = devp->hd_timer;
+	hpet = devp->hd_hpet;
+	hpetp = devp->hd_hpets;
+
+	v = readq(&timer->hpet_config);
+	spin_lock_irq(&hpet_lock);
+
+	if (devp->hd_flags & HPET_IE) {
+		spin_unlock_irq(&hpet_lock);
+		return -EBUSY;
+	}
+
+	devp->hd_flags |= HPET_IE;
+	spin_unlock_irq(&hpet_lock);
+
+	t = readq(&timer->hpet_config);
+	irq = devp->hd_hdwirq;
+
+	if (irq) {
+		sprintf(devp->hd_name, "hpet%d", (int)(devp - hpetp->hp_dev));
+
+		if (request_irq
+		    (irq, hpet_interrupt, SA_INTERRUPT, devp->hd_name, (void *)devp)) {
+			printk(KERN_ERR "hpet: IRQ %d is not free\n", irq);
+			irq = 0;
+		}
+	}
+
+	if (irq == 0) {
+		spin_lock_irq(&hpet_lock);
+		devp->hd_flags ^= HPET_IE;
+		spin_unlock_irq(&hpet_lock);
+		return -EIO;
+	}
+
+	devp->hd_irq = irq;
+	t = devp->hd_ireqfreq;
+	v = readq(&timer->hpet_config);
+	g = v | Tn_INT_ENB_CNF_MASK;
+
+	if (devp->hd_flags & HPET_PERIODIC) {
+		write_counter(t, &timer->hpet_compare);
+		g |= Tn_TYPE_CNF_MASK;
+		v |= Tn_TYPE_CNF_MASK;
+		writeq(v, &timer->hpet_config);
+		v |= Tn_VAL_SET_CNF_MASK;
+		writeq(v, &timer->hpet_config);
+		local_irq_save(flags);
+		m = read_counter(&hpet->hpet_mc);
+		write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
+	} else {
+		local_irq_save(flags);
+		m = read_counter(&hpet->hpet_mc);
+		write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
+	}
+
+	isr = (1 << (devp - hpets->hp_dev));
+	writeq(isr, &hpet->hpet_isr);
+	writeq(g, &timer->hpet_config);
+	local_irq_restore(flags);
+
+	return 0;
+}
+
+static inline unsigned long hpet_time_div(unsigned long dis)
+{
+	unsigned long long m = 1000000000000000ULL;
+
+	do_div(m, dis);
+
+	return (unsigned long)m;
+}
+
+static int
+hpet_ioctl_common(struct hpet_dev *devp, int cmd, unsigned long arg, int kernel)
+{
+	struct hpet_timer __iomem *timer;
+	struct hpet __iomem *hpet;
+	struct hpets *hpetp;
+	int err;
+	unsigned long v;
+
+	switch (cmd) {
+	case HPET_IE_OFF:
+	case HPET_INFO:
+	case HPET_EPI:
+	case HPET_DPI:
+	case HPET_IRQFREQ:
+		timer = devp->hd_timer;
+		hpet = devp->hd_hpet;
+		hpetp = devp->hd_hpets;
+		break;
+	case HPET_IE_ON:
+		return hpet_ioctl_ieon(devp);
+	default:
+		return -EINVAL;
+	}
+
+	err = 0;
+
+	switch (cmd) {
+	case HPET_IE_OFF:
+		if ((devp->hd_flags & HPET_IE) == 0)
+			break;
+		v = readq(&timer->hpet_config);
+		v &= ~Tn_INT_ENB_CNF_MASK;
+		writeq(v, &timer->hpet_config);
+		if (devp->hd_irq) {
+			free_irq(devp->hd_irq, devp);
+			devp->hd_irq = 0;
+		}
+		devp->hd_flags ^= HPET_IE;
+		break;
+	case HPET_INFO:
+		{
+			struct hpet_info info;
+
+			info.hi_ireqfreq = hpet_time_div(hpetp->hp_period *
+							 devp->hd_ireqfreq);
+			info.hi_flags =
+			    readq(&timer->hpet_config) & Tn_PER_INT_CAP_MASK;
+			info.hi_hpet = devp->hd_hpets->hp_which;
+			info.hi_timer = devp - devp->hd_hpets->hp_dev;
+			if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+				err = -EFAULT;
+			break;
+		}
+	case HPET_EPI:
+		v = readq(&timer->hpet_config);
+		if ((v & Tn_PER_INT_CAP_MASK) == 0) {
+			err = -ENXIO;
+			break;
+		}
+		devp->hd_flags |= HPET_PERIODIC;
+		break;
+	case HPET_DPI:
+		v = readq(&timer->hpet_config);
+		if ((v & Tn_PER_INT_CAP_MASK) == 0) {
+			err = -ENXIO;
+			break;
+		}
+		if (devp->hd_flags & HPET_PERIODIC &&
+		    readq(&timer->hpet_config) & Tn_TYPE_CNF_MASK) {
+			v = readq(&timer->hpet_config);
+			v ^= Tn_TYPE_CNF_MASK;
+			writeq(v, &timer->hpet_config);
+		}
+		devp->hd_flags &= ~HPET_PERIODIC;
+		break;
+	case HPET_IRQFREQ:
+		if (!kernel && (arg > hpet_max_freq) &&
+		    !capable(CAP_SYS_RESOURCE)) {
+			err = -EACCES;
+			break;
+		}
+
+		if (arg & (arg - 1)) {
+			err = -EINVAL;
+			break;
+		}
+
+		devp->hd_ireqfreq = hpet_time_div(hpetp->hp_period * arg);
+	}
+
+	return err;
+}
+
+static struct file_operations hpet_fops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.read = hpet_read,
+	.poll = hpet_poll,
+	.ioctl = hpet_ioctl,
+	.open = hpet_open,
+	.release = hpet_release,
+	.fasync = hpet_fasync,
+	.mmap = hpet_mmap,
+};
+
+EXPORT_SYMBOL(hpet_alloc);
+EXPORT_SYMBOL(hpet_register);
+EXPORT_SYMBOL(hpet_unregister);
+EXPORT_SYMBOL(hpet_control);
+
+int hpet_register(struct hpet_task *tp, int periodic)
+{
+	unsigned int i;
+	u64 mask;
+	struct hpet_timer __iomem *timer;
+	struct hpet_dev *devp;
+	struct hpets *hpetp;
+
+	switch (periodic) {
+	case 1:
+		mask = Tn_PER_INT_CAP_MASK;
+		break;
+	case 0:
+		mask = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	spin_lock_irq(&hpet_task_lock);
+	spin_lock(&hpet_lock);
+
+	for (devp = NULL, hpetp = hpets; hpetp && !devp; hpetp = hpetp->hp_next)
+		for (timer = hpetp->hp_hpet->hpet_timers, i = 0;
+		     i < hpetp->hp_ntimer; i++, timer++) {
+			if ((readq(&timer->hpet_config) & Tn_PER_INT_CAP_MASK)
+			    != mask)
+				continue;
+
+			devp = &hpetp->hp_dev[i];
+
+			if (devp->hd_flags & HPET_OPEN || devp->hd_task) {
+				devp = NULL;
+				continue;
+			}
+
+			tp->ht_opaque = devp;
+			devp->hd_task = tp;
+			break;
+		}
+
+	spin_unlock(&hpet_lock);
+	spin_unlock_irq(&hpet_task_lock);
+
+	if (tp->ht_opaque)
+		return 0;
+	else
+		return -EBUSY;
+}
+
+static inline int hpet_tpcheck(struct hpet_task *tp)
+{
+	struct hpet_dev *devp;
+	struct hpets *hpetp;
+
+	devp = tp->ht_opaque;
+
+	if (!devp)
+		return -ENXIO;
+
+	for (hpetp = hpets; hpetp; hpetp = hpetp->hp_next)
+		if (devp >= hpetp->hp_dev
+		    && devp < (hpetp->hp_dev + hpetp->hp_ntimer)
+		    && devp->hd_hpet == hpetp->hp_hpet)
+			return 0;
+
+	return -ENXIO;
+}
+
+int hpet_unregister(struct hpet_task *tp)
+{
+	struct hpet_dev *devp;
+	struct hpet_timer __iomem *timer;
+	int err;
+
+	if ((err = hpet_tpcheck(tp)))
+		return err;
+
+	spin_lock_irq(&hpet_task_lock);
+	spin_lock(&hpet_lock);
+
+	devp = tp->ht_opaque;
+	if (devp->hd_task != tp) {
+		spin_unlock(&hpet_lock);
+		spin_unlock_irq(&hpet_task_lock);
+		return -ENXIO;
+	}
+
+	timer = devp->hd_timer;
+	writeq((readq(&timer->hpet_config) & ~Tn_INT_ENB_CNF_MASK),
+	       &timer->hpet_config);
+	devp->hd_flags &= ~(HPET_IE | HPET_PERIODIC);
+	devp->hd_task = NULL;
+	spin_unlock(&hpet_lock);
+	spin_unlock_irq(&hpet_task_lock);
+
+	return 0;
+}
+
+int hpet_control(struct hpet_task *tp, unsigned int cmd, unsigned long arg)
+{
+	struct hpet_dev *devp;
+	int err;
+
+	if ((err = hpet_tpcheck(tp)))
+		return err;
+
+	spin_lock_irq(&hpet_lock);
+	devp = tp->ht_opaque;
+	if (devp->hd_task != tp) {
+		spin_unlock_irq(&hpet_lock);
+		return -ENXIO;
+	}
+	spin_unlock_irq(&hpet_lock);
+	return hpet_ioctl_common(devp, cmd, arg, 1);
+}
+
+static ctl_table hpet_table[] = {
+	{
+	 .ctl_name = 1,
+	 .procname = "max-user-freq",
+	 .data = &hpet_max_freq,
+	 .maxlen = sizeof(int),
+	 .mode = 0644,
+	 .proc_handler = &proc_dointvec,
+	 },
+	{.ctl_name = 0}
+};
+
+static ctl_table hpet_root[] = {
+	{
+	 .ctl_name = 1,
+	 .procname = "hpet",
+	 .maxlen = 0,
+	 .mode = 0555,
+	 .child = hpet_table,
+	 },
+	{.ctl_name = 0}
+};
+
+static ctl_table dev_root[] = {
+	{
+	 .ctl_name = CTL_DEV,
+	 .procname = "dev",
+	 .maxlen = 0,
+	 .mode = 0555,
+	 .child = hpet_root,
+	 },
+	{.ctl_name = 0}
+};
+
+static struct ctl_table_header *sysctl_header;
+
+static void hpet_register_interpolator(struct hpets *hpetp)
+{
+#ifdef	CONFIG_TIME_INTERPOLATION
+	struct time_interpolator *ti;
+
+	ti = kmalloc(sizeof(*ti), GFP_KERNEL);
+	if (!ti)
+		return;
+
+	memset(ti, 0, sizeof(*ti));
+	ti->source = TIME_SOURCE_MMIO64;
+	ti->shift = 10;
+	ti->addr = &hpetp->hp_hpet->hpet_mc;
+	ti->frequency = hpet_time_div(hpets->hp_period);
+	ti->drift = ti->frequency * HPET_DRIFT / 1000000;
+	ti->mask = -1;
+
+	hpetp->hp_interpolator = ti;
+	register_time_interpolator(ti);
+#endif
+}
+
+/*
+ * Adjustment for when arming the timer with
+ * initial conditions.  That is, main counter
+ * ticks expired before interrupts are enabled.
+ */
+#define	TICK_CALIBRATE	(1000UL)
+
+static unsigned long hpet_calibrate(struct hpets *hpetp)
+{
+	struct hpet_timer __iomem *timer = NULL;
+	unsigned long t, m, count, i, flags, start;
+	struct hpet_dev *devp;
+	int j;
+	struct hpet __iomem *hpet;
+
+	for (j = 0, devp = hpetp->hp_dev; j < hpetp->hp_ntimer; j++, devp++)
+		if ((devp->hd_flags & HPET_OPEN) == 0) {
+			timer = devp->hd_timer;
+			break;
+		}
+
+	if (!timer)
+		return 0;
+
+	hpet = hpets->hp_hpet;
+	t = read_counter(&timer->hpet_compare);
+
+	i = 0;
+	count = hpet_time_div(hpetp->hp_period * TICK_CALIBRATE);
+
+	local_irq_save(flags);
+
+	start = read_counter(&hpet->hpet_mc);
+
+	do {
+		m = read_counter(&hpet->hpet_mc);
+		write_counter(t + m + hpetp->hp_delta, &timer->hpet_compare);
+	} while (i++, (m - start) < count);
+
+	local_irq_restore(flags);
+
+	return (m - start) / i;
+}
+
+int hpet_alloc(struct hpet_data *hdp)
+{
+	u64 cap, mcfg;
+	struct hpet_dev *devp;
+	u32 i, ntimer;
+	struct hpets *hpetp;
+	size_t siz;
+	struct hpet __iomem *hpet;
+	static struct hpets *last = (struct hpets *)0;
+	unsigned long ns;
+
+	/*
+	 * hpet_alloc can be called by platform dependent code.
+	 * if platform dependent code has allocated the hpet
+	 * ACPI also reports hpet, then we catch it here.
+	 */
+	for (hpetp = hpets; hpetp; hpetp = hpetp->hp_next)
+		if (hpetp->hp_hpet == hdp->hd_address)
+			return 0;
+
+	siz = sizeof(struct hpets) + ((hdp->hd_nirqs - 1) *
+				      sizeof(struct hpet_dev));
+
+	hpetp = kmalloc(siz, GFP_KERNEL);
+
+	if (!hpetp)
+		return -ENOMEM;
+
+	memset(hpetp, 0, siz);
+
+	hpetp->hp_which = hpet_nhpet++;
+	hpetp->hp_hpet = hdp->hd_address;
+	hpetp->hp_hpet_phys = hdp->hd_phys_address;
+
+	hpetp->hp_ntimer = hdp->hd_nirqs;
+
+	for (i = 0; i < hdp->hd_nirqs; i++)
+		hpetp->hp_dev[i].hd_hdwirq = hdp->hd_irq[i];
+
+	hpet = hpetp->hp_hpet;
+
+	cap = readq(&hpet->hpet_cap);
+
+	ntimer = ((cap & HPET_NUM_TIM_CAP_MASK) >> HPET_NUM_TIM_CAP_SHIFT) + 1;
+
+	if (hpetp->hp_ntimer != ntimer) {
+		printk(KERN_WARNING "hpet: number irqs doesn't agree"
+		       " with number of timers\n");
+		kfree(hpetp);
+		return -ENODEV;
+	}
+
+	if (last)
+		last->hp_next = hpetp;
+	else
+		hpets = hpetp;
+
+	last = hpetp;
+
+	hpetp->hp_period = (cap & HPET_COUNTER_CLK_PERIOD_MASK) >>
+	    HPET_COUNTER_CLK_PERIOD_SHIFT;
+
+	printk(KERN_INFO "hpet%d: at MMIO 0x%lx, IRQ%s",
+		hpetp->hp_which, hdp->hd_phys_address,
+		hpetp->hp_ntimer > 1 ? "s" : "");
+	for (i = 0; i < hpetp->hp_ntimer; i++)
+		printk("%s %d", i > 0 ? "," : "", hdp->hd_irq[i]);
+	printk("\n");
+
+	ns = hpetp->hp_period;	/* femptoseconds, 10^-15 */
+	do_div(ns, 1000000);	/* convert to nanoseconds, 10^-9 */
+	printk(KERN_INFO "hpet%d: %ldns tick, %d %d-bit timers\n",
+		hpetp->hp_which, ns, hpetp->hp_ntimer,
+		cap & HPET_COUNTER_SIZE_MASK ? 64 : 32);
+
+	mcfg = readq(&hpet->hpet_config);
+	if ((mcfg & HPET_ENABLE_CNF_MASK) == 0) {
+		write_counter(0L, &hpet->hpet_mc);
+		mcfg |= HPET_ENABLE_CNF_MASK;
+		writeq(mcfg, &hpet->hpet_config);
+	}
+
+	for (i = 0, devp = hpetp->hp_dev; i < hpetp->hp_ntimer;
+	     i++, hpet_ntimer++, devp++) {
+		unsigned long v;
+		struct hpet_timer __iomem *timer;
+
+		timer = &hpet->hpet_timers[devp - hpetp->hp_dev];
+		v = readq(&timer->hpet_config);
+
+		devp->hd_hpets = hpetp;
+		devp->hd_hpet = hpet;
+		devp->hd_timer = timer;
+
+		/*
+		 * If the timer was reserved by platform code,
+		 * then make timer unavailable for opens.
+		 */
+		if (hdp->hd_state & (1 << i)) {
+			devp->hd_flags = HPET_OPEN;
+			continue;
+		}
+
+		init_waitqueue_head(&devp->hd_waitqueue);
+	}
+
+	hpetp->hp_delta = hpet_calibrate(hpetp);
+	hpet_register_interpolator(hpetp);
+
+	return 0;
+}
+
+static acpi_status hpet_resources(struct acpi_resource *res, void *data)
+{
+	struct hpet_data *hdp;
+	acpi_status status;
+	struct acpi_resource_address64 addr;
+	struct hpets *hpetp;
+
+	hdp = data;
+
+	status = acpi_resource_to_address64(res, &addr);
+
+	if (ACPI_SUCCESS(status)) {
+		unsigned long size;
+
+		size = addr.max_address_range - addr.min_address_range + 1;
+		hdp->hd_phys_address = addr.min_address_range;
+		hdp->hd_address = ioremap(addr.min_address_range, size);
+
+		for (hpetp = hpets; hpetp; hpetp = hpetp->hp_next)
+			if (hpetp->hp_hpet == hdp->hd_address)
+				return -EBUSY;
+	} else if (res->id == ACPI_RSTYPE_EXT_IRQ) {
+		struct acpi_resource_ext_irq *irqp;
+		int i;
+
+		irqp = &res->data.extended_irq;
+
+		if (irqp->number_of_interrupts > 0) {
+			hdp->hd_nirqs = irqp->number_of_interrupts;
+
+			for (i = 0; i < hdp->hd_nirqs; i++)
+				hdp->hd_irq[i] =
+				    acpi_register_gsi(irqp->interrupts[i],
+						      irqp->edge_level,
+						      irqp->active_high_low);
+		}
+	}
+
+	return AE_OK;
+}
+
+static int hpet_acpi_add(struct acpi_device *device)
+{
+	acpi_status result;
+	struct hpet_data data;
+
+	memset(&data, 0, sizeof(data));
+
+	result =
+	    acpi_walk_resources(device->handle, METHOD_NAME__CRS,
+				hpet_resources, &data);
+
+	if (ACPI_FAILURE(result))
+		return -ENODEV;
+
+	if (!data.hd_address || !data.hd_nirqs) {
+		printk("%s: no address or irqs in _CRS\n", __FUNCTION__);
+		return -ENODEV;
+	}
+
+	return hpet_alloc(&data);
+}
+
+static int hpet_acpi_remove(struct acpi_device *device, int type)
+{
+	/* XXX need to unregister interpolator, dealloc mem, etc */
+	return -EINVAL;
+}
+
+static struct acpi_driver hpet_acpi_driver = {
+	.name = "hpet",
+	.ids = "PNP0103",
+	.ops = {
+		.add = hpet_acpi_add,
+		.remove = hpet_acpi_remove,
+		},
+};
+
+static struct miscdevice hpet_misc = { HPET_MINOR, "hpet", &hpet_fops };
+
+static int __init hpet_init(void)
+{
+	int result;
+
+	result = misc_register(&hpet_misc);
+	if (result < 0)
+		return -ENODEV;
+
+	sysctl_header = register_sysctl_table(dev_root, 0);
+
+	result = acpi_bus_register_driver(&hpet_acpi_driver);
+	if (result < 0) {
+		if (sysctl_header)
+			unregister_sysctl_table(sysctl_header);
+		misc_deregister(&hpet_misc);
+		return result;
+	}
+
+	return 0;
+}
+
+static void __exit hpet_exit(void)
+{
+	acpi_bus_unregister_driver(&hpet_acpi_driver);
+
+	if (sysctl_header)
+		unregister_sysctl_table(sysctl_header);
+	misc_deregister(&hpet_misc);
+
+	return;
+}
+
+module_init(hpet_init);
+module_exit(hpet_exit);
+MODULE_AUTHOR("Bob Picco <Robert.Picco@hp.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/hvc_console.c b/drivers/char/hvc_console.c
new file mode 100644
index 0000000..88cd858
--- /dev/null
+++ b/drivers/char/hvc_console.c
@@ -0,0 +1,831 @@
+/*
+ * Copyright (C) 2001 Anton Blanchard <anton@au.ibm.com>, IBM
+ * Copyright (C) 2001 Paul Mackerras <paulus@au.ibm.com>, IBM
+ * Copyright (C) 2004 Benjamin Herrenschmidt <benh@kernel.crashing.org>, IBM Corp.
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Additional Author(s):
+ *  Ryan S. Arnold <rsa@us.ibm.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <linux/console.h>
+#include <linux/cpumask.h>
+#include <linux/init.h>
+#include <linux/kbd_kern.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/major.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+#include <asm/hvconsole.h>
+#include <asm/vio.h>
+
+#define HVC_MAJOR	229
+#define HVC_MINOR	0
+
+#define TIMEOUT		(10)
+
+/*
+ * Wait this long per iteration while trying to push buffered data to the
+ * hypervisor before allowing the tty to complete a close operation.
+ */
+#define HVC_CLOSE_WAIT (HZ/100) /* 1/10 of a second */
+
+/*
+ * The Linux TTY code does not support dynamic addition of tty derived devices
+ * so we need to know how many tty devices we might need when space is allocated
+ * for the tty device.  Since this driver supports hotplug of vty adapters we
+ * need to make sure we have enough allocated.
+ */
+#define HVC_ALLOC_TTY_ADAPTERS	8
+
+static struct tty_driver *hvc_driver;
+#ifdef CONFIG_MAGIC_SYSRQ
+static int sysrq_pressed;
+#endif
+
+#define N_OUTBUF	16
+#define N_INBUF		16
+
+#define __ALIGNED__	__attribute__((__aligned__(8)))
+
+struct hvc_struct {
+	spinlock_t lock;
+	int index;
+	struct tty_struct *tty;
+	unsigned int count;
+	int do_wakeup;
+	char outbuf[N_OUTBUF] __ALIGNED__;
+	int n_outbuf;
+	uint32_t vtermno;
+	int irq_requested;
+	int irq;
+	struct list_head next;
+	struct kobject kobj; /* ref count & hvc_struct lifetime */
+	struct vio_dev *vdev;
+};
+
+/* dynamic list of hvc_struct instances */
+static struct list_head hvc_structs = LIST_HEAD_INIT(hvc_structs);
+
+/*
+ * Protect the list of hvc_struct instances from inserts and removals during
+ * list traversal.
+ */
+static DEFINE_SPINLOCK(hvc_structs_lock);
+
+/*
+ * Initial console vtermnos for console API usage prior to full console
+ * initialization.  Any vty adapter outside this range will not have usable
+ * console interfaces but can still be used as a tty device.  This has to be
+ * static because kmalloc will not work during early console init.
+ */
+static uint32_t vtermnos[MAX_NR_HVC_CONSOLES];
+
+/* Used for accounting purposes */
+static int num_vterms = 0;
+
+static struct task_struct *hvc_task;
+
+/*
+ * This value is used to associate a tty->index value to a hvc_struct based
+ * upon order of exposure via hvc_probe().
+ */
+static int hvc_count = -1;
+
+/* Picks up late kicks after list walk but before schedule() */
+static int hvc_kicked;
+
+/* Wake the sleeping khvcd */
+static void hvc_kick(void)
+{
+	hvc_kicked = 1;
+	wake_up_process(hvc_task);
+}
+
+/*
+ * NOTE: This API isn't used if the console adapter doesn't support interrupts.
+ * In this case the console is poll driven.
+ */
+static irqreturn_t hvc_handle_interrupt(int irq, void *dev_instance, struct pt_regs *regs)
+{
+	hvc_kick();
+	return IRQ_HANDLED;
+}
+
+static void hvc_unthrottle(struct tty_struct *tty)
+{
+	hvc_kick();
+}
+
+/*
+ * Do not call this function with either the hvc_strucst_lock or the hvc_struct
+ * lock held.  If successful, this function increments the kobject reference
+ * count against the target hvc_struct so it should be released when finished.
+ */
+struct hvc_struct *hvc_get_by_index(int index)
+{
+	struct hvc_struct *hp;
+	unsigned long flags;
+
+	spin_lock(&hvc_structs_lock);
+
+	list_for_each_entry(hp, &hvc_structs, next) {
+		spin_lock_irqsave(&hp->lock, flags);
+		if (hp->index == index) {
+			kobject_get(&hp->kobj);
+			spin_unlock_irqrestore(&hp->lock, flags);
+			spin_unlock(&hvc_structs_lock);
+			return hp;
+		}
+		spin_unlock_irqrestore(&hp->lock, flags);
+	}
+	hp = NULL;
+
+	spin_unlock(&hvc_structs_lock);
+	return hp;
+}
+
+/*
+ * The TTY interface won't be used until after the vio layer has exposed the vty
+ * adapter to the kernel.
+ */
+static int hvc_open(struct tty_struct *tty, struct file * filp)
+{
+	struct hvc_struct *hp;
+	unsigned long flags;
+	int irq = NO_IRQ;
+	int rc = 0;
+	struct kobject *kobjp;
+
+	/* Auto increments kobject reference if found. */
+	if (!(hp = hvc_get_by_index(tty->index))) {
+		printk(KERN_WARNING "hvc_console: tty open failed, no vty associated with tty.\n");
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&hp->lock, flags);
+	/* Check and then increment for fast path open. */
+	if (hp->count++ > 0) {
+		spin_unlock_irqrestore(&hp->lock, flags);
+		hvc_kick();
+		return 0;
+	} /* else count == 0 */
+
+	tty->driver_data = hp;
+	hp->tty = tty;
+	/* Save for request_irq outside of spin_lock. */
+	irq = hp->irq;
+	if (irq != NO_IRQ)
+		hp->irq_requested = 1;
+
+	kobjp = &hp->kobj;
+
+	spin_unlock_irqrestore(&hp->lock, flags);
+	/* check error, fallback to non-irq */
+	if (irq != NO_IRQ)
+		rc = request_irq(irq, hvc_handle_interrupt, SA_INTERRUPT, "hvc_console", hp);
+
+	/*
+	 * If the request_irq() fails and we return an error.  The tty layer
+	 * will call hvc_close() after a failed open but we don't want to clean
+	 * up there so we'll clean up here and clear out the previously set
+	 * tty fields and return the kobject reference.
+	 */
+	if (rc) {
+		spin_lock_irqsave(&hp->lock, flags);
+		hp->tty = NULL;
+		hp->irq_requested = 0;
+		spin_unlock_irqrestore(&hp->lock, flags);
+		tty->driver_data = NULL;
+		kobject_put(kobjp);
+		printk(KERN_ERR "hvc_open: request_irq failed with rc %d.\n", rc);
+	}
+	/* Force wakeup of the polling thread */
+	hvc_kick();
+
+	return rc;
+}
+
+static void hvc_close(struct tty_struct *tty, struct file * filp)
+{
+	struct hvc_struct *hp;
+	struct kobject *kobjp;
+	int irq = NO_IRQ;
+	unsigned long flags;
+
+	if (tty_hung_up_p(filp))
+		return;
+
+	/*
+	 * No driver_data means that this close was issued after a failed
+	 * hvc_open by the tty layer's release_dev() function and we can just
+	 * exit cleanly because the kobject reference wasn't made.
+	 */
+	if (!tty->driver_data)
+		return;
+
+	hp = tty->driver_data;
+	spin_lock_irqsave(&hp->lock, flags);
+
+	kobjp = &hp->kobj;
+	if (--hp->count == 0) {
+		if (hp->irq_requested)
+			irq = hp->irq;
+		hp->irq_requested = 0;
+
+		/* We are done with the tty pointer now. */
+		hp->tty = NULL;
+		spin_unlock_irqrestore(&hp->lock, flags);
+
+		/*
+		 * Chain calls chars_in_buffer() and returns immediately if
+		 * there is no buffered data otherwise sleeps on a wait queue
+		 * waking periodically to check chars_in_buffer().
+		 */
+		tty_wait_until_sent(tty, HVC_CLOSE_WAIT);
+
+		if (irq != NO_IRQ)
+			free_irq(irq, hp);
+
+	} else {
+		if (hp->count < 0)
+			printk(KERN_ERR "hvc_close %X: oops, count is %d\n",
+				hp->vtermno, hp->count);
+		spin_unlock_irqrestore(&hp->lock, flags);
+	}
+
+	kobject_put(kobjp);
+}
+
+static void hvc_hangup(struct tty_struct *tty)
+{
+	struct hvc_struct *hp = tty->driver_data;
+	unsigned long flags;
+	int irq = NO_IRQ;
+	int temp_open_count;
+	struct kobject *kobjp;
+
+	if (!hp)
+		return;
+
+	spin_lock_irqsave(&hp->lock, flags);
+
+	/*
+	 * The N_TTY line discipline has problems such that in a close vs
+	 * open->hangup case this can be called after the final close so prevent
+	 * that from happening for now.
+	 */
+	if (hp->count <= 0) {
+		spin_unlock_irqrestore(&hp->lock, flags);
+		return;
+	}
+
+	kobjp = &hp->kobj;
+	temp_open_count = hp->count;
+	hp->count = 0;
+	hp->n_outbuf = 0;
+	hp->tty = NULL;
+	if (hp->irq_requested)
+		/* Saved for use outside of spin_lock. */
+		irq = hp->irq;
+	hp->irq_requested = 0;
+	spin_unlock_irqrestore(&hp->lock, flags);
+	if (irq != NO_IRQ)
+		free_irq(irq, hp);
+	while(temp_open_count) {
+		--temp_open_count;
+		kobject_put(kobjp);
+	}
+}
+
+/*
+ * Push buffered characters whether they were just recently buffered or waiting
+ * on a blocked hypervisor.  Call this function with hp->lock held.
+ */
+static void hvc_push(struct hvc_struct *hp)
+{
+	int n;
+
+	n = hvc_put_chars(hp->vtermno, hp->outbuf, hp->n_outbuf);
+	if (n <= 0) {
+		if (n == 0)
+			return;
+		/* throw away output on error; this happens when
+		   there is no session connected to the vterm. */
+		hp->n_outbuf = 0;
+	} else
+		hp->n_outbuf -= n;
+	if (hp->n_outbuf > 0)
+		memmove(hp->outbuf, hp->outbuf + n, hp->n_outbuf);
+	else
+		hp->do_wakeup = 1;
+}
+
+static inline int __hvc_write_kernel(struct hvc_struct *hp,
+				   const unsigned char *buf, int count)
+{
+	unsigned long flags;
+	int rsize, written = 0;
+
+	spin_lock_irqsave(&hp->lock, flags);
+
+	/* Push pending writes */
+	if (hp->n_outbuf > 0)
+		hvc_push(hp);
+
+	while (count > 0 && (rsize = N_OUTBUF - hp->n_outbuf) > 0) {
+		if (rsize > count)
+			rsize = count;
+		memcpy(hp->outbuf + hp->n_outbuf, buf, rsize);
+		count -= rsize;
+		buf += rsize;
+		hp->n_outbuf += rsize;
+		written += rsize;
+		hvc_push(hp);
+	}
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	return written;
+}
+static int hvc_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	struct hvc_struct *hp = tty->driver_data;
+	int written;
+
+	/* This write was probably executed during a tty close. */
+	if (!hp)
+		return -EPIPE;
+
+	if (hp->count <= 0)
+		return -EIO;
+
+	written = __hvc_write_kernel(hp, buf, count);
+
+	/*
+	 * Racy, but harmless, kick thread if there is still pending data.
+	 * There really is nothing wrong with kicking the thread, even if there
+	 * is no buffered data.
+	 */
+	if (hp->n_outbuf)
+		hvc_kick();
+
+	return written;
+}
+
+/*
+ * This is actually a contract between the driver and the tty layer outlining
+ * how much write room the driver can guarentee will be sent OR BUFFERED.  This
+ * driver MUST honor the return value.
+ */
+static int hvc_write_room(struct tty_struct *tty)
+{
+	struct hvc_struct *hp = tty->driver_data;
+
+	if (!hp)
+		return -1;
+
+	return N_OUTBUF - hp->n_outbuf;
+}
+
+static int hvc_chars_in_buffer(struct tty_struct *tty)
+{
+	struct hvc_struct *hp = tty->driver_data;
+
+	if (!hp)
+		return -1;
+	return hp->n_outbuf;
+}
+
+#define HVC_POLL_READ	0x00000001
+#define HVC_POLL_WRITE	0x00000002
+#define HVC_POLL_QUICK	0x00000004
+
+static int hvc_poll(struct hvc_struct *hp)
+{
+	struct tty_struct *tty;
+	int i, n, poll_mask = 0;
+	char buf[N_INBUF] __ALIGNED__;
+	unsigned long flags;
+	int read_total = 0;
+
+	spin_lock_irqsave(&hp->lock, flags);
+
+	/* Push pending writes */
+	if (hp->n_outbuf > 0)
+		hvc_push(hp);
+	/* Reschedule us if still some write pending */
+	if (hp->n_outbuf > 0)
+		poll_mask |= HVC_POLL_WRITE;
+
+	/* No tty attached, just skip */
+	tty = hp->tty;
+	if (tty == NULL)
+		goto bail;
+
+	/* Now check if we can get data (are we throttled ?) */
+	if (test_bit(TTY_THROTTLED, &tty->flags))
+		goto throttled;
+
+	/* If we aren't interrupt driven and aren't throttled, we always
+	 * request a reschedule
+	 */
+	if (hp->irq == NO_IRQ)
+		poll_mask |= HVC_POLL_READ;
+
+	/* Read data if any */
+	for (;;) {
+		int count = N_INBUF;
+		if (count > (TTY_FLIPBUF_SIZE - tty->flip.count))
+			count = TTY_FLIPBUF_SIZE - tty->flip.count;
+
+		/* If flip is full, just reschedule a later read */
+		if (count == 0) {
+			poll_mask |= HVC_POLL_READ;
+			break;
+		}
+
+		n = hvc_get_chars(hp->vtermno, buf, count);
+		if (n <= 0) {
+			/* Hangup the tty when disconnected from host */
+			if (n == -EPIPE) {
+				spin_unlock_irqrestore(&hp->lock, flags);
+				tty_hangup(tty);
+				spin_lock_irqsave(&hp->lock, flags);
+			}
+			break;
+		}
+		for (i = 0; i < n; ++i) {
+#ifdef CONFIG_MAGIC_SYSRQ
+			/* Handle the SysRq Hack */
+			if (buf[i] == '\x0f') {	/* ^O -- should support a sequence */
+				sysrq_pressed = 1;
+				continue;
+			} else if (sysrq_pressed) {
+				handle_sysrq(buf[i], NULL, tty);
+				sysrq_pressed = 0;
+				continue;
+			}
+#endif /* CONFIG_MAGIC_SYSRQ */
+			tty_insert_flip_char(tty, buf[i], 0);
+		}
+
+		if (tty->flip.count)
+			tty_schedule_flip(tty);
+
+		/*
+		 * Account for the total amount read in one loop, and if above
+		 * 64 bytes, we do a quick schedule loop to let the tty grok the
+		 * data and eventually throttle us.
+		 */
+		read_total += n;
+		if (read_total >= 64) {
+			poll_mask |= HVC_POLL_QUICK;
+			break;
+		}
+	}
+ throttled:
+	/* Wakeup write queue if necessary */
+	if (hp->do_wakeup) {
+		hp->do_wakeup = 0;
+		tty_wakeup(tty);
+	}
+ bail:
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	return poll_mask;
+}
+
+#if defined(CONFIG_XMON) && defined(CONFIG_SMP)
+extern cpumask_t cpus_in_xmon;
+#else
+static const cpumask_t cpus_in_xmon = CPU_MASK_NONE;
+#endif
+
+/*
+ * This kthread is either polling or interrupt driven.  This is determined by
+ * calling hvc_poll() who determines whether a console adapter support
+ * interrupts.
+ */
+int khvcd(void *unused)
+{
+	int poll_mask;
+	struct hvc_struct *hp;
+
+	__set_current_state(TASK_RUNNING);
+	do {
+		poll_mask = 0;
+		hvc_kicked = 0;
+		wmb();
+		if (cpus_empty(cpus_in_xmon)) {
+			spin_lock(&hvc_structs_lock);
+			list_for_each_entry(hp, &hvc_structs, next) {
+				/*hp = list_entry(node, struct hvc_struct, * next); */
+				poll_mask |= hvc_poll(hp);
+			}
+			spin_unlock(&hvc_structs_lock);
+		} else
+			poll_mask |= HVC_POLL_READ;
+		if (hvc_kicked)
+			continue;
+		if (poll_mask & HVC_POLL_QUICK) {
+			yield();
+			continue;
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (!hvc_kicked) {
+			if (poll_mask == 0)
+				schedule();
+			else
+				msleep_interruptible(TIMEOUT);
+		}
+		__set_current_state(TASK_RUNNING);
+	} while (!kthread_should_stop());
+
+	return 0;
+}
+
+static struct tty_operations hvc_ops = {
+	.open = hvc_open,
+	.close = hvc_close,
+	.write = hvc_write,
+	.hangup = hvc_hangup,
+	.unthrottle = hvc_unthrottle,
+	.write_room = hvc_write_room,
+	.chars_in_buffer = hvc_chars_in_buffer,
+};
+
+char hvc_driver_name[] = "hvc_console";
+
+static struct vio_device_id hvc_driver_table[] __devinitdata= {
+	{"serial", "hvterm1"},
+	{ NULL, }
+};
+MODULE_DEVICE_TABLE(vio, hvc_driver_table);
+
+/* callback when the kboject ref count reaches zero. */
+static void destroy_hvc_struct(struct kobject *kobj)
+{
+	struct hvc_struct *hp = container_of(kobj, struct hvc_struct, kobj);
+	unsigned long flags;
+
+	spin_lock(&hvc_structs_lock);
+
+	spin_lock_irqsave(&hp->lock, flags);
+	list_del(&(hp->next));
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	spin_unlock(&hvc_structs_lock);
+
+	kfree(hp);
+}
+
+static struct kobj_type hvc_kobj_type = {
+	.release = destroy_hvc_struct,
+};
+
+static int __devinit hvc_probe(
+		struct vio_dev *dev,
+		const struct vio_device_id *id)
+{
+	struct hvc_struct *hp;
+
+	/* probed with invalid parameters. */
+	if (!dev || !id)
+		return -EPERM;
+
+	hp = kmalloc(sizeof(*hp), GFP_KERNEL);
+	if (!hp)
+		return -ENOMEM;
+
+	memset(hp, 0x00, sizeof(*hp));
+	hp->vtermno = dev->unit_address;
+	hp->vdev = dev;
+	hp->vdev->dev.driver_data = hp;
+	hp->irq = dev->irq;
+
+	kobject_init(&hp->kobj);
+	hp->kobj.ktype = &hvc_kobj_type;
+
+	spin_lock_init(&hp->lock);
+	spin_lock(&hvc_structs_lock);
+	hp->index = ++hvc_count;
+	list_add_tail(&(hp->next), &hvc_structs);
+	spin_unlock(&hvc_structs_lock);
+
+	return 0;
+}
+
+static int __devexit hvc_remove(struct vio_dev *dev)
+{
+	struct hvc_struct *hp = dev->dev.driver_data;
+	unsigned long flags;
+	struct kobject *kobjp;
+	struct tty_struct *tty;
+
+	spin_lock_irqsave(&hp->lock, flags);
+	tty = hp->tty;
+	kobjp = &hp->kobj;
+
+	if (hp->index < MAX_NR_HVC_CONSOLES)
+		vtermnos[hp->index] = -1;
+
+	/* Don't whack hp->irq because tty_hangup() will need to free the irq. */
+
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	/*
+	 * We 'put' the instance that was grabbed when the kobject instance
+	 * was intialized using kobject_init().  Let the last holder of this
+	 * kobject cause it to be removed, which will probably be the tty_hangup
+	 * below.
+	 */
+	kobject_put(kobjp);
+
+	/*
+	 * This function call will auto chain call hvc_hangup.  The tty should
+	 * always be valid at this time unless a simultaneous tty close already
+	 * cleaned up the hvc_struct.
+	 */
+	if (tty)
+		tty_hangup(tty);
+	return 0;
+}
+
+static struct vio_driver hvc_vio_driver = {
+	.name		= hvc_driver_name,
+	.id_table	= hvc_driver_table,
+	.probe		= hvc_probe,
+	.remove		= hvc_remove,
+};
+
+/* Driver initialization.  Follow console initialization.  This is where the TTY
+ * interfaces start to become available. */
+int __init hvc_init(void)
+{
+	int rc;
+
+	/* We need more than num_vterms adapters due to hotplug additions. */
+	hvc_driver = alloc_tty_driver(HVC_ALLOC_TTY_ADAPTERS);
+	/* hvc_driver = alloc_tty_driver(num_vterms); */
+	if (!hvc_driver)
+		return -ENOMEM;
+
+	hvc_driver->owner = THIS_MODULE;
+	hvc_driver->devfs_name = "hvc/";
+	hvc_driver->driver_name = "hvc";
+	hvc_driver->name = "hvc";
+	hvc_driver->major = HVC_MAJOR;
+	hvc_driver->minor_start = HVC_MINOR;
+	hvc_driver->type = TTY_DRIVER_TYPE_SYSTEM;
+	hvc_driver->init_termios = tty_std_termios;
+	hvc_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(hvc_driver, &hvc_ops);
+
+	if (tty_register_driver(hvc_driver))
+		panic("Couldn't register hvc console driver\n");
+
+	/* Always start the kthread because there can be hotplug vty adapters
+	 * added later. */
+	hvc_task = kthread_run(khvcd, NULL, "khvcd");
+	if (IS_ERR(hvc_task)) {
+		panic("Couldn't create kthread for console.\n");
+		put_tty_driver(hvc_driver);
+		return -EIO;
+	}
+
+	/* Register as a vio device to receive callbacks */
+	rc = vio_register_driver(&hvc_vio_driver);
+
+	return rc;
+}
+
+/* This isn't particularily necessary due to this being a console driver but it
+ * is nice to be thorough */
+static void __exit hvc_exit(void)
+{
+	kthread_stop(hvc_task);
+
+	vio_unregister_driver(&hvc_vio_driver);
+	tty_unregister_driver(hvc_driver);
+	/* return tty_struct instances allocated in hvc_init(). */
+	put_tty_driver(hvc_driver);
+}
+
+/*
+ * Console APIs, NOT TTY.  These APIs are available immediately when
+ * hvc_console_setup() finds adapters.
+ */
+
+/*
+ * hvc_instantiate() is an early console discovery method which locates consoles
+ * prior to the vio subsystem discovering them.  Hotplugged vty adapters do NOT
+ * get an hvc_instantiate() callback since the appear after early console init.
+ */
+int hvc_instantiate(uint32_t vtermno, int index)
+{
+	if (index < 0 || index >= MAX_NR_HVC_CONSOLES)
+		return -1;
+
+	if (vtermnos[index] != -1)
+		return -1;
+
+	vtermnos[index] = vtermno;
+	return 0;
+}
+
+void hvc_console_print(struct console *co, const char *b, unsigned count)
+{
+	char c[16] __ALIGNED__;
+	unsigned i = 0, n = 0;
+	int r, donecr = 0;
+
+	/* Console access attempt outside of acceptable console range. */
+	if (co->index >= MAX_NR_HVC_CONSOLES)
+		return;
+
+	/* This console adapter was removed so it is not useable. */
+	if (vtermnos[co->index] < 0)
+		return;
+
+	while (count > 0 || i > 0) {
+		if (count > 0 && i < sizeof(c)) {
+			if (b[n] == '\n' && !donecr) {
+				c[i++] = '\r';
+				donecr = 1;
+			} else {
+				c[i++] = b[n++];
+				donecr = 0;
+				--count;
+			}
+		} else {
+			r = hvc_put_chars(vtermnos[co->index], c, i);
+			if (r < 0) {
+				/* throw away chars on error */
+				i = 0;
+			} else if (r > 0) {
+				i -= r;
+				if (i > 0)
+					memmove(c, c+r, i);
+			}
+		}
+	}
+}
+
+static struct tty_driver *hvc_console_device(struct console *c, int *index)
+{
+	*index = c->index;
+	return hvc_driver;
+}
+
+static int __init hvc_console_setup(struct console *co, char *options)
+{
+	return 0;
+}
+
+struct console hvc_con_driver = {
+	.name		= "hvc",
+	.write		= hvc_console_print,
+	.device		= hvc_console_device,
+	.setup		= hvc_console_setup,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+};
+
+/* Early console initialization.  Preceeds driver initialization. */
+static int __init hvc_console_init(void)
+{
+	int i;
+
+	for (i=0; i<MAX_NR_HVC_CONSOLES; i++)
+		vtermnos[i] = -1;
+	num_vterms = hvc_find_vtys();
+	register_console(&hvc_con_driver);
+	return 0;
+}
+console_initcall(hvc_console_init);
+
+module_init(hvc_init);
+module_exit(hvc_exit);
diff --git a/drivers/char/hvcs.c b/drivers/char/hvcs.c
new file mode 100644
index 0000000..abfbdcf
--- /dev/null
+++ b/drivers/char/hvcs.c
@@ -0,0 +1,1649 @@
+/*
+ * IBM eServer Hypervisor Virtual Console Server Device Driver
+ * Copyright (C) 2003, 2004 IBM Corp.
+ *  Ryan S. Arnold (rsa@us.ibm.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ * Author(s) :  Ryan S. Arnold <rsa@us.ibm.com>
+ *
+ * This is the device driver for the IBM Hypervisor Virtual Console Server,
+ * "hvcs".  The IBM hvcs provides a tty driver interface to allow Linux
+ * user space applications access to the system consoles of logically
+ * partitioned operating systems, e.g. Linux, running on the same partitioned
+ * Power5 ppc64 system.  Physical hardware consoles per partition are not
+ * practical on this hardware so system consoles are accessed by this driver
+ * using inter-partition firmware interfaces to virtual terminal devices.
+ *
+ * A vty is known to the HMC as a "virtual serial server adapter".  It is a
+ * virtual terminal device that is created by firmware upon partition creation
+ * to act as a partitioned OS's console device.
+ *
+ * Firmware dynamically (via hotplug) exposes vty-servers to a running ppc64
+ * Linux system upon their creation by the HMC or their exposure during boot.
+ * The non-user interactive backend of this driver is implemented as a vio
+ * device driver so that it can receive notification of vty-server lifetimes
+ * after it registers with the vio bus to handle vty-server probe and remove
+ * callbacks.
+ *
+ * Many vty-servers can be configured to connect to one vty, but a vty can
+ * only be actively connected to by a single vty-server, in any manner, at one
+ * time.  If the HMC is currently hosting the console for a target Linux
+ * partition; attempts to open the tty device to the partition's console using
+ * the hvcs on any partition will return -EBUSY with every open attempt until
+ * the HMC frees the connection between its vty-server and the desired
+ * partition's vty device.  Conversely, a vty-server may only be connected to
+ * a single vty at one time even though it may have several configured vty
+ * partner possibilities.
+ *
+ * Firmware does not provide notification of vty partner changes to this
+ * driver.  This means that an HMC Super Admin may add or remove partner vtys
+ * from a vty-server's partner list but the changes will not be signaled to
+ * the vty-server.  Firmware only notifies the driver when a vty-server is
+ * added or removed from the system.  To compensate for this deficiency, this
+ * driver implements a sysfs update attribute which provides a method for
+ * rescanning partner information upon a user's request.
+ *
+ * Each vty-server, prior to being exposed to this driver is reference counted
+ * using the 2.6 Linux kernel kobject construct.  This kobject is also used by
+ * the vio bus to provide a vio device sysfs entry that this driver attaches
+ * device specific attributes to, including partner information.  The vio bus
+ * framework also provides a sysfs entry for each vio driver.  The hvcs driver
+ * provides driver attributes in this entry.
+ *
+ * For direction on installation and usage of this driver please reference
+ * Documentation/powerpc/hvcs.txt.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/major.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/stat.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <asm/hvconsole.h>
+#include <asm/hvcserver.h>
+#include <asm/uaccess.h>
+#include <asm/vio.h>
+
+/*
+ * 1.3.0 -> 1.3.1 In hvcs_open memset(..,0x00,..) instead of memset(..,0x3F,00).
+ * Removed braces around single statements following conditionals.  Removed '=
+ * 0' after static int declarations since these default to zero.  Removed
+ * list_for_each_safe() and replaced with list_for_each_entry() in
+ * hvcs_get_by_index().  The 'safe' version is un-needed now that the driver is
+ * using spinlocks.  Changed spin_lock_irqsave() to spin_lock() when locking
+ * hvcs_structs_lock and hvcs_pi_lock since these are not touched in an int
+ * handler.  Initialized hvcs_structs_lock and hvcs_pi_lock to
+ * SPIN_LOCK_UNLOCKED at declaration time rather than in hvcs_module_init().
+ * Added spin_lock around list_del() in destroy_hvcs_struct() to protect the
+ * list traversals from a deletion.  Removed '= NULL' from pointer declaration
+ * statements since they are initialized NULL by default.  Removed wmb()
+ * instances from hvcs_try_write().  They probably aren't needed with locking in
+ * place.  Added check and cleanup for hvcs_pi_buff = kmalloc() in
+ * hvcs_module_init().  Exposed hvcs_struct.index via a sysfs attribute so that
+ * the coupling between /dev/hvcs* and a vty-server can be automatically
+ * determined.  Moved kobject_put() in hvcs_open outside of the
+ * spin_unlock_irqrestore().
+ *
+ * 1.3.1 -> 1.3.2 Changed method for determining hvcs_struct->index and had it
+ * align with how the tty layer always assigns the lowest index available.  This
+ * change resulted in a list of ints that denotes which indexes are available.
+ * Device additions and removals use the new hvcs_get_index() and
+ * hvcs_return_index() helper functions.  The list is created with
+ * hvsc_alloc_index_list() and it is destroyed with hvcs_free_index_list().
+ * Without these fixes hotplug vty-server adapter support goes crazy with this
+ * driver if the user removes a vty-server adapter.  Moved free_irq() outside of
+ * the hvcs_final_close() function in order to get it out of the spinlock.
+ * Rearranged hvcs_close().  Cleaned up some printks and did some housekeeping
+ * on the changelog.  Removed local CLC_LENGTH and used HVCS_CLC_LENGTH from
+ * arch/ppc64/hvcserver.h.
+ *
+ * 1.3.2 -> 1.3.3 Replaced yield() in hvcs_close() with tty_wait_until_sent() to
+ * prevent possible lockup with realtime scheduling as similarily pointed out by
+ * akpm in hvc_console.  Changed resulted in the removal of hvcs_final_close()
+ * to reorder cleanup operations and prevent discarding of pending data during
+ * an hvcs_close().  Removed spinlock protection of hvcs_struct data members in
+ * hvcs_write_room() and hvcs_chars_in_buffer() because they aren't needed.
+ */
+
+#define HVCS_DRIVER_VERSION "1.3.3"
+
+MODULE_AUTHOR("Ryan S. Arnold <rsa@us.ibm.com>");
+MODULE_DESCRIPTION("IBM hvcs (Hypervisor Virtual Console Server) Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(HVCS_DRIVER_VERSION);
+
+/*
+ * Wait this long per iteration while trying to push buffered data to the
+ * hypervisor before allowing the tty to complete a close operation.
+ */
+#define HVCS_CLOSE_WAIT (HZ/100) /* 1/10 of a second */
+
+/*
+ * Since the Linux TTY code does not currently (2-04-2004) support dynamic
+ * addition of tty derived devices and we shouldn't allocate thousands of
+ * tty_device pointers when the number of vty-server & vty partner connections
+ * will most often be much lower than this, we'll arbitrarily allocate
+ * HVCS_DEFAULT_SERVER_ADAPTERS tty_structs and cdev's by default when we
+ * register the tty_driver. This can be overridden using an insmod parameter.
+ */
+#define HVCS_DEFAULT_SERVER_ADAPTERS	64
+
+/*
+ * The user can't insmod with more than HVCS_MAX_SERVER_ADAPTERS hvcs device
+ * nodes as a sanity check.  Theoretically there can be over 1 Billion
+ * vty-server & vty partner connections.
+ */
+#define HVCS_MAX_SERVER_ADAPTERS	1024
+
+/*
+ * We let Linux assign us a major number and we start the minors at zero.  There
+ * is no intuitive mapping between minor number and the target vty-server
+ * adapter except that each new vty-server adapter is always assigned to the
+ * smallest minor number available.
+ */
+#define HVCS_MINOR_START	0
+
+/*
+ * The hcall interface involves putting 8 chars into each of two registers.
+ * We load up those 2 registers (in arch/ppc64/hvconsole.c) by casting char[16]
+ * to long[2].  It would work without __ALIGNED__, but a little (tiny) bit
+ * slower because an unaligned load is slower than aligned load.
+ */
+#define __ALIGNED__	__attribute__((__aligned__(8)))
+
+/*
+ * How much data can firmware send with each hvc_put_chars()?  Maybe this
+ * should be moved into an architecture specific area.
+ */
+#define HVCS_BUFF_LEN	16
+
+/*
+ * This is the maximum amount of data we'll let the user send us (hvcs_write) at
+ * once in a chunk as a sanity check.
+ */
+#define HVCS_MAX_FROM_USER	4096
+
+/*
+ * Be careful when adding flags to this line discipline.  Don't add anything
+ * that will cause echoing or we'll go into recursive loop echoing chars back
+ * and forth with the console drivers.
+ */
+static struct termios hvcs_tty_termios = {
+	.c_iflag = IGNBRK | IGNPAR,
+	.c_oflag = OPOST,
+	.c_cflag = B38400 | CS8 | CREAD | HUPCL,
+	.c_cc = INIT_C_CC
+};
+
+/*
+ * This value is used to take the place of a command line parameter when the
+ * module is inserted.  It starts as -1 and stays as such if the user doesn't
+ * specify a module insmod parameter.  If they DO specify one then it is set to
+ * the value of the integer passed in.
+ */
+static int hvcs_parm_num_devs = -1;
+module_param(hvcs_parm_num_devs, int, 0);
+
+char hvcs_driver_name[] = "hvcs";
+char hvcs_device_node[] = "hvcs";
+char hvcs_driver_string[]
+	= "IBM hvcs (Hypervisor Virtual Console Server) Driver";
+
+/* Status of partner info rescan triggered via sysfs. */
+static int hvcs_rescan_status;
+
+static struct tty_driver *hvcs_tty_driver;
+
+/*
+ * In order to be somewhat sane this driver always associates the hvcs_struct
+ * index element with the numerically equal tty->index.  This means that a
+ * hotplugged vty-server adapter will always map to the lowest index valued
+ * device node.  If vty-servers were hotplug removed from the system and then
+ * new ones added the new vty-server may have the largest slot number of all
+ * the vty-server adapters in the partition but it may have the lowest dev node
+ * index of all the adapters due to the hole left by the hotplug removed
+ * adapter.  There are a set of functions provided to get the lowest index for
+ * a new device as well as return the index to the list.  This list is allocated
+ * with a number of elements equal to the number of device nodes requested when
+ * the module was inserted.
+ */
+static int *hvcs_index_list;
+
+/*
+ * How large is the list?  This is kept for traversal since the list is
+ * dynamically created.
+ */
+static int hvcs_index_count;
+
+/*
+ * Used by the khvcsd to pick up I/O operations when the kernel_thread is
+ * already awake but potentially shifted to TASK_INTERRUPTIBLE state.
+ */
+static int hvcs_kicked;
+
+/*
+ * Use by the kthread construct for task operations like waking the sleeping
+ * thread and stopping the kthread.
+ */
+static struct task_struct *hvcs_task;
+
+/*
+ * We allocate this for the use of all of the hvcs_structs when they fetch
+ * partner info.
+ */
+static unsigned long *hvcs_pi_buff;
+
+/* Only allow one hvcs_struct to use the hvcs_pi_buff at a time. */
+static DEFINE_SPINLOCK(hvcs_pi_lock);
+
+/* One vty-server per hvcs_struct */
+struct hvcs_struct {
+	spinlock_t lock;
+
+	/*
+	 * This index identifies this hvcs device as the complement to a
+	 * specific tty index.
+	 */
+	unsigned int index;
+
+	struct tty_struct *tty;
+	unsigned int open_count;
+
+	/*
+	 * Used to tell the driver kernel_thread what operations need to take
+	 * place upon this hvcs_struct instance.
+	 */
+	int todo_mask;
+
+	/*
+	 * This buffer is required so that when hvcs_write_room() reports that
+	 * it can send HVCS_BUFF_LEN characters that it will buffer the full
+	 * HVCS_BUFF_LEN characters if need be.  This is essential for opost
+	 * writes since they do not do high level buffering and expect to be
+	 * able to send what the driver commits to sending buffering
+	 * [e.g. tab to space conversions in n_tty.c opost()].
+	 */
+	char buffer[HVCS_BUFF_LEN];
+	int chars_in_buffer;
+
+	/*
+	 * Any variable below the kobject is valid before a tty is connected and
+	 * stays valid after the tty is disconnected.  These shouldn't be
+	 * whacked until the koject refcount reaches zero though some entries
+	 * may be changed via sysfs initiatives.
+	 */
+	struct kobject kobj; /* ref count & hvcs_struct lifetime */
+	int connected; /* is the vty-server currently connected to a vty? */
+	uint32_t p_unit_address; /* partner unit address */
+	uint32_t p_partition_ID; /* partner partition ID */
+	char p_location_code[HVCS_CLC_LENGTH + 1]; /* CLC + Null Term */
+	struct list_head next; /* list management */
+	struct vio_dev *vdev;
+};
+
+/* Required to back map a kobject to its containing object */
+#define from_kobj(kobj) container_of(kobj, struct hvcs_struct, kobj)
+
+static struct list_head hvcs_structs = LIST_HEAD_INIT(hvcs_structs);
+static DEFINE_SPINLOCK(hvcs_structs_lock);
+
+static void hvcs_unthrottle(struct tty_struct *tty);
+static void hvcs_throttle(struct tty_struct *tty);
+static irqreturn_t hvcs_handle_interrupt(int irq, void *dev_instance,
+		struct pt_regs *regs);
+
+static int hvcs_write(struct tty_struct *tty,
+		const unsigned char *buf, int count);
+static int hvcs_write_room(struct tty_struct *tty);
+static int hvcs_chars_in_buffer(struct tty_struct *tty);
+
+static int hvcs_has_pi(struct hvcs_struct *hvcsd);
+static void hvcs_set_pi(struct hvcs_partner_info *pi,
+		struct hvcs_struct *hvcsd);
+static int hvcs_get_pi(struct hvcs_struct *hvcsd);
+static int hvcs_rescan_devices_list(void);
+
+static int hvcs_partner_connect(struct hvcs_struct *hvcsd);
+static void hvcs_partner_free(struct hvcs_struct *hvcsd);
+
+static int hvcs_enable_device(struct hvcs_struct *hvcsd,
+		uint32_t unit_address, unsigned int irq, struct vio_dev *dev);
+
+static void destroy_hvcs_struct(struct kobject *kobj);
+static int hvcs_open(struct tty_struct *tty, struct file *filp);
+static void hvcs_close(struct tty_struct *tty, struct file *filp);
+static void hvcs_hangup(struct tty_struct * tty);
+
+static void hvcs_create_device_attrs(struct hvcs_struct *hvcsd);
+static void hvcs_remove_device_attrs(struct vio_dev *vdev);
+static void hvcs_create_driver_attrs(void);
+static void hvcs_remove_driver_attrs(void);
+
+static int __devinit hvcs_probe(struct vio_dev *dev,
+		const struct vio_device_id *id);
+static int __devexit hvcs_remove(struct vio_dev *dev);
+static int __init hvcs_module_init(void);
+static void __exit hvcs_module_exit(void);
+
+#define HVCS_SCHED_READ	0x00000001
+#define HVCS_QUICK_READ	0x00000002
+#define HVCS_TRY_WRITE	0x00000004
+#define HVCS_READ_MASK	(HVCS_SCHED_READ | HVCS_QUICK_READ)
+
+static void hvcs_kick(void)
+{
+	hvcs_kicked = 1;
+	wmb();
+	wake_up_process(hvcs_task);
+}
+
+static void hvcs_unthrottle(struct tty_struct *tty)
+{
+	struct hvcs_struct *hvcsd = tty->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	hvcsd->todo_mask |= HVCS_SCHED_READ;
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	hvcs_kick();
+}
+
+static void hvcs_throttle(struct tty_struct *tty)
+{
+	struct hvcs_struct *hvcsd = tty->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	vio_disable_interrupts(hvcsd->vdev);
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+}
+
+/*
+ * If the device is being removed we don't have to worry about this interrupt
+ * handler taking any further interrupts because they are disabled which means
+ * the hvcs_struct will always be valid in this handler.
+ */
+static irqreturn_t hvcs_handle_interrupt(int irq, void *dev_instance,
+		struct pt_regs *regs)
+{
+	struct hvcs_struct *hvcsd = dev_instance;
+
+	spin_lock(&hvcsd->lock);
+	vio_disable_interrupts(hvcsd->vdev);
+	hvcsd->todo_mask |= HVCS_SCHED_READ;
+	spin_unlock(&hvcsd->lock);
+	hvcs_kick();
+
+	return IRQ_HANDLED;
+}
+
+/* This function must be called with the hvcsd->lock held */
+static void hvcs_try_write(struct hvcs_struct *hvcsd)
+{
+	uint32_t unit_address = hvcsd->vdev->unit_address;
+	struct tty_struct *tty = hvcsd->tty;
+	int sent;
+
+	if (hvcsd->todo_mask & HVCS_TRY_WRITE) {
+		/* won't send partial writes */
+		sent = hvc_put_chars(unit_address,
+				&hvcsd->buffer[0],
+				hvcsd->chars_in_buffer );
+		if (sent > 0) {
+			hvcsd->chars_in_buffer = 0;
+			/* wmb(); */
+			hvcsd->todo_mask &= ~(HVCS_TRY_WRITE);
+			/* wmb(); */
+
+			/*
+			 * We are still obligated to deliver the data to the
+			 * hypervisor even if the tty has been closed because
+			 * we commited to delivering it.  But don't try to wake
+			 * a non-existent tty.
+			 */
+			if (tty) {
+				tty_wakeup(tty);
+			}
+		}
+	}
+}
+
+static int hvcs_io(struct hvcs_struct *hvcsd)
+{
+	uint32_t unit_address;
+	struct tty_struct *tty;
+	char buf[HVCS_BUFF_LEN] __ALIGNED__;
+	unsigned long flags;
+	int got = 0;
+	int i;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+
+	unit_address = hvcsd->vdev->unit_address;
+	tty = hvcsd->tty;
+
+	hvcs_try_write(hvcsd);
+
+	if (!tty || test_bit(TTY_THROTTLED, &tty->flags)) {
+		hvcsd->todo_mask &= ~(HVCS_READ_MASK);
+		goto bail;
+	} else if (!(hvcsd->todo_mask & (HVCS_READ_MASK)))
+		goto bail;
+
+	/* remove the read masks */
+	hvcsd->todo_mask &= ~(HVCS_READ_MASK);
+
+	if ((tty->flip.count + HVCS_BUFF_LEN) < TTY_FLIPBUF_SIZE) {
+		got = hvc_get_chars(unit_address,
+				&buf[0],
+				HVCS_BUFF_LEN);
+		for (i=0;got && i<got;i++)
+			tty_insert_flip_char(tty, buf[i], TTY_NORMAL);
+	}
+
+	/* Give the TTY time to process the data we just sent. */
+	if (got)
+		hvcsd->todo_mask |= HVCS_QUICK_READ;
+
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	if (tty->flip.count) {
+		/* This is synch because tty->low_latency == 1 */
+		tty_flip_buffer_push(tty);
+	}
+
+	if (!got) {
+		/* Do this _after_ the flip_buffer_push */
+		spin_lock_irqsave(&hvcsd->lock, flags);
+		vio_enable_interrupts(hvcsd->vdev);
+		spin_unlock_irqrestore(&hvcsd->lock, flags);
+	}
+
+	return hvcsd->todo_mask;
+
+ bail:
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	return hvcsd->todo_mask;
+}
+
+static int khvcsd(void *unused)
+{
+	struct hvcs_struct *hvcsd;
+	int hvcs_todo_mask;
+
+	__set_current_state(TASK_RUNNING);
+
+	do {
+		hvcs_todo_mask = 0;
+		hvcs_kicked = 0;
+		wmb();
+
+		spin_lock(&hvcs_structs_lock);
+		list_for_each_entry(hvcsd, &hvcs_structs, next) {
+			hvcs_todo_mask |= hvcs_io(hvcsd);
+		}
+		spin_unlock(&hvcs_structs_lock);
+
+		/*
+		 * If any of the hvcs adapters want to try a write or quick read
+		 * don't schedule(), yield a smidgen then execute the hvcs_io
+		 * thread again for those that want the write.
+		 */
+		 if (hvcs_todo_mask & (HVCS_TRY_WRITE | HVCS_QUICK_READ)) {
+			yield();
+			continue;
+		}
+
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (!hvcs_kicked)
+			schedule();
+		__set_current_state(TASK_RUNNING);
+	} while (!kthread_should_stop());
+
+	return 0;
+}
+
+static struct vio_device_id hvcs_driver_table[] __devinitdata= {
+	{"serial-server", "hvterm2"},
+	{ NULL, }
+};
+MODULE_DEVICE_TABLE(vio, hvcs_driver_table);
+
+static void hvcs_return_index(int index)
+{
+	/* Paranoia check */
+	if (!hvcs_index_list)
+		return;
+	if (index < 0 || index >= hvcs_index_count)
+		return;
+	if (hvcs_index_list[index] == -1)
+		return;
+	else
+		hvcs_index_list[index] = -1;
+}
+
+/* callback when the kboject ref count reaches zero */
+static void destroy_hvcs_struct(struct kobject *kobj)
+{
+	struct hvcs_struct *hvcsd = from_kobj(kobj);
+	struct vio_dev *vdev;
+	unsigned long flags;
+
+	spin_lock(&hvcs_structs_lock);
+	spin_lock_irqsave(&hvcsd->lock, flags);
+
+	/* the list_del poisons the pointers */
+	list_del(&(hvcsd->next));
+
+	if (hvcsd->connected == 1) {
+		hvcs_partner_free(hvcsd);
+		printk(KERN_INFO "HVCS: Closed vty-server@%X and"
+				" partner vty@%X:%d connection.\n",
+				hvcsd->vdev->unit_address,
+				hvcsd->p_unit_address,
+				(uint32_t)hvcsd->p_partition_ID);
+	}
+	printk(KERN_INFO "HVCS: Destroyed hvcs_struct for vty-server@%X.\n",
+			hvcsd->vdev->unit_address);
+
+	vdev = hvcsd->vdev;
+	hvcsd->vdev = NULL;
+
+	hvcsd->p_unit_address = 0;
+	hvcsd->p_partition_ID = 0;
+	hvcs_return_index(hvcsd->index);
+	memset(&hvcsd->p_location_code[0], 0x00, HVCS_CLC_LENGTH + 1);
+
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	spin_unlock(&hvcs_structs_lock);
+
+	hvcs_remove_device_attrs(vdev);
+
+	kfree(hvcsd);
+}
+
+static struct kobj_type hvcs_kobj_type = {
+	.release = destroy_hvcs_struct,
+};
+
+static int hvcs_get_index(void)
+{
+	int i;
+	/* Paranoia check */
+	if (!hvcs_index_list) {
+		printk(KERN_ERR "HVCS: hvcs_index_list NOT valid!.\n");
+		return -EFAULT;
+	}
+	/* Find the numerically lowest first free index. */
+	for(i = 0; i < hvcs_index_count; i++) {
+		if (hvcs_index_list[i] == -1) {
+			hvcs_index_list[i] = 0;
+			return i;
+		}
+	}
+	return -1;
+}
+
+static int __devinit hvcs_probe(
+	struct vio_dev *dev,
+	const struct vio_device_id *id)
+{
+	struct hvcs_struct *hvcsd;
+	int index;
+
+	if (!dev || !id) {
+		printk(KERN_ERR "HVCS: probed with invalid parameter.\n");
+		return -EPERM;
+	}
+
+	/* early to avoid cleanup on failure */
+	index = hvcs_get_index();
+	if (index < 0) {
+		return -EFAULT;
+	}
+
+	hvcsd = kmalloc(sizeof(*hvcsd), GFP_KERNEL);
+	if (!hvcsd)
+		return -ENODEV;
+
+	/* hvcsd->tty is zeroed out with the memset */
+	memset(hvcsd, 0x00, sizeof(*hvcsd));
+
+	spin_lock_init(&hvcsd->lock);
+	/* Automatically incs the refcount the first time */
+	kobject_init(&hvcsd->kobj);
+	/* Set up the callback for terminating the hvcs_struct's life */
+	hvcsd->kobj.ktype = &hvcs_kobj_type;
+
+	hvcsd->vdev = dev;
+	dev->dev.driver_data = hvcsd;
+
+	hvcsd->index = index;
+
+	/* hvcsd->index = ++hvcs_struct_count; */
+	hvcsd->chars_in_buffer = 0;
+	hvcsd->todo_mask = 0;
+	hvcsd->connected = 0;
+
+	/*
+	 * This will populate the hvcs_struct's partner info fields for the
+	 * first time.
+	 */
+	if (hvcs_get_pi(hvcsd)) {
+		printk(KERN_ERR "HVCS: Failed to fetch partner"
+			" info for vty-server@%X on device probe.\n",
+			hvcsd->vdev->unit_address);
+	}
+
+	/*
+	 * If a user app opens a tty that corresponds to this vty-server before
+	 * the hvcs_struct has been added to the devices list then the user app
+	 * will get -ENODEV.
+	 */
+
+	spin_lock(&hvcs_structs_lock);
+
+	list_add_tail(&(hvcsd->next), &hvcs_structs);
+
+	spin_unlock(&hvcs_structs_lock);
+
+	hvcs_create_device_attrs(hvcsd);
+
+	printk(KERN_INFO "HVCS: vty-server@%X added to the vio bus.\n", dev->unit_address);
+
+	/*
+	 * DON'T enable interrupts here because there is no user to receive the
+	 * data.
+	 */
+	return 0;
+}
+
+static int __devexit hvcs_remove(struct vio_dev *dev)
+{
+	struct hvcs_struct *hvcsd = dev->dev.driver_data;
+	unsigned long flags;
+	struct kobject *kobjp;
+	struct tty_struct *tty;
+
+	if (!hvcsd)
+		return -ENODEV;
+
+	/* By this time the vty-server won't be getting any more interrups */
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+
+	tty = hvcsd->tty;
+
+	kobjp = &hvcsd->kobj;
+
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+	/*
+	 * Let the last holder of this object cause it to be removed, which
+	 * would probably be tty_hangup below.
+	 */
+	kobject_put (kobjp);
+
+	/*
+	 * The hangup is a scheduled function which will auto chain call
+	 * hvcs_hangup.  The tty should always be valid at this time unless a
+	 * simultaneous tty close already cleaned up the hvcs_struct.
+	 */
+	if (tty)
+		tty_hangup(tty);
+
+	printk(KERN_INFO "HVCS: vty-server@%X removed from the"
+			" vio bus.\n", dev->unit_address);
+	return 0;
+};
+
+static struct vio_driver hvcs_vio_driver = {
+	.name		= hvcs_driver_name,
+	.id_table	= hvcs_driver_table,
+	.probe		= hvcs_probe,
+	.remove		= hvcs_remove,
+};
+
+/* Only called from hvcs_get_pi please */
+static void hvcs_set_pi(struct hvcs_partner_info *pi, struct hvcs_struct *hvcsd)
+{
+	int clclength;
+
+	hvcsd->p_unit_address = pi->unit_address;
+	hvcsd->p_partition_ID  = pi->partition_ID;
+	clclength = strlen(&pi->location_code[0]);
+	if (clclength > HVCS_CLC_LENGTH)
+		clclength = HVCS_CLC_LENGTH;
+
+	/* copy the null-term char too */
+	strncpy(&hvcsd->p_location_code[0],
+			&pi->location_code[0], clclength + 1);
+}
+
+/*
+ * Traverse the list and add the partner info that is found to the hvcs_struct
+ * struct entry. NOTE: At this time I know that partner info will return a
+ * single entry but in the future there may be multiple partner info entries per
+ * vty-server and you'll want to zero out that list and reset it.  If for some
+ * reason you have an old version of this driver but there IS more than one
+ * partner info then hvcsd->p_* will hold the last partner info data from the
+ * firmware query.  A good way to update this code would be to replace the three
+ * partner info fields in hvcs_struct with a list of hvcs_partner_info
+ * instances.
+ *
+ * This function must be called with the hvcsd->lock held.
+ */
+static int hvcs_get_pi(struct hvcs_struct *hvcsd)
+{
+	struct hvcs_partner_info *pi;
+	uint32_t unit_address = hvcsd->vdev->unit_address;
+	struct list_head head;
+	int retval;
+
+	spin_lock(&hvcs_pi_lock);
+	if (!hvcs_pi_buff) {
+		spin_unlock(&hvcs_pi_lock);
+		return -EFAULT;
+	}
+	retval = hvcs_get_partner_info(unit_address, &head, hvcs_pi_buff);
+	spin_unlock(&hvcs_pi_lock);
+	if (retval) {
+		printk(KERN_ERR "HVCS: Failed to fetch partner"
+			" info for vty-server@%x.\n", unit_address);
+		return retval;
+	}
+
+	/* nixes the values if the partner vty went away */
+	hvcsd->p_unit_address = 0;
+	hvcsd->p_partition_ID = 0;
+
+	list_for_each_entry(pi, &head, node)
+		hvcs_set_pi(pi, hvcsd);
+
+	hvcs_free_partner_info(&head);
+	return 0;
+}
+
+/*
+ * This function is executed by the driver "rescan" sysfs entry.  It shouldn't
+ * be executed elsewhere, in order to prevent deadlock issues.
+ */
+static int hvcs_rescan_devices_list(void)
+{
+	struct hvcs_struct *hvcsd;
+	unsigned long flags;
+
+	spin_lock(&hvcs_structs_lock);
+
+	list_for_each_entry(hvcsd, &hvcs_structs, next) {
+		spin_lock_irqsave(&hvcsd->lock, flags);
+		hvcs_get_pi(hvcsd);
+		spin_unlock_irqrestore(&hvcsd->lock, flags);
+	}
+
+	spin_unlock(&hvcs_structs_lock);
+
+	return 0;
+}
+
+/*
+ * Farm this off into its own function because it could be more complex once
+ * multiple partners support is added. This function should be called with
+ * the hvcsd->lock held.
+ */
+static int hvcs_has_pi(struct hvcs_struct *hvcsd)
+{
+	if ((!hvcsd->p_unit_address) || (!hvcsd->p_partition_ID))
+		return 0;
+	return 1;
+}
+
+/*
+ * NOTE: It is possible that the super admin removed a partner vty and then
+ * added a different vty as the new partner.
+ *
+ * This function must be called with the hvcsd->lock held.
+ */
+static int hvcs_partner_connect(struct hvcs_struct *hvcsd)
+{
+	int retval;
+	unsigned int unit_address = hvcsd->vdev->unit_address;
+
+	/*
+	 * If there wasn't any pi when the device was added it doesn't meant
+	 * there isn't any now.  This driver isn't notified when a new partner
+	 * vty is added to a vty-server so we discover changes on our own.
+	 * Please see comments in hvcs_register_connection() for justification
+	 * of this bizarre code.
+	 */
+	retval = hvcs_register_connection(unit_address,
+			hvcsd->p_partition_ID,
+			hvcsd->p_unit_address);
+	if (!retval) {
+		hvcsd->connected = 1;
+		return 0;
+	} else if (retval != -EINVAL)
+		return retval;
+
+	/*
+	 * As per the spec re-get the pi and try again if -EINVAL after the
+	 * first connection attempt.
+	 */
+	if (hvcs_get_pi(hvcsd))
+		return -ENOMEM;
+
+	if (!hvcs_has_pi(hvcsd))
+		return -ENODEV;
+
+	retval = hvcs_register_connection(unit_address,
+			hvcsd->p_partition_ID,
+			hvcsd->p_unit_address);
+	if (retval != -EINVAL) {
+		hvcsd->connected = 1;
+		return retval;
+	}
+
+	/*
+	 * EBUSY is the most likely scenario though the vty could have been
+	 * removed or there really could be an hcall error due to the parameter
+	 * data but thanks to ambiguous firmware return codes we can't really
+	 * tell.
+	 */
+	printk(KERN_INFO "HVCS: vty-server or partner"
+			" vty is busy.  Try again later.\n");
+	return -EBUSY;
+}
+
+/* This function must be called with the hvcsd->lock held */
+static void hvcs_partner_free(struct hvcs_struct *hvcsd)
+{
+	int retval;
+	do {
+		retval = hvcs_free_connection(hvcsd->vdev->unit_address);
+	} while (retval == -EBUSY);
+	hvcsd->connected = 0;
+}
+
+/* This helper function must be called WITHOUT the hvcsd->lock held */
+static int hvcs_enable_device(struct hvcs_struct *hvcsd, uint32_t unit_address,
+		unsigned int irq, struct vio_dev *vdev)
+{
+	unsigned long flags;
+	int rc;
+
+	/*
+	 * It is possible that the vty-server was removed between the time that
+	 * the conn was registered and now.
+	 */
+	if (!(rc = request_irq(irq, &hvcs_handle_interrupt,
+				SA_INTERRUPT, "ibmhvcs", hvcsd))) {
+		/*
+		 * It is possible the vty-server was removed after the irq was
+		 * requested but before we have time to enable interrupts.
+		 */
+		if (vio_enable_interrupts(vdev) == H_Success)
+			return 0;
+		else {
+			printk(KERN_ERR "HVCS: int enable failed for"
+					" vty-server@%X.\n", unit_address);
+			free_irq(irq, hvcsd);
+		}
+	} else
+		printk(KERN_ERR "HVCS: irq req failed for"
+				" vty-server@%X.\n", unit_address);
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	hvcs_partner_free(hvcsd);
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+	return rc;
+
+}
+
+/*
+ * This always increments the kobject ref count if the call is successful.
+ * Please remember to dec when you are done with the instance.
+ *
+ * NOTICE: Do NOT hold either the hvcs_struct.lock or hvcs_structs_lock when
+ * calling this function or you will get deadlock.
+ */
+struct hvcs_struct *hvcs_get_by_index(int index)
+{
+	struct hvcs_struct *hvcsd = NULL;
+	unsigned long flags;
+
+	spin_lock(&hvcs_structs_lock);
+	/* We can immediately discard OOB requests */
+	if (index >= 0 && index < HVCS_MAX_SERVER_ADAPTERS) {
+		list_for_each_entry(hvcsd, &hvcs_structs, next) {
+			spin_lock_irqsave(&hvcsd->lock, flags);
+			if (hvcsd->index == index) {
+				kobject_get(&hvcsd->kobj);
+				spin_unlock_irqrestore(&hvcsd->lock, flags);
+				spin_unlock(&hvcs_structs_lock);
+				return hvcsd;
+			}
+			spin_unlock_irqrestore(&hvcsd->lock, flags);
+		}
+		hvcsd = NULL;
+	}
+
+	spin_unlock(&hvcs_structs_lock);
+	return hvcsd;
+}
+
+/*
+ * This is invoked via the tty_open interface when a user app connects to the
+ * /dev node.
+ */
+static int hvcs_open(struct tty_struct *tty, struct file *filp)
+{
+	struct hvcs_struct *hvcsd;
+	int rc, retval = 0;
+	unsigned long flags;
+	unsigned int irq;
+	struct vio_dev *vdev;
+	unsigned long unit_address;
+	struct kobject *kobjp;
+
+	if (tty->driver_data)
+		goto fast_open;
+
+	/*
+	 * Is there a vty-server that shares the same index?
+	 * This function increments the kobject index.
+	 */
+	if (!(hvcsd = hvcs_get_by_index(tty->index))) {
+		printk(KERN_WARNING "HVCS: open failed, no device associated"
+				" with tty->index %d.\n", tty->index);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+
+	if (hvcsd->connected == 0)
+		if ((retval = hvcs_partner_connect(hvcsd)))
+			goto error_release;
+
+	hvcsd->open_count = 1;
+	hvcsd->tty = tty;
+	tty->driver_data = hvcsd;
+
+	/*
+	 * Set this driver to low latency so that we actually have a chance at
+	 * catching a throttled TTY after we flip_buffer_push.  Otherwise the
+	 * flush_to_async may not execute until after the kernel_thread has
+	 * yielded and resumed the next flip_buffer_push resulting in data
+	 * loss.
+	 */
+	tty->low_latency = 1;
+
+	memset(&hvcsd->buffer[0], 0x00, HVCS_BUFF_LEN);
+
+	/*
+	 * Save these in the spinlock for the enable operations that need them
+	 * outside of the spinlock.
+	 */
+	irq = hvcsd->vdev->irq;
+	vdev = hvcsd->vdev;
+	unit_address = hvcsd->vdev->unit_address;
+
+	hvcsd->todo_mask |= HVCS_SCHED_READ;
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+	/*
+	 * This must be done outside of the spinlock because it requests irqs
+	 * and will grab the spinlock and free the connection if it fails.
+	 */
+	if (((rc = hvcs_enable_device(hvcsd, unit_address, irq, vdev)))) {
+		kobject_put(&hvcsd->kobj);
+		printk(KERN_WARNING "HVCS: enable device failed.\n");
+		return rc;
+	}
+
+	goto open_success;
+
+fast_open:
+	hvcsd = tty->driver_data;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	if (!kobject_get(&hvcsd->kobj)) {
+		spin_unlock_irqrestore(&hvcsd->lock, flags);
+		printk(KERN_ERR "HVCS: Kobject of open"
+			" hvcs doesn't exist.\n");
+		return -EFAULT; /* Is this the right return value? */
+	}
+
+	hvcsd->open_count++;
+
+	hvcsd->todo_mask |= HVCS_SCHED_READ;
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+open_success:
+	hvcs_kick();
+
+	printk(KERN_INFO "HVCS: vty-server@%X connection opened.\n",
+		hvcsd->vdev->unit_address );
+
+	return 0;
+
+error_release:
+	kobjp = &hvcsd->kobj;
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	kobject_put(&hvcsd->kobj);
+
+	printk(KERN_WARNING "HVCS: partner connect failed.\n");
+	return retval;
+}
+
+static void hvcs_close(struct tty_struct *tty, struct file *filp)
+{
+	struct hvcs_struct *hvcsd;
+	unsigned long flags;
+	struct kobject *kobjp;
+	int irq = NO_IRQ;
+
+	/*
+	 * Is someone trying to close the file associated with this device after
+	 * we have hung up?  If so tty->driver_data wouldn't be valid.
+	 */
+	if (tty_hung_up_p(filp))
+		return;
+
+	/*
+	 * No driver_data means that this close was probably issued after a
+	 * failed hvcs_open by the tty layer's release_dev() api and we can just
+	 * exit cleanly.
+	 */
+	if (!tty->driver_data)
+		return;
+
+	hvcsd = tty->driver_data;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	kobjp = &hvcsd->kobj;
+	if (--hvcsd->open_count == 0) {
+
+		vio_disable_interrupts(hvcsd->vdev);
+
+		/*
+		 * NULL this early so that the kernel_thread doesn't try to
+		 * execute any operations on the TTY even though it is obligated
+		 * to deliver any pending I/O to the hypervisor.
+		 */
+		hvcsd->tty = NULL;
+
+		irq = hvcsd->vdev->irq;
+		spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+		tty_wait_until_sent(tty, HVCS_CLOSE_WAIT);
+
+		/*
+		 * This line is important because it tells hvcs_open that this
+		 * device needs to be re-configured the next time hvcs_open is
+		 * called.
+		 */
+		tty->driver_data = NULL;
+
+		free_irq(irq, hvcsd);
+		kobject_put(kobjp);
+		return;
+	} else if (hvcsd->open_count < 0) {
+		printk(KERN_ERR "HVCS: vty-server@%X open_count: %d"
+				" is missmanaged.\n",
+		hvcsd->vdev->unit_address, hvcsd->open_count);
+	}
+
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	kobject_put(kobjp);
+}
+
+static void hvcs_hangup(struct tty_struct * tty)
+{
+	struct hvcs_struct *hvcsd = tty->driver_data;
+	unsigned long flags;
+	int temp_open_count;
+	struct kobject *kobjp;
+	int irq = NO_IRQ;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	/* Preserve this so that we know how many kobject refs to put */
+	temp_open_count = hvcsd->open_count;
+
+	/*
+	 * Don't kobject put inside the spinlock because the destruction
+	 * callback may use the spinlock and it may get called before the
+	 * spinlock has been released.  Get a pointer to the kobject and
+	 * kobject_put on that after releasing the spinlock.
+	 */
+	kobjp = &hvcsd->kobj;
+
+	vio_disable_interrupts(hvcsd->vdev);
+
+	hvcsd->todo_mask = 0;
+
+	/* I don't think the tty needs the hvcs_struct pointer after a hangup */
+	hvcsd->tty->driver_data = NULL;
+	hvcsd->tty = NULL;
+
+	hvcsd->open_count = 0;
+
+	/* This will drop any buffered data on the floor which is OK in a hangup
+	 * scenario. */
+	memset(&hvcsd->buffer[0], 0x00, HVCS_BUFF_LEN);
+	hvcsd->chars_in_buffer = 0;
+
+	irq = hvcsd->vdev->irq;
+
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+	free_irq(irq, hvcsd);
+
+	/*
+	 * We need to kobject_put() for every open_count we have since the
+	 * tty_hangup() function doesn't invoke a close per open connection on a
+	 * non-console device.
+	 */
+	while(temp_open_count) {
+		--temp_open_count;
+		/*
+		 * The final put will trigger destruction of the hvcs_struct.
+		 * NOTE:  If this hangup was signaled from user space then the
+		 * final put will never happen.
+		 */
+		kobject_put(kobjp);
+	}
+}
+
+/*
+ * NOTE: This is almost always from_user since user level apps interact with the
+ * /dev nodes. I'm trusting that if hvcs_write gets called and interrupted by
+ * hvcs_remove (which removes the target device and executes tty_hangup()) that
+ * tty_hangup will allow hvcs_write time to complete execution before it
+ * terminates our device.
+ */
+static int hvcs_write(struct tty_struct *tty,
+		const unsigned char *buf, int count)
+{
+	struct hvcs_struct *hvcsd = tty->driver_data;
+	unsigned int unit_address;
+	const unsigned char *charbuf;
+	unsigned long flags;
+	int total_sent = 0;
+	int tosend = 0;
+	int result = 0;
+
+	/*
+	 * If they don't check the return code off of their open they may
+	 * attempt this even if there is no connected device.
+	 */
+	if (!hvcsd)
+		return -ENODEV;
+
+	/* Reasonable size to prevent user level flooding */
+	if (count > HVCS_MAX_FROM_USER) {
+		printk(KERN_WARNING "HVCS write: count being truncated to"
+				" HVCS_MAX_FROM_USER.\n");
+		count = HVCS_MAX_FROM_USER;
+	}
+
+	charbuf = buf;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+
+	/*
+	 * Somehow an open succedded but the device was removed or the
+	 * connection terminated between the vty-server and partner vty during
+	 * the middle of a write operation?  This is a crummy place to do this
+	 * but we want to keep it all in the spinlock.
+	 */
+	if (hvcsd->open_count <= 0) {
+		spin_unlock_irqrestore(&hvcsd->lock, flags);
+		return -ENODEV;
+	}
+
+	unit_address = hvcsd->vdev->unit_address;
+
+	while (count > 0) {
+		tosend = min(count, (HVCS_BUFF_LEN - hvcsd->chars_in_buffer));
+		/*
+		 * No more space, this probably means that the last call to
+		 * hvcs_write() didn't succeed and the buffer was filled up.
+		 */
+		if (!tosend)
+			break;
+
+		memcpy(&hvcsd->buffer[hvcsd->chars_in_buffer],
+				&charbuf[total_sent],
+				tosend);
+
+		hvcsd->chars_in_buffer += tosend;
+
+		result = 0;
+
+		/*
+		 * If this is true then we don't want to try writing to the
+		 * hypervisor because that is the kernel_threads job now.  We'll
+		 * just add to the buffer.
+		 */
+		if (!(hvcsd->todo_mask & HVCS_TRY_WRITE))
+			/* won't send partial writes */
+			result = hvc_put_chars(unit_address,
+					&hvcsd->buffer[0],
+					hvcsd->chars_in_buffer);
+
+		/*
+		 * Since we know we have enough room in hvcsd->buffer for
+		 * tosend we record that it was sent regardless of whether the
+		 * hypervisor actually took it because we have it buffered.
+		 */
+		total_sent+=tosend;
+		count-=tosend;
+		if (result == 0) {
+			hvcsd->todo_mask |= HVCS_TRY_WRITE;
+			hvcs_kick();
+			break;
+		}
+
+		hvcsd->chars_in_buffer = 0;
+		/*
+		 * Test after the chars_in_buffer reset otherwise this could
+		 * deadlock our writes if hvc_put_chars fails.
+		 */
+		if (result < 0)
+			break;
+	}
+
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+
+	if (result == -1)
+		return -EIO;
+	else
+		return total_sent;
+}
+
+/*
+ * This is really asking how much can we guarentee that we can send or that we
+ * absolutely WILL BUFFER if we can't send it.  This driver MUST honor the
+ * return value, hence the reason for hvcs_struct buffering.
+ */
+static int hvcs_write_room(struct tty_struct *tty)
+{
+	struct hvcs_struct *hvcsd = tty->driver_data;
+
+	if (!hvcsd || hvcsd->open_count <= 0)
+		return 0;
+
+	return HVCS_BUFF_LEN - hvcsd->chars_in_buffer;
+}
+
+static int hvcs_chars_in_buffer(struct tty_struct *tty)
+{
+	struct hvcs_struct *hvcsd = tty->driver_data;
+
+	return hvcsd->chars_in_buffer;
+}
+
+static struct tty_operations hvcs_ops = {
+	.open = hvcs_open,
+	.close = hvcs_close,
+	.hangup = hvcs_hangup,
+	.write = hvcs_write,
+	.write_room = hvcs_write_room,
+	.chars_in_buffer = hvcs_chars_in_buffer,
+	.unthrottle = hvcs_unthrottle,
+	.throttle = hvcs_throttle,
+};
+
+static int hvcs_alloc_index_list(int n)
+{
+	int i;
+	hvcs_index_list = kmalloc(n * sizeof(hvcs_index_count),GFP_KERNEL);
+	if (!hvcs_index_list)
+		return -ENOMEM;
+	hvcs_index_count = n;
+	for(i = 0; i < hvcs_index_count; i++)
+		hvcs_index_list[i] = -1;
+	return 0;
+}
+
+static void hvcs_free_index_list(void)
+{
+	/* Paranoia check to be thorough. */
+	if (hvcs_index_list) {
+		kfree(hvcs_index_list);
+		hvcs_index_list = NULL;
+		hvcs_index_count = 0;
+	}
+}
+
+static int __init hvcs_module_init(void)
+{
+	int rc;
+	int num_ttys_to_alloc;
+
+	printk(KERN_INFO "Initializing %s\n", hvcs_driver_string);
+
+	/* Has the user specified an overload with an insmod param? */
+	if (hvcs_parm_num_devs <= 0 ||
+		(hvcs_parm_num_devs > HVCS_MAX_SERVER_ADAPTERS)) {
+		num_ttys_to_alloc = HVCS_DEFAULT_SERVER_ADAPTERS;
+	} else
+		num_ttys_to_alloc = hvcs_parm_num_devs;
+
+	hvcs_tty_driver = alloc_tty_driver(num_ttys_to_alloc);
+	if (!hvcs_tty_driver)
+		return -ENOMEM;
+
+	if (hvcs_alloc_index_list(num_ttys_to_alloc))
+		return -ENOMEM;
+
+	hvcs_tty_driver->owner = THIS_MODULE;
+
+	hvcs_tty_driver->driver_name = hvcs_driver_name;
+	hvcs_tty_driver->name = hvcs_device_node;
+	hvcs_tty_driver->devfs_name = hvcs_device_node;
+
+	/*
+	 * We'll let the system assign us a major number, indicated by leaving
+	 * it blank.
+	 */
+
+	hvcs_tty_driver->minor_start = HVCS_MINOR_START;
+	hvcs_tty_driver->type = TTY_DRIVER_TYPE_SYSTEM;
+
+	/*
+	 * We role our own so that we DONT ECHO.  We can't echo because the
+	 * device we are connecting to already echoes by default and this would
+	 * throw us into a horrible recursive echo-echo-echo loop.
+	 */
+	hvcs_tty_driver->init_termios = hvcs_tty_termios;
+	hvcs_tty_driver->flags = TTY_DRIVER_REAL_RAW;
+
+	tty_set_operations(hvcs_tty_driver, &hvcs_ops);
+
+	/*
+	 * The following call will result in sysfs entries that denote the
+	 * dynamically assigned major and minor numbers for our devices.
+	 */
+	if (tty_register_driver(hvcs_tty_driver)) {
+		printk(KERN_ERR "HVCS: registration "
+			" as a tty driver failed.\n");
+		hvcs_free_index_list();
+		put_tty_driver(hvcs_tty_driver);
+		return -EIO;
+	}
+
+	hvcs_pi_buff = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!hvcs_pi_buff) {
+		tty_unregister_driver(hvcs_tty_driver);
+		hvcs_free_index_list();
+		put_tty_driver(hvcs_tty_driver);
+		return -ENOMEM;
+	}
+
+	hvcs_task = kthread_run(khvcsd, NULL, "khvcsd");
+	if (IS_ERR(hvcs_task)) {
+		printk(KERN_ERR "HVCS: khvcsd creation failed.  Driver not loaded.\n");
+		kfree(hvcs_pi_buff);
+		tty_unregister_driver(hvcs_tty_driver);
+		hvcs_free_index_list();
+		put_tty_driver(hvcs_tty_driver);
+		return -EIO;
+	}
+
+	rc = vio_register_driver(&hvcs_vio_driver);
+
+	/*
+	 * This needs to be done AFTER the vio_register_driver() call or else
+	 * the kobjects won't be initialized properly.
+	 */
+	hvcs_create_driver_attrs();
+
+	printk(KERN_INFO "HVCS: driver module inserted.\n");
+
+	return rc;
+}
+
+static void __exit hvcs_module_exit(void)
+{
+	/*
+	 * This driver receives hvcs_remove callbacks for each device upon
+	 * module removal.
+	 */
+
+	/*
+	 * This synchronous operation  will wake the khvcsd kthread if it is
+	 * asleep and will return when khvcsd has terminated.
+	 */
+	kthread_stop(hvcs_task);
+
+	spin_lock(&hvcs_pi_lock);
+	kfree(hvcs_pi_buff);
+	hvcs_pi_buff = NULL;
+	spin_unlock(&hvcs_pi_lock);
+
+	hvcs_remove_driver_attrs();
+
+	vio_unregister_driver(&hvcs_vio_driver);
+
+	tty_unregister_driver(hvcs_tty_driver);
+
+	hvcs_free_index_list();
+
+	put_tty_driver(hvcs_tty_driver);
+
+	printk(KERN_INFO "HVCS: driver module removed.\n");
+}
+
+module_init(hvcs_module_init);
+module_exit(hvcs_module_exit);
+
+static inline struct hvcs_struct *from_vio_dev(struct vio_dev *viod)
+{
+	return viod->dev.driver_data;
+}
+/* The sysfs interface for the driver and devices */
+
+static ssize_t hvcs_partner_vtys_show(struct device *dev, char *buf)
+{
+	struct vio_dev *viod = to_vio_dev(dev);
+	struct hvcs_struct *hvcsd = from_vio_dev(viod);
+	unsigned long flags;
+	int retval;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	retval = sprintf(buf, "%X\n", hvcsd->p_unit_address);
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	return retval;
+}
+static DEVICE_ATTR(partner_vtys, S_IRUGO, hvcs_partner_vtys_show, NULL);
+
+static ssize_t hvcs_partner_clcs_show(struct device *dev, char *buf)
+{
+	struct vio_dev *viod = to_vio_dev(dev);
+	struct hvcs_struct *hvcsd = from_vio_dev(viod);
+	unsigned long flags;
+	int retval;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	retval = sprintf(buf, "%s\n", &hvcsd->p_location_code[0]);
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	return retval;
+}
+static DEVICE_ATTR(partner_clcs, S_IRUGO, hvcs_partner_clcs_show, NULL);
+
+static ssize_t hvcs_current_vty_store(struct device *dev, const char * buf,
+		size_t count)
+{
+	/*
+	 * Don't need this feature at the present time because firmware doesn't
+	 * yet support multiple partners.
+	 */
+	printk(KERN_INFO "HVCS: Denied current_vty change: -EPERM.\n");
+	return -EPERM;
+}
+
+static ssize_t hvcs_current_vty_show(struct device *dev, char *buf)
+{
+	struct vio_dev *viod = to_vio_dev(dev);
+	struct hvcs_struct *hvcsd = from_vio_dev(viod);
+	unsigned long flags;
+	int retval;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	retval = sprintf(buf, "%s\n", &hvcsd->p_location_code[0]);
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	return retval;
+}
+
+static DEVICE_ATTR(current_vty,
+	S_IRUGO | S_IWUSR, hvcs_current_vty_show, hvcs_current_vty_store);
+
+static ssize_t hvcs_vterm_state_store(struct device *dev, const char *buf,
+		size_t count)
+{
+	struct vio_dev *viod = to_vio_dev(dev);
+	struct hvcs_struct *hvcsd = from_vio_dev(viod);
+	unsigned long flags;
+
+	/* writing a '0' to this sysfs entry will result in the disconnect. */
+	if (simple_strtol(buf, NULL, 0) != 0)
+		return -EINVAL;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+
+	if (hvcsd->open_count > 0) {
+		spin_unlock_irqrestore(&hvcsd->lock, flags);
+		printk(KERN_INFO "HVCS: vterm state unchanged.  "
+				"The hvcs device node is still in use.\n");
+		return -EPERM;
+	}
+
+	if (hvcsd->connected == 0) {
+		spin_unlock_irqrestore(&hvcsd->lock, flags);
+		printk(KERN_INFO "HVCS: vterm state unchanged. The"
+				" vty-server is not connected to a vty.\n");
+		return -EPERM;
+	}
+
+	hvcs_partner_free(hvcsd);
+	printk(KERN_INFO "HVCS: Closed vty-server@%X and"
+			" partner vty@%X:%d connection.\n",
+			hvcsd->vdev->unit_address,
+			hvcsd->p_unit_address,
+			(uint32_t)hvcsd->p_partition_ID);
+
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	return count;
+}
+
+static ssize_t hvcs_vterm_state_show(struct device *dev, char *buf)
+{
+	struct vio_dev *viod = to_vio_dev(dev);
+	struct hvcs_struct *hvcsd = from_vio_dev(viod);
+	unsigned long flags;
+	int retval;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	retval = sprintf(buf, "%d\n", hvcsd->connected);
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	return retval;
+}
+static DEVICE_ATTR(vterm_state, S_IRUGO | S_IWUSR,
+		hvcs_vterm_state_show, hvcs_vterm_state_store);
+
+static ssize_t hvcs_index_show(struct device *dev, char *buf)
+{
+	struct vio_dev *viod = to_vio_dev(dev);
+	struct hvcs_struct *hvcsd = from_vio_dev(viod);
+	unsigned long flags;
+	int retval;
+
+	spin_lock_irqsave(&hvcsd->lock, flags);
+	retval = sprintf(buf, "%d\n", hvcsd->index);
+	spin_unlock_irqrestore(&hvcsd->lock, flags);
+	return retval;
+}
+
+static DEVICE_ATTR(index, S_IRUGO, hvcs_index_show, NULL);
+
+static struct attribute *hvcs_attrs[] = {
+	&dev_attr_partner_vtys.attr,
+	&dev_attr_partner_clcs.attr,
+	&dev_attr_current_vty.attr,
+	&dev_attr_vterm_state.attr,
+	&dev_attr_index.attr,
+	NULL,
+};
+
+static struct attribute_group hvcs_attr_group = {
+	.attrs = hvcs_attrs,
+};
+
+static void hvcs_create_device_attrs(struct hvcs_struct *hvcsd)
+{
+	struct vio_dev *vdev = hvcsd->vdev;
+	sysfs_create_group(&vdev->dev.kobj, &hvcs_attr_group);
+}
+
+static void hvcs_remove_device_attrs(struct vio_dev *vdev)
+{
+	sysfs_remove_group(&vdev->dev.kobj, &hvcs_attr_group);
+}
+
+static ssize_t hvcs_rescan_show(struct device_driver *ddp, char *buf)
+{
+	/* A 1 means it is updating, a 0 means it is done updating */
+	return snprintf(buf, PAGE_SIZE, "%d\n", hvcs_rescan_status);
+}
+
+static ssize_t hvcs_rescan_store(struct device_driver *ddp, const char * buf,
+		size_t count)
+{
+	if ((simple_strtol(buf, NULL, 0) != 1)
+		&& (hvcs_rescan_status != 0))
+		return -EINVAL;
+
+	hvcs_rescan_status = 1;
+	printk(KERN_INFO "HVCS: rescanning partner info for all"
+		" vty-servers.\n");
+	hvcs_rescan_devices_list();
+	hvcs_rescan_status = 0;
+	return count;
+}
+static DRIVER_ATTR(rescan,
+	S_IRUGO | S_IWUSR, hvcs_rescan_show, hvcs_rescan_store);
+
+static void hvcs_create_driver_attrs(void)
+{
+	struct device_driver *driverfs = &(hvcs_vio_driver.driver);
+	driver_create_file(driverfs, &driver_attr_rescan);
+}
+
+static void hvcs_remove_driver_attrs(void)
+{
+	struct device_driver *driverfs = &(hvcs_vio_driver.driver);
+	driver_remove_file(driverfs, &driver_attr_rescan);
+}
diff --git a/drivers/char/hvsi.c b/drivers/char/hvsi.c
new file mode 100644
index 0000000..f1f1192
--- /dev/null
+++ b/drivers/char/hvsi.c
@@ -0,0 +1,1320 @@
+/*
+ * Copyright (C) 2004 Hollis Blanchard <hollisb@us.ibm.com>, IBM
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+/* Host Virtual Serial Interface (HVSI) is a protocol between the hosted OS
+ * and the service processor on IBM pSeries servers. On these servers, there
+ * are no serial ports under the OS's control, and sometimes there is no other
+ * console available either. However, the service processor has two standard
+ * serial ports, so this over-complicated protocol allows the OS to control
+ * those ports by proxy.
+ *
+ * Besides data, the procotol supports the reading/writing of the serial
+ * port's DTR line, and the reading of the CD line. This is to allow the OS to
+ * control a modem attached to the service processor's serial port. Note that
+ * the OS cannot change the speed of the port through this protocol.
+ */
+
+#undef DEBUG
+
+#include <linux/console.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/major.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/sysrq.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <asm/hvcall.h>
+#include <asm/hvconsole.h>
+#include <asm/prom.h>
+#include <asm/uaccess.h>
+#include <asm/vio.h>
+#include <asm/param.h>
+
+#define HVSI_MAJOR	229
+#define HVSI_MINOR	128
+#define MAX_NR_HVSI_CONSOLES 4
+
+#define HVSI_TIMEOUT (5*HZ)
+#define HVSI_VERSION 1
+#define HVSI_MAX_PACKET 256
+#define HVSI_MAX_READ 16
+#define HVSI_MAX_OUTGOING_DATA 12
+#define N_OUTBUF 12
+
+/*
+ * we pass data via two 8-byte registers, so we would like our char arrays
+ * properly aligned for those loads.
+ */
+#define __ALIGNED__	__attribute__((__aligned__(sizeof(long))))
+
+struct hvsi_struct {
+	struct work_struct writer;
+	struct work_struct handshaker;
+	wait_queue_head_t emptyq; /* woken when outbuf is emptied */
+	wait_queue_head_t stateq; /* woken when HVSI state changes */
+	spinlock_t lock;
+	int index;
+	struct tty_struct *tty;
+	unsigned int count;
+	uint8_t throttle_buf[128];
+	uint8_t outbuf[N_OUTBUF]; /* to implement write_room and chars_in_buffer */
+	/* inbuf is for packet reassembly. leave a little room for leftovers. */
+	uint8_t inbuf[HVSI_MAX_PACKET + HVSI_MAX_READ];
+	uint8_t *inbuf_end;
+	int n_throttle;
+	int n_outbuf;
+	uint32_t vtermno;
+	uint32_t virq;
+	atomic_t seqno; /* HVSI packet sequence number */
+	uint16_t mctrl;
+	uint8_t state;  /* HVSI protocol state */
+	uint8_t flags;
+#ifdef CONFIG_MAGIC_SYSRQ
+	uint8_t sysrq;
+#endif /* CONFIG_MAGIC_SYSRQ */
+};
+static struct hvsi_struct hvsi_ports[MAX_NR_HVSI_CONSOLES];
+
+static struct tty_driver *hvsi_driver;
+static int hvsi_count;
+static int (*hvsi_wait)(struct hvsi_struct *hp, int state);
+
+enum HVSI_PROTOCOL_STATE {
+	HVSI_CLOSED,
+	HVSI_WAIT_FOR_VER_RESPONSE,
+	HVSI_WAIT_FOR_VER_QUERY,
+	HVSI_OPEN,
+	HVSI_WAIT_FOR_MCTRL_RESPONSE,
+	HVSI_FSP_DIED,
+};
+#define HVSI_CONSOLE 0x1
+
+#define VS_DATA_PACKET_HEADER           0xff
+#define VS_CONTROL_PACKET_HEADER        0xfe
+#define VS_QUERY_PACKET_HEADER          0xfd
+#define VS_QUERY_RESPONSE_PACKET_HEADER 0xfc
+
+/* control verbs */
+#define VSV_SET_MODEM_CTL    1 /* to service processor only */
+#define VSV_MODEM_CTL_UPDATE 2 /* from service processor only */
+#define VSV_CLOSE_PROTOCOL   3
+
+/* query verbs */
+#define VSV_SEND_VERSION_NUMBER 1
+#define VSV_SEND_MODEM_CTL_STATUS 2
+
+/* yes, these masks are not consecutive. */
+#define HVSI_TSDTR 0x01
+#define HVSI_TSCD  0x20
+
+struct hvsi_header {
+	uint8_t  type;
+	uint8_t  len;
+	uint16_t seqno;
+} __attribute__((packed));
+
+struct hvsi_data {
+	uint8_t  type;
+	uint8_t  len;
+	uint16_t seqno;
+	uint8_t  data[HVSI_MAX_OUTGOING_DATA];
+} __attribute__((packed));
+
+struct hvsi_control {
+	uint8_t  type;
+	uint8_t  len;
+	uint16_t seqno;
+	uint16_t verb;
+	/* optional depending on verb: */
+	uint32_t word;
+	uint32_t mask;
+} __attribute__((packed));
+
+struct hvsi_query {
+	uint8_t  type;
+	uint8_t  len;
+	uint16_t seqno;
+	uint16_t verb;
+} __attribute__((packed));
+
+struct hvsi_query_response {
+	uint8_t  type;
+	uint8_t  len;
+	uint16_t seqno;
+	uint16_t verb;
+	uint16_t query_seqno;
+	union {
+		uint8_t  version;
+		uint32_t mctrl_word;
+	} u;
+} __attribute__((packed));
+
+
+
+static inline int is_console(struct hvsi_struct *hp)
+{
+	return hp->flags & HVSI_CONSOLE;
+}
+
+static inline int is_open(struct hvsi_struct *hp)
+{
+	/* if we're waiting for an mctrl then we're already open */
+	return (hp->state == HVSI_OPEN)
+			|| (hp->state == HVSI_WAIT_FOR_MCTRL_RESPONSE);
+}
+
+static inline void print_state(struct hvsi_struct *hp)
+{
+#ifdef DEBUG
+	static const char *state_names[] = {
+		"HVSI_CLOSED",
+		"HVSI_WAIT_FOR_VER_RESPONSE",
+		"HVSI_WAIT_FOR_VER_QUERY",
+		"HVSI_OPEN",
+		"HVSI_WAIT_FOR_MCTRL_RESPONSE",
+		"HVSI_FSP_DIED",
+	};
+	const char *name = state_names[hp->state];
+
+	if (hp->state > (sizeof(state_names)/sizeof(char*)))
+		name = "UNKNOWN";
+
+	pr_debug("hvsi%i: state = %s\n", hp->index, name);
+#endif /* DEBUG */
+}
+
+static inline void __set_state(struct hvsi_struct *hp, int state)
+{
+	hp->state = state;
+	print_state(hp);
+	wake_up_all(&hp->stateq);
+}
+
+static inline void set_state(struct hvsi_struct *hp, int state)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&hp->lock, flags);
+	__set_state(hp, state);
+	spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+static inline int len_packet(const uint8_t *packet)
+{
+	return (int)((struct hvsi_header *)packet)->len;
+}
+
+static inline int is_header(const uint8_t *packet)
+{
+	struct hvsi_header *header = (struct hvsi_header *)packet;
+	return header->type >= VS_QUERY_RESPONSE_PACKET_HEADER;
+}
+
+static inline int got_packet(const struct hvsi_struct *hp, uint8_t *packet)
+{
+	if (hp->inbuf_end < packet + sizeof(struct hvsi_header))
+		return 0; /* don't even have the packet header */
+
+	if (hp->inbuf_end < (packet + len_packet(packet)))
+		return 0; /* don't have the rest of the packet */
+
+	return 1;
+}
+
+/* shift remaining bytes in packetbuf down */
+static void compact_inbuf(struct hvsi_struct *hp, uint8_t *read_to)
+{
+	int remaining = (int)(hp->inbuf_end - read_to);
+
+	pr_debug("%s: %i chars remain\n", __FUNCTION__, remaining);
+
+	if (read_to != hp->inbuf)
+		memmove(hp->inbuf, read_to, remaining);
+
+	hp->inbuf_end = hp->inbuf + remaining;
+}
+
+#ifdef DEBUG
+#define dbg_dump_packet(packet) dump_packet(packet)
+#define dbg_dump_hex(data, len) dump_hex(data, len)
+#else
+#define dbg_dump_packet(packet) do { } while (0)
+#define dbg_dump_hex(data, len) do { } while (0)
+#endif
+
+static void dump_hex(const uint8_t *data, int len)
+{
+	int i;
+
+	printk("    ");
+	for (i=0; i < len; i++)
+		printk("%.2x", data[i]);
+
+	printk("\n    ");
+	for (i=0; i < len; i++) {
+		if (isprint(data[i]))
+			printk("%c", data[i]);
+		else
+			printk(".");
+	}
+	printk("\n");
+}
+
+static void dump_packet(uint8_t *packet)
+{
+	struct hvsi_header *header = (struct hvsi_header *)packet;
+
+	printk("type 0x%x, len %i, seqno %i:\n", header->type, header->len,
+			header->seqno);
+
+	dump_hex(packet, header->len);
+}
+
+/* can't use hvc_get_chars because that strips CRs */
+static int hvsi_read(struct hvsi_struct *hp, char *buf, int count)
+{
+	unsigned long got;
+
+	if (plpar_hcall(H_GET_TERM_CHAR, hp->vtermno, 0, 0, 0, &got,
+			(unsigned long *)buf, (unsigned long *)buf+1) == H_Success)
+		return got;
+	return 0;
+}
+
+static void hvsi_recv_control(struct hvsi_struct *hp, uint8_t *packet,
+	struct tty_struct **to_hangup, struct hvsi_struct **to_handshake)
+{
+	struct hvsi_control *header = (struct hvsi_control *)packet;
+
+	switch (header->verb) {
+		case VSV_MODEM_CTL_UPDATE:
+			if ((header->word & HVSI_TSCD) == 0) {
+				/* CD went away; no more connection */
+				pr_debug("hvsi%i: CD dropped\n", hp->index);
+				hp->mctrl &= TIOCM_CD;
+				if (!(hp->tty->flags & CLOCAL))
+					*to_hangup = hp->tty;
+			}
+			break;
+		case VSV_CLOSE_PROTOCOL:
+			pr_debug("hvsi%i: service processor came back\n", hp->index);
+			if (hp->state != HVSI_CLOSED) {
+				*to_handshake = hp;
+			}
+			break;
+		default:
+			printk(KERN_WARNING "hvsi%i: unknown HVSI control packet: ",
+				hp->index);
+			dump_packet(packet);
+			break;
+	}
+}
+
+static void hvsi_recv_response(struct hvsi_struct *hp, uint8_t *packet)
+{
+	struct hvsi_query_response *resp = (struct hvsi_query_response *)packet;
+
+	switch (hp->state) {
+		case HVSI_WAIT_FOR_VER_RESPONSE:
+			__set_state(hp, HVSI_WAIT_FOR_VER_QUERY);
+			break;
+		case HVSI_WAIT_FOR_MCTRL_RESPONSE:
+			hp->mctrl = 0;
+			if (resp->u.mctrl_word & HVSI_TSDTR)
+				hp->mctrl |= TIOCM_DTR;
+			if (resp->u.mctrl_word & HVSI_TSCD)
+				hp->mctrl |= TIOCM_CD;
+			__set_state(hp, HVSI_OPEN);
+			break;
+		default:
+			printk(KERN_ERR "hvsi%i: unexpected query response: ", hp->index);
+			dump_packet(packet);
+			break;
+	}
+}
+
+/* respond to service processor's version query */
+static int hvsi_version_respond(struct hvsi_struct *hp, uint16_t query_seqno)
+{
+	struct hvsi_query_response packet __ALIGNED__;
+	int wrote;
+
+	packet.type = VS_QUERY_RESPONSE_PACKET_HEADER;
+	packet.len = sizeof(struct hvsi_query_response);
+	packet.seqno = atomic_inc_return(&hp->seqno);
+	packet.verb = VSV_SEND_VERSION_NUMBER;
+	packet.u.version = HVSI_VERSION;
+	packet.query_seqno = query_seqno+1;
+
+	pr_debug("%s: sending %i bytes\n", __FUNCTION__, packet.len);
+	dbg_dump_hex((uint8_t*)&packet, packet.len);
+
+	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+	if (wrote != packet.len) {
+		printk(KERN_ERR "hvsi%i: couldn't send query response!\n",
+			hp->index);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void hvsi_recv_query(struct hvsi_struct *hp, uint8_t *packet)
+{
+	struct hvsi_query *query = (struct hvsi_query *)packet;
+
+	switch (hp->state) {
+		case HVSI_WAIT_FOR_VER_QUERY:
+			hvsi_version_respond(hp, query->seqno);
+			__set_state(hp, HVSI_OPEN);
+			break;
+		default:
+			printk(KERN_ERR "hvsi%i: unexpected query: ", hp->index);
+			dump_packet(packet);
+			break;
+	}
+}
+
+static void hvsi_insert_chars(struct hvsi_struct *hp, const char *buf, int len)
+{
+	int i;
+
+	for (i=0; i < len; i++) {
+		char c = buf[i];
+#ifdef CONFIG_MAGIC_SYSRQ
+		if (c == '\0') {
+			hp->sysrq = 1;
+			continue;
+		} else if (hp->sysrq) {
+			handle_sysrq(c, NULL, hp->tty);
+			hp->sysrq = 0;
+			continue;
+		}
+#endif /* CONFIG_MAGIC_SYSRQ */
+		tty_insert_flip_char(hp->tty, c, 0);
+	}
+}
+
+/*
+ * We could get 252 bytes of data at once here. But the tty layer only
+ * throttles us at TTY_THRESHOLD_THROTTLE (128) bytes, so we could overflow
+ * it. Accordingly we won't send more than 128 bytes at a time to the flip
+ * buffer, which will give the tty buffer a chance to throttle us. Should the
+ * value of TTY_THRESHOLD_THROTTLE change in n_tty.c, this code should be
+ * revisited.
+ */
+#define TTY_THRESHOLD_THROTTLE 128
+static struct tty_struct *hvsi_recv_data(struct hvsi_struct *hp,
+		const uint8_t *packet)
+{
+	const struct hvsi_header *header = (const struct hvsi_header *)packet;
+	const uint8_t *data = packet + sizeof(struct hvsi_header);
+	int datalen = header->len - sizeof(struct hvsi_header);
+	int overflow = datalen - TTY_THRESHOLD_THROTTLE;
+
+	pr_debug("queueing %i chars '%.*s'\n", datalen, datalen, data);
+
+	if (datalen == 0)
+		return NULL;
+
+	if (overflow > 0) {
+		pr_debug("%s: got >TTY_THRESHOLD_THROTTLE bytes\n", __FUNCTION__);
+		datalen = TTY_THRESHOLD_THROTTLE;
+	}
+
+	hvsi_insert_chars(hp, data, datalen);
+
+	if (overflow > 0) {
+		/*
+		 * we still have more data to deliver, so we need to save off the
+		 * overflow and send it later
+		 */
+		pr_debug("%s: deferring overflow\n", __FUNCTION__);
+		memcpy(hp->throttle_buf, data + TTY_THRESHOLD_THROTTLE, overflow);
+		hp->n_throttle = overflow;
+	}
+
+	return hp->tty;
+}
+
+/*
+ * Returns true/false indicating data successfully read from hypervisor.
+ * Used both to get packets for tty connections and to advance the state
+ * machine during console handshaking (in which case tty = NULL and we ignore
+ * incoming data).
+ */
+static int hvsi_load_chunk(struct hvsi_struct *hp, struct tty_struct **flip,
+		struct tty_struct **hangup, struct hvsi_struct **handshake)
+{
+	uint8_t *packet = hp->inbuf;
+	int chunklen;
+
+	*flip = NULL;
+	*hangup = NULL;
+	*handshake = NULL;
+
+	chunklen = hvsi_read(hp, hp->inbuf_end, HVSI_MAX_READ);
+	if (chunklen == 0) {
+		pr_debug("%s: 0-length read\n", __FUNCTION__);
+		return 0;
+	}
+
+	pr_debug("%s: got %i bytes\n", __FUNCTION__, chunklen);
+	dbg_dump_hex(hp->inbuf_end, chunklen);
+
+	hp->inbuf_end += chunklen;
+
+	/* handle all completed packets */
+	while ((packet < hp->inbuf_end) && got_packet(hp, packet)) {
+		struct hvsi_header *header = (struct hvsi_header *)packet;
+
+		if (!is_header(packet)) {
+			printk(KERN_ERR "hvsi%i: got malformed packet\n", hp->index);
+			/* skip bytes until we find a header or run out of data */
+			while ((packet < hp->inbuf_end) && (!is_header(packet)))
+				packet++;
+			continue;
+		}
+
+		pr_debug("%s: handling %i-byte packet\n", __FUNCTION__,
+				len_packet(packet));
+		dbg_dump_packet(packet);
+
+		switch (header->type) {
+			case VS_DATA_PACKET_HEADER:
+				if (!is_open(hp))
+					break;
+				if (hp->tty == NULL)
+					break; /* no tty buffer to put data in */
+				*flip = hvsi_recv_data(hp, packet);
+				break;
+			case VS_CONTROL_PACKET_HEADER:
+				hvsi_recv_control(hp, packet, hangup, handshake);
+				break;
+			case VS_QUERY_RESPONSE_PACKET_HEADER:
+				hvsi_recv_response(hp, packet);
+				break;
+			case VS_QUERY_PACKET_HEADER:
+				hvsi_recv_query(hp, packet);
+				break;
+			default:
+				printk(KERN_ERR "hvsi%i: unknown HVSI packet type 0x%x\n",
+						hp->index, header->type);
+				dump_packet(packet);
+				break;
+		}
+
+		packet += len_packet(packet);
+
+		if (*hangup || *handshake) {
+			pr_debug("%s: hangup or handshake\n", __FUNCTION__);
+			/*
+			 * we need to send the hangup now before receiving any more data.
+			 * If we get "data, hangup, data", we can't deliver the second
+			 * data before the hangup.
+			 */
+			break;
+		}
+	}
+
+	compact_inbuf(hp, packet);
+
+	return 1;
+}
+
+static void hvsi_send_overflow(struct hvsi_struct *hp)
+{
+	pr_debug("%s: delivering %i bytes overflow\n", __FUNCTION__,
+			hp->n_throttle);
+
+	hvsi_insert_chars(hp, hp->throttle_buf, hp->n_throttle);
+	hp->n_throttle = 0;
+}
+
+/*
+ * must get all pending data because we only get an irq on empty->non-empty
+ * transition
+ */
+static irqreturn_t hvsi_interrupt(int irq, void *arg, struct pt_regs *regs)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)arg;
+	struct tty_struct *flip;
+	struct tty_struct *hangup;
+	struct hvsi_struct *handshake;
+	unsigned long flags;
+	int again = 1;
+
+	pr_debug("%s\n", __FUNCTION__);
+
+	while (again) {
+		spin_lock_irqsave(&hp->lock, flags);
+		again = hvsi_load_chunk(hp, &flip, &hangup, &handshake);
+		spin_unlock_irqrestore(&hp->lock, flags);
+
+		/*
+		 * we have to call tty_flip_buffer_push() and tty_hangup() outside our
+		 * spinlock. But we also have to keep going until we've read all the
+		 * available data.
+		 */
+
+		if (flip) {
+			/* there was data put in the tty flip buffer */
+			tty_flip_buffer_push(flip);
+			flip = NULL;
+		}
+
+		if (hangup) {
+			tty_hangup(hangup);
+		}
+
+		if (handshake) {
+			pr_debug("hvsi%i: attempting re-handshake\n", handshake->index);
+			schedule_work(&handshake->handshaker);
+		}
+	}
+
+	spin_lock_irqsave(&hp->lock, flags);
+	if (hp->tty && hp->n_throttle
+			&& (!test_bit(TTY_THROTTLED, &hp->tty->flags))) {
+		/* we weren't hung up and we weren't throttled, so we can deliver the
+		 * rest now */
+		flip = hp->tty;
+		hvsi_send_overflow(hp);
+	}
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	if (flip) {
+		tty_flip_buffer_push(flip);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/* for boot console, before the irq handler is running */
+static int __init poll_for_state(struct hvsi_struct *hp, int state)
+{
+	unsigned long end_jiffies = jiffies + HVSI_TIMEOUT;
+
+	for (;;) {
+		hvsi_interrupt(hp->virq, (void *)hp, NULL); /* get pending data */
+
+		if (hp->state == state)
+			return 0;
+
+		mdelay(5);
+		if (time_after(jiffies, end_jiffies))
+			return -EIO;
+	}
+}
+
+/* wait for irq handler to change our state */
+static int wait_for_state(struct hvsi_struct *hp, int state)
+{
+	int ret = 0;
+
+	if (!wait_event_timeout(hp->stateq, (hp->state == state), HVSI_TIMEOUT))
+		ret = -EIO;
+
+	return ret;
+}
+
+static int hvsi_query(struct hvsi_struct *hp, uint16_t verb)
+{
+	struct hvsi_query packet __ALIGNED__;
+	int wrote;
+
+	packet.type = VS_QUERY_PACKET_HEADER;
+	packet.len = sizeof(struct hvsi_query);
+	packet.seqno = atomic_inc_return(&hp->seqno);
+	packet.verb = verb;
+
+	pr_debug("%s: sending %i bytes\n", __FUNCTION__, packet.len);
+	dbg_dump_hex((uint8_t*)&packet, packet.len);
+
+	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+	if (wrote != packet.len) {
+		printk(KERN_ERR "hvsi%i: couldn't send query (%i)!\n", hp->index,
+			wrote);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int hvsi_get_mctrl(struct hvsi_struct *hp)
+{
+	int ret;
+
+	set_state(hp, HVSI_WAIT_FOR_MCTRL_RESPONSE);
+	hvsi_query(hp, VSV_SEND_MODEM_CTL_STATUS);
+
+	ret = hvsi_wait(hp, HVSI_OPEN);
+	if (ret < 0) {
+		printk(KERN_ERR "hvsi%i: didn't get modem flags\n", hp->index);
+		set_state(hp, HVSI_OPEN);
+		return ret;
+	}
+
+	pr_debug("%s: mctrl 0x%x\n", __FUNCTION__, hp->mctrl);
+
+	return 0;
+}
+
+/* note that we can only set DTR */
+static int hvsi_set_mctrl(struct hvsi_struct *hp, uint16_t mctrl)
+{
+	struct hvsi_control packet __ALIGNED__;
+	int wrote;
+
+	packet.type = VS_CONTROL_PACKET_HEADER,
+	packet.seqno = atomic_inc_return(&hp->seqno);
+	packet.len = sizeof(struct hvsi_control);
+	packet.verb = VSV_SET_MODEM_CTL;
+	packet.mask = HVSI_TSDTR;
+
+	if (mctrl & TIOCM_DTR)
+		packet.word = HVSI_TSDTR;
+
+	pr_debug("%s: sending %i bytes\n", __FUNCTION__, packet.len);
+	dbg_dump_hex((uint8_t*)&packet, packet.len);
+
+	wrote = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+	if (wrote != packet.len) {
+		printk(KERN_ERR "hvsi%i: couldn't set DTR!\n", hp->index);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void hvsi_drain_input(struct hvsi_struct *hp)
+{
+	uint8_t buf[HVSI_MAX_READ] __ALIGNED__;
+	unsigned long end_jiffies = jiffies + HVSI_TIMEOUT;
+
+	while (time_before(end_jiffies, jiffies))
+		if (0 == hvsi_read(hp, buf, HVSI_MAX_READ))
+			break;
+}
+
+static int hvsi_handshake(struct hvsi_struct *hp)
+{
+	int ret;
+
+	/*
+	 * We could have a CLOSE or other data waiting for us before we even try
+	 * to open; try to throw it all away so we don't get confused. (CLOSE
+	 * is the first message sent up the pipe when the FSP comes online. We
+	 * need to distinguish between "it came up a while ago and we're the first
+	 * user" and "it was just reset before it saw our handshake packet".)
+	 */
+	hvsi_drain_input(hp);
+
+	set_state(hp, HVSI_WAIT_FOR_VER_RESPONSE);
+	ret = hvsi_query(hp, VSV_SEND_VERSION_NUMBER);
+	if (ret < 0) {
+		printk(KERN_ERR "hvsi%i: couldn't send version query\n", hp->index);
+		return ret;
+	}
+
+	ret = hvsi_wait(hp, HVSI_OPEN);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void hvsi_handshaker(void *arg)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)arg;
+
+	if (hvsi_handshake(hp) >= 0)
+		return;
+
+	printk(KERN_ERR "hvsi%i: re-handshaking failed\n", hp->index);
+	if (is_console(hp)) {
+		/*
+		 * ttys will re-attempt the handshake via hvsi_open, but
+		 * the console will not.
+		 */
+		printk(KERN_ERR "hvsi%i: lost console!\n", hp->index);
+	}
+}
+
+static int hvsi_put_chars(struct hvsi_struct *hp, const char *buf, int count)
+{
+	struct hvsi_data packet __ALIGNED__;
+	int ret;
+
+	BUG_ON(count > HVSI_MAX_OUTGOING_DATA);
+
+	packet.type = VS_DATA_PACKET_HEADER;
+	packet.seqno = atomic_inc_return(&hp->seqno);
+	packet.len = count + sizeof(struct hvsi_header);
+	memcpy(&packet.data, buf, count);
+
+	ret = hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+	if (ret == packet.len) {
+		/* return the number of chars written, not the packet length */
+		return count;
+	}
+	return ret; /* return any errors */
+}
+
+static void hvsi_close_protocol(struct hvsi_struct *hp)
+{
+	struct hvsi_control packet __ALIGNED__;
+
+	packet.type = VS_CONTROL_PACKET_HEADER;
+	packet.seqno = atomic_inc_return(&hp->seqno);
+	packet.len = 6;
+	packet.verb = VSV_CLOSE_PROTOCOL;
+
+	pr_debug("%s: sending %i bytes\n", __FUNCTION__, packet.len);
+	dbg_dump_hex((uint8_t*)&packet, packet.len);
+
+	hvc_put_chars(hp->vtermno, (char *)&packet, packet.len);
+}
+
+static int hvsi_open(struct tty_struct *tty, struct file *filp)
+{
+	struct hvsi_struct *hp;
+	unsigned long flags;
+	int line = tty->index;
+	int ret;
+
+	pr_debug("%s\n", __FUNCTION__);
+
+	if (line < 0 || line >= hvsi_count)
+		return -ENODEV;
+	hp = &hvsi_ports[line];
+
+	tty->driver_data = hp;
+	tty->low_latency = 1; /* avoid throttle/tty_flip_buffer_push race */
+
+	mb();
+	if (hp->state == HVSI_FSP_DIED)
+		return -EIO;
+
+	spin_lock_irqsave(&hp->lock, flags);
+	hp->tty = tty;
+	hp->count++;
+	atomic_set(&hp->seqno, 0);
+	h_vio_signal(hp->vtermno, VIO_IRQ_ENABLE);
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	if (is_console(hp))
+		return 0; /* this has already been handshaked as the console */
+
+	ret = hvsi_handshake(hp);
+	if (ret < 0) {
+		printk(KERN_ERR "%s: HVSI handshaking failed\n", tty->name);
+		return ret;
+	}
+
+	ret = hvsi_get_mctrl(hp);
+	if (ret < 0) {
+		printk(KERN_ERR "%s: couldn't get initial modem flags\n", tty->name);
+		return ret;
+	}
+
+	ret = hvsi_set_mctrl(hp, hp->mctrl | TIOCM_DTR);
+	if (ret < 0) {
+		printk(KERN_ERR "%s: couldn't set DTR\n", tty->name);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* wait for hvsi_write_worker to empty hp->outbuf */
+static void hvsi_flush_output(struct hvsi_struct *hp)
+{
+	wait_event_timeout(hp->emptyq, (hp->n_outbuf <= 0), HVSI_TIMEOUT);
+
+	/* 'writer' could still be pending if it didn't see n_outbuf = 0 yet */
+	cancel_delayed_work(&hp->writer);
+	flush_scheduled_work();
+
+	/*
+	 * it's also possible that our timeout expired and hvsi_write_worker
+	 * didn't manage to push outbuf. poof.
+	 */
+	hp->n_outbuf = 0;
+}
+
+static void hvsi_close(struct tty_struct *tty, struct file *filp)
+{
+	struct hvsi_struct *hp = tty->driver_data;
+	unsigned long flags;
+
+	pr_debug("%s\n", __FUNCTION__);
+
+	if (tty_hung_up_p(filp))
+		return;
+
+	spin_lock_irqsave(&hp->lock, flags);
+
+	if (--hp->count == 0) {
+		hp->tty = NULL;
+		hp->inbuf_end = hp->inbuf; /* discard remaining partial packets */
+
+		/* only close down connection if it is not the console */
+		if (!is_console(hp)) {
+			h_vio_signal(hp->vtermno, VIO_IRQ_DISABLE); /* no more irqs */
+			__set_state(hp, HVSI_CLOSED);
+			/*
+			 * any data delivered to the tty layer after this will be
+			 * discarded (except for XON/XOFF)
+			 */
+			tty->closing = 1;
+
+			spin_unlock_irqrestore(&hp->lock, flags);
+
+			/* let any existing irq handlers finish. no more will start. */
+			synchronize_irq(hp->virq);
+
+			/* hvsi_write_worker will re-schedule until outbuf is empty. */
+			hvsi_flush_output(hp);
+
+			/* tell FSP to stop sending data */
+			hvsi_close_protocol(hp);
+
+			/*
+			 * drain anything FSP is still in the middle of sending, and let
+			 * hvsi_handshake drain the rest on the next open.
+			 */
+			hvsi_drain_input(hp);
+
+			spin_lock_irqsave(&hp->lock, flags);
+		}
+	} else if (hp->count < 0)
+		printk(KERN_ERR "hvsi_close %lu: oops, count is %d\n",
+		       hp - hvsi_ports, hp->count);
+
+	spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+static void hvsi_hangup(struct tty_struct *tty)
+{
+	struct hvsi_struct *hp = tty->driver_data;
+	unsigned long flags;
+
+	pr_debug("%s\n", __FUNCTION__);
+
+	spin_lock_irqsave(&hp->lock, flags);
+
+	hp->count = 0;
+	hp->n_outbuf = 0;
+	hp->tty = NULL;
+
+	spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+/* called with hp->lock held */
+static void hvsi_push(struct hvsi_struct *hp)
+{
+	int n;
+
+	if (hp->n_outbuf <= 0)
+		return;
+
+	n = hvsi_put_chars(hp, hp->outbuf, hp->n_outbuf);
+	if (n > 0) {
+		/* success */
+		pr_debug("%s: wrote %i chars\n", __FUNCTION__, n);
+		hp->n_outbuf = 0;
+	} else if (n == -EIO) {
+		__set_state(hp, HVSI_FSP_DIED);
+		printk(KERN_ERR "hvsi%i: service processor died\n", hp->index);
+	}
+}
+
+/* hvsi_write_worker will keep rescheduling itself until outbuf is empty */
+static void hvsi_write_worker(void *arg)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)arg;
+	unsigned long flags;
+#ifdef DEBUG
+	static long start_j = 0;
+
+	if (start_j == 0)
+		start_j = jiffies;
+#endif /* DEBUG */
+
+	spin_lock_irqsave(&hp->lock, flags);
+
+	pr_debug("%s: %i chars in buffer\n", __FUNCTION__, hp->n_outbuf);
+
+	if (!is_open(hp)) {
+		/*
+		 * We could have a non-open connection if the service processor died
+		 * while we were busily scheduling ourselves. In that case, it could
+		 * be minutes before the service processor comes back, so only try
+		 * again once a second.
+		 */
+		schedule_delayed_work(&hp->writer, HZ);
+		goto out;
+	}
+
+	hvsi_push(hp);
+	if (hp->n_outbuf > 0)
+		schedule_delayed_work(&hp->writer, 10);
+	else {
+#ifdef DEBUG
+		pr_debug("%s: outbuf emptied after %li jiffies\n", __FUNCTION__,
+				jiffies - start_j);
+		start_j = 0;
+#endif /* DEBUG */
+		wake_up_all(&hp->emptyq);
+		if (test_bit(TTY_DO_WRITE_WAKEUP, &hp->tty->flags)
+				&& hp->tty->ldisc.write_wakeup)
+			hp->tty->ldisc.write_wakeup(hp->tty);
+		wake_up_interruptible(&hp->tty->write_wait);
+	}
+
+out:
+	spin_unlock_irqrestore(&hp->lock, flags);
+}
+
+static int hvsi_write_room(struct tty_struct *tty)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+
+	return N_OUTBUF - hp->n_outbuf;
+}
+
+static int hvsi_chars_in_buffer(struct tty_struct *tty)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+
+	return hp->n_outbuf;
+}
+
+static int hvsi_write(struct tty_struct *tty,
+		     const unsigned char *buf, int count)
+{
+	struct hvsi_struct *hp = tty->driver_data;
+	const char *source = buf;
+	unsigned long flags;
+	int total = 0;
+	int origcount = count;
+
+	spin_lock_irqsave(&hp->lock, flags);
+
+	pr_debug("%s: %i chars in buffer\n", __FUNCTION__, hp->n_outbuf);
+
+	if (!is_open(hp)) {
+		/* we're either closing or not yet open; don't accept data */
+		pr_debug("%s: not open\n", __FUNCTION__);
+		goto out;
+	}
+
+	/*
+	 * when the hypervisor buffer (16K) fills, data will stay in hp->outbuf
+	 * and hvsi_write_worker will be scheduled. subsequent hvsi_write() calls
+	 * will see there is no room in outbuf and return.
+	 */
+	while ((count > 0) && (hvsi_write_room(hp->tty) > 0)) {
+		int chunksize = min(count, hvsi_write_room(hp->tty));
+
+		BUG_ON(hp->n_outbuf < 0);
+		memcpy(hp->outbuf + hp->n_outbuf, source, chunksize);
+		hp->n_outbuf += chunksize;
+
+		total += chunksize;
+		source += chunksize;
+		count -= chunksize;
+		hvsi_push(hp);
+	}
+
+	if (hp->n_outbuf > 0) {
+		/*
+		 * we weren't able to write it all to the hypervisor.
+		 * schedule another push attempt.
+		 */
+		schedule_delayed_work(&hp->writer, 10);
+	}
+
+out:
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	if (total != origcount)
+		pr_debug("%s: wanted %i, only wrote %i\n", __FUNCTION__, origcount,
+			total);
+
+	return total;
+}
+
+/*
+ * I have never seen throttle or unthrottle called, so this little throttle
+ * buffering scheme may or may not work.
+ */
+static void hvsi_throttle(struct tty_struct *tty)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+
+	pr_debug("%s\n", __FUNCTION__);
+
+	h_vio_signal(hp->vtermno, VIO_IRQ_DISABLE);
+}
+
+static void hvsi_unthrottle(struct tty_struct *tty)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+	unsigned long flags;
+	int shouldflip = 0;
+
+	pr_debug("%s\n", __FUNCTION__);
+
+	spin_lock_irqsave(&hp->lock, flags);
+	if (hp->n_throttle) {
+		hvsi_send_overflow(hp);
+		shouldflip = 1;
+	}
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	if (shouldflip)
+		tty_flip_buffer_push(hp->tty);
+
+	h_vio_signal(hp->vtermno, VIO_IRQ_ENABLE);
+}
+
+static int hvsi_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+
+	hvsi_get_mctrl(hp);
+	return hp->mctrl;
+}
+
+static int hvsi_tiocmset(struct tty_struct *tty, struct file *file,
+		unsigned int set, unsigned int clear)
+{
+	struct hvsi_struct *hp = (struct hvsi_struct *)tty->driver_data;
+	unsigned long flags;
+	uint16_t new_mctrl;
+
+	/* we can only alter DTR */
+	clear &= TIOCM_DTR;
+	set &= TIOCM_DTR;
+
+	spin_lock_irqsave(&hp->lock, flags);
+
+	new_mctrl = (hp->mctrl & ~clear) | set;
+
+	if (hp->mctrl != new_mctrl) {
+		hvsi_set_mctrl(hp, new_mctrl);
+		hp->mctrl = new_mctrl;
+	}
+	spin_unlock_irqrestore(&hp->lock, flags);
+
+	return 0;
+}
+
+
+static struct tty_operations hvsi_ops = {
+	.open = hvsi_open,
+	.close = hvsi_close,
+	.write = hvsi_write,
+	.hangup = hvsi_hangup,
+	.write_room = hvsi_write_room,
+	.chars_in_buffer = hvsi_chars_in_buffer,
+	.throttle = hvsi_throttle,
+	.unthrottle = hvsi_unthrottle,
+	.tiocmget = hvsi_tiocmget,
+	.tiocmset = hvsi_tiocmset,
+};
+
+static int __init hvsi_init(void)
+{
+	int i;
+
+	hvsi_driver = alloc_tty_driver(hvsi_count);
+	if (!hvsi_driver)
+		return -ENOMEM;
+
+	hvsi_driver->owner = THIS_MODULE;
+	hvsi_driver->devfs_name = "hvsi/";
+	hvsi_driver->driver_name = "hvsi";
+	hvsi_driver->name = "hvsi";
+	hvsi_driver->major = HVSI_MAJOR;
+	hvsi_driver->minor_start = HVSI_MINOR;
+	hvsi_driver->type = TTY_DRIVER_TYPE_SYSTEM;
+	hvsi_driver->init_termios = tty_std_termios;
+	hvsi_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL;
+	hvsi_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(hvsi_driver, &hvsi_ops);
+
+	for (i=0; i < hvsi_count; i++) {
+		struct hvsi_struct *hp = &hvsi_ports[i];
+		int ret = 1;
+
+		ret = request_irq(hp->virq, hvsi_interrupt, SA_INTERRUPT, "hvsi", hp);
+		if (ret)
+			printk(KERN_ERR "HVSI: couldn't reserve irq 0x%x (error %i)\n",
+				hp->virq, ret);
+	}
+	hvsi_wait = wait_for_state; /* irqs active now */
+
+	if (tty_register_driver(hvsi_driver))
+		panic("Couldn't register hvsi console driver\n");
+
+	printk(KERN_INFO "HVSI: registered %i devices\n", hvsi_count);
+
+	return 0;
+}
+device_initcall(hvsi_init);
+
+/***** console (not tty) code: *****/
+
+static void hvsi_console_print(struct console *console, const char *buf,
+		unsigned int count)
+{
+	struct hvsi_struct *hp = &hvsi_ports[console->index];
+	char c[HVSI_MAX_OUTGOING_DATA] __ALIGNED__;
+	unsigned int i = 0, n = 0;
+	int ret, donecr = 0;
+
+	mb();
+	if (!is_open(hp))
+		return;
+
+	/*
+	 * ugh, we have to translate LF -> CRLF ourselves, in place.
+	 * copied from hvc_console.c:
+	 */
+	while (count > 0 || i > 0) {
+		if (count > 0 && i < sizeof(c)) {
+			if (buf[n] == '\n' && !donecr) {
+				c[i++] = '\r';
+				donecr = 1;
+			} else {
+				c[i++] = buf[n++];
+				donecr = 0;
+				--count;
+			}
+		} else {
+			ret = hvsi_put_chars(hp, c, i);
+			if (ret < 0)
+				i = 0;
+			i -= ret;
+		}
+	}
+}
+
+static struct tty_driver *hvsi_console_device(struct console *console,
+	int *index)
+{
+	*index = console->index;
+	return hvsi_driver;
+}
+
+static int __init hvsi_console_setup(struct console *console, char *options)
+{
+	struct hvsi_struct *hp = &hvsi_ports[console->index];
+	int ret;
+
+	if (console->index < 0 || console->index >= hvsi_count)
+		return -1;
+
+	/* give the FSP a chance to change the baud rate when we re-open */
+	hvsi_close_protocol(hp);
+
+	ret = hvsi_handshake(hp);
+	if (ret < 0)
+		return ret;
+
+	ret = hvsi_get_mctrl(hp);
+	if (ret < 0)
+		return ret;
+
+	ret = hvsi_set_mctrl(hp, hp->mctrl | TIOCM_DTR);
+	if (ret < 0)
+		return ret;
+
+	hp->flags |= HVSI_CONSOLE;
+
+	return 0;
+}
+
+static struct console hvsi_con_driver = {
+	.name		= "hvsi",
+	.write		= hvsi_console_print,
+	.device		= hvsi_console_device,
+	.setup		= hvsi_console_setup,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+};
+
+static int __init hvsi_console_init(void)
+{
+	struct device_node *vty;
+
+	hvsi_wait = poll_for_state; /* no irqs yet; must poll */
+
+	/* search device tree for vty nodes */
+	for (vty = of_find_compatible_node(NULL, "serial", "hvterm-protocol");
+			vty != NULL;
+			vty = of_find_compatible_node(vty, "serial", "hvterm-protocol")) {
+		struct hvsi_struct *hp;
+		uint32_t *vtermno;
+		uint32_t *irq;
+
+		vtermno = (uint32_t *)get_property(vty, "reg", NULL);
+		irq = (uint32_t *)get_property(vty, "interrupts", NULL);
+		if (!vtermno || !irq)
+			continue;
+
+		if (hvsi_count >= MAX_NR_HVSI_CONSOLES) {
+			of_node_put(vty);
+			break;
+		}
+
+		hp = &hvsi_ports[hvsi_count];
+		INIT_WORK(&hp->writer, hvsi_write_worker, hp);
+		INIT_WORK(&hp->handshaker, hvsi_handshaker, hp);
+		init_waitqueue_head(&hp->emptyq);
+		init_waitqueue_head(&hp->stateq);
+		spin_lock_init(&hp->lock);
+		hp->index = hvsi_count;
+		hp->inbuf_end = hp->inbuf;
+		hp->state = HVSI_CLOSED;
+		hp->vtermno = *vtermno;
+		hp->virq = virt_irq_create_mapping(irq[0]);
+		if (hp->virq == NO_IRQ) {
+			printk(KERN_ERR "%s: couldn't create irq mapping for 0x%x\n",
+				__FUNCTION__, hp->virq);
+			continue;
+		} else
+			hp->virq = irq_offset_up(hp->virq);
+
+		hvsi_count++;
+	}
+
+	if (hvsi_count)
+		register_console(&hvsi_con_driver);
+	return 0;
+}
+console_initcall(hvsi_console_init);
diff --git a/drivers/char/hw_random.c b/drivers/char/hw_random.c
new file mode 100644
index 0000000..7e6ac14
--- /dev/null
+++ b/drivers/char/hw_random.c
@@ -0,0 +1,630 @@
+/*
+ 	Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG)
+	(c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com>
+ 
+ 	derived from
+ 
+        Hardware driver for the AMD 768 Random Number Generator (RNG)
+        (c) Copyright 2001 Red Hat Inc <alan@redhat.com>
+
+ 	derived from
+ 
+	Hardware driver for Intel i810 Random Number Generator (RNG)
+	Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com>
+	Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com>
+
+	Please read Documentation/hw_random.txt for details on use.
+
+	----------------------------------------------------------
+	This software may be used and distributed according to the terms
+        of the GNU General Public License, incorporated herein by reference.
+
+ */
+
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <linux/miscdevice.h>
+#include <linux/smp_lock.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+
+#ifdef __i386__
+#include <asm/msr.h>
+#include <asm/cpufeature.h>
+#endif
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+
+/*
+ * core module and version information
+ */
+#define RNG_VERSION "1.0.0"
+#define RNG_MODULE_NAME "hw_random"
+#define RNG_DRIVER_NAME   RNG_MODULE_NAME " hardware driver " RNG_VERSION
+#define PFX RNG_MODULE_NAME ": "
+
+
+/*
+ * debugging macros
+ */
+
+/* pr_debug() collapses to a no-op if DEBUG is not defined */
+#define DPRINTK(fmt, args...) pr_debug(PFX "%s: " fmt, __FUNCTION__ , ## args)
+
+
+#undef RNG_NDEBUG        /* define to enable lightweight runtime checks */
+#ifdef RNG_NDEBUG
+#define assert(expr)							\
+		if(!(expr)) {						\
+		printk(KERN_DEBUG PFX "Assertion failed! %s,%s,%s,"	\
+		"line=%d\n", #expr, __FILE__, __FUNCTION__, __LINE__);	\
+		}
+#else
+#define assert(expr)
+#endif
+
+#define RNG_MISCDEV_MINOR		183 /* official */
+
+static int rng_dev_open (struct inode *inode, struct file *filp);
+static ssize_t rng_dev_read (struct file *filp, char __user *buf, size_t size,
+				loff_t * offp);
+
+static int __init intel_init (struct pci_dev *dev);
+static void intel_cleanup(void);
+static unsigned int intel_data_present (void);
+static u32 intel_data_read (void);
+
+static int __init amd_init (struct pci_dev *dev);
+static void amd_cleanup(void);
+static unsigned int amd_data_present (void);
+static u32 amd_data_read (void);
+
+#ifdef __i386__
+static int __init via_init(struct pci_dev *dev);
+static void via_cleanup(void);
+static unsigned int via_data_present (void);
+static u32 via_data_read (void);
+#endif
+
+struct rng_operations {
+	int (*init) (struct pci_dev *dev);
+	void (*cleanup) (void);
+	unsigned int (*data_present) (void);
+	u32 (*data_read) (void);
+	unsigned int n_bytes; /* number of bytes per ->data_read */
+};
+static struct rng_operations *rng_ops;
+
+static struct file_operations rng_chrdev_ops = {
+	.owner		= THIS_MODULE,
+	.open		= rng_dev_open,
+	.read		= rng_dev_read,
+};
+
+
+static struct miscdevice rng_miscdev = {
+	RNG_MISCDEV_MINOR,
+	RNG_MODULE_NAME,
+	&rng_chrdev_ops,
+};
+
+enum {
+	rng_hw_none,
+	rng_hw_intel,
+	rng_hw_amd,
+	rng_hw_via,
+};
+
+static struct rng_operations rng_vendor_ops[] = {
+	/* rng_hw_none */
+	{ },
+
+	/* rng_hw_intel */
+	{ intel_init, intel_cleanup, intel_data_present,
+	  intel_data_read, 1 },
+
+	/* rng_hw_amd */
+	{ amd_init, amd_cleanup, amd_data_present, amd_data_read, 4 },
+
+#ifdef __i386__
+	/* rng_hw_via */
+	{ via_init, via_cleanup, via_data_present, via_data_read, 1 },
+#endif
+};
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE.  We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+static struct pci_device_id rng_pci_tbl[] = {
+	{ 0x1022, 0x7443, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_amd },
+	{ 0x1022, 0x746b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_amd },
+
+	{ 0x8086, 0x2418, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+	{ 0x8086, 0x2428, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+	{ 0x8086, 0x2448, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+	{ 0x8086, 0x244e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+	{ 0x8086, 0x245e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, rng_hw_intel },
+
+	{ 0, },	/* terminate list */
+};
+MODULE_DEVICE_TABLE (pci, rng_pci_tbl);
+
+
+/***********************************************************************
+ *
+ * Intel RNG operations
+ *
+ */
+
+/*
+ * RNG registers (offsets from rng_mem)
+ */
+#define INTEL_RNG_HW_STATUS			0
+#define         INTEL_RNG_PRESENT		0x40
+#define         INTEL_RNG_ENABLED		0x01
+#define INTEL_RNG_STATUS			1
+#define         INTEL_RNG_DATA_PRESENT		0x01
+#define INTEL_RNG_DATA				2
+
+/*
+ * Magic address at which Intel PCI bridges locate the RNG
+ */
+#define INTEL_RNG_ADDR				0xFFBC015F
+#define INTEL_RNG_ADDR_LEN			3
+
+/* token to our ioremap'd RNG register area */
+static void __iomem *rng_mem;
+
+static inline u8 intel_hwstatus (void)
+{
+	assert (rng_mem != NULL);
+	return readb (rng_mem + INTEL_RNG_HW_STATUS);
+}
+
+static inline u8 intel_hwstatus_set (u8 hw_status)
+{
+	assert (rng_mem != NULL);
+	writeb (hw_status, rng_mem + INTEL_RNG_HW_STATUS);
+	return intel_hwstatus ();
+}
+
+static unsigned int intel_data_present(void)
+{
+	assert (rng_mem != NULL);
+
+	return (readb (rng_mem + INTEL_RNG_STATUS) & INTEL_RNG_DATA_PRESENT) ?
+		1 : 0;
+}
+
+static u32 intel_data_read(void)
+{
+	assert (rng_mem != NULL);
+
+	return readb (rng_mem + INTEL_RNG_DATA);
+}
+
+static int __init intel_init (struct pci_dev *dev)
+{
+	int rc;
+	u8 hw_status;
+
+	DPRINTK ("ENTER\n");
+
+	rng_mem = ioremap (INTEL_RNG_ADDR, INTEL_RNG_ADDR_LEN);
+	if (rng_mem == NULL) {
+		printk (KERN_ERR PFX "cannot ioremap RNG Memory\n");
+		rc = -EBUSY;
+		goto err_out;
+	}
+
+	/* Check for Intel 82802 */
+	hw_status = intel_hwstatus ();
+	if ((hw_status & INTEL_RNG_PRESENT) == 0) {
+		printk (KERN_ERR PFX "RNG not detected\n");
+		rc = -ENODEV;
+		goto err_out_free_map;
+	}
+
+	/* turn RNG h/w on, if it's off */
+	if ((hw_status & INTEL_RNG_ENABLED) == 0)
+		hw_status = intel_hwstatus_set (hw_status | INTEL_RNG_ENABLED);
+	if ((hw_status & INTEL_RNG_ENABLED) == 0) {
+		printk (KERN_ERR PFX "cannot enable RNG, aborting\n");
+		rc = -EIO;
+		goto err_out_free_map;
+	}
+
+	DPRINTK ("EXIT, returning 0\n");
+	return 0;
+
+err_out_free_map:
+	iounmap (rng_mem);
+	rng_mem = NULL;
+err_out:
+	DPRINTK ("EXIT, returning %d\n", rc);
+	return rc;
+}
+
+static void intel_cleanup(void)
+{
+	u8 hw_status;
+
+	hw_status = intel_hwstatus ();
+	if (hw_status & INTEL_RNG_ENABLED)
+		intel_hwstatus_set (hw_status & ~INTEL_RNG_ENABLED);
+	else
+		printk(KERN_WARNING PFX "unusual: RNG already disabled\n");
+	iounmap(rng_mem);
+	rng_mem = NULL;
+}
+
+/***********************************************************************
+ *
+ * AMD RNG operations
+ *
+ */
+
+static u32 pmbase;			/* PMxx I/O base */
+static struct pci_dev *amd_dev;
+
+static unsigned int amd_data_present (void)
+{
+      	return inl(pmbase + 0xF4) & 1;
+}
+
+
+static u32 amd_data_read (void)
+{
+	return inl(pmbase + 0xF0);
+}
+
+static int __init amd_init (struct pci_dev *dev)
+{
+	int rc;
+	u8 rnen;
+
+	DPRINTK ("ENTER\n");
+
+	pci_read_config_dword(dev, 0x58, &pmbase);
+
+	pmbase &= 0x0000FF00;
+
+	if (pmbase == 0)
+	{
+		printk (KERN_ERR PFX "power management base not set\n");
+		rc = -EIO;
+		goto err_out;
+	}
+
+	pci_read_config_byte(dev, 0x40, &rnen);
+	rnen |= (1 << 7);	/* RNG on */
+	pci_write_config_byte(dev, 0x40, rnen);
+
+	pci_read_config_byte(dev, 0x41, &rnen);
+	rnen |= (1 << 7);	/* PMIO enable */
+	pci_write_config_byte(dev, 0x41, rnen);
+
+	pr_info( PFX "AMD768 system management I/O registers at 0x%X.\n",
+			pmbase);
+
+	amd_dev = dev;
+
+	DPRINTK ("EXIT, returning 0\n");
+	return 0;
+
+err_out:
+	DPRINTK ("EXIT, returning %d\n", rc);
+	return rc;
+}
+
+static void amd_cleanup(void)
+{
+	u8 rnen;
+
+	pci_read_config_byte(amd_dev, 0x40, &rnen);
+	rnen &= ~(1 << 7);	/* RNG off */
+	pci_write_config_byte(amd_dev, 0x40, rnen);
+
+	/* FIXME: twiddle pmio, also? */
+}
+
+#ifdef __i386__
+/***********************************************************************
+ *
+ * VIA RNG operations
+ *
+ */
+
+enum {
+	VIA_STRFILT_CNT_SHIFT	= 16,
+	VIA_STRFILT_FAIL	= (1 << 15),
+	VIA_STRFILT_ENABLE	= (1 << 14),
+	VIA_RAWBITS_ENABLE	= (1 << 13),
+	VIA_RNG_ENABLE		= (1 << 6),
+	VIA_XSTORE_CNT_MASK	= 0x0F,
+
+	VIA_RNG_CHUNK_8		= 0x00,	/* 64 rand bits, 64 stored bits */
+	VIA_RNG_CHUNK_4		= 0x01,	/* 32 rand bits, 32 stored bits */
+	VIA_RNG_CHUNK_4_MASK	= 0xFFFFFFFF,
+	VIA_RNG_CHUNK_2		= 0x02,	/* 16 rand bits, 32 stored bits */
+	VIA_RNG_CHUNK_2_MASK	= 0xFFFF,
+	VIA_RNG_CHUNK_1		= 0x03,	/* 8 rand bits, 32 stored bits */
+	VIA_RNG_CHUNK_1_MASK	= 0xFF,
+};
+
+static u32 via_rng_datum;
+
+/*
+ * Investigate using the 'rep' prefix to obtain 32 bits of random data
+ * in one insn.  The upside is potentially better performance.  The
+ * downside is that the instruction becomes no longer atomic.  Due to
+ * this, just like familiar issues with /dev/random itself, the worst
+ * case of a 'rep xstore' could potentially pause a cpu for an
+ * unreasonably long time.  In practice, this condition would likely
+ * only occur when the hardware is failing.  (or so we hope :))
+ *
+ * Another possible performance boost may come from simply buffering
+ * until we have 4 bytes, thus returning a u32 at a time,
+ * instead of the current u8-at-a-time.
+ */
+
+static inline u32 xstore(u32 *addr, u32 edx_in)
+{
+	u32 eax_out;
+
+	asm(".byte 0x0F,0xA7,0xC0 /* xstore %%edi (addr=%0) */"
+		:"=m"(*addr), "=a"(eax_out)
+		:"D"(addr), "d"(edx_in));
+
+	return eax_out;
+}
+
+static unsigned int via_data_present(void)
+{
+	u32 bytes_out;
+
+	/* We choose the recommended 1-byte-per-instruction RNG rate,
+	 * for greater randomness at the expense of speed.  Larger
+	 * values 2, 4, or 8 bytes-per-instruction yield greater
+	 * speed at lesser randomness.
+	 *
+	 * If you change this to another VIA_CHUNK_n, you must also
+	 * change the ->n_bytes values in rng_vendor_ops[] tables.
+	 * VIA_CHUNK_8 requires further code changes.
+	 *
+	 * A copy of MSR_VIA_RNG is placed in eax_out when xstore
+	 * completes.
+	 */
+	via_rng_datum = 0; /* paranoia, not really necessary */
+	bytes_out = xstore(&via_rng_datum, VIA_RNG_CHUNK_1) & VIA_XSTORE_CNT_MASK;
+	if (bytes_out == 0)
+		return 0;
+
+	return 1;
+}
+
+static u32 via_data_read(void)
+{
+	return via_rng_datum;
+}
+
+static int __init via_init(struct pci_dev *dev)
+{
+	u32 lo, hi, old_lo;
+
+	/* Control the RNG via MSR.  Tread lightly and pay very close
+	 * close attention to values written, as the reserved fields
+	 * are documented to be "undefined and unpredictable"; but it
+	 * does not say to write them as zero, so I make a guess that
+	 * we restore the values we find in the register.
+	 */
+	rdmsr(MSR_VIA_RNG, lo, hi);
+
+	old_lo = lo;
+	lo &= ~(0x7f << VIA_STRFILT_CNT_SHIFT);
+	lo &= ~VIA_XSTORE_CNT_MASK;
+	lo &= ~(VIA_STRFILT_ENABLE | VIA_STRFILT_FAIL | VIA_RAWBITS_ENABLE);
+	lo |= VIA_RNG_ENABLE;
+
+	if (lo != old_lo)
+		wrmsr(MSR_VIA_RNG, lo, hi);
+
+	/* perhaps-unnecessary sanity check; remove after testing if
+	   unneeded */
+	rdmsr(MSR_VIA_RNG, lo, hi);
+	if ((lo & VIA_RNG_ENABLE) == 0) {
+		printk(KERN_ERR PFX "cannot enable VIA C3 RNG, aborting\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void via_cleanup(void)
+{
+	/* do nothing */
+}
+#endif
+
+
+/***********************************************************************
+ *
+ * /dev/hwrandom character device handling (major 10, minor 183)
+ *
+ */
+
+static int rng_dev_open (struct inode *inode, struct file *filp)
+{
+	/* enforce read-only access to this chrdev */
+	if ((filp->f_mode & FMODE_READ) == 0)
+		return -EINVAL;
+	if (filp->f_mode & FMODE_WRITE)
+		return -EINVAL;
+
+	return 0;
+}
+
+
+static ssize_t rng_dev_read (struct file *filp, char __user *buf, size_t size,
+				loff_t * offp)
+{
+	static DEFINE_SPINLOCK(rng_lock);
+	unsigned int have_data;
+	u32 data = 0;
+	ssize_t ret = 0;
+
+	while (size) {
+		spin_lock(&rng_lock);
+
+		have_data = 0;
+		if (rng_ops->data_present()) {
+			data = rng_ops->data_read();
+			have_data = rng_ops->n_bytes;
+		}
+
+		spin_unlock (&rng_lock);
+
+		while (have_data && size) {
+			if (put_user((u8)data, buf++)) {
+				ret = ret ? : -EFAULT;
+				break;
+			}
+			size--;
+			ret++;
+			have_data--;
+			data>>=8;
+		}
+
+		if (filp->f_flags & O_NONBLOCK)
+			return ret ? : -EAGAIN;
+
+		if(need_resched())
+		{
+			current->state = TASK_INTERRUPTIBLE;
+			schedule_timeout(1);
+		}
+		else
+			udelay(200);	/* FIXME: We could poll for 250uS ?? */
+
+		if (signal_pending (current))
+			return ret ? : -ERESTARTSYS;
+	}
+	return ret;
+}
+
+
+
+/*
+ * rng_init_one - look for and attempt to init a single RNG
+ */
+static int __init rng_init_one (struct pci_dev *dev)
+{
+	int rc;
+
+	DPRINTK ("ENTER\n");
+
+	assert(rng_ops != NULL);
+
+	rc = rng_ops->init(dev);
+	if (rc)
+		goto err_out;
+
+	rc = misc_register (&rng_miscdev);
+	if (rc) {
+		printk (KERN_ERR PFX "misc device register failed\n");
+		goto err_out_cleanup_hw;
+	}
+
+	DPRINTK ("EXIT, returning 0\n");
+	return 0;
+
+err_out_cleanup_hw:
+	rng_ops->cleanup();
+err_out:
+	DPRINTK ("EXIT, returning %d\n", rc);
+	return rc;
+}
+
+
+
+MODULE_AUTHOR("The Linux Kernel team");
+MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver");
+MODULE_LICENSE("GPL");
+
+
+/*
+ * rng_init - initialize RNG module
+ */
+static int __init rng_init (void)
+{
+	int rc;
+	struct pci_dev *pdev = NULL;
+	const struct pci_device_id *ent;
+
+	DPRINTK ("ENTER\n");
+
+	/* Probe for Intel, AMD RNGs */
+	for_each_pci_dev(pdev) {
+		ent = pci_match_device (rng_pci_tbl, pdev);
+		if (ent) {
+			rng_ops = &rng_vendor_ops[ent->driver_data];
+			goto match;
+		}
+	}
+
+#ifdef __i386__
+	/* Probe for VIA RNG */
+	if (cpu_has_xstore) {
+		rng_ops = &rng_vendor_ops[rng_hw_via];
+		pdev = NULL;
+		goto match;
+	}
+#endif
+
+	DPRINTK ("EXIT, returning -ENODEV\n");
+	return -ENODEV;
+
+match:
+	rc = rng_init_one (pdev);
+	if (rc)
+		return rc;
+
+	pr_info( RNG_DRIVER_NAME " loaded\n");
+
+	DPRINTK ("EXIT, returning 0\n");
+	return 0;
+}
+
+
+/*
+ * rng_init - shutdown RNG module
+ */
+static void __exit rng_cleanup (void)
+{
+	DPRINTK ("ENTER\n");
+
+	misc_deregister (&rng_miscdev);
+
+	if (rng_ops->cleanup)
+		rng_ops->cleanup();
+
+	DPRINTK ("EXIT\n");
+}
+
+
+module_init (rng_init);
+module_exit (rng_cleanup);
diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c
new file mode 100644
index 0000000..a811976
--- /dev/null
+++ b/drivers/char/i8k.c
@@ -0,0 +1,788 @@
+/*
+ * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
+ *	    See http://www.debian.org/~dz/i8k/ for more information
+ *	    and for latest version of this driver.
+ *
+ * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
+ *
+ * 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, 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.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/apm_bios.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/i8k.h>
+
+#define I8K_VERSION		"1.13 14/05/2002"
+
+#define I8K_SMM_FN_STATUS	0x0025
+#define I8K_SMM_POWER_STATUS	0x0069
+#define I8K_SMM_SET_FAN		0x01a3
+#define I8K_SMM_GET_FAN		0x00a3
+#define I8K_SMM_GET_SPEED	0x02a3
+#define I8K_SMM_GET_TEMP	0x10a3
+#define I8K_SMM_GET_DELL_SIG	0xffa3
+#define I8K_SMM_BIOS_VERSION	0x00a6
+
+#define I8K_FAN_MULT		30
+#define I8K_MAX_TEMP		127
+
+#define I8K_FN_NONE		0x00
+#define I8K_FN_UP		0x01
+#define I8K_FN_DOWN		0x02
+#define I8K_FN_MUTE		0x04
+#define I8K_FN_MASK		0x07
+#define I8K_FN_SHIFT		8
+
+#define I8K_POWER_AC		0x05
+#define I8K_POWER_BATTERY	0x01
+
+#define I8K_TEMPERATURE_BUG	1
+
+#define DELL_SIGNATURE		"Dell Computer"
+
+static char *supported_models[] = {
+    "Inspiron",
+    "Latitude",
+    NULL
+};
+
+static char system_vendor[48] = "?";
+static char product_name [48] = "?";
+static char bios_version [4]  = "?";
+static char serial_number[16] = "?";
+
+MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
+MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
+MODULE_LICENSE("GPL");
+
+static int force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force loading without checking for supported models");
+
+static int restricted;
+module_param(restricted, bool, 0);
+MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
+
+static int power_status;
+module_param(power_status, bool, 0600);
+MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
+
+static ssize_t i8k_read(struct file *, char __user *, size_t, loff_t *);
+static int i8k_ioctl(struct inode *, struct file *, unsigned int,
+		     unsigned long);
+
+static struct file_operations i8k_fops = {
+    .read	= i8k_read,
+    .ioctl	= i8k_ioctl,
+};
+
+typedef struct {
+    unsigned int eax;
+    unsigned int ebx __attribute__ ((packed));
+    unsigned int ecx __attribute__ ((packed));
+    unsigned int edx __attribute__ ((packed));
+    unsigned int esi __attribute__ ((packed));
+    unsigned int edi __attribute__ ((packed));
+} SMMRegisters;
+
+typedef struct {
+    u8	type;
+    u8	length;
+    u16	handle;
+} DMIHeader;
+
+/*
+ * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
+ */
+static int i8k_smm(SMMRegisters *regs)
+{
+    int rc;
+    int eax = regs->eax;
+
+    asm("pushl %%eax\n\t" \
+	"movl 0(%%eax),%%edx\n\t" \
+	"push %%edx\n\t" \
+	"movl 4(%%eax),%%ebx\n\t" \
+	"movl 8(%%eax),%%ecx\n\t" \
+	"movl 12(%%eax),%%edx\n\t" \
+	"movl 16(%%eax),%%esi\n\t" \
+	"movl 20(%%eax),%%edi\n\t" \
+	"popl %%eax\n\t" \
+	"out %%al,$0xb2\n\t" \
+	"out %%al,$0x84\n\t" \
+	"xchgl %%eax,(%%esp)\n\t"
+	"movl %%ebx,4(%%eax)\n\t" \
+	"movl %%ecx,8(%%eax)\n\t" \
+	"movl %%edx,12(%%eax)\n\t" \
+	"movl %%esi,16(%%eax)\n\t" \
+	"movl %%edi,20(%%eax)\n\t" \
+	"popl %%edx\n\t" \
+	"movl %%edx,0(%%eax)\n\t" \
+	"lahf\n\t" \
+	"shrl $8,%%eax\n\t" \
+	"andl $1,%%eax\n" \
+	: "=a" (rc)
+	: "a" (regs)
+	: "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+
+    if ((rc != 0) || ((regs->eax & 0xffff) == 0xffff) || (regs->eax == eax)) {
+	return -EINVAL;
+    }
+
+    return 0;
+}
+
+/*
+ * Read the bios version. Return the version as an integer corresponding
+ * to the ascii value, for example "A17" is returned as 0x00413137.
+ */
+static int i8k_get_bios_version(void)
+{
+    SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+    int rc;
+
+    regs.eax = I8K_SMM_BIOS_VERSION;
+    if ((rc=i8k_smm(&regs)) < 0) {
+	return rc;
+    }
+
+    return regs.eax;
+}
+
+/*
+ * Read the machine id.
+ */
+static int i8k_get_serial_number(unsigned char *buff)
+{
+    strlcpy(buff, serial_number, sizeof(serial_number));
+    return 0;
+}
+
+/*
+ * Read the Fn key status.
+ */
+static int i8k_get_fn_status(void)
+{
+    SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+    int rc;
+
+    regs.eax = I8K_SMM_FN_STATUS;
+    if ((rc=i8k_smm(&regs)) < 0) {
+	return rc;
+    }
+
+    switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
+    case I8K_FN_UP:
+	return I8K_VOL_UP;
+    case I8K_FN_DOWN:
+	return I8K_VOL_DOWN;
+    case I8K_FN_MUTE:
+	return I8K_VOL_MUTE;
+    default:
+	return 0;
+    }
+}
+
+/*
+ * Read the power status.
+ */
+static int i8k_get_power_status(void)
+{
+    SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+    int rc;
+
+    regs.eax = I8K_SMM_POWER_STATUS;
+    if ((rc=i8k_smm(&regs)) < 0) {
+	return rc;
+    }
+
+    switch (regs.eax & 0xff) {
+    case I8K_POWER_AC:
+	return I8K_AC;
+    default:
+	return I8K_BATTERY;
+    }
+}
+
+/*
+ * Read the fan status.
+ */
+static int i8k_get_fan_status(int fan)
+{
+    SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+    int rc;
+
+    regs.eax = I8K_SMM_GET_FAN;
+    regs.ebx = fan & 0xff;
+    if ((rc=i8k_smm(&regs)) < 0) {
+	return rc;
+    }
+
+    return (regs.eax & 0xff);
+}
+
+/*
+ * Read the fan speed in RPM.
+ */
+static int i8k_get_fan_speed(int fan)
+{
+    SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+    int rc;
+
+    regs.eax = I8K_SMM_GET_SPEED;
+    regs.ebx = fan & 0xff;
+    if ((rc=i8k_smm(&regs)) < 0) {
+	return rc;
+    }
+
+    return (regs.eax & 0xffff) * I8K_FAN_MULT;
+}
+
+/*
+ * Set the fan speed (off, low, high). Returns the new fan status.
+ */
+static int i8k_set_fan(int fan, int speed)
+{
+    SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+    int rc;
+
+    speed = (speed < 0) ? 0 : ((speed > I8K_FAN_MAX) ? I8K_FAN_MAX : speed);
+
+    regs.eax = I8K_SMM_SET_FAN;
+    regs.ebx = (fan & 0xff) | (speed << 8);
+    if ((rc=i8k_smm(&regs)) < 0) {
+	return rc;
+    }
+
+    return (i8k_get_fan_status(fan));
+}
+
+/*
+ * Read the cpu temperature.
+ */
+static int i8k_get_cpu_temp(void)
+{
+    SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+    int rc;
+    int temp;
+
+#ifdef I8K_TEMPERATURE_BUG
+    static int prev = 0;
+#endif
+
+    regs.eax = I8K_SMM_GET_TEMP;
+    if ((rc=i8k_smm(&regs)) < 0) {
+	return rc;
+    }
+    temp = regs.eax & 0xff;
+
+#ifdef I8K_TEMPERATURE_BUG
+    /*
+     * Sometimes the temperature sensor returns 0x99, which is out of range.
+     * In this case we return (once) the previous cached value. For example:
+     # 1003655137 00000058 00005a4b
+     # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
+     # 1003655139 00000054 00005c52
+     */
+    if (temp > I8K_MAX_TEMP) {
+	temp = prev;
+	prev = I8K_MAX_TEMP;
+    } else {
+	prev = temp;
+    }
+#endif
+
+    return temp;
+}
+
+static int i8k_get_dell_signature(void)
+{
+    SMMRegisters regs = { 0, 0, 0, 0, 0, 0 };
+    int rc;
+
+    regs.eax = I8K_SMM_GET_DELL_SIG;
+    if ((rc=i8k_smm(&regs)) < 0) {
+	return rc;
+    }
+
+    if ((regs.eax == 1145651527) && (regs.edx == 1145392204)) {
+	return 0;
+    } else {
+	return -1;
+    }
+}
+
+static int i8k_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+		     unsigned long arg)
+{
+    int val;
+    int speed;
+    unsigned char buff[16];
+    int __user *argp = (int __user *)arg;
+
+    if (!argp)
+	return -EINVAL;
+
+    switch (cmd) {
+    case I8K_BIOS_VERSION:
+	val = i8k_get_bios_version();
+	break;
+
+    case I8K_MACHINE_ID:
+	memset(buff, 0, 16);
+	val = i8k_get_serial_number(buff);
+	break;
+
+    case I8K_FN_STATUS:
+	val = i8k_get_fn_status();
+	break;
+
+    case I8K_POWER_STATUS:
+	val = i8k_get_power_status();
+	break;
+
+    case I8K_GET_TEMP:
+	val = i8k_get_cpu_temp();
+	break;
+
+    case I8K_GET_SPEED:
+	if (copy_from_user(&val, argp, sizeof(int))) {
+	    return -EFAULT;
+	}
+	val = i8k_get_fan_speed(val);
+	break;
+
+    case I8K_GET_FAN:
+	if (copy_from_user(&val, argp, sizeof(int))) {
+	    return -EFAULT;
+	}
+	val = i8k_get_fan_status(val);
+	break;
+
+    case I8K_SET_FAN:
+	if (restricted && !capable(CAP_SYS_ADMIN)) {
+	    return -EPERM;
+	}
+	if (copy_from_user(&val, argp, sizeof(int))) {
+	    return -EFAULT;
+	}
+	if (copy_from_user(&speed, argp+1, sizeof(int))) {
+	    return -EFAULT;
+	}
+	val = i8k_set_fan(val, speed);
+	break;
+
+    default:
+	return -EINVAL;
+    }
+
+    if (val < 0) {
+	return val;
+    }
+
+    switch (cmd) {
+    case I8K_BIOS_VERSION:
+	if (copy_to_user(argp, &val, 4)) {
+	    return -EFAULT;
+	}
+	break;
+    case I8K_MACHINE_ID:
+	if (copy_to_user(argp, buff, 16)) {
+	    return -EFAULT;
+	}
+	break;
+    default:
+	if (copy_to_user(argp, &val, sizeof(int))) {
+	    return -EFAULT;
+	}
+	break;
+    }
+
+    return 0;
+}
+
+/*
+ * Print the information for /proc/i8k.
+ */
+static int i8k_get_info(char *buffer, char **start, off_t fpos, int length)
+{
+    int n, fn_key, cpu_temp, ac_power;
+    int left_fan, right_fan, left_speed, right_speed;
+
+    cpu_temp     = i8k_get_cpu_temp();			/* 11100 µs */
+    left_fan     = i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
+    right_fan    = i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
+    left_speed   = i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
+    right_speed  = i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
+    fn_key       = i8k_get_fn_status();			/*   750 µs */
+    if (power_status) {
+	ac_power = i8k_get_power_status();		/* 14700 µs */
+    } else {
+	ac_power = -1;
+    }
+
+    /*
+     * Info:
+     *
+     * 1)  Format version (this will change if format changes)
+     * 2)  BIOS version
+     * 3)  BIOS machine ID
+     * 4)  Cpu temperature
+     * 5)  Left fan status
+     * 6)  Right fan status
+     * 7)  Left fan speed
+     * 8)  Right fan speed
+     * 9)  AC power
+     * 10) Fn Key status
+     */
+    n = sprintf(buffer, "%s %s %s %d %d %d %d %d %d %d\n",
+		I8K_PROC_FMT,
+		bios_version,
+		serial_number,
+		cpu_temp,
+		left_fan,
+		right_fan,
+		left_speed,
+		right_speed,
+		ac_power,
+		fn_key);
+
+    return n;
+}
+
+static ssize_t i8k_read(struct file *f, char __user *buffer, size_t len, loff_t *fpos)
+{
+    int n;
+    char info[128];
+
+    n = i8k_get_info(info, NULL, 0, 128);
+    if (n <= 0) {
+	return n;
+    }
+
+    if (*fpos >= n) {
+	return 0;
+    }
+
+    if ((*fpos + len) >= n) {
+	len = n - *fpos;
+    }
+
+    if (copy_to_user(buffer, info, len) != 0) {
+	return -EFAULT;
+    }
+
+    *fpos += len;
+    return len;
+}
+
+static char* __init string_trim(char *s, int size)
+{
+    int len;
+    char *p;
+
+    if ((len = strlen(s)) > size) {
+	len = size;
+    }
+
+    for (p=s+len-1; len && (*p==' '); len--,p--) {
+	*p = '\0';
+    }
+
+    return s;
+}
+
+/* DMI code, stolen from arch/i386/kernel/dmi_scan.c */
+
+/*
+ * |<-- dmi->length -->|
+ * |                   |
+ * |dmi header    s=N  | string1,\0, ..., stringN,\0, ..., \0
+ *                |                       |
+ *                +-----------------------+
+ */
+static char* __init dmi_string(DMIHeader *dmi, u8 s)
+{
+    u8 *p;
+
+    if (!s) {
+	return "";
+    }
+    s--;
+
+    p = (u8 *)dmi + dmi->length;
+    while (s > 0) {
+	p += strlen(p);
+	p++;
+	s--;
+    }
+
+    return p;
+}
+
+static void __init dmi_decode(DMIHeader *dmi)
+{
+    u8 *data = (u8 *) dmi;
+    char *p;
+
+#ifdef I8K_DEBUG
+    int i;
+    printk("%08x ", (int)data);
+    for (i=0; i<data[1] && i<64; i++) {
+	printk("%02x ", data[i]);
+    }
+    printk("\n");
+#endif
+
+    switch (dmi->type) {
+    case  0:	/* BIOS Information */
+	p = dmi_string(dmi,data[5]);
+	if (*p) {
+	    strlcpy(bios_version, p, sizeof(bios_version));
+	    string_trim(bios_version, sizeof(bios_version));
+	}
+	break;	
+    case 1:	/* System Information */
+	p = dmi_string(dmi,data[4]);
+	if (*p) {
+	    strlcpy(system_vendor, p, sizeof(system_vendor));
+	    string_trim(system_vendor, sizeof(system_vendor));
+	}
+	p = dmi_string(dmi,data[5]);
+	if (*p) {
+	    strlcpy(product_name, p, sizeof(product_name));
+	    string_trim(product_name, sizeof(product_name));
+	}
+	p = dmi_string(dmi,data[7]);
+	if (*p) {
+	    strlcpy(serial_number, p, sizeof(serial_number));
+	    string_trim(serial_number, sizeof(serial_number));
+	}
+	break;
+    }
+}
+
+static int __init dmi_table(u32 base, int len, int num, void (*fn)(DMIHeader*))
+{
+    u8 *buf;
+    u8 *data;
+    DMIHeader *dmi;
+    int i = 1;
+
+    buf = ioremap(base, len);
+    if (buf == NULL) {
+	return -1;
+    }
+    data = buf;
+
+    /*
+     * Stop when we see al the items the table claimed to have
+     * or we run off the end of the table (also happens)
+     */
+    while ((i<num) && ((data-buf) < len)) {
+	dmi = (DMIHeader *)data;
+	/*
+	 * Avoid misparsing crud if the length of the last
+	 * record is crap
+	 */
+	if ((data-buf+dmi->length) >= len) {
+	    break;
+	}
+	fn(dmi);
+	data += dmi->length;
+	/*
+	 * Don't go off the end of the data if there is
+	 * stuff looking like string fill past the end
+	 */
+	while (((data-buf) < len) && (*data || data[1])) {
+	    data++;
+	}
+	data += 2;
+	i++;
+    }
+    iounmap(buf);
+
+    return 0;
+}
+
+static int __init dmi_iterate(void (*decode)(DMIHeader *))
+{
+	unsigned char buf[20];
+	void __iomem *p = ioremap(0xe0000, 0x20000), *q;
+
+	if (!p)
+		return -1;
+
+	for (q = p; q < p + 0x20000; q += 16) {
+		memcpy_fromio(buf, q, 20);
+		if (memcmp(buf, "_DMI_", 5)==0) {
+			u16 num  = buf[13]<<8  | buf[12];
+			u16 len  = buf [7]<<8  | buf [6];
+			u32 base = buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8];
+#ifdef I8K_DEBUG
+			printk(KERN_INFO "DMI %d.%d present.\n",
+			   buf[14]>>4, buf[14]&0x0F);
+			printk(KERN_INFO "%d structures occupying %d bytes.\n",
+			   buf[13]<<8 | buf[12],
+			   buf [7]<<8 | buf[6]);
+			printk(KERN_INFO "DMI table at 0x%08X.\n",
+			   buf[11]<<24 | buf[10]<<16 | buf[9]<<8 | buf[8]);
+#endif
+			if (dmi_table(base, len, num, decode)==0) {
+				iounmap(p);
+				return 0;
+			}
+		}
+	}
+	iounmap(p);
+	return -1;
+}
+/* end of DMI code */
+
+/*
+ * Get DMI information.
+ */
+static int __init i8k_dmi_probe(void)
+{
+    char **p;
+
+    if (dmi_iterate(dmi_decode) != 0) {
+	printk(KERN_INFO "i8k: unable to get DMI information\n");
+	return -ENODEV;
+    }
+
+    if (strncmp(system_vendor,DELL_SIGNATURE,strlen(DELL_SIGNATURE)) != 0) {
+	printk(KERN_INFO "i8k: not running on a Dell system\n");
+	return -ENODEV;
+    }
+
+    for (p=supported_models; ; p++) {
+	if (!*p) {
+	    printk(KERN_INFO "i8k: unsupported model: %s\n", product_name);
+	    return -ENODEV;
+	}
+	if (strncmp(product_name,*p,strlen(*p)) == 0) {
+	    break;
+	}
+    }
+
+    return 0;
+}
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
+static int __init i8k_probe(void)
+{
+    char buff[4];
+    int version;
+    int smm_found = 0;
+
+    /*
+     * Get DMI information
+     */
+    if (i8k_dmi_probe() != 0) {
+	printk(KERN_INFO "i8k: vendor=%s, model=%s, version=%s\n",
+	       system_vendor, product_name, bios_version);
+    }
+
+    /*
+     * Get SMM Dell signature
+     */
+    if (i8k_get_dell_signature() != 0) {
+	printk(KERN_INFO "i8k: unable to get SMM Dell signature\n");
+    } else {
+	smm_found = 1;
+    }
+
+    /*
+     * Get SMM BIOS version.
+     */
+    version = i8k_get_bios_version();
+    if (version <= 0) {
+	printk(KERN_INFO "i8k: unable to get SMM BIOS version\n");
+    } else {
+	smm_found = 1;
+	buff[0] = (version >> 16) & 0xff;
+	buff[1] = (version >>  8) & 0xff;
+	buff[2] = (version)       & 0xff;
+	buff[3] = '\0';
+	/*
+	 * If DMI BIOS version is unknown use SMM BIOS version.
+	 */
+	if (bios_version[0] == '?') {
+	    strcpy(bios_version, buff);
+	}
+	/*
+	 * Check if the two versions match.
+	 */
+	if (strncmp(buff,bios_version,sizeof(bios_version)) != 0) {
+	    printk(KERN_INFO "i8k: BIOS version mismatch: %s != %s\n",
+		   buff, bios_version);
+	}
+    }
+
+    if (!smm_found && !force) {
+	return -ENODEV;
+    }
+
+    return 0;
+}
+
+#ifdef MODULE
+static
+#endif
+int __init i8k_init(void)
+{
+    struct proc_dir_entry *proc_i8k;
+
+    /* Are we running on an supported laptop? */
+    if (i8k_probe() != 0) {
+	return -ENODEV;
+    }
+
+    /* Register the proc entry */
+    proc_i8k = create_proc_info_entry("i8k", 0, NULL, i8k_get_info);
+    if (!proc_i8k) {
+	return -ENOENT;
+    }
+    proc_i8k->proc_fops = &i8k_fops;
+    proc_i8k->owner = THIS_MODULE;
+
+    printk(KERN_INFO
+	   "Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n",
+	   I8K_VERSION);
+
+    return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+    return i8k_init();
+}
+
+void cleanup_module(void)
+{
+    /* Remove the proc entry */
+    remove_proc_entry("i8k", NULL);
+
+    printk(KERN_INFO "i8k: module unloaded\n");
+}
+#endif
+
+/* end of file */
diff --git a/drivers/char/ip2.c b/drivers/char/ip2.c
new file mode 100644
index 0000000..6cd12f2
--- /dev/null
+++ b/drivers/char/ip2.c
@@ -0,0 +1,110 @@
+// ip2.c
+// This is a dummy module to make the firmware available when needed
+// and allows it to be unloaded when not. Rumor is the __initdata 
+// macro doesn't always works on all platforms so we use this kludge.
+// If not compiled as a module it just makes fip_firm avaliable then
+//  __initdata should work as advertized
+//
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+
+#ifndef __init
+#define __init
+#endif
+#ifndef __initfunc
+#define __initfunc(a) a
+#endif
+#ifndef __initdata
+#define __initdata
+#endif
+
+#include "./ip2/ip2types.h"		
+#include "./ip2/fip_firm.h"		// the meat
+
+int
+ip2_loadmain(int *, int  *, unsigned char *, int ); // ref into ip2main.c
+
+/* Note: Add compiled in defaults to these arrays, not to the structure
+	in ip2/ip2.h any longer.  That structure WILL get overridden
+	by these values, or command line values, or insmod values!!!  =mhw=
+*/
+static int io[IP2_MAX_BOARDS]= { 0, 0, 0, 0 };
+static int irq[IP2_MAX_BOARDS] = { -1, -1, -1, -1 }; 
+
+static int poll_only = 0;
+
+MODULE_AUTHOR("Doug McNash");
+MODULE_DESCRIPTION("Computone IntelliPort Plus Driver");
+module_param_array(irq, int, NULL, 0);
+MODULE_PARM_DESC(irq,"Interrupts for IntelliPort Cards");
+module_param_array(io, int, NULL, 0);
+MODULE_PARM_DESC(io,"I/O ports for IntelliPort Cards");
+module_param(poll_only, bool, 0);
+MODULE_PARM_DESC(poll_only,"Do not use card interrupts");
+
+
+static int __init ip2_init(void)
+{
+	if( poll_only ) {
+		/* Hard lock the interrupts to zero */
+		irq[0] = irq[1] = irq[2] = irq[3] = 0;
+	}
+
+	return ip2_loadmain(io,irq,(unsigned char *)fip_firm,sizeof(fip_firm));
+}
+module_init(ip2_init);
+
+MODULE_LICENSE("GPL");
+
+#ifndef MODULE
+/******************************************************************************
+ *	ip2_setup:
+ *		str: kernel command line string
+ *
+ *	Can't autoprobe the boards so user must specify configuration on
+ *	kernel command line.  Sane people build it modular but the others
+ *	come here.
+ *
+ *	Alternating pairs of io,irq for up to 4 boards.
+ *		ip2=io0,irq0,io1,irq1,io2,irq2,io3,irq3
+ *
+ *		io=0 => No board
+ *		io=1 => PCI
+ *		io=2 => EISA
+ *		else => ISA I/O address
+ *
+ *		irq=0 or invalid for ISA will revert to polling mode
+ *
+ *		Any value = -1, do not overwrite compiled in value.
+ *
+ ******************************************************************************/
+static int __init ip2_setup(char *str)
+{
+	int	ints[10];	/* 4 boards, 2 parameters + 2 */
+	int	i, j;
+
+	str = get_options (str, ARRAY_SIZE(ints), ints);
+
+	for( i = 0, j = 1; i < 4; i++ ) {
+		if( j > ints[0] ) {
+			break;
+		}
+		if( ints[j] >= 0 ) {
+			io[i] = ints[j];
+		}
+		j++;
+		if( j > ints[0] ) {
+			break;
+		}
+		if( ints[j] >= 0 ) {
+			irq[i] = ints[j];
+		}
+		j++;
+	}
+	return 1;
+}
+__setup("ip2=", ip2_setup);
+#endif /* !MODULE */
diff --git a/drivers/char/ip2/fip_firm.h b/drivers/char/ip2/fip_firm.h
new file mode 100644
index 0000000..4c525fa
--- /dev/null
+++ b/drivers/char/ip2/fip_firm.h
@@ -0,0 +1,2149 @@
+/* fip_firm.h - Intelliport II loadware */
+/* -31232 bytes read from ff.lod */
+
+static unsigned char fip_firm[] __initdata = {
+0x3C,0x42,0x37,0x18,0x02,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x57,0x65,0x64,0x20,0x44,0x65,0x63,0x20,0x30,0x31,0x20,0x31,0x32,0x3A,0x32,0x34,
+0x3A,0x33,0x30,0x20,0x31,0x39,0x39,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0xE9,0x6C,0x0F,0x42,0x65,0x47,0x69,0x4E,0x6E,0x49,0x6E,0x47,0x20,0x6F,0x46,0x20,
+0x63,0x4F,0x64,0x45,0xCC,0x13,0x5A,0x15,0xE8,0x16,0x76,0x18,0x04,0x1A,0x92,0x1B,
+0x20,0x1D,0xAE,0x1E,0x3C,0x20,0xCA,0x21,0x58,0x23,0xE6,0x24,0x74,0x26,0x02,0x28,
+0x90,0x29,0x1E,0x2B,0xAC,0x2C,0x3A,0x2E,0xC8,0x2F,0x56,0x31,0xE4,0x32,0x72,0x34,
+0x00,0x36,0x8E,0x37,0x1C,0x39,0xAA,0x3A,0x38,0x3C,0xC6,0x3D,0x54,0x3F,0xE2,0x40,
+0x70,0x42,0xFE,0x43,0x8C,0x45,0x1A,0x47,0xA8,0x48,0x36,0x4A,0xC4,0x4B,0x52,0x4D,
+0xE0,0x4E,0x6E,0x50,0xFC,0x51,0x8A,0x53,0x18,0x55,0xA6,0x56,0x34,0x58,0xC2,0x59,
+0x50,0x5B,0xDE,0x5C,0x6C,0x5E,0xFA,0x5F,0x88,0x61,0x16,0x63,0xA4,0x64,0x32,0x66,
+0xC0,0x67,0x4E,0x69,0xDC,0x6A,0x6A,0x6C,0xF8,0x6D,0x86,0x6F,0x14,0x71,0xA2,0x72,
+0x30,0x74,0xBE,0x75,0x4C,0x77,0x6C,0x77,0x8C,0x77,0xAC,0x77,0x33,0xDB,0x8A,0xDC,
+0x53,0x33,0xDB,0x25,0x07,0x00,0x75,0x0A,0x8A,0x1E,0x08,0x01,0x83,0xE3,0x0C,0xEB,
+0x20,0x90,0x3C,0x01,0x75,0x0A,0x8A,0x1E,0x08,0x01,0x80,0xE3,0xC0,0xEB,0x12,0x90,
+0x8A,0x1E,0x0D,0x01,0x3C,0x02,0x75,0x06,0x80,0xE3,0x0C,0xEB,0x04,0x90,0x80,0xE3,
+0xC0,0x53,0x50,0x8B,0x1E,0xBA,0x13,0x8E,0xDB,0xE8,0x6A,0x65,0x55,0x8B,0xEC,0x53,
+0x1E,0x2B,0xC0,0x8E,0xD8,0x8B,0x5E,0x04,0xC1,0xE3,0x04,0x03,0x5E,0x06,0xD1,0xE3,
+0x2E,0x8B,0x9F,0x44,0x00,0x8D,0x47,0x2A,0x1E,0x5A,0x1F,0x5B,0x5D,0xC3,0x55,0x8B,
+0xEC,0x53,0x1E,0x2B,0xC0,0x8E,0xD8,0x8B,0x5E,0x04,0xC1,0xE3,0x04,0x03,0x5E,0x06,
+0xD1,0xE3,0x2E,0x8B,0x9F,0x44,0x00,0x8D,0x47,0x34,0x1E,0x5A,0x1F,0x5B,0x5D,0xC3,
+0xFB,0x55,0x8B,0xEC,0x53,0x51,0x52,0x56,0x57,0x1E,0x06,0x1E,0x07,0x33,0xC0,0x8E,
+0xD8,0x8B,0x5E,0x04,0x26,0x8A,0x47,0x59,0x25,0x03,0x00,0x8B,0xF0,0xD1,0xE6,0x2E,
+0x8B,0xB4,0xC4,0x00,0xC1,0xE0,0x04,0x26,0x02,0x47,0x1A,0xD1,0xE0,0x8B,0xE8,0x2E,
+0x8B,0xAE,0x44,0x00,0x89,0x2C,0x26,0x8A,0x47,0x1C,0x88,0x44,0x0F,0x26,0x8A,0x47,
+0x1D,0x88,0x44,0x10,0x26,0x8A,0x47,0x1E,0x88,0x44,0x11,0x26,0x8A,0x47,0x1F,0x88,
+0x44,0x12,0x26,0x8A,0x47,0x20,0x88,0x44,0x13,0x26,0x8A,0x47,0x23,0x88,0x44,0x14,
+0x26,0x8A,0x47,0x24,0x88,0x44,0x15,0x26,0x8A,0x47,0x5A,0x88,0x44,0x0E,0x33,0xC0,
+0x89,0x44,0x06,0x89,0x44,0x08,0x88,0x44,0x0B,0x88,0x44,0x0A,0xB0,0x21,0xB4,0x64,
+0x89,0x44,0x04,0x89,0x44,0x02,0xB0,0x55,0x88,0x44,0x0D,0x88,0x44,0x0C,0xE8,0x6A,
+0x00,0x72,0x5B,0xE8,0xC9,0x00,0xE8,0xC1,0x10,0x89,0x44,0x08,0x80,0x7C,0x0F,0x01,
+0x74,0x29,0xE8,0x2B,0x02,0xE8,0x7F,0x02,0x80,0x7C,0x0F,0x03,0x74,0x1D,0xE8,0xA9,
+0x10,0x8B,0xF8,0x2B,0x44,0x08,0x3D,0xA0,0x0F,0x72,0x10,0x89,0x7C,0x08,0x33,0xC0,
+0x87,0x44,0x06,0x85,0xC0,0x75,0x04,0xC6,0x44,0x0A,0xFF,0x8A,0x44,0x0A,0x84,0xC0,
+0x75,0x0B,0xB8,0x08,0x00,0xE8,0x6A,0x4A,0xE8,0xA9,0x01,0x73,0xBF,0xE8,0x4F,0x01,
+0x81,0x66,0x48,0x7F,0xFF,0x83,0x66,0x7A,0xBF,0xB0,0x02,0xE8,0x04,0x0E,0x8A,0x44,
+0x0A,0x98,0x07,0x1F,0x5F,0x5E,0x5A,0x59,0x5B,0x5D,0xC3,0x81,0x4E,0x48,0x80,0x00,
+0xB0,0x40,0xE8,0x3D,0x4A,0xE8,0x89,0x40,0x73,0x2A,0xE8,0x4D,0x10,0x8B,0xD8,0xB0,
+0x05,0xE8,0x2E,0x4A,0xF6,0x46,0x27,0x02,0x75,0x1A,0xE8,0x3D,0x10,0x2B,0xC3,0x3D,
+0x58,0x1B,0x72,0xEB,0x81,0x66,0x48,0x7F,0xFF,0xB0,0x02,0xE8,0xC4,0x0D,0xC6,0x44,
+0x0A,0x01,0xF9,0xC3,0x83,0x4E,0x7A,0x40,0xF8,0xC3,0xFB,0xB0,0x01,0xE8,0x02,0x4A,
+0xFA,0xE8,0x99,0x1E,0xE4,0x0A,0x84,0xC0,0x75,0xF0,0xB0,0x4E,0xE6,0x0A,0xFB,0xB0,
+0x01,0xE8,0xEE,0x49,0xFA,0xE8,0x85,0x1E,0xE4,0x0A,0x84,0xC0,0x75,0xF0,0xC3,0xFA,
+0xE8,0x7A,0x1E,0xE4,0xEC,0x88,0x44,0x16,0xE4,0xE4,0x88,0x44,0x17,0xE4,0xF8,0x88,
+0x44,0x18,0xE4,0xF0,0x88,0x44,0x19,0xE4,0x10,0x88,0x44,0x1A,0xE4,0x12,0x88,0x44,
+0x1B,0xE4,0x14,0x88,0x44,0x1C,0xE4,0x34,0x88,0x44,0x1D,0xE4,0x36,0x88,0x44,0x1E,
+0xE4,0xD8,0x24,0x01,0x8A,0xE0,0xE4,0xDA,0x24,0x02,0x0A,0xC4,0x88,0x44,0x1F,0x8A,
+0x44,0x10,0xE8,0xCD,0x1F,0x8A,0x44,0x11,0xE8,0x35,0x21,0x8A,0x44,0x12,0xE8,0x89,
+0x21,0x8A,0x44,0x13,0xE8,0x43,0x21,0xC6,0x86,0xA1,0x00,0x00,0xE4,0x14,0x24,0x10,
+0xE6,0x14,0xE4,0x12,0x24,0x3D,0xE6,0x12,0x8A,0x44,0x15,0x3C,0x01,0x72,0x1E,0x77,
+0x16,0xB0,0x11,0xE6,0x34,0xB0,0x13,0xE6,0x36,0xE4,0x14,0x0C,0x10,0xE6,0x14,0xE4,
+0x12,0x0C,0x40,0xE6,0x12,0xEB,0x06,0xE4,0x12,0x0C,0x02,0xE6,0x12,0x8A,0x44,0x0F,
+0x3C,0x01,0x74,0x06,0x3C,0x02,0x74,0x0A,0xEB,0x0E,0xE4,0x12,0x0C,0x08,0xE6,0x12,
+0xEB,0x06,0xE4,0x12,0x0C,0x10,0xE6,0x12,0xE8,0x2F,0xFF,0x8A,0x44,0x14,0x3C,0x02,
+0x75,0x08,0xB0,0x55,0x88,0x44,0x0C,0x88,0x44,0x0D,0xB0,0x21,0xB4,0x64,0x89,0x44,
+0x04,0x89,0x44,0x02,0xE4,0x0C,0x0C,0x10,0xE6,0x0C,0xE8,0xED,0x39,0xFB,0xC3,0xE8,
+0x5F,0x3F,0x73,0x08,0xFB,0xB0,0x0A,0xE8,0x08,0x49,0xEB,0xF3,0xFA,0xE8,0x9D,0x1D,
+0x8A,0x64,0x16,0x8A,0x44,0x17,0x89,0x86,0x94,0x00,0xE6,0xE4,0x8A,0xC4,0xE6,0xEC,
+0x8A,0x64,0x18,0x8A,0x44,0x19,0x89,0x86,0x96,0x00,0xE6,0xF0,0x8A,0xC4,0xE6,0xF8,
+0x8A,0x44,0x1A,0xE6,0x10,0x8A,0x44,0x1B,0xE6,0x12,0x8A,0x44,0x1C,0xE6,0x14,0x8A,
+0x44,0x1D,0xE6,0x34,0x8A,0x44,0x1E,0xE6,0x36,0x8A,0x44,0x1F,0xE6,0xD8,0xE6,0xDA,
+0xE9,0xB7,0xFE,0x90,0xFA,0x8A,0x44,0x0E,0xE6,0xFE,0xE4,0x02,0xA8,0x01,0x75,0x05,
+0x33,0xC0,0xFB,0xF8,0xC3,0x33,0xC0,0xE4,0x00,0xFB,0xF9,0xC3,0x8A,0x64,0x14,0x80,
+0xFC,0x02,0x74,0x2B,0xFE,0xC0,0xFE,0xC7,0x80,0xFF,0x4E,0x72,0x1C,0x74,0x09,0x80,
+0xFF,0x50,0x73,0x08,0xB0,0x0A,0xEB,0x17,0xB0,0x0D,0xEB,0x13,0x02,0xDC,0x32,0xFF,
+0x80,0xFB,0x7F,0x7C,0x02,0xB3,0x21,0x8A,0xC3,0x3C,0x7F,0x7C,0x02,0xB0,0x21,0xC3,
+0xFA,0x80,0x7C,0x0B,0x04,0x76,0x02,0xFB,0xC3,0x8B,0x46,0x24,0x3D,0x08,0x00,0x72,
+0xF6,0x8E,0x46,0x02,0x8B,0x7E,0x22,0x8A,0x44,0x0C,0x8B,0x5C,0x02,0xAA,0xE8,0xAB,
+0xFF,0xAA,0xE8,0xA7,0xFF,0xAA,0xE8,0xA3,0xFF,0xAA,0xE8,0x9F,0xFF,0x88,0x44,0x0C,
+0x89,0x5C,0x02,0x80,0x44,0x0B,0x04,0x89,0x7E,0x22,0x83,0x6E,0x24,0x04,0x83,0x46,
+0x1A,0x04,0x80,0x7E,0x26,0x02,0x74,0x06,0x80,0x66,0x26,0xFD,0xFB,0xC3,0x60,0xB0,
+0xFD,0xE8,0x02,0x3F,0x61,0xFB,0xC3,0xFA,0x80,0x7C,0x0F,0x03,0x75,0x09,0xC6,0x44,
+0x0B,0x00,0xE8,0xE5,0x38,0xFB,0xC3,0xC4,0x7E,0x14,0x8B,0x4E,0x3A,0x85,0xC9,0x75,
+0x35,0x26,0x8B,0x0D,0x47,0x47,0xE3,0xEA,0x3B,0x7E,0x04,0x76,0x22,0xB8,0x02,0x00,
+0x39,0x46,0x2E,0x77,0x07,0xC7,0x46,0x2E,0x00,0x00,0xEB,0x13,0x8B,0x5E,0x2C,0x89,
+0x5E,0x04,0x26,0xC7,0x07,0x00,0x00,0x43,0x43,0x89,0x5E,0x2C,0x29,0x46,0x2E,0x85,
+0xC9,0x78,0xCE,0x89,0x4E,0x3A,0x8A,0x44,0x0D,0x8B,0x5C,0x04,0x26,0x8A,0x25,0x47,
+0x3A,0xC4,0x75,0x16,0xFE,0x4C,0x0B,0xFF,0x44,0x06,0xE8,0x0F,0xFF,0xE2,0xED,0x88,
+0x44,0x0D,0x89,0x5C,0x04,0x89,0x4E,0x3A,0xEB,0xA7,0xC6,0x44,0x0A,0xFE,0xE8,0x79,
+0x38,0xFB,0xC3,0x90,0xE8,0xB3,0x0D,0x8A,0xE8,0x8A,0x0E,0xCB,0x13,0xB3,0x07,0x8A,
+0xC1,0xEE,0xEB,0x00,0xEC,0x3A,0xC1,0x75,0x09,0x02,0xCD,0xFE,0xCB,0x75,0xF0,0xEB,
+0x0C,0x90,0x88,0x0E,0xCB,0x13,0x8A,0xE8,0xBB,0xFF,0xFF,0xF9,0xC3,0x88,0x0E,0xCB,
+0x13,0xF8,0xC3,0x90,0xBB,0x3F,0x3F,0x8A,0x8E,0x9E,0x00,0xBA,0xFE,0x00,0xEC,0x8A,
+0xE8,0x32,0xC1,0x22,0xC3,0x75,0x02,0xF8,0xC3,0xF9,0xC3,0x90,0xE8,0xE5,0xFF,0x73,
+0x01,0xC3,0xBA,0xD0,0x00,0xBB,0x03,0x03,0x8A,0x8E,0x9F,0x00,0xEC,0x8A,0xE8,0x32,
+0xC1,0x22,0xC3,0x75,0x02,0xF8,0xC3,0xF9,0xC3,0x90,0x33,0xC0,0x8E,0xD8,0x8E,0xC0,
+0x80,0x3E,0xC8,0x13,0x00,0x75,0x07,0xB0,0x0A,0xE8,0x26,0x47,0xEB,0xF2,0xFB,0x33,
+0xDB,0x8A,0x1E,0xC9,0x13,0x43,0x43,0x83,0xFB,0x7E,0x76,0x07,0x33,0xDB,0xB0,0x02,
+0xE8,0x0F,0x47,0x2E,0x8B,0xAF,0x44,0x00,0x83,0x7E,0x08,0x00,0x74,0xE7,0x88,0x1E,
+0xC9,0x13,0xB0,0x02,0xE8,0xFB,0x46,0xFA,0xF7,0x46,0x38,0x40,0x00,0x74,0x14,0xE8,
+0x96,0x1B,0xE8,0x7F,0xFF,0x72,0x1C,0x33,0xD2,0x8A,0x96,0x9F,0x00,0x83,0xC2,0x0E,
+0xEB,0x0C,0x90,0xE8,0x77,0x1B,0xE8,0x83,0xFF,0x72,0x08,0xBA,0x48,0x00,0xE8,0x33,
+0xFF,0x73,0xAB,0x23,0xCB,0x89,0x8E,0x9A,0x00,0x89,0x96,0x9C,0x00,0xFE,0x86,0xB5,
+0x00,0xC6,0x06,0xC8,0x13,0x00,0xB0,0x0A,0xE8,0x67,0x0A,0xFB,0xEB,0x89,0x10,0x18,
+0x08,0x28,0x33,0xC0,0xA0,0x05,0x01,0x8A,0xC8,0x24,0x40,0x75,0x24,0xC7,0x06,0x7C,
+0x12,0x8E,0x45,0xC7,0x06,0x42,0x12,0x01,0x00,0xC6,0x06,0x54,0x12,0x02,0xB0,0x08,
+0xF6,0xC1,0x01,0x74,0x02,0xB0,0x04,0xA3,0x46,0x12,0xA2,0x4C,0x12,0xA2,0x94,0x12,
+0xC3,0xC7,0x06,0x7C,0x12,0xB6,0x45,0xA0,0x0F,0x01,0x84,0xC0,0x75,0x0E,0x6A,0x00,
+0x1F,0xC6,0x06,0x93,0x12,0x1E,0x9C,0x0E,0xE8,0xB1,0x0C,0x90,0xC7,0x06,0x44,0x12,
+0x01,0x00,0xA3,0x42,0x12,0x8B,0xD8,0xC1,0xE3,0x04,0x88,0x1E,0x94,0x12,0xBE,0xE2,
+0x05,0x2B,0xF0,0x8B,0xC8,0x33,0xDB,0x8B,0xFB,0x2E,0xAC,0x88,0x85,0x48,0x12,0x8A,
+0xD8,0x0C,0x05,0xE6,0xFE,0x8A,0xE0,0xEB,0x00,0xE4,0xFE,0x32,0xC4,0xA8,0x3F,0x74,
+0x03,0xE9,0x9E,0x00,0xE4,0x00,0x88,0x85,0x50,0x12,0x8A,0xE0,0x24,0x30,0xBA,0x10,
+0xFF,0x3C,0x30,0x74,0x12,0x80,0xFC,0x04,0x74,0x0A,0xBA,0x04,0x03,0xF6,0x06,0x08,
+0x01,0xFE,0x74,0x03,0xBA,0x08,0x0F,0x88,0x95,0x4C,0x12,0x02,0xFA,0x32,0xC0,0xF6,
+0xC4,0x08,0x74,0x02,0xB0,0x01,0x88,0x85,0x58,0x12,0x8A,0xC4,0x3C,0x35,0x74,0x5B,
+0x3C,0x36,0x74,0x57,0x3C,0x34,0x74,0x53,0x3C,0x04,0x74,0x4F,0x3C,0x14,0x74,0x4B,
+0x3C,0x15,0x74,0x47,0xA8,0x40,0x74,0x25,0xC6,0x85,0x54,0x12,0x04,0xD1,0xE7,0xB4,
+0x03,0x8A,0xC3,0x89,0x85,0x5C,0x12,0x8A,0xC3,0x8A,0xE3,0x80,0xCC,0x01,0x89,0x85,
+0x64,0x12,0xD1,0xEF,0x47,0xE2,0x03,0xEB,0x1A,0x90,0xE9,0x6C,0xFF,0xC6,0x85,0x54,
+0x12,0x02,0xD1,0xE7,0x8A,0xE6,0x8A,0xC3,0x0C,0x04,0x89,0x85,0x5C,0x12,0xD1,0xEF,
+0x47,0xE2,0xE7,0x33,0xC0,0x8A,0xC7,0xA3,0x46,0x12,0xC3,0xC6,0x85,0x54,0x12,0x06,
+0xEB,0xBB,0xC6,0x85,0x54,0x12,0x00,0x33,0xC0,0x88,0x85,0x50,0x12,0x88,0x85,0x4C,
+0x12,0x88,0x85,0x58,0x12,0xEB,0xA6,0xC7,0x46,0x26,0x02,0x12,0x8B,0x46,0x1E,0x89,
+0x46,0x00,0x89,0x46,0x22,0x8B,0x46,0x20,0x89,0x46,0x24,0xC7,0x46,0x1A,0x00,0x00,
+0xC3,0xC7,0x46,0x3C,0x80,0x00,0xC7,0x46,0x38,0x01,0x00,0x1E,0x56,0x8B,0x76,0x30,
+0x89,0x76,0x04,0x89,0x76,0x14,0x8E,0x5E,0x06,0x33,0xC0,0x89,0x04,0x46,0x46,0x89,
+0x76,0x2C,0x89,0x46,0x3A,0x8B,0x46,0x32,0x48,0x48,0x89,0x46,0x2E,0x5E,0x1F,0xC3,
+0x33,0xC0,0x89,0x46,0x48,0x89,0x46,0x4A,0xC7,0x46,0x46,0xAE,0x01,0x89,0x46,0x4E,
+0x8B,0x46,0x44,0x89,0x46,0x50,0x8B,0x46,0x42,0x89,0x46,0x40,0x89,0x46,0x08,0xC3,
+0x33,0xC0,0x89,0x46,0x76,0x89,0x46,0x78,0xC7,0x46,0x7A,0x10,0x00,0x56,0x1E,0x8B,
+0x76,0x70,0x89,0x76,0x10,0x89,0x76,0x0C,0x8E,0x5E,0x12,0xC7,0x04,0x00,0x00,0x8B,
+0x46,0x72,0x89,0x46,0x74,0x1F,0x5E,0xC3,0x89,0x56,0x18,0x89,0x56,0x02,0x89,0x56,
+0x06,0x89,0x56,0x0A,0x89,0x56,0x0E,0x89,0x56,0x12,0x89,0x56,0x16,0x8B,0xD8,0x4B,
+0x4B,0xC1,0xE3,0x02,0xBF,0x02,0x00,0x89,0x7E,0x1E,0x03,0xFB,0x89,0x7E,0x30,0x03,
+0xFB,0x89,0x7E,0x42,0x03,0xFB,0x89,0x7E,0x70,0x83,0xEB,0x08,0x89,0x5E,0x20,0x89,
+0x5E,0x32,0x89,0x5E,0x44,0x89,0x5E,0x72,0x50,0xE8,0x2B,0xFF,0xE8,0x71,0xFF,0xE8,
+0x3F,0xFF,0xE8,0x8B,0xFF,0x58,0xC3,0xB8,0x30,0x75,0xC1,0xE8,0x04,0x0E,0x5B,0x03,
+0xC3,0xA3,0xBA,0x13,0x83,0x3E,0x42,0x12,0x00,0x74,0x07,0x80,0x3E,0x94,0x12,0x00,
+0x75,0x0E,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x1E,0x9C,0x0E,0xE8,0xBD,0x0A,0x90,
+0xB8,0x30,0x7A,0xC1,0xE8,0x04,0x40,0xA3,0xC0,0x13,0x2B,0x06,0x12,0x01,0xF7,0xD8,
+0x33,0xD2,0x8B,0xCA,0x8A,0x0E,0x94,0x12,0xF7,0xF1,0x3D,0x80,0x00,0x77,0x0E,0x6A,
+0x00,0x1F,0xC6,0x06,0x93,0x12,0x25,0x9C,0x0E,0xE8,0x90,0x0A,0x90,0x48,0x3D,0xFF,
+0x07,0x72,0x03,0xB8,0xFF,0x07,0xA3,0xC2,0x13,0x33,0xC9,0x8A,0x0E,0x94,0x12,0x33,
+0xF6,0xB8,0x00,0x09,0x2E,0x8B,0xAC,0x44,0x00,0x89,0x46,0x4C,0x40,0x46,0x46,0xE2,
+0xF3,0x8A,0x0E,0x94,0x12,0x33,0xF6,0x8B,0x16,0xC0,0x13,0xA1,0xC2,0x13,0x2E,0x8B,
+0xAC,0x44,0x00,0xE8,0x22,0xFF,0x03,0xD0,0x46,0x46,0xE2,0xF2,0xC3,0x33,0xC0,0x2E,
+0x8B,0xAD,0x44,0x00,0x89,0x46,0x08,0x47,0x47,0xE2,0xF4,0xC3,0x51,0x33,0xC0,0x0A,
+0xC2,0x2E,0x8B,0xAD,0x44,0x00,0x89,0x86,0x9E,0x00,0x81,0x4E,0x38,0x00,0x20,0x47,
+0x47,0xFE,0xC4,0x80,0xFC,0x04,0x72,0x04,0x32,0xE4,0xFE,0xC0,0xE2,0xE3,0x59,0x83,
+0xE9,0x10,0x74,0x05,0xF7,0xD9,0xE8,0xC4,0xFF,0xC3,0x51,0x33,0xC0,0x0A,0xC2,0x2E,
+0x8B,0xAD,0x44,0x00,0x89,0x86,0x9E,0x00,0x83,0x4E,0x38,0x40,0x47,0x47,0x80,0xC4,
+0x10,0x79,0x04,0x32,0xE4,0xFE,0xC0,0xE2,0xE6,0x59,0x83,0xE9,0x10,0x74,0x05,0xF7,
+0xD9,0xE8,0x99,0xFF,0xC3,0xE8,0xD2,0xFF,0xC3,0x8D,0x08,0x9C,0x08,0xCA,0x08,0xF5,
+0x08,0x8B,0x0E,0x42,0x12,0x33,0xF6,0x51,0x56,0x33,0xDB,0x8B,0xCB,0x8A,0x94,0x48,
+0x12,0x8A,0x8C,0x4C,0x12,0x8A,0x9C,0x54,0x12,0x8B,0xFE,0xC1,0xE7,0x05,0x85,0xDB,
+0x75,0x02,0xB1,0x10,0x2E,0xFF,0x97,0xF9,0x08,0x5E,0x59,0x46,0xE2,0xD9,0xC3,0x01,
+0xCC,0x03,0xD0,0x00,0xE8,0x02,0xD0,0x00,0xE8,0x01,0xD0,0x00,0xE8,0x00,0xD0,0x00,
+0xE8,0x04,0xD0,0xA8,0xDA,0x00,0xDC,0x00,0xDE,0x01,0xD8,0x03,0xCC,0x03,0xCC,0x03,
+0xCC,0x04,0xD0,0xA8,0xDA,0x20,0xDC,0x00,0xDE,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x00,
+0xD8,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,
+0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,0xCC,0x03,
+0xCC,0x04,0xD0,0x00,0xDA,0x20,0xDC,0x03,0xDE,0x01,0xD8,0x03,0xCC,0x03,0xCC,0x03,
+0xCC,0x03,0xCC,0x00,0xD8,0x00,0xCC,0x00,0xD0,0x00,0x00,0x56,0x52,0x1E,0x0E,0x1F,
+0xBE,0x2F,0x09,0x33,0xD2,0xFC,0xAD,0x85,0xC0,0x74,0x0D,0x8A,0xD4,0xEE,0xAD,0x85,
+0xC0,0x74,0x05,0x8A,0xD4,0xEE,0xEB,0xEE,0x1F,0x5A,0x5E,0xC3,0xE4,0x80,0x84,0xC0,
+0x74,0x16,0x78,0x14,0xB0,0x27,0xE6,0xFC,0xB0,0x11,0xE6,0x34,0xE4,0xFC,0x3C,0x27,
+0x75,0x06,0xE4,0x11,0x75,0x02,0xF8,0xC3,0xF9,0xC3,0x83,0xC2,0x06,0xB0,0xBF,0xEE,
+0x83,0xEA,0x02,0xB0,0x10,0xEE,0x88,0x86,0xAF,0x00,0xB0,0x11,0x83,0xC2,0x04,0xEE,
+0x83,0xC2,0x02,0xEE,0xB0,0x13,0x83,0xC2,0x02,0xEE,0x83,0xC2,0x02,0xEE,0x2E,0xA1,
+0x4C,0x2D,0x89,0x86,0x94,0x00,0x83,0xEA,0x0E,0xEE,0x83,0xC2,0x02,0x8A,0xC4,0xEE,
+0x83,0xC2,0x04,0xB0,0x03,0xEE,0x88,0x86,0xA8,0x00,0x83,0xEA,0x04,0x32,0xC0,0xEE,
+0x83,0xC2,0x02,0xB0,0x89,0xEE,0x88,0x86,0xA6,0x00,0x0C,0x06,0xEE,0xB0,0x40,0xB4,
+0x38,0x89,0x46,0x1C,0xC7,0x46,0x36,0x38,0x00,0x83,0xC2,0x04,0x32,0xC0,0xEE,0x88,
+0x86,0xA7,0x00,0xC3,0x83,0xC2,0x06,0xB0,0xBF,0xEE,0x83,0xEA,0x02,0xEC,0x3A,0x86,
+0xAF,0x00,0x75,0x24,0x83,0xC2,0x04,0xEC,0x3C,0x11,0x75,0x1C,0x83,0xC2,0x06,0xEC,
+0x3C,0x13,0x75,0x14,0x83,0xEA,0x08,0x8A,0x86,0xA8,0x00,0xEE,0x83,0xEA,0x02,0xEC,
+0x24,0xC0,0x3C,0xC0,0x75,0x02,0xF8,0xC3,0xF9,0xC3,0x33,0xC9,0x8B,0xD1,0x8B,0xF1,
+0x8A,0x0E,0x94,0x12,0xC1,0xE9,0x02,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x00,
+0x20,0x74,0x0E,0x8A,0x86,0x9E,0x00,0xE6,0xFE,0x32,0xC0,0xE6,0x80,0x42,0xE8,0xFA,
+0xFE,0x83,0xC6,0x08,0xE2,0xE1,0x85,0xD2,0x74,0x03,0xE8,0x05,0x08,0xC3,0x33,0xC9,
+0x8B,0xF1,0x8A,0x0E,0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x40,0x00,
+0x74,0x06,0xE8,0x73,0x16,0xE8,0x12,0xFF,0x46,0x46,0xE2,0xEA,0xC3,0x33,0xC9,0x8B,
+0xF1,0x8A,0x0E,0x94,0x12,0xC1,0xE9,0x02,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,
+0x00,0x20,0x74,0x16,0xE8,0x46,0x16,0xE8,0xD2,0xFE,0x73,0x0E,0x6A,0x00,0x1F,0xC6,
+0x06,0x93,0x12,0x1C,0x9C,0x0E,0xE8,0xE3,0x07,0x90,0x83,0xC6,0x08,0xE2,0xD9,0xC3,
+0x33,0xC9,0x8B,0xF1,0x8A,0x0E,0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,
+0x40,0x00,0x74,0x16,0xE8,0x21,0x16,0xE8,0x2A,0xFF,0x73,0x0E,0x6A,0x00,0x1F,0xC6,
+0x06,0x93,0x12,0x1C,0x9C,0x0E,0xE8,0xB3,0x07,0x90,0x46,0x46,0xE2,0xDA,0xC3,0x0C,
+0x00,0x00,0x10,0x00,0x13,0x12,0x00,0x00,0x14,0x00,0x28,0x3C,0x00,0x1B,0x3E,0x00,
+0x00,0x2A,0x00,0x00,0x2C,0x00,0x00,0x42,0x00,0x14,0xD8,0x00,0x00,0xDA,0x00,0x00,
+0x34,0x00,0x11,0x36,0x00,0x13,0x38,0x00,0x11,0x3A,0x00,0x13,0x00,0x00,0x56,0x50,
+0x52,0xBE,0x2F,0x0B,0x2E,0xAD,0x85,0xC0,0x74,0x06,0x92,0x2E,0xAC,0xEE,0xEB,0xF4,
+0x5A,0x58,0x5E,0xC3,0x53,0x2E,0xA1,0x60,0x22,0xE6,0xE4,0xE6,0xF0,0x8A,0xC4,0xE6,
+0xEC,0xE6,0xF8,0xE8,0xD8,0xFF,0xB0,0x4B,0xE6,0x10,0xB0,0x50,0xE6,0x12,0xB0,0x38,
+0xE6,0x14,0xE8,0xAE,0x15,0xB0,0x46,0xE6,0x0A,0xE8,0xA7,0x15,0xB0,0x1A,0xE6,0x0A,
+0xE8,0xA0,0x15,0xB0,0x22,0xE6,0x0A,0xE8,0x99,0x15,0xE8,0xFD,0x06,0x8B,0xD8,0xE4,
+0x16,0xA8,0x04,0x75,0x18,0xE8,0xF2,0x06,0x2B,0xC3,0x3D,0x32,0x00,0x72,0xF0,0x6A,
+0x00,0x1F,0xC6,0x06,0x93,0x12,0x23,0x9C,0x0E,0xE8,0x10,0x07,0x90,0xE8,0xDA,0x06,
+0x2B,0xC3,0x3D,0x24,0x00,0x77,0x1B,0xB0,0x31,0xE6,0xFC,0x56,0x51,0x55,0xB9,0x10,
+0x00,0x2E,0x8B,0xAC,0x44,0x00,0x81,0x4E,0x38,0x80,0x00,0x46,0x46,0xE2,0xF2,0x5D,
+0x59,0x5E,0xE8,0x69,0xFF,0xE8,0x4B,0x15,0xB0,0x46,0xE6,0x0A,0xE8,0x44,0x15,0x5B,
+0xC3,0x33,0xF6,0x8B,0x0E,0x42,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x00,
+0x20,0x74,0x06,0xE8,0x17,0x15,0xE8,0x5B,0xFF,0x83,0xC6,0x20,0xE2,0xE9,0xC3,0x8B,
+0xC2,0x05,0x04,0x00,0x89,0x46,0x28,0x2E,0xA1,0x4C,0x2D,0x89,0x86,0x8E,0x00,0x89,
+0x86,0x90,0x00,0x89,0x86,0x92,0x00,0xC6,0x86,0xA3,0x00,0x0A,0xC6,0x86,0xC3,0x00,
+0x03,0x52,0x83,0xC2,0x04,0x8A,0x86,0xA6,0x00,0x0C,0x06,0xEE,0x5A,0x83,0xC2,0x02,
+0xB0,0x05,0xEE,0x88,0x86,0xA5,0x00,0xC3,0xE8,0x03,0xFF,0xE8,0xE5,0x14,0xB0,0x42,
+0xE6,0x0A,0xF7,0x46,0x38,0x80,0x00,0x74,0x06,0x2E,0xA1,0x9C,0x22,0xEB,0x04,0x2E,
+0xA1,0x6C,0x22,0xC7,0x46,0x1C,0x0C,0x00,0x89,0x86,0x94,0x00,0x89,0x86,0x96,0x00,
+0x89,0x86,0x8E,0x00,0x89,0x86,0x90,0x00,0x89,0x86,0x92,0x00,0xE6,0xF0,0xE6,0xE4,
+0x8A,0xC4,0xE6,0xF8,0xE6,0xEC,0xC6,0x86,0xC3,0x00,0x03,0xE8,0xA5,0x14,0xB0,0x1A,
+0xE6,0x0A,0xB0,0x10,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,0x33,0xC9,0x8B,0xF1,0x8A,
+0x0E,0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x40,0x00,0x74,0x06,0xE8,
+0x76,0x14,0xE8,0x5A,0xFF,0x46,0x46,0xE2,0xEA,0xC3,0x33,0xC9,0x8B,0xF1,0x8A,0x0E,
+0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xF7,0x46,0x38,0x00,0x20,0x74,0x06,0xE8,0x4C,
+0x14,0xE8,0x74,0xFF,0x46,0x46,0xE2,0xEA,0xC3,0x90,0x83,0x3E,0x44,0x12,0x00,0x75,
+0x14,0xB0,0x01,0xBA,0x06,0x01,0xEE,0x2A,0xC0,0xEE,0xB0,0x02,0xEE,0xB0,0x04,0xEE,
+0xB8,0x00,0x02,0xEB,0x0F,0xBA,0x06,0x01,0xB0,0x40,0xEE,0xB8,0x01,0x00,0x8A,0x0E,
+0x0E,0x01,0xD3,0xE0,0xA3,0x88,0x12,0xC3,0xA1,0x88,0x12,0xA3,0x84,0x12,0x2D,0x20,
+0x00,0xA3,0x8A,0x12,0x2D,0x20,0x00,0xA3,0x82,0x12,0xC7,0x06,0x86,0x12,0x20,0x00,
+0xC7,0x06,0x80,0x12,0x32,0x00,0xC3,0x83,0x3E,0x44,0x12,0x00,0x74,0x76,0x8B,0x0E,
+0x42,0x12,0x33,0xF6,0x8A,0xA4,0x54,0x12,0x84,0xE4,0x74,0x5F,0x8A,0x84,0x48,0x12,
+0x0C,0x04,0xE6,0xFE,0xF6,0xC4,0x04,0x74,0x25,0xB0,0x1B,0xBA,0x00,0x00,0xEE,0xEB,
+0x00,0x2A,0xC0,0xBA,0x02,0x00,0xEE,0xEB,0x00,0xB0,0x03,0xEE,0xEB,0x00,0x32,0xC0,
+0xBA,0x02,0x00,0xEE,0xEB,0x00,0xBA,0x00,0x00,0xB0,0x00,0xEE,0xEB,0x2D,0xB0,0x1F,
+0xBA,0x00,0x00,0xEE,0xEB,0x00,0x2A,0xC0,0xBA,0x02,0x00,0xEE,0xEB,0x00,0xB0,0x03,
+0xEE,0xEB,0x00,0xD1,0xE6,0x8A,0x84,0x5D,0x12,0xD1,0xEE,0xF6,0xD0,0xBA,0x02,0x00,
+0xEE,0xEB,0x00,0xBA,0x00,0x00,0xB0,0x0A,0xEE,0xEB,0x00,0xE4,0x04,0xEB,0x00,0xE4,
+0x04,0x46,0xE2,0x90,0xC3,0x90,0xB8,0x14,0x00,0xBA,0x3E,0xFF,0xEF,0xB8,0x06,0x00,
+0xBA,0x32,0xFF,0xEF,0xB8,0x0F,0x00,0xBA,0x34,0xFF,0xEF,0xBA,0x36,0xFF,0xEF,0x83,
+0x3E,0x44,0x12,0x00,0x75,0x16,0xB8,0x11,0x00,0xBA,0x38,0xFF,0xEF,0xB8,0x12,0x00,
+0xBA,0x3A,0xFF,0xEF,0xB8,0x1B,0x00,0xBA,0x3C,0xFF,0xEF,0xC3,0xB8,0x11,0x00,0xBA,
+0x38,0xFF,0xEF,0xB8,0x12,0x00,0xBA,0x3A,0xFF,0xEF,0xB8,0x1B,0x00,0xBA,0x3C,0xFF,
+0xEF,0xC3,0xB8,0xFC,0x00,0xBA,0x28,0xFF,0xEF,0xFB,0x83,0x3E,0x44,0x12,0x00,0x74,
+0x07,0xB8,0xCC,0x00,0xBA,0x28,0xFF,0xEF,0xC3,0x00,0xFF,0xFF,0x20,0x24,0x28,0xFF,
+0x2C,0xFF,0xFF,0x30,0x34,0x38,0xFF,0xFF,0x3C,0x90,0x3C,0x0F,0x77,0x0E,0xBB,0x19,
+0x0E,0x2E,0xD7,0x3C,0xFF,0x74,0x05,0x8A,0xD8,0xF8,0xC3,0x90,0x2A,0xDB,0xF9,0xC3,
+0x83,0x3E,0x44,0x12,0x00,0x74,0x27,0xA0,0x06,0x01,0x80,0x26,0x06,0x01,0x30,0x80,
+0x3E,0x06,0x01,0x30,0x75,0x18,0xB9,0x02,0x00,0xBF,0xC4,0x13,0xBA,0x06,0x01,0xEC,
+0xA8,0x20,0x75,0xF8,0xBA,0x04,0x01,0xED,0xAB,0xE2,0xF1,0xEB,0x16,0x90,0xB9,0x04,
+0x00,0xBF,0xC4,0x13,0xBA,0x06,0x01,0xEC,0xA8,0x20,0x75,0xF8,0xBA,0x04,0x01,0xEC,
+0xAA,0xE2,0xF1,0xFA,0x90,0xBE,0xC4,0x13,0xAD,0x80,0xE4,0x3F,0x80,0xFC,0x02,0x74,
+0x0E,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x0A,0x9C,0x0E,0xE8,0x3E,0x04,0x90,0xAD,
+0x3C,0x0F,0x75,0xED,0x8A,0xC4,0xE8,0x81,0xFF,0x72,0xE6,0x88,0x1E,0x1A,0x01,0xC6,
+0x06,0x8E,0x12,0x00,0xB0,0x00,0x0A,0x06,0x1A,0x01,0xBA,0x00,0x01,0xEE,0xC6,0x06,
+0x8F,0x12,0x40,0x83,0x3E,0x44,0x12,0x00,0x75,0x06,0xB8,0x0C,0x00,0xEB,0x04,0x90,
+0xB8,0x4C,0x00,0xBA,0x28,0xFF,0xEF,0xC3,0x83,0x3E,0x44,0x12,0x00,0x75,0x01,0xC3,
+0xA1,0x50,0x12,0x0B,0x06,0x52,0x12,0x0A,0xC4,0xA8,0x08,0x74,0xF2,0xA0,0x0F,0x01,
+0x2A,0xE4,0x50,0xFF,0x36,0xBA,0x13,0x1F,0xE8,0x50,0x56,0x83,0xC4,0x02,0x6A,0x00,
+0x1F,0x33,0xC0,0xA3,0xBC,0x13,0xA0,0x0F,0x01,0xA3,0xBE,0x13,0x8B,0x1E,0xBC,0x13,
+0x8A,0x87,0x50,0x12,0xF6,0x87,0x50,0x12,0x08,0x74,0x0D,0x24,0x07,0x8A,0xE0,0xBE,
+0xCC,0x00,0xA0,0xBC,0x13,0xE8,0x94,0x3D,0xFF,0x06,0xBC,0x13,0xFF,0x0E,0xBE,0x13,
+0x75,0xDA,0xC3,0x90,0x1E,0x33,0xC0,0x8E,0xD8,0xB0,0x01,0xE8,0x54,0x3D,0x1F,0xC3,
+0x33,0xC9,0x8B,0xF1,0x8A,0x0E,0x94,0x12,0x2E,0x8B,0xAC,0x44,0x00,0xC7,0x46,0x62,
+0x38,0x44,0xC7,0x46,0x7C,0xFC,0x3B,0xC7,0x46,0x7E,0xE2,0x3B,0xC7,0x86,0x80,0x00,
+0xEC,0x3C,0xE8,0xAB,0x16,0xC6,0x86,0xC0,0x00,0x11,0x83,0x7E,0x08,0x00,0x74,0x07,
+0x51,0x56,0xE8,0x33,0x33,0x5E,0x59,0x46,0x46,0xE2,0xCD,0xC3,0x33,0xC9,0x8B,0xF1,
+0x8B,0xF9,0x8A,0x0E,0x94,0x12,0xC1,0xE9,0x02,0xE3,0x13,0x2E,0x8B,0xAC,0x44,0x00,
+0x8A,0x86,0x9E,0x00,0x88,0x85,0x6C,0x12,0x83,0xC6,0x08,0x47,0xE2,0xED,0xC3,0xFA,
+0xFC,0xB0,0xC0,0xBA,0x00,0x01,0xEE,0x33,0xC0,0x8E,0xD8,0x8E,0xC0,0x8E,0xD0,0xBF,
+0x16,0x01,0xB9,0xCC,0x77,0x2B,0xCF,0xD1,0xE9,0xF3,0xAB,0xBC,0x40,0x12,0xE8,0xD9,
+0x02,0xE8,0x70,0x3C,0xBE,0xCC,0x0F,0xE8,0xF2,0x3C,0xF4,0x90,0x33,0xC0,0x8E,0xD8,
+0x8E,0xC0,0x8E,0xD0,0xF6,0x06,0x0A,0x01,0x80,0x74,0x0B,0xBE,0x35,0x55,0xE8,0xDB,
+0x3C,0xB0,0x01,0xE8,0xAC,0x3C,0xE8,0xB3,0x00,0xE8,0xF6,0xF5,0xE8,0x08,0xF8,0xE8,
+0x0F,0xF9,0xE8,0x85,0xFA,0xE8,0xB6,0xFA,0xE8,0xEF,0xFC,0xE8,0xC2,0x10,0xE8,0x03,
+0x3C,0xE8,0xB2,0xFD,0xE8,0x30,0xFD,0xE8,0x54,0x02,0xC6,0x06,0x8F,0x12,0xC0,0xE8,
+0xBB,0xFA,0xE8,0xEB,0xFA,0xE8,0xE9,0xFB,0xE8,0xAF,0xFC,0xE8,0x8D,0xFC,0xE8,0x1F,
+0xFF,0xE8,0x58,0xFF,0xE8,0xDB,0xFD,0xE8,0x16,0xFE,0x33,0xC0,0xBE,0x5A,0x05,0xE8,
+0x8A,0x3C,0xE8,0xA3,0xFE,0xE8,0xE0,0xFC,0xFB,0xBE,0xA4,0x44,0xE8,0x7D,0x3C,0xE9,
+0xCA,0x2D,0x56,0x98,0x8B,0xF0,0x8B,0x42,0x52,0x85,0xC0,0x75,0x27,0xC7,0x42,0x52,
+0x01,0x00,0x53,0x36,0x8B,0x9C,0x2C,0x01,0xF6,0xC3,0x01,0x75,0x0C,0x36,0x89,0x68,
+0x52,0x36,0x89,0xAC,0x2C,0x01,0x5B,0x5E,0xC3,0x36,0x89,0xAC,0x2C,0x01,0x36,0x89,
+0xAC,0x1C,0x01,0x5B,0x5E,0xC3,0x56,0x98,0x8B,0xF0,0x33,0xED,0x36,0x8B,0x84,0x1C,
+0x01,0xA8,0x01,0x75,0x15,0x8B,0xE8,0x33,0xC0,0x87,0x42,0x52,0x36,0x89,0x84,0x1C,
+0x01,0xA8,0x01,0x74,0x05,0x36,0x89,0x84,0x2C,0x01,0x5E,0xC3,0x56,0x51,0x33,0xF6,
+0xB8,0x01,0x00,0xB9,0x08,0x00,0x89,0x84,0x1C,0x01,0x89,0x84,0x2C,0x01,0x46,0x46,
+0xE2,0xF4,0x59,0x5E,0xC3,0x90,0xBB,0x01,0x00,0x8B,0xE8,0xFF,0x4E,0x6E,0x74,0x0A,
+0x8B,0xDD,0x8B,0x46,0x58,0xA8,0x01,0x74,0xF0,0xC3,0x8B,0x46,0x48,0xA9,0x08,0x00,
+0x74,0x45,0xF7,0x46,0x38,0x40,0x00,0x74,0x27,0xE8,0x5C,0x10,0x80,0xC2,0x06,0x8A,
+0x86,0xA8,0x00,0x24,0xBF,0x88,0x86,0xA8,0x00,0xEE,0x60,0xB0,0xFE,0xE8,0x86,0x32,
+0x61,0xB0,0x02,0xE8,0x4C,0xFF,0x8B,0x46,0x48,0x24,0xF7,0x89,0x46,0x48,0xEB,0x17,
+0xE8,0x2A,0x10,0x81,0x4E,0x26,0x00,0x40,0x8A,0x86,0xA5,0x00,0x0C,0x02,0x88,0x86,
+0xA5,0x00,0xE6,0x0C,0x8B,0x46,0x48,0xA9,0x04,0x00,0x74,0x14,0xB0,0x02,0xE8,0x21,
+0xFF,0x8B,0x46,0x48,0x24,0xFB,0x89,0x46,0x48,0x60,0xB0,0xDF,0xE8,0x47,0x32,0x61,
+0x33,0xC0,0x87,0x46,0x58,0xF6,0xC3,0x01,0x75,0x0B,0x36,0x89,0x47,0x58,0xA8,0x01,
+0x75,0x0D,0xE9,0x74,0xFF,0xA3,0x22,0x01,0xA8,0x01,0x75,0x03,0xE9,0x6A,0xFF,0x89,
+0x1E,0x32,0x01,0xC3,0xBB,0x01,0x00,0x8B,0xE8,0xF7,0x46,0x38,0x40,0x00,0x74,0x15,
+0xE8,0xD5,0x0F,0x80,0xC2,0x0A,0xEC,0xA8,0x40,0x75,0x0A,0x8B,0xDD,0x8B,0x46,0x56,
+0xA8,0x01,0x74,0xE3,0xC3,0x8B,0x46,0x26,0x80,0xE4,0xFE,0x80,0xCC,0x02,0x89,0x46,
+0x26,0xB0,0x02,0xE8,0xBC,0xFE,0x33,0xC0,0x87,0x46,0x56,0xF6,0xC3,0x01,0x75,0x0A,
+0x36,0x89,0x47,0x56,0xA8,0x01,0x75,0x0B,0xEB,0xBD,0xA3,0x20,0x01,0xA8,0x01,0x75,
+0x02,0xEB,0xB4,0x89,0x1E,0x30,0x01,0xC3,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA0,
+0x90,0x12,0x84,0xC0,0x75,0x49,0xA1,0x22,0x01,0xA8,0x01,0x75,0x03,0xE8,0xF6,0xFE,
+0xA1,0x20,0x01,0xA8,0x01,0x75,0x03,0xE8,0x8A,0xFF,0xA1,0xAC,0x13,0x48,0x78,0x05,
+0x74,0x45,0xA3,0xAC,0x13,0xA1,0xAE,0x13,0x48,0x78,0x05,0x74,0x51,0xA3,0xAE,0x13,
+0xA1,0xB0,0x13,0x48,0x78,0x05,0x74,0x63,0xA3,0xB0,0x13,0xA1,0x7E,0x12,0x40,0x78,
+0x03,0xA3,0x7E,0x12,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0xA0,
+0x91,0x12,0x40,0x3C,0x02,0x72,0x0B,0x33,0xC0,0xA2,0x91,0x12,0xFF,0x16,0x7C,0x12,
+0xEB,0xA4,0xA2,0x91,0x12,0xEB,0x9F,0xA0,0x8E,0x12,0x32,0x06,0x8F,0x12,0xA2,0x8E,
+0x12,0x0A,0x06,0x1A,0x01,0xBA,0x00,0x01,0xEE,0xB8,0x2C,0x01,0xEB,0xA4,0x83,0x3E,
+0x84,0x12,0x10,0x72,0x11,0xBA,0x28,0xFF,0xED,0x0C,0x81,0xEF,0xE8,0x53,0x37,0xBA,
+0x28,0xFF,0xED,0x24,0x7E,0xEF,0xB8,0x04,0x00,0xEB,0x92,0xC6,0x06,0x8D,0x12,0x01,
+0xE8,0x3F,0x37,0xC6,0x06,0x8D,0x12,0x00,0xA1,0xB2,0x13,0xEB,0x8B,0x90,0x8A,0x1E,
+0x0B,0x01,0x2A,0xFF,0x6B,0xC3,0x19,0xBA,0x62,0xFF,0xEF,0xB8,0x0A,0x00,0xBA,0x60,
+0xFF,0xEF,0xB8,0x01,0xE0,0xBA,0x66,0xFF,0xEF,0xB8,0xFF,0xFF,0xBA,0x52,0xFF,0xEF,
+0xB8,0x09,0xC0,0xBA,0x56,0xFF,0xEF,0xC7,0x06,0xAC,0x13,0x2C,0x01,0xC7,0x06,0xAE,
+0x13,0x04,0x00,0xC6,0x06,0x91,0x12,0x00,0xC3,0x90,0x8A,0x1E,0x0B,0x01,0x2A,0xFF,
+0x6B,0xC3,0x05,0xD1,0xE8,0xA3,0x18,0x01,0xC3,0x90,0x52,0xBA,0x50,0xFF,0xED,0x5A,
+0xC3,0x90,0x53,0x51,0x8B,0x1E,0x18,0x01,0xB9,0x32,0x05,0x90,0xE2,0xFE,0x4B,0x75,
+0xF7,0x59,0x5B,0xC3,0xB0,0x80,0xBA,0x00,0x01,0x0A,0x06,0x1A,0x01,0xEE,0xC3,0x90,
+0xB0,0x40,0xEB,0xF2,0xB0,0xC0,0xEB,0xEE,0xB0,0x00,0xEB,0xEA,0xFA,0x60,0x06,0x1E,
+0x16,0x2B,0xDB,0x8E,0xDB,0x2E,0xA1,0xBA,0x4C,0x2E,0xA3,0x92,0x4C,0xA0,0x93,0x12,
+0x98,0x8B,0xE8,0x89,0x26,0x2D,0x7A,0x80,0x3E,0xCA,0x13,0x00,0x74,0x03,0xE9,0x6B,
+0x42,0xE8,0xC0,0xFF,0xE8,0xAB,0xFF,0xE8,0xA8,0xFF,0xB0,0x20,0xC6,0x06,0x90,0x12,
+0x00,0xFF,0x16,0x7C,0x12,0x8B,0xFD,0x83,0xFF,0x0A,0x72,0x11,0xE8,0xB9,0xFF,0xE8,
+0x90,0xFF,0xE8,0xAB,0xFF,0xE8,0x8A,0xFF,0x83,0xEF,0x0A,0xEB,0xEA,0x0B,0xFF,0x74,
+0x0F,0xE8,0xA4,0xFF,0xE8,0x7B,0xFF,0xE8,0x9A,0xFF,0xE8,0x75,0xFF,0x4F,0x75,0xF1,
+0xE8,0x95,0xFF,0xE8,0x6C,0xFF,0xEB,0xB9,0x8A,0x86,0xA5,0x00,0x24,0xFD,0xEE,0x88,
+0x86,0xA5,0x00,0xC3,0x8A,0x86,0xA6,0x00,0x0C,0x02,0xEE,0xC3,0x8B,0x76,0x38,0xF7,
+0xC6,0x01,0x00,0x74,0xEF,0x8B,0x4E,0x36,0x8B,0x46,0x2E,0x3B,0xC1,0x73,0x02,0x8B,
+0xC8,0x2B,0xC1,0x89,0x46,0x2E,0x01,0x4E,0x34,0xC4,0x7E,0x04,0x26,0x01,0x0D,0x8B,
+0x7E,0x2C,0x83,0xEA,0x04,0xF3,0x6C,0x8E,0xC1,0x89,0x7E,0x2C,0x3B,0x46,0x3C,0x72,
+0x12,0xF7,0xC6,0x20,0x00,0x75,0x0B,0x83,0xCE,0x20,0x89,0x76,0x38,0xB0,0x00,0xE8,
+0xA0,0xFC,0xC3,0xF7,0xC6,0x04,0x00,0x74,0x1B,0x8B,0xD8,0x83,0xCE,0x10,0x89,0x76,
+0x38,0x8A,0x86,0xA7,0x00,0x24,0xFE,0x88,0x86,0xA7,0x00,0x83,0xC2,0x08,0xEE,0x83,
+0xEA,0x08,0x8B,0xC3,0x3D,0x40,0x00,0x72,0x01,0xC3,0x81,0x4E,0x38,0x00,0x04,0x83,
+0xC2,0x02,0x8A,0x86,0xA5,0x00,0x24,0xFA,0x88,0x86,0xA5,0x00,0xEE,0xC3,0x8A,0x86,
+0xA6,0x00,0x0C,0x02,0xEE,0xC3,0xF7,0x46,0x38,0x01,0x00,0x74,0xF1,0x8B,0x4E,0x2E,
+0x32,0xDB,0x8A,0xBE,0xA3,0x00,0x83,0xC2,0x06,0xC4,0x76,0x04,0x8B,0x7E,0x2C,0x83,
+0xF9,0x08,0x72,0x2C,0xEC,0xA8,0x01,0x74,0x16,0x8A,0xE0,0x83,0xEA,0x0A,0xEC,0x83,
+0xC2,0x0A,0x84,0xE7,0x75,0x51,0xAA,0xFE,0xC3,0x49,0x83,0xF9,0x08,0x73,0xE5,0x32,
+0xFF,0x26,0x01,0x1C,0x01,0x5E,0x34,0x89,0x76,0x04,0x89,0x4E,0x2E,0x89,0x7E,0x2C,
+0x3B,0x4E,0x3C,0x72,0x11,0xF6,0x46,0x38,0x20,0x74,0x01,0xC3,0x83,0x4E,0x38,0x20,
+0xB0,0x00,0xE8,0xFD,0xFB,0xC3,0xF6,0x46,0x38,0x04,0x74,0x15,0x83,0x4E,0x38,0x10,
+0x8A,0x86,0xA7,0x00,0x24,0xFE,0x88,0x86,0xA7,0x00,0x83,0xEA,0x02,0xEE,0x83,0xC2,
+0x02,0x3D,0x40,0x00,0x72,0x5D,0xC3,0x32,0xFF,0x26,0x03,0x1C,0x85,0xDB,0x74,0x09,
+0x26,0x89,0x1C,0x8B,0xF7,0x47,0x47,0x49,0x49,0x80,0xE4,0x1E,0x80,0xCC,0xC0,0x26,
+0x89,0x04,0xF6,0xC4,0x10,0x74,0x27,0x8B,0x76,0x38,0xF7,0xC6,0x00,0x10,0x74,0x0B,
+0x50,0xFE,0x86,0xB2,0x00,0xB0,0x0A,0xE8,0xA8,0xFB,0x58,0xF7,0xC6,0x00,0x01,0x74,
+0x0D,0xE8,0x82,0x26,0x8B,0x76,0x38,0x8B,0x4E,0x2E,0x8B,0x7E,0x04,0xAB,0x8B,0xF7,
+0x33,0xC0,0xAB,0x32,0xDB,0x8A,0xBE,0xA3,0x00,0x49,0x49,0x83,0xF9,0x08,0x72,0x17,
+0xE9,0x41,0xFF,0x81,0x4E,0x38,0x00,0x04,0x83,0xC2,0xF8,0x8A,0x86,0xA5,0x00,0x24,
+0xFA,0x88,0x86,0xA5,0x00,0xEE,0xC3,0xE9,0x45,0xFF,0x83,0xC2,0x08,0xEC,0x88,0x86,
+0xAA,0x00,0xC0,0xE8,0x04,0x8A,0xE0,0x8A,0xC8,0x86,0x86,0xA9,0x00,0x32,0xE0,0x8B,
+0x5E,0x3E,0x84,0xE3,0x74,0x4F,0x8A,0xC1,0x8B,0x4E,0x26,0xF6,0xC5,0x04,0x74,0x0C,
+0xA8,0x08,0x74,0x05,0x80,0xE1,0xBF,0xEB,0x03,0x80,0xC9,0x40,0xF6,0xC5,0x08,0x74,
+0x0C,0xA8,0x02,0x74,0x05,0x80,0xE1,0x7F,0xEB,0x03,0x80,0xC9,0x80,0x88,0x4E,0x26,
+0x8B,0xF0,0x8A,0x86,0xA5,0x00,0x84,0xC9,0x74,0x08,0xA8,0x02,0x74,0x15,0x24,0xFD,
+0xEB,0x06,0xA8,0x02,0x75,0x0D,0x0C,0x02,0x88,0x86,0xA5,0x00,0x83,0xEA,0x0A,0xEE,
+0x83,0xC2,0x0A,0x8B,0xC6,0x84,0xE7,0x75,0x01,0xC3,0xC6,0x86,0xBA,0x00,0x01,0xB0,
+0x0E,0xE8,0xEE,0xFA,0xF7,0x46,0x38,0x00,0x02,0x74,0xEE,0x83,0x7E,0x2E,0x06,0x72,
+0xE8,0x8A,0xA6,0xAA,0x00,0xC4,0x5E,0x04,0x8B,0x7E,0x2C,0xB0,0xFF,0xAA,0xB0,0x02,
+0xAB,0x26,0x83,0x07,0x03,0x83,0x6E,0x2E,0x03,0x89,0x7E,0x2C,0xF6,0x46,0x38,0x20,
+0x74,0x01,0xC3,0x83,0x4E,0x38,0x20,0xB0,0x00,0xE8,0xB6,0xFA,0xC3,0x90,0x83,0xEA,
+0x08,0xE9,0xB4,0xFD,0x83,0xC2,0x06,0x8B,0x5E,0x26,0xF6,0xC3,0xC0,0x75,0xEF,0x8B,
+0x4E,0x1C,0xEC,0x88,0x86,0xA4,0x00,0x83,0xEA,0x0A,0xA8,0x20,0x75,0x02,0x8A,0xCD,
+0x32,0xED,0x8B,0x46,0x1A,0x3B,0xC8,0x73,0x18,0x01,0x4E,0x2A,0x2B,0xC1,0x89,0x46,
+0x1A,0xC5,0x76,0x00,0xF3,0x6E,0x8E,0xD9,0x89,0x76,0x00,0x3D,0x20,0x00,0x72,0x30,
+0xC3,0x85,0xC0,0x74,0x31,0x8B,0xC8,0x01,0x46,0x2A,0xC5,0x76,0x00,0xF3,0x6E,0x8E,
+0xD9,0x80,0xCB,0x02,0x89,0x5E,0x26,0xE8,0x32,0xF1,0xF6,0xC7,0x01,0x75,0x16,0x83,
+0xC2,0x02,0xE8,0x53,0xFD,0xF6,0xC7,0x10,0x75,0x0B,0xB0,0x02,0xE8,0x43,0xFA,0xC3,
+0xF6,0xC7,0x01,0x74,0xF0,0xC3,0x80,0xCB,0x02,0x89,0x5E,0x26,0xF6,0xC7,0x01,0x74,
+0xDE,0x83,0xC2,0x02,0xE8,0x31,0xFD,0xF6,0x86,0xA4,0x00,0x40,0x74,0x0B,0x80,0xE7,
+0xFE,0x80,0xCF,0x02,0x89,0x5E,0x26,0xEB,0xCC,0xB0,0x04,0xE8,0x14,0xFA,0xC3,0xC0,
+0xC2,0xC8,0xCA,0xC4,0xC6,0xCC,0xCE,0xD0,0xD2,0xD8,0xDA,0xD4,0xD6,0xDC,0xDE,0x90,
+0xE9,0x0E,0x01,0xE4,0xC4,0x8A,0xE0,0xE4,0xC4,0x8B,0xD0,0x83,0xF9,0x08,0x72,0xF0,
+0x26,0x83,0x3F,0x00,0x74,0x04,0x8B,0xDF,0x49,0x49,0x8B,0xFB,0x8A,0xDE,0x83,0xE3,
+0x0F,0x2E,0x8A,0xA7,0x2F,0x16,0xAB,0xF6,0xC4,0x10,0x74,0x24,0xF7,0xC6,0x00,0x10,
+0x74,0x0B,0x50,0xFE,0x86,0xB2,0x00,0xB0,0x0A,0xE8,0xC6,0xF9,0x58,0xF7,0xC6,0x00,
+0x01,0x74,0x0D,0xE8,0xA0,0x24,0x8B,0x76,0x38,0x8B,0x4E,0x2E,0x8B,0x7E,0x04,0xAB,
+0x89,0x7E,0x04,0x33,0xC0,0xAB,0x49,0x49,0x89,0x4E,0x2E,0x89,0x7E,0x2C,0x8B,0xC1,
+0xEB,0x4E,0x90,0xEB,0x9E,0x90,0xE4,0xD6,0x84,0xC0,0x79,0x63,0xE6,0xD0,0x8A,0xC8,
+0x25,0x03,0x00,0x03,0xD8,0xD1,0xE3,0x2E,0x8B,0xAF,0x44,0x00,0x88,0x8E,0xAE,0x00,
+0x8B,0x4E,0x2E,0xC4,0x5E,0x04,0x8B,0x7E,0x2C,0x8B,0x76,0x38,0xE4,0x86,0x24,0x07,
+0x3C,0x03,0x75,0xCF,0xE4,0x1C,0x91,0x3B,0xC1,0x73,0x02,0x8B,0xC8,0x2B,0xC1,0x89,
+0x46,0x2E,0x01,0x4E,0x34,0x26,0x01,0x0F,0xBA,0xC4,0x00,0xF3,0x6C,0x89,0x7E,0x2C,
+0x3B,0x46,0x3C,0x72,0x1C,0xF7,0xC6,0x20,0x00,0x75,0x0B,0x83,0xCE,0x20,0x89,0x76,
+0x38,0xB0,0x00,0xE8,0x3C,0xF9,0x8A,0x86,0xAE,0x00,0x24,0x3F,0xE6,0xD6,0xC3,0xF9,
+0xC3,0xF7,0xC6,0x0A,0x00,0x74,0x35,0xF7,0xC6,0x10,0x00,0x75,0x2F,0x83,0xCE,0x10,
+0x89,0x76,0x38,0xF7,0xC6,0x02,0x00,0x74,0x0E,0x50,0xE4,0xD8,0x24,0xFE,0xE6,0xD8,
+0x58,0xF7,0xC6,0x08,0x00,0x74,0x15,0x50,0x51,0xB9,0xE8,0x03,0xE4,0x0A,0x84,0xC0,
+0xE0,0xFA,0x84,0xC0,0x75,0x04,0xB0,0x24,0xE6,0x0A,0x59,0x58,0x3D,0x40,0x00,0x73,
+0xB5,0x8A,0x86,0xA5,0x00,0x24,0xEF,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x81,0xCE,0x10,
+0x04,0x89,0x76,0x38,0xEB,0xA0,0x00,0x08,0x04,0x0C,0x01,0x09,0x05,0x0D,0x02,0x0A,
+0x06,0x0E,0x03,0x0B,0x07,0x0F,0x00,0x40,0x80,0xC0,0x20,0x60,0xA0,0xE0,0x10,0x50,
+0x90,0xD0,0x30,0x70,0xB0,0xF0,0xE4,0xD2,0xE6,0xD0,0x8A,0xC8,0x25,0x03,0x00,0x03,
+0xD8,0xD1,0xE3,0x2E,0x8B,0xAF,0x44,0x00,0x88,0x8E,0xAE,0x00,0xE4,0xD8,0xC0,0xE8,
+0x04,0x8B,0xD8,0x2E,0x8A,0x87,0x66,0x17,0x8A,0xE0,0x8A,0xC8,0x86,0x86,0xA9,0x00,
+0x32,0xE0,0xE4,0x98,0x8B,0x5E,0x3E,0x84,0xE3,0x74,0x54,0x8A,0xC1,0x8B,0x4E,0x26,
+0xF6,0xC5,0x04,0x74,0x0C,0xA8,0x08,0x74,0x05,0x80,0xE1,0xBF,0xEB,0x03,0x80,0xC9,
+0x40,0xF6,0xC5,0x08,0x74,0x0C,0xA8,0x02,0x74,0x05,0x80,0xE1,0x7F,0xEB,0x03,0x80,
+0xC9,0x80,0x88,0x4E,0x26,0x8B,0xF0,0x8A,0x86,0xA5,0x00,0xF6,0xC1,0xFD,0x74,0x08,
+0xA8,0x06,0x74,0x19,0x24,0xF9,0xEB,0x0F,0xA8,0x06,0x75,0x11,0xF6,0xC5,0x01,0x75,
+0x04,0x0C,0x04,0xEB,0x02,0x0C,0x02,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x8B,0xC6,0x84,
+0xE7,0x75,0x09,0x8A,0x86,0xAE,0x00,0x24,0x3F,0xE6,0xD2,0xC3,0xC6,0x86,0xBA,0x00,
+0x01,0xB0,0x0E,0xE8,0x1C,0xF8,0xF7,0x46,0x38,0x00,0x02,0x74,0xE6,0x83,0x7E,0x2E,
+0x06,0x72,0xE0,0x8A,0x86,0xA9,0x00,0x8A,0xE0,0x86,0x86,0xAA,0x00,0x8A,0xC8,0x32,
+0xC4,0x80,0xC9,0x0B,0x22,0xC1,0xC0,0xE4,0x04,0x0A,0xE0,0xC4,0x5E,0x04,0x8B,0x7E,
+0x2C,0xB0,0xFF,0xAA,0xB0,0x02,0xAB,0x26,0x83,0x07,0x03,0x83,0x6E,0x2E,0x03,0x89,
+0x7E,0x2C,0xF6,0x46,0x38,0x20,0x75,0xAB,0x83,0x4E,0x38,0x20,0xB0,0x00,0xE8,0xD1,
+0xF7,0xEB,0xA0,0x90,0xE4,0x12,0x24,0xDF,0xE6,0x12,0x81,0xE3,0xFE,0x9F,0x89,0x5E,
+0x26,0x83,0x66,0x48,0xF7,0xEB,0x73,0x90,0xF6,0xC7,0x20,0x75,0xE7,0xE4,0x12,0x0C,
+0x20,0xE6,0x12,0x32,0xC0,0xE6,0xC6,0xB0,0x83,0xE6,0xC6,0x80,0xCF,0x20,0x89,0x5E,
+0x26,0x8A,0x86,0xA5,0x00,0x0C,0x02,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xEB,0x74,0x90,
+0xF6,0xC7,0x40,0x75,0xD3,0xE4,0x12,0x0C,0x20,0xE6,0x12,0x32,0xC0,0xE6,0xC6,0xB0,
+0x81,0xE6,0xC6,0x80,0xE7,0xDF,0x80,0xCB,0x01,0x89,0x5E,0x26,0xB0,0x06,0xE8,0x71,
+0xF7,0x90,0x8A,0x86,0xA5,0x00,0x24,0xF9,0xE6,0x0C,0x88,0x86,0xA5,0x00,0xEB,0x43,
+0xE4,0xD4,0xE6,0xD0,0x8B,0xF8,0x25,0x03,0x00,0x03,0xD8,0xD1,0xE3,0x2E,0x8B,0xAF,
+0x44,0x00,0x8B,0x5E,0x26,0xF6,0xC7,0x60,0x75,0xB6,0xF6,0xC3,0xC0,0x75,0xD3,0xBA,
+0xC6,0x00,0x8B,0x4E,0x1C,0x8B,0x46,0x1A,0x3B,0xC8,0x73,0x1E,0x01,0x4E,0x2A,0x2B,
+0xC1,0x89,0x46,0x1A,0xC5,0x76,0x00,0xF3,0x6E,0x8E,0xD9,0x89,0x76,0x00,0x3D,0x20,
+0x00,0x72,0x3D,0x8B,0xC7,0x24,0x3F,0xE6,0xD4,0xC3,0x85,0xC0,0x74,0x39,0x8B,0xC8,
+0x01,0x46,0x2A,0xC5,0x76,0x00,0xF3,0x6E,0x8E,0xD9,0x83,0xCB,0x02,0x89,0x5E,0x26,
+0xE8,0xD9,0xED,0xF6,0xC7,0x01,0x75,0x39,0x8A,0x86,0xA5,0x00,0x24,0xF9,0xE6,0x0C,
+0x88,0x86,0xA5,0x00,0xF6,0xC7,0x10,0x75,0xCA,0xB0,0x02,0xE8,0xE4,0xF6,0xEB,0xC3,
+0xF6,0xC7,0x01,0x74,0xEF,0xEB,0xBC,0xF6,0xC7,0x01,0x74,0xDC,0x8A,0x86,0xA5,0x00,
+0xA8,0x02,0x74,0x11,0x81,0xE3,0xFF,0xFE,0x81,0xCB,0x00,0x02,0x89,0x5E,0x26,0xEB,
+0xC7,0x8A,0x86,0xA5,0x00,0x24,0xFB,0x0C,0x02,0xE6,0x0C,0x88,0x86,0xA5,0x00,0xEB,
+0x92,0x90,0xFD,0xF7,0xDF,0x7F,0xFE,0xFB,0xEF,0xBF,0x00,0x04,0x00,0x04,0x05,0x04,
+0x05,0x04,0x01,0x04,0x00,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x02,0x04,0x00,0x04,0x05,0x04,
+0x05,0x04,0x01,0x04,0x00,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,
+0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,
+0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x03,0x04,0x00,0x04,0x05,0x04,
+0x05,0x04,0x01,0x04,0x00,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x02,0x04,0x00,0x04,0x05,0x04,
+0x05,0x04,0x01,0x04,0x00,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,
+0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,
+0x05,0x04,0x07,0x04,0x07,0x04,0x05,0x04,0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,
+0x05,0x04,0x06,0x04,0x06,0x04,0x05,0x04,0x05,0x04,0x33,0xDB,0x8A,0xD8,0x8A,0x87,
+0x6C,0x12,0xE6,0xFE,0xC1,0xE3,0x02,0xE4,0xCE,0xA8,0x04,0x75,0x09,0xA8,0x02,0x74,
+0x03,0xE9,0x2C,0xFE,0xF9,0xC3,0x50,0x53,0xE8,0xCB,0xFC,0x5B,0x58,0xA8,0x02,0x74,
+0x03,0xE9,0x1C,0xFE,0xF8,0xC3,0x33,0xDB,0x8A,0xD8,0x8A,0x87,0x6C,0x12,0xE6,0xFE,
+0xC1,0xE3,0x02,0xE9,0xD0,0xFB,0x9A,0x1A,0xC6,0x1A,0x00,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0A,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0C,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0A,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0E,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0A,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0C,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x0A,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0x08,0x00,0x02,0x00,0x04,0x00,
+0x02,0x00,0x06,0x00,0x02,0x00,0x04,0x00,0x02,0x00,0xC3,0x90,0xDA,0x14,0x94,0x15,
+0x5C,0x13,0xE6,0x13,0xDA,0x1B,0xDA,0x1B,0xE6,0x13,0xDA,0x1B,0x8B,0x94,0x64,0x12,
+0xC1,0xE6,0x04,0xA8,0x01,0x74,0x35,0x50,0x33,0xC0,0x8A,0xC2,0xE6,0xFE,0xE4,0xA0,
+0x85,0xC0,0x74,0x27,0x8B,0xD8,0x2E,0x8A,0x9F,0xDA,0x1A,0x52,0x56,0x2E,0x8B,0xA8,
+0x44,0x00,0x8B,0x56,0x28,0xEC,0xA8,0x01,0x75,0x0D,0x88,0x86,0xAD,0x00,0x24,0x0E,
+0x8A,0xD8,0x2E,0xFF,0x97,0xDC,0x1B,0x5E,0x5A,0xEB,0xCD,0x58,0xA8,0x02,0x74,0x36,
+0x83,0xC6,0x10,0x33,0xC0,0x8A,0xC6,0xE6,0xFE,0xE4,0xA0,0x85,0xC0,0x74,0x27,0x8B,
+0xD8,0x2E,0x8A,0x9F,0xDA,0x1A,0x52,0x56,0x2E,0x8B,0xA8,0x44,0x00,0x8B,0x56,0x28,
+0xEC,0xA8,0x01,0x75,0x0D,0x88,0x86,0xAD,0x00,0x24,0x0E,0x8A,0xD8,0x2E,0xFF,0x97,
+0xDC,0x1B,0x5E,0x5A,0xEB,0xCD,0xC3,0x90,0x32,0xE4,0x8B,0xD8,0x8B,0xD0,0x2E,0x8A,
+0x9F,0x9A,0x19,0x2E,0x22,0x97,0x92,0x19,0x56,0x52,0x8A,0xC3,0x24,0x03,0x03,0xC6,
+0x80,0xE3,0x04,0xD0,0xEB,0x2E,0xFF,0x97,0xD6,0x1A,0x58,0x5E,0xA9,0x55,0x00,0x75,
+0xD9,0xC3,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x00,
+0x22,0xC4,0x74,0x08,0x33,0xF6,0xE8,0xBF,0xFF,0xEB,0xEE,0x90,0xE4,0x04,0x07,0xE4,
+0x04,0x1F,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,
+0xC0,0x8E,0xD8,0xA1,0x5E,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0x08,0xBE,0x04,
+0x00,0xE8,0x94,0xFF,0xEB,0xED,0xE4,0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,0x80,0xBA,
+0x22,0xFF,0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5C,0x12,
+0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0x18,0x33,0xF6,0xE8,0x6B,0xFF,0xA1,0x60,0x12,
+0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0xE5,0xBE,0x08,0x00,0xE8,0x5A,0xFF,0xEB,0xDD,
+0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x75,0xED,0xE4,0x04,0x07,0xE4,0x04,
+0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x04,0x1F,0xE4,0x04,0xB8,0x00,0x80,0xBA,0x22,0xFF,
+0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5E,0x12,0xE6,0xFE,
+0xE4,0x00,0x22,0xC4,0x74,0x19,0xBE,0x04,0x00,0xE8,0x1C,0xFF,0xA1,0x62,0x12,0xE6,
+0xFE,0xE4,0x00,0x22,0xC4,0x74,0xE4,0xBE,0x0C,0x00,0xE8,0x0B,0xFF,0xEB,0xDC,0xA1,
+0x62,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x75,0xED,0xE4,0x04,0x07,0xE4,0x04,0xA1,
+0x5E,0x12,0xE6,0xFE,0xE4,0x04,0x1F,0xE4,0x04,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,
+0x61,0xCF,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x80,
+0x84,0xC4,0x74,0x08,0x33,0xF6,0xE8,0x53,0xFE,0xEB,0xEE,0x90,0xB8,0x00,0x80,0xBA,
+0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,
+0x5E,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x08,0xBE,0x02,0x00,0xE8,0x2C,0xFE,
+0xEB,0xED,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0x60,0x1E,
+0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x08,
+0xBE,0x04,0x00,0xE8,0x06,0xFE,0xEB,0xED,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,
+0x1F,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x62,0x12,0xE6,0xFE,
+0xE4,0x80,0x84,0xC4,0x74,0x08,0xBE,0x06,0x00,0xE8,0xE0,0xFD,0xEB,0xED,0xB8,0x00,
+0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,
+0xD8,0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0x18,0x33,0xF6,0xE8,0x37,
+0xFE,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0xE5,0xBE,0x04,0x00,0xE8,
+0xAA,0xFD,0xEB,0xDD,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x75,0xED,0xA1,
+0x5C,0x12,0xE6,0xFE,0xE4,0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,0x80,0xBA,0x22,0xFF,
+0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5E,0x12,0xE6,0xFE,
+0xE4,0x00,0x22,0xC4,0x74,0x19,0xBE,0x04,0x00,0xE8,0xEC,0xFD,0xA1,0x62,0x12,0xE6,
+0xFE,0xE4,0x80,0x84,0xC4,0x74,0xE4,0xBE,0x06,0x00,0xE8,0x5F,0xFD,0xEB,0xDC,0xA1,
+0x62,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x75,0xED,0xA1,0x5E,0x12,0xE6,0xFE,0xE4,
+0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x60,0x1E,
+0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5C,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x18,
+0x33,0xF6,0xE8,0x27,0xFD,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x74,0xE5,
+0xBE,0x08,0x00,0xE8,0x92,0xFD,0xEB,0xDD,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x00,0x22,
+0xC4,0x75,0xED,0xE4,0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,
+0x61,0xCF,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,0x5E,0x12,0xE6,0xFE,0xE4,0x80,
+0x84,0xC4,0x74,0x19,0xBE,0x02,0x00,0xE8,0xE2,0xFC,0xA1,0x62,0x12,0xE6,0xFE,0xE4,
+0x00,0x22,0xC4,0x74,0xE4,0xBE,0x0C,0x00,0xE8,0x4D,0xFD,0xEB,0xDC,0xA1,0x62,0x12,
+0xE6,0xFE,0xE4,0x00,0x22,0xC4,0x75,0xED,0xE4,0x04,0x07,0xE4,0x04,0x1F,0xB8,0x00,
+0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,
+0x5C,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x18,0x33,0xF6,0xE8,0x9D,0xFC,0xA1,
+0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0xE5,0xBE,0x04,0x00,0xE8,0x8C,0xFC,
+0xEB,0xDD,0xA1,0x60,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x75,0xED,0x07,0x1F,0xB8,
+0x00,0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x60,0x1E,0x06,0x2B,0xC0,0x8E,0xD8,0xA1,
+0x5E,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0x19,0xBE,0x02,0x00,0xE8,0x5C,0xFC,
+0xA1,0x62,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x74,0xE4,0xBE,0x06,0x00,0xE8,0x4B,
+0xFC,0xEB,0xDC,0xA1,0x62,0x12,0xE6,0xFE,0xE4,0x80,0x84,0xC4,0x75,0xED,0x07,0x1F,
+0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x61,0xCF,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,
+0xD8,0x90,0x2A,0xC0,0xE6,0xFE,0xE4,0xCE,0xA8,0x01,0x74,0x14,0x33,0xDB,0xE8,0xD5,
+0xF6,0xEB,0xEF,0x90,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,
+0xF6,0x06,0x05,0x01,0x01,0x75,0xED,0xB0,0x01,0xE6,0xFE,0xE4,0xCE,0xA8,0x01,0x74,
+0xE3,0xBB,0x04,0x00,0xE8,0xAF,0xF6,0xEB,0xC9,0x90,0x60,0x1E,0x06,0x2B,0xC0,0x8E,
+0xD8,0x90,0xFB,0x90,0xFA,0x2A,0xC0,0xE6,0xFE,0xE4,0xCE,0xA8,0x02,0x74,0x13,0x33,
+0xDB,0xE8,0xCC,0xF8,0xEB,0xEC,0xB8,0x00,0x80,0xBA,0x22,0xFF,0xEF,0x07,0x1F,0x61,
+0xCF,0x90,0xA8,0x04,0x74,0xF0,0x33,0xDB,0xE8,0x5B,0xF7,0xEB,0xD5,0x90,0x60,0x1E,
+0x06,0x2B,0xC0,0x8E,0xD8,0x90,0xFB,0x90,0xFA,0xB0,0x01,0xE6,0xFE,0xE4,0xCE,0xA8,
+0x02,0x74,0x15,0xBB,0x04,0x00,0xE8,0x97,0xF8,0xEB,0xEB,0x90,0xB8,0x00,0x80,0xBA,
+0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0xA8,0x04,0x74,0xF0,0xBB,0x04,0x00,0xE8,
+0x24,0xF7,0xEB,0xD2,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x09,0x9C,0x0E,0xE8,0x6B,
+0xF2,0x90,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x29,0x9C,0x0E,0xE8,0x5D,0xF2,0x90,
+0x72,0x20,0x72,0x20,0x72,0x20,0xCE,0x1D,0x92,0x1C,0xE6,0x1C,0x1A,0x1E,0x72,0x20,
+0x82,0x1D,0xAE,0x1E,0x38,0x1F,0x72,0x20,0x82,0x1D,0x72,0x20,0x72,0x20,0x38,0x1F,
+0x72,0x20,0x72,0x20,0x72,0x20,0xF4,0x1D,0xBC,0x1C,0x34,0x1D,0x64,0x1E,0x72,0x20,
+0xA8,0x1D,0xF2,0x1E,0x78,0x1F,0x72,0x20,0xA8,0x1D,0x72,0x20,0x72,0x20,0x78,0x1F,
+0xFC,0xB9,0x40,0x00,0x8C,0xCB,0xB8,0x64,0x20,0x2B,0xFF,0xAB,0x93,0xAB,0x93,0xE2,
+0xFA,0xC7,0x06,0x4C,0x00,0xA8,0x11,0x83,0x3E,0x44,0x12,0x00,0x75,0x20,0xC7,0x06,
+0x3C,0x00,0x08,0x4B,0xC7,0x06,0x30,0x00,0xBA,0x1F,0xC7,0x06,0x34,0x00,0xFA,0x1F,
+0xF6,0x06,0x05,0x01,0x01,0x75,0x06,0xC7,0x06,0x38,0x00,0x2E,0x20,0xC3,0xC7,0x06,
+0x3C,0x00,0x56,0x4B,0x33,0xDB,0x8A,0x1E,0x54,0x12,0xC1,0xE3,0x02,0x02,0x1E,0x56,
+0x12,0x2E,0x8B,0x87,0x80,0x20,0xA3,0x30,0x00,0x8A,0x1E,0x55,0x12,0xC1,0xE3,0x02,
+0x02,0x1E,0x57,0x12,0x2E,0x8B,0x87,0xA0,0x20,0xA3,0x34,0x00,0xC3,0x8B,0x86,0x9E,
+0x00,0xE6,0xFE,0x86,0xC4,0xE6,0xD0,0xC3,0x8B,0x86,0x9E,0x00,0xE6,0xFE,0x33,0xD2,
+0x8A,0xD4,0xC3,0x51,0xB9,0x10,0x27,0xE4,0x0A,0x90,0x90,0x84,0xC0,0x74,0x05,0xE2,
+0xF6,0x59,0xF9,0xC3,0x59,0xF8,0xC3,0x84,0xC0,0x78,0x1E,0x51,0x8A,0xE8,0x8A,0xC8,
+0xB8,0x01,0x00,0xD3,0xE0,0x09,0x86,0x98,0x00,0x3A,0xAE,0xA0,0x00,0x59,0x75,0x10,
+0xE8,0xA9,0xE5,0x83,0x4E,0x26,0x02,0xF9,0xC3,0x98,0x89,0x86,0x98,0x00,0xEB,0xF0,
+0xF8,0xC3,0x84,0xC0,0x78,0x12,0x51,0x8A,0xE0,0x8A,0xC8,0xB8,0x01,0x00,0xD3,0xE0,
+0x59,0xF7,0xD0,0x21,0x86,0x98,0x00,0xC3,0xC7,0x86,0x98,0x00,0x00,0x00,0xC3,0x83,
+0xC2,0x04,0x8A,0x86,0xA6,0x00,0x0C,0x04,0xEE,0x83,0xEA,0x04,0xC3,0xE8,0x93,0xFF,
+0x72,0x04,0xB0,0x82,0xE6,0x0A,0xC3,0x8B,0x46,0x26,0xA8,0xFD,0x74,0x11,0x8A,0x86,
+0xA5,0x00,0xA8,0x06,0x74,0x08,0x24,0xF9,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,0xF6,
+0xC4,0x01,0x74,0x0A,0x8A,0x86,0xA5,0x00,0x24,0xFB,0x0C,0x02,0xEB,0x0C,0xA8,0x02,
+0x75,0x0F,0x8A,0x86,0xA5,0x00,0x24,0xFD,0x0C,0x04,0x3A,0x86,0xA5,0x00,0x75,0xD8,
+0xC3,0x8A,0x86,0xA5,0x00,0xEB,0xCF,0xE4,0xD8,0x33,0xDB,0x8A,0xD8,0xC0,0xEB,0x04,
+0x2E,0x8A,0x9F,0x66,0x17,0x88,0x9E,0xA9,0x00,0x8B,0x5E,0x26,0x80,0xE3,0x3F,0xF6,
+0xC7,0x04,0x74,0x07,0xA8,0x10,0x75,0x03,0x80,0xCB,0x40,0xF6,0xC7,0x08,0x74,0x07,
+0xA8,0x80,0x75,0x03,0x80,0xCB,0x40,0x88,0x5E,0x26,0x8A,0x86,0xA5,0x00,0xF6,0xC3,
+0xFD,0x74,0x0D,0xA8,0x06,0x74,0x08,0x24,0xF9,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,
+0xF6,0xC7,0x01,0x74,0x04,0x0C,0x02,0xEB,0xF0,0xF6,0xC3,0x02,0x75,0xE9,0x0C,0x04,
+0xEB,0xE7,0xC4,0x04,0xC4,0x04,0x85,0x04,0x59,0x04,0x48,0x04,0x41,0x04,0xC3,0x03,
+0x82,0x03,0x41,0x03,0x82,0x02,0x57,0x02,0x41,0x02,0x82,0x01,0x41,0x01,0x82,0x00,
+0x41,0x00,0x4E,0x02,0xAD,0x01,0x57,0x01,0x2D,0x00,0x2B,0x00,0x27,0x00,0x21,0x00,
+0x16,0x00,0xF4,0x04,0xF4,0x04,0xA3,0x04,0x6F,0x04,0x5B,0x04,0x51,0x04,0xF4,0x03,
+0xA3,0x03,0x51,0x03,0xA3,0x02,0x6D,0x02,0x51,0x02,0xA3,0x01,0x51,0x01,0xA3,0x00,
+0x51,0x00,0x62,0x02,0xD9,0x01,0x6D,0x01,0x38,0x00,0x36,0x00,0x31,0x00,0x29,0x00,
+0x1B,0x00,0x51,0x57,0xBF,0x02,0x00,0xEB,0x0F,0x90,0x51,0x56,0xBF,0x01,0x00,0xEB,
+0x07,0x90,0x51,0x56,0xBF,0x03,0x00,0x90,0x3C,0x19,0x76,0x02,0xB0,0x17,0x98,0x8B,
+0xF0,0x8A,0x82,0xC4,0x00,0x2A,0xE4,0x8B,0xF0,0x83,0xFE,0x18,0x73,0x46,0xD1,0xE6,
+0x2E,0x8B,0x8C,0x52,0x22,0xF7,0x46,0x38,0x80,0x00,0x74,0x05,0x2E,0x8B,0x8C,0x82,
+0x22,0xF7,0xC7,0x02,0x00,0x74,0x12,0x3B,0x8E,0x94,0x00,0x74,0x0C,0x89,0x8E,0x94,
+0x00,0x8A,0xC5,0xE6,0xEC,0x8A,0xC1,0xE6,0xE4,0xF7,0xC7,0x01,0x00,0x74,0x12,0x3B,
+0x8E,0x96,0x00,0x74,0x0C,0x89,0x8E,0x96,0x00,0x8A,0xC5,0xE6,0xF8,0x8A,0xC1,0xE6,
+0xF0,0x5E,0x59,0xC3,0x77,0x06,0x8B,0x8E,0x8E,0x00,0xEB,0xC5,0x8B,0x8E,0x90,0x00,
+0xEB,0xBF,0xD5,0x03,0xF6,0x00,0x3E,0x00,0x10,0x00,0x04,0x00,0xCA,0x04,0x33,0x01,
+0x4D,0x00,0x14,0x00,0x05,0x00,0x01,0x03,0x05,0x07,0x09,0x00,0x01,0x02,0x03,0x04,
+0x80,0x84,0x1E,0x00,0xA0,0x25,0x26,0x00,0x00,0x00,0x60,0x8B,0xF0,0x33,0xFF,0x2E,
+0xA1,0x50,0x23,0x2E,0x8B,0x16,0x52,0x23,0xBB,0x32,0x23,0xF7,0x46,0x38,0x80,0x00,
+0x74,0x0C,0x2E,0xA1,0x54,0x23,0x2E,0x8B,0x16,0x56,0x23,0xBB,0x3C,0x23,0xB9,0x05,
+0x00,0x2E,0x3B,0x31,0x73,0x0A,0x47,0x47,0xE2,0xF7,0xB8,0xFF,0xFF,0xEB,0x1D,0x90,
+0xD1,0xEF,0x2E,0x8A,0x8D,0x46,0x23,0x2A,0xED,0xD1,0xEA,0xD1,0xD8,0xE2,0xFA,0xF7,
+0xF6,0x05,0x02,0x00,0xC1,0xE8,0x02,0x2E,0x8A,0xA5,0x4B,0x23,0x2E,0xA3,0x58,0x23,
+0x61,0x2E,0xA1,0x58,0x23,0xC3,0x08,0x00,0x20,0x00,0x80,0x00,0x00,0x02,0x60,0x09,
+0x08,0x00,0x20,0x00,0x80,0x00,0x00,0x02,0x00,0x08,0x00,0x00,0x01,0x00,0x02,0x00,
+0x03,0x00,0x04,0x00,0x52,0x56,0x57,0x85,0xC0,0x74,0x05,0x3D,0x01,0x09,0x76,0x03,
+0xB8,0x01,0x09,0xBF,0x5B,0x01,0xF7,0x46,0x38,0x80,0x00,0x74,0x03,0xBF,0xB2,0x01,
+0x33,0xF6,0x2E,0x3B,0x84,0xB6,0x23,0x76,0x04,0x46,0x46,0xEB,0xF5,0xF7,0xE7,0x2E,
+0x8B,0xBC,0xC0,0x23,0x03,0xC7,0x83,0xD2,0x00,0xD1,0xE7,0xF7,0xF7,0x2E,0x8A,0xA4,
+0xCA,0x23,0x5F,0x5E,0x5A,0xC3,0xE4,0x3E,0x80,0xBE,0xC3,0x00,0x03,0x75,0x0C,0xF7,
+0x46,0x7A,0x20,0x00,0x74,0x05,0x0C,0x80,0xE6,0x3E,0xC3,0x24,0x7F,0xE6,0x3E,0xC3,
+0x24,0x03,0x88,0x86,0xC3,0x00,0x8A,0xE0,0xE4,0x10,0x24,0xFC,0x0A,0xC4,0xE6,0x10,
+0x80,0x8E,0xA1,0x00,0x42,0xE8,0xCE,0xFF,0xC3,0x90,0x56,0x8B,0xF0,0x83,0xE6,0x07,
+0xD1,0xE6,0x2E,0xFF,0xA4,0x58,0x24,0x90,0x68,0x24,0x6C,0x24,0x70,0x24,0x74,0x24,
+0x78,0x24,0x87,0x24,0x87,0x24,0x87,0x24,0xB4,0x00,0xEB,0x0E,0xB4,0xC0,0xEB,0x0A,
+0xB4,0x40,0xEB,0x06,0xB4,0x20,0xEB,0x02,0xB4,0xA0,0xE4,0x10,0x24,0x1F,0x0A,0xC4,
+0xE6,0x10,0x80,0x8E,0xA1,0x00,0x42,0x5E,0xC3,0x90,0x3C,0x02,0x77,0x12,0x8A,0xE0,
+0xE4,0x10,0x24,0xF3,0xC0,0xE4,0x02,0x0A,0xC4,0xE6,0x10,0x80,0x8E,0xA1,0x00,0x42,
+0xC3,0x90,0x8B,0x5E,0x38,0x84,0xC0,0x74,0x1F,0x3C,0x02,0x74,0x20,0x83,0xCB,0x08,
+0x8B,0x46,0x2E,0x3B,0x46,0x3C,0x77,0x0C,0xE8,0x88,0xFC,0x72,0x07,0xB0,0x24,0xE6,
+0x0A,0x83,0xCB,0x10,0x89,0x5E,0x38,0xC3,0x83,0xE3,0xF7,0xEB,0xF7,0xF7,0xC3,0x10,
+0x00,0x74,0xF5,0xE8,0x6D,0xFC,0x72,0xEC,0x8A,0x86,0xC0,0x00,0xE6,0x38,0xB0,0x23,
+0xE6,0x0A,0xEB,0xE0,0x8B,0x5E,0x38,0x8B,0x46,0x2E,0x3B,0x46,0x3C,0xE4,0xD8,0x77,
+0x0B,0x24,0xFE,0x80,0xCB,0x12,0xE6,0xD8,0x89,0x5E,0x38,0xC3,0x0C,0x01,0x80,0xCB,
+0x02,0xEB,0xF3,0x50,0x33,0xDB,0xC1,0xE8,0x04,0x25,0x0F,0x0F,0x8A,0xD8,0x2E,0x8A,
+0x87,0x66,0x17,0x8A,0xDC,0x2E,0x8A,0xA7,0x66,0x17,0x09,0x46,0x3E,0x58,0xC3,0x50,
+0x33,0xDB,0xC1,0xE8,0x04,0x25,0x0F,0x0F,0x8A,0xD8,0x2E,0x8A,0x87,0x66,0x17,0x8A,
+0xDC,0x2E,0x8A,0xA7,0x66,0x17,0xF7,0xD0,0x21,0x46,0x3E,0x58,0xC3,0x8B,0x46,0x3E,
+0x33,0xDB,0x8A,0xD8,0x0A,0xDC,0x2E,0x8A,0x87,0x76,0x17,0xE6,0x2C,0x8A,0xE0,0xE4,
+0x2A,0x24,0x0F,0x0A,0xC4,0xE6,0x2A,0x8A,0x86,0xA5,0x00,0x84,0xE4,0x75,0x0D,0xA8,
+0x80,0x74,0x11,0x24,0x7F,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,0xA8,0x80,0x75,0x04,
+0x0C,0x80,0xEB,0xF1,0xC3,0x1E,0x60,0x33,0xC9,0x33,0xD2,0x33,0xF6,0x8E,0xD9,0x8D,
+0xBE,0xFD,0x00,0x57,0x8B,0x05,0x84,0xC0,0x74,0x16,0x8B,0xD1,0x42,0x8B,0xFE,0x4F,
+0x78,0x09,0x38,0xA3,0xE4,0x00,0x74,0x08,0x4F,0x79,0xF7,0x88,0xA2,0xE4,0x00,0x46,
+0x5F,0x83,0xC7,0x09,0x41,0x83,0xF9,0x10,0x72,0xD9,0x89,0xB6,0x86,0x00,0x89,0x96,
+0x84,0x00,0x61,0x1F,0xC3,0x53,0xC7,0x46,0x66,0x00,0x00,0x8B,0x46,0x64,0xA9,0x40,
+0x00,0x74,0x0D,0xB3,0x00,0xA9,0x80,0x00,0x74,0x02,0xB3,0x7F,0x88,0x9E,0xC1,0x00,
+0x32,0xDB,0xA9,0x02,0x00,0x74,0x03,0x80,0xCB,0x40,0xA9,0x00,0x40,0x74,0x03,0x80,
+0xCB,0x02,0xA9,0x00,0x80,0x74,0x03,0x80,0xCB,0x01,0xA9,0x30,0x1E,0x74,0x03,0x80,
+0xCB,0xBC,0xA9,0x00,0x20,0x74,0x03,0x80,0xCB,0x08,0xA9,0x04,0x01,0x74,0x03,0x80,
+0xCB,0x10,0xA9,0x08,0x00,0x74,0x03,0x80,0xCB,0x20,0x88,0x9E,0xC2,0x00,0x5B,0xC3,
+0x06,0x51,0x57,0x50,0x16,0x07,0x8D,0xBE,0xC4,0x00,0xB9,0x1F,0x00,0x33,0xC0,0xAA,
+0x40,0xE2,0xFC,0x8B,0x86,0x92,0x00,0x89,0x86,0x8E,0x00,0x89,0x86,0x90,0x00,0x58,
+0x5F,0x59,0x07,0xC3,0xE4,0xD8,0xC0,0xE8,0x04,0x53,0x25,0x0F,0x00,0x8B,0xD8,0x2E,
+0x8A,0x87,0x66,0x17,0x88,0x86,0xA9,0x00,0x5A,0xC3,0x08,0x86,0xAC,0x00,0xC6,0x86,
+0xBA,0x00,0x01,0xB0,0x0E,0xE8,0xEA,0xE9,0xC3,0xAD,0x36,0xA3,0xB4,0x13,0xAD,0x36,
+0xA3,0xB6,0x13,0xAD,0x36,0xA3,0xB8,0x13,0x83,0xE9,0x06,0x36,0xF7,0x06,0xB6,0x13,
+0x0F,0x00,0xC3,0x8A,0x46,0x26,0xF7,0x46,0x48,0x80,0x00,0x74,0x02,0x0C,0x10,0x88,
+0x86,0xBD,0x00,0x32,0xC0,0x83,0x7E,0x1A,0x00,0x75,0x0E,0x8B,0x5E,0x40,0x43,0x80,
+0xE3,0xFE,0x3B,0x5E,0x08,0x75,0x02,0x0C,0x01,0x83,0x7E,0x3A,0x00,0x75,0x0D,0x1E,
+0xC5,0x5E,0x14,0x8B,0x1F,0x1F,0x85,0xDB,0x75,0x02,0x0C,0x02,0xF7,0x46,0x38,0x10,
+0x00,0x74,0x02,0x0C,0x04,0x8B,0x5E,0x7A,0xF7,0xC3,0x02,0x00,0x74,0x02,0x0C,0x08,
+0xF7,0xC3,0x04,0x00,0x74,0x02,0x0C,0x10,0xF7,0xC3,0x08,0x00,0x74,0x02,0x0C,0x20,
+0xF7,0xC3,0x40,0x00,0x74,0x02,0x0C,0x40,0x88,0x86,0xBF,0x00,0xC3,0x90,0x6A,0x00,
+0x1F,0xC6,0x06,0x93,0x12,0x0D,0x9C,0x0E,0xE8,0xF1,0xEB,0x90,0xB0,0x02,0xE6,0xDA,
+0xF8,0xC3,0x33,0xC0,0xE6,0xDA,0xF8,0xC3,0xB0,0x01,0xE6,0xD8,0xF8,0xC3,0x33,0xC0,
+0xE6,0xD8,0xF8,0xC3,0xB0,0xFF,0xE8,0x4E,0xFA,0xE8,0xA1,0xFA,0xF8,0xC3,0xAC,0x49,
+0xE8,0xAF,0xFB,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0x15,0xFD,0xF8,0xC3,0x90,0xAC,0x49,
+0xE8,0x67,0xFD,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0x1F,0xFD,0xF8,0xC3,0x90,0xAC,0x49,
+0xE6,0x34,0xF8,0xC3,0xAC,0x49,0xE6,0x36,0xF8,0xC3,0xAC,0x49,0x3C,0x02,0x77,0x1F,
+0x84,0xC0,0x75,0x1D,0xE4,0x14,0x24,0xEF,0xE6,0x14,0xE4,0x12,0x24,0x3F,0xE6,0x12,
+0xE4,0x16,0xA8,0x04,0x74,0x09,0xE8,0xEA,0xF9,0x72,0x04,0xB0,0x18,0xE6,0x0A,0xF8,
+0xC3,0x8A,0xE0,0xE4,0x14,0x0C,0x10,0xE6,0x14,0xE4,0x12,0x0C,0xC0,0xF6,0xC4,0x01,
+0x74,0x02,0x24,0x7F,0xE6,0x12,0xF8,0xC3,0xAC,0x49,0xE8,0x25,0xFD,0xF8,0xC3,0x90,
+0xB8,0x00,0x40,0xE8,0x7D,0xFD,0xE8,0xB4,0xFD,0xE8,0xA8,0xFE,0xB0,0x01,0xE8,0xB9,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x40,0xE8,0x85,0xFD,0xE8,0xA0,0xFD,0xF8,0xC3,0x90,
+0xB8,0x00,0x10,0xE8,0x5D,0xFD,0xE8,0x94,0xFD,0xE8,0x88,0xFE,0xB0,0x08,0xE8,0x99,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x10,0xE8,0x65,0xFD,0xE8,0x80,0xFD,0xF8,0xC3,0x90,
+0xB8,0x00,0x80,0xE8,0x3D,0xFD,0xE8,0x74,0xFD,0xE8,0x68,0xFE,0xB0,0x02,0xE8,0x79,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x80,0xE8,0x45,0xFD,0xE8,0x60,0xFD,0xF8,0xC3,0x90,
+0xB8,0x00,0x20,0xE8,0x1D,0xFD,0xE8,0x54,0xFD,0xE8,0x48,0xFE,0xB0,0x04,0xE8,0x59,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x20,0xE8,0x25,0xFD,0xE8,0x40,0xFD,0xF8,0xC3,0x90,
+0xAC,0x49,0xE8,0x48,0x14,0xE4,0x3C,0x24,0xE7,0x0A,0xC4,0xE6,0x3C,0xF8,0xC3,0x90,
+0xB8,0xFC,0x3B,0x89,0x46,0x7C,0xE4,0x3C,0x0C,0x18,0xE6,0x3C,0xF8,0xC3,0xE4,0x12,
+0x0C,0x02,0xE6,0x12,0xF8,0xC3,0xE4,0x12,0x24,0xFD,0xEB,0xF6,0xE8,0xB5,0xFC,0xF8,
+0xC3,0x90,0x83,0x66,0x38,0xFD,0xF8,0xC3,0xAC,0x49,0xA8,0x01,0x74,0x06,0x83,0x4E,
+0x7A,0x20,0xEB,0x04,0x83,0x66,0x7A,0xDF,0xE8,0xCB,0xFB,0xF8,0xC3,0x90,0x8A,0x86,
+0xA5,0x00,0x0C,0x02,0x24,0xFB,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x81,0x4E,0x26,0x01,
+0x20,0xAC,0x49,0x32,0xE4,0x89,0x46,0x6E,0x83,0x4E,0x48,0x08,0x49,0x46,0xF9,0xC3,
+0x8A,0x86,0xA5,0x00,0x0C,0x02,0x24,0xFB,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x81,0x4E,
+0x26,0x01,0x20,0xAC,0xB4,0x0A,0xF6,0xE4,0xEB,0xD8,0xE8,0xFA,0x13,0xE4,0x3C,0x24,
+0xF8,0x0A,0xC4,0xE6,0x3C,0xF8,0xC3,0x90,0xAD,0x49,0x49,0x89,0x46,0x64,0xA9,0x01,
+0x00,0x74,0x1B,0x8B,0xD8,0x83,0xE3,0xFA,0x75,0x1A,0xA9,0x04,0x00,0x74,0x0F,0xE4,
+0x3E,0x0C,0x02,0xE6,0x3E,0xB8,0x38,0x44,0x89,0x46,0x62,0xF8,0xC3,0x90,0xE4,0x3E,
+0x24,0xFC,0xEB,0xEF,0xE4,0x3E,0x24,0xFC,0xE6,0x3E,0xE8,0xE8,0xFC,0xB8,0xAA,0x40,
+0xEB,0xE6,0xE8,0x6E,0xF8,0x72,0x05,0xB0,0x18,0xE6,0x0A,0xF8,0xC3,0x90,0xAC,0x49,
+0xE8,0xCF,0xF9,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0xCF,0xF9,0xF8,0xC3,0x90,0xE8,0x68,
+0xFD,0x75,0x06,0x32,0xC0,0xE6,0xDA,0xF8,0xC3,0xB0,0x02,0xE6,0xDA,0x36,0xA0,0xB4,
+0x13,0x24,0x10,0x34,0x10,0xE8,0x16,0x01,0x36,0xA1,0xB4,0x13,0xA9,0x01,0x00,0x74,
+0x05,0xE8,0xFC,0xFE,0xEB,0x0E,0xA9,0x02,0x00,0x74,0x04,0x32,0xC0,0xEB,0x02,0xB0,
+0x01,0xE8,0xDE,0xFE,0x36,0xA1,0xB4,0x13,0xE8,0xB5,0x13,0xE4,0x3C,0x24,0xF8,0x0A,
+0xC4,0xE6,0x3C,0x36,0xA1,0xB4,0x13,0xC1,0xE8,0x05,0x25,0x01,0x00,0xE8,0xFA,0xFE,
+0x36,0xA0,0xB5,0x13,0x24,0x10,0xE8,0x59,0xFB,0x32,0xC0,0x36,0x8A,0x26,0xB5,0x13,
+0xF6,0xC4,0x04,0x74,0x09,0xFE,0xC0,0xF6,0xC4,0x08,0x74,0x02,0xFE,0xC0,0xE8,0xDB,
+0xFD,0x36,0xA1,0xB6,0x13,0x25,0x0F,0x00,0xE8,0x57,0xF9,0x36,0xA1,0xB6,0x13,0xC1,
+0xE8,0x04,0x25,0x03,0x00,0xE8,0xB8,0xFA,0x36,0xA1,0xB6,0x13,0xC1,0xE8,0x05,0x25,
+0x02,0x00,0xE8,0x05,0xFB,0x36,0xA1,0xB6,0x13,0xF6,0xC4,0x01,0x75,0x04,0x32,0xC0,
+0xEB,0x09,0x80,0xE4,0x02,0xD0,0xEC,0xB0,0x02,0x2A,0xC4,0xE8,0xAC,0xFA,0x36,0xF6,
+0x06,0xB7,0x13,0x40,0x74,0x05,0xE8,0x83,0xFE,0xEB,0x03,0xE8,0x84,0xFE,0x36,0xF6,
+0x06,0xB7,0x13,0x20,0x74,0x05,0xE8,0x65,0xFE,0xEB,0x03,0xE8,0x68,0xFE,0xF8,0xC3,
+0xE4,0x12,0x0C,0x01,0xE6,0x12,0xF8,0xC3,0xE4,0x12,0x24,0xFE,0xEB,0xF6,0xE4,0x14,
+0x24,0xF0,0x0C,0x05,0xE6,0x14,0xE4,0x2A,0x24,0xF0,0x0C,0x06,0xE6,0x2A,0xF8,0xC3,
+0xE4,0x2A,0x24,0xF0,0xE6,0x2A,0xE4,0x14,0x24,0xF0,0x0C,0x07,0xE6,0x14,0xF8,0xC3,
+0xAD,0x49,0x49,0xE8,0x64,0xF9,0x89,0x86,0x8E,0x00,0xF8,0xC3,0xAD,0x49,0x49,0xE8,
+0x58,0xF9,0x89,0x86,0x90,0x00,0xF8,0xC3,0x83,0x4E,0x26,0x04,0xE8,0xA8,0xF7,0xF8,
+0xC3,0x90,0x83,0x66,0x26,0xFB,0xE8,0x9E,0xF7,0xF8,0xC3,0x90,0xAC,0x49,0x84,0xC0,
+0x75,0x0D,0xE4,0x10,0x24,0xEF,0xE6,0x10,0x80,0x8E,0xA1,0x00,0x42,0xF8,0xC3,0xE4,
+0x10,0x0C,0x10,0xEB,0xF1,0x90,0xAC,0x49,0x3C,0x02,0x76,0x02,0x32,0xC0,0xC0,0xE0,
+0x04,0xA8,0x20,0x74,0x02,0x0C,0x08,0x24,0x18,0x8A,0xE0,0xE4,0x12,0x24,0xE7,0x0A,
+0xC4,0xE6,0x12,0x80,0x8E,0xA1,0x00,0x44,0xF8,0xC3,0xAC,0x49,0x88,0x86,0xC0,0x00,
+0xF8,0xC3,0xAC,0x49,0xE6,0x3A,0xF8,0xC3,0xAC,0x49,0x84,0xC0,0x74,0x08,0xE4,0x12,
+0x0C,0x04,0xE6,0x12,0xF8,0xC3,0xE4,0x12,0x24,0xFB,0xEB,0xF6,0xAC,0x49,0xE8,0xD6,
+0xF6,0x73,0x03,0xE8,0x27,0xF7,0xF8,0xC3,0xE4,0x12,0xA8,0x02,0x74,0x04,0x24,0xFD,
+0xE6,0x12,0xB8,0xF0,0x00,0xE8,0x87,0xFA,0x81,0x66,0x26,0xFF,0xF3,0xE8,0x57,0xF7,
+0xE8,0x9A,0xFA,0xF8,0xC3,0x90,0xB8,0x80,0x00,0xE8,0x57,0xFA,0x80,0x4E,0x27,0x08,
+0xE8,0x44,0xF7,0xE8,0x87,0xFA,0xF8,0xC3,0xB8,0x80,0x00,0xE8,0x61,0xFA,0x81,0x66,
+0x26,0xFF,0xF7,0xE8,0x31,0xF7,0xE8,0x74,0xFA,0xF8,0xC3,0x90,0xB8,0x10,0x00,0xE8,
+0x31,0xFA,0x80,0x4E,0x27,0x04,0xE8,0x1E,0xF7,0xE8,0x61,0xFA,0xF8,0xC3,0xB8,0x10,
+0x00,0xE8,0x3B,0xFA,0x81,0x66,0x26,0xFF,0xFB,0xE8,0x0B,0xF7,0xE8,0x4E,0xFA,0xF8,
+0xC3,0x90,0x33,0xC0,0xAC,0x49,0x3C,0x01,0x73,0x04,0xB0,0x01,0xEB,0x06,0x3C,0x0C,
+0x76,0x02,0xB0,0x0C,0x89,0x46,0x1C,0xF8,0xC3,0x90,0x81,0x4E,0x26,0x00,0x20,0x8A,
+0x86,0xA5,0x00,0x0C,0x02,0x24,0xFB,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x83,0x4E,0x26,
+0x01,0xF8,0xC3,0x90,0x81,0x4E,0x26,0x00,0x40,0x8A,0x86,0xA5,0x00,0x0C,0x02,0x88,
+0x86,0xA5,0x00,0xE6,0x0C,0xF8,0xC3,0x90,0xAC,0x49,0x50,0xE8,0x05,0xF6,0x58,0x72,
+0x08,0xE6,0x38,0xB0,0x23,0xE6,0x0A,0xF8,0xC3,0xF9,0xC3,0x90,0xAC,0x50,0xAD,0xE8,
+0x82,0xF8,0x5A,0xF6,0xC2,0x01,0x74,0x12,0x39,0x86,0x96,0x00,0x74,0x0C,0x89,0x86,
+0x96,0x00,0xE6,0xF0,0x86,0xE0,0xE6,0xF8,0x86,0xE0,0xF6,0xC2,0x02,0x74,0x10,0x39,
+0x86,0x94,0x00,0x74,0x0A,0x89,0x86,0x94,0x00,0xE6,0xE4,0x86,0xE0,0xE6,0xEC,0x83,
+0xE9,0x03,0xC3,0x90,0xE4,0x16,0x88,0x86,0xBC,0x00,0xE8,0xE6,0xFA,0x33,0xDB,0xE4,
+0x0C,0xA8,0x06,0x74,0x03,0x80,0xCB,0x01,0xA8,0x10,0x74,0x03,0x80,0xCB,0x02,0xA8,
+0x80,0x74,0x03,0x80,0xCB,0x04,0xE4,0x12,0x8A,0xE0,0x24,0x18,0x0A,0xD8,0xE4,0xDA,
+0xF6,0xC4,0x02,0x74,0x07,0xA8,0x40,0x75,0x03,0x80,0xCB,0x20,0xA8,0x02,0x75,0x09,
+0xE4,0x2A,0xA8,0x0F,0x74,0x03,0x80,0xCB,0x40,0xF7,0x46,0x38,0x02,0x00,0x74,0x09,
+0xE4,0xD8,0xA8,0x01,0x75,0x03,0x80,0xCB,0x80,0x88,0x9E,0xBE,0x00,0xFE,0x86,0xB4,
+0x00,0xB0,0x0A,0xE8,0x5C,0xE4,0xF8,0xC3,0xAC,0x49,0x3C,0x02,0x74,0x41,0x77,0x1F,
+0x50,0xE8,0x4F,0xF5,0x58,0x72,0x0C,0x84,0xC0,0x74,0x0A,0xB0,0x12,0xE6,0x0A,0x80,
+0x4E,0x38,0x01,0xF8,0xC3,0xB0,0x11,0xE6,0x0A,0x80,0x66,0x38,0xFE,0xF8,0xC3,0x8B,
+0x46,0x38,0x25,0xFF,0xF7,0x89,0x46,0x38,0xA9,0x00,0x04,0x75,0xE6,0x8A,0x86,0xA5,
+0x00,0xA8,0x10,0x75,0xDE,0x0C,0x10,0x88,0x86,0xA5,0x00,0xE6,0x0C,0xF8,0xC3,0x81,
+0x4E,0x38,0x00,0x08,0x8A,0x86,0xA5,0x00,0xA8,0x10,0x74,0xC7,0x24,0xEF,0xEB,0xE7,
+0xAD,0x49,0x49,0x3C,0x01,0x72,0x11,0x3C,0x0C,0x77,0x0D,0x50,0x8A,0xE0,0xE4,0x14,
+0x25,0xF0,0x0F,0x0A,0xC4,0xE6,0x14,0x58,0x8A,0xC4,0x84,0xC0,0x74,0x02,0xE6,0x42,
+0xF8,0xC3,0xE8,0xCF,0xF9,0xFE,0x86,0xB9,0x00,0xB0,0x0E,0xE8,0xD4,0xE3,0xF8,0xC3,
+0x3A,0x86,0xAF,0x00,0x74,0x1F,0x88,0x86,0xAF,0x00,0x8A,0xE0,0x80,0xC2,0x06,0xB0,
+0xBF,0xEE,0x80,0xEA,0x02,0x8A,0xC4,0xEE,0x8A,0x86,0xA8,0x00,0x80,0xC2,0x02,0xEE,
+0x80,0xEA,0x06,0x8A,0xC4,0xC3,0x8B,0x46,0x3E,0x85,0xC0,0x8A,0x86,0xA5,0x00,0x74,
+0x12,0xA8,0x08,0x75,0x0D,0x0C,0x08,0x88,0x86,0xA5,0x00,0x80,0xC2,0x02,0xEE,0x80,
+0xEA,0x02,0xC3,0xA8,0x08,0x74,0xFB,0x24,0xF7,0xEB,0xEC,0x8B,0x46,0x26,0x84,0xC0,
+0x74,0x16,0x8A,0x86,0xA5,0x00,0xA8,0x02,0x74,0x0D,0x24,0xFD,0x88,0x86,0xA5,0x00,
+0x83,0xC2,0x02,0xEE,0x83,0xEA,0x02,0xC3,0x8A,0x86,0xA5,0x00,0xA8,0x02,0x75,0xF7,
+0x0C,0x02,0xEB,0xE8,0x52,0x83,0xC2,0x0C,0xEC,0xC0,0xE8,0x04,0x88,0x86,0xA9,0x00,
+0x8B,0x5E,0x26,0x80,0xE3,0x3F,0xF6,0xC7,0x04,0x74,0x07,0xA8,0x08,0x75,0x03,0x80,
+0xCB,0x40,0xF6,0xC7,0x08,0x74,0x07,0xA8,0x02,0x75,0x03,0x80,0xCB,0x80,0x88,0x5E,
+0x26,0x8A,0x86,0xA5,0x00,0x84,0xDB,0x74,0x10,0xA8,0x02,0x74,0x0A,0x24,0xFD,0x88,
+0x86,0xA5,0x00,0x83,0xEA,0x0A,0xEE,0x5A,0xC3,0xA8,0x02,0x75,0xFA,0x0C,0x02,0xEB,
+0xEE,0x90,0xFF,0xFF,0x00,0x48,0x00,0x30,0xBA,0x20,0xC4,0x1A,0x00,0x18,0x00,0x12,
+0x00,0x0C,0x00,0x06,0x00,0x03,0x00,0x02,0x80,0x01,0xC0,0x00,0x60,0x00,0x30,0x00,
+0x18,0x00,0xCD,0x01,0x00,0x01,0x80,0x00,0x10,0x00,0x10,0x00,0x0E,0x00,0x0C,0x00,
+0x08,0x00,0x00,0x00,0x00,0x00,0x06,0x00,0x04,0x00,0x03,0x00,0x02,0x00,0x01,0x00,
+0x52,0x51,0x56,0x3C,0x1E,0x77,0x47,0x98,0x8B,0xF0,0x8A,0x82,0xC4,0x00,0x32,0xE4,
+0x83,0xFE,0x18,0x74,0x3D,0x83,0xFE,0x19,0x74,0x3E,0x83,0xFE,0x1E,0x77,0x2F,0xD1,
+0xE6,0x2E,0x8B,0x8C,0x32,0x2D,0x3B,0x8E,0x94,0x00,0x74,0x22,0x89,0x8E,0x94,0x00,
+0x83,0xC2,0x06,0x8A,0x86,0xA8,0x00,0x8A,0xE0,0x0C,0x80,0xEE,0x83,0xEA,0x06,0x8A,
+0xC1,0xEE,0x83,0xC2,0x02,0x8A,0xC5,0xEE,0x83,0xC2,0x04,0x8A,0xC4,0xEE,0x5E,0x59,
+0x5A,0xC3,0x8B,0x8E,0x8E,0x00,0xEB,0xCE,0x8B,0x8E,0x90,0x00,0xEB,0xC8,0x52,0x51,
+0x3D,0x05,0x00,0x77,0x03,0xB8,0x05,0x00,0x8B,0xC8,0xBA,0x02,0x00,0xB8,0x00,0xD0,
+0xF7,0xF1,0x05,0x01,0x00,0xD1,0xE8,0x59,0x5A,0xC3,0x8B,0x46,0x7A,0xA8,0x20,0x74,
+0x0B,0x80,0xBE,0xC3,0x00,0x03,0x75,0x04,0x0C,0x01,0xEB,0x02,0x24,0xFE,0x89,0x46,
+0x7A,0xC3,0x24,0x03,0x88,0x86,0xC3,0x00,0x8A,0xA6,0xA8,0x00,0x8A,0xDC,0x80,0xE4,
+0xFC,0x0A,0xC4,0x3A,0xC3,0x74,0x0B,0x88,0x86,0xA8,0x00,0x83,0xC2,0x06,0xEE,0x83,
+0xEA,0x06,0xE8,0xC5,0xFF,0xC3,0x00,0x08,0x18,0x38,0x28,0x90,0x3C,0x04,0x77,0x23,
+0x32,0xE4,0x8B,0xD8,0x2E,0x8A,0x87,0x26,0x2E,0x8A,0xA6,0xA8,0x00,0x8A,0xDC,0x80,
+0xE4,0xC7,0x0A,0xC4,0x3A,0xC3,0x74,0x0B,0x88,0x86,0xA8,0x00,0x83,0xC2,0x06,0xEE,
+0x83,0xEA,0x06,0xC3,0x84,0xC0,0x74,0x02,0xB0,0x04,0x8A,0xA6,0xA8,0x00,0x8A,0xDC,
+0x80,0xE4,0xFB,0x0A,0xC4,0x3A,0xC3,0x74,0x0B,0x88,0x86,0xA8,0x00,0x83,0xC2,0x06,
+0xEE,0x83,0xEA,0x06,0xC3,0x90,0x8B,0x5E,0x38,0x84,0xC0,0x74,0x34,0x3C,0x02,0x74,
+0x3B,0x8A,0x86,0xAF,0x00,0x0C,0x04,0xE8,0xE6,0xFD,0x8B,0x46,0x2E,0x3B,0x46,0x3C,
+0x77,0x1B,0xF7,0xC3,0x00,0x04,0x75,0x15,0x81,0xCB,0x00,0x04,0x83,0xC2,0x02,0x8A,
+0x86,0xA5,0x00,0x24,0xFA,0x88,0x86,0xA5,0x00,0xEE,0x83,0xEA,0x02,0x89,0x5E,0x38,
+0xC3,0x8A,0x86,0xAF,0x00,0x24,0xFB,0xE8,0xB6,0xFD,0xEB,0xF1,0xF7,0xC3,0x10,0x00,
+0x74,0xEF,0xEB,0xED,0x83,0xC2,0x0C,0xEC,0x83,0xEA,0x0C,0xC0,0xE8,0x04,0x88,0x86,
+0xA9,0x00,0xC3,0x90,0x8A,0x86,0xA7,0x00,0x0C,0x01,0x88,0x86,0xA7,0x00,0x8B,0xDA,
+0x80,0xC2,0x08,0xEE,0x8B,0xD3,0xF8,0xC3,0x8A,0x86,0xA7,0x00,0x24,0xFE,0xEB,0xEA,
+0x8A,0x86,0xA7,0x00,0x0C,0x02,0xEB,0xE2,0x8A,0x86,0xA7,0x00,0x24,0xFD,0xEB,0xDA,
+0xB0,0xFF,0xE8,0x52,0xF2,0xE8,0x97,0xF2,0xF8,0xC3,0xAC,0x49,0xE8,0x61,0xFE,0xF8,
+0xC3,0x90,0xAC,0x49,0xE8,0xEB,0xFE,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0x35,0xFF,0xF8,
+0xC3,0x90,0xAC,0x49,0xE8,0x05,0xFF,0xF8,0xC3,0x90,0x52,0x83,0xC2,0x06,0xB0,0xBF,
+0xEE,0x52,0x83,0xC2,0x02,0xAC,0x49,0xEE,0x5A,0x8A,0x86,0xA8,0x00,0xEE,0x5A,0xF8,
+0xC3,0x90,0x52,0x83,0xC2,0x06,0xB0,0xBF,0xEE,0x52,0x83,0xC2,0x06,0xEB,0xE6,0x90,
+0xAC,0x49,0x3C,0x02,0x77,0x0D,0x84,0xC0,0x75,0x0B,0x8A,0x86,0xAF,0x00,0x24,0xFD,
+0xE8,0x0D,0xFD,0xF8,0xC3,0x50,0x8A,0x86,0xAF,0x00,0x0C,0x02,0xE8,0x01,0xFD,0x5B,
+0x83,0xC2,0x08,0x8A,0x86,0xA7,0x00,0xF6,0xC3,0x01,0x74,0x0C,0x24,0xDF,0x88,0x86,
+0xA7,0x00,0xEE,0x83,0xEA,0x08,0xF8,0xC3,0x0C,0x20,0xEB,0xF2,0xAC,0x49,0xE8,0xE5,
+0xFE,0xF8,0xC3,0x90,0xB8,0x00,0x40,0xE8,0x69,0xF5,0xE8,0xF9,0xFC,0xE8,0x24,0xFF,
+0xB0,0x01,0xE8,0xA5,0xF6,0xF8,0xC3,0x90,0xB8,0x00,0x40,0xE8,0x71,0xF5,0xE8,0xE5,
+0xFC,0xF8,0xC3,0x90,0xB8,0x00,0x10,0xE8,0x49,0xF5,0xE8,0xD9,0xFC,0xE8,0x04,0xFF,
+0xB0,0x08,0xE8,0x85,0xF6,0xF8,0xC3,0x90,0xB8,0x00,0x10,0xE8,0x51,0xF5,0xE8,0xC5,
+0xFC,0xF8,0xC3,0x90,0xB8,0x00,0x80,0xE8,0x29,0xF5,0xE8,0xB9,0xFC,0xE8,0xE4,0xFE,
+0xB0,0x02,0xE8,0x65,0xF6,0xF8,0xC3,0x90,0xB8,0x00,0x80,0xE8,0x31,0xF5,0xE8,0xA5,
+0xFC,0xF8,0xC3,0x90,0xB8,0x00,0x20,0xE8,0x09,0xF5,0xE8,0x99,0xFC,0xE8,0xC4,0xFE,
+0xB0,0x04,0xE8,0x45,0xF6,0xF8,0xC3,0x90,0xB8,0x00,0x20,0xE8,0x11,0xF5,0xE8,0x85,
+0xFC,0xF8,0xC3,0x90,0xAC,0x49,0xE8,0x34,0x0C,0xF8,0xC3,0x90,0xB8,0xFC,0x3B,0x89,
+0x46,0x7C,0xF8,0xC3,0x8A,0x86,0xAF,0x00,0x0C,0x80,0xE8,0x43,0xFC,0xF8,0xC3,0x90,
+0x8A,0x86,0xAF,0x00,0x24,0x7F,0xEB,0xF2,0x8A,0x86,0xAF,0x00,0x0C,0x40,0xE8,0x2F,
+0xFC,0xF8,0xC3,0x90,0x8A,0x86,0xAF,0x00,0x24,0xBF,0xEB,0xF2,0xAC,0x49,0xA8,0x01,
+0x74,0x07,0x83,0x4E,0x7A,0x20,0xEB,0x05,0x90,0x83,0x66,0x7A,0xDF,0xE8,0x8A,0xFD,
+0xF8,0xC3,0x83,0xC2,0x06,0x8A,0x86,0xA8,0x00,0x0C,0x40,0x88,0x86,0xA8,0x00,0xEE,
+0x83,0xEA,0x06,0xAC,0x49,0x32,0xE4,0x89,0x46,0x6E,0x83,0x4E,0x26,0x01,0x83,0x4E,
+0x48,0x08,0xB0,0x06,0xE8,0xBB,0xDF,0x49,0x46,0xF9,0xC3,0x90,0x83,0xC2,0x06,0x8A,
+0x86,0xA8,0x00,0x0C,0x40,0x88,0x86,0xA8,0x00,0xEE,0x83,0xEA,0x06,0xAC,0xB4,0x0A,
+0xF6,0xE4,0xEB,0xD0,0xE8,0xE0,0x0B,0xF8,0xC3,0x90,0xAD,0x49,0x49,0x89,0x46,0x64,
+0xA9,0x01,0x00,0x74,0x19,0x8B,0xD8,0x83,0xE3,0xFA,0x75,0x0A,0xA9,0x04,0x00,0x74,
+0x0D,0xB8,0xE2,0x3F,0xEB,0x0B,0xE8,0xEC,0xF4,0xB8,0xAA,0x40,0xEB,0x03,0xB8,0x38,
+0x44,0x89,0x46,0x62,0xF8,0xC3,0x8A,0x86,0xAF,0x00,0xA8,0x02,0x74,0x0A,0x24,0xFD,
+0xE8,0x8D,0xFB,0x0C,0x02,0xE8,0x88,0xFB,0xF8,0xC3,0xAC,0x49,0xE8,0x81,0xFC,0xF8,
+0xC3,0x90,0xAC,0x49,0xE8,0x79,0xFC,0xF8,0xC3,0x90,0xE8,0x5C,0xF5,0x75,0x05,0xE8,
+0xE6,0xFD,0xF8,0xC3,0xE8,0xCD,0xFD,0x36,0xA0,0xB4,0x13,0x24,0x10,0x34,0x10,0xE8,
+0x26,0x01,0x36,0xA1,0xB4,0x13,0xA9,0x01,0x00,0x74,0x05,0xE8,0xFE,0xFE,0xEB,0x0E,
+0xA9,0x02,0x00,0x74,0x04,0x32,0xC0,0xEB,0x02,0xB0,0x01,0xE8,0xE8,0xFE,0x36,0xA1,
+0xB4,0x13,0xE8,0xAB,0x0B,0x36,0xA1,0xB4,0x13,0xC1,0xE8,0x05,0x25,0x01,0x00,0xE8,
+0x0C,0xFF,0x36,0xA0,0xB5,0x13,0x24,0x10,0xE8,0x2B,0xFD,0x32,0xC0,0x36,0x8A,0x26,
+0xB5,0x13,0xF6,0xC4,0x04,0x74,0x09,0xFE,0xC0,0xF6,0xC4,0x08,0x74,0x02,0xFE,0xC0,
+0xE8,0xEF,0xFD,0x36,0xA1,0xB6,0x13,0x25,0x0F,0x00,0xE8,0x03,0xFC,0x36,0xA1,0xB6,
+0x13,0xC1,0xE8,0x04,0x25,0x03,0x00,0xE8,0x88,0xFC,0x36,0xA1,0xB6,0x13,0xC1,0xE8,
+0x05,0x25,0x02,0x00,0xE8,0xCD,0xFC,0x36,0xA1,0xB6,0x13,0xF6,0xC4,0x01,0x75,0x04,
+0x32,0xC0,0xEB,0x09,0x80,0xE4,0x02,0xD0,0xEC,0xB0,0x02,0x2A,0xC4,0xE8,0x8C,0xFC,
+0x36,0xF6,0x06,0xB7,0x13,0x40,0x74,0x05,0xE8,0x8D,0xFE,0xEB,0x03,0xE8,0x94,0xFE,
+0x36,0xF6,0x06,0xB7,0x13,0x20,0x74,0x05,0xE8,0x69,0xFE,0xEB,0x03,0xE8,0x70,0xFE,
+0xF8,0xC3,0xF8,0xC3,0x8B,0x46,0x38,0xA9,0x04,0x00,0x75,0x23,0x0D,0x04,0x00,0x89,
+0x46,0x38,0x83,0xC2,0x08,0x8B,0x46,0x2E,0x3B,0x46,0x3C,0x73,0x14,0x83,0x4E,0x38,
+0x10,0x8A,0x86,0xA7,0x00,0x24,0xFE,0x88,0x86,0xA7,0x00,0xEE,0x83,0xEA,0x08,0xF8,
+0xC3,0x8A,0x86,0xA7,0x00,0x0C,0x01,0xEB,0xEE,0x90,0x8B,0x46,0x38,0xA9,0x04,0x00,
+0x74,0x06,0x25,0xFB,0xFF,0x89,0x46,0x38,0xF8,0xC3,0xAD,0x49,0x49,0xE8,0xBE,0xFB,
+0x89,0x86,0x8E,0x00,0xF8,0xC3,0xAD,0x49,0x49,0xE8,0xB2,0xFB,0x89,0x86,0x90,0x00,
+0xF8,0xC3,0x83,0x4E,0x26,0x04,0xE8,0x92,0xFA,0xF8,0xC3,0x90,0x83,0x66,0x26,0xFB,
+0xE8,0x88,0xFA,0xF8,0xC3,0x90,0xAC,0x49,0x84,0xC0,0x75,0x07,0x80,0x8E,0xA3,0x00,
+0x04,0xF8,0xC3,0x80,0xA6,0xA3,0x00,0xFB,0xF8,0xC3,0xAC,0x49,0x83,0xC2,0x08,0x3C,
+0x02,0x76,0x02,0x32,0xC0,0x3C,0x01,0x74,0x12,0x77,0x0B,0x8A,0x86,0xA7,0x00,0x24,
+0xEF,0x88,0x86,0xA7,0x00,0xEE,0x83,0xEA,0x08,0xF8,0xC3,0x8A,0x86,0xA7,0x00,0x0C,
+0x10,0xEB,0xEE,0x90,0x52,0x83,0xC2,0x06,0xB0,0xBF,0xEE,0x52,0x83,0xC2,0x04,0xAC,
+0x49,0xEE,0x5A,0x8A,0x86,0xA8,0x00,0xEE,0x5A,0xF8,0xC3,0x90,0x52,0x83,0xC2,0x06,
+0xB0,0xBF,0xEE,0x52,0x83,0xC2,0x08,0xEB,0xE6,0x90,0xAC,0x49,0xF8,0xC3,0xAC,0x49,
+0xE8,0xB4,0xEE,0x73,0x03,0xE8,0xF7,0xEE,0xF8,0xC3,0x8A,0x86,0xAF,0x00,0x24,0x7F,
+0xE8,0xBD,0xF9,0xB8,0xF0,0x00,0xE8,0x66,0xF2,0x81,0x66,0x26,0xFF,0xF3,0xE8,0x23,
+0xFA,0xE8,0xD2,0xF9,0xF8,0xC3,0xB8,0x80,0x00,0xE8,0x37,0xF2,0x80,0x4E,0x27,0x08,
+0xE8,0x11,0xFA,0xE8,0xC0,0xF9,0xF8,0xC3,0xB8,0x80,0x00,0xE8,0x41,0xF2,0x81,0x66,
+0x26,0xFF,0xF7,0xE8,0xFE,0xF9,0xE8,0xAD,0xF9,0xF8,0xC3,0x90,0xB8,0x10,0x00,0xE8,
+0x11,0xF2,0x80,0x4E,0x27,0x04,0xE8,0xEB,0xF9,0xE8,0x9A,0xF9,0xF8,0xC3,0xB8,0x10,
+0x00,0xE8,0xFF,0xF1,0x81,0x66,0x26,0xFF,0xFB,0xE8,0xD8,0xF9,0xF8,0xC3,0xAC,0x49,
+0xF8,0xC3,0x83,0xC2,0x06,0x8A,0x86,0xA8,0x00,0x0C,0x40,0x88,0x86,0xA8,0x00,0xEE,
+0x83,0xEA,0x06,0xF8,0xC3,0x90,0x83,0xC2,0x06,0x8A,0x86,0xA8,0x00,0x24,0xBF,0xEB,
+0xEA,0x90,0xAC,0x49,0x8A,0xE0,0x80,0xC2,0x0A,0xEC,0x80,0xEA,0x0A,0xA8,0x20,0x74,
+0x05,0x8A,0xC4,0xEE,0xF8,0xC3,0x06,0x51,0x57,0x8B,0x4E,0x24,0xE3,0x34,0x49,0x89,
+0x4E,0x24,0xFF,0x46,0x1A,0x8E,0x46,0x02,0x8B,0x7E,0x22,0x8A,0xC4,0xAA,0x89,0x7E,
+0x22,0x8B,0x46,0x26,0x24,0xFD,0x89,0x46,0x26,0x75,0x29,0x8A,0x86,0xA5,0x00,0xA8,
+0x02,0x75,0x21,0x80,0xC2,0x02,0x0C,0x02,0x88,0x86,0xA5,0x00,0xEE,0x80,0xEA,0x02,
+0xEB,0x12,0xC4,0x7E,0x00,0x3B,0x7E,0x1E,0x76,0x0A,0x4F,0x26,0x88,0x25,0x89,0x7E,
+0x00,0xFF,0x46,0x1A,0x5F,0x59,0x07,0xF8,0xC3,0x90,0xAC,0xAD,0x83,0xE9,0x03,0x85,
+0xC0,0x74,0x05,0x3D,0x00,0x20,0x72,0x05,0xB8,0xFF,0xFF,0xEB,0x03,0xC1,0xE0,0x03,
+0x3B,0x86,0x94,0x00,0x74,0x26,0x89,0x86,0x94,0x00,0x8B,0xD8,0x52,0x83,0xC2,0x06,
+0x8A,0x86,0xA8,0x00,0x8A,0xE0,0x0C,0x80,0xEE,0x83,0xEA,0x06,0x8A,0xC3,0xEE,0x83,
+0xC2,0x02,0x8A,0xC7,0xEE,0x83,0xC2,0x04,0x8A,0xC4,0xEE,0x5A,0xF8,0xC3,0xB0,0x88,
+0x88,0x86,0xBC,0x00,0xE8,0x8C,0xF2,0x33,0xDB,0x8A,0x86,0xA5,0x00,0xA8,0x02,0x74,
+0x03,0x80,0xCB,0x01,0xA8,0x05,0x74,0x03,0x80,0xCB,0x02,0xA8,0x08,0x74,0x03,0x80,
+0xCB,0x04,0xF6,0x86,0xA7,0x00,0x10,0x74,0x03,0x80,0xCB,0x10,0x8A,0x86,0xA9,0x00,
+0xF6,0xC3,0x04,0x75,0x0A,0x83,0xC2,0x0C,0xEC,0x83,0xEA,0x0C,0xC0,0xE8,0x04,0x8A,
+0xE0,0x8A,0x86,0xAF,0x00,0xA8,0x80,0x74,0x08,0xF6,0xC4,0x01,0x75,0x03,0x80,0xCB,
+0x20,0xF6,0x86,0xA7,0x00,0x02,0x75,0x0A,0xF7,0x46,0x38,0x04,0x00,0x74,0x03,0x80,
+0xCB,0x40,0x88,0x9E,0xBE,0x00,0xFE,0x86,0xB4,0x00,0xB0,0x0A,0xE8,0xF3,0xDB,0xF8,
+0xC3,0xFE,0x86,0xB4,0x00,0xB0,0x0A,0xE8,0xE8,0xDB,0xF8,0xC3,0xAC,0x49,0x3C,0x02,
+0x74,0x37,0x77,0x10,0x84,0xC0,0x74,0x06,0x80,0x4E,0x38,0x01,0xF8,0xC3,0x80,0x66,
+0x38,0xFE,0xF8,0xC3,0x8B,0x46,0x38,0x25,0xFF,0xF7,0x89,0x46,0x38,0xA9,0x00,0x04,
+0x75,0xEA,0x8A,0x86,0xA5,0x00,0xA8,0x01,0x75,0xE2,0x0C,0x05,0x83,0xC2,0x02,0x88,
+0x86,0xA5,0x00,0xEE,0x83,0xEA,0x02,0xF8,0xC3,0x81,0x4E,0x38,0x00,0x08,0x8A,0x86,
+0xA5,0x00,0xA8,0x01,0x74,0xC6,0x24,0xFA,0xEB,0xE2,0xAD,0x49,0x49,0xF8,0xC3,0x90,
+0xE8,0x11,0xFA,0xFE,0x86,0xB9,0x00,0xB0,0x0E,0xE8,0x86,0xDB,0xF8,0xC3,0xB0,0xFF,
+0xE8,0xBF,0xEC,0xF8,0xC3,0x90,0x83,0x66,0x7A,0xFB,0xB0,0x00,0xE8,0x73,0xDB,0xF8,
+0xC3,0x90,0xAC,0x49,0xE8,0x53,0xD9,0x72,0x11,0x36,0x88,0x1E,0x1A,0x01,0x36,0xA0,
+0x8E,0x12,0x0A,0xC3,0x52,0xBA,0x00,0x01,0xEE,0x5A,0xF8,0xC3,0xAC,0x49,0x32,0xE4,
+0x36,0xA3,0x86,0x12,0x05,0x06,0x00,0x36,0x8B,0x1E,0x88,0x12,0x2B,0xD8,0x36,0x89,
+0x1E,0x8A,0x12,0xF8,0xC3,0x90,0xAD,0x8B,0xD8,0xAD,0x83,0xE9,0x04,0x03,0xC3,0x2B,
+0x46,0x76,0x89,0x46,0x78,0xF7,0x46,0x7A,0x02,0x00,0x74,0x0A,0x83,0x66,0x7A,0xFD,
+0xB8,0x00,0x00,0xE8,0x1C,0xDB,0xF8,0xC3,0x06,0x16,0x07,0xAC,0x49,0x25,0x0F,0x00,
+0x6B,0xC0,0x09,0x8D,0xBE,0xFD,0x00,0x03,0xF8,0xAC,0x49,0x25,0x0F,0x00,0xAA,0x85,
+0xC0,0x74,0x08,0x2B,0xC8,0x51,0x8B,0xC8,0xF3,0xA4,0x59,0xE8,0x27,0xF0,0xE8,0x44,
+0x03,0x07,0xF8,0xC3,0x33,0xC0,0xAC,0x49,0x36,0xA3,0xB2,0x13,0x36,0xA3,0xB0,0x13,
+0xF8,0xC3,0x83,0x66,0x7A,0xEF,0xE8,0x2C,0x03,0xF8,0xC3,0x90,0x83,0x4E,0x7A,0x10,
+0xEB,0xF4,0xE8,0x9B,0xF0,0xF8,0xC3,0x90,0xAD,0x3C,0x19,0x77,0x0E,0x3C,0x19,0x77,
+0x0A,0x8B,0xF8,0x81,0xE7,0xFF,0x00,0x88,0xA6,0xC4,0x00,0xF8,0xC3,0x90,0x83,0x4E,
+0x26,0x20,0xAC,0x49,0x32,0xE4,0xD1,0xE0,0x8B,0xD8,0xC1,0xE3,0x02,0x03,0xC3,0x89,
+0x46,0x6E,0x83,0x4E,0x48,0x04,0xB0,0x06,0xE8,0x97,0xDA,0x49,0x46,0xF9,0xC3,0x90,
+0xFE,0x86,0xB3,0x00,0xB0,0x0A,0xE8,0x89,0xDA,0xF8,0xC3,0x90,0x33,0xC0,0xAC,0x49,
+0x6B,0xC0,0x0A,0x89,0x86,0x8A,0x00,0xF8,0xC3,0x90,0xAC,0x49,0x32,0xE4,0x3D,0x0A,
+0x00,0x77,0x05,0xB8,0x0A,0x00,0xEB,0x08,0x3D,0x5A,0x00,0x72,0x03,0xB8,0x5A,0x00,
+0x51,0xF7,0xD8,0x05,0x64,0x00,0x8B,0xC8,0x8B,0x46,0x44,0xF7,0xE1,0xB9,0x64,0x00,
+0xF7,0xF1,0x89,0x46,0x46,0x59,0xF8,0xC3,0xAC,0x49,0xE8,0x85,0xEB,0xF8,0xC3,0x90,
+0xAC,0x49,0x84,0xC0,0x75,0x07,0x81,0x66,0x38,0xFF,0xFD,0xF8,0xC3,0x81,0x4E,0x38,
+0x00,0x02,0xF7,0x46,0x38,0x40,0x00,0x75,0x08,0x8A,0x86,0xA9,0x00,0x88,0x86,0xAA,
+0x00,0xF8,0xC3,0x90,0x51,0x56,0xE8,0x7F,0x0C,0x5E,0x59,0xF8,0xC3,0x90,0xFE,0x86,
+0xB6,0x00,0xB0,0x0A,0xE8,0x0B,0xDA,0xF8,0xC3,0x90,0xFE,0x86,0xB7,0x00,0xB0,0x0A,
+0xE8,0xFF,0xD9,0xF8,0xC3,0x90,0xFE,0x86,0xB8,0x00,0xB0,0x0A,0xE8,0xF3,0xD9,0xF8,
+0xC3,0x90,0x00,0x90,0x51,0x55,0xAC,0x2E,0xA2,0x52,0x36,0x33,0xC9,0xAD,0x8B,0xF9,
+0xC1,0xE7,0x05,0xA9,0x01,0x00,0x74,0x23,0x2E,0x8B,0xAD,0x44,0x00,0x83,0x7E,0x08,
+0x00,0x74,0x18,0x2E,0x80,0x3E,0x52,0x36,0x01,0x74,0x09,0x60,0xB0,0x04,0xE8,0xBB,
+0x0C,0x61,0xEB,0x07,0x60,0xB0,0xFB,0xE8,0xEC,0x0C,0x61,0x47,0x47,0xD1,0xE8,0x75,
+0xD2,0x41,0x83,0xF9,0x04,0x72,0xC6,0x5D,0x59,0x83,0xE9,0x05,0xF7,0x46,0x38,0x40,
+0x00,0x74,0x05,0xE8,0x87,0xEA,0xF8,0xC3,0xE8,0x8D,0xEA,0xF8,0xC3,0x90,0x36,0xC6,
+0x06,0xC8,0x13,0x01,0xF8,0xC3,0x33,0xC0,0xAC,0x49,0x36,0xA3,0x80,0x12,0xAC,0x49,
+0x36,0x2B,0x06,0x88,0x12,0xF7,0xD8,0x36,0xA3,0x82,0x12,0xF8,0xC3,0x90,0xDE,0x26,
+0xDE,0x26,0xEC,0x26,0xF2,0x26,0xF8,0x26,0xFE,0x26,0x04,0x27,0x0E,0x27,0x16,0x27,
+0x1E,0x27,0x26,0x27,0x2E,0x27,0x34,0x27,0xBE,0x34,0xC6,0x34,0xD2,0x34,0x3A,0x27,
+0x78,0x27,0x80,0x27,0x94,0x27,0xA0,0x27,0xB4,0x27,0xC0,0x27,0xD4,0x27,0xE0,0x27,
+0xF4,0x27,0x00,0x28,0x10,0x28,0xEC,0x34,0xDE,0x26,0x1E,0x28,0x26,0x28,0x2C,0x28,
+0x32,0x28,0x38,0x28,0x4E,0x28,0x8A,0x28,0x06,0x35,0x28,0x35,0x98,0x28,0xBE,0x28,
+0xD2,0x28,0xDE,0x28,0xE6,0x28,0x54,0x35,0x62,0x35,0x6C,0x35,0xEE,0x28,0xC0,0x29,
+0xC8,0x29,0xCE,0x29,0xE0,0x29,0x72,0x35,0x78,0x35,0xF0,0x29,0xFC,0x29,0x8E,0x35,
+0x08,0x2A,0x12,0x2A,0x1C,0x2A,0xB0,0x35,0x36,0x2A,0xBC,0x35,0x5A,0x2A,0x62,0x2A,
+0x68,0x2A,0xCA,0x35,0x7C,0x2A,0xF8,0x35,0x88,0x2A,0xA6,0x2A,0xB8,0x2A,0xCC,0x2A,
+0xDE,0x2A,0xF2,0x2A,0x00,0x36,0x0A,0x2B,0x24,0x2B,0x24,0x36,0x38,0x2B,0x4C,0x2B,
+0x84,0x2B,0x2E,0x36,0x3A,0x36,0x46,0x36,0x54,0x36,0xE8,0x2B,0xAE,0x36,0x40,0x2C,
+0x62,0x2C,0xB6,0x36,0x70,0x28,0xDE,0x26,0xDE,0x26,0xD4,0x2E,0xE8,0x2E,0xF0,0x2E,
+0xF8,0x2E,0x00,0x2F,0x0A,0x2F,0x12,0x2F,0x1A,0x2F,0x22,0x2F,0x2A,0x2F,0x42,0x2F,
+0xBE,0x34,0xC6,0x34,0xD2,0x34,0x50,0x2F,0x8C,0x2F,0x94,0x2F,0xA8,0x2F,0xB4,0x2F,
+0xC8,0x2F,0xD4,0x2F,0xE8,0x2F,0xF4,0x2F,0x08,0x30,0x14,0x30,0x1C,0x30,0xEC,0x34,
+0xDE,0x26,0x24,0x30,0x30,0x30,0x38,0x30,0x44,0x30,0x4C,0x30,0x62,0x30,0xA4,0x30,
+0x06,0x35,0x28,0x35,0xAA,0x30,0xCE,0x30,0xD6,0x30,0xEA,0x30,0xF2,0x30,0x54,0x35,
+0x62,0x35,0x6C,0x35,0xFA,0x30,0xC2,0x31,0xC2,0x31,0xC4,0x31,0xFA,0x31,0x72,0x35,
+0x78,0x35,0x0A,0x32,0x16,0x32,0x8E,0x35,0x22,0x32,0x2C,0x32,0x36,0x32,0xB0,0x35,
+0x4A,0x32,0xBC,0x35,0x74,0x32,0x8C,0x32,0x9A,0x32,0xCA,0x35,0x9E,0x32,0xF8,0x35,
+0xAA,0x32,0xC6,0x32,0xD8,0x32,0xEC,0x32,0xFE,0x32,0x0E,0x33,0x00,0x36,0x12,0x33,
+0x26,0x33,0x24,0x36,0x32,0x33,0x9A,0x33,0xDE,0x33,0x2E,0x36,0x3A,0x36,0x46,0x36,
+0x54,0x36,0x5C,0x34,0xAE,0x36,0xAA,0x34,0xB0,0x34,0xB6,0x36,0x8C,0x30,0xE3,0x28,
+0xF7,0x46,0x38,0x40,0x00,0x75,0x32,0xE8,0xE3,0xE8,0x33,0xC0,0xAC,0x49,0x3D,0x5B,
+0x00,0x77,0x19,0x8B,0xD8,0xD1,0xE3,0x2E,0xFF,0x97,0xCE,0x36,0x72,0x0B,0x85,0xC9,
+0x75,0xE8,0x8B,0x46,0x48,0xE8,0x1A,0x0C,0xC3,0x4E,0x41,0xC3,0x6A,0x00,0x1F,0xC6,
+0x06,0x93,0x12,0x0C,0x9C,0x0E,0xE8,0x63,0xDA,0xE8,0xBC,0xE8,0x33,0xC0,0xAC,0x49,
+0x3D,0x5B,0x00,0x77,0xE7,0x8B,0xD8,0xD1,0xE3,0x2E,0xFF,0x97,0x86,0x37,0x72,0xD9,
+0x85,0xC9,0x75,0xE8,0xC3,0xF7,0x46,0x7A,0x10,0x00,0x75,0x0F,0x83,0xBE,0x84,0x00,
+0x00,0x74,0x08,0xB8,0x48,0x3A,0x89,0x86,0x80,0x00,0xC3,0x81,0xBE,0x80,0x00,0xEC,
+0x3C,0x74,0xF7,0x83,0xBE,0x88,0x00,0x00,0x75,0x05,0xB8,0xEC,0x3C,0xEB,0xE7,0xF7,
+0x46,0x7A,0x08,0x00,0x75,0x40,0x1E,0x60,0x8B,0x8E,0x88,0x00,0x3B,0x4E,0x74,0x77,
+0x33,0x3B,0x4E,0x78,0x77,0x2E,0xC4,0x7E,0x10,0x8B,0xDF,0x26,0x03,0x3D,0x47,0x47,
+0x33,0xC0,0x8E,0xD8,0x8D,0xB6,0xF4,0x00,0x8B,0xC1,0xF7,0x46,0x7A,0x01,0x00,0x75,
+0x1D,0xF3,0xA4,0x26,0x01,0x07,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0xB0,
+0x0C,0xE8,0x3E,0xD7,0x61,0x1F,0xC7,0x86,0x88,0x00,0x00,0x00,0xEB,0xAC,0xE3,0xE3,
+0x50,0x90,0xAC,0x24,0x7F,0xAA,0xE2,0xFA,0x58,0xEB,0xD8,0x90,0x8B,0x8E,0x88,0x00,
+0xE3,0x46,0x8B,0x9E,0x8A,0x00,0x85,0xDB,0x74,0x3E,0xBA,0x50,0xFF,0xED,0x2B,0x86,
+0x82,0x00,0x3B,0xC3,0x72,0x37,0x8D,0xB6,0xF4,0x00,0xC4,0x7E,0x10,0x8B,0xDF,0x26,
+0x03,0x3D,0x47,0x47,0x8B,0xC1,0x16,0x1F,0xF7,0x46,0x7A,0x01,0x00,0x75,0x24,0xF3,
+0xA4,0x26,0x01,0x07,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0xC7,0x86,0x88,
+0x00,0x00,0x00,0xB0,0x0C,0xE8,0xDA,0xD6,0x83,0x66,0x7A,0xF7,0xC3,0xB0,0x00,0xE8,
+0xD0,0xD6,0xC3,0xE3,0xDC,0x50,0xAC,0x24,0x7F,0xAA,0xE2,0xFA,0x58,0xEB,0xD2,0x90,
+0x1E,0x60,0x33,0xC0,0x8E,0xD8,0x8D,0xB6,0xFD,0x00,0x8B,0x86,0x88,0x00,0x8B,0x96,
+0x84,0x00,0x3A,0x04,0x75,0x10,0x8B,0xDE,0x46,0x8B,0xC8,0x8D,0xBE,0xF4,0x00,0xF3,
+0xA6,0x74,0x66,0x8B,0xF3,0x90,0x83,0xC6,0x09,0x4A,0x75,0xE6,0x8D,0xB6,0xFD,0x00,
+0x8B,0x96,0x84,0x00,0x3A,0x04,0x73,0x10,0x8B,0xDE,0x46,0x8B,0xC8,0x8D,0xBE,0xF4,
+0x00,0xF3,0xA6,0x74,0x76,0x8B,0xF3,0x90,0x83,0xC6,0x09,0x4A,0x75,0xE6,0x8D,0xB6,
+0xF4,0x00,0xAC,0xF7,0x46,0x7A,0x01,0x00,0x74,0x02,0x24,0x7F,0x1E,0xC5,0x5E,0x10,
+0x8B,0x37,0x88,0x40,0x02,0x46,0x89,0x37,0xFF,0x4E,0x78,0xFF,0x46,0x76,0xFF,0x4E,
+0x74,0x1F,0x8B,0x8E,0x88,0x00,0x49,0x89,0x8E,0x88,0x00,0xE3,0x43,0x8D,0xB6,0xF4,
+0x00,0x8B,0xFE,0x46,0xF3,0xA4,0xE9,0x7D,0xFF,0xC5,0x76,0x10,0x8B,0x1C,0x85,0xDB,
+0x74,0x08,0x03,0xF3,0x83,0xC6,0x03,0x83,0xE6,0xFE,0x8B,0x86,0x84,0x00,0x2B,0xC2,
+0xB4,0x80,0x89,0x04,0x46,0x46,0xC7,0x04,0x00,0x00,0x89,0x76,0x10,0x83,0x4E,0x7A,
+0x04,0xC7,0x86,0x88,0x00,0x00,0x00,0x61,0x1F,0xF9,0xC3,0x33,0xC0,0x61,0x1F,0xC3,
+0xB0,0x80,0x84,0xC0,0x61,0x1F,0xC3,0x90,0x8B,0x4E,0x78,0x2B,0x8E,0x88,0x00,0x76,
+0x27,0x89,0xB6,0x8C,0x00,0x8B,0x5E,0x74,0x3B,0xCB,0x72,0x02,0x8B,0xCB,0x3B,0xC8,
+0x72,0x02,0x8B,0xC8,0x8B,0xC1,0xE3,0x44,0x33,0xD2,0x8E,0xC2,0x8B,0xD1,0x83,0xBE,
+0x88,0x00,0x00,0x74,0x06,0xE9,0x8E,0x00,0x33,0xC0,0xC3,0x8B,0x5E,0x10,0x03,0x1F,
+0x43,0x43,0x52,0xF7,0x46,0x7A,0x01,0x00,0x75,0x2A,0xAC,0x8D,0xBE,0xE4,0x00,0x8B,
+0x8E,0x86,0x00,0xF2,0xAE,0x74,0x34,0x88,0x07,0x43,0x4A,0x75,0xED,0x58,0x8B,0x5E,
+0x10,0x01,0x07,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0x8B,0xC6,0x2B,0x86,
+0x8C,0x00,0xC3,0x90,0xAC,0x8D,0xBE,0xE4,0x00,0x8B,0x8E,0x86,0x00,0xF2,0xAE,0x74,
+0x0A,0x24,0x7F,0x88,0x07,0x43,0x4A,0x75,0xEB,0xEB,0xD2,0x88,0x86,0xF4,0x00,0xC7,
+0x86,0x88,0x00,0x01,0x00,0x58,0x2B,0xC2,0x74,0x0E,0x8B,0x5E,0x10,0x01,0x07,0x29,
+0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0x40,0xE8,0x94,0xFE,0x72,0xBE,0x4A,0x75,
+0x15,0x83,0xBE,0x8A,0x00,0x00,0x74,0xB4,0xBA,0x50,0xFF,0xED,0x89,0x86,0x82,0x00,
+0x83,0x4E,0x7A,0x08,0xEB,0xA6,0x8D,0xBE,0xF4,0x00,0x03,0xBE,0x88,0x00,0xA4,0xFF,
+0x86,0x88,0x00,0xE8,0x6A,0xFE,0x72,0x94,0x79,0x06,0x4A,0x74,0x8F,0xE9,0x5B,0xFF,
+0x4A,0x74,0xCE,0xEB,0xE1,0x90,0x50,0xE8,0x11,0xCC,0x8B,0x46,0x74,0x39,0x46,0x72,
+0x74,0x27,0x1E,0x56,0x51,0x33,0xC9,0xC5,0x76,0x0C,0xAD,0x74,0x10,0x78,0x09,0x03,
+0xC8,0x05,0x01,0x00,0x24,0xFE,0x03,0xF0,0x3B,0x76,0x10,0x76,0xED,0x29,0x4E,0x76,
+0x01,0x4E,0x78,0xE8,0x37,0xCC,0x59,0x5E,0x1F,0x58,0xC3,0x90,0xC4,0x7E,0x10,0x26,
+0x8B,0x1D,0x83,0xC3,0x03,0x26,0x89,0x1D,0x4B,0x03,0xFB,0xAB,0x91,0xAA,0xB8,0x03,
+0x00,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0xC3,0x90,0xC4,0x7E,0x10,0x26,
+0x8B,0x1D,0x43,0x26,0x89,0x1D,0x43,0x03,0xFB,0xAA,0xFF,0x4E,0x78,0xFF,0x46,0x76,
+0xFF,0x4E,0x74,0xC3,0xE8,0xE5,0xFF,0xC3,0x80,0x81,0x84,0x85,0x82,0x83,0x86,0x87,
+0x50,0x53,0x8A,0xDC,0x83,0xE3,0x0E,0xD1,0xEB,0x2E,0x8A,0x87,0x98,0x3B,0x08,0x86,
+0xB0,0x00,0xFE,0x86,0xB1,0x00,0xB0,0x0A,0xE8,0x87,0xD4,0x5B,0x58,0xC3,0x50,0x8A,
+0xC8,0xB8,0xFF,0x00,0xE8,0x95,0xFF,0x58,0xC3,0x90,0x8A,0x86,0xBB,0x00,0xE8,0xAB,
+0xFF,0xC3,0xE8,0xCB,0xFF,0xE8,0xF2,0xFF,0xC3,0x90,0xE8,0xC3,0xFF,0xE8,0xB4,0xFF,
+0xC3,0x90,0x33,0xC0,0xE8,0x95,0xFF,0xC3,0xB8,0xFF,0x00,0x33,0xC9,0xE8,0x6C,0xFF,
+0xC3,0x90,0xB8,0xFF,0x01,0xB1,0x10,0xE8,0x62,0xFF,0xC3,0x90,0xC3,0xFC,0x3B,0xE2,
+0x3B,0xF2,0x3B,0xF2,0x3B,0xFC,0x3B,0xE2,0x3B,0xE8,0x3B,0xE8,0x3B,0xFC,0x3B,0xE2,
+0x3B,0xE8,0x3B,0xE8,0x3B,0xFC,0x3B,0xE2,0x3B,0xE2,0x3B,0xE2,0x3B,0x00,0x10,0x00,
+0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,
+0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x08,0x00,
+0x00,0x00,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x08,0x00,0x00,0x51,0x53,0x8B,
+0x4E,0x38,0x81,0xE1,0xFF,0xEE,0xA8,0x04,0x74,0x04,0x81,0xC9,0x00,0x01,0x8A,0xE0,
+0x80,0xE4,0x03,0x24,0x18,0xD0,0xE4,0x0A,0xC4,0x33,0xDB,0x8A,0xD8,0x2E,0x8B,0x87,
+0xFD,0x3B,0x89,0x46,0x7C,0x2E,0x0B,0x8F,0x1D,0x3C,0x89,0x4E,0x38,0xD1,0xEB,0x2E,
+0x8A,0xA7,0x3D,0x3C,0x5B,0x59,0xC3,0xAC,0x49,0x3C,0x01,0x72,0x1D,0x74,0x20,0x3C,
+0x03,0x72,0x23,0x74,0x28,0x3C,0x08,0x72,0x2B,0x74,0x30,0x3C,0x20,0x72,0x37,0x74,
+0x3A,0xBB,0xDA,0x3B,0x32,0xE4,0x89,0x5E,0x7E,0xC3,0xBB,0xA0,0x3B,0xEB,0xF5,0xBB,
+0x94,0x3B,0xB4,0x01,0xEB,0xF0,0xBB,0xFC,0x3B,0xB4,0x02,0xEB,0xE9,0xBB,0xE2,0x3B,
+0xB4,0x03,0xEB,0xE2,0xBB,0xBE,0x3B,0xB4,0x04,0xEB,0xDB,0xBB,0xCA,0x3B,0xAC,0x49,
+0x88,0x86,0xBB,0x00,0xEB,0xCE,0xBB,0xD2,0x3B,0xEB,0xF3,0xBB,0xFC,0x3B,0xEB,0xC4,
+0xA9,0x04,0x00,0x75,0xD1,0xA9,0x08,0x00,0x75,0xDA,0xEB,0xD1,0x8B,0x5E,0x74,0x8B,
+0x4E,0x78,0x3B,0xCB,0x72,0x02,0x8B,0xCB,0x3B,0xC8,0x72,0x02,0x8B,0xC8,0x8B,0xC1,
+0xE3,0x2C,0xC4,0x7E,0x10,0x8B,0xDF,0x26,0x03,0x3D,0x47,0x47,0xF7,0x46,0x7A,0x01,
+0x00,0x75,0x1C,0xF7,0xC7,0x01,0x00,0x74,0x02,0x49,0xA4,0xD1,0xE9,0xF3,0xA5,0x73,
+0x01,0xA4,0x26,0x01,0x07,0x29,0x46,0x78,0x01,0x46,0x76,0x29,0x46,0x74,0xC3,0x50,
+0x53,0xBB,0x7F,0x7F,0xF7,0xC7,0x01,0x00,0x74,0x05,0x49,0xAC,0x22,0xC3,0xAA,0xD1,
+0xE9,0xE3,0x1D,0x9C,0xAD,0x23,0xC3,0xAB,0x49,0x74,0x14,0xAD,0x23,0xC3,0xAB,0x49,
+0x74,0x0D,0xAD,0x23,0xC3,0xAB,0x49,0x74,0x06,0xAD,0x23,0xC3,0xAB,0xE2,0xE5,0x9D,
+0x73,0x04,0xAC,0x22,0xC3,0xAB,0x5B,0x58,0xEB,0xB8,0xE8,0xCE,0xC9,0x8B,0x5E,0x38,
+0xF7,0xC3,0x10,0x04,0x75,0x01,0xC3,0xF7,0xC3,0x40,0x00,0x74,0x05,0xE8,0xB8,0xE3,
+0xEB,0x03,0xE8,0xA8,0xE3,0x81,0x66,0x38,0xEF,0xFB,0xF6,0xC3,0x10,0x74,0x3C,0xF6,
+0xC3,0x02,0x74,0x06,0xE4,0xD8,0x0C,0x01,0xE6,0xD8,0xF6,0xC3,0x04,0x74,0x11,0x83,
+0xC2,0x08,0x8A,0x86,0xA7,0x00,0x0C,0x01,0xEE,0x88,0x86,0xA7,0x00,0x83,0xEA,0x08,
+0xF6,0xC3,0x08,0x74,0x0F,0xE8,0x8B,0xE3,0x72,0x0A,0x8A,0x86,0xC0,0x00,0xE6,0x38,
+0xB0,0x23,0xE6,0x0A,0xF7,0xC3,0x00,0x04,0x75,0x01,0xC3,0xF7,0xC3,0x00,0x08,0x75,
+0xF9,0x8A,0x86,0xA5,0x00,0xF6,0xC3,0x40,0x75,0x0D,0xA8,0x10,0x75,0xEC,0x0C,0x10,
+0x88,0x86,0xA5,0x00,0xE6,0x0C,0xC3,0xA8,0x01,0x75,0xDF,0x83,0xC2,0x02,0x0C,0x05,
+0xEE,0x88,0x86,0xA5,0x00,0xC3,0xB0,0x00,0xE8,0x47,0xD2,0xEB,0x0F,0xB0,0x02,0xE8,
+0x90,0x0E,0xEB,0x08,0x83,0x66,0x38,0xDF,0x83,0x4E,0x7A,0x02,0x33,0xC0,0x8E,0xD8,
+0xFA,0xA0,0x92,0x12,0x40,0xA2,0x92,0x12,0x3C,0x05,0x72,0x1E,0xC6,0x06,0x92,0x12,
+0x00,0xFB,0xB0,0x01,0xE8,0x6B,0x0E,0xFA,0xA1,0x26,0x01,0x23,0x06,0x2A,0x01,0xA8,
+0x01,0x75,0x07,0xE8,0xE2,0x07,0xE8,0x61,0x09,0x90,0xB0,0x00,0xE8,0x37,0xD2,0xFB,
+0x85,0xED,0x74,0xB9,0xFA,0xF7,0x46,0x7A,0x46,0x00,0x75,0xC0,0x8B,0x46,0x78,0x3D,
+0x0A,0x00,0x72,0xB0,0x8B,0x4E,0x74,0x83,0xF9,0x50,0x72,0x9A,0x83,0x66,0x38,0xDF,
+0xC5,0x76,0x14,0x8B,0x46,0x3A,0x85,0xC0,0x75,0x58,0xAD,0x85,0xC0,0x75,0x0F,0xE8,
+0xF8,0xFE,0xF7,0x46,0x7A,0x08,0x00,0x74,0x93,0xE8,0xA0,0xFA,0xEB,0x8E,0x3B,0x76,
+0x04,0x76,0x21,0xB9,0x02,0x00,0x39,0x4E,0x2E,0x77,0x05,0xC7,0x46,0x2E,0x00,0x00,
+0x56,0x8B,0x76,0x2C,0x89,0x76,0x04,0xC7,0x04,0x00,0x00,0x46,0x46,0x89,0x76,0x2C,
+0x29,0x4E,0x2E,0x5E,0x85,0xC0,0x79,0x17,0xF6,0xC4,0x10,0x74,0x05,0xFF,0x56,0x7C,
+0xEB,0x03,0xFF,0x56,0x7E,0x89,0x76,0x14,0xB0,0x0C,0xE8,0x85,0xD1,0xEB,0x86,0x89,
+0x46,0x3A,0xFF,0x96,0x80,0x00,0x29,0x46,0x3A,0x89,0x76,0x14,0xB0,0x0C,0xE8,0x71,
+0xD1,0xE9,0x71,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x04,0x10,0x02,
+0x01,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,
+0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x80,
+0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
+0x80,0x80,0x80,0x80,0x4E,0x41,0x78,0x41,0xD0,0x41,0xF4,0x41,0x06,0x42,0x18,0x42,
+0xC3,0x90,0x8E,0x46,0x02,0x8B,0x7E,0x22,0x89,0x7E,0x6C,0x80,0x66,0x27,0xFD,0x8B,
+0x56,0x24,0x83,0xFA,0x04,0x72,0xE9,0x83,0xEA,0x02,0x8B,0xD9,0x3B,0xCA,0x76,0x02,
+0x8B,0xCA,0xB0,0x0A,0x57,0x51,0x8B,0xFE,0xF2,0xAE,0x8B,0xC1,0x59,0x5F,0x75,0x1E,
+0x50,0x40,0x2B,0xC8,0x74,0x06,0x2B,0xD1,0x2B,0xD9,0xF3,0xA4,0x59,0x4B,0x4A,0x4A,
+0xB0,0x0D,0xAA,0xA4,0x3B,0xCA,0x76,0x02,0x8B,0xCA,0xE3,0x13,0xEB,0xD4,0x2B,0xD9,
+0xF7,0xC6,0x01,0x00,0x74,0x02,0xA4,0x49,0xD1,0xE9,0xF3,0xA5,0x73,0x01,0xA4,0x89,
+0x7E,0x22,0x2B,0x7E,0x6C,0x29,0x7E,0x24,0x01,0x7E,0x1A,0x8B,0xCB,0x80,0x7E,0x26,
+0x02,0x74,0x05,0x80,0x66,0x26,0xFD,0xC3,0x60,0xB0,0xFD,0xE8,0x18,0x03,0x61,0xC3,
+0xC3,0x90,0xE8,0x7C,0x02,0x72,0xF9,0x90,0x83,0x4E,0x26,0x20,0x8B,0x46,0x6A,0x89,
+0x46,0x6E,0x8B,0x46,0x48,0x0D,0x04,0x00,0x25,0xBF,0xFF,0x89,0x46,0x48,0xB0,0x06,
+0xE8,0xBF,0xCF,0xC3,0x89,0x7E,0x22,0x2B,0x7E,0x6C,0x01,0x7E,0x1A,0x29,0x7E,0x24,
+0x80,0x7E,0x26,0x02,0x74,0x05,0x83,0x66,0x26,0xFD,0xC3,0x60,0xB0,0xFD,0xE8,0xD5,
+0x02,0x61,0xC3,0x90,0x8A,0xBE,0xC2,0x00,0xEB,0x24,0xF7,0x46,0x48,0x40,0x00,0x75,
+0xB1,0x8E,0x46,0x02,0x8B,0x7E,0x22,0x89,0x7E,0x6C,0x8B,0x56,0x24,0x83,0xEA,0x0A,
+0x78,0x9E,0x03,0xD7,0x80,0x66,0x27,0xFD,0x33,0xC0,0x8A,0xBE,0xC2,0x00,0xE3,0xB4,
+0x3B,0xFA,0x77,0xB0,0xAC,0x49,0x93,0x2E,0x8A,0x87,0xD4,0x3E,0x93,0x22,0xDF,0x75,
+0x17,0xAA,0xE3,0xA0,0x3B,0xFA,0x77,0x9C,0xAC,0x49,0x93,0x2E,0x8A,0x87,0xD4,0x3E,
+0x93,0x22,0xDF,0x75,0x03,0xAA,0xEB,0xD6,0xF6,0xC3,0x7F,0x75,0x05,0xFF,0x46,0x66,
+0xEB,0xDF,0xF6,0xC3,0x40,0x75,0x0C,0x8B,0xD8,0x83,0xEB,0x08,0xD1,0xE3,0x2E,0xFF,
+0xA7,0xD4,0x3F,0xFF,0x46,0x66,0x2C,0x20,0xEB,0xC7,0x85,0xC0,0x74,0x2C,0x89,0x46,
+0x6A,0x83,0x4E,0x48,0x40,0x89,0x7E,0x22,0x2B,0x7E,0x6C,0x01,0x7E,0x1A,0x29,0x7E,
+0x24,0x80,0x7E,0x26,0x02,0x74,0x08,0x83,0x66,0x26,0xFD,0xE8,0xA3,0x01,0xC3,0x60,
+0xB0,0xFD,0xE8,0x31,0x02,0x61,0xE8,0x98,0x01,0xC3,0xE9,0x57,0xFF,0x90,0x8B,0x5E,
+0x66,0x4B,0x78,0x03,0x89,0x5E,0x66,0xAA,0x8B,0x5E,0x64,0xF7,0xC3,0x00,0x20,0x75,
+0x03,0xE9,0x40,0xFF,0xF7,0xC3,0x40,0x00,0x74,0x08,0x8A,0x86,0xC1,0x00,0xAA,0xE9,
+0x32,0xFF,0xB8,0x32,0x00,0xEB,0xA3,0x90,0x8B,0x5E,0x66,0x89,0x5E,0x68,0x83,0xC3,
+0x08,0x80,0xE3,0xF8,0x89,0x5E,0x66,0x8B,0x5E,0x64,0x81,0xE3,0x00,0x18,0x81,0xFB,
+0x00,0x18,0x74,0x2D,0xAA,0x85,0xDB,0x74,0x25,0xF7,0x46,0x64,0x40,0x00,0x75,0x18,
+0x81,0xFB,0x00,0x10,0x74,0x0C,0x8B,0x46,0x66,0x2B,0x46,0x68,0xC1,0xE0,0x04,0xE9,
+0x68,0xFF,0xB8,0x64,0x00,0xE9,0x62,0xFF,0x8A,0x86,0xC1,0x00,0xAA,0xAA,0xE9,0xE3,
+0xFE,0x51,0x8B,0x4E,0x66,0x2B,0x4E,0x68,0xB0,0x20,0xF3,0xAA,0x59,0xE9,0xD4,0xFE,
+0x8B,0x5E,0x66,0x89,0x5E,0x68,0x8B,0x5E,0x64,0xF7,0xC3,0x24,0x00,0x74,0x10,0xC7,
+0x46,0x66,0x00,0x00,0xF7,0xC3,0x04,0x00,0x74,0x05,0xB0,0x0D,0xAA,0xB0,0x0A,0xAA,
+0xEB,0x48,0x90,0x90,0xAA,0xF7,0x46,0x64,0x00,0x40,0x74,0x06,0xB8,0xD0,0x07,0xE9,
+0x18,0xFF,0xE9,0x9F,0xFE,0x90,0xAA,0xF7,0x46,0x64,0x00,0x80,0x74,0x06,0xB8,0xD0,
+0x07,0xE9,0x06,0xFF,0xE9,0x8D,0xFE,0x90,0x8B,0x5E,0x66,0x89,0x5E,0x68,0x85,0xDB,
+0x75,0x0C,0x8B,0x5E,0x64,0xF7,0xC3,0x10,0x00,0x74,0x06,0xE9,0x76,0xFE,0x8B,0x5E,
+0x64,0xF7,0xC3,0x08,0x00,0x74,0x27,0xB0,0x0A,0xAA,0xF7,0xC3,0x20,0x00,0x75,0x1F,
+0xF7,0xC3,0x00,0x01,0x75,0x03,0xE9,0x5B,0xFE,0xF7,0xC3,0x40,0x00,0x75,0x06,0xB8,
+0x64,0x00,0xE9,0xC5,0xFE,0x8A,0x86,0xC1,0x00,0xAA,0xAA,0xE9,0x46,0xFE,0xAA,0xC7,
+0x46,0x66,0x00,0x00,0xF7,0xC3,0x00,0x06,0x74,0xF1,0xF7,0xC3,0x40,0x00,0x74,0x19,
+0x8A,0x86,0xC1,0x00,0x81,0xE3,0x00,0x06,0x81,0xFB,0x00,0x04,0x72,0x06,0x76,0x02,
+0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xE9,0x1B,0xFE,0x81,0xE3,0x00,0x06,0x81,0xFB,0x00,
+0x04,0x72,0x0E,0x76,0x06,0xB8,0x96,0x00,0xE9,0x7F,0xFE,0xB8,0x64,0x00,0xE9,0x79,
+0xFE,0x8B,0x46,0x68,0xE9,0x73,0xFE,0x90,0x36,0x8B,0x0E,0xDA,0x12,0x83,0xF9,0x32,
+0x73,0x1D,0x1E,0x06,0x33,0xC0,0x8E,0xD8,0x8E,0xC0,0x8D,0x76,0x4C,0xBF,0xDC,0x12,
+0x03,0xF9,0xA5,0xA5,0xA5,0x83,0xC1,0x06,0x89,0x0E,0xDA,0x12,0x07,0x1F,0xC3,0xB0,
+0x08,0xE8,0x6E,0xCD,0xC3,0x90,0x83,0x66,0x48,0xFE,0xE8,0x93,0xC4,0xE8,0xC8,0xFF,
+0xC3,0xF6,0x46,0x27,0x02,0x75,0x0F,0x9C,0xFA,0x83,0x7E,0x1A,0x00,0x74,0x09,0x80,
+0x4E,0x27,0x01,0x9D,0xF9,0xC3,0xF8,0xC3,0x50,0x52,0xF7,0x46,0x38,0x40,0x00,0x74,
+0x1D,0xE8,0x34,0xDE,0x83,0xC2,0x0A,0xEC,0xA8,0x40,0x75,0x27,0x83,0xEA,0x08,0x8A,
+0x86,0xA5,0x00,0x0C,0x02,0x88,0x86,0xA5,0x00,0xEE,0x5A,0x58,0xEB,0xD1,0xE8,0x0C,
+0xDE,0x8A,0x86,0xA5,0x00,0x24,0xFB,0x0C,0x02,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x5A,
+0x58,0xEB,0xBC,0x80,0x4E,0x27,0x02,0x5A,0x58,0x9D,0xF8,0xC3,0x08,0x46,0x26,0x9C,
+0xFA,0x8A,0x8E,0xA5,0x00,0xF7,0x46,0x38,0x40,0x00,0x75,0x14,0xF6,0xC1,0x06,0x74,
+0x23,0xE8,0xD9,0xDD,0x8A,0xC1,0x24,0xF9,0x88,0x86,0xA5,0x00,0xE6,0x0C,0x9D,0xC3,
+0xF6,0xC1,0x02,0x74,0x0F,0xE8,0xD0,0xDD,0x83,0xC2,0x02,0x8A,0xC1,0x24,0xFD,0x88,
+0x86,0xA5,0x00,0xEE,0x9D,0xC3,0x8B,0x5E,0x26,0x22,0xC3,0x88,0x46,0x26,0x74,0x01,
+0xC3,0x80,0x66,0x27,0xFD,0x9C,0xFA,0x8A,0x8E,0xA5,0x00,0xF7,0x46,0x38,0x40,0x00,
+0x75,0x16,0xF6,0xC1,0x04,0x75,0x0F,0xE8,0x93,0xDD,0x8A,0xC1,0x24,0xFD,0x0C,0x04,
+0x88,0x86,0xA5,0x00,0xE6,0x0C,0x9D,0xC3,0xF6,0xC1,0x02,0x75,0xF9,0xE8,0x88,0xDD,
+0x83,0xC2,0x0A,0xEC,0xA8,0x20,0x75,0x0E,0x83,0xEA,0x08,0x8A,0xC1,0x0C,0x02,0x88,
+0x86,0xA5,0x00,0xEE,0x9D,0xC3,0x83,0xEA,0x0A,0x33,0xC9,0x8A,0x4E,0x1C,0x8B,0x46,
+0x1A,0x3B,0xC8,0x73,0x1B,0x01,0x4E,0x2A,0x2B,0xC1,0x89,0x46,0x1A,0x1E,0xC5,0x76,
+0x00,0xF3,0x6E,0x1F,0x89,0x76,0x00,0x83,0xC2,0x02,0x8A,0x86,0xA5,0x00,0xEB,0xCD,
+0x85,0xC0,0x74,0x12,0x01,0x46,0x2A,0x8B,0xC8,0x1E,0xC5,0x76,0x00,0xF3,0x6E,0x1F,
+0x89,0x76,0x00,0x89,0x4E,0x1A,0xF6,0xC7,0x01,0x75,0x23,0x80,0xCB,0x02,0x89,0x5E,
+0x26,0xE8,0x08,0xC3,0x83,0xC2,0x02,0x8A,0x86,0xA5,0x00,0x24,0xFD,0xEE,0x88,0x86,
+0xA5,0x00,0xF6,0xC7,0x10,0x75,0x05,0xB0,0x02,0xE8,0x16,0xCC,0x9D,0xC3,0x83,0xC2,
+0x02,0x8A,0x86,0xA5,0x00,0xEB,0x86,0x90,0x8B,0xD1,0x8B,0x46,0x24,0x3B,0xC8,0x76,
+0x02,0x8B,0xC8,0x2B,0xD1,0x2B,0xC1,0x8B,0xD9,0xE3,0x22,0x80,0x66,0x27,0xFD,0x8E,
+0x46,0x02,0x8B,0x7E,0x22,0xF7,0xC6,0x01,0x00,0x74,0x02,0xA4,0x49,0xD1,0xE9,0xF3,
+0xA5,0x73,0x01,0xA4,0x89,0x7E,0x22,0x89,0x46,0x24,0x01,0x5E,0x1A,0x8B,0xCA,0x80,
+0x7E,0x26,0x02,0x74,0x05,0x80,0x66,0x26,0xFD,0xC3,0x60,0xB0,0xFD,0xE8,0xF6,0xFE,
+0x61,0xC3,0x50,0xE4,0x0A,0x84,0xC0,0x75,0x0A,0x86,0x86,0xA1,0x00,0x84,0xC0,0x74,
+0x0A,0xE6,0x0A,0x58,0x0C,0x20,0x89,0x46,0x48,0xF9,0xC3,0x58,0x24,0xDF,0x89,0x46,
+0x48,0xF8,0xC3,0x90,0xFB,0xB0,0x02,0xE8,0xE8,0x07,0xFA,0xE8,0x2E,0x01,0xFB,0xB0,
+0x01,0xE8,0xDE,0x07,0xFA,0xB0,0x02,0xE8,0xBC,0xCB,0xFB,0x85,0xED,0x74,0xE5,0xFA,
+0x8E,0x5E,0x0A,0xFB,0x90,0xFA,0x8B,0x46,0x48,0x8B,0x76,0x40,0xA8,0x8C,0x75,0xDE,
+0xA8,0x20,0x74,0x1A,0x50,0xE8,0x55,0xDC,0x58,0xE8,0xA6,0xFF,0x73,0x10,0xB0,0x02,
+0xE8,0x5F,0xCB,0xEB,0xC9,0x90,0x25,0xFF,0x00,0x8B,0xC8,0xEB,0x36,0x90,0xA8,0x01,
+0x75,0x22,0x46,0x83,0xE6,0xFE,0x3B,0x76,0x08,0x74,0x79,0xAD,0x8A,0xFC,0xB3,0xF0,
+0x22,0xFB,0x3A,0xFB,0x74,0xE0,0x3A,0xBE,0xA0,0x00,0x74,0x2E,0xE8,0xD2,0xFD,0x73,
+0x77,0xEB,0x9B,0x90,0x8A,0xE0,0x24,0xFC,0x88,0x46,0x48,0x8B,0x4E,0x4A,0xF6,0xC4,
+0x02,0x74,0x1D,0xE8,0xBB,0xFD,0x72,0x86,0xE8,0x13,0xF3,0x89,0x76,0x40,0xE3,0x93,
+0x83,0x4E,0x48,0x03,0x89,0x4E,0x4A,0xE9,0x74,0xFF,0x25,0xFF,0x0F,0x8B,0xC8,0x90,
+0x8B,0x86,0x98,0x00,0x85,0xC0,0x74,0x1A,0x51,0x8A,0x8E,0xA0,0x00,0xC0,0xE9,0x04,
+0xBA,0x01,0x00,0xD3,0xE2,0x59,0x23,0xC2,0x74,0x08,0x03,0xF1,0x89,0x76,0x40,0xE9,
+0x61,0xFF,0xFF,0x56,0x62,0xE3,0xF5,0x83,0x4E,0x48,0x01,0x89,0x4E,0x4A,0x89,0x76,
+0x40,0xE9,0x3A,0xFF,0x81,0x4E,0x26,0x00,0x10,0x8B,0x46,0x50,0x3B,0x46,0x46,0x77,
+0x03,0xE8,0x52,0xFD,0xE9,0x27,0xFF,0x90,0x88,0xBE,0xA0,0x00,0xEB,0xAC,0x0A,0x06,
+0x90,0x12,0x8A,0xE0,0xBA,0x06,0x01,0xB0,0x04,0xEE,0xEC,0x84,0xC0,0x75,0x12,0xB0,
+0x04,0xEE,0x8A,0xC4,0xEE,0x32,0xE4,0xA8,0x80,0x74,0x06,0xC7,0x06,0x84,0x12,0x00,
+0x00,0x88,0x26,0x90,0x12,0xC3,0x0A,0x06,0x90,0x12,0x8A,0xE0,0xBA,0x06,0x01,0xEC,
+0xA8,0x01,0x75,0xED,0xBA,0x08,0x01,0x8A,0xC4,0xEE,0x32,0xE4,0xA8,0x80,0x74,0xE1,
+0xC7,0x06,0x84,0x12,0x00,0x00,0x88,0x26,0x90,0x12,0xC3,0x90,0x36,0xF7,0x06,0x24,
+0x01,0x01,0x00,0x75,0x30,0x36,0x8B,0x0E,0xDA,0x12,0x80,0xF9,0x36,0x73,0x26,0x33,
+0xC0,0x8E,0xC0,0x8E,0xD8,0xBF,0xDC,0x12,0x03,0xF9,0xB0,0x08,0xE8,0x77,0xCA,0x85,
+0xED,0x74,0x0E,0x8D,0x76,0x4C,0xA5,0xA5,0xA5,0x80,0xC1,0x06,0x80,0xF9,0x36,0x72,
+0xE9,0x89,0x0E,0xDA,0x12,0xC3,0xC3,0x90,0xF7,0x06,0x26,0x01,0x01,0x00,0x75,0xF6,
+0x8B,0x0E,0x20,0x13,0x85,0xC9,0x75,0xEE,0x33,0xC0,0x8E,0xC0,0x8E,0xD8,0xBF,0x24,
+0x13,0xB9,0x36,0x00,0xB0,0x0A,0xE8,0x3D,0xCA,0x85,0xED,0x75,0x06,0xE9,0x12,0x01,
+0xE9,0x0A,0x01,0x33,0xDB,0x8A,0x46,0x4C,0x8A,0xA6,0xB3,0x00,0xFE,0xCC,0x78,0x0E,
+0x88,0xA6,0xB3,0x00,0x0A,0xDC,0xB4,0x0A,0xAB,0x83,0xE9,0x02,0x76,0xE2,0x8A,0xA6,
+0xB2,0x00,0xFE,0xCC,0x78,0x0E,0x88,0xA6,0xB2,0x00,0x0A,0xDC,0xB4,0x08,0xAB,0x83,
+0xE9,0x02,0x76,0xCC,0x8A,0xA6,0xB1,0x00,0xFE,0xCC,0x78,0x18,0x8A,0xBE,0xB0,0x00,
+0x75,0x04,0x88,0xA6,0xB0,0x00,0x88,0xA6,0xB1,0x00,0x0A,0xDC,0x8A,0xE7,0xAB,0x83,
+0xE9,0x02,0x76,0xAC,0x8A,0xA6,0xB4,0x00,0xFE,0xCC,0x78,0x1F,0x88,0xA6,0xB4,0x00,
+0x0A,0xDC,0xB4,0x0B,0xAB,0x8A,0x86,0xBC,0x00,0x8A,0xA6,0xBD,0x00,0xAB,0x8B,0x86,
+0xBE,0x00,0xAB,0x83,0xE9,0x06,0x76,0x88,0x8A,0x46,0x4C,0x8A,0xA6,0xB6,0x00,0xFE,
+0xCC,0x78,0x19,0x88,0xA6,0xB6,0x00,0x0A,0xDC,0xB4,0x0C,0xAB,0xE8,0xDB,0xCB,0xAB,
+0x8B,0x46,0x2A,0xAB,0x83,0xE9,0x06,0x76,0x74,0x8A,0x46,0x4C,0x8A,0xA6,0xB7,0x00,
+0xFE,0xCC,0x78,0x19,0x88,0xA6,0xB7,0x00,0x0A,0xDC,0xB4,0x0D,0xAB,0xE8,0xBA,0xCB,
+0xAB,0x8B,0x46,0x34,0xAB,0x83,0xE9,0x06,0x76,0x53,0x8A,0x46,0x4C,0x8A,0xA6,0xB8,
+0x00,0xFE,0xCC,0x78,0x19,0x88,0xA6,0xB8,0x00,0x0A,0xDC,0xB4,0x0E,0xAB,0xA1,0x50,
+0x12,0xAB,0xA1,0x52,0x12,0xAB,0x83,0xE9,0x06,0x76,0x32,0x8A,0x46,0x4C,0x8A,0xA6,
+0xB5,0x00,0xFE,0xCC,0x78,0x18,0x88,0xA6,0xB5,0x00,0x0A,0xDC,0xB4,0x0F,0xAB,0x8B,
+0x86,0x9A,0x00,0xAB,0x8B,0x86,0x9C,0x00,0xAB,0x83,0xE9,0x06,0x76,0x0F,0x84,0xDB,
+0x75,0x03,0xE9,0xEF,0xFE,0xB0,0x0A,0xE8,0xF8,0xC8,0xE9,0xE7,0xFE,0xB0,0x0A,0xE8,
+0xF0,0xC8,0xF7,0xD9,0x83,0xC1,0x36,0x8B,0xC1,0x0D,0x80,0x00,0x86,0xC4,0xA3,0x22,
+0x13,0x41,0x41,0x89,0x0E,0x20,0x13,0xC3,0xA1,0x84,0x12,0x2B,0xC1,0x72,0x11,0xA3,
+0x84,0x12,0xBE,0x22,0x13,0xD1,0xE9,0xF3,0x6F,0x90,0x89,0x0E,0x20,0x13,0xF8,0xC3,
+0xF9,0xC3,0xC3,0x81,0xEF,0x6A,0x13,0x74,0xF9,0x8B,0xC7,0x0D,0x80,0x00,0x86,0xC4,
+0xA3,0x68,0x13,0x47,0x47,0x89,0x3E,0x66,0x13,0xC3,0xF7,0x06,0x2A,0x01,0x01,0x00,
+0x75,0xE0,0x8B,0x0E,0x66,0x13,0xE3,0x07,0x80,0xF9,0x20,0x77,0xD5,0x49,0x49,0x33,
+0xC0,0x8E,0xC0,0x8E,0xD8,0xBF,0x6A,0x13,0x8B,0xF7,0x03,0xF9,0x83,0xC6,0x34,0x3B,
+0xFE,0x77,0xC0,0xB0,0x0E,0xE8,0xAE,0xC8,0x85,0xED,0x74,0xB7,0x8A,0x46,0x4C,0x8A,
+0xB6,0xB9,0x00,0xFE,0xCE,0x78,0x15,0x88,0xB6,0xB9,0x00,0x8A,0xA6,0xA9,0x00,0x80,
+0xCC,0xC0,0xAB,0x84,0xF6,0x74,0x05,0xB0,0x0E,0xE8,0x56,0xC8,0x8A,0xB6,0xBA,0x00,
+0xFE,0xCE,0x78,0xCB,0x8A,0x9E,0xA9,0x00,0x8A,0xBE,0xAB,0x00,0x8A,0x56,0x3F,0x8A,
+0xF3,0x32,0xF7,0x0A,0xB6,0xAC,0x00,0xC6,0x86,0xAC,0x00,0x00,0x22,0xF2,0x74,0x4B,
+0xF6,0xC6,0x08,0x74,0x0F,0xB4,0x02,0xF6,0xC3,0x08,0x75,0x02,0xB4,0x03,0xAB,0x80,
+0xE6,0xF7,0x74,0x37,0xF6,0xC6,0x01,0x74,0x0F,0xB4,0x00,0xF6,0xC3,0x01,0x75,0x02,
+0xB4,0x01,0xAB,0x80,0xE6,0xFE,0x74,0x23,0xF6,0xC6,0x02,0x74,0x0F,0xB4,0x04,0xF6,
+0xC3,0x02,0x75,0x02,0xB4,0x05,0xAB,0x80,0xE6,0xFD,0x74,0x0F,0xF6,0xC6,0x04,0x74,
+0x0A,0xB4,0x06,0xF6,0xC3,0x04,0x75,0x02,0xB4,0x07,0xAB,0xC6,0x86,0xBA,0x00,0x00,
+0x88,0x9E,0xAB,0x00,0xE9,0x58,0xFF,0x90,0xA1,0x84,0x12,0x2B,0xC1,0x72,0x11,0xA3,
+0x84,0x12,0xBE,0x68,0x13,0xD1,0xE9,0xF3,0x6F,0x90,0x89,0x0E,0x66,0x13,0xF8,0xC3,
+0xF9,0xC3,0xA1,0x84,0x12,0x41,0x41,0x2B,0xC1,0x72,0x23,0xA3,0x84,0x12,0x8B,0xC1,
+0x48,0x48,0x32,0xE4,0x0C,0x80,0x86,0xC4,0xEF,0x90,0x90,0x90,0x90,0x90,0xBE,0xDC,
+0x12,0x49,0x49,0xD1,0xE9,0xF3,0x6F,0x90,0x89,0x0E,0xDA,0x12,0xF8,0xC3,0xF9,0xC3,
+0x8A,0xC8,0x8A,0x46,0x4C,0xB4,0x01,0x83,0xEB,0x06,0xEF,0x90,0x90,0x90,0x90,0x90,
+0xB8,0x01,0x00,0xEF,0x90,0x90,0x90,0x90,0x90,0x8A,0xC1,0xEF,0x90,0x90,0x90,0x90,
+0x90,0xE9,0x97,0x00,0xE9,0xAC,0x00,0x33,0xC0,0x8E,0xD8,0x89,0x1E,0x84,0x12,0xC3,
+0x36,0x8B,0x1E,0x84,0x12,0xFB,0x90,0xFA,0xB0,0x0C,0xE8,0x89,0xC7,0x85,0xED,0x74,
+0xE6,0xC5,0x76,0x0C,0x83,0xFB,0x14,0x72,0xDB,0xFB,0x90,0xFA,0xAD,0x85,0xC0,0x78,
+0xAF,0x74,0xE2,0x8B,0xFE,0x03,0xF8,0x36,0x8B,0x0E,0x86,0x12,0x3B,0xC1,0x77,0x02,
+0x8B,0xC8,0x83,0xEB,0x04,0x3B,0xD9,0x77,0x02,0x8B,0xCB,0x33,0xC0,0x8A,0x46,0x4C,
+0xEF,0x90,0x90,0x90,0x90,0x90,0x8B,0xC1,0xEF,0x90,0x90,0x90,0x90,0x90,0x41,0x80,
+0xE1,0xFE,0x2B,0xD9,0x51,0xD1,0xE9,0xF3,0x6F,0x90,0x59,0x8B,0xC7,0x40,0x24,0xFE,
+0x3B,0xC6,0x74,0x27,0x2B,0xFE,0x4E,0x4E,0x53,0x8B,0x5E,0x10,0x3B,0xF3,0x72,0x13,
+0x03,0x1F,0x83,0xC3,0x03,0x80,0xE3,0xFE,0xC7,0x07,0x00,0x00,0x83,0x6E,0x74,0x02,
+0x89,0x5E,0x10,0x5B,0x89,0x3C,0x89,0x76,0x0C,0xEB,0x89,0x89,0x76,0x0C,0x39,0x76,
+0x10,0x77,0x81,0x72,0x08,0x83,0x3C,0x00,0x74,0x03,0xE9,0x77,0xFF,0xE8,0x0D,0xBE,
+0xE9,0x62,0xFF,0x36,0x89,0x1E,0x84,0x12,0xB0,0x0C,0xE8,0xB5,0xC6,0x33,0xC0,0x8E,
+0xD8,0xC3,0xA1,0x84,0x12,0x3D,0x10,0x00,0x72,0x77,0xBA,0x04,0x01,0x3B,0x06,0x88,
+0x12,0x75,0x06,0xC7,0x06,0x7E,0x12,0x00,0x00,0x8B,0x0E,0xDA,0x12,0xE3,0x0B,0xE8,
+0xD0,0xFE,0x72,0x57,0xC7,0x06,0x7E,0x12,0xFF,0x7F,0x8B,0x0E,0x20,0x13,0xE3,0x0B,
+0xE8,0xA5,0xFD,0x72,0x46,0xC7,0x06,0x7E,0x12,0xFF,0x7F,0x8B,0x0E,0x66,0x13,0xE3,
+0x0B,0xE8,0x94,0xFE,0x72,0x35,0xC7,0x06,0x7E,0x12,0xFF,0x7F,0xA1,0x28,0x01,0xA9,
+0x01,0x00,0x75,0x03,0xE8,0xF9,0xFE,0x80,0x3E,0x8D,0x12,0x00,0x75,0x1D,0xA1,0x84,
+0x12,0x3D,0x20,0x00,0x76,0x15,0x3B,0x06,0x82,0x12,0x76,0x09,0xA1,0x7E,0x12,0x3B,
+0x06,0x80,0x12,0x72,0x0C,0x80,0x0E,0x90,0x12,0x80,0xC3,0xB0,0x80,0xFF,0x16,0x7C,
+0x12,0xC3,0x80,0x0E,0x90,0x12,0x40,0xC3,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x17,
+0x9C,0x0E,0xE8,0xB7,0xC8,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x20,0x9C,0x0E,0xE8,
+0xAA,0xC8,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x16,0x9C,0x0E,0xE8,0x9D,0xC8,0x90,
+0xBA,0x06,0x01,0xEC,0xA8,0x20,0x75,0xCA,0xFB,0x90,0xFA,0xBA,0x04,0x01,0xED,0x90,
+0x90,0x90,0x90,0x90,0x3A,0x06,0x94,0x12,0x77,0xBE,0x33,0xDB,0x8A,0xD8,0xD1,0xE3,
+0x2E,0x8B,0xAF,0x44,0x00,0xC4,0x7E,0x08,0x85,0xFF,0x74,0xB9,0xF6,0xC4,0xC0,0x75,
+0x55,0x32,0xC0,0xC1,0xE0,0x02,0x80,0xE4,0xF0,0x8B,0xF0,0xED,0x90,0x90,0x90,0x90,
+0x90,0x85,0xC0,0x74,0xBB,0x8B,0xC8,0x41,0x80,0xE1,0xFE,0x0B,0xC6,0x8B,0x5E,0x50,
+0x4B,0x4B,0x2B,0xD9,0x78,0x9C,0xAB,0x8B,0xC1,0x40,0x40,0x01,0x46,0x4E,0xD1,0xE9,
+0xF3,0x6D,0x90,0x89,0x5E,0x50,0x89,0x7E,0x08,0x8B,0x46,0x26,0x80,0xE4,0xEF,0x89,
+0x46,0x26,0xF6,0xC4,0x01,0x75,0x0C,0xF7,0x46,0x48,0x0C,0x00,0x75,0x05,0xB0,0x02,
+0xE8,0x7F,0xC5,0xE9,0x7A,0xFF,0x86,0xC4,0x8B,0xC8,0x83,0xE1,0x3F,0x41,0x80,0xE1,
+0xFE,0xE3,0x0A,0x3C,0x80,0x72,0x09,0x24,0x3F,0xB4,0xF0,0xEB,0xB0,0xE9,0x60,0xFF,
+0x25,0x3F,0x00,0x33,0xFF,0x8E,0xC7,0xBF,0x96,0x12,0x8B,0xF7,0xD1,0xE9,0xF3,0x6D,
+0x90,0x8B,0xC8,0xE8,0x48,0xED,0xE9,0x47,0xFF,0x90,0x6A,0x00,0x1F,0xC6,0x06,0x93,
+0x12,0x1B,0x9C,0x0E,0xE8,0xD5,0xC7,0x90,0x60,0x1E,0x06,0x33,0xC0,0x8E,0xD8,0x8E,
+0xC0,0xBA,0x06,0x01,0xEC,0xA8,0x04,0x74,0xE1,0xB0,0x06,0xEE,0xEC,0xA2,0x8C,0x12,
+0xA8,0x40,0x74,0x11,0xA1,0x88,0x12,0xA3,0x84,0x12,0xC6,0x06,0x8D,0x12,0x00,0xE8,
+0x60,0xFE,0xA0,0x8C,0x12,0xA8,0x80,0x74,0x03,0xE8,0x04,0xFF,0xB8,0x00,0x80,0xBA,
+0x22,0xFF,0xEF,0x07,0x1F,0x61,0xCF,0x90,0x6A,0x00,0x1F,0xC6,0x06,0x93,0x12,0x1B,
+0x9C,0x0E,0xE8,0x87,0xC7,0x90,0x60,0x1E,0x06,0x33,0xC0,0x8E,0xD8,0x8E,0xC0,0xBA,
+0x06,0x01,0xEC,0xA8,0x04,0x74,0xE1,0xBA,0x08,0x01,0xEC,0xA2,0x8C,0x12,0xA8,0x40,
+0x74,0x11,0xA1,0x88,0x12,0xA3,0x84,0x12,0xC6,0x06,0x8D,0x12,0x00,0xE8,0x12,0xFE,
+0xA0,0x8C,0x12,0xA8,0x80,0x74,0x03,0xE8,0xB6,0xFE,0xB8,0x00,0x80,0xBA,0x22,0xFF,
+0xEF,0x07,0x1F,0x61,0xCF,0x90,0xEE,0x86,0xE0,0xEE,0x86,0xE0,0xEC,0x86,0xE0,0xEC,
+0x86,0xE0,0x80,0xE1,0xFE,0xF3,0x6C,0x90,0x80,0xE1,0xFE,0xF3,0x6E,0x90,0x05,0x00,
+0x75,0x47,0xA8,0x4B,0x05,0x00,0x75,0x48,0xA8,0x4B,0x05,0x00,0xA3,0x48,0xA8,0x4B,
+0x05,0x00,0x35,0x49,0xA8,0x4B,0x06,0x00,0x98,0x48,0x96,0x4B,0x06,0x00,0xBA,0x48,
+0x96,0x4B,0x06,0x00,0xC3,0x48,0x96,0x4B,0x06,0x00,0xCB,0x48,0x96,0x4B,0x06,0x00,
+0x20,0x49,0x96,0x4B,0x06,0x00,0x28,0x49,0x96,0x4B,0x06,0x00,0x4E,0x4A,0x9C,0x4B,
+0x06,0x00,0x7B,0x4A,0x9C,0x4B,0x05,0x00,0x9E,0x4A,0xA2,0x4B,0x05,0x00,0xEC,0x4A,
+0xA2,0x4B,0x00,0x00,0x1E,0x06,0x83,0x3E,0x44,0x12,0x00,0x74,0x09,0xA0,0x06,0x01,
+0x24,0x30,0x3C,0x30,0x74,0x1A,0x8C,0xC8,0x8E,0xD8,0x8E,0xC0,0xBB,0xAE,0x4B,0x8B,
+0x0F,0xE3,0x0D,0x8B,0x7F,0x02,0x8B,0x77,0x04,0xF3,0xA4,0x83,0xC3,0x06,0xEB,0xEF,
+0x07,0x1F,0xC3,0x90,0x33,0xC0,0xA3,0x3E,0x01,0xB9,0x0C,0x01,0xBE,0x40,0x01,0x8B,
+0xFE,0x81,0xC6,0xB4,0x0F,0x89,0x04,0x8B,0xC6,0x2B,0xF1,0x3B,0xC7,0x77,0xF6,0xA3,
+0x3C,0x01,0xC3,0x90,0x1E,0x06,0x60,0x36,0x8B,0x2E,0x3E,0x01,0x8B,0x5E,0x00,0x3B,
+0xEB,0x74,0x2B,0x8B,0x76,0x02,0x89,0x1C,0x89,0x77,0x02,0x36,0xA1,0x3C,0x01,0x89,
+0x46,0x00,0x36,0x89,0x2E,0x3C,0x01,0x8B,0xEB,0xFF,0x4E,0x06,0x74,0x08,0x8B,0x6E,
+0x00,0xFF,0x4E,0x06,0x75,0xF8,0x36,0x89,0x2E,0x3E,0x01,0x8B,0x66,0x04,0x61,0x07,
+0x1F,0xC3,0x1E,0x06,0x60,0x36,0x8B,0x2E,0x3E,0x01,0x98,0x89,0x46,0x06,0x89,0x66,
+0x04,0x3B,0x6E,0x00,0x74,0x10,0x8B,0x6E,0x00,0xFF,0x4E,0x06,0x75,0xF8,0x36,0x89,
+0x2E,0x3E,0x01,0x8B,0x66,0x04,0x61,0x07,0x1F,0xC3,0xC3,0x90,0x1E,0x06,0x60,0x9C,
+0xFA,0x33,0xED,0x8E,0xDD,0x8B,0x2E,0x3C,0x01,0x85,0xED,0x74,0x3D,0x8B,0x4E,0x00,
+0x89,0x0E,0x3C,0x01,0x8B,0xCC,0x8D,0xA6,0x0A,0x01,0x56,0x1E,0x06,0x60,0x89,0x66,
+0x04,0xC7,0x46,0x08,0x0F,0x1A,0xC7,0x46,0x06,0x01,0x00,0x8B,0x1E,0x3E,0x01,0x85,
+0xDB,0x74,0x1D,0x8B,0xC5,0x87,0x07,0x89,0x46,0x00,0x89,0x5E,0x02,0x8B,0xD8,0x89,
+0x6F,0x02,0x8B,0xE1,0x9D,0x61,0x07,0x1F,0xF8,0xC3,0x9D,0x61,0x07,0x1F,0xF9,0xC3,
+0x89,0x2E,0x3E,0x01,0x89,0x6E,0x00,0x89,0x6E,0x02,0x87,0xE1,0x9D,0x8B,0xE1,0xEB,
+0xE4,0x00,0x0D,0x0A,0x54,0x65,0x72,0x6D,0x69,0x6E,0x61,0x6C,0x73,0x20,0x73,0x75,
+0x70,0x70,0x6F,0x72,0x74,0x65,0x64,0x3A,0x0D,0x0A,0x31,0x29,0x20,0x41,0x4E,0x53,
+0x49,0x20,0x63,0x6F,0x6D,0x70,0x61,0x74,0x69,0x62,0x6C,0x65,0x0D,0x0A,0x32,0x29,
+0x20,0x57,0x79,0x73,0x65,0x20,0x33,0x30,0x0D,0x0A,0x50,0x6C,0x65,0x61,0x73,0x65,
+0x20,0x73,0x65,0x6C,0x65,0x63,0x74,0x3A,0x20,0x00,0x0D,0x0A,0x63,0x6F,0x64,0x65,
+0x20,0x73,0x65,0x67,0x6D,0x65,0x6E,0x74,0x3D,0x00,0x0D,0x0A,0x4D,0x6F,0x6E,0x69,
+0x74,0x6F,0x72,0x20,0x76,0x32,0x2E,0x35,0x0A,0x0D,0x0A,0x3E,0x00,0x0D,0x0A,0x50,
+0x61,0x72,0x64,0x6F,0x6E,0x3F,0x00,0x0D,0x0A,0x4E,0x6F,0x20,0x61,0x64,0x64,0x72,
+0x65,0x73,0x73,0x20,0x73,0x70,0x65,0x63,0x69,0x66,0x69,0x65,0x64,0x00,0x0D,0x0A,
+0x3A,0x00,0x0D,0x0A,0x00,0x4C,0x6F,0x63,0x3D,0x00,0x0D,0x0A,0x46,0x41,0x54,0x41,
+0x4C,0x20,0x45,0x52,0x52,0x4F,0x52,0x3D,0x00,0x0D,0x0A,0x4D,0x6F,0x6E,0x69,0x74,
+0x6F,0x72,0x20,0x63,0x6F,0x6D,0x6D,0x61,0x6E,0x64,0x73,0x3A,0x2D,0x0D,0x0A,0x20,
+0x20,0x20,0x44,0x2C,0x64,0x5B,0x5B,0x78,0x78,0x78,0x78,0x3A,0x5D,0x78,0x78,0x78,
+0x78,0x5D,0x20,0x2D,0x20,0x64,0x75,0x6D,0x70,0x20,0x6D,0x65,0x6D,0x6F,0x72,0x79,
+0x0D,0x0A,0x20,0x20,0x20,0x4C,0x2C,0x6C,0x5B,0x5B,0x78,0x78,0x78,0x78,0x3A,0x5D,
+0x78,0x78,0x78,0x78,0x5D,0x20,0x2D,0x20,0x64,0x75,0x6D,0x70,0x20,0x73,0x69,0x6E,
+0x67,0x6C,0x65,0x20,0x6C,0x69,0x6E,0x65,0x0D,0x0A,0x20,0x20,0x20,0x45,0x2C,0x65,
+0x5B,0x5B,0x78,0x78,0x78,0x78,0x3A,0x5D,0x78,0x78,0x78,0x78,0x5D,0x20,0x2D,0x20,
+0x65,0x64,0x69,0x74,0x20,0x6D,0x65,0x6D,0x6F,0x72,0x79,0x0D,0x0A,0x20,0x20,0x20,
+0x46,0x2C,0x66,0x5B,0x5B,0x78,0x78,0x78,0x78,0x20,0x5D,0x78,0x78,0x78,0x78,0x5D,
+0x20,0x2D,0x20,0x66,0x69,0x6C,0x6C,0x20,0x6D,0x65,0x6D,0x6F,0x72,0x79,0x20,0x70,
+0x61,0x72,0x61,0x67,0x72,0x61,0x70,0x68,0x73,0x0D,0x0A,0x20,0x20,0x20,0x49,0x5B,
+0x78,0x78,0x78,0x78,0x5D,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,
+0x20,0x77,0x6F,0x72,0x64,0x20,0x69,0x6E,0x70,0x75,0x74,0x20,0x66,0x72,0x6F,0x6D,
+0x20,0x70,0x6F,0x72,0x74,0x0D,0x0A,0x20,0x20,0x20,0x69,0x5B,0x78,0x78,0x78,0x78,
+0x5D,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x62,0x79,0x74,
+0x65,0x20,0x69,0x6E,0x70,0x75,0x74,0x20,0x66,0x72,0x6F,0x6D,0x20,0x70,0x6F,0x72,
+0x74,0x0D,0x0A,0x20,0x20,0x20,0x4F,0x78,0x78,0x78,0x78,0x20,0x78,0x78,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x6F,0x75,0x74,0x70,0x75,0x74,0x20,
+0x77,0x6F,0x72,0x64,0x20,0x74,0x6F,0x20,0x70,0x6F,0x72,0x74,0x0D,0x0A,0x20,0x20,
+0x20,0x6F,0x78,0x78,0x78,0x78,0x20,0x78,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x2D,0x20,0x6F,0x75,0x74,0x70,0x75,0x74,0x20,0x62,0x79,0x74,0x65,0x20,
+0x74,0x6F,0x20,0x70,0x6F,0x72,0x74,0x0D,0x0A,0x20,0x20,0x20,0x47,0x5B,0x5B,0x78,
+0x78,0x78,0x78,0x3A,0x5D,0x78,0x78,0x78,0x78,0x5D,0x20,0x20,0x20,0x2D,0x20,0x67,
+0x6F,0x74,0x6F,0x20,0x61,0x64,0x64,0x72,0x65,0x73,0x73,0x0D,0x0A,0x20,0x20,0x20,
+0x57,0x5B,0x5B,0x78,0x78,0x78,0x78,0x3A,0x5D,0x78,0x78,0x78,0x78,0x5D,0x20,0x20,
+0x20,0x2D,0x20,0x77,0x61,0x74,0x63,0x68,0x20,0x61,0x20,0x77,0x6F,0x72,0x64,0x0D,
+0x0A,0x20,0x20,0x20,0x43,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x69,0x6E,0x74,0x65,0x72,0x72,0x75,0x70,0x74,
+0x73,0x20,0x6F,0x66,0x66,0x0D,0x0A,0x20,0x20,0x20,0x53,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x69,0x6E,0x74,
+0x65,0x72,0x72,0x75,0x70,0x74,0x73,0x20,0x6F,0x6E,0x0D,0x0A,0x20,0x20,0x20,0x73,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x2D,0x20,0x73,0x69,0x6E,0x67,0x6C,0x65,0x20,0x73,0x74,0x65,0x70,0x0D,0x0A,0x20,
+0x20,0x20,0x42,0x78,0x78,0x78,0x78,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x2D,0x20,0x62,0x72,0x65,0x61,0x6B,0x70,0x6F,0x69,0x6E,0x74,0x20,
+0x73,0x65,0x74,0x0D,0x0A,0x20,0x20,0x20,0x62,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x62,0x72,0x65,0x61,0x6B,
+0x70,0x6F,0x69,0x6E,0x74,0x20,0x63,0x6C,0x65,0x61,0x72,0x0D,0x0A,0x20,0x20,0x20,
+0x52,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x2D,0x20,0x72,0x65,0x73,0x74,0x61,0x72,0x74,0x20,0x62,0x72,0x65,0x61,0x6B,
+0x70,0x6F,0x69,0x6E,0x74,0x0D,0x0A,0x20,0x20,0x20,0x72,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x72,0x65,0x67,
+0x69,0x73,0x74,0x65,0x72,0x73,0x20,0x61,0x74,0x20,0x62,0x72,0x6B,0x70,0x74,0x0D,
+0x0A,0x20,0x20,0x20,0x58,0x2C,0x78,0x20,0x6E,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
+0x20,0x20,0x20,0x20,0x20,0x2D,0x20,0x65,0x78,0x61,0x6D,0x69,0x6E,0x65,0x20,0x63,
+0x68,0x61,0x6E,0x6E,0x65,0x6C,0x20,0x6E,0x0D,0x0A,0x20,0x20,0x20,0x48,0x2C,0x3F,
+0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2D,0x20,
+0x74,0x68,0x69,0x73,0x20,0x6D,0x65,0x73,0x73,0x61,0x67,0x65,0x00,0x1B,0x5B,0x32,
+0x4A,0x1B,0x5B,0x31,0x3B,0x31,0x48,0x41,0x4E,0x53,0x49,0x20,0x54,0x65,0x72,0x6D,
+0x69,0x6E,0x61,0x6C,0x0D,0x0A,0x0A,0x00,0x1B,0x5B,0x4B,0x00,0x1B,0x5B,0x4A,0x00,
+0x1B,0x5B,0x32,0x4A,0x1B,0x5B,0x31,0x3B,0x31,0x48,0x00,0x1B,0x5B,0x44,0x20,0x1B,
+0x5B,0x44,0x00,0x1B,0x5B,0x31,0x3B,0x37,0x32,0x48,0x00,0x1B,0x5B,0x00,0x3B,0x00,
+0x48,0x00,0x1B,0x5B,0x73,0x00,0x1B,0x5B,0x75,0x00,0x1B,0x7A,0x2B,0x0B,0x7F,0x1B,
+0x7A,0x2E,0x0C,0x7F,0x1B,0x7A,0x2D,0x08,0x7F,0x1B,0x7A,0x2C,0x0A,0x7F,0x1B,0x7A,
+0x22,0x08,0x7F,0x1A,0x57,0x79,0x73,0x65,0x20,0x33,0x30,0x20,0x54,0x65,0x72,0x6D,
+0x69,0x6E,0x61,0x6C,0x0D,0x0A,0x00,0x1B,0x54,0x00,0x1B,0x59,0x00,0x1A,0x00,0x1E,
+0x00,0x08,0x20,0x08,0x00,0x00,0x1B,0x3D,0x00,0x00,0x00,0x1B,0x46,0x00,0x0D,0x00,
+0x3F,0x44,0x64,0x45,0x65,0x46,0x66,0x47,0x67,0x48,0x68,0x49,0x69,0x4F,0x6F,0x43,
+0x63,0x53,0x73,0x42,0x62,0x52,0x72,0x57,0x77,0x58,0x78,0x4C,0x6C,0x3C,0x60,0xD4,
+0x57,0xD4,0x57,0x50,0x58,0x50,0x58,0xD6,0x59,0xD6,0x59,0xB4,0x59,0xB4,0x59,0x3C,
+0x60,0x3C,0x60,0x6C,0x57,0x48,0x57,0x26,0x57,0x06,0x57,0x90,0x57,0x90,0x57,0x98,
+0x57,0x48,0x5F,0x0C,0x5F,0x58,0x5F,0x33,0x5F,0x40,0x5F,0xA0,0x57,0xA0,0x57,0xFE,
+0x59,0xFE,0x59,0xDC,0x57,0xDC,0x57,0x88,0x61,0x98,0x61,0xC0,0x61,0xCC,0x61,0xD8,
+0x61,0xF6,0x61,0x02,0x62,0x22,0x62,0xF8,0x56,0x4A,0x62,0x58,0x62,0x60,0x59,0x20,
+0x20,0x66,0x6C,0x61,0x67,0x73,0x3D,0x00,0x20,0x20,0x61,0x78,0x3D,0x00,0x20,0x20,
+0x62,0x78,0x3D,0x00,0x20,0x20,0x63,0x78,0x3D,0x00,0x20,0x20,0x64,0x78,0x3D,0x00,
+0x20,0x20,0x63,0x73,0x3D,0x00,0x20,0x20,0x64,0x73,0x3D,0x00,0x20,0x20,0x65,0x73,
+0x3D,0x00,0x20,0x20,0x73,0x73,0x3D,0x00,0x20,0x20,0x64,0x69,0x3D,0x00,0x20,0x20,
+0x73,0x69,0x3D,0x00,0x20,0x20,0x62,0x70,0x3D,0x00,0x20,0x20,0x73,0x70,0x3D,0x00,
+0x20,0x20,0x69,0x70,0x3D,0x00,0x20,0x63,0x68,0x61,0x6E,0x65,0x6C,0x3D,0x00,0x20,
+0x20,0x20,0x20,0x73,0x65,0x67,0x3D,0x00,0x20,0x74,0x69,0x5F,0x73,0x74,0x72,0x3D,
+0x00,0x20,0x74,0x69,0x5F,0x74,0x6F,0x73,0x3D,0x00,0x20,0x74,0x69,0x5F,0x6D,0x61,
+0x78,0x3D,0x00,0x20,0x74,0x69,0x5F,0x62,0x61,0x73,0x3D,0x00,0x20,0x74,0x69,0x5F,
+0x73,0x69,0x7A,0x3D,0x00,0x20,0x74,0x69,0x5F,0x73,0x74,0x66,0x3D,0x00,0x20,0x74,
+0x69,0x5F,0x72,0x6F,0x6F,0x3D,0x00,0x20,0x74,0x69,0x5F,0x66,0x6C,0x67,0x3D,0x00,
+0x20,0x74,0x69,0x5F,0x74,0x6F,0x74,0x3D,0x00,0x20,0x72,0x69,0x5F,0x70,0x63,0x6E,
+0x3D,0x00,0x20,0x72,0x69,0x5F,0x73,0x74,0x72,0x3D,0x00,0x20,0x72,0x69,0x5F,0x73,
+0x74,0x66,0x3D,0x00,0x20,0x72,0x69,0x5F,0x72,0x6F,0x6F,0x3D,0x00,0x20,0x72,0x69,
+0x5F,0x62,0x61,0x73,0x3D,0x00,0x20,0x72,0x69,0x5F,0x73,0x69,0x7A,0x3D,0x00,0x20,
+0x72,0x69,0x5F,0x74,0x6F,0x74,0x3D,0x00,0x20,0x72,0x69,0x5F,0x6D,0x69,0x6E,0x3D,
+0x00,0x20,0x72,0x69,0x5F,0x66,0x6C,0x67,0x3D,0x00,0x20,0x72,0x69,0x5F,0x74,0x6F,
+0x73,0x3D,0x00,0x20,0x72,0x69,0x5F,0x74,0x68,0x72,0x3D,0x00,0x20,0x74,0x68,0x5F,
+0x73,0x74,0x66,0x3D,0x00,0x20,0x74,0x68,0x5F,0x73,0x74,0x72,0x3D,0x00,0x20,0x74,
+0x68,0x5F,0x62,0x61,0x73,0x3D,0x00,0x20,0x74,0x68,0x5F,0x73,0x69,0x7A,0x3D,0x00,
+0x20,0x74,0x68,0x5F,0x74,0x72,0x67,0x3D,0x00,0x20,0x74,0x68,0x5F,0x66,0x6C,0x67,
+0x3D,0x00,0x20,0x74,0x68,0x5F,0x63,0x6E,0x74,0x3D,0x00,0x20,0x72,0x68,0x5F,0x73,
+0x74,0x72,0x3D,0x00,0x20,0x72,0x68,0x5F,0x73,0x74,0x66,0x3D,0x00,0x20,0x72,0x68,
+0x5F,0x62,0x61,0x73,0x3D,0x00,0x20,0x72,0x68,0x5F,0x73,0x69,0x7A,0x3D,0x00,0x20,
+0x72,0x68,0x5F,0x73,0x70,0x61,0x3D,0x00,0x20,0x72,0x68,0x5F,0x61,0x73,0x6F,0x3D,
+0x00,0x20,0x72,0x68,0x5F,0x72,0x6F,0x6F,0x3D,0x00,0x20,0x72,0x68,0x5F,0x66,0x6C,
+0x67,0x3D,0x00,0x20,0x6D,0x5F,0x63,0x61,0x72,0x65,0x3D,0x00,0x20,0x70,0x74,0x5F,
+0x66,0x6C,0x6F,0x3D,0x00,0x20,0x61,0x73,0x5F,0x66,0x6C,0x6F,0x3D,0x00,0x20,0x72,
+0x6D,0x5F,0x66,0x6C,0x6F,0x3D,0x00,0x20,0x20,0x20,0x71,0x5F,0x69,0x6E,0x3D,0x00,
+0x20,0x20,0x71,0x5F,0x6F,0x75,0x74,0x3D,0x00,0x20,0x71,0x5F,0x64,0x72,0x61,0x6E,
+0x3D,0x00,0x20,0x20,0x71,0x5F,0x74,0x69,0x6D,0x3D,0x00,0x20,0x20,0x20,0x71,0x5F,
+0x66,0x63,0x3D,0x00,0x20,0x71,0x5F,0x73,0x74,0x61,0x74,0x3D,0x00,0x20,0x71,0x5F,
+0x64,0x61,0x74,0x61,0x3D,0x00,0x20,0x71,0x5F,0x6D,0x6F,0x64,0x6D,0x3D,0x00,0x20,
+0x68,0x61,0x6E,0x64,0x5F,0x6F,0x3D,0x00,0x20,0x68,0x61,0x6E,0x64,0x5F,0x62,0x3D,
+0x00,0x20,0x68,0x61,0x6E,0x64,0x5F,0x65,0x3D,0x00,0x20,0x68,0x61,0x6E,0x64,0x5F,
+0x69,0x3D,0x00,0x20,0x20,0x6F,0x70,0x6F,0x73,0x74,0x3D,0x00,0x20,0x20,0x74,0x69,
+0x6D,0x65,0x6F,0x3D,0x00,0x20,0x63,0x75,0x73,0x74,0x6D,0x31,0x3D,0x00,0x20,0x63,
+0x75,0x73,0x74,0x6D,0x32,0x3D,0x00,0x20,0x63,0x75,0x73,0x74,0x6D,0x64,0x3D,0x00,
+0x20,0x74,0x78,0x72,0x61,0x74,0x65,0x3D,0x00,0x20,0x72,0x78,0x72,0x61,0x74,0x65,
+0x3D,0x00,0x20,0x20,0x63,0x5F,0x6D,0x61,0x70,0x3D,0x00,0x20,0x63,0x5F,0x61,0x64,
+0x64,0x72,0x3D,0x00,0x20,0x63,0x5F,0x61,0x69,0x73,0x72,0x3D,0x00,0x20,0x63,0x5F,
+0x78,0x74,0x61,0x67,0x3D,0x00,0x20,0x63,0x5F,0x64,0x65,0x66,0x72,0x3D,0x00,0x20,
+0x63,0x5F,0x66,0x6C,0x73,0x68,0x3D,0x00,0x20,0x74,0x78,0x6D,0x61,0x78,0x73,0x3D,
+0x00,0x20,0x72,0x69,0x5F,0x65,0x6D,0x73,0x3D,0x00,0x20,0x20,0x63,0x5F,0x6C,0x73,
+0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x69,0x65,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,
+0x66,0x63,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x6D,0x63,0x72,0x3D,0x00,0x20,0x20,
+0x63,0x5F,0x6C,0x63,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x64,0x73,0x73,0x3D,0x00,
+0x20,0x63,0x5F,0x64,0x73,0x73,0x69,0x3D,0x00,0x20,0x63,0x5F,0x64,0x73,0x73,0x72,
+0x3D,0x00,0x20,0x20,0x63,0x5F,0x69,0x73,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x63,
+0x61,0x72,0x3D,0x00,0x20,0x20,0x63,0x5F,0x65,0x66,0x72,0x3D,0x00,0x20,0x63,0x5F,
+0x65,0x72,0x73,0x74,0x3D,0x00,0x20,0x63,0x5F,0x65,0x63,0x6E,0x74,0x3D,0x00,0x20,
+0x63,0x5F,0x62,0x72,0x6B,0x63,0x3D,0x00,0x20,0x63,0x5F,0x62,0x6F,0x6B,0x63,0x3D,
+0x00,0x20,0x63,0x5F,0x72,0x65,0x70,0x6C,0x3D,0x00,0x20,0x63,0x5F,0x63,0x63,0x73,
+0x72,0x3D,0x00,0x20,0x63,0x5F,0x73,0x74,0x74,0x31,0x3D,0x00,0x20,0x63,0x5F,0x73,
+0x74,0x74,0x32,0x3D,0x00,0x2B,0xC0,0x8E,0xD8,0x8E,0xC0,0xE8,0xC2,0x00,0xE8,0xE5,
+0x00,0xFA,0xBF,0x84,0x00,0xC7,0x05,0xDC,0x56,0x8C,0x4D,0x02,0xBF,0x0C,0x00,0xC7,
+0x05,0x6E,0x5E,0x8C,0x4D,0x02,0xBF,0x04,0x00,0xC7,0x05,0xBA,0x5E,0x8C,0x4D,0x02,
+0xE8,0xF1,0x00,0x90,0xE8,0x49,0x01,0xE8,0x16,0x00,0xF4,0x90,0xE8,0xE5,0x00,0xBE,
+0xBA,0x4D,0xE8,0x09,0x0C,0xA0,0x93,0x12,0xE8,0x5D,0x0C,0xE8,0xC2,0x09,0xEB,0xE4,
+0xE8,0xD5,0x0C,0xE8,0xC4,0x0C,0x0A,0xC0,0x74,0xF6,0x8B,0x1E,0xF8,0x79,0x3C,0x0D,
+0x74,0x2E,0x3C,0x08,0x74,0x17,0x3C,0x7F,0x74,0x13,0x83,0xFB,0x20,0x7F,0xE1,0x88,
+0x87,0xD6,0x79,0x43,0x89,0x1E,0xF8,0x79,0xE8,0x77,0x0C,0xEB,0xD3,0x0B,0xDB,0x74,
+0xCF,0x4B,0x89,0x1E,0xF8,0x79,0x8B,0x36,0x16,0x7A,0xE8,0xC1,0x0B,0xEB,0xC1,0x90,
+0xE8,0x02,0x00,0xEB,0xBB,0xC6,0x87,0xD6,0x79,0x00,0x0B,0xDB,0x74,0x1E,0xA0,0xD6,
+0x79,0xBF,0x60,0x51,0xB9,0x1D,0x00,0x8B,0xD9,0x06,0x0E,0x07,0xF2,0xAE,0x07,0x75,
+0x17,0x41,0x2B,0xD9,0xD1,0xE3,0x2E,0xFF,0x97,0x7D,0x51,0x90,0x33,0xC0,0xA3,0xF8,
+0x79,0xBE,0x89,0x4D,0xE8,0x87,0x0B,0xC3,0xBE,0x8D,0x4D,0xE8,0x80,0x0B,0xEB,0xEC,
+0xBA,0x00,0x02,0xB0,0x93,0xEE,0xB0,0x55,0xEE,0xBA,0x10,0x02,0xB0,0x93,0xEE,0xB0,
+0xAA,0xEE,0xBA,0x00,0x02,0xEC,0x3C,0x55,0x75,0x08,0xBA,0x10,0x02,0xEC,0x3C,0xAA,
+0x74,0x03,0xE8,0x2F,0xF6,0xC3,0xBA,0x04,0x02,0xB0,0x1A,0xEE,0xB0,0x20,0xEE,0xB0,
+0x30,0xEE,0xB0,0x40,0xEE,0xB0,0x80,0xEE,0xBA,0x00,0x02,0xB0,0x13,0xEE,0xB0,0x07,
+0xEE,0xBA,0x08,0x02,0xB0,0x80,0xEE,0xBA,0x02,0x02,0xB0,0xBB,0xEE,0xBA,0x04,0x02,
+0xB0,0x05,0xEE,0xC3,0xC6,0x06,0xCA,0x13,0x01,0xC7,0x06,0xF8,0x79,0x00,0x00,0xC6,
+0x06,0xF6,0x79,0x01,0xC7,0x06,0xD0,0x79,0x00,0x00,0xC7,0x06,0xD2,0x79,0x00,0x00,
+0xC7,0x06,0xD4,0x79,0x00,0x00,0xC7,0x06,0xFA,0x79,0x00,0x00,0xC7,0x06,0xFC,0x79,
+0x00,0x00,0xC7,0x06,0xFE,0x79,0x00,0x00,0xC7,0x06,0x00,0x7A,0x00,0x00,0xC7,0x06,
+0x02,0x7A,0xCE,0x59,0x8C,0x0E,0x04,0x7A,0xC7,0x06,0x06,0x7A,0x00,0x00,0xC7,0x06,
+0x27,0x7A,0x00,0x00,0xC6,0x06,0x29,0x7A,0x00,0xC6,0x06,0x2A,0x7A,0x00,0xC3,0x90,
+0xBE,0x22,0x4D,0xE8,0xC8,0x0A,0xE8,0x3F,0x00,0x2C,0x31,0x3C,0x01,0x77,0xF7,0xE8,
+0x81,0x09,0x8B,0x36,0x0C,0x7A,0xE8,0xB5,0x0A,0xBE,0x6A,0x4D,0xE8,0xAF,0x0A,0x0E,
+0x58,0xE8,0xF8,0x0A,0xBE,0x7A,0x4D,0xE8,0xA4,0x0A,0xC3,0x90,0x60,0xD1,0xE3,0x83,
+0xFB,0x18,0x73,0x11,0x1E,0xBA,0x00,0x00,0x8E,0xDA,0x2E,0xFF,0x97,0xB7,0x51,0x8B,
+0xEC,0x89,0x46,0x10,0x1F,0x61,0xCF,0x90,0xE8,0x4F,0x0B,0x0A,0xC0,0x75,0x05,0xE8,
+0x56,0x0B,0xEB,0xF4,0xC3,0x90,0x83,0x3E,0xF8,0x79,0x01,0x74,0x16,0xBE,0xD7,0x79,
+0xE8,0x31,0x0A,0x8B,0xD0,0xAC,0x3C,0x2C,0x74,0x04,0x3C,0x20,0x75,0x05,0xE8,0x23,
+0x0A,0xEE,0xC3,0xE9,0xD2,0xFE,0x83,0x3E,0xF8,0x79,0x01,0x74,0xF6,0xBE,0xD7,0x79,
+0xE8,0x11,0x0A,0x8B,0xD0,0xAC,0x3C,0x2C,0x74,0x08,0x3C,0x20,0x74,0x04,0xE9,0xB7,
+0xFE,0x90,0xE8,0xFF,0x09,0xEF,0xC3,0x90,0x8B,0x16,0x06,0x7A,0x83,0x3E,0xF8,0x79,
+0x01,0x74,0x0B,0xBE,0xD7,0x79,0xE8,0xEB,0x09,0x8B,0xD0,0xA3,0x06,0x7A,0xB0,0x20,
+0xE8,0x57,0x0B,0x8B,0x16,0x06,0x7A,0xEC,0xE8,0x6F,0x0B,0xC3,0x8B,0x16,0x06,0x7A,
+0x83,0x3E,0xF8,0x79,0x01,0x74,0x0B,0xBE,0xD7,0x79,0xE8,0xC7,0x09,0x8B,0xD0,0xA3,
+0x06,0x7A,0xB0,0x20,0xE8,0x33,0x0B,0x8B,0x16,0x06,0x7A,0xED,0xE8,0x67,0x0B,0xC3,
+0xFA,0xC6,0x06,0xF6,0x79,0x00,0xC3,0x90,0xC6,0x06,0xF6,0x79,0x01,0xFB,0xC3,0x90,
+0x06,0xE8,0x58,0x09,0xB0,0x20,0xE8,0x11,0x0B,0x26,0x8B,0x05,0xE8,0x47,0x0B,0xB0,
+0x08,0xE8,0x06,0x0B,0xE8,0x03,0x0B,0xE8,0x00,0x0B,0xE8,0xFD,0x0A,0xB8,0x01,0x00,
+0xE8,0xCF,0xF4,0xBA,0x02,0x02,0xEC,0x24,0x01,0x75,0x02,0xEB,0xDC,0xBA,0x06,0x02,
+0xEC,0x07,0xC3,0x90,0xC7,0x06,0x08,0x7A,0x10,0x00,0xEB,0x06,0xC7,0x06,0x08,0x7A,
+0x01,0x00,0x06,0x8E,0x06,0xFC,0x79,0x8B,0x3E,0xFA,0x79,0xE8,0x0E,0x09,0xE8,0x0B,
+0x00,0x89,0x3E,0xFA,0x79,0x8C,0x06,0xFC,0x79,0x07,0xC3,0x90,0xBE,0xB2,0x4D,0xE8,
+0x7C,0x09,0x8B,0x16,0x08,0x7A,0x52,0xE8,0x2A,0x09,0xE8,0x0F,0x0A,0xE8,0x0C,0x0A,
+0x33,0xDB,0xB9,0x10,0x00,0x90,0x26,0x8A,0x01,0xE8,0xBC,0x09,0xE8,0xFD,0x09,0x43,
+0xE2,0xF4,0xE8,0xF7,0x09,0xE8,0xF4,0x09,0x33,0xDB,0xB9,0x10,0x00,0x90,0x26,0x8A,
+0x01,0x3C,0x20,0x72,0x05,0x3C,0x7E,0x76,0x03,0x90,0xB0,0x2E,0xE8,0xE3,0x09,0x43,
+0xE2,0xEC,0xBE,0xB2,0x4D,0xE8,0x36,0x09,0x83,0xC7,0x10,0x5A,0x4A,0x75,0xB7,0xC3,
+0x06,0x8E,0x06,0x00,0x7A,0x8B,0x3E,0xFE,0x79,0xE8,0xA0,0x08,0x89,0x3E,0xFE,0x79,
+0x8C,0x06,0x00,0x7A,0x57,0x8B,0x36,0x0E,0x7A,0xE8,0x12,0x09,0xC7,0x06,0x08,0x7A,
+0x10,0x00,0xBA,0x00,0x02,0xE8,0xE8,0x00,0xE8,0x81,0xFF,0x5F,0xBA,0x00,0x00,0xE8,
+0xDE,0x00,0xBE,0xB5,0x4D,0xE8,0xF6,0x08,0x8C,0xC0,0xE8,0x3F,0x09,0xB0,0x3A,0xE8,
+0x90,0x09,0x8B,0xC7,0xE8,0x35,0x09,0xE8,0x7E,0x08,0xE8,0xC3,0x00,0x90,0xE8,0xB7,
+0x09,0xE8,0xA6,0x09,0x0A,0xC0,0x74,0xF6,0x3C,0x0B,0x75,0x06,0x83,0xEF,0x10,0xEB,
+0x19,0x90,0x3C,0x0A,0x75,0x06,0x83,0xC7,0x10,0xEB,0x0F,0x90,0x3C,0x0C,0x75,0x04,
+0x47,0xEB,0x07,0x90,0x3C,0x08,0x75,0x24,0x4F,0x90,0x8B,0x36,0xFE,0x79,0x8B,0xC7,
+0x2B,0xC6,0x3D,0x00,0x01,0x72,0xA5,0x3D,0x10,0x01,0x72,0x04,0x83,0xEE,0x20,0x90,
+0x83,0xC6,0x10,0x89,0x36,0xFE,0x79,0x57,0x8B,0xFE,0xEB,0x80,0x3C,0x2E,0x75,0x08,
+0xBA,0x01,0x13,0xE8,0x6A,0x00,0x07,0xC3,0xC6,0x06,0x0A,0x7A,0x02,0x32,0xC9,0x90,
+0x3C,0x30,0x72,0x4C,0x3C,0x39,0x76,0x0C,0x24,0x5F,0x3C,0x41,0x72,0x42,0x3C,0x46,
+0x77,0x3E,0x2C,0x07,0x2C,0x30,0x50,0xE8,0xCC,0x08,0x58,0x02,0xC8,0xFE,0x0E,0x0A,
+0x7A,0x74,0x0F,0xC0,0xE1,0x04,0xE8,0x2F,0x09,0xE8,0x1E,0x09,0x0A,0xC0,0x74,0xF6,
+0xEB,0xCE,0x26,0x88,0x0D,0xE8,0xE0,0x07,0x8A,0xD0,0xE8,0x23,0x00,0x8A,0xC1,0x3C,
+0x20,0x72,0x05,0x3C,0x7E,0x76,0x03,0x90,0xB0,0x2E,0xE8,0xD5,0x08,0xE9,0x70,0xFF,
+0xE8,0xC5,0x07,0xE8,0x0A,0x00,0x26,0x8A,0x05,0xE8,0x7C,0x08,0xE9,0x1D,0xFF,0x90,
+0xF6,0x06,0x26,0x7A,0x02,0x75,0x02,0x86,0xF2,0x52,0x8B,0x36,0x1A,0x7A,0xE8,0x0D,
+0x08,0x5A,0x52,0x8A,0xC6,0x02,0x06,0x24,0x7A,0xF6,0x06,0x26,0x7A,0x01,0x75,0x06,
+0xE8,0x9F,0x08,0xEB,0x0D,0x90,0x32,0xE4,0xE8,0x0D,0x08,0x8B,0x36,0x1C,0x7A,0xE8,
+0xEC,0x07,0x5A,0x8A,0xC2,0x02,0x06,0x25,0x7A,0xF6,0x06,0x26,0x7A,0x01,0x75,0x06,
+0xE8,0x7F,0x08,0xEB,0x06,0x90,0x32,0xE4,0xE8,0xED,0x07,0x8B,0x36,0x1E,0x7A,0xE8,
+0xCC,0x07,0xC3,0x90,0x06,0x8E,0x06,0x04,0x7A,0x8B,0x3E,0x02,0x7A,0xE8,0x3C,0x07,
+0x89,0x3E,0x02,0x7A,0x8C,0x06,0x04,0x7A,0x07,0xFF,0x1E,0x02,0x7A,0xC3,0xBE,0x97,
+0x4D,0xE8,0xAA,0x07,0xCB,0x90,0x06,0x57,0xBE,0xD7,0x79,0xE8,0x66,0x07,0x8B,0xD8,
+0xE8,0x61,0x07,0x8B,0xC8,0x2B,0xCB,0x78,0x11,0x8E,0xC3,0xBF,0x00,0x00,0xB8,0xFF,
+0xFF,0x51,0xB9,0x08,0x00,0xF3,0xAB,0x59,0xE2,0xF7,0x5F,0x07,0xC3,0x90,0x06,0xBE,
+0xD7,0x79,0xE8,0x3F,0x07,0x8B,0xD8,0xD1,0xE3,0x2E,0x8B,0x9F,0x44,0x00,0xBE,0x26,
+0x52,0xE8,0xF1,0x08,0x8B,0xC3,0xE8,0xDD,0x08,0xB8,0x01,0x00,0xE8,0x73,0xF2,0xE8,
+0xE0,0x08,0xBE,0x2F,0x52,0xE8,0xDD,0x08,0x8B,0x47,0x18,0xE8,0xC8,0x08,0xBE,0x77,
+0x52,0xE8,0xD1,0x08,0x8B,0x47,0x26,0xE8,0xBC,0x08,0xBE,0x53,0x52,0xE8,0xC5,0x08,
+0x8B,0x47,0x1E,0xE8,0xB0,0x08,0xBE,0x5C,0x52,0xE8,0xB9,0x08,0x8B,0x47,0x20,0xE8,
+0xA4,0x08,0xBE,0x6E,0x52,0xE8,0xAD,0x08,0x8B,0x47,0x24,0xE8,0x98,0x08,0xBE,0x80,
+0x52,0xE8,0xA1,0x08,0x8B,0x47,0x2A,0xE8,0x8C,0x08,0xE8,0x95,0x08,0xBE,0x38,0x52,
+0xE8,0x92,0x08,0x8B,0x07,0xE8,0x7E,0x08,0xBE,0x41,0x52,0xE8,0x87,0x08,0x8B,0x47,
+0x1A,0xE8,0x72,0x08,0xBE,0x4A,0x52,0xE8,0x7B,0x08,0x8B,0x47,0x1C,0xE8,0x66,0x08,
+0xBE,0x65,0x52,0xE8,0x6F,0x08,0x8B,0x47,0x22,0xE8,0x5A,0x08,0xE8,0x63,0x08,0xBE,
+0xD1,0x52,0xE8,0x60,0x08,0x8B,0x47,0x38,0xE8,0x4B,0x08,0xBE,0xAD,0x52,0xE8,0x54,
+0x08,0x8B,0x47,0x30,0xE8,0x3F,0x08,0xBE,0xB6,0x52,0xE8,0x48,0x08,0x8B,0x47,0x32,
+0xE8,0x33,0x08,0xBE,0xA4,0x52,0xE8,0x3C,0x08,0x8B,0x47,0x2E,0xE8,0x27,0x08,0xBE,
+0xBF,0x52,0xE8,0x30,0x08,0x8B,0x47,0x34,0xE8,0x1B,0x08,0xE8,0x24,0x08,0xBE,0x89,
+0x52,0xE8,0x21,0x08,0x8B,0x47,0x04,0xE8,0x0C,0x08,0xBE,0x92,0x52,0xE8,0x15,0x08,
+0x8B,0x47,0x14,0xE8,0x00,0x08,0xBE,0x9B,0x52,0xE8,0x09,0x08,0x8B,0x47,0x2C,0xE8,
+0xF4,0x07,0xBE,0xC8,0x52,0xE8,0xFD,0x07,0x8B,0x47,0x36,0xE8,0xE8,0x07,0xBE,0xDA,
+0x52,0xE8,0xF1,0x07,0x8B,0x47,0x3A,0xE8,0xDC,0x07,0xBE,0xE3,0x52,0xE8,0xE5,0x07,
+0x8B,0x47,0x3C,0xE8,0xD0,0x07,0xE8,0xD9,0x07,0xBE,0x19,0x53,0xE8,0xD6,0x07,0x8B,
+0x47,0x48,0xE8,0xC1,0x07,0xBE,0xFE,0x52,0xE8,0xCA,0x07,0x8B,0x47,0x42,0xE8,0xB5,
+0x07,0xBE,0x07,0x53,0xE8,0xBE,0x07,0x8B,0x47,0x44,0xE8,0xA9,0x07,0xBE,0x7C,0x53,
+0xE8,0xB2,0x07,0x8B,0x47,0x4C,0xE8,0x9D,0x07,0xBE,0x85,0x53,0xE8,0xA6,0x07,0x8B,
+0x47,0x4E,0xE8,0x91,0x07,0xBE,0x8E,0x53,0xE8,0x9A,0x07,0x8B,0x47,0x50,0xE8,0x85,
+0x07,0xE8,0x8E,0x07,0xBE,0x22,0x53,0xE8,0x8B,0x07,0x8B,0x47,0x4A,0xE8,0x76,0x07,
+0xBE,0xEC,0x52,0xE8,0x7F,0x07,0x8B,0x47,0x08,0xE8,0x6A,0x07,0xBE,0xF5,0x52,0xE8,
+0x73,0x07,0x8B,0x47,0x40,0xE8,0x5E,0x07,0xBE,0x10,0x53,0xE8,0x67,0x07,0x8B,0x47,
+0x46,0xE8,0x52,0x07,0xE8,0x5B,0x07,0xBE,0x6A,0x53,0xE8,0x58,0x07,0x8B,0x47,0x7A,
+0xE8,0x43,0x07,0xBE,0x3D,0x53,0xE8,0x4C,0x07,0x8B,0x47,0x70,0xE8,0x37,0x07,0xBE,
+0x46,0x53,0xE8,0x40,0x07,0x8B,0x47,0x72,0xE8,0x2B,0x07,0xBE,0x4F,0x53,0xE8,0x34,
+0x07,0x8B,0x47,0x74,0xE8,0x1F,0x07,0xE8,0x28,0x07,0xBE,0x2B,0x53,0xE8,0x25,0x07,
+0x8B,0x47,0x0C,0xE8,0x10,0x07,0xBE,0x34,0x53,0xE8,0x19,0x07,0x8B,0x47,0x10,0xE8,
+0x04,0x07,0xBE,0x58,0x53,0xE8,0x0D,0x07,0x8B,0x47,0x76,0xE8,0xF8,0x06,0xBE,0x61,
+0x53,0xE8,0x01,0x07,0x8B,0x47,0x78,0xE8,0xEC,0x06,0xBE,0x73,0x53,0xE8,0xF5,0x06,
+0x8B,0x47,0x3E,0xE8,0xE0,0x06,0xE8,0xE9,0x06,0xBE,0x97,0x53,0xE8,0xE6,0x06,0x8B,
+0x47,0x52,0xE8,0xD1,0x06,0xBE,0xA0,0x53,0xE8,0xDA,0x06,0x8B,0x47,0x54,0xE8,0xC5,
+0x06,0xBE,0xA9,0x53,0xE8,0xCE,0x06,0x8B,0x47,0x56,0xE8,0xB9,0x06,0xBE,0xB2,0x53,
+0xE8,0xC2,0x06,0x8B,0x47,0x58,0xE8,0xAD,0x06,0xBE,0xBB,0x53,0xE8,0xB6,0x06,0x8B,
+0x47,0x5A,0xE8,0xA1,0x06,0xBE,0xC4,0x53,0xE8,0xAA,0x06,0x8B,0x47,0x5C,0xE8,0x95,
+0x06,0xE8,0x9E,0x06,0xBE,0xCD,0x53,0xE8,0x9B,0x06,0x8B,0x47,0x5E,0xE8,0x86,0x06,
+0xBE,0xD6,0x53,0xE8,0x8F,0x06,0x8B,0x47,0x60,0xE8,0x7A,0x06,0xBE,0xDF,0x53,0xE8,
+0x83,0x06,0x8B,0x47,0x62,0xE8,0x6E,0x06,0xBE,0xE8,0x53,0xE8,0x77,0x06,0x8B,0x47,
+0x7C,0xE8,0x62,0x06,0xBE,0xF1,0x53,0xE8,0x6B,0x06,0x8B,0x47,0x7E,0xE8,0x56,0x06,
+0xBE,0xFA,0x53,0xE8,0x5F,0x06,0x8B,0x87,0x80,0x00,0xE8,0x49,0x06,0xE8,0x52,0x06,
+0xBE,0x42,0x54,0xE8,0x4F,0x06,0x8B,0x87,0x9E,0x00,0xE8,0x39,0x06,0xBE,0x03,0x54,
+0xE8,0x42,0x06,0x8B,0x47,0x64,0xE8,0x2D,0x06,0xBE,0x0C,0x54,0xE8,0x36,0x06,0x8B,
+0x47,0x6E,0xE8,0x21,0x06,0xBE,0x15,0x54,0xE8,0x2A,0x06,0x8B,0x87,0x8E,0x00,0xE8,
+0x14,0x06,0xBE,0x1E,0x54,0xE8,0x1D,0x06,0x8B,0x87,0x90,0x00,0xE8,0x07,0x06,0xBE,
+0x27,0x54,0xE8,0x10,0x06,0x8B,0x87,0x92,0x00,0xE8,0xFA,0x05,0xE8,0x03,0x06,0xBE,
+0x30,0x54,0xE8,0x00,0x06,0x8B,0x87,0x94,0x00,0xE8,0xEA,0x05,0xBE,0x39,0x54,0xE8,
+0xF3,0x05,0x8B,0x87,0x96,0x00,0xE8,0xDD,0x05,0xBE,0x6F,0x54,0xE8,0xE6,0x05,0x8B,
+0x87,0x98,0x00,0xE8,0xD0,0x05,0xBE,0x5D,0x54,0xE8,0xD9,0x05,0x8A,0x87,0xA0,0x00,
+0xE8,0xA7,0x05,0xBE,0x54,0x54,0xE8,0xCC,0x05,0x8A,0x47,0x28,0xE8,0x9B,0x05,0xBE,
+0x66,0x54,0xE8,0xC0,0x05,0x8A,0x87,0xA1,0x00,0xE8,0x8E,0x05,0xE8,0xB3,0x05,0xBE,
+0x78,0x54,0xE8,0xB0,0x05,0x8A,0x87,0xA2,0x00,0xE8,0x7E,0x05,0xBE,0x81,0x54,0xE8,
+0xA3,0x05,0x8A,0x87,0xA3,0x00,0xE8,0x71,0x05,0xBE,0x8A,0x54,0xE8,0x96,0x05,0x8A,
+0x87,0xA4,0x00,0xE8,0x64,0x05,0xBE,0x93,0x54,0xE8,0x89,0x05,0x8A,0x87,0xA5,0x00,
+0xE8,0x57,0x05,0xBE,0x9C,0x54,0xE8,0x7C,0x05,0x8A,0x87,0xA6,0x00,0xE8,0x4A,0x05,
+0xBE,0xA5,0x54,0xE8,0x6F,0x05,0x8A,0x87,0xA7,0x00,0xE8,0x3D,0x05,0xBE,0xAE,0x54,
+0xE8,0x62,0x05,0x8A,0x87,0xA8,0x00,0xE8,0x30,0x05,0xE8,0x55,0x05,0xBE,0xB7,0x54,
+0xE8,0x52,0x05,0x8A,0x87,0xA9,0x00,0xE8,0x20,0x05,0xBE,0xC0,0x54,0xE8,0x45,0x05,
+0x8A,0x87,0xAA,0x00,0xE8,0x13,0x05,0xBE,0xC9,0x54,0xE8,0x38,0x05,0x8A,0x87,0xAB,
+0x00,0xE8,0x06,0x05,0xBE,0xD2,0x54,0xE8,0x2B,0x05,0x8A,0x87,0xAD,0x00,0xE8,0xF9,
+0x04,0xBE,0xDB,0x54,0xE8,0x1E,0x05,0x8A,0x87,0xAE,0x00,0xE8,0xEC,0x04,0xBE,0xE4,
+0x54,0xE8,0x11,0x05,0x8A,0x87,0xAF,0x00,0xE8,0xDF,0x04,0xBE,0xED,0x54,0xE8,0x04,
+0x05,0x8A,0x87,0xB0,0x00,0xE8,0xD2,0x04,0xE8,0xF7,0x04,0xBE,0xF6,0x54,0xE8,0xF4,
+0x04,0x8A,0x87,0xB1,0x00,0xE8,0xC2,0x04,0xBE,0xFF,0x54,0xE8,0xE7,0x04,0x8A,0x87,
+0xB2,0x00,0xE8,0xB5,0x04,0xBE,0x08,0x55,0xE8,0xDA,0x04,0x8A,0x87,0xB3,0x00,0xE8,
+0xA8,0x04,0xBE,0x11,0x55,0xE8,0xCD,0x04,0x8A,0x87,0xBB,0x00,0xE8,0x9B,0x04,0xE8,
+0xC0,0x04,0xBE,0x1A,0x55,0xE8,0xBD,0x04,0x8A,0x87,0xBC,0x00,0xE8,0x8B,0x04,0xBE,
+0x23,0x55,0xE8,0xB0,0x04,0x8A,0x87,0xBE,0x00,0xE8,0x7E,0x04,0xBE,0x2C,0x55,0xE8,
+0xA3,0x04,0x8A,0x87,0xBF,0x00,0xE8,0x71,0x04,0xE8,0x96,0x04,0x07,0xC3,0x60,0x06,
+0x1E,0x16,0x8B,0xEC,0xFF,0x4E,0x16,0xF7,0x46,0x1A,0x00,0x02,0x74,0x01,0xFB,0xB8,
+0x00,0x00,0x8E,0xD8,0x8E,0xC0,0x89,0x2E,0x2D,0x7A,0xE8,0xCB,0x00,0x81,0x66,0x1A,
+0xFF,0xFE,0xC6,0x06,0x2A,0x7A,0x00,0xE8,0xD8,0x00,0xB8,0x00,0x5F,0xA3,0x2B,0x7A,
+0xE8,0x5D,0x00,0x80,0x3E,0x2A,0x7A,0x00,0x74,0x0A,0x81,0x4E,0x1A,0x00,0x01,0xC6,
+0x06,0x2A,0x7A,0x00,0x17,0x1F,0x07,0x61,0xCF,0x90,0x60,0x06,0x1E,0x16,0x8B,0xEC,
+0xF7,0x46,0x1A,0x00,0x02,0x74,0x01,0xFB,0xB8,0x00,0x00,0x8E,0xD8,0x8E,0xC0,0x89,
+0x2E,0x2D,0x7A,0x81,0x66,0x1A,0xFF,0xFE,0xC6,0x06,0x2A,0x7A,0x00,0xE8,0x92,0x00,
+0xB8,0x00,0x5F,0xA3,0x2B,0x7A,0xE8,0x17,0x00,0x80,0x3E,0x2A,0x7A,0x00,0x74,0x0A,
+0x81,0x4E,0x1A,0x00,0x01,0xC6,0x06,0x2A,0x7A,0x00,0x17,0x1F,0x07,0x61,0xCF,0x90,
+0xB8,0xF0,0x00,0xE8,0x8C,0xED,0xFF,0x26,0x2B,0x7A,0xC3,0x90,0x06,0x53,0x56,0x80,
+0x3E,0x29,0x7A,0x00,0x74,0x03,0xE8,0x3F,0x00,0xBE,0xD7,0x79,0xE8,0x25,0x02,0x8B,
+0xD8,0xA3,0x27,0x7A,0x2E,0x8A,0x07,0xA2,0x29,0x7A,0xB0,0xCC,0x2E,0x88,0x07,0x5E,
+0x5B,0x07,0xC3,0xC6,0x06,0x2A,0x7A,0x00,0xB8,0x0A,0x5F,0xA3,0x2B,0x7A,0xC3,0x90,
+0x8B,0x2E,0x2D,0x7A,0xE8,0x2B,0x00,0xC3,0xC6,0x06,0x2A,0x7A,0x01,0xE8,0x08,0x00,
+0xB8,0x0A,0x5F,0xA3,0x2B,0x7A,0xC3,0x90,0x57,0x80,0x3E,0x29,0x7A,0x00,0x74,0x0F,
+0x8B,0x3E,0x27,0x7A,0xA0,0x29,0x7A,0x2E,0x88,0x05,0xC6,0x06,0x29,0x7A,0x00,0x5F,
+0xC3,0x90,0xBE,0xB2,0x4D,0xE8,0x06,0x02,0xBE,0xD8,0x51,0xE8,0x00,0x02,0xFF,0x76,
+0x14,0x58,0xE8,0x47,0x02,0xBE,0xDE,0x51,0xE8,0xF3,0x01,0xFF,0x76,0x0E,0x58,0xE8,
+0x3A,0x02,0xBE,0xE4,0x51,0xE8,0xE6,0x01,0xFF,0x76,0x12,0x58,0xE8,0x2D,0x02,0xBE,
+0xEA,0x51,0xE8,0xD9,0x01,0xFF,0x76,0x10,0x58,0xE8,0x20,0x02,0xBE,0x14,0x52,0xE8,
+0xCC,0x01,0xFF,0x76,0x0A,0x58,0xE8,0x13,0x02,0xBE,0x1A,0x52,0xE8,0xBF,0x01,0xFF,
+0x76,0x0C,0x58,0xE8,0x06,0x02,0xBE,0xCF,0x51,0xE8,0xB2,0x01,0xFF,0x76,0x1A,0x58,
+0xE8,0xF9,0x01,0xBE,0xB2,0x4D,0xE8,0xA5,0x01,0xBE,0xF0,0x51,0xE8,0x9F,0x01,0xFF,
+0x76,0x18,0x58,0xE8,0xE6,0x01,0xBE,0xF6,0x51,0xE8,0x92,0x01,0xFF,0x76,0x02,0x58,
+0xE8,0xD9,0x01,0xBE,0xFC,0x51,0xE8,0x85,0x01,0xFF,0x76,0x04,0x58,0xE8,0xCC,0x01,
+0xBE,0x02,0x52,0xE8,0x78,0x01,0xFF,0x76,0x00,0x58,0xE8,0xBF,0x01,0xBE,0x08,0x52,
+0xE8,0x6B,0x01,0xFF,0x76,0x06,0x58,0xE8,0xB2,0x01,0xBE,0x0E,0x52,0xE8,0x5E,0x01,
+0xFF,0x76,0x08,0x58,0xE8,0xA5,0x01,0xBE,0x20,0x52,0xE8,0x51,0x01,0xFF,0x76,0x16,
+0x58,0xE8,0x98,0x01,0xBE,0x89,0x4D,0xE8,0x44,0x01,0xC3,0x90,0xBE,0xC9,0x4D,0xE8,
+0x3C,0x01,0xC3,0x3C,0x00,0x74,0x05,0x3C,0x01,0x74,0x59,0xC3,0xC7,0x06,0x0C,0x7A,
+0xCD,0x50,0xC7,0x06,0x0E,0x7A,0xF0,0x50,0xC7,0x06,0x10,0x7A,0xE8,0x50,0xC7,0x06,
+0x12,0x7A,0xEC,0x50,0xC7,0x06,0x14,0x7A,0xF4,0x50,0xC7,0x06,0x16,0x7A,0xFB,0x50,
+0xC7,0x06,0x18,0x7A,0x03,0x51,0xC7,0x06,0x1A,0x7A,0x0B,0x51,0xC7,0x06,0x1C,0x7A,
+0x0E,0x51,0xC7,0x06,0x1E,0x7A,0x10,0x51,0xC7,0x06,0x20,0x7A,0x12,0x51,0xC7,0x06,
+0x22,0x7A,0x16,0x51,0xC6,0x06,0x24,0x7A,0x01,0xC6,0x06,0x25,0x7A,0x01,0xC6,0x06,
+0x26,0x7A,0x03,0xC3,0xC7,0x06,0x0C,0x7A,0x1A,0x51,0xC7,0x06,0x0E,0x7A,0x4D,0x51,
+0xC7,0x06,0x10,0x7A,0x47,0x51,0xC7,0x06,0x12,0x7A,0x4A,0x51,0xC7,0x06,0x14,0x7A,
+0x4F,0x51,0xC7,0x06,0x16,0x7A,0x51,0x51,0xC7,0x06,0x18,0x7A,0x55,0x51,0xC7,0x06,
+0x1A,0x7A,0x56,0x51,0xC7,0x06,0x1C,0x7A,0x59,0x51,0xC7,0x06,0x1E,0x7A,0x5A,0x51,
+0xC7,0x06,0x20,0x7A,0x5B,0x51,0xC7,0x06,0x22,0x7A,0x5E,0x51,0xC6,0x06,0x24,0x7A,
+0x20,0xC6,0x06,0x25,0x7A,0x20,0xC6,0x06,0x26,0x7A,0x02,0xC3,0xA1,0xF8,0x79,0x48,
+0x74,0x14,0xBE,0xD7,0x79,0xE8,0x3C,0x00,0x8B,0xF8,0xAC,0x3C,0x3A,0x75,0x07,0x8E,
+0xC7,0xE8,0x30,0x00,0x8B,0xF8,0xC3,0x90,0x8B,0xC7,0x2B,0x06,0xFE,0x79,0x8A,0xF0,
+0x24,0x0F,0x8A,0xD0,0x02,0xD0,0x02,0xD0,0x80,0xC2,0x0B,0xC0,0xEE,0x04,0x80,0xC6,
+0x03,0x04,0x3D,0xC3,0x8C,0xC0,0xE8,0x93,0x00,0xB0,0x3A,0xE8,0xE4,0x00,0x8B,0xC7,
+0xE8,0x89,0x00,0xC3,0x51,0x33,0xC9,0x90,0xAC,0x3C,0x20,0x74,0xFB,0x90,0x0A,0xC0,
+0x74,0x26,0x2C,0x30,0x72,0x22,0x3C,0x09,0x76,0x14,0x3C,0x11,0x72,0x1A,0x2C,0x07,
+0x3C,0x0F,0x76,0x0A,0x3C,0x2A,0x72,0x10,0x2C,0x20,0x3C,0x0F,0x77,0x0A,0x98,0xC1,
+0xE1,0x04,0x03,0xC8,0xAC,0xEB,0xD7,0x90,0x4E,0x8B,0xC1,0x59,0xC3,0x90,0x06,0x8C,
+0xC8,0x8E,0xC0,0xE8,0x02,0x00,0x07,0xC3,0x26,0x8A,0x04,0x46,0x0A,0xC0,0x74,0x06,
+0xE8,0x8F,0x00,0xEB,0xF3,0x90,0xC3,0x90,0x0B,0xC0,0x74,0x7A,0x51,0x33,0xD2,0xB9,
+0xE8,0x03,0xF7,0xF1,0x8B,0xCA,0xE8,0x03,0x00,0x8B,0xC1,0x59,0xBA,0x64,0x00,0xF6,
+0xF2,0xE8,0x0C,0x00,0x8A,0xC4,0x98,0xB2,0x0A,0xF6,0xF2,0xE8,0x02,0x00,0x8A,0xC4,
+0x50,0x0A,0xF0,0x74,0x05,0x04,0x30,0xE8,0x58,0x00,0x58,0xC3,0x86,0xC4,0xE8,0x07,
+0x00,0x86,0xC4,0xE8,0x02,0x00,0xC3,0x90,0xC1,0xC8,0x04,0xE8,0x08,0x00,0xC1,0xC0,
+0x04,0xE8,0x02,0x00,0xC3,0x90,0x53,0x50,0x24,0x0F,0xBB,0xCA,0x62,0x2E,0xD7,0xE8,
+0x30,0x00,0x58,0x5B,0xC3,0x90,0x86,0xC4,0xE8,0x07,0x00,0x86,0xC4,0xE8,0x02,0x00,
+0xC3,0x90,0x50,0xB9,0x08,0x00,0x8A,0xE0,0x32,0xC0,0xD1,0xC0,0x04,0x30,0xE8,0x11,
+0x00,0xE2,0xF5,0x58,0xC3,0x90,0xB0,0x30,0xE8,0x07,0x00,0xC3,0xB0,0x20,0xE8,0x01,
+0x00,0xC3,0x56,0x8B,0x36,0xD0,0x79,0x88,0x84,0xD0,0x77,0x46,0x81,0xE6,0xFF,0x01,
+0xFF,0x06,0xD4,0x79,0x89,0x36,0xD0,0x79,0x81,0x3E,0xD4,0x79,0xFE,0x01,0x75,0x08,
+0x56,0xE8,0x14,0x00,0x5E,0xEB,0xF1,0x90,0x5E,0xC3,0xBA,0x02,0x02,0xEC,0x24,0x01,
+0x74,0x04,0xBA,0x06,0x02,0xEC,0xC3,0x90,0x80,0x3E,0xF6,0x79,0x00,0x74,0x09,0x60,
+0xB8,0x01,0x00,0xE8,0x2C,0xEA,0x61,0x90,0xBA,0x02,0x02,0xEC,0xA8,0x04,0x74,0x28,
+0x8B,0x36,0xD2,0x79,0x83,0x3E,0xD4,0x79,0x00,0x74,0x1D,0x8A,0x84,0xD0,0x77,0x46,
+0x81,0xE6,0xFF,0x01,0x89,0x36,0xD2,0x79,0xFF,0x0E,0xD4,0x79,0xBA,0x06,0x02,0xEE,
+0xBA,0x02,0x02,0xEC,0xA8,0x04,0x75,0xDC,0xA1,0xD4,0x79,0xC3,0x52,0xBA,0x06,0x02,
+0xEE,0x5A,0xC3,0x90,0x52,0x50,0xBA,0x02,0x02,0xEC,0xA8,0x04,0x74,0x08,0x58,0x5A,
+0xE8,0xE9,0xFF,0xF9,0xC3,0x90,0x58,0x5A,0xF8,0xC3,0x52,0x50,0xBA,0x02,0x02,0xEC,
+0xA8,0x04,0x74,0xFB,0x58,0x5A,0xE8,0xD3,0xFF,0xC3,0x30,0x31,0x32,0x33,0x34,0x35,
+0x36,0x37,0x38,0x39,0x41,0x42,0x43,0x44,0x45,0x46,0x53,0x50,0x8A,0xE0,0x80,0xE4,
+0x0F,0xBB,0xCA,0x62,0xC0,0xE8,0x04,0x2E,0xD7,0xE8,0xCE,0xFF,0x8A,0xC4,0x2E,0xD7,
+0xE8,0xC7,0xFF,0x58,0x5B,0xC3,0x86,0xE0,0xE8,0xDF,0xFF,0x86,0xE0,0xE8,0xDA,0xFF,
+0xC3,0x90,0xBE,0xB2,0x4D,0x50,0x2E,0xAC,0x3C,0x00,0x74,0x05,0xE8,0xAB,0xFF,0xEB,
+0xF5,0x58,0xC3,0x90,0xC8,0x08,0x00,0x00,0x56,0x57,0x8B,0x76,0x04,0xBF,0x04,0x00,
+0xC7,0x46,0xFC,0x00,0x00,0xC7,0x46,0xFA,0x00,0x00,0xC7,0x46,0xF8,0x00,0x00,0x83,
+0x7E,0x06,0x00,0x75,0x0E,0x56,0xE8,0xB6,0x0E,0x59,0x0B,0xC0,0x75,0x05,0x8B,0xC7,
+0xE9,0x5B,0x01,0x8B,0x46,0xFC,0x89,0x46,0xFE,0x0B,0xFF,0x75,0x05,0xB8,0x01,0x00,
+0xEB,0x02,0x33,0xC0,0x50,0x56,0xE8,0xA4,0x0D,0x59,0x59,0xB4,0x00,0x89,0x46,0xFC,
+0x8B,0x5E,0xFC,0x83,0xFB,0x08,0x76,0x03,0xE9,0x2B,0x01,0xD1,0xE3,0x2E,0xFF,0xA7,
+0xB2,0x64,0xB8,0x03,0x00,0xE9,0x26,0x01,0x83,0x7E,0xFA,0x00,0x74,0x14,0xC7,0x46,
+0xFA,0x00,0x00,0x8A,0x44,0x58,0x98,0x50,0x8A,0x44,0x59,0x98,0x50,0xE8,0xC2,0x0F,
+0x59,0x59,0x83,0x7E,0xF8,0x00,0x74,0x0A,0xC7,0x46,0xF8,0x00,0x00,0x56,0xE8,0x9B,
+0x08,0x59,0x83,0x7E,0x06,0x00,0x75,0x05,0x8B,0xC7,0xE9,0xF1,0x00,0x83,0xFF,0x04,
+0x75,0x03,0xE9,0xE6,0x00,0x8B,0xC7,0xE9,0xE4,0x00,0x83,0x7E,0xFE,0x00,0x75,0x03,
+0xBF,0x02,0x00,0xE9,0xD5,0x00,0x83,0x7E,0xFE,0x00,0x75,0x03,0xBF,0x01,0x00,0xE9,
+0xC9,0x00,0x8B,0x5E,0xFE,0x83,0xFB,0x07,0x76,0x03,0xE9,0x86,0x00,0xD1,0xE3,0x2E,
+0xFF,0xA7,0xA2,0x64,0x33,0xFF,0xE9,0x7F,0x00,0xBF,0x04,0x00,0x80,0x7C,0x58,0x0F,
+0x74,0x22,0x83,0x7E,0xF8,0x00,0x75,0x1C,0xFE,0x44,0x58,0x6A,0x08,0x56,0xE8,0x7E,
+0x0C,0x59,0x59,0x8A,0x44,0x58,0x04,0x80,0x50,0x56,0xE8,0x72,0x0C,0x59,0x59,0xC7,
+0x46,0xFA,0x01,0x00,0x83,0x7E,0xF8,0x00,0x74,0x0A,0xC7,0x46,0xF8,0x00,0x00,0x56,
+0xE8,0x19,0x08,0x59,0xEB,0x42,0xBF,0x04,0x00,0x80,0x7C,0x58,0x00,0x74,0x22,0x83,
+0x7E,0xF8,0x00,0x75,0x1C,0xFE,0x4C,0x58,0x6A,0x08,0x56,0xE8,0x41,0x0C,0x59,0x59,
+0x8A,0x44,0x58,0x04,0x80,0x50,0x56,0xE8,0x35,0x0C,0x59,0x59,0xC7,0x46,0xFA,0x01,
+0x00,0x83,0x7E,0xF8,0x00,0x74,0x0A,0xC7,0x46,0xF8,0x00,0x00,0x56,0xE8,0xDC,0x07,
+0x59,0xEB,0x05,0xBF,0x04,0x00,0xEB,0x00,0xEB,0x31,0xBF,0x04,0x00,0xEB,0x2C,0xC7,
+0x46,0xF8,0x01,0x00,0x6A,0x08,0x56,0xE8,0x05,0x0C,0x59,0x59,0x80,0x7C,0x58,0x09,
+0x7D,0x04,0xB0,0x0F,0xEB,0x02,0xB0,0x00,0x04,0x80,0x50,0x56,0xE8,0xF0,0x0B,0x59,
+0x59,0xBF,0x04,0x00,0xEB,0x05,0xBF,0x04,0x00,0xEB,0x00,0xE9,0xA5,0xFE,0x5F,0x5E,
+0xC9,0xC3,0xE4,0x63,0x63,0x64,0x63,0x64,0x63,0x64,0x63,0x64,0xE9,0x63,0x26,0x64,
+0x51,0x64,0x78,0x63,0xBA,0x63,0xC6,0x63,0x96,0x64,0xD2,0x63,0x6A,0x64,0x6A,0x64,
+0x6F,0x64,0x72,0x63,0xC8,0x08,0x00,0x00,0x56,0x57,0x8B,0x76,0x04,0x8B,0x7E,0x08,
+0x6A,0x01,0x56,0xE8,0xA9,0x0B,0x59,0x59,0x8A,0x46,0x06,0xC0,0xE0,0x06,0x04,0x80,
+0x50,0x56,0xE8,0x9A,0x0B,0x59,0x59,0xC7,0x46,0xFE,0x00,0x00,0x89,0x7E,0xF8,0xEB,
+0x03,0xFF,0x46,0xFE,0x8B,0x5E,0xF8,0xFF,0x46,0xF8,0x80,0x3F,0x00,0x75,0xF2,0x83,
+0x7E,0xFE,0x10,0x7D,0x25,0xB8,0x10,0x00,0x2B,0x46,0xFE,0xD1,0xF8,0x89,0x46,0xFC,
+0xC7,0x46,0xFA,0x00,0x00,0xEB,0x0B,0x6A,0x20,0x56,0xE8,0x62,0x0B,0x59,0x59,0xFF,
+0x46,0xFA,0x8B,0x46,0xFA,0x3B,0x46,0xFC,0x7C,0xED,0xEB,0x0C,0x8B,0xDF,0x47,0x8A,
+0x07,0x50,0x56,0xE8,0x49,0x0B,0x59,0x59,0x80,0x3D,0x00,0x75,0xEF,0x6A,0x02,0x56,
+0xE8,0x3C,0x0B,0x59,0x59,0xEB,0x00,0x5F,0x5E,0xC9,0xC3,0xC8,0x04,0x00,0x00,0x56,
+0x57,0x8B,0x7E,0x04,0xC7,0x46,0xFE,0x00,0x00,0xBE,0x14,0x00,0xE9,0x09,0x01,0x8B,
+0x5E,0xFE,0x83,0xC3,0x04,0x2B,0xDF,0x8A,0x87,0xAC,0x0B,0x88,0x44,0x5A,0xC6,0x44,
+0x58,0x08,0x8A,0x46,0xFE,0x88,0x44,0x59,0xC7,0x44,0x06,0x00,0x00,0xC6,0x44,0x19,
+0x00,0xC6,0x44,0x1A,0x00,0xC6,0x44,0x1B,0x00,0xC6,0x44,0x1D,0x0D,0xC6,0x44,0x1E,
+0x03,0xC6,0x44,0x1F,0x00,0xC6,0x44,0x20,0x00,0xC6,0x44,0x21,0x00,0xC6,0x44,0x5B,
+0x00,0xC6,0x44,0x5D,0x00,0xC6,0x44,0x5E,0x00,0xC6,0x44,0x5F,0x00,0xC6,0x44,0x60,
+0x00,0xC7,0x46,0xFC,0x00,0x00,0xEB,0x0D,0x8B,0x5E,0xFC,0xD1,0xE3,0xC7,0x40,0x30,
+0x00,0x00,0xFF,0x46,0xFC,0x83,0x7E,0xFC,0x10,0x7C,0xED,0xC7,0x46,0xFC,0x00,0x00,
+0xEB,0x0A,0x8B,0x5E,0xFC,0xC6,0x40,0x50,0x00,0xFF,0x46,0xFC,0x83,0x7E,0xFC,0x04,
+0x7C,0xF0,0xC7,0x44,0x54,0x00,0x00,0xC7,0x44,0x56,0x00,0x00,0x8A,0x44,0x5A,0x98,
+0xBA,0xF8,0x00,0x23,0xD0,0xB8,0x05,0x00,0x0B,0xC2,0x89,0x46,0xFC,0x9C,0xFA,0x8A,
+0x46,0xFC,0xBA,0xFE,0x00,0xEE,0xBA,0x00,0x00,0xEC,0x9D,0x24,0x08,0x88,0x46,0xFC,
+0x83,0x7E,0xFC,0x00,0x75,0x02,0xEB,0x4A,0xFF,0x76,0xFE,0xE8,0x7A,0x0C,0x59,0x68,
+0x35,0x02,0x56,0xE8,0x32,0x0A,0x59,0x59,0x0B,0xC0,0x75,0x34,0x68,0x38,0x02,0x56,
+0xE8,0x25,0x0A,0x59,0x59,0x0B,0xC0,0x75,0x27,0x68,0x42,0x02,0x56,0xE8,0x18,0x0A,
+0x59,0x59,0x0B,0xC0,0x75,0x1A,0x68,0x4C,0x02,0x56,0xE8,0x0B,0x0A,0x59,0x59,0x0B,
+0xC0,0x75,0x0D,0x68,0x56,0x02,0x56,0xE8,0xFE,0x09,0x59,0x59,0x0B,0xC0,0x74,0x02,
+0xEB,0x00,0xFF,0x46,0xFE,0x83,0xC6,0x62,0x39,0x7E,0xFE,0x7D,0x03,0xE9,0xEF,0xFE,
+0xEB,0x00,0x5F,0x5E,0xC9,0xC3,0xC8,0x08,0x00,0x00,0x56,0x57,0x8B,0x46,0x04,0xBA,
+0x62,0x00,0xF7,0xEA,0x05,0x14,0x00,0x8B,0xF0,0x83,0x7E,0x06,0x00,0x74,0x05,0xB8,
+0x10,0x00,0xEB,0x03,0xB8,0x08,0x00,0x89,0x44,0x04,0x8A,0x46,0x08,0x88,0x44,0x5C,
+0x56,0xE8,0x59,0x04,0x59,0x8B,0xF8,0x8B,0xC7,0x89,0x44,0x56,0x89,0x44,0x54,0x8A,
+0x44,0x5D,0x88,0x44,0x2F,0x0B,0xFF,0x75,0x1D,0x68,0xC2,0x0F,0x6A,0x01,0x56,0xE8,
+0x02,0xFE,0x83,0xC4,0x06,0xEB,0x00,0x6A,0x01,0x56,0xE8,0x47,0xFC,0x59,0x59,0x0B,
+0xC0,0x75,0xF4,0xBF,0x01,0x00,0x89,0x7E,0xFA,0xB9,0x05,0x00,0xBB,0xE9,0x6A,0x2E,
+0x8B,0x07,0x3B,0x46,0xFA,0x74,0x07,0x43,0x43,0xE2,0xF4,0xE9,0xA4,0x03,0x2E,0xFF,
+0x67,0x0A,0xC7,0x44,0x06,0x02,0x00,0xC7,0x44,0x08,0xF4,0x08,0x8B,0x5E,0x04,0xD1,
+0xE3,0x8B,0x87,0xFC,0x08,0x89,0x44,0x0A,0x33,0xC0,0x8B,0xF8,0x89,0x44,0x54,0xE9,
+0x80,0x03,0x56,0xE8,0xBB,0x05,0x59,0xBF,0x01,0x00,0x8A,0x44,0x5D,0x88,0x44,0x60,
+0xE9,0x6F,0x03,0x83,0x7C,0x04,0x08,0x75,0x30,0x80,0x7C,0x5C,0x01,0x75,0x15,0x8A,
+0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xE4,0x08,0x56,0xE8,0xF7,0x08,
+0x59,0x59,0xEB,0x13,0x8A,0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xC4,
+0x08,0x56,0xE8,0xE2,0x08,0x59,0x59,0xEB,0x2E,0x80,0x7C,0x5C,0x01,0x75,0x15,0x8A,
+0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xD4,0x08,0x56,0xE8,0xC7,0x08,
+0x59,0x59,0xEB,0x13,0x8A,0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xB4,
+0x08,0x56,0xE8,0xB2,0x08,0x59,0x59,0x6A,0x01,0x56,0xE8,0x87,0xFB,0x59,0x59,0x8B,
+0xD8,0x83,0xFB,0x03,0x77,0x2A,0xD1,0xE3,0x2E,0xFF,0xA7,0xE1,0x6A,0xBF,0x01,0x00,
+0x8A,0x44,0x5D,0x88,0x44,0x5E,0xEB,0x18,0x8A,0x44,0x5D,0x04,0xFF,0x24,0x07,0x88,
+0x44,0x5D,0xEB,0x0C,0x8A,0x44,0x5D,0xFE,0xC0,0x24,0x07,0x88,0x44,0x5D,0xEB,0x00,
+0xE9,0xCF,0x02,0x8A,0x44,0x5D,0xB4,0x00,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0xFD,0x02,
+0x56,0xE8,0x63,0x08,0x59,0x59,0x68,0x1D,0x03,0x56,0xE8,0x5A,0x08,0x59,0x59,0x6A,
+0x01,0x56,0xE8,0x2F,0xFB,0x59,0x59,0x8B,0xD8,0x83,0xFB,0x03,0x77,0x36,0xD1,0xE3,
+0x2E,0xFF,0xA7,0xD9,0x6A,0xBF,0x01,0x00,0x8A,0x44,0x5D,0x88,0x44,0x5F,0xEB,0x24,
+0x8A,0x44,0x5D,0x04,0xFF,0x8A,0x54,0x04,0x80,0xC2,0xFF,0x22,0xC2,0x88,0x44,0x5D,
+0xEB,0x12,0x8A,0x44,0x5D,0xFE,0xC0,0x8A,0x54,0x04,0x80,0xC2,0xFF,0x22,0xC2,0x88,
+0x44,0x5D,0xEB,0x00,0xE9,0x6B,0x02,0x8B,0x5C,0x06,0x83,0xC3,0xFE,0xD1,0xE3,0x8B,
+0x40,0x08,0x89,0x04,0x8B,0x1C,0xFF,0x77,0x06,0x6A,0x00,0x56,0xE8,0x85,0xFC,0x83,
+0xC4,0x06,0x8B,0x5C,0x06,0x4B,0xD1,0xE3,0x8B,0x40,0x08,0x89,0x44,0x02,0x8B,0x5C,
+0x02,0xFF,0x77,0x06,0x6A,0x01,0x56,0xE8,0x6A,0xFC,0x83,0xC4,0x06,0x6A,0x01,0x56,
+0xE8,0xB1,0xFA,0x59,0x59,0x8B,0xD8,0x83,0xFB,0x03,0x76,0x03,0xE9,0x1F,0x02,0xD1,
+0xE3,0x2E,0xFF,0xA7,0xD1,0x6A,0x8B,0x5C,0x02,0x8B,0x47,0x04,0x89,0x44,0x02,0x8B,
+0x5C,0x02,0x80,0x3F,0x44,0x75,0x0D,0x8B,0x5C,0x02,0x8A,0x47,0x01,0xB4,0x00,0x3B,
+0x44,0x04,0x7D,0xE2,0x8B,0x46,0x04,0xD1,0xE0,0x8B,0x1C,0x03,0xD8,0x8B,0x44,0x02,
+0x89,0x47,0x08,0x8B,0x5C,0x06,0x4B,0xD1,0xE3,0x8B,0x44,0x02,0x89,0x40,0x08,0xE9,
+0xDE,0x01,0x8B,0x5C,0x02,0x8B,0x47,0x02,0x89,0x44,0x02,0x8B,0x5C,0x02,0x80,0x3F,
+0x44,0x75,0x0D,0x8B,0x5C,0x02,0x8A,0x47,0x01,0xB4,0x00,0x3B,0x44,0x04,0x7D,0xE2,
+0x8B,0x46,0x04,0xD1,0xE0,0x8B,0x1C,0x03,0xD8,0x8B,0x44,0x02,0x89,0x47,0x08,0x8B,
+0x5C,0x06,0x4B,0xD1,0xE3,0x8B,0x44,0x02,0x89,0x40,0x08,0xE9,0xA2,0x01,0xBF,0x01,
+0x00,0xE9,0x9C,0x01,0x8B,0x5C,0x02,0x8A,0x07,0xB4,0x00,0x89,0x46,0xF8,0xB9,0x0C,
+0x00,0xBB,0xA1,0x6A,0x2E,0x8B,0x07,0x3B,0x46,0xF8,0x74,0x07,0x43,0x43,0xE2,0xF4,
+0xE9,0x77,0x01,0x2E,0xFF,0x67,0x18,0x8B,0x46,0x04,0xD1,0xE0,0x8B,0x5C,0x02,0x03,
+0xD8,0x8B,0x47,0x08,0x8B,0x5C,0x06,0xFF,0x44,0x06,0xD1,0xE3,0x89,0x40,0x08,0x8B,
+0x1C,0x80,0x7F,0x01,0x00,0x74,0x12,0x8B,0x5C,0x02,0x8A,0x47,0x01,0x8B,0x1C,0x8A,
+0x57,0x01,0xB6,0x00,0x8B,0xDA,0x88,0x40,0x18,0xE9,0x40,0x01,0xFF,0x4C,0x06,0xE9,
+0x3A,0x01,0x8B,0x5C,0x02,0x8A,0x47,0x01,0x8B,0x1C,0x8A,0x57,0x01,0xB6,0x00,0x8B,
+0xDA,0x88,0x40,0x18,0xE9,0x25,0x01,0x8B,0x5C,0x02,0x8A,0x47,0x01,0x8B,0x1C,0x8A,
+0x57,0x01,0xB6,0x00,0x8B,0xDA,0x88,0x40,0x18,0xFF,0x4C,0x06,0xE9,0x0D,0x01,0x8B,
+0x5C,0x02,0x8A,0x47,0x01,0x8B,0x1C,0x8A,0x57,0x01,0xB6,0x00,0x8B,0xDA,0x30,0x40,
+0x18,0xE9,0xF8,0x00,0xB8,0xF0,0x10,0x8B,0xF8,0x89,0x44,0x54,0x8A,0x44,0x5F,0x88,
+0x44,0x5D,0xE9,0xE7,0x00,0x8A,0x44,0x1C,0x98,0x3D,0x02,0x00,0x74,0x07,0x3D,0x03,
+0x00,0x74,0x02,0xEB,0x07,0xC7,0x46,0xFE,0x00,0x00,0xEB,0x2B,0x8A,0x44,0x1C,0x98,
+0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0x69,0x02,0x56,0xE8,0x6B,0x06,0x59,0x59,0x6A,0x01,
+0x56,0xE8,0x40,0xF9,0x59,0x59,0x89,0x46,0xFE,0x83,0x7E,0xFE,0x00,0x74,0x06,0x83,
+0x7E,0xFE,0x03,0x75,0xE9,0xEB,0x00,0x83,0x7E,0xFE,0x03,0x74,0x62,0x8A,0x44,0x1C,
+0x98,0xD1,0xE0,0x8B,0xD8,0xFF,0xB7,0x6D,0x02,0x56,0xE8,0x3A,0x06,0x59,0x59,0x56,
+0xE8,0x4D,0x97,0x59,0x89,0x46,0xFC,0x8B,0x5E,0xFC,0x83,0xEB,0xFE,0x83,0xFB,0x03,
+0x77,0x33,0xD1,0xE3,0x2E,0xFF,0xA7,0x99,0x6A,0x68,0xAC,0x02,0x56,0xE8,0x17,0x06,
+0x59,0x59,0xEB,0x23,0x68,0x8F,0x02,0x56,0xE8,0x0C,0x06,0x59,0x59,0xEB,0x18,0x68,
+0x75,0x02,0x56,0xE8,0x01,0x06,0x59,0x59,0xEB,0x0D,0x68,0xC6,0x02,0x56,0xE8,0xF6,
+0x05,0x59,0x59,0xEB,0x02,0xEB,0x00,0x6A,0x01,0x56,0xE8,0xC7,0xF8,0x59,0x59,0xBF,
+0x01,0x00,0xEB,0x38,0x68,0xDD,0x02,0x56,0xE8,0xDC,0x05,0x59,0x59,0x6A,0x01,0x56,
+0xE8,0xB1,0xF8,0x59,0x59,0xBF,0x01,0x00,0xEB,0x22,0xB8,0xD0,0x30,0x8B,0xF8,0x89,
+0x44,0x54,0x8A,0x44,0x60,0x88,0x44,0x5D,0xEB,0x12,0xB8,0xE0,0x20,0x8B,0xF8,0x89,
+0x44,0x54,0x8A,0x44,0x5E,0x88,0x44,0x5D,0xEB,0x02,0xEB,0x00,0xEB,0x02,0xEB,0x00,
+0xEB,0x00,0xE9,0x41,0xFC,0x5F,0x5E,0xC9,0xC3,0x19,0x6A,0x24,0x6A,0x2F,0x6A,0x3A,
+0x6A,0x00,0x00,0x01,0x00,0x02,0x00,0x04,0x00,0x41,0x00,0x42,0x00,0x43,0x00,0x44,
+0x00,0x80,0x00,0x81,0x00,0x82,0x00,0xFF,0x00,0x17,0x69,0x54,0x6A,0x7A,0x6A,0xA5,
+0x69,0x52,0x69,0x94,0x69,0x6A,0x6A,0x67,0x69,0x52,0x69,0x7F,0x69,0x67,0x69,0x4C,
+0x69,0xF4,0x68,0x76,0x68,0xB2,0x68,0xEE,0x68,0xF5,0x67,0x00,0x68,0x12,0x68,0xF5,
+0x67,0x9D,0x67,0xA8,0x67,0xB4,0x67,0x9D,0x67,0x00,0x00,0x01,0x00,0xF0,0x10,0xE0,
+0x20,0xD0,0x30,0x27,0x68,0xF2,0x66,0xC3,0x67,0x23,0x67,0x12,0x67,0xC8,0x04,0x00,
+0x00,0x56,0x57,0x8B,0x76,0x04,0x8A,0x44,0x59,0x98,0x89,0x46,0xFC,0x6A,0x09,0x8B,
+0x46,0xFC,0x05,0x84,0x01,0x50,0xE8,0x93,0x08,0x59,0x59,0x8B,0xF8,0x8B,0xC7,0x25,
+0x00,0xF0,0x3D,0x00,0x10,0x75,0x55,0x8B,0xC7,0x25,0xF0,0x00,0x3D,0xF0,0x00,0x75,
+0x4B,0x8B,0xC7,0x25,0x00,0x0F,0xC1,0xF8,0x08,0x89,0x46,0xFE,0x8B,0x44,0x04,0x3B,
+0x46,0xFE,0x7D,0x05,0x33,0xC0,0xE9,0xEF,0x00,0x8B,0xC7,0x25,0x0F,0x00,0xBA,0x0F,
+0x00,0x2B,0xD0,0x3B,0x56,0xFE,0x74,0x05,0x33,0xC0,0xE9,0xDB,0x00,0xC7,0x44,0x02,
+0x04,0x09,0x8A,0x46,0xFE,0x88,0x44,0x5F,0x88,0x44,0x5D,0x8B,0x5E,0xFC,0xD1,0xE3,
+0xC7,0x87,0xFC,0x08,0x04,0x09,0xB8,0xF0,0x10,0xE9,0xBC,0x00,0x8B,0xC7,0x25,0x00,
+0xF0,0x3D,0x00,0x20,0x75,0x52,0x8B,0xC7,0x25,0xF0,0x00,0x3D,0xE0,0x00,0x75,0x48,
+0x8B,0xC7,0x25,0x00,0x0F,0xC1,0xF8,0x08,0x89,0x46,0xFE,0x83,0x7E,0xFE,0x08,0x7E,
+0x05,0x33,0xC0,0xE9,0x92,0x00,0x8B,0xC7,0x25,0x0F,0x00,0xBA,0x0F,0x00,0x2B,0xD0,
+0x3B,0x56,0xFE,0x74,0x05,0x33,0xC0,0xEB,0x7F,0x90,0xC7,0x44,0x02,0x0C,0x09,0x8A,
+0x46,0xFE,0x88,0x44,0x5E,0x88,0x44,0x5D,0x8B,0x5E,0xFC,0xD1,0xE3,0xC7,0x87,0xFC,
+0x08,0x0C,0x09,0xB8,0xE0,0x20,0xEB,0x60,0x8B,0xC7,0x25,0x00,0xF0,0x3D,0x00,0x30,
+0x75,0x52,0x8B,0xC7,0x25,0xF0,0x00,0x3D,0xD0,0x00,0x75,0x48,0x8B,0xC7,0x25,0x00,
+0x0F,0xC1,0xF8,0x08,0x89,0x46,0xFE,0x8B,0x44,0x04,0x3B,0x46,0xFE,0x7D,0x04,0x33,
+0xC0,0xEB,0x35,0x8B,0xC7,0x25,0x0F,0x00,0xBA,0x0F,0x00,0x2B,0xD0,0x3B,0x56,0xFE,
+0x74,0x04,0x33,0xC0,0xEB,0x22,0xC7,0x44,0x02,0x14,0x09,0x8A,0x46,0xFE,0x88,0x44,
+0x60,0x88,0x44,0x5D,0x8B,0x5E,0xFC,0xD1,0xE3,0xC7,0x87,0xFC,0x08,0x14,0x09,0xB8,
+0xD0,0x30,0xEB,0x04,0x33,0xC0,0xEB,0x00,0x5F,0x5E,0xC9,0xC3,0xC8,0x06,0x00,0x00,
+0x56,0x8B,0x76,0x04,0x6A,0x08,0x56,0xE8,0x35,0x04,0x59,0x59,0x8A,0x44,0x58,0x04,
+0x80,0x50,0x56,0xE8,0x29,0x04,0x59,0x59,0x8B,0x44,0x54,0x3B,0x44,0x56,0x75,0x0A,
+0x8A,0x44,0x5D,0x3A,0x44,0x2F,0x75,0x02,0xEB,0x64,0x8B,0x44,0x54,0x89,0x44,0x56,
+0x8B,0x5C,0x02,0x8A,0x47,0x01,0x88,0x44,0x2F,0x8A,0x44,0x5D,0xB4,0x00,0xC1,0xE0,
+0x08,0x8B,0x54,0x54,0x0B,0xD0,0x8A,0x44,0x5D,0xB4,0x00,0xBB,0x0F,0x00,0x2B,0xD8,
+0x0B,0xD3,0x89,0x56,0xFE,0x6A,0x10,0x8A,0x44,0x59,0x98,0x05,0x04,0x00,0x99,0x05,
+0x40,0x01,0x83,0xD2,0x00,0x52,0x50,0xE8,0x54,0x08,0x83,0xC4,0x06,0x89,0x56,0xFC,
+0x89,0x46,0xFA,0x8B,0x46,0xFE,0x09,0x46,0xFA,0x83,0x4E,0xFC,0x00,0x6A,0x19,0xFF,
+0x76,0xFC,0xFF,0x76,0xFA,0xE8,0x73,0x07,0x83,0xC4,0x06,0xE8,0xFE,0x07,0x5E,0xC9,
+0xC3,0xC8,0x1C,0x00,0x00,0x56,0x57,0x8B,0x5E,0x04,0x8A,0x47,0x59,0x98,0x8B,0xF0,
+0x8B,0x5E,0x04,0x8A,0x47,0x5D,0xB4,0x00,0x89,0x46,0xE6,0x83,0x7E,0xE6,0x00,0x7D,
+0x0A,0x8B,0x5E,0x04,0x8B,0x47,0x04,0x48,0x89,0x46,0xE6,0x8B,0x5E,0x04,0x8B,0x47,
+0x04,0x3B,0x46,0xE6,0x7F,0x05,0xC7,0x46,0xE6,0x00,0x00,0x8B,0x5E,0x04,0x8A,0x46,
+0xE6,0x88,0x47,0x5D,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,0xC6,0x47,0x02,0x20,
+0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,0xC6,0x47,0x03,0x30,0x8B,0xDE,0xD1,0xE3,
+0x8B,0x9F,0x61,0x02,0xC6,0x47,0x02,0x20,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x61,0x02,
+0xC6,0x47,0x03,0x30,0x8B,0x46,0xE6,0x89,0x46,0xFA,0x83,0x7E,0xFA,0x00,0x74,0x18,
+0x8B,0x46,0xFA,0xBB,0x0A,0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,
+0xE3,0x8B,0x9F,0x59,0x02,0x88,0x57,0x03,0xBB,0x0A,0x00,0x8B,0x46,0xFA,0x33,0xD2,
+0xF7,0xF3,0x89,0x46,0xFA,0x83,0x7E,0xFA,0x00,0x74,0x18,0x8B,0x46,0xFA,0xBB,0x0A,
+0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,
+0x88,0x57,0x02,0x8B,0x46,0xE6,0x89,0x46,0xFA,0x83,0x7E,0xFA,0x00,0x74,0x18,0x8B,
+0x46,0xFA,0xBB,0x0A,0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,0xE3,
+0x8B,0x9F,0x61,0x02,0x88,0x57,0x03,0xBB,0x0A,0x00,0x8B,0x46,0xFA,0x33,0xD2,0xF7,
+0xF3,0x89,0x46,0xFA,0x83,0x7E,0xFA,0x00,0x74,0x18,0x8B,0x46,0xFA,0xBB,0x0A,0x00,
+0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x61,0x02,0x88,
+0x57,0x02,0x8B,0x5E,0xE6,0xD1,0xE3,0xFF,0xB7,0x12,0x02,0x6A,0x00,0xFF,0x76,0x04,
+0xE8,0xD1,0xF6,0x83,0xC4,0x06,0x68,0xD3,0x0F,0x6A,0x01,0xFF,0x76,0x04,0xE8,0xC3,
+0xF6,0x83,0xC4,0x06,0xFF,0x76,0xE6,0x56,0xE8,0x01,0x93,0x59,0x59,0x89,0x56,0xF2,
+0x89,0x46,0xF0,0xFF,0x76,0xE6,0x56,0xE8,0x14,0x93,0x59,0x59,0x89,0x56,0xEE,0x89,
+0x46,0xEC,0x9C,0xFA,0xC4,0x5E,0xF0,0x26,0x8B,0x07,0x89,0x46,0xEA,0xC4,0x5E,0xEC,
+0x26,0x8B,0x07,0x89,0x46,0xE8,0xBA,0x50,0xFF,0xED,0x89,0x46,0xFE,0x9D,0xC7,0x46,
+0xE4,0x01,0x00,0xE8,0xEE,0xA0,0xBA,0x50,0xFF,0xED,0x89,0x46,0xFC,0x8B,0x46,0xFC,
+0x2B,0x46,0xFE,0x3D,0xE8,0x03,0x73,0x03,0xE9,0x80,0x01,0x9C,0xFA,0xBA,0x50,0xFF,
+0xED,0x89,0x46,0xFC,0x8B,0x46,0xFC,0x2B,0x46,0xFE,0x89,0x46,0xF8,0xC4,0x5E,0xF0,
+0x26,0x8B,0x07,0x2B,0x46,0xEA,0x89,0x46,0xF6,0xC4,0x5E,0xF0,0x26,0x8B,0x07,0x89,
+0x46,0xEA,0xC4,0x5E,0xEC,0x26,0x8B,0x07,0x2B,0x46,0xE8,0x89,0x46,0xF4,0xC4,0x5E,
+0xEC,0x26,0x8B,0x07,0x89,0x46,0xE8,0xBA,0x50,0xFF,0xED,0x89,0x46,0xFE,0x9D,0x81,
+0x7E,0xF8,0xE8,0x03,0x76,0x1C,0xFF,0x76,0xF8,0xFF,0x76,0xF6,0xE8,0x76,0x01,0x59,
+0x59,0x89,0x46,0xF6,0xFF,0x76,0xF8,0xFF,0x76,0xF4,0xE8,0x68,0x01,0x59,0x59,0x89,
+0x46,0xF4,0xBF,0x0E,0x00,0xEB,0x17,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,0xC6,
+0x01,0x20,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x61,0x02,0xC6,0x01,0x20,0x47,0x83,0xFF,
+0x11,0x76,0xE4,0x8B,0xDE,0xD1,0xE3,0x8B,0x9F,0x59,0x02,0xC6,0x47,0x0D,0x30,0x8B,
+0xDE,0xD1,0xE3,0x8B,0x9F,0x61,0x02,0xC6,0x47,0x0D,0x30,0x83,0x7E,0xF6,0x09,0x77,
+0x05,0xB8,0x0D,0x00,0xEB,0x26,0x83,0x7E,0xF6,0x63,0x77,0x05,0xB8,0x0E,0x00,0xEB,
+0x1B,0x81,0x7E,0xF6,0xE7,0x03,0x77,0x05,0xB8,0x0F,0x00,0xEB,0x0F,0x81,0x7E,0xF6,
+0x0F,0x27,0x77,0x05,0xB8,0x10,0x00,0xEB,0x03,0xB8,0x11,0x00,0x8B,0xF8,0xEB,0x25,
+0x8B,0x46,0xF6,0xBB,0x0A,0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,
+0xE3,0x8B,0x9F,0x59,0x02,0x88,0x11,0x4F,0xBB,0x0A,0x00,0x8B,0x46,0xF6,0x33,0xD2,
+0xF7,0xF3,0x89,0x46,0xF6,0x83,0x7E,0xF6,0x00,0x75,0xD5,0x83,0x7E,0xF4,0x09,0x77,
+0x05,0xB8,0x0D,0x00,0xEB,0x26,0x83,0x7E,0xF4,0x63,0x77,0x05,0xB8,0x0E,0x00,0xEB,
+0x1B,0x81,0x7E,0xF4,0xE7,0x03,0x77,0x05,0xB8,0x0F,0x00,0xEB,0x0F,0x81,0x7E,0xF4,
+0x0F,0x27,0x77,0x05,0xB8,0x10,0x00,0xEB,0x03,0xB8,0x11,0x00,0x8B,0xF8,0xEB,0x25,
+0x8B,0x46,0xF4,0xBB,0x0A,0x00,0x33,0xD2,0xF7,0xF3,0x80,0xC2,0x30,0x8B,0xDE,0xD1,
+0xE3,0x8B,0x9F,0x61,0x02,0x88,0x11,0x4F,0xBB,0x0A,0x00,0x8B,0x46,0xF4,0x33,0xD2,
+0xF7,0xF3,0x89,0x46,0xF4,0x83,0x7E,0xF4,0x00,0x75,0xD5,0x8B,0xDE,0xD1,0xE3,0xFF,
+0xB7,0x59,0x02,0xFF,0x76,0x04,0xE8,0x6E,0x00,0x59,0x59,0x8B,0xDE,0xD1,0xE3,0xFF,
+0xB7,0x61,0x02,0xFF,0x76,0x04,0xE8,0x5E,0x00,0x59,0x59,0x6A,0x00,0xFF,0x76,0x04,
+0xE8,0x31,0xF3,0x59,0x59,0x8B,0xD8,0x83,0xFB,0x04,0x77,0x1F,0xD1,0xE3,0x2E,0xFF,
+0xA7,0x1B,0x70,0xEB,0x22,0xC7,0x46,0xE4,0x00,0x00,0xFF,0x4E,0xE6,0xEB,0x0C,0xC7,
+0x46,0xE4,0x00,0x00,0xFF,0x46,0xE6,0xEB,0x02,0xEB,0x00,0x83,0x7E,0xE4,0x00,0x74,
+0x03,0xE9,0x2A,0xFE,0xE9,0xD4,0xFC,0x5F,0x5E,0xC9,0xC3,0xF3,0x6F,0xF5,0x6F,0xFF,
+0x6F,0xF3,0x6F,0x09,0x70,0x55,0x8B,0xEC,0x8B,0x46,0x04,0xB9,0xE8,0x03,0xF7,0xE1,
+0x8B,0x4E,0x06,0xF7,0xF1,0x5D,0xC3,0x55,0x8B,0xEC,0x56,0x8B,0x76,0x06,0xEB,0x0E,
+0x8B,0xDE,0x46,0x8A,0x07,0x50,0xFF,0x76,0x04,0xE8,0x33,0x00,0x59,0x59,0x80,0x3C,
+0x00,0x75,0xED,0xEB,0x00,0x5E,0x5D,0xC3,0x55,0x8B,0xEC,0x56,0x8B,0x76,0x06,0xEB,
+0x14,0x8B,0xDE,0x46,0x8A,0x07,0x50,0xFF,0x76,0x04,0xE8,0x45,0x00,0x59,0x59,0x0B,
+0xC0,0x74,0x02,0xEB,0x07,0x80,0x3C,0x00,0x75,0xE7,0xEB,0x00,0x5E,0x5D,0xC3,0xC8,
+0x02,0x00,0x00,0x56,0x8B,0x76,0x04,0x8A,0x44,0x5A,0x98,0x89,0x46,0xFE,0x9C,0xFA,
+0x8A,0x46,0xFE,0xBA,0xFE,0x00,0xEE,0xBA,0x02,0x00,0xEC,0xA8,0x02,0x74,0x06,0x9D,
+0xE8,0x91,0x9E,0xEB,0xE9,0xBA,0x00,0x00,0x8A,0x46,0x06,0xEE,0x9D,0xEB,0x00,0x5E,
+0xC9,0xC3,0xC8,0x04,0x00,0x00,0x56,0x8B,0x76,0x04,0x8A,0x44,0x5A,0x98,0x89,0x46,
+0xFE,0xE8,0xE6,0xA1,0x89,0x46,0xFC,0xE8,0xE0,0xA1,0x2B,0x46,0xFC,0x3D,0xB8,0x0B,
+0x76,0x05,0xB8,0x01,0x00,0xEB,0x23,0x9C,0xFA,0x8A,0x46,0xFE,0xBA,0xFE,0x00,0xEE,
+0xBA,0x02,0x00,0xEC,0xA8,0x02,0x74,0x06,0x9D,0xE8,0x48,0x9E,0xEB,0xD9,0xBA,0x00,
+0x00,0x8A,0x46,0x06,0xEE,0x9D,0x33,0xC0,0xEB,0x00,0x5E,0xC9,0xC3,0xC8,0x04,0x00,
+0x00,0x56,0x57,0x8B,0x76,0x04,0x83,0x7E,0x06,0x00,0x74,0x07,0x56,0xE8,0x03,0x01,
+0x59,0xEB,0x05,0x56,0xE8,0xA2,0x00,0x59,0x88,0x46,0xFF,0x80,0x7E,0xFF,0x08,0x77,
+0x06,0x8A,0x46,0xFF,0xE9,0x84,0x00,0x80,0x7E,0xFF,0x0F,0x76,0x03,0xEB,0x79,0x90,
+0x8A,0x46,0xFF,0xB4,0x00,0x2D,0x0A,0x00,0x8B,0xD8,0x83,0xFB,0x04,0x77,0x67,0xD1,
+0xE3,0x2E,0xFF,0xA7,0xAF,0x71,0xB0,0x00,0xEB,0x61,0x56,0xE8,0x6B,0x00,0x59,0xB4,
+0x00,0x25,0x0F,0x00,0x89,0x46,0xFC,0x56,0xE8,0x5E,0x00,0x59,0xB4,0x00,0x8B,0xF8,
+0x56,0xE8,0x55,0x00,0x59,0xB4,0x00,0xC1,0xE0,0x08,0x8B,0xD7,0x03,0xD0,0x8B,0xFA,
+0x8B,0x5E,0xFC,0xD1,0xE3,0x89,0x78,0x30,0xEB,0x2E,0x56,0xE8,0x3B,0x00,0x59,0x88,
+0x44,0x5B,0xEB,0x24,0x56,0xE8,0x31,0x00,0x59,0x88,0x44,0x50,0x56,0xE8,0x29,0x00,
+0x59,0x88,0x44,0x51,0x56,0xE8,0x21,0x00,0x59,0x88,0x44,0x52,0x56,0xE8,0x19,0x00,
+0x59,0x88,0x44,0x53,0xEB,0x02,0xEB,0x00,0xE9,0x5B,0xFF,0x5F,0x5E,0xC9,0xC3,0x46,
+0x71,0xA6,0x71,0x4A,0x71,0x7A,0x71,0x84,0x71,0xC8,0x04,0x00,0x00,0x56,0x8B,0x76,
+0x04,0x8A,0x44,0x5A,0x98,0x89,0x46,0xFE,0x9C,0xFA,0x8A,0x46,0xFE,0xBA,0xFE,0x00,
+0xEE,0xBA,0x02,0x00,0xEC,0xA8,0x01,0x75,0x06,0x9D,0xE8,0x57,0x9D,0xEB,0xE9,0xBA,
+0x00,0x00,0xEC,0x88,0x46,0xFD,0x9D,0x8A,0x46,0xFD,0xEB,0x00,0x5E,0xC9,0xC3,0xC8,
+0x02,0x00,0x00,0x56,0x8B,0x76,0x04,0x8A,0x44,0x5A,0x98,0x89,0x46,0xFE,0x9C,0xFA,
+0x8A,0x46,0xFE,0xBA,0xFE,0x00,0xEE,0xBA,0x02,0x00,0xEC,0x32,0xE4,0x24,0x01,0x9D,
+0x5E,0xC9,0xC3,0xC8,0x06,0x00,0x00,0x56,0x8B,0x76,0x04,0x8A,0x44,0x5A,0x98,0x89,
+0x46,0xFE,0xE8,0x85,0xA0,0x89,0x46,0xFA,0xE8,0x7F,0xA0,0x2B,0x46,0xFA,0x3D,0xB8,
+0x0B,0x76,0x04,0xB0,0x08,0xEB,0x24,0x9C,0xFA,0x8A,0x46,0xFE,0xBA,0xFE,0x00,0xEE,
+0xBA,0x02,0x00,0xEC,0xA8,0x01,0x75,0x06,0x9D,0xE8,0xE8,0x9C,0xEB,0xDA,0xBA,0x00,
+0x00,0xEC,0x88,0x46,0xFD,0x9D,0x8A,0x46,0xFD,0xEB,0x00,0x5E,0xC9,0xC3,0x55,0x8B,
+0xEC,0x56,0x8B,0x56,0x04,0x8A,0x46,0x06,0xEE,0x33,0xF6,0xEB,0x03,0x50,0x58,0x46,
+0x83,0xFE,0x14,0x7C,0xF8,0x5E,0x5D,0xC3,0xC8,0x02,0x00,0x00,0x56,0x8B,0x56,0x04,
+0xEC,0x88,0x46,0xFF,0x33,0xF6,0xEB,0x03,0x50,0x58,0x46,0x83,0xFE,0x14,0x7C,0xF8,
+0x8A,0x46,0xFF,0xEB,0x00,0x5E,0xC9,0xC3,0xC8,0x02,0x00,0x00,0x56,0x57,0x8B,0x76,
+0x04,0x83,0x3E,0xB0,0x0B,0x00,0x75,0x1F,0xBA,0x88,0x01,0xB0,0x00,0xEE,0xBA,0x86,
+0x01,0xB0,0x00,0xEE,0x6A,0x09,0x6A,0x00,0x68,0x30,0x01,0xE8,0x7D,0x01,0x83,0xC4,
+0x06,0xC7,0x06,0xB0,0x0B,0x01,0x00,0x6A,0x09,0x8B,0xC6,0x05,0x80,0x01,0x50,0xE8,
+0xDA,0x00,0x59,0x59,0x8B,0xF8,0x8B,0xC7,0xC1,0xE8,0x0C,0x25,0x0F,0x00,0x89,0x46,
+0xFE,0x8B,0xC7,0xC1,0xE8,0x08,0x25,0x0F,0x00,0x8B,0x56,0xFE,0x83,0xF2,0x0C,0x3B,
+0xC2,0x75,0x21,0x8B,0xC7,0xC1,0xE8,0x04,0x25,0x0F,0x00,0x8B,0x56,0xFE,0x83,0xF2,
+0x06,0x3B,0xC2,0x75,0x0F,0x8B,0xC7,0x25,0x0F,0x00,0x8B,0x56,0xFE,0x83,0xF2,0x09,
+0x3B,0xC2,0x74,0x0D,0x6A,0x07,0x56,0xE8,0x38,0x00,0x59,0x59,0xC7,0x46,0xFE,0x07,
+0x00,0x8A,0x46,0xFE,0x04,0x80,0xA2,0x33,0x02,0x8B,0xC6,0xBA,0x62,0x00,0xF7,0xEA,
+0x8A,0x56,0xFE,0x8B,0xD8,0x88,0x97,0x6C,0x00,0x68,0x32,0x02,0x8B,0xC6,0xBA,0x62,
+0x00,0xF7,0xEA,0x05,0x14,0x00,0x50,0xE8,0x0E,0xFD,0x59,0x59,0xEB,0x00,0x5F,0x5E,
+0xC9,0xC3,0xC8,0x02,0x00,0x00,0x56,0x8B,0x76,0x06,0x83,0xE6,0x0F,0x8B,0xC6,0xC1,
+0xE0,0x0C,0x8B,0xD6,0x83,0xF2,0x0C,0xC1,0xE2,0x08,0x0B,0xC2,0x8B,0xD6,0x83,0xF2,
+0x06,0xC1,0xE2,0x04,0x0B,0xC2,0x8B,0xD6,0x83,0xF2,0x09,0x0B,0xC2,0x89,0x46,0xFE,
+0x6A,0x19,0x6A,0x10,0x8B,0x46,0x04,0x99,0x05,0x40,0x01,0x83,0xD2,0x00,0x52,0x50,
+0xE8,0x6B,0x01,0x83,0xC4,0x06,0x0B,0x46,0xFE,0x83,0xCA,0x00,0x52,0x50,0xE8,0x9A,
+0x00,0x83,0xC4,0x06,0xE8,0x25,0x01,0xEB,0x00,0x5E,0xC9,0xC3,0x55,0x8B,0xEC,0x56,
+0x57,0x33,0xFF,0x6A,0x01,0x68,0x86,0x01,0xE8,0xA3,0xFE,0x59,0x59,0xB1,0x10,0x2A,
+0x4E,0x06,0xD3,0x66,0x04,0x33,0xF6,0xEB,0x2E,0x81,0x7E,0x04,0x00,0x80,0x72,0x04,
+0xB0,0x01,0xEB,0x02,0xB0,0x00,0x50,0x68,0x88,0x01,0xE8,0x81,0xFE,0x59,0x59,0x6A,
+0x03,0x68,0x86,0x01,0xE8,0x77,0xFE,0x59,0x59,0x6A,0x01,0x68,0x86,0x01,0xE8,0x6D,
+0xFE,0x59,0x59,0xD1,0x66,0x04,0x46,0x3B,0x76,0x06,0x7C,0xCD,0x33,0xF6,0xEB,0x24,
+0xD1,0xE7,0x6A,0x03,0x68,0x86,0x01,0xE8,0x54,0xFE,0x59,0x59,0x6A,0x01,0x68,0x86,
+0x01,0xE8,0x4A,0xFE,0x59,0x59,0x68,0x88,0x01,0xE8,0x5C,0xFE,0x59,0x98,0x25,0x01,
+0x00,0x0B,0xF8,0x46,0x83,0xFE,0x10,0x7C,0xD7,0x6A,0x00,0x68,0x86,0x01,0xE8,0x2D,
+0xFE,0x59,0x59,0x8B,0xC7,0xEB,0x00,0x5F,0x5E,0x5D,0xC3,0x55,0x8B,0xEC,0x56,0x57,
+0x8B,0x7E,0x08,0x6A,0x01,0x68,0x86,0x01,0xE8,0x13,0xFE,0x59,0x59,0xB8,0x20,0x00,
+0x2B,0xC7,0x50,0xFF,0x76,0x06,0xFF,0x76,0x04,0xE8,0xA2,0x00,0x83,0xC4,0x06,0x89,
+0x56,0x06,0x89,0x46,0x04,0x33,0xF6,0xEB,0x47,0x81,0x7E,0x06,0x00,0x80,0x72,0x0C,
+0x75,0x06,0x83,0x7E,0x04,0x00,0x72,0x04,0xB0,0x01,0xEB,0x02,0xB0,0x00,0x50,0x68,
+0x88,0x01,0xE8,0xD9,0xFD,0x59,0x59,0x6A,0x03,0x68,0x86,0x01,0xE8,0xCF,0xFD,0x59,
+0x59,0x6A,0x01,0x68,0x86,0x01,0xE8,0xC5,0xFD,0x59,0x59,0x6A,0x01,0xFF,0x76,0x06,
+0xFF,0x76,0x04,0xE8,0x58,0x00,0x83,0xC4,0x06,0x89,0x56,0x06,0x89,0x46,0x04,0x46,
+0x3B,0xF7,0x7C,0xB5,0x6A,0x00,0x68,0x86,0x01,0xE8,0xA2,0xFD,0x59,0x59,0x6A,0x00,
+0x68,0x86,0x01,0xE8,0x98,0xFD,0x59,0x59,0x5F,0x5E,0x5D,0xC3,0x55,0x8B,0xEC,0x56,
+0x6A,0x01,0x68,0x86,0x01,0xE8,0x86,0xFD,0x59,0x59,0x33,0xF6,0xEB,0x00,0x68,0x88,
+0x01,0xE8,0x94,0xFD,0x59,0xA8,0x01,0x75,0x08,0x8B,0xC6,0x46,0x3D,0x64,0x00,0x7C,
+0xED,0x6A,0x00,0x68,0x86,0x01,0xE8,0x65,0xFD,0x59,0x59,0x5E,0x5D,0xC3,0xC8,0x04,
+0x00,0x00,0x8B,0x46,0x04,0x8B,0x56,0x06,0x8B,0x4E,0x08,0xE3,0x06,0xD1,0xE0,0xD1,
+0xD2,0xE2,0xFA,0x89,0x46,0xFC,0x89,0x56,0xFE,0x8B,0x56,0xFE,0x8B,0x46,0xFC,0xEB,
+0x00,0xC9,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x50,0x72,0x65,0x76,0x69,0x6F,0x75,0x73,0x20,0x4D,0x65,0x6E,0x75,0x00,0x42,0x65,
+0x67,0x69,0x6E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x6F,0x72,0x74,
+0x20,0x30,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,0x00,0x50,0x6F,0x72,0x74,0x20,0x32,
+0x00,0x50,0x6F,0x72,0x74,0x20,0x33,0x00,0x50,0x6F,0x72,0x74,0x20,0x34,0x00,0x50,
+0x6F,0x72,0x74,0x20,0x35,0x00,0x50,0x6F,0x72,0x74,0x20,0x36,0x00,0x50,0x6F,0x72,
+0x74,0x20,0x37,0x00,0x50,0x6F,0x72,0x74,0x20,0x38,0x00,0x50,0x6F,0x72,0x74,0x20,
+0x39,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,0x30,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,
+0x31,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,0x32,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,
+0x33,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,0x34,0x00,0x50,0x6F,0x72,0x74,0x20,0x31,
+0x35,0x00,0x9C,0x01,0xA3,0x01,0xAA,0x01,0xB1,0x01,0xB8,0x01,0xBF,0x01,0xC6,0x01,
+0xCD,0x01,0xD4,0x01,0xDB,0x01,0xE2,0x01,0xEA,0x01,0xF2,0x01,0xFA,0x01,0x02,0x02,
+0x0A,0x02,0x08,0x00,0x00,0x07,0x81,0x00,0x03,0x80,0x80,0x80,0x9F,0x91,0x95,0x91,
+0x9F,0x00,0x03,0x81,0x84,0x8E,0x95,0x84,0x84,0x84,0x84,0x00,0x03,0x82,0x84,0x84,
+0x84,0x84,0x95,0x8E,0x84,0x00,0x04,0x88,0x00,0xB2,0x0B,0xC6,0x0B,0xDA,0x0B,0xEE,
+0x0B,0x02,0x0C,0x16,0x0C,0x2A,0x0C,0x3E,0x0C,0x52,0x0C,0x77,0x0C,0x9C,0x0C,0xBE,
+0x0C,0xE0,0x0C,0x02,0x0D,0x01,0x80,0x20,0x54,0x65,0x73,0x74,0x20,0x50,0x61,0x73,
+0x73,0x65,0x64,0x20,0x1F,0x20,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x02,0x00,0x01,
+0x80,0x20,0x4D,0x69,0x73,0x73,0x69,0x6E,0x67,0x20,0x52,0x78,0x20,0x44,0x61,0x74,
+0x61,0x1F,0x20,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x02,0x00,0x01,0x80,0x20,0x42,
+0x61,0x64,0x20,0x52,0x78,0x20,0x44,0x61,0x74,0x61,0x20,0x1F,0x20,0x50,0x72,0x65,
+0x73,0x73,0x20,0x80,0x02,0x00,0x01,0x80,0x20,0x58,0x6D,0x74,0x72,0x20,0x42,0x75,
+0x73,0x79,0x1F,0x20,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x02,0x00,0x01,0x80,0x20,
+0x6E,0x6F,0x74,0x20,0x63,0x75,0x72,0x72,0x65,0x6E,0x74,0x6C,0x79,0x1F,0x20,0x20,
+0x69,0x6D,0x70,0x6C,0x65,0x6D,0x65,0x6E,0x74,0x65,0x64,0x02,0x00,0x24,0x0D,0x2F,
+0x0D,0x3A,0x0D,0x45,0x0D,0x50,0x0D,0x5B,0x0D,0x66,0x0D,0x71,0x0D,0x7C,0x0D,0x87,
+0x0D,0x92,0x0D,0x9D,0x0D,0xA8,0x0D,0xB3,0x0D,0xBE,0x0D,0xC9,0x0D,0x53,0x80,0x2C,
+0x32,0x54,0x44,0x20,0x53,0x86,0x2C,0x33,0x44,0x54,0x52,0x20,0x53,0x82,0x2C,0x33,
+0x52,0x54,0x53,0x20,0x1F,0x53,0x81,0x2C,0x32,0x52,0x44,0x20,0x53,0x85,0x2C,0x32,
+0x43,0x44,0x20,0x53,0x83,0x2C,0x33,0x43,0x54,0x53,0x20,0x53,0x84,0x2C,0x33,0x44,
+0x53,0x52,0x20,0x53,0x87,0x2C,0x32,0x52,0x49,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x44,0x43,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x30,0x1F,0x27,0x53,0x85,
+0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
+0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x53,0x52,
+0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x31,0x31,0x1F,0x27,0x53,0x84,0x2E,0x31,0x81,
+0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,
+0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x43,0x54,0x53,0x20,0x2D,0x20,
+0x70,0x69,0x6E,0x20,0x34,0x1F,0x27,0x53,0x83,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,
+0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,
+0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x49,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,
+0x32,0x1F,0x27,0x53,0x87,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,
+0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,
+0x20,0x20,0x44,0x54,0x52,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x36,0x2F,0x38,0x1F,
+0x27,0x53,0x86,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,
+0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x52,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,0x27,0x53,0x82,0x2E,
+0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,
+0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x78,0x44,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x1F,0x27,0x53,0x81,0x2E,0x30,0x53,0x4D,0x81,
+0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,
+0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x54,0x78,0x44,0x20,0x2D,0x20,
+0x70,0x69,0x6E,0x20,0x33,0x1F,0x27,0x53,0x80,0x2E,0x30,0x53,0x4D,0x81,0x82,0x63,
+0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,
+0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x43,0x44,0x20,0x2D,0x20,0x70,0x69,
+0x6E,0x20,0x35,0x1F,0x27,0x53,0x85,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x44,0x53,0x52,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,
+0x27,0x53,0x84,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,
+0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x43,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x31,0x1F,0x27,0x53,0x83,0x2E,
+0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,
+0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x49,0x20,0x2D,
+0x20,0x28,0x6E,0x2E,0x63,0x2E,0x29,0x1F,0x27,0x53,0x87,0x2E,0x31,0x81,0x82,0x63,
+0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,
+0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x54,0x52,0x20,0x2D,0x20,0x70,0x69,
+0x6E,0x20,0x32,0x1F,0x27,0x53,0x86,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x52,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x37,0x1F,
+0x27,0x53,0x82,0x2E,0x31,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,
+0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x52,0x78,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x36,0x1F,0x27,0x53,0x81,0x2E,
+0x30,0x53,0x4D,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,
+0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x54,0x78,
+0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x33,0x1F,0x27,0x53,0x80,0x2E,0x30,0x53,
+0x4D,0x81,0x82,0x63,0x90,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,
+0x8B,0x8C,0x8D,0x8E,0x8F,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x43,0x44,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x85,0x2E,
+0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x44,0x53,0x52,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x84,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x43,0x54,0x53,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x31,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x83,0x2E,
+0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x52,0x49,0x20,0x2D,0x20,0x28,0x6E,0x2E,0x63,0x2E,0x29,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x87,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x54,0x52,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x86,0x2E,
+0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x52,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x37,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x82,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x78,0x44,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x36,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x81,0x2E,
+0x30,0x53,0x4D,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,
+0x02,0x00,0x01,0x80,0x20,0x20,0x54,0x78,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,
+0x33,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x80,0x2E,0x30,0x53,0x4D,0x81,0x82,0x63,
+0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,
+0x44,0x43,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x30,0x1F,0x20,0x20,0x20,
+0x20,0x27,0x53,0x85,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,
+0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x44,0x53,0x52,0x20,0x2D,0x20,0x70,
+0x69,0x6E,0x20,0x31,0x31,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x84,0x2E,0x31,0x81,
+0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,
+0x20,0x20,0x43,0x54,0x53,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x34,0x1F,0x20,0x20,
+0x20,0x20,0x27,0x53,0x83,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,
+0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x49,0x20,0x2D,0x20,0x70,
+0x69,0x6E,0x20,0x32,0x32,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x87,0x2E,0x31,0x81,
+0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,
+0x20,0x20,0x44,0x54,0x52,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x36,0x2F,0x38,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x86,0x2E,0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,
+0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x52,0x54,0x53,0x20,
+0x2D,0x20,0x70,0x69,0x6E,0x20,0x35,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,0x82,0x2E,
+0x31,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,
+0x01,0x80,0x20,0x20,0x52,0x78,0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x32,0x1F,
+0x20,0x20,0x20,0x20,0x27,0x53,0x81,0x2E,0x30,0x53,0x4D,0x81,0x82,0x63,0x88,0x80,
+0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x27,0x02,0x00,0x01,0x80,0x20,0x20,0x54,0x78,
+0x44,0x20,0x2D,0x20,0x70,0x69,0x6E,0x20,0x33,0x1F,0x20,0x20,0x20,0x20,0x27,0x53,
+0x80,0x2E,0x30,0x53,0x4D,0x81,0x82,0x63,0x88,0x80,0x81,0x82,0x83,0x84,0x85,0x86,
+0x87,0x27,0x02,0x00,0x68,0x04,0x96,0x04,0xB6,0x03,0x3C,0x04,0x0E,0x04,0x89,0x03,
+0x5C,0x03,0xE2,0x03,0x60,0x08,0x8A,0x08,0xBE,0x07,0x38,0x08,0x0E,0x08,0x95,0x07,
+0x6C,0x07,0xE6,0x07,0x1C,0x05,0x74,0x05,0xFA,0x05,0xC4,0x04,0xF0,0x04,0xCC,0x05,
+0xA0,0x05,0x48,0x05,0x78,0x06,0xC8,0x06,0x42,0x07,0x28,0x06,0x50,0x06,0x18,0x07,
+0xF0,0x06,0xA0,0x06,0x00,0x00,0xF4,0x08,0xF4,0x08,0xD4,0x0D,0x04,0x09,0x04,0x09,
+0x04,0x09,0x04,0x09,0x42,0x00,0x0C,0x09,0x1C,0x09,0xE5,0x0D,0x02,0x00,0x14,0x09,
+0x04,0x09,0xF4,0x0D,0x43,0x00,0x1C,0x09,0x0C,0x09,0x05,0x0E,0x00,0x04,0x04,0x09,
+0x14,0x09,0x12,0x0E,0x2C,0x09,0x2C,0x09,0x2C,0x09,0x2C,0x09,0x00,0x00,0x3C,0x09,
+0x6C,0x09,0x1E,0x0E,0x74,0x09,0x74,0x09,0x74,0x09,0x74,0x09,0x00,0x01,0x4C,0x09,
+0x2C,0x09,0x2D,0x0E,0x74,0x09,0x74,0x09,0x74,0x09,0x74,0x09,0x00,0x02,0x5C,0x09,
+0x3C,0x09,0x3D,0x0E,0x74,0x09,0x74,0x09,0x74,0x09,0x74,0x09,0x00,0x03,0x6C,0x09,
+0x4C,0x09,0x4D,0x0E,0x74,0x09,0x74,0x09,0x74,0x09,0x74,0x09,0xFF,0x00,0x2C,0x09,
+0x5C,0x09,0x00,0x00,0x00,0x05,0x84,0x09,0xEC,0x09,0x5E,0x0E,0xF4,0x09,0xF4,0x09,
+0xF4,0x09,0xF4,0x09,0x00,0x06,0x94,0x09,0x74,0x09,0x68,0x0E,0xAC,0x0A,0xAC,0x0A,
+0xAC,0x0A,0xAC,0x0A,0x00,0x07,0xA4,0x09,0x84,0x09,0x72,0x0E,0xBC,0x0A,0xBC,0x0A,
+0xBC,0x0A,0xBC,0x0A,0x00,0x08,0xB4,0x09,0x94,0x09,0x7C,0x0E,0xD4,0x0A,0xD4,0x0A,
+0xD4,0x0A,0xD4,0x0A,0x00,0x0B,0xC4,0x09,0xA4,0x09,0x83,0x0E,0xFC,0x0A,0xFC,0x0A,
+0xFC,0x0A,0xFC,0x0A,0x00,0x0C,0xD4,0x09,0xB4,0x09,0x90,0x0E,0x14,0x0B,0x14,0x0B,
+0x14,0x0B,0x14,0x0B,0x00,0x02,0xE4,0x09,0xC4,0x09,0xA0,0x0E,0x2C,0x0B,0x2C,0x0B,
+0x2C,0x0B,0x2C,0x0B,0x04,0x00,0xEC,0x09,0xD4,0x09,0x0E,0x00,0xFF,0x00,0x74,0x09,
+0xE4,0x09,0x00,0x00,0x82,0x01,0xFC,0x09,0xA4,0x0A,0xAC,0x0E,0x82,0x02,0x04,0x0A,
+0xF4,0x09,0xAF,0x0E,0x82,0x03,0x0C,0x0A,0xFC,0x09,0xB2,0x0E,0x82,0x04,0x14,0x0A,
+0x04,0x0A,0xB6,0x0E,0x82,0x05,0x1C,0x0A,0x0C,0x0A,0xBC,0x0E,0x82,0x06,0x24,0x0A,
+0x14,0x0A,0xC0,0x0E,0x82,0x07,0x2C,0x0A,0x1C,0x0A,0xC4,0x0E,0x82,0x08,0x34,0x0A,
+0x24,0x0A,0xC8,0x0E,0x82,0x09,0x3C,0x0A,0x2C,0x0A,0xCC,0x0E,0x82,0x0A,0x44,0x0A,
+0x34,0x0A,0xD1,0x0E,0x82,0x10,0x4C,0x0A,0x3C,0x0A,0xD6,0x0E,0x82,0x0B,0x54,0x0A,
+0x44,0x0A,0xDB,0x0E,0x82,0x11,0x5C,0x0A,0x4C,0x0A,0xE0,0x0E,0x82,0x0C,0x64,0x0A,
+0x54,0x0A,0xE5,0x0E,0x82,0x12,0x6C,0x0A,0x5C,0x0A,0xEA,0x0E,0x82,0x0D,0x74,0x0A,
+0x64,0x0A,0xEF,0x0E,0x82,0x0E,0x7C,0x0A,0x6C,0x0A,0xF4,0x0E,0x82,0x0F,0x84,0x0A,
+0x74,0x0A,0xFB,0x0E,0x82,0x13,0x8C,0x0A,0x7C,0x0A,0x02,0x0F,0x82,0x14,0x94,0x0A,
+0x84,0x0A,0x09,0x0F,0x82,0x15,0x9C,0x0A,0x8C,0x0A,0x10,0x0F,0x82,0x16,0xA4,0x0A,
+0x94,0x0A,0x17,0x0F,0x82,0x17,0xF4,0x09,0x9C,0x0A,0x1E,0x0F,0x82,0x02,0xB4,0x0A,
+0xB4,0x0A,0x26,0x0F,0x82,0x03,0xAC,0x0A,0xAC,0x0A,0x2D,0x0F,0x82,0x00,0xC4,0x0A,
+0xCC,0x0A,0x34,0x0F,0x82,0x01,0xCC,0x0A,0xBC,0x0A,0x3F,0x0F,0x82,0x02,0xBC,0x0A,
+0xC4,0x0A,0x4D,0x0F,0x82,0x00,0xDC,0x0A,0xF4,0x0A,0x59,0x0F,0x82,0x01,0xE4,0x0A,
+0xD4,0x0A,0x63,0x0F,0x82,0x02,0xEC,0x0A,0xDC,0x0A,0x6E,0x0F,0x82,0x03,0xF4,0x0A,
+0xE4,0x0A,0x7A,0x0F,0x82,0x04,0xD4,0x0A,0xEC,0x0A,0x87,0x0F,0x82,0x00,0x04,0x0B,
+0x0C,0x0B,0x93,0x0F,0x82,0x01,0x0C,0x0B,0xFC,0x0A,0x9B,0x0F,0x82,0x02,0xFC,0x0A,
+0x04,0x0B,0xA7,0x0F,0x82,0x00,0x1C,0x0B,0x24,0x0B,0xB0,0x0F,0x82,0x01,0x24,0x0B,
+0x14,0x0B,0xB5,0x0F,0x82,0x02,0x14,0x0B,0x1C,0x0B,0xBE,0x0F,0x44,0x00,0x34,0x0B,
+0xA4,0x0B,0x9C,0x01,0x44,0x01,0x3C,0x0B,0x2C,0x0B,0xA3,0x01,0x44,0x02,0x44,0x0B,
+0x34,0x0B,0xAA,0x01,0x44,0x03,0x4C,0x0B,0x3C,0x0B,0xB1,0x01,0x44,0x04,0x54,0x0B,
+0x44,0x0B,0xB8,0x01,0x44,0x05,0x5C,0x0B,0x4C,0x0B,0xBF,0x01,0x44,0x06,0x64,0x0B,
+0x54,0x0B,0xC6,0x01,0x44,0x07,0x6C,0x0B,0x5C,0x0B,0xCD,0x01,0x44,0x08,0x74,0x0B,
+0x64,0x0B,0xD4,0x01,0x44,0x09,0x7C,0x0B,0x6C,0x0B,0xDB,0x01,0x44,0x0A,0x84,0x0B,
+0x74,0x0B,0xE2,0x01,0x44,0x0B,0x8C,0x0B,0x7C,0x0B,0xEA,0x01,0x44,0x0C,0x94,0x0B,
+0x84,0x0B,0xF2,0x01,0x44,0x0D,0x9C,0x0B,0x8C,0x0B,0xFA,0x01,0x44,0x0E,0xA4,0x0B,
+0x94,0x0B,0x02,0x02,0x44,0x0F,0x2C,0x0B,0x9C,0x0B,0x0A,0x02,0x17,0x1F,0x0F,0x2F,
+0x00,0x00,0x01,0x80,0x78,0x78,0x3A,0x20,0x74,0x78,0x20,0x63,0x70,0x73,0x20,0x2A,
+0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0x80,0x78,0x78,0x3A,0x20,0x74,0x78,0x20,0x63,
+0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0x80,0x78,0x78,0x3A,0x20,
+0x74,0x78,0x20,0x63,0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0x80,
+0x78,0x78,0x3A,0x20,0x74,0x78,0x20,0x63,0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,
+0x02,0x00,0x01,0xC0,0x78,0x78,0x3A,0x20,0x72,0x63,0x20,0x63,0x70,0x73,0x20,0x2A,
+0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0xC0,0x78,0x78,0x3A,0x20,0x72,0x63,0x20,0x63,
+0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0xC0,0x78,0x78,0x3A,0x20,
+0x72,0x63,0x20,0x63,0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,0x02,0x00,0x01,0xC0,
+0x78,0x78,0x3A,0x20,0x72,0x63,0x20,0x63,0x70,0x73,0x20,0x2A,0x2A,0x2A,0x2A,0x2A,
+0x02,0x00,0x01,0x80,0x49,0x6E,0x73,0x74,0x61,0x6C,0x6C,0x20,0x4C,0x6F,0x6F,0x70,
+0x62,0x61,0x63,0x6B,0x1F,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x20,0x74,0x6F,0x20,
+0x73,0x74,0x61,0x72,0x74,0x02,0x00,0x01,0x80,0x20,0x43,0x61,0x62,0x6C,0x65,0x20,
+0x74,0x6F,0x20,0x52,0x65,0x6D,0x6F,0x74,0x65,0x1F,0x50,0x72,0x65,0x73,0x73,0x20,
+0x80,0x20,0x74,0x6F,0x20,0x73,0x74,0x61,0x72,0x74,0x02,0x00,0x01,0x80,0x20,0x4C,
+0x6F,0x63,0x61,0x6C,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x20,0x1F,0x20,
+0x20,0x52,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x2E,0x2E,0x2E,0x02,0x00,0x01,0x80,
+0x52,0x65,0x6D,0x6F,0x74,0x65,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x20,
+0x1F,0x20,0x20,0x52,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x2E,0x2E,0x2E,0x02,0x00,
+0x01,0x80,0x20,0x49,0x6E,0x74,0x72,0x6E,0x6C,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,
+0x63,0x6B,0x1F,0x20,0x20,0x52,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x2E,0x2E,0x2E,
+0x02,0x00,0x01,0x80,0x54,0x72,0x61,0x6E,0x73,0x6D,0x69,0x74,0x20,0x50,0x61,0x74,
+0x74,0x65,0x72,0x6E,0x1F,0x20,0x20,0x52,0x75,0x6E,0x6E,0x69,0x6E,0x67,0x20,0x2E,
+0x2E,0x2E,0x02,0x00,0x01,0x80,0x20,0x20,0x30,0x3A,0x20,0x27,0x43,0x80,0x00,0x01,
+0x80,0x20,0x20,0x31,0x3A,0x20,0x27,0x43,0x81,0x00,0x01,0x80,0x20,0x20,0x32,0x3A,
+0x20,0x27,0x43,0x82,0x00,0x01,0x80,0x20,0x20,0x33,0x3A,0x20,0x27,0x43,0x83,0x00,
+0x01,0x80,0x20,0x20,0x34,0x3A,0x20,0x27,0x43,0x84,0x00,0x01,0x80,0x20,0x20,0x35,
+0x3A,0x20,0x27,0x43,0x85,0x00,0x01,0x80,0x20,0x20,0x36,0x3A,0x20,0x27,0x43,0x86,
+0x00,0x01,0x80,0x20,0x20,0x37,0x3A,0x20,0x27,0x43,0x87,0x00,0x01,0x80,0x20,0x20,
+0x38,0x3A,0x20,0x27,0x43,0x88,0x00,0x01,0x80,0x20,0x20,0x39,0x3A,0x20,0x27,0x43,
+0x89,0x00,0x01,0x80,0x20,0x31,0x30,0x3A,0x20,0x27,0x43,0x8A,0x00,0x01,0x80,0x20,
+0x31,0x31,0x3A,0x20,0x27,0x43,0x8B,0x00,0x01,0x80,0x20,0x31,0x32,0x3A,0x20,0x27,
+0x43,0x8C,0x00,0x01,0x80,0x20,0x31,0x33,0x3A,0x20,0x27,0x43,0x8D,0x00,0x01,0x80,
+0x20,0x31,0x34,0x3A,0x20,0x27,0x43,0x8E,0x00,0x01,0x80,0x20,0x31,0x35,0x3A,0x20,
+0x27,0x43,0x8F,0x00,0x2A,0x2A,0x20,0x4D,0x61,0x69,0x6E,0x20,0x20,0x4D,0x65,0x6E,
+0x75,0x20,0x2A,0x2A,0x00,0x4D,0x6F,0x6E,0x69,0x74,0x6F,0x72,0x20,0x61,0x20,0x50,
+0x6F,0x72,0x74,0x00,0x4D,0x6F,0x6E,0x69,0x74,0x6F,0x72,0x20,0x61,0x20,0x53,0x69,
+0x67,0x6E,0x61,0x6C,0x00,0x45,0x73,0x74,0x69,0x6D,0x61,0x74,0x65,0x20,0x43,0x50,
+0x53,0x00,0x44,0x69,0x61,0x67,0x6E,0x6F,0x73,0x74,0x69,0x63,0x73,0x00,0x4C,0x6F,
+0x63,0x61,0x6C,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x00,0x52,0x65,0x6D,
+0x6F,0x74,0x65,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x00,0x49,0x6E,0x74,
+0x72,0x6E,0x6C,0x20,0x4C,0x6F,0x6F,0x70,0x62,0x61,0x63,0x6B,0x00,0x54,0x72,0x61,
+0x6E,0x73,0x6D,0x69,0x74,0x20,0x50,0x61,0x74,0x74,0x65,0x72,0x6E,0x00,0x42,0x61,
+0x75,0x64,0x20,0x52,0x61,0x74,0x65,0x00,0x44,0x61,0x74,0x61,0x20,0x42,0x69,0x74,
+0x73,0x00,0x53,0x74,0x6F,0x70,0x20,0x42,0x69,0x74,0x73,0x00,0x50,0x61,0x72,0x69,
+0x74,0x79,0x00,0x44,0x61,0x74,0x61,0x20,0x50,0x61,0x74,0x74,0x65,0x72,0x6E,0x00,
+0x54,0x78,0x20,0x46,0x6C,0x6F,0x77,0x20,0x43,0x6F,0x6E,0x74,0x72,0x6F,0x6C,0x00,
+0x50,0x6F,0x72,0x74,0x20,0x4E,0x75,0x6D,0x62,0x65,0x72,0x00,0x35,0x30,0x00,0x37,
+0x35,0x00,0x31,0x31,0x30,0x00,0x31,0x33,0x34,0x2E,0x35,0x00,0x31,0x35,0x30,0x00,
+0x32,0x30,0x30,0x00,0x33,0x30,0x30,0x00,0x36,0x30,0x30,0x00,0x31,0x32,0x30,0x30,
+0x00,0x31,0x38,0x30,0x30,0x00,0x32,0x30,0x30,0x30,0x00,0x32,0x34,0x30,0x30,0x00,
+0x33,0x36,0x30,0x30,0x00,0x34,0x38,0x30,0x30,0x00,0x37,0x32,0x30,0x30,0x00,0x39,
+0x36,0x30,0x30,0x00,0x31,0x39,0x2C,0x32,0x30,0x30,0x00,0x33,0x38,0x2C,0x34,0x30,
+0x30,0x00,0x35,0x36,0x2C,0x30,0x30,0x30,0x00,0x35,0x37,0x2C,0x36,0x30,0x30,0x00,
+0x36,0x34,0x2C,0x30,0x30,0x30,0x00,0x37,0x36,0x2C,0x38,0x30,0x30,0x00,0x31,0x31,
+0x35,0x2C,0x32,0x30,0x30,0x00,0x37,0x20,0x62,0x69,0x74,0x73,0x00,0x38,0x20,0x62,
+0x69,0x74,0x73,0x00,0x31,0x20,0x73,0x74,0x6F,0x70,0x20,0x62,0x69,0x74,0x00,0x31,
+0x2E,0x35,0x20,0x73,0x74,0x6F,0x70,0x20,0x62,0x69,0x74,0x73,0x00,0x32,0x20,0x73,
+0x74,0x6F,0x70,0x20,0x62,0x69,0x74,0x73,0x00,0x6E,0x6F,0x20,0x70,0x61,0x72,0x69,
+0x74,0x79,0x00,0x6F,0x64,0x64,0x20,0x70,0x61,0x72,0x69,0x74,0x79,0x00,0x65,0x76,
+0x65,0x6E,0x20,0x70,0x61,0x72,0x69,0x74,0x79,0x00,0x73,0x70,0x61,0x63,0x65,0x20,
+0x70,0x61,0x72,0x69,0x74,0x79,0x00,0x6D,0x61,0x72,0x6B,0x20,0x70,0x61,0x72,0x69,
+0x74,0x79,0x00,0x43,0x6F,0x6C,0x75,0x6D,0x6E,0x73,0x00,0x42,0x61,0x72,0x62,0x65,
+0x72,0x20,0x50,0x6F,0x6C,0x65,0x00,0x55,0x55,0x55,0x55,0x55,0x2E,0x2E,0x2E,0x00,
+0x4E,0x6F,0x6E,0x65,0x00,0x58,0x6F,0x6E,0x2F,0x58,0x6F,0x66,0x66,0x00,0x43,0x54,
+0x53,0x00,0x50,0x72,0x65,0x73,0x73,0x20,0x80,0x20,0x66,0x6F,0x72,0x20,0x6D,0x65,
+0x6E,0x75,0x00,0x28,0x63,0x6F,0x75,0x6E,0x74,0x69,0x6E,0x67,0x2E,0x2E,0x2E,0x29,
+0x00,0x00,0x65,0x4E,0x64,0x20,0x4F,0x66,0x20,0x43,0x6F,0x44,0x65,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+};
diff --git a/drivers/char/ip2/i2cmd.c b/drivers/char/ip2/i2cmd.c
new file mode 100644
index 0000000..fd299d6
--- /dev/null
+++ b/drivers/char/ip2/i2cmd.c
@@ -0,0 +1,209 @@
+/*******************************************************************************
+*
+*   (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Definition table for In-line and Bypass commands. Applicable
+*                only when the standard loadware is active. (This is included
+*                source code, not a separate compilation module.)
+*
+*******************************************************************************/
+
+//------------------------------------------------------------------------------
+//
+// Revision History:
+//
+// 10 October 1991   MAG First Draft
+//  7 November 1991  MAG Reflects additional commands.
+// 24 February 1992  MAG Additional commands for 1.4.x loadware
+// 11 March 1992     MAG Additional commands
+// 30 March 1992     MAG Additional command: CMD_DSS_NOW
+// 18 May 1992       MAG Discovered commands 39 & 40 must be at the end of a
+//                       packet: affects implementation.
+//------------------------------------------------------------------------------
+
+//************
+//* Includes *
+//************
+
+#include "i2cmd.h"   /* To get some bit-defines */
+
+//------------------------------------------------------------------------------
+// Here is the table of global arrays which represent each type of command
+// supported in the IntelliPort standard loadware. See also i2cmd.h
+// for a more complete explanation of what is going on.
+//------------------------------------------------------------------------------
+
+// Here are the various globals: note that the names are not used except through
+// the macros defined in i2cmd.h. Also note that although they are character
+// arrays here (for extendability) they are cast to structure pointers in the
+// i2cmd.h macros. See i2cmd.h for flags definitions.
+
+//                     Length Flags Command
+static UCHAR ct02[] = { 1, BTH,     0x02                     }; // DTR UP
+static UCHAR ct03[] = { 1, BTH,     0x03                     }; // DTR DN
+static UCHAR ct04[] = { 1, BTH,     0x04                     }; // RTS UP
+static UCHAR ct05[] = { 1, BTH,     0x05                     }; // RTS DN
+static UCHAR ct06[] = { 1, BYP,     0x06                     }; // START FL
+static UCHAR ct07[] = { 2, BTH,     0x07,0                   }; // BAUD
+static UCHAR ct08[] = { 2, BTH,     0x08,0                   }; // BITS
+static UCHAR ct09[] = { 2, BTH,     0x09,0                   }; // STOP
+static UCHAR ct10[] = { 2, BTH,     0x0A,0                   }; // PARITY
+static UCHAR ct11[] = { 2, BTH,     0x0B,0                   }; // XON
+static UCHAR ct12[] = { 2, BTH,     0x0C,0                   }; // XOFF
+static UCHAR ct13[] = { 1, BTH,     0x0D                     }; // STOP FL
+static UCHAR ct14[] = { 1, BYP|VIP, 0x0E                     }; // ACK HOTK
+//static UCHAR ct15[]={ 2, BTH|VIP, 0x0F,0                   }; // IRQ SET
+static UCHAR ct16[] = { 2, INL,     0x10,0                   }; // IXONOPTS
+static UCHAR ct17[] = { 2, INL,     0x11,0                   }; // OXONOPTS
+static UCHAR ct18[] = { 1, INL,     0x12                     }; // CTSENAB
+static UCHAR ct19[] = { 1, BTH,     0x13                     }; // CTSDSAB
+static UCHAR ct20[] = { 1, INL,     0x14                     }; // DCDENAB
+static UCHAR ct21[] = { 1, BTH,     0x15                     }; // DCDDSAB
+static UCHAR ct22[] = { 1, BTH,     0x16                     }; // DSRENAB
+static UCHAR ct23[] = { 1, BTH,     0x17                     }; // DSRDSAB
+static UCHAR ct24[] = { 1, BTH,     0x18                     }; // RIENAB
+static UCHAR ct25[] = { 1, BTH,     0x19                     }; // RIDSAB
+static UCHAR ct26[] = { 2, BTH,     0x1A,0                   }; // BRKENAB
+static UCHAR ct27[] = { 1, BTH,     0x1B                     }; // BRKDSAB
+//static UCHAR ct28[]={ 2, BTH,     0x1C,0                   }; // MAXBLOKSIZE
+//static UCHAR ct29[]={ 2, 0,       0x1D,0                   }; // reserved
+static UCHAR ct30[] = { 1, INL,     0x1E                     }; // CTSFLOWENAB
+static UCHAR ct31[] = { 1, INL,     0x1F                     }; // CTSFLOWDSAB
+static UCHAR ct32[] = { 1, INL,     0x20                     }; // RTSFLOWENAB
+static UCHAR ct33[] = { 1, INL,     0x21                     }; // RTSFLOWDSAB
+static UCHAR ct34[] = { 2, BTH,     0x22,0                   }; // ISTRIPMODE
+static UCHAR ct35[] = { 2, BTH|END, 0x23,0                   }; // SENDBREAK
+static UCHAR ct36[] = { 2, BTH,     0x24,0                   }; // SETERRMODE
+//static UCHAR ct36a[]={ 3, INL,    0x24,0,0                 }; // SET_REPLACE
+
+// The following is listed for completeness, but should never be sent directly
+// by user-level code. It is sent only by library routines in response to data
+// movement.
+//static UCHAR ct37[]={ 5, BYP|VIP, 0x25,0,0,0,0             }; // FLOW PACKET
+
+// Back to normal
+//static UCHAR ct38[] = {11, BTH|VAR, 0x26,0,0,0,0,0,0,0,0,0,0 }; // DEF KEY SEQ
+//static UCHAR ct39[]={ 3, BTH|END, 0x27,0,0                 }; // OPOSTON
+//static UCHAR ct40[]={ 1, BTH|END, 0x28                     }; // OPOSTOFF
+static UCHAR ct41[] = { 1, BYP,     0x29                     }; // RESUME
+//static UCHAR ct42[]={ 2, BTH,     0x2A,0                   }; // TXBAUD
+//static UCHAR ct43[]={ 2, BTH,     0x2B,0                   }; // RXBAUD
+//static UCHAR ct44[]={ 2, BTH,     0x2C,0                   }; // MS PING
+//static UCHAR ct45[]={ 1, BTH,     0x2D                     }; // HOTENAB
+//static UCHAR ct46[]={ 1, BTH,     0x2E                     }; // HOTDSAB
+static UCHAR ct47[] = { 7, BTH,     0x2F,0,0,0,0,0,0         }; // UNIX FLAGS
+//static UCHAR ct48[]={ 1, BTH,     0x30                     }; // DSRFLOWENAB
+//static UCHAR ct49[]={ 1, BTH,     0x31                     }; // DSRFLOWDSAB
+//static UCHAR ct50[]={ 1, BTH,     0x32                     }; // DTRFLOWENAB
+//static UCHAR ct51[]={ 1, BTH,     0x33                     }; // DTRFLOWDSAB
+//static UCHAR ct52[]={ 1, BTH,     0x34                     }; // BAUDTABRESET
+//static UCHAR ct53[] = { 3, BTH,     0x35,0,0                 }; // BAUDREMAP
+static UCHAR ct54[] = { 3, BTH,     0x36,0,0                 }; // CUSTOMBAUD1
+static UCHAR ct55[] = { 3, BTH,     0x37,0,0                 }; // CUSTOMBAUD2
+static UCHAR ct56[] = { 2, BTH|END, 0x38,0                   }; // PAUSE
+static UCHAR ct57[] = { 1, BYP,     0x39                     }; // SUSPEND
+static UCHAR ct58[] = { 1, BYP,     0x3A                     }; // UNSUSPEND
+static UCHAR ct59[] = { 2, BTH,     0x3B,0                   }; // PARITYCHK
+static UCHAR ct60[] = { 1, INL|VIP, 0x3C                     }; // BOOKMARKREQ
+//static UCHAR ct61[]={ 2, BTH,     0x3D,0                   }; // INTERNALLOOP
+//static UCHAR ct62[]={ 2, BTH,     0x3E,0                   }; // HOTKTIMEOUT
+static UCHAR ct63[] = { 2, INL,     0x3F,0                   }; // SETTXON
+static UCHAR ct64[] = { 2, INL,     0x40,0                   }; // SETTXOFF
+//static UCHAR ct65[]={ 2, BTH,     0x41,0                   }; // SETAUTORTS
+//static UCHAR ct66[]={ 2, BTH,     0x42,0                   }; // SETHIGHWAT
+//static UCHAR ct67[]={ 2, BYP,     0x43,0                   }; // STARTSELFL
+//static UCHAR ct68[]={ 2, INL,     0x44,0                   }; // ENDSELFL
+//static UCHAR ct69[]={ 1, BYP,     0x45                     }; // HWFLOW_OFF
+//static UCHAR ct70[]={ 1, BTH,     0x46                     }; // ODSRFL_ENAB
+//static UCHAR ct71[]={ 1, BTH,     0x47                     }; // ODSRFL_DSAB
+//static UCHAR ct72[]={ 1, BTH,     0x48                     }; // ODCDFL_ENAB
+//static UCHAR ct73[]={ 1, BTH,     0x49                     }; // ODCDFL_DSAB
+//static UCHAR ct74[]={ 2, BTH,     0x4A,0                   }; // LOADLEVEL
+//static UCHAR ct75[]={ 2, BTH,     0x4B,0                   }; // STATDATA
+//static UCHAR ct76[]={ 1, BYP,     0x4C                     }; // BREAK_ON
+//static UCHAR ct77[]={ 1, BYP,     0x4D                     }; // BREAK_OFF
+//static UCHAR ct78[]={ 1, BYP,     0x4E                     }; // GETFC
+static UCHAR ct79[] = { 2, BYP,     0x4F,0                   }; // XMIT_NOW
+//static UCHAR ct80[]={ 4, BTH,     0x50,0,0,0               }; // DIVISOR_LATCH
+//static UCHAR ct81[]={ 1, BYP,     0x51                     }; // GET_STATUS
+//static UCHAR ct82[]={ 1, BYP,     0x52                     }; // GET_TXCNT
+//static UCHAR ct83[]={ 1, BYP,     0x53                     }; // GET_RXCNT
+//static UCHAR ct84[]={ 1, BYP,     0x54                     }; // GET_BOXIDS
+//static UCHAR ct85[]={10, BYP,     0x55,0,0,0,0,0,0,0,0,0   }; // ENAB_MULT
+//static UCHAR ct86[]={ 2, BTH,     0x56,0                   }; // RCV_ENABLE
+static UCHAR ct87[] = { 1, BYP,     0x57                     }; // HW_TEST
+//static UCHAR ct88[]={ 3, BTH,     0x58,0,0                 }; // RCV_THRESHOLD
+static UCHAR ct89[]={ 1, BYP,     0x59                     }; // DSS_NOW
+//static UCHAR ct90[]={ 3, BYP,     0x5A,0,0                 }; // Set SILO
+//static UCHAR ct91[]={ 2, BYP,     0x5B,0                   }; // timed break
+
+// Some composite commands as well
+//static UCHAR cc01[]={ 2, BTH,     0x02,0x04                }; // DTR & RTS UP
+//static UCHAR cc02[]={ 2, BTH,     0x03,0x05                }; // DTR & RTS DN
+
+//********
+//* Code *
+//********
+
+//******************************************************************************
+// Function:   i2cmdUnixFlags(iflag, cflag, lflag)
+// Parameters: Unix tty flags
+//
+// Returns:    Pointer to command structure
+//
+// Description:
+//
+// This routine sets the parameters of command 47 and returns a pointer to the
+// appropriate structure.
+//******************************************************************************
+cmdSyntaxPtr
+i2cmdUnixFlags(unsigned short iflag,unsigned short cflag,unsigned short lflag)
+{
+	cmdSyntaxPtr pCM = (cmdSyntaxPtr) ct47;
+
+	pCM->cmd[1] = (unsigned char)  iflag;
+	pCM->cmd[2] = (unsigned char) (iflag >> 8);
+	pCM->cmd[3] = (unsigned char)  cflag;
+	pCM->cmd[4] = (unsigned char) (cflag >> 8);
+	pCM->cmd[5] = (unsigned char)  lflag;
+	pCM->cmd[6] = (unsigned char) (lflag >> 8);
+	return pCM;
+}
+
+//******************************************************************************
+// Function:   i2cmdBaudDef(which, rate)
+// Parameters: ?
+//
+// Returns:    Pointer to command structure
+//
+// Description:
+//
+// This routine sets the parameters of commands 54 or 55 (according to the
+// argument which), and returns a pointer to the appropriate structure.
+//******************************************************************************
+cmdSyntaxPtr
+i2cmdBaudDef(int which, unsigned short rate)
+{
+	cmdSyntaxPtr pCM;
+
+	switch(which)
+	{
+	case 1:
+		pCM = (cmdSyntaxPtr) ct54;
+		break;
+	default:
+	case 2:
+		pCM = (cmdSyntaxPtr) ct55;
+		break;
+	}
+	pCM->cmd[1] = (unsigned char) rate;
+	pCM->cmd[2] = (unsigned char) (rate >> 8);
+	return pCM;
+}
+
diff --git a/drivers/char/ip2/i2cmd.h b/drivers/char/ip2/i2cmd.h
new file mode 100644
index 0000000..c41728a
--- /dev/null
+++ b/drivers/char/ip2/i2cmd.h
@@ -0,0 +1,643 @@
+/*******************************************************************************
+*
+*   (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Definitions and support for In-line and Bypass commands.
+*                Applicable only when the standard loadware is active.
+*
+*******************************************************************************/
+//------------------------------------------------------------------------------
+// Revision History:
+//
+// 10 October 1991   MAG First Draft
+//  7 November 1991  MAG Reflects some new commands
+// 20 February 1992  MAG CMD_HOTACK corrected: no argument.
+// 24 February 1992  MAG Support added for new commands for 1.4.x loadware.
+// 11 March 1992     MAG Additional commands.
+// 16 March 1992     MAG Additional commands.
+// 30 March 1992     MAG Additional command: CMD_DSS_NOW
+// 18 May   1992     MAG Changed CMD_OPOST
+//
+//------------------------------------------------------------------------------
+#ifndef I2CMD_H      // To prevent multiple includes
+#define I2CMD_H   1
+
+#include "ip2types.h"
+
+// This module is designed to provide a uniform method of sending commands to
+// the board through command packets. The difficulty is, some commands take
+// parameters, others do not. Furthermore, it is often useful to send several
+// commands to the same channel as part of the same packet. (See also i2pack.h.)
+//
+// This module is designed so that the caller should not be responsible for
+// remembering the exact syntax of each command, or at least so that the
+// compiler could check things somewhat. I'll explain as we go...
+//
+// First, a structure which can embody the syntax of each type of command.
+//
+typedef struct _cmdSyntax
+{
+	UCHAR length;   // Number of bytes in the command
+	UCHAR flags;    // Information about the command (see below)
+
+	// The command and its parameters, which may be of arbitrary length. Don't
+	// worry yet how the parameters will be initialized; macros later take care
+	// of it. Also, don't worry about the arbitrary length issue; this structure
+	// is never used to allocate space (see i2cmd.c).
+	UCHAR cmd[2];
+} cmdSyntax, *cmdSyntaxPtr;
+
+// Bit assignments for flags
+
+#define INL 1           // Set if suitable for inline commands
+#define BYP 2           // Set if suitable for bypass commands
+#define BTH (INL|BYP)   // suitable for either!
+#define END 4           // Set if this must be the last command in a block
+#define VIP 8           // Set if this command is special in some way and really
+						// should only be sent from the library-level and not
+						// directly from user-level
+#define VAR 0x10        // This command is of variable length!
+
+//-----------------------------------
+// External declarations for i2cmd.c
+//-----------------------------------
+// Routine to set up parameters for the "define hot-key sequence" command. Since
+// there is more than one parameter to assign, we must use a function rather
+// than a macro (used usually).
+//
+extern cmdSyntaxPtr i2cmdUnixFlags(USHORT iflag,USHORT cflag,USHORT lflag);
+extern cmdSyntaxPtr i2cmdBaudDef(int which, USHORT rate);
+
+// Declarations for the global arrays used to bear the commands and their
+// arguments.
+//
+// Note: Since these are globals and the arguments might change, it is important
+// that the library routine COPY these into buffers from whence they would be
+// sent, rather than merely storing the pointers. In multi-threaded
+// environments, important that the copy should obtain before any context switch
+// is allowed. Also, for parameterized commands, DO NOT ISSUE THE SAME COMMAND
+// MORE THAN ONCE WITH THE SAME PARAMETERS in the same call.
+//
+static UCHAR ct02[];
+static UCHAR ct03[];
+static UCHAR ct04[];
+static UCHAR ct05[];
+static UCHAR ct06[];
+static UCHAR ct07[];
+static UCHAR ct08[];
+static UCHAR ct09[];
+static UCHAR ct10[];
+static UCHAR ct11[];
+static UCHAR ct12[];
+static UCHAR ct13[];
+static UCHAR ct14[];
+static UCHAR ct15[];
+static UCHAR ct16[];
+static UCHAR ct17[];
+static UCHAR ct18[];
+static UCHAR ct19[];
+static UCHAR ct20[];
+static UCHAR ct21[];
+static UCHAR ct22[];
+static UCHAR ct23[];
+static UCHAR ct24[];
+static UCHAR ct25[];
+static UCHAR ct26[];
+static UCHAR ct27[];
+static UCHAR ct28[];
+static UCHAR ct29[];
+static UCHAR ct30[];
+static UCHAR ct31[];
+static UCHAR ct32[];
+static UCHAR ct33[];
+static UCHAR ct34[];
+static UCHAR ct35[];
+static UCHAR ct36[];
+static UCHAR ct36a[];
+static UCHAR ct41[];
+static UCHAR ct42[];
+static UCHAR ct43[];
+static UCHAR ct44[];
+static UCHAR ct45[];
+static UCHAR ct46[];
+static UCHAR ct48[];
+static UCHAR ct49[];
+static UCHAR ct50[];
+static UCHAR ct51[];
+static UCHAR ct52[];
+static UCHAR ct56[];
+static UCHAR ct57[];
+static UCHAR ct58[];
+static UCHAR ct59[];
+static UCHAR ct60[];
+static UCHAR ct61[];
+static UCHAR ct62[];
+static UCHAR ct63[];
+static UCHAR ct64[];
+static UCHAR ct65[];
+static UCHAR ct66[];
+static UCHAR ct67[];
+static UCHAR ct68[];
+static UCHAR ct69[];
+static UCHAR ct70[];
+static UCHAR ct71[];
+static UCHAR ct72[];
+static UCHAR ct73[];
+static UCHAR ct74[];
+static UCHAR ct75[];
+static UCHAR ct76[];
+static UCHAR ct77[];
+static UCHAR ct78[];
+static UCHAR ct79[];
+static UCHAR ct80[];
+static UCHAR ct81[];
+static UCHAR ct82[];
+static UCHAR ct83[];
+static UCHAR ct84[];
+static UCHAR ct85[];
+static UCHAR ct86[];
+static UCHAR ct87[];
+static UCHAR ct88[];
+static UCHAR ct89[];
+static UCHAR ct90[];
+static UCHAR ct91[];
+static UCHAR cc01[];
+static UCHAR cc02[];
+
+// Now, refer to i2cmd.c, and see the character arrays defined there. They are
+// cast here to cmdSyntaxPtr.
+//
+// There are library functions for issuing bypass or inline commands. These
+// functions take one or more arguments of the type cmdSyntaxPtr. The routine
+// then can figure out how long each command is supposed to be and easily add it
+// to the list.
+//
+// For ease of use, we define manifests which return pointers to appropriate
+// cmdSyntaxPtr things. But some commands also take arguments. If a single
+// argument is used, we define a macro which performs the single assignment and
+// (through the expedient of a comma expression) references the appropriate
+// pointer. For commands requiring several arguments, we actually define a
+// function to perform the assignments.
+
+#define CMD_DTRUP	(cmdSyntaxPtr)(ct02)	// Raise DTR
+#define CMD_DTRDN	(cmdSyntaxPtr)(ct03)	// Lower DTR
+#define CMD_RTSUP	(cmdSyntaxPtr)(ct04)	// Raise RTS
+#define CMD_RTSDN	(cmdSyntaxPtr)(ct05)	// Lower RTS
+#define CMD_STARTFL	(cmdSyntaxPtr)(ct06)	// Start Flushing Data
+
+#define CMD_DTRRTS_UP (cmdSyntaxPtr)(cc01)	// Raise DTR and RTS
+#define CMD_DTRRTS_DN (cmdSyntaxPtr)(cc02)	// Lower DTR and RTS
+
+// Set Baud Rate for transmit and receive
+#define CMD_SETBAUD(arg) \
+	(((cmdSyntaxPtr)(ct07))->cmd[1] = (arg),(cmdSyntaxPtr)(ct07))
+
+#define CBR_50       1
+#define CBR_75       2
+#define CBR_110      3
+#define CBR_134      4
+#define CBR_150      5
+#define CBR_200      6
+#define CBR_300      7
+#define CBR_600      8
+#define CBR_1200     9
+#define CBR_1800     10
+#define CBR_2400     11
+#define CBR_4800     12
+#define CBR_9600     13
+#define CBR_19200    14
+#define CBR_38400    15
+#define CBR_2000     16
+#define CBR_3600     17
+#define CBR_7200     18
+#define CBR_56000    19
+#define CBR_57600    20
+#define CBR_64000    21
+#define CBR_76800    22
+#define CBR_115200   23
+#define CBR_C1       24    // Custom baud rate 1
+#define CBR_C2       25    // Custom baud rate 2
+#define CBR_153600   26
+#define CBR_230400   27
+#define CBR_307200   28
+#define CBR_460800   29
+#define CBR_921600   30
+
+// Set Character size
+//
+#define CMD_SETBITS(arg) \
+	(((cmdSyntaxPtr)(ct08))->cmd[1] = (arg),(cmdSyntaxPtr)(ct08))
+
+#define CSZ_5  0
+#define CSZ_6  1
+#define CSZ_7  2
+#define CSZ_8  3
+
+// Set number of stop bits
+//
+#define CMD_SETSTOP(arg) \
+	(((cmdSyntaxPtr)(ct09))->cmd[1] = (arg),(cmdSyntaxPtr)(ct09))
+
+#define CST_1  0
+#define CST_15 1  // 1.5 stop bits
+#define CST_2  2
+
+// Set parity option
+//
+#define CMD_SETPAR(arg) \
+	(((cmdSyntaxPtr)(ct10))->cmd[1] = (arg),(cmdSyntaxPtr)(ct10))
+
+#define CSP_NP 0  // no parity
+#define CSP_OD 1  // odd parity
+#define CSP_EV 2  // Even parity
+#define CSP_SP 3  // Space parity
+#define CSP_MK 4  // Mark parity
+
+// Define xon char for transmitter flow control
+//
+#define CMD_DEF_IXON(arg) \
+	(((cmdSyntaxPtr)(ct11))->cmd[1] = (arg),(cmdSyntaxPtr)(ct11))
+
+// Define xoff char for transmitter flow control
+//
+#define CMD_DEF_IXOFF(arg) \
+	(((cmdSyntaxPtr)(ct12))->cmd[1] = (arg),(cmdSyntaxPtr)(ct12))
+
+#define CMD_STOPFL   (cmdSyntaxPtr)(ct13) // Stop Flushing data
+
+// Acknowledge receipt of hotkey signal
+//
+#define CMD_HOTACK   (cmdSyntaxPtr)(ct14)
+
+// Define irq level to use. Should actually be sent by library-level code, not
+// directly from user...
+//
+#define CMDVALUE_IRQ 15 // For library use at initialization. Until this command
+						// is sent, board processing doesn't really start.
+#define CMD_SET_IRQ(arg) \
+	(((cmdSyntaxPtr)(ct15))->cmd[1] = (arg),(cmdSyntaxPtr)(ct15))
+
+#define CIR_POLL  0  // No IRQ - Poll
+#define CIR_3     3  // IRQ 3
+#define CIR_4     4  // IRQ 4
+#define CIR_5     5  // IRQ 5
+#define CIR_7     7  // IRQ 7
+#define CIR_10    10 // IRQ 10
+#define CIR_11    11 // IRQ 11
+#define CIR_12    12 // IRQ 12
+#define CIR_15    15 // IRQ 15
+
+// Select transmit flow xon/xoff options
+//
+#define CMD_IXON_OPT(arg) \
+	(((cmdSyntaxPtr)(ct16))->cmd[1] = (arg),(cmdSyntaxPtr)(ct16))
+
+#define CIX_NONE  0  // Incoming Xon/Xoff characters not special
+#define CIX_XON   1  // Xoff disable, Xon enable
+#define CIX_XANY  2  // Xoff disable, any key enable
+
+// Select receive flow xon/xoff options
+//
+#define CMD_OXON_OPT(arg) \
+	(((cmdSyntaxPtr)(ct17))->cmd[1] = (arg),(cmdSyntaxPtr)(ct17))
+
+#define COX_NONE  0  // Don't send Xon/Xoff
+#define COX_XON   1  // Send xon/xoff to start/stop incoming data
+
+
+#define CMD_CTS_REP  (cmdSyntaxPtr)(ct18) // Enable  CTS reporting
+#define CMD_CTS_NREP (cmdSyntaxPtr)(ct19) // Disable CTS reporting
+
+#define CMD_DCD_REP  (cmdSyntaxPtr)(ct20) // Enable  DCD reporting
+#define CMD_DCD_NREP (cmdSyntaxPtr)(ct21) // Disable DCD reporting
+
+#define CMD_DSR_REP  (cmdSyntaxPtr)(ct22) // Enable  DSR reporting
+#define CMD_DSR_NREP (cmdSyntaxPtr)(ct23) // Disable DSR reporting
+
+#define CMD_RI_REP   (cmdSyntaxPtr)(ct24) // Enable  RI  reporting
+#define CMD_RI_NREP  (cmdSyntaxPtr)(ct25) // Disable RI  reporting
+
+// Enable break reporting and select style
+//
+#define CMD_BRK_REP(arg) \
+	(((cmdSyntaxPtr)(ct26))->cmd[1] = (arg),(cmdSyntaxPtr)(ct26))
+
+#define CBK_STAT     0x00  // Report breaks as a status (exception,irq)
+#define CBK_NULL     0x01  // Report breaks as a good null
+#define CBK_STAT_SEQ 0x02  // Report breaks as a status AND as in-band character
+                           //  sequence FFh, 01h, 10h
+#define CBK_SEQ      0x03  // Report breaks as the in-band 
+						   //sequence FFh, 01h, 10h ONLY.
+#define CBK_FLSH     0x04  // if this bit set also flush input data
+#define CBK_POSIX    0x08  // if this bit set report as FF,0,0 sequence
+#define CBK_SINGLE   0x10  // if this bit set with CBK_SEQ or CBK_STAT_SEQ
+						   //then reports single null instead of triple
+
+#define CMD_BRK_NREP (cmdSyntaxPtr)(ct27) // Disable break reporting
+
+// Specify maximum block size for received data
+//
+#define CMD_MAX_BLOCK(arg) \
+	(((cmdSyntaxPtr)(ct28))->cmd[1] = (arg),(cmdSyntaxPtr)(ct28))
+
+// -- COMMAND 29 is reserved --
+
+#define CMD_CTSFL_ENAB  (cmdSyntaxPtr)(ct30) // Enable  CTS flow control
+#define CMD_CTSFL_DSAB  (cmdSyntaxPtr)(ct31) // Disable CTS flow control
+#define CMD_RTSFL_ENAB  (cmdSyntaxPtr)(ct32) // Enable  RTS flow control
+#define CMD_RTSFL_DSAB  (cmdSyntaxPtr)(ct33) // Disable RTS flow control
+
+// Specify istrip option
+//
+#define CMD_ISTRIP_OPT(arg) \
+	(((cmdSyntaxPtr)(ct34))->cmd[1] = (arg),(cmdSyntaxPtr)(ct34))
+
+#define CIS_NOSTRIP  0  // Strip characters to character size
+#define CIS_STRIP    1  // Strip any 8-bit characters to 7 bits
+
+// Send a break of arg milliseconds
+//
+#define CMD_SEND_BRK(arg) \
+	(((cmdSyntaxPtr)(ct35))->cmd[1] = (arg),(cmdSyntaxPtr)(ct35))
+
+// Set error reporting mode
+//
+#define CMD_SET_ERROR(arg) \
+	(((cmdSyntaxPtr)(ct36))->cmd[1] = (arg),(cmdSyntaxPtr)(ct36))
+
+#define CSE_ESTAT 0  // Report error in a status packet
+#define CSE_NOREP 1  // Treat character as though it were good
+#define CSE_DROP  2  // Discard the character
+#define CSE_NULL  3  // Replace with a null
+#define CSE_MARK  4  // Replace with a 3-character sequence (as Unix)
+
+#define  CMD_SET_REPLACEMENT(arg,ch)   \
+			(((cmdSyntaxPtr)(ct36a))->cmd[1] = (arg), \
+			(((cmdSyntaxPtr)(ct36a))->cmd[2] = (ch),  \
+			(cmdSyntaxPtr)(ct36a))
+
+#define CSE_REPLACE  0x8	// Replace the errored character with the
+							// replacement character defined here
+
+#define CSE_STAT_REPLACE   0x18	// Replace the errored character with the
+								// replacement character defined here AND
+								// report the error as a status packet (as in
+								// CSE_ESTAT).
+
+
+// COMMAND 37, to send flow control packets, is handled only by low-level
+// library code in response to data movement and shouldn't ever be sent by the
+// user code. See i2pack.h and the body of i2lib.c for details.
+
+// Enable on-board post-processing, using options given in oflag argument.
+// Formerly, this command was automatically preceded by a CMD_OPOST_OFF command
+// because the loadware does not permit sending back-to-back CMD_OPOST_ON
+// commands without an intervening CMD_OPOST_OFF. BUT, WE LEARN 18 MAY 92, that
+// CMD_OPOST_ON and CMD_OPOST_OFF must each be at the end of a packet (or in a
+// solo packet). This means the caller must specify separately CMD_OPOST_OFF,
+// CMD_OPOST_ON(parm) when he calls i2QueueCommands(). That function will ensure
+// each gets a separate packet. Extra CMD_OPOST_OFF's are always ok.
+//
+#define CMD_OPOST_ON(oflag)   \
+	(*(USHORT *)(((cmdSyntaxPtr)(ct39))->cmd[1]) = (oflag), \
+		(cmdSyntaxPtr)(ct39))
+
+#define CMD_OPOST_OFF   (cmdSyntaxPtr)(ct40) // Disable on-board post-proc
+
+#define CMD_RESUME   (cmdSyntaxPtr)(ct41)	// Resume: behave as though an XON
+											// were received;
+
+// Set Transmit baud rate (see command 7 for arguments)
+//
+#define CMD_SETBAUD_TX(arg) \
+	(((cmdSyntaxPtr)(ct42))->cmd[1] = (arg),(cmdSyntaxPtr)(ct42))
+
+// Set Receive baud rate (see command 7 for arguments)
+//
+#define CMD_SETBAUD_RX(arg) \
+	(((cmdSyntaxPtr)(ct43))->cmd[1] = (arg),(cmdSyntaxPtr)(ct43))
+
+// Request interrupt from board each arg milliseconds. Interrupt will specify
+// "received data", even though there may be no data present. If arg == 0,
+// disables any such interrupts.
+//
+#define CMD_PING_REQ(arg) \
+	(((cmdSyntaxPtr)(ct44))->cmd[1] = (arg),(cmdSyntaxPtr)(ct44))
+
+#define CMD_HOT_ENAB (cmdSyntaxPtr)(ct45) // Enable Hot-key checking
+#define CMD_HOT_DSAB (cmdSyntaxPtr)(ct46) // Disable Hot-key checking
+
+// COMMAND 47: Send Protocol info via Unix flags:
+// iflag = Unix tty t_iflag
+// cflag = Unix tty t_cflag
+// lflag = Unix tty t_lflag
+// See System V Unix/Xenix documentation for the meanings of the bit fields
+// within these flags
+//
+#define CMD_UNIX_FLAGS(iflag,cflag,lflag) i2cmdUnixFlags(iflag,cflag,lflag)
+
+#define CMD_DSRFL_ENAB  (cmdSyntaxPtr)(ct48) // Enable  DSR receiver ctrl
+#define CMD_DSRFL_DSAB  (cmdSyntaxPtr)(ct49) // Disable DSR receiver ctrl
+#define CMD_DTRFL_ENAB  (cmdSyntaxPtr)(ct50) // Enable  DTR flow control
+#define CMD_DTRFL_DSAB  (cmdSyntaxPtr)(ct51) // Disable DTR flow control
+#define CMD_BAUD_RESET  (cmdSyntaxPtr)(ct52) // Reset baudrate table
+
+// COMMAND 54: Define custom rate #1
+// rate = (short) 1/10 of the desired baud rate
+//
+#define CMD_BAUD_DEF1(rate) i2cmdBaudDef(1,rate)
+
+// COMMAND 55: Define custom rate #2
+// rate = (short) 1/10 of the desired baud rate
+//
+#define CMD_BAUD_DEF2(rate) i2cmdBaudDef(2,rate)
+
+// Pause arg hundredths of seconds. (Note, this is NOT milliseconds.)
+//
+#define CMD_PAUSE(arg) \
+	(((cmdSyntaxPtr)(ct56))->cmd[1] = (arg),(cmdSyntaxPtr)(ct56))
+
+#define CMD_SUSPEND     (cmdSyntaxPtr)(ct57) // Suspend output
+#define CMD_UNSUSPEND   (cmdSyntaxPtr)(ct58) // Un-Suspend output
+
+// Set parity-checking options
+//
+#define CMD_PARCHK(arg) \
+	(((cmdSyntaxPtr)(ct59))->cmd[1] = (arg),(cmdSyntaxPtr)(ct59))
+
+#define CPK_ENAB  0     // Enable parity checking on input
+#define CPK_DSAB  1     // Disable parity checking on input
+
+#define CMD_BMARK_REQ   (cmdSyntaxPtr)(ct60) // Bookmark request
+
+
+// Enable/Disable internal loopback mode
+//
+#define CMD_INLOOP(arg) \
+	(((cmdSyntaxPtr)(ct61))->cmd[1] = (arg),(cmdSyntaxPtr)(ct61))
+
+#define CIN_DISABLE  0  // Normal operation (default)
+#define CIN_ENABLE   1  // Internal (local) loopback
+#define CIN_REMOTE   2  // Remote loopback
+
+// Specify timeout for hotkeys: Delay will be (arg x 10) milliseconds, arg == 0
+// --> no timeout: wait forever.
+//
+#define CMD_HOT_TIME(arg) \
+	(((cmdSyntaxPtr)(ct62))->cmd[1] = (arg),(cmdSyntaxPtr)(ct62))
+
+
+// Define (outgoing) xon for receive flow control
+//
+#define CMD_DEF_OXON(arg) \
+	(((cmdSyntaxPtr)(ct63))->cmd[1] = (arg),(cmdSyntaxPtr)(ct63))
+
+// Define (outgoing) xoff for receiver flow control
+//
+#define CMD_DEF_OXOFF(arg) \
+	(((cmdSyntaxPtr)(ct64))->cmd[1] = (arg),(cmdSyntaxPtr)(ct64))
+
+// Enable/Disable RTS on transmit (1/2 duplex-style)
+//
+#define CMD_RTS_XMIT(arg) \
+	(((cmdSyntaxPtr)(ct65))->cmd[1] = (arg),(cmdSyntaxPtr)(ct65))
+
+#define CHD_DISABLE  0
+#define CHD_ENABLE   1
+
+// Set high-water-mark level (debugging use only)
+//
+#define CMD_SETHIGHWAT(arg) \
+	(((cmdSyntaxPtr)(ct66))->cmd[1] = (arg),(cmdSyntaxPtr)(ct66))
+
+// Start flushing tagged data (tag = 0-14)
+//
+#define CMD_START_SELFL(tag) \
+	(((cmdSyntaxPtr)(ct67))->cmd[1] = (tag),(cmdSyntaxPtr)(ct67))
+
+// End flushing tagged data (tag = 0-14)
+//
+#define CMD_END_SELFL(tag) \
+	(((cmdSyntaxPtr)(ct68))->cmd[1] = (tag),(cmdSyntaxPtr)(ct68))
+
+#define CMD_HWFLOW_OFF  (cmdSyntaxPtr)(ct69) // Disable HW TX flow control
+#define CMD_ODSRFL_ENAB (cmdSyntaxPtr)(ct70) // Enable DSR output f/c
+#define CMD_ODSRFL_DSAB (cmdSyntaxPtr)(ct71) // Disable DSR output f/c
+#define CMD_ODCDFL_ENAB (cmdSyntaxPtr)(ct72) // Enable DCD output f/c
+#define CMD_ODCDFL_DSAB (cmdSyntaxPtr)(ct73) // Disable DCD output f/c
+
+// Set transmit interrupt load level. Count should be an even value 2-12
+//
+#define CMD_LOADLEVEL(count) \
+	(((cmdSyntaxPtr)(ct74))->cmd[1] = (count),(cmdSyntaxPtr)(ct74))
+
+// If reporting DSS changes, map to character sequence FFh, 2, MSR
+//
+#define CMD_STATDATA(arg) \
+	(((cmdSyntaxPtr)(ct75))->cmd[1] = (arg),(cmdSyntaxPtr)(ct75))
+
+#define CSTD_DISABLE// Report DSS changes as status packets only (default)
+#define CSTD_ENABLE	// Report DSS changes as in-band data sequence as well as
+					// by status packet.
+
+#define CMD_BREAK_ON    (cmdSyntaxPtr)(ct76)// Set break and stop xmit
+#define CMD_BREAK_OFF   (cmdSyntaxPtr)(ct77)// End break and restart xmit
+#define CMD_GETFC       (cmdSyntaxPtr)(ct78)// Request for flow control packet
+											// from board.
+
+// Transmit this character immediately
+//
+#define CMD_XMIT_NOW(ch) \
+	(((cmdSyntaxPtr)(ct79))->cmd[1] = (ch),(cmdSyntaxPtr)(ct79))
+
+// Set baud rate via "divisor latch"
+//
+#define CMD_DIVISOR_LATCH(which,value) \
+			(((cmdSyntaxPtr)(ct80))->cmd[1] = (which), \
+			*(USHORT *)(((cmdSyntaxPtr)(ct80))->cmd[2]) = (value), \
+			(cmdSyntaxPtr)(ct80))
+
+#define CDL_RX 1	// Set receiver rate
+#define CDL_TX 2	// Set transmit rate
+					// (CDL_TX | CDL_RX) Set both rates
+
+// Request for special diagnostic status pkt from the board.
+//
+#define CMD_GET_STATUS (cmdSyntaxPtr)(ct81)
+
+// Request time-stamped transmit character count packet.
+//
+#define CMD_GET_TXCNT  (cmdSyntaxPtr)(ct82)
+
+// Request time-stamped receive character count packet.
+//
+#define CMD_GET_RXCNT  (cmdSyntaxPtr)(ct83)
+
+// Request for box/board I.D. packet.
+#define CMD_GET_BOXIDS (cmdSyntaxPtr)(ct84)
+
+// Enable or disable multiple channels according to bit-mapped ushorts box 1-4
+//
+#define CMD_ENAB_MULT(enable, box1, box2, box3, box4)    \
+			(((cmdSytaxPtr)(ct85))->cmd[1] = (enable),            \
+			*(USHORT *)(((cmdSyntaxPtr)(ct85))->cmd[2]) = (box1), \
+			*(USHORT *)(((cmdSyntaxPtr)(ct85))->cmd[4]) = (box2), \
+			*(USHORT *)(((cmdSyntaxPtr)(ct85))->cmd[6]) = (box3), \
+			*(USHORT *)(((cmdSyntaxPtr)(ct85))->cmd[8]) = (box4), \
+			(cmdSyntaxPtr)(ct85))
+
+#define CEM_DISABLE  0
+#define CEM_ENABLE   1
+
+// Enable or disable receiver or receiver interrupts (default both enabled)
+//
+#define CMD_RCV_ENABLE(ch) \
+	(((cmdSyntaxPtr)(ct86))->cmd[1] = (ch),(cmdSyntaxPtr)(ct86))
+
+#define CRE_OFF      0  // Disable the receiver
+#define CRE_ON       1  // Enable the receiver
+#define CRE_INTOFF   2  // Disable receiver interrupts (to loadware)
+#define CRE_INTON    3  // Enable receiver interrupts (to loadware)
+
+// Starts up a hardware test process, which runs transparently, and sends a
+// STAT_HWFAIL packet in case a hardware failure is detected.
+//
+#define CMD_HW_TEST  (cmdSyntaxPtr)(ct87)
+
+// Change receiver threshold and timeout value:
+// Defaults: timeout = 20mS
+// threshold count = 8 when DTRflow not in use,
+// threshold count = 5 when DTRflow in use.
+//
+#define CMD_RCV_THRESHOLD(count,ms) \
+			(((cmdSyntaxPtr)(ct88))->cmd[1] = (count), \
+			((cmdSyntaxPtr)(ct88))->cmd[2] = (ms), \
+			(cmdSyntaxPtr)(ct88))
+
+// Makes the loadware report DSS signals for this channel immediately.
+//
+#define CMD_DSS_NOW (cmdSyntaxPtr)(ct89)
+	
+// Set the receive silo parameters 
+// 	timeout is ms idle wait until delivery       (~VTIME)
+// 	threshold is max characters cause interrupt  (~VMIN)
+//
+#define CMD_SET_SILO(timeout,threshold) \
+			(((cmdSyntaxPtr)(ct90))->cmd[1] = (timeout), \
+			((cmdSyntaxPtr)(ct90))->cmd[2]  = (threshold), \
+			(cmdSyntaxPtr)(ct90))
+
+// Set timed break in decisecond (1/10s)
+//
+#define CMD_LBREAK(ds) \
+	(((cmdSyntaxPtr)(ct91))->cmd[1] = (ds),(cmdSyntaxPtr)(ct66))
+
+
+
+#endif // I2CMD_H
diff --git a/drivers/char/ip2/i2ellis.c b/drivers/char/ip2/i2ellis.c
new file mode 100644
index 0000000..f834d05
--- /dev/null
+++ b/drivers/char/ip2/i2ellis.c
@@ -0,0 +1,1487 @@
+/*******************************************************************************
+*
+*   (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Low-level interface code for the device driver
+*                (This is included source code, not a separate compilation
+*                module.)
+*
+*******************************************************************************/
+//---------------------------------------------
+// Function declarations private to this module
+//---------------------------------------------
+// Functions called only indirectly through i2eBordStr entries.
+
+static int iiWriteBuf16(i2eBordStrPtr, unsigned char *, int);
+static int iiWriteBuf8(i2eBordStrPtr, unsigned char *, int);
+static int iiReadBuf16(i2eBordStrPtr, unsigned char *, int);
+static int iiReadBuf8(i2eBordStrPtr, unsigned char *, int);
+
+static unsigned short iiReadWord16(i2eBordStrPtr);
+static unsigned short iiReadWord8(i2eBordStrPtr);
+static void iiWriteWord16(i2eBordStrPtr, unsigned short);
+static void iiWriteWord8(i2eBordStrPtr, unsigned short);
+
+static int iiWaitForTxEmptyII(i2eBordStrPtr, int);
+static int iiWaitForTxEmptyIIEX(i2eBordStrPtr, int);
+static int iiTxMailEmptyII(i2eBordStrPtr);
+static int iiTxMailEmptyIIEX(i2eBordStrPtr);
+static int iiTrySendMailII(i2eBordStrPtr, unsigned char);
+static int iiTrySendMailIIEX(i2eBordStrPtr, unsigned char);
+
+static unsigned short iiGetMailII(i2eBordStrPtr);
+static unsigned short iiGetMailIIEX(i2eBordStrPtr);
+
+static void iiEnableMailIrqII(i2eBordStrPtr);
+static void iiEnableMailIrqIIEX(i2eBordStrPtr);
+static void iiWriteMaskII(i2eBordStrPtr, unsigned char);
+static void iiWriteMaskIIEX(i2eBordStrPtr, unsigned char);
+
+static void ii2DelayTimer(unsigned int);
+static void ii2DelayWakeup(unsigned long id);
+static void ii2Nop(void);
+
+//***************
+//* Static Data *
+//***************
+
+static int ii2Safe;         // Safe I/O address for delay routine
+
+static int iiDelayed;	// Set when the iiResetDelay function is
+							// called. Cleared when ANY board is reset.
+static struct timer_list * pDelayTimer;   // Used by iiDelayTimer
+static wait_queue_head_t pDelayWait;    // Used by iiDelayTimer
+static rwlock_t Dl_spinlock;
+
+//********
+//* Code *
+//********
+
+//=======================================================
+// Initialization Routines
+//
+// iiSetAddress
+// iiReset
+// iiResetDelay
+// iiInitialize
+//=======================================================
+
+//******************************************************************************
+// Function:   iiEllisInit()
+// Parameters: None
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// This routine performs any required initialization of the iiEllis subsystem.
+//
+//******************************************************************************
+static void
+iiEllisInit(void)
+{
+	pDelayTimer = kmalloc ( sizeof (struct timer_list), GFP_KERNEL );
+	init_timer(pDelayTimer);
+	init_waitqueue_head(&pDelayWait);
+	LOCK_INIT(&Dl_spinlock);
+}
+
+//******************************************************************************
+// Function:   iiEllisCleanup()
+// Parameters: None
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// This routine performs any required cleanup of the iiEllis subsystem.
+//
+//******************************************************************************
+static void
+iiEllisCleanup(void)
+{
+	if ( pDelayTimer != NULL ) {
+		kfree ( pDelayTimer );
+	}
+}
+
+//******************************************************************************
+// Function:   iiSetAddress(pB, address, delay)
+// Parameters: pB      - pointer to the board structure
+//             address - the purported I/O address of the board
+//             delay   - pointer to the 1-ms delay function to use
+//                       in this and any future operations to this board
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// This routine (roughly) checks for address validity, sets the i2eValid OK and
+// sets the state to II_STATE_COLD which means that we haven't even sent a reset
+// yet.
+//
+//******************************************************************************
+static int
+iiSetAddress( i2eBordStrPtr pB, int address, delayFunc_t delay )
+{
+	// Should any failure occur before init is finished...
+	pB->i2eValid = I2E_INCOMPLETE;
+
+	// Cannot check upper limit except extremely: Might be microchannel
+	// Address must be on an 8-byte boundary
+
+	if ((unsigned int)address <= 0x100
+		|| (unsigned int)address >= 0xfff8
+		|| (address & 0x7)
+		)
+	{
+		COMPLETE(pB,I2EE_BADADDR);
+	}
+
+	// Initialize accelerators
+	pB->i2eBase    = address;
+	pB->i2eData    = address + FIFO_DATA;
+	pB->i2eStatus  = address + FIFO_STATUS;
+	pB->i2ePointer = address + FIFO_PTR;
+	pB->i2eXMail   = address + FIFO_MAIL;
+	pB->i2eXMask   = address + FIFO_MASK;
+
+	// Initialize i/o address for ii2DelayIO
+	ii2Safe = address + FIFO_NOP;
+
+	// Initialize the delay routine
+	pB->i2eDelay = ((delay != (delayFunc_t)NULL) ? delay : (delayFunc_t)ii2Nop);
+
+	pB->i2eValid = I2E_MAGIC;
+	pB->i2eState = II_STATE_COLD;
+
+	COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function:   iiReset(pB)
+// Parameters: pB - pointer to the board structure
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Attempts to reset the board (see also i2hw.h). Normally, we would use this to
+// reset a board immediately after iiSetAddress(), but it is valid to reset a
+// board from any state, say, in order to change or re-load loadware. (Under
+// such circumstances, no reason to re-run iiSetAddress(), which is why it is a
+// separate routine and not included in this routine.
+//
+//******************************************************************************
+static int
+iiReset(i2eBordStrPtr pB)
+{
+	// Magic number should be set, else even the address is suspect
+	if (pB->i2eValid != I2E_MAGIC)
+	{
+		COMPLETE(pB, I2EE_BADMAGIC);
+	}
+
+	OUTB(pB->i2eBase + FIFO_RESET, 0);  // Any data will do
+	iiDelay(pB, 50);                    // Pause between resets
+	OUTB(pB->i2eBase + FIFO_RESET, 0);  // Second reset
+
+	// We must wait before even attempting to read anything from the FIFO: the
+	// board's P.O.S.T may actually attempt to read and write its end of the
+	// FIFO in order to check flags, loop back (where supported), etc. On
+	// completion of this testing it would reset the FIFO, and on completion
+	// of all // P.O.S.T., write the message. We must not mistake data which
+	// might have been sent for testing as part of the reset message. To
+	// better utilize time, say, when resetting several boards, we allow the
+	// delay to be performed externally; in this way the caller can reset 
+	// several boards, delay a single time, then call the initialization
+	// routine for all.
+
+	pB->i2eState = II_STATE_RESET;
+
+	iiDelayed = 0;	// i.e., the delay routine hasn't been called since the most
+					// recent reset.
+
+	// Ensure anything which would have been of use to standard loadware is
+	// blanked out, since board has now forgotten everything!.
+
+	pB->i2eUsingIrq = IRQ_UNDEFINED; // Not set up to use an interrupt yet
+	pB->i2eWaitingForEmptyFifo = 0;
+	pB->i2eOutMailWaiting = 0;
+	pB->i2eChannelPtr = NULL;
+	pB->i2eChannelCnt = 0;
+
+	pB->i2eLeadoffWord[0] = 0;
+	pB->i2eFifoInInts = 0;
+	pB->i2eFifoOutInts = 0;
+	pB->i2eFatalTrap = NULL;
+	pB->i2eFatal = 0;
+
+	COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function:   iiResetDelay(pB)
+// Parameters: pB - pointer to the board structure
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Using the delay defined in board structure, waits two seconds (for board to
+// reset).
+//
+//******************************************************************************
+static int
+iiResetDelay(i2eBordStrPtr pB)
+{
+	if (pB->i2eValid != I2E_MAGIC) {
+		COMPLETE(pB, I2EE_BADMAGIC);
+	}
+	if (pB->i2eState != II_STATE_RESET) {
+		COMPLETE(pB, I2EE_BADSTATE);
+	}
+	iiDelay(pB,2000);       /* Now we wait for two seconds. */
+	iiDelayed = 1;          /* Delay has been called: ok to initialize */
+	COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function:   iiInitialize(pB)
+// Parameters: pB - pointer to the board structure
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Attempts to read the Power-on reset message. Initializes any remaining fields
+// in the pB structure.
+//
+// This should be called as the third step of a process beginning with
+// iiReset(), then iiResetDelay(). This routine checks to see that the structure
+// is "valid" and in the reset state, also confirms that the delay routine has
+// been called since the latest reset (to any board! overly strong!).
+//
+//******************************************************************************
+static int
+iiInitialize(i2eBordStrPtr pB)
+{
+	int itemp;
+	unsigned char c;
+	unsigned short utemp;
+	unsigned int ilimit;
+
+	if (pB->i2eValid != I2E_MAGIC)
+	{
+		COMPLETE(pB, I2EE_BADMAGIC);
+	}
+
+	if (pB->i2eState != II_STATE_RESET || !iiDelayed)
+	{
+		COMPLETE(pB, I2EE_BADSTATE);
+	}
+
+	// In case there is a failure short of our completely reading the power-up
+	// message.
+	pB->i2eValid = I2E_INCOMPLETE;
+
+
+	// Now attempt to read the message.
+
+	for (itemp = 0; itemp < sizeof(porStr); itemp++)
+	{
+		// We expect the entire message is ready.
+		if (HAS_NO_INPUT(pB))
+		{
+			pB->i2ePomSize = itemp;
+			COMPLETE(pB, I2EE_PORM_SHORT);
+		}
+
+		pB->i2ePom.c[itemp] = c = BYTE_FROM(pB);
+
+		// We check the magic numbers as soon as they are supposed to be read
+		// (rather than after) to minimize effect of reading something we
+		// already suspect can't be "us".
+		if (  (itemp == POR_1_INDEX && c != POR_MAGIC_1) ||
+				(itemp == POR_2_INDEX && c != POR_MAGIC_2))
+		{
+			pB->i2ePomSize = itemp+1;
+			COMPLETE(pB, I2EE_BADMAGIC);
+		}
+	}
+
+	pB->i2ePomSize = itemp;
+
+	// Ensure that this was all the data...
+	if (HAS_INPUT(pB))
+		COMPLETE(pB, I2EE_PORM_LONG);
+
+	// For now, we'll fail to initialize if P.O.S.T reports bad chip mapper:
+	// Implying we will not be able to download any code either:  That's ok: the
+	// condition is pretty explicit.
+	if (pB->i2ePom.e.porDiag1 & POR_BAD_MAPPER)
+	{
+		COMPLETE(pB, I2EE_POSTERR);
+	}
+
+	// Determine anything which must be done differently depending on the family
+	// of boards!
+	switch (pB->i2ePom.e.porID & POR_ID_FAMILY)
+	{
+	case POR_ID_FII:  // IntelliPort-II
+
+		pB->i2eFifoStyle   = FIFO_II;
+		pB->i2eFifoSize    = 512;     // 512 bytes, always
+		pB->i2eDataWidth16 = NO;
+
+		pB->i2eMaxIrq = 15;	// Because board cannot tell us it is in an 8-bit
+							// slot, we do allow it to be done (documentation!)
+
+		pB->i2eGoodMap[1] =
+		pB->i2eGoodMap[2] =
+		pB->i2eGoodMap[3] =
+		pB->i2eChannelMap[1] =
+		pB->i2eChannelMap[2] =
+		pB->i2eChannelMap[3] = 0;
+
+		switch (pB->i2ePom.e.porID & POR_ID_SIZE)
+		{
+		case POR_ID_II_4:
+			pB->i2eGoodMap[0] =
+			pB->i2eChannelMap[0] = 0x0f;  // four-port
+
+			// Since porPorts1 is based on the Hardware ID register, the numbers
+			// should always be consistent for IntelliPort-II.  Ditto below...
+			if (pB->i2ePom.e.porPorts1 != 4)
+			{
+				COMPLETE(pB, I2EE_INCONSIST);
+			}
+			break;
+
+		case POR_ID_II_8:
+		case POR_ID_II_8R:
+			pB->i2eGoodMap[0] =
+			pB->i2eChannelMap[0] = 0xff;  // Eight port
+			if (pB->i2ePom.e.porPorts1 != 8)
+			{
+				COMPLETE(pB, I2EE_INCONSIST);
+			}
+			break;
+
+		case POR_ID_II_6:
+			pB->i2eGoodMap[0] =
+			pB->i2eChannelMap[0] = 0x3f;  // Six Port
+			if (pB->i2ePom.e.porPorts1 != 6)
+			{
+				COMPLETE(pB, I2EE_INCONSIST);
+			}
+			break;
+		}
+
+		// Fix up the "good channel list based on any errors reported.
+		if (pB->i2ePom.e.porDiag1 & POR_BAD_UART1)
+		{
+			pB->i2eGoodMap[0] &= ~0x0f;
+		}
+
+		if (pB->i2ePom.e.porDiag1 & POR_BAD_UART2)
+		{
+			pB->i2eGoodMap[0] &= ~0xf0;
+		}
+
+		break;   // POR_ID_FII case
+
+	case POR_ID_FIIEX:   // IntelliPort-IIEX
+
+		pB->i2eFifoStyle = FIFO_IIEX;
+
+		itemp = pB->i2ePom.e.porFifoSize;
+
+		// Implicit assumption that fifo would not grow beyond 32k, 
+		// nor would ever be less than 256.
+
+		if (itemp < 8 || itemp > 15)
+		{
+			COMPLETE(pB, I2EE_INCONSIST);
+		}
+		pB->i2eFifoSize = (1 << itemp);
+
+		// These are based on what P.O.S.T thinks should be there, based on
+		// box ID registers
+		ilimit = pB->i2ePom.e.porNumBoxes;
+		if (ilimit > ABS_MAX_BOXES)
+		{
+			ilimit = ABS_MAX_BOXES;
+		}
+
+		// For as many boxes as EXIST, gives the type of box.
+		// Added 8/6/93: check for the ISA-4 (asic) which looks like an
+		// expandable but for whom "8 or 16?" is not the right question.
+
+		utemp = pB->i2ePom.e.porFlags;
+		if (utemp & POR_CEX4)
+		{
+			pB->i2eChannelMap[0] = 0x000f;
+		} else {
+			utemp &= POR_BOXES;
+			for (itemp = 0; itemp < ilimit; itemp++)
+			{
+				pB->i2eChannelMap[itemp] = 
+					((utemp & POR_BOX_16) ? 0xffff : 0x00ff);
+				utemp >>= 1;
+			}
+		}
+
+		// These are based on what P.O.S.T actually found.
+
+		utemp = (pB->i2ePom.e.porPorts2 << 8) + pB->i2ePom.e.porPorts1;
+
+		for (itemp = 0; itemp < ilimit; itemp++)
+		{
+			pB->i2eGoodMap[itemp] = 0;
+			if (utemp & 1) pB->i2eGoodMap[itemp] |= 0x000f;
+			if (utemp & 2) pB->i2eGoodMap[itemp] |= 0x00f0;
+			if (utemp & 4) pB->i2eGoodMap[itemp] |= 0x0f00;
+			if (utemp & 8) pB->i2eGoodMap[itemp] |= 0xf000;
+			utemp >>= 4;
+		}
+
+		// Now determine whether we should transfer in 8 or 16-bit mode.
+		switch (pB->i2ePom.e.porBus & (POR_BUS_SLOT16 | POR_BUS_DIP16) )
+		{
+		case POR_BUS_SLOT16 | POR_BUS_DIP16:
+			pB->i2eDataWidth16 = YES;
+			pB->i2eMaxIrq = 15;
+			break;
+
+		case POR_BUS_SLOT16:
+			pB->i2eDataWidth16 = NO;
+			pB->i2eMaxIrq = 15;
+			break;
+
+		case 0:
+		case POR_BUS_DIP16:     // In an 8-bit slot, DIP switch don't care.
+		default:
+			pB->i2eDataWidth16 = NO;
+			pB->i2eMaxIrq = 7;
+			break;
+		}
+		break;   // POR_ID_FIIEX case
+
+	default:    // Unknown type of board
+		COMPLETE(pB, I2EE_BAD_FAMILY);
+		break;
+	}  // End the switch based on family
+
+	// Temporarily, claim there is no room in the outbound fifo. 
+	// We will maintain this whenever we check for an empty outbound FIFO.
+	pB->i2eFifoRemains = 0;
+
+	// Now, based on the bus type, should we expect to be able to re-configure
+	// interrupts (say, for testing purposes).
+	switch (pB->i2ePom.e.porBus & POR_BUS_TYPE)
+	{
+	case POR_BUS_T_ISA:
+	case POR_BUS_T_UNK:  // If the type of bus is undeclared, assume ok.
+		pB->i2eChangeIrq = YES;
+		break;
+	case POR_BUS_T_MCA:
+	case POR_BUS_T_EISA:
+		pB->i2eChangeIrq = NO;
+		break;
+	default:
+		COMPLETE(pB, I2EE_BADBUS);
+	}
+
+	if (pB->i2eDataWidth16 == YES)
+	{
+		pB->i2eWriteBuf  = iiWriteBuf16;
+		pB->i2eReadBuf   = iiReadBuf16;
+		pB->i2eWriteWord = iiWriteWord16;
+		pB->i2eReadWord  = iiReadWord16;
+	} else {
+		pB->i2eWriteBuf  = iiWriteBuf8;
+		pB->i2eReadBuf   = iiReadBuf8;
+		pB->i2eWriteWord = iiWriteWord8;
+		pB->i2eReadWord  = iiReadWord8;
+	}
+
+	switch(pB->i2eFifoStyle)
+	{
+	case FIFO_II:
+		pB->i2eWaitForTxEmpty = iiWaitForTxEmptyII;
+		pB->i2eTxMailEmpty    = iiTxMailEmptyII;
+		pB->i2eTrySendMail    = iiTrySendMailII;
+		pB->i2eGetMail        = iiGetMailII;
+		pB->i2eEnableMailIrq  = iiEnableMailIrqII;
+		pB->i2eWriteMask      = iiWriteMaskII;
+
+		break;
+
+	case FIFO_IIEX:
+		pB->i2eWaitForTxEmpty = iiWaitForTxEmptyIIEX;
+		pB->i2eTxMailEmpty    = iiTxMailEmptyIIEX;
+		pB->i2eTrySendMail    = iiTrySendMailIIEX;
+		pB->i2eGetMail        = iiGetMailIIEX;
+		pB->i2eEnableMailIrq  = iiEnableMailIrqIIEX;
+		pB->i2eWriteMask      = iiWriteMaskIIEX;
+
+		break;
+
+	default:
+		COMPLETE(pB, I2EE_INCONSIST);
+	}
+
+	// Initialize state information.
+	pB->i2eState = II_STATE_READY;   // Ready to load loadware.
+
+	// Some Final cleanup:
+	// For some boards, the bootstrap firmware may perform some sort of test
+	// resulting in a stray character pending in the incoming mailbox. If one is
+	// there, it should be read and discarded, especially since for the standard
+	// firmware, it's the mailbox that interrupts the host.
+
+	pB->i2eStartMail = iiGetMail(pB);
+
+	// Throw it away and clear the mailbox structure element
+	pB->i2eStartMail = NO_MAIL_HERE;
+
+	// Everything is ok now, return with good status/
+
+	pB->i2eValid = I2E_MAGIC;
+	COMPLETE(pB, I2EE_GOOD);
+}
+
+//=======================================================
+// Delay Routines
+//
+// iiDelayIO
+// iiNop
+//=======================================================
+
+static void
+ii2DelayWakeup(unsigned long id)
+{
+	wake_up_interruptible ( &pDelayWait );
+}
+
+//******************************************************************************
+// Function:   ii2DelayTimer(mseconds)
+// Parameters: mseconds - number of milliseconds to delay
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// This routine delays for approximately mseconds milliseconds and is intended
+// to be called indirectly through i2Delay field in i2eBordStr. It uses the
+// Linux timer_list mechanism.
+//
+// The Linux timers use a unit called "jiffies" which are 10mS in the Intel
+// architecture. This function rounds the delay period up to the next "jiffy".
+// In the Alpha architecture the "jiffy" is 1mS, but this driver is not intended
+// for Alpha platforms at this time.
+//
+//******************************************************************************
+static void
+ii2DelayTimer(unsigned int mseconds)
+{
+	wait_queue_t wait;
+
+	init_waitqueue_entry(&wait, current);
+
+	init_timer ( pDelayTimer );
+
+	add_wait_queue(&pDelayWait, &wait);
+
+	set_current_state( TASK_INTERRUPTIBLE );
+
+	pDelayTimer->expires  = jiffies + ( mseconds + 9 ) / 10;
+	pDelayTimer->function = ii2DelayWakeup;
+	pDelayTimer->data     = 0;
+
+	add_timer ( pDelayTimer );
+
+	schedule();
+
+	set_current_state( TASK_RUNNING );
+	remove_wait_queue(&pDelayWait, &wait);
+
+	del_timer ( pDelayTimer );
+}
+
+#if 0
+//static void ii2DelayIO(unsigned int);
+//******************************************************************************
+// !!! Not Used, this is DOS crap, some of you young folks may be interested in
+//     in how things were done in the stone age of caculating machines       !!!
+// Function:   ii2DelayIO(mseconds)
+// Parameters: mseconds - number of milliseconds to delay
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// This routine delays for approximately mseconds milliseconds and is intended
+// to be called indirectly through i2Delay field in i2eBordStr. It is intended
+// for use where a clock-based function is impossible: for example, DOS drivers.
+//
+// This function uses the IN instruction to place bounds on the timing and
+// assumes that ii2Safe has been set. This is because I/O instructions are not
+// subject to caching and will therefore take a certain minimum time. To ensure
+// the delay is at least long enough on fast machines, it is based on some
+// fastest-case calculations.  On slower machines this may cause VERY long
+// delays. (3 x fastest case). In the fastest case, everything is cached except
+// the I/O instruction itself.
+//
+// Timing calculations:
+// The fastest bus speed for I/O operations is likely to be 10 MHz. The I/O
+// operation in question is a byte operation to an odd address. For 8-bit
+// operations, the architecture generally enforces two wait states. At 10 MHz, a
+// single cycle time is 100nS. A read operation at two wait states takes 6
+// cycles for a total time of 600nS. Therefore approximately 1666 iterations
+// would be required to generate a single millisecond delay. The worst
+// (reasonable) case would be an 8MHz system with no cacheing. In this case, the
+// I/O instruction would take 125nS x 6 cyles = 750 nS. More importantly, code
+// fetch of other instructions in the loop would take time (zero wait states,
+// however) and would be hard to estimate. This is minimized by using in-line
+// assembler for the in inner loop of IN instructions. This consists of just a
+// few bytes. So we'll guess about four code fetches per loop. Each code fetch
+// should take four cycles, so we have 125nS * 8 = 1000nS. Worst case then is
+// that what should have taken 1 mS takes instead 1666 * (1750) = 2.9 mS.
+//
+// So much for theoretical timings: results using 1666 value on some actual
+// machines:
+// IBM      286      6MHz     3.15 mS
+// Zenith   386      33MHz    2.45 mS
+// (brandX) 386      33MHz    1.90 mS  (has cache)
+// (brandY) 486      33MHz    2.35 mS
+// NCR      486      ??       1.65 mS (microchannel)
+//
+// For most machines, it is probably safe to scale this number back (remember,
+// for robust operation use an actual timed delay if possible), so we are using
+// a value of 1190. This yields 1.17 mS for the fastest machine in our sample,
+// 1.75 mS for typical 386 machines, and 2.25 mS the absolute slowest machine.
+//
+// 1/29/93:
+// The above timings are too slow. Actual cycle times might be faster. ISA cycle
+// times could approach 500 nS, and ...
+// The IBM model 77 being microchannel has no wait states for 8-bit reads and
+// seems to be accessing the I/O at 440 nS per access (from start of one to
+// start of next). This would imply we need 1000/.440 = 2272 iterations to
+// guarantee we are fast enough. In actual testing, we see that 2 * 1190 are in
+// fact enough. For diagnostics, we keep the level at 1190, but developers note
+// this needs tuning.
+//
+// Safe assumption:  2270 i/o reads = 1 millisecond
+//
+//******************************************************************************
+
+
+static int ii2DelValue = 1190;  // See timing calculations below
+						// 1666 for fastest theoretical machine
+						// 1190 safe for most fast 386 machines
+						// 1000 for fastest machine tested here
+						//  540 (sic) for AT286/6Mhz
+static void
+ii2DelayIO(unsigned int mseconds)
+{
+	if (!ii2Safe) 
+		return;   /* Do nothing if this variable uninitialized */
+
+	while(mseconds--) {
+		int i = ii2DelValue;
+		while ( i-- ) {
+			INB ( ii2Safe );
+		}
+	}
+}
+#endif 
+
+//******************************************************************************
+// Function:   ii2Nop()
+// Parameters: None
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// iiInitialize will set i2eDelay to this if the delay parameter is NULL. This
+// saves checking for a NULL pointer at every call.
+//******************************************************************************
+static void
+ii2Nop(void)
+{
+	return;	// no mystery here
+}
+
+//=======================================================
+// Routines which are available in 8/16-bit versions, or
+// in different fifo styles. These are ALL called
+// indirectly through the board structure.
+//=======================================================
+
+//******************************************************************************
+// Function:   iiWriteBuf16(pB, address, count)
+// Parameters: pB      - pointer to board structure
+//             address - address of data to write
+//             count   - number of data bytes to write
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Writes 'count' bytes from 'address' to the data fifo specified by the board
+// structure pointer pB. Should count happen to be odd, an extra pad byte is
+// sent (identity unknown...). Uses 16-bit (word) operations. Is called
+// indirectly through pB->i2eWriteBuf.
+//
+//******************************************************************************
+static int
+iiWriteBuf16(i2eBordStrPtr pB, unsigned char *address, int count)
+{
+	// Rudimentary sanity checking here.
+	if (pB->i2eValid != I2E_MAGIC)
+		COMPLETE(pB, I2EE_INVALID);
+
+	OUTSW ( pB->i2eData, address, count);
+
+	COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function:   iiWriteBuf8(pB, address, count)
+// Parameters: pB      - pointer to board structure
+//             address - address of data to write
+//             count   - number of data bytes to write
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Writes 'count' bytes from 'address' to the data fifo specified by the board
+// structure pointer pB. Should count happen to be odd, an extra pad byte is
+// sent (identity unknown...). This is to be consistent with the 16-bit version.
+// Uses 8-bit (byte) operations. Is called indirectly through pB->i2eWriteBuf.
+//
+//******************************************************************************
+static int
+iiWriteBuf8(i2eBordStrPtr pB, unsigned char *address, int count)
+{
+	/* Rudimentary sanity checking here */
+	if (pB->i2eValid != I2E_MAGIC)
+		COMPLETE(pB, I2EE_INVALID);
+
+	OUTSB ( pB->i2eData, address, count );
+
+	COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function:   iiReadBuf16(pB, address, count)
+// Parameters: pB      - pointer to board structure
+//             address - address to put data read
+//             count   - number of data bytes to read
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Reads 'count' bytes into 'address' from the data fifo specified by the board
+// structure pointer pB. Should count happen to be odd, an extra pad byte is
+// received (identity unknown...). Uses 16-bit (word) operations. Is called
+// indirectly through pB->i2eReadBuf.
+//
+//******************************************************************************
+static int
+iiReadBuf16(i2eBordStrPtr pB, unsigned char *address, int count)
+{
+	// Rudimentary sanity checking here.
+	if (pB->i2eValid != I2E_MAGIC)
+		COMPLETE(pB, I2EE_INVALID);
+
+	INSW ( pB->i2eData, address, count);
+
+	COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function:   iiReadBuf8(pB, address, count)
+// Parameters: pB      - pointer to board structure
+//             address - address to put data read
+//             count   - number of data bytes to read
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Reads 'count' bytes into 'address' from the data fifo specified by the board
+// structure pointer pB. Should count happen to be odd, an extra pad byte is
+// received (identity unknown...). This to match the 16-bit behaviour. Uses
+// 8-bit (byte) operations. Is called indirectly through pB->i2eReadBuf.
+//
+//******************************************************************************
+static int
+iiReadBuf8(i2eBordStrPtr pB, unsigned char *address, int count)
+{
+	// Rudimentary sanity checking here.
+	if (pB->i2eValid != I2E_MAGIC)
+		COMPLETE(pB, I2EE_INVALID);
+
+	INSB ( pB->i2eData, address, count);
+
+	COMPLETE(pB, I2EE_GOOD);
+}
+
+//******************************************************************************
+// Function:   iiReadWord16(pB)
+// Parameters: pB      - pointer to board structure
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Returns the word read from the data fifo specified by the board-structure
+// pointer pB. Uses a 16-bit operation. Is called indirectly through
+// pB->i2eReadWord.
+//
+//******************************************************************************
+static unsigned short
+iiReadWord16(i2eBordStrPtr pB)
+{
+	return (unsigned short)( INW(pB->i2eData) );
+}
+
+//******************************************************************************
+// Function:   iiReadWord8(pB)
+// Parameters: pB      - pointer to board structure
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Returns the word read from the data fifo specified by the board-structure
+// pointer pB. Uses two 8-bit operations. Bytes are assumed to be LSB first. Is
+// called indirectly through pB->i2eReadWord.
+//
+//******************************************************************************
+static unsigned short
+iiReadWord8(i2eBordStrPtr pB)
+{
+	unsigned short urs;
+
+	urs = INB ( pB->i2eData );
+
+	return ( ( INB ( pB->i2eData ) << 8 ) | urs );
+}
+
+//******************************************************************************
+// Function:   iiWriteWord16(pB, value)
+// Parameters: pB    - pointer to board structure
+//             value - data to write
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Writes the word 'value' to the data fifo specified by the board-structure
+// pointer pB. Uses 16-bit operation. Is called indirectly through
+// pB->i2eWriteWord.
+//
+//******************************************************************************
+static void
+iiWriteWord16(i2eBordStrPtr pB, unsigned short value)
+{
+	WORD_TO(pB, (int)value);
+}
+
+//******************************************************************************
+// Function:   iiWriteWord8(pB, value)
+// Parameters: pB    - pointer to board structure
+//             value - data to write
+//
+// Returns:    True if everything appears copacetic.
+//             False if there is any error: the pB->i2eError field has the error
+//
+// Description:
+//
+// Writes the word 'value' to the data fifo specified by the board-structure
+// pointer pB. Uses two 8-bit operations (writes LSB first). Is called
+// indirectly through pB->i2eWriteWord.
+//
+//******************************************************************************
+static void
+iiWriteWord8(i2eBordStrPtr pB, unsigned short value)
+{
+	BYTE_TO(pB, (char)value);
+	BYTE_TO(pB, (char)(value >> 8) );
+}
+
+//******************************************************************************
+// Function:   iiWaitForTxEmptyII(pB, mSdelay)
+// Parameters: pB      - pointer to board structure
+//             mSdelay - period to wait before returning
+//
+// Returns:    True if the FIFO is empty.
+//             False if it not empty in the required time: the pB->i2eError
+//             field has the error.
+//
+// Description:
+//
+// Waits up to "mSdelay" milliseconds for the outgoing FIFO to become empty; if
+// not empty by the required time, returns false and error in pB->i2eError,
+// otherwise returns true.
+//
+// mSdelay == 0 is taken to mean must be empty on the first test.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+// Note this routine is organized so that if status is ok there is no delay at
+// all called either before or after the test.  Is called indirectly through
+// pB->i2eWaitForTxEmpty.
+//
+//******************************************************************************
+static int
+iiWaitForTxEmptyII(i2eBordStrPtr pB, int mSdelay)
+{
+	unsigned long	flags;
+	int itemp;
+
+	for (;;)
+	{
+		// This routine hinges on being able to see the "other" status register
+		// (as seen by the local processor).  His incoming fifo is our outgoing
+		// FIFO.
+		//
+		// By the nature of this routine, you would be using this as part of a
+		// larger atomic context: i.e., you would use this routine to ensure the
+		// fifo empty, then act on this information. Between these two halves, 
+		// you will generally not want to service interrupts or in any way 
+		// disrupt the assumptions implicit in the larger context.
+		//
+		// Even worse, however, this routine "shifts" the status register to 
+		// point to the local status register which is not the usual situation.
+		// Therefore for extra safety, we force the critical section to be
+		// completely atomic, and pick up after ourselves before allowing any
+		// interrupts of any kind.
+
+
+		WRITE_LOCK_IRQSAVE(&Dl_spinlock,flags)
+		OUTB(pB->i2ePointer, SEL_COMMAND);
+		OUTB(pB->i2ePointer, SEL_CMD_SH);
+
+		itemp = INB(pB->i2eStatus);
+
+		OUTB(pB->i2ePointer, SEL_COMMAND);
+		OUTB(pB->i2ePointer, SEL_CMD_UNSH);
+
+		if (itemp & ST_IN_EMPTY)
+		{
+			UPDATE_FIFO_ROOM(pB);
+			WRITE_UNLOCK_IRQRESTORE(&Dl_spinlock,flags)
+			COMPLETE(pB, I2EE_GOOD);
+		}
+
+		WRITE_UNLOCK_IRQRESTORE(&Dl_spinlock,flags)
+
+		if (mSdelay-- == 0)
+			break;
+
+		iiDelay(pB, 1);      /* 1 mS granularity on checking condition */
+	}
+	COMPLETE(pB, I2EE_TXE_TIME);
+}
+
+//******************************************************************************
+// Function:   iiWaitForTxEmptyIIEX(pB, mSdelay)
+// Parameters: pB      - pointer to board structure
+//             mSdelay - period to wait before returning
+//
+// Returns:    True if the FIFO is empty.
+//             False if it not empty in the required time: the pB->i2eError
+//             field has the error.
+//
+// Description:
+//
+// Waits up to "mSdelay" milliseconds for the outgoing FIFO to become empty; if
+// not empty by the required time, returns false and error in pB->i2eError,
+// otherwise returns true.
+//
+// mSdelay == 0 is taken to mean must be empty on the first test.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+// Note this routine is organized so that if status is ok there is no delay at
+// all called either before or after the test.  Is called indirectly through
+// pB->i2eWaitForTxEmpty.
+//
+//******************************************************************************
+static int
+iiWaitForTxEmptyIIEX(i2eBordStrPtr pB, int mSdelay)
+{
+	unsigned long	flags;
+
+	for (;;)
+	{
+		// By the nature of this routine, you would be using this as part of a
+		// larger atomic context: i.e., you would use this routine to ensure the
+		// fifo empty, then act on this information. Between these two halves,
+		// you will generally not want to service interrupts or in any way
+		// disrupt the assumptions implicit in the larger context.
+
+		WRITE_LOCK_IRQSAVE(&Dl_spinlock,flags)
+
+		if (INB(pB->i2eStatus) & STE_OUT_MT) {
+			UPDATE_FIFO_ROOM(pB);
+			WRITE_UNLOCK_IRQRESTORE(&Dl_spinlock,flags)
+			COMPLETE(pB, I2EE_GOOD);
+		}
+		WRITE_UNLOCK_IRQRESTORE(&Dl_spinlock,flags)
+
+		if (mSdelay-- == 0)
+			break;
+
+		iiDelay(pB, 1);      // 1 mS granularity on checking condition
+	}
+	COMPLETE(pB, I2EE_TXE_TIME);
+}
+
+//******************************************************************************
+// Function:   iiTxMailEmptyII(pB)
+// Parameters: pB      - pointer to board structure
+//
+// Returns:    True if the transmit mailbox is empty.
+//             False if it not empty.
+//
+// Description:
+//
+// Returns true or false according to whether the transmit mailbox is empty (and
+// therefore able to accept more mail)
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static int
+iiTxMailEmptyII(i2eBordStrPtr pB)
+{
+	int port = pB->i2ePointer;
+	OUTB ( port, SEL_OUTMAIL );
+	return ( INB(port) == 0 );
+}
+
+//******************************************************************************
+// Function:   iiTxMailEmptyIIEX(pB)
+// Parameters: pB      - pointer to board structure
+//
+// Returns:    True if the transmit mailbox is empty.
+//             False if it not empty.
+//
+// Description:
+//
+// Returns true or false according to whether the transmit mailbox is empty (and
+// therefore able to accept more mail)
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static int
+iiTxMailEmptyIIEX(i2eBordStrPtr pB)
+{
+	return !(INB(pB->i2eStatus) & STE_OUT_MAIL);
+}
+
+//******************************************************************************
+// Function:   iiTrySendMailII(pB,mail)
+// Parameters: pB   - pointer to board structure
+//             mail - value to write to mailbox
+//
+// Returns:    True if the transmit mailbox is empty, and mail is sent.
+//             False if it not empty.
+//
+// Description:
+//
+// If outgoing mailbox is empty, sends mail and returns true. If outgoing
+// mailbox is not empty, returns false.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static int
+iiTrySendMailII(i2eBordStrPtr pB, unsigned char mail)
+{
+	int port = pB->i2ePointer;
+
+	OUTB(port, SEL_OUTMAIL);
+	if (INB(port) == 0) {
+		OUTB(port, SEL_OUTMAIL);
+		OUTB(port, mail);
+		return 1;
+	}
+	return 0;
+}
+
+//******************************************************************************
+// Function:   iiTrySendMailIIEX(pB,mail)
+// Parameters: pB   - pointer to board structure
+//             mail - value to write to mailbox
+//
+// Returns:    True if the transmit mailbox is empty, and mail is sent.
+//             False if it not empty.
+//
+// Description:
+//
+// If outgoing mailbox is empty, sends mail and returns true. If outgoing
+// mailbox is not empty, returns false.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static int
+iiTrySendMailIIEX(i2eBordStrPtr pB, unsigned char mail)
+{
+	if(INB(pB->i2eStatus) & STE_OUT_MAIL) {
+		return 0;
+	}
+	OUTB(pB->i2eXMail, mail);
+	return 1;
+}
+
+//******************************************************************************
+// Function:   iiGetMailII(pB,mail)
+// Parameters: pB   - pointer to board structure
+//
+// Returns:    Mailbox data or NO_MAIL_HERE.
+//
+// Description:
+//
+// If no mail available, returns NO_MAIL_HERE otherwise returns the data from
+// the mailbox, which is guaranteed != NO_MAIL_HERE.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static unsigned short
+iiGetMailII(i2eBordStrPtr pB)
+{
+	if (HAS_MAIL(pB)) {
+		OUTB(pB->i2ePointer, SEL_INMAIL);
+		return INB(pB->i2ePointer);
+	} else {
+		return NO_MAIL_HERE;
+	}
+}
+
+//******************************************************************************
+// Function:   iiGetMailIIEX(pB,mail)
+// Parameters: pB   - pointer to board structure
+//
+// Returns:    Mailbox data or NO_MAIL_HERE.
+//
+// Description:
+//
+// If no mail available, returns NO_MAIL_HERE otherwise returns the data from
+// the mailbox, which is guaranteed != NO_MAIL_HERE.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static unsigned short
+iiGetMailIIEX(i2eBordStrPtr pB)
+{
+	if (HAS_MAIL(pB)) {
+		return INB(pB->i2eXMail);
+	} else {
+		return NO_MAIL_HERE;
+	}
+}
+
+//******************************************************************************
+// Function:   iiEnableMailIrqII(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// Enables board to interrupt host (only) by writing to host's in-bound mailbox.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static void
+iiEnableMailIrqII(i2eBordStrPtr pB)
+{
+	OUTB(pB->i2ePointer, SEL_MASK);
+	OUTB(pB->i2ePointer, ST_IN_MAIL);
+}
+
+//******************************************************************************
+// Function:   iiEnableMailIrqIIEX(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// Enables board to interrupt host (only) by writing to host's in-bound mailbox.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static void
+iiEnableMailIrqIIEX(i2eBordStrPtr pB)
+{
+	OUTB(pB->i2eXMask, MX_IN_MAIL);
+}
+
+//******************************************************************************
+// Function:   iiWriteMaskII(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// Writes arbitrary value to the mask register.
+//
+// This version operates on IntelliPort-II - style FIFO's
+//
+//******************************************************************************
+static void
+iiWriteMaskII(i2eBordStrPtr pB, unsigned char value)
+{
+	OUTB(pB->i2ePointer, SEL_MASK);
+	OUTB(pB->i2ePointer, value);
+}
+
+//******************************************************************************
+// Function:   iiWriteMaskIIEX(pB)
+// Parameters: pB - pointer to board structure
+//
+// Returns:    Nothing
+//
+// Description:
+//
+// Writes arbitrary value to the mask register.
+//
+// This version operates on IntelliPort-IIEX - style FIFO's
+//
+//******************************************************************************
+static void
+iiWriteMaskIIEX(i2eBordStrPtr pB, unsigned char value)
+{
+	OUTB(pB->i2eXMask, value);
+}
+
+//******************************************************************************
+// Function:   iiDownloadBlock(pB, pSource, isStandard)
+// Parameters: pB         - pointer to board structure
+//             pSource    - loadware block to download
+//             isStandard - True if "standard" loadware, else false.
+//
+// Returns:    Success or Failure
+//
+// Description:
+//
+// Downloads a single block (at pSource)to the board referenced by pB. Caller
+// sets isStandard to true/false according to whether the "standard" loadware is
+// what's being loaded. The normal process, then, is to perform an iiInitialize
+// to the board, then perform some number of iiDownloadBlocks using the returned
+// state to determine when download is complete.
+//
+// Possible return values: (see I2ELLIS.H)
+// II_DOWN_BADVALID
+// II_DOWN_BADFILE
+// II_DOWN_CONTINUING
+// II_DOWN_GOOD
+// II_DOWN_BAD
+// II_DOWN_BADSTATE
+// II_DOWN_TIMEOUT
+//
+// Uses the i2eState and i2eToLoad fields (initialized at iiInitialize) to
+// determine whether this is the first block, whether to check for magic
+// numbers, how many blocks there are to go...
+//
+//******************************************************************************
+static int
+iiDownloadBlock ( i2eBordStrPtr pB, loadHdrStrPtr pSource, int isStandard)
+{
+	int itemp;
+	int loadedFirst;
+
+	if (pB->i2eValid != I2E_MAGIC) return II_DOWN_BADVALID;
+
+	switch(pB->i2eState)
+	{
+	case II_STATE_READY:
+
+		// Loading the first block after reset. Must check the magic number of the
+		// loadfile, store the number of blocks we expect to load.
+		if (pSource->e.loadMagic != MAGIC_LOADFILE)
+		{
+			return II_DOWN_BADFILE;
+		}
+
+		// Next we store the total number of blocks to load, including this one.
+		pB->i2eToLoad = 1 + pSource->e.loadBlocksMore;
+
+		// Set the state, store the version numbers. ('Cause this may have come
+		// from a file - we might want to report these versions and revisions in
+		// case of an error!
+		pB->i2eState = II_STATE_LOADING;
+		pB->i2eLVersion = pSource->e.loadVersion;
+		pB->i2eLRevision = pSource->e.loadRevision;
+		pB->i2eLSub = pSource->e.loadSubRevision;
+
+		// The time and date of compilation is also available but don't bother
+		// storing it for normal purposes.
+		loadedFirst = 1;
+		break;
+
+	case II_STATE_LOADING:
+		loadedFirst = 0;
+		break;
+
+	default:
+		return II_DOWN_BADSTATE;
+	}
+
+	// Now we must be in the II_STATE_LOADING state, and we assume i2eToLoad
+	// must be positive still, because otherwise we would have cleaned up last
+	// time and set the state to II_STATE_LOADED.
+	if (!iiWaitForTxEmpty(pB, MAX_DLOAD_READ_TIME)) {
+		return II_DOWN_TIMEOUT;
+	}
+
+	if (!iiWriteBuf(pB, pSource->c, LOADWARE_BLOCK_SIZE)) {
+		return II_DOWN_BADVALID;
+	}
+
+	// If we just loaded the first block, wait for the fifo to empty an extra
+	// long time to allow for any special startup code in the firmware, like
+	// sending status messages to the LCD's.
+
+	if (loadedFirst) {
+		if (!iiWaitForTxEmpty(pB, MAX_DLOAD_START_TIME)) {
+			return II_DOWN_TIMEOUT;
+		}
+	}
+
+	// Determine whether this was our last block!
+	if (--(pB->i2eToLoad)) {
+		return II_DOWN_CONTINUING;    // more to come...
+	}
+
+	// It WAS our last block: Clean up operations...
+	// ...Wait for last buffer to drain from the board...
+	if (!iiWaitForTxEmpty(pB, MAX_DLOAD_READ_TIME)) {
+		return II_DOWN_TIMEOUT;
+	}
+	// If there were only a single block written, this would come back
+	// immediately and be harmless, though not strictly necessary.
+	itemp = MAX_DLOAD_ACK_TIME/10;
+	while (--itemp) {
+		if (HAS_INPUT(pB)) {
+			switch(BYTE_FROM(pB))
+			{
+			case LOADWARE_OK:
+				pB->i2eState =
+					isStandard ? II_STATE_STDLOADED :II_STATE_LOADED;
+
+				// Some revisions of the bootstrap firmware (e.g. ISA-8 1.0.2)
+				// will, // if there is a debug port attached, require some
+				// time to send information to the debug port now. It will do
+				// this before // executing any of the code we just downloaded.
+				// It may take up to 700 milliseconds.
+				if (pB->i2ePom.e.porDiag2 & POR_DEBUG_PORT) {
+					iiDelay(pB, 700);
+				}
+
+				return II_DOWN_GOOD;
+
+			case LOADWARE_BAD:
+			default:
+				return II_DOWN_BAD;
+			}
+		}
+
+		iiDelay(pB, 10);      // 10 mS granularity on checking condition
+	}
+
+	// Drop-through --> timed out waiting for firmware confirmation
+
+	pB->i2eState = II_STATE_BADLOAD;
+	return II_DOWN_TIMEOUT;
+}
+
+//******************************************************************************
+// Function:   iiDownloadAll(pB, pSource, isStandard, size)
+// Parameters: pB         - pointer to board structure
+//             pSource    - loadware block to download
+//             isStandard - True if "standard" loadware, else false.
+//             size       - size of data to download (in bytes)
+//
+// Returns:    Success or Failure
+//
+// Description:
+//
+// Given a pointer to a board structure, a pointer to the beginning of some
+// loadware, whether it is considered the "standard loadware", and the size of
+// the array in bytes loads the entire array to the board as loadware.
+//
+// Assumes the board has been freshly reset and the power-up reset message read.
+// (i.e., in II_STATE_READY). Complains if state is bad, or if there seems to be
+// too much or too little data to load, or if iiDownloadBlock complains.
+//******************************************************************************
+static int
+iiDownloadAll(i2eBordStrPtr pB, loadHdrStrPtr pSource, int isStandard, int size)
+{
+	int status;
+
+	// We know (from context) board should be ready for the first block of
+	// download.  Complain if not.
+	if (pB->i2eState != II_STATE_READY) return II_DOWN_BADSTATE;
+
+	while (size > 0) {
+		size -= LOADWARE_BLOCK_SIZE;	// How much data should there be left to
+										// load after the following operation ?
+
+		// Note we just bump pSource by "one", because its size is actually that
+		// of an entire block, same as LOADWARE_BLOCK_SIZE.
+		status = iiDownloadBlock(pB, pSource++, isStandard);
+
+		switch(status)
+		{
+		case II_DOWN_GOOD:
+			return ( (size > 0) ? II_DOWN_OVER : II_DOWN_GOOD);
+
+		case II_DOWN_CONTINUING:
+			break;
+
+		default:
+			return status;
+		}
+	}
+
+	// We shouldn't drop out: it means "while" caught us with nothing left to
+	// download, yet the previous DownloadBlock did not return complete. Ergo,
+	// not enough data to match the size byte in the header.
+	return II_DOWN_UNDER;
+}
diff --git a/drivers/char/ip2/i2ellis.h b/drivers/char/ip2/i2ellis.h
new file mode 100644
index 0000000..510b026
--- /dev/null
+++ b/drivers/char/ip2/i2ellis.h
@@ -0,0 +1,615 @@
+/*******************************************************************************
+*
+*   (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Mainline code for the device driver
+*
+*******************************************************************************/
+//------------------------------------------------------------------------------
+// i2ellis.h
+//
+// IntelliPort-II and IntelliPort-IIEX
+//
+// Extremely
+// Low
+// Level
+// Interface
+// Services
+//
+// Structure Definitions and declarations for "ELLIS" service routines found in
+// i2ellis.c
+//
+// These routines are based on properties of the IntelliPort-II and -IIEX
+// hardware and bootstrap firmware, and are not sensitive to particular
+// conventions of any particular loadware.
+//
+// Unlike i2hw.h, which provides IRONCLAD hardware definitions, the material
+// here and in i2ellis.c is intended to provice a useful, but not required,
+// layer of insulation from the hardware specifics.
+//------------------------------------------------------------------------------
+#ifndef  I2ELLIS_H   /* To prevent multiple includes */
+#define  I2ELLIS_H   1
+//------------------------------------------------
+// Revision History:
+//
+// 30 September 1991 MAG First Draft Started
+// 12 October   1991 ...continued...
+//
+// 20 December  1996 AKM Linux version
+//-------------------------------------------------
+
+//----------------------
+// Mandatory Includes:
+//----------------------
+#include <linux/config.h>
+#include "ip2types.h"
+#include "i2hw.h"       // The hardware definitions
+
+//------------------------------------------
+// STAT_BOXIDS packets
+//------------------------------------------
+#define MAX_BOX		4
+
+typedef struct _bidStat
+{
+	unsigned char bid_value[MAX_BOX];
+} bidStat, *bidStatPtr;
+
+// This packet is sent in response to a CMD_GET_BOXIDS bypass command. For -IIEX
+// boards, reports the hardware-specific "asynchronous resource register" on
+// each expansion box. Boxes not present report 0xff. For -II boards, the first
+// element contains 0x80 for 8-port, 0x40 for 4-port boards.
+
+// Box IDs aka ARR or Async Resource Register (more than you want to know)
+//   7   6   5   4   3   2   1   0
+//   F   F   N   N   L   S   S   S
+//   =============================
+//   F   F   -  Product Family Designator
+//   =====+++++++++++++++++++++++++++++++
+//   0   0   -  Intelliport II EX / ISA-8
+//   1   0   -  IntelliServer
+//   0   1   -  SAC - Port Device (Intelliport III ??? )
+//           =====+++++++++++++++++++++++++++++++++++++++
+//           N   N   -  Number of Ports
+//           0   0   -  8  (eight)
+//           0   1   -  4  (four)
+//           1   0   -  12 (twelve)
+//           1   1   -  16 (sixteen)
+//                   =++++++++++++++++++++++++++++++++++
+//                   L  -   LCD Display Module Present
+//                   0  -   No
+//                   1  -   LCD module present
+//                   =========+++++++++++++++++++++++++++++++++++++
+//                      S   S   S - Async Signals Supported Designator
+//                      0   0   0 - 8dss, Mod DCE DB25 Female
+//                      0   0   1 - 6dss, RJ-45
+//                      0   1   0 - RS-232/422 dss, DB25 Female
+//                      0   1   1 - RS-232/422 dss, separate 232/422 DB25 Female
+//                      1   0   0 - 6dss, 921.6 I/F with ST654's
+//                      1   0   1 - RS-423/232 8dss, RJ-45 10Pin
+//                      1   1   0 - 6dss, Mod DCE DB25 Female
+//                      1   1   1 - NO BOX PRESENT
+
+#define FF(c)	((c & 0xC0) >> 6)
+#define NN(c)	((c & 0x30) >> 4)
+#define L(c)	((c & 0x08) >> 3)
+#define SSS(c)	 (c & 0x07)
+
+#define BID_HAS_654(x)	(SSS(x) == 0x04)
+#define BID_NO_BOX	0xff /* no box */
+#define BID_8PORT  	0x80 /* IP2-8 port */
+#define BID_4PORT   	0x81 /* IP2-4 port */
+#define BID_EXP_MASK   	0x30 /* IP2-EX  */
+#define BID_EXP_8PORT	0x00 /*     8, */
+#define BID_EXP_4PORT	0x10 /*     4, */
+#define BID_EXP_UNDEF	0x20 /*     UNDEF, */
+#define BID_EXP_16PORT	0x30 /*    16, */
+#define BID_LCD_CTRL   	0x08 /* LCD Controller */
+#define BID_LCD_NONE	0x00 /* - no controller present */
+#define BID_LCD_PRES   	0x08 /* - controller present */
+#define BID_CON_MASK	0x07 /* - connector pinouts */
+#define BID_CON_DB25	0x00 /* - DB-25 F */
+#define BID_CON_RJ45	0x01 /* - rj45 */
+
+//------------------------------------------------------------------------------
+// i2eBordStr
+//
+// This structure contains all the information the ELLIS routines require in
+// dealing with a particular board.
+//------------------------------------------------------------------------------
+// There are some queues here which are guaranteed to never contain the entry
+// for a single channel twice. So they must be slightly larger to allow
+// unambiguous full/empty management
+//
+#define CH_QUEUE_SIZE ABS_MOST_PORTS+2
+
+typedef struct _i2eBordStr
+{
+	porStr         i2ePom;	// Structure containing the power-on message.
+
+	unsigned short i2ePomSize;
+						// The number of bytes actually read if
+						// different from sizeof i2ePom, indicates
+						// there is an error!
+
+	unsigned short i2eStartMail;
+						// Contains whatever inbound mailbox data
+						// present at startup. NO_MAIL_HERE indicates
+						// nothing was present. No special
+						// significance as of this writing, but may be
+						// useful for diagnostic reasons.
+
+	unsigned short i2eValid;
+						// Indicates validity of the structure; if
+						// i2eValid == I2E_MAGIC, then we can trust
+						// the other fields. Some (especially
+						// initialization) functions are good about
+						// checking for validity.  Many functions do
+						// not, it being assumed that the larger
+						// context assures we are using a valid
+						// i2eBordStrPtr.
+
+	unsigned short i2eError;
+						// Used for returning an error condition from
+						// several functions which use i2eBordStrPtr
+						// as an argument.
+
+	// Accelerators to characterize separate features of a board, derived from a
+	// number of sources.
+
+	unsigned short i2eFifoSize;
+						// Always, the size of the FIFO. For
+						// IntelliPort-II, always the same, for -IIEX
+						// taken from the Power-On reset message.
+
+	volatile 
+	unsigned short i2eFifoRemains;
+						// Used during normal operation to indicate a
+						// lower bound on the amount of data which
+						// might be in the outbound fifo.
+
+	unsigned char  i2eFifoStyle;
+						// Accelerator which tells which style (-II or
+						// -IIEX) FIFO we are using.
+
+	unsigned char  i2eDataWidth16;
+						// Accelerator which tells whether we should
+						// do 8 or 16-bit data transfers.
+
+	unsigned char  i2eMaxIrq;
+						// The highest allowable IRQ, based on the
+						// slot size.
+
+	unsigned char  i2eChangeIrq;
+						// Whether tis valid to change IRQ's
+						// ISA = ok, EISA, MicroChannel, no
+
+	// Accelerators for various addresses on the board
+	int            i2eBase;        // I/O Address of the Board
+	int            i2eData;        // From here data transfers happen
+	int            i2eStatus;      // From here status reads happen
+	int            i2ePointer;     // (IntelliPort-II: pointer/commands)
+	int            i2eXMail;       // (IntelliPOrt-IIEX: mailboxes
+	int            i2eXMask;       // (IntelliPort-IIEX: mask write
+
+	//-------------------------------------------------------
+	// Information presented in a common format across boards
+	// For each box, bit map of the channels present.  Box closest to 
+	// the host is box 0. LSB is channel 0. IntelliPort-II (non-expandable)
+	// is taken to be box 0. These are derived from product i.d. registers.
+
+	unsigned short i2eChannelMap[ABS_MAX_BOXES];
+
+	// Same as above, except each is derived from firmware attempting to detect
+	// the uart presence (by reading a valid GFRCR register). If bits are set in
+	// i2eChannelMap and not in i2eGoodMap, there is a potential problem.
+
+	unsigned short i2eGoodMap[ABS_MAX_BOXES];
+
+	// ---------------------------
+	// For indirect function calls
+
+	// Routine to cause an N-millisecond delay: Patched by the ii2Initialize
+	// function.
+
+	void  (*i2eDelay)(unsigned int);
+
+	// Routine to write N bytes to the board through the FIFO. Returns true if
+	// all copacetic, otherwise returns false and error is in i2eError field.
+	// IF COUNT IS ODD, ROUNDS UP TO THE NEXT EVEN NUMBER.
+
+	int   (*i2eWriteBuf)(struct _i2eBordStr *, unsigned char *, int);
+
+	// Routine to read N bytes from the board through the FIFO. Returns true if
+	// copacetic, otherwise returns false and error in i2eError.
+	// IF COUNT IS ODD, ROUNDS UP TO THE NEXT EVEN NUMBER.
+
+	int   (*i2eReadBuf)(struct _i2eBordStr *, unsigned char *, int);
+
+	// Returns a word from FIFO. Will use 2 byte operations if needed.
+
+	unsigned short (*i2eReadWord)(struct _i2eBordStr *);
+
+	// Writes a word to FIFO. Will use 2 byte operations if needed.
+
+	void  (*i2eWriteWord)(struct _i2eBordStr *, unsigned short);
+
+	// Waits specified time for the Transmit FIFO to go empty. Returns true if
+	//  ok, otherwise returns false and error in i2eError.
+
+	int   (*i2eWaitForTxEmpty)(struct _i2eBordStr *, int);
+
+	// Returns true or false according to whether the outgoing mailbox is empty.
+
+	int   (*i2eTxMailEmpty)(struct _i2eBordStr *);
+
+	// Checks whether outgoing mailbox is empty.  If so, sends mail and returns
+	// true.  Otherwise returns false.
+
+	int   (*i2eTrySendMail)(struct _i2eBordStr *, unsigned char);
+
+	// If no mail available, returns NO_MAIL_HERE, else returns the value in the
+	// mailbox (guaranteed can't be NO_MAIL_HERE).
+
+	unsigned short (*i2eGetMail)(struct _i2eBordStr *);
+
+	// Enables the board to interrupt the host when it writes to the mailbox.
+	// Irqs will not occur, however, until the loadware separately enables
+	// interrupt generation to the host.  The standard loadware does this in
+	// response to a command packet sent by the host. (Also, disables
+	// any other potential interrupt sources from the board -- other than the
+	// inbound mailbox).
+
+	void  (*i2eEnableMailIrq)(struct _i2eBordStr *);
+
+	// Writes an arbitrary value to the mask register.
+
+	void  (*i2eWriteMask)(struct _i2eBordStr *, unsigned char);
+
+
+	// State information
+
+	// During downloading, indicates the number of blocks remaining to download
+	// to the board.
+
+	short i2eToLoad;
+
+	// State of board (see manifests below) (e.g., whether in reset condition,
+	// whether standard loadware is installed, etc.
+
+	unsigned char  i2eState;
+
+	// These three fields are only valid when there is loadware running on the
+	// board. (i2eState == II_STATE_LOADED or i2eState == II_STATE_STDLOADED )
+
+	unsigned char  i2eLVersion;  // Loadware version
+	unsigned char  i2eLRevision; // Loadware revision
+	unsigned char  i2eLSub;      // Loadware subrevision
+
+	// Flags which only have meaning in the context of the standard loadware.
+	// Somewhat violates the layering concept, but there is so little additional
+	// needed at the board level (while much additional at the channel level),
+	// that this beats maintaining two different per-board structures.
+
+	// Indicates which IRQ the board has been initialized (from software) to use
+	// For MicroChannel boards, any value different from IRQ_UNDEFINED means
+	// that the software command has been sent to enable interrupts (or specify
+	// they are disabled). Special value: IRQ_UNDEFINED indicates that the
+	// software command to select the interrupt has not yet been sent, therefore
+	// (since the standard loadware insists that it be sent before any other
+	// packets are sent) no other packets should be sent yet.
+
+	unsigned short i2eUsingIrq;
+
+	// This is set when we hit the MB_OUT_STUFFED mailbox, which prevents us
+	// putting more in the mailbox until an appropriate mailbox message is
+	// received.
+
+	unsigned char  i2eWaitingForEmptyFifo;
+
+	// Any mailbox bits waiting to be sent to the board are OR'ed in here.
+
+	unsigned char  i2eOutMailWaiting;
+
+	// The head of any incoming packet is read into here, is then examined and 
+	// we dispatch accordingly.
+
+	unsigned short i2eLeadoffWord[1];
+
+	// Running counter of interrupts where the mailbox indicated incoming data.
+
+	unsigned short i2eFifoInInts;
+
+	// Running counter of interrupts where the mailbox indicated outgoing data
+	// had been stripped.
+
+	unsigned short i2eFifoOutInts;
+
+	// If not void, gives the address of a routine to call if fatal board error
+	// is found (only applies to standard l/w).
+
+	void  (*i2eFatalTrap)(struct _i2eBordStr *);
+
+	// Will point to an array of some sort of channel structures (whose format
+	// is unknown at this level, being a function of what loadware is
+	// installed and the code configuration (max sizes of buffers, etc.)).
+
+	void  *i2eChannelPtr;
+
+	// Set indicates that the board has gone fatal.
+
+	unsigned short i2eFatal;
+
+	// The number of elements pointed to by i2eChannelPtr.
+
+	unsigned short i2eChannelCnt;
+
+	// Ring-buffers of channel structures whose channels have particular needs.
+
+	rwlock_t	Fbuf_spinlock;
+	volatile
+	unsigned short i2Fbuf_strip;	// Strip index
+	volatile 
+	unsigned short i2Fbuf_stuff;	// Stuff index
+	void  *i2Fbuf[CH_QUEUE_SIZE];	// An array of channel pointers
+									// of channels who need to send
+									// flow control packets.
+	rwlock_t	Dbuf_spinlock;
+	volatile
+	unsigned short i2Dbuf_strip;	// Strip index
+	volatile
+	unsigned short i2Dbuf_stuff;	// Stuff index
+	void  *i2Dbuf[CH_QUEUE_SIZE];	// An array of channel pointers
+									// of channels who need to send
+									// data or in-line command packets.
+	rwlock_t	Bbuf_spinlock;
+	volatile
+	unsigned short i2Bbuf_strip;	// Strip index
+	volatile
+	unsigned short i2Bbuf_stuff;	// Stuff index
+	void  *i2Bbuf[CH_QUEUE_SIZE];	// An array of channel pointers
+									// of channels who need to send
+									// bypass command packets.
+
+	/*
+	 * A set of flags to indicate that certain events have occurred on at least
+	 * one of the ports on this board. We use this to decide whether to spin
+	 * through the channels looking for breaks, etc.
+	 */
+	int		got_input;
+	int		status_change;
+	bidStat	channelBtypes;
+
+	/*
+	 * Debugging counters, etc.
+	 */
+	unsigned long debugFlowQueued;
+	unsigned long debugInlineQueued;
+	unsigned long debugDataQueued;
+	unsigned long debugBypassQueued;
+	unsigned long debugFlowCount;
+	unsigned long debugInlineCount;
+	unsigned long debugBypassCount;
+	
+	rwlock_t	read_fifo_spinlock;
+	rwlock_t	write_fifo_spinlock;
+
+//	For queuing interrupt bottom half handlers.	/\/\|=mhw=|\/\/
+	struct work_struct	tqueue_interrupt;
+
+	struct timer_list  SendPendingTimer;   // Used by iiSendPending
+	unsigned int	SendPendingRetry;
+} i2eBordStr, *i2eBordStrPtr;
+
+//-------------------------------------------------------------------
+// Macro Definitions for the indirect calls defined in the i2eBordStr
+//-------------------------------------------------------------------
+//
+#define iiDelay(a,b)          (*(a)->i2eDelay)(b)
+#define iiWriteBuf(a,b,c)     (*(a)->i2eWriteBuf)(a,b,c)
+#define iiReadBuf(a,b,c)      (*(a)->i2eReadBuf)(a,b,c)
+
+#define iiWriteWord(a,b)      (*(a)->i2eWriteWord)(a,b)
+#define iiReadWord(a)         (*(a)->i2eReadWord)(a)
+
+#define iiWaitForTxEmpty(a,b) (*(a)->i2eWaitForTxEmpty)(a,b)
+
+#define iiTxMailEmpty(a)      (*(a)->i2eTxMailEmpty)(a)
+#define iiTrySendMail(a,b)    (*(a)->i2eTrySendMail)(a,b)
+
+#define iiGetMail(a)          (*(a)->i2eGetMail)(a)
+#define iiEnableMailIrq(a)    (*(a)->i2eEnableMailIrq)(a)
+#define iiDisableMailIrq(a)   (*(a)->i2eWriteMask)(a,0)
+#define iiWriteMask(a,b)      (*(a)->i2eWriteMask)(a,b)
+
+//-------------------------------------------
+// Manifests for i2eBordStr:
+//-------------------------------------------
+
+#define YES 1
+#define NO  0
+
+#define NULLFUNC (void (*)(void))0
+#define NULLPTR (void *)0
+
+typedef void (*delayFunc_t)(unsigned int);
+
+// i2eValid
+//
+#define I2E_MAGIC       0x4251   // Structure is valid.
+#define I2E_INCOMPLETE  0x1122   // Structure failed during init.
+
+
+// i2eError
+//
+#define I2EE_GOOD       0	// Operation successful
+#define I2EE_BADADDR    1	// Address out of range
+#define I2EE_BADSTATE   2	// Attempt to perform a function when the board
+							// structure was in the incorrect state
+#define I2EE_BADMAGIC   3	// Bad magic number from Power On test (i2ePomSize
+							// reflects what was read
+#define I2EE_PORM_SHORT 4	// Power On message too short
+#define I2EE_PORM_LONG  5	// Power On message too long
+#define I2EE_BAD_FAMILY 6	// Un-supported board family type
+#define I2EE_INCONSIST  7	// Firmware reports something impossible,
+							// e.g. unexpected number of ports... Almost no
+							// excuse other than bad FIFO...
+#define I2EE_POSTERR    8	// Power-On self test reported a bad error
+#define I2EE_BADBUS     9	// Unknown Bus type declared in message
+#define I2EE_TXE_TIME   10	// Timed out waiting for TX Fifo to empty
+#define I2EE_INVALID    11	// i2eValid field does not indicate a valid and
+							// complete board structure (for functions which
+							// require this be so.)
+#define I2EE_BAD_PORT   12	// Discrepancy between channels actually found and
+							// what the product is supposed to have. Check
+							// i2eGoodMap vs i2eChannelMap for details.
+#define I2EE_BAD_IRQ    13	// Someone specified an unsupported IRQ
+#define I2EE_NOCHANNELS 14	// No channel structures have been defined (for
+							// functions requiring this).
+
+// i2eFifoStyle
+//
+#define FIFO_II   0  /* IntelliPort-II style: see also i2hw.h */
+#define FIFO_IIEX 1  /* IntelliPort-IIEX style */
+
+// i2eGetMail
+//
+#define NO_MAIL_HERE    0x1111	// Since mail is unsigned char, cannot possibly
+								// promote to 0x1111.
+// i2eState
+//
+#define II_STATE_COLD      0  // Addresses have been defined, but board not even
+							  // reset yet.
+#define II_STATE_RESET     1  // Board,if it exists, has just been reset
+#define II_STATE_READY     2  // Board ready for its first block
+#define II_STATE_LOADING   3  // Board continuing load
+#define II_STATE_LOADED    4  // Board has finished load: status ok
+#define II_STATE_BADLOAD   5  // Board has finished load: failed!
+#define II_STATE_STDLOADED 6  // Board has finished load: standard firmware
+
+// i2eUsingIrq
+//
+#define IRQ_UNDEFINED   0x1352  // No valid irq (or polling = 0) can ever
+								// promote to this!
+//------------------------------------------
+// Handy Macros for i2ellis.c and others
+// Note these are common to -II and -IIEX
+//------------------------------------------
+
+// Given a pointer to the board structure, does the input FIFO have any data or
+// not?
+//
+#define HAS_INPUT(pB)      !(INB(pB->i2eStatus) & ST_IN_EMPTY)
+#define HAS_NO_INPUT(pB)   (INB(pB->i2eStatus) & ST_IN_EMPTY)
+
+// Given a pointer to board structure, read a byte or word from the fifo
+//
+#define BYTE_FROM(pB)      (unsigned char)INB(pB->i2eData)
+#define WORD_FROM(pB)      (unsigned short)INW(pB->i2eData)
+
+// Given a pointer to board structure, is there room for any data to be written
+// to the data fifo?
+//
+#define HAS_OUTROOM(pB)    !(INB(pB->i2eStatus) & ST_OUT_FULL)
+#define HAS_NO_OUTROOM(pB) (INB(pB->i2eStatus) & ST_OUT_FULL)
+
+// Given a pointer to board structure, write a single byte to the fifo
+// structure. Note that for 16-bit interfaces, the high order byte is undefined
+// and unknown.
+//
+#define BYTE_TO(pB, c)     OUTB(pB->i2eData,(c))
+
+// Write a word to the fifo structure. For 8-bit interfaces, this may have
+// unknown results.
+//
+#define WORD_TO(pB, c)     OUTW(pB->i2eData,(c))
+
+// Given a pointer to the board structure, is there anything in the incoming
+// mailbox?
+//
+#define HAS_MAIL(pB)       (INB(pB->i2eStatus) & ST_IN_MAIL)
+
+#define UPDATE_FIFO_ROOM(pB)  (pB)->i2eFifoRemains=(pB)->i2eFifoSize
+
+// Handy macro to round up a number (like the buffer write and read routines do)
+// 
+#define ROUNDUP(number)    (((number)+1) & (~1))
+
+//------------------------------------------
+// Function Declarations for i2ellis.c
+//------------------------------------------
+//
+// Functions called directly
+//
+// Initialization of a board & structure is in four (five!) parts:
+//
+// 0) iiEllisInit()  - Initialize iiEllis subsystem.
+// 1) iiSetAddress() - Define the board address & delay function for a board.
+// 2) iiReset()      - Reset the board   (provided it exists)
+//       -- Note you may do this to several boards --
+// 3) iiResetDelay() - Delay for 2 seconds (once for all boards)
+// 4) iiInitialize() - Attempt to read Power-up message; further initialize
+//                     accelerators
+//
+// Then you may use iiDownloadAll() or iiDownloadFile() (in i2file.c) to write
+// loadware.  To change loadware, you must begin again with step 2, resetting
+// the board again (step 1 not needed).
+
+static void iiEllisInit(void);
+static int iiSetAddress(i2eBordStrPtr, int, delayFunc_t );
+static int iiReset(i2eBordStrPtr);
+static int iiResetDelay(i2eBordStrPtr);
+static int iiInitialize(i2eBordStrPtr);
+
+// Routine to validate that all channels expected are there.
+//
+extern int iiValidateChannels(i2eBordStrPtr);
+
+// Routine used to download a block of loadware.
+//
+static int iiDownloadBlock(i2eBordStrPtr, loadHdrStrPtr, int);
+
+// Return values given by iiDownloadBlock, iiDownloadAll, iiDownloadFile:
+//
+#define II_DOWN_BADVALID   0	// board structure is invalid
+#define II_DOWN_CONTINUING 1	// So far, so good, firmware expects more
+#define II_DOWN_GOOD       2	// Download complete, CRC good
+#define II_DOWN_BAD        3	// Download complete, but CRC bad
+#define II_DOWN_BADFILE    4	// Bad magic number in loadware file
+#define II_DOWN_BADSTATE   5	// Board is in an inappropriate state for
+								// downloading loadware. (see i2eState)
+#define II_DOWN_TIMEOUT    6	// Timeout waiting for firmware
+#define II_DOWN_OVER       7	// Too much data
+#define II_DOWN_UNDER      8	// Not enough data
+#define II_DOWN_NOFILE     9	// Loadware file not found
+
+// Routine to download an entire loadware module: Return values are a subset of
+// iiDownloadBlock's, excluding, of course, II_DOWN_CONTINUING
+//
+static int iiDownloadAll(i2eBordStrPtr, loadHdrStrPtr, int, int);
+
+// Called indirectly always.  Needed externally so the routine might be
+// SPECIFIED as an argument to iiReset()
+//
+//static void ii2DelayIO(unsigned int);		// N-millisecond delay using
+											//hardware spin
+//static void ii2DelayTimer(unsigned int);	// N-millisecond delay using Linux
+											//timer
+
+// Many functions defined here return True if good, False otherwise, with an
+// error code in i2eError field. Here is a handy macro for setting the error
+// code and returning.
+//
+#define COMPLETE(pB,code) \
+	if(1){ \
+		 pB->i2eError = code; \
+		 return (code == I2EE_GOOD);\
+	}
+
+#endif   // I2ELLIS_H
diff --git a/drivers/char/ip2/i2hw.h b/drivers/char/ip2/i2hw.h
new file mode 100644
index 0000000..15fe04e
--- /dev/null
+++ b/drivers/char/ip2/i2hw.h
@@ -0,0 +1,648 @@
+/*******************************************************************************
+*
+*   (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Definitions limited to properties of the hardware or the
+*                bootstrap firmware. As such, they are applicable regardless of
+*                operating system or loadware (standard or diagnostic).
+*
+*******************************************************************************/
+#ifndef I2HW_H
+#define I2HW_H 1
+//------------------------------------------------------------------------------
+// Revision History:
+//
+// 23 September 1991 MAG   First Draft Started...through...
+// 11 October 1991   ...   Continuing development...
+//  6 August 1993          Added support for ISA-4 (asic) which is architected
+//                         as an ISA-CEX with a single 4-port box.
+//
+// 20 December 1996  AKM   Version for Linux
+//
+//------------------------------------------------------------------------------
+/*------------------------------------------------------------------------------
+
+HARDWARE DESCRIPTION:
+
+Introduction:
+
+The IntelliPort-II and IntelliPort-IIEX products occupy a block of eight (8)
+addresses in the host's I/O space.
+
+Some addresses are used to transfer data to/from the board, some to transfer
+so-called "mailbox" messages, and some to read bit-mapped status information.
+While all the products in the line are functionally similar, some use a 16-bit
+data path to transfer data while others use an 8-bit path. Also, the use of
+command /status/mailbox registers differs slightly between the II and IIEX
+branches of the family.
+
+The host determines what type of board it is dealing with by reading a string of
+sixteen characters from the board. These characters are always placed in the
+fifo by the board's local processor whenever the board is reset (either from
+power-on or under software control) and are known as the "Power-on Reset
+Message." In order that this message can be read from either type of board, the
+hardware registers used in reading this message are the same. Once this message
+has been read by the host, then it has the information required to operate.
+
+General Differences between boards:
+
+The greatest structural difference is between the -II and -IIEX families of
+product. The -II boards use the Am4701 dual 512x8 bidirectional fifo to support
+the data path, mailbox registers, and status registers. This chip contains some
+features which are not used in the IntelliPort-II products; a description of
+these is omitted here. Because of these many features, it contains many
+registers, too many to access directly within a small address space. They are
+accessed by first writing a value to a "pointer" register. This value selects
+the register to be accessed.  The next read or write to that address accesses
+the selected register rather than the pointer register.
+
+The -IIEX boards use a proprietary design similar to the Am4701 in function. But
+because of a simpler, more streamlined design it doesn't require so many
+registers. This means they can be accessed directly in single operations rather
+than through a pointer register.
+
+Besides these differences, there are differences in whether 8-bit or 16-bit
+transfers are used to move data to the board.
+
+The -II boards are capable only of 8-bit data transfers, while the -IIEX boards
+may be configured for either 8-bit or 16-bit data transfers. If the on-board DIP
+switch #8 is ON, and the card has been installed in a 16-bit slot, 16-bit
+transfers are supported (and will be expected by the standard loadware). The
+on-board firmware can determine the position of the switch, and whether the
+board is installed in a 16-bit slot; it supplies this information to the host as
+part of the power-up reset message.
+
+The configuration switch (#8) and slot selection do not directly configure the
+hardware. It is up to the on-board loadware and host-based drivers to act
+according to the selected options. That is, loadware and drivers could be
+written to perform 8-bit transfers regardless of the state of the DIP switch or
+slot (and in a diagnostic environment might well do so). Likewise, 16-bit
+transfers could be performed as long as the card is in a 16-bit slot.
+
+Note the slot selection and DIP switch selection are provided separately: a
+board running in 8-bit mode in a 16-bit slot has a greater range of possible
+interrupts to choose from; information of potential use to the host.
+
+All 8-bit data transfers are done in the same way, regardless of whether on a
+-II board or a -IIEX board.
+
+The host must consider two things then: 1) whether a -II or -IIEX product is
+being used, and 2) whether an 8-bit or 16-bit data path is used.
+
+A further difference is that -II boards always have a 512-byte fifo operating in
+each direction. -IIEX boards may use fifos of varying size; this size is
+reported as part of the power-up message.
+
+I/O Map Of IntelliPort-II and IntelliPort-IIEX boards:
+(Relative to the chosen base address)
+
+Addr  R/W      IntelliPort-II    IntelliPort-IIEX
+----  ---      --------------    ----------------
+0     R/W      Data Port (byte)  Data Port (byte or word)
+1     R/W      (Not used)        (MSB of word-wide data written to Data Port)
+2     R        Status Register   Status Register
+2     W        Pointer Register  Interrupt Mask Register
+3     R/W      (Not used)        Mailbox Registers (6 bits: 11111100)
+4,5   --       Reserved for future products
+6     --       Reserved for future products
+7     R        Guaranteed to have no effect
+7     W        Hardware reset of board.
+
+
+Rules:
+All data transfers are performed using the even i/o address. If byte-wide data
+transfers are being used, do INB/OUTB operations on the data port. If word-wide
+transfers are used, do INW/OUTW operations. In some circumstances (such as
+reading the power-up message) you will do INB from the data port, but in this
+case the MSB of each word read is lost. When accessing all other unreserved
+registers, use byte operations only.
+------------------------------------------------------------------------------*/
+
+//------------------------------------------------
+// Mandatory Includes:
+//------------------------------------------------
+//
+#include "ip2types.h"
+#include "i2os.h"    /* For any o.s., compiler, or host-related issues */
+
+//-------------------------------------------------------------------------
+// Manifests for the I/O map:
+//-------------------------------------------------------------------------
+// R/W: Data port (byte) for IntelliPort-II,
+// R/W: Data port (byte or word) for IntelliPort-IIEX
+// Incoming or outgoing data passes through a FIFO, the status of which is
+// available in some of the bits in FIFO_STATUS. This (bidirectional) FIFO is
+// the primary means of transferring data, commands, flow-control, and status
+// information between the host and board.
+//
+#define FIFO_DATA 0
+
+// Another way of passing information between the board and the host is
+// through "mailboxes". Unlike a FIFO, a mailbox holds only a single byte of
+// data.  Writing data to the mailbox causes a status bit to be set, and
+// potentially interrupting the intended receiver. The sender has some way to
+// determine whether the data has been read yet; as soon as it has, it may send
+// more. The mailboxes are handled differently on -II and -IIEX products, as
+// suggested below.
+//------------------------------------------------------------------------------
+// Read: Status Register for IntelliPort-II or -IIEX
+// The presence of any bit set here will cause an interrupt to the host,
+// provided the corresponding bit has been unmasked in the interrupt mask
+// register. Furthermore, interrupts to the host are disabled globally until the
+// loadware selects the irq line to use. With the exception of STN_MR, the bits
+// remain set so long as the associated condition is true.
+//
+#define FIFO_STATUS 2
+
+// Bit map of status bits which are identical for -II and -IIEX
+//
+#define ST_OUT_FULL  0x40  // Outbound FIFO full
+#define ST_IN_EMPTY  0x20  // Inbound FIFO empty
+#define ST_IN_MAIL   0x04  // Inbound Mailbox full
+
+// The following exists only on the Intelliport-IIEX, and indicates that the
+// board has not read the last outgoing mailbox data yet. In the IntelliPort-II,
+// the outgoing mailbox may be read back: a zero indicates the board has read
+// the data.
+//
+#define STE_OUT_MAIL 0x80  // Outbound mailbox full (!)
+
+// The following bits are defined differently for -II and -IIEX boards. Code
+// which relies on these bits will need to be functionally different for the two
+// types of boards and should be generally avoided because of the additional
+// complexity this creates:
+
+// Bit map of status bits only on -II
+
+// Fifo has been RESET (cleared when the status register is read). Note that
+// this condition cannot be masked and would always interrupt the host, except
+// that the hardware reset also disables interrupts globally from the board
+// until re-enabled by loadware. This could also arise from the
+// Am4701-supported command to reset the chip, but this command is generally not
+// used here.
+//
+#define STN_MR       0x80
+
+// See the AMD Am4701 data sheet for details on the following four bits. They
+// are not presently used by Computone drivers.
+//
+#define STN_OUT_AF  0x10  // Outbound FIFO almost full (programmable)
+#define STN_IN_AE   0x08  // Inbound FIFO almost empty (programmable)
+#define STN_BD      0x02  // Inbound byte detected
+#define STN_PE      0x01  // Parity/Framing condition detected
+
+// Bit-map of status bits only on -IIEX
+//
+#define STE_OUT_HF  0x10  // Outbound FIFO half full
+#define STE_IN_HF   0x08  // Inbound FIFO half full
+#define STE_IN_FULL 0x02  // Inbound FIFO full
+#define STE_OUT_MT  0x01  // Outbound FIFO empty
+
+//------------------------------------------------------------------------------
+
+// Intelliport-II -- Write Only: the pointer register.
+// Values are written to this register to select the Am4701 internal register to
+// be accessed on the next operation.
+//
+#define FIFO_PTR    0x02
+
+// Values for the pointer register
+//
+#define SEL_COMMAND 0x1    // Selects the Am4701 command register
+
+// Some possible commands:
+//
+#define SEL_CMD_MR  0x80	// Am4701 command to reset the chip
+#define SEL_CMD_SH  0x40	// Am4701 command to map the "other" port into the
+							// status register.
+#define SEL_CMD_UNSH   0	// Am4701 command to "unshift": port maps into its
+							// own status register.
+#define SEL_MASK     0x2	// Selects the Am4701 interrupt mask register. The
+							// interrupt mask register is bit-mapped to match 
+							// the status register (FIFO_STATUS) except for
+							// STN_MR. (See above.)
+#define SEL_BYTE_DET 0x3	// Selects the Am4701 byte-detect register. (Not
+							// normally used except in diagnostics.)
+#define SEL_OUTMAIL  0x4	// Selects the outbound mailbox (R/W). Reading back
+							// a value of zero indicates that the mailbox has
+							// been read by the board and is available for more
+							// data./ Writing to the mailbox optionally
+							// interrupts the board, depending on the loadware's
+							// setting of its interrupt mask register.
+#define SEL_AEAF     0x5	// Selects AE/AF threshold register.
+#define SEL_INMAIL   0x6	// Selects the inbound mailbox (Read)
+
+//------------------------------------------------------------------------------
+// IntelliPort-IIEX --  Write Only: interrupt mask (and misc flags) register:
+// Unlike IntelliPort-II, bit assignments do NOT match those of the status
+// register.
+//
+#define FIFO_MASK    0x2
+
+// Mailbox readback select:
+// If set, reads to FIFO_MAIL will read the OUTBOUND mailbox (host to board). If
+// clear (default on reset) reads to FIFO_MAIL will read the INBOUND mailbox.
+// This is the normal situation. The clearing of a mailbox is determined on
+// -IIEX boards by waiting for the STE_OUT_MAIL bit to clear. Readback
+// capability is provided for diagnostic purposes only.
+//
+#define  MX_OUTMAIL_RSEL   0x80
+
+#define  MX_IN_MAIL  0x40	// Enables interrupts when incoming mailbox goes
+							// full (ST_IN_MAIL set).
+#define  MX_IN_FULL  0x20	// Enables interrupts when incoming FIFO goes full
+							// (STE_IN_FULL).
+#define  MX_IN_MT    0x08	// Enables interrupts when incoming FIFO goes empty
+							// (ST_IN_MT).
+#define  MX_OUT_FULL 0x04	// Enables interrupts when outgoing FIFO goes full
+							// (ST_OUT_FULL).
+#define  MX_OUT_MT   0x01	// Enables interrupts when outgoing FIFO goes empty
+							// (STE_OUT_MT).
+
+// Any remaining bits are reserved, and should be written to ZERO for
+// compatibility with future Computone products.
+
+//------------------------------------------------------------------------------
+// IntelliPort-IIEX: -- These are only 6-bit mailboxes !!! -- 11111100 (low two
+// bits always read back 0).
+// Read:  One of the mailboxes, usually Inbound.
+//        Inbound Mailbox (MX_OUTMAIL_RSEL = 0)
+//        Outbound Mailbox (MX_OUTMAIL_RSEL = 1)
+// Write: Outbound Mailbox
+// For the IntelliPort-II boards, the outbound mailbox is read back to determine
+// whether the board has read the data (0 --> data has been read). For the
+// IntelliPort-IIEX, this is done by reading a status register. To determine
+// whether mailbox is available for more outbound data, use the STE_OUT_MAIL bit
+// in FIFO_STATUS. Moreover, although the Outbound Mailbox can be read back by
+// setting MX_OUTMAIL_RSEL, it is NOT cleared when the board reads it, as is the
+// case with the -II boards. For this reason, FIFO_MAIL is normally used to read
+// the inbound FIFO, and MX_OUTMAIL_RSEL kept clear. (See above for
+// MX_OUTMAIL_RSEL description.)
+//
+#define  FIFO_MAIL   0x3
+
+//------------------------------------------------------------------------------
+// WRITE ONLY:  Resets the board. (Data doesn't matter).
+//
+#define  FIFO_RESET  0x7
+
+//------------------------------------------------------------------------------
+// READ ONLY:  Will have no effect. (Data is undefined.)
+// Actually, there will be an effect, in that the operation is sure to generate
+// a bus cycle: viz., an I/O byte Read. This fact can be used to enforce short
+// delays when no comparable time constant is available.
+//
+#define  FIFO_NOP    0x7
+
+//------------------------------------------------------------------------------
+// RESET & POWER-ON RESET MESSAGE
+/*------------------------------------------------------------------------------
+RESET:
+
+The IntelliPort-II and -IIEX boards are reset in three ways: Power-up, channel
+reset, and via a write to the reset register described above. For products using
+the ISA bus, these three sources of reset are equvalent. For MCA and EISA buses,
+the Power-up and channel reset sources cause additional hardware initialization
+which should only occur at system startup time.
+
+The third type of reset, called a "command reset", is done by writing any data
+to the FIFO_RESET address described above. This resets the on-board processor,
+FIFO, UARTS, and associated hardware.
+
+This passes control of the board to the bootstrap firmware, which performs a
+Power-On Self Test and which detects its current configuration. For example,
+-IIEX products determine the size of FIFO which has been installed, and the
+number and type of expansion boxes attached.
+
+This and other information is then written to the FIFO in a 16-byte data block
+to be read by the host. This block is guaranteed to be present within two (2)
+seconds of having received the command reset. The firmware is now ready to
+receive loadware from the host.
+
+It is good practice to perform a command reset to the board explicitly as part
+of your software initialization.  This allows your code to properly restart from
+a soft boot. (Many systems do not issue channel reset on soft boot).
+
+Because of a hardware reset problem on some of the Cirrus Logic 1400's which are
+used on the product, it is recommended that you reset the board twice, separated
+by an approximately 50 milliseconds delay. (VERY approximately: probably ok to
+be off by a factor of five. The important point is that the first command reset
+in fact generates a reset pulse on the board. This pulse is guaranteed to last
+less than 10 milliseconds. The additional delay ensures the 1400 has had the
+chance to respond sufficiently to the first reset. Why not a longer delay? Much
+more than 50 milliseconds gets to be noticable, but the board would still work.
+
+Once all 16 bytes of the Power-on Reset Message have been read, the bootstrap
+firmware is ready to receive loadware.
+
+Note on Power-on Reset Message format:
+The various fields have been designed with future expansion in view.
+Combinations of bitfields and values have been defined which define products
+which may not currently exist. This has been done to allow drivers to anticipate
+the possible introduction of products in a systematic fashion. This is not
+intended to suggest that each potential product is actually under consideration.
+------------------------------------------------------------------------------*/
+
+//----------------------------------------
+// Format of Power-on Reset Message
+//----------------------------------------
+
+typedef union _porStr		// "por" stands for Power On Reset
+{
+	unsigned char  c[16];	// array used when considering the message as a
+							// string of undifferentiated characters
+
+	struct					// Elements used when considering values
+	{
+		// The first two bytes out of the FIFO are two magic numbers. These are
+		// intended to establish that there is indeed a member of the
+		// IntelliPort-II(EX) family present. The remaining bytes may be 
+		// expected // to be valid. When reading the Power-on Reset message, 
+		// if the magic numbers do not match it is probably best to stop
+		// reading immediately. You are certainly not reading our board (unless
+		// hardware is faulty), and may in fact be reading some other piece of
+		// hardware.
+
+		unsigned char porMagic1;   // magic number: first byte == POR_MAGIC_1 
+		unsigned char porMagic2;   // magic number: second byte == POR_MAGIC_2 
+
+		// The Version, Revision, and Subrevision are stored as absolute numbers
+		// and would normally be displayed in the format V.R.S (e.g. 1.0.2)
+
+		unsigned char porVersion;  // Bootstrap firmware version number
+		unsigned char porRevision; // Bootstrap firmware revision number
+		unsigned char porSubRev;   // Bootstrap firmware sub-revision number
+
+		unsigned char porID;	// Product ID:  Bit-mapped according to
+								// conventions described below. Among other
+								// things, this allows us to distinguish
+								// IntelliPort-II boards from IntelliPort-IIEX
+								// boards.
+
+		unsigned char porBus;	// IntelliPort-II: Unused
+								// IntelliPort-IIEX: Bus Information:
+								// Bit-mapped below
+
+		unsigned char porMemory;	// On-board DRAM size: in 32k blocks
+
+		// porPorts1 (and porPorts2) are used to determine the ports which are
+		// available to the board. For non-expandable product, a single number 
+		// is sufficient. For expandable product, the board may be connected
+		// to as many as four boxes. Each box may be (so far) either a 16-port
+		// or an 8-port size. Whenever an 8-port box is used, the remaining 8
+		// ports leave gaps between existing channels. For that reason,
+		// expandable products must report a MAP of available channels. Since 
+		// each UART supports four ports, we represent each UART found by a
+		// single bit. Using two bytes to supply the mapping information we
+		// report the presense or absense of up to 16 UARTS, or 64 ports in
+		// steps of 4 ports. For -IIEX products, the ports are numbered
+		// starting at the box closest to the controller in the "chain".
+
+		// Interpreted Differently for IntelliPort-II and -IIEX.
+		// -II:   Number of ports (Derived actually from product ID). See
+		// Diag1&2 to indicate if uart was actually detected.
+		// -IIEX: Bit-map of UARTS found, LSB (see below for MSB of this). This
+		//        bitmap is based on detecting the uarts themselves; 
+		//        see porFlags for information from the box i.d's.
+		unsigned char  porPorts1;
+
+		unsigned char  porDiag1;	// Results of on-board P.O.S.T, 1st byte
+		unsigned char  porDiag2;	// Results of on-board P.O.S.T, 2nd byte
+		unsigned char  porSpeed;	// Speed of local CPU: given as MHz x10
+									// e.g., 16.0 MHz CPU is reported as 160
+		unsigned char  porFlags;	// Misc information (see manifests below)
+									// Bit-mapped: CPU type, UART's present
+
+		unsigned char  porPorts2;	// -II:  Undefined
+									// -IIEX: Bit-map of UARTS found, MSB (see
+									//        above for LSB)
+
+		// IntelliPort-II: undefined
+		// IntelliPort-IIEX: 1 << porFifoSize gives the size, in bytes, of the
+		// host interface FIFO, in each direction. When running the -IIEX in
+		// 8-bit mode, fifo capacity is halved. The bootstrap firmware will
+		// have already accounted for this fact in generating this number.
+		unsigned char  porFifoSize;
+
+		// IntelliPort-II: undefined
+		// IntelliPort-IIEX: The number of boxes connected. (Presently 1-4)
+		unsigned char  porNumBoxes;
+	} e;
+} porStr, *porStrPtr;
+
+//--------------------------
+// Values for porStr fields
+//--------------------------
+
+//---------------------
+// porMagic1, porMagic2
+//----------------------
+//
+#define  POR_MAGIC_1    0x96  // The only valid value for porMagic1
+#define  POR_MAGIC_2    0x35  // The only valid value for porMagic2
+#define  POR_1_INDEX    0     // Byte position of POR_MAGIC_1
+#define  POR_2_INDEX    1     // Ditto for POR_MAGIC_2
+
+//----------------------
+// porID
+//----------------------
+//
+#define  POR_ID_FAMILY  0xc0	// These bits indicate the general family of
+								// product.
+#define  POR_ID_FII     0x00	// Family is "IntelliPort-II"
+#define  POR_ID_FIIEX   0x40	// Family is "IntelliPort-IIEX"
+
+// These bits are reserved, presently zero. May be used at a later date to
+// convey other product information.
+//
+#define POR_ID_RESERVED 0x3c
+
+#define POR_ID_SIZE     0x03	// Remaining bits indicate number of ports &
+								// Connector information.
+#define POR_ID_II_8     0x00	// For IntelliPort-II, indicates 8-port using
+								// standard brick.
+#define POR_ID_II_8R    0x01	// For IntelliPort-II, indicates 8-port using
+								// RJ11's (no CTS)
+#define POR_ID_II_6     0x02	// For IntelliPort-II, indicates 6-port using
+								// RJ45's
+#define POR_ID_II_4     0x03	// For IntelliPort-II, indicates 4-port using
+								// 4xRJ45 connectors
+#define POR_ID_EX       0x00	// For IntelliPort-IIEX, indicates standard
+								// expandable controller (other values reserved)
+
+//----------------------
+// porBus
+//----------------------
+
+// IntelliPort-IIEX only: Board is installed in a 16-bit slot
+//
+#define POR_BUS_SLOT16  0x20
+
+// IntelliPort-IIEX only: DIP switch #8 is on, selecting 16-bit host interface
+// operation.
+// 
+#define POR_BUS_DIP16   0x10
+
+// Bits 0-2 indicate type of bus: This information is stored in the bootstrap
+// loadware, different loadware being used on different products for different
+// buses. For most situations, the drivers do not need this information; but it
+// is handy in a diagnostic environment. For example, on microchannel boards,
+// you would not want to try to test several interrupts, only the one for which
+// you were configured.
+//
+#define  POR_BUS_TYPE   0x07
+
+// Unknown:  this product doesn't know what bus it is running in. (e.g. if same
+// bootstrap firmware were wanted for two different buses.)
+//
+#define  POR_BUS_T_UNK  0
+
+// Note: existing firmware for ISA-8 and MC-8 currently report the POR_BUS_T_UNK
+// state, since the same bootstrap firmware is used for each.
+
+#define  POR_BUS_T_MCA  1  // MCA BUS */
+#define  POR_BUS_T_EISA 2  // EISA BUS */
+#define  POR_BUS_T_ISA  3  // ISA BUS */
+
+// Values 4-7 Reserved
+
+// Remaining bits are reserved
+
+//----------------------
+// porDiag1
+//----------------------
+
+#define  POR_BAD_MAPPER 0x80	// HW failure on P.O.S.T: Chip mapper failed
+
+// These two bits valid only for the IntelliPort-II
+//
+#define  POR_BAD_UART1  0x01	// First  1400 bad
+#define  POR_BAD_UART2  0x02	// Second 1400 bad
+
+//----------------------
+// porDiag2
+//----------------------
+
+#define  POR_DEBUG_PORT 0x80	// debug port was detected by the P.O.S.T
+#define  POR_DIAG_OK    0x00	// Indicates passage: Failure codes not yet
+								// available.
+								// Other bits undefined.
+//----------------------
+// porFlags
+//----------------------
+
+#define  POR_CPU     0x03	// These bits indicate supposed CPU type
+#define  POR_CPU_8   0x01	// Board uses an 80188 (no such thing yet)
+#define  POR_CPU_6   0x02	// Board uses an 80186 (all existing products)
+#define  POR_CEX4    0x04	// If set, this is an ISA-CEX/4: An ISA-4 (asic)
+							// which is architected like an ISA-CEX connected
+							// to a (hitherto impossible) 4-port box.
+#define POR_BOXES    0xf0	// Valid for IntelliPort-IIEX only: Map of Box
+							// sizes based on box I.D.
+#define POR_BOX_16   0x10	// Set indicates 16-port, clear 8-port
+
+//-------------------------------------
+// LOADWARE and DOWNLOADING CODE
+//-------------------------------------
+
+/*
+Loadware may be sent to the board in two ways:
+1) It may be read from a (binary image) data file block by block as each block
+	is sent to the board. This is only possible when the initialization is
+	performed by code which can access your file system. This is most suitable
+	for diagnostics and appications which use the interface library directly.
+
+2) It may be hard-coded into your source by including a .h file (typically
+	supplied by Computone), which declares a data array and initializes every
+	element. This acheives the same result as if an entire loadware file had 
+	been read into the array.
+
+	This requires more data space in your program, but access to the file system
+	is not required. This method is more suited to driver code, which typically
+	is running at a level too low to access the file system directly.
+
+At present, loadware can only be generated at Computone.
+
+All Loadware begins with a header area which has a particular format. This
+includes a magic number which identifies the file as being (purportedly)
+loadware, CRC (for the loader), and version information.
+*/
+
+
+//-----------------------------------------------------------------------------
+// Format of loadware block
+//
+// This is defined as a union so we can pass a pointer to one of these items
+// and (if it is the first block) pick out the version information, etc.
+//
+// Otherwise, to deal with this as a simple character array
+//------------------------------------------------------------------------------
+
+#define LOADWARE_BLOCK_SIZE   512   // Number of bytes in each block of loadware
+
+typedef union _loadHdrStr
+{
+	unsigned char c[LOADWARE_BLOCK_SIZE];  // Valid for every block
+
+	struct	// These fields are valid for only the first block of loadware.
+	{
+		unsigned char loadMagic;		// Magic number: see below
+		unsigned char loadBlocksMore;	// How many more blocks?
+		unsigned char loadCRC[2];		// Two CRC bytes: used by loader
+		unsigned char loadVersion;		// Version number
+		unsigned char loadRevision;		// Revision number
+		unsigned char loadSubRevision;	// Sub-revision number
+		unsigned char loadSpares[9];	// Presently unused
+		unsigned char loadDates[32];	// Null-terminated string which can give
+										// date and time of compilation
+	} e;
+} loadHdrStr, *loadHdrStrPtr;
+
+//------------------------------------
+// Defines for downloading code:
+//------------------------------------
+
+// The loadMagic field in the first block of the loadfile must be this, else the
+// file is not valid.
+//
+#define  MAGIC_LOADFILE 0x3c
+
+// How do we know the load was successful? On completion of the load, the
+// bootstrap firmware returns a code to indicate whether it thought the download
+// was valid and intends to execute it. These are the only possible valid codes:
+//
+#define  LOADWARE_OK    0xc3        // Download was ok
+#define  LOADWARE_BAD   0x5a        // Download was bad (CRC error)
+
+// Constants applicable to writing blocks of loadware:
+// The first block of loadware might take 600 mS to load, in extreme cases.
+// (Expandable board: worst case for sending startup messages to the LCD's).
+// The 600mS figure is not really a calculation, but a conservative
+// guess/guarantee. Usually this will be within 100 mS, like subsequent blocks.
+//
+#define  MAX_DLOAD_START_TIME 1000  // 1000 mS
+#define  MAX_DLOAD_READ_TIME  100   // 100 mS
+
+// Firmware should respond with status (see above) within this long of host
+// having sent the final block.
+//
+#define  MAX_DLOAD_ACK_TIME   100   // 100 mS, again!
+
+//------------------------------------------------------
+// MAXIMUM NUMBER OF PORTS PER BOARD:
+// This is fixed for now (with the expandable), but may
+// be expanding according to even newer products.
+//------------------------------------------------------
+//
+#define ABS_MAX_BOXES   4     // Absolute most boxes per board
+#define ABS_BIGGEST_BOX 16    // Absolute the most ports per box
+#define ABS_MOST_PORTS  (ABS_MAX_BOXES * ABS_BIGGEST_BOX)
+
+#endif   // I2HW_H
+
diff --git a/drivers/char/ip2/i2lib.c b/drivers/char/ip2/i2lib.c
new file mode 100644
index 0000000..82c5f30
--- /dev/null
+++ b/drivers/char/ip2/i2lib.c
@@ -0,0 +1,2219 @@
+/*******************************************************************************
+*
+*   (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: High-level interface code for the device driver. Uses the
+*                Extremely Low Level Interface Support (i2ellis.c). Provides an
+*                interface to the standard loadware, to support drivers or
+*                application code. (This is included source code, not a separate
+*                compilation module.)
+*
+*******************************************************************************/
+//------------------------------------------------------------------------------
+// Note on Strategy:
+// Once the board has been initialized, it will interrupt us when:
+// 1) It has something in the fifo for us to read (incoming data, flow control
+// packets, or whatever).
+// 2) It has stripped whatever we have sent last time in the FIFO (and
+// consequently is ready for more).
+//
+// Note also that the buffer sizes declared in i2lib.h are VERY SMALL. This
+// worsens performance considerably, but is done so that a great many channels
+// might use only a little memory.
+//------------------------------------------------------------------------------
+
+//------------------------------------------------------------------------------
+// Revision History:
+//
+// 0.00 -  4/16/91 --- First Draft
+// 0.01 -  4/29/91 --- 1st beta release
+// 0.02 -  6/14/91 --- Changes to allow small model compilation
+// 0.03 -  6/17/91 MAG Break reporting protected from interrupts routines with
+//                     in-line asm added for moving data to/from ring buffers,
+//                     replacing a variety of methods used previously.
+// 0.04 -  6/21/91 MAG Initial flow-control packets not queued until
+//                     i2_enable_interrupts time. Former versions would enqueue
+//                     them at i2_init_channel time, before we knew how many
+//                     channels were supposed to exist!
+// 0.05 - 10/12/91 MAG Major changes: works through the ellis.c routines now;
+//                     supports new 16-bit protocol and expandable boards.
+//      - 10/24/91 MAG Most changes in place and stable.
+// 0.06 -  2/20/92 MAG Format of CMD_HOTACK corrected: the command takes no
+//                     argument.
+// 0.07 -- 3/11/92 MAG Support added to store special packet types at interrupt
+//                     level (mostly responses to specific commands.)
+// 0.08 -- 3/30/92 MAG Support added for STAT_MODEM packet
+// 0.09 -- 6/24/93 MAG i2Link... needed to update number of boards BEFORE
+//                     turning on the interrupt.
+// 0.10 -- 6/25/93 MAG To avoid gruesome death from a bad board, we sanity check
+//                     some incoming.
+//
+// 1.1  - 12/25/96 AKM Linux version.
+//      - 10/09/98 DMC Revised Linux version.
+//------------------------------------------------------------------------------
+
+//************
+//* Includes *
+//************
+
+#include <linux/sched.h>
+#include "i2lib.h"
+
+
+//***********************
+//* Function Prototypes *
+//***********************
+static void i2QueueNeeds(i2eBordStrPtr, i2ChanStrPtr, int);
+static i2ChanStrPtr i2DeQueueNeeds(i2eBordStrPtr, int );
+static void i2StripFifo(i2eBordStrPtr);
+static void i2StuffFifoBypass(i2eBordStrPtr);
+static void i2StuffFifoFlow(i2eBordStrPtr);
+static void i2StuffFifoInline(i2eBordStrPtr);
+static int i2RetryFlushOutput(i2ChanStrPtr);
+
+// Not a documented part of the library routines (careful...) but the Diagnostic
+// i2diag.c finds them useful to help the throughput in certain limited
+// single-threaded operations.
+static void iiSendPendingMail(i2eBordStrPtr);
+static void serviceOutgoingFifo(i2eBordStrPtr);
+
+// Functions defined in ip2.c as part of interrupt handling
+static void do_input(void *);
+static void do_status(void *);
+
+//***************
+//* Debug  Data *
+//***************
+#ifdef DEBUG_FIFO
+
+unsigned char DBGBuf[0x4000];
+unsigned short I = 0;
+
+static void
+WriteDBGBuf(char *s, unsigned char *src, unsigned short n ) 
+{
+	char *p = src;
+
+	// XXX: We need a spin lock here if we ever use this again
+
+	while (*s) {	// copy label
+		DBGBuf[I] = *s++;
+		I = I++ & 0x3fff;
+	}
+	while (n--) {	// copy data
+		DBGBuf[I] = *p++;
+		I = I++ & 0x3fff;
+	}
+}
+
+static void
+fatality(i2eBordStrPtr pB )
+{
+	int i;
+
+	for (i=0;i<sizeof(DBGBuf);i++) {
+		if ((i%16) == 0)
+			printk("\n%4x:",i);
+		printk("%02x ",DBGBuf[i]);
+	}
+	printk("\n");
+	for (i=0;i<sizeof(DBGBuf);i++) {
+		if ((i%16) == 0)
+			printk("\n%4x:",i);
+		if (DBGBuf[i] >= ' ' && DBGBuf[i] <= '~') {
+			printk(" %c ",DBGBuf[i]);
+		} else {
+			printk(" . ");
+		}
+	}
+	printk("\n");
+	printk("Last index %x\n",I);
+}
+#endif /* DEBUG_FIFO */
+
+//********
+//* Code *
+//********
+
+static inline int
+i2Validate ( i2ChanStrPtr pCh )
+{
+	//ip2trace(pCh->port_index, ITRC_VERIFY,ITRC_ENTER,2,pCh->validity,
+	//	(CHANNEL_MAGIC | CHANNEL_SUPPORT));
+	return ((pCh->validity & (CHANNEL_MAGIC_BITS | CHANNEL_SUPPORT)) 
+			  == (CHANNEL_MAGIC | CHANNEL_SUPPORT));
+}
+
+//******************************************************************************
+// Function:   iiSendPendingMail(pB)
+// Parameters: Pointer to a board structure
+// Returns:    Nothing
+//
+// Description:
+// If any outgoing mail bits are set and there is outgoing mailbox is empty,
+// send the mail and clear the bits.
+//******************************************************************************
+static inline void
+iiSendPendingMail(i2eBordStrPtr pB)
+{
+	if (pB->i2eOutMailWaiting && (!pB->i2eWaitingForEmptyFifo) )
+	{
+		if (iiTrySendMail(pB, pB->i2eOutMailWaiting))
+		{
+			/* If we were already waiting for fifo to empty,
+			 * or just sent MB_OUT_STUFFED, then we are
+			 * still waiting for it to empty, until we should
+			 * receive an MB_IN_STRIPPED from the board.
+			 */
+			pB->i2eWaitingForEmptyFifo |=
+				(pB->i2eOutMailWaiting & MB_OUT_STUFFED);
+			pB->i2eOutMailWaiting = 0;
+			pB->SendPendingRetry = 0;
+		} else {
+/*		The only time we hit this area is when "iiTrySendMail" has
+		failed.  That only occurs when the outbound mailbox is
+		still busy with the last message.  We take a short breather
+		to let the board catch up with itself and then try again.
+		16 Retries is the limit - then we got a borked board.
+			/\/\|=mhw=|\/\/				*/
+
+			if( ++pB->SendPendingRetry < 16 ) {
+
+				init_timer( &(pB->SendPendingTimer) );
+				pB->SendPendingTimer.expires  = jiffies + 1;
+				pB->SendPendingTimer.function = (void*)(unsigned long)iiSendPendingMail;
+				pB->SendPendingTimer.data     = (unsigned long)pB;
+				add_timer( &(pB->SendPendingTimer) );
+			} else {
+				printk( KERN_ERR "IP2: iiSendPendingMail unable to queue outbound mail\n" );
+			}
+		}
+	}
+}
+
+//******************************************************************************
+// Function:   i2InitChannels(pB, nChannels, pCh)
+// Parameters: Pointer to Ellis Board structure
+//             Number of channels to initialize
+//             Pointer to first element in an array of channel structures
+// Returns:    Success or failure
+//
+// Description:
+//
+// This function patches pointers, back-pointers, and initializes all the
+// elements in the channel structure array.
+//
+// This should be run after the board structure is initialized, through having
+// loaded the standard loadware (otherwise it complains).
+//
+// In any case, it must be done before any serious work begins initializing the
+// irq's or sending commands...
+//
+//******************************************************************************
+static int
+i2InitChannels ( i2eBordStrPtr pB, int nChannels, i2ChanStrPtr pCh)
+{
+	int index, stuffIndex;
+	i2ChanStrPtr *ppCh;
+	
+	if (pB->i2eValid != I2E_MAGIC) {
+		COMPLETE(pB, I2EE_BADMAGIC);
+	}
+	if (pB->i2eState != II_STATE_STDLOADED) {
+		COMPLETE(pB, I2EE_BADSTATE);
+	}
+
+	LOCK_INIT(&pB->read_fifo_spinlock);
+	LOCK_INIT(&pB->write_fifo_spinlock);
+	LOCK_INIT(&pB->Dbuf_spinlock);
+	LOCK_INIT(&pB->Bbuf_spinlock);
+	LOCK_INIT(&pB->Fbuf_spinlock);
+	
+	// NO LOCK needed yet - this is init
+
+	pB->i2eChannelPtr = pCh;
+	pB->i2eChannelCnt = nChannels;
+
+	pB->i2Fbuf_strip = pB->i2Fbuf_stuff = 0;
+	pB->i2Dbuf_strip = pB->i2Dbuf_stuff = 0;
+	pB->i2Bbuf_strip = pB->i2Bbuf_stuff = 0;
+
+	pB->SendPendingRetry = 0;
+
+	memset ( pCh, 0, sizeof (i2ChanStr) * nChannels );
+
+	for (index = stuffIndex = 0, ppCh = (i2ChanStrPtr *)(pB->i2Fbuf);
+		  nChannels && index < ABS_MOST_PORTS;
+		  index++)
+	{
+		if ( !(pB->i2eChannelMap[index >> 4] & (1 << (index & 0xf)) ) ) {
+			continue;
+		}
+		LOCK_INIT(&pCh->Ibuf_spinlock);
+		LOCK_INIT(&pCh->Obuf_spinlock);
+		LOCK_INIT(&pCh->Cbuf_spinlock);
+		LOCK_INIT(&pCh->Pbuf_spinlock);
+		// NO LOCK needed yet - this is init
+		// Set up validity flag according to support level
+		if (pB->i2eGoodMap[index >> 4] & (1 << (index & 0xf)) ) {
+			pCh->validity = CHANNEL_MAGIC | CHANNEL_SUPPORT;
+		} else {
+			pCh->validity = CHANNEL_MAGIC;
+		}
+		pCh->pMyBord = pB;      /* Back-pointer */
+
+		// Prepare an outgoing flow-control packet to send as soon as the chance
+		// occurs.
+		if ( pCh->validity & CHANNEL_SUPPORT ) {
+			pCh->infl.hd.i2sChannel = index;
+			pCh->infl.hd.i2sCount = 5;
+			pCh->infl.hd.i2sType = PTYPE_BYPASS;
+			pCh->infl.fcmd = 37;
+			pCh->infl.asof = 0;
+			pCh->infl.room = IBUF_SIZE - 1;
+
+			pCh->whenSendFlow = (IBUF_SIZE/5)*4; // when 80% full
+
+		// The following is similar to calling i2QueueNeeds, except that this
+		// is done in longhand, since we are setting up initial conditions on
+		// many channels at once.
+			pCh->channelNeeds = NEED_FLOW;  // Since starting from scratch
+			pCh->sinceLastFlow = 0;         // No bytes received since last flow
+											// control packet was queued
+			stuffIndex++;
+			*ppCh++ = pCh;      // List this channel as needing
+								// initial flow control packet sent
+		}
+
+		// Don't allow anything to be sent until the status packets come in from
+		// the board.
+
+		pCh->outfl.asof = 0;
+		pCh->outfl.room = 0;
+
+		// Initialize all the ring buffers
+
+		pCh->Ibuf_stuff = pCh->Ibuf_strip = 0;
+		pCh->Obuf_stuff = pCh->Obuf_strip = 0;
+		pCh->Cbuf_stuff = pCh->Cbuf_strip = 0;
+
+		memset( &pCh->icount, 0, sizeof (struct async_icount) );
+		pCh->hotKeyIn       = HOT_CLEAR;
+		pCh->channelOptions = 0;
+		pCh->bookMarks      = 0;
+		init_waitqueue_head(&pCh->pBookmarkWait);
+
+		init_waitqueue_head(&pCh->open_wait);
+		init_waitqueue_head(&pCh->close_wait);
+		init_waitqueue_head(&pCh->delta_msr_wait);
+
+		// Set base and divisor so default custom rate is 9600
+		pCh->BaudBase    = 921600;	// MAX for ST654, changed after we get
+		pCh->BaudDivisor = 96;		// the boxids (UART types) later
+
+		pCh->dataSetIn   = 0;
+		pCh->dataSetOut  = 0;
+
+		pCh->wopen       = 0;
+		pCh->throttled   = 0;
+
+		pCh->speed       = CBR_9600;
+
+		pCh->flags    = 0;
+
+		pCh->ClosingDelay     = 5*HZ/10;
+		pCh->ClosingWaitTime  = 30*HZ;
+
+		// Initialize task queue objects
+		INIT_WORK(&pCh->tqueue_input, do_input, pCh);
+		INIT_WORK(&pCh->tqueue_status, do_status, pCh);
+
+#ifdef IP2DEBUG_TRACE
+		pCh->trace = ip2trace;
+#endif
+
+		++pCh;
+     	--nChannels;
+	}
+	// No need to check for wrap here; this is initialization.
+	pB->i2Fbuf_stuff = stuffIndex;
+	COMPLETE(pB, I2EE_GOOD);
+
+}
+
+//******************************************************************************
+// Function:   i2DeQueueNeeds(pB, type)
+// Parameters: Pointer to a board structure
+//             type bit map: may include NEED_INLINE, NEED_BYPASS, or NEED_FLOW
+// Returns:   
+//             Pointer to a channel structure
+//
+// Description: Returns pointer struct of next channel that needs service of
+//  the type specified. Otherwise returns a NULL reference.
+//
+//******************************************************************************
+static i2ChanStrPtr 
+i2DeQueueNeeds(i2eBordStrPtr pB, int type)
+{
+	unsigned short queueIndex;
+	unsigned long flags;
+
+	i2ChanStrPtr pCh = NULL;
+
+	switch(type) {
+
+	case  NEED_INLINE:
+
+		WRITE_LOCK_IRQSAVE(&pB->Dbuf_spinlock,flags);
+		if ( pB->i2Dbuf_stuff != pB->i2Dbuf_strip)
+		{
+			queueIndex = pB->i2Dbuf_strip;
+			pCh = pB->i2Dbuf[queueIndex];
+			queueIndex++;
+			if (queueIndex >= CH_QUEUE_SIZE) {
+				queueIndex = 0;
+			}
+			pB->i2Dbuf_strip = queueIndex;
+			pCh->channelNeeds &= ~NEED_INLINE;
+		}
+		WRITE_UNLOCK_IRQRESTORE(&pB->Dbuf_spinlock,flags); 
+		break;
+
+	case NEED_BYPASS:
+
+		WRITE_LOCK_IRQSAVE(&pB->Bbuf_spinlock,flags);
+		if (pB->i2Bbuf_stuff != pB->i2Bbuf_strip)
+		{
+			queueIndex = pB->i2Bbuf_strip;
+			pCh = pB->i2Bbuf[queueIndex];
+			queueIndex++;
+			if (queueIndex >= CH_QUEUE_SIZE) {
+				queueIndex = 0;
+			}
+			pB->i2Bbuf_strip = queueIndex;
+			pCh->channelNeeds &= ~NEED_BYPASS;
+		}
+		WRITE_UNLOCK_IRQRESTORE(&pB->Bbuf_spinlock,flags); 
+		break;
+	
+	case NEED_FLOW:
+
+		WRITE_LOCK_IRQSAVE(&pB->Fbuf_spinlock,flags);
+		if (pB->i2Fbuf_stuff != pB->i2Fbuf_strip)
+		{
+			queueIndex = pB->i2Fbuf_strip;
+			pCh = pB->i2Fbuf[queueIndex];
+			queueIndex++;
+			if (queueIndex >= CH_QUEUE_SIZE) {
+				queueIndex = 0;
+			}
+			pB->i2Fbuf_strip = queueIndex;
+			pCh->channelNeeds &= ~NEED_FLOW;
+		}
+		WRITE_UNLOCK_IRQRESTORE(&pB->Fbuf_spinlock,flags); 
+		break;
+	default:
+		printk(KERN_ERR "i2DeQueueNeeds called with bad type:%x\n",type);
+		break;
+	}
+	return pCh;
+}
+
+//******************************************************************************
+// Function:   i2QueueNeeds(pB, pCh, type)
+// Parameters: Pointer to a board structure
+//             Pointer to a channel structure
+//             type bit map: may include NEED_INLINE, NEED_BYPASS, or NEED_FLOW
+// Returns:    Nothing
+//
+// Description:
+// For each type of need selected, if the given channel is not already in the
+// queue, adds it, and sets the flag indicating it is in the queue.
+//******************************************************************************
+static void
+i2QueueNeeds(i2eBordStrPtr pB, i2ChanStrPtr pCh, int type)
+{
+	unsigned short queueIndex;
+	unsigned long flags;
+
+	// We turn off all the interrupts during this brief process, since the
+	// interrupt-level code might want to put things on the queue as well.
+
+	switch (type) {
+
+	case NEED_INLINE:
+
+		WRITE_LOCK_IRQSAVE(&pB->Dbuf_spinlock,flags);
+		if ( !(pCh->channelNeeds & NEED_INLINE) )
+		{
+			pCh->channelNeeds |= NEED_INLINE;
+			queueIndex = pB->i2Dbuf_stuff;
+			pB->i2Dbuf[queueIndex++] = pCh;
+			if (queueIndex >= CH_QUEUE_SIZE)
+				queueIndex = 0;
+			pB->i2Dbuf_stuff = queueIndex;
+		}
+		WRITE_UNLOCK_IRQRESTORE(&pB->Dbuf_spinlock,flags); 
+		break;
+
+	case NEED_BYPASS:
+
+		WRITE_LOCK_IRQSAVE(&pB->Bbuf_spinlock,flags);
+		if ((type & NEED_BYPASS) && !(pCh->channelNeeds & NEED_BYPASS))
+		{
+			pCh->channelNeeds |= NEED_BYPASS;
+			queueIndex = pB->i2Bbuf_stuff;
+			pB->i2Bbuf[queueIndex++] = pCh;
+			if (queueIndex >= CH_QUEUE_SIZE)
+				queueIndex = 0;
+			pB->i2Bbuf_stuff = queueIndex;
+		} 
+		WRITE_UNLOCK_IRQRESTORE(&pB->Bbuf_spinlock,flags); 
+		break;
+
+	case NEED_FLOW:
+
+		WRITE_LOCK_IRQSAVE(&pB->Fbuf_spinlock,flags);
+		if ((type & NEED_FLOW) && !(pCh->channelNeeds & NEED_FLOW))
+		{
+			pCh->channelNeeds |= NEED_FLOW;
+			queueIndex = pB->i2Fbuf_stuff;
+			pB->i2Fbuf[queueIndex++] = pCh;
+			if (queueIndex >= CH_QUEUE_SIZE)
+				queueIndex = 0;
+			pB->i2Fbuf_stuff = queueIndex;
+		}
+		WRITE_UNLOCK_IRQRESTORE(&pB->Fbuf_spinlock,flags); 
+		break;
+
+	case NEED_CREDIT:
+		pCh->channelNeeds |= NEED_CREDIT;
+		break;
+	default:
+		printk(KERN_ERR "i2QueueNeeds called with bad type:%x\n",type);
+		break;
+	}
+	return;
+}
+
+//******************************************************************************
+// Function:   i2QueueCommands(type, pCh, timeout, nCommands, pCs,...)
+// Parameters: type - PTYPE_BYPASS or PTYPE_INLINE
+//             pointer to the channel structure
+//             maximum period to wait
+//             number of commands (n)
+//             n commands
+// Returns:    Number of commands sent, or -1 for error
+//
+// get board lock before calling
+//
+// Description:
+// Queues up some commands to be sent to a channel. To send possibly several
+// bypass or inline commands to the given channel. The timeout parameter
+// indicates how many HUNDREDTHS OF SECONDS to wait until there is room:
+// 0 = return immediately if no room, -ive  = wait forever, +ive = number of
+// 1/100 seconds to wait. Return values:
+// -1 Some kind of nasty error: bad channel structure or invalid arguments.
+//  0 No room to send all the commands
+// (+)   Number of commands sent
+//******************************************************************************
+static int
+i2QueueCommands(int type, i2ChanStrPtr pCh, int timeout, int nCommands,
+					 cmdSyntaxPtr pCs0,...)
+{
+	int totalsize = 0;
+	int blocksize;
+	int lastended;
+	cmdSyntaxPtr *ppCs;
+	cmdSyntaxPtr pCs;
+	int count;
+	int flag;
+	i2eBordStrPtr pB;
+
+	unsigned short maxBlock;
+	unsigned short maxBuff;
+	short bufroom;
+	unsigned short stuffIndex;
+	unsigned char *pBuf;
+	unsigned char *pInsert;
+	unsigned char *pDest, *pSource;
+	unsigned short channel;
+	int cnt;
+	unsigned long flags = 0;
+	rwlock_t *lock_var_p = NULL;
+
+	// Make sure the channel exists, otherwise do nothing
+	if ( !i2Validate ( pCh ) ) {
+		return -1;
+	}
+
+	ip2trace (CHANN, ITRC_QUEUE, ITRC_ENTER, 0 );
+
+	pB = pCh->pMyBord;
+
+	// Board must also exist, and THE INTERRUPT COMMAND ALREADY SENT
+	if (pB->i2eValid != I2E_MAGIC || pB->i2eUsingIrq == IRQ_UNDEFINED) {
+		return -2;
+	}
+	// If the board has gone fatal, return bad, and also hit the trap routine if
+	// it exists.
+	if (pB->i2eFatal) {
+		if ( pB->i2eFatalTrap ) {
+			(*(pB)->i2eFatalTrap)(pB);
+		}
+		return -3;
+	}
+	// Set up some variables, Which buffers are we using?  How big are they?
+	switch(type)
+	{
+	case PTYPE_INLINE:
+		flag = INL;
+		maxBlock = MAX_OBUF_BLOCK;
+		maxBuff = OBUF_SIZE;
+		pBuf = pCh->Obuf;
+		break;
+	case PTYPE_BYPASS:
+		flag = BYP;
+		maxBlock = MAX_CBUF_BLOCK;
+		maxBuff = CBUF_SIZE;
+		pBuf = pCh->Cbuf;
+		break;
+	default:
+		return -4;
+	}
+	// Determine the total size required for all the commands
+	totalsize = blocksize = sizeof(i2CmdHeader);
+	lastended = 0;
+	ppCs = &pCs0;
+	for ( count = nCommands; count; count--, ppCs++)
+	{
+		pCs = *ppCs;
+		cnt = pCs->length;
+		// Will a new block be needed for this one? 
+		// Two possible reasons: too
+		// big or previous command has to be at the end of a packet.
+		if ((blocksize + cnt > maxBlock) || lastended) {
+			blocksize = sizeof(i2CmdHeader);
+			totalsize += sizeof(i2CmdHeader);
+		}
+		totalsize += cnt;
+		blocksize += cnt;
+
+		// If this command had to end a block, then we will make sure to
+		// account for it should there be any more blocks.
+		lastended = pCs->flags & END;
+	}
+	for (;;) {
+		// Make sure any pending flush commands go out before we add more data.
+		if ( !( pCh->flush_flags && i2RetryFlushOutput( pCh ) ) ) {
+			// How much room (this time through) ?
+			switch(type) {
+			case PTYPE_INLINE:
+				lock_var_p = &pCh->Obuf_spinlock;
+				WRITE_LOCK_IRQSAVE(lock_var_p,flags);
+				stuffIndex = pCh->Obuf_stuff;
+				bufroom = pCh->Obuf_strip - stuffIndex;
+				break;
+			case PTYPE_BYPASS:
+				lock_var_p = &pCh->Cbuf_spinlock;
+				WRITE_LOCK_IRQSAVE(lock_var_p,flags);
+				stuffIndex = pCh->Cbuf_stuff;
+				bufroom = pCh->Cbuf_strip - stuffIndex;
+				break;
+			default:
+				return -5;
+			}
+			if (--bufroom < 0) {
+				bufroom += maxBuff;
+			}
+
+			ip2trace (CHANN, ITRC_QUEUE, 2, 1, bufroom );
+
+			// Check for overflow
+			if (totalsize <= bufroom) {
+				// Normal Expected path - We still hold LOCK
+				break; /* from for()- Enough room: goto proceed */
+			}
+		}
+
+		ip2trace (CHANN, ITRC_QUEUE, 3, 1, totalsize );
+
+		// Prepare to wait for buffers to empty
+		WRITE_UNLOCK_IRQRESTORE(lock_var_p,flags); 
+		serviceOutgoingFifo(pB);	// Dump what we got
+
+		if (timeout == 0) {
+			return 0;   // Tired of waiting
+		}
+		if (timeout > 0)
+			timeout--;   // So negative values == forever
+		
+		if (!in_interrupt()) {
+			current->state = TASK_INTERRUPTIBLE;
+			schedule_timeout(1);	// short nap 
+		} else {
+			// we cannot sched/sleep in interrrupt silly
+			return 0;   
+		}
+		if (signal_pending(current)) {
+			return 0;   // Wake up! Time to die!!!
+		}
+
+		ip2trace (CHANN, ITRC_QUEUE, 4, 0 );
+
+	}	// end of for(;;)
+
+	// At this point we have room and the lock - stick them in.
+	channel = pCh->infl.hd.i2sChannel;
+	pInsert = &pBuf[stuffIndex];     // Pointer to start of packet
+	pDest = CMD_OF(pInsert);         // Pointer to start of command
+
+	// When we start counting, the block is the size of the header
+	for (blocksize = sizeof(i2CmdHeader), count = nCommands,
+			lastended = 0, ppCs = &pCs0;
+		count;
+		count--, ppCs++)
+	{
+		pCs = *ppCs;         // Points to command protocol structure
+
+		// If this is a bookmark request command, post the fact that a bookmark
+		// request is pending. NOTE THIS TRICK ONLY WORKS BECAUSE CMD_BMARK_REQ
+		// has no parameters!  The more general solution would be to reference
+		// pCs->cmd[0].
+		if (pCs == CMD_BMARK_REQ) {
+			pCh->bookMarks++;
+
+			ip2trace (CHANN, ITRC_DRAIN, 30, 1, pCh->bookMarks );
+
+		}
+		cnt = pCs->length;
+
+		// If this command would put us over the maximum block size or 
+		// if the last command had to be at the end of a block, we end
+		// the existing block here and start a new one.
+		if ((blocksize + cnt > maxBlock) || lastended) {
+
+			ip2trace (CHANN, ITRC_QUEUE, 5, 0 );
+
+			PTYPE_OF(pInsert) = type;
+			CHANNEL_OF(pInsert) = channel;
+			// count here does not include the header
+			CMD_COUNT_OF(pInsert) = blocksize - sizeof(i2CmdHeader);
+			stuffIndex += blocksize;
+			if(stuffIndex >= maxBuff) {
+				stuffIndex = 0;
+				pInsert = pBuf;
+			}
+			pInsert = &pBuf[stuffIndex];  // Pointer to start of next pkt
+			pDest = CMD_OF(pInsert);
+			blocksize = sizeof(i2CmdHeader);
+		}
+		// Now we know there is room for this one in the current block
+
+		blocksize += cnt;       // Total bytes in this command
+		pSource = pCs->cmd;     // Copy the command into the buffer
+		while (cnt--) {
+			*pDest++ = *pSource++;
+		}
+		// If this command had to end a block, then we will make sure to account
+		// for it should there be any more blocks.
+		lastended = pCs->flags & END;
+	}	// end for
+	// Clean up the final block by writing header, etc
+
+	PTYPE_OF(pInsert) = type;
+	CHANNEL_OF(pInsert) = channel;
+	// count here does not include the header
+	CMD_COUNT_OF(pInsert) = blocksize - sizeof(i2CmdHeader);
+	stuffIndex += blocksize;
+	if(stuffIndex >= maxBuff) {
+		stuffIndex = 0;
+		pInsert = pBuf;
+	}
+	// Updates the index, and post the need for service. When adding these to
+	// the queue of channels, we turn off the interrupt while doing so,
+	// because at interrupt level we might want to push a channel back to the
+	// end of the queue.
+	switch(type)
+	{
+	case PTYPE_INLINE:
+		pCh->Obuf_stuff = stuffIndex;  // Store buffer pointer
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags); 
+
+		pB->debugInlineQueued++;
+		// Add the channel pointer to list of channels needing service (first
+		// come...), if it's not already there.
+		i2QueueNeeds(pB, pCh, NEED_INLINE);
+		break;
+
+	case PTYPE_BYPASS:
+		pCh->Cbuf_stuff = stuffIndex;  // Store buffer pointer
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Cbuf_spinlock,flags); 
+
+		pB->debugBypassQueued++;
+		// Add the channel pointer to list of channels needing service (first
+		// come...), if it's not already there.
+		i2QueueNeeds(pB, pCh, NEED_BYPASS);
+		break;
+	}
+
+	ip2trace (CHANN, ITRC_QUEUE, ITRC_RETURN, 1, nCommands );
+
+	return nCommands; // Good status: number of commands sent
+}
+
+//******************************************************************************
+// Function:   i2GetStatus(pCh,resetBits)
+// Parameters: Pointer to a channel structure
+//             Bit map of status bits to clear
+// Returns:    Bit map of current status bits
+//
+// Description:
+// Returns the state of data set signals, and whether a break has been received,
+// (see i2lib.h for bit-mapped result). resetBits is a bit-map of any status
+// bits to be cleared: I2_BRK, I2_PAR, I2_FRA, I2_OVR,... These are cleared
+// AFTER the condition is passed. If pCh does not point to a valid channel,
+// returns -1 (which would be impossible otherwise.
+//******************************************************************************
+static int
+i2GetStatus(i2ChanStrPtr pCh, int resetBits)
+{
+	unsigned short status;
+	i2eBordStrPtr pB;
+
+	ip2trace (CHANN, ITRC_STATUS, ITRC_ENTER, 2, pCh->dataSetIn, resetBits );
+
+	// Make sure the channel exists, otherwise do nothing */
+	if ( !i2Validate ( pCh ) )
+		return -1;
+
+	pB = pCh->pMyBord;
+
+	status = pCh->dataSetIn;
+
+	// Clear any specified error bits: but note that only actual error bits can
+	// be cleared, regardless of the value passed.
+	if (resetBits)
+	{
+		pCh->dataSetIn &= ~(resetBits & (I2_BRK | I2_PAR | I2_FRA | I2_OVR));
+		pCh->dataSetIn &= ~(I2_DDCD | I2_DCTS | I2_DDSR | I2_DRI);
+	}
+
+	ip2trace (CHANN, ITRC_STATUS, ITRC_RETURN, 1, pCh->dataSetIn );
+
+	return status;
+}
+
+//******************************************************************************
+// Function:   i2Input(pChpDest,count)
+// Parameters: Pointer to a channel structure
+//             Pointer to data buffer
+//             Number of bytes to read
+// Returns:    Number of bytes read, or -1 for error
+//
+// Description:
+// Strips data from the input buffer and writes it to pDest. If there is a
+// collosal blunder, (invalid structure pointers or the like), returns -1.
+// Otherwise, returns the number of bytes read.
+//******************************************************************************
+static int
+i2Input(i2ChanStrPtr pCh)
+{
+	int amountToMove;
+	unsigned short stripIndex;
+	int count;
+	unsigned long flags = 0;
+
+	ip2trace (CHANN, ITRC_INPUT, ITRC_ENTER, 0);
+
+	// Ensure channel structure seems real
+	if ( !i2Validate( pCh ) ) {
+		count = -1;
+		goto i2Input_exit;
+	}
+	WRITE_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags);
+
+	// initialize some accelerators and private copies
+	stripIndex = pCh->Ibuf_strip;
+
+	count = pCh->Ibuf_stuff - stripIndex;
+
+	// If buffer is empty or requested data count was 0, (trivial case) return
+	// without any further thought.
+	if ( count == 0 ) {
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+		goto i2Input_exit;
+	}
+	// Adjust for buffer wrap
+	if ( count < 0 ) {
+		count += IBUF_SIZE;
+	}
+	// Don't give more than can be taken by the line discipline
+	amountToMove = pCh->pTTY->ldisc.receive_room( pCh->pTTY );
+	if (count > amountToMove) {
+		count = amountToMove;
+	}
+	// How much could we copy without a wrap?
+	amountToMove = IBUF_SIZE - stripIndex;
+
+	if (amountToMove > count) {
+		amountToMove = count;
+	}
+	// Move the first block
+	pCh->pTTY->ldisc.receive_buf( pCh->pTTY, 
+		 &(pCh->Ibuf[stripIndex]), NULL, amountToMove );
+	// If we needed to wrap, do the second data move
+	if (count > amountToMove) {
+		pCh->pTTY->ldisc.receive_buf( pCh->pTTY, 
+		 pCh->Ibuf, NULL, count - amountToMove );
+	}
+	// Bump and wrap the stripIndex all at once by the amount of data read. This
+	// method is good regardless of whether the data was in one or two pieces.
+	stripIndex += count;
+	if (stripIndex >= IBUF_SIZE) {
+		stripIndex -= IBUF_SIZE;
+	}
+	pCh->Ibuf_strip = stripIndex;
+
+	// Update our flow control information and possibly queue ourselves to send
+	// it, depending on how much data has been stripped since the last time a
+	// packet was sent.
+	pCh->infl.asof += count;
+
+	if ((pCh->sinceLastFlow += count) >= pCh->whenSendFlow) {
+		pCh->sinceLastFlow -= pCh->whenSendFlow;
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+		i2QueueNeeds(pCh->pMyBord, pCh, NEED_FLOW);
+	} else {
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+	}
+
+i2Input_exit:
+
+	ip2trace (CHANN, ITRC_INPUT, ITRC_RETURN, 1, count);
+
+	return count;
+}
+
+//******************************************************************************
+// Function:   i2InputFlush(pCh)
+// Parameters: Pointer to a channel structure
+// Returns:    Number of bytes stripped, or -1 for error
+//
+// Description:
+// Strips any data from the input buffer. If there is a collosal blunder,
+// (invalid structure pointers or the like), returns -1. Otherwise, returns the
+// number of bytes stripped.
+//******************************************************************************
+static int
+i2InputFlush(i2ChanStrPtr pCh)
+{
+	int count;
+	unsigned long flags;
+
+	// Ensure channel structure seems real
+	if ( !i2Validate ( pCh ) )
+		return -1;
+
+	ip2trace (CHANN, ITRC_INPUT, 10, 0);
+
+	WRITE_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags);
+	count = pCh->Ibuf_stuff - pCh->Ibuf_strip;
+
+	// Adjust for buffer wrap
+	if (count < 0) {
+		count += IBUF_SIZE;
+	}
+
+	// Expedient way to zero out the buffer
+	pCh->Ibuf_strip = pCh->Ibuf_stuff;
+
+
+	// Update our flow control information and possibly queue ourselves to send
+	// it, depending on how much data has been stripped since the last time a
+	// packet was sent.
+
+	pCh->infl.asof += count;
+
+	if ( (pCh->sinceLastFlow += count) >= pCh->whenSendFlow )
+	{
+		pCh->sinceLastFlow -= pCh->whenSendFlow;
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+		i2QueueNeeds(pCh->pMyBord, pCh, NEED_FLOW);
+	} else {
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+	}
+
+	ip2trace (CHANN, ITRC_INPUT, 19, 1, count);
+
+	return count;
+}
+
+//******************************************************************************
+// Function:   i2InputAvailable(pCh)
+// Parameters: Pointer to a channel structure
+// Returns:    Number of bytes available, or -1 for error
+//
+// Description:
+// If there is a collosal blunder, (invalid structure pointers or the like),
+// returns -1. Otherwise, returns the number of bytes stripped. Otherwise,
+// returns the number of bytes available in the buffer.
+//******************************************************************************
+#if 0
+static int
+i2InputAvailable(i2ChanStrPtr pCh)
+{
+	int count;
+
+	// Ensure channel structure seems real
+	if ( !i2Validate ( pCh ) ) return -1;
+
+
+	// initialize some accelerators and private copies
+	READ_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags);
+	count = pCh->Ibuf_stuff - pCh->Ibuf_strip;
+	READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags);
+
+	// Adjust for buffer wrap
+	if (count < 0)
+	{
+		count += IBUF_SIZE;
+	}
+
+	return count;
+}
+#endif 
+
+//******************************************************************************
+// Function:   i2Output(pCh, pSource, count)
+// Parameters: Pointer to channel structure
+//             Pointer to source data
+//             Number of bytes to send
+// Returns:    Number of bytes sent, or -1 for error
+//
+// Description:
+// Queues the data at pSource to be sent as data packets to the board. If there
+// is a collosal blunder, (invalid structure pointers or the like), returns -1.
+// Otherwise, returns the number of bytes written. What if there is not enough
+// room for all the data? If pCh->channelOptions & CO_NBLOCK_WRITE is set, then
+// we transfer as many characters as we can now, then return. If this bit is
+// clear (default), routine will spin along until all the data is buffered.
+// Should this occur, the 1-ms delay routine is called while waiting to avoid
+// applications that one cannot break out of.
+//******************************************************************************
+static int
+i2Output(i2ChanStrPtr pCh, const char *pSource, int count, int user )
+{
+	i2eBordStrPtr pB;
+	unsigned char *pInsert;
+	int amountToMove;
+	int countOriginal = count;
+	unsigned short channel;
+	unsigned short stuffIndex;
+	unsigned long flags;
+	int rc = 0;
+
+	int bailout = 10;
+
+	ip2trace (CHANN, ITRC_OUTPUT, ITRC_ENTER, 2, count, user );
+
+	// Ensure channel structure seems real
+	if ( !i2Validate ( pCh ) ) 
+		return -1;
+
+	// initialize some accelerators and private copies
+	pB = pCh->pMyBord;
+	channel = pCh->infl.hd.i2sChannel;
+
+	// If the board has gone fatal, return bad, and also hit the trap routine if
+	// it exists.
+	if (pB->i2eFatal) {
+		if (pB->i2eFatalTrap) {
+			(*(pB)->i2eFatalTrap)(pB);
+		}
+		return -1;
+	}
+	// Proceed as though we would do everything
+	while ( count > 0 ) {
+
+		// How much room in output buffer is there?
+		READ_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+		amountToMove = pCh->Obuf_strip - pCh->Obuf_stuff - 1;
+		READ_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+		if (amountToMove < 0) {
+			amountToMove += OBUF_SIZE;
+		}
+		// Subtract off the headers size and see how much room there is for real
+		// data. If this is negative, we will discover later.
+		amountToMove -= sizeof (i2DataHeader);
+
+		// Don't move more (now) than can go in a single packet
+		if ( amountToMove > (int)(MAX_OBUF_BLOCK - sizeof(i2DataHeader)) ) {
+			amountToMove = MAX_OBUF_BLOCK - sizeof(i2DataHeader);
+		}
+		// Don't move more than the count we were given
+		if (amountToMove > count) {
+			amountToMove = count;
+		}
+		// Now we know how much we must move: NB because the ring buffers have
+		// an overflow area at the end, we needn't worry about wrapping in the
+		// middle of a packet.
+
+// Small WINDOW here with no LOCK but I can't call Flush with LOCK
+// We would be flushing (or ending flush) anyway
+
+		ip2trace (CHANN, ITRC_OUTPUT, 10, 1, amountToMove );
+
+		if ( !(pCh->flush_flags && i2RetryFlushOutput(pCh) ) 
+				&& amountToMove > 0 )
+		{
+			WRITE_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+			stuffIndex = pCh->Obuf_stuff;
+      
+			// Had room to move some data: don't know whether the block size,
+			// buffer space, or what was the limiting factor...
+			pInsert = &(pCh->Obuf[stuffIndex]);
+
+			// Set up the header
+			CHANNEL_OF(pInsert)     = channel;
+			PTYPE_OF(pInsert)       = PTYPE_DATA;
+			TAG_OF(pInsert)         = 0;
+			ID_OF(pInsert)          = ID_ORDINARY_DATA;
+			DATA_COUNT_OF(pInsert)  = amountToMove;
+
+			// Move the data
+			if ( user ) {
+				rc = copy_from_user((char*)(DATA_OF(pInsert)), pSource,
+						amountToMove );
+			} else {
+				memcpy( (char*)(DATA_OF(pInsert)), pSource, amountToMove );
+			}
+			// Adjust pointers and indices
+			pSource					+= amountToMove;
+			pCh->Obuf_char_count	+= amountToMove;
+			stuffIndex 				+= amountToMove + sizeof(i2DataHeader);
+			count 					-= amountToMove;
+
+			if (stuffIndex >= OBUF_SIZE) {
+				stuffIndex = 0;
+			}
+			pCh->Obuf_stuff = stuffIndex;
+
+			WRITE_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+
+			ip2trace (CHANN, ITRC_OUTPUT, 13, 1, stuffIndex );
+
+		} else {
+
+			// Cannot move data
+			// becuz we need to stuff a flush 
+			// or amount to move is <= 0
+
+			ip2trace(CHANN, ITRC_OUTPUT, 14, 3,
+				amountToMove,  pB->i2eFifoRemains,
+				pB->i2eWaitingForEmptyFifo );
+
+			// Put this channel back on queue
+			// this ultimatly gets more data or wakes write output
+			i2QueueNeeds(pB, pCh, NEED_INLINE);
+
+			if ( pB->i2eWaitingForEmptyFifo ) {
+
+				ip2trace (CHANN, ITRC_OUTPUT, 16, 0 );
+
+				// or schedule
+				if (!in_interrupt()) {
+
+					ip2trace (CHANN, ITRC_OUTPUT, 61, 0 );
+
+					current->state = TASK_INTERRUPTIBLE;
+					schedule_timeout(2);
+					if (signal_pending(current)) {
+						break;
+					}
+					continue;
+				} else {
+
+					ip2trace (CHANN, ITRC_OUTPUT, 62, 0 );
+
+					// let interrupt in = WAS restore_flags()
+					// We hold no lock nor is irq off anymore???
+					
+					break;
+				}
+				break;   // from while(count)
+			}
+			else if ( pB->i2eFifoRemains < 32 && !pB->i2eTxMailEmpty ( pB ) )
+			{
+				ip2trace (CHANN, ITRC_OUTPUT, 19, 2,
+					pB->i2eFifoRemains,
+					pB->i2eTxMailEmpty );
+
+				break;   // from while(count)
+			} else if ( pCh->channelNeeds & NEED_CREDIT ) {
+
+				ip2trace (CHANN, ITRC_OUTPUT, 22, 0 );
+
+				break;   // from while(count)
+			} else if ( --bailout) {
+
+				// Try to throw more things (maybe not us) in the fifo if we're
+				// not already waiting for it.
+	
+				ip2trace (CHANN, ITRC_OUTPUT, 20, 0 );
+
+				serviceOutgoingFifo(pB);
+				//break;  CONTINUE;
+			} else {
+				ip2trace (CHANN, ITRC_OUTPUT, 21, 3,
+					pB->i2eFifoRemains,
+					pB->i2eOutMailWaiting,
+					pB->i2eWaitingForEmptyFifo );
+
+				break;   // from while(count)
+			}
+		}
+	} // End of while(count)
+
+	i2QueueNeeds(pB, pCh, NEED_INLINE);
+
+	// We drop through either when the count expires, or when there is some
+	// count left, but there was a non-blocking write.
+	if (countOriginal > count) {
+
+		ip2trace (CHANN, ITRC_OUTPUT, 17, 2, countOriginal, count );
+
+		serviceOutgoingFifo( pB );
+	}
+
+	ip2trace (CHANN, ITRC_OUTPUT, ITRC_RETURN, 2, countOriginal, count );
+
+	return countOriginal - count;
+}
+
+//******************************************************************************
+// Function:   i2FlushOutput(pCh)
+// Parameters: Pointer to a channel structure
+// Returns:    Nothing
+//
+// Description:
+// Sends bypass command to start flushing (waiting possibly forever until there
+// is room), then sends inline command to stop flushing output, (again waiting
+// possibly forever).
+//******************************************************************************
+static inline void
+i2FlushOutput(i2ChanStrPtr pCh)
+{
+
+	ip2trace (CHANN, ITRC_FLUSH, 1, 1, pCh->flush_flags );
+
+	if (pCh->flush_flags)
+		return;
+
+	if ( 1 != i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_STARTFL) ) {
+		pCh->flush_flags = STARTFL_FLAG;		// Failed - flag for later
+
+		ip2trace (CHANN, ITRC_FLUSH, 2, 0 );
+
+	} else if ( 1 != i2QueueCommands(PTYPE_INLINE, pCh, 0, 1, CMD_STOPFL) ) {
+		pCh->flush_flags = STOPFL_FLAG;		// Failed - flag for later
+
+		ip2trace (CHANN, ITRC_FLUSH, 3, 0 );
+	}
+}
+
+static int 
+i2RetryFlushOutput(i2ChanStrPtr pCh)
+{
+	int old_flags = pCh->flush_flags;
+
+	ip2trace (CHANN, ITRC_FLUSH, 14, 1, old_flags );
+
+	pCh->flush_flags = 0;	// Clear flag so we can avoid recursion
+									// and queue the commands
+
+	if ( old_flags & STARTFL_FLAG ) {
+		if ( 1 == i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_STARTFL) ) {
+			old_flags = STOPFL_FLAG;	//Success - send stop flush
+		} else {
+			old_flags = STARTFL_FLAG;	//Failure - Flag for retry later
+		}
+
+		ip2trace (CHANN, ITRC_FLUSH, 15, 1, old_flags );
+
+	}
+	if ( old_flags & STOPFL_FLAG ) {
+		if (1 == i2QueueCommands(PTYPE_INLINE, pCh, 0, 1, CMD_STOPFL)) {
+			old_flags = 0;	// Success - clear flags
+		}
+
+		ip2trace (CHANN, ITRC_FLUSH, 16, 1, old_flags );
+	}
+	pCh->flush_flags = old_flags;
+
+	ip2trace (CHANN, ITRC_FLUSH, 17, 1, old_flags );
+
+	return old_flags;
+}
+
+//******************************************************************************
+// Function:   i2DrainOutput(pCh,timeout)
+// Parameters: Pointer to a channel structure
+//             Maximum period to wait
+// Returns:    ?
+//
+// Description:
+// Uses the bookmark request command to ask the board to send a bookmark back as
+// soon as all the data is completely sent.
+//******************************************************************************
+static void
+i2DrainWakeup(i2ChanStrPtr pCh)
+{
+	ip2trace (CHANN, ITRC_DRAIN, 10, 1, pCh->BookmarkTimer.expires );
+
+	pCh->BookmarkTimer.expires = 0;
+	wake_up_interruptible( &pCh->pBookmarkWait );
+}
+
+static void
+i2DrainOutput(i2ChanStrPtr pCh, int timeout)
+{
+	wait_queue_t wait;
+	i2eBordStrPtr pB;
+
+	ip2trace (CHANN, ITRC_DRAIN, ITRC_ENTER, 1, pCh->BookmarkTimer.expires);
+
+	pB = pCh->pMyBord;
+	// If the board has gone fatal, return bad, 
+	// and also hit the trap routine if it exists.
+	if (pB->i2eFatal) {
+		if (pB->i2eFatalTrap) {
+			(*(pB)->i2eFatalTrap)(pB);
+		}
+		return;
+	}
+	if ((timeout > 0) && (pCh->BookmarkTimer.expires == 0 )) {
+		// One per customer (channel)
+		init_timer( &(pCh->BookmarkTimer) );
+		pCh->BookmarkTimer.expires  = jiffies + timeout;
+		pCh->BookmarkTimer.function = (void*)(unsigned long)i2DrainWakeup;
+		pCh->BookmarkTimer.data     = (unsigned long)pCh;
+
+		ip2trace (CHANN, ITRC_DRAIN, 1, 1, pCh->BookmarkTimer.expires );
+
+		add_timer( &(pCh->BookmarkTimer) );
+	}
+	
+	i2QueueCommands( PTYPE_INLINE, pCh, -1, 1, CMD_BMARK_REQ );
+
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&(pCh->pBookmarkWait), &wait);
+	set_current_state( TASK_INTERRUPTIBLE );
+
+	serviceOutgoingFifo( pB );
+	
+	schedule();	// Now we take our interruptible sleep on
+
+	// Clean up the queue
+	set_current_state( TASK_RUNNING );
+	remove_wait_queue(&(pCh->pBookmarkWait), &wait);
+
+	// if expires == 0 then timer poped, then do not need to del_timer
+	if ((timeout > 0) && pCh->BookmarkTimer.expires && 
+	                     time_before(jiffies, pCh->BookmarkTimer.expires)) {
+		del_timer( &(pCh->BookmarkTimer) );
+		pCh->BookmarkTimer.expires = 0;
+
+		ip2trace (CHANN, ITRC_DRAIN, 3, 1, pCh->BookmarkTimer.expires );
+
+	}
+	ip2trace (CHANN, ITRC_DRAIN, ITRC_RETURN, 1, pCh->BookmarkTimer.expires );
+	return;
+}
+
+//******************************************************************************
+// Function:   i2OutputFree(pCh)
+// Parameters: Pointer to a channel structure
+// Returns:    Space in output buffer
+//
+// Description:
+// Returns -1 if very gross error. Otherwise returns the amount of bytes still
+// free in the output buffer.
+//******************************************************************************
+static int
+i2OutputFree(i2ChanStrPtr pCh)
+{
+	int amountToMove;
+	unsigned long flags;
+
+	// Ensure channel structure seems real
+	if ( !i2Validate ( pCh ) ) {
+		return -1;
+	}
+	READ_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+	amountToMove = pCh->Obuf_strip - pCh->Obuf_stuff - 1;
+	READ_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+
+	if (amountToMove < 0) {
+		amountToMove += OBUF_SIZE;
+	}
+	// If this is negative, we will discover later
+	amountToMove -= sizeof(i2DataHeader);
+
+	return (amountToMove < 0) ? 0 : amountToMove;
+}
+static void
+
+ip2_owake( PTTY tp)
+{
+	i2ChanStrPtr  pCh;
+
+	if (tp == NULL) return;
+
+	pCh = tp->driver_data;
+
+	ip2trace (CHANN, ITRC_SICMD, 10, 2, tp->flags,
+			(1 << TTY_DO_WRITE_WAKEUP) );
+
+	wake_up_interruptible ( &tp->write_wait );
+	if ( ( tp->flags & (1 << TTY_DO_WRITE_WAKEUP) ) 
+	  && tp->ldisc.write_wakeup )
+	{
+		(tp->ldisc.write_wakeup) ( tp );
+
+		ip2trace (CHANN, ITRC_SICMD, 11, 0 );
+
+	}
+}
+
+static inline void
+set_baud_params(i2eBordStrPtr pB) 
+{
+	int i,j;
+	i2ChanStrPtr  *pCh;
+
+	pCh = (i2ChanStrPtr *) pB->i2eChannelPtr;
+
+	for (i = 0; i < ABS_MAX_BOXES; i++) {
+		if (pB->channelBtypes.bid_value[i]) {
+			if (BID_HAS_654(pB->channelBtypes.bid_value[i])) {
+				for (j = 0; j < ABS_BIGGEST_BOX; j++) {
+					if (pCh[i*16+j] == NULL)
+						break;
+					(pCh[i*16+j])->BaudBase    = 921600;	// MAX for ST654
+					(pCh[i*16+j])->BaudDivisor = 96;
+				}
+			} else {	// has cirrus cd1400
+				for (j = 0; j < ABS_BIGGEST_BOX; j++) {
+					if (pCh[i*16+j] == NULL)
+						break;
+					(pCh[i*16+j])->BaudBase    = 115200;	// MAX for CD1400
+					(pCh[i*16+j])->BaudDivisor = 12;
+				}
+			}
+		}
+	}
+}
+
+//******************************************************************************
+// Function:   i2StripFifo(pB)
+// Parameters: Pointer to a board structure
+// Returns:    ?
+//
+// Description:
+// Strips all the available data from the incoming FIFO, identifies the type of
+// packet, and either buffers the data or does what needs to be done.
+//
+// Note there is no overflow checking here: if the board sends more data than it
+// ought to, we will not detect it here, but blindly overflow...
+//******************************************************************************
+
+// A buffer for reading in blocks for unknown channels
+static unsigned char junkBuffer[IBUF_SIZE];
+
+// A buffer to read in a status packet. Because of the size of the count field
+// for these things, the maximum packet size must be less than MAX_CMD_PACK_SIZE
+static unsigned char cmdBuffer[MAX_CMD_PACK_SIZE + 4];
+
+// This table changes the bit order from MSR order given by STAT_MODEM packet to
+// status bits used in our library.
+static char xlatDss[16] = {
+0      | 0     | 0      | 0      ,
+0      | 0     | 0      | I2_CTS ,
+0      | 0     | I2_DSR | 0      ,
+0      | 0     | I2_DSR | I2_CTS ,
+0      | I2_RI | 0      | 0      ,
+0      | I2_RI | 0      | I2_CTS ,
+0      | I2_RI | I2_DSR | 0      ,
+0      | I2_RI | I2_DSR | I2_CTS ,
+I2_DCD | 0     | 0      | 0      ,
+I2_DCD | 0     | 0      | I2_CTS ,
+I2_DCD | 0     | I2_DSR | 0      ,
+I2_DCD | 0     | I2_DSR | I2_CTS ,
+I2_DCD | I2_RI | 0      | 0      ,
+I2_DCD | I2_RI | 0      | I2_CTS ,
+I2_DCD | I2_RI | I2_DSR | 0      ,
+I2_DCD | I2_RI | I2_DSR | I2_CTS };
+
+static inline void
+i2StripFifo(i2eBordStrPtr pB)
+{
+	i2ChanStrPtr pCh;
+	int channel;
+	int count;
+	unsigned short stuffIndex;
+	int amountToRead;
+	unsigned char *pc, *pcLimit;
+	unsigned char uc;
+	unsigned char dss_change;
+	unsigned long bflags,cflags;
+
+//	ip2trace (ITRC_NO_PORT, ITRC_SFIFO, ITRC_ENTER, 0 );
+
+	while (HAS_INPUT(pB)) {
+//		ip2trace (ITRC_NO_PORT, ITRC_SFIFO, 2, 0 );
+
+		// Process packet from fifo a one atomic unit
+		WRITE_LOCK_IRQSAVE(&pB->read_fifo_spinlock,bflags);
+   
+		// The first word (or two bytes) will have channel number and type of
+		// packet, possibly other information
+		pB->i2eLeadoffWord[0] = iiReadWord(pB);
+
+		switch(PTYPE_OF(pB->i2eLeadoffWord))
+		{
+		case PTYPE_DATA:
+			pB->got_input = 1;
+
+//			ip2trace (ITRC_NO_PORT, ITRC_SFIFO, 3, 0 );
+
+			channel = CHANNEL_OF(pB->i2eLeadoffWord); /* Store channel */
+			count = iiReadWord(pB);          /* Count is in the next word */
+
+// NEW: Check the count for sanity! Should the hardware fail, our death
+// is more pleasant. While an oversize channel is acceptable (just more
+// than the driver supports), an over-length count clearly means we are
+// sick!
+			if ( ((unsigned int)count) > IBUF_SIZE ) {
+				pB->i2eFatal = 2;
+				WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+				return;     /* Bail out ASAP */
+			}
+			// Channel is illegally big ?
+			if ((channel >= pB->i2eChannelCnt) ||
+				(NULL==(pCh = ((i2ChanStrPtr*)pB->i2eChannelPtr)[channel])))
+			{
+				iiReadBuf(pB, junkBuffer, count);
+				WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+				break;         /* From switch: ready for next packet */
+			}
+
+			// Channel should be valid, then
+
+			// If this is a hot-key, merely post its receipt for now. These are
+			// always supposed to be 1-byte packets, so we won't even check the
+			// count. Also we will post an acknowledgement to the board so that
+			// more data can be forthcoming. Note that we are not trying to use
+			// these sequences in this driver, merely to robustly ignore them.
+			if(ID_OF(pB->i2eLeadoffWord) == ID_HOT_KEY)
+			{
+				pCh->hotKeyIn = iiReadWord(pB) & 0xff;
+				WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+				i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_HOTACK);
+				break;   /* From the switch: ready for next packet */
+			}
+
+			// Normal data! We crudely assume there is room for the data in our
+			// buffer because the board wouldn't have exceeded his credit limit.
+			WRITE_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,cflags);
+													// We have 2 locks now
+			stuffIndex = pCh->Ibuf_stuff;
+			amountToRead = IBUF_SIZE - stuffIndex;
+			if (amountToRead > count)
+				amountToRead = count;
+
+			// stuffIndex would have been already adjusted so there would 
+			// always be room for at least one, and count is always at least
+			// one.
+
+			iiReadBuf(pB, &(pCh->Ibuf[stuffIndex]), amountToRead);
+			pCh->icount.rx += amountToRead;
+
+			// Update the stuffIndex by the amount of data moved. Note we could
+			// never ask for more data than would just fit. However, we might
+			// have read in one more byte than we wanted because the read
+			// rounds up to even bytes. If this byte is on the end of the
+			// packet, and is padding, we ignore it. If the byte is part of
+			// the actual data, we need to move it.
+
+			stuffIndex += amountToRead;
+
+			if (stuffIndex >= IBUF_SIZE) {
+				if ((amountToRead & 1) && (count > amountToRead)) {
+					pCh->Ibuf[0] = pCh->Ibuf[IBUF_SIZE];
+					amountToRead++;
+					stuffIndex = 1;
+				} else {
+					stuffIndex = 0;
+				}
+			}
+
+			// If there is anything left over, read it as well
+			if (count > amountToRead) {
+				amountToRead = count - amountToRead;
+				iiReadBuf(pB, &(pCh->Ibuf[stuffIndex]), amountToRead);
+				pCh->icount.rx += amountToRead;
+				stuffIndex += amountToRead;
+			}
+
+			// Update stuff index
+			pCh->Ibuf_stuff = stuffIndex;
+			WRITE_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,cflags);
+			WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+
+#ifdef USE_IQ
+			schedule_work(&pCh->tqueue_input);
+#else
+			do_input(pCh);
+#endif
+
+			// Note we do not need to maintain any flow-control credits at this
+			// time:  if we were to increment .asof and decrement .room, there
+			// would be no net effect. Instead, when we strip data, we will
+			// increment .asof and leave .room unchanged.
+
+			break;   // From switch: ready for next packet
+
+		case PTYPE_STATUS:
+			ip2trace (ITRC_NO_PORT, ITRC_SFIFO, 4, 0 );
+      
+			count = CMD_COUNT_OF(pB->i2eLeadoffWord);
+
+			iiReadBuf(pB, cmdBuffer, count);
+			// We can release early with buffer grab
+			WRITE_UNLOCK_IRQRESTORE(&pB->read_fifo_spinlock,bflags);
+
+			pc = cmdBuffer;
+			pcLimit = &(cmdBuffer[count]);
+
+			while (pc < pcLimit) {
+				channel = *pc++;
+
+				ip2trace (channel, ITRC_SFIFO, 7, 2, channel, *pc );
+
+				/* check for valid channel */
+				if (channel < pB->i2eChannelCnt
+					 && 
+					 (pCh = (((i2ChanStrPtr*)pB->i2eChannelPtr)[channel])) != NULL
+					)
+				{
+					dss_change = 0;
+
+					switch (uc = *pc++)
+					{
+					/* Breaks and modem signals are easy: just update status */
+					case STAT_CTS_UP:
+						if ( !(pCh->dataSetIn & I2_CTS) )
+						{
+							pCh->dataSetIn |= I2_DCTS;
+							pCh->icount.cts++;
+							dss_change = 1;
+						}
+						pCh->dataSetIn |= I2_CTS;
+						break;
+
+					case STAT_CTS_DN:
+						if ( pCh->dataSetIn & I2_CTS )
+						{
+							pCh->dataSetIn |= I2_DCTS;
+							pCh->icount.cts++;
+							dss_change = 1;
+						}
+						pCh->dataSetIn &= ~I2_CTS;
+						break;
+
+					case STAT_DCD_UP:
+						ip2trace (channel, ITRC_MODEM, 1, 1, pCh->dataSetIn );
+
+						if ( !(pCh->dataSetIn & I2_DCD) )
+						{
+							ip2trace (CHANN, ITRC_MODEM, 2, 0 );
+							pCh->dataSetIn |= I2_DDCD;
+							pCh->icount.dcd++;
+							dss_change = 1;
+						}
+						pCh->dataSetIn |= I2_DCD;
+
+						ip2trace (channel, ITRC_MODEM, 3, 1, pCh->dataSetIn );
+						break;
+
+					case STAT_DCD_DN:
+						ip2trace (channel, ITRC_MODEM, 4, 1, pCh->dataSetIn );
+						if ( pCh->dataSetIn & I2_DCD )
+						{
+							ip2trace (channel, ITRC_MODEM, 5, 0 );
+							pCh->dataSetIn |= I2_DDCD;
+							pCh->icount.dcd++;
+							dss_change = 1;
+						}
+						pCh->dataSetIn &= ~I2_DCD;
+
+						ip2trace (channel, ITRC_MODEM, 6, 1, pCh->dataSetIn );
+						break;
+
+					case STAT_DSR_UP:
+						if ( !(pCh->dataSetIn & I2_DSR) )
+						{
+							pCh->dataSetIn |= I2_DDSR;
+							pCh->icount.dsr++;
+							dss_change = 1;
+						}
+						pCh->dataSetIn |= I2_DSR;
+						break;
+
+					case STAT_DSR_DN:
+						if ( pCh->dataSetIn & I2_DSR )
+						{
+							pCh->dataSetIn |= I2_DDSR;
+							pCh->icount.dsr++;
+							dss_change = 1;
+						}
+						pCh->dataSetIn &= ~I2_DSR;
+						break;
+
+					case STAT_RI_UP:
+						if ( !(pCh->dataSetIn & I2_RI) )
+						{
+							pCh->dataSetIn |= I2_DRI;
+							pCh->icount.rng++;
+							dss_change = 1;
+						}
+						pCh->dataSetIn |= I2_RI ;
+						break;
+
+					case STAT_RI_DN:
+						// to be compat with serial.c
+						//if ( pCh->dataSetIn & I2_RI )
+						//{
+						//	pCh->dataSetIn |= I2_DRI;
+						//	pCh->icount.rng++; 
+						//	dss_change = 1;
+						//}
+						pCh->dataSetIn &= ~I2_RI ;
+						break;
+
+					case STAT_BRK_DET:
+						pCh->dataSetIn |= I2_BRK;
+						pCh->icount.brk++;
+						dss_change = 1;
+						break;
+
+					// Bookmarks? one less request we're waiting for
+					case STAT_BMARK:
+						pCh->bookMarks--;
+						if (pCh->bookMarks <= 0 ) {
+							pCh->bookMarks = 0;
+							wake_up_interruptible( &pCh->pBookmarkWait );
+
+						ip2trace (channel, ITRC_DRAIN, 20, 1, pCh->BookmarkTimer.expires );
+						}
+						break;
+
+					// Flow control packets? Update the new credits, and if
+					// someone was waiting for output, queue him up again.
+					case STAT_FLOW:
+						pCh->outfl.room =
+							((flowStatPtr)pc)->room -
+							(pCh->outfl.asof - ((flowStatPtr)pc)->asof);
+
+						ip2trace (channel, ITRC_STFLW, 1, 1, pCh->outfl.room );
+
+						if (pCh->channelNeeds & NEED_CREDIT)
+						{
+							ip2trace (channel, ITRC_STFLW, 2, 1, pCh->channelNeeds);
+
+							pCh->channelNeeds &= ~NEED_CREDIT;
+							i2QueueNeeds(pB, pCh, NEED_INLINE);
+							if ( pCh->pTTY )
+								ip2_owake(pCh->pTTY);
+						}
+
+						ip2trace (channel, ITRC_STFLW, 3, 1, pCh->channelNeeds);
+
+						pc += sizeof(flowStat);
+						break;
+
+					/* Special packets: */
+					/* Just copy the information into the channel structure */
+
+					case STAT_STATUS:
+
+						pCh->channelStatus = *((debugStatPtr)pc);
+						pc += sizeof(debugStat);
+						break;
+
+					case STAT_TXCNT:
+
+						pCh->channelTcount = *((cntStatPtr)pc);
+						pc += sizeof(cntStat);
+						break;
+
+					case STAT_RXCNT:
+
+						pCh->channelRcount = *((cntStatPtr)pc);
+						pc += sizeof(cntStat);
+						break;
+
+					case STAT_BOXIDS:
+						pB->channelBtypes = *((bidStatPtr)pc);
+						pc += sizeof(bidStat);
+						set_baud_params(pB);
+						break;
+
+					case STAT_HWFAIL:
+						i2QueueCommands (PTYPE_INLINE, pCh, 0, 1, CMD_HW_TEST);
+						pCh->channelFail = *((failStatPtr)pc);
+						pc += sizeof(failStat);
+						break;
+
+					/* No explicit match? then
+					 * Might be an error packet...
+					 */
+					default:
+						switch (uc & STAT_MOD_ERROR)
+						{
+						case STAT_ERROR:
+							if (uc & STAT_E_PARITY) {
+								pCh->dataSetIn |= I2_PAR;
+								pCh->icount.parity++;
+							}
+							if (uc & STAT_E_FRAMING){
+								pCh->dataSetIn |= I2_FRA;
+								pCh->icount.frame++;
+							}
+							if (uc & STAT_E_OVERRUN){
+								pCh->dataSetIn |= I2_OVR;
+								pCh->icount.overrun++;
+							}
+							break;
+
+						case STAT_MODEM:
+							// the answer to DSS_NOW request (not change)
+							pCh->dataSetIn = (pCh->dataSetIn
+								& ~(I2_RI | I2_CTS | I2_DCD | I2_DSR) )
+								| xlatDss[uc & 0xf];
+							wake_up_interruptible ( &pCh->dss_now_wait );
+						default:
+							break;
+						}
+					}  /* End of switch on status type */
+					if (dss_change) {
+#ifdef USE_IQ
+						schedule_work(&pCh->tqueue_status);
+#else
+						do_status(pCh);
+#endif
+					}
+				}
+				else  /* Or else, channel is invalid */
+				{
+					// Even though the channel is invalid, we must test the
+					// status to see how much additional data it has (to be
+					// skipped)
+					switch (*pc++)
+					{
+					case STAT_FLOW:
+						pc += 4;    /* Skip the data */
+						break;
+
+					default:
+						break;
+					}
+				}
+			}  // End of while (there is still some status packet left)
+			break;
+
+		default: // Neither packet? should be impossible
+			ip2trace (ITRC_NO_PORT, ITRC_SFIFO, 5, 1,
+				PTYPE_OF(pB->i2eLeadoffWord) );
+
+			break;
+		}  // End of switch on type of packets
+	}	//while(board HAS_INPUT)
+
+	ip2trace (ITRC_NO_PORT, ITRC_SFIFO, ITRC_RETURN, 0 );
+
+	// Send acknowledgement to the board even if there was no data!
+	pB->i2eOutMailWaiting |= MB_IN_STRIPPED;
+	return;
+}
+
+//******************************************************************************
+// Function:   i2Write2Fifo(pB,address,count)
+// Parameters: Pointer to a board structure, source address, byte count
+// Returns:    bytes written
+//
+// Description:
+//  Writes count bytes to board io address(implied) from source
+//  Adjusts count, leaves reserve for next time around bypass cmds
+//******************************************************************************
+static int
+i2Write2Fifo(i2eBordStrPtr pB, unsigned char *source, int count,int reserve)
+{
+	int rc = 0;
+	unsigned long flags;
+	WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+	if (!pB->i2eWaitingForEmptyFifo) {
+		if (pB->i2eFifoRemains > (count+reserve)) {
+			pB->i2eFifoRemains -= count;
+			iiWriteBuf(pB, source, count);
+			pB->i2eOutMailWaiting |= MB_OUT_STUFFED;
+			rc =  count;
+		}
+	}
+	WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+	return rc;
+}
+//******************************************************************************
+// Function:   i2StuffFifoBypass(pB)
+// Parameters: Pointer to a board structure
+// Returns:    Nothing
+//
+// Description:
+// Stuffs as many bypass commands into the fifo as possible. This is simpler
+// than stuffing data or inline commands to fifo, since we do not have
+// flow-control to deal with.
+//******************************************************************************
+static inline void
+i2StuffFifoBypass(i2eBordStrPtr pB)
+{
+	i2ChanStrPtr pCh;
+	unsigned char *pRemove;
+	unsigned short stripIndex;
+	unsigned short packetSize;
+	unsigned short paddedSize;
+	unsigned short notClogged = 1;
+	unsigned long flags;
+
+	int bailout = 1000;
+
+	// Continue processing so long as there are entries, or there is room in the
+	// fifo. Each entry represents a channel with something to do.
+	while ( --bailout && notClogged && 
+			(NULL != (pCh = i2DeQueueNeeds(pB,NEED_BYPASS))))
+	{
+		WRITE_LOCK_IRQSAVE(&pCh->Cbuf_spinlock,flags);
+		stripIndex = pCh->Cbuf_strip;
+
+		// as long as there are packets for this channel...
+
+		while (stripIndex != pCh->Cbuf_stuff) {
+			pRemove = &(pCh->Cbuf[stripIndex]);
+			packetSize = CMD_COUNT_OF(pRemove) + sizeof(i2CmdHeader);
+			paddedSize = ROUNDUP(packetSize);
+
+			if (paddedSize > 0) {
+				if ( 0 == i2Write2Fifo(pB, pRemove, paddedSize,0)) {
+					notClogged = 0;	/* fifo full */
+					i2QueueNeeds(pB, pCh, NEED_BYPASS);	// Put back on queue
+					break;   // Break from the channel
+				} 
+			}
+#ifdef DEBUG_FIFO
+WriteDBGBuf("BYPS", pRemove, paddedSize);
+#endif	/* DEBUG_FIFO */
+			pB->debugBypassCount++;
+
+			pRemove += packetSize;
+			stripIndex += packetSize;
+			if (stripIndex >= CBUF_SIZE) {
+				stripIndex = 0;
+				pRemove = pCh->Cbuf;
+			}
+		}
+		// Done with this channel. Move to next, removing this one from 
+		// the queue of channels if we cleaned it out (i.e., didn't get clogged.
+		pCh->Cbuf_strip = stripIndex;
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Cbuf_spinlock,flags);
+	}  // Either clogged or finished all the work
+
+#ifdef IP2DEBUG_TRACE
+	if ( !bailout ) {
+		ip2trace (ITRC_NO_PORT, ITRC_ERROR, 1, 0 );
+	}
+#endif
+}
+
+//******************************************************************************
+// Function:   i2StuffFifoFlow(pB)
+// Parameters: Pointer to a board structure
+// Returns:    Nothing
+//
+// Description:
+// Stuffs as many flow control packets into the fifo as possible. This is easier
+// even than doing normal bypass commands, because there is always at most one
+// packet, already assembled, for each channel.
+//******************************************************************************
+static inline void
+i2StuffFifoFlow(i2eBordStrPtr pB)
+{
+	i2ChanStrPtr pCh;
+	unsigned short paddedSize		= ROUNDUP(sizeof(flowIn));
+
+	ip2trace (ITRC_NO_PORT, ITRC_SFLOW, ITRC_ENTER, 2,
+		pB->i2eFifoRemains, paddedSize );
+
+	// Continue processing so long as there are entries, or there is room in the
+	// fifo. Each entry represents a channel with something to do.
+	while ( (NULL != (pCh = i2DeQueueNeeds(pB,NEED_FLOW)))) {
+		pB->debugFlowCount++;
+
+		// NO Chan LOCK needed ???
+		if ( 0 == i2Write2Fifo(pB,(unsigned char *)&(pCh->infl),paddedSize,0)) {
+			break;
+		}
+#ifdef DEBUG_FIFO
+		WriteDBGBuf("FLOW",(unsigned char *) &(pCh->infl), paddedSize);
+#endif /* DEBUG_FIFO */
+
+	}  // Either clogged or finished all the work
+
+	ip2trace (ITRC_NO_PORT, ITRC_SFLOW, ITRC_RETURN, 0 );
+}
+
+//******************************************************************************
+// Function:   i2StuffFifoInline(pB)
+// Parameters: Pointer to a board structure
+// Returns:    Nothing
+//
+// Description:
+// Stuffs as much data and inline commands into the fifo as possible. This is
+// the most complex fifo-stuffing operation, since there if now the channel
+// flow-control issue to deal with.
+//******************************************************************************
+static inline void
+i2StuffFifoInline(i2eBordStrPtr pB)
+{
+	i2ChanStrPtr pCh;
+	unsigned char *pRemove;
+	unsigned short stripIndex;
+	unsigned short packetSize;
+	unsigned short paddedSize;
+	unsigned short notClogged = 1;
+	unsigned short flowsize;
+	unsigned long flags;
+
+	int bailout  = 1000;
+	int bailout2;
+
+	ip2trace (ITRC_NO_PORT, ITRC_SICMD, ITRC_ENTER, 3, pB->i2eFifoRemains, 
+			pB->i2Dbuf_strip, pB->i2Dbuf_stuff );
+
+	// Continue processing so long as there are entries, or there is room in the
+	// fifo. Each entry represents a channel with something to do.
+	while ( --bailout && notClogged && 
+			(NULL != (pCh = i2DeQueueNeeds(pB,NEED_INLINE))) )
+	{
+		WRITE_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+		stripIndex = pCh->Obuf_strip;
+
+		ip2trace (CHANN, ITRC_SICMD, 3, 2, stripIndex, pCh->Obuf_stuff );
+
+		// as long as there are packets for this channel...
+		bailout2 = 1000;
+		while ( --bailout2 && stripIndex != pCh->Obuf_stuff) {
+			pRemove = &(pCh->Obuf[stripIndex]);
+
+			// Must determine whether this be a data or command packet to
+			// calculate correctly the header size and the amount of
+			// flow-control credit this type of packet will use.
+			if (PTYPE_OF(pRemove) == PTYPE_DATA) {
+				flowsize = DATA_COUNT_OF(pRemove);
+				packetSize = flowsize + sizeof(i2DataHeader);
+			} else {
+				flowsize = CMD_COUNT_OF(pRemove);
+				packetSize = flowsize + sizeof(i2CmdHeader);
+			}
+			flowsize = CREDIT_USAGE(flowsize);
+			paddedSize = ROUNDUP(packetSize);
+
+			ip2trace (CHANN, ITRC_SICMD, 4, 2, pB->i2eFifoRemains, paddedSize );
+
+			// If we don't have enough credits from the board to send the data,
+			// flag the channel that we are waiting for flow control credit, and
+			// break out. This will clean up this channel and remove us from the
+			// queue of hot things to do.
+
+				ip2trace (CHANN, ITRC_SICMD, 5, 2, pCh->outfl.room, flowsize );
+
+			if (pCh->outfl.room <= flowsize)	{
+				// Do Not have the credits to send this packet.
+				i2QueueNeeds(pB, pCh, NEED_CREDIT);
+				notClogged = 0;
+				break;   // So to do next channel
+			}
+			if ( (paddedSize > 0) 
+				&& ( 0 == i2Write2Fifo(pB, pRemove, paddedSize, 128))) {
+				// Do Not have room in fifo to send this packet.
+				notClogged = 0;
+				i2QueueNeeds(pB, pCh, NEED_INLINE);	
+				break;   // Break from the channel
+			}
+#ifdef DEBUG_FIFO
+WriteDBGBuf("DATA", pRemove, paddedSize);
+#endif /* DEBUG_FIFO */
+			pB->debugInlineCount++;
+
+			pCh->icount.tx += flowsize;
+			// Update current credits
+			pCh->outfl.room -= flowsize;
+			pCh->outfl.asof += flowsize;
+			if (PTYPE_OF(pRemove) == PTYPE_DATA) {
+				pCh->Obuf_char_count -= DATA_COUNT_OF(pRemove);
+			}
+			pRemove += packetSize;
+			stripIndex += packetSize;
+
+			ip2trace (CHANN, ITRC_SICMD, 6, 2, stripIndex, pCh->Obuf_strip);
+
+			if (stripIndex >= OBUF_SIZE) {
+				stripIndex = 0;
+				pRemove = pCh->Obuf;
+
+				ip2trace (CHANN, ITRC_SICMD, 7, 1, stripIndex );
+
+			}
+		}	/* while */
+		if ( !bailout2 ) {
+			ip2trace (CHANN, ITRC_ERROR, 3, 0 );
+		}
+		// Done with this channel. Move to next, removing this one from the
+		// queue of channels if we cleaned it out (i.e., didn't get clogged.
+		pCh->Obuf_strip = stripIndex;
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+		if ( notClogged )
+		{
+
+			ip2trace (CHANN, ITRC_SICMD, 8, 0 );
+
+			if ( pCh->pTTY ) {
+				ip2_owake(pCh->pTTY);
+			}
+		}
+	}  // Either clogged or finished all the work
+
+	if ( !bailout ) {
+		ip2trace (ITRC_NO_PORT, ITRC_ERROR, 4, 0 );
+	}
+
+	ip2trace (ITRC_NO_PORT, ITRC_SICMD, ITRC_RETURN, 1,pB->i2Dbuf_strip);
+}
+
+//******************************************************************************
+// Function:   serviceOutgoingFifo(pB)
+// Parameters: Pointer to a board structure
+// Returns:    Nothing
+//
+// Description:
+// Helper routine to put data in the outgoing fifo, if we aren't already waiting
+// for something to be there. If the fifo has only room for a very little data,
+// go head and hit the board with a mailbox hit immediately. Otherwise, it will
+// have to happen later in the interrupt processing. Since this routine may be
+// called both at interrupt and foreground time, we must turn off interrupts
+// during the entire process.
+//******************************************************************************
+static void
+serviceOutgoingFifo(i2eBordStrPtr pB)
+{
+	// If we aren't currently waiting for the board to empty our fifo, service
+	// everything that is pending, in priority order (especially, Bypass before
+	// Inline).
+	if ( ! pB->i2eWaitingForEmptyFifo )
+	{
+		i2StuffFifoFlow(pB);
+		i2StuffFifoBypass(pB);
+		i2StuffFifoInline(pB);
+
+		iiSendPendingMail(pB);
+	} 
+}
+
+//******************************************************************************
+// Function:   i2ServiceBoard(pB)
+// Parameters: Pointer to a board structure
+// Returns:    Nothing
+//
+// Description:
+// Normally this is called from interrupt level, but there is deliberately
+// nothing in here specific to being called from interrupt level. All the
+// hardware-specific, interrupt-specific things happen at the outer levels.
+//
+// For example, a timer interrupt could drive this routine for some sort of
+// polled operation. The only requirement is that the programmer deal with any
+// atomiticity/concurrency issues that result.
+//
+// This routine responds to the board's having sent mailbox information to the
+// host (which would normally cause an interrupt). This routine reads the
+// incoming mailbox. If there is no data in it, this board did not create the
+// interrupt and/or has nothing to be done to it. (Except, if we have been
+// waiting to write mailbox data to it, we may do so.
+//
+// Based on the value in the mailbox, we may take various actions.
+//
+// No checking here of pB validity: after all, it shouldn't have been called by
+// the handler unless pB were on the list.
+//******************************************************************************
+static inline int
+i2ServiceBoard ( i2eBordStrPtr pB )
+{
+	unsigned inmail;
+	unsigned long flags;
+
+
+	/* This should be atomic because of the way we are called... */
+	if (NO_MAIL_HERE == ( inmail = pB->i2eStartMail ) ) {
+		inmail = iiGetMail(pB);
+	}
+	pB->i2eStartMail = NO_MAIL_HERE;
+
+	ip2trace (ITRC_NO_PORT, ITRC_INTR, 2, 1, inmail );
+
+	if (inmail != NO_MAIL_HERE) {
+		// If the board has gone fatal, nothing to do but hit a bit that will
+		// alert foreground tasks to protest!
+		if ( inmail & MB_FATAL_ERROR ) {
+			pB->i2eFatal = 1;
+			goto exit_i2ServiceBoard;
+		}
+
+		/* Assuming no fatal condition, we proceed to do work */
+		if ( inmail & MB_IN_STUFFED ) {
+			pB->i2eFifoInInts++;
+			i2StripFifo(pB);     /* There might be incoming packets */
+		}
+
+		if (inmail & MB_OUT_STRIPPED) {
+			pB->i2eFifoOutInts++;
+			WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+			pB->i2eFifoRemains = pB->i2eFifoSize;
+			pB->i2eWaitingForEmptyFifo = 0;
+			WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+
+			ip2trace (ITRC_NO_PORT, ITRC_INTR, 30, 1, pB->i2eFifoRemains );
+
+		}
+		serviceOutgoingFifo(pB);
+	}
+
+	ip2trace (ITRC_NO_PORT, ITRC_INTR, 8, 0 );
+
+exit_i2ServiceBoard:
+
+	return 0;
+}
diff --git a/drivers/char/ip2/i2lib.h b/drivers/char/ip2/i2lib.h
new file mode 100644
index 0000000..952e113
--- /dev/null
+++ b/drivers/char/ip2/i2lib.h
@@ -0,0 +1,351 @@
+/*******************************************************************************
+*
+*   (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Header file for high level library functions
+*
+*******************************************************************************/
+#ifndef I2LIB_H
+#define I2LIB_H   1
+//------------------------------------------------------------------------------
+// I2LIB.H
+//
+// IntelliPort-II and IntelliPort-IIEX
+//
+// Defines, structure definitions, and external declarations for i2lib.c
+//------------------------------------------------------------------------------
+//--------------------------------------
+// Mandatory Includes:
+//--------------------------------------
+#include "ip2types.h"
+#include "i2ellis.h"
+#include "i2pack.h"
+#include "i2cmd.h"
+#include <linux/workqueue.h>
+
+//------------------------------------------------------------------------------
+// i2ChanStr -- Channel Structure:
+// Used to track per-channel information for the library routines using standard
+// loadware. Note also, a pointer to an array of these structures is patched
+// into the i2eBordStr (see i2ellis.h)
+//------------------------------------------------------------------------------
+//
+// If we make some limits on the maximum block sizes, we can avoid dealing with
+// buffer wrap. The wrapping of the buffer is based on where the start of the
+// packet is. Then there is always room for the packet contiguously.
+//
+// Maximum total length of an outgoing data or in-line command block. The limit
+// of 36 on data is quite arbitrary and based more on DOS memory limitations
+// than the board interface. However, for commands, the maximum packet length is
+// MAX_CMD_PACK_SIZE, because the field size for the count is only a few bits
+// (see I2PACK.H) in such packets. For data packets, the count field size is not
+// the limiting factor. As of this writing, MAX_OBUF_BLOCK < MAX_CMD_PACK_SIZE,
+// but be careful if wanting to modify either.
+//
+#define MAX_OBUF_BLOCK  36
+
+// Another note on maximum block sizes: we are buffering packets here. Data is
+// put into the buffer (if there is room) regardless of the credits from the
+// board. The board sends new credits whenever it has removed from his buffers a
+// number of characters equal to 80% of total buffer size. (Of course, the total
+// buffer size is what is reported when the very first set of flow control
+// status packets are received from the board. Therefore, to be robust, you must
+// always fill the board to at least 80% of the current credit limit, else you
+// might not give it enough to trigger a new report. These conditions are
+// obtained here so long as the maximum output block size is less than 20% the
+// size of the board's output buffers. This is true at present by "coincidence"
+// or "infernal knowledge": the board's output buffers are at least 700 bytes
+// long (20% = 140 bytes, at least). The 80% figure is "official", so the safest
+// strategy might be to trap the first flow control report and guarantee that
+// the effective maxObufBlock is the minimum of MAX_OBUF_BLOCK and 20% of first
+// reported buffer credit.
+//
+#define MAX_CBUF_BLOCK  6	// Maximum total length of a bypass command block
+
+#define IBUF_SIZE       512	// character capacity of input buffer per channel
+#define OBUF_SIZE       1024// character capacity of output buffer per channel
+#define CBUF_SIZE       10	// character capacity of output bypass buffer
+
+typedef struct _i2ChanStr
+{
+	// First, back-pointers so that given a pointer to this structure, you can
+	// determine the correct board and channel number to reference, (say, when
+	// issuing commands, etc. (Note, channel number is in infl.hd.i2sChannel.)
+
+	int      port_index;    // Index of port in channel structure array attached
+							// to board structure.
+	PTTY     pTTY;          // Pointer to tty structure for port (OS specific)
+	USHORT   validity;      // Indicates whether the given channel has been
+							// initialized, really exists (or is a missing
+							// channel, e.g. channel 9 on an 8-port box.)
+
+	i2eBordStrPtr  pMyBord; // Back-pointer to this channel's board structure 
+
+	int      wopen;			// waiting fer carrier
+
+	int      throttled;		// Set if upper layer can take no data
+
+	int      flags;         // Defined in tty.h
+
+	PWAITQ   open_wait;     // Pointer for OS sleep function.
+	PWAITQ   close_wait;    // Pointer for OS sleep function.
+	PWAITQ   delta_msr_wait;// Pointer for OS sleep function.
+	PWAITQ   dss_now_wait;	// Pointer for OS sleep function.
+
+	struct timer_list  BookmarkTimer;   // Used by i2DrainOutput
+	wait_queue_head_t pBookmarkWait;   // Used by i2DrainOutput
+
+	int      BaudBase;
+	int      BaudDivisor;
+
+	USHORT   ClosingDelay;
+	USHORT   ClosingWaitTime;
+
+	volatile
+	flowIn   infl;	// This structure is initialized as a completely
+					// formed flow-control command packet, and as such
+					// has the channel number, also the capacity and
+					// "as-of" data needed continuously.
+
+	USHORT   sinceLastFlow; // Counts the number of characters read from input
+							// buffers, since the last time flow control info
+							// was sent.
+
+	USHORT   whenSendFlow;  // Determines when new flow control is to be sent to
+							// the board. Note unlike earlier manifestations of
+							// the driver, these packets can be sent from
+							// in-place.
+
+	USHORT   channelNeeds;  // Bit map of important things which must be done
+							// for this channel. (See bits below )
+
+	volatile
+	flowStat outfl;         // Same type of structure is used to hold current
+							// flow control information used to control our
+							// output. "asof" is kept updated as data is sent,
+							// and "room" never goes to zero.
+
+	// The incoming ring buffer
+	// Unlike the outgoing buffers, this holds raw data, not packets. The two
+	// extra bytes are used to hold the byte-padding when there is room for an
+	// odd number of bytes before we must wrap.
+	//
+	UCHAR    Ibuf[IBUF_SIZE + 2];
+	volatile
+	USHORT   Ibuf_stuff;     // Stuffing index
+	volatile
+	USHORT   Ibuf_strip;     // Stripping index
+
+	// The outgoing ring-buffer: Holds Data and command packets. N.B., even
+	// though these are in the channel structure, the channel is also written
+	// here, the easier to send it to the fifo when ready. HOWEVER, individual
+	// packets here are NOT padded to even length: the routines for writing
+	// blocks to the fifo will pad to even byte counts.
+	//
+	UCHAR	Obuf[OBUF_SIZE+MAX_OBUF_BLOCK+4];
+	volatile
+	USHORT	Obuf_stuff;     // Stuffing index
+	volatile
+	USHORT	Obuf_strip;     // Stripping index
+	int	Obuf_char_count;
+
+	// The outgoing bypass-command buffer. Unlike earlier manifestations, the
+	// flow control packets are sent directly from the structures. As above, the
+	// channel number is included in the packet, but they are NOT padded to even
+	// size.
+	//
+	UCHAR    Cbuf[CBUF_SIZE+MAX_CBUF_BLOCK+2];
+	volatile
+	USHORT   Cbuf_stuff;     // Stuffing index
+	volatile
+	USHORT   Cbuf_strip;     // Stripping index
+
+	// The temporary buffer for the Linux tty driver PutChar entry.
+	//
+	UCHAR    Pbuf[MAX_OBUF_BLOCK - sizeof (i2DataHeader)];
+	volatile
+	USHORT   Pbuf_stuff;     // Stuffing index
+
+	// The state of incoming data-set signals
+	//
+	USHORT   dataSetIn;     // Bit-mapped according to below. Also indicates
+							// whether a break has been detected since last
+							// inquiry.
+
+	// The state of outcoming data-set signals (as far as we can tell!)
+	//
+	USHORT   dataSetOut;     // Bit-mapped according to below. 
+
+	// Most recent hot-key identifier detected
+	//
+	USHORT   hotKeyIn;      // Hot key as sent by the board, HOT_CLEAR indicates
+				// no hot key detected since last examined.
+
+	// Counter of outstanding requests for bookmarks
+	//
+	short   bookMarks;	// Number of outstanding bookmark requests, (+ive
+						// whenever a bookmark request if queued up, -ive
+						// whenever a bookmark is received).
+
+	// Misc options
+	//
+	USHORT   channelOptions;   // See below
+
+	// To store various incoming special packets
+	//
+	debugStat   channelStatus;
+	cntStat     channelRcount;
+	cntStat     channelTcount;
+	failStat    channelFail;
+
+	// To store the last values for line characteristics we sent to the board.
+	//
+	int	speed;
+
+	int flush_flags;
+
+	void (*trace)(unsigned short,unsigned char,unsigned char,unsigned long,...);
+
+	/*
+	 * Kernel counters for the 4 input interrupts 
+	 */
+	struct async_icount icount;
+
+	/*
+	 *	Task queues for processing input packets from the board.
+	 */
+	struct work_struct	tqueue_input;
+	struct work_struct	tqueue_status;
+	struct work_struct	tqueue_hangup;
+
+	rwlock_t Ibuf_spinlock;
+	rwlock_t Obuf_spinlock;
+	rwlock_t Cbuf_spinlock;
+	rwlock_t Pbuf_spinlock;
+
+} i2ChanStr, *i2ChanStrPtr;
+
+//---------------------------------------------------
+// Manifests and bit-maps for elements in i2ChanStr
+//---------------------------------------------------
+//
+// flush flags
+//
+#define STARTFL_FLAG 1
+#define STOPFL_FLAG  2
+
+// validity
+//
+#define CHANNEL_MAGIC_BITS 0xff00
+#define CHANNEL_MAGIC      0x5300   // (validity & CHANNEL_MAGIC_BITS) ==
+									// CHANNEL_MAGIC --> structure good
+
+#define CHANNEL_SUPPORT    0x0001   // Indicates channel is supported, exists,
+									// and passed P.O.S.T.
+
+// channelNeeds
+//
+#define NEED_FLOW    1  // Indicates flow control has been queued
+#define NEED_INLINE  2  // Indicates inline commands or data queued
+#define NEED_BYPASS  4  // Indicates bypass commands queued
+#define NEED_CREDIT  8  // Indicates would be sending except has not sufficient
+						// credit. The data is still in the channel structure,
+						// but the channel is not enqueued in the board
+						// structure again until there is a credit received from
+						// the board.
+
+// dataSetIn (Also the bits for i2GetStatus return value)
+//
+#define I2_DCD 1
+#define I2_CTS 2
+#define I2_DSR 4
+#define I2_RI  8
+
+// dataSetOut (Also the bits for i2GetStatus return value)
+//
+#define I2_DTR 1
+#define I2_RTS 2
+
+// i2GetStatus() can optionally clear these bits
+//
+#define I2_BRK    0x10  // A break was detected
+#define I2_PAR    0x20  // A parity error was received 
+#define I2_FRA    0x40  // A framing error was received
+#define I2_OVR    0x80  // An overrun error was received 
+
+// i2GetStatus() automatically clears these bits */
+//
+#define I2_DDCD   0x100 // DCD changed from its  former value
+#define I2_DCTS   0x200 // CTS changed from its former value 
+#define I2_DDSR   0x400 // DSR changed from its former value 
+#define I2_DRI    0x800 // RI changed from its former value 
+
+// hotKeyIn
+//
+#define HOT_CLEAR 0x1322   // Indicates that no hot-key has been detected
+
+// channelOptions
+//
+#define CO_NBLOCK_WRITE 1  	// Writes don't block waiting for buffer. (Default
+							// is, they do wait.)
+
+// fcmodes
+//
+#define I2_OUTFLOW_CTS  0x0001
+#define I2_INFLOW_RTS   0x0002
+#define I2_INFLOW_DSR   0x0004
+#define I2_INFLOW_DTR   0x0008
+#define I2_OUTFLOW_DSR  0x0010
+#define I2_OUTFLOW_DTR  0x0020
+#define I2_OUTFLOW_XON  0x0040
+#define I2_OUTFLOW_XANY 0x0080
+#define I2_INFLOW_XON   0x0100
+
+#define I2_CRTSCTS      (I2_OUTFLOW_CTS|I2_INFLOW_RTS)
+#define I2_IXANY_MODE   (I2_OUTFLOW_XON|I2_OUTFLOW_XANY)
+
+//-------------------------------------------
+// Macros used from user level like functions
+//-------------------------------------------
+
+// Macros to set and clear channel options
+//
+#define i2SetOption(pCh, option) pCh->channelOptions |= option
+#define i2ClrOption(pCh, option) pCh->channelOptions &= ~option
+
+// Macro to set fatal-error trap
+//
+#define i2SetFatalTrap(pB, routine) pB->i2eFatalTrap = routine
+
+//--------------------------------------------
+// Declarations and prototypes for i2lib.c
+//--------------------------------------------
+//
+static int  i2InitChannels(i2eBordStrPtr, int, i2ChanStrPtr);
+static int  i2QueueCommands(int, i2ChanStrPtr, int, int, cmdSyntaxPtr,...);
+static int  i2GetStatus(i2ChanStrPtr, int);
+static int  i2Input(i2ChanStrPtr);
+static int  i2InputFlush(i2ChanStrPtr);
+static int  i2Output(i2ChanStrPtr, const char *, int, int);
+static int  i2OutputFree(i2ChanStrPtr);
+static int  i2ServiceBoard(i2eBordStrPtr);
+static void i2DrainOutput(i2ChanStrPtr, int);
+
+#ifdef IP2DEBUG_TRACE
+void ip2trace(unsigned short,unsigned char,unsigned char,unsigned long,...);
+#else
+#define ip2trace(a,b,c,d...) do {} while (0)
+#endif
+
+// Argument to i2QueueCommands
+//
+#define C_IN_LINE 1
+#define C_BYPASS  0
+
+#endif   // I2LIB_H
diff --git a/drivers/char/ip2/i2os.h b/drivers/char/ip2/i2os.h
new file mode 100644
index 0000000..eff9b54
--- /dev/null
+++ b/drivers/char/ip2/i2os.h
@@ -0,0 +1,127 @@
+/*******************************************************************************
+*
+*   (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Defines, definitions and includes which are heavily dependent
+*                on O/S, host, compiler, etc. This file is tailored for:
+*                 Linux v2.0.0 and later
+*                 Gnu gcc c2.7.2
+*                 80x86 architecture
+*
+*******************************************************************************/
+
+#ifndef I2OS_H    /* To prevent multiple includes */
+#define I2OS_H 1
+
+//-------------------------------------------------
+// Required Includes
+//-------------------------------------------------
+
+#include "ip2types.h"
+#include <asm/io.h>  /* For inb, etc */
+
+//------------------------------------
+// Defines for I/O instructions:
+//------------------------------------
+
+#define INB(port)                inb(port)
+#define OUTB(port,value)         outb((value),(port))
+#define INW(port)                inw(port)
+#define OUTW(port,value)         outw((value),(port))
+#define OUTSW(port,addr,count)   outsw((port),(addr),(((count)+1)/2))
+#define OUTSB(port,addr,count)   outsb((port),(addr),(((count)+1))&-2)
+#define INSW(port,addr,count)    insw((port),(addr),(((count)+1)/2))
+#define INSB(port,addr,count)    insb((port),(addr),(((count)+1))&-2)
+
+//--------------------------------------------
+// Interrupt control
+//--------------------------------------------
+
+#define LOCK_INIT(a)	rwlock_init(a)
+
+#define SAVE_AND_DISABLE_INTS(a,b) { \
+	/* printk("get_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+	spin_lock_irqsave(a,b); \
+}
+
+#define RESTORE_INTS(a,b) { \
+	/* printk("rel_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+	spin_unlock_irqrestore(a,b); \
+}
+
+#define READ_LOCK_IRQSAVE(a,b) { \
+	/* printk("get_read_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+	read_lock_irqsave(a,b); \
+}
+
+#define READ_UNLOCK_IRQRESTORE(a,b) { \
+	/* printk("rel_read_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+	read_unlock_irqrestore(a,b); \
+}
+
+#define WRITE_LOCK_IRQSAVE(a,b) { \
+	/* printk("get_write_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+	write_lock_irqsave(a,b); \
+}
+
+#define WRITE_UNLOCK_IRQRESTORE(a,b) { \
+	/* printk("rel_write_lock: 0x%x,%4d,%s\n",(int)a,__LINE__,__FILE__);*/ \
+	write_unlock_irqrestore(a,b); \
+}
+
+
+//------------------------------------------------------------------------------
+// Hardware-delay loop
+//
+// Probably used in only one place (see i2ellis.c) but this helps keep things
+// together. Note we have unwound the IN instructions. On machines with a
+// reasonable cache, the eight instructions (1 byte each) should fit in cache
+// nicely, and on un-cached machines, the code-fetch would tend not to dominate.
+// Note that cx is shifted so that "count" still reflects the total number of
+// iterations assuming no unwinding.
+//------------------------------------------------------------------------------
+
+//#define  DELAY1MS(port,count,label)
+
+//------------------------------------------------------------------------------
+// Macros to switch to a new stack, saving stack pointers, and to restore the
+// old stack (Used, for example, in i2lib.c) "heap" is the address of some
+// buffer which will become the new stack (working down from highest address).
+// The two words at the two lowest addresses in this stack are for storing the
+// SS and SP.
+//------------------------------------------------------------------------------
+
+//#define  TO_NEW_STACK(heap,size)
+//#define  TO_OLD_STACK(heap)
+
+//------------------------------------------------------------------------------
+// Macros to save the original IRQ vectors and masks, and to patch in new ones.
+//------------------------------------------------------------------------------
+
+//#define  SAVE_IRQ_MASKS(dest)
+//#define  WRITE_IRQ_MASKS(src)
+//#define  SAVE_IRQ_VECTOR(value,dest)
+//#define  WRITE_IRQ_VECTOR(value,src)
+
+//------------------------------------------------------------------------------
+// Macro to copy data from one far pointer to another.
+//------------------------------------------------------------------------------
+
+#define  I2_MOVE_DATA(fpSource,fpDest,count) memmove(fpDest,fpSource,count);
+
+//------------------------------------------------------------------------------
+// Macros to issue eoi's to host interrupt control (IBM AT 8259-style).
+//------------------------------------------------------------------------------
+
+//#define MASTER_EOI
+//#define SLAVE_EOI
+
+#endif   /* I2OS_H */
+
+
diff --git a/drivers/char/ip2/i2pack.h b/drivers/char/ip2/i2pack.h
new file mode 100644
index 0000000..e9b87a7
--- /dev/null
+++ b/drivers/char/ip2/i2pack.h
@@ -0,0 +1,364 @@
+/*******************************************************************************
+*
+*   (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Definitions of the packets used to transfer data and commands
+*                Host <--> Board. Information provided here is only applicable
+*                when the standard loadware is active.
+*
+*******************************************************************************/
+#ifndef I2PACK_H
+#define I2PACK_H  1
+
+//-----------------------------------------------
+// Revision History:
+//
+// 10 October 1991   MAG First draft
+// 24 February 1992  MAG Additions for 1.4.x loadware
+// 11 March 1992     MAG New status packets
+//
+//-----------------------------------------------
+
+//------------------------------------------------------------------------------
+// Packet Formats:
+//
+// Information passes between the host and board through the FIFO in packets.
+// These have headers which indicate the type of packet. Because the fifo data
+// path may be 16-bits wide, the protocol is constrained such that each packet
+// is always padded to an even byte count. (The lower-level interface routines
+// -- i2ellis.c -- are designed to do this).
+//
+// The sender (be it host or board) must place some number of complete packets
+// in the fifo, then place a message in the mailbox that packets are available.
+// Placing such a message interrupts the "receiver" (be it board or host), who
+// reads the mailbox message and determines that there are incoming packets
+// ready. Since there are no partial packets, and the length of a packet is
+// given in the header, the remainder of the packet can be read without checking
+// for FIFO empty condition. The process is repeated, packet by packet, until
+// the incoming FIFO is empty. Then the receiver uses the outbound mailbox to
+// signal the board that it has read the data. Only then can the sender place
+// additional data in the fifo.
+//------------------------------------------------------------------------------
+//
+//------------------------------------------------
+// Definition of Packet Header Area
+//------------------------------------------------
+//
+// Caution: these only define header areas. In actual use the data runs off
+// beyond the end of these structures.
+//
+// Since these structures are based on sequences of bytes which go to the board,
+// there cannot be ANY padding between the elements.
+#pragma pack(1)
+
+//----------------------------
+// DATA PACKETS
+//----------------------------
+
+typedef struct _i2DataHeader
+{
+	unsigned char i2sChannel;  /* The channel number: 0-255 */
+
+	// -- Bitfields are allocated LSB first --
+
+	// For incoming data, indicates whether this is an ordinary packet or a
+	// special one (e.g., hot key hit).
+	unsigned i2sId : 2 __attribute__ ((__packed__));
+
+	// For tagging data packets. There are flush commands which flush only data
+	// packets bearing a particular tag. (used in implementing IntelliView and
+	// IntelliPrint). THE TAG VALUE 0xf is RESERVED and must not be used (it has
+	// meaning internally to the loadware).
+	unsigned i2sTag : 4;
+
+	// These two bits determine the type of packet sent/received.
+	unsigned i2sType : 2;
+
+	// The count of data to follow: does not include the possible additional
+	// padding byte. MAXIMUM COUNT: 4094. The top four bits must be 0.
+	unsigned short i2sCount;
+
+} i2DataHeader, *i2DataHeaderPtr;
+
+// Structure is immediately followed by the data, proper.
+
+//----------------------------
+// NON-DATA PACKETS
+//----------------------------
+
+typedef struct _i2CmdHeader
+{
+	unsigned char i2sChannel;	// The channel number: 0-255 (Except where noted
+								// - see below
+
+	// Number of bytes of commands, status or whatever to follow
+	unsigned i2sCount : 6;
+
+	// These two bits determine the type of packet sent/received.
+	unsigned i2sType : 2;
+
+} i2CmdHeader, *i2CmdHeaderPtr;
+
+// Structure is immediately followed by the applicable data.
+
+//---------------------------------------
+// Flow Control Packets (Outbound)
+//---------------------------------------
+
+// One type of outbound command packet is so important that the entire structure
+// is explicitly defined here. That is the flow-control packet. This is never
+// sent by user-level code (as would be the commands to raise/lower DTR, for
+// example). These are only sent by the library routines in response to reading
+// incoming data into the buffers.
+//
+// The parameters inside the command block are maintained in place, then the
+// block is sent at the appropriate time.
+
+typedef struct _flowIn
+{
+	i2CmdHeader    hd;      // Channel #, count, type (see above)
+	unsigned char  fcmd;    // The flow control command (37)
+	unsigned short asof;    // As of byte number "asof" (LSB first!) I have room
+							// for "room" bytes
+	unsigned short room;
+} flowIn, *flowInPtr;
+
+//----------------------------------------
+// (Incoming) Status Packets
+//----------------------------------------
+
+// Incoming packets which are non-data packets are status packets. In this case,
+// the channel number in the header is unimportant. What follows are one or more
+// sub-packets, the first word of which consists of the channel (first or low
+// byte) and the status indicator (second or high byte), followed by possibly
+// more data.
+
+#define STAT_CTS_UP     0  /* CTS raised  (no other bytes) */
+#define STAT_CTS_DN     1  /* CTS dropped (no other bytes) */
+#define STAT_DCD_UP     2  /* DCD raised  (no other bytes) */
+#define STAT_DCD_DN     3  /* DCD dropped (no other bytes) */
+#define STAT_DSR_UP     4  /* DSR raised  (no other bytes) */
+#define STAT_DSR_DN     5  /* DSR dropped (no other bytes) */
+#define STAT_RI_UP      6  /* RI  raised  (no other bytes) */
+#define STAT_RI_DN      7  /* RI  dropped (no other bytes) */
+#define STAT_BRK_DET    8  /* BRK detect  (no other bytes) */
+#define STAT_FLOW       9  /* Flow control(-- more: see below */
+#define STAT_BMARK      10 /* Bookmark    (no other bytes)
+							* Bookmark is sent as a response to
+							* a command 60: request for bookmark
+							*/
+#define STAT_STATUS     11 /* Special packet: see below */
+#define STAT_TXCNT      12 /* Special packet: see below */
+#define STAT_RXCNT      13 /* Special packet: see below */
+#define STAT_BOXIDS     14 /* Special packet: see below */
+#define STAT_HWFAIL     15 /* Special packet: see below */
+
+#define STAT_MOD_ERROR  0xc0
+#define STAT_MODEM      0xc0/* If status & STAT_MOD_ERROR:
+							 * == STAT_MODEM, then this is a modem
+							 * status packet, given in response to a
+							 * CMD_DSS_NOW command.
+							 * The low nibble has each data signal:
+							 */
+#define STAT_MOD_DCD    0x8
+#define STAT_MOD_RI     0x4
+#define STAT_MOD_DSR    0x2
+#define STAT_MOD_CTS    0x1
+
+#define STAT_ERROR      0x80/* If status & STAT_MOD_ERROR
+							 * == STAT_ERROR, then
+							 * sort of error on the channel.
+							 * The remaining seven bits indicate
+							 * what sort of error it is.
+							 */
+/* The low three bits indicate parity, framing, or overrun errors */
+
+#define STAT_E_PARITY   4     /* Parity error */
+#define STAT_E_FRAMING  2     /* Framing error */
+#define STAT_E_OVERRUN  1     /* (uxart) overrun error */
+
+//---------------------------------------
+// STAT_FLOW packets
+//---------------------------------------
+
+typedef struct _flowStat
+{
+	unsigned short asof;
+	unsigned short room;
+}flowStat, *flowStatPtr;
+
+// flowStat packets are received from the board to regulate the flow of outgoing
+// data. A local copy of this structure is also kept to track the amount of
+// credits used and credits remaining. "room" is the amount of space in the
+// board's buffers, "as of" having received a certain byte number. When sending
+// data to the fifo, you must calculate how much buffer space your packet will
+// use.  Add this to the current "asof" and subtract it from the current "room".
+//
+// The calculation for the board's buffer is given by CREDIT_USAGE, where size
+// is the un-rounded count of either data characters or command characters.
+// (Which is to say, the count rounded up, plus two).
+
+#define CREDIT_USAGE(size) (((size) + 3) & ~1)
+
+//---------------------------------------
+// STAT_STATUS packets
+//---------------------------------------
+
+typedef  struct   _debugStat
+{
+	unsigned char d_ccsr;
+	unsigned char d_txinh;
+	unsigned char d_stat1;
+	unsigned char d_stat2;
+} debugStat, *debugStatPtr;
+
+// debugStat packets are sent to the host in response to a CMD_GET_STATUS
+// command.  Each byte is bit-mapped as described below:
+
+#define D_CCSR_XON      2     /* Has received XON, ready to transmit */
+#define D_CCSR_XOFF     4     /* Has received XOFF, not transmitting */
+#define D_CCSR_TXENAB   8     /* Transmitter is enabled */
+#define D_CCSR_RXENAB   0x80  /* Receiver is enabled */
+
+#define D_TXINH_BREAK   1     /* We are sending a break */
+#define D_TXINH_EMPTY   2     /* No data to send */
+#define D_TXINH_SUSP    4     /* Output suspended via command 57 */
+#define D_TXINH_CMD     8     /* We are processing an in-line command */
+#define D_TXINH_LCD     0x10  /* LCD diagnostics are running */
+#define D_TXINH_PAUSE   0x20  /* We are processing a PAUSE command */
+#define D_TXINH_DCD     0x40  /* DCD is low, preventing transmission */
+#define D_TXINH_DSR     0x80  /* DSR is low, preventing transmission */
+
+#define D_STAT1_TXEN    1     /* Transmit INTERRUPTS enabled */
+#define D_STAT1_RXEN    2     /* Receiver INTERRUPTS enabled */
+#define D_STAT1_MDEN    4     /* Modem (data set sigs) interrupts enabled */
+#define D_STAT1_RLM     8     /* Remote loopback mode selected */
+#define D_STAT1_LLM     0x10  /* Local internal loopback mode selected */
+#define D_STAT1_CTS     0x20  /* CTS is low, preventing transmission */
+#define D_STAT1_DTR     0x40  /* DTR is low, to stop remote transmission */
+#define D_STAT1_RTS     0x80  /* RTS is low, to stop remote transmission */
+
+#define D_STAT2_TXMT    1     /* Transmit buffers are all empty */
+#define D_STAT2_RXMT    2     /* Receive buffers are all empty */
+#define D_STAT2_RXINH   4     /* Loadware has tried to inhibit remote
+							   * transmission:  dropped DTR, sent XOFF,
+							   * whatever...
+							   */
+#define D_STAT2_RXFLO   8     /* Loadware can send no more data to host
+							   * until it receives a flow-control packet
+							   */
+//-----------------------------------------
+// STAT_TXCNT and STAT_RXCNT packets
+//----------------------------------------
+
+typedef  struct   _cntStat
+{
+	unsigned short cs_time;    // (Assumes host is little-endian!)
+	unsigned short cs_count;
+} cntStat, *cntStatPtr;
+
+// These packets are sent in response to a CMD_GET_RXCNT or a CMD_GET_TXCNT
+// bypass command. cs_time is a running 1 Millisecond counter which acts as a
+// time stamp. cs_count is a running counter of data sent or received from the
+// uxarts. (Not including data added by the chip itself, as with CRLF
+// processing).
+//------------------------------------------
+// STAT_HWFAIL packets
+//------------------------------------------
+
+typedef struct _failStat
+{
+	unsigned char fs_written;
+	unsigned char fs_read;
+	unsigned short fs_address;
+} failStat, *failStatPtr;
+
+// This packet is sent whenever the on-board diagnostic process detects an
+// error. At startup, this process is dormant. The host can wake it up by
+// issuing the bypass command CMD_HW_TEST. The process runs at low priority and
+// performs continuous hardware verification; writing data to certain on-board
+// registers, reading it back, and comparing. If it detects an error, this
+// packet is sent to the host, and the process goes dormant again until the host
+// sends another CMD_HW_TEST. It then continues with the next register to be
+// tested.
+
+//------------------------------------------------------------------------------
+// Macros to deal with the headers more easily! Note that these are defined so
+// they may be used as "left" as well as "right" expressions.
+//------------------------------------------------------------------------------
+
+// Given a pointer to the packet, reference the channel number
+//
+#define CHANNEL_OF(pP)  ((i2DataHeaderPtr)(pP))->i2sChannel
+
+// Given a pointer to the packet, reference the Packet type
+//
+#define PTYPE_OF(pP) ((i2DataHeaderPtr)(pP))->i2sType
+
+// The possible types of packets
+//
+#define PTYPE_DATA   0  /* Host <--> Board */
+#define PTYPE_BYPASS 1  /* Host ---> Board */
+#define PTYPE_INLINE 2  /* Host ---> Board */
+#define PTYPE_STATUS 2  /* Host <--- Board */
+
+// Given a pointer to a Data packet, reference the Tag
+//
+#define TAG_OF(pP) ((i2DataHeaderPtr)(pP))->i2sTag
+
+// Given a pointer to a Data packet, reference the data i.d.
+//
+#define ID_OF(pP)  ((i2DataHeaderPtr)(pP))->i2sId
+
+// The possible types of ID's
+//
+#define ID_ORDINARY_DATA   0
+#define ID_HOT_KEY         1
+
+// Given a pointer to a Data packet, reference the count
+//
+#define DATA_COUNT_OF(pP) ((i2DataHeaderPtr)(pP))->i2sCount
+
+// Given a pointer to a Data packet, reference the beginning of data
+//
+#define DATA_OF(pP) &((unsigned char *)(pP))[4] // 4 = size of header
+
+// Given a pointer to a Non-Data packet, reference the count
+//
+#define CMD_COUNT_OF(pP) ((i2CmdHeaderPtr)(pP))->i2sCount
+
+#define MAX_CMD_PACK_SIZE  62 // Maximum size of such a count
+
+// Given a pointer to a Non-Data packet, reference the beginning of data
+//
+#define CMD_OF(pP) &((unsigned char *)(pP))[2]  // 2 = size of header
+
+//--------------------------------
+// MailBox Bits:
+//--------------------------------
+
+//--------------------------
+// Outgoing (host to board)
+//--------------------------
+//
+#define MB_OUT_STUFFED     0x80  // Host has placed output in fifo 
+#define MB_IN_STRIPPED     0x40  // Host has read in all input from fifo 
+
+//--------------------------
+// Incoming (board to host)
+//--------------------------
+//
+#define MB_IN_STUFFED      0x80  // Board has placed input in fifo 
+#define MB_OUT_STRIPPED    0x40  // Board has read all output from fifo 
+#define MB_FATAL_ERROR     0x20  // Board has encountered a fatal error
+
+#pragma pack(4)                  // Reset padding to command-line default
+
+#endif      // I2PACK_H
+
diff --git a/drivers/char/ip2/ip2.h b/drivers/char/ip2/ip2.h
new file mode 100644
index 0000000..936ccc5
--- /dev/null
+++ b/drivers/char/ip2/ip2.h
@@ -0,0 +1,107 @@
+/*******************************************************************************
+*
+*   (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Driver constants for configuration and tuning
+*
+*   NOTES:
+*
+*******************************************************************************/
+#ifndef IP2_H
+#define IP2_H
+
+#include "ip2types.h"
+#include "i2cmd.h"
+
+/*************/
+/* Constants */
+/*************/
+
+/* Device major numbers - since version 2.0.26. */
+#define IP2_TTY_MAJOR      71
+#define IP2_CALLOUT_MAJOR  72
+#define IP2_IPL_MAJOR      73
+
+/* Board configuration array.
+ * This array defines the hardware irq and address for up to IP2_MAX_BOARDS
+ * (4 supported per ip2_types.h) ISA board addresses and irqs MUST be specified,
+ * PCI and EISA boards are probed for and automagicly configed
+ * iff the addresses are set to 1 and 2 respectivily.
+ *    0x0100 - 0x03f0 == ISA
+ *	         1        == PCI
+ *	         2        == EISA
+ *	         0        == (skip this board)
+ * This array defines the hardware addresses for them. Special 
+ * addresses are EISA and PCI which go sniffing for boards. 
+
+ * In a multiboard system the position in the array determines which port
+ * devices are assigned to each board: 
+ *		board 0 is assigned ttyF0.. to ttyF63, 
+ *		board 1 is assigned ttyF64  to ttyF127,
+ *		board 2 is assigned ttyF128 to ttyF191,
+ *		board 3 is assigned ttyF192 to ttyF255. 
+ *
+ * In PCI and EISA bus systems each range is mapped to card in 
+ * monotonically increasing slot number order, ISA position is as specified
+ * here.
+
+ * If the irqs are ALL set to 0,0,0,0 all boards operate in 
+ * polled mode. For interrupt operation ISA boards require that the IRQ be 
+ * specified, while PCI and EISA boards any nonzero entry 
+ * will enable interrupts using the BIOS configured irq for the board. 
+ * An invalid irq entry will default to polled mode for that card and print
+ * console warning.
+ 
+ * When the driver is loaded as a module these setting can be overridden on the 
+ * modprobe command line or on an option line in /etc/modprobe.conf.
+ * If the driver is built-in the configuration must be 
+ * set here for ISA cards and address set to 1 and 2 for PCI and EISA.
+ *
+ * Here is an example that shows most if not all possibe combinations:
+
+ *static ip2config_t ip2config =
+ *{
+ *	{11,1,0,0},		// irqs
+ *	{				// Addresses
+ *		0x0308,		// Board 0, ttyF0   - ttyF63// ISA card at io=0x308, irq=11
+ *		0x0001,		// Board 1, ttyF64  - ttyF127//PCI card configured by BIOS
+ *		0x0000,		// Board 2, ttyF128 - ttyF191// Slot skipped
+ *		0x0002		// Board 3, ttyF192 - ttyF255//EISA card configured by BIOS
+ *												 // but polled not irq driven
+ *	}
+ *};
+ */
+
+ /* this structure is zeroed out because the suggested method is to configure
+  * the driver as a module, set up the parameters with an options line in
+  * /etc/modprobe.conf and load with modprobe or kmod, the kernel
+  * module loader
+  */
+
+ /* This structure is NOW always initialized when the driver is initialized.
+  * Compiled in defaults MUST be added to the io and irq arrays in
+  * ip2.c.  Those values are configurable from insmod parameters in the
+  * case of modules or from command line parameters (ip2=io,irq) when
+  * compiled in.
+  */
+
+static ip2config_t ip2config =
+{
+	{0,0,0,0},		// irqs
+	{				// Addresses
+	/* Do NOT set compile time defaults HERE!  Use the arrays in
+		ip2.c!  These WILL be overwritten!  =mhw= */
+		0x0000,		// Board 0, ttyF0   - ttyF63
+		0x0000,		// Board 1, ttyF64  - ttyF127
+		0x0000,		// Board 2, ttyF128 - ttyF191
+		0x0000		// Board 3, ttyF192 - ttyF255
+	}
+};
+
+#endif
diff --git a/drivers/char/ip2/ip2ioctl.h b/drivers/char/ip2/ip2ioctl.h
new file mode 100644
index 0000000..aa0a9da
--- /dev/null
+++ b/drivers/char/ip2/ip2ioctl.h
@@ -0,0 +1,35 @@
+/*******************************************************************************
+*
+*   (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Driver constants for configuration and tuning
+*
+*   NOTES:
+*
+*******************************************************************************/
+
+#ifndef IP2IOCTL_H
+#define IP2IOCTL_H
+
+//*************
+//* Constants *
+//*************
+
+// High baud rates (if not defined elsewhere.
+#ifndef B153600   
+#	define B153600   0010005
+#endif
+#ifndef B307200   
+#	define B307200   0010006
+#endif
+#ifndef B921600   
+#	define B921600   0010007
+#endif
+
+#endif
diff --git a/drivers/char/ip2/ip2trace.h b/drivers/char/ip2/ip2trace.h
new file mode 100644
index 0000000..da20435
--- /dev/null
+++ b/drivers/char/ip2/ip2trace.h
@@ -0,0 +1,42 @@
+
+//
+union ip2breadcrumb 
+{
+	struct { 
+		unsigned char port, cat, codes, label;
+	} __attribute__ ((packed)) hdr;
+	unsigned long value;
+};
+
+#define ITRC_NO_PORT 	0xFF
+#define CHANN	(pCh->port_index)
+
+#define	ITRC_ERROR	'!'
+#define	ITRC_INIT 	'A'
+#define	ITRC_OPEN	'B'
+#define	ITRC_CLOSE	'C'
+#define	ITRC_DRAIN	'D'
+#define	ITRC_IOCTL	'E'
+#define	ITRC_FLUSH	'F'
+#define	ITRC_STATUS	'G'
+#define	ITRC_HANGUP	'H'
+#define	ITRC_INTR 	'I'
+#define	ITRC_SFLOW	'J'
+#define	ITRC_SBCMD	'K'
+#define	ITRC_SICMD	'L'
+#define	ITRC_MODEM	'M'
+#define	ITRC_INPUT	'N'
+#define	ITRC_OUTPUT	'O'
+#define	ITRC_PUTC	'P'
+#define	ITRC_QUEUE	'Q'
+#define	ITRC_STFLW	'R'
+#define	ITRC_SFIFO	'S'
+#define	ITRC_VERIFY	'V'
+#define	ITRC_WRITE	'W'
+
+#define	ITRC_ENTER	0x00
+#define	ITRC_RETURN	0xFF
+
+#define	ITRC_QUEUE_ROOM	2
+#define	ITRC_QUEUE_CMD	6
+
diff --git a/drivers/char/ip2/ip2types.h b/drivers/char/ip2/ip2types.h
new file mode 100644
index 0000000..9d67b26
--- /dev/null
+++ b/drivers/char/ip2/ip2types.h
@@ -0,0 +1,57 @@
+/*******************************************************************************
+*
+*   (c) 1998 by Computone Corporation
+*
+********************************************************************************
+*
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort II family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Driver constants and type definitions.
+*
+*   NOTES:
+*
+*******************************************************************************/
+#ifndef IP2TYPES_H
+#define IP2TYPES_H
+
+//*************
+//* Constants *
+//*************
+
+// Define some limits for this driver. Ports per board is a hardware limitation
+// that will not change. Current hardware limits this to 64 ports per board.
+// Boards per driver is a self-imposed limit.
+//
+#define IP2_MAX_BOARDS        4
+#define IP2_PORTS_PER_BOARD   ABS_MOST_PORTS
+#define IP2_MAX_PORTS         (IP2_MAX_BOARDS*IP2_PORTS_PER_BOARD)
+
+#define ISA    0
+#define PCI    1
+#define EISA   2
+
+//********************
+//* Type Definitions *
+//********************
+
+typedef struct tty_struct *   PTTY;
+typedef wait_queue_head_t   PWAITQ;
+
+typedef unsigned char         UCHAR;
+typedef unsigned int          UINT;
+typedef unsigned short        USHORT;
+typedef unsigned long         ULONG;
+
+typedef struct 
+{
+	short irq[IP2_MAX_BOARDS]; 
+	unsigned short addr[IP2_MAX_BOARDS];
+	int type[IP2_MAX_BOARDS];
+#ifdef CONFIG_PCI
+	struct pci_dev *pci_dev[IP2_MAX_BOARDS];
+#endif
+} ip2config_t;
+
+#endif
diff --git a/drivers/char/ip27-rtc.c b/drivers/char/ip27-rtc.c
new file mode 100644
index 0000000..3acdac3
--- /dev/null
+++ b/drivers/char/ip27-rtc.c
@@ -0,0 +1,327 @@
+/*
+ *	Driver for the SGS-Thomson M48T35 Timekeeper RAM chip
+ *
+ *	Real Time Clock interface for Linux
+ *
+ *	TODO: Implement periodic interrupts.
+ *
+ *	Copyright (C) 2000 Silicon Graphics, Inc.
+ *	Written by Ulf Carlsson (ulfc@engr.sgi.com)
+ *
+ *	Based on code written by Paul Gortmaker.
+ *
+ *	This driver allows use of the real time clock (built into
+ *	nearly all computers) from user space. It exports the /dev/rtc
+ *	interface supporting various ioctl() and also the /proc/rtc
+ *	pseudo-file for status information.
+ *
+ *	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.
+ *
+ */
+
+#define RTC_VERSION		"1.09b"
+
+#include <linux/bcd.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/rtc.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/smp_lock.h>
+
+#include <asm/m48t35.h>
+#include <asm/sn/ioc3.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/sn/klconfig.h>
+#include <asm/sn/sn0/ip27.h>
+#include <asm/sn/sn0/hub.h>
+#include <asm/sn/sn_private.h>
+
+static int rtc_ioctl(struct inode *inode, struct file *file,
+		     unsigned int cmd, unsigned long arg);
+
+static int rtc_read_proc(char *page, char **start, off_t off,
+                         int count, int *eof, void *data);
+
+static void get_rtc_time(struct rtc_time *rtc_tm);
+
+/*
+ *	Bits in rtc_status. (6 bits of room for future expansion)
+ */
+
+#define RTC_IS_OPEN		0x01	/* means /dev/rtc is in use	*/
+#define RTC_TIMER_ON		0x02	/* missed irq timer active	*/
+
+static unsigned char rtc_status;	/* bitmapped status byte.	*/
+static unsigned long rtc_freq;	/* Current periodic IRQ rate	*/
+static struct m48t35_rtc *rtc;
+
+/*
+ *	If this driver ever becomes modularised, it will be really nice
+ *	to make the epoch retain its value across module reload...
+ */
+
+static unsigned long epoch = 1970;	/* year corresponding to 0x00	*/
+
+static const unsigned char days_in_mo[] =
+{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+		     unsigned long arg)
+{
+
+	struct rtc_time wtime;
+
+	switch (cmd) {
+	case RTC_RD_TIME:	/* Read the time/date from RTC	*/
+	{
+		get_rtc_time(&wtime);
+		break;
+	}
+	case RTC_SET_TIME:	/* Set the RTC */
+	{
+		struct rtc_time rtc_tm;
+		unsigned char mon, day, hrs, min, sec, leap_yr;
+		unsigned int yrs;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		if (copy_from_user(&rtc_tm, (struct rtc_time*)arg,
+				   sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		yrs = rtc_tm.tm_year + 1900;
+		mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */
+		day = rtc_tm.tm_mday;
+		hrs = rtc_tm.tm_hour;
+		min = rtc_tm.tm_min;
+		sec = rtc_tm.tm_sec;
+
+		if (yrs < 1970)
+			return -EINVAL;
+
+		leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+		if ((mon > 12) || (day == 0))
+			return -EINVAL;
+
+		if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+			return -EINVAL;
+
+		if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+			return -EINVAL;
+
+		if ((yrs -= epoch) > 255)    /* They are unsigned */
+			return -EINVAL;
+
+		if (yrs > 169)
+			return -EINVAL;
+
+		if (yrs >= 100)
+			yrs -= 100;
+
+		sec = BIN2BCD(sec);
+		min = BIN2BCD(min);
+		hrs = BIN2BCD(hrs);
+		day = BIN2BCD(day);
+		mon = BIN2BCD(mon);
+		yrs = BIN2BCD(yrs);
+
+		spin_lock_irq(&rtc_lock);
+		rtc->control |= M48T35_RTC_SET;
+		rtc->year = yrs;
+		rtc->month = mon;
+		rtc->date = day;
+		rtc->hour = hrs;
+		rtc->min = min;
+		rtc->sec = sec;
+		rtc->control &= ~M48T35_RTC_SET;
+		spin_unlock_irq(&rtc_lock);
+
+		return 0;
+	}
+	default:
+		return -EINVAL;
+	}
+	return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0;
+}
+
+/*
+ *	We enforce only one user at a time here with the open/close.
+ *	Also clear the previous interrupt data on an open, and clean
+ *	up things on a close.
+ */
+
+static int rtc_open(struct inode *inode, struct file *file)
+{
+	spin_lock_irq(&rtc_lock);
+
+	if (rtc_status & RTC_IS_OPEN) {
+		spin_unlock_irq(&rtc_lock);
+		return -EBUSY;
+	}
+
+	rtc_status |= RTC_IS_OPEN;
+	spin_unlock_irq(&rtc_lock);
+
+	return 0;
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+	/*
+	 * Turn off all interrupts once the device is no longer
+	 * in use, and clear the data.
+	 */
+
+	spin_lock_irq(&rtc_lock);
+	rtc_status &= ~RTC_IS_OPEN;
+	spin_unlock_irq(&rtc_lock);
+
+	return 0;
+}
+
+/*
+ *	The various file operations we support.
+ */
+
+static struct file_operations rtc_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= rtc_ioctl,
+	.open		= rtc_open,
+	.release	= rtc_release,
+};
+
+static struct miscdevice rtc_dev=
+{
+	RTC_MINOR,
+	"rtc",
+	&rtc_fops
+};
+
+static int __init rtc_init(void)
+{
+	rtc = (struct m48t35_rtc *)
+	(KL_CONFIG_CH_CONS_INFO(master_nasid)->memory_base + IOC3_BYTEBUS_DEV0);
+
+	printk(KERN_INFO "Real Time Clock Driver v%s\n", RTC_VERSION);
+	if (misc_register(&rtc_dev)) {
+		printk(KERN_ERR "rtc: cannot register misc device.\n");
+		return -ENODEV;
+	}
+	if (!create_proc_read_entry("driver/rtc", 0, NULL, rtc_read_proc, NULL)) {
+		printk(KERN_ERR "rtc: cannot create /proc/rtc.\n");
+		misc_deregister(&rtc_dev);
+		return -ENOENT;
+	}
+
+	rtc_freq = 1024;
+
+	return 0;
+}
+
+static void __exit rtc_exit (void)
+{
+	/* interrupts and timer disabled at this point by rtc_release */
+
+	remove_proc_entry ("rtc", NULL);
+	misc_deregister(&rtc_dev);
+}
+
+module_init(rtc_init);
+module_exit(rtc_exit);
+
+/*
+ *	Info exported via "/proc/rtc".
+ */
+
+static int rtc_get_status(char *buf)
+{
+	char *p;
+	struct rtc_time tm;
+
+	/*
+	 * Just emulate the standard /proc/rtc
+	 */
+
+	p = buf;
+
+	get_rtc_time(&tm);
+
+	/*
+	 * There is no way to tell if the luser has the RTC set for local
+	 * time or for Universal Standard Time (GMT). Probably local though.
+	 */
+	p += sprintf(p,
+		     "rtc_time\t: %02d:%02d:%02d\n"
+		     "rtc_date\t: %04d-%02d-%02d\n"
+	 	     "rtc_epoch\t: %04lu\n"
+		     "24hr\t\t: yes\n",
+		     tm.tm_hour, tm.tm_min, tm.tm_sec,
+		     tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, epoch);
+
+	return  p - buf;
+}
+
+static int rtc_read_proc(char *page, char **start, off_t off,
+                                 int count, int *eof, void *data)
+{
+        int len = rtc_get_status(page);
+        if (len <= off+count) *eof = 1;
+        *start = page + off;
+        len -= off;
+        if (len>count) len = count;
+        if (len<0) len = 0;
+        return len;
+}
+
+static void get_rtc_time(struct rtc_time *rtc_tm)
+{
+	/*
+	 * Do we need to wait for the last update to finish?
+	 */
+
+	/*
+	 * Only the values that we read from the RTC are set. We leave
+	 * tm_wday, tm_yday and tm_isdst untouched. Even though the
+	 * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated
+	 * by the RTC when initially set to a non-zero value.
+	 */
+	spin_lock_irq(&rtc_lock);
+	rtc->control |= M48T35_RTC_READ;
+	rtc_tm->tm_sec = rtc->sec;
+	rtc_tm->tm_min = rtc->min;
+	rtc_tm->tm_hour = rtc->hour;
+	rtc_tm->tm_mday = rtc->date;
+	rtc_tm->tm_mon = rtc->month;
+	rtc_tm->tm_year = rtc->year;
+	rtc->control &= ~M48T35_RTC_READ;
+	spin_unlock_irq(&rtc_lock);
+
+	rtc_tm->tm_sec = BCD2BIN(rtc_tm->tm_sec);
+	rtc_tm->tm_min = BCD2BIN(rtc_tm->tm_min);
+	rtc_tm->tm_hour = BCD2BIN(rtc_tm->tm_hour);
+	rtc_tm->tm_mday = BCD2BIN(rtc_tm->tm_mday);
+	rtc_tm->tm_mon = BCD2BIN(rtc_tm->tm_mon);
+	rtc_tm->tm_year = BCD2BIN(rtc_tm->tm_year);
+
+	/*
+	 * Account for differences between how the RTC uses the values
+	 * and how they are defined in a struct rtc_time;
+	 */
+	if ((rtc_tm->tm_year += (epoch - 1900)) <= 69)
+		rtc_tm->tm_year += 100;
+
+	rtc_tm->tm_mon--;
+}
diff --git a/drivers/char/ip2main.c b/drivers/char/ip2main.c
new file mode 100644
index 0000000..fca9a97
--- /dev/null
+++ b/drivers/char/ip2main.c
@@ -0,0 +1,3265 @@
+/*
+*
+*   (c) 1999 by Computone Corporation
+*
+********************************************************************************
+*
+*   PACKAGE:     Linux tty Device Driver for IntelliPort family of multiport
+*                serial I/O controllers.
+*
+*   DESCRIPTION: Mainline code for the device driver
+*
+*******************************************************************************/
+// ToDo:
+//
+// Fix the immediate DSS_NOW problem.
+// Work over the channel stats return logic in ip2_ipl_ioctl so they
+//	make sense for all 256 possible channels and so the user space
+//	utilities will compile and work properly.
+//
+// Done:
+//
+// 1.2.14	/\/\|=mhw=|\/\/
+// Added bounds checking to ip2_ipl_ioctl to avoid potential terroristic acts.
+// Changed the definition of ip2trace to be more consistent with kernel style
+//	Thanks to Andreas Dilger <adilger@turbolabs.com> for these updates
+//
+// 1.2.13	/\/\|=mhw=|\/\/
+// DEVFS: Renamed ttf/{n} to tts/F{n} and cuf/{n} to cua/F{n} to conform
+//	to agreed devfs serial device naming convention.
+//
+// 1.2.12	/\/\|=mhw=|\/\/
+// Cleaned up some remove queue cut and paste errors
+//
+// 1.2.11	/\/\|=mhw=|\/\/
+// Clean up potential NULL pointer dereferences
+// Clean up devfs registration
+// Add kernel command line parsing for io and irq
+//	Compile defaults for io and irq are now set in ip2.c not ip2/ip2.h!
+// Reworked poll_only hack for explicit parameter setting
+//	You must now EXPLICITLY set poll_only = 1 or set all irqs to 0
+// Merged ip2_loadmain and old_ip2_init
+// Converted all instances of interruptible_sleep_on into queue calls
+//	Most of these had no race conditions but better to clean up now
+//
+// 1.2.10	/\/\|=mhw=|\/\/
+// Fixed the bottom half interrupt handler and enabled USE_IQI
+//	to split the interrupt handler into a formal top-half / bottom-half
+// Fixed timing window on high speed processors that queued messages to
+// 	the outbound mail fifo faster than the board could handle.
+//
+// 1.2.9
+// Four box EX was barfing on >128k kmalloc, made structure smaller by
+// reducing output buffer size
+//
+// 1.2.8
+// Device file system support (MHW)
+//
+// 1.2.7 
+// Fixed
+// Reload of ip2 without unloading ip2main hangs system on cat of /proc/modules
+//
+// 1.2.6
+//Fixes DCD problems
+//	DCD was not reported when CLOCAL was set on call to TIOCMGET
+//
+//Enhancements:
+//	TIOCMGET requests and waits for status return
+//	No DSS interrupts enabled except for DCD when needed
+//
+// For internal use only
+//
+//#define IP2DEBUG_INIT
+//#define IP2DEBUG_OPEN
+//#define IP2DEBUG_WRITE
+//#define IP2DEBUG_READ
+//#define IP2DEBUG_IOCTL
+//#define IP2DEBUG_IPL
+
+//#define IP2DEBUG_TRACE
+//#define DEBUG_FIFO
+
+/************/
+/* Includes */
+/************/
+#include <linux/config.h>
+
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/major.h>
+#include <linux/wait.h>
+#include <linux/device.h>
+
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/termios.h>
+#include <linux/tty_driver.h>
+#include <linux/serial.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+
+#include <linux/cdk.h>
+#include <linux/comstats.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <asm/serial.h>
+
+#include <asm/uaccess.h>
+
+#include "./ip2/ip2types.h"
+#include "./ip2/ip2trace.h"
+#include "./ip2/ip2ioctl.h"
+#include "./ip2/ip2.h"
+#include "./ip2/i2ellis.h"
+#include "./ip2/i2lib.h"
+
+/*****************
+ * /proc/ip2mem  *
+ *****************/
+
+#include <linux/proc_fs.h>
+
+static int ip2_read_procmem(char *, char **, off_t, int);
+static int ip2_read_proc(char *, char **, off_t, int, int *, void * );
+
+/********************/
+/* Type Definitions */
+/********************/
+
+/*************/
+/* Constants */
+/*************/
+
+/* String constants to identify ourselves */
+static char *pcName    = "Computone IntelliPort Plus multiport driver";
+static char *pcVersion = "1.2.14";
+
+/* String constants for port names */
+static char *pcDriver_name   = "ip2";
+static char *pcIpl    		 = "ip2ipl";
+
+/* Serial subtype definitions */
+#define SERIAL_TYPE_NORMAL    1
+
+// cheezy kludge or genius - you decide?
+int ip2_loadmain(int *, int *, unsigned char *, int);
+static unsigned char *Fip_firmware;
+static int Fip_firmware_size;
+
+/***********************/
+/* Function Prototypes */
+/***********************/
+
+/* Global module entry functions */
+
+/* Private (static) functions */
+static int  ip2_open(PTTY, struct file *);
+static void ip2_close(PTTY, struct file *);
+static int  ip2_write(PTTY, int, const unsigned char *, int);
+static void ip2_putchar(PTTY, unsigned char);
+static void ip2_flush_chars(PTTY);
+static int  ip2_write_room(PTTY);
+static int  ip2_chars_in_buf(PTTY);
+static void ip2_flush_buffer(PTTY);
+static int  ip2_ioctl(PTTY, struct file *, UINT, ULONG);
+static void ip2_set_termios(PTTY, struct termios *);
+static void ip2_set_line_discipline(PTTY);
+static void ip2_throttle(PTTY);
+static void ip2_unthrottle(PTTY);
+static void ip2_stop(PTTY);
+static void ip2_start(PTTY);
+static void ip2_hangup(PTTY);
+static int  ip2_tiocmget(struct tty_struct *tty, struct file *file);
+static int  ip2_tiocmset(struct tty_struct *tty, struct file *file,
+			 unsigned int set, unsigned int clear);
+
+static void set_irq(int, int);
+static void ip2_interrupt_bh(i2eBordStrPtr pB);
+static irqreturn_t ip2_interrupt(int irq, void *dev_id, struct pt_regs * regs);
+static void ip2_poll(unsigned long arg);
+static inline void service_all_boards(void);
+static void do_input(void *p);
+static void do_status(void *p);
+
+static void ip2_wait_until_sent(PTTY,int);
+
+static void set_params (i2ChanStrPtr, struct termios *);
+static int get_serial_info(i2ChanStrPtr, struct serial_struct __user *);
+static int set_serial_info(i2ChanStrPtr, struct serial_struct __user *);
+
+static ssize_t ip2_ipl_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t ip2_ipl_write(struct file *, const char __user *, size_t, loff_t *);
+static int ip2_ipl_ioctl(struct inode *, struct file *, UINT, ULONG);
+static int ip2_ipl_open(struct inode *, struct file *);
+
+static int DumpTraceBuffer(char __user *, int);
+static int DumpFifoBuffer( char __user *, int);
+
+static void ip2_init_board(int);
+static unsigned short find_eisa_board(int);
+
+/***************/
+/* Static Data */
+/***************/
+
+static struct tty_driver *ip2_tty_driver;
+
+/* Here, then is a table of board pointers which the interrupt routine should
+ * scan through to determine who it must service.
+ */
+static unsigned short i2nBoards; // Number of boards here
+
+static i2eBordStrPtr i2BoardPtrTable[IP2_MAX_BOARDS];
+
+static i2ChanStrPtr  DevTable[IP2_MAX_PORTS];
+//DevTableMem just used to save addresses for kfree
+static void  *DevTableMem[IP2_MAX_BOARDS];
+
+/* This is the driver descriptor for the ip2ipl device, which is used to
+ * download the loadware to the boards.
+ */
+static struct file_operations ip2_ipl = {
+	.owner		= THIS_MODULE,
+	.read		= ip2_ipl_read,
+	.write		= ip2_ipl_write,
+	.ioctl		= ip2_ipl_ioctl,
+	.open		= ip2_ipl_open,
+}; 
+
+static unsigned long irq_counter = 0;
+static unsigned long bh_counter = 0;
+
+// Use immediate queue to service interrupts
+#define USE_IQI
+//#define USE_IQ	// PCI&2.2 needs work
+
+/* The timer_list entry for our poll routine. If interrupt operation is not
+ * selected, the board is serviced periodically to see if anything needs doing.
+ */
+#define  POLL_TIMEOUT   (jiffies + 1)
+static struct timer_list PollTimer = TIMER_INITIALIZER(ip2_poll, 0, 0);
+static char  TimerOn;
+
+#ifdef IP2DEBUG_TRACE
+/* Trace (debug) buffer data */
+#define TRACEMAX  1000
+static unsigned long tracebuf[TRACEMAX];
+static int tracestuff;
+static int tracestrip;
+static int tracewrap;
+#endif
+
+/**********/
+/* Macros */
+/**********/
+
+#if defined(MODULE) && defined(IP2DEBUG_OPEN)
+#define DBG_CNT(s) printk(KERN_DEBUG "(%s): [%x] refc=%d, ttyc=%d, modc=%x -> %s\n", \
+		    tty->name,(pCh->flags),ip2_tty_driver->refcount, \
+		    tty->count,/*GET_USE_COUNT(module)*/0,s)
+#else
+#define DBG_CNT(s)
+#endif
+
+/********/
+/* Code */
+/********/
+
+#include "./ip2/i2ellis.c"    /* Extremely low-level interface services */
+#include "./ip2/i2cmd.c"      /* Standard loadware command definitions */
+#include "./ip2/i2lib.c"      /* High level interface services */
+
+/* Configuration area for modprobe */
+
+MODULE_AUTHOR("Doug McNash");
+MODULE_DESCRIPTION("Computone IntelliPort Plus Driver");
+
+static int poll_only = 0;
+
+static int Eisa_irq;
+static int Eisa_slot;
+
+static int iindx;
+static char rirqs[IP2_MAX_BOARDS];
+static int Valid_Irqs[] = { 3, 4, 5, 7, 10, 11, 12, 15, 0};
+
+/* for sysfs class support */
+static struct class_simple *ip2_class;
+
+// Some functions to keep track of what irq's we have
+
+static int __init
+is_valid_irq(int irq)
+{
+	int *i = Valid_Irqs;
+	
+	while ((*i != 0) && (*i != irq)) {
+		i++;
+	}
+	return (*i);
+}
+
+static void __init
+mark_requested_irq( char irq )
+{
+	rirqs[iindx++] = irq;
+}
+
+#ifdef MODULE
+static int __init
+clear_requested_irq( char irq )
+{
+	int i;
+	for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		if (rirqs[i] == irq) {
+			rirqs[i] = 0;
+			return 1;
+		}
+	}
+	return 0;
+}
+#endif
+
+static int __init
+have_requested_irq( char irq )
+{
+	// array init to zeros so 0 irq will not be requested as a side effect
+	int i;
+	for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		if (rirqs[i] == irq)
+			return 1;
+	}
+	return 0;
+}
+
+/******************************************************************************/
+/* Function:   init_module()                                                  */
+/* Parameters: None                                                           */
+/* Returns:    Success (0)                                                    */
+/*                                                                            */
+/* Description:                                                               */
+/* This is a required entry point for an installable module. It simply calls  */
+/* the driver initialisation function and returns what it returns.            */
+/******************************************************************************/
+#ifdef MODULE
+int
+init_module(void)
+{
+#ifdef IP2DEBUG_INIT
+	printk (KERN_DEBUG "Loading module ...\n" );
+#endif
+    return 0;
+}
+#endif /* MODULE */
+
+/******************************************************************************/
+/* Function:   cleanup_module()                                               */
+/* Parameters: None                                                           */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/* This is a required entry point for an installable module. It has to return */
+/* the device and the driver to a passive state. It should not be necessary   */
+/* to reset the board fully, especially as the loadware is downloaded         */
+/* externally rather than in the driver. We just want to disable the board    */
+/* and clear the loadware to a reset state. To allow this there has to be a   */
+/* way to detect whether the board has the loadware running at init time to   */
+/* handle subsequent installations of the driver. All memory allocated by the */
+/* driver should be returned since it may be unloaded from memory.            */
+/******************************************************************************/
+#ifdef MODULE
+void
+cleanup_module(void)
+{
+	int err;
+	int i;
+
+#ifdef IP2DEBUG_INIT
+	printk (KERN_DEBUG "Unloading %s: version %s\n", pcName, pcVersion );
+#endif
+	/* Stop poll timer if we had one. */
+	if ( TimerOn ) {
+		del_timer ( &PollTimer );
+		TimerOn = 0;
+	}
+
+	/* Reset the boards we have. */
+	for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		if ( i2BoardPtrTable[i] ) {
+			iiReset( i2BoardPtrTable[i] );
+		}
+	}
+
+	/* The following is done at most once, if any boards were installed. */
+	for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		if ( i2BoardPtrTable[i] ) {
+			iiResetDelay( i2BoardPtrTable[i] );
+			/* free io addresses and Tibet */
+			release_region( ip2config.addr[i], 8 );
+			class_simple_device_remove(MKDEV(IP2_IPL_MAJOR, 4 * i)); 
+			devfs_remove("ip2/ipl%d", i);
+			class_simple_device_remove(MKDEV(IP2_IPL_MAJOR, 4 * i + 1));
+			devfs_remove("ip2/stat%d", i);
+		}
+		/* Disable and remove interrupt handler. */
+		if ( (ip2config.irq[i] > 0) && have_requested_irq(ip2config.irq[i]) ) {	
+			free_irq ( ip2config.irq[i], (void *)&pcName);
+			clear_requested_irq( ip2config.irq[i]);
+		}
+	}
+	class_simple_destroy(ip2_class);
+	devfs_remove("ip2");
+	if ( ( err = tty_unregister_driver ( ip2_tty_driver ) ) ) {
+		printk(KERN_ERR "IP2: failed to unregister tty driver (%d)\n", err);
+	}
+	put_tty_driver(ip2_tty_driver);
+	if ( ( err = unregister_chrdev ( IP2_IPL_MAJOR, pcIpl ) ) ) {
+		printk(KERN_ERR "IP2: failed to unregister IPL driver (%d)\n", err);
+	}
+	remove_proc_entry("ip2mem", &proc_root);
+
+	// free memory
+	for (i = 0; i < IP2_MAX_BOARDS; i++) {
+		void *pB;
+#ifdef CONFIG_PCI
+		if (ip2config.type[i] == PCI && ip2config.pci_dev[i]) {
+			pci_disable_device(ip2config.pci_dev[i]);
+			ip2config.pci_dev[i] = NULL;
+		}
+#endif
+		if ((pB = i2BoardPtrTable[i]) != 0 ) {
+			kfree ( pB );
+			i2BoardPtrTable[i] = NULL;
+		}
+		if ((DevTableMem[i]) != NULL ) {
+			kfree ( DevTableMem[i]  );
+			DevTableMem[i] = NULL;
+		}
+	}
+
+	/* Cleanup the iiEllis subsystem. */
+	iiEllisCleanup();
+#ifdef IP2DEBUG_INIT
+	printk (KERN_DEBUG "IP2 Unloaded\n" );
+#endif
+}
+#endif /* MODULE */
+
+static struct tty_operations ip2_ops = {
+	.open            = ip2_open,
+	.close           = ip2_close,
+	.write           = ip2_write,
+	.put_char        = ip2_putchar,
+	.flush_chars     = ip2_flush_chars,
+	.write_room      = ip2_write_room,
+	.chars_in_buffer = ip2_chars_in_buf,
+	.flush_buffer    = ip2_flush_buffer,
+	.ioctl           = ip2_ioctl,
+	.throttle        = ip2_throttle,
+	.unthrottle      = ip2_unthrottle,
+	.set_termios     = ip2_set_termios,
+	.set_ldisc       = ip2_set_line_discipline,
+	.stop            = ip2_stop,
+	.start           = ip2_start,
+	.hangup          = ip2_hangup,
+	.read_proc       = ip2_read_proc,
+	.tiocmget	 = ip2_tiocmget,
+	.tiocmset	 = ip2_tiocmset,
+};
+
+/******************************************************************************/
+/* Function:   ip2_loadmain()                                                 */
+/* Parameters: irq, io from command line of insmod et. al.                    */
+/*		pointer to fip firmware and firmware size for boards	      */
+/* Returns:    Success (0)                                                    */
+/*                                                                            */
+/* Description:                                                               */
+/* This was the required entry point for all drivers (now in ip2.c)           */
+/* It performs all                                                            */
+/* initialisation of the devices and driver structures, and registers itself  */
+/* with the relevant kernel modules.                                          */
+/******************************************************************************/
+/* SA_INTERRUPT- if set blocks all interrupts else only this line */
+/* SA_SHIRQ    - for shared irq PCI or maybe EISA only */
+/* SA_RANDOM   - can be source for cert. random number generators */
+#define IP2_SA_FLAGS	0
+
+int
+ip2_loadmain(int *iop, int *irqp, unsigned char *firmware, int firmsize) 
+{
+	int i, j, box;
+	int err = 0;
+	int status = 0;
+	static int loaded;
+	i2eBordStrPtr pB = NULL;
+	int rc = -1;
+
+	ip2trace (ITRC_NO_PORT, ITRC_INIT, ITRC_ENTER, 0 );
+
+	/* process command line arguments to modprobe or
+		insmod i.e. iop & irqp */
+	/* irqp and iop should ALWAYS be specified now...  But we check
+		them individually just to be sure, anyways... */
+	for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		if (iop) {
+			ip2config.addr[i] = iop[i];
+			if (irqp) {
+				if( irqp[i] >= 0 ) {
+					ip2config.irq[i] = irqp[i];
+				} else {
+					ip2config.irq[i] = 0;
+				}
+	// This is a little bit of a hack.  If poll_only=1 on command
+	// line back in ip2.c OR all IRQs on all specified boards are
+	// explicitly set to 0, then drop to poll only mode and override
+	// PCI or EISA interrupts.  This superceeds the old hack of
+	// triggering if all interrupts were zero (like da default).
+	// Still a hack but less prone to random acts of terrorism.
+	//
+	// What we really should do, now that the IRQ default is set
+	// to -1, is to use 0 as a hard coded, do not probe.
+	//
+	//	/\/\|=mhw=|\/\/
+				poll_only |= irqp[i];
+			}
+		}
+	}
+	poll_only = !poll_only;
+
+	Fip_firmware = firmware;
+	Fip_firmware_size = firmsize;
+
+	/* Announce our presence */
+	printk( KERN_INFO "%s version %s\n", pcName, pcVersion );
+
+	// ip2 can be unloaded and reloaded for no good reason
+	// we can't let that happen here or bad things happen
+	// second load hoses board but not system - fixme later
+	if (loaded) {
+		printk( KERN_INFO "Still loaded\n" );
+		return 0;
+	}
+	loaded++;
+
+	ip2_tty_driver = alloc_tty_driver(IP2_MAX_PORTS);
+	if (!ip2_tty_driver)
+		return -ENOMEM;
+
+	/* Initialise the iiEllis subsystem. */
+	iiEllisInit();
+
+	/* Initialize arrays. */
+	memset( i2BoardPtrTable, 0, sizeof i2BoardPtrTable );
+	memset( DevTable, 0, sizeof DevTable );
+
+	/* Initialise all the boards we can find (up to the maximum). */
+	for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		switch ( ip2config.addr[i] ) { 
+		case 0:	/* skip this slot even if card is present */
+			break;
+		default: /* ISA */
+		   /* ISA address must be specified */
+			if ( (ip2config.addr[i] < 0x100) || (ip2config.addr[i] > 0x3f8) ) {
+				printk ( KERN_ERR "IP2: Bad ISA board %d address %x\n",
+							 i, ip2config.addr[i] );
+				ip2config.addr[i] = 0;
+			} else {
+				ip2config.type[i] = ISA;
+
+				/* Check for valid irq argument, set for polling if invalid */
+				if (ip2config.irq[i] && !is_valid_irq(ip2config.irq[i])) {
+					printk(KERN_ERR "IP2: Bad IRQ(%d) specified\n",ip2config.irq[i]);
+					ip2config.irq[i] = 0;// 0 is polling and is valid in that sense
+				}
+			}
+			break;
+		case PCI:
+#ifdef CONFIG_PCI
+			{
+				struct pci_dev *pci_dev_i = NULL;
+				pci_dev_i = pci_find_device(PCI_VENDOR_ID_COMPUTONE,
+							  PCI_DEVICE_ID_COMPUTONE_IP2EX, pci_dev_i);
+				if (pci_dev_i != NULL) {
+					unsigned int addr;
+
+					if (pci_enable_device(pci_dev_i)) {
+						printk( KERN_ERR "IP2: can't enable PCI device at %s\n",
+							pci_name(pci_dev_i));
+						break;
+					}
+					ip2config.type[i] = PCI;
+					ip2config.pci_dev[i] = pci_dev_i;
+					status =
+					pci_read_config_dword(pci_dev_i, PCI_BASE_ADDRESS_1, &addr);
+					if ( addr & 1 ) {
+						ip2config.addr[i]=(USHORT)(addr&0xfffe);
+					} else {
+						printk( KERN_ERR "IP2: PCI I/O address error\n");
+					}
+
+//		If the PCI BIOS assigned it, lets try and use it.  If we
+//		can't acquire it or it screws up, deal with it then.
+
+//					if (!is_valid_irq(pci_irq)) {
+//						printk( KERN_ERR "IP2: Bad PCI BIOS IRQ(%d)\n",pci_irq);
+//						pci_irq = 0;
+//					}
+					ip2config.irq[i] = pci_dev_i->irq;
+				} else {	// ann error
+					ip2config.addr[i] = 0;
+					if (status == PCIBIOS_DEVICE_NOT_FOUND) {
+						printk( KERN_ERR "IP2: PCI board %d not found\n", i );
+					} else {
+						printk( KERN_ERR "IP2: PCI error 0x%x \n", status );
+					}
+				} 
+			}
+#else
+			printk( KERN_ERR "IP2: PCI card specified but PCI support not\n");
+			printk( KERN_ERR "IP2: configured in this kernel.\n");
+			printk( KERN_ERR "IP2: Recompile kernel with CONFIG_PCI defined!\n");
+#endif /* CONFIG_PCI */
+			break;
+		case EISA:
+			if ( (ip2config.addr[i] = find_eisa_board( Eisa_slot + 1 )) != 0) {
+				/* Eisa_irq set as side effect, boo */
+				ip2config.type[i] = EISA;
+			} 
+			ip2config.irq[i] = Eisa_irq;
+			break;
+		}	/* switch */
+	}	/* for */
+	for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		if ( ip2config.addr[i] ) {
+			pB = kmalloc( sizeof(i2eBordStr), GFP_KERNEL);
+			if ( pB != NULL ) {
+				i2BoardPtrTable[i] = pB;
+				memset( pB, 0, sizeof(i2eBordStr) );
+				iiSetAddress( pB, ip2config.addr[i], ii2DelayTimer );
+				iiReset( pB );
+			} else {
+				printk(KERN_ERR "IP2: board memory allocation error\n");
+			}
+		}
+	}
+	for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		if ( ( pB = i2BoardPtrTable[i] ) != NULL ) {
+			iiResetDelay( pB );
+			break;
+		}
+	}
+	for ( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		if ( i2BoardPtrTable[i] != NULL ) {
+			ip2_init_board( i );
+		}
+	}
+
+	ip2trace (ITRC_NO_PORT, ITRC_INIT, 2, 0 );
+
+	ip2_tty_driver->owner		    = THIS_MODULE;
+	ip2_tty_driver->name                 = "ttyF";
+	ip2_tty_driver->devfs_name	    = "tts/F";
+	ip2_tty_driver->driver_name          = pcDriver_name;
+	ip2_tty_driver->major                = IP2_TTY_MAJOR;
+	ip2_tty_driver->minor_start          = 0;
+	ip2_tty_driver->type                 = TTY_DRIVER_TYPE_SERIAL;
+	ip2_tty_driver->subtype              = SERIAL_TYPE_NORMAL;
+	ip2_tty_driver->init_termios         = tty_std_termios;
+	ip2_tty_driver->init_termios.c_cflag = B9600|CS8|CREAD|HUPCL|CLOCAL;
+	ip2_tty_driver->flags                = TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
+	tty_set_operations(ip2_tty_driver, &ip2_ops);
+
+	ip2trace (ITRC_NO_PORT, ITRC_INIT, 3, 0 );
+
+	/* Register the tty devices. */
+	if ( ( err = tty_register_driver ( ip2_tty_driver ) ) ) {
+		printk(KERN_ERR "IP2: failed to register tty driver (%d)\n", err);
+		put_tty_driver(ip2_tty_driver);
+		return -EINVAL;
+	} else
+	/* Register the IPL driver. */
+	if ( ( err = register_chrdev ( IP2_IPL_MAJOR, pcIpl, &ip2_ipl ) ) ) {
+		printk(KERN_ERR "IP2: failed to register IPL device (%d)\n", err );
+	} else {
+		/* create the sysfs class */
+		ip2_class = class_simple_create(THIS_MODULE, "ip2");
+		if (IS_ERR(ip2_class)) {
+			err = PTR_ERR(ip2_class);
+			goto out_chrdev;	
+		}
+	}
+	/* Register the read_procmem thing */
+	if (!create_proc_info_entry("ip2mem",0,&proc_root,ip2_read_procmem)) {
+		printk(KERN_ERR "IP2: failed to register read_procmem\n");
+	} else {
+
+	ip2trace (ITRC_NO_PORT, ITRC_INIT, 4, 0 );
+		/* Register the interrupt handler or poll handler, depending upon the
+		 * specified interrupt.
+		 */
+
+		for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+			if ( 0 == ip2config.addr[i] ) {
+				continue;
+			}
+
+			if ( NULL != ( pB = i2BoardPtrTable[i] ) ) {
+				class_simple_device_add(ip2_class, MKDEV(IP2_IPL_MAJOR, 
+						4 * i), NULL, "ipl%d", i);
+				err = devfs_mk_cdev(MKDEV(IP2_IPL_MAJOR, 4 * i),
+						S_IRUSR | S_IWUSR | S_IRGRP | S_IFCHR,
+						"ip2/ipl%d", i);
+				if (err) {
+					class_simple_device_remove(MKDEV(IP2_IPL_MAJOR, 
+						4 * i));
+					goto out_class;
+				}
+
+				class_simple_device_add(ip2_class, MKDEV(IP2_IPL_MAJOR, 
+						4 * i + 1), NULL, "stat%d", i);
+				err = devfs_mk_cdev(MKDEV(IP2_IPL_MAJOR, 4 * i + 1),
+						S_IRUSR | S_IWUSR | S_IRGRP | S_IFCHR,
+						"ip2/stat%d", i);
+				if (err) {
+					class_simple_device_remove(MKDEV(IP2_IPL_MAJOR, 
+						4 * i + 1));
+					goto out_class;
+				}
+
+			    for ( box = 0; box < ABS_MAX_BOXES; ++box )
+			    {
+			        for ( j = 0; j < ABS_BIGGEST_BOX; ++j )
+			        {
+				    if ( pB->i2eChannelMap[box] & (1 << j) )
+				    {
+				        tty_register_device(ip2_tty_driver,
+					    j + ABS_BIGGEST_BOX *
+						    (box+i*ABS_MAX_BOXES), NULL);
+			    	    }
+			        }
+			    }
+			}
+
+			if (poll_only) {
+//		Poll only forces driver to only use polling and
+//		to ignore the probed PCI or EISA interrupts.
+				ip2config.irq[i] = CIR_POLL;
+			}
+			if ( ip2config.irq[i] == CIR_POLL ) {
+retry:
+				if (!TimerOn) {
+					PollTimer.expires = POLL_TIMEOUT;
+					add_timer ( &PollTimer );
+					TimerOn = 1;
+					printk( KERN_INFO "IP2: polling\n");
+				}
+			} else {
+				if (have_requested_irq(ip2config.irq[i]))
+					continue;
+				rc = request_irq( ip2config.irq[i], ip2_interrupt,
+					IP2_SA_FLAGS | (ip2config.type[i] == PCI ? SA_SHIRQ : 0),
+					pcName, (void *)&pcName);
+				if (rc) {
+					printk(KERN_ERR "IP2: an request_irq failed: error %d\n",rc);
+					ip2config.irq[i] = CIR_POLL;
+					printk( KERN_INFO "IP2: Polling %ld/sec.\n",
+							(POLL_TIMEOUT - jiffies));
+					goto retry;
+				} 
+				mark_requested_irq(ip2config.irq[i]);
+				/* Initialise the interrupt handler bottom half (aka slih). */
+			}
+		}
+		for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+			if ( i2BoardPtrTable[i] ) {
+				set_irq( i, ip2config.irq[i] ); /* set and enable board interrupt */
+			}
+		}
+	}
+	ip2trace (ITRC_NO_PORT, ITRC_INIT, ITRC_RETURN, 0 );
+	goto out;
+
+out_class:
+	class_simple_destroy(ip2_class);
+out_chrdev:
+	unregister_chrdev(IP2_IPL_MAJOR, "ip2");
+out:
+	return err;
+}
+
+EXPORT_SYMBOL(ip2_loadmain);
+
+/******************************************************************************/
+/* Function:   ip2_init_board()                                               */
+/* Parameters: Index of board in configuration structure                      */
+/* Returns:    Success (0)                                                    */
+/*                                                                            */
+/* Description:                                                               */
+/* This function initializes the specified board. The loadware is copied to   */
+/* the board, the channel structures are initialized, and the board details   */
+/* are reported on the console.                                               */
+/******************************************************************************/
+static void __init
+ip2_init_board( int boardnum )
+{
+	int i;
+	int nports = 0, nboxes = 0;
+	i2ChanStrPtr pCh;
+	i2eBordStrPtr pB = i2BoardPtrTable[boardnum];
+
+	if ( !iiInitialize ( pB ) ) {
+		printk ( KERN_ERR "IP2: Failed to initialize board at 0x%x, error %d\n",
+			 pB->i2eBase, pB->i2eError );
+		goto err_initialize;
+	}
+	printk(KERN_INFO "IP2: Board %d: addr=0x%x irq=%d\n", boardnum + 1,
+	       ip2config.addr[boardnum], ip2config.irq[boardnum] );
+
+	if (!request_region( ip2config.addr[boardnum], 8, pcName )) {
+		printk(KERN_ERR "IP2: bad addr=0x%x\n", ip2config.addr[boardnum]);
+		goto err_initialize;
+	}
+
+	if ( iiDownloadAll ( pB, (loadHdrStrPtr)Fip_firmware, 1, Fip_firmware_size )
+	    != II_DOWN_GOOD ) {
+		printk ( KERN_ERR "IP2: failed to download loadware\n" );
+		goto err_release_region;
+	} else {
+		printk ( KERN_INFO "IP2: fv=%d.%d.%d lv=%d.%d.%d\n",
+			 pB->i2ePom.e.porVersion,
+			 pB->i2ePom.e.porRevision,
+			 pB->i2ePom.e.porSubRev, pB->i2eLVersion,
+			 pB->i2eLRevision, pB->i2eLSub );
+	}
+
+	switch ( pB->i2ePom.e.porID & ~POR_ID_RESERVED ) {
+
+	default:
+		printk( KERN_ERR "IP2: Unknown board type, ID = %x\n",
+				pB->i2ePom.e.porID );
+		nports = 0;
+		goto err_release_region;
+		break;
+
+	case POR_ID_II_4: /* IntelliPort-II, ISA-4 (4xRJ45) */
+		printk ( KERN_INFO "IP2: ISA-4\n" );
+		nports = 4;
+		break;
+
+	case POR_ID_II_8: /* IntelliPort-II, 8-port using standard brick. */
+		printk ( KERN_INFO "IP2: ISA-8 std\n" );
+		nports = 8;
+		break;
+
+	case POR_ID_II_8R: /* IntelliPort-II, 8-port using RJ11's (no CTS) */
+		printk ( KERN_INFO "IP2: ISA-8 RJ11\n" );
+		nports = 8;
+		break;
+
+	case POR_ID_FIIEX: /* IntelliPort IIEX */
+	{
+		int portnum = IP2_PORTS_PER_BOARD * boardnum;
+		int            box;
+
+		for( box = 0; box < ABS_MAX_BOXES; ++box ) {
+			if ( pB->i2eChannelMap[box] != 0 ) {
+				++nboxes;
+			}
+			for( i = 0; i < ABS_BIGGEST_BOX; ++i ) {
+				if ( pB->i2eChannelMap[box] & 1<< i ) {
+					++nports;
+				}
+			}
+		}
+		DevTableMem[boardnum] = pCh =
+			kmalloc( sizeof(i2ChanStr) * nports, GFP_KERNEL );
+		if ( !pCh ) {
+			printk ( KERN_ERR "IP2: (i2_init_channel:) Out of memory.\n");
+			goto err_release_region;
+		}
+		if ( !i2InitChannels( pB, nports, pCh ) ) {
+			printk(KERN_ERR "IP2: i2InitChannels failed: %d\n",pB->i2eError);
+			kfree ( pCh );
+			goto err_release_region;
+		}
+		pB->i2eChannelPtr = &DevTable[portnum];
+		pB->i2eChannelCnt = ABS_MOST_PORTS;
+
+		for( box = 0; box < ABS_MAX_BOXES; ++box, portnum += ABS_BIGGEST_BOX ) {
+			for( i = 0; i < ABS_BIGGEST_BOX; ++i ) {
+				if ( pB->i2eChannelMap[box] & (1 << i) ) {
+					DevTable[portnum + i] = pCh;
+					pCh->port_index = portnum + i;
+					pCh++;
+				}
+			}
+		}
+		printk(KERN_INFO "IP2: EX box=%d ports=%d %d bit\n",
+			nboxes, nports, pB->i2eDataWidth16 ? 16 : 8 );
+		}
+		goto ex_exit;
+	}
+	DevTableMem[boardnum] = pCh =
+		kmalloc ( sizeof (i2ChanStr) * nports, GFP_KERNEL );
+	if ( !pCh ) {
+		printk ( KERN_ERR "IP2: (i2_init_channel:) Out of memory.\n");
+		goto err_release_region;
+	}
+	pB->i2eChannelPtr = pCh;
+	pB->i2eChannelCnt = nports;
+	if ( !i2InitChannels( pB, nports, pCh ) ) {
+		printk(KERN_ERR "IP2: i2InitChannels failed: %d\n",pB->i2eError);
+		kfree ( pCh );
+		goto err_release_region;
+	}
+	pB->i2eChannelPtr = &DevTable[IP2_PORTS_PER_BOARD * boardnum];
+
+	for( i = 0; i < pB->i2eChannelCnt; ++i ) {
+		DevTable[IP2_PORTS_PER_BOARD * boardnum + i] = pCh;
+		pCh->port_index = (IP2_PORTS_PER_BOARD * boardnum) + i;
+		pCh++;
+	}
+ex_exit:
+	INIT_WORK(&pB->tqueue_interrupt, (void(*)(void*)) ip2_interrupt_bh, pB);
+	return;
+
+err_release_region:
+	release_region(ip2config.addr[boardnum], 8);
+err_initialize:
+	kfree ( pB );
+	i2BoardPtrTable[boardnum] = NULL;
+	return;
+}
+
+/******************************************************************************/
+/* Function:   find_eisa_board ( int start_slot )                             */
+/* Parameters: First slot to check                                            */
+/* Returns:    Address of EISA IntelliPort II controller                      */
+/*                                                                            */
+/* Description:                                                               */
+/* This function searches for an EISA IntelliPort controller, starting        */
+/* from the specified slot number. If the motherboard is not identified as an */
+/* EISA motherboard, or no valid board ID is selected it returns 0. Otherwise */
+/* it returns the base address of the controller.                             */
+/******************************************************************************/
+static unsigned short __init
+find_eisa_board( int start_slot )
+{
+	int i, j;
+	unsigned int idm = 0;
+	unsigned int idp = 0;
+	unsigned int base = 0;
+	unsigned int value;
+	int setup_address;
+	int setup_irq;
+	int ismine = 0;
+
+	/*
+	 * First a check for an EISA motherboard, which we do by comparing the
+	 * EISA ID registers for the system board and the first couple of slots.
+	 * No slot ID should match the system board ID, but on an ISA or PCI
+	 * machine the odds are that an empty bus will return similar values for
+	 * each slot.
+	 */
+	i = 0x0c80;
+	value = (inb(i) << 24) + (inb(i+1) << 16) + (inb(i+2) << 8) + inb(i+3);
+	for( i = 0x1c80; i <= 0x4c80; i += 0x1000 ) {
+		j = (inb(i)<<24)+(inb(i+1)<<16)+(inb(i+2)<<8)+inb(i+3);
+		if ( value == j )
+			return 0;
+	}
+
+	/*
+	 * OK, so we are inclined to believe that this is an EISA machine. Find
+	 * an IntelliPort controller.
+	 */
+	for( i = start_slot; i < 16; i++ ) {
+		base = i << 12;
+		idm = (inb(base + 0xc80) << 8) | (inb(base + 0xc81) & 0xff);
+		idp = (inb(base + 0xc82) << 8) | (inb(base + 0xc83) & 0xff);
+		ismine = 0;
+		if ( idm == 0x0e8e ) {
+			if ( idp == 0x0281 || idp == 0x0218 ) {
+				ismine = 1;
+			} else if ( idp == 0x0282 || idp == 0x0283 ) {
+				ismine = 3;	/* Can do edge-trigger */
+			}
+			if ( ismine ) {
+				Eisa_slot = i;
+				break;
+			}
+		}
+	}
+	if ( !ismine )
+		return 0;
+
+	/* It's some sort of EISA card, but at what address is it configured? */
+
+	setup_address = base + 0xc88;
+	value = inb(base + 0xc86);
+	setup_irq = (value & 8) ? Valid_Irqs[value & 7] : 0;
+
+	if ( (ismine & 2) && !(value & 0x10) ) {
+		ismine = 1;	/* Could be edging, but not */
+	}
+
+	if ( Eisa_irq == 0 ) {
+		Eisa_irq = setup_irq;
+	} else if ( Eisa_irq != setup_irq ) {
+		printk ( KERN_ERR "IP2: EISA irq mismatch between EISA controllers\n" );
+	}
+
+#ifdef IP2DEBUG_INIT
+printk(KERN_DEBUG "Computone EISA board in slot %d, I.D. 0x%x%x, Address 0x%x",
+	       base >> 12, idm, idp, setup_address);
+	if ( Eisa_irq ) {
+		printk(KERN_DEBUG ", Interrupt %d %s\n",
+		       setup_irq, (ismine & 2) ? "(edge)" : "(level)");
+	} else {
+		printk(KERN_DEBUG ", (polled)\n");
+	}
+#endif
+	return setup_address;
+}
+
+/******************************************************************************/
+/* Function:   set_irq()                                                      */
+/* Parameters: index to board in board table                                  */
+/*             IRQ to use                                                     */
+/* Returns:    Success (0)                                                    */
+/*                                                                            */
+/* Description:                                                               */
+/******************************************************************************/
+static void
+set_irq( int boardnum, int boardIrq )
+{
+	unsigned char tempCommand[16];
+	i2eBordStrPtr pB = i2BoardPtrTable[boardnum];
+	unsigned long flags;
+
+	/*
+	 * Notify the boards they may generate interrupts. This is done by
+	 * sending an in-line command to channel 0 on each board. This is why
+	 * the channels have to be defined already. For each board, if the
+	 * interrupt has never been defined, we must do so NOW, directly, since
+	 * board will not send flow control or even give an interrupt until this
+	 * is done.  If polling we must send 0 as the interrupt parameter.
+	 */
+
+	// We will get an interrupt here at the end of this function
+
+	iiDisableMailIrq(pB);
+
+	/* We build up the entire packet header. */
+	CHANNEL_OF(tempCommand) = 0;
+	PTYPE_OF(tempCommand) = PTYPE_INLINE;
+	CMD_COUNT_OF(tempCommand) = 2;
+	(CMD_OF(tempCommand))[0] = CMDVALUE_IRQ;
+	(CMD_OF(tempCommand))[1] = boardIrq;
+	/*
+	 * Write to FIFO; don't bother to adjust fifo capacity for this, since
+	 * board will respond almost immediately after SendMail hit.
+	 */
+	WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+	iiWriteBuf(pB, tempCommand, 4);
+	WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+	pB->i2eUsingIrq = boardIrq;
+	pB->i2eOutMailWaiting |= MB_OUT_STUFFED;
+
+	/* Need to update number of boards before you enable mailbox int */
+	++i2nBoards;
+
+	CHANNEL_OF(tempCommand) = 0;
+	PTYPE_OF(tempCommand) = PTYPE_BYPASS;
+	CMD_COUNT_OF(tempCommand) = 6;
+	(CMD_OF(tempCommand))[0] = 88;	// SILO
+	(CMD_OF(tempCommand))[1] = 64;	// chars
+	(CMD_OF(tempCommand))[2] = 32;	// ms
+
+	(CMD_OF(tempCommand))[3] = 28;	// MAX_BLOCK
+	(CMD_OF(tempCommand))[4] = 64;	// chars
+
+	(CMD_OF(tempCommand))[5] = 87;	// HW_TEST
+	WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+	iiWriteBuf(pB, tempCommand, 8);
+	WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+
+	CHANNEL_OF(tempCommand) = 0;
+	PTYPE_OF(tempCommand) = PTYPE_BYPASS;
+	CMD_COUNT_OF(tempCommand) = 1;
+	(CMD_OF(tempCommand))[0] = 84;	/* get BOX_IDS */
+	iiWriteBuf(pB, tempCommand, 3);
+
+#ifdef XXX
+	// enable heartbeat for test porpoises
+	CHANNEL_OF(tempCommand) = 0;
+	PTYPE_OF(tempCommand) = PTYPE_BYPASS;
+	CMD_COUNT_OF(tempCommand) = 2;
+	(CMD_OF(tempCommand))[0] = 44;	/* get ping */
+	(CMD_OF(tempCommand))[1] = 200;	/* 200 ms */
+	WRITE_LOCK_IRQSAVE(&pB->write_fifo_spinlock,flags);
+	iiWriteBuf(pB, tempCommand, 4);
+	WRITE_UNLOCK_IRQRESTORE(&pB->write_fifo_spinlock,flags);
+#endif
+
+	iiEnableMailIrq(pB);
+	iiSendPendingMail(pB);
+}
+
+/******************************************************************************/
+/* Interrupt Handler Section                                                  */
+/******************************************************************************/
+
+static inline void
+service_all_boards(void)
+{
+	int i;
+	i2eBordStrPtr  pB;
+
+	/* Service every board on the list */
+	for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		pB = i2BoardPtrTable[i];
+		if ( pB ) {
+			i2ServiceBoard( pB );
+		}
+	}
+}
+
+
+/******************************************************************************/
+/* Function:   ip2_interrupt_bh(pB)                                           */
+/* Parameters: pB - pointer to the board structure                            */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*	Service the board in a bottom half interrupt handler and then         */
+/*	reenable the board's interrupts if it has an IRQ number               */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_interrupt_bh(i2eBordStrPtr pB)
+{
+//	pB better well be set or we have a problem!  We can only get
+//	here from the IMMEDIATE queue.  Here, we process the boards.
+//	Checking pB doesn't cost much and it saves us from the sanity checkers.
+
+	bh_counter++; 
+
+	if ( pB ) {
+		i2ServiceBoard( pB );
+		if( pB->i2eUsingIrq ) {
+//			Re-enable his interrupts
+			iiEnableMailIrq(pB);
+		}
+	}
+}
+
+
+/******************************************************************************/
+/* Function:   ip2_interrupt(int irq, void *dev_id, struct pt_regs * regs)    */
+/* Parameters: irq - interrupt number                                         */
+/*             pointer to optional device ID structure                        */
+/*             pointer to register structure                                  */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*	Our task here is simply to identify each board which needs servicing. */
+/*	If we are queuing then, queue it to be serviced, and disable its irq  */
+/*	mask otherwise process the board directly.                            */
+/*                                                                            */
+/*	We could queue by IRQ but that just complicates things on both ends   */
+/*	with very little gain in performance (how many instructions does      */
+/*	it take to iterate on the immediate queue).                           */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static irqreturn_t
+ip2_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+	int i;
+	i2eBordStrPtr  pB;
+	int handled = 0;
+
+	ip2trace (ITRC_NO_PORT, ITRC_INTR, 99, 1, irq );
+
+	/* Service just the boards on the list using this irq */
+	for( i = 0; i < i2nBoards; ++i ) {
+		pB = i2BoardPtrTable[i];
+
+//		Only process those boards which match our IRQ.
+//			IRQ = 0 for polled boards, we won't poll "IRQ" boards
+
+		if ( pB && (pB->i2eUsingIrq == irq) ) {
+			handled = 1;
+#ifdef USE_IQI
+
+		    if (NO_MAIL_HERE != ( pB->i2eStartMail = iiGetMail(pB))) {
+//			Disable his interrupt (will be enabled when serviced)
+//			This is mostly to protect from reentrancy.
+			iiDisableMailIrq(pB);
+
+//			Park the board on the immediate queue for processing.
+			schedule_work(&pB->tqueue_interrupt);
+
+//			Make sure the immediate queue is flagged to fire.
+		    }
+#else
+//		We are using immediate servicing here.  This sucks and can
+//		cause all sorts of havoc with ppp and others.  The failsafe
+//		check on iiSendPendingMail could also throw a hairball.
+			i2ServiceBoard( pB );
+#endif /* USE_IQI */
+		}
+	}
+
+	++irq_counter;
+
+	ip2trace (ITRC_NO_PORT, ITRC_INTR, ITRC_RETURN, 0 );
+	return IRQ_RETVAL(handled);
+}
+
+/******************************************************************************/
+/* Function:   ip2_poll(unsigned long arg)                                    */
+/* Parameters: ?                                                              */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/* This function calls the library routine i2ServiceBoard for each board in   */
+/* the board table. This is used instead of the interrupt routine when polled */
+/* mode is specified.                                                         */
+/******************************************************************************/
+static void
+ip2_poll(unsigned long arg)
+{
+	ip2trace (ITRC_NO_PORT, ITRC_INTR, 100, 0 );
+
+	TimerOn = 0; // it's the truth but not checked in service
+
+	// Just polled boards, IRQ = 0 will hit all non-interrupt boards.
+	// It will NOT poll boards handled by hard interrupts.
+	// The issue of queued BH interrups is handled in ip2_interrupt().
+	ip2_interrupt(0, NULL, NULL);
+
+	PollTimer.expires = POLL_TIMEOUT;
+	add_timer( &PollTimer );
+	TimerOn = 1;
+
+	ip2trace (ITRC_NO_PORT, ITRC_INTR, ITRC_RETURN, 0 );
+}
+
+static void do_input(void *p)
+{
+	i2ChanStrPtr pCh = p;
+	unsigned long flags;
+
+	ip2trace(CHANN, ITRC_INPUT, 21, 0 );
+
+	// Data input
+	if ( pCh->pTTY != NULL ) {
+		READ_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags)
+		if (!pCh->throttled && (pCh->Ibuf_stuff != pCh->Ibuf_strip)) {
+			READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags)
+			i2Input( pCh );
+		} else
+			READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags)
+	} else {
+		ip2trace(CHANN, ITRC_INPUT, 22, 0 );
+
+		i2InputFlush( pCh );
+	}
+}
+
+// code duplicated from n_tty (ldisc)
+static inline void  isig(int sig, struct tty_struct *tty, int flush)
+{
+	if (tty->pgrp > 0)
+		kill_pg(tty->pgrp, sig, 1);
+	if (flush || !L_NOFLSH(tty)) {
+		if ( tty->ldisc.flush_buffer )  
+			tty->ldisc.flush_buffer(tty);
+		i2InputFlush( tty->driver_data );
+	}
+}
+
+static void do_status(void *p)
+{
+	i2ChanStrPtr pCh = p;
+	int status;
+
+	status =  i2GetStatus( pCh, (I2_BRK|I2_PAR|I2_FRA|I2_OVR) );
+
+	ip2trace (CHANN, ITRC_STATUS, 21, 1, status );
+
+	if (pCh->pTTY && (status & (I2_BRK|I2_PAR|I2_FRA|I2_OVR)) ) {
+		if ( (status & I2_BRK) ) {
+			// code duplicated from n_tty (ldisc)
+			if (I_IGNBRK(pCh->pTTY))
+				goto skip_this;
+			if (I_BRKINT(pCh->pTTY)) {
+				isig(SIGINT, pCh->pTTY, 1);
+				goto skip_this;
+			}
+			wake_up_interruptible(&pCh->pTTY->read_wait);
+		}
+#ifdef NEVER_HAPPENS_AS_SETUP_XXX
+	// and can't work because we don't know the_char
+	// as the_char is reported on a separate path
+	// The intelligent board does this stuff as setup
+	{
+	char brkf = TTY_NORMAL;
+	unsigned char brkc = '\0';
+	unsigned char tmp;
+		if ( (status & I2_BRK) ) {
+			brkf = TTY_BREAK;
+			brkc = '\0';
+		} 
+		else if (status & I2_PAR) {
+			brkf = TTY_PARITY;
+			brkc = the_char;
+		} else if (status & I2_FRA) {
+			brkf = TTY_FRAME;
+			brkc = the_char;
+		} else if (status & I2_OVR) {
+			brkf = TTY_OVERRUN;
+			brkc = the_char;
+		}
+		tmp = pCh->pTTY->real_raw;
+		pCh->pTTY->real_raw = 0;
+		pCh->pTTY->ldisc.receive_buf( pCh->pTTY, &brkc, &brkf, 1 );
+		pCh->pTTY->real_raw = tmp;
+	}
+#endif /* NEVER_HAPPENS_AS_SETUP_XXX */
+	}
+skip_this:
+
+	if ( status & (I2_DDCD | I2_DDSR | I2_DCTS | I2_DRI) ) {
+		wake_up_interruptible(&pCh->delta_msr_wait);
+
+		if ( (pCh->flags & ASYNC_CHECK_CD) && (status & I2_DDCD) ) {
+			if ( status & I2_DCD ) {
+				if ( pCh->wopen ) {
+					wake_up_interruptible ( &pCh->open_wait );
+				}
+			} else {
+				if (pCh->pTTY &&  (!(pCh->pTTY->termios->c_cflag & CLOCAL)) ) {
+					tty_hangup( pCh->pTTY );
+				}
+			}
+		}
+	}
+
+	ip2trace (CHANN, ITRC_STATUS, 26, 0 );
+}
+
+/******************************************************************************/
+/* Device Open/Close/Ioctl Entry Point Section                                */
+/******************************************************************************/
+
+/******************************************************************************/
+/* Function:   open_sanity_check()                                            */
+/* Parameters: Pointer to tty structure                                       */
+/*             Pointer to file structure                                      */
+/* Returns:    Success or failure                                             */
+/*                                                                            */
+/* Description:                                                               */
+/* Verifies the structure magic numbers and cross links.                      */
+/******************************************************************************/
+#ifdef IP2DEBUG_OPEN
+static void 
+open_sanity_check( i2ChanStrPtr pCh, i2eBordStrPtr pBrd )
+{
+	if ( pBrd->i2eValid != I2E_MAGIC ) {
+		printk(KERN_ERR "IP2: invalid board structure\n" );
+	} else if ( pBrd != pCh->pMyBord ) {
+		printk(KERN_ERR "IP2: board structure pointer mismatch (%p)\n",
+			 pCh->pMyBord );
+	} else if ( pBrd->i2eChannelCnt < pCh->port_index ) {
+		printk(KERN_ERR "IP2: bad device index (%d)\n", pCh->port_index );
+	} else if (&((i2ChanStrPtr)pBrd->i2eChannelPtr)[pCh->port_index] != pCh) {
+	} else {
+		printk(KERN_INFO "IP2: all pointers check out!\n" );
+	}
+}
+#endif
+
+
+/******************************************************************************/
+/* Function:   ip2_open()                                                     */
+/* Parameters: Pointer to tty structure                                       */
+/*             Pointer to file structure                                      */
+/* Returns:    Success or failure                                             */
+/*                                                                            */
+/* Description: (MANDATORY)                                                   */
+/* A successful device open has to run a gauntlet of checks before it         */
+/* completes. After some sanity checking and pointer setup, the function      */
+/* blocks until all conditions are satisfied. It then initialises the port to */
+/* the default characteristics and returns.                                   */
+/******************************************************************************/
+static int
+ip2_open( PTTY tty, struct file *pFile )
+{
+	wait_queue_t wait;
+	int rc = 0;
+	int do_clocal = 0;
+	i2ChanStrPtr  pCh = DevTable[tty->index];
+
+	ip2trace (tty->index, ITRC_OPEN, ITRC_ENTER, 0 );
+
+	if ( pCh == NULL ) {
+		return -ENODEV;
+	}
+	/* Setup pointer links in device and tty structures */
+	pCh->pTTY = tty;
+	tty->driver_data = pCh;
+
+#ifdef IP2DEBUG_OPEN
+	printk(KERN_DEBUG \
+			"IP2:open(tty=%p,pFile=%p):dev=%s,ch=%d,idx=%d\n",
+	       tty, pFile, tty->name, pCh->infl.hd.i2sChannel, pCh->port_index);
+	open_sanity_check ( pCh, pCh->pMyBord );
+#endif
+
+	i2QueueCommands(PTYPE_INLINE, pCh, 100, 3, CMD_DTRUP,CMD_RTSUP,CMD_DCD_REP);
+	pCh->dataSetOut |= (I2_DTR | I2_RTS);
+	serviceOutgoingFifo( pCh->pMyBord );
+
+	/* Block here until the port is ready (per serial and istallion) */
+	/*
+	 * 1. If the port is in the middle of closing wait for the completion
+	 *    and then return the appropriate error.
+	 */
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&pCh->close_wait, &wait);
+	set_current_state( TASK_INTERRUPTIBLE );
+
+	if ( tty_hung_up_p(pFile) || ( pCh->flags & ASYNC_CLOSING )) {
+		if ( pCh->flags & ASYNC_CLOSING ) {
+			schedule();
+		}
+		if ( tty_hung_up_p(pFile) ) {
+			set_current_state( TASK_RUNNING );
+			remove_wait_queue(&pCh->close_wait, &wait);
+			return( pCh->flags & ASYNC_HUP_NOTIFY ) ? -EAGAIN : -ERESTARTSYS;
+		}
+	}
+	set_current_state( TASK_RUNNING );
+	remove_wait_queue(&pCh->close_wait, &wait);
+
+	/*
+	 * 3. Handle a non-blocking open of a normal port.
+	 */
+	if ( (pFile->f_flags & O_NONBLOCK) || (tty->flags & (1<<TTY_IO_ERROR) )) {
+		pCh->flags |= ASYNC_NORMAL_ACTIVE;
+		goto noblock;
+	}
+	/*
+	 * 4. Now loop waiting for the port to be free and carrier present
+	 *    (if required).
+	 */
+	if ( tty->termios->c_cflag & CLOCAL )
+		do_clocal = 1;
+
+#ifdef IP2DEBUG_OPEN
+	printk(KERN_DEBUG "OpenBlock: do_clocal = %d\n", do_clocal);
+#endif
+
+	++pCh->wopen;
+
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&pCh->open_wait, &wait);
+
+	for(;;) {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 2, CMD_DTRUP, CMD_RTSUP);
+		pCh->dataSetOut |= (I2_DTR | I2_RTS);
+		set_current_state( TASK_INTERRUPTIBLE );
+		serviceOutgoingFifo( pCh->pMyBord );
+		if ( tty_hung_up_p(pFile) ) {
+			set_current_state( TASK_RUNNING );
+			remove_wait_queue(&pCh->open_wait, &wait);
+			return ( pCh->flags & ASYNC_HUP_NOTIFY ) ? -EBUSY : -ERESTARTSYS;
+		}
+		if (!(pCh->flags & ASYNC_CLOSING) && 
+				(do_clocal || (pCh->dataSetIn & I2_DCD) )) {
+			rc = 0;
+			break;
+		}
+
+#ifdef IP2DEBUG_OPEN
+		printk(KERN_DEBUG "ASYNC_CLOSING = %s\n",
+			(pCh->flags & ASYNC_CLOSING)?"True":"False");
+		printk(KERN_DEBUG "OpenBlock: waiting for CD or signal\n");
+#endif
+		ip2trace (CHANN, ITRC_OPEN, 3, 2, 0,
+				(pCh->flags & ASYNC_CLOSING) );
+		/* check for signal */
+		if (signal_pending(current)) {
+			rc = (( pCh->flags & ASYNC_HUP_NOTIFY ) ? -EAGAIN : -ERESTARTSYS);
+			break;
+		}
+		schedule();
+	}
+	set_current_state( TASK_RUNNING );
+	remove_wait_queue(&pCh->open_wait, &wait);
+
+	--pCh->wopen; //why count?
+
+	ip2trace (CHANN, ITRC_OPEN, 4, 0 );
+
+	if (rc != 0 ) {
+		return rc;
+	}
+	pCh->flags |= ASYNC_NORMAL_ACTIVE;
+
+noblock:
+
+	/* first open - Assign termios structure to port */
+	if ( tty->count == 1 ) {
+		i2QueueCommands(PTYPE_INLINE, pCh, 0, 2, CMD_CTSFL_DSAB, CMD_RTSFL_DSAB);
+		/* Now we must send the termios settings to the loadware */
+		set_params( pCh, NULL );
+	}
+
+	/*
+	 * Now set any i2lib options. These may go away if the i2lib code ends
+	 * up rolled into the mainline.
+	 */
+	pCh->channelOptions |= CO_NBLOCK_WRITE;
+
+#ifdef IP2DEBUG_OPEN
+	printk (KERN_DEBUG "IP2: open completed\n" );
+#endif
+	serviceOutgoingFifo( pCh->pMyBord );
+
+	ip2trace (CHANN, ITRC_OPEN, ITRC_RETURN, 0 );
+
+	return 0;
+}
+
+/******************************************************************************/
+/* Function:   ip2_close()                                                    */
+/* Parameters: Pointer to tty structure                                       */
+/*             Pointer to file structure                                      */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_close( PTTY tty, struct file *pFile )
+{
+	i2ChanStrPtr  pCh = tty->driver_data;
+
+	if ( !pCh ) {
+		return;
+	}
+
+	ip2trace (CHANN, ITRC_CLOSE, ITRC_ENTER, 0 );
+
+#ifdef IP2DEBUG_OPEN
+	printk(KERN_DEBUG "IP2:close %s:\n",tty->name);
+#endif
+
+	if ( tty_hung_up_p ( pFile ) ) {
+
+		ip2trace (CHANN, ITRC_CLOSE, 2, 1, 2 );
+
+		return;
+	}
+	if ( tty->count > 1 ) { /* not the last close */
+
+		ip2trace (CHANN, ITRC_CLOSE, 2, 1, 3 );
+
+		return;
+	}
+	pCh->flags |= ASYNC_CLOSING;	// last close actually
+
+	tty->closing = 1;
+
+	if (pCh->ClosingWaitTime != ASYNC_CLOSING_WAIT_NONE) {
+		/*
+		 * Before we drop DTR, make sure the transmitter has completely drained.
+		 * This uses an timeout, after which the close
+		 * completes.
+		 */
+		ip2_wait_until_sent(tty, pCh->ClosingWaitTime );
+	}
+	/*
+	 * At this point we stop accepting input. Here we flush the channel
+	 * input buffer which will allow the board to send up more data. Any
+	 * additional input is tossed at interrupt/poll time.
+	 */
+	i2InputFlush( pCh );
+
+	/* disable DSS reporting */
+	i2QueueCommands(PTYPE_INLINE, pCh, 100, 4,
+				CMD_DCD_NREP, CMD_CTS_NREP, CMD_DSR_NREP, CMD_RI_NREP);
+	if ( !tty || (tty->termios->c_cflag & HUPCL) ) {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 2, CMD_RTSDN, CMD_DTRDN);
+		pCh->dataSetOut &= ~(I2_DTR | I2_RTS);
+		i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_PAUSE(25));
+	}
+
+	serviceOutgoingFifo ( pCh->pMyBord );
+
+	if ( tty->driver->flush_buffer ) 
+		tty->driver->flush_buffer(tty);
+	if ( tty->ldisc.flush_buffer )  
+		tty->ldisc.flush_buffer(tty);
+	tty->closing = 0;
+	
+	pCh->pTTY = NULL;
+
+	if (pCh->wopen) {
+		if (pCh->ClosingDelay) {
+			msleep_interruptible(jiffies_to_msecs(pCh->ClosingDelay));
+		}
+		wake_up_interruptible(&pCh->open_wait);
+	}
+
+	pCh->flags &=~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+	wake_up_interruptible(&pCh->close_wait);
+
+#ifdef IP2DEBUG_OPEN
+	DBG_CNT("ip2_close: after wakeups--");
+#endif
+
+
+	ip2trace (CHANN, ITRC_CLOSE, ITRC_RETURN, 1, 1 );
+
+	return;
+}
+
+/******************************************************************************/
+/* Function:   ip2_hangup()                                                   */
+/* Parameters: Pointer to tty structure                                       */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_hangup ( PTTY tty )
+{
+	i2ChanStrPtr  pCh = tty->driver_data;
+
+	if( !pCh ) {
+		return;
+	}
+
+	ip2trace (CHANN, ITRC_HANGUP, ITRC_ENTER, 0 );
+
+	ip2_flush_buffer(tty);
+
+	/* disable DSS reporting */
+
+	i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_DCD_NREP);
+	i2QueueCommands(PTYPE_INLINE, pCh, 0, 2, CMD_CTSFL_DSAB, CMD_RTSFL_DSAB);
+	if ( (tty->termios->c_cflag & HUPCL) ) {
+		i2QueueCommands(PTYPE_BYPASS, pCh, 0, 2, CMD_RTSDN, CMD_DTRDN);
+		pCh->dataSetOut &= ~(I2_DTR | I2_RTS);
+		i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_PAUSE(25));
+	}
+	i2QueueCommands(PTYPE_INLINE, pCh, 1, 3, 
+				CMD_CTS_NREP, CMD_DSR_NREP, CMD_RI_NREP);
+	serviceOutgoingFifo ( pCh->pMyBord );
+
+	wake_up_interruptible ( &pCh->delta_msr_wait );
+
+	pCh->flags &= ~ASYNC_NORMAL_ACTIVE;
+	pCh->pTTY = NULL;
+	wake_up_interruptible ( &pCh->open_wait );
+
+	ip2trace (CHANN, ITRC_HANGUP, ITRC_RETURN, 0 );
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/* Device Output Section                                                      */
+/******************************************************************************/
+/******************************************************************************/
+
+/******************************************************************************/
+/* Function:   ip2_write()                                                    */
+/* Parameters: Pointer to tty structure                                       */
+/*             Flag denoting data is in user (1) or kernel (0) space          */
+/*             Pointer to data                                                */
+/*             Number of bytes to write                                       */
+/* Returns:    Number of bytes actually written                               */
+/*                                                                            */
+/* Description: (MANDATORY)                                                   */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static int
+ip2_write( PTTY tty, int user, const unsigned char *pData, int count)
+{
+	i2ChanStrPtr  pCh = tty->driver_data;
+	int bytesSent = 0;
+	unsigned long flags;
+
+	ip2trace (CHANN, ITRC_WRITE, ITRC_ENTER, 2, count, -1 );
+
+	/* Flush out any buffered data left over from ip2_putchar() calls. */
+	ip2_flush_chars( tty );
+
+	/* This is the actual move bit. Make sure it does what we need!!!!! */
+	WRITE_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+	bytesSent = i2Output( pCh, pData, count, user );
+	WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+
+	ip2trace (CHANN, ITRC_WRITE, ITRC_RETURN, 1, bytesSent );
+
+	return bytesSent > 0 ? bytesSent : 0;
+}
+
+/******************************************************************************/
+/* Function:   ip2_putchar()                                                  */
+/* Parameters: Pointer to tty structure                                       */
+/*             Character to write                                             */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_putchar( PTTY tty, unsigned char ch )
+{
+	i2ChanStrPtr  pCh = tty->driver_data;
+	unsigned long flags;
+
+//	ip2trace (CHANN, ITRC_PUTC, ITRC_ENTER, 1, ch );
+
+	WRITE_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+	pCh->Pbuf[pCh->Pbuf_stuff++] = ch;
+	if ( pCh->Pbuf_stuff == sizeof pCh->Pbuf ) {
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+		ip2_flush_chars( tty );
+	} else
+		WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+
+//	ip2trace (CHANN, ITRC_PUTC, ITRC_RETURN, 1, ch );
+}
+
+/******************************************************************************/
+/* Function:   ip2_flush_chars()                                              */
+/* Parameters: Pointer to tty structure                                       */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_flush_chars( PTTY tty )
+{
+	int   strip;
+	i2ChanStrPtr  pCh = tty->driver_data;
+	unsigned long flags;
+
+	WRITE_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+	if ( pCh->Pbuf_stuff ) {
+
+//		ip2trace (CHANN, ITRC_PUTC, 10, 1, strip );
+
+		//
+		// We may need to restart i2Output if it does not fullfill this request
+		//
+		strip = i2Output( pCh, pCh->Pbuf, pCh->Pbuf_stuff, 0 );
+		if ( strip != pCh->Pbuf_stuff ) {
+			memmove( pCh->Pbuf, &pCh->Pbuf[strip], pCh->Pbuf_stuff - strip );
+		}
+		pCh->Pbuf_stuff -= strip;
+	}
+	WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+}
+
+/******************************************************************************/
+/* Function:   ip2_write_room()                                               */
+/* Parameters: Pointer to tty structure                                       */
+/* Returns:    Number of bytes that the driver can accept                     */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/******************************************************************************/
+static int
+ip2_write_room ( PTTY tty )
+{
+	int bytesFree;
+	i2ChanStrPtr  pCh = tty->driver_data;
+	unsigned long flags;
+
+	READ_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+	bytesFree = i2OutputFree( pCh ) - pCh->Pbuf_stuff;
+	READ_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+
+	ip2trace (CHANN, ITRC_WRITE, 11, 1, bytesFree );
+
+	return ((bytesFree > 0) ? bytesFree : 0);
+}
+
+/******************************************************************************/
+/* Function:   ip2_chars_in_buf()                                             */
+/* Parameters: Pointer to tty structure                                       */
+/* Returns:    Number of bytes queued for transmission                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static int
+ip2_chars_in_buf ( PTTY tty )
+{
+	i2ChanStrPtr  pCh = tty->driver_data;
+	int rc;
+	unsigned long flags;
+
+	ip2trace (CHANN, ITRC_WRITE, 12, 1, pCh->Obuf_char_count + pCh->Pbuf_stuff );
+
+#ifdef IP2DEBUG_WRITE
+	printk (KERN_DEBUG "IP2: chars in buffer = %d (%d,%d)\n",
+				 pCh->Obuf_char_count + pCh->Pbuf_stuff,
+				 pCh->Obuf_char_count, pCh->Pbuf_stuff );
+#endif
+	READ_LOCK_IRQSAVE(&pCh->Obuf_spinlock,flags);
+	rc =  pCh->Obuf_char_count;
+	READ_UNLOCK_IRQRESTORE(&pCh->Obuf_spinlock,flags);
+	READ_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+	rc +=  pCh->Pbuf_stuff;
+	READ_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+	return rc;
+}
+
+/******************************************************************************/
+/* Function:   ip2_flush_buffer()                                             */
+/* Parameters: Pointer to tty structure                                       */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_flush_buffer( PTTY tty )
+{
+	i2ChanStrPtr  pCh = tty->driver_data;
+	unsigned long flags;
+
+	ip2trace (CHANN, ITRC_FLUSH, ITRC_ENTER, 0 );
+
+#ifdef IP2DEBUG_WRITE
+	printk (KERN_DEBUG "IP2: flush buffer\n" );
+#endif
+	WRITE_LOCK_IRQSAVE(&pCh->Pbuf_spinlock,flags);
+	pCh->Pbuf_stuff = 0;
+	WRITE_UNLOCK_IRQRESTORE(&pCh->Pbuf_spinlock,flags);
+	i2FlushOutput( pCh );
+	ip2_owake(tty);
+
+	ip2trace (CHANN, ITRC_FLUSH, ITRC_RETURN, 0 );
+
+}
+
+/******************************************************************************/
+/* Function:   ip2_wait_until_sent()                                          */
+/* Parameters: Pointer to tty structure                                       */
+/*             Timeout for wait.                                              */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/* This function is used in place of the normal tty_wait_until_sent, which    */
+/* only waits for the driver buffers to be empty (or rather, those buffers    */
+/* reported by chars_in_buffer) which doesn't work for IP2 due to the         */
+/* indeterminate number of bytes buffered on the board.                       */
+/******************************************************************************/
+static void
+ip2_wait_until_sent ( PTTY tty, int timeout )
+{
+	int i = jiffies;
+	i2ChanStrPtr  pCh = tty->driver_data;
+
+	tty_wait_until_sent(tty, timeout );
+	if ( (i = timeout - (jiffies -i)) > 0)
+		i2DrainOutput( pCh, i );
+}
+
+/******************************************************************************/
+/******************************************************************************/
+/* Device Input Section                                                       */
+/******************************************************************************/
+/******************************************************************************/
+
+/******************************************************************************/
+/* Function:   ip2_throttle()                                                 */
+/* Parameters: Pointer to tty structure                                       */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_throttle ( PTTY tty )
+{
+	i2ChanStrPtr  pCh = tty->driver_data;
+
+#ifdef IP2DEBUG_READ
+	printk (KERN_DEBUG "IP2: throttle\n" );
+#endif
+	/*
+	 * Signal the poll/interrupt handlers not to forward incoming data to
+	 * the line discipline. This will cause the buffers to fill up in the
+	 * library and thus cause the library routines to send the flow control
+	 * stuff.
+	 */
+	pCh->throttled = 1;
+}
+
+/******************************************************************************/
+/* Function:   ip2_unthrottle()                                               */
+/* Parameters: Pointer to tty structure                                       */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_unthrottle ( PTTY tty )
+{
+	i2ChanStrPtr  pCh = tty->driver_data;
+	unsigned long flags;
+
+#ifdef IP2DEBUG_READ
+	printk (KERN_DEBUG "IP2: unthrottle\n" );
+#endif
+
+	/* Pass incoming data up to the line discipline again. */
+	pCh->throttled = 0;
+ 	i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_RESUME);
+	serviceOutgoingFifo( pCh->pMyBord );
+	READ_LOCK_IRQSAVE(&pCh->Ibuf_spinlock,flags)
+	if ( pCh->Ibuf_stuff != pCh->Ibuf_strip ) {
+		READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags)
+#ifdef IP2DEBUG_READ
+		printk (KERN_DEBUG "i2Input called from unthrottle\n" );
+#endif
+		i2Input( pCh );
+	} else
+		READ_UNLOCK_IRQRESTORE(&pCh->Ibuf_spinlock,flags)
+}
+
+static void
+ip2_start ( PTTY tty )
+{
+ 	i2ChanStrPtr  pCh = DevTable[tty->index];
+
+ 	i2QueueCommands(PTYPE_BYPASS, pCh, 0, 1, CMD_RESUME);
+ 	i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_UNSUSPEND);
+ 	i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_RESUME);
+#ifdef IP2DEBUG_WRITE
+	printk (KERN_DEBUG "IP2: start tx\n" );
+#endif
+}
+
+static void
+ip2_stop ( PTTY tty )
+{
+ 	i2ChanStrPtr  pCh = DevTable[tty->index];
+
+ 	i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_SUSPEND);
+#ifdef IP2DEBUG_WRITE
+	printk (KERN_DEBUG "IP2: stop tx\n" );
+#endif
+}
+
+/******************************************************************************/
+/* Device Ioctl Section                                                       */
+/******************************************************************************/
+
+static int ip2_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	i2ChanStrPtr pCh = DevTable[tty->index];
+	wait_queue_t wait;
+
+	if (pCh == NULL)
+		return -ENODEV;
+
+/*
+	FIXME - the following code is causing a NULL pointer dereference in
+	2.3.51 in an interrupt handler.  It's suppose to prompt the board
+	to return the DSS signal status immediately.  Why doesn't it do
+	the same thing in 2.2.14?
+*/
+
+/*	This thing is still busted in the 1.2.12 driver on 2.4.x
+	and even hoses the serial console so the oops can be trapped.
+		/\/\|=mhw=|\/\/			*/
+
+#ifdef	ENABLE_DSSNOW
+	i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_DSS_NOW);
+
+	init_waitqueue_entry(&wait, current);
+	add_wait_queue(&pCh->dss_now_wait, &wait);
+	set_current_state( TASK_INTERRUPTIBLE );
+
+	serviceOutgoingFifo( pCh->pMyBord );
+
+	schedule();
+
+	set_current_state( TASK_RUNNING );
+	remove_wait_queue(&pCh->dss_now_wait, &wait);
+
+	if (signal_pending(current)) {
+		return -EINTR;
+	}
+#endif
+	return  ((pCh->dataSetOut & I2_RTS) ? TIOCM_RTS : 0)
+	      | ((pCh->dataSetOut & I2_DTR) ? TIOCM_DTR : 0)
+	      | ((pCh->dataSetIn  & I2_DCD) ? TIOCM_CAR : 0)
+	      | ((pCh->dataSetIn  & I2_RI)  ? TIOCM_RNG : 0)
+	      | ((pCh->dataSetIn  & I2_DSR) ? TIOCM_DSR : 0)
+	      | ((pCh->dataSetIn  & I2_CTS) ? TIOCM_CTS : 0);
+}
+
+static int ip2_tiocmset(struct tty_struct *tty, struct file *file,
+			unsigned int set, unsigned int clear)
+{
+	i2ChanStrPtr pCh = DevTable[tty->index];
+
+	if (pCh == NULL)
+		return -ENODEV;
+
+	if (set & TIOCM_RTS) {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_RTSUP);
+		pCh->dataSetOut |= I2_RTS;
+	}
+	if (set & TIOCM_DTR) {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DTRUP);
+		pCh->dataSetOut |= I2_DTR;
+	}
+
+	if (clear & TIOCM_RTS) {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_RTSDN);
+		pCh->dataSetOut &= ~I2_RTS;
+	}
+	if (clear & TIOCM_DTR) {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DTRDN);
+		pCh->dataSetOut &= ~I2_DTR;
+	}
+	serviceOutgoingFifo( pCh->pMyBord );
+	return 0;
+}
+
+/******************************************************************************/
+/* Function:   ip2_ioctl()                                                    */
+/* Parameters: Pointer to tty structure                                       */
+/*             Pointer to file structure                                      */
+/*             Command                                                        */
+/*             Argument                                                       */
+/* Returns:    Success or failure                                             */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static int
+ip2_ioctl ( PTTY tty, struct file *pFile, UINT cmd, ULONG arg )
+{
+	wait_queue_t wait;
+	i2ChanStrPtr pCh = DevTable[tty->index];
+	struct async_icount cprev, cnow;	/* kernel counter temps */
+	struct serial_icounter_struct __user *p_cuser;
+	int rc = 0;
+	unsigned long flags;
+	void __user *argp = (void __user *)arg;
+
+	if ( pCh == NULL ) {
+		return -ENODEV;
+	}
+
+	ip2trace (CHANN, ITRC_IOCTL, ITRC_ENTER, 2, cmd, arg );
+
+#ifdef IP2DEBUG_IOCTL
+	printk(KERN_DEBUG "IP2: ioctl cmd (%x), arg (%lx)\n", cmd, arg );
+#endif
+
+	switch(cmd) {
+	case TIOCGSERIAL:
+
+		ip2trace (CHANN, ITRC_IOCTL, 2, 1, rc );
+
+		rc = get_serial_info(pCh, argp);
+		if (rc)
+			return rc;
+		break;
+
+	case TIOCSSERIAL:
+
+		ip2trace (CHANN, ITRC_IOCTL, 3, 1, rc );
+
+		rc = set_serial_info(pCh, argp);
+		if (rc)
+			return rc;
+		break;
+
+	case TCXONC:
+		rc = tty_check_change(tty);
+		if (rc)
+			return rc;
+		switch (arg) {
+		case TCOOFF:
+			//return  -ENOIOCTLCMD;
+			break;
+		case TCOON:
+			//return  -ENOIOCTLCMD;
+			break;
+		case TCIOFF:
+			if (STOP_CHAR(tty) != __DISABLED_CHAR) {
+				i2QueueCommands( PTYPE_BYPASS, pCh, 100, 1,
+						CMD_XMIT_NOW(STOP_CHAR(tty)));
+			}
+			break;
+		case TCION:
+			if (START_CHAR(tty) != __DISABLED_CHAR) {
+				i2QueueCommands( PTYPE_BYPASS, pCh, 100, 1,
+						CMD_XMIT_NOW(START_CHAR(tty)));
+			}
+			break;
+		default:
+			return -EINVAL;
+		}
+		return 0;
+
+	case TCSBRK:   /* SVID version: non-zero arg --> no break */
+		rc = tty_check_change(tty);
+
+		ip2trace (CHANN, ITRC_IOCTL, 4, 1, rc );
+
+		if (!rc) {
+			ip2_wait_until_sent(tty,0);
+			if (!arg) {
+				i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_SEND_BRK(250));
+				serviceOutgoingFifo( pCh->pMyBord );
+			}
+		}
+		break;
+
+	case TCSBRKP:  /* support for POSIX tcsendbreak() */
+		rc = tty_check_change(tty);
+
+		ip2trace (CHANN, ITRC_IOCTL, 5, 1, rc );
+
+		if (!rc) {
+			ip2_wait_until_sent(tty,0);
+			i2QueueCommands(PTYPE_INLINE, pCh, 100, 1,
+				CMD_SEND_BRK(arg ? arg*100 : 250));
+			serviceOutgoingFifo ( pCh->pMyBord );	
+		}
+		break;
+
+	case TIOCGSOFTCAR:
+
+		ip2trace (CHANN, ITRC_IOCTL, 6, 1, rc );
+
+			rc = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *)argp);
+		if (rc)	
+			return rc;
+	break;
+
+	case TIOCSSOFTCAR:
+
+		ip2trace (CHANN, ITRC_IOCTL, 7, 1, rc );
+
+		rc = get_user(arg,(unsigned long __user *) argp);
+		if (rc) 
+			return rc;
+		tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL)
+					 | (arg ? CLOCAL : 0));
+		
+		break;
+
+	/*
+	 * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change - mask
+	 * passed in arg for lines of interest (use |'ed TIOCM_RNG/DSR/CD/CTS
+	 * for masking). Caller should use TIOCGICOUNT to see which one it was
+	 */
+	case TIOCMIWAIT:
+		save_flags(flags);cli();
+		cprev = pCh->icount;	 /* note the counters on entry */
+		restore_flags(flags);
+		i2QueueCommands(PTYPE_BYPASS, pCh, 100, 4, 
+						CMD_DCD_REP, CMD_CTS_REP, CMD_DSR_REP, CMD_RI_REP);
+		init_waitqueue_entry(&wait, current);
+		add_wait_queue(&pCh->delta_msr_wait, &wait);
+		set_current_state( TASK_INTERRUPTIBLE );
+
+		serviceOutgoingFifo( pCh->pMyBord );
+		for(;;) {
+			ip2trace (CHANN, ITRC_IOCTL, 10, 0 );
+
+			schedule();
+
+			ip2trace (CHANN, ITRC_IOCTL, 11, 0 );
+
+			/* see if a signal did it */
+			if (signal_pending(current)) {
+				rc = -ERESTARTSYS;
+				break;
+			}
+			save_flags(flags);cli();
+			cnow = pCh->icount; /* atomic copy */
+			restore_flags(flags);
+			if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+				cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+				rc =  -EIO; /* no change => rc */
+				break;
+			}
+			if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) ||
+			    ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) ||
+			    ((arg & TIOCM_CD)  && (cnow.dcd != cprev.dcd)) ||
+			    ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts)) ) {
+				rc =  0;
+				break;
+			}
+			cprev = cnow;
+		}
+		set_current_state( TASK_RUNNING );
+		remove_wait_queue(&pCh->delta_msr_wait, &wait);
+
+		i2QueueCommands(PTYPE_BYPASS, pCh, 100, 3, 
+						 CMD_CTS_NREP, CMD_DSR_NREP, CMD_RI_NREP);
+		if ( ! (pCh->flags	& ASYNC_CHECK_CD)) {
+			i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_DCD_NREP);
+		}
+		serviceOutgoingFifo( pCh->pMyBord );
+		return rc;
+		break;
+
+	/*
+	 * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+	 * Return: write counters to the user passed counter struct
+	 * NB: both 1->0 and 0->1 transitions are counted except for RI where
+	 * only 0->1 is counted. The controller is quite capable of counting
+	 * both, but this done to preserve compatibility with the standard
+	 * serial driver.
+	 */
+	case TIOCGICOUNT:
+		ip2trace (CHANN, ITRC_IOCTL, 11, 1, rc );
+
+		save_flags(flags);cli();
+		cnow = pCh->icount;
+		restore_flags(flags);
+		p_cuser = argp;
+		rc = put_user(cnow.cts, &p_cuser->cts);
+		rc = put_user(cnow.dsr, &p_cuser->dsr);
+		rc = put_user(cnow.rng, &p_cuser->rng);
+		rc = put_user(cnow.dcd, &p_cuser->dcd);
+		rc = put_user(cnow.rx, &p_cuser->rx);
+		rc = put_user(cnow.tx, &p_cuser->tx);
+		rc = put_user(cnow.frame, &p_cuser->frame);
+		rc = put_user(cnow.overrun, &p_cuser->overrun);
+		rc = put_user(cnow.parity, &p_cuser->parity);
+		rc = put_user(cnow.brk, &p_cuser->brk);
+		rc = put_user(cnow.buf_overrun, &p_cuser->buf_overrun);
+		break;
+
+	/*
+	 * The rest are not supported by this driver. By returning -ENOIOCTLCMD they
+	 * will be passed to the line discipline for it to handle.
+	 */
+	case TIOCSERCONFIG:
+	case TIOCSERGWILD:
+	case TIOCSERGETLSR:
+	case TIOCSERSWILD:
+	case TIOCSERGSTRUCT:
+	case TIOCSERGETMULTI:
+	case TIOCSERSETMULTI:
+
+	default:
+		ip2trace (CHANN, ITRC_IOCTL, 12, 0 );
+
+		rc =  -ENOIOCTLCMD;
+		break;
+	}
+
+	ip2trace (CHANN, ITRC_IOCTL, ITRC_RETURN, 0 );
+
+	return rc;
+}
+
+/******************************************************************************/
+/* Function:   GetSerialInfo()                                                */
+/* Parameters: Pointer to channel structure                                   */
+/*             Pointer to old termios structure                               */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/* This is to support the setserial command, and requires processing of the   */
+/* standard Linux serial structure.                                           */
+/******************************************************************************/
+static int
+get_serial_info ( i2ChanStrPtr pCh, struct serial_struct __user *retinfo )
+{
+	struct serial_struct tmp;
+
+	memset ( &tmp, 0, sizeof(tmp) );
+	tmp.type = pCh->pMyBord->channelBtypes.bid_value[(pCh->port_index & (IP2_PORTS_PER_BOARD-1))/16];
+	if (BID_HAS_654(tmp.type)) {
+		tmp.type = PORT_16650;
+	} else {
+		tmp.type = PORT_CIRRUS;
+	}
+	tmp.line = pCh->port_index;
+	tmp.port = pCh->pMyBord->i2eBase;
+	tmp.irq  = ip2config.irq[pCh->port_index/64];
+	tmp.flags = pCh->flags;
+	tmp.baud_base = pCh->BaudBase;
+	tmp.close_delay = pCh->ClosingDelay;
+	tmp.closing_wait = pCh->ClosingWaitTime;
+	tmp.custom_divisor = pCh->BaudDivisor;
+   	return copy_to_user(retinfo,&tmp,sizeof(*retinfo));
+}
+
+/******************************************************************************/
+/* Function:   SetSerialInfo()                                                */
+/* Parameters: Pointer to channel structure                                   */
+/*             Pointer to old termios structure                               */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/* This function provides support for setserial, which uses the TIOCSSERIAL   */
+/* ioctl. Not all setserial parameters are relevant. If the user attempts to  */
+/* change the IRQ, address or type of the port the ioctl fails.               */
+/******************************************************************************/
+static int
+set_serial_info( i2ChanStrPtr pCh, struct serial_struct __user *new_info )
+{
+	struct serial_struct ns;
+	int   old_flags, old_baud_divisor;
+
+	if (copy_from_user(&ns, new_info, sizeof (ns)))
+		return -EFAULT;
+
+	/*
+	 * We don't allow setserial to change IRQ, board address, type or baud
+	 * base. Also line nunber as such is meaningless but we use it for our
+	 * array index so it is fixed also.
+	 */
+	if ( (ns.irq  	    != ip2config.irq[pCh->port_index])
+	    || ((int) ns.port      != ((int) (pCh->pMyBord->i2eBase)))
+	    || (ns.baud_base != pCh->BaudBase)
+	    || (ns.line      != pCh->port_index) ) {
+		return -EINVAL;
+	}
+
+	old_flags = pCh->flags;
+	old_baud_divisor = pCh->BaudDivisor;
+
+	if ( !capable(CAP_SYS_ADMIN) ) {
+		if ( ( ns.close_delay != pCh->ClosingDelay ) ||
+		    ( (ns.flags & ~ASYNC_USR_MASK) !=
+		      (pCh->flags & ~ASYNC_USR_MASK) ) ) {
+			return -EPERM;
+		}
+
+		pCh->flags = (pCh->flags & ~ASYNC_USR_MASK) |
+			       (ns.flags & ASYNC_USR_MASK);
+		pCh->BaudDivisor = ns.custom_divisor;
+	} else {
+		pCh->flags = (pCh->flags & ~ASYNC_FLAGS) |
+			       (ns.flags & ASYNC_FLAGS);
+		pCh->BaudDivisor = ns.custom_divisor;
+		pCh->ClosingDelay = ns.close_delay * HZ/100;
+		pCh->ClosingWaitTime = ns.closing_wait * HZ/100;
+	}
+
+	if ( ( (old_flags & ASYNC_SPD_MASK) != (pCh->flags & ASYNC_SPD_MASK) )
+	    || (old_baud_divisor != pCh->BaudDivisor) ) {
+		// Invalidate speed and reset parameters
+		set_params( pCh, NULL );
+	}
+
+	return 0;
+}
+
+/******************************************************************************/
+/* Function:   ip2_set_termios()                                              */
+/* Parameters: Pointer to tty structure                                       */
+/*             Pointer to old termios structure                               */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_set_termios( PTTY tty, struct termios *old_termios )
+{
+	i2ChanStrPtr pCh = (i2ChanStrPtr)tty->driver_data;
+
+#ifdef IP2DEBUG_IOCTL
+	printk (KERN_DEBUG "IP2: set termios %p\n", old_termios );
+#endif
+
+	set_params( pCh, old_termios );
+}
+
+/******************************************************************************/
+/* Function:   ip2_set_line_discipline()                                      */
+/* Parameters: Pointer to tty structure                                       */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:  Does nothing                                                 */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static void
+ip2_set_line_discipline ( PTTY tty )
+{
+#ifdef IP2DEBUG_IOCTL
+	printk (KERN_DEBUG "IP2: set line discipline\n" );
+#endif
+
+	ip2trace (((i2ChanStrPtr)tty->driver_data)->port_index, ITRC_IOCTL, 16, 0 );
+
+}
+
+/******************************************************************************/
+/* Function:   SetLine Characteristics()                                      */
+/* Parameters: Pointer to channel structure                                   */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/* This routine is called to update the channel structure with the new line   */
+/* characteristics, and send the appropriate commands to the board when they  */
+/* change.                                                                    */
+/******************************************************************************/
+static void
+set_params( i2ChanStrPtr pCh, struct termios *o_tios )
+{
+	tcflag_t cflag, iflag, lflag;
+	char stop_char, start_char;
+	struct termios dummy;
+
+	lflag = pCh->pTTY->termios->c_lflag;
+	cflag = pCh->pTTY->termios->c_cflag;
+	iflag = pCh->pTTY->termios->c_iflag;
+
+	if (o_tios == NULL) {
+		dummy.c_lflag = ~lflag;
+		dummy.c_cflag = ~cflag;
+		dummy.c_iflag = ~iflag;
+		o_tios = &dummy;
+	}
+
+	{
+		switch ( cflag & CBAUD ) {
+		case B0:
+			i2QueueCommands( PTYPE_BYPASS, pCh, 100, 2, CMD_RTSDN, CMD_DTRDN);
+			pCh->dataSetOut &= ~(I2_DTR | I2_RTS);
+			i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_PAUSE(25));
+			pCh->pTTY->termios->c_cflag |= (CBAUD & o_tios->c_cflag);
+			goto service_it;
+			break;
+		case B38400:
+			/*
+			 * This is the speed that is overloaded with all the other high
+			 * speeds, depending upon the flag settings.
+			 */
+			if ( ( pCh->flags & ASYNC_SPD_MASK ) == ASYNC_SPD_HI ) {
+				pCh->speed = CBR_57600;
+			} else if ( (pCh->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI ) {
+				pCh->speed = CBR_115200;
+			} else if ( (pCh->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST ) {
+				pCh->speed = CBR_C1;
+			} else {
+				pCh->speed = CBR_38400;
+			}
+			break;
+		case B50:      pCh->speed = CBR_50;      break;
+		case B75:      pCh->speed = CBR_75;      break;
+		case B110:     pCh->speed = CBR_110;     break;
+		case B134:     pCh->speed = CBR_134;     break;
+		case B150:     pCh->speed = CBR_150;     break;
+		case B200:     pCh->speed = CBR_200;     break;
+		case B300:     pCh->speed = CBR_300;     break;
+		case B600:     pCh->speed = CBR_600;     break;
+		case B1200:    pCh->speed = CBR_1200;    break;
+		case B1800:    pCh->speed = CBR_1800;    break;
+		case B2400:    pCh->speed = CBR_2400;    break;
+		case B4800:    pCh->speed = CBR_4800;    break;
+		case B9600:    pCh->speed = CBR_9600;    break;
+		case B19200:   pCh->speed = CBR_19200;   break;
+		case B57600:   pCh->speed = CBR_57600;   break;
+		case B115200:  pCh->speed = CBR_115200;  break;
+		case B153600:  pCh->speed = CBR_153600;  break;
+		case B230400:  pCh->speed = CBR_230400;  break;
+		case B307200:  pCh->speed = CBR_307200;  break;
+		case B460800:  pCh->speed = CBR_460800;  break;
+		case B921600:  pCh->speed = CBR_921600;  break;
+		default:       pCh->speed = CBR_9600;    break;
+		}
+		if ( pCh->speed == CBR_C1 ) {
+			// Process the custom speed parameters.
+			int bps = pCh->BaudBase / pCh->BaudDivisor;
+			if ( bps == 921600 ) {
+				pCh->speed = CBR_921600;
+			} else {
+				bps = bps/10;
+				i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_BAUD_DEF1(bps) );
+			}
+		}
+		i2QueueCommands( PTYPE_INLINE, pCh, 100, 1, CMD_SETBAUD(pCh->speed));
+		
+		i2QueueCommands ( PTYPE_INLINE, pCh, 100, 2, CMD_DTRUP, CMD_RTSUP);
+		pCh->dataSetOut |= (I2_DTR | I2_RTS);
+	}
+	if ( (CSTOPB & cflag) ^ (CSTOPB & o_tios->c_cflag)) 
+	{
+		i2QueueCommands ( PTYPE_INLINE, pCh, 100, 1, 
+			CMD_SETSTOP( ( cflag & CSTOPB ) ? CST_2 : CST_1));
+	}
+	if (((PARENB|PARODD) & cflag) ^ ((PARENB|PARODD) & o_tios->c_cflag)) 
+	{
+		i2QueueCommands ( PTYPE_INLINE, pCh, 100, 1,
+			CMD_SETPAR( 
+				(cflag & PARENB ?  (cflag & PARODD ? CSP_OD : CSP_EV) : CSP_NP)
+			)
+		);
+	}
+	/* byte size and parity */
+	if ( (CSIZE & cflag)^(CSIZE & o_tios->c_cflag)) 
+	{
+		int datasize;
+		switch ( cflag & CSIZE ) {
+		case CS5: datasize = CSZ_5; break;
+		case CS6: datasize = CSZ_6; break;
+		case CS7: datasize = CSZ_7; break;
+		case CS8: datasize = CSZ_8; break;
+		default:  datasize = CSZ_5; break;	/* as per serial.c */
+		}
+		i2QueueCommands ( PTYPE_INLINE, pCh, 100, 1, CMD_SETBITS(datasize) );
+	}
+	/* Process CTS flow control flag setting */
+	if ( (cflag & CRTSCTS) ) {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100,
+						2, CMD_CTSFL_ENAB, CMD_RTSFL_ENAB);
+	} else {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100,
+						2, CMD_CTSFL_DSAB, CMD_RTSFL_DSAB);
+	}
+	//
+	// Process XON/XOFF flow control flags settings
+	//
+	stop_char = STOP_CHAR(pCh->pTTY);
+	start_char = START_CHAR(pCh->pTTY);
+
+	//////////// can't be \000
+	if (stop_char == __DISABLED_CHAR ) 
+	{
+		stop_char = ~__DISABLED_CHAR; 
+	}
+	if (start_char == __DISABLED_CHAR ) 
+	{
+		start_char = ~__DISABLED_CHAR;
+	}
+	/////////////////////////////////
+
+	if ( o_tios->c_cc[VSTART] != start_char ) 
+	{
+		i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_DEF_IXON(start_char));
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DEF_OXON(start_char));
+	}
+	if ( o_tios->c_cc[VSTOP] != stop_char ) 
+	{
+		 i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, CMD_DEF_IXOFF(stop_char));
+		 i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DEF_OXOFF(stop_char));
+	}
+	if (stop_char == __DISABLED_CHAR ) 
+	{
+		stop_char = ~__DISABLED_CHAR;  //TEST123
+		goto no_xoff;
+	}
+	if ((iflag & (IXOFF))^(o_tios->c_iflag & (IXOFF))) 
+	{
+		if ( iflag & IXOFF ) {	// Enable XOFF output flow control
+			i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_OXON_OPT(COX_XON));
+		} else {	// Disable XOFF output flow control
+no_xoff:
+			i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_OXON_OPT(COX_NONE));
+		}
+	}
+	if (start_char == __DISABLED_CHAR ) 
+	{
+		goto no_xon;
+	}
+	if ((iflag & (IXON|IXANY)) ^ (o_tios->c_iflag & (IXON|IXANY))) 
+	{
+		if ( iflag & IXON ) {
+			if ( iflag & IXANY ) { // Enable XON/XANY output flow control
+				i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_IXON_OPT(CIX_XANY));
+			} else { // Enable XON output flow control
+				i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_IXON_OPT(CIX_XON));
+			}
+		} else { // Disable XON output flow control
+no_xon:
+			i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_IXON_OPT(CIX_NONE));
+		}
+	}
+	if ( (iflag & ISTRIP) ^ ( o_tios->c_iflag & (ISTRIP)) ) 
+	{
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, 
+				CMD_ISTRIP_OPT((iflag & ISTRIP ? 1 : 0)));
+	}
+	if ( (iflag & INPCK) ^ ( o_tios->c_iflag & (INPCK)) ) 
+	{
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, 
+				CMD_PARCHK((iflag & INPCK) ? CPK_ENAB : CPK_DSAB));
+	}
+
+	if ( (iflag & (IGNBRK|PARMRK|BRKINT|IGNPAR)) 
+			^	( o_tios->c_iflag & (IGNBRK|PARMRK|BRKINT|IGNPAR)) ) 
+	{
+		char brkrpt = 0;
+		char parrpt = 0;
+
+		if ( iflag & IGNBRK ) { /* Ignore breaks altogether */
+			/* Ignore breaks altogether */
+			i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_BRK_NREP);
+		} else {
+			if ( iflag & BRKINT ) {
+				if ( iflag & PARMRK ) {
+					brkrpt = 0x0a;	// exception an inline triple
+				} else {
+					brkrpt = 0x1a;	// exception and NULL
+				}
+				brkrpt |= 0x04;	// flush input
+			} else {
+				if ( iflag & PARMRK ) {
+					brkrpt = 0x0b;	//POSIX triple \0377 \0 \0
+				} else {
+					brkrpt = 0x01;	// Null only
+				}
+			}
+			i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_BRK_REP(brkrpt));
+		} 
+
+		if (iflag & IGNPAR) {
+			parrpt = 0x20;
+													/* would be 2 for not cirrus bug */
+													/* would be 0x20 cept for cirrus bug */
+		} else {
+			if ( iflag & PARMRK ) {
+				/*
+				 * Replace error characters with 3-byte sequence (\0377,\0,char)
+				 */
+				parrpt = 0x04 ;
+				i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_ISTRIP_OPT((char)0));
+			} else {
+				parrpt = 0x03;
+			} 
+		}
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_SET_ERROR(parrpt));
+	}
+	if (cflag & CLOCAL) {
+		// Status reporting fails for DCD if this is off
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DCD_NREP);
+		pCh->flags &= ~ASYNC_CHECK_CD;
+	} else {
+		i2QueueCommands(PTYPE_INLINE, pCh, 100, 1, CMD_DCD_REP);
+		pCh->flags	|= ASYNC_CHECK_CD;
+	}
+
+#ifdef XXX
+do_flags_thing:	// This is a test, we don't do the flags thing
+	
+	if ( (cflag & CRTSCTS) ) {
+		cflag |= 014000000000;
+	}
+	i2QueueCommands(PTYPE_BYPASS, pCh, 100, 1, 
+				CMD_UNIX_FLAGS(iflag,cflag,lflag));
+#endif
+		
+service_it:
+	i2DrainOutput( pCh, 100 );		
+}
+
+/******************************************************************************/
+/* IPL Device Section                                                         */
+/******************************************************************************/
+
+/******************************************************************************/
+/* Function:   ip2_ipl_read()                                                  */
+/* Parameters: Pointer to device inode                                        */
+/*             Pointer to file structure                                      */
+/*             Pointer to data                                                */
+/*             Number of bytes to read                                        */
+/* Returns:    Success or failure                                             */
+/*                                                                            */
+/* Description:   Ugly                                                        */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+
+static 
+ssize_t
+ip2_ipl_read(struct file *pFile, char __user *pData, size_t count, loff_t *off )
+{
+	unsigned int minor = iminor(pFile->f_dentry->d_inode);
+	int rc = 0;
+
+#ifdef IP2DEBUG_IPL
+	printk (KERN_DEBUG "IP2IPL: read %p, %d bytes\n", pData, count );
+#endif
+
+	switch( minor ) {
+	case 0:	    // IPL device
+		rc = -EINVAL;
+		break;
+	case 1:	    // Status dump
+		rc = -EINVAL;
+		break;
+	case 2:	    // Ping device
+		rc = -EINVAL;
+		break;
+	case 3:	    // Trace device
+		rc = DumpTraceBuffer ( pData, count );
+		break;
+	case 4:	    // Trace device
+		rc = DumpFifoBuffer ( pData, count );
+		break;
+	default:
+		rc = -ENODEV;
+		break;
+	}
+	return rc;
+}
+
+static int
+DumpFifoBuffer ( char __user *pData, int count )
+{
+#ifdef DEBUG_FIFO
+	int rc;
+	rc = copy_to_user(pData, DBGBuf, count);
+
+	printk(KERN_DEBUG "Last index %d\n", I );
+
+	return count;
+#endif	/* DEBUG_FIFO */
+	return 0;
+}
+
+static int
+DumpTraceBuffer ( char __user *pData, int count )
+{
+#ifdef IP2DEBUG_TRACE
+	int rc;
+	int dumpcount;
+	int chunk;
+	int *pIndex = (int __user *)pData;
+
+	if ( count < (sizeof(int) * 6) ) {
+		return -EIO;
+	}
+	rc = put_user(tracewrap, pIndex );
+	rc = put_user(TRACEMAX, ++pIndex );
+	rc = put_user(tracestrip, ++pIndex );
+	rc = put_user(tracestuff, ++pIndex );
+	pData += sizeof(int) * 6;
+	count -= sizeof(int) * 6;
+
+	dumpcount = tracestuff - tracestrip;
+	if ( dumpcount < 0 ) {
+		dumpcount += TRACEMAX;
+	}
+	if ( dumpcount > count ) {
+		dumpcount = count;
+	}
+	chunk = TRACEMAX - tracestrip;
+	if ( dumpcount > chunk ) {
+		rc = copy_to_user(pData, &tracebuf[tracestrip],
+			      chunk * sizeof(tracebuf[0]) );
+		pData += chunk * sizeof(tracebuf[0]);
+		tracestrip = 0;
+		chunk = dumpcount - chunk;
+	} else {
+		chunk = dumpcount;
+	}
+	rc = copy_to_user(pData, &tracebuf[tracestrip],
+		      chunk * sizeof(tracebuf[0]) );
+	tracestrip += chunk;
+	tracewrap = 0;
+
+	rc = put_user(tracestrip, ++pIndex );
+	rc = put_user(tracestuff, ++pIndex );
+
+	return dumpcount;
+#else
+	return 0;
+#endif
+}
+
+/******************************************************************************/
+/* Function:   ip2_ipl_write()                                                 */
+/* Parameters:                                                                */
+/*             Pointer to file structure                                      */
+/*             Pointer to data                                                */
+/*             Number of bytes to write                                       */
+/* Returns:    Success or failure                                             */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static ssize_t
+ip2_ipl_write(struct file *pFile, const char __user *pData, size_t count, loff_t *off)
+{
+#ifdef IP2DEBUG_IPL
+	printk (KERN_DEBUG "IP2IPL: write %p, %d bytes\n", pData, count );
+#endif
+	return 0;
+}
+
+/******************************************************************************/
+/* Function:   ip2_ipl_ioctl()                                                */
+/* Parameters: Pointer to device inode                                        */
+/*             Pointer to file structure                                      */
+/*             Command                                                        */
+/*             Argument                                                       */
+/* Returns:    Success or failure                                             */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static int
+ip2_ipl_ioctl ( struct inode *pInode, struct file *pFile, UINT cmd, ULONG arg )
+{
+	unsigned int iplminor = iminor(pInode);
+	int rc = 0;
+	void __user *argp = (void __user *)arg;
+	ULONG __user *pIndex = argp;
+	i2eBordStrPtr pB = i2BoardPtrTable[iplminor / 4];
+	i2ChanStrPtr pCh;
+
+#ifdef IP2DEBUG_IPL
+	printk (KERN_DEBUG "IP2IPL: ioctl cmd %d, arg %ld\n", cmd, arg );
+#endif
+
+	switch ( iplminor ) {
+	case 0:	    // IPL device
+		rc = -EINVAL;
+		break;
+	case 1:	    // Status dump
+	case 5:
+	case 9:
+	case 13:
+		switch ( cmd ) {
+		case 64:	/* Driver - ip2stat */
+			rc = put_user(ip2_tty_driver->refcount, pIndex++ );
+			rc = put_user(irq_counter, pIndex++  );
+			rc = put_user(bh_counter, pIndex++  );
+			break;
+
+		case 65:	/* Board  - ip2stat */
+			if ( pB ) {
+				rc = copy_to_user(argp, pB, sizeof(i2eBordStr));
+				rc = put_user(INB(pB->i2eStatus),
+					(ULONG __user *)(arg + (ULONG)(&pB->i2eStatus) - (ULONG)pB ) );
+			} else {
+				rc = -ENODEV;
+			}
+			break;
+
+		default:
+			if (cmd < IP2_MAX_PORTS) {
+				pCh = DevTable[cmd];
+				if ( pCh )
+				{
+					rc = copy_to_user(argp, pCh, sizeof(i2ChanStr));
+				} else {
+					rc = -ENODEV;
+				}
+			} else {
+				rc = -EINVAL;
+			}
+		}
+		break;
+
+	case 2:	    // Ping device
+		rc = -EINVAL;
+		break;
+	case 3:	    // Trace device
+		if ( cmd == 1 ) {
+			rc = put_user(iiSendPendingMail, pIndex++ );
+			rc = put_user(i2InitChannels, pIndex++ );
+			rc = put_user(i2QueueNeeds, pIndex++ );
+			rc = put_user(i2QueueCommands, pIndex++ );
+			rc = put_user(i2GetStatus, pIndex++ );
+			rc = put_user(i2Input, pIndex++ );
+			rc = put_user(i2InputFlush, pIndex++ );
+			rc = put_user(i2Output, pIndex++ );
+			rc = put_user(i2FlushOutput, pIndex++ );
+			rc = put_user(i2DrainWakeup, pIndex++ );
+			rc = put_user(i2DrainOutput, pIndex++ );
+			rc = put_user(i2OutputFree, pIndex++ );
+			rc = put_user(i2StripFifo, pIndex++ );
+			rc = put_user(i2StuffFifoBypass, pIndex++ );
+			rc = put_user(i2StuffFifoFlow, pIndex++ );
+			rc = put_user(i2StuffFifoInline, pIndex++ );
+			rc = put_user(i2ServiceBoard, pIndex++ );
+			rc = put_user(serviceOutgoingFifo, pIndex++ );
+			// rc = put_user(ip2_init, pIndex++ );
+			rc = put_user(ip2_init_board, pIndex++ );
+			rc = put_user(find_eisa_board, pIndex++ );
+			rc = put_user(set_irq, pIndex++ );
+			rc = put_user(ip2_interrupt, pIndex++ );
+			rc = put_user(ip2_poll, pIndex++ );
+			rc = put_user(service_all_boards, pIndex++ );
+			rc = put_user(do_input, pIndex++ );
+			rc = put_user(do_status, pIndex++ );
+#ifndef IP2DEBUG_OPEN
+			rc = put_user(0, pIndex++ );
+#else
+			rc = put_user(open_sanity_check, pIndex++ );
+#endif
+			rc = put_user(ip2_open, pIndex++ );
+			rc = put_user(ip2_close, pIndex++ );
+			rc = put_user(ip2_hangup, pIndex++ );
+			rc = put_user(ip2_write, pIndex++ );
+			rc = put_user(ip2_putchar, pIndex++ );
+			rc = put_user(ip2_flush_chars, pIndex++ );
+			rc = put_user(ip2_write_room, pIndex++ );
+			rc = put_user(ip2_chars_in_buf, pIndex++ );
+			rc = put_user(ip2_flush_buffer, pIndex++ );
+
+			//rc = put_user(ip2_wait_until_sent, pIndex++ );
+			rc = put_user(0, pIndex++ );
+
+			rc = put_user(ip2_throttle, pIndex++ );
+			rc = put_user(ip2_unthrottle, pIndex++ );
+			rc = put_user(ip2_ioctl, pIndex++ );
+			rc = put_user(0, pIndex++ );
+			rc = put_user(get_serial_info, pIndex++ );
+			rc = put_user(set_serial_info, pIndex++ );
+			rc = put_user(ip2_set_termios, pIndex++ );
+			rc = put_user(ip2_set_line_discipline, pIndex++ );
+			rc = put_user(set_params, pIndex++ );
+		} else {
+			rc = -EINVAL;
+		}
+
+		break;
+
+	default:
+		rc = -ENODEV;
+		break;
+	}
+	return rc;
+}
+
+/******************************************************************************/
+/* Function:   ip2_ipl_open()                                                 */
+/* Parameters: Pointer to device inode                                        */
+/*             Pointer to file structure                                      */
+/* Returns:    Success or failure                                             */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+static int
+ip2_ipl_open( struct inode *pInode, struct file *pFile )
+{
+	unsigned int iplminor = iminor(pInode);
+	i2eBordStrPtr pB;
+	i2ChanStrPtr  pCh;
+
+#ifdef IP2DEBUG_IPL
+	printk (KERN_DEBUG "IP2IPL: open\n" );
+#endif
+
+	switch(iplminor) {
+	// These are the IPL devices
+	case 0:
+	case 4:
+	case 8:
+	case 12:
+		break;
+
+	// These are the status devices
+	case 1:
+	case 5:
+	case 9:
+	case 13:
+		break;
+
+	// These are the debug devices
+	case 2:
+	case 6:
+	case 10:
+	case 14:
+		pB = i2BoardPtrTable[iplminor / 4];
+		pCh = (i2ChanStrPtr) pB->i2eChannelPtr;
+		break;
+
+	// This is the trace device
+	case 3:
+		break;
+	}
+	return 0;
+}
+/******************************************************************************/
+/* Function:   ip2_read_procmem                                               */
+/* Parameters:                                                                */
+/*                                                                            */
+/* Returns: Length of output                                                  */
+/*                                                                            */
+/* Description:                                                               */
+/*   Supplies some driver operating parameters                                */
+/*	Not real useful unless your debugging the fifo							  */
+/*                                                                            */
+/******************************************************************************/
+
+#define LIMIT  (PAGE_SIZE - 120)
+
+static int
+ip2_read_procmem(char *buf, char **start, off_t offset, int len)
+{
+	i2eBordStrPtr  pB;
+	i2ChanStrPtr  pCh;
+	PTTY tty;
+	int i;
+
+	len = 0;
+
+#define FMTLINE	"%3d: 0x%08x 0x%08x 0%011o 0%011o\n"
+#define FMTLIN2	"     0x%04x 0x%04x tx flow 0x%x\n"
+#define FMTLIN3	"     0x%04x 0x%04x rc flow\n"
+
+	len += sprintf(buf+len,"\n");
+
+	for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		pB = i2BoardPtrTable[i];
+		if ( pB ) {
+			len += sprintf(buf+len,"board %d:\n",i);
+			len += sprintf(buf+len,"\tFifo rem: %d mty: %x outM %x\n",
+				pB->i2eFifoRemains,pB->i2eWaitingForEmptyFifo,pB->i2eOutMailWaiting);
+		}
+	}
+
+	len += sprintf(buf+len,"#: tty flags, port flags,     cflags,     iflags\n");
+	for (i=0; i < IP2_MAX_PORTS; i++) {
+		if (len > LIMIT)
+			break;
+		pCh = DevTable[i];
+		if (pCh) {
+			tty = pCh->pTTY;
+			if (tty && tty->count) {
+				len += sprintf(buf+len,FMTLINE,i,(int)tty->flags,pCh->flags,
+									tty->termios->c_cflag,tty->termios->c_iflag);
+
+				len += sprintf(buf+len,FMTLIN2,
+						pCh->outfl.asof,pCh->outfl.room,pCh->channelNeeds);
+				len += sprintf(buf+len,FMTLIN3,pCh->infl.asof,pCh->infl.room);
+			}
+		}
+	}
+	return len;
+}
+
+/*
+ * This is the handler for /proc/tty/driver/ip2
+ *
+ * This stretch of code has been largely plagerized from at least three
+ * different sources including ip2mkdev.c and a couple of other drivers.
+ * The bugs are all mine.  :-)	=mhw=
+ */
+static int ip2_read_proc(char *page, char **start, off_t off,
+				int count, int *eof, void *data)
+{
+	int	i, j, box;
+	int	len = 0;
+	int	boxes = 0;
+	int	ports = 0;
+	int	tports = 0;
+	off_t	begin = 0;
+	i2eBordStrPtr  pB;
+
+	len += sprintf(page, "ip2info: 1.0 driver: %s\n", pcVersion );
+	len += sprintf(page+len, "Driver: SMajor=%d CMajor=%d IMajor=%d MaxBoards=%d MaxBoxes=%d MaxPorts=%d\n",
+			IP2_TTY_MAJOR, IP2_CALLOUT_MAJOR, IP2_IPL_MAJOR,
+			IP2_MAX_BOARDS, ABS_MAX_BOXES, ABS_BIGGEST_BOX);
+
+	for( i = 0; i < IP2_MAX_BOARDS; ++i ) {
+		/* This need to be reset for a board by board count... */
+		boxes = 0;
+		pB = i2BoardPtrTable[i];
+		if( pB ) {
+			switch( pB->i2ePom.e.porID & ~POR_ID_RESERVED ) 
+			{
+			case POR_ID_FIIEX:
+				len += sprintf( page+len, "Board %d: EX ports=", i );
+				for( box = 0; box < ABS_MAX_BOXES; ++box )
+				{
+					ports = 0;
+
+					if( pB->i2eChannelMap[box] != 0 ) ++boxes;
+					for( j = 0; j < ABS_BIGGEST_BOX; ++j ) 
+					{
+						if( pB->i2eChannelMap[box] & 1<< j ) {
+							++ports;
+						}
+					}
+					len += sprintf( page+len, "%d,", ports );
+					tports += ports;
+				}
+
+				--len;	/* Backup over that last comma */
+
+				len += sprintf( page+len, " boxes=%d width=%d", boxes, pB->i2eDataWidth16 ? 16 : 8 );
+				break;
+
+			case POR_ID_II_4:
+				len += sprintf(page+len, "Board %d: ISA-4 ports=4 boxes=1", i );
+				tports = ports = 4;
+				break;
+
+			case POR_ID_II_8:
+				len += sprintf(page+len, "Board %d: ISA-8-std ports=8 boxes=1", i );
+				tports = ports = 8;
+				break;
+
+			case POR_ID_II_8R:
+				len += sprintf(page+len, "Board %d: ISA-8-RJ11 ports=8 boxes=1", i );
+				tports = ports = 8;
+				break;
+
+			default:
+				len += sprintf(page+len, "Board %d: unknown", i );
+				/* Don't try and probe for minor numbers */
+				tports = ports = 0;
+			}
+
+		} else {
+			/* Don't try and probe for minor numbers */
+			len += sprintf(page+len, "Board %d: vacant", i );
+			tports = ports = 0;
+		}
+
+		if( tports ) {
+			len += sprintf(page+len, " minors=" );
+
+			for ( box = 0; box < ABS_MAX_BOXES; ++box )
+			{
+				for ( j = 0; j < ABS_BIGGEST_BOX; ++j )
+				{
+					if ( pB->i2eChannelMap[box] & (1 << j) )
+					{
+						len += sprintf (page+len,"%d,",
+							j + ABS_BIGGEST_BOX *
+							(box+i*ABS_MAX_BOXES));
+					}
+				}
+			}
+
+			page[ len - 1 ] = '\n';	/* Overwrite that last comma */
+		} else {
+			len += sprintf (page+len,"\n" );
+		}
+
+		if (len+begin > off+count)
+			break;
+		if (len+begin < off) {
+			begin += len;
+			len = 0;
+		}
+	}
+
+	if (i >= IP2_MAX_BOARDS)
+		*eof = 1;
+	if (off >= len+begin)
+		return 0;
+
+	*start = page + (off-begin);
+	return ((count < begin+len-off) ? count : begin+len-off);
+ }
+ 
+/******************************************************************************/
+/* Function:   ip2trace()                                                     */
+/* Parameters: Value to add to trace buffer                                   */
+/* Returns:    Nothing                                                        */
+/*                                                                            */
+/* Description:                                                               */
+/*                                                                            */
+/*                                                                            */
+/******************************************************************************/
+#ifdef IP2DEBUG_TRACE
+void
+ip2trace (unsigned short pn, unsigned char cat, unsigned char label, unsigned long codes, ...)
+{
+	long flags;
+	unsigned long *pCode = &codes;
+	union ip2breadcrumb bc;
+	i2ChanStrPtr  pCh;
+
+
+	tracebuf[tracestuff++] = jiffies;
+	if ( tracestuff == TRACEMAX ) {
+		tracestuff = 0;
+	}
+	if ( tracestuff == tracestrip ) {
+		if ( ++tracestrip == TRACEMAX ) {
+			tracestrip = 0;
+		}
+		++tracewrap;
+	}
+
+	bc.hdr.port  = 0xff & pn;
+	bc.hdr.cat   = cat;
+	bc.hdr.codes = (unsigned char)( codes & 0xff );
+	bc.hdr.label = label;
+	tracebuf[tracestuff++] = bc.value;
+
+	for (;;) {
+		if ( tracestuff == TRACEMAX ) {
+			tracestuff = 0;
+		}
+		if ( tracestuff == tracestrip ) {
+			if ( ++tracestrip == TRACEMAX ) {
+				tracestrip = 0;
+			}
+			++tracewrap;
+		}
+
+		if ( !codes-- )
+			break;
+
+		tracebuf[tracestuff++] = *++pCode;
+	}
+}
+#endif
+
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/Kconfig b/drivers/char/ipmi/Kconfig
new file mode 100644
index 0000000..a6dcb29
--- /dev/null
+++ b/drivers/char/ipmi/Kconfig
@@ -0,0 +1,67 @@
+#
+# IPMI device configuration
+#
+
+menu "IPMI"
+config IPMI_HANDLER
+       tristate 'IPMI top-level message handler'
+       help
+         This enables the central IPMI message handler, required for IPMI
+	 to work.
+
+         IPMI is a standard for managing sensors (temperature,
+         voltage, etc.) in a system.
+
+         See <file:Documentation/IPMI.txt> for more details on the driver.
+
+	 If unsure, say N.
+
+config IPMI_PANIC_EVENT
+       bool 'Generate a panic event to all BMCs on a panic'
+       depends on IPMI_HANDLER
+       help
+         When a panic occurs, this will cause the IPMI message handler to
+	 generate an IPMI event describing the panic to each interface
+	 registered with the message handler.
+
+config IPMI_PANIC_STRING
+	bool 'Generate OEM events containing the panic string'
+	depends on IPMI_PANIC_EVENT
+	help
+	  When a panic occurs, this will cause the IPMI message handler to
+	  generate IPMI OEM type f0 events holding the IPMB address of the
+	  panic generator (byte 4 of the event), a sequence number for the
+	  string (byte 5 of the event) and part of the string (the rest of the
+	  event).  Bytes 1, 2, and 3 are the normal usage for an OEM event.
+	  You can fetch these events and use the sequence numbers to piece the
+	  string together.
+
+config IPMI_DEVICE_INTERFACE
+       tristate 'Device interface for IPMI'
+       depends on IPMI_HANDLER
+       help
+         This provides an IOCTL interface to the IPMI message handler so
+	 userland processes may use IPMI.  It supports poll() and select().
+
+config IPMI_SI
+       tristate 'IPMI System Interface handler'
+       depends on IPMI_HANDLER
+       help
+         Provides a driver for System Interfaces (KCS, SMIC, BT).
+	 Currently, only KCS and SMIC are supported.  If
+	 you are using IPMI, you should probably say "y" here.
+
+config IPMI_WATCHDOG
+       tristate 'IPMI Watchdog Timer'
+       depends on IPMI_HANDLER
+       help
+         This enables the IPMI watchdog timer.
+
+config IPMI_POWEROFF
+       tristate 'IPMI Poweroff'
+       depends on IPMI_HANDLER
+       help
+         This enables a function to power off the system with IPMI if
+	 the IPMI management controller is capable of this.
+
+endmenu
diff --git a/drivers/char/ipmi/Makefile b/drivers/char/ipmi/Makefile
new file mode 100644
index 0000000..553f0a4
--- /dev/null
+++ b/drivers/char/ipmi/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for the ipmi drivers.
+#
+
+ipmi_si-objs := ipmi_si_intf.o ipmi_kcs_sm.o ipmi_smic_sm.o ipmi_bt_sm.o
+
+obj-$(CONFIG_IPMI_HANDLER) += ipmi_msghandler.o
+obj-$(CONFIG_IPMI_DEVICE_INTERFACE) += ipmi_devintf.o
+obj-$(CONFIG_IPMI_SI) += ipmi_si.o
+obj-$(CONFIG_IPMI_WATCHDOG) += ipmi_watchdog.o
+obj-$(CONFIG_IPMI_POWEROFF) += ipmi_poweroff.o
+
+ipmi_si.o:	$(ipmi_si-objs)
+	$(LD) -r -o $@ $(ipmi_si-objs)
+
diff --git a/drivers/char/ipmi/ipmi_bt_sm.c b/drivers/char/ipmi/ipmi_bt_sm.c
new file mode 100644
index 0000000..225b330
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_bt_sm.c
@@ -0,0 +1,513 @@
+/*
+ *  ipmi_bt_sm.c
+ *
+ *  The state machine for an Open IPMI BT sub-driver under ipmi_si.c, part
+ *  of the driver architecture at http://sourceforge.net/project/openipmi
+ *
+ *  Author:	Rocky Craig <first.last@hp.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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.  */
+
+#include <linux/kernel.h> /* For printk. */
+#include <linux/string.h>
+#include <linux/ipmi_msgdefs.h>		/* for completion codes */
+#include "ipmi_si_sm.h"
+
+#define IPMI_BT_VERSION "v33"
+
+static int bt_debug = 0x00;	/* Production value 0, see following flags */
+
+#define	BT_DEBUG_ENABLE	1
+#define BT_DEBUG_MSG	2
+#define BT_DEBUG_STATES	4
+
+/* Typical "Get BT Capabilities" values are 2-3 retries, 5-10 seconds,
+   and 64 byte buffers.  However, one HP implementation wants 255 bytes of
+   buffer (with a documented message of 160 bytes) so go for the max.
+   Since the Open IPMI architecture is single-message oriented at this
+   stage, the queue depth of BT is of no concern. */
+
+#define BT_NORMAL_TIMEOUT	2000000	/* seconds in microseconds */
+#define BT_RETRY_LIMIT		2
+#define BT_RESET_DELAY		6000000	/* 6 seconds after warm reset */
+
+enum bt_states {
+	BT_STATE_IDLE,
+	BT_STATE_XACTION_START,
+	BT_STATE_WRITE_BYTES,
+	BT_STATE_WRITE_END,
+	BT_STATE_WRITE_CONSUME,
+	BT_STATE_B2H_WAIT,
+	BT_STATE_READ_END,
+	BT_STATE_RESET1,		/* These must come last */
+	BT_STATE_RESET2,
+	BT_STATE_RESET3,
+	BT_STATE_RESTART,
+	BT_STATE_HOSED
+};
+
+struct si_sm_data {
+	enum bt_states	state;
+	enum bt_states	last_state;	/* assist printing and resets */
+	unsigned char	seq;		/* BT sequence number */
+	struct si_sm_io	*io;
+        unsigned char	write_data[IPMI_MAX_MSG_LENGTH];
+        int		write_count;
+        unsigned char	read_data[IPMI_MAX_MSG_LENGTH];
+        int		read_count;
+        int		truncated;
+        long		timeout;
+        unsigned int	error_retries;	/* end of "common" fields */
+	int		nonzero_status;	/* hung BMCs stay all 0 */
+};
+
+#define BT_CLR_WR_PTR	0x01	/* See IPMI 1.5 table 11.6.4 */
+#define BT_CLR_RD_PTR	0x02
+#define BT_H2B_ATN	0x04
+#define BT_B2H_ATN	0x08
+#define BT_SMS_ATN	0x10
+#define BT_OEM0		0x20
+#define BT_H_BUSY	0x40
+#define BT_B_BUSY	0x80
+
+/* Some bits are toggled on each write: write once to set it, once
+   more to clear it; writing a zero does nothing.  To absolutely
+   clear it, check its state and write if set.  This avoids the "get
+   current then use as mask" scheme to modify one bit.  Note that the
+   variable "bt" is hardcoded into these macros. */
+
+#define BT_STATUS	bt->io->inputb(bt->io, 0)
+#define BT_CONTROL(x)	bt->io->outputb(bt->io, 0, x)
+
+#define BMC2HOST	bt->io->inputb(bt->io, 1)
+#define HOST2BMC(x)	bt->io->outputb(bt->io, 1, x)
+
+#define BT_INTMASK_R	bt->io->inputb(bt->io, 2)
+#define BT_INTMASK_W(x)	bt->io->outputb(bt->io, 2, x)
+
+/* Convenience routines for debugging.  These are not multi-open safe!
+   Note the macros have hardcoded variables in them. */
+
+static char *state2txt(unsigned char state)
+{
+	switch (state) {
+		case BT_STATE_IDLE:		return("IDLE");
+		case BT_STATE_XACTION_START:	return("XACTION");
+		case BT_STATE_WRITE_BYTES:	return("WR_BYTES");
+		case BT_STATE_WRITE_END:	return("WR_END");
+		case BT_STATE_WRITE_CONSUME:	return("WR_CONSUME");
+		case BT_STATE_B2H_WAIT:		return("B2H_WAIT");
+		case BT_STATE_READ_END:		return("RD_END");
+		case BT_STATE_RESET1:		return("RESET1");
+		case BT_STATE_RESET2:		return("RESET2");
+		case BT_STATE_RESET3:		return("RESET3");
+		case BT_STATE_RESTART:		return("RESTART");
+		case BT_STATE_HOSED:		return("HOSED");
+	}
+	return("BAD STATE");
+}
+#define STATE2TXT state2txt(bt->state)
+
+static char *status2txt(unsigned char status, char *buf)
+{
+	strcpy(buf, "[ ");
+	if (status & BT_B_BUSY) strcat(buf, "B_BUSY ");
+	if (status & BT_H_BUSY) strcat(buf, "H_BUSY ");
+	if (status & BT_OEM0) strcat(buf, "OEM0 ");
+	if (status & BT_SMS_ATN) strcat(buf, "SMS ");
+	if (status & BT_B2H_ATN) strcat(buf, "B2H ");
+	if (status & BT_H2B_ATN) strcat(buf, "H2B ");
+	strcat(buf, "]");
+	return buf;
+}
+#define STATUS2TXT(buf) status2txt(status, buf)
+
+/* This will be called from within this module on a hosed condition */
+#define FIRST_SEQ	0
+static unsigned int bt_init_data(struct si_sm_data *bt, struct si_sm_io *io)
+{
+	bt->state = BT_STATE_IDLE;
+	bt->last_state = BT_STATE_IDLE;
+	bt->seq = FIRST_SEQ;
+	bt->io = io;
+	bt->write_count = 0;
+	bt->read_count = 0;
+	bt->error_retries = 0;
+	bt->nonzero_status = 0;
+	bt->truncated = 0;
+	bt->timeout = BT_NORMAL_TIMEOUT;
+	return 3; /* We claim 3 bytes of space; ought to check SPMI table */
+}
+
+static int bt_start_transaction(struct si_sm_data *bt,
+				unsigned char *data,
+				unsigned int size)
+{
+	unsigned int i;
+
+	if ((size < 2) || (size > IPMI_MAX_MSG_LENGTH)) return -1;
+
+	if ((bt->state != BT_STATE_IDLE) && (bt->state != BT_STATE_HOSED))
+		return -2;
+
+	if (bt_debug & BT_DEBUG_MSG) {
+    		printk(KERN_WARNING "+++++++++++++++++++++++++++++++++++++\n");
+		printk(KERN_WARNING "BT: write seq=0x%02X:", bt->seq);
+		for (i = 0; i < size; i ++) printk (" %02x", data[i]);
+		printk("\n");
+	}
+	bt->write_data[0] = size + 1;	/* all data plus seq byte */
+	bt->write_data[1] = *data;	/* NetFn/LUN */
+	bt->write_data[2] = bt->seq;
+	memcpy(bt->write_data + 3, data + 1, size - 1);
+	bt->write_count = size + 2;
+
+	bt->error_retries = 0;
+	bt->nonzero_status = 0;
+	bt->read_count = 0;
+	bt->truncated = 0;
+	bt->state = BT_STATE_XACTION_START;
+	bt->last_state = BT_STATE_IDLE;
+	bt->timeout = BT_NORMAL_TIMEOUT;
+	return 0;
+}
+
+/* After the upper state machine has been told SI_SM_TRANSACTION_COMPLETE
+   it calls this.  Strip out the length and seq bytes. */
+
+static int bt_get_result(struct si_sm_data *bt,
+			   unsigned char *data,
+			   unsigned int length)
+{
+	int i, msg_len;
+
+	msg_len = bt->read_count - 2;		/* account for length & seq */
+	/* Always NetFn, Cmd, cCode */
+	if (msg_len < 3 || msg_len > IPMI_MAX_MSG_LENGTH) {
+		printk(KERN_WARNING "BT results: bad msg_len = %d\n", msg_len);
+		data[0] = bt->write_data[1] | 0x4;	/* Kludge a response */
+		data[1] = bt->write_data[3];
+		data[2] = IPMI_ERR_UNSPECIFIED;
+		msg_len = 3;
+	} else {
+		data[0] = bt->read_data[1];
+		data[1] = bt->read_data[3];
+		if (length < msg_len) bt->truncated = 1;
+		if (bt->truncated) {	/* can be set in read_all_bytes() */
+			data[2] = IPMI_ERR_MSG_TRUNCATED;
+			msg_len = 3;
+		} else memcpy(data + 2, bt->read_data + 4, msg_len - 2);
+
+		if (bt_debug & BT_DEBUG_MSG) {
+			printk (KERN_WARNING "BT: res (raw)");
+			for (i = 0; i < msg_len; i++) printk(" %02x", data[i]);
+			printk ("\n");
+		}
+	}
+	bt->read_count = 0;	/* paranoia */
+	return msg_len;
+}
+
+/* This bit's functionality is optional */
+#define BT_BMC_HWRST	0x80
+
+static void reset_flags(struct si_sm_data *bt)
+{
+	if (BT_STATUS & BT_H_BUSY) BT_CONTROL(BT_H_BUSY);
+	if (BT_STATUS & BT_B_BUSY) BT_CONTROL(BT_B_BUSY);
+	BT_CONTROL(BT_CLR_WR_PTR);
+	BT_CONTROL(BT_SMS_ATN);
+	BT_INTMASK_W(BT_BMC_HWRST);
+#ifdef DEVELOPMENT_ONLY_NOT_FOR_PRODUCTION
+	if (BT_STATUS & BT_B2H_ATN) {
+		int i;
+		BT_CONTROL(BT_H_BUSY);
+		BT_CONTROL(BT_B2H_ATN);
+		BT_CONTROL(BT_CLR_RD_PTR);
+		for (i = 0; i < IPMI_MAX_MSG_LENGTH + 2; i++) BMC2HOST;
+		BT_CONTROL(BT_H_BUSY);
+	}
+#endif
+}
+
+static inline void write_all_bytes(struct si_sm_data *bt)
+{
+	int i;
+
+	if (bt_debug & BT_DEBUG_MSG) {
+    		printk(KERN_WARNING "BT: write %d bytes seq=0x%02X",
+			bt->write_count, bt->seq);
+		for (i = 0; i < bt->write_count; i++)
+			printk (" %02x", bt->write_data[i]);
+		printk ("\n");
+	}
+	for (i = 0; i < bt->write_count; i++) HOST2BMC(bt->write_data[i]);
+}
+
+static inline int read_all_bytes(struct si_sm_data *bt)
+{
+	unsigned char i;
+
+	bt->read_data[0] = BMC2HOST;
+	bt->read_count = bt->read_data[0];
+	if (bt_debug & BT_DEBUG_MSG)
+    		printk(KERN_WARNING "BT: read %d bytes:", bt->read_count);
+
+	/* minimum: length, NetFn, Seq, Cmd, cCode == 5 total, or 4 more
+	   following the length byte. */
+	if (bt->read_count < 4 || bt->read_count >= IPMI_MAX_MSG_LENGTH) {
+		if (bt_debug & BT_DEBUG_MSG)
+			printk("bad length %d\n", bt->read_count);
+		bt->truncated = 1;
+		return 1;	/* let next XACTION START clean it up */
+	}
+	for (i = 1; i <= bt->read_count; i++) bt->read_data[i] = BMC2HOST;
+	bt->read_count++;	/* account for the length byte */
+
+	if (bt_debug & BT_DEBUG_MSG) {
+	    	for (i = 0; i < bt->read_count; i++)
+			printk (" %02x", bt->read_data[i]);
+	    	printk ("\n");
+	}
+	if (bt->seq != bt->write_data[2])	/* idiot check */
+		printk(KERN_WARNING "BT: internal error: sequence mismatch\n");
+
+	/* per the spec, the (NetFn, Seq, Cmd) tuples should match */
+	if ((bt->read_data[3] == bt->write_data[3]) &&		/* Cmd */
+        	(bt->read_data[2] == bt->write_data[2]) &&	/* Sequence */
+        	((bt->read_data[1] & 0xF8) == (bt->write_data[1] & 0xF8)))
+			return 1;
+
+	if (bt_debug & BT_DEBUG_MSG) printk(KERN_WARNING "BT: bad packet: "
+		"want 0x(%02X, %02X, %02X) got (%02X, %02X, %02X)\n",
+		bt->write_data[1], bt->write_data[2], bt->write_data[3],
+		bt->read_data[1],  bt->read_data[2],  bt->read_data[3]);
+	return 0;
+}
+
+/* Modifies bt->state appropriately, need to get into the bt_event() switch */
+
+static void error_recovery(struct si_sm_data *bt, char *reason)
+{
+	unsigned char status;
+	char buf[40]; /* For getting status */
+
+	bt->timeout = BT_NORMAL_TIMEOUT; /* various places want to retry */
+
+	status = BT_STATUS;
+	printk(KERN_WARNING "BT: %s in %s %s ", reason, STATE2TXT,
+	       STATUS2TXT(buf));
+
+	(bt->error_retries)++;
+	if (bt->error_retries > BT_RETRY_LIMIT) {
+		printk("retry limit (%d) exceeded\n", BT_RETRY_LIMIT);
+		bt->state = BT_STATE_HOSED;
+		if (!bt->nonzero_status)
+			printk(KERN_ERR "IPMI: BT stuck, try power cycle\n");
+		else if (bt->seq == FIRST_SEQ + BT_RETRY_LIMIT) {
+			/* most likely during insmod */
+			printk(KERN_WARNING "IPMI: BT reset (takes 5 secs)\n");
+        		bt->state = BT_STATE_RESET1;
+		}
+	return;
+	}
+
+	/* Sometimes the BMC queues get in an "off-by-one" state...*/
+	if ((bt->state == BT_STATE_B2H_WAIT) && (status & BT_B2H_ATN)) {
+    		printk("retry B2H_WAIT\n");
+		return;
+	}
+
+	printk("restart command\n");
+	bt->state = BT_STATE_RESTART;
+}
+
+/* Check the status and (possibly) advance the BT state machine.  The
+   default return is SI_SM_CALL_WITH_DELAY. */
+
+static enum si_sm_result bt_event(struct si_sm_data *bt, long time)
+{
+	unsigned char status;
+	char buf[40]; /* For getting status */
+	int i;
+
+	status = BT_STATUS;
+	bt->nonzero_status |= status;
+
+	if ((bt_debug & BT_DEBUG_STATES) && (bt->state != bt->last_state))
+		printk(KERN_WARNING "BT: %s %s TO=%ld - %ld \n",
+			STATE2TXT,
+			STATUS2TXT(buf),
+			bt->timeout,
+			time);
+	bt->last_state = bt->state;
+
+	if (bt->state == BT_STATE_HOSED) return SI_SM_HOSED;
+
+	if (bt->state != BT_STATE_IDLE) {	/* do timeout test */
+
+		/* Certain states, on error conditions, can lock up a CPU
+		   because they are effectively in an infinite loop with
+		   CALL_WITHOUT_DELAY (right back here with time == 0).
+		   Prevent infinite lockup by ALWAYS decrementing timeout. */
+
+    	/* FIXME: bt_event is sometimes called with time > BT_NORMAL_TIMEOUT
+              (noticed in ipmi_smic_sm.c January 2004) */
+
+		if ((time <= 0) || (time >= BT_NORMAL_TIMEOUT)) time = 100;
+		bt->timeout -= time;
+		if ((bt->timeout < 0) && (bt->state < BT_STATE_RESET1)) {
+			error_recovery(bt, "timed out");
+			return SI_SM_CALL_WITHOUT_DELAY;
+		}
+	}
+
+	switch (bt->state) {
+
+    	case BT_STATE_IDLE:	/* check for asynchronous messages */
+		if (status & BT_SMS_ATN) {
+			BT_CONTROL(BT_SMS_ATN);	/* clear it */
+			return SI_SM_ATTN;
+		}
+		return SI_SM_IDLE;
+
+	case BT_STATE_XACTION_START:
+		if (status & BT_H_BUSY) {
+			BT_CONTROL(BT_H_BUSY);
+			break;
+		}
+    		if (status & BT_B2H_ATN) break;
+		bt->state = BT_STATE_WRITE_BYTES;
+		return SI_SM_CALL_WITHOUT_DELAY;	/* for logging */
+
+	case BT_STATE_WRITE_BYTES:
+		if (status & (BT_B_BUSY | BT_H2B_ATN)) break;
+		BT_CONTROL(BT_CLR_WR_PTR);
+		write_all_bytes(bt);
+		BT_CONTROL(BT_H2B_ATN);	/* clears too fast to catch? */
+		bt->state = BT_STATE_WRITE_CONSUME;
+		return SI_SM_CALL_WITHOUT_DELAY; /* it MIGHT sail through */
+
+	case BT_STATE_WRITE_CONSUME: /* BMCs usually blow right thru here */
+        	if (status & (BT_H2B_ATN | BT_B_BUSY)) break;
+		bt->state = BT_STATE_B2H_WAIT;
+		/* fall through with status */
+
+	/* Stay in BT_STATE_B2H_WAIT until a packet matches.  However, spinning
+	   hard here, constantly reading status, seems to hold off the
+	   generation of B2H_ATN so ALWAYS return CALL_WITH_DELAY. */
+
+	case BT_STATE_B2H_WAIT:
+    		if (!(status & BT_B2H_ATN)) break;
+
+		/* Assume ordered, uncached writes: no need to wait */
+		if (!(status & BT_H_BUSY)) BT_CONTROL(BT_H_BUSY); /* set */
+		BT_CONTROL(BT_B2H_ATN);		/* clear it, ACK to the BMC */
+		BT_CONTROL(BT_CLR_RD_PTR);	/* reset the queue */
+		i = read_all_bytes(bt);
+		BT_CONTROL(BT_H_BUSY);		/* clear */
+		if (!i) break;			/* Try this state again */
+		bt->state = BT_STATE_READ_END;
+		return SI_SM_CALL_WITHOUT_DELAY;	/* for logging */
+
+    	case BT_STATE_READ_END:
+
+		/* I could wait on BT_H_BUSY to go clear for a truly clean
+		   exit.  However, this is already done in XACTION_START
+		   and the (possible) extra loop/status/possible wait affects
+		   performance.  So, as long as it works, just ignore H_BUSY */
+
+#ifdef MAKE_THIS_TRUE_IF_NECESSARY
+
+		if (status & BT_H_BUSY) break;
+#endif
+		bt->seq++;
+		bt->state = BT_STATE_IDLE;
+		return SI_SM_TRANSACTION_COMPLETE;
+
+	case BT_STATE_RESET1:
+    		reset_flags(bt);
+    		bt->timeout = BT_RESET_DELAY;
+		bt->state = BT_STATE_RESET2;
+		break;
+
+	case BT_STATE_RESET2:		/* Send a soft reset */
+		BT_CONTROL(BT_CLR_WR_PTR);
+		HOST2BMC(3);		/* number of bytes following */
+		HOST2BMC(0x18);		/* NetFn/LUN == Application, LUN 0 */
+		HOST2BMC(42);		/* Sequence number */
+		HOST2BMC(3);		/* Cmd == Soft reset */
+		BT_CONTROL(BT_H2B_ATN);
+		bt->state = BT_STATE_RESET3;
+		break;
+
+	case BT_STATE_RESET3:
+		if (bt->timeout > 0) return SI_SM_CALL_WITH_DELAY;
+		bt->state = BT_STATE_RESTART;	/* printk in debug modes */
+		break;
+
+	case BT_STATE_RESTART:		/* don't reset retries! */
+		bt->write_data[2] = ++bt->seq;
+		bt->read_count = 0;
+		bt->nonzero_status = 0;
+		bt->timeout = BT_NORMAL_TIMEOUT;
+		bt->state = BT_STATE_XACTION_START;
+		break;
+
+	default:	/* HOSED is supposed to be caught much earlier */
+		error_recovery(bt, "internal logic error");
+		break;
+  	}
+  	return SI_SM_CALL_WITH_DELAY;
+}
+
+static int bt_detect(struct si_sm_data *bt)
+{
+	/* It's impossible for the BT status and interrupt registers to be
+	   all 1's, (assuming a properly functioning, self-initialized BMC)
+	   but that's what you get from reading a bogus address, so we
+	   test that first.  The calling routine uses negative logic. */
+
+	if ((BT_STATUS == 0xFF) && (BT_INTMASK_R == 0xFF)) return 1;
+	reset_flags(bt);
+	return 0;
+}
+
+static void bt_cleanup(struct si_sm_data *bt)
+{
+}
+
+static int bt_size(void)
+{
+	return sizeof(struct si_sm_data);
+}
+
+struct si_sm_handlers bt_smi_handlers =
+{
+	.version           = IPMI_BT_VERSION,
+	.init_data         = bt_init_data,
+	.start_transaction = bt_start_transaction,
+	.get_result        = bt_get_result,
+	.event             = bt_event,
+	.detect            = bt_detect,
+	.cleanup           = bt_cleanup,
+	.size              = bt_size,
+};
diff --git a/drivers/char/ipmi/ipmi_devintf.c b/drivers/char/ipmi/ipmi_devintf.c
new file mode 100644
index 0000000..49d67f5
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_devintf.c
@@ -0,0 +1,582 @@
+/*
+ * ipmi_devintf.c
+ *
+ * Linux device interface for the IPMI message handler.
+ *
+ * Author: MontaVista Software, Inc.
+ *         Corey Minyard <minyard@mvista.com>
+ *         source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software 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.
+ *
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/errno.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/ipmi.h>
+#include <asm/semaphore.h>
+#include <linux/init.h>
+
+#define IPMI_DEVINTF_VERSION "v33"
+
+struct ipmi_file_private
+{
+	ipmi_user_t          user;
+	spinlock_t           recv_msg_lock;
+	struct list_head     recv_msgs;
+	struct file          *file;
+	struct fasync_struct *fasync_queue;
+	wait_queue_head_t    wait;
+	struct semaphore     recv_sem;
+	int                  default_retries;
+	unsigned int         default_retry_time_ms;
+};
+
+static void file_receive_handler(struct ipmi_recv_msg *msg,
+				 void                 *handler_data)
+{
+	struct ipmi_file_private *priv = handler_data;
+	int                      was_empty;
+	unsigned long            flags;
+
+	spin_lock_irqsave(&(priv->recv_msg_lock), flags);
+
+	was_empty = list_empty(&(priv->recv_msgs));
+	list_add_tail(&(msg->link), &(priv->recv_msgs));
+
+	if (was_empty) {
+		wake_up_interruptible(&priv->wait);
+		kill_fasync(&priv->fasync_queue, SIGIO, POLL_IN);
+	}
+
+	spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
+}
+
+static unsigned int ipmi_poll(struct file *file, poll_table *wait)
+{
+	struct ipmi_file_private *priv = file->private_data;
+	unsigned int             mask = 0;
+	unsigned long            flags;
+
+	poll_wait(file, &priv->wait, wait);
+
+	spin_lock_irqsave(&priv->recv_msg_lock, flags);
+
+	if (! list_empty(&(priv->recv_msgs)))
+		mask |= (POLLIN | POLLRDNORM);
+
+	spin_unlock_irqrestore(&priv->recv_msg_lock, flags);
+
+	return mask;
+}
+
+static int ipmi_fasync(int fd, struct file *file, int on)
+{
+	struct ipmi_file_private *priv = file->private_data;
+	int                      result;
+
+	result = fasync_helper(fd, file, on, &priv->fasync_queue);
+
+	return (result);
+}
+
+static struct ipmi_user_hndl ipmi_hndlrs =
+{
+	.ipmi_recv_hndl	= file_receive_handler,
+};
+
+static int ipmi_open(struct inode *inode, struct file *file)
+{
+	int                      if_num = iminor(inode);
+	int                      rv;
+	struct ipmi_file_private *priv;
+
+
+	priv = kmalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->file = file;
+
+	rv = ipmi_create_user(if_num,
+			      &ipmi_hndlrs,
+			      priv,
+			      &(priv->user));
+	if (rv) {
+		kfree(priv);
+		return rv;
+	}
+
+	file->private_data = priv;
+
+	spin_lock_init(&(priv->recv_msg_lock));
+	INIT_LIST_HEAD(&(priv->recv_msgs));
+	init_waitqueue_head(&priv->wait);
+	priv->fasync_queue = NULL;
+	sema_init(&(priv->recv_sem), 1);
+
+	/* Use the low-level defaults. */
+	priv->default_retries = -1;
+	priv->default_retry_time_ms = 0;
+
+	return 0;
+}
+
+static int ipmi_release(struct inode *inode, struct file *file)
+{
+	struct ipmi_file_private *priv = file->private_data;
+	int                      rv;
+
+	rv = ipmi_destroy_user(priv->user);
+	if (rv)
+		return rv;
+
+	ipmi_fasync (-1, file, 0);
+
+	/* FIXME - free the messages in the list. */
+	kfree(priv);
+
+	return 0;
+}
+
+static int handle_send_req(ipmi_user_t     user,
+			   struct ipmi_req *req,
+			   int             retries,
+			   unsigned int    retry_time_ms)
+{
+	int              rv;
+	struct ipmi_addr addr;
+	struct kernel_ipmi_msg msg;
+
+	if (req->addr_len > sizeof(struct ipmi_addr))
+		return -EINVAL;
+
+	if (copy_from_user(&addr, req->addr, req->addr_len))
+		return -EFAULT;
+
+	msg.netfn = req->msg.netfn;
+	msg.cmd = req->msg.cmd;
+	msg.data_len = req->msg.data_len;
+	msg.data = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL);
+	if (!msg.data)
+		return -ENOMEM;
+
+	/* From here out we cannot return, we must jump to "out" for
+	   error exits to free msgdata. */
+
+	rv = ipmi_validate_addr(&addr, req->addr_len);
+	if (rv)
+		goto out;
+
+	if (req->msg.data != NULL) {
+		if (req->msg.data_len > IPMI_MAX_MSG_LENGTH) {
+			rv = -EMSGSIZE;
+			goto out;
+		}
+
+		if (copy_from_user(msg.data,
+				   req->msg.data,
+				   req->msg.data_len))
+		{
+			rv = -EFAULT;
+			goto out;
+		}
+	} else {
+		msg.data_len = 0;
+	}
+
+	rv = ipmi_request_settime(user,
+				  &addr,
+				  req->msgid,
+				  &msg,
+				  NULL,
+				  0,
+				  retries,
+				  retry_time_ms);
+ out:
+	kfree(msg.data);
+	return rv;
+}
+
+static int ipmi_ioctl(struct inode  *inode,
+		      struct file   *file,
+		      unsigned int  cmd,
+		      unsigned long data)
+{
+	int                      rv = -EINVAL;
+	struct ipmi_file_private *priv = file->private_data;
+	void __user *arg = (void __user *)data;
+
+	switch (cmd) 
+	{
+	case IPMICTL_SEND_COMMAND:
+	{
+		struct ipmi_req req;
+
+		if (copy_from_user(&req, arg, sizeof(req))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		rv = handle_send_req(priv->user,
+				     &req,
+				     priv->default_retries,
+				     priv->default_retry_time_ms);
+		break;
+	}
+
+	case IPMICTL_SEND_COMMAND_SETTIME:
+	{
+		struct ipmi_req_settime req;
+
+		if (copy_from_user(&req, arg, sizeof(req))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		rv = handle_send_req(priv->user,
+				     &req.req,
+				     req.retries,
+				     req.retry_time_ms);
+		break;
+	}
+
+	case IPMICTL_RECEIVE_MSG:
+	case IPMICTL_RECEIVE_MSG_TRUNC:
+	{
+		struct ipmi_recv      rsp;
+		int              addr_len;
+		struct list_head *entry;
+		struct ipmi_recv_msg  *msg;
+		unsigned long    flags;
+		
+
+		rv = 0;
+		if (copy_from_user(&rsp, arg, sizeof(rsp))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		/* We claim a semaphore because we don't want two
+                   users getting something from the queue at a time.
+                   Since we have to release the spinlock before we can
+                   copy the data to the user, it's possible another
+                   user will grab something from the queue, too.  Then
+                   the messages might get out of order if something
+                   fails and the message gets put back onto the
+                   queue.  This semaphore prevents that problem. */
+		down(&(priv->recv_sem));
+
+		/* Grab the message off the list. */
+		spin_lock_irqsave(&(priv->recv_msg_lock), flags);
+		if (list_empty(&(priv->recv_msgs))) {
+			spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
+			rv = -EAGAIN;
+			goto recv_err;
+		}
+		entry = priv->recv_msgs.next;
+		msg = list_entry(entry, struct ipmi_recv_msg, link);
+		list_del(entry);
+		spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
+
+		addr_len = ipmi_addr_length(msg->addr.addr_type);
+		if (rsp.addr_len < addr_len)
+		{
+			rv = -EINVAL;
+			goto recv_putback_on_err;
+		}
+
+		if (copy_to_user(rsp.addr, &(msg->addr), addr_len)) {
+			rv = -EFAULT;
+			goto recv_putback_on_err;
+		}
+		rsp.addr_len = addr_len;
+
+		rsp.recv_type = msg->recv_type;
+		rsp.msgid = msg->msgid;
+		rsp.msg.netfn = msg->msg.netfn;
+		rsp.msg.cmd = msg->msg.cmd;
+
+		if (msg->msg.data_len > 0) {
+			if (rsp.msg.data_len < msg->msg.data_len) {
+				rv = -EMSGSIZE;
+				if (cmd == IPMICTL_RECEIVE_MSG_TRUNC) {
+					msg->msg.data_len = rsp.msg.data_len;
+				} else {
+					goto recv_putback_on_err;
+				}
+			}
+
+			if (copy_to_user(rsp.msg.data,
+					 msg->msg.data,
+					 msg->msg.data_len))
+			{
+				rv = -EFAULT;
+				goto recv_putback_on_err;
+			}
+			rsp.msg.data_len = msg->msg.data_len;
+		} else {
+			rsp.msg.data_len = 0;
+		}
+
+		if (copy_to_user(arg, &rsp, sizeof(rsp))) {
+			rv = -EFAULT;
+			goto recv_putback_on_err;
+		}
+
+		up(&(priv->recv_sem));
+		ipmi_free_recv_msg(msg);
+		break;
+
+	recv_putback_on_err:
+		/* If we got an error, put the message back onto
+		   the head of the queue. */
+		spin_lock_irqsave(&(priv->recv_msg_lock), flags);
+		list_add(entry, &(priv->recv_msgs));
+		spin_unlock_irqrestore(&(priv->recv_msg_lock), flags);
+		up(&(priv->recv_sem));
+		break;
+
+	recv_err:
+		up(&(priv->recv_sem));
+		break;
+	}
+
+	case IPMICTL_REGISTER_FOR_CMD:
+	{
+		struct ipmi_cmdspec val;
+
+		if (copy_from_user(&val, arg, sizeof(val))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		rv = ipmi_register_for_cmd(priv->user, val.netfn, val.cmd);
+		break;
+	}
+
+	case IPMICTL_UNREGISTER_FOR_CMD:
+	{
+		struct ipmi_cmdspec   val;
+
+		if (copy_from_user(&val, arg, sizeof(val))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		rv = ipmi_unregister_for_cmd(priv->user, val.netfn, val.cmd);
+		break;
+	}
+
+	case IPMICTL_SET_GETS_EVENTS_CMD:
+	{
+		int val;
+
+		if (copy_from_user(&val, arg, sizeof(val))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		rv = ipmi_set_gets_events(priv->user, val);
+		break;
+	}
+
+	case IPMICTL_SET_MY_ADDRESS_CMD:
+	{
+		unsigned int val;
+
+		if (copy_from_user(&val, arg, sizeof(val))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		ipmi_set_my_address(priv->user, val);
+		rv = 0;
+		break;
+	}
+
+	case IPMICTL_GET_MY_ADDRESS_CMD:
+	{
+		unsigned int val;
+
+		val = ipmi_get_my_address(priv->user);
+
+		if (copy_to_user(arg, &val, sizeof(val))) {
+			rv = -EFAULT;
+			break;
+		}
+		rv = 0;
+		break;
+	}
+
+	case IPMICTL_SET_MY_LUN_CMD:
+	{
+		unsigned int val;
+
+		if (copy_from_user(&val, arg, sizeof(val))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		ipmi_set_my_LUN(priv->user, val);
+		rv = 0;
+		break;
+	}
+
+	case IPMICTL_GET_MY_LUN_CMD:
+	{
+		unsigned int val;
+
+		val = ipmi_get_my_LUN(priv->user);
+
+		if (copy_to_user(arg, &val, sizeof(val))) {
+			rv = -EFAULT;
+			break;
+		}
+		rv = 0;
+		break;
+	}
+	case IPMICTL_SET_TIMING_PARMS_CMD:
+	{
+		struct ipmi_timing_parms parms;
+
+		if (copy_from_user(&parms, arg, sizeof(parms))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		priv->default_retries = parms.retries;
+		priv->default_retry_time_ms = parms.retry_time_ms;
+		rv = 0;
+		break;
+	}
+
+	case IPMICTL_GET_TIMING_PARMS_CMD:
+	{
+		struct ipmi_timing_parms parms;
+
+		parms.retries = priv->default_retries;
+		parms.retry_time_ms = priv->default_retry_time_ms;
+
+		if (copy_to_user(arg, &parms, sizeof(parms))) {
+			rv = -EFAULT;
+			break;
+		}
+
+		rv = 0;
+		break;
+	}
+	}
+  
+	return rv;
+}
+
+
+static struct file_operations ipmi_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= ipmi_ioctl,
+	.open		= ipmi_open,
+	.release	= ipmi_release,
+	.fasync		= ipmi_fasync,
+	.poll		= ipmi_poll,
+};
+
+#define DEVICE_NAME     "ipmidev"
+
+static int ipmi_major = 0;
+module_param(ipmi_major, int, 0);
+MODULE_PARM_DESC(ipmi_major, "Sets the major number of the IPMI device.  By"
+		 " default, or if you set it to zero, it will choose the next"
+		 " available device.  Setting it to -1 will disable the"
+		 " interface.  Other values will set the major device number"
+		 " to that value.");
+
+static void ipmi_new_smi(int if_num)
+{
+	devfs_mk_cdev(MKDEV(ipmi_major, if_num),
+		      S_IFCHR | S_IRUSR | S_IWUSR,
+		      "ipmidev/%d", if_num);
+}
+
+static void ipmi_smi_gone(int if_num)
+{
+	devfs_remove("ipmidev/%d", if_num);
+}
+
+static struct ipmi_smi_watcher smi_watcher =
+{
+	.owner    = THIS_MODULE,
+	.new_smi  = ipmi_new_smi,
+	.smi_gone = ipmi_smi_gone,
+};
+
+static __init int init_ipmi_devintf(void)
+{
+	int rv;
+
+	if (ipmi_major < 0)
+		return -EINVAL;
+
+	printk(KERN_INFO "ipmi device interface version "
+	       IPMI_DEVINTF_VERSION "\n");
+
+	rv = register_chrdev(ipmi_major, DEVICE_NAME, &ipmi_fops);
+	if (rv < 0) {
+		printk(KERN_ERR "ipmi: can't get major %d\n", ipmi_major);
+		return rv;
+	}
+
+	if (ipmi_major == 0) {
+		ipmi_major = rv;
+	}
+
+	devfs_mk_dir(DEVICE_NAME);
+
+	rv = ipmi_smi_watcher_register(&smi_watcher);
+	if (rv) {
+		unregister_chrdev(ipmi_major, DEVICE_NAME);
+		printk(KERN_WARNING "ipmi: can't register smi watcher\n");
+		return rv;
+	}
+
+	return 0;
+}
+module_init(init_ipmi_devintf);
+
+static __exit void cleanup_ipmi(void)
+{
+	ipmi_smi_watcher_unregister(&smi_watcher);
+	devfs_remove(DEVICE_NAME);
+	unregister_chrdev(ipmi_major, DEVICE_NAME);
+}
+module_exit(cleanup_ipmi);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/ipmi_kcs_sm.c b/drivers/char/ipmi/ipmi_kcs_sm.c
new file mode 100644
index 0000000..48cce24
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_kcs_sm.c
@@ -0,0 +1,500 @@
+/*
+ * ipmi_kcs_sm.c
+ *
+ * State machine for handling IPMI KCS interfaces.
+ *
+ * Author: MontaVista Software, Inc.
+ *         Corey Minyard <minyard@mvista.com>
+ *         source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software 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.
+ *
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This state machine is taken from the state machine in the IPMI spec,
+ * pretty much verbatim.  If you have questions about the states, see
+ * that document.
+ */
+
+#include <linux/kernel.h> /* For printk. */
+#include <linux/string.h>
+#include <linux/ipmi_msgdefs.h>		/* for completion codes */
+#include "ipmi_si_sm.h"
+
+#define IPMI_KCS_VERSION "v33"
+
+/* Set this if you want a printout of why the state machine was hosed
+   when it gets hosed. */
+#define DEBUG_HOSED_REASON
+
+/* Print the state machine state on entry every time. */
+#undef DEBUG_STATE
+
+/* The states the KCS driver may be in. */
+enum kcs_states {
+	KCS_IDLE,		/* The KCS interface is currently
+                                   doing nothing. */
+	KCS_START_OP,		/* We are starting an operation.  The
+				   data is in the output buffer, but
+				   nothing has been done to the
+				   interface yet.  This was added to
+				   the state machine in the spec to
+				   wait for the initial IBF. */
+	KCS_WAIT_WRITE_START,	/* We have written a write cmd to the
+				   interface. */
+	KCS_WAIT_WRITE,		/* We are writing bytes to the
+                                   interface. */
+	KCS_WAIT_WRITE_END,	/* We have written the write end cmd
+                                   to the interface, and still need to
+                                   write the last byte. */
+	KCS_WAIT_READ,		/* We are waiting to read data from
+				   the interface. */
+	KCS_ERROR0,		/* State to transition to the error
+				   handler, this was added to the
+				   state machine in the spec to be
+				   sure IBF was there. */
+	KCS_ERROR1,		/* First stage error handler, wait for
+                                   the interface to respond. */
+	KCS_ERROR2,		/* The abort cmd has been written,
+				   wait for the interface to
+				   respond. */
+	KCS_ERROR3,		/* We wrote some data to the
+				   interface, wait for it to switch to
+				   read mode. */
+	KCS_HOSED		/* The hardware failed to follow the
+				   state machine. */
+};
+
+#define MAX_KCS_READ_SIZE 80
+#define MAX_KCS_WRITE_SIZE 80
+
+/* Timeouts in microseconds. */
+#define IBF_RETRY_TIMEOUT 1000000
+#define OBF_RETRY_TIMEOUT 1000000
+#define MAX_ERROR_RETRIES 10
+
+struct si_sm_data
+{
+	enum kcs_states  state;
+	struct si_sm_io *io;
+	unsigned char    write_data[MAX_KCS_WRITE_SIZE];
+	int              write_pos;
+	int              write_count;
+	int              orig_write_count;
+	unsigned char    read_data[MAX_KCS_READ_SIZE];
+	int              read_pos;
+	int	         truncated;
+
+	unsigned int  error_retries;
+	long          ibf_timeout;
+	long          obf_timeout;
+};
+
+static unsigned int init_kcs_data(struct si_sm_data *kcs,
+				  struct si_sm_io *io)
+{
+	kcs->state = KCS_IDLE;
+	kcs->io = io;
+	kcs->write_pos = 0;
+	kcs->write_count = 0;
+	kcs->orig_write_count = 0;
+	kcs->read_pos = 0;
+	kcs->error_retries = 0;
+	kcs->truncated = 0;
+	kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+	kcs->obf_timeout = OBF_RETRY_TIMEOUT;
+
+	/* Reserve 2 I/O bytes. */
+	return 2;
+}
+
+static inline unsigned char read_status(struct si_sm_data *kcs)
+{
+	return kcs->io->inputb(kcs->io, 1);
+}
+
+static inline unsigned char read_data(struct si_sm_data *kcs)
+{
+	return kcs->io->inputb(kcs->io, 0);
+}
+
+static inline void write_cmd(struct si_sm_data *kcs, unsigned char data)
+{
+	kcs->io->outputb(kcs->io, 1, data);
+}
+
+static inline void write_data(struct si_sm_data *kcs, unsigned char data)
+{
+	kcs->io->outputb(kcs->io, 0, data);
+}
+
+/* Control codes. */
+#define KCS_GET_STATUS_ABORT	0x60
+#define KCS_WRITE_START		0x61
+#define KCS_WRITE_END		0x62
+#define KCS_READ_BYTE		0x68
+
+/* Status bits. */
+#define GET_STATUS_STATE(status) (((status) >> 6) & 0x03)
+#define KCS_IDLE_STATE	0
+#define KCS_READ_STATE	1
+#define KCS_WRITE_STATE	2
+#define KCS_ERROR_STATE	3
+#define GET_STATUS_ATN(status) ((status) & 0x04)
+#define GET_STATUS_IBF(status) ((status) & 0x02)
+#define GET_STATUS_OBF(status) ((status) & 0x01)
+
+
+static inline void write_next_byte(struct si_sm_data *kcs)
+{
+	write_data(kcs, kcs->write_data[kcs->write_pos]);
+	(kcs->write_pos)++;
+	(kcs->write_count)--;
+}
+
+static inline void start_error_recovery(struct si_sm_data *kcs, char *reason)
+{
+	(kcs->error_retries)++;
+	if (kcs->error_retries > MAX_ERROR_RETRIES) {
+#ifdef DEBUG_HOSED_REASON
+		printk("ipmi_kcs_sm: kcs hosed: %s\n", reason);
+#endif
+		kcs->state = KCS_HOSED;
+	} else {
+		kcs->state = KCS_ERROR0;
+	}
+}
+
+static inline void read_next_byte(struct si_sm_data *kcs)
+{
+	if (kcs->read_pos >= MAX_KCS_READ_SIZE) {
+		/* Throw the data away and mark it truncated. */
+		read_data(kcs);
+		kcs->truncated = 1;
+	} else {
+		kcs->read_data[kcs->read_pos] = read_data(kcs);
+		(kcs->read_pos)++;
+	}
+	write_data(kcs, KCS_READ_BYTE);
+}
+
+static inline int check_ibf(struct si_sm_data *kcs, unsigned char status,
+			    long time)
+{
+	if (GET_STATUS_IBF(status)) {
+		kcs->ibf_timeout -= time;
+		if (kcs->ibf_timeout < 0) {
+			start_error_recovery(kcs, "IBF not ready in time");
+			kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+			return 1;
+		}
+		return 0;
+	}
+	kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+	return 1;
+}
+
+static inline int check_obf(struct si_sm_data *kcs, unsigned char status,
+			    long time)
+{
+	if (! GET_STATUS_OBF(status)) {
+		kcs->obf_timeout -= time;
+		if (kcs->obf_timeout < 0) {
+		    start_error_recovery(kcs, "OBF not ready in time");
+		    return 1;
+		}
+		return 0;
+	}
+	kcs->obf_timeout = OBF_RETRY_TIMEOUT;
+	return 1;
+}
+
+static void clear_obf(struct si_sm_data *kcs, unsigned char status)
+{
+	if (GET_STATUS_OBF(status))
+		read_data(kcs);
+}
+
+static void restart_kcs_transaction(struct si_sm_data *kcs)
+{
+	kcs->write_count = kcs->orig_write_count;
+	kcs->write_pos = 0;
+	kcs->read_pos = 0;
+	kcs->state = KCS_WAIT_WRITE_START;
+	kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+	kcs->obf_timeout = OBF_RETRY_TIMEOUT;
+	write_cmd(kcs, KCS_WRITE_START);
+}
+
+static int start_kcs_transaction(struct si_sm_data *kcs, unsigned char *data,
+				 unsigned int size)
+{
+	if ((size < 2) || (size > MAX_KCS_WRITE_SIZE)) {
+		return -1;
+	}
+
+	if ((kcs->state != KCS_IDLE) && (kcs->state != KCS_HOSED)) {
+		return -2;
+	}
+
+	kcs->error_retries = 0;
+	memcpy(kcs->write_data, data, size);
+	kcs->write_count = size;
+	kcs->orig_write_count = size;
+	kcs->write_pos = 0;
+	kcs->read_pos = 0;
+	kcs->state = KCS_START_OP;
+	kcs->ibf_timeout = IBF_RETRY_TIMEOUT;
+	kcs->obf_timeout = OBF_RETRY_TIMEOUT;
+	return 0;
+}
+
+static int get_kcs_result(struct si_sm_data *kcs, unsigned char *data,
+			  unsigned int length)
+{
+	if (length < kcs->read_pos) {
+		kcs->read_pos = length;
+		kcs->truncated = 1;
+	}
+
+	memcpy(data, kcs->read_data, kcs->read_pos);
+
+	if ((length >= 3) && (kcs->read_pos < 3)) {
+		/* Guarantee that we return at least 3 bytes, with an
+		   error in the third byte if it is too short. */
+		data[2] = IPMI_ERR_UNSPECIFIED;
+		kcs->read_pos = 3;
+	}
+	if (kcs->truncated) {
+		/* Report a truncated error.  We might overwrite
+		   another error, but that's too bad, the user needs
+		   to know it was truncated. */
+		data[2] = IPMI_ERR_MSG_TRUNCATED;
+		kcs->truncated = 0;
+	}
+
+	return kcs->read_pos;
+}
+
+/* This implements the state machine defined in the IPMI manual, see
+   that for details on how this works.  Divide that flowchart into
+   sections delimited by "Wait for IBF" and this will become clear. */
+static enum si_sm_result kcs_event(struct si_sm_data *kcs, long time)
+{
+	unsigned char status;
+	unsigned char state;
+
+	status = read_status(kcs);
+
+#ifdef DEBUG_STATE
+	printk("  State = %d, %x\n", kcs->state, status);
+#endif
+	/* All states wait for ibf, so just do it here. */
+	if (!check_ibf(kcs, status, time))
+		return SI_SM_CALL_WITH_DELAY;
+
+	/* Just about everything looks at the KCS state, so grab that, too. */
+	state = GET_STATUS_STATE(status);
+
+	switch (kcs->state) {
+	case KCS_IDLE:
+		/* If there's and interrupt source, turn it off. */
+		clear_obf(kcs, status);
+
+		if (GET_STATUS_ATN(status))
+			return SI_SM_ATTN;
+		else
+			return SI_SM_IDLE;
+
+	case KCS_START_OP:
+		if (state != KCS_IDLE) {
+			start_error_recovery(kcs,
+					     "State machine not idle at start");
+			break;
+		}
+
+		clear_obf(kcs, status);
+		write_cmd(kcs, KCS_WRITE_START);
+		kcs->state = KCS_WAIT_WRITE_START;
+		break;
+
+	case KCS_WAIT_WRITE_START:
+		if (state != KCS_WRITE_STATE) {
+			start_error_recovery(
+				kcs,
+				"Not in write state at write start");
+			break;
+		}
+		read_data(kcs);
+		if (kcs->write_count == 1) {
+			write_cmd(kcs, KCS_WRITE_END);
+			kcs->state = KCS_WAIT_WRITE_END;
+		} else {
+			write_next_byte(kcs);
+			kcs->state = KCS_WAIT_WRITE;
+		}
+		break;
+
+	case KCS_WAIT_WRITE:
+		if (state != KCS_WRITE_STATE) {
+			start_error_recovery(kcs,
+					     "Not in write state for write");
+			break;
+		}
+		clear_obf(kcs, status);
+		if (kcs->write_count == 1) {
+			write_cmd(kcs, KCS_WRITE_END);
+			kcs->state = KCS_WAIT_WRITE_END;
+		} else {
+			write_next_byte(kcs);
+		}
+		break;
+		
+	case KCS_WAIT_WRITE_END:
+		if (state != KCS_WRITE_STATE) {
+			start_error_recovery(kcs,
+					     "Not in write state for write end");
+			break;
+		}
+		clear_obf(kcs, status);
+		write_next_byte(kcs);
+		kcs->state = KCS_WAIT_READ;
+		break;
+
+	case KCS_WAIT_READ:
+		if ((state != KCS_READ_STATE) && (state != KCS_IDLE_STATE)) {
+			start_error_recovery(
+				kcs,
+				"Not in read or idle in read state");
+			break;
+		}
+
+		if (state == KCS_READ_STATE) {
+			if (! check_obf(kcs, status, time))
+				return SI_SM_CALL_WITH_DELAY;
+			read_next_byte(kcs);
+		} else {
+			/* We don't implement this exactly like the state
+			   machine in the spec.  Some broken hardware
+			   does not write the final dummy byte to the
+			   read register.  Thus obf will never go high
+			   here.  We just go straight to idle, and we
+			   handle clearing out obf in idle state if it
+			   happens to come in. */
+			clear_obf(kcs, status);
+			kcs->orig_write_count = 0;
+			kcs->state = KCS_IDLE;
+			return SI_SM_TRANSACTION_COMPLETE;
+		}
+		break;
+
+	case KCS_ERROR0:
+		clear_obf(kcs, status);
+		write_cmd(kcs, KCS_GET_STATUS_ABORT);
+		kcs->state = KCS_ERROR1;
+		break;
+
+	case KCS_ERROR1:
+		clear_obf(kcs, status);
+		write_data(kcs, 0);
+		kcs->state = KCS_ERROR2;
+		break;
+		
+	case KCS_ERROR2:
+		if (state != KCS_READ_STATE) {
+			start_error_recovery(kcs,
+					     "Not in read state for error2");
+			break;
+		}
+		if (! check_obf(kcs, status, time))
+			return SI_SM_CALL_WITH_DELAY;
+
+		clear_obf(kcs, status);
+		write_data(kcs, KCS_READ_BYTE);
+		kcs->state = KCS_ERROR3;
+		break;
+		
+	case KCS_ERROR3:
+		if (state != KCS_IDLE_STATE) {
+			start_error_recovery(kcs,
+					     "Not in idle state for error3");
+			break;
+		}
+
+		if (! check_obf(kcs, status, time))
+			return SI_SM_CALL_WITH_DELAY;
+
+		clear_obf(kcs, status);
+		if (kcs->orig_write_count) {
+			restart_kcs_transaction(kcs);
+		} else {
+			kcs->state = KCS_IDLE;
+			return SI_SM_TRANSACTION_COMPLETE;
+		}
+		break;
+			
+	case KCS_HOSED:
+		break;
+	}
+
+	if (kcs->state == KCS_HOSED) {
+		init_kcs_data(kcs, kcs->io);
+		return SI_SM_HOSED;
+	}
+
+	return SI_SM_CALL_WITHOUT_DELAY;
+}
+
+static int kcs_size(void)
+{
+	return sizeof(struct si_sm_data);
+}
+
+static int kcs_detect(struct si_sm_data *kcs)
+{
+	/* It's impossible for the KCS status register to be all 1's,
+	   (assuming a properly functioning, self-initialized BMC)
+	   but that's what you get from reading a bogus address, so we
+	   test that first. */
+	if (read_status(kcs) == 0xff)
+		return 1;
+
+	return 0;
+}
+
+static void kcs_cleanup(struct si_sm_data *kcs)
+{
+}
+
+struct si_sm_handlers kcs_smi_handlers =
+{
+	.version           = IPMI_KCS_VERSION,
+	.init_data         = init_kcs_data,
+	.start_transaction = start_kcs_transaction,
+	.get_result        = get_kcs_result,
+	.event             = kcs_event,
+	.detect            = kcs_detect,
+	.cleanup           = kcs_cleanup,
+	.size              = kcs_size,
+};
diff --git a/drivers/char/ipmi/ipmi_msghandler.c b/drivers/char/ipmi/ipmi_msghandler.c
new file mode 100644
index 0000000..a6606a1
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_msghandler.c
@@ -0,0 +1,3174 @@
+/*
+ * ipmi_msghandler.c
+ *
+ * Incoming and outgoing message routing for an IPMI interface.
+ *
+ * Author: MontaVista Software, Inc.
+ *         Corey Minyard <minyard@mvista.com>
+ *         source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software 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.
+ *
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/ipmi.h>
+#include <linux/ipmi_smi.h>
+#include <linux/notifier.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+
+#define PFX "IPMI message handler: "
+#define IPMI_MSGHANDLER_VERSION "v33"
+
+static struct ipmi_recv_msg *ipmi_alloc_recv_msg(void);
+static int ipmi_init_msghandler(void);
+
+static int initialized = 0;
+
+static struct proc_dir_entry *proc_ipmi_root = NULL;
+
+#define MAX_EVENTS_IN_QUEUE	25
+
+/* Don't let a message sit in a queue forever, always time it with at lest
+   the max message timer.  This is in milliseconds. */
+#define MAX_MSG_TIMEOUT		60000
+
+struct ipmi_user
+{
+	struct list_head link;
+
+	/* The upper layer that handles receive messages. */
+	struct ipmi_user_hndl *handler;
+	void             *handler_data;
+
+	/* The interface this user is bound to. */
+	ipmi_smi_t intf;
+
+	/* Does this interface receive IPMI events? */
+	int gets_events;
+};
+
+struct cmd_rcvr
+{
+	struct list_head link;
+
+	ipmi_user_t   user;
+	unsigned char netfn;
+	unsigned char cmd;
+};
+
+struct seq_table
+{
+	unsigned int         inuse : 1;
+	unsigned int         broadcast : 1;
+
+	unsigned long        timeout;
+	unsigned long        orig_timeout;
+	unsigned int         retries_left;
+
+	/* To verify on an incoming send message response that this is
+           the message that the response is for, we keep a sequence id
+           and increment it every time we send a message. */
+	long                 seqid;
+
+	/* This is held so we can properly respond to the message on a
+           timeout, and it is used to hold the temporary data for
+           retransmission, too. */
+	struct ipmi_recv_msg *recv_msg;
+};
+
+/* Store the information in a msgid (long) to allow us to find a
+   sequence table entry from the msgid. */
+#define STORE_SEQ_IN_MSGID(seq, seqid) (((seq&0xff)<<26) | (seqid&0x3ffffff))
+
+#define GET_SEQ_FROM_MSGID(msgid, seq, seqid) \
+	do {								\
+		seq = ((msgid >> 26) & 0x3f);				\
+		seqid = (msgid & 0x3fffff);				\
+        } while(0)
+
+#define NEXT_SEQID(seqid) (((seqid) + 1) & 0x3fffff)
+
+struct ipmi_channel
+{
+	unsigned char medium;
+	unsigned char protocol;
+};
+
+struct ipmi_proc_entry
+{
+	char                   *name;
+	struct ipmi_proc_entry *next;
+};
+
+#define IPMI_IPMB_NUM_SEQ	64
+#define IPMI_MAX_CHANNELS       8
+struct ipmi_smi
+{
+	/* What interface number are we? */
+	int intf_num;
+
+	/* The list of upper layers that are using me.  We read-lock
+           this when delivering messages to the upper layer to keep
+           the user from going away while we are processing the
+           message.  This means that you cannot add or delete a user
+           from the receive callback. */
+	rwlock_t                users_lock;
+	struct list_head        users;
+
+	/* Used for wake ups at startup. */
+	wait_queue_head_t waitq;
+
+	/* The IPMI version of the BMC on the other end. */
+	unsigned char       version_major;
+	unsigned char       version_minor;
+
+	/* This is the lower-layer's sender routine. */
+	struct ipmi_smi_handlers *handlers;
+	void                     *send_info;
+
+	/* A list of proc entries for this interface.  This does not
+	   need a lock, only one thread creates it and only one thread
+	   destroys it. */
+	struct ipmi_proc_entry *proc_entries;
+
+	/* A table of sequence numbers for this interface.  We use the
+           sequence numbers for IPMB messages that go out of the
+           interface to match them up with their responses.  A routine
+           is called periodically to time the items in this list. */
+	spinlock_t       seq_lock;
+	struct seq_table seq_table[IPMI_IPMB_NUM_SEQ];
+	int curr_seq;
+
+	/* Messages that were delayed for some reason (out of memory,
+           for instance), will go in here to be processed later in a
+           periodic timer interrupt. */
+	spinlock_t       waiting_msgs_lock;
+	struct list_head waiting_msgs;
+
+	/* The list of command receivers that are registered for commands
+	   on this interface. */
+	rwlock_t	 cmd_rcvr_lock;
+	struct list_head cmd_rcvrs;
+
+	/* Events that were queues because no one was there to receive
+           them. */
+	spinlock_t       events_lock; /* For dealing with event stuff. */
+	struct list_head waiting_events;
+	unsigned int     waiting_events_count; /* How many events in queue? */
+
+	/* This will be non-null if someone registers to receive all
+	   IPMI commands (this is for interface emulation).  There
+	   may not be any things in the cmd_rcvrs list above when
+	   this is registered. */
+	ipmi_user_t all_cmd_rcvr;
+
+	/* My slave address.  This is initialized to IPMI_BMC_SLAVE_ADDR,
+	   but may be changed by the user. */
+	unsigned char my_address;
+
+	/* My LUN.  This should generally stay the SMS LUN, but just in
+	   case... */
+	unsigned char my_lun;
+
+	/* The event receiver for my BMC, only really used at panic
+	   shutdown as a place to store this. */
+	unsigned char event_receiver;
+	unsigned char event_receiver_lun;
+	unsigned char local_sel_device;
+	unsigned char local_event_generator;
+
+	/* A cheap hack, if this is non-null and a message to an
+	   interface comes in with a NULL user, call this routine with
+	   it.  Note that the message will still be freed by the
+	   caller.  This only works on the system interface. */
+	void (*null_user_handler)(ipmi_smi_t intf, struct ipmi_smi_msg *msg);
+
+	/* When we are scanning the channels for an SMI, this will
+	   tell which channel we are scanning. */
+	int curr_channel;
+
+	/* Channel information */
+	struct ipmi_channel channels[IPMI_MAX_CHANNELS];
+
+	/* Proc FS stuff. */
+	struct proc_dir_entry *proc_dir;
+	char                  proc_dir_name[10];
+
+	spinlock_t   counter_lock; /* For making counters atomic. */
+
+	/* Commands we got that were invalid. */
+	unsigned int sent_invalid_commands;
+
+	/* Commands we sent to the MC. */
+	unsigned int sent_local_commands;
+	/* Responses from the MC that were delivered to a user. */
+	unsigned int handled_local_responses;
+	/* Responses from the MC that were not delivered to a user. */
+	unsigned int unhandled_local_responses;
+
+	/* Commands we sent out to the IPMB bus. */
+	unsigned int sent_ipmb_commands;
+	/* Commands sent on the IPMB that had errors on the SEND CMD */
+	unsigned int sent_ipmb_command_errs;
+	/* Each retransmit increments this count. */
+	unsigned int retransmitted_ipmb_commands;
+	/* When a message times out (runs out of retransmits) this is
+           incremented. */
+	unsigned int timed_out_ipmb_commands;
+
+	/* This is like above, but for broadcasts.  Broadcasts are
+           *not* included in the above count (they are expected to
+           time out). */
+	unsigned int timed_out_ipmb_broadcasts;
+
+	/* Responses I have sent to the IPMB bus. */
+	unsigned int sent_ipmb_responses;
+
+	/* The response was delivered to the user. */
+	unsigned int handled_ipmb_responses;
+	/* The response had invalid data in it. */
+	unsigned int invalid_ipmb_responses;
+	/* The response didn't have anyone waiting for it. */
+	unsigned int unhandled_ipmb_responses;
+
+	/* Commands we sent out to the IPMB bus. */
+	unsigned int sent_lan_commands;
+	/* Commands sent on the IPMB that had errors on the SEND CMD */
+	unsigned int sent_lan_command_errs;
+	/* Each retransmit increments this count. */
+	unsigned int retransmitted_lan_commands;
+	/* When a message times out (runs out of retransmits) this is
+           incremented. */
+	unsigned int timed_out_lan_commands;
+
+	/* Responses I have sent to the IPMB bus. */
+	unsigned int sent_lan_responses;
+
+	/* The response was delivered to the user. */
+	unsigned int handled_lan_responses;
+	/* The response had invalid data in it. */
+	unsigned int invalid_lan_responses;
+	/* The response didn't have anyone waiting for it. */
+	unsigned int unhandled_lan_responses;
+
+	/* The command was delivered to the user. */
+	unsigned int handled_commands;
+	/* The command had invalid data in it. */
+	unsigned int invalid_commands;
+	/* The command didn't have anyone waiting for it. */
+	unsigned int unhandled_commands;
+
+	/* Invalid data in an event. */
+	unsigned int invalid_events;
+	/* Events that were received with the proper format. */
+	unsigned int events;
+};
+
+#define MAX_IPMI_INTERFACES 4
+static ipmi_smi_t ipmi_interfaces[MAX_IPMI_INTERFACES];
+
+/* Used to keep interfaces from going away while operations are
+   operating on interfaces.  Grab read if you are not modifying the
+   interfaces, write if you are. */
+static DECLARE_RWSEM(interfaces_sem);
+
+/* Directly protects the ipmi_interfaces data structure.  This is
+   claimed in the timer interrupt. */
+static DEFINE_SPINLOCK(interfaces_lock);
+
+/* List of watchers that want to know when smi's are added and
+   deleted. */
+static struct list_head smi_watchers = LIST_HEAD_INIT(smi_watchers);
+static DECLARE_RWSEM(smi_watchers_sem);
+
+int ipmi_smi_watcher_register(struct ipmi_smi_watcher *watcher)
+{
+	int i;
+
+	down_read(&interfaces_sem);
+	down_write(&smi_watchers_sem);
+	list_add(&(watcher->link), &smi_watchers);
+	for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+		if (ipmi_interfaces[i] != NULL) {
+			watcher->new_smi(i);
+		}
+	}
+	up_write(&smi_watchers_sem);
+	up_read(&interfaces_sem);
+	return 0;
+}
+
+int ipmi_smi_watcher_unregister(struct ipmi_smi_watcher *watcher)
+{
+	down_write(&smi_watchers_sem);
+	list_del(&(watcher->link));
+	up_write(&smi_watchers_sem);
+	return 0;
+}
+
+static void
+call_smi_watchers(int i)
+{
+	struct ipmi_smi_watcher *w;
+
+	down_read(&smi_watchers_sem);
+	list_for_each_entry(w, &smi_watchers, link) {
+		if (try_module_get(w->owner)) {
+			w->new_smi(i);
+			module_put(w->owner);
+		}
+	}
+	up_read(&smi_watchers_sem);
+}
+
+static int
+ipmi_addr_equal(struct ipmi_addr *addr1, struct ipmi_addr *addr2)
+{
+	if (addr1->addr_type != addr2->addr_type)
+		return 0;
+
+	if (addr1->channel != addr2->channel)
+		return 0;
+
+	if (addr1->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+		struct ipmi_system_interface_addr *smi_addr1
+		    = (struct ipmi_system_interface_addr *) addr1;
+		struct ipmi_system_interface_addr *smi_addr2
+		    = (struct ipmi_system_interface_addr *) addr2;
+		return (smi_addr1->lun == smi_addr2->lun);
+	}
+
+	if ((addr1->addr_type == IPMI_IPMB_ADDR_TYPE)
+	    || (addr1->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+	{
+		struct ipmi_ipmb_addr *ipmb_addr1
+		    = (struct ipmi_ipmb_addr *) addr1;
+		struct ipmi_ipmb_addr *ipmb_addr2
+		    = (struct ipmi_ipmb_addr *) addr2;
+
+		return ((ipmb_addr1->slave_addr == ipmb_addr2->slave_addr)
+			&& (ipmb_addr1->lun == ipmb_addr2->lun));
+	}
+
+	if (addr1->addr_type == IPMI_LAN_ADDR_TYPE) {
+		struct ipmi_lan_addr *lan_addr1
+			= (struct ipmi_lan_addr *) addr1;
+		struct ipmi_lan_addr *lan_addr2
+		    = (struct ipmi_lan_addr *) addr2;
+
+		return ((lan_addr1->remote_SWID == lan_addr2->remote_SWID)
+			&& (lan_addr1->local_SWID == lan_addr2->local_SWID)
+			&& (lan_addr1->session_handle
+			    == lan_addr2->session_handle)
+			&& (lan_addr1->lun == lan_addr2->lun));
+	}
+
+	return 1;
+}
+
+int ipmi_validate_addr(struct ipmi_addr *addr, int len)
+{
+	if (len < sizeof(struct ipmi_system_interface_addr)) {
+		return -EINVAL;
+	}
+
+	if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+		if (addr->channel != IPMI_BMC_CHANNEL)
+			return -EINVAL;
+		return 0;
+	}
+
+	if ((addr->channel == IPMI_BMC_CHANNEL)
+	    || (addr->channel >= IPMI_NUM_CHANNELS)
+	    || (addr->channel < 0))
+		return -EINVAL;
+
+	if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE)
+	    || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+	{
+		if (len < sizeof(struct ipmi_ipmb_addr)) {
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	if (addr->addr_type == IPMI_LAN_ADDR_TYPE) {
+		if (len < sizeof(struct ipmi_lan_addr)) {
+			return -EINVAL;
+		}
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+unsigned int ipmi_addr_length(int addr_type)
+{
+	if (addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE)
+		return sizeof(struct ipmi_system_interface_addr);
+
+	if ((addr_type == IPMI_IPMB_ADDR_TYPE)
+	    || (addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+	{
+		return sizeof(struct ipmi_ipmb_addr);
+	}
+
+	if (addr_type == IPMI_LAN_ADDR_TYPE)
+		return sizeof(struct ipmi_lan_addr);
+
+	return 0;
+}
+
+static void deliver_response(struct ipmi_recv_msg *msg)
+{
+	msg->user->handler->ipmi_recv_hndl(msg, msg->user->handler_data);
+}
+
+/* Find the next sequence number not being used and add the given
+   message with the given timeout to the sequence table.  This must be
+   called with the interface's seq_lock held. */
+static int intf_next_seq(ipmi_smi_t           intf,
+			 struct ipmi_recv_msg *recv_msg,
+			 unsigned long        timeout,
+			 int                  retries,
+			 int                  broadcast,
+			 unsigned char        *seq,
+			 long                 *seqid)
+{
+	int          rv = 0;
+	unsigned int i;
+
+	for (i=intf->curr_seq;
+	     (i+1)%IPMI_IPMB_NUM_SEQ != intf->curr_seq;
+	     i=(i+1)%IPMI_IPMB_NUM_SEQ)
+	{
+		if (! intf->seq_table[i].inuse)
+			break;
+	}
+
+	if (! intf->seq_table[i].inuse) {
+		intf->seq_table[i].recv_msg = recv_msg;
+
+		/* Start with the maximum timeout, when the send response
+		   comes in we will start the real timer. */
+		intf->seq_table[i].timeout = MAX_MSG_TIMEOUT;
+		intf->seq_table[i].orig_timeout = timeout;
+		intf->seq_table[i].retries_left = retries;
+		intf->seq_table[i].broadcast = broadcast;
+		intf->seq_table[i].inuse = 1;
+		intf->seq_table[i].seqid = NEXT_SEQID(intf->seq_table[i].seqid);
+		*seq = i;
+		*seqid = intf->seq_table[i].seqid;
+		intf->curr_seq = (i+1)%IPMI_IPMB_NUM_SEQ;
+	} else {
+		rv = -EAGAIN;
+	}
+	
+	return rv;
+}
+
+/* Return the receive message for the given sequence number and
+   release the sequence number so it can be reused.  Some other data
+   is passed in to be sure the message matches up correctly (to help
+   guard against message coming in after their timeout and the
+   sequence number being reused). */
+static int intf_find_seq(ipmi_smi_t           intf,
+			 unsigned char        seq,
+			 short                channel,
+			 unsigned char        cmd,
+			 unsigned char        netfn,
+			 struct ipmi_addr     *addr,
+			 struct ipmi_recv_msg **recv_msg)
+{
+	int           rv = -ENODEV;
+	unsigned long flags;
+
+	if (seq >= IPMI_IPMB_NUM_SEQ)
+		return -EINVAL;
+
+	spin_lock_irqsave(&(intf->seq_lock), flags);
+	if (intf->seq_table[seq].inuse) {
+		struct ipmi_recv_msg *msg = intf->seq_table[seq].recv_msg;
+
+		if ((msg->addr.channel == channel)
+		    && (msg->msg.cmd == cmd)
+		    && (msg->msg.netfn == netfn)
+		    && (ipmi_addr_equal(addr, &(msg->addr))))
+		{
+			*recv_msg = msg;
+			intf->seq_table[seq].inuse = 0;
+			rv = 0;
+		}
+	}
+	spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+	return rv;
+}
+
+
+/* Start the timer for a specific sequence table entry. */
+static int intf_start_seq_timer(ipmi_smi_t intf,
+				long       msgid)
+{
+	int           rv = -ENODEV;
+	unsigned long flags;
+	unsigned char seq;
+	unsigned long seqid;
+
+
+	GET_SEQ_FROM_MSGID(msgid, seq, seqid);
+
+	spin_lock_irqsave(&(intf->seq_lock), flags);
+	/* We do this verification because the user can be deleted
+           while a message is outstanding. */
+	if ((intf->seq_table[seq].inuse)
+	    && (intf->seq_table[seq].seqid == seqid))
+	{
+		struct seq_table *ent = &(intf->seq_table[seq]);
+		ent->timeout = ent->orig_timeout;
+		rv = 0;
+	}
+	spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+	return rv;
+}
+
+/* Got an error for the send message for a specific sequence number. */
+static int intf_err_seq(ipmi_smi_t   intf,
+			long         msgid,
+			unsigned int err)
+{
+	int                  rv = -ENODEV;
+	unsigned long        flags;
+	unsigned char        seq;
+	unsigned long        seqid;
+	struct ipmi_recv_msg *msg = NULL;
+
+
+	GET_SEQ_FROM_MSGID(msgid, seq, seqid);
+
+	spin_lock_irqsave(&(intf->seq_lock), flags);
+	/* We do this verification because the user can be deleted
+           while a message is outstanding. */
+	if ((intf->seq_table[seq].inuse)
+	    && (intf->seq_table[seq].seqid == seqid))
+	{
+		struct seq_table *ent = &(intf->seq_table[seq]);
+
+		ent->inuse = 0;
+		msg = ent->recv_msg;
+		rv = 0;
+	}
+	spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+	if (msg) {
+		msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+		msg->msg_data[0] = err;
+		msg->msg.netfn |= 1; /* Convert to a response. */
+		msg->msg.data_len = 1;
+		msg->msg.data = msg->msg_data;
+		deliver_response(msg);
+	}
+
+	return rv;
+}
+
+
+int ipmi_create_user(unsigned int          if_num,
+		     struct ipmi_user_hndl *handler,
+		     void                  *handler_data,
+		     ipmi_user_t           *user)
+{
+	unsigned long flags;
+	ipmi_user_t   new_user;
+	int           rv = 0;
+	ipmi_smi_t    intf;
+
+	/* There is no module usecount here, because it's not
+           required.  Since this can only be used by and called from
+           other modules, they will implicitly use this module, and
+           thus this can't be removed unless the other modules are
+           removed. */
+
+	if (handler == NULL)
+		return -EINVAL;
+
+	/* Make sure the driver is actually initialized, this handles
+	   problems with initialization order. */
+	if (!initialized) {
+		rv = ipmi_init_msghandler();
+		if (rv)
+			return rv;
+
+		/* The init code doesn't return an error if it was turned
+		   off, but it won't initialize.  Check that. */
+		if (!initialized)
+			return -ENODEV;
+	}
+
+	new_user = kmalloc(sizeof(*new_user), GFP_KERNEL);
+	if (! new_user)
+		return -ENOMEM;
+
+	down_read(&interfaces_sem);
+	if ((if_num > MAX_IPMI_INTERFACES) || ipmi_interfaces[if_num] == NULL)
+	{
+		rv = -EINVAL;
+		goto out_unlock;
+	}
+
+	intf = ipmi_interfaces[if_num];
+
+	new_user->handler = handler;
+	new_user->handler_data = handler_data;
+	new_user->intf = intf;
+	new_user->gets_events = 0;
+
+	if (!try_module_get(intf->handlers->owner)) {
+		rv = -ENODEV;
+		goto out_unlock;
+	}
+
+	if (intf->handlers->inc_usecount) {
+		rv = intf->handlers->inc_usecount(intf->send_info);
+		if (rv) {
+			module_put(intf->handlers->owner);
+			goto out_unlock;
+		}
+	}
+
+	write_lock_irqsave(&intf->users_lock, flags);
+	list_add_tail(&new_user->link, &intf->users);
+	write_unlock_irqrestore(&intf->users_lock, flags);
+
+ out_unlock:	
+	if (rv) {
+		kfree(new_user);
+	} else {
+		*user = new_user;
+	}
+
+	up_read(&interfaces_sem);
+	return rv;
+}
+
+static int ipmi_destroy_user_nolock(ipmi_user_t user)
+{
+	int              rv = -ENODEV;
+	ipmi_user_t      t_user;
+	struct cmd_rcvr  *rcvr, *rcvr2;
+	int              i;
+	unsigned long    flags;
+
+	/* Find the user and delete them from the list. */
+	list_for_each_entry(t_user, &(user->intf->users), link) {
+		if (t_user == user) {
+			list_del(&t_user->link);
+			rv = 0;
+			break;
+		}
+	}
+
+	if (rv) {
+		goto out_unlock;
+	}
+
+	/* Remove the user from the interfaces sequence table. */
+	spin_lock_irqsave(&(user->intf->seq_lock), flags);
+	for (i=0; i<IPMI_IPMB_NUM_SEQ; i++) {
+		if (user->intf->seq_table[i].inuse
+		    && (user->intf->seq_table[i].recv_msg->user == user))
+		{
+			user->intf->seq_table[i].inuse = 0;
+		}
+	}
+	spin_unlock_irqrestore(&(user->intf->seq_lock), flags);
+
+	/* Remove the user from the command receiver's table. */
+	write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+	list_for_each_entry_safe(rcvr, rcvr2, &(user->intf->cmd_rcvrs), link) {
+		if (rcvr->user == user) {
+			list_del(&rcvr->link);
+			kfree(rcvr);
+		}
+	}
+	write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+
+	kfree(user);
+
+ out_unlock:
+
+	return rv;
+}
+
+int ipmi_destroy_user(ipmi_user_t user)
+{
+	int           rv;
+	ipmi_smi_t    intf = user->intf;
+	unsigned long flags;
+
+	down_read(&interfaces_sem);
+	write_lock_irqsave(&intf->users_lock, flags);
+	rv = ipmi_destroy_user_nolock(user);
+	if (!rv) {
+		module_put(intf->handlers->owner);
+		if (intf->handlers->dec_usecount)
+			intf->handlers->dec_usecount(intf->send_info);
+	}
+		
+	write_unlock_irqrestore(&intf->users_lock, flags);
+	up_read(&interfaces_sem);
+	return rv;
+}
+
+void ipmi_get_version(ipmi_user_t   user,
+		      unsigned char *major,
+		      unsigned char *minor)
+{
+	*major = user->intf->version_major;
+	*minor = user->intf->version_minor;
+}
+
+void ipmi_set_my_address(ipmi_user_t   user,
+			 unsigned char address)
+{
+	user->intf->my_address = address;
+}
+
+unsigned char ipmi_get_my_address(ipmi_user_t user)
+{
+	return user->intf->my_address;
+}
+
+void ipmi_set_my_LUN(ipmi_user_t   user,
+		     unsigned char LUN)
+{
+	user->intf->my_lun = LUN & 0x3;
+}
+
+unsigned char ipmi_get_my_LUN(ipmi_user_t user)
+{
+	return user->intf->my_lun;
+}
+
+int ipmi_set_gets_events(ipmi_user_t user, int val)
+{
+	unsigned long         flags;
+	struct ipmi_recv_msg  *msg, *msg2;
+
+	read_lock(&(user->intf->users_lock));
+	spin_lock_irqsave(&(user->intf->events_lock), flags);
+	user->gets_events = val;
+
+	if (val) {
+		/* Deliver any queued events. */
+		list_for_each_entry_safe(msg, msg2, &(user->intf->waiting_events), link) {
+			list_del(&msg->link);
+			msg->user = user;
+			deliver_response(msg);
+		}
+	}
+	
+	spin_unlock_irqrestore(&(user->intf->events_lock), flags);
+	read_unlock(&(user->intf->users_lock));
+
+	return 0;
+}
+
+int ipmi_register_for_cmd(ipmi_user_t   user,
+			  unsigned char netfn,
+			  unsigned char cmd)
+{
+	struct cmd_rcvr  *cmp;
+	unsigned long    flags;
+	struct cmd_rcvr  *rcvr;
+	int              rv = 0;
+
+
+	rcvr = kmalloc(sizeof(*rcvr), GFP_KERNEL);
+	if (! rcvr)
+		return -ENOMEM;
+
+	read_lock(&(user->intf->users_lock));
+	write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+	if (user->intf->all_cmd_rcvr != NULL) {
+		rv = -EBUSY;
+		goto out_unlock;
+	}
+
+	/* Make sure the command/netfn is not already registered. */
+	list_for_each_entry(cmp, &(user->intf->cmd_rcvrs), link) {
+		if ((cmp->netfn == netfn) && (cmp->cmd == cmd)) {
+			rv = -EBUSY;
+			break;
+		}
+	}
+
+	if (! rv) {
+		rcvr->cmd = cmd;
+		rcvr->netfn = netfn;
+		rcvr->user = user;
+		list_add_tail(&(rcvr->link), &(user->intf->cmd_rcvrs));
+	}
+ out_unlock:
+	write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+	read_unlock(&(user->intf->users_lock));
+
+	if (rv)
+		kfree(rcvr);
+
+	return rv;
+}
+
+int ipmi_unregister_for_cmd(ipmi_user_t   user,
+			    unsigned char netfn,
+			    unsigned char cmd)
+{
+	unsigned long    flags;
+	struct cmd_rcvr  *rcvr;
+	int              rv = -ENOENT;
+
+	read_lock(&(user->intf->users_lock));
+	write_lock_irqsave(&(user->intf->cmd_rcvr_lock), flags);
+	/* Make sure the command/netfn is not already registered. */
+	list_for_each_entry(rcvr, &(user->intf->cmd_rcvrs), link) {
+		if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+			rv = 0;
+			list_del(&rcvr->link);
+			kfree(rcvr);
+			break;
+		}
+	}
+	write_unlock_irqrestore(&(user->intf->cmd_rcvr_lock), flags);
+	read_unlock(&(user->intf->users_lock));
+
+	return rv;
+}
+
+void ipmi_user_set_run_to_completion(ipmi_user_t user, int val)
+{
+	user->intf->handlers->set_run_to_completion(user->intf->send_info,
+						    val);
+}
+
+static unsigned char
+ipmb_checksum(unsigned char *data, int size)
+{
+	unsigned char csum = 0;
+	
+	for (; size > 0; size--, data++)
+		csum += *data;
+
+	return -csum;
+}
+
+static inline void format_ipmb_msg(struct ipmi_smi_msg   *smi_msg,
+				   struct kernel_ipmi_msg *msg,
+				   struct ipmi_ipmb_addr *ipmb_addr,
+				   long                  msgid,
+				   unsigned char         ipmb_seq,
+				   int                   broadcast,
+				   unsigned char         source_address,
+				   unsigned char         source_lun)
+{
+	int i = broadcast;
+
+	/* Format the IPMB header data. */
+	smi_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+	smi_msg->data[1] = IPMI_SEND_MSG_CMD;
+	smi_msg->data[2] = ipmb_addr->channel;
+	if (broadcast)
+		smi_msg->data[3] = 0;
+	smi_msg->data[i+3] = ipmb_addr->slave_addr;
+	smi_msg->data[i+4] = (msg->netfn << 2) | (ipmb_addr->lun & 0x3);
+	smi_msg->data[i+5] = ipmb_checksum(&(smi_msg->data[i+3]), 2);
+	smi_msg->data[i+6] = source_address;
+	smi_msg->data[i+7] = (ipmb_seq << 2) | source_lun;
+	smi_msg->data[i+8] = msg->cmd;
+
+	/* Now tack on the data to the message. */
+	if (msg->data_len > 0)
+		memcpy(&(smi_msg->data[i+9]), msg->data,
+		       msg->data_len);
+	smi_msg->data_size = msg->data_len + 9;
+
+	/* Now calculate the checksum and tack it on. */
+	smi_msg->data[i+smi_msg->data_size]
+		= ipmb_checksum(&(smi_msg->data[i+6]),
+				smi_msg->data_size-6);
+
+	/* Add on the checksum size and the offset from the
+	   broadcast. */
+	smi_msg->data_size += 1 + i;
+
+	smi_msg->msgid = msgid;
+}
+
+static inline void format_lan_msg(struct ipmi_smi_msg   *smi_msg,
+				  struct kernel_ipmi_msg *msg,
+				  struct ipmi_lan_addr  *lan_addr,
+				  long                  msgid,
+				  unsigned char         ipmb_seq,
+				  unsigned char         source_lun)
+{
+	/* Format the IPMB header data. */
+	smi_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+	smi_msg->data[1] = IPMI_SEND_MSG_CMD;
+	smi_msg->data[2] = lan_addr->channel;
+	smi_msg->data[3] = lan_addr->session_handle;
+	smi_msg->data[4] = lan_addr->remote_SWID;
+	smi_msg->data[5] = (msg->netfn << 2) | (lan_addr->lun & 0x3);
+	smi_msg->data[6] = ipmb_checksum(&(smi_msg->data[4]), 2);
+	smi_msg->data[7] = lan_addr->local_SWID;
+	smi_msg->data[8] = (ipmb_seq << 2) | source_lun;
+	smi_msg->data[9] = msg->cmd;
+
+	/* Now tack on the data to the message. */
+	if (msg->data_len > 0)
+		memcpy(&(smi_msg->data[10]), msg->data,
+		       msg->data_len);
+	smi_msg->data_size = msg->data_len + 10;
+
+	/* Now calculate the checksum and tack it on. */
+	smi_msg->data[smi_msg->data_size]
+		= ipmb_checksum(&(smi_msg->data[7]),
+				smi_msg->data_size-7);
+
+	/* Add on the checksum size and the offset from the
+	   broadcast. */
+	smi_msg->data_size += 1;
+
+	smi_msg->msgid = msgid;
+}
+
+/* Separate from ipmi_request so that the user does not have to be
+   supplied in certain circumstances (mainly at panic time).  If
+   messages are supplied, they will be freed, even if an error
+   occurs. */
+static inline int i_ipmi_request(ipmi_user_t          user,
+				 ipmi_smi_t           intf,
+				 struct ipmi_addr     *addr,
+				 long                 msgid,
+				 struct kernel_ipmi_msg *msg,
+				 void                 *user_msg_data,
+				 void                 *supplied_smi,
+				 struct ipmi_recv_msg *supplied_recv,
+				 int                  priority,
+				 unsigned char        source_address,
+				 unsigned char        source_lun,
+				 int                  retries,
+				 unsigned int         retry_time_ms)
+{
+	int                  rv = 0;
+	struct ipmi_smi_msg  *smi_msg;
+	struct ipmi_recv_msg *recv_msg;
+	unsigned long        flags;
+
+
+	if (supplied_recv) {
+		recv_msg = supplied_recv;
+	} else {
+		recv_msg = ipmi_alloc_recv_msg();
+		if (recv_msg == NULL) {
+			return -ENOMEM;
+		}
+	}
+	recv_msg->user_msg_data = user_msg_data;
+
+	if (supplied_smi) {
+		smi_msg = (struct ipmi_smi_msg *) supplied_smi;
+	} else {
+		smi_msg = ipmi_alloc_smi_msg();
+		if (smi_msg == NULL) {
+			ipmi_free_recv_msg(recv_msg);
+			return -ENOMEM;
+		}
+	}
+
+	recv_msg->user = user;
+	recv_msg->msgid = msgid;
+	/* Store the message to send in the receive message so timeout
+	   responses can get the proper response data. */
+	recv_msg->msg = *msg;
+
+	if (addr->addr_type == IPMI_SYSTEM_INTERFACE_ADDR_TYPE) {
+		struct ipmi_system_interface_addr *smi_addr;
+
+		if (msg->netfn & 1) {
+			/* Responses are not allowed to the SMI. */
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		smi_addr = (struct ipmi_system_interface_addr *) addr;
+		if (smi_addr->lun > 3) {
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		memcpy(&recv_msg->addr, smi_addr, sizeof(*smi_addr));
+
+		if ((msg->netfn == IPMI_NETFN_APP_REQUEST)
+		    && ((msg->cmd == IPMI_SEND_MSG_CMD)
+			|| (msg->cmd == IPMI_GET_MSG_CMD)
+			|| (msg->cmd == IPMI_READ_EVENT_MSG_BUFFER_CMD)))
+		{
+			/* We don't let the user do these, since we manage
+			   the sequence numbers. */
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		if ((msg->data_len + 2) > IPMI_MAX_MSG_LENGTH) {
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EMSGSIZE;
+			goto out_err;
+		}
+
+		smi_msg->data[0] = (msg->netfn << 2) | (smi_addr->lun & 0x3);
+		smi_msg->data[1] = msg->cmd;
+		smi_msg->msgid = msgid;
+		smi_msg->user_data = recv_msg;
+		if (msg->data_len > 0)
+			memcpy(&(smi_msg->data[2]), msg->data, msg->data_len);
+		smi_msg->data_size = msg->data_len + 2;
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->sent_local_commands++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+	} else if ((addr->addr_type == IPMI_IPMB_ADDR_TYPE)
+		   || (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE))
+	{
+		struct ipmi_ipmb_addr *ipmb_addr;
+		unsigned char         ipmb_seq;
+		long                  seqid;
+		int                   broadcast = 0;
+
+		if (addr->channel > IPMI_NUM_CHANNELS) {
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		if (intf->channels[addr->channel].medium
+		    != IPMI_CHANNEL_MEDIUM_IPMB)
+		{
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		if (retries < 0) {
+		    if (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE)
+			retries = 0; /* Don't retry broadcasts. */
+		    else
+			retries = 4;
+		}
+		if (addr->addr_type == IPMI_IPMB_BROADCAST_ADDR_TYPE) {
+		    /* Broadcasts add a zero at the beginning of the
+		       message, but otherwise is the same as an IPMB
+		       address. */
+		    addr->addr_type = IPMI_IPMB_ADDR_TYPE;
+		    broadcast = 1;
+		}
+
+
+		/* Default to 1 second retries. */
+		if (retry_time_ms == 0)
+		    retry_time_ms = 1000;
+
+		/* 9 for the header and 1 for the checksum, plus
+                   possibly one for the broadcast. */
+		if ((msg->data_len + 10 + broadcast) > IPMI_MAX_MSG_LENGTH) {
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EMSGSIZE;
+			goto out_err;
+		}
+
+		ipmb_addr = (struct ipmi_ipmb_addr *) addr;
+		if (ipmb_addr->lun > 3) {
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		memcpy(&recv_msg->addr, ipmb_addr, sizeof(*ipmb_addr));
+
+		if (recv_msg->msg.netfn & 0x1) {
+			/* It's a response, so use the user's sequence
+                           from msgid. */
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_ipmb_responses++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			format_ipmb_msg(smi_msg, msg, ipmb_addr, msgid,
+					msgid, broadcast,
+					source_address, source_lun);
+
+			/* Save the receive message so we can use it
+			   to deliver the response. */
+			smi_msg->user_data = recv_msg;
+		} else {
+			/* It's a command, so get a sequence for it. */
+
+			spin_lock_irqsave(&(intf->seq_lock), flags);
+
+			spin_lock(&intf->counter_lock);
+			intf->sent_ipmb_commands++;
+			spin_unlock(&intf->counter_lock);
+
+			/* Create a sequence number with a 1 second
+                           timeout and 4 retries. */
+			rv = intf_next_seq(intf,
+					   recv_msg,
+					   retry_time_ms,
+					   retries,
+					   broadcast,
+					   &ipmb_seq,
+					   &seqid);
+			if (rv) {
+				/* We have used up all the sequence numbers,
+				   probably, so abort. */
+				spin_unlock_irqrestore(&(intf->seq_lock),
+						       flags);
+				goto out_err;
+			}
+
+			/* Store the sequence number in the message,
+                           so that when the send message response
+                           comes back we can start the timer. */
+			format_ipmb_msg(smi_msg, msg, ipmb_addr,
+					STORE_SEQ_IN_MSGID(ipmb_seq, seqid),
+					ipmb_seq, broadcast,
+					source_address, source_lun);
+
+			/* Copy the message into the recv message data, so we
+			   can retransmit it later if necessary. */
+			memcpy(recv_msg->msg_data, smi_msg->data,
+			       smi_msg->data_size);
+			recv_msg->msg.data = recv_msg->msg_data;
+			recv_msg->msg.data_len = smi_msg->data_size;
+
+			/* We don't unlock until here, because we need
+                           to copy the completed message into the
+                           recv_msg before we release the lock.
+                           Otherwise, race conditions may bite us.  I
+                           know that's pretty paranoid, but I prefer
+                           to be correct. */
+			spin_unlock_irqrestore(&(intf->seq_lock), flags);
+		}
+	} else if (addr->addr_type == IPMI_LAN_ADDR_TYPE) {
+		struct ipmi_lan_addr  *lan_addr;
+		unsigned char         ipmb_seq;
+		long                  seqid;
+
+		if (addr->channel > IPMI_NUM_CHANNELS) {
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		if ((intf->channels[addr->channel].medium
+		    != IPMI_CHANNEL_MEDIUM_8023LAN)
+		    && (intf->channels[addr->channel].medium
+			!= IPMI_CHANNEL_MEDIUM_ASYNC))
+		{
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		retries = 4;
+
+		/* Default to 1 second retries. */
+		if (retry_time_ms == 0)
+		    retry_time_ms = 1000;
+
+		/* 11 for the header and 1 for the checksum. */
+		if ((msg->data_len + 12) > IPMI_MAX_MSG_LENGTH) {
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EMSGSIZE;
+			goto out_err;
+		}
+
+		lan_addr = (struct ipmi_lan_addr *) addr;
+		if (lan_addr->lun > 3) {
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_invalid_commands++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			rv = -EINVAL;
+			goto out_err;
+		}
+
+		memcpy(&recv_msg->addr, lan_addr, sizeof(*lan_addr));
+
+		if (recv_msg->msg.netfn & 0x1) {
+			/* It's a response, so use the user's sequence
+                           from msgid. */
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->sent_lan_responses++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			format_lan_msg(smi_msg, msg, lan_addr, msgid,
+				       msgid, source_lun);
+
+			/* Save the receive message so we can use it
+			   to deliver the response. */
+			smi_msg->user_data = recv_msg;
+		} else {
+			/* It's a command, so get a sequence for it. */
+
+			spin_lock_irqsave(&(intf->seq_lock), flags);
+
+			spin_lock(&intf->counter_lock);
+			intf->sent_lan_commands++;
+			spin_unlock(&intf->counter_lock);
+
+			/* Create a sequence number with a 1 second
+                           timeout and 4 retries. */
+			rv = intf_next_seq(intf,
+					   recv_msg,
+					   retry_time_ms,
+					   retries,
+					   0,
+					   &ipmb_seq,
+					   &seqid);
+			if (rv) {
+				/* We have used up all the sequence numbers,
+				   probably, so abort. */
+				spin_unlock_irqrestore(&(intf->seq_lock),
+						       flags);
+				goto out_err;
+			}
+
+			/* Store the sequence number in the message,
+                           so that when the send message response
+                           comes back we can start the timer. */
+			format_lan_msg(smi_msg, msg, lan_addr,
+				       STORE_SEQ_IN_MSGID(ipmb_seq, seqid),
+				       ipmb_seq, source_lun);
+
+			/* Copy the message into the recv message data, so we
+			   can retransmit it later if necessary. */
+			memcpy(recv_msg->msg_data, smi_msg->data,
+			       smi_msg->data_size);
+			recv_msg->msg.data = recv_msg->msg_data;
+			recv_msg->msg.data_len = smi_msg->data_size;
+
+			/* We don't unlock until here, because we need
+                           to copy the completed message into the
+                           recv_msg before we release the lock.
+                           Otherwise, race conditions may bite us.  I
+                           know that's pretty paranoid, but I prefer
+                           to be correct. */
+			spin_unlock_irqrestore(&(intf->seq_lock), flags);
+		}
+	} else {
+	    /* Unknown address type. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->sent_invalid_commands++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		rv = -EINVAL;
+		goto out_err;
+	}
+
+#ifdef DEBUG_MSGING
+	{
+		int m;
+		for (m=0; m<smi_msg->data_size; m++)
+			printk(" %2.2x", smi_msg->data[m]);
+		printk("\n");
+	}
+#endif
+	intf->handlers->sender(intf->send_info, smi_msg, priority);
+
+	return 0;
+
+ out_err:
+	ipmi_free_smi_msg(smi_msg);
+	ipmi_free_recv_msg(recv_msg);
+	return rv;
+}
+
+int ipmi_request_settime(ipmi_user_t      user,
+			 struct ipmi_addr *addr,
+			 long             msgid,
+			 struct kernel_ipmi_msg  *msg,
+			 void             *user_msg_data,
+			 int              priority,
+			 int              retries,
+			 unsigned int     retry_time_ms)
+{
+	return i_ipmi_request(user,
+			      user->intf,
+			      addr,
+			      msgid,
+			      msg,
+			      user_msg_data,
+			      NULL, NULL,
+			      priority,
+			      user->intf->my_address,
+			      user->intf->my_lun,
+			      retries,
+			      retry_time_ms);
+}
+
+int ipmi_request_supply_msgs(ipmi_user_t          user,
+			     struct ipmi_addr     *addr,
+			     long                 msgid,
+			     struct kernel_ipmi_msg *msg,
+			     void                 *user_msg_data,
+			     void                 *supplied_smi,
+			     struct ipmi_recv_msg *supplied_recv,
+			     int                  priority)
+{
+	return i_ipmi_request(user,
+			      user->intf,
+			      addr,
+			      msgid,
+			      msg,
+			      user_msg_data,
+			      supplied_smi,
+			      supplied_recv,
+			      priority,
+			      user->intf->my_address,
+			      user->intf->my_lun,
+			      -1, 0);
+}
+
+static int ipmb_file_read_proc(char *page, char **start, off_t off,
+			       int count, int *eof, void *data)
+{
+	char       *out = (char *) page;
+	ipmi_smi_t intf = data;
+
+	return sprintf(out, "%x\n", intf->my_address);
+}
+
+static int version_file_read_proc(char *page, char **start, off_t off,
+				  int count, int *eof, void *data)
+{
+	char       *out = (char *) page;
+	ipmi_smi_t intf = data;
+
+	return sprintf(out, "%d.%d\n",
+		       intf->version_major, intf->version_minor);
+}
+
+static int stat_file_read_proc(char *page, char **start, off_t off,
+			       int count, int *eof, void *data)
+{
+	char       *out = (char *) page;
+	ipmi_smi_t intf = data;
+
+	out += sprintf(out, "sent_invalid_commands:       %d\n",
+		       intf->sent_invalid_commands);
+	out += sprintf(out, "sent_local_commands:         %d\n",
+		       intf->sent_local_commands);
+	out += sprintf(out, "handled_local_responses:     %d\n",
+		       intf->handled_local_responses);
+	out += sprintf(out, "unhandled_local_responses:   %d\n",
+		       intf->unhandled_local_responses);
+	out += sprintf(out, "sent_ipmb_commands:          %d\n",
+		       intf->sent_ipmb_commands);
+	out += sprintf(out, "sent_ipmb_command_errs:      %d\n",
+		       intf->sent_ipmb_command_errs);
+	out += sprintf(out, "retransmitted_ipmb_commands: %d\n",
+		       intf->retransmitted_ipmb_commands);
+	out += sprintf(out, "timed_out_ipmb_commands:     %d\n",
+		       intf->timed_out_ipmb_commands);
+	out += sprintf(out, "timed_out_ipmb_broadcasts:   %d\n",
+		       intf->timed_out_ipmb_broadcasts);
+	out += sprintf(out, "sent_ipmb_responses:         %d\n",
+		       intf->sent_ipmb_responses);
+	out += sprintf(out, "handled_ipmb_responses:      %d\n",
+		       intf->handled_ipmb_responses);
+	out += sprintf(out, "invalid_ipmb_responses:      %d\n",
+		       intf->invalid_ipmb_responses);
+	out += sprintf(out, "unhandled_ipmb_responses:    %d\n",
+		       intf->unhandled_ipmb_responses);
+	out += sprintf(out, "sent_lan_commands:           %d\n",
+		       intf->sent_lan_commands);
+	out += sprintf(out, "sent_lan_command_errs:       %d\n",
+		       intf->sent_lan_command_errs);
+	out += sprintf(out, "retransmitted_lan_commands:  %d\n",
+		       intf->retransmitted_lan_commands);
+	out += sprintf(out, "timed_out_lan_commands:      %d\n",
+		       intf->timed_out_lan_commands);
+	out += sprintf(out, "sent_lan_responses:          %d\n",
+		       intf->sent_lan_responses);
+	out += sprintf(out, "handled_lan_responses:       %d\n",
+		       intf->handled_lan_responses);
+	out += sprintf(out, "invalid_lan_responses:       %d\n",
+		       intf->invalid_lan_responses);
+	out += sprintf(out, "unhandled_lan_responses:     %d\n",
+		       intf->unhandled_lan_responses);
+	out += sprintf(out, "handled_commands:            %d\n",
+		       intf->handled_commands);
+	out += sprintf(out, "invalid_commands:            %d\n",
+		       intf->invalid_commands);
+	out += sprintf(out, "unhandled_commands:          %d\n",
+		       intf->unhandled_commands);
+	out += sprintf(out, "invalid_events:              %d\n",
+		       intf->invalid_events);
+	out += sprintf(out, "events:                      %d\n",
+		       intf->events);
+
+	return (out - ((char *) page));
+}
+
+int ipmi_smi_add_proc_entry(ipmi_smi_t smi, char *name,
+			    read_proc_t *read_proc, write_proc_t *write_proc,
+			    void *data, struct module *owner)
+{
+	struct proc_dir_entry  *file;
+	int                    rv = 0;
+	struct ipmi_proc_entry *entry;
+
+	/* Create a list element. */
+	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+	entry->name = kmalloc(strlen(name)+1, GFP_KERNEL);
+	if (!entry->name) {
+		kfree(entry);
+		return -ENOMEM;
+	}
+	strcpy(entry->name, name);
+
+	file = create_proc_entry(name, 0, smi->proc_dir);
+	if (!file) {
+		kfree(entry->name);
+		kfree(entry);
+		rv = -ENOMEM;
+	} else {
+		file->nlink = 1;
+		file->data = data;
+		file->read_proc = read_proc;
+		file->write_proc = write_proc;
+		file->owner = owner;
+
+		/* Stick it on the list. */
+		entry->next = smi->proc_entries;
+		smi->proc_entries = entry;
+	}
+
+	return rv;
+}
+
+static int add_proc_entries(ipmi_smi_t smi, int num)
+{
+	int rv = 0;
+
+	sprintf(smi->proc_dir_name, "%d", num);
+	smi->proc_dir = proc_mkdir(smi->proc_dir_name, proc_ipmi_root);
+	if (!smi->proc_dir)
+		rv = -ENOMEM;
+	else {
+		smi->proc_dir->owner = THIS_MODULE;
+	}
+
+	if (rv == 0)
+		rv = ipmi_smi_add_proc_entry(smi, "stats",
+					     stat_file_read_proc, NULL,
+					     smi, THIS_MODULE);
+
+	if (rv == 0)
+		rv = ipmi_smi_add_proc_entry(smi, "ipmb",
+					     ipmb_file_read_proc, NULL,
+					     smi, THIS_MODULE);
+
+	if (rv == 0)
+		rv = ipmi_smi_add_proc_entry(smi, "version",
+					     version_file_read_proc, NULL,
+					     smi, THIS_MODULE);
+
+	return rv;
+}
+
+static void remove_proc_entries(ipmi_smi_t smi)
+{
+	struct ipmi_proc_entry *entry;
+
+	while (smi->proc_entries) {
+		entry = smi->proc_entries;
+		smi->proc_entries = entry->next;
+
+		remove_proc_entry(entry->name, smi->proc_dir);
+		kfree(entry->name);
+		kfree(entry);
+	}
+	remove_proc_entry(smi->proc_dir_name, proc_ipmi_root);
+}
+
+static int
+send_channel_info_cmd(ipmi_smi_t intf, int chan)
+{
+	struct kernel_ipmi_msg            msg;
+	unsigned char                     data[1];
+	struct ipmi_system_interface_addr si;
+
+	si.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	si.channel = IPMI_BMC_CHANNEL;
+	si.lun = 0;
+
+	msg.netfn = IPMI_NETFN_APP_REQUEST;
+	msg.cmd = IPMI_GET_CHANNEL_INFO_CMD;
+	msg.data = data;
+	msg.data_len = 1;
+	data[0] = chan;
+	return i_ipmi_request(NULL,
+			      intf,
+			      (struct ipmi_addr *) &si,
+			      0,
+			      &msg,
+			      NULL,
+			      NULL,
+			      NULL,
+			      0,
+			      intf->my_address,
+			      intf->my_lun,
+			      -1, 0);
+}
+
+static void
+channel_handler(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+	int rv = 0;
+	int chan;
+
+	if ((msg->rsp[0] == (IPMI_NETFN_APP_RESPONSE << 2))
+	    && (msg->rsp[1] == IPMI_GET_CHANNEL_INFO_CMD))
+	{
+		/* It's the one we want */
+		if (msg->rsp[2] != 0) {
+			/* Got an error from the channel, just go on. */
+
+			if (msg->rsp[2] == IPMI_INVALID_COMMAND_ERR) {
+				/* If the MC does not support this
+				   command, that is legal.  We just
+				   assume it has one IPMB at channel
+				   zero. */
+				intf->channels[0].medium
+					= IPMI_CHANNEL_MEDIUM_IPMB;
+				intf->channels[0].protocol
+					= IPMI_CHANNEL_PROTOCOL_IPMB;
+				rv = -ENOSYS;
+
+				intf->curr_channel = IPMI_MAX_CHANNELS;
+				wake_up(&intf->waitq);
+				goto out;
+			}
+			goto next_channel;
+		}
+		if (msg->rsp_size < 6) {
+			/* Message not big enough, just go on. */
+			goto next_channel;
+		}
+		chan = intf->curr_channel;
+		intf->channels[chan].medium = msg->rsp[4] & 0x7f;
+		intf->channels[chan].protocol = msg->rsp[5] & 0x1f;
+
+	next_channel:
+		intf->curr_channel++;
+		if (intf->curr_channel >= IPMI_MAX_CHANNELS)
+			wake_up(&intf->waitq);
+		else
+			rv = send_channel_info_cmd(intf, intf->curr_channel);
+
+		if (rv) {
+			/* Got an error somehow, just give up. */
+			intf->curr_channel = IPMI_MAX_CHANNELS;
+			wake_up(&intf->waitq);
+
+			printk(KERN_WARNING PFX
+			       "Error sending channel information: %d\n",
+			       rv);
+		}
+	}
+ out:
+	return;
+}
+
+int ipmi_register_smi(struct ipmi_smi_handlers *handlers,
+		      void		       *send_info,
+		      unsigned char            version_major,
+		      unsigned char            version_minor,
+		      unsigned char            slave_addr,
+		      ipmi_smi_t               *intf)
+{
+	int              i, j;
+	int              rv;
+	ipmi_smi_t       new_intf;
+	unsigned long    flags;
+
+
+	/* Make sure the driver is actually initialized, this handles
+	   problems with initialization order. */
+	if (!initialized) {
+		rv = ipmi_init_msghandler();
+		if (rv)
+			return rv;
+		/* The init code doesn't return an error if it was turned
+		   off, but it won't initialize.  Check that. */
+		if (!initialized)
+			return -ENODEV;
+	}
+
+	new_intf = kmalloc(sizeof(*new_intf), GFP_KERNEL);
+	if (!new_intf)
+		return -ENOMEM;
+	memset(new_intf, 0, sizeof(*new_intf));
+
+	new_intf->proc_dir = NULL;
+
+	rv = -ENOMEM;
+
+	down_write(&interfaces_sem);
+	for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+		if (ipmi_interfaces[i] == NULL) {
+			new_intf->intf_num = i;
+			new_intf->version_major = version_major;
+			new_intf->version_minor = version_minor;
+			if (slave_addr == 0)
+				new_intf->my_address = IPMI_BMC_SLAVE_ADDR;
+			else
+				new_intf->my_address = slave_addr;
+			new_intf->my_lun = 2;  /* the SMS LUN. */
+			rwlock_init(&(new_intf->users_lock));
+			INIT_LIST_HEAD(&(new_intf->users));
+			new_intf->handlers = handlers;
+			new_intf->send_info = send_info;
+			spin_lock_init(&(new_intf->seq_lock));
+			for (j=0; j<IPMI_IPMB_NUM_SEQ; j++) {
+				new_intf->seq_table[j].inuse = 0;
+				new_intf->seq_table[j].seqid = 0;
+			}
+			new_intf->curr_seq = 0;
+			spin_lock_init(&(new_intf->waiting_msgs_lock));
+			INIT_LIST_HEAD(&(new_intf->waiting_msgs));
+			spin_lock_init(&(new_intf->events_lock));
+			INIT_LIST_HEAD(&(new_intf->waiting_events));
+			new_intf->waiting_events_count = 0;
+			rwlock_init(&(new_intf->cmd_rcvr_lock));
+			init_waitqueue_head(&new_intf->waitq);
+			INIT_LIST_HEAD(&(new_intf->cmd_rcvrs));
+			new_intf->all_cmd_rcvr = NULL;
+
+			spin_lock_init(&(new_intf->counter_lock));
+
+			spin_lock_irqsave(&interfaces_lock, flags);
+			ipmi_interfaces[i] = new_intf;
+			spin_unlock_irqrestore(&interfaces_lock, flags);
+
+			rv = 0;
+			*intf = new_intf;
+			break;
+		}
+	}
+
+	downgrade_write(&interfaces_sem);
+
+	if (rv == 0)
+		rv = add_proc_entries(*intf, i);
+
+	if (rv == 0) {
+		if ((version_major > 1)
+		    || ((version_major == 1) && (version_minor >= 5)))
+		{
+			/* Start scanning the channels to see what is
+			   available. */
+			(*intf)->null_user_handler = channel_handler;
+			(*intf)->curr_channel = 0;
+			rv = send_channel_info_cmd(*intf, 0);
+			if (rv)
+				goto out;
+
+			/* Wait for the channel info to be read. */
+			up_read(&interfaces_sem);
+			wait_event((*intf)->waitq,
+				   ((*intf)->curr_channel>=IPMI_MAX_CHANNELS));
+			down_read(&interfaces_sem);
+
+			if (ipmi_interfaces[i] != new_intf)
+				/* Well, it went away.  Just return. */
+				goto out;
+		} else {
+			/* Assume a single IPMB channel at zero. */
+			(*intf)->channels[0].medium = IPMI_CHANNEL_MEDIUM_IPMB;
+			(*intf)->channels[0].protocol
+				= IPMI_CHANNEL_PROTOCOL_IPMB;
+  		}
+
+		/* Call all the watcher interfaces to tell
+		   them that a new interface is available. */
+		call_smi_watchers(i);
+	}
+
+ out:
+	up_read(&interfaces_sem);
+
+	if (rv) {
+		if (new_intf->proc_dir)
+			remove_proc_entries(new_intf);
+		kfree(new_intf);
+	}
+
+	return rv;
+}
+
+static void free_recv_msg_list(struct list_head *q)
+{
+	struct ipmi_recv_msg *msg, *msg2;
+
+	list_for_each_entry_safe(msg, msg2, q, link) {
+		list_del(&msg->link);
+		ipmi_free_recv_msg(msg);
+	}
+}
+
+static void free_cmd_rcvr_list(struct list_head *q)
+{
+	struct cmd_rcvr  *rcvr, *rcvr2;
+
+	list_for_each_entry_safe(rcvr, rcvr2, q, link) {
+		list_del(&rcvr->link);
+		kfree(rcvr);
+	}
+}
+
+static void clean_up_interface_data(ipmi_smi_t intf)
+{
+	int i;
+
+	free_recv_msg_list(&(intf->waiting_msgs));
+	free_recv_msg_list(&(intf->waiting_events));
+	free_cmd_rcvr_list(&(intf->cmd_rcvrs));
+
+	for (i=0; i<IPMI_IPMB_NUM_SEQ; i++) {
+		if ((intf->seq_table[i].inuse)
+		    && (intf->seq_table[i].recv_msg))
+		{
+			ipmi_free_recv_msg(intf->seq_table[i].recv_msg);
+		}	
+	}
+}
+
+int ipmi_unregister_smi(ipmi_smi_t intf)
+{
+	int                     rv = -ENODEV;
+	int                     i;
+	struct ipmi_smi_watcher *w;
+	unsigned long           flags;
+
+	down_write(&interfaces_sem);
+	if (list_empty(&(intf->users)))
+	{
+		for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+			if (ipmi_interfaces[i] == intf) {
+				remove_proc_entries(intf);
+				spin_lock_irqsave(&interfaces_lock, flags);
+				ipmi_interfaces[i] = NULL;
+				clean_up_interface_data(intf);
+				spin_unlock_irqrestore(&interfaces_lock,flags);
+				kfree(intf);
+				rv = 0;
+				goto out_call_watcher;
+			}
+		}
+	} else {
+		rv = -EBUSY;
+	}
+	up_write(&interfaces_sem);
+
+	return rv;
+
+ out_call_watcher:
+	downgrade_write(&interfaces_sem);
+
+	/* Call all the watcher interfaces to tell them that
+	   an interface is gone. */
+	down_read(&smi_watchers_sem);
+	list_for_each_entry(w, &smi_watchers, link) {
+		w->smi_gone(i);
+	}
+	up_read(&smi_watchers_sem);
+	up_read(&interfaces_sem);
+	return 0;
+}
+
+static int handle_ipmb_get_msg_rsp(ipmi_smi_t          intf,
+				   struct ipmi_smi_msg *msg)
+{
+	struct ipmi_ipmb_addr ipmb_addr;
+	struct ipmi_recv_msg  *recv_msg;
+	unsigned long         flags;
+
+	
+	/* This is 11, not 10, because the response must contain a
+	 * completion code. */
+	if (msg->rsp_size < 11) {
+		/* Message not big enough, just ignore it. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->invalid_ipmb_responses++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		return 0;
+	}
+
+	if (msg->rsp[2] != 0) {
+		/* An error getting the response, just ignore it. */
+		return 0;
+	}
+
+	ipmb_addr.addr_type = IPMI_IPMB_ADDR_TYPE;
+	ipmb_addr.slave_addr = msg->rsp[6];
+	ipmb_addr.channel = msg->rsp[3] & 0x0f;
+	ipmb_addr.lun = msg->rsp[7] & 3;
+
+	/* It's a response from a remote entity.  Look up the sequence
+	   number and handle the response. */
+	if (intf_find_seq(intf,
+			  msg->rsp[7] >> 2,
+			  msg->rsp[3] & 0x0f,
+			  msg->rsp[8],
+			  (msg->rsp[4] >> 2) & (~1),
+			  (struct ipmi_addr *) &(ipmb_addr),
+			  &recv_msg))
+	{
+		/* We were unable to find the sequence number,
+		   so just nuke the message. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->unhandled_ipmb_responses++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		return 0;
+	}
+
+	memcpy(recv_msg->msg_data,
+	       &(msg->rsp[9]),
+	       msg->rsp_size - 9);
+	/* THe other fields matched, so no need to set them, except
+           for netfn, which needs to be the response that was
+           returned, not the request value. */
+	recv_msg->msg.netfn = msg->rsp[4] >> 2;
+	recv_msg->msg.data = recv_msg->msg_data;
+	recv_msg->msg.data_len = msg->rsp_size - 10;
+	recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+	spin_lock_irqsave(&intf->counter_lock, flags);
+	intf->handled_ipmb_responses++;
+	spin_unlock_irqrestore(&intf->counter_lock, flags);
+	deliver_response(recv_msg);
+
+	return 0;
+}
+
+static int handle_ipmb_get_msg_cmd(ipmi_smi_t          intf,
+				   struct ipmi_smi_msg *msg)
+{
+	struct cmd_rcvr       *rcvr;
+	int                   rv = 0;
+	unsigned char         netfn;
+	unsigned char         cmd;
+	ipmi_user_t           user = NULL;
+	struct ipmi_ipmb_addr *ipmb_addr;
+	struct ipmi_recv_msg  *recv_msg;
+	unsigned long         flags;
+
+	if (msg->rsp_size < 10) {
+		/* Message not big enough, just ignore it. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->invalid_commands++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		return 0;
+	}
+
+	if (msg->rsp[2] != 0) {
+		/* An error getting the response, just ignore it. */
+		return 0;
+	}
+
+	netfn = msg->rsp[4] >> 2;
+	cmd = msg->rsp[8];
+
+	read_lock(&(intf->cmd_rcvr_lock));
+	
+	if (intf->all_cmd_rcvr) {
+		user = intf->all_cmd_rcvr;
+	} else {
+		/* Find the command/netfn. */
+		list_for_each_entry(rcvr, &(intf->cmd_rcvrs), link) {
+			if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+				user = rcvr->user;
+				break;
+			}
+		}
+	}
+	read_unlock(&(intf->cmd_rcvr_lock));
+
+	if (user == NULL) {
+		/* We didn't find a user, deliver an error response. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->unhandled_commands++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+		msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+		msg->data[1] = IPMI_SEND_MSG_CMD;
+		msg->data[2] = msg->rsp[3];
+		msg->data[3] = msg->rsp[6];
+                msg->data[4] = ((netfn + 1) << 2) | (msg->rsp[7] & 0x3);
+		msg->data[5] = ipmb_checksum(&(msg->data[3]), 2);
+		msg->data[6] = intf->my_address;
+                /* rqseq/lun */
+                msg->data[7] = (msg->rsp[7] & 0xfc) | (msg->rsp[4] & 0x3);
+		msg->data[8] = msg->rsp[8]; /* cmd */
+		msg->data[9] = IPMI_INVALID_CMD_COMPLETION_CODE;
+		msg->data[10] = ipmb_checksum(&(msg->data[6]), 4);
+		msg->data_size = 11;
+
+#ifdef DEBUG_MSGING
+	{
+		int m;
+		printk("Invalid command:");
+		for (m=0; m<msg->data_size; m++)
+			printk(" %2.2x", msg->data[m]);
+		printk("\n");
+	}
+#endif
+		intf->handlers->sender(intf->send_info, msg, 0);
+
+		rv = -1; /* We used the message, so return the value that
+			    causes it to not be freed or queued. */
+	} else {
+		/* Deliver the message to the user. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->handled_commands++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+		recv_msg = ipmi_alloc_recv_msg();
+		if (! recv_msg) {
+			/* We couldn't allocate memory for the
+                           message, so requeue it for handling
+                           later. */
+			rv = 1;
+		} else {
+			/* Extract the source address from the data. */
+			ipmb_addr = (struct ipmi_ipmb_addr *) &recv_msg->addr;
+			ipmb_addr->addr_type = IPMI_IPMB_ADDR_TYPE;
+			ipmb_addr->slave_addr = msg->rsp[6];
+			ipmb_addr->lun = msg->rsp[7] & 3;
+			ipmb_addr->channel = msg->rsp[3] & 0xf;
+
+			/* Extract the rest of the message information
+			   from the IPMB header.*/
+			recv_msg->user = user;
+			recv_msg->recv_type = IPMI_CMD_RECV_TYPE;
+			recv_msg->msgid = msg->rsp[7] >> 2;
+			recv_msg->msg.netfn = msg->rsp[4] >> 2;
+			recv_msg->msg.cmd = msg->rsp[8];
+			recv_msg->msg.data = recv_msg->msg_data;
+
+			/* We chop off 10, not 9 bytes because the checksum
+			   at the end also needs to be removed. */
+			recv_msg->msg.data_len = msg->rsp_size - 10;
+			memcpy(recv_msg->msg_data,
+			       &(msg->rsp[9]),
+			       msg->rsp_size - 10);
+			deliver_response(recv_msg);
+		}
+	}
+
+	return rv;
+}
+
+static int handle_lan_get_msg_rsp(ipmi_smi_t          intf,
+				  struct ipmi_smi_msg *msg)
+{
+	struct ipmi_lan_addr  lan_addr;
+	struct ipmi_recv_msg  *recv_msg;
+	unsigned long         flags;
+
+
+	/* This is 13, not 12, because the response must contain a
+	 * completion code. */
+	if (msg->rsp_size < 13) {
+		/* Message not big enough, just ignore it. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->invalid_lan_responses++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		return 0;
+	}
+
+	if (msg->rsp[2] != 0) {
+		/* An error getting the response, just ignore it. */
+		return 0;
+	}
+
+	lan_addr.addr_type = IPMI_LAN_ADDR_TYPE;
+	lan_addr.session_handle = msg->rsp[4];
+	lan_addr.remote_SWID = msg->rsp[8];
+	lan_addr.local_SWID = msg->rsp[5];
+	lan_addr.channel = msg->rsp[3] & 0x0f;
+	lan_addr.privilege = msg->rsp[3] >> 4;
+	lan_addr.lun = msg->rsp[9] & 3;
+
+	/* It's a response from a remote entity.  Look up the sequence
+	   number and handle the response. */
+	if (intf_find_seq(intf,
+			  msg->rsp[9] >> 2,
+			  msg->rsp[3] & 0x0f,
+			  msg->rsp[10],
+			  (msg->rsp[6] >> 2) & (~1),
+			  (struct ipmi_addr *) &(lan_addr),
+			  &recv_msg))
+	{
+		/* We were unable to find the sequence number,
+		   so just nuke the message. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->unhandled_lan_responses++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		return 0;
+	}
+
+	memcpy(recv_msg->msg_data,
+	       &(msg->rsp[11]),
+	       msg->rsp_size - 11);
+	/* The other fields matched, so no need to set them, except
+           for netfn, which needs to be the response that was
+           returned, not the request value. */
+	recv_msg->msg.netfn = msg->rsp[6] >> 2;
+	recv_msg->msg.data = recv_msg->msg_data;
+	recv_msg->msg.data_len = msg->rsp_size - 12;
+	recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+	spin_lock_irqsave(&intf->counter_lock, flags);
+	intf->handled_lan_responses++;
+	spin_unlock_irqrestore(&intf->counter_lock, flags);
+	deliver_response(recv_msg);
+
+	return 0;
+}
+
+static int handle_lan_get_msg_cmd(ipmi_smi_t          intf,
+				  struct ipmi_smi_msg *msg)
+{
+	struct cmd_rcvr       *rcvr;
+	int                   rv = 0;
+	unsigned char         netfn;
+	unsigned char         cmd;
+	ipmi_user_t           user = NULL;
+	struct ipmi_lan_addr  *lan_addr;
+	struct ipmi_recv_msg  *recv_msg;
+	unsigned long         flags;
+
+	if (msg->rsp_size < 12) {
+		/* Message not big enough, just ignore it. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->invalid_commands++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		return 0;
+	}
+
+	if (msg->rsp[2] != 0) {
+		/* An error getting the response, just ignore it. */
+		return 0;
+	}
+
+	netfn = msg->rsp[6] >> 2;
+	cmd = msg->rsp[10];
+
+	read_lock(&(intf->cmd_rcvr_lock));
+
+	if (intf->all_cmd_rcvr) {
+		user = intf->all_cmd_rcvr;
+	} else {
+		/* Find the command/netfn. */
+		list_for_each_entry(rcvr, &(intf->cmd_rcvrs), link) {
+			if ((rcvr->netfn == netfn) && (rcvr->cmd == cmd)) {
+				user = rcvr->user;
+				break;
+			}
+		}
+	}
+	read_unlock(&(intf->cmd_rcvr_lock));
+
+	if (user == NULL) {
+		/* We didn't find a user, deliver an error response. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->unhandled_commands++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+		rv = 0; /* Don't do anything with these messages, just
+			   allow them to be freed. */
+	} else {
+		/* Deliver the message to the user. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->handled_commands++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+
+		recv_msg = ipmi_alloc_recv_msg();
+		if (! recv_msg) {
+			/* We couldn't allocate memory for the
+                           message, so requeue it for handling
+                           later. */
+			rv = 1;
+		} else {
+			/* Extract the source address from the data. */
+			lan_addr = (struct ipmi_lan_addr *) &recv_msg->addr;
+			lan_addr->addr_type = IPMI_LAN_ADDR_TYPE;
+			lan_addr->session_handle = msg->rsp[4];
+			lan_addr->remote_SWID = msg->rsp[8];
+			lan_addr->local_SWID = msg->rsp[5];
+			lan_addr->lun = msg->rsp[9] & 3;
+			lan_addr->channel = msg->rsp[3] & 0xf;
+			lan_addr->privilege = msg->rsp[3] >> 4;
+
+			/* Extract the rest of the message information
+			   from the IPMB header.*/
+			recv_msg->user = user;
+			recv_msg->recv_type = IPMI_CMD_RECV_TYPE;
+			recv_msg->msgid = msg->rsp[9] >> 2;
+			recv_msg->msg.netfn = msg->rsp[6] >> 2;
+			recv_msg->msg.cmd = msg->rsp[10];
+			recv_msg->msg.data = recv_msg->msg_data;
+
+			/* We chop off 12, not 11 bytes because the checksum
+			   at the end also needs to be removed. */
+			recv_msg->msg.data_len = msg->rsp_size - 12;
+			memcpy(recv_msg->msg_data,
+			       &(msg->rsp[11]),
+			       msg->rsp_size - 12);
+			deliver_response(recv_msg);
+		}
+	}
+
+	return rv;
+}
+
+static void copy_event_into_recv_msg(struct ipmi_recv_msg *recv_msg,
+				     struct ipmi_smi_msg  *msg)
+{
+	struct ipmi_system_interface_addr *smi_addr;
+	
+	recv_msg->msgid = 0;
+	smi_addr = (struct ipmi_system_interface_addr *) &(recv_msg->addr);
+	smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	smi_addr->channel = IPMI_BMC_CHANNEL;
+	smi_addr->lun = msg->rsp[0] & 3;
+	recv_msg->recv_type = IPMI_ASYNC_EVENT_RECV_TYPE;
+	recv_msg->msg.netfn = msg->rsp[0] >> 2;
+	recv_msg->msg.cmd = msg->rsp[1];
+	memcpy(recv_msg->msg_data, &(msg->rsp[3]), msg->rsp_size - 3);
+	recv_msg->msg.data = recv_msg->msg_data;
+	recv_msg->msg.data_len = msg->rsp_size - 3;
+}
+
+/* This will be called with the intf->users_lock read-locked, so no need
+   to do that here. */
+static int handle_read_event_rsp(ipmi_smi_t          intf,
+				 struct ipmi_smi_msg *msg)
+{
+	struct ipmi_recv_msg *recv_msg, *recv_msg2;
+	struct list_head     msgs;
+	ipmi_user_t          user;
+	int                  rv = 0;
+	int                  deliver_count = 0;
+	unsigned long        flags;
+
+	if (msg->rsp_size < 19) {
+		/* Message is too small to be an IPMB event. */
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->invalid_events++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		return 0;
+	}
+
+	if (msg->rsp[2] != 0) {
+		/* An error getting the event, just ignore it. */
+		return 0;
+	}
+
+	INIT_LIST_HEAD(&msgs);
+
+	spin_lock_irqsave(&(intf->events_lock), flags);
+
+	spin_lock(&intf->counter_lock);
+	intf->events++;
+	spin_unlock(&intf->counter_lock);
+
+	/* Allocate and fill in one message for every user that is getting
+	   events. */
+	list_for_each_entry(user, &(intf->users), link) {
+		if (! user->gets_events)
+			continue;
+
+		recv_msg = ipmi_alloc_recv_msg();
+		if (! recv_msg) {
+			list_for_each_entry_safe(recv_msg, recv_msg2, &msgs, link) {
+				list_del(&recv_msg->link);
+				ipmi_free_recv_msg(recv_msg);
+			}
+			/* We couldn't allocate memory for the
+                           message, so requeue it for handling
+                           later. */
+			rv = 1;
+			goto out;
+		}
+
+		deliver_count++;
+
+		copy_event_into_recv_msg(recv_msg, msg);
+		recv_msg->user = user;
+		list_add_tail(&(recv_msg->link), &msgs);
+	}
+
+	if (deliver_count) {
+		/* Now deliver all the messages. */
+		list_for_each_entry_safe(recv_msg, recv_msg2, &msgs, link) {
+			list_del(&recv_msg->link);
+			deliver_response(recv_msg);
+		}
+	} else if (intf->waiting_events_count < MAX_EVENTS_IN_QUEUE) {
+		/* No one to receive the message, put it in queue if there's
+		   not already too many things in the queue. */
+		recv_msg = ipmi_alloc_recv_msg();
+		if (! recv_msg) {
+			/* We couldn't allocate memory for the
+                           message, so requeue it for handling
+                           later. */
+			rv = 1;
+			goto out;
+		}
+
+		copy_event_into_recv_msg(recv_msg, msg);
+		list_add_tail(&(recv_msg->link), &(intf->waiting_events));
+	} else {
+		/* There's too many things in the queue, discard this
+		   message. */
+		printk(KERN_WARNING PFX "Event queue full, discarding an"
+		       " incoming event\n");
+	}
+
+ out:
+	spin_unlock_irqrestore(&(intf->events_lock), flags);
+
+	return rv;
+}
+
+static int handle_bmc_rsp(ipmi_smi_t          intf,
+			  struct ipmi_smi_msg *msg)
+{
+	struct ipmi_recv_msg *recv_msg;
+	int                  found = 0;
+	struct ipmi_user     *user;
+	unsigned long        flags;
+
+	recv_msg = (struct ipmi_recv_msg *) msg->user_data;
+
+	/* Make sure the user still exists. */
+	list_for_each_entry(user, &(intf->users), link) {
+		if (user == recv_msg->user) {
+			/* Found it, so we can deliver it */
+			found = 1;
+			break;
+		}
+	}
+
+	if (!found) {
+		/* Special handling for NULL users. */
+		if (!recv_msg->user && intf->null_user_handler){
+			intf->null_user_handler(intf, msg);
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->handled_local_responses++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+		}else{
+			/* The user for the message went away, so give up. */
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			intf->unhandled_local_responses++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+		}
+		ipmi_free_recv_msg(recv_msg);
+	} else {
+		struct ipmi_system_interface_addr *smi_addr;
+
+		spin_lock_irqsave(&intf->counter_lock, flags);
+		intf->handled_local_responses++;
+		spin_unlock_irqrestore(&intf->counter_lock, flags);
+		recv_msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+		recv_msg->msgid = msg->msgid;
+		smi_addr = ((struct ipmi_system_interface_addr *)
+			    &(recv_msg->addr));
+		smi_addr->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+		smi_addr->channel = IPMI_BMC_CHANNEL;
+		smi_addr->lun = msg->rsp[0] & 3;
+		recv_msg->msg.netfn = msg->rsp[0] >> 2;
+		recv_msg->msg.cmd = msg->rsp[1];
+		memcpy(recv_msg->msg_data,
+		       &(msg->rsp[2]),
+		       msg->rsp_size - 2);
+		recv_msg->msg.data = recv_msg->msg_data;
+		recv_msg->msg.data_len = msg->rsp_size - 2;
+		deliver_response(recv_msg);
+	}
+
+	return 0;
+}
+
+/* Handle a new message.  Return 1 if the message should be requeued,
+   0 if the message should be freed, or -1 if the message should not
+   be freed or requeued. */
+static int handle_new_recv_msg(ipmi_smi_t          intf,
+			       struct ipmi_smi_msg *msg)
+{
+	int requeue;
+	int chan;
+
+#ifdef DEBUG_MSGING
+	int m;
+	printk("Recv:");
+	for (m=0; m<msg->rsp_size; m++)
+		printk(" %2.2x", msg->rsp[m]);
+	printk("\n");
+#endif
+	if (msg->rsp_size < 2) {
+		/* Message is too small to be correct. */
+		printk(KERN_WARNING PFX "BMC returned to small a message"
+		       " for netfn %x cmd %x, got %d bytes\n",
+		       (msg->data[0] >> 2) | 1, msg->data[1], msg->rsp_size);
+
+		/* Generate an error response for the message. */
+		msg->rsp[0] = msg->data[0] | (1 << 2);
+		msg->rsp[1] = msg->data[1];
+		msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
+		msg->rsp_size = 3;
+	} else if (((msg->rsp[0] >> 2) != ((msg->data[0] >> 2) | 1))/* Netfn */
+		   || (msg->rsp[1] != msg->data[1]))		  /* Command */
+	{
+		/* The response is not even marginally correct. */
+		printk(KERN_WARNING PFX "BMC returned incorrect response,"
+		       " expected netfn %x cmd %x, got netfn %x cmd %x\n",
+		       (msg->data[0] >> 2) | 1, msg->data[1],
+		       msg->rsp[0] >> 2, msg->rsp[1]);
+
+		/* Generate an error response for the message. */
+		msg->rsp[0] = msg->data[0] | (1 << 2);
+		msg->rsp[1] = msg->data[1];
+		msg->rsp[2] = IPMI_ERR_UNSPECIFIED;
+		msg->rsp_size = 3;
+	}
+
+	if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+	    && (msg->rsp[1] == IPMI_SEND_MSG_CMD)
+	    && (msg->user_data != NULL))
+	{
+		/* It's a response to a response we sent.  For this we
+		   deliver a send message response to the user. */
+		struct ipmi_recv_msg *recv_msg = msg->user_data;
+
+		requeue = 0;
+		if (msg->rsp_size < 2)
+			/* Message is too small to be correct. */
+			goto out;
+
+		chan = msg->data[2] & 0x0f;
+		if (chan >= IPMI_MAX_CHANNELS)
+			/* Invalid channel number */
+			goto out;
+
+		if (recv_msg) {
+			recv_msg->recv_type = IPMI_RESPONSE_RESPONSE_TYPE;
+			recv_msg->msg.data = recv_msg->msg_data;
+			recv_msg->msg.data_len = 1;
+			recv_msg->msg_data[0] = msg->rsp[2];
+			deliver_response(recv_msg);
+		}
+	} else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+		   && (msg->rsp[1] == IPMI_GET_MSG_CMD))
+	{
+		/* It's from the receive queue. */
+		chan = msg->rsp[3] & 0xf;
+		if (chan >= IPMI_MAX_CHANNELS) {
+			/* Invalid channel number */
+			requeue = 0;
+			goto out;
+		}
+
+		switch (intf->channels[chan].medium) {
+		case IPMI_CHANNEL_MEDIUM_IPMB:
+			if (msg->rsp[4] & 0x04) {
+				/* It's a response, so find the
+				   requesting message and send it up. */
+				requeue = handle_ipmb_get_msg_rsp(intf, msg);
+			} else {
+				/* It's a command to the SMS from some other
+				   entity.  Handle that. */
+				requeue = handle_ipmb_get_msg_cmd(intf, msg);
+			}
+			break;
+
+		case IPMI_CHANNEL_MEDIUM_8023LAN:
+		case IPMI_CHANNEL_MEDIUM_ASYNC:
+			if (msg->rsp[6] & 0x04) {
+				/* It's a response, so find the
+				   requesting message and send it up. */
+				requeue = handle_lan_get_msg_rsp(intf, msg);
+			} else {
+				/* It's a command to the SMS from some other
+				   entity.  Handle that. */
+				requeue = handle_lan_get_msg_cmd(intf, msg);
+			}
+			break;
+
+		default:
+			/* We don't handle the channel type, so just
+			 * free the message. */
+			requeue = 0;
+		}
+
+	} else if ((msg->rsp[0] == ((IPMI_NETFN_APP_REQUEST|1) << 2))
+		   && (msg->rsp[1] == IPMI_READ_EVENT_MSG_BUFFER_CMD))
+	{
+		/* It's an asyncronous event. */
+		requeue = handle_read_event_rsp(intf, msg);
+	} else {
+		/* It's a response from the local BMC. */
+		requeue = handle_bmc_rsp(intf, msg);
+	}
+
+ out:
+	return requeue;
+}
+
+/* Handle a new message from the lower layer. */
+void ipmi_smi_msg_received(ipmi_smi_t          intf,
+			   struct ipmi_smi_msg *msg)
+{
+	unsigned long flags;
+	int           rv;
+
+
+	/* Lock the user lock so the user can't go away while we are
+	   working on it. */
+	read_lock(&(intf->users_lock));
+
+	if ((msg->data_size >= 2)
+	    && (msg->data[0] == (IPMI_NETFN_APP_REQUEST << 2))
+	    && (msg->data[1] == IPMI_SEND_MSG_CMD)
+	    && (msg->user_data == NULL)) {
+		/* This is the local response to a command send, start
+                   the timer for these.  The user_data will not be
+                   NULL if this is a response send, and we will let
+                   response sends just go through. */
+
+		/* Check for errors, if we get certain errors (ones
+                   that mean basically we can try again later), we
+                   ignore them and start the timer.  Otherwise we
+                   report the error immediately. */
+		if ((msg->rsp_size >= 3) && (msg->rsp[2] != 0)
+		    && (msg->rsp[2] != IPMI_NODE_BUSY_ERR)
+		    && (msg->rsp[2] != IPMI_LOST_ARBITRATION_ERR))
+		{
+			int chan = msg->rsp[3] & 0xf;
+
+			/* Got an error sending the message, handle it. */
+			spin_lock_irqsave(&intf->counter_lock, flags);
+			if (chan >= IPMI_MAX_CHANNELS)
+				; /* This shouldn't happen */
+			else if ((intf->channels[chan].medium
+				  == IPMI_CHANNEL_MEDIUM_8023LAN)
+				 || (intf->channels[chan].medium
+				     == IPMI_CHANNEL_MEDIUM_ASYNC))
+				intf->sent_lan_command_errs++;
+			else
+				intf->sent_ipmb_command_errs++;
+			spin_unlock_irqrestore(&intf->counter_lock, flags);
+			intf_err_seq(intf, msg->msgid, msg->rsp[2]);
+		} else {
+			/* The message was sent, start the timer. */
+			intf_start_seq_timer(intf, msg->msgid);
+		}
+
+		ipmi_free_smi_msg(msg);
+		goto out_unlock;
+	}
+
+	/* To preserve message order, if the list is not empty, we
+           tack this message onto the end of the list. */
+	spin_lock_irqsave(&(intf->waiting_msgs_lock), flags);
+	if (!list_empty(&(intf->waiting_msgs))) {
+		list_add_tail(&(msg->link), &(intf->waiting_msgs));
+		spin_unlock(&(intf->waiting_msgs_lock));
+		goto out_unlock;
+	}
+	spin_unlock_irqrestore(&(intf->waiting_msgs_lock), flags);
+		
+	rv = handle_new_recv_msg(intf, msg);
+	if (rv > 0) {
+		/* Could not handle the message now, just add it to a
+                   list to handle later. */
+		spin_lock(&(intf->waiting_msgs_lock));
+		list_add_tail(&(msg->link), &(intf->waiting_msgs));
+		spin_unlock(&(intf->waiting_msgs_lock));
+	} else if (rv == 0) {
+		ipmi_free_smi_msg(msg);
+	}
+
+ out_unlock:
+	read_unlock(&(intf->users_lock));
+}
+
+void ipmi_smi_watchdog_pretimeout(ipmi_smi_t intf)
+{
+	ipmi_user_t user;
+
+	read_lock(&(intf->users_lock));
+	list_for_each_entry(user, &(intf->users), link) {
+		if (! user->handler->ipmi_watchdog_pretimeout)
+			continue;
+
+		user->handler->ipmi_watchdog_pretimeout(user->handler_data);
+	}
+	read_unlock(&(intf->users_lock));
+}
+
+static void
+handle_msg_timeout(struct ipmi_recv_msg *msg)
+{
+	msg->recv_type = IPMI_RESPONSE_RECV_TYPE;
+	msg->msg_data[0] = IPMI_TIMEOUT_COMPLETION_CODE;
+	msg->msg.netfn |= 1; /* Convert to a response. */
+	msg->msg.data_len = 1;
+	msg->msg.data = msg->msg_data;
+	deliver_response(msg);
+}
+
+static void
+send_from_recv_msg(ipmi_smi_t intf, struct ipmi_recv_msg *recv_msg,
+		   struct ipmi_smi_msg *smi_msg,
+		   unsigned char seq, long seqid)
+{
+	if (!smi_msg)
+		smi_msg = ipmi_alloc_smi_msg();
+	if (!smi_msg)
+		/* If we can't allocate the message, then just return, we
+		   get 4 retries, so this should be ok. */
+		return;
+
+	memcpy(smi_msg->data, recv_msg->msg.data, recv_msg->msg.data_len);
+	smi_msg->data_size = recv_msg->msg.data_len;
+	smi_msg->msgid = STORE_SEQ_IN_MSGID(seq, seqid);
+		
+	/* Send the new message.  We send with a zero priority.  It
+	   timed out, I doubt time is that critical now, and high
+	   priority messages are really only for messages to the local
+	   MC, which don't get resent. */
+	intf->handlers->sender(intf->send_info, smi_msg, 0);
+
+#ifdef DEBUG_MSGING
+	{
+		int m;
+		printk("Resend: ");
+		for (m=0; m<smi_msg->data_size; m++)
+			printk(" %2.2x", smi_msg->data[m]);
+		printk("\n");
+	}
+#endif
+}
+
+static void
+ipmi_timeout_handler(long timeout_period)
+{
+	ipmi_smi_t           intf;
+	struct list_head     timeouts;
+	struct ipmi_recv_msg *msg, *msg2;
+	struct ipmi_smi_msg  *smi_msg, *smi_msg2;
+	unsigned long        flags;
+	int                  i, j;
+
+	INIT_LIST_HEAD(&timeouts);
+
+	spin_lock(&interfaces_lock);
+	for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+		intf = ipmi_interfaces[i];
+		if (intf == NULL)
+			continue;
+
+		read_lock(&(intf->users_lock));
+
+		/* See if any waiting messages need to be processed. */
+		spin_lock_irqsave(&(intf->waiting_msgs_lock), flags);
+		list_for_each_entry_safe(smi_msg, smi_msg2, &(intf->waiting_msgs), link) {
+			if (! handle_new_recv_msg(intf, smi_msg)) {
+				list_del(&smi_msg->link);
+				ipmi_free_smi_msg(smi_msg);
+			} else {
+				/* To preserve message order, quit if we
+				   can't handle a message. */
+				break;
+			}
+		}
+		spin_unlock_irqrestore(&(intf->waiting_msgs_lock), flags);
+
+		/* Go through the seq table and find any messages that
+		   have timed out, putting them in the timeouts
+		   list. */
+		spin_lock_irqsave(&(intf->seq_lock), flags);
+		for (j=0; j<IPMI_IPMB_NUM_SEQ; j++) {
+			struct seq_table *ent = &(intf->seq_table[j]);
+			if (!ent->inuse)
+				continue;
+
+			ent->timeout -= timeout_period;
+			if (ent->timeout > 0)
+				continue;
+
+			if (ent->retries_left == 0) {
+				/* The message has used all its retries. */
+				ent->inuse = 0;
+				msg = ent->recv_msg;
+				list_add_tail(&(msg->link), &timeouts);
+				spin_lock(&intf->counter_lock);
+				if (ent->broadcast)
+					intf->timed_out_ipmb_broadcasts++;
+				else if (ent->recv_msg->addr.addr_type
+					 == IPMI_LAN_ADDR_TYPE)
+					intf->timed_out_lan_commands++;
+				else
+					intf->timed_out_ipmb_commands++;
+				spin_unlock(&intf->counter_lock);
+			} else {
+				/* More retries, send again. */
+
+				/* Start with the max timer, set to normal
+				   timer after the message is sent. */
+				ent->timeout = MAX_MSG_TIMEOUT;
+				ent->retries_left--;
+				send_from_recv_msg(intf, ent->recv_msg, NULL,
+						   j, ent->seqid);
+				spin_lock(&intf->counter_lock);
+				if (ent->recv_msg->addr.addr_type
+				    == IPMI_LAN_ADDR_TYPE)
+					intf->retransmitted_lan_commands++;
+				else
+					intf->retransmitted_ipmb_commands++;
+				spin_unlock(&intf->counter_lock);
+			}
+		}
+		spin_unlock_irqrestore(&(intf->seq_lock), flags);
+
+		list_for_each_entry_safe(msg, msg2, &timeouts, link) {
+			handle_msg_timeout(msg);
+		}
+
+		read_unlock(&(intf->users_lock));
+	}
+	spin_unlock(&interfaces_lock);
+}
+
+static void ipmi_request_event(void)
+{
+	ipmi_smi_t intf;
+	int        i;
+
+	spin_lock(&interfaces_lock);
+	for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+		intf = ipmi_interfaces[i];
+		if (intf == NULL)
+			continue;
+
+		intf->handlers->request_events(intf->send_info);
+	}
+	spin_unlock(&interfaces_lock);
+}
+
+static struct timer_list ipmi_timer;
+
+/* Call every ~100 ms. */
+#define IPMI_TIMEOUT_TIME	100
+
+/* How many jiffies does it take to get to the timeout time. */
+#define IPMI_TIMEOUT_JIFFIES	((IPMI_TIMEOUT_TIME * HZ) / 1000)
+
+/* Request events from the queue every second (this is the number of
+   IPMI_TIMEOUT_TIMES between event requests).  Hopefully, in the
+   future, IPMI will add a way to know immediately if an event is in
+   the queue and this silliness can go away. */
+#define IPMI_REQUEST_EV_TIME	(1000 / (IPMI_TIMEOUT_TIME))
+
+static volatile int stop_operation = 0;
+static volatile int timer_stopped = 0;
+static unsigned int ticks_to_req_ev = IPMI_REQUEST_EV_TIME;
+
+static void ipmi_timeout(unsigned long data)
+{
+	if (stop_operation) {
+		timer_stopped = 1;
+		return;
+	}
+
+	ticks_to_req_ev--;
+	if (ticks_to_req_ev == 0) {
+		ipmi_request_event();
+		ticks_to_req_ev = IPMI_REQUEST_EV_TIME;
+	}
+
+	ipmi_timeout_handler(IPMI_TIMEOUT_TIME);
+
+	ipmi_timer.expires += IPMI_TIMEOUT_JIFFIES;
+	add_timer(&ipmi_timer);
+}
+
+
+static atomic_t smi_msg_inuse_count = ATOMIC_INIT(0);
+static atomic_t recv_msg_inuse_count = ATOMIC_INIT(0);
+
+/* FIXME - convert these to slabs. */
+static void free_smi_msg(struct ipmi_smi_msg *msg)
+{
+	atomic_dec(&smi_msg_inuse_count);
+	kfree(msg);
+}
+
+struct ipmi_smi_msg *ipmi_alloc_smi_msg(void)
+{
+	struct ipmi_smi_msg *rv;
+	rv = kmalloc(sizeof(struct ipmi_smi_msg), GFP_ATOMIC);
+	if (rv) {
+		rv->done = free_smi_msg;
+		rv->user_data = NULL;
+		atomic_inc(&smi_msg_inuse_count);
+	}
+	return rv;
+}
+
+static void free_recv_msg(struct ipmi_recv_msg *msg)
+{
+	atomic_dec(&recv_msg_inuse_count);
+	kfree(msg);
+}
+
+struct ipmi_recv_msg *ipmi_alloc_recv_msg(void)
+{
+	struct ipmi_recv_msg *rv;
+
+	rv = kmalloc(sizeof(struct ipmi_recv_msg), GFP_ATOMIC);
+	if (rv) {
+		rv->done = free_recv_msg;
+		atomic_inc(&recv_msg_inuse_count);
+	}
+	return rv;
+}
+
+#ifdef CONFIG_IPMI_PANIC_EVENT
+
+static void dummy_smi_done_handler(struct ipmi_smi_msg *msg)
+{
+}
+
+static void dummy_recv_done_handler(struct ipmi_recv_msg *msg)
+{
+}
+
+#ifdef CONFIG_IPMI_PANIC_STRING
+static void event_receiver_fetcher(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+	if ((msg->rsp[0] == (IPMI_NETFN_SENSOR_EVENT_RESPONSE << 2))
+	    && (msg->rsp[1] == IPMI_GET_EVENT_RECEIVER_CMD)
+	    && (msg->rsp[2] == IPMI_CC_NO_ERROR))
+	{
+		/* A get event receiver command, save it. */
+		intf->event_receiver = msg->rsp[3];
+		intf->event_receiver_lun = msg->rsp[4] & 0x3;
+	}
+}
+
+static void device_id_fetcher(ipmi_smi_t intf, struct ipmi_smi_msg *msg)
+{
+	if ((msg->rsp[0] == (IPMI_NETFN_APP_RESPONSE << 2))
+	    && (msg->rsp[1] == IPMI_GET_DEVICE_ID_CMD)
+	    && (msg->rsp[2] == IPMI_CC_NO_ERROR))
+	{
+		/* A get device id command, save if we are an event
+		   receiver or generator. */
+		intf->local_sel_device = (msg->rsp[8] >> 2) & 1;
+		intf->local_event_generator = (msg->rsp[8] >> 5) & 1;
+	}
+}
+#endif
+
+static void send_panic_events(char *str)
+{
+	struct kernel_ipmi_msg            msg;
+	ipmi_smi_t                        intf;
+	unsigned char                     data[16];
+	int                               i;
+	struct ipmi_system_interface_addr *si;
+	struct ipmi_addr                  addr;
+	struct ipmi_smi_msg               smi_msg;
+	struct ipmi_recv_msg              recv_msg;
+
+	si = (struct ipmi_system_interface_addr *) &addr;
+	si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	si->channel = IPMI_BMC_CHANNEL;
+	si->lun = 0;
+
+	/* Fill in an event telling that we have failed. */
+	msg.netfn = 0x04; /* Sensor or Event. */
+	msg.cmd = 2; /* Platform event command. */
+	msg.data = data;
+	msg.data_len = 8;
+	data[0] = 0x21; /* Kernel generator ID, IPMI table 5-4 */
+	data[1] = 0x03; /* This is for IPMI 1.0. */
+	data[2] = 0x20; /* OS Critical Stop, IPMI table 36-3 */
+	data[4] = 0x6f; /* Sensor specific, IPMI table 36-1 */
+	data[5] = 0xa1; /* Runtime stop OEM bytes 2 & 3. */
+
+	/* Put a few breadcrumbs in.  Hopefully later we can add more things
+	   to make the panic events more useful. */
+	if (str) {
+		data[3] = str[0];
+		data[6] = str[1];
+		data[7] = str[2];
+	}
+
+	smi_msg.done = dummy_smi_done_handler;
+	recv_msg.done = dummy_recv_done_handler;
+
+	/* For every registered interface, send the event. */
+	for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+		intf = ipmi_interfaces[i];
+		if (intf == NULL)
+			continue;
+
+		/* Send the event announcing the panic. */
+		intf->handlers->set_run_to_completion(intf->send_info, 1);
+		i_ipmi_request(NULL,
+			       intf,
+			       &addr,
+			       0,
+			       &msg,
+			       NULL,
+			       &smi_msg,
+			       &recv_msg,
+			       0,
+			       intf->my_address,
+			       intf->my_lun,
+			       0, 1); /* Don't retry, and don't wait. */
+	}
+
+#ifdef CONFIG_IPMI_PANIC_STRING
+	/* On every interface, dump a bunch of OEM event holding the
+	   string. */
+	if (!str) 
+		return;
+
+	for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+		char                  *p = str;
+		struct ipmi_ipmb_addr *ipmb;
+		int                   j;
+
+		intf = ipmi_interfaces[i];
+		if (intf == NULL)
+			continue;
+
+		/* First job here is to figure out where to send the
+		   OEM events.  There's no way in IPMI to send OEM
+		   events using an event send command, so we have to
+		   find the SEL to put them in and stick them in
+		   there. */
+
+		/* Get capabilities from the get device id. */
+		intf->local_sel_device = 0;
+		intf->local_event_generator = 0;
+		intf->event_receiver = 0;
+
+		/* Request the device info from the local MC. */
+		msg.netfn = IPMI_NETFN_APP_REQUEST;
+		msg.cmd = IPMI_GET_DEVICE_ID_CMD;
+		msg.data = NULL;
+		msg.data_len = 0;
+		intf->null_user_handler = device_id_fetcher;
+		i_ipmi_request(NULL,
+			       intf,
+			       &addr,
+			       0,
+			       &msg,
+			       NULL,
+			       &smi_msg,
+			       &recv_msg,
+			       0,
+			       intf->my_address,
+			       intf->my_lun,
+			       0, 1); /* Don't retry, and don't wait. */
+
+		if (intf->local_event_generator) {
+			/* Request the event receiver from the local MC. */
+			msg.netfn = IPMI_NETFN_SENSOR_EVENT_REQUEST;
+			msg.cmd = IPMI_GET_EVENT_RECEIVER_CMD;
+			msg.data = NULL;
+			msg.data_len = 0;
+			intf->null_user_handler = event_receiver_fetcher;
+			i_ipmi_request(NULL,
+				       intf,
+				       &addr,
+				       0,
+				       &msg,
+				       NULL,
+				       &smi_msg,
+				       &recv_msg,
+				       0,
+				       intf->my_address,
+				       intf->my_lun,
+				       0, 1); /* no retry, and no wait. */
+		}
+		intf->null_user_handler = NULL;
+
+		/* Validate the event receiver.  The low bit must not
+		   be 1 (it must be a valid IPMB address), it cannot
+		   be zero, and it must not be my address. */
+                if (((intf->event_receiver & 1) == 0)
+		    && (intf->event_receiver != 0)
+		    && (intf->event_receiver != intf->my_address))
+		{
+			/* The event receiver is valid, send an IPMB
+			   message. */
+			ipmb = (struct ipmi_ipmb_addr *) &addr;
+			ipmb->addr_type = IPMI_IPMB_ADDR_TYPE;
+			ipmb->channel = 0; /* FIXME - is this right? */
+			ipmb->lun = intf->event_receiver_lun;
+			ipmb->slave_addr = intf->event_receiver;
+		} else if (intf->local_sel_device) {
+			/* The event receiver was not valid (or was
+			   me), but I am an SEL device, just dump it
+			   in my SEL. */
+			si = (struct ipmi_system_interface_addr *) &addr;
+			si->addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+			si->channel = IPMI_BMC_CHANNEL;
+			si->lun = 0;
+		} else
+			continue; /* No where to send the event. */
+
+		
+		msg.netfn = IPMI_NETFN_STORAGE_REQUEST; /* Storage. */
+		msg.cmd = IPMI_ADD_SEL_ENTRY_CMD;
+		msg.data = data;
+		msg.data_len = 16;
+
+		j = 0;
+		while (*p) {
+			int size = strlen(p);
+
+			if (size > 11)
+				size = 11;
+			data[0] = 0;
+			data[1] = 0;
+			data[2] = 0xf0; /* OEM event without timestamp. */
+			data[3] = intf->my_address;
+			data[4] = j++; /* sequence # */
+			/* Always give 11 bytes, so strncpy will fill
+			   it with zeroes for me. */
+			strncpy(data+5, p, 11);
+			p += size;
+
+			i_ipmi_request(NULL,
+				       intf,
+				       &addr,
+				       0,
+				       &msg,
+				       NULL,
+				       &smi_msg,
+				       &recv_msg,
+				       0,
+				       intf->my_address,
+				       intf->my_lun,
+				       0, 1); /* no retry, and no wait. */
+		}
+	}	
+#endif /* CONFIG_IPMI_PANIC_STRING */
+}
+#endif /* CONFIG_IPMI_PANIC_EVENT */
+
+static int has_paniced = 0;
+
+static int panic_event(struct notifier_block *this,
+		       unsigned long         event,
+                       void                  *ptr)
+{
+	int        i;
+	ipmi_smi_t intf;
+
+	if (has_paniced)
+		return NOTIFY_DONE;
+	has_paniced = 1;
+
+	/* For every registered interface, set it to run to completion. */
+	for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+		intf = ipmi_interfaces[i];
+		if (intf == NULL)
+			continue;
+
+		intf->handlers->set_run_to_completion(intf->send_info, 1);
+	}
+
+#ifdef CONFIG_IPMI_PANIC_EVENT
+	send_panic_events(ptr);
+#endif
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block panic_block = {
+	.notifier_call	= panic_event,
+	.next		= NULL,
+	.priority	= 200	/* priority: INT_MAX >= x >= 0 */
+};
+
+static int ipmi_init_msghandler(void)
+{
+	int i;
+
+	if (initialized)
+		return 0;
+
+	printk(KERN_INFO "ipmi message handler version "
+	       IPMI_MSGHANDLER_VERSION "\n");
+
+	for (i=0; i<MAX_IPMI_INTERFACES; i++) {
+		ipmi_interfaces[i] = NULL;
+	}
+
+	proc_ipmi_root = proc_mkdir("ipmi", NULL);
+	if (!proc_ipmi_root) {
+	    printk(KERN_ERR PFX "Unable to create IPMI proc dir");
+	    return -ENOMEM;
+	}
+
+	proc_ipmi_root->owner = THIS_MODULE;
+
+	init_timer(&ipmi_timer);
+	ipmi_timer.data = 0;
+	ipmi_timer.function = ipmi_timeout;
+	ipmi_timer.expires = jiffies + IPMI_TIMEOUT_JIFFIES;
+	add_timer(&ipmi_timer);
+
+	notifier_chain_register(&panic_notifier_list, &panic_block);
+
+	initialized = 1;
+
+	return 0;
+}
+
+static __init int ipmi_init_msghandler_mod(void)
+{
+	ipmi_init_msghandler();
+	return 0;
+}
+
+static __exit void cleanup_ipmi(void)
+{
+	int count;
+
+	if (!initialized)
+		return;
+
+	notifier_chain_unregister(&panic_notifier_list, &panic_block);
+
+	/* This can't be called if any interfaces exist, so no worry about
+	   shutting down the interfaces. */
+
+	/* Tell the timer to stop, then wait for it to stop.  This avoids
+	   problems with race conditions removing the timer here. */
+	stop_operation = 1;
+	while (!timer_stopped) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+	remove_proc_entry(proc_ipmi_root->name, &proc_root);
+
+	initialized = 0;
+
+	/* Check for buffer leaks. */
+	count = atomic_read(&smi_msg_inuse_count);
+	if (count != 0)
+		printk(KERN_WARNING PFX "SMI message count %d at exit\n",
+		       count);
+	count = atomic_read(&recv_msg_inuse_count);
+	if (count != 0)
+		printk(KERN_WARNING PFX "recv message count %d at exit\n",
+		       count);
+}
+module_exit(cleanup_ipmi);
+
+module_init(ipmi_init_msghandler_mod);
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(ipmi_create_user);
+EXPORT_SYMBOL(ipmi_destroy_user);
+EXPORT_SYMBOL(ipmi_get_version);
+EXPORT_SYMBOL(ipmi_request_settime);
+EXPORT_SYMBOL(ipmi_request_supply_msgs);
+EXPORT_SYMBOL(ipmi_register_smi);
+EXPORT_SYMBOL(ipmi_unregister_smi);
+EXPORT_SYMBOL(ipmi_register_for_cmd);
+EXPORT_SYMBOL(ipmi_unregister_for_cmd);
+EXPORT_SYMBOL(ipmi_smi_msg_received);
+EXPORT_SYMBOL(ipmi_smi_watchdog_pretimeout);
+EXPORT_SYMBOL(ipmi_alloc_smi_msg);
+EXPORT_SYMBOL(ipmi_addr_length);
+EXPORT_SYMBOL(ipmi_validate_addr);
+EXPORT_SYMBOL(ipmi_set_gets_events);
+EXPORT_SYMBOL(ipmi_smi_watcher_register);
+EXPORT_SYMBOL(ipmi_smi_watcher_unregister);
+EXPORT_SYMBOL(ipmi_set_my_address);
+EXPORT_SYMBOL(ipmi_get_my_address);
+EXPORT_SYMBOL(ipmi_set_my_LUN);
+EXPORT_SYMBOL(ipmi_get_my_LUN);
+EXPORT_SYMBOL(ipmi_smi_add_proc_entry);
+EXPORT_SYMBOL(ipmi_user_set_run_to_completion);
diff --git a/drivers/char/ipmi/ipmi_poweroff.c b/drivers/char/ipmi/ipmi_poweroff.c
new file mode 100644
index 0000000..cb5cdc6
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_poweroff.c
@@ -0,0 +1,549 @@
+/*
+ * ipmi_poweroff.c
+ *
+ * MontaVista IPMI Poweroff extension to sys_reboot
+ *
+ * Author: MontaVista Software, Inc.
+ *         Steven Dake <sdake@mvista.com>
+ *         Corey Minyard <cminyard@mvista.com>
+ *         source@mvista.com
+ *
+ * Copyright 2002,2004 MontaVista Software 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.
+ *
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.
+ */
+#include <asm/semaphore.h>
+#include <linux/kdev_t.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/ipmi.h>
+#include <linux/ipmi_smi.h>
+
+#define PFX "IPMI poweroff: "
+#define IPMI_POWEROFF_VERSION	"v33"
+
+/* Where to we insert our poweroff function? */
+extern void (*pm_power_off)(void);
+
+/* Stuff from the get device id command. */
+static unsigned int mfg_id;
+static unsigned int prod_id;
+static unsigned char capabilities;
+
+/* We use our own messages for this operation, we don't let the system
+   allocate them, since we may be in a panic situation.  The whole
+   thing is single-threaded, anyway, so multiple messages are not
+   required. */
+static void dummy_smi_free(struct ipmi_smi_msg *msg)
+{
+}
+static void dummy_recv_free(struct ipmi_recv_msg *msg)
+{
+}
+static struct ipmi_smi_msg halt_smi_msg =
+{
+	.done = dummy_smi_free
+};
+static struct ipmi_recv_msg halt_recv_msg =
+{
+	.done = dummy_recv_free
+};
+
+
+/*
+ * Code to send a message and wait for the reponse.
+ */
+
+static void receive_handler(struct ipmi_recv_msg *recv_msg, void *handler_data)
+{
+	struct semaphore *sem = recv_msg->user_msg_data;
+
+	if (sem)
+		up(sem);
+}
+
+static struct ipmi_user_hndl ipmi_poweroff_handler =
+{
+	.ipmi_recv_hndl = receive_handler
+};
+
+
+static int ipmi_request_wait_for_response(ipmi_user_t            user,
+					  struct ipmi_addr       *addr,
+					  struct kernel_ipmi_msg *send_msg)
+{
+	int              rv;
+	struct semaphore sem;
+
+	sema_init (&sem, 0);
+
+	rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, &sem,
+				      &halt_smi_msg, &halt_recv_msg, 0);
+	if (rv)
+		return rv;
+
+	down (&sem);
+
+	return halt_recv_msg.msg.data[0];
+}
+
+/* We are in run-to-completion mode, no semaphore is desired. */
+static int ipmi_request_in_rc_mode(ipmi_user_t            user,
+				   struct ipmi_addr       *addr,
+				   struct kernel_ipmi_msg *send_msg)
+{
+	int              rv;
+
+	rv = ipmi_request_supply_msgs(user, addr, 0, send_msg, NULL,
+				      &halt_smi_msg, &halt_recv_msg, 0);
+	if (rv)
+		return rv;
+
+	return halt_recv_msg.msg.data[0];
+}
+
+/*
+ * ATCA Support
+ */
+
+#define IPMI_NETFN_ATCA			0x2c
+#define IPMI_ATCA_SET_POWER_CMD		0x11
+#define IPMI_ATCA_GET_ADDR_INFO_CMD	0x01
+#define IPMI_PICMG_ID			0
+
+static int ipmi_atca_detect (ipmi_user_t user)
+{
+	struct ipmi_system_interface_addr smi_addr;
+	struct kernel_ipmi_msg            send_msg;
+	int                               rv;
+	unsigned char                     data[1];
+
+        /*
+         * Configure IPMI address for local access
+         */
+        smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+        smi_addr.channel = IPMI_BMC_CHANNEL;
+        smi_addr.lun = 0;
+
+	/*
+	 * Use get address info to check and see if we are ATCA
+	 */
+	send_msg.netfn = IPMI_NETFN_ATCA;
+	send_msg.cmd = IPMI_ATCA_GET_ADDR_INFO_CMD;
+	data[0] = IPMI_PICMG_ID;
+	send_msg.data = data;
+	send_msg.data_len = sizeof(data);
+	rv = ipmi_request_wait_for_response(user,
+					    (struct ipmi_addr *) &smi_addr,
+					    &send_msg);
+	return !rv;
+}
+
+static void ipmi_poweroff_atca (ipmi_user_t user)
+{
+	struct ipmi_system_interface_addr smi_addr;
+	struct kernel_ipmi_msg            send_msg;
+	int                               rv;
+	unsigned char                     data[4];
+
+        /*
+         * Configure IPMI address for local access
+         */
+        smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+        smi_addr.channel = IPMI_BMC_CHANNEL;
+        smi_addr.lun = 0;
+
+	printk(KERN_INFO PFX "Powering down via ATCA power command\n");
+
+	/*
+	 * Power down
+	 */
+	send_msg.netfn = IPMI_NETFN_ATCA;
+	send_msg.cmd = IPMI_ATCA_SET_POWER_CMD;
+	data[0] = IPMI_PICMG_ID;
+	data[1] = 0; /* FRU id */
+	data[2] = 0; /* Power Level */
+	data[3] = 0; /* Don't change saved presets */
+	send_msg.data = data;
+	send_msg.data_len = sizeof (data);
+	rv = ipmi_request_in_rc_mode(user,
+				     (struct ipmi_addr *) &smi_addr,
+				     &send_msg);
+	if (rv) {
+		printk(KERN_ERR PFX "Unable to send ATCA powerdown message,"
+		       " IPMI error 0x%x\n", rv);
+		goto out;
+	}
+
+ out:
+	return;
+}
+
+/*
+ * CPI1 Support
+ */
+
+#define IPMI_NETFN_OEM_1				0xf8
+#define OEM_GRP_CMD_SET_RESET_STATE		0x84
+#define OEM_GRP_CMD_SET_POWER_STATE		0x82
+#define IPMI_NETFN_OEM_8				0xf8
+#define OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL	0x80
+#define OEM_GRP_CMD_GET_SLOT_GA			0xa3
+#define IPMI_NETFN_SENSOR_EVT			0x10
+#define IPMI_CMD_GET_EVENT_RECEIVER		0x01
+
+#define IPMI_CPI1_PRODUCT_ID		0x000157
+#define IPMI_CPI1_MANUFACTURER_ID	0x0108
+
+static int ipmi_cpi1_detect (ipmi_user_t user)
+{
+	return ((mfg_id == IPMI_CPI1_MANUFACTURER_ID)
+		&& (prod_id == IPMI_CPI1_PRODUCT_ID));
+}
+
+static void ipmi_poweroff_cpi1 (ipmi_user_t user)
+{
+	struct ipmi_system_interface_addr smi_addr;
+	struct ipmi_ipmb_addr             ipmb_addr;
+	struct kernel_ipmi_msg            send_msg;
+	int                               rv;
+	unsigned char                     data[1];
+	int                               slot;
+	unsigned char                     hotswap_ipmb;
+	unsigned char                     aer_addr;
+	unsigned char                     aer_lun;
+
+        /*
+         * Configure IPMI address for local access
+         */
+        smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+        smi_addr.channel = IPMI_BMC_CHANNEL;
+        smi_addr.lun = 0;
+
+	printk(KERN_INFO PFX "Powering down via CPI1 power command\n");
+
+	/*
+	 * Get IPMI ipmb address
+	 */
+	send_msg.netfn = IPMI_NETFN_OEM_8 >> 2;
+	send_msg.cmd = OEM_GRP_CMD_GET_SLOT_GA;
+	send_msg.data = NULL;
+	send_msg.data_len = 0;
+	rv = ipmi_request_in_rc_mode(user,
+				     (struct ipmi_addr *) &smi_addr,
+				     &send_msg);
+	if (rv)
+		goto out;
+	slot = halt_recv_msg.msg.data[1];
+	hotswap_ipmb = (slot > 9) ? (0xb0 + 2 * slot) : (0xae + 2 * slot);
+
+	/*
+	 * Get active event receiver
+	 */
+	send_msg.netfn = IPMI_NETFN_SENSOR_EVT >> 2;
+	send_msg.cmd = IPMI_CMD_GET_EVENT_RECEIVER;
+	send_msg.data = NULL;
+	send_msg.data_len = 0;
+	rv = ipmi_request_in_rc_mode(user,
+				     (struct ipmi_addr *) &smi_addr,
+				     &send_msg);
+	if (rv)
+		goto out;
+	aer_addr = halt_recv_msg.msg.data[1];
+	aer_lun = halt_recv_msg.msg.data[2];
+
+	/*
+	 * Setup IPMB address target instead of local target
+	 */
+	ipmb_addr.addr_type = IPMI_IPMB_ADDR_TYPE;
+	ipmb_addr.channel = 0;
+	ipmb_addr.slave_addr = aer_addr;
+	ipmb_addr.lun = aer_lun;
+
+	/*
+	 * Send request hotswap control to remove blade from dpv
+	 */
+	send_msg.netfn = IPMI_NETFN_OEM_8 >> 2;
+	send_msg.cmd = OEM_GRP_CMD_REQUEST_HOTSWAP_CTRL;
+	send_msg.data = &hotswap_ipmb;
+	send_msg.data_len = 1;
+	ipmi_request_in_rc_mode(user,
+				(struct ipmi_addr *) &ipmb_addr,
+				&send_msg);
+
+	/*
+	 * Set reset asserted
+	 */
+	send_msg.netfn = IPMI_NETFN_OEM_1 >> 2;
+	send_msg.cmd = OEM_GRP_CMD_SET_RESET_STATE;
+	send_msg.data = data;
+	data[0] = 1; /* Reset asserted state */
+	send_msg.data_len = 1;
+	rv = ipmi_request_in_rc_mode(user,
+				     (struct ipmi_addr *) &smi_addr,
+				     &send_msg);
+	if (rv)
+		goto out;
+
+	/*
+	 * Power down
+	 */
+	send_msg.netfn = IPMI_NETFN_OEM_1 >> 2;
+	send_msg.cmd = OEM_GRP_CMD_SET_POWER_STATE;
+	send_msg.data = data;
+	data[0] = 1; /* Power down state */
+	send_msg.data_len = 1;
+	rv = ipmi_request_in_rc_mode(user,
+				     (struct ipmi_addr *) &smi_addr,
+				     &send_msg);
+	if (rv)
+		goto out;
+
+ out:
+	return;
+}
+
+/*
+ * Standard chassis support
+ */
+
+#define IPMI_NETFN_CHASSIS_REQUEST	0
+#define IPMI_CHASSIS_CONTROL_CMD	0x02
+
+static int ipmi_chassis_detect (ipmi_user_t user)
+{
+	/* Chassis support, use it. */
+	return (capabilities & 0x80);
+}
+
+static void ipmi_poweroff_chassis (ipmi_user_t user)
+{
+	struct ipmi_system_interface_addr smi_addr;
+	struct kernel_ipmi_msg            send_msg;
+	int                               rv;
+	unsigned char                     data[1];
+
+        /*
+         * Configure IPMI address for local access
+         */
+        smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+        smi_addr.channel = IPMI_BMC_CHANNEL;
+        smi_addr.lun = 0;
+
+	printk(KERN_INFO PFX "Powering down via IPMI chassis control command\n");
+
+	/*
+	 * Power down
+	 */
+	send_msg.netfn = IPMI_NETFN_CHASSIS_REQUEST;
+	send_msg.cmd = IPMI_CHASSIS_CONTROL_CMD;
+	data[0] = 0; /* Power down */
+	send_msg.data = data;
+	send_msg.data_len = sizeof(data);
+	rv = ipmi_request_in_rc_mode(user,
+				     (struct ipmi_addr *) &smi_addr,
+				     &send_msg);
+	if (rv) {
+		printk(KERN_ERR PFX "Unable to send chassis powerdown message,"
+		       " IPMI error 0x%x\n", rv);
+		goto out;
+	}
+
+ out:
+	return;
+}
+
+
+/* Table of possible power off functions. */
+struct poweroff_function {
+	char *platform_type;
+	int  (*detect)(ipmi_user_t user);
+	void (*poweroff_func)(ipmi_user_t user);
+};
+
+static struct poweroff_function poweroff_functions[] = {
+	{ .platform_type	= "ATCA",
+	  .detect		= ipmi_atca_detect,
+	  .poweroff_func	= ipmi_poweroff_atca },
+	{ .platform_type	= "CPI1",
+	  .detect		= ipmi_cpi1_detect,
+	  .poweroff_func	= ipmi_poweroff_cpi1 },
+	/* Chassis should generally be last, other things should override
+	   it. */
+	{ .platform_type	= "chassis",
+	  .detect		= ipmi_chassis_detect,
+	  .poweroff_func	= ipmi_poweroff_chassis },
+};
+#define NUM_PO_FUNCS (sizeof(poweroff_functions) \
+		      / sizeof(struct poweroff_function))
+
+
+/* Our local state. */
+static int ready = 0;
+static ipmi_user_t ipmi_user;
+static void (*specific_poweroff_func)(ipmi_user_t user) = NULL;
+
+/* Holds the old poweroff function so we can restore it on removal. */
+static void (*old_poweroff_func)(void);
+
+
+/* Called on a powerdown request. */
+static void ipmi_poweroff_function (void)
+{
+	if (!ready)
+		return;
+
+	/* Use run-to-completion mode, since interrupts may be off. */
+	ipmi_user_set_run_to_completion(ipmi_user, 1);
+	specific_poweroff_func(ipmi_user);
+	ipmi_user_set_run_to_completion(ipmi_user, 0);
+}
+
+/* Wait for an IPMI interface to be installed, the first one installed
+   will be grabbed by this code and used to perform the powerdown. */
+static void ipmi_po_new_smi(int if_num)
+{
+	struct ipmi_system_interface_addr smi_addr;
+	struct kernel_ipmi_msg            send_msg;
+	int                               rv;
+	int                               i;
+
+	if (ready)
+		return;
+
+	rv = ipmi_create_user(if_num, &ipmi_poweroff_handler, NULL, &ipmi_user);
+	if (rv) {
+		printk(KERN_ERR PFX "could not create IPMI user, error %d\n",
+		       rv);
+		return;
+	}
+
+        /*
+         * Do a get device ide and store some results, since this is
+	 * used by several functions.
+         */
+        smi_addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+        smi_addr.channel = IPMI_BMC_CHANNEL;
+        smi_addr.lun = 0;
+
+	send_msg.netfn = IPMI_NETFN_APP_REQUEST;
+	send_msg.cmd = IPMI_GET_DEVICE_ID_CMD;
+	send_msg.data = NULL;
+	send_msg.data_len = 0;
+	rv = ipmi_request_wait_for_response(ipmi_user,
+					    (struct ipmi_addr *) &smi_addr,
+					    &send_msg);
+	if (rv) {
+		printk(KERN_ERR PFX "Unable to send IPMI get device id info,"
+		       " IPMI error 0x%x\n", rv);
+		goto out_err;
+	}
+
+	if (halt_recv_msg.msg.data_len < 12) {
+		printk(KERN_ERR PFX "(chassis) IPMI get device id info too,"
+		       " short, was %d bytes, needed %d bytes\n",
+		       halt_recv_msg.msg.data_len, 12);
+		goto out_err;
+	}
+
+	mfg_id = (halt_recv_msg.msg.data[7]
+		  | (halt_recv_msg.msg.data[8] << 8)
+		  | (halt_recv_msg.msg.data[9] << 16));
+	prod_id = (halt_recv_msg.msg.data[10]
+		   | (halt_recv_msg.msg.data[11] << 8));
+	capabilities = halt_recv_msg.msg.data[6];
+
+
+	/* Scan for a poweroff method */
+	for (i=0; i<NUM_PO_FUNCS; i++) {
+		if (poweroff_functions[i].detect(ipmi_user))
+			goto found;
+	}
+
+ out_err:
+	printk(KERN_ERR PFX "Unable to find a poweroff function that"
+	       " will work, giving up\n");
+	ipmi_destroy_user(ipmi_user);
+	return;
+
+ found:
+	printk(KERN_INFO PFX "Found a %s style poweroff function\n",
+	       poweroff_functions[i].platform_type);
+	specific_poweroff_func = poweroff_functions[i].poweroff_func;
+	old_poweroff_func = pm_power_off;
+	pm_power_off = ipmi_poweroff_function;
+	ready = 1;
+}
+
+static void ipmi_po_smi_gone(int if_num)
+{
+	/* This can never be called, because once poweroff driver is
+	   registered, the interface can't go away until the power
+	   driver is unregistered. */
+}
+
+static struct ipmi_smi_watcher smi_watcher =
+{
+	.owner    = THIS_MODULE,
+	.new_smi  = ipmi_po_new_smi,
+	.smi_gone = ipmi_po_smi_gone
+};
+
+
+/*
+ * Startup and shutdown functions.
+ */
+static int ipmi_poweroff_init (void)
+{
+	int rv;
+
+	printk ("Copyright (C) 2004 MontaVista Software -"
+		" IPMI Powerdown via sys_reboot version "
+		IPMI_POWEROFF_VERSION ".\n");
+
+	rv = ipmi_smi_watcher_register(&smi_watcher);
+	if (rv)
+		printk(KERN_ERR PFX "Unable to register SMI watcher: %d\n", rv);
+
+	return rv;
+}
+
+#ifdef MODULE
+static __exit void ipmi_poweroff_cleanup(void)
+{
+	int rv;
+
+	ipmi_smi_watcher_unregister(&smi_watcher);
+
+	if (ready) {
+		rv = ipmi_destroy_user(ipmi_user);
+		if (rv)
+			printk(KERN_ERR PFX "could not cleanup the IPMI"
+			       " user: 0x%x\n", rv);
+		pm_power_off = old_poweroff_func;
+	}
+}
+module_exit(ipmi_poweroff_cleanup);
+#endif
+
+module_init(ipmi_poweroff_init);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/ipmi_si_intf.c b/drivers/char/ipmi/ipmi_si_intf.c
new file mode 100644
index 0000000..29de259
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_si_intf.c
@@ -0,0 +1,2359 @@
+/*
+ * ipmi_si.c
+ *
+ * The interface to the IPMI driver for the system interfaces (KCS, SMIC,
+ * BT).
+ *
+ * Author: MontaVista Software, Inc.
+ *         Corey Minyard <minyard@mvista.com>
+ *         source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software 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.
+ *
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This file holds the "policy" for the interface to the SMI state
+ * machine.  It does the configuration, handles timers and interrupts,
+ * and drives the real SMI state machine.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <asm/irq.h>
+#ifdef CONFIG_HIGH_RES_TIMERS
+#include <linux/hrtime.h>
+# if defined(schedule_next_int)
+/* Old high-res timer code, do translations. */
+#  define get_arch_cycles(a) quick_update_jiffies_sub(a)
+#  define arch_cycles_per_jiffy cycles_per_jiffies
+# endif
+static inline void add_usec_to_timer(struct timer_list *t, long v)
+{
+	t->sub_expires += nsec_to_arch_cycle(v * 1000);
+	while (t->sub_expires >= arch_cycles_per_jiffy)
+	{
+		t->expires++;
+		t->sub_expires -= arch_cycles_per_jiffy;
+	}
+}
+#endif
+#include <linux/interrupt.h>
+#include <linux/rcupdate.h>
+#include <linux/ipmi_smi.h>
+#include <asm/io.h>
+#include "ipmi_si_sm.h"
+#include <linux/init.h>
+
+#define IPMI_SI_VERSION "v33"
+
+/* Measure times between events in the driver. */
+#undef DEBUG_TIMING
+
+/* Call every 10 ms. */
+#define SI_TIMEOUT_TIME_USEC	10000
+#define SI_USEC_PER_JIFFY	(1000000/HZ)
+#define SI_TIMEOUT_JIFFIES	(SI_TIMEOUT_TIME_USEC/SI_USEC_PER_JIFFY)
+#define SI_SHORT_TIMEOUT_USEC  250 /* .25ms when the SM request a
+                                       short timeout */
+
+enum si_intf_state {
+	SI_NORMAL,
+	SI_GETTING_FLAGS,
+	SI_GETTING_EVENTS,
+	SI_CLEARING_FLAGS,
+	SI_CLEARING_FLAGS_THEN_SET_IRQ,
+	SI_GETTING_MESSAGES,
+	SI_ENABLE_INTERRUPTS1,
+	SI_ENABLE_INTERRUPTS2
+	/* FIXME - add watchdog stuff. */
+};
+
+enum si_type {
+    SI_KCS, SI_SMIC, SI_BT
+};
+
+struct smi_info
+{
+	ipmi_smi_t             intf;
+	struct si_sm_data      *si_sm;
+	struct si_sm_handlers  *handlers;
+	enum si_type           si_type;
+	spinlock_t             si_lock;
+	spinlock_t             msg_lock;
+	struct list_head       xmit_msgs;
+	struct list_head       hp_xmit_msgs;
+	struct ipmi_smi_msg    *curr_msg;
+	enum si_intf_state     si_state;
+
+	/* Used to handle the various types of I/O that can occur with
+           IPMI */
+	struct si_sm_io io;
+	int (*io_setup)(struct smi_info *info);
+	void (*io_cleanup)(struct smi_info *info);
+	int (*irq_setup)(struct smi_info *info);
+	void (*irq_cleanup)(struct smi_info *info);
+	unsigned int io_size;
+
+	/* Flags from the last GET_MSG_FLAGS command, used when an ATTN
+	   is set to hold the flags until we are done handling everything
+	   from the flags. */
+#define RECEIVE_MSG_AVAIL	0x01
+#define EVENT_MSG_BUFFER_FULL	0x02
+#define WDT_PRE_TIMEOUT_INT	0x08
+	unsigned char       msg_flags;
+
+	/* If set to true, this will request events the next time the
+	   state machine is idle. */
+	atomic_t            req_events;
+
+	/* If true, run the state machine to completion on every send
+	   call.  Generally used after a panic to make sure stuff goes
+	   out. */
+	int                 run_to_completion;
+
+	/* The I/O port of an SI interface. */
+	int                 port;
+
+	/* The space between start addresses of the two ports.  For
+	   instance, if the first port is 0xca2 and the spacing is 4, then
+	   the second port is 0xca6. */
+	unsigned int        spacing;
+
+	/* zero if no irq; */
+	int                 irq;
+
+	/* The timer for this si. */
+	struct timer_list   si_timer;
+
+	/* The time (in jiffies) the last timeout occurred at. */
+	unsigned long       last_timeout_jiffies;
+
+	/* Used to gracefully stop the timer without race conditions. */
+	volatile int        stop_operation;
+	volatile int        timer_stopped;
+
+	/* The driver will disable interrupts when it gets into a
+	   situation where it cannot handle messages due to lack of
+	   memory.  Once that situation clears up, it will re-enable
+	   interrupts. */
+	int interrupt_disabled;
+
+	unsigned char ipmi_si_dev_rev;
+	unsigned char ipmi_si_fw_rev_major;
+	unsigned char ipmi_si_fw_rev_minor;
+	unsigned char ipmi_version_major;
+	unsigned char ipmi_version_minor;
+
+	/* Slave address, could be reported from DMI. */
+	unsigned char slave_addr;
+
+	/* Counters and things for the proc filesystem. */
+	spinlock_t count_lock;
+	unsigned long short_timeouts;
+	unsigned long long_timeouts;
+	unsigned long timeout_restarts;
+	unsigned long idles;
+	unsigned long interrupts;
+	unsigned long attentions;
+	unsigned long flag_fetches;
+	unsigned long hosed_count;
+	unsigned long complete_transactions;
+	unsigned long events;
+	unsigned long watchdog_pretimeouts;
+	unsigned long incoming_messages;
+};
+
+static void si_restart_short_timer(struct smi_info *smi_info);
+
+static void deliver_recv_msg(struct smi_info *smi_info,
+			     struct ipmi_smi_msg *msg)
+{
+	/* Deliver the message to the upper layer with the lock
+           released. */
+	spin_unlock(&(smi_info->si_lock));
+	ipmi_smi_msg_received(smi_info->intf, msg);
+	spin_lock(&(smi_info->si_lock));
+}
+
+static void return_hosed_msg(struct smi_info *smi_info)
+{
+	struct ipmi_smi_msg *msg = smi_info->curr_msg;
+
+	/* Make it a reponse */
+	msg->rsp[0] = msg->data[0] | 4;
+	msg->rsp[1] = msg->data[1];
+	msg->rsp[2] = 0xFF; /* Unknown error. */
+	msg->rsp_size = 3;
+
+	smi_info->curr_msg = NULL;
+	deliver_recv_msg(smi_info, msg);
+}
+
+static enum si_sm_result start_next_msg(struct smi_info *smi_info)
+{
+	int              rv;
+	struct list_head *entry = NULL;
+#ifdef DEBUG_TIMING
+	struct timeval t;
+#endif
+
+	/* No need to save flags, we aleady have interrupts off and we
+	   already hold the SMI lock. */
+	spin_lock(&(smi_info->msg_lock));
+
+	/* Pick the high priority queue first. */
+	if (! list_empty(&(smi_info->hp_xmit_msgs))) {
+		entry = smi_info->hp_xmit_msgs.next;
+	} else if (! list_empty(&(smi_info->xmit_msgs))) {
+		entry = smi_info->xmit_msgs.next;
+	}
+
+	if (!entry) {
+		smi_info->curr_msg = NULL;
+		rv = SI_SM_IDLE;
+	} else {
+		int err;
+
+		list_del(entry);
+		smi_info->curr_msg = list_entry(entry,
+						struct ipmi_smi_msg,
+						link);
+#ifdef DEBUG_TIMING
+		do_gettimeofday(&t);
+		printk("**Start2: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+		err = smi_info->handlers->start_transaction(
+			smi_info->si_sm,
+			smi_info->curr_msg->data,
+			smi_info->curr_msg->data_size);
+		if (err) {
+			return_hosed_msg(smi_info);
+		}
+
+		rv = SI_SM_CALL_WITHOUT_DELAY;
+	}
+	spin_unlock(&(smi_info->msg_lock));
+
+	return rv;
+}
+
+static void start_enable_irq(struct smi_info *smi_info)
+{
+	unsigned char msg[2];
+
+	/* If we are enabling interrupts, we have to tell the
+	   BMC to use them. */
+	msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+	msg[1] = IPMI_GET_BMC_GLOBAL_ENABLES_CMD;
+
+	smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2);
+	smi_info->si_state = SI_ENABLE_INTERRUPTS1;
+}
+
+static void start_clear_flags(struct smi_info *smi_info)
+{
+	unsigned char msg[3];
+
+	/* Make sure the watchdog pre-timeout flag is not set at startup. */
+	msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+	msg[1] = IPMI_CLEAR_MSG_FLAGS_CMD;
+	msg[2] = WDT_PRE_TIMEOUT_INT;
+
+	smi_info->handlers->start_transaction(smi_info->si_sm, msg, 3);
+	smi_info->si_state = SI_CLEARING_FLAGS;
+}
+
+/* When we have a situtaion where we run out of memory and cannot
+   allocate messages, we just leave them in the BMC and run the system
+   polled until we can allocate some memory.  Once we have some
+   memory, we will re-enable the interrupt. */
+static inline void disable_si_irq(struct smi_info *smi_info)
+{
+	if ((smi_info->irq) && (!smi_info->interrupt_disabled)) {
+		disable_irq_nosync(smi_info->irq);
+		smi_info->interrupt_disabled = 1;
+	}
+}
+
+static inline void enable_si_irq(struct smi_info *smi_info)
+{
+	if ((smi_info->irq) && (smi_info->interrupt_disabled)) {
+		enable_irq(smi_info->irq);
+		smi_info->interrupt_disabled = 0;
+	}
+}
+
+static void handle_flags(struct smi_info *smi_info)
+{
+	if (smi_info->msg_flags & WDT_PRE_TIMEOUT_INT) {
+		/* Watchdog pre-timeout */
+		spin_lock(&smi_info->count_lock);
+		smi_info->watchdog_pretimeouts++;
+		spin_unlock(&smi_info->count_lock);
+
+		start_clear_flags(smi_info);
+		smi_info->msg_flags &= ~WDT_PRE_TIMEOUT_INT;
+		spin_unlock(&(smi_info->si_lock));
+		ipmi_smi_watchdog_pretimeout(smi_info->intf);
+		spin_lock(&(smi_info->si_lock));
+	} else if (smi_info->msg_flags & RECEIVE_MSG_AVAIL) {
+		/* Messages available. */
+		smi_info->curr_msg = ipmi_alloc_smi_msg();
+		if (!smi_info->curr_msg) {
+			disable_si_irq(smi_info);
+			smi_info->si_state = SI_NORMAL;
+			return;
+		}
+		enable_si_irq(smi_info);
+
+		smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+		smi_info->curr_msg->data[1] = IPMI_GET_MSG_CMD;
+		smi_info->curr_msg->data_size = 2;
+
+		smi_info->handlers->start_transaction(
+			smi_info->si_sm,
+			smi_info->curr_msg->data,
+			smi_info->curr_msg->data_size);
+		smi_info->si_state = SI_GETTING_MESSAGES;
+	} else if (smi_info->msg_flags & EVENT_MSG_BUFFER_FULL) {
+		/* Events available. */
+		smi_info->curr_msg = ipmi_alloc_smi_msg();
+		if (!smi_info->curr_msg) {
+			disable_si_irq(smi_info);
+			smi_info->si_state = SI_NORMAL;
+			return;
+		}
+		enable_si_irq(smi_info);
+
+		smi_info->curr_msg->data[0] = (IPMI_NETFN_APP_REQUEST << 2);
+		smi_info->curr_msg->data[1] = IPMI_READ_EVENT_MSG_BUFFER_CMD;
+		smi_info->curr_msg->data_size = 2;
+
+		smi_info->handlers->start_transaction(
+			smi_info->si_sm,
+			smi_info->curr_msg->data,
+			smi_info->curr_msg->data_size);
+		smi_info->si_state = SI_GETTING_EVENTS;
+	} else {
+		smi_info->si_state = SI_NORMAL;
+	}
+}
+
+static void handle_transaction_done(struct smi_info *smi_info)
+{
+	struct ipmi_smi_msg *msg;
+#ifdef DEBUG_TIMING
+	struct timeval t;
+
+	do_gettimeofday(&t);
+	printk("**Done: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+	switch (smi_info->si_state) {
+	case SI_NORMAL:
+		if (!smi_info->curr_msg)
+			break;
+
+		smi_info->curr_msg->rsp_size
+			= smi_info->handlers->get_result(
+				smi_info->si_sm,
+				smi_info->curr_msg->rsp,
+				IPMI_MAX_MSG_LENGTH);
+
+		/* Do this here becase deliver_recv_msg() releases the
+		   lock, and a new message can be put in during the
+		   time the lock is released. */
+		msg = smi_info->curr_msg;
+		smi_info->curr_msg = NULL;
+		deliver_recv_msg(smi_info, msg);
+		break;
+
+	case SI_GETTING_FLAGS:
+	{
+		unsigned char msg[4];
+		unsigned int  len;
+
+		/* We got the flags from the SMI, now handle them. */
+		len = smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+		if (msg[2] != 0) {
+			/* Error fetching flags, just give up for
+			   now. */
+			smi_info->si_state = SI_NORMAL;
+		} else if (len < 4) {
+			/* Hmm, no flags.  That's technically illegal, but
+			   don't use uninitialized data. */
+			smi_info->si_state = SI_NORMAL;
+		} else {
+			smi_info->msg_flags = msg[3];
+			handle_flags(smi_info);
+		}
+		break;
+	}
+
+	case SI_CLEARING_FLAGS:
+	case SI_CLEARING_FLAGS_THEN_SET_IRQ:
+	{
+		unsigned char msg[3];
+
+		/* We cleared the flags. */
+		smi_info->handlers->get_result(smi_info->si_sm, msg, 3);
+		if (msg[2] != 0) {
+			/* Error clearing flags */
+			printk(KERN_WARNING
+			       "ipmi_si: Error clearing flags: %2.2x\n",
+			       msg[2]);
+		}
+		if (smi_info->si_state == SI_CLEARING_FLAGS_THEN_SET_IRQ)
+			start_enable_irq(smi_info);
+		else
+			smi_info->si_state = SI_NORMAL;
+		break;
+	}
+
+	case SI_GETTING_EVENTS:
+	{
+		smi_info->curr_msg->rsp_size
+			= smi_info->handlers->get_result(
+				smi_info->si_sm,
+				smi_info->curr_msg->rsp,
+				IPMI_MAX_MSG_LENGTH);
+
+		/* Do this here becase deliver_recv_msg() releases the
+		   lock, and a new message can be put in during the
+		   time the lock is released. */
+		msg = smi_info->curr_msg;
+		smi_info->curr_msg = NULL;
+		if (msg->rsp[2] != 0) {
+			/* Error getting event, probably done. */
+			msg->done(msg);
+
+			/* Take off the event flag. */
+			smi_info->msg_flags &= ~EVENT_MSG_BUFFER_FULL;
+			handle_flags(smi_info);
+		} else {
+			spin_lock(&smi_info->count_lock);
+			smi_info->events++;
+			spin_unlock(&smi_info->count_lock);
+
+			/* Do this before we deliver the message
+			   because delivering the message releases the
+			   lock and something else can mess with the
+			   state. */
+			handle_flags(smi_info);
+
+			deliver_recv_msg(smi_info, msg);
+		}
+		break;
+	}
+
+	case SI_GETTING_MESSAGES:
+	{
+		smi_info->curr_msg->rsp_size
+			= smi_info->handlers->get_result(
+				smi_info->si_sm,
+				smi_info->curr_msg->rsp,
+				IPMI_MAX_MSG_LENGTH);
+
+		/* Do this here becase deliver_recv_msg() releases the
+		   lock, and a new message can be put in during the
+		   time the lock is released. */
+		msg = smi_info->curr_msg;
+		smi_info->curr_msg = NULL;
+		if (msg->rsp[2] != 0) {
+			/* Error getting event, probably done. */
+			msg->done(msg);
+
+			/* Take off the msg flag. */
+			smi_info->msg_flags &= ~RECEIVE_MSG_AVAIL;
+			handle_flags(smi_info);
+		} else {
+			spin_lock(&smi_info->count_lock);
+			smi_info->incoming_messages++;
+			spin_unlock(&smi_info->count_lock);
+
+			/* Do this before we deliver the message
+			   because delivering the message releases the
+			   lock and something else can mess with the
+			   state. */
+			handle_flags(smi_info);
+
+			deliver_recv_msg(smi_info, msg);
+		}
+		break;
+	}
+
+	case SI_ENABLE_INTERRUPTS1:
+	{
+		unsigned char msg[4];
+
+		/* We got the flags from the SMI, now handle them. */
+		smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+		if (msg[2] != 0) {
+			printk(KERN_WARNING
+			       "ipmi_si: Could not enable interrupts"
+			       ", failed get, using polled mode.\n");
+			smi_info->si_state = SI_NORMAL;
+		} else {
+			msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+			msg[1] = IPMI_SET_BMC_GLOBAL_ENABLES_CMD;
+			msg[2] = msg[3] | 1; /* enable msg queue int */
+			smi_info->handlers->start_transaction(
+				smi_info->si_sm, msg, 3);
+			smi_info->si_state = SI_ENABLE_INTERRUPTS2;
+		}
+		break;
+	}
+
+	case SI_ENABLE_INTERRUPTS2:
+	{
+		unsigned char msg[4];
+
+		/* We got the flags from the SMI, now handle them. */
+		smi_info->handlers->get_result(smi_info->si_sm, msg, 4);
+		if (msg[2] != 0) {
+			printk(KERN_WARNING
+			       "ipmi_si: Could not enable interrupts"
+			       ", failed set, using polled mode.\n");
+		}
+		smi_info->si_state = SI_NORMAL;
+		break;
+	}
+	}
+}
+
+/* Called on timeouts and events.  Timeouts should pass the elapsed
+   time, interrupts should pass in zero. */
+static enum si_sm_result smi_event_handler(struct smi_info *smi_info,
+					   int time)
+{
+	enum si_sm_result si_sm_result;
+
+ restart:
+	/* There used to be a loop here that waited a little while
+	   (around 25us) before giving up.  That turned out to be
+	   pointless, the minimum delays I was seeing were in the 300us
+	   range, which is far too long to wait in an interrupt.  So
+	   we just run until the state machine tells us something
+	   happened or it needs a delay. */
+	si_sm_result = smi_info->handlers->event(smi_info->si_sm, time);
+	time = 0;
+	while (si_sm_result == SI_SM_CALL_WITHOUT_DELAY)
+	{
+		si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+	}
+
+	if (si_sm_result == SI_SM_TRANSACTION_COMPLETE)
+	{
+		spin_lock(&smi_info->count_lock);
+		smi_info->complete_transactions++;
+		spin_unlock(&smi_info->count_lock);
+
+		handle_transaction_done(smi_info);
+		si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+	}
+	else if (si_sm_result == SI_SM_HOSED)
+	{
+		spin_lock(&smi_info->count_lock);
+		smi_info->hosed_count++;
+		spin_unlock(&smi_info->count_lock);
+
+		/* Do the before return_hosed_msg, because that
+		   releases the lock. */
+		smi_info->si_state = SI_NORMAL;
+		if (smi_info->curr_msg != NULL) {
+			/* If we were handling a user message, format
+                           a response to send to the upper layer to
+                           tell it about the error. */
+			return_hosed_msg(smi_info);
+		}
+		si_sm_result = smi_info->handlers->event(smi_info->si_sm, 0);
+	}
+
+	/* We prefer handling attn over new messages. */
+	if (si_sm_result == SI_SM_ATTN)
+	{
+		unsigned char msg[2];
+
+		spin_lock(&smi_info->count_lock);
+		smi_info->attentions++;
+		spin_unlock(&smi_info->count_lock);
+
+		/* Got a attn, send down a get message flags to see
+                   what's causing it.  It would be better to handle
+                   this in the upper layer, but due to the way
+                   interrupts work with the SMI, that's not really
+                   possible. */
+		msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+		msg[1] = IPMI_GET_MSG_FLAGS_CMD;
+
+		smi_info->handlers->start_transaction(
+			smi_info->si_sm, msg, 2);
+		smi_info->si_state = SI_GETTING_FLAGS;
+		goto restart;
+	}
+
+	/* If we are currently idle, try to start the next message. */
+	if (si_sm_result == SI_SM_IDLE) {
+		spin_lock(&smi_info->count_lock);
+		smi_info->idles++;
+		spin_unlock(&smi_info->count_lock);
+
+		si_sm_result = start_next_msg(smi_info);
+		if (si_sm_result != SI_SM_IDLE)
+			goto restart;
+        }
+
+	if ((si_sm_result == SI_SM_IDLE)
+	    && (atomic_read(&smi_info->req_events)))
+	{
+		/* We are idle and the upper layer requested that I fetch
+		   events, so do so. */
+		unsigned char msg[2];
+
+		spin_lock(&smi_info->count_lock);
+		smi_info->flag_fetches++;
+		spin_unlock(&smi_info->count_lock);
+
+		atomic_set(&smi_info->req_events, 0);
+		msg[0] = (IPMI_NETFN_APP_REQUEST << 2);
+		msg[1] = IPMI_GET_MSG_FLAGS_CMD;
+
+		smi_info->handlers->start_transaction(
+			smi_info->si_sm, msg, 2);
+		smi_info->si_state = SI_GETTING_FLAGS;
+		goto restart;
+	}
+
+	return si_sm_result;
+}
+
+static void sender(void                *send_info,
+		   struct ipmi_smi_msg *msg,
+		   int                 priority)
+{
+	struct smi_info   *smi_info = send_info;
+	enum si_sm_result result;
+	unsigned long     flags;
+#ifdef DEBUG_TIMING
+	struct timeval    t;
+#endif
+
+	spin_lock_irqsave(&(smi_info->msg_lock), flags);
+#ifdef DEBUG_TIMING
+	do_gettimeofday(&t);
+	printk("**Enqueue: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+
+	if (smi_info->run_to_completion) {
+		/* If we are running to completion, then throw it in
+		   the list and run transactions until everything is
+		   clear.  Priority doesn't matter here. */
+		list_add_tail(&(msg->link), &(smi_info->xmit_msgs));
+
+		/* We have to release the msg lock and claim the smi
+		   lock in this case, because of race conditions. */
+		spin_unlock_irqrestore(&(smi_info->msg_lock), flags);
+
+		spin_lock_irqsave(&(smi_info->si_lock), flags);
+		result = smi_event_handler(smi_info, 0);
+		while (result != SI_SM_IDLE) {
+			udelay(SI_SHORT_TIMEOUT_USEC);
+			result = smi_event_handler(smi_info,
+						   SI_SHORT_TIMEOUT_USEC);
+		}
+		spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+		return;
+	} else {
+		if (priority > 0) {
+			list_add_tail(&(msg->link), &(smi_info->hp_xmit_msgs));
+		} else {
+			list_add_tail(&(msg->link), &(smi_info->xmit_msgs));
+		}
+	}
+	spin_unlock_irqrestore(&(smi_info->msg_lock), flags);
+
+	spin_lock_irqsave(&(smi_info->si_lock), flags);
+	if ((smi_info->si_state == SI_NORMAL)
+	    && (smi_info->curr_msg == NULL))
+	{
+		start_next_msg(smi_info);
+		si_restart_short_timer(smi_info);
+	}
+	spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+}
+
+static void set_run_to_completion(void *send_info, int i_run_to_completion)
+{
+	struct smi_info   *smi_info = send_info;
+	enum si_sm_result result;
+	unsigned long     flags;
+
+	spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+	smi_info->run_to_completion = i_run_to_completion;
+	if (i_run_to_completion) {
+		result = smi_event_handler(smi_info, 0);
+		while (result != SI_SM_IDLE) {
+			udelay(SI_SHORT_TIMEOUT_USEC);
+			result = smi_event_handler(smi_info,
+						   SI_SHORT_TIMEOUT_USEC);
+		}
+	}
+
+	spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+}
+
+static void poll(void *send_info)
+{
+	struct smi_info *smi_info = send_info;
+
+	smi_event_handler(smi_info, 0);
+}
+
+static void request_events(void *send_info)
+{
+	struct smi_info *smi_info = send_info;
+
+	atomic_set(&smi_info->req_events, 1);
+}
+
+static int initialized = 0;
+
+/* Must be called with interrupts off and with the si_lock held. */
+static void si_restart_short_timer(struct smi_info *smi_info)
+{
+#if defined(CONFIG_HIGH_RES_TIMERS)
+	unsigned long flags;
+	unsigned long jiffies_now;
+
+	if (del_timer(&(smi_info->si_timer))) {
+		/* If we don't delete the timer, then it will go off
+		   immediately, anyway.  So we only process if we
+		   actually delete the timer. */
+
+		/* We already have irqsave on, so no need for it
+                   here. */
+		read_lock(&xtime_lock);
+		jiffies_now = jiffies;
+		smi_info->si_timer.expires = jiffies_now;
+		smi_info->si_timer.sub_expires = get_arch_cycles(jiffies_now);
+
+		add_usec_to_timer(&smi_info->si_timer, SI_SHORT_TIMEOUT_USEC);
+
+		add_timer(&(smi_info->si_timer));
+		spin_lock_irqsave(&smi_info->count_lock, flags);
+		smi_info->timeout_restarts++;
+		spin_unlock_irqrestore(&smi_info->count_lock, flags);
+	}
+#endif
+}
+
+static void smi_timeout(unsigned long data)
+{
+	struct smi_info   *smi_info = (struct smi_info *) data;
+	enum si_sm_result smi_result;
+	unsigned long     flags;
+	unsigned long     jiffies_now;
+	unsigned long     time_diff;
+#ifdef DEBUG_TIMING
+	struct timeval    t;
+#endif
+
+	if (smi_info->stop_operation) {
+		smi_info->timer_stopped = 1;
+		return;
+	}
+
+	spin_lock_irqsave(&(smi_info->si_lock), flags);
+#ifdef DEBUG_TIMING
+	do_gettimeofday(&t);
+	printk("**Timer: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+	jiffies_now = jiffies;
+	time_diff = ((jiffies_now - smi_info->last_timeout_jiffies)
+		     * SI_USEC_PER_JIFFY);
+	smi_result = smi_event_handler(smi_info, time_diff);
+
+	spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+
+	smi_info->last_timeout_jiffies = jiffies_now;
+
+	if ((smi_info->irq) && (! smi_info->interrupt_disabled)) {
+		/* Running with interrupts, only do long timeouts. */
+		smi_info->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+		spin_lock_irqsave(&smi_info->count_lock, flags);
+		smi_info->long_timeouts++;
+		spin_unlock_irqrestore(&smi_info->count_lock, flags);
+		goto do_add_timer;
+	}
+
+	/* If the state machine asks for a short delay, then shorten
+           the timer timeout. */
+	if (smi_result == SI_SM_CALL_WITH_DELAY) {
+		spin_lock_irqsave(&smi_info->count_lock, flags);
+		smi_info->short_timeouts++;
+		spin_unlock_irqrestore(&smi_info->count_lock, flags);
+#if defined(CONFIG_HIGH_RES_TIMERS)
+		read_lock(&xtime_lock);
+                smi_info->si_timer.expires = jiffies;
+                smi_info->si_timer.sub_expires
+                        = get_arch_cycles(smi_info->si_timer.expires);
+                read_unlock(&xtime_lock);
+		add_usec_to_timer(&smi_info->si_timer, SI_SHORT_TIMEOUT_USEC);
+#else
+		smi_info->si_timer.expires = jiffies + 1;
+#endif
+	} else {
+		spin_lock_irqsave(&smi_info->count_lock, flags);
+		smi_info->long_timeouts++;
+		spin_unlock_irqrestore(&smi_info->count_lock, flags);
+		smi_info->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+#if defined(CONFIG_HIGH_RES_TIMERS)
+		smi_info->si_timer.sub_expires = 0;
+#endif
+	}
+
+ do_add_timer:
+	add_timer(&(smi_info->si_timer));
+}
+
+static irqreturn_t si_irq_handler(int irq, void *data, struct pt_regs *regs)
+{
+	struct smi_info *smi_info = data;
+	unsigned long   flags;
+#ifdef DEBUG_TIMING
+	struct timeval  t;
+#endif
+
+	spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+	spin_lock(&smi_info->count_lock);
+	smi_info->interrupts++;
+	spin_unlock(&smi_info->count_lock);
+
+	if (smi_info->stop_operation)
+		goto out;
+
+#ifdef DEBUG_TIMING
+	do_gettimeofday(&t);
+	printk("**Interrupt: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+	smi_event_handler(smi_info, 0);
+ out:
+	spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+	return IRQ_HANDLED;
+}
+
+static struct ipmi_smi_handlers handlers =
+{
+	.owner                  = THIS_MODULE,
+	.sender			= sender,
+	.request_events		= request_events,
+	.set_run_to_completion  = set_run_to_completion,
+	.poll			= poll,
+};
+
+/* There can be 4 IO ports passed in (with or without IRQs), 4 addresses,
+   a default IO port, and 1 ACPI/SPMI address.  That sets SI_MAX_DRIVERS */
+
+#define SI_MAX_PARMS 4
+#define SI_MAX_DRIVERS ((SI_MAX_PARMS * 2) + 2)
+static struct smi_info *smi_infos[SI_MAX_DRIVERS] =
+{ NULL, NULL, NULL, NULL };
+
+#define DEVICE_NAME "ipmi_si"
+
+#define DEFAULT_KCS_IO_PORT	0xca2
+#define DEFAULT_SMIC_IO_PORT	0xca9
+#define DEFAULT_BT_IO_PORT	0xe4
+#define DEFAULT_REGSPACING	1
+
+static int           si_trydefaults = 1;
+static char          *si_type[SI_MAX_PARMS];
+#define MAX_SI_TYPE_STR 30
+static char          si_type_str[MAX_SI_TYPE_STR];
+static unsigned long addrs[SI_MAX_PARMS];
+static int num_addrs;
+static unsigned int  ports[SI_MAX_PARMS];
+static int num_ports;
+static int           irqs[SI_MAX_PARMS];
+static int num_irqs;
+static int           regspacings[SI_MAX_PARMS];
+static int num_regspacings = 0;
+static int           regsizes[SI_MAX_PARMS];
+static int num_regsizes = 0;
+static int           regshifts[SI_MAX_PARMS];
+static int num_regshifts = 0;
+static int slave_addrs[SI_MAX_PARMS];
+static int num_slave_addrs = 0;
+
+
+module_param_named(trydefaults, si_trydefaults, bool, 0);
+MODULE_PARM_DESC(trydefaults, "Setting this to 'false' will disable the"
+		 " default scan of the KCS and SMIC interface at the standard"
+		 " address");
+module_param_string(type, si_type_str, MAX_SI_TYPE_STR, 0);
+MODULE_PARM_DESC(type, "Defines the type of each interface, each"
+		 " interface separated by commas.  The types are 'kcs',"
+		 " 'smic', and 'bt'.  For example si_type=kcs,bt will set"
+		 " the first interface to kcs and the second to bt");
+module_param_array(addrs, long, &num_addrs, 0);
+MODULE_PARM_DESC(addrs, "Sets the memory address of each interface, the"
+		 " addresses separated by commas.  Only use if an interface"
+		 " is in memory.  Otherwise, set it to zero or leave"
+		 " it blank.");
+module_param_array(ports, int, &num_ports, 0);
+MODULE_PARM_DESC(ports, "Sets the port address of each interface, the"
+		 " addresses separated by commas.  Only use if an interface"
+		 " is a port.  Otherwise, set it to zero or leave"
+		 " it blank.");
+module_param_array(irqs, int, &num_irqs, 0);
+MODULE_PARM_DESC(irqs, "Sets the interrupt of each interface, the"
+		 " addresses separated by commas.  Only use if an interface"
+		 " has an interrupt.  Otherwise, set it to zero or leave"
+		 " it blank.");
+module_param_array(regspacings, int, &num_regspacings, 0);
+MODULE_PARM_DESC(regspacings, "The number of bytes between the start address"
+		 " and each successive register used by the interface.  For"
+		 " instance, if the start address is 0xca2 and the spacing"
+		 " is 2, then the second address is at 0xca4.  Defaults"
+		 " to 1.");
+module_param_array(regsizes, int, &num_regsizes, 0);
+MODULE_PARM_DESC(regsizes, "The size of the specific IPMI register in bytes."
+		 " This should generally be 1, 2, 4, or 8 for an 8-bit,"
+		 " 16-bit, 32-bit, or 64-bit register.  Use this if you"
+		 " the 8-bit IPMI register has to be read from a larger"
+		 " register.");
+module_param_array(regshifts, int, &num_regshifts, 0);
+MODULE_PARM_DESC(regshifts, "The amount to shift the data read from the."
+		 " IPMI register, in bits.  For instance, if the data"
+		 " is read from a 32-bit word and the IPMI data is in"
+		 " bit 8-15, then the shift would be 8");
+module_param_array(slave_addrs, int, &num_slave_addrs, 0);
+MODULE_PARM_DESC(slave_addrs, "Set the default IPMB slave address for"
+		 " the controller.  Normally this is 0x20, but can be"
+		 " overridden by this parm.  This is an array indexed"
+		 " by interface number.");
+
+
+#define IPMI_MEM_ADDR_SPACE 1
+#define IPMI_IO_ADDR_SPACE  2
+
+#if defined(CONFIG_ACPI_INTERPRETER) || defined(CONFIG_X86) || defined(CONFIG_PCI)
+static int is_new_interface(int intf, u8 addr_space, unsigned long base_addr)
+{
+	int i;
+
+	for (i = 0; i < SI_MAX_PARMS; ++i) {
+		/* Don't check our address. */
+		if (i == intf)
+			continue;
+		if (si_type[i] != NULL) {
+			if ((addr_space == IPMI_MEM_ADDR_SPACE &&
+			     base_addr == addrs[i]) ||
+			    (addr_space == IPMI_IO_ADDR_SPACE &&
+			     base_addr == ports[i]))
+				return 0;
+		}
+		else
+			break;
+	}
+
+	return 1;
+}
+#endif
+
+static int std_irq_setup(struct smi_info *info)
+{
+	int rv;
+
+	if (!info->irq)
+		return 0;
+
+	rv = request_irq(info->irq,
+			 si_irq_handler,
+			 SA_INTERRUPT,
+			 DEVICE_NAME,
+			 info);
+	if (rv) {
+		printk(KERN_WARNING
+		       "ipmi_si: %s unable to claim interrupt %d,"
+		       " running polled\n",
+		       DEVICE_NAME, info->irq);
+		info->irq = 0;
+	} else {
+		printk("  Using irq %d\n", info->irq);
+	}
+
+	return rv;
+}
+
+static void std_irq_cleanup(struct smi_info *info)
+{
+	if (!info->irq)
+		return;
+
+	free_irq(info->irq, info);
+}
+
+static unsigned char port_inb(struct si_sm_io *io, unsigned int offset)
+{
+	unsigned int *addr = io->info;
+
+	return inb((*addr)+(offset*io->regspacing));
+}
+
+static void port_outb(struct si_sm_io *io, unsigned int offset,
+		      unsigned char b)
+{
+	unsigned int *addr = io->info;
+
+	outb(b, (*addr)+(offset * io->regspacing));
+}
+
+static unsigned char port_inw(struct si_sm_io *io, unsigned int offset)
+{
+	unsigned int *addr = io->info;
+
+	return (inw((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
+
+static void port_outw(struct si_sm_io *io, unsigned int offset,
+		      unsigned char b)
+{
+	unsigned int *addr = io->info;
+
+	outw(b << io->regshift, (*addr)+(offset * io->regspacing));
+}
+
+static unsigned char port_inl(struct si_sm_io *io, unsigned int offset)
+{
+	unsigned int *addr = io->info;
+
+	return (inl((*addr)+(offset * io->regspacing)) >> io->regshift) & 0xff;
+}
+
+static void port_outl(struct si_sm_io *io, unsigned int offset,
+		      unsigned char b)
+{
+	unsigned int *addr = io->info;
+
+	outl(b << io->regshift, (*addr)+(offset * io->regspacing));
+}
+
+static void port_cleanup(struct smi_info *info)
+{
+	unsigned int *addr = info->io.info;
+	int           mapsize;
+
+	if (addr && (*addr)) {
+		mapsize = ((info->io_size * info->io.regspacing)
+			   - (info->io.regspacing - info->io.regsize));
+
+		release_region (*addr, mapsize);
+	}
+	kfree(info);
+}
+
+static int port_setup(struct smi_info *info)
+{
+	unsigned int *addr = info->io.info;
+	int           mapsize;
+
+	if (!addr || (!*addr))
+		return -ENODEV;
+
+	info->io_cleanup = port_cleanup;
+
+	/* Figure out the actual inb/inw/inl/etc routine to use based
+	   upon the register size. */
+	switch (info->io.regsize) {
+	case 1:
+		info->io.inputb = port_inb;
+		info->io.outputb = port_outb;
+		break;
+	case 2:
+		info->io.inputb = port_inw;
+		info->io.outputb = port_outw;
+		break;
+	case 4:
+		info->io.inputb = port_inl;
+		info->io.outputb = port_outl;
+		break;
+	default:
+		printk("ipmi_si: Invalid register size: %d\n",
+		       info->io.regsize);
+		return -EINVAL;
+	}
+
+	/* Calculate the total amount of memory to claim.  This is an
+	 * unusual looking calculation, but it avoids claiming any
+	 * more memory than it has to.  It will claim everything
+	 * between the first address to the end of the last full
+	 * register. */
+	mapsize = ((info->io_size * info->io.regspacing)
+		   - (info->io.regspacing - info->io.regsize));
+
+	if (request_region(*addr, mapsize, DEVICE_NAME) == NULL)
+		return -EIO;
+	return 0;
+}
+
+static int try_init_port(int intf_num, struct smi_info **new_info)
+{
+	struct smi_info *info;
+
+	if (!ports[intf_num])
+		return -ENODEV;
+
+	if (!is_new_interface(intf_num, IPMI_IO_ADDR_SPACE,
+			      ports[intf_num]))
+		return -ENODEV;
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		printk(KERN_ERR "ipmi_si: Could not allocate SI data (1)\n");
+		return -ENOMEM;
+	}
+	memset(info, 0, sizeof(*info));
+
+	info->io_setup = port_setup;
+	info->io.info = &(ports[intf_num]);
+	info->io.addr = NULL;
+	info->io.regspacing = regspacings[intf_num];
+	if (!info->io.regspacing)
+		info->io.regspacing = DEFAULT_REGSPACING;
+	info->io.regsize = regsizes[intf_num];
+	if (!info->io.regsize)
+		info->io.regsize = DEFAULT_REGSPACING;
+	info->io.regshift = regshifts[intf_num];
+	info->irq = 0;
+	info->irq_setup = NULL;
+	*new_info = info;
+
+	if (si_type[intf_num] == NULL)
+		si_type[intf_num] = "kcs";
+
+	printk("ipmi_si: Trying \"%s\" at I/O port 0x%x\n",
+	       si_type[intf_num], ports[intf_num]);
+	return 0;
+}
+
+static unsigned char mem_inb(struct si_sm_io *io, unsigned int offset)
+{
+	return readb((io->addr)+(offset * io->regspacing));
+}
+
+static void mem_outb(struct si_sm_io *io, unsigned int offset,
+		     unsigned char b)
+{
+	writeb(b, (io->addr)+(offset * io->regspacing));
+}
+
+static unsigned char mem_inw(struct si_sm_io *io, unsigned int offset)
+{
+	return (readw((io->addr)+(offset * io->regspacing)) >> io->regshift)
+		&& 0xff;
+}
+
+static void mem_outw(struct si_sm_io *io, unsigned int offset,
+		     unsigned char b)
+{
+	writeb(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+
+static unsigned char mem_inl(struct si_sm_io *io, unsigned int offset)
+{
+	return (readl((io->addr)+(offset * io->regspacing)) >> io->regshift)
+		&& 0xff;
+}
+
+static void mem_outl(struct si_sm_io *io, unsigned int offset,
+		     unsigned char b)
+{
+	writel(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+
+#ifdef readq
+static unsigned char mem_inq(struct si_sm_io *io, unsigned int offset)
+{
+	return (readq((io->addr)+(offset * io->regspacing)) >> io->regshift)
+		&& 0xff;
+}
+
+static void mem_outq(struct si_sm_io *io, unsigned int offset,
+		     unsigned char b)
+{
+	writeq(b << io->regshift, (io->addr)+(offset * io->regspacing));
+}
+#endif
+
+static void mem_cleanup(struct smi_info *info)
+{
+	unsigned long *addr = info->io.info;
+	int           mapsize;
+
+	if (info->io.addr) {
+		iounmap(info->io.addr);
+
+		mapsize = ((info->io_size * info->io.regspacing)
+			   - (info->io.regspacing - info->io.regsize));
+
+		release_mem_region(*addr, mapsize);
+	}
+	kfree(info);
+}
+
+static int mem_setup(struct smi_info *info)
+{
+	unsigned long *addr = info->io.info;
+	int           mapsize;
+
+	if (!addr || (!*addr))
+		return -ENODEV;
+
+	info->io_cleanup = mem_cleanup;
+
+	/* Figure out the actual readb/readw/readl/etc routine to use based
+	   upon the register size. */
+	switch (info->io.regsize) {
+	case 1:
+		info->io.inputb = mem_inb;
+		info->io.outputb = mem_outb;
+		break;
+	case 2:
+		info->io.inputb = mem_inw;
+		info->io.outputb = mem_outw;
+		break;
+	case 4:
+		info->io.inputb = mem_inl;
+		info->io.outputb = mem_outl;
+		break;
+#ifdef readq
+	case 8:
+		info->io.inputb = mem_inq;
+		info->io.outputb = mem_outq;
+		break;
+#endif
+	default:
+		printk("ipmi_si: Invalid register size: %d\n",
+		       info->io.regsize);
+		return -EINVAL;
+	}
+
+	/* Calculate the total amount of memory to claim.  This is an
+	 * unusual looking calculation, but it avoids claiming any
+	 * more memory than it has to.  It will claim everything
+	 * between the first address to the end of the last full
+	 * register. */
+	mapsize = ((info->io_size * info->io.regspacing)
+		   - (info->io.regspacing - info->io.regsize));
+
+	if (request_mem_region(*addr, mapsize, DEVICE_NAME) == NULL)
+		return -EIO;
+
+	info->io.addr = ioremap(*addr, mapsize);
+	if (info->io.addr == NULL) {
+		release_mem_region(*addr, mapsize);
+		return -EIO;
+	}
+	return 0;
+}
+
+static int try_init_mem(int intf_num, struct smi_info **new_info)
+{
+	struct smi_info *info;
+
+	if (!addrs[intf_num])
+		return -ENODEV;
+
+	if (!is_new_interface(intf_num, IPMI_MEM_ADDR_SPACE,
+			      addrs[intf_num]))
+		return -ENODEV;
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		printk(KERN_ERR "ipmi_si: Could not allocate SI data (2)\n");
+		return -ENOMEM;
+	}
+	memset(info, 0, sizeof(*info));
+
+	info->io_setup = mem_setup;
+	info->io.info = &addrs[intf_num];
+	info->io.addr = NULL;
+	info->io.regspacing = regspacings[intf_num];
+	if (!info->io.regspacing)
+		info->io.regspacing = DEFAULT_REGSPACING;
+	info->io.regsize = regsizes[intf_num];
+	if (!info->io.regsize)
+		info->io.regsize = DEFAULT_REGSPACING;
+	info->io.regshift = regshifts[intf_num];
+	info->irq = 0;
+	info->irq_setup = NULL;
+	*new_info = info;
+
+	if (si_type[intf_num] == NULL)
+		si_type[intf_num] = "kcs";
+
+	printk("ipmi_si: Trying \"%s\" at memory address 0x%lx\n",
+	       si_type[intf_num], addrs[intf_num]);
+	return 0;
+}
+
+
+#ifdef CONFIG_ACPI_INTERPRETER
+
+#include <linux/acpi.h>
+
+/* Once we get an ACPI failure, we don't try any more, because we go
+   through the tables sequentially.  Once we don't find a table, there
+   are no more. */
+static int acpi_failure = 0;
+
+/* For GPE-type interrupts. */
+static u32 ipmi_acpi_gpe(void *context)
+{
+	struct smi_info *smi_info = context;
+	unsigned long   flags;
+#ifdef DEBUG_TIMING
+	struct timeval t;
+#endif
+
+	spin_lock_irqsave(&(smi_info->si_lock), flags);
+
+	spin_lock(&smi_info->count_lock);
+	smi_info->interrupts++;
+	spin_unlock(&smi_info->count_lock);
+
+	if (smi_info->stop_operation)
+		goto out;
+
+#ifdef DEBUG_TIMING
+	do_gettimeofday(&t);
+	printk("**ACPI_GPE: %d.%9.9d\n", t.tv_sec, t.tv_usec);
+#endif
+	smi_event_handler(smi_info, 0);
+ out:
+	spin_unlock_irqrestore(&(smi_info->si_lock), flags);
+
+	return ACPI_INTERRUPT_HANDLED;
+}
+
+static int acpi_gpe_irq_setup(struct smi_info *info)
+{
+	acpi_status status;
+
+	if (!info->irq)
+		return 0;
+
+	/* FIXME - is level triggered right? */
+	status = acpi_install_gpe_handler(NULL,
+					  info->irq,
+					  ACPI_GPE_LEVEL_TRIGGERED,
+					  &ipmi_acpi_gpe,
+					  info);
+	if (status != AE_OK) {
+		printk(KERN_WARNING
+		       "ipmi_si: %s unable to claim ACPI GPE %d,"
+		       " running polled\n",
+		       DEVICE_NAME, info->irq);
+		info->irq = 0;
+		return -EINVAL;
+	} else {
+		printk("  Using ACPI GPE %d\n", info->irq);
+		return 0;
+	}
+}
+
+static void acpi_gpe_irq_cleanup(struct smi_info *info)
+{
+	if (!info->irq)
+		return;
+
+	acpi_remove_gpe_handler(NULL, info->irq, &ipmi_acpi_gpe);
+}
+
+/*
+ * Defined at
+ * http://h21007.www2.hp.com/dspp/files/unprotected/devresource/Docs/TechPapers/IA64/hpspmi.pdf
+ */
+struct SPMITable {
+	s8	Signature[4];
+	u32	Length;
+	u8	Revision;
+	u8	Checksum;
+	s8	OEMID[6];
+	s8	OEMTableID[8];
+	s8	OEMRevision[4];
+	s8	CreatorID[4];
+	s8	CreatorRevision[4];
+	u8	InterfaceType;
+	u8	IPMIlegacy;
+	s16	SpecificationRevision;
+
+	/*
+	 * Bit 0 - SCI interrupt supported
+	 * Bit 1 - I/O APIC/SAPIC
+	 */
+	u8	InterruptType;
+
+	/* If bit 0 of InterruptType is set, then this is the SCI
+           interrupt in the GPEx_STS register. */
+	u8	GPE;
+
+	s16	Reserved;
+
+	/* If bit 1 of InterruptType is set, then this is the I/O
+           APIC/SAPIC interrupt. */
+	u32	GlobalSystemInterrupt;
+
+	/* The actual register address. */
+	struct acpi_generic_address addr;
+
+	u8	UID[4];
+
+	s8      spmi_id[1]; /* A '\0' terminated array starts here. */
+};
+
+static int try_init_acpi(int intf_num, struct smi_info **new_info)
+{
+	struct smi_info  *info;
+	acpi_status      status;
+	struct SPMITable *spmi;
+	char             *io_type;
+	u8 		 addr_space;
+
+	if (acpi_failure)
+		return -ENODEV;
+
+	status = acpi_get_firmware_table("SPMI", intf_num+1,
+					 ACPI_LOGICAL_ADDRESSING,
+					 (struct acpi_table_header **) &spmi);
+	if (status != AE_OK) {
+		acpi_failure = 1;
+		return -ENODEV;
+	}
+
+	if (spmi->IPMIlegacy != 1) {
+	    printk(KERN_INFO "IPMI: Bad SPMI legacy %d\n", spmi->IPMIlegacy);
+  	    return -ENODEV;
+	}
+
+	if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY)
+		addr_space = IPMI_MEM_ADDR_SPACE;
+	else
+		addr_space = IPMI_IO_ADDR_SPACE;
+	if (!is_new_interface(-1, addr_space, spmi->addr.address))
+		return -ENODEV;
+
+	if (!spmi->addr.register_bit_width) {
+		acpi_failure = 1;
+		return -ENODEV;
+	}
+
+	/* Figure out the interface type. */
+	switch (spmi->InterfaceType)
+	{
+	case 1:	/* KCS */
+		si_type[intf_num] = "kcs";
+		break;
+
+	case 2:	/* SMIC */
+		si_type[intf_num] = "smic";
+		break;
+
+	case 3:	/* BT */
+		si_type[intf_num] = "bt";
+		break;
+
+	default:
+		printk(KERN_INFO "ipmi_si: Unknown ACPI/SPMI SI type %d\n",
+			spmi->InterfaceType);
+		return -EIO;
+	}
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		printk(KERN_ERR "ipmi_si: Could not allocate SI data (3)\n");
+		return -ENOMEM;
+	}
+	memset(info, 0, sizeof(*info));
+
+	if (spmi->InterruptType & 1) {
+		/* We've got a GPE interrupt. */
+		info->irq = spmi->GPE;
+		info->irq_setup = acpi_gpe_irq_setup;
+		info->irq_cleanup = acpi_gpe_irq_cleanup;
+	} else if (spmi->InterruptType & 2) {
+		/* We've got an APIC/SAPIC interrupt. */
+		info->irq = spmi->GlobalSystemInterrupt;
+		info->irq_setup = std_irq_setup;
+		info->irq_cleanup = std_irq_cleanup;
+	} else {
+		/* Use the default interrupt setting. */
+		info->irq = 0;
+		info->irq_setup = NULL;
+	}
+
+	regspacings[intf_num] = spmi->addr.register_bit_width / 8;
+	info->io.regspacing = spmi->addr.register_bit_width / 8;
+	regsizes[intf_num] = regspacings[intf_num];
+	info->io.regsize = regsizes[intf_num];
+	regshifts[intf_num] = spmi->addr.register_bit_offset;
+	info->io.regshift = regshifts[intf_num];
+
+	if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
+		io_type = "memory";
+		info->io_setup = mem_setup;
+		addrs[intf_num] = spmi->addr.address;
+		info->io.info = &(addrs[intf_num]);
+	} else if (spmi->addr.address_space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
+		io_type = "I/O";
+		info->io_setup = port_setup;
+		ports[intf_num] = spmi->addr.address;
+		info->io.info = &(ports[intf_num]);
+	} else {
+		kfree(info);
+		printk("ipmi_si: Unknown ACPI I/O Address type\n");
+		return -EIO;
+	}
+
+	*new_info = info;
+
+	printk("ipmi_si: ACPI/SPMI specifies \"%s\" %s SI @ 0x%lx\n",
+	       si_type[intf_num], io_type, (unsigned long) spmi->addr.address);
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_X86
+typedef struct dmi_ipmi_data
+{
+	u8   		type;
+	u8   		addr_space;
+	unsigned long	base_addr;
+	u8   		irq;
+	u8              offset;
+	u8              slave_addr;
+} dmi_ipmi_data_t;
+
+static dmi_ipmi_data_t dmi_data[SI_MAX_DRIVERS];
+static int dmi_data_entries;
+
+typedef struct dmi_header
+{
+	u8	type;
+	u8	length;
+	u16	handle;
+} dmi_header_t;
+
+static int decode_dmi(dmi_header_t *dm, int intf_num)
+{
+	u8		*data = (u8 *)dm;
+	unsigned long  	base_addr;
+	u8		reg_spacing;
+	u8              len = dm->length;
+	dmi_ipmi_data_t *ipmi_data = dmi_data+intf_num;
+
+	ipmi_data->type = data[4];
+
+	memcpy(&base_addr, data+8, sizeof(unsigned long));
+	if (len >= 0x11) {
+		if (base_addr & 1) {
+			/* I/O */
+			base_addr &= 0xFFFE;
+			ipmi_data->addr_space = IPMI_IO_ADDR_SPACE;
+		}
+		else {
+			/* Memory */
+			ipmi_data->addr_space = IPMI_MEM_ADDR_SPACE;
+		}
+		/* If bit 4 of byte 0x10 is set, then the lsb for the address
+		   is odd. */
+		ipmi_data->base_addr = base_addr | ((data[0x10] & 0x10) >> 4);
+
+		ipmi_data->irq = data[0x11];
+
+		/* The top two bits of byte 0x10 hold the register spacing. */
+		reg_spacing = (data[0x10] & 0xC0) >> 6;
+		switch(reg_spacing){
+		case 0x00: /* Byte boundaries */
+		    ipmi_data->offset = 1;
+		    break;
+		case 0x01: /* 32-bit boundaries */
+		    ipmi_data->offset = 4;
+		    break;
+		case 0x02: /* 16-byte boundaries */
+		    ipmi_data->offset = 16;
+		    break;
+		default:
+		    /* Some other interface, just ignore it. */
+		    return -EIO;
+		}
+	} else {
+		/* Old DMI spec. */
+		ipmi_data->base_addr = base_addr;
+		ipmi_data->addr_space = IPMI_IO_ADDR_SPACE;
+		ipmi_data->offset = 1;
+	}
+
+	ipmi_data->slave_addr = data[6];
+
+	if (is_new_interface(-1, ipmi_data->addr_space,ipmi_data->base_addr)) {
+		dmi_data_entries++;
+		return 0;
+	}
+
+	memset(ipmi_data, 0, sizeof(dmi_ipmi_data_t));
+
+	return -1;
+}
+
+static int dmi_table(u32 base, int len, int num)
+{
+	u8 		  *buf;
+	struct dmi_header *dm;
+	u8 		  *data;
+	int 		  i=1;
+	int		  status=-1;
+	int               intf_num = 0;
+
+	buf = ioremap(base, len);
+	if(buf==NULL)
+		return -1;
+
+	data = buf;
+
+	while(i<num && (data - buf) < len)
+	{
+		dm=(dmi_header_t *)data;
+
+		if((data-buf+dm->length) >= len)
+        		break;
+
+		if (dm->type == 38) {
+			if (decode_dmi(dm, intf_num) == 0) {
+				intf_num++;
+				if (intf_num >= SI_MAX_DRIVERS)
+					break;
+			}
+		}
+
+	        data+=dm->length;
+		while((data-buf) < len && (*data || data[1]))
+			data++;
+		data+=2;
+		i++;
+	}
+	iounmap(buf);
+
+	return status;
+}
+
+inline static int dmi_checksum(u8 *buf)
+{
+	u8   sum=0;
+	int  a;
+
+	for(a=0; a<15; a++)
+		sum+=buf[a];
+	return (sum==0);
+}
+
+static int dmi_decode(void)
+{
+	u8   buf[15];
+	u32  fp=0xF0000;
+
+#ifdef CONFIG_SIMNOW
+	return -1;
+#endif
+
+	while(fp < 0xFFFFF)
+	{
+		isa_memcpy_fromio(buf, fp, 15);
+		if(memcmp(buf, "_DMI_", 5)==0 && dmi_checksum(buf))
+		{
+			u16 num=buf[13]<<8|buf[12];
+			u16 len=buf[7]<<8|buf[6];
+			u32 base=buf[11]<<24|buf[10]<<16|buf[9]<<8|buf[8];
+
+			if(dmi_table(base, len, num) == 0)
+				return 0;
+		}
+		fp+=16;
+	}
+
+	return -1;
+}
+
+static int try_init_smbios(int intf_num, struct smi_info **new_info)
+{
+	struct smi_info   *info;
+	dmi_ipmi_data_t   *ipmi_data = dmi_data+intf_num;
+	char              *io_type;
+
+	if (intf_num >= dmi_data_entries)
+		return -ENODEV;
+
+	switch(ipmi_data->type) {
+		case 0x01: /* KCS */
+			si_type[intf_num] = "kcs";
+			break;
+		case 0x02: /* SMIC */
+			si_type[intf_num] = "smic";
+			break;
+		case 0x03: /* BT */
+			si_type[intf_num] = "bt";
+			break;
+		default:
+			return -EIO;
+	}
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		printk(KERN_ERR "ipmi_si: Could not allocate SI data (4)\n");
+		return -ENOMEM;
+	}
+	memset(info, 0, sizeof(*info));
+
+	if (ipmi_data->addr_space == 1) {
+		io_type = "memory";
+		info->io_setup = mem_setup;
+		addrs[intf_num] = ipmi_data->base_addr;
+		info->io.info = &(addrs[intf_num]);
+	} else if (ipmi_data->addr_space == 2) {
+		io_type = "I/O";
+		info->io_setup = port_setup;
+		ports[intf_num] = ipmi_data->base_addr;
+		info->io.info = &(ports[intf_num]);
+	} else {
+		kfree(info);
+		printk("ipmi_si: Unknown SMBIOS I/O Address type.\n");
+		return -EIO;
+	}
+
+	regspacings[intf_num] = ipmi_data->offset;
+	info->io.regspacing = regspacings[intf_num];
+	if (!info->io.regspacing)
+		info->io.regspacing = DEFAULT_REGSPACING;
+	info->io.regsize = DEFAULT_REGSPACING;
+	info->io.regshift = regshifts[intf_num];
+
+	info->slave_addr = ipmi_data->slave_addr;
+
+	irqs[intf_num] = ipmi_data->irq;
+
+	*new_info = info;
+
+	printk("ipmi_si: Found SMBIOS-specified state machine at %s"
+	       " address 0x%lx, slave address 0x%x\n",
+	       io_type, (unsigned long)ipmi_data->base_addr,
+	       ipmi_data->slave_addr);
+	return 0;
+}
+#endif /* CONFIG_X86 */
+
+#ifdef CONFIG_PCI
+
+#define PCI_ERMC_CLASSCODE  0x0C0700
+#define PCI_HP_VENDOR_ID    0x103C
+#define PCI_MMC_DEVICE_ID   0x121A
+#define PCI_MMC_ADDR_CW     0x10
+
+/* Avoid more than one attempt to probe pci smic. */
+static int pci_smic_checked = 0;
+
+static int find_pci_smic(int intf_num, struct smi_info **new_info)
+{
+	struct smi_info  *info;
+	int              error;
+	struct pci_dev   *pci_dev = NULL;
+	u16    		 base_addr;
+	int              fe_rmc = 0;
+
+	if (pci_smic_checked)
+		return -ENODEV;
+
+	pci_smic_checked = 1;
+
+	if ((pci_dev = pci_get_device(PCI_HP_VENDOR_ID, PCI_MMC_DEVICE_ID,
+				       NULL)))
+		;
+	else if ((pci_dev = pci_get_class(PCI_ERMC_CLASSCODE, NULL)) &&
+		 pci_dev->subsystem_vendor == PCI_HP_VENDOR_ID)
+		fe_rmc = 1;
+	else
+		return -ENODEV;
+
+	error = pci_read_config_word(pci_dev, PCI_MMC_ADDR_CW, &base_addr);
+	if (error)
+	{
+		pci_dev_put(pci_dev);
+		printk(KERN_ERR
+		       "ipmi_si: pci_read_config_word() failed (%d).\n",
+		       error);
+		return -ENODEV;
+	}
+
+	/* Bit 0: 1 specifies programmed I/O, 0 specifies memory mapped I/O */
+	if (!(base_addr & 0x0001))
+	{
+		pci_dev_put(pci_dev);
+		printk(KERN_ERR
+		       "ipmi_si: memory mapped I/O not supported for PCI"
+		       " smic.\n");
+		return -ENODEV;
+	}
+
+	base_addr &= 0xFFFE;
+	if (!fe_rmc)
+		/* Data register starts at base address + 1 in eRMC */
+		++base_addr;
+
+	if (!is_new_interface(-1, IPMI_IO_ADDR_SPACE, base_addr)) {
+		pci_dev_put(pci_dev);
+		return -ENODEV;
+	}
+
+	info = kmalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		pci_dev_put(pci_dev);
+		printk(KERN_ERR "ipmi_si: Could not allocate SI data (5)\n");
+		return -ENOMEM;
+	}
+	memset(info, 0, sizeof(*info));
+
+	info->io_setup = port_setup;
+	ports[intf_num] = base_addr;
+	info->io.info = &(ports[intf_num]);
+	info->io.regspacing = regspacings[intf_num];
+	if (!info->io.regspacing)
+		info->io.regspacing = DEFAULT_REGSPACING;
+	info->io.regsize = DEFAULT_REGSPACING;
+	info->io.regshift = regshifts[intf_num];
+
+	*new_info = info;
+
+	irqs[intf_num] = pci_dev->irq;
+	si_type[intf_num] = "smic";
+
+	printk("ipmi_si: Found PCI SMIC at I/O address 0x%lx\n",
+		(long unsigned int) base_addr);
+
+	pci_dev_put(pci_dev);
+	return 0;
+}
+#endif /* CONFIG_PCI */
+
+static int try_init_plug_and_play(int intf_num, struct smi_info **new_info)
+{
+#ifdef CONFIG_PCI
+	if (find_pci_smic(intf_num, new_info)==0)
+		return 0;
+#endif
+	/* Include other methods here. */
+
+	return -ENODEV;
+}
+
+
+static int try_get_dev_id(struct smi_info *smi_info)
+{
+	unsigned char      msg[2];
+	unsigned char      *resp;
+	unsigned long      resp_len;
+	enum si_sm_result smi_result;
+	int               rv = 0;
+
+	resp = kmalloc(IPMI_MAX_MSG_LENGTH, GFP_KERNEL);
+	if (!resp)
+		return -ENOMEM;
+
+	/* Do a Get Device ID command, since it comes back with some
+	   useful info. */
+	msg[0] = IPMI_NETFN_APP_REQUEST << 2;
+	msg[1] = IPMI_GET_DEVICE_ID_CMD;
+	smi_info->handlers->start_transaction(smi_info->si_sm, msg, 2);
+
+	smi_result = smi_info->handlers->event(smi_info->si_sm, 0);
+	for (;;)
+	{
+		if (smi_result == SI_SM_CALL_WITH_DELAY) {
+			set_current_state(TASK_UNINTERRUPTIBLE);
+			schedule_timeout(1);
+			smi_result = smi_info->handlers->event(
+				smi_info->si_sm, 100);
+		}
+		else if (smi_result == SI_SM_CALL_WITHOUT_DELAY)
+		{
+			smi_result = smi_info->handlers->event(
+				smi_info->si_sm, 0);
+		}
+		else
+			break;
+	}
+	if (smi_result == SI_SM_HOSED) {
+		/* We couldn't get the state machine to run, so whatever's at
+		   the port is probably not an IPMI SMI interface. */
+		rv = -ENODEV;
+		goto out;
+	}
+
+	/* Otherwise, we got some data. */
+	resp_len = smi_info->handlers->get_result(smi_info->si_sm,
+						  resp, IPMI_MAX_MSG_LENGTH);
+	if (resp_len < 6) {
+		/* That's odd, it should be longer. */
+		rv = -EINVAL;
+		goto out;
+	}
+
+	if ((resp[1] != IPMI_GET_DEVICE_ID_CMD) || (resp[2] != 0)) {
+		/* That's odd, it shouldn't be able to fail. */
+		rv = -EINVAL;
+		goto out;
+	}
+
+	/* Record info from the get device id, in case we need it. */
+	smi_info->ipmi_si_dev_rev = resp[4] & 0xf;
+	smi_info->ipmi_si_fw_rev_major = resp[5] & 0x7f;
+	smi_info->ipmi_si_fw_rev_minor = resp[6];
+	smi_info->ipmi_version_major = resp[7] & 0xf;
+	smi_info->ipmi_version_minor = resp[7] >> 4;
+
+ out:
+	kfree(resp);
+	return rv;
+}
+
+static int type_file_read_proc(char *page, char **start, off_t off,
+			       int count, int *eof, void *data)
+{
+	char            *out = (char *) page;
+	struct smi_info *smi = data;
+
+	switch (smi->si_type) {
+	    case SI_KCS:
+		return sprintf(out, "kcs\n");
+	    case SI_SMIC:
+		return sprintf(out, "smic\n");
+	    case SI_BT:
+		return sprintf(out, "bt\n");
+	    default:
+		return 0;
+	}
+}
+
+static int stat_file_read_proc(char *page, char **start, off_t off,
+			       int count, int *eof, void *data)
+{
+	char            *out = (char *) page;
+	struct smi_info *smi = data;
+
+	out += sprintf(out, "interrupts_enabled:    %d\n",
+		       smi->irq && !smi->interrupt_disabled);
+	out += sprintf(out, "short_timeouts:        %ld\n",
+		       smi->short_timeouts);
+	out += sprintf(out, "long_timeouts:         %ld\n",
+		       smi->long_timeouts);
+	out += sprintf(out, "timeout_restarts:      %ld\n",
+		       smi->timeout_restarts);
+	out += sprintf(out, "idles:                 %ld\n",
+		       smi->idles);
+	out += sprintf(out, "interrupts:            %ld\n",
+		       smi->interrupts);
+	out += sprintf(out, "attentions:            %ld\n",
+		       smi->attentions);
+	out += sprintf(out, "flag_fetches:          %ld\n",
+		       smi->flag_fetches);
+	out += sprintf(out, "hosed_count:           %ld\n",
+		       smi->hosed_count);
+	out += sprintf(out, "complete_transactions: %ld\n",
+		       smi->complete_transactions);
+	out += sprintf(out, "events:                %ld\n",
+		       smi->events);
+	out += sprintf(out, "watchdog_pretimeouts:  %ld\n",
+		       smi->watchdog_pretimeouts);
+	out += sprintf(out, "incoming_messages:     %ld\n",
+		       smi->incoming_messages);
+
+	return (out - ((char *) page));
+}
+
+/* Returns 0 if initialized, or negative on an error. */
+static int init_one_smi(int intf_num, struct smi_info **smi)
+{
+	int		rv;
+	struct smi_info *new_smi;
+
+
+	rv = try_init_mem(intf_num, &new_smi);
+	if (rv)
+		rv = try_init_port(intf_num, &new_smi);
+#ifdef CONFIG_ACPI_INTERPRETER
+	if ((rv) && (si_trydefaults)) {
+		rv = try_init_acpi(intf_num, &new_smi);
+	}
+#endif
+#ifdef CONFIG_X86
+	if ((rv) && (si_trydefaults)) {
+		rv = try_init_smbios(intf_num, &new_smi);
+        }
+#endif
+	if ((rv) && (si_trydefaults)) {
+		rv = try_init_plug_and_play(intf_num, &new_smi);
+	}
+
+
+	if (rv)
+		return rv;
+
+	/* So we know not to free it unless we have allocated one. */
+	new_smi->intf = NULL;
+	new_smi->si_sm = NULL;
+	new_smi->handlers = NULL;
+
+	if (!new_smi->irq_setup) {
+		new_smi->irq = irqs[intf_num];
+		new_smi->irq_setup = std_irq_setup;
+		new_smi->irq_cleanup = std_irq_cleanup;
+	}
+
+	/* Default to KCS if no type is specified. */
+	if (si_type[intf_num] == NULL) {
+		if (si_trydefaults)
+			si_type[intf_num] = "kcs";
+		else {
+			rv = -EINVAL;
+			goto out_err;
+		}
+	}
+
+	/* Set up the state machine to use. */
+	if (strcmp(si_type[intf_num], "kcs") == 0) {
+		new_smi->handlers = &kcs_smi_handlers;
+		new_smi->si_type = SI_KCS;
+	} else if (strcmp(si_type[intf_num], "smic") == 0) {
+		new_smi->handlers = &smic_smi_handlers;
+		new_smi->si_type = SI_SMIC;
+	} else if (strcmp(si_type[intf_num], "bt") == 0) {
+		new_smi->handlers = &bt_smi_handlers;
+		new_smi->si_type = SI_BT;
+	} else {
+		/* No support for anything else yet. */
+		rv = -EIO;
+		goto out_err;
+	}
+
+	/* Allocate the state machine's data and initialize it. */
+	new_smi->si_sm = kmalloc(new_smi->handlers->size(), GFP_KERNEL);
+	if (!new_smi->si_sm) {
+		printk(" Could not allocate state machine memory\n");
+		rv = -ENOMEM;
+		goto out_err;
+	}
+	new_smi->io_size = new_smi->handlers->init_data(new_smi->si_sm,
+							&new_smi->io);
+
+	/* Now that we know the I/O size, we can set up the I/O. */
+	rv = new_smi->io_setup(new_smi);
+	if (rv) {
+		printk(" Could not set up I/O space\n");
+		goto out_err;
+	}
+
+	spin_lock_init(&(new_smi->si_lock));
+	spin_lock_init(&(new_smi->msg_lock));
+	spin_lock_init(&(new_smi->count_lock));
+
+	/* Do low-level detection first. */
+	if (new_smi->handlers->detect(new_smi->si_sm)) {
+		rv = -ENODEV;
+		goto out_err;
+	}
+
+	/* Attempt a get device id command.  If it fails, we probably
+           don't have a SMI here. */
+	rv = try_get_dev_id(new_smi);
+	if (rv)
+		goto out_err;
+
+	/* Try to claim any interrupts. */
+	new_smi->irq_setup(new_smi);
+
+	INIT_LIST_HEAD(&(new_smi->xmit_msgs));
+	INIT_LIST_HEAD(&(new_smi->hp_xmit_msgs));
+	new_smi->curr_msg = NULL;
+	atomic_set(&new_smi->req_events, 0);
+	new_smi->run_to_completion = 0;
+
+	new_smi->interrupt_disabled = 0;
+	new_smi->timer_stopped = 0;
+	new_smi->stop_operation = 0;
+
+	/* Start clearing the flags before we enable interrupts or the
+	   timer to avoid racing with the timer. */
+	start_clear_flags(new_smi);
+	/* IRQ is defined to be set when non-zero. */
+	if (new_smi->irq)
+		new_smi->si_state = SI_CLEARING_FLAGS_THEN_SET_IRQ;
+
+	/* The ipmi_register_smi() code does some operations to
+	   determine the channel information, so we must be ready to
+	   handle operations before it is called.  This means we have
+	   to stop the timer if we get an error after this point. */
+	init_timer(&(new_smi->si_timer));
+	new_smi->si_timer.data = (long) new_smi;
+	new_smi->si_timer.function = smi_timeout;
+	new_smi->last_timeout_jiffies = jiffies;
+	new_smi->si_timer.expires = jiffies + SI_TIMEOUT_JIFFIES;
+	add_timer(&(new_smi->si_timer));
+
+	rv = ipmi_register_smi(&handlers,
+			       new_smi,
+			       new_smi->ipmi_version_major,
+			       new_smi->ipmi_version_minor,
+			       new_smi->slave_addr,
+			       &(new_smi->intf));
+	if (rv) {
+		printk(KERN_ERR
+		       "ipmi_si: Unable to register device: error %d\n",
+		       rv);
+		goto out_err_stop_timer;
+	}
+
+	rv = ipmi_smi_add_proc_entry(new_smi->intf, "type",
+				     type_file_read_proc, NULL,
+				     new_smi, THIS_MODULE);
+	if (rv) {
+		printk(KERN_ERR
+		       "ipmi_si: Unable to create proc entry: %d\n",
+		       rv);
+		goto out_err_stop_timer;
+	}
+
+	rv = ipmi_smi_add_proc_entry(new_smi->intf, "si_stats",
+				     stat_file_read_proc, NULL,
+				     new_smi, THIS_MODULE);
+	if (rv) {
+		printk(KERN_ERR
+		       "ipmi_si: Unable to create proc entry: %d\n",
+		       rv);
+		goto out_err_stop_timer;
+	}
+
+	*smi = new_smi;
+
+	printk(" IPMI %s interface initialized\n", si_type[intf_num]);
+
+	return 0;
+
+ out_err_stop_timer:
+	new_smi->stop_operation = 1;
+
+	/* Wait for the timer to stop.  This avoids problems with race
+	   conditions removing the timer here. */
+	while (!new_smi->timer_stopped) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+ out_err:
+	if (new_smi->intf)
+		ipmi_unregister_smi(new_smi->intf);
+
+	new_smi->irq_cleanup(new_smi);
+
+	/* Wait until we know that we are out of any interrupt
+	   handlers might have been running before we freed the
+	   interrupt. */
+	synchronize_kernel();
+
+	if (new_smi->si_sm) {
+		if (new_smi->handlers)
+			new_smi->handlers->cleanup(new_smi->si_sm);
+		kfree(new_smi->si_sm);
+	}
+	new_smi->io_cleanup(new_smi);
+
+	return rv;
+}
+
+static __init int init_ipmi_si(void)
+{
+	int  rv = 0;
+	int  pos = 0;
+	int  i;
+	char *str;
+
+	if (initialized)
+		return 0;
+	initialized = 1;
+
+	/* Parse out the si_type string into its components. */
+	str = si_type_str;
+	if (*str != '\0') {
+		for (i=0; (i<SI_MAX_PARMS) && (*str != '\0'); i++) {
+			si_type[i] = str;
+			str = strchr(str, ',');
+			if (str) {
+				*str = '\0';
+				str++;
+			} else {
+				break;
+			}
+		}
+	}
+
+	printk(KERN_INFO "IPMI System Interface driver version "
+	       IPMI_SI_VERSION);
+	if (kcs_smi_handlers.version)
+		printk(", KCS version %s", kcs_smi_handlers.version);
+	if (smic_smi_handlers.version)
+		printk(", SMIC version %s", smic_smi_handlers.version);
+	if (bt_smi_handlers.version)
+   	        printk(", BT version %s", bt_smi_handlers.version);
+	printk("\n");
+
+#ifdef CONFIG_X86
+	dmi_decode();
+#endif
+
+	rv = init_one_smi(0, &(smi_infos[pos]));
+	if (rv && !ports[0] && si_trydefaults) {
+		/* If we are trying defaults and the initial port is
+                   not set, then set it. */
+		si_type[0] = "kcs";
+		ports[0] = DEFAULT_KCS_IO_PORT;
+		rv = init_one_smi(0, &(smi_infos[pos]));
+		if (rv) {
+			/* No KCS - try SMIC */
+			si_type[0] = "smic";
+			ports[0] = DEFAULT_SMIC_IO_PORT;
+			rv = init_one_smi(0, &(smi_infos[pos]));
+		}
+		if (rv) {
+			/* No SMIC - try BT */
+			si_type[0] = "bt";
+			ports[0] = DEFAULT_BT_IO_PORT;
+			rv = init_one_smi(0, &(smi_infos[pos]));
+		}
+	}
+	if (rv == 0)
+		pos++;
+
+	for (i=1; i < SI_MAX_PARMS; i++) {
+		rv = init_one_smi(i, &(smi_infos[pos]));
+		if (rv == 0)
+			pos++;
+	}
+
+	if (smi_infos[0] == NULL) {
+		printk("ipmi_si: Unable to find any System Interface(s)\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+module_init(init_ipmi_si);
+
+static void __exit cleanup_one_si(struct smi_info *to_clean)
+{
+	int           rv;
+	unsigned long flags;
+
+	if (! to_clean)
+		return;
+
+	/* Tell the timer and interrupt handlers that we are shutting
+	   down. */
+	spin_lock_irqsave(&(to_clean->si_lock), flags);
+	spin_lock(&(to_clean->msg_lock));
+
+	to_clean->stop_operation = 1;
+
+	to_clean->irq_cleanup(to_clean);
+
+	spin_unlock(&(to_clean->msg_lock));
+	spin_unlock_irqrestore(&(to_clean->si_lock), flags);
+
+	/* Wait until we know that we are out of any interrupt
+	   handlers might have been running before we freed the
+	   interrupt. */
+	synchronize_kernel();
+
+	/* Wait for the timer to stop.  This avoids problems with race
+	   conditions removing the timer here. */
+	while (!to_clean->timer_stopped) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+	/* Interrupts and timeouts are stopped, now make sure the
+	   interface is in a clean state. */
+	while ((to_clean->curr_msg) || (to_clean->si_state != SI_NORMAL)) {
+		poll(to_clean);
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+	rv = ipmi_unregister_smi(to_clean->intf);
+	if (rv) {
+		printk(KERN_ERR
+		       "ipmi_si: Unable to unregister device: errno=%d\n",
+		       rv);
+	}
+
+	to_clean->handlers->cleanup(to_clean->si_sm);
+
+	kfree(to_clean->si_sm);
+
+	to_clean->io_cleanup(to_clean);
+}
+
+static __exit void cleanup_ipmi_si(void)
+{
+	int i;
+
+	if (!initialized)
+		return;
+
+	for (i=0; i<SI_MAX_DRIVERS; i++) {
+		cleanup_one_si(smi_infos[i]);
+	}
+}
+module_exit(cleanup_ipmi_si);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ipmi/ipmi_si_sm.h b/drivers/char/ipmi/ipmi_si_sm.h
new file mode 100644
index 0000000..a0212b0
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_si_sm.h
@@ -0,0 +1,120 @@
+/*
+ * ipmi_si_sm.h
+ *
+ * State machine interface for low-level IPMI system management
+ * interface state machines.  This code is the interface between
+ * the ipmi_smi code (that handles the policy of a KCS, SMIC, or
+ * BT interface) and the actual low-level state machine.
+ *
+ * Author: MontaVista Software, Inc.
+ *         Corey Minyard <minyard@mvista.com>
+ *         source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software 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.
+ *
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* This is defined by the state machines themselves, it is an opaque
+   data type for them to use. */
+struct si_sm_data;
+
+/* The structure for doing I/O in the state machine.  The state
+   machine doesn't have the actual I/O routines, they are done through
+   this interface. */
+struct si_sm_io
+{
+	unsigned char (*inputb)(struct si_sm_io *io, unsigned int offset);
+	void (*outputb)(struct si_sm_io *io,
+			unsigned int  offset,
+			unsigned char b);
+
+	/* Generic info used by the actual handling routines, the
+           state machine shouldn't touch these. */
+	void *info;
+	void *addr;
+	int  regspacing;
+	int  regsize;
+	int  regshift;
+};
+
+/* Results of SMI events. */
+enum si_sm_result
+{
+	SI_SM_CALL_WITHOUT_DELAY, /* Call the driver again immediately */
+	SI_SM_CALL_WITH_DELAY,	/* Delay some before calling again. */
+	SI_SM_TRANSACTION_COMPLETE, /* A transaction is finished. */
+	SI_SM_IDLE,		/* The SM is in idle state. */
+	SI_SM_HOSED,		/* The hardware violated the state machine. */
+	SI_SM_ATTN		/* The hardware is asserting attn and the
+				   state machine is idle. */
+};
+
+/* Handlers for the SMI state machine. */
+struct si_sm_handlers
+{
+	/* Put the version number of the state machine here so the
+           upper layer can print it. */
+	char *version;
+
+	/* Initialize the data and return the amount of I/O space to
+           reserve for the space. */
+	unsigned int (*init_data)(struct si_sm_data *smi,
+				  struct si_sm_io   *io);
+
+	/* Start a new transaction in the state machine.  This will
+	   return -2 if the state machine is not idle, -1 if the size
+	   is invalid (to large or too small), or 0 if the transaction
+	   is successfully completed. */
+	int (*start_transaction)(struct si_sm_data *smi,
+				 unsigned char *data, unsigned int size);
+
+	/* Return the results after the transaction.  This will return
+	   -1 if the buffer is too small, zero if no transaction is
+	   present, or the actual length of the result data. */
+	int (*get_result)(struct si_sm_data *smi,
+			  unsigned char *data, unsigned int length);
+
+	/* Call this periodically (for a polled interface) or upon
+	   receiving an interrupt (for a interrupt-driven interface).
+	   If interrupt driven, you should probably poll this
+	   periodically when not in idle state.  This should be called
+	   with the time that passed since the last call, if it is
+	   significant.  Time is in microseconds. */
+	enum si_sm_result (*event)(struct si_sm_data *smi, long time);
+
+	/* Attempt to detect an SMI.  Returns 0 on success or nonzero
+           on failure. */
+	int (*detect)(struct si_sm_data *smi);
+
+	/* The interface is shutting down, so clean it up. */
+	void (*cleanup)(struct si_sm_data *smi);
+
+	/* Return the size of the SMI structure in bytes. */
+	int (*size)(void);
+};
+
+/* Current state machines that we can use. */
+extern struct si_sm_handlers kcs_smi_handlers;
+extern struct si_sm_handlers smic_smi_handlers;
+extern struct si_sm_handlers bt_smi_handlers;
+
diff --git a/drivers/char/ipmi/ipmi_smic_sm.c b/drivers/char/ipmi/ipmi_smic_sm.c
new file mode 100644
index 0000000..ae18747
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_smic_sm.c
@@ -0,0 +1,599 @@
+/*
+ * ipmi_smic_sm.c
+ *
+ * The state-machine driver for an IPMI SMIC driver
+ *
+ * It started as a copy of Corey Minyard's driver for the KSC interface
+ * and the kernel patch "mmcdev-patch-245" by HP
+ *
+ * modified by:	Hannes Schulz <schulz@schwaar.com>
+ *		ipmi@schwaar.com
+ *
+ *
+ * Corey Minyard's driver for the KSC interface has the following
+ * copyright notice:
+ *   Copyright 2002 MontaVista Software Inc.
+ *
+ * the kernel patch "mmcdev-patch-245" by HP has the following
+ * copyright notice:
+ * (c) Copyright 2001 Grant Grundler (c) Copyright
+ * 2001 Hewlett-Packard Company
+ *
+ *
+ *  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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.  */
+
+#include <linux/kernel.h> /* For printk. */
+#include <linux/string.h>
+#include <linux/ipmi_msgdefs.h>		/* for completion codes */
+#include "ipmi_si_sm.h"
+
+#define IPMI_SMIC_VERSION "v33"
+
+/* smic_debug is a bit-field
+ *	SMIC_DEBUG_ENABLE -	turned on for now
+ *	SMIC_DEBUG_MSG -	commands and their responses
+ *	SMIC_DEBUG_STATES -	state machine
+*/
+#define SMIC_DEBUG_STATES	4
+#define SMIC_DEBUG_MSG		2
+#define	SMIC_DEBUG_ENABLE	1
+
+static int smic_debug = 1;
+
+enum smic_states {
+	SMIC_IDLE,
+	SMIC_START_OP,
+	SMIC_OP_OK,
+	SMIC_WRITE_START,
+	SMIC_WRITE_NEXT,
+	SMIC_WRITE_END,
+	SMIC_WRITE2READ,
+	SMIC_READ_START,
+	SMIC_READ_NEXT,
+	SMIC_READ_END,
+	SMIC_HOSED
+};
+
+#define MAX_SMIC_READ_SIZE 80
+#define MAX_SMIC_WRITE_SIZE 80
+#define SMIC_MAX_ERROR_RETRIES 3
+
+/* Timeouts in microseconds. */
+#define SMIC_RETRY_TIMEOUT 100000
+
+/* SMIC Flags Register Bits */
+#define SMIC_RX_DATA_READY	0x80
+#define SMIC_TX_DATA_READY	0x40
+#define SMIC_SMI		0x10
+#define SMIC_EVM_DATA_AVAIL	0x08
+#define SMIC_SMS_DATA_AVAIL	0x04
+#define SMIC_FLAG_BSY		0x01
+
+/* SMIC Error Codes */
+#define	EC_NO_ERROR		0x00
+#define	EC_ABORTED		0x01
+#define	EC_ILLEGAL_CONTROL	0x02
+#define	EC_NO_RESPONSE		0x03
+#define	EC_ILLEGAL_COMMAND	0x04
+#define	EC_BUFFER_FULL		0x05
+
+struct si_sm_data
+{
+	enum smic_states state;
+	struct si_sm_io *io;
+        unsigned char	 write_data[MAX_SMIC_WRITE_SIZE];
+        int		 write_pos;
+        int		 write_count;
+        int		 orig_write_count;
+        unsigned char	 read_data[MAX_SMIC_READ_SIZE];
+        int		 read_pos;
+        int		 truncated;
+        unsigned int	 error_retries;
+        long		 smic_timeout;
+};
+
+static unsigned int init_smic_data (struct si_sm_data *smic,
+				    struct si_sm_io *io)
+{
+	smic->state = SMIC_IDLE;
+	smic->io = io;
+	smic->write_pos = 0;
+	smic->write_count = 0;
+	smic->orig_write_count = 0;
+	smic->read_pos = 0;
+	smic->error_retries = 0;
+	smic->truncated = 0;
+	smic->smic_timeout = SMIC_RETRY_TIMEOUT;
+
+	/* We use 3 bytes of I/O. */
+	return 3;
+}
+
+static int start_smic_transaction(struct si_sm_data *smic,
+				  unsigned char *data, unsigned int size)
+{
+	unsigned int i;
+
+	if ((size < 2) || (size > MAX_SMIC_WRITE_SIZE)) {
+		return -1;
+	}
+	if ((smic->state != SMIC_IDLE) && (smic->state != SMIC_HOSED)) {
+		return -2;
+	}
+	if (smic_debug & SMIC_DEBUG_MSG) {
+		printk(KERN_INFO "start_smic_transaction -");
+		for (i = 0; i < size; i ++) {
+			printk (" %02x", (unsigned char) (data [i]));
+		}
+		printk ("\n");
+	}
+	smic->error_retries = 0;
+	memcpy(smic->write_data, data, size);
+	smic->write_count = size;
+	smic->orig_write_count = size;
+	smic->write_pos = 0;
+	smic->read_pos = 0;
+	smic->state = SMIC_START_OP;
+	smic->smic_timeout = SMIC_RETRY_TIMEOUT;
+	return 0;
+}
+
+static int smic_get_result(struct si_sm_data *smic,
+			   unsigned char *data, unsigned int length)
+{
+	int i;
+
+	if (smic_debug & SMIC_DEBUG_MSG) {
+		printk (KERN_INFO "smic_get result -");
+		for (i = 0; i < smic->read_pos; i ++) {
+			printk (" %02x", (smic->read_data [i]));
+		}
+		printk ("\n");
+	}
+	if (length < smic->read_pos) {
+		smic->read_pos = length;
+		smic->truncated = 1;
+	}
+	memcpy(data, smic->read_data, smic->read_pos);
+
+	if ((length >= 3) && (smic->read_pos < 3)) {
+		data[2] = IPMI_ERR_UNSPECIFIED;
+		smic->read_pos = 3;
+	}
+	if (smic->truncated) {
+		data[2] = IPMI_ERR_MSG_TRUNCATED;
+		smic->truncated = 0;
+	}
+	return smic->read_pos;
+}
+
+static inline unsigned char read_smic_flags(struct si_sm_data *smic)
+{
+	return smic->io->inputb(smic->io, 2);
+}
+
+static inline unsigned char read_smic_status(struct si_sm_data *smic)
+{
+	return smic->io->inputb(smic->io, 1);
+}
+
+static inline unsigned char read_smic_data(struct si_sm_data *smic)
+{
+	return smic->io->inputb(smic->io, 0);
+}
+
+static inline void write_smic_flags(struct si_sm_data *smic,
+				    unsigned char   flags)
+{
+	smic->io->outputb(smic->io, 2, flags);
+}
+
+static inline void write_smic_control(struct si_sm_data *smic,
+				      unsigned char   control)
+{
+	smic->io->outputb(smic->io, 1, control);
+}
+
+static inline void write_si_sm_data (struct si_sm_data *smic,
+				   unsigned char   data)
+{
+	smic->io->outputb(smic->io, 0, data);
+}
+
+static inline void start_error_recovery(struct si_sm_data *smic, char *reason)
+{
+	(smic->error_retries)++;
+	if (smic->error_retries > SMIC_MAX_ERROR_RETRIES) {
+		if (smic_debug & SMIC_DEBUG_ENABLE) {
+			printk(KERN_WARNING
+			       "ipmi_smic_drv: smic hosed: %s\n", reason);
+		}
+		smic->state = SMIC_HOSED;
+	} else {
+		smic->write_count = smic->orig_write_count;
+		smic->write_pos = 0;
+		smic->read_pos = 0;
+		smic->state = SMIC_START_OP;
+		smic->smic_timeout = SMIC_RETRY_TIMEOUT;
+	}
+}
+
+static inline void write_next_byte(struct si_sm_data *smic)
+{
+	write_si_sm_data(smic, smic->write_data[smic->write_pos]);
+	(smic->write_pos)++;
+	(smic->write_count)--;
+}
+
+static inline void read_next_byte (struct si_sm_data *smic)
+{
+	if (smic->read_pos >= MAX_SMIC_READ_SIZE) {
+		read_smic_data (smic);
+		smic->truncated = 1;
+	} else {
+		smic->read_data[smic->read_pos] = read_smic_data(smic);
+		(smic->read_pos)++;
+	}
+}
+
+/*  SMIC Control/Status Code Components */
+#define	SMIC_GET_STATUS		0x00	/* Control form's name */
+#define	SMIC_READY		0x00	/* Status  form's name */
+#define	SMIC_WR_START		0x01	/* Unified Control/Status names... */
+#define	SMIC_WR_NEXT		0x02
+#define	SMIC_WR_END		0x03
+#define	SMIC_RD_START		0x04
+#define	SMIC_RD_NEXT		0x05
+#define	SMIC_RD_END		0x06
+#define	SMIC_CODE_MASK		0x0f
+
+#define	SMIC_CONTROL		0x00
+#define	SMIC_STATUS		0x80
+#define	SMIC_CS_MASK		0x80
+
+#define	SMIC_SMS		0x40
+#define	SMIC_SMM		0x60
+#define	SMIC_STREAM_MASK	0x60
+
+/*  SMIC Control Codes */
+#define	SMIC_CC_SMS_GET_STATUS	(SMIC_CONTROL|SMIC_SMS|SMIC_GET_STATUS)
+#define	SMIC_CC_SMS_WR_START	(SMIC_CONTROL|SMIC_SMS|SMIC_WR_START)
+#define	SMIC_CC_SMS_WR_NEXT	(SMIC_CONTROL|SMIC_SMS|SMIC_WR_NEXT)
+#define	SMIC_CC_SMS_WR_END	(SMIC_CONTROL|SMIC_SMS|SMIC_WR_END)
+#define	SMIC_CC_SMS_RD_START	(SMIC_CONTROL|SMIC_SMS|SMIC_RD_START)
+#define	SMIC_CC_SMS_RD_NEXT	(SMIC_CONTROL|SMIC_SMS|SMIC_RD_NEXT)
+#define	SMIC_CC_SMS_RD_END	(SMIC_CONTROL|SMIC_SMS|SMIC_RD_END)
+
+#define	SMIC_CC_SMM_GET_STATUS	(SMIC_CONTROL|SMIC_SMM|SMIC_GET_STATUS)
+#define	SMIC_CC_SMM_WR_START	(SMIC_CONTROL|SMIC_SMM|SMIC_WR_START)
+#define	SMIC_CC_SMM_WR_NEXT	(SMIC_CONTROL|SMIC_SMM|SMIC_WR_NEXT)
+#define	SMIC_CC_SMM_WR_END	(SMIC_CONTROL|SMIC_SMM|SMIC_WR_END)
+#define	SMIC_CC_SMM_RD_START	(SMIC_CONTROL|SMIC_SMM|SMIC_RD_START)
+#define	SMIC_CC_SMM_RD_NEXT	(SMIC_CONTROL|SMIC_SMM|SMIC_RD_NEXT)
+#define	SMIC_CC_SMM_RD_END	(SMIC_CONTROL|SMIC_SMM|SMIC_RD_END)
+
+/*  SMIC Status Codes */
+#define	SMIC_SC_SMS_READY	(SMIC_STATUS|SMIC_SMS|SMIC_READY)
+#define	SMIC_SC_SMS_WR_START	(SMIC_STATUS|SMIC_SMS|SMIC_WR_START)
+#define	SMIC_SC_SMS_WR_NEXT	(SMIC_STATUS|SMIC_SMS|SMIC_WR_NEXT)
+#define	SMIC_SC_SMS_WR_END	(SMIC_STATUS|SMIC_SMS|SMIC_WR_END)
+#define	SMIC_SC_SMS_RD_START	(SMIC_STATUS|SMIC_SMS|SMIC_RD_START)
+#define	SMIC_SC_SMS_RD_NEXT	(SMIC_STATUS|SMIC_SMS|SMIC_RD_NEXT)
+#define	SMIC_SC_SMS_RD_END	(SMIC_STATUS|SMIC_SMS|SMIC_RD_END)
+
+#define	SMIC_SC_SMM_READY	(SMIC_STATUS|SMIC_SMM|SMIC_READY)
+#define	SMIC_SC_SMM_WR_START	(SMIC_STATUS|SMIC_SMM|SMIC_WR_START)
+#define	SMIC_SC_SMM_WR_NEXT	(SMIC_STATUS|SMIC_SMM|SMIC_WR_NEXT)
+#define	SMIC_SC_SMM_WR_END	(SMIC_STATUS|SMIC_SMM|SMIC_WR_END)
+#define	SMIC_SC_SMM_RD_START	(SMIC_STATUS|SMIC_SMM|SMIC_RD_START)
+#define	SMIC_SC_SMM_RD_NEXT	(SMIC_STATUS|SMIC_SMM|SMIC_RD_NEXT)
+#define	SMIC_SC_SMM_RD_END	(SMIC_STATUS|SMIC_SMM|SMIC_RD_END)
+
+/* these are the control/status codes we actually use
+	SMIC_CC_SMS_GET_STATUS	0x40
+	SMIC_CC_SMS_WR_START	0x41
+	SMIC_CC_SMS_WR_NEXT	0x42
+	SMIC_CC_SMS_WR_END	0x43
+	SMIC_CC_SMS_RD_START	0x44
+	SMIC_CC_SMS_RD_NEXT	0x45
+	SMIC_CC_SMS_RD_END	0x46
+
+	SMIC_SC_SMS_READY	0xC0
+	SMIC_SC_SMS_WR_START	0xC1
+	SMIC_SC_SMS_WR_NEXT	0xC2
+	SMIC_SC_SMS_WR_END	0xC3
+	SMIC_SC_SMS_RD_START	0xC4
+	SMIC_SC_SMS_RD_NEXT	0xC5
+	SMIC_SC_SMS_RD_END	0xC6
+*/
+
+static enum si_sm_result smic_event (struct si_sm_data *smic, long time)
+{
+	unsigned char status;
+	unsigned char flags;
+	unsigned char data;
+
+	if (smic->state == SMIC_HOSED) {
+		init_smic_data(smic, smic->io);
+		return SI_SM_HOSED;
+	}
+	if (smic->state != SMIC_IDLE) {
+		if (smic_debug & SMIC_DEBUG_STATES) {
+			printk(KERN_INFO
+			       "smic_event - smic->smic_timeout = %ld,"
+			       " time = %ld\n",
+			       smic->smic_timeout, time);
+		}
+/* FIXME: smic_event is sometimes called with time > SMIC_RETRY_TIMEOUT */
+		if (time < SMIC_RETRY_TIMEOUT) {
+			smic->smic_timeout -= time;
+			if (smic->smic_timeout < 0) {
+				start_error_recovery(smic, "smic timed out.");
+				return SI_SM_CALL_WITH_DELAY;
+			}
+		}
+	}
+	flags = read_smic_flags(smic);
+	if (flags & SMIC_FLAG_BSY)
+		return SI_SM_CALL_WITH_DELAY;
+
+	status = read_smic_status (smic);
+	if (smic_debug & SMIC_DEBUG_STATES)
+		printk(KERN_INFO
+		       "smic_event - state = %d, flags = 0x%02x,"
+		       " status = 0x%02x\n",
+		       smic->state, flags, status);
+
+	switch (smic->state) {
+	case SMIC_IDLE:
+		/* in IDLE we check for available messages */
+		if (flags & (SMIC_SMI |
+			     SMIC_EVM_DATA_AVAIL | SMIC_SMS_DATA_AVAIL))
+		{
+			return SI_SM_ATTN;
+		}
+		return SI_SM_IDLE;
+
+	case SMIC_START_OP:
+		/* sanity check whether smic is really idle */
+		write_smic_control(smic, SMIC_CC_SMS_GET_STATUS);
+		write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+		smic->state = SMIC_OP_OK;
+		break;
+
+	case SMIC_OP_OK:
+		if (status != SMIC_SC_SMS_READY) {
+				/* this should not happen */
+			start_error_recovery(smic,
+					     "state = SMIC_OP_OK,"
+					     " status != SMIC_SC_SMS_READY");
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		/* OK so far; smic is idle let us start ... */
+		write_smic_control(smic, SMIC_CC_SMS_WR_START);
+		write_next_byte(smic);
+		write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+		smic->state = SMIC_WRITE_START;
+		break;
+
+	case SMIC_WRITE_START:
+		if (status != SMIC_SC_SMS_WR_START) {
+			start_error_recovery(smic,
+					     "state = SMIC_WRITE_START, "
+					     "status != SMIC_SC_SMS_WR_START");
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		/* we must not issue WR_(NEXT|END) unless
+                   TX_DATA_READY is set */
+		if (flags & SMIC_TX_DATA_READY) {
+			if (smic->write_count == 1) {
+				/* last byte */
+				write_smic_control(smic, SMIC_CC_SMS_WR_END);
+				smic->state = SMIC_WRITE_END;
+			} else {
+				write_smic_control(smic, SMIC_CC_SMS_WR_NEXT);
+				smic->state = SMIC_WRITE_NEXT;
+			}
+			write_next_byte(smic);
+			write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+		}
+		else {
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		break;
+
+	case SMIC_WRITE_NEXT:
+		if (status != SMIC_SC_SMS_WR_NEXT) {
+			start_error_recovery(smic,
+					     "state = SMIC_WRITE_NEXT, "
+					     "status != SMIC_SC_SMS_WR_NEXT");
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		/* this is the same code as in SMIC_WRITE_START */
+		if (flags & SMIC_TX_DATA_READY) {
+			if (smic->write_count == 1) {
+				write_smic_control(smic, SMIC_CC_SMS_WR_END);
+				smic->state = SMIC_WRITE_END;
+			}
+			else {
+				write_smic_control(smic, SMIC_CC_SMS_WR_NEXT);
+				smic->state = SMIC_WRITE_NEXT;
+			}
+			write_next_byte(smic);
+			write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+		}
+		else {
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		break;
+
+	case SMIC_WRITE_END:
+		if (status != SMIC_SC_SMS_WR_END) {
+			start_error_recovery (smic,
+					      "state = SMIC_WRITE_END, "
+					      "status != SMIC_SC_SMS_WR_END");
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		/* data register holds an error code */
+		data = read_smic_data(smic);
+		if (data != 0) {
+			if (smic_debug & SMIC_DEBUG_ENABLE) {
+				printk(KERN_INFO
+				       "SMIC_WRITE_END: data = %02x\n", data);
+			}
+			start_error_recovery(smic,
+					     "state = SMIC_WRITE_END, "
+					     "data != SUCCESS");
+			return SI_SM_CALL_WITH_DELAY;
+		} else {
+			smic->state = SMIC_WRITE2READ;
+		}
+		break;
+
+	case SMIC_WRITE2READ:
+		/* we must wait for RX_DATA_READY to be set before we
+                   can continue */
+		if (flags & SMIC_RX_DATA_READY) {
+			write_smic_control(smic, SMIC_CC_SMS_RD_START);
+			write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+			smic->state = SMIC_READ_START;
+		} else {
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		break;
+
+	case SMIC_READ_START:
+		if (status != SMIC_SC_SMS_RD_START) {
+			start_error_recovery(smic,
+					     "state = SMIC_READ_START, "
+					     "status != SMIC_SC_SMS_RD_START");
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		if (flags & SMIC_RX_DATA_READY) {
+			read_next_byte(smic);
+			write_smic_control(smic, SMIC_CC_SMS_RD_NEXT);
+			write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+			smic->state = SMIC_READ_NEXT;
+		} else {
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		break;
+
+	case SMIC_READ_NEXT:
+		switch (status) {
+		/* smic tells us that this is the last byte to be read
+                   --> clean up */
+		case SMIC_SC_SMS_RD_END:
+			read_next_byte(smic);
+			write_smic_control(smic, SMIC_CC_SMS_RD_END);
+			write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+			smic->state = SMIC_READ_END;
+			break;
+		case SMIC_SC_SMS_RD_NEXT:
+			if (flags & SMIC_RX_DATA_READY) {
+				read_next_byte(smic);
+				write_smic_control(smic, SMIC_CC_SMS_RD_NEXT);
+				write_smic_flags(smic, flags | SMIC_FLAG_BSY);
+				smic->state = SMIC_READ_NEXT;
+			} else {
+				return SI_SM_CALL_WITH_DELAY;
+			}
+			break;
+		default:
+			start_error_recovery(
+				smic,
+				"state = SMIC_READ_NEXT, "
+				"status != SMIC_SC_SMS_RD_(NEXT|END)");
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		break;
+
+	case SMIC_READ_END:
+		if (status != SMIC_SC_SMS_READY) {
+			start_error_recovery(smic,
+					     "state = SMIC_READ_END, "
+					     "status != SMIC_SC_SMS_READY");
+			return SI_SM_CALL_WITH_DELAY;
+		}
+		data = read_smic_data(smic);
+		/* data register holds an error code */
+		if (data != 0) {
+			if (smic_debug & SMIC_DEBUG_ENABLE) {
+				printk(KERN_INFO
+				       "SMIC_READ_END: data = %02x\n", data);
+			}
+			start_error_recovery(smic,
+					     "state = SMIC_READ_END, "
+					     "data != SUCCESS");
+			return SI_SM_CALL_WITH_DELAY;
+		} else {
+			smic->state = SMIC_IDLE;
+			return SI_SM_TRANSACTION_COMPLETE;
+		}
+
+	case SMIC_HOSED:
+		init_smic_data(smic, smic->io);
+		return SI_SM_HOSED;
+
+	default:
+		if (smic_debug & SMIC_DEBUG_ENABLE) {
+			printk(KERN_WARNING "smic->state = %d\n", smic->state);
+			start_error_recovery(smic, "state = UNKNOWN");
+			return SI_SM_CALL_WITH_DELAY;
+		}
+	}
+	smic->smic_timeout = SMIC_RETRY_TIMEOUT;
+	return SI_SM_CALL_WITHOUT_DELAY;
+}
+
+static int smic_detect(struct si_sm_data *smic)
+{
+	/* It's impossible for the SMIC fnags register to be all 1's,
+	   (assuming a properly functioning, self-initialized BMC)
+	   but that's what you get from reading a bogus address, so we
+	   test that first. */
+	if (read_smic_flags(smic) == 0xff)
+		return 1;
+
+	return 0;
+}
+
+static void smic_cleanup(struct si_sm_data *kcs)
+{
+}
+
+static int smic_size(void)
+{
+	return sizeof(struct si_sm_data);
+}
+
+struct si_sm_handlers smic_smi_handlers =
+{
+	.version           = IPMI_SMIC_VERSION,
+	.init_data         = init_smic_data,
+	.start_transaction = start_smic_transaction,
+	.get_result        = smic_get_result,
+	.event             = smic_event,
+	.detect            = smic_detect,
+	.cleanup           = smic_cleanup,
+	.size              = smic_size,
+};
diff --git a/drivers/char/ipmi/ipmi_watchdog.c b/drivers/char/ipmi/ipmi_watchdog.c
new file mode 100644
index 0000000..fd70938
--- /dev/null
+++ b/drivers/char/ipmi/ipmi_watchdog.c
@@ -0,0 +1,1068 @@
+/*
+ * ipmi_watchdog.c
+ *
+ * A watchdog timer based upon the IPMI interface.
+ *
+ * Author: MontaVista Software, Inc.
+ *         Corey Minyard <minyard@mvista.com>
+ *         source@mvista.com
+ *
+ * Copyright 2002 MontaVista Software 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.
+ *
+ *
+ *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ *  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/ipmi.h>
+#include <linux/ipmi_smi.h>
+#include <linux/watchdog.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/rwsem.h>
+#include <linux/errno.h>
+#include <asm/uaccess.h>
+#include <linux/notifier.h>
+#include <linux/nmi.h>
+#include <linux/reboot.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#ifdef CONFIG_X86_LOCAL_APIC
+#include <asm/apic.h>
+#endif
+
+#define	PFX "IPMI Watchdog: "
+
+#define IPMI_WATCHDOG_VERSION "v33"
+
+/*
+ * The IPMI command/response information for the watchdog timer.
+ */
+
+/* values for byte 1 of the set command, byte 2 of the get response. */
+#define WDOG_DONT_LOG		(1 << 7)
+#define WDOG_DONT_STOP_ON_SET	(1 << 6)
+#define WDOG_SET_TIMER_USE(byte, use) \
+	byte = ((byte) & 0xf8) | ((use) & 0x7)
+#define WDOG_GET_TIMER_USE(byte) ((byte) & 0x7)
+#define WDOG_TIMER_USE_BIOS_FRB2	1
+#define WDOG_TIMER_USE_BIOS_POST	2
+#define WDOG_TIMER_USE_OS_LOAD		3
+#define WDOG_TIMER_USE_SMS_OS		4
+#define WDOG_TIMER_USE_OEM		5
+
+/* values for byte 2 of the set command, byte 3 of the get response. */
+#define WDOG_SET_PRETIMEOUT_ACT(byte, use) \
+	byte = ((byte) & 0x8f) | (((use) & 0x7) << 4)
+#define WDOG_GET_PRETIMEOUT_ACT(byte) (((byte) >> 4) & 0x7)
+#define WDOG_PRETIMEOUT_NONE		0
+#define WDOG_PRETIMEOUT_SMI		1
+#define WDOG_PRETIMEOUT_NMI		2
+#define WDOG_PRETIMEOUT_MSG_INT		3
+
+/* Operations that can be performed on a pretimout. */
+#define WDOG_PREOP_NONE		0
+#define WDOG_PREOP_PANIC	1
+#define WDOG_PREOP_GIVE_DATA	2 /* Cause data to be available to
+                                     read.  Doesn't work in NMI
+                                     mode. */
+
+/* Actions to perform on a full timeout. */
+#define WDOG_SET_TIMEOUT_ACT(byte, use) \
+	byte = ((byte) & 0xf8) | ((use) & 0x7)
+#define WDOG_GET_TIMEOUT_ACT(byte) ((byte) & 0x7)
+#define WDOG_TIMEOUT_NONE		0
+#define WDOG_TIMEOUT_RESET		1
+#define WDOG_TIMEOUT_POWER_DOWN		2
+#define WDOG_TIMEOUT_POWER_CYCLE	3
+
+/* Byte 3 of the get command, byte 4 of the get response is the
+   pre-timeout in seconds. */
+
+/* Bits for setting byte 4 of the set command, byte 5 of the get response. */
+#define WDOG_EXPIRE_CLEAR_BIOS_FRB2	(1 << 1)
+#define WDOG_EXPIRE_CLEAR_BIOS_POST	(1 << 2)
+#define WDOG_EXPIRE_CLEAR_OS_LOAD	(1 << 3)
+#define WDOG_EXPIRE_CLEAR_SMS_OS	(1 << 4)
+#define WDOG_EXPIRE_CLEAR_OEM		(1 << 5)
+
+/* Setting/getting the watchdog timer value.  This is for bytes 5 and
+   6 (the timeout time) of the set command, and bytes 6 and 7 (the
+   timeout time) and 8 and 9 (the current countdown value) of the
+   response.  The timeout value is given in seconds (in the command it
+   is 100ms intervals). */
+#define WDOG_SET_TIMEOUT(byte1, byte2, val) \
+	(byte1) = (((val) * 10) & 0xff), (byte2) = (((val) * 10) >> 8)
+#define WDOG_GET_TIMEOUT(byte1, byte2) \
+	(((byte1) | ((byte2) << 8)) / 10)
+
+#define IPMI_WDOG_RESET_TIMER		0x22
+#define IPMI_WDOG_SET_TIMER		0x24
+#define IPMI_WDOG_GET_TIMER		0x25
+
+/* These are here until the real ones get into the watchdog.h interface. */
+#ifndef WDIOC_GETTIMEOUT
+#define	WDIOC_GETTIMEOUT        _IOW(WATCHDOG_IOCTL_BASE, 20, int)
+#endif
+#ifndef WDIOC_SET_PRETIMEOUT
+#define	WDIOC_SET_PRETIMEOUT     _IOW(WATCHDOG_IOCTL_BASE, 21, int)
+#endif
+#ifndef WDIOC_GET_PRETIMEOUT
+#define	WDIOC_GET_PRETIMEOUT     _IOW(WATCHDOG_IOCTL_BASE, 22, int)
+#endif
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout;
+#endif
+
+static ipmi_user_t watchdog_user = NULL;
+
+/* Default the timeout to 10 seconds. */
+static int timeout = 10;
+
+/* The pre-timeout is disabled by default. */
+static int pretimeout = 0;
+
+/* Default action is to reset the board on a timeout. */
+static unsigned char action_val = WDOG_TIMEOUT_RESET;
+
+static char action[16] = "reset";
+
+static unsigned char preaction_val = WDOG_PRETIMEOUT_NONE;
+
+static char preaction[16] = "pre_none";
+
+static unsigned char preop_val = WDOG_PREOP_NONE;
+
+static char preop[16] = "preop_none";
+static DEFINE_SPINLOCK(ipmi_read_lock);
+static char data_to_read = 0;
+static DECLARE_WAIT_QUEUE_HEAD(read_q);
+static struct fasync_struct *fasync_q = NULL;
+static char pretimeout_since_last_heartbeat = 0;
+static char expect_close;
+
+/* If true, the driver will start running as soon as it is configured
+   and ready. */
+static int start_now = 0;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Timeout value in seconds.");
+module_param(pretimeout, int, 0);
+MODULE_PARM_DESC(pretimeout, "Pretimeout value in seconds.");
+module_param_string(action, action, sizeof(action), 0);
+MODULE_PARM_DESC(action, "Timeout action. One of: "
+		 "reset, none, power_cycle, power_off.");
+module_param_string(preaction, preaction, sizeof(preaction), 0);
+MODULE_PARM_DESC(preaction, "Pretimeout action.  One of: "
+		 "pre_none, pre_smi, pre_nmi, pre_int.");
+module_param_string(preop, preop, sizeof(preop), 0);
+MODULE_PARM_DESC(preop, "Pretimeout driver operation.  One of: "
+		 "preop_none, preop_panic, preop_give_data.");
+module_param(start_now, int, 0);
+MODULE_PARM_DESC(start_now, "Set to 1 to start the watchdog as"
+		 "soon as the driver is loaded.");
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/* Default state of the timer. */
+static unsigned char ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+
+/* If shutting down via IPMI, we ignore the heartbeat. */
+static int ipmi_ignore_heartbeat = 0;
+
+/* Is someone using the watchdog?  Only one user is allowed. */
+static unsigned long ipmi_wdog_open = 0;
+
+/* If set to 1, the heartbeat command will set the state to reset and
+   start the timer.  The timer doesn't normally run when the driver is
+   first opened until the heartbeat is set the first time, this
+   variable is used to accomplish this. */
+static int ipmi_start_timer_on_heartbeat = 0;
+
+/* IPMI version of the BMC. */
+static unsigned char ipmi_version_major;
+static unsigned char ipmi_version_minor;
+
+
+static int ipmi_heartbeat(void);
+static void panic_halt_ipmi_heartbeat(void);
+
+
+/* We use a semaphore to make sure that only one thing can send a set
+   timeout at one time, because we only have one copy of the data.
+   The semaphore is claimed when the set_timeout is sent and freed
+   when both messages are free. */
+static atomic_t set_timeout_tofree = ATOMIC_INIT(0);
+static DECLARE_MUTEX(set_timeout_lock);
+static void set_timeout_free_smi(struct ipmi_smi_msg *msg)
+{
+    if (atomic_dec_and_test(&set_timeout_tofree))
+	    up(&set_timeout_lock);
+}
+static void set_timeout_free_recv(struct ipmi_recv_msg *msg)
+{
+    if (atomic_dec_and_test(&set_timeout_tofree))
+	    up(&set_timeout_lock);
+}
+static struct ipmi_smi_msg set_timeout_smi_msg =
+{
+	.done = set_timeout_free_smi
+};
+static struct ipmi_recv_msg set_timeout_recv_msg =
+{
+	.done = set_timeout_free_recv
+};
+ 
+static int i_ipmi_set_timeout(struct ipmi_smi_msg  *smi_msg,
+			      struct ipmi_recv_msg *recv_msg,
+			      int                  *send_heartbeat_now)
+{
+	struct kernel_ipmi_msg            msg;
+	unsigned char                     data[6];
+	int                               rv;
+	struct ipmi_system_interface_addr addr;
+	int                               hbnow = 0;
+
+
+	data[0] = 0;
+	WDOG_SET_TIMER_USE(data[0], WDOG_TIMER_USE_SMS_OS);
+
+	if ((ipmi_version_major > 1)
+	    || ((ipmi_version_major == 1) && (ipmi_version_minor >= 5)))
+	{
+		/* This is an IPMI 1.5-only feature. */
+		data[0] |= WDOG_DONT_STOP_ON_SET;
+	} else if (ipmi_watchdog_state != WDOG_TIMEOUT_NONE) {
+		/* In ipmi 1.0, setting the timer stops the watchdog, we
+		   need to start it back up again. */
+		hbnow = 1;
+	}
+
+	data[1] = 0;
+	WDOG_SET_TIMEOUT_ACT(data[1], ipmi_watchdog_state);
+	if (pretimeout > 0) {
+	    WDOG_SET_PRETIMEOUT_ACT(data[1], preaction_val);
+	    data[2] = pretimeout;
+	} else {
+	    WDOG_SET_PRETIMEOUT_ACT(data[1], WDOG_PRETIMEOUT_NONE);
+	    data[2] = 0; /* No pretimeout. */
+	}
+	data[3] = 0;
+	WDOG_SET_TIMEOUT(data[4], data[5], timeout);
+
+	addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	addr.channel = IPMI_BMC_CHANNEL;
+	addr.lun = 0;
+
+	msg.netfn = 0x06;
+	msg.cmd = IPMI_WDOG_SET_TIMER;
+	msg.data = data;
+	msg.data_len = sizeof(data);
+	rv = ipmi_request_supply_msgs(watchdog_user,
+				      (struct ipmi_addr *) &addr,
+				      0,
+				      &msg,
+				      NULL,
+				      smi_msg,
+				      recv_msg,
+				      1);
+	if (rv) {
+		printk(KERN_WARNING PFX "set timeout error: %d\n",
+		       rv);
+	}
+
+	if (send_heartbeat_now)
+	    *send_heartbeat_now = hbnow;
+
+	return rv;
+}
+
+/* Parameters to ipmi_set_timeout */
+#define IPMI_SET_TIMEOUT_NO_HB			0
+#define IPMI_SET_TIMEOUT_HB_IF_NECESSARY	1
+#define IPMI_SET_TIMEOUT_FORCE_HB		2
+
+static int ipmi_set_timeout(int do_heartbeat)
+{
+	int send_heartbeat_now;
+	int rv;
+
+
+	/* We can only send one of these at a time. */
+	down(&set_timeout_lock);
+
+	atomic_set(&set_timeout_tofree, 2);
+
+	rv = i_ipmi_set_timeout(&set_timeout_smi_msg,
+				&set_timeout_recv_msg,
+				&send_heartbeat_now);
+	if (rv) {
+		up(&set_timeout_lock);
+	} else {
+		if ((do_heartbeat == IPMI_SET_TIMEOUT_FORCE_HB)
+		    || ((send_heartbeat_now)
+			&& (do_heartbeat == IPMI_SET_TIMEOUT_HB_IF_NECESSARY)))
+		{
+			rv = ipmi_heartbeat();
+		}
+	}
+
+	return rv;
+}
+
+static void dummy_smi_free(struct ipmi_smi_msg *msg)
+{
+}
+static void dummy_recv_free(struct ipmi_recv_msg *msg)
+{
+}
+static struct ipmi_smi_msg panic_halt_smi_msg =
+{
+	.done = dummy_smi_free
+};
+static struct ipmi_recv_msg panic_halt_recv_msg =
+{
+	.done = dummy_recv_free
+};
+
+/* Special call, doesn't claim any locks.  This is only to be called
+   at panic or halt time, in run-to-completion mode, when the caller
+   is the only CPU and the only thing that will be going is these IPMI
+   calls. */
+static void panic_halt_ipmi_set_timeout(void)
+{
+	int send_heartbeat_now;
+	int rv;
+
+	rv = i_ipmi_set_timeout(&panic_halt_smi_msg,
+				&panic_halt_recv_msg,
+				&send_heartbeat_now);
+	if (!rv) {
+		if (send_heartbeat_now)
+			panic_halt_ipmi_heartbeat();
+	}
+}
+
+/* We use a semaphore to make sure that only one thing can send a
+   heartbeat at one time, because we only have one copy of the data.
+   The semaphore is claimed when the set_timeout is sent and freed
+   when both messages are free. */
+static atomic_t heartbeat_tofree = ATOMIC_INIT(0);
+static DECLARE_MUTEX(heartbeat_lock);
+static DECLARE_MUTEX_LOCKED(heartbeat_wait_lock);
+static void heartbeat_free_smi(struct ipmi_smi_msg *msg)
+{
+    if (atomic_dec_and_test(&heartbeat_tofree))
+	    up(&heartbeat_wait_lock);
+}
+static void heartbeat_free_recv(struct ipmi_recv_msg *msg)
+{
+    if (atomic_dec_and_test(&heartbeat_tofree))
+	    up(&heartbeat_wait_lock);
+}
+static struct ipmi_smi_msg heartbeat_smi_msg =
+{
+	.done = heartbeat_free_smi
+};
+static struct ipmi_recv_msg heartbeat_recv_msg =
+{
+	.done = heartbeat_free_recv
+};
+ 
+static struct ipmi_smi_msg panic_halt_heartbeat_smi_msg =
+{
+	.done = dummy_smi_free
+};
+static struct ipmi_recv_msg panic_halt_heartbeat_recv_msg =
+{
+	.done = dummy_recv_free
+};
+ 
+static int ipmi_heartbeat(void)
+{
+	struct kernel_ipmi_msg            msg;
+	int                               rv;
+	struct ipmi_system_interface_addr addr;
+
+	if (ipmi_ignore_heartbeat) {
+		return 0;
+	}
+
+	if (ipmi_start_timer_on_heartbeat) {
+		ipmi_start_timer_on_heartbeat = 0;
+		ipmi_watchdog_state = action_val;
+		return ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+	} else if (pretimeout_since_last_heartbeat) {
+		/* A pretimeout occurred, make sure we set the timeout.
+		   We don't want to set the action, though, we want to
+		   leave that alone (thus it can't be combined with the
+		   above operation. */
+		pretimeout_since_last_heartbeat = 0;
+		return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+	}
+
+	down(&heartbeat_lock);
+
+	atomic_set(&heartbeat_tofree, 2);
+
+	/* Don't reset the timer if we have the timer turned off, that
+           re-enables the watchdog. */
+	if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE) {
+		up(&heartbeat_lock);
+		return 0;
+	}
+
+	addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	addr.channel = IPMI_BMC_CHANNEL;
+	addr.lun = 0;
+
+	msg.netfn = 0x06;
+	msg.cmd = IPMI_WDOG_RESET_TIMER;
+	msg.data = NULL;
+	msg.data_len = 0;
+	rv = ipmi_request_supply_msgs(watchdog_user,
+				      (struct ipmi_addr *) &addr,
+				      0,
+				      &msg,
+				      NULL,
+				      &heartbeat_smi_msg,
+				      &heartbeat_recv_msg,
+				      1);
+	if (rv) {
+		up(&heartbeat_lock);
+		printk(KERN_WARNING PFX "heartbeat failure: %d\n",
+		       rv);
+		return rv;
+	}
+
+	/* Wait for the heartbeat to be sent. */
+	down(&heartbeat_wait_lock);
+
+	if (heartbeat_recv_msg.msg.data[0] != 0) {
+	    /* Got an error in the heartbeat response.  It was already
+	       reported in ipmi_wdog_msg_handler, but we should return
+	       an error here. */
+	    rv = -EINVAL;
+	}
+
+	up(&heartbeat_lock);
+
+	return rv;
+}
+
+static void panic_halt_ipmi_heartbeat(void)
+{
+	struct kernel_ipmi_msg             msg;
+	struct ipmi_system_interface_addr addr;
+
+
+	/* Don't reset the timer if we have the timer turned off, that
+           re-enables the watchdog. */
+	if (ipmi_watchdog_state == WDOG_TIMEOUT_NONE)
+		return;
+
+	addr.addr_type = IPMI_SYSTEM_INTERFACE_ADDR_TYPE;
+	addr.channel = IPMI_BMC_CHANNEL;
+	addr.lun = 0;
+
+	msg.netfn = 0x06;
+	msg.cmd = IPMI_WDOG_RESET_TIMER;
+	msg.data = NULL;
+	msg.data_len = 0;
+	ipmi_request_supply_msgs(watchdog_user,
+				 (struct ipmi_addr *) &addr,
+				 0,
+				 &msg,
+				 NULL,
+				 &panic_halt_heartbeat_smi_msg,
+				 &panic_halt_heartbeat_recv_msg,
+				 1);
+}
+
+static struct watchdog_info ident=
+{
+	.options	= 0,	/* WDIOF_SETTIMEOUT, */
+	.firmware_version = 1,
+	.identity	= "IPMI"
+};
+
+static int ipmi_ioctl(struct inode *inode, struct file *file,
+		      unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int i;
+	int val;
+
+	switch(cmd) {
+	case WDIOC_GETSUPPORT:
+		i = copy_to_user(argp, &ident, sizeof(ident));
+		return i ? -EFAULT : 0;
+
+	case WDIOC_SETTIMEOUT:
+		i = copy_from_user(&val, argp, sizeof(int));
+		if (i)
+			return -EFAULT;
+		timeout = val;
+		return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+
+	case WDIOC_GETTIMEOUT:
+		i = copy_to_user(argp, &timeout, sizeof(timeout));
+		if (i)
+			return -EFAULT;
+		return 0;
+
+	case WDIOC_SET_PRETIMEOUT:
+		i = copy_from_user(&val, argp, sizeof(int));
+		if (i)
+			return -EFAULT;
+		pretimeout = val;
+		return ipmi_set_timeout(IPMI_SET_TIMEOUT_HB_IF_NECESSARY);
+
+	case WDIOC_GET_PRETIMEOUT:
+		i = copy_to_user(argp, &pretimeout, sizeof(pretimeout));
+		if (i)
+			return -EFAULT;
+		return 0;
+
+	case WDIOC_KEEPALIVE:
+		return ipmi_heartbeat();
+
+	case WDIOC_SETOPTIONS:
+		i = copy_from_user(&val, argp, sizeof(int));
+		if (i)
+			return -EFAULT;
+		if (val & WDIOS_DISABLECARD)
+		{
+			ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+			ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
+			ipmi_start_timer_on_heartbeat = 0;
+		}
+
+		if (val & WDIOS_ENABLECARD)
+		{
+			ipmi_watchdog_state = action_val;
+			ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+		}
+		return 0;
+
+	case WDIOC_GETSTATUS:
+		val = 0;
+		i = copy_to_user(argp, &val, sizeof(val));
+		if (i)
+			return -EFAULT;
+		return 0;
+
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+static ssize_t ipmi_write(struct file *file,
+			  const char  __user *buf,
+			  size_t      len,
+			  loff_t      *ppos)
+{
+	int rv;
+
+	if (len) {
+	    	if (!nowayout) {
+		    	size_t i;
+
+			/* In case it was set long ago */
+			expect_close = 0;
+
+    			for (i = 0; i != len; i++) {
+				char c;
+
+				if (get_user(c, buf + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		rv = ipmi_heartbeat();
+		if (rv)
+			return rv;
+		return 1;
+	}
+	return 0;
+}
+
+static ssize_t ipmi_read(struct file *file,
+			 char        __user *buf,
+			 size_t      count,
+			 loff_t      *ppos)
+{
+	int          rv = 0;
+	wait_queue_t wait;
+
+	if (count <= 0)
+		return 0;
+
+	/* Reading returns if the pretimeout has gone off, and it only does
+	   it once per pretimeout. */
+	spin_lock(&ipmi_read_lock);
+	if (!data_to_read) {
+		if (file->f_flags & O_NONBLOCK) {
+			rv = -EAGAIN;
+			goto out;
+		}
+		
+		init_waitqueue_entry(&wait, current);
+		add_wait_queue(&read_q, &wait);
+		while (!data_to_read) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			spin_unlock(&ipmi_read_lock);
+			schedule();
+			spin_lock(&ipmi_read_lock);
+		}
+		remove_wait_queue(&read_q, &wait);
+	    
+		if (signal_pending(current)) {
+			rv = -ERESTARTSYS;
+			goto out;
+		}
+	}
+	data_to_read = 0;
+
+ out:
+	spin_unlock(&ipmi_read_lock);
+
+	if (rv == 0) {
+		if (copy_to_user(buf, &data_to_read, 1))
+			rv = -EFAULT;
+		else
+			rv = 1;
+	}
+
+	return rv;
+}
+
+static int ipmi_open(struct inode *ino, struct file *filep)
+{
+        switch (iminor(ino))
+        {
+                case WATCHDOG_MINOR:
+		    if(test_and_set_bit(0, &ipmi_wdog_open))
+                        return -EBUSY;
+
+		    /* Don't start the timer now, let it start on the
+		       first heartbeat. */
+		    ipmi_start_timer_on_heartbeat = 1;
+                    return nonseekable_open(ino, filep);
+
+                default:
+                    return (-ENODEV);
+        }
+}
+
+static unsigned int ipmi_poll(struct file *file, poll_table *wait)
+{
+	unsigned int mask = 0;
+	
+	poll_wait(file, &read_q, wait);
+
+	spin_lock(&ipmi_read_lock);
+	if (data_to_read)
+		mask |= (POLLIN | POLLRDNORM);
+	spin_unlock(&ipmi_read_lock);
+
+	return mask;
+}
+
+static int ipmi_fasync(int fd, struct file *file, int on)
+{
+	int result;
+
+	result = fasync_helper(fd, file, on, &fasync_q);
+
+	return (result);
+}
+
+static int ipmi_close(struct inode *ino, struct file *filep)
+{
+	if (iminor(ino)==WATCHDOG_MINOR)
+	{
+		if (expect_close == 42) {
+			ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+			ipmi_set_timeout(IPMI_SET_TIMEOUT_NO_HB);
+			clear_bit(0, &ipmi_wdog_open);
+		} else {
+			printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+			ipmi_heartbeat();
+		}
+	}
+
+	ipmi_fasync (-1, filep, 0);
+	expect_close = 0;
+
+	return 0;
+}
+
+static struct file_operations ipmi_wdog_fops = {
+	.owner   = THIS_MODULE,
+	.read    = ipmi_read,
+	.poll    = ipmi_poll,
+	.write   = ipmi_write,
+	.ioctl   = ipmi_ioctl,
+	.open    = ipmi_open,
+	.release = ipmi_close,
+	.fasync  = ipmi_fasync,
+};
+
+static struct miscdevice ipmi_wdog_miscdev = {
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &ipmi_wdog_fops
+};
+
+static DECLARE_RWSEM(register_sem);
+
+static void ipmi_wdog_msg_handler(struct ipmi_recv_msg *msg,
+				  void                 *handler_data)
+{
+	if (msg->msg.data[0] != 0) {
+		printk(KERN_ERR PFX "response: Error %x on cmd %x\n",
+		       msg->msg.data[0],
+		       msg->msg.cmd);
+	}
+	
+	ipmi_free_recv_msg(msg);
+}
+
+static void ipmi_wdog_pretimeout_handler(void *handler_data)
+{
+	if (preaction_val != WDOG_PRETIMEOUT_NONE) {
+		if (preop_val == WDOG_PREOP_PANIC)
+			panic("Watchdog pre-timeout");
+		else if (preop_val == WDOG_PREOP_GIVE_DATA) {
+			spin_lock(&ipmi_read_lock);
+			data_to_read = 1;
+			wake_up_interruptible(&read_q);
+			kill_fasync(&fasync_q, SIGIO, POLL_IN);
+
+			spin_unlock(&ipmi_read_lock);
+		}
+	}
+
+	/* On some machines, the heartbeat will give
+	   an error and not work unless we re-enable
+	   the timer.   So do so. */
+	pretimeout_since_last_heartbeat = 1;
+}
+
+static struct ipmi_user_hndl ipmi_hndlrs =
+{
+	.ipmi_recv_hndl           = ipmi_wdog_msg_handler,
+	.ipmi_watchdog_pretimeout = ipmi_wdog_pretimeout_handler
+};
+
+static void ipmi_register_watchdog(int ipmi_intf)
+{
+	int rv = -EBUSY;
+
+	down_write(&register_sem);
+	if (watchdog_user)
+		goto out;
+
+	rv = ipmi_create_user(ipmi_intf, &ipmi_hndlrs, NULL, &watchdog_user);
+	if (rv < 0) {
+		printk(KERN_CRIT PFX "Unable to register with ipmi\n");
+		goto out;
+	}
+
+	ipmi_get_version(watchdog_user,
+			 &ipmi_version_major,
+			 &ipmi_version_minor);
+
+	rv = misc_register(&ipmi_wdog_miscdev);
+	if (rv < 0) {
+		ipmi_destroy_user(watchdog_user);
+		watchdog_user = NULL;
+		printk(KERN_CRIT PFX "Unable to register misc device\n");
+	}
+
+ out:
+	up_write(&register_sem);
+
+	if ((start_now) && (rv == 0)) {
+		/* Run from startup, so start the timer now. */
+		start_now = 0; /* Disable this function after first startup. */
+		ipmi_watchdog_state = action_val;
+		ipmi_set_timeout(IPMI_SET_TIMEOUT_FORCE_HB);
+		printk(KERN_INFO PFX "Starting now!\n");
+	}
+}
+
+#ifdef HAVE_NMI_HANDLER
+static int
+ipmi_nmi(void *dev_id, struct pt_regs *regs, int cpu, int handled)
+{
+	/* If no one else handled the NMI, we assume it was the IPMI
+           watchdog. */
+	if ((!handled) && (preop_val == WDOG_PREOP_PANIC))
+		panic(PFX "pre-timeout");
+
+	/* On some machines, the heartbeat will give
+	   an error and not work unless we re-enable
+	   the timer.   So do so. */
+	pretimeout_since_last_heartbeat = 1;
+
+	return NOTIFY_DONE;
+}
+
+static struct nmi_handler ipmi_nmi_handler =
+{
+	.link     = LIST_HEAD_INIT(ipmi_nmi_handler.link),
+	.dev_name = "ipmi_watchdog",
+	.dev_id   = NULL,
+	.handler  = ipmi_nmi,
+	.priority = 0, /* Call us last. */
+};
+#endif
+
+static int wdog_reboot_handler(struct notifier_block *this,
+			       unsigned long         code,
+			       void                  *unused)
+{
+	static int reboot_event_handled = 0;
+
+	if ((watchdog_user) && (!reboot_event_handled)) {
+		/* Make sure we only do this once. */
+		reboot_event_handled = 1;
+
+		if (code == SYS_DOWN || code == SYS_HALT) {
+			/* Disable the WDT if we are shutting down. */
+			ipmi_watchdog_state = WDOG_TIMEOUT_NONE;
+			panic_halt_ipmi_set_timeout();
+		} else {
+			/* Set a long timer to let the reboot happens, but
+			   reboot if it hangs. */
+			timeout = 120;
+			pretimeout = 0;
+			ipmi_watchdog_state = WDOG_TIMEOUT_RESET;
+			panic_halt_ipmi_set_timeout();
+		}
+	}
+	return NOTIFY_OK;
+}
+
+static struct notifier_block wdog_reboot_notifier = {
+	.notifier_call	= wdog_reboot_handler,
+	.next		= NULL,
+	.priority	= 0
+};
+
+static int wdog_panic_handler(struct notifier_block *this,
+			      unsigned long         event,
+			      void                  *unused)
+{
+	static int panic_event_handled = 0;
+
+	/* On a panic, if we have a panic timeout, make sure that the thing
+	   reboots, even if it hangs during that panic. */
+	if (watchdog_user && !panic_event_handled) {
+		/* Make sure the panic doesn't hang, and make sure we
+		   do this only once. */
+		panic_event_handled = 1;
+	    
+		timeout = 255;
+		pretimeout = 0;
+		ipmi_watchdog_state = WDOG_TIMEOUT_RESET;
+		panic_halt_ipmi_set_timeout();
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block wdog_panic_notifier = {
+	.notifier_call	= wdog_panic_handler,
+	.next		= NULL,
+	.priority	= 150	/* priority: INT_MAX >= x >= 0 */
+};
+
+
+static void ipmi_new_smi(int if_num)
+{
+	ipmi_register_watchdog(if_num);
+}
+
+static void ipmi_smi_gone(int if_num)
+{
+	/* This can never be called, because once the watchdog is
+	   registered, the interface can't go away until the watchdog
+	   is unregistered. */
+}
+
+static struct ipmi_smi_watcher smi_watcher =
+{
+	.owner    = THIS_MODULE,
+	.new_smi  = ipmi_new_smi,
+	.smi_gone = ipmi_smi_gone
+};
+
+static int __init ipmi_wdog_init(void)
+{
+	int rv;
+
+	printk(KERN_INFO PFX "driver version "
+	       IPMI_WATCHDOG_VERSION "\n");
+
+	if (strcmp(action, "reset") == 0) {
+		action_val = WDOG_TIMEOUT_RESET;
+	} else if (strcmp(action, "none") == 0) {
+		action_val = WDOG_TIMEOUT_NONE;
+	} else if (strcmp(action, "power_cycle") == 0) {
+		action_val = WDOG_TIMEOUT_POWER_CYCLE;
+	} else if (strcmp(action, "power_off") == 0) {
+		action_val = WDOG_TIMEOUT_POWER_DOWN;
+	} else {
+		action_val = WDOG_TIMEOUT_RESET;
+		printk(KERN_INFO PFX "Unknown action '%s', defaulting to"
+		       " reset\n", action);
+	}
+
+	if (strcmp(preaction, "pre_none") == 0) {
+		preaction_val = WDOG_PRETIMEOUT_NONE;
+	} else if (strcmp(preaction, "pre_smi") == 0) {
+		preaction_val = WDOG_PRETIMEOUT_SMI;
+#ifdef HAVE_NMI_HANDLER
+	} else if (strcmp(preaction, "pre_nmi") == 0) {
+		preaction_val = WDOG_PRETIMEOUT_NMI;
+#endif
+	} else if (strcmp(preaction, "pre_int") == 0) {
+		preaction_val = WDOG_PRETIMEOUT_MSG_INT;
+	} else {
+		preaction_val = WDOG_PRETIMEOUT_NONE;
+		printk(KERN_INFO PFX "Unknown preaction '%s', defaulting to"
+		       " none\n", preaction);
+	}
+
+	if (strcmp(preop, "preop_none") == 0) {
+		preop_val = WDOG_PREOP_NONE;
+	} else if (strcmp(preop, "preop_panic") == 0) {
+		preop_val = WDOG_PREOP_PANIC;
+	} else if (strcmp(preop, "preop_give_data") == 0) {
+		preop_val = WDOG_PREOP_GIVE_DATA;
+	} else {
+		preop_val = WDOG_PREOP_NONE;
+		printk(KERN_INFO PFX "Unknown preop '%s', defaulting to"
+		       " none\n", preop);
+	}
+
+#ifdef HAVE_NMI_HANDLER
+	if (preaction_val == WDOG_PRETIMEOUT_NMI) {
+		if (preop_val == WDOG_PREOP_GIVE_DATA) {
+			printk(KERN_WARNING PFX "Pretimeout op is to give data"
+			       " but NMI pretimeout is enabled, setting"
+			       " pretimeout op to none\n");
+			preop_val = WDOG_PREOP_NONE;
+		}
+#ifdef CONFIG_X86_LOCAL_APIC
+		if (nmi_watchdog == NMI_IO_APIC) {
+			printk(KERN_WARNING PFX "nmi_watchdog is set to IO APIC"
+			       " mode (value is %d), that is incompatible"
+			       " with using NMI in the IPMI watchdog."
+			       " Disabling IPMI nmi pretimeout.\n",
+			       nmi_watchdog);
+			preaction_val = WDOG_PRETIMEOUT_NONE;
+		} else {
+#endif
+		rv = request_nmi(&ipmi_nmi_handler);
+		if (rv) {
+			printk(KERN_WARNING PFX "Can't register nmi handler\n");
+			return rv;
+		}
+#ifdef CONFIG_X86_LOCAL_APIC
+		}
+#endif
+	}
+#endif
+
+	rv = ipmi_smi_watcher_register(&smi_watcher);
+	if (rv) {
+#ifdef HAVE_NMI_HANDLER
+		if (preaction_val == WDOG_PRETIMEOUT_NMI)
+			release_nmi(&ipmi_nmi_handler);
+#endif
+		printk(KERN_WARNING PFX "can't register smi watcher\n");
+		return rv;
+	}
+
+	register_reboot_notifier(&wdog_reboot_notifier);
+	notifier_chain_register(&panic_notifier_list, &wdog_panic_notifier);
+
+	return 0;
+}
+
+static __exit void ipmi_unregister_watchdog(void)
+{
+	int rv;
+
+	down_write(&register_sem);
+
+#ifdef HAVE_NMI_HANDLER
+	if (preaction_val == WDOG_PRETIMEOUT_NMI)
+		release_nmi(&ipmi_nmi_handler);
+#endif
+
+	notifier_chain_unregister(&panic_notifier_list, &wdog_panic_notifier);
+	unregister_reboot_notifier(&wdog_reboot_notifier);
+
+	if (! watchdog_user)
+		goto out;
+
+	/* Make sure no one can call us any more. */
+	misc_deregister(&ipmi_wdog_miscdev);
+
+	/* Wait to make sure the message makes it out.  The lower layer has
+	   pointers to our buffers, we want to make sure they are done before
+	   we release our memory. */
+	while (atomic_read(&set_timeout_tofree)) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(1);
+	}
+
+	/* Disconnect from IPMI. */
+	rv = ipmi_destroy_user(watchdog_user);
+	if (rv) {
+		printk(KERN_WARNING PFX "error unlinking from IPMI: %d\n",
+		       rv);
+	}
+	watchdog_user = NULL;
+
+ out:
+	up_write(&register_sem);
+}
+
+static void __exit ipmi_wdog_exit(void)
+{
+	ipmi_smi_watcher_unregister(&smi_watcher);
+	ipmi_unregister_watchdog();
+}
+module_exit(ipmi_wdog_exit);
+module_init(ipmi_wdog_init);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/isicom.c b/drivers/char/isicom.c
new file mode 100644
index 0000000..601c7fc
--- /dev/null
+++ b/drivers/char/isicom.c
@@ -0,0 +1,2079 @@
+/*
+ *	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.
+ *
+ *	Original driver code supplied by Multi-Tech
+ *
+ *	Changes
+ *	1/9/98	alan@redhat.com		Merge to 2.0.x kernel tree
+ *					Obtain and use official major/minors
+ *					Loader switched to a misc device
+ *					(fixed range check bug as a side effect)
+ *					Printk clean up
+ *	9/12/98	alan@redhat.com		Rough port to 2.1.x
+ *
+ *	10/6/99 sameer			Merged the ISA and PCI drivers to
+ *					a new unified driver.
+ *
+ *	3/9/99	sameer			Added support for ISI4616 cards.
+ *
+ *	16/9/99	sameer			We do not force RTS low anymore.
+ *					This is to prevent the firmware 
+ *					from getting confused.
+ *
+ *	26/10/99 sameer			Cosmetic changes:The driver now
+ *					dumps the Port Count information
+ *					along with I/O address and IRQ.
+ *
+ *	13/12/99 sameer			Fixed the problem with IRQ sharing.
+ *
+ *	10/5/00  sameer			Fixed isicom_shutdown_board()
+ *					to not lower DTR on all the ports
+ *					when the last port on the card is 
+ *					closed.
+ *
+ *	10/5/00  sameer			Signal mask setup command added
+ *					to  isicom_setup_port and 
+ *					isicom_shutdown_port.
+ *
+ *	24/5/00  sameer			The driver is now SMP aware.
+ *					
+ *	
+ *	27/11/00 Vinayak P Risbud	Fixed the Driver Crash Problem
+ *	
+ *	
+ *	03/01/01  anil .s		Added support for resetting the
+ *					internal modems on ISI cards.
+ *
+ *	08/02/01  anil .s		Upgraded the driver for kernel
+ *					2.4.x
+ *
+ *      11/04/01  Kevin			Fixed firmware load problem with
+ *					ISIHP-4X card
+ *	
+ *	30/04/01  anil .s		Fixed the remote login through
+ *					ISI port problem. Now the link
+ *					does not go down before password
+ *					prompt.
+ *
+ *	03/05/01  anil .s		Fixed the problem with IRQ sharing
+ *					among ISI-PCI cards.
+ *
+ *	03/05/01  anil .s		Added support to display the version
+ *					info during insmod as well as module 
+ *					listing by lsmod.
+ *	
+ *	10/05/01  anil .s		Done the modifications to the source
+ *					file and Install script so that the
+ *					same installation can be used for
+ *					2.2.x and 2.4.x kernel.
+ *
+ *	06/06/01  anil .s		Now we drop both dtr and rts during
+ *					shutdown_port as well as raise them
+ *					during isicom_config_port.
+ *  	
+ *	09/06/01 acme@conectiva.com.br	use capable, not suser, do
+ *					restore_flags on failure in
+ *					isicom_send_break, verify put_user
+ *					result
+ *
+ *  	11/02/03  ranjeeth		Added support for 230 Kbps and 460 Kbps
+ *  					Baud index extended to 21
+ *  	
+ *  	20/03/03  ranjeeth		Made to work for Linux Advanced server.
+ *  					Taken care of license warning.	
+ *      
+ *	10/12/03  Ravindra		Made to work for Fedora Core 1 of 
+ *					Red Hat Distribution
+ *
+ *	06/01/05  Alan Cox 		Merged the ISI and base kernel strands
+ *					into a single 2.6 driver
+ *
+ *	***********************************************************
+ *
+ *	To use this driver you also need the support package. You 
+ *	can find this in RPM format on
+ *		ftp://ftp.linux.org.uk/pub/linux/alan
+ * 	
+ *	You can find the original tools for this direct from Multitech
+ *		ftp://ftp.multitech.com/ISI-Cards/
+ *
+ *	Having installed the cards the module options (/etc/modprobe.conf)
+ *
+ *	options isicom   io=card1,card2,card3,card4 irq=card1,card2,card3,card4
+ *
+ *	Omit those entries for boards you don't have installed.
+ *
+ *	TODO
+ *		Hotplug
+ *		Merge testing
+ *		64-bit verification
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/tty.h>
+#include <linux/termios.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/serial.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/system.h>
+
+#include <linux/pci.h>
+
+#include <linux/isicom.h>
+
+static struct pci_device_id isicom_pci_tbl[] = {
+	{ VENDOR_ID, 0x2028, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ VENDOR_ID, 0x2051, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ VENDOR_ID, 0x2052, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ VENDOR_ID, 0x2053, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ VENDOR_ID, 0x2054, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ VENDOR_ID, 0x2055, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ VENDOR_ID, 0x2056, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ VENDOR_ID, 0x2057, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ VENDOR_ID, 0x2058, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ 0 }
+};
+MODULE_DEVICE_TABLE(pci, isicom_pci_tbl);
+
+static int prev_card = 3;	/*	start servicing isi_card[0]	*/
+static struct tty_driver *isicom_normal;
+
+static struct timer_list tx;
+static char re_schedule = 1;
+#ifdef ISICOM_DEBUG
+static unsigned long tx_count = 0;
+#endif
+
+static int ISILoad_ioctl(struct inode *inode, struct file *filp, unsigned  int cmd, unsigned long arg);
+
+static void isicom_tx(unsigned long _data);
+static void isicom_start(struct tty_struct * tty);
+
+static unsigned char * tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+/*   baud index mappings from linux defns to isi */
+
+static signed char linuxb_to_isib[] = {
+	-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 13, 15, 16, 17,     
+	18, 19
+};
+
+struct	isi_board {
+	unsigned short		base;
+	unsigned char		irq;
+	unsigned char		port_count;
+	unsigned short		status;
+	unsigned short		port_status; /* each bit represents a single port */
+	unsigned short		shift_count;
+	struct isi_port		* ports;
+	signed char		count;
+	unsigned char		isa;
+	spinlock_t		card_lock; /* Card wide lock 11/5/00 -sameer */
+	unsigned long		flags;
+};
+
+struct	isi_port {
+	unsigned short		magic;
+	unsigned int		flags;
+	int			count;
+	int			blocked_open;
+	int			close_delay;
+	unsigned short		channel;
+	unsigned short		status;
+	unsigned short		closing_wait;
+	struct isi_board	* card;
+	struct tty_struct 	* tty;
+	wait_queue_head_t	close_wait;
+	wait_queue_head_t	open_wait;
+	struct work_struct	hangup_tq;
+	struct work_struct	bh_tqueue;
+	unsigned char		* xmit_buf;
+	int			xmit_head;
+	int			xmit_tail;
+	int			xmit_cnt;
+};
+
+static struct isi_board isi_card[BOARD_COUNT];
+static struct isi_port  isi_ports[PORT_COUNT];
+
+/*
+ *	Locking functions for card level locking. We need to own both
+ *	the kernel lock for the card and have the card in a position that
+ *	it wants to talk.
+ */
+ 
+static int lock_card(struct isi_board *card)
+{
+	char		retries;
+	unsigned short base = card->base;
+
+	for (retries = 0; retries < 100; retries++) {
+		spin_lock_irqsave(&card->card_lock, card->flags);
+		if (inw(base + 0xe) & 0x1) {
+			return 1; 
+		} else {
+			spin_unlock_irqrestore(&card->card_lock, card->flags);
+			udelay(1000);   /* 1ms */
+		}
+	}
+	printk(KERN_WARNING "ISICOM: Failed to lock Card (0x%x)\n", card->base);
+	return 0;	/* Failed to aquire the card! */
+}
+
+static int lock_card_at_interrupt(struct isi_board *card)
+{
+	unsigned char		retries;
+	unsigned short 		base = card->base;
+
+	for (retries = 0; retries < 200; retries++) {
+		spin_lock_irqsave(&card->card_lock, card->flags);
+
+		if (inw(base + 0xe) & 0x1)
+			return 1; 
+		else
+			spin_unlock_irqrestore(&card->card_lock, card->flags);
+	}
+	/* Failing in interrupt is an acceptable event */
+	return 0;	/* Failed to aquire the card! */
+}
+
+static void unlock_card(struct isi_board *card)
+{
+	spin_unlock_irqrestore(&card->card_lock, card->flags);
+}
+
+/*
+ *  ISI Card specific ops ...
+ */
+ 
+static void raise_dtr(struct isi_port * port)
+{
+	struct isi_board * card = port->card;
+	unsigned short base = card->base;
+	unsigned char channel = port->channel;
+
+	if (!lock_card(card))
+		return;
+
+	outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+	outw(0x0504, base);
+	InterruptTheCard(base);
+	port->status |= ISI_DTR;
+	unlock_card(card);
+}
+
+static inline void drop_dtr(struct isi_port * port)
+{	
+	struct isi_board * card = port->card;
+	unsigned short base = card->base;
+	unsigned char channel = port->channel;
+
+	if (!lock_card(card))
+		return;
+
+	outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+	outw(0x0404, base);
+	InterruptTheCard(base);	
+	port->status &= ~ISI_DTR;
+	unlock_card(card);
+}
+
+static inline void raise_rts(struct isi_port * port)
+{
+	struct isi_board * card = port->card;
+	unsigned short base = card->base;
+	unsigned char channel = port->channel;
+
+	if (!lock_card(card))
+		return;
+
+	outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+	outw(0x0a04, base);
+	InterruptTheCard(base);	
+	port->status |= ISI_RTS;
+	unlock_card(card);
+}
+static inline void drop_rts(struct isi_port * port)
+{
+	struct isi_board * card = port->card;
+	unsigned short base = card->base;
+	unsigned char channel = port->channel;
+
+	if (!lock_card(card))
+		return;
+
+	outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+	outw(0x0804, base);
+	InterruptTheCard(base);	
+	port->status &= ~ISI_RTS;
+	unlock_card(card);
+}
+
+static inline void raise_dtr_rts(struct isi_port * port)
+{
+	struct isi_board * card = port->card;
+	unsigned short base = card->base;
+	unsigned char channel = port->channel;
+
+	if (!lock_card(card))
+		return;
+
+	outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+	outw(0x0f04, base);
+	InterruptTheCard(base);
+	port->status |= (ISI_DTR | ISI_RTS);
+	unlock_card(card);
+}
+
+static void drop_dtr_rts(struct isi_port * port)
+{
+	struct isi_board * card = port->card;
+	unsigned short base = card->base;
+	unsigned char channel = port->channel;
+
+	if (!lock_card(card))
+		return;
+
+	outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+	outw(0x0c04, base);
+	InterruptTheCard(base);	
+	port->status &= ~(ISI_RTS | ISI_DTR);
+	unlock_card(card);
+}
+
+static inline void kill_queue(struct isi_port * port, short queue)
+{
+	struct isi_board * card = port->card;
+	unsigned short base = card->base;
+	unsigned char channel = port->channel;
+
+	if (!lock_card(card))
+		return;
+
+	outw(0x8000 | (channel << card->shift_count) | 0x02 , base);
+	outw((queue << 8) | 0x06, base);
+	InterruptTheCard(base);	
+	unlock_card(card);
+}
+
+
+/* 
+ *  Firmware loader driver specific routines. This needs to mostly die
+ *  and be replaced with request_firmware.
+ */
+
+static struct file_operations ISILoad_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= ISILoad_ioctl,
+};
+
+static struct miscdevice isiloader_device = {
+	ISILOAD_MISC_MINOR, "isictl", &ISILoad_fops
+};
+
+ 
+static inline int WaitTillCardIsFree(unsigned short base)
+{
+	unsigned long count=0;
+	while( (!(inw(base+0xe) & 0x1)) && (count++ < 6000000));
+	if (inw(base+0xe)&0x1)  
+		return 0;
+	else
+		return 1;
+}
+
+static int ISILoad_ioctl(struct inode *inode, struct file *filp,
+		         unsigned int cmd, unsigned long arg)
+{
+	unsigned int card, i, j, signature, status, portcount = 0;
+	unsigned long t;
+	unsigned short word_count, base;
+	bin_frame frame;
+	void __user *argp = (void __user *)arg;
+	/* exec_record exec_rec; */
+	
+	if(get_user(card, (int __user *)argp))
+		return -EFAULT;
+		
+	if(card < 0 || card >= BOARD_COUNT)
+		return -ENXIO;
+		
+	base=isi_card[card].base;
+	
+	if(base==0)
+		return -ENXIO;	/* disabled or not used */
+	
+	switch(cmd) {
+		case MIOCTL_RESET_CARD:
+			if (!capable(CAP_SYS_ADMIN))
+				return -EPERM;
+			printk(KERN_DEBUG "ISILoad:Resetting Card%d at 0x%x ",card+1,base);
+								
+			inw(base+0x8);
+			
+			for(t=jiffies+HZ/100;time_before(jiffies, t););
+				
+			outw(0,base+0x8); /* Reset */
+			
+			for(j=1;j<=3;j++) {
+				for(t=jiffies+HZ;time_before(jiffies, t););
+				printk(".");
+			}	
+			signature=(inw(base+0x4)) & 0xff;	
+			if (isi_card[card].isa) {
+					
+				if (!(inw(base+0xe) & 0x1) || (inw(base+0x2))) {
+#ifdef ISICOM_DEBUG				
+					printk("\nbase+0x2=0x%x , base+0xe=0x%x",inw(base+0x2),inw(base+0xe));
+#endif				
+					printk("\nISILoad:ISA Card%d reset failure (Possible bad I/O Port Address 0x%x).\n",card+1,base);
+					return -EIO;					
+				}
+			}	
+			else {
+				portcount = inw(base+0x2);
+				if (!(inw(base+0xe) & 0x1) || ((portcount!=0) && (portcount!=4) && (portcount!=8))) {	
+#ifdef ISICOM_DEBUG
+					printk("\nbase+0x2=0x%x , base+0xe=0x%x",inw(base+0x2),inw(base+0xe));
+#endif
+					printk("\nISILoad:PCI Card%d reset failure (Possible bad I/O Port Address 0x%x).\n",card+1,base);
+					return -EIO;
+				}
+			}	
+			switch(signature) {
+			case	0xa5:
+			case	0xbb:
+			case	0xdd:	
+					if (isi_card[card].isa) 
+						isi_card[card].port_count = 8;
+					else {
+						if (portcount == 4)
+							isi_card[card].port_count = 4;
+						else
+							isi_card[card].port_count = 8;
+					}	
+				     	isi_card[card].shift_count = 12;
+				     	break;
+				        
+			case	0xcc:	isi_card[card].port_count = 16;
+					isi_card[card].shift_count = 11;
+					break;  			
+					
+			default: printk("ISILoad:Card%d reset failure (Possible bad I/O Port Address 0x%x).\n",card+1,base);
+#ifdef ISICOM_DEBUG			
+				 printk("Sig=0x%x\n",signature);
+#endif				 
+				 return -EIO;
+			}
+			printk("-Done\n");
+			return put_user(signature,(unsigned __user *)argp);
+						
+	case	MIOCTL_LOAD_FIRMWARE:
+			if (!capable(CAP_SYS_ADMIN))
+				return -EPERM;
+				
+			if(copy_from_user(&frame, argp, sizeof(bin_frame)))
+				return -EFAULT;
+			
+			if (WaitTillCardIsFree(base))
+				return -EIO;
+			
+			outw(0xf0,base);	/* start upload sequence */ 
+			outw(0x00,base);
+			outw((frame.addr), base);/*      lsb of adderess    */
+			
+			word_count=(frame.count >> 1) + frame.count % 2;
+			outw(word_count, base);
+			InterruptTheCard(base);
+			
+			for(i=0;i<=0x2f;i++);	/* a wee bit of delay */
+			
+			if (WaitTillCardIsFree(base)) 
+				return -EIO;
+				
+			if ((status=inw(base+0x4))!=0) {
+				printk(KERN_WARNING "ISILoad:Card%d rejected load header:\nAddress:0x%x \nCount:0x%x \nStatus:0x%x \n", 
+				card+1, frame.addr, frame.count, status);
+				return -EIO;
+			}
+			outsw(base, (void *) frame.bin_data, word_count);
+			
+			InterruptTheCard(base);
+			
+			for(i=0;i<=0x0f;i++);	/* another wee bit of delay */ 
+			
+			if (WaitTillCardIsFree(base)) 
+				return -EIO;
+				
+			if ((status=inw(base+0x4))!=0) {
+				printk(KERN_ERR "ISILoad:Card%d got out of sync.Card Status:0x%x\n",card+1, status);
+				return -EIO;
+			}	
+			return 0;
+						
+	case	MIOCTL_READ_FIRMWARE:
+			if (!capable(CAP_SYS_ADMIN))
+				return -EPERM;
+				
+			if(copy_from_user(&frame, argp, sizeof(bin_header)))
+				return -EFAULT;
+			
+			if (WaitTillCardIsFree(base))
+				return -EIO;
+			
+			outw(0xf1,base);	/* start download sequence */ 
+			outw(0x00,base);
+			outw((frame.addr), base);/*      lsb of adderess    */
+			
+			word_count=(frame.count >> 1) + frame.count % 2;
+			outw(word_count+1, base);
+			InterruptTheCard(base);
+			
+			for(i=0;i<=0xf;i++);	/* a wee bit of delay */
+			
+			if (WaitTillCardIsFree(base)) 
+				return -EIO;
+				
+			if ((status=inw(base+0x4))!=0) {
+				printk(KERN_WARNING "ISILoad:Card%d rejected verify header:\nAddress:0x%x \nCount:0x%x \nStatus:0x%x \n", 
+				card+1, frame.addr, frame.count, status);
+				return -EIO;
+			}
+			
+			inw(base);
+			insw(base, frame.bin_data, word_count);
+			InterruptTheCard(base);
+			
+			for(i=0;i<=0x0f;i++);	/* another wee bit of delay */ 
+			
+			if (WaitTillCardIsFree(base)) 
+				return -EIO;
+				
+			if ((status=inw(base+0x4))!=0) {
+				printk(KERN_ERR "ISILoad:Card%d verify got out of sync.Card Status:0x%x\n",card+1, status);
+				return -EIO;
+			}	
+			
+			if(copy_to_user(argp, &frame, sizeof(bin_frame)))
+				return -EFAULT;
+			return 0;
+	
+	case	MIOCTL_XFER_CTRL:
+			if (!capable(CAP_SYS_ADMIN))
+				return -EPERM;
+			if (WaitTillCardIsFree(base)) 
+				return -EIO;
+					
+			outw(0xf2, base);
+			outw(0x800, base);
+			outw(0x0, base);
+			outw(0x0, base);
+			InterruptTheCard(base);
+			outw(0x0, base+0x4);    /* for ISI4608 cards */
+							
+			isi_card[card].status |= FIRMWARE_LOADED;
+			return 0;	
+			
+	default:
+#ifdef ISICOM_DEBUG	
+		printk(KERN_DEBUG "ISILoad: Received Ioctl cmd 0x%x.\n", cmd); 
+#endif
+		return -ENOIOCTLCMD;
+	
+	}
+	
+}
+		        	
+
+/*
+ *	ISICOM Driver specific routines ...
+ *
+ */
+ 
+static inline int isicom_paranoia_check(struct isi_port const * port, char *name, 
+					const char * routine)
+{
+#ifdef ISICOM_DEBUG 
+	static const char * badmagic = 
+			KERN_WARNING "ISICOM: Warning: bad isicom magic for dev %s in %s.\n";
+	static const char * badport = 
+			KERN_WARNING "ISICOM: Warning: NULL isicom port for dev %s in %s.\n";		
+	if (!port) {
+		printk(badport, name, routine);
+		return 1;
+	}
+	if (port->magic != ISICOM_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}	
+#endif	
+	return 0;
+}
+			
+/*
+ *	Transmitter. 
+ *
+ *	We shovel data into the card buffers on a regular basis. The card
+ *	will do the rest of the work for us.
+ */
+
+static void isicom_tx(unsigned long _data)
+{
+	short count = (BOARD_COUNT-1), card, base;
+	short txcount, wrd, residue, word_count, cnt;
+	struct isi_port * port;
+	struct tty_struct * tty;
+	
+#ifdef ISICOM_DEBUG
+	++tx_count;
+#endif	
+	
+	/*	find next active board	*/
+	card = (prev_card + 1) & 0x0003;
+	while(count-- > 0) {
+		if (isi_card[card].status & BOARD_ACTIVE) 
+			break;
+		card = (card + 1) & 0x0003;	
+	}
+	if (!(isi_card[card].status & BOARD_ACTIVE))
+		goto sched_again;
+		
+	prev_card = card;
+	
+	count = isi_card[card].port_count;
+	port = isi_card[card].ports;
+	base = isi_card[card].base;
+	for (;count > 0;count--, port++) {
+		if (!lock_card_at_interrupt(&isi_card[card]))
+			continue;
+		/* port not active or tx disabled to force flow control */
+		if (!(port->flags & ASYNC_INITIALIZED) ||
+		 	!(port->status & ISI_TXOK))
+			unlock_card(&isi_card[card]);
+			continue;
+		
+		tty = port->tty;
+		
+		
+		if(tty == NULL) {
+			unlock_card(&isi_card[card]);
+			continue;
+		}
+		
+		txcount = min_t(short, TX_SIZE, port->xmit_cnt);
+		if (txcount <= 0 || tty->stopped || tty->hw_stopped) {
+			unlock_card(&isi_card[card]);
+			continue;
+		}
+		if (!(inw(base + 0x02) & (1 << port->channel))) {
+			unlock_card(&isi_card[card]);
+			continue;		
+		}
+#ifdef ISICOM_DEBUG
+		printk(KERN_DEBUG "ISICOM: txing %d bytes, port%d.\n", 
+				txcount, port->channel+1); 
+#endif	
+		outw((port->channel << isi_card[card].shift_count) | txcount
+					, base);
+		residue = NO;
+		wrd = 0;			
+		while (1) {
+			cnt = min_t(int, txcount, (SERIAL_XMIT_SIZE - port->xmit_tail));
+			if (residue == YES) {
+				residue = NO;
+				if (cnt > 0) {
+					wrd |= (port->xmit_buf[port->xmit_tail] << 8);
+					port->xmit_tail = (port->xmit_tail + 1) & (SERIAL_XMIT_SIZE - 1);
+					port->xmit_cnt--;
+					txcount--;
+					cnt--;
+					outw(wrd, base);			
+				}
+				else {
+					outw(wrd, base);
+					break;
+				}
+			}		
+			if (cnt <= 0) break;
+			word_count = cnt >> 1;
+			outsw(base, port->xmit_buf+port->xmit_tail, word_count);
+			port->xmit_tail = (port->xmit_tail + (word_count << 1)) &
+						(SERIAL_XMIT_SIZE - 1);
+			txcount -= (word_count << 1);
+			port->xmit_cnt -= (word_count << 1);
+			if (cnt & 0x0001) {
+				residue = YES;
+				wrd = port->xmit_buf[port->xmit_tail];
+				port->xmit_tail = (port->xmit_tail + 1) & (SERIAL_XMIT_SIZE - 1);
+				port->xmit_cnt--;
+				txcount--;
+			}
+		}
+
+		InterruptTheCard(base);
+		if (port->xmit_cnt <= 0)
+			port->status &= ~ISI_TXOK;
+		if (port->xmit_cnt <= WAKEUP_CHARS)
+			schedule_work(&port->bh_tqueue);
+		unlock_card(&isi_card[card]);
+	}	
+
+	/*	schedule another tx for hopefully in about 10ms	*/	
+sched_again:	
+	if (!re_schedule)	
+		return;
+	init_timer(&tx);
+	tx.expires = jiffies + HZ/100;
+	tx.data = 0;
+	tx.function = isicom_tx;
+	add_timer(&tx);
+	
+	return;	
+}		
+ 
+/* 	Interrupt handlers 	*/
+
+ 
+static void isicom_bottomhalf(void * data)
+{
+	struct isi_port * port = (struct isi_port *) data;
+	struct tty_struct * tty = port->tty;
+	
+	if (!tty)
+		return;
+
+	tty_wakeup(tty);	
+	wake_up_interruptible(&tty->write_wait);
+} 		
+ 		
+/*
+ *	Main interrupt handler routine 
+ */
+ 
+static irqreturn_t isicom_interrupt(int irq, void *dev_id,
+					struct pt_regs *regs)
+{
+	struct isi_board * card;
+	struct isi_port * port;
+	struct tty_struct * tty;
+	unsigned short base, header, word_count, count;
+	unsigned char channel;
+	short byte_count;
+	
+	card = (struct isi_board *) dev_id;
+
+	if (!card || !(card->status & FIRMWARE_LOADED))
+		return IRQ_NONE;
+	
+	base = card->base;
+	spin_lock(&card->card_lock);
+	
+	if (card->isa == NO) {
+		/*
+		 *      disable any interrupts from the PCI card and lower the
+		 *      interrupt line
+		 */
+		outw(0x8000, base+0x04);
+		ClearInterrupt(base);
+	}
+	
+	inw(base);		/* get the dummy word out */
+	header = inw(base);
+	channel = (header & 0x7800) >> card->shift_count;
+	byte_count = header & 0xff;
+
+	if (channel + 1 > card->port_count) {
+		printk(KERN_WARNING "ISICOM: isicom_interrupt(0x%x): %d(channel) > port_count.\n",
+				base, channel+1);
+		if (card->isa)
+			ClearInterrupt(base);
+		else
+			outw(0x0000, base+0x04); /* enable interrupts */		
+		spin_unlock(&card->card_lock);
+		return IRQ_HANDLED;			
+	}
+	port = card->ports + channel;
+	if (!(port->flags & ASYNC_INITIALIZED)) {
+		if (card->isa)
+			ClearInterrupt(base);
+		else
+			outw(0x0000, base+0x04); /* enable interrupts */
+		return IRQ_HANDLED;
+	}	
+		
+	tty = port->tty;
+	if (tty == NULL) {
+		word_count = byte_count >> 1;
+		while(byte_count > 1) {
+			inw(base);
+			byte_count -= 2;
+		}
+		if (byte_count & 0x01)
+			inw(base);
+		if (card->isa == YES)
+			ClearInterrupt(base);
+		else
+			outw(0x0000, base+0x04); /* enable interrupts */
+		spin_unlock(&card->card_lock);
+		return IRQ_HANDLED;
+	}
+	
+	if (header & 0x8000) {		/* Status Packet */
+		header = inw(base);
+		switch(header & 0xff) {
+			case 0:	/* Change in EIA signals */
+				
+				if (port->flags & ASYNC_CHECK_CD) {
+					if (port->status & ISI_DCD) {
+						if (!(header & ISI_DCD)) {
+						/* Carrier has been lost  */
+#ifdef ISICOM_DEBUG						
+							printk(KERN_DEBUG "ISICOM: interrupt: DCD->low.\n");
+#endif							
+							port->status &= ~ISI_DCD;
+							schedule_work(&port->hangup_tq);
+						}
+					}
+					else {
+						if (header & ISI_DCD) {
+						/* Carrier has been detected */
+#ifdef ISICOM_DEBUG
+							printk(KERN_DEBUG "ISICOM: interrupt: DCD->high.\n");
+#endif							
+							port->status |= ISI_DCD;
+							wake_up_interruptible(&port->open_wait);
+						}
+					}
+				}
+				else {
+					if (header & ISI_DCD) 
+						port->status |= ISI_DCD;
+					else
+						port->status &= ~ISI_DCD;
+				}	
+				
+				if (port->flags & ASYNC_CTS_FLOW) {
+					if (port->tty->hw_stopped) {
+						if (header & ISI_CTS) {
+							port->tty->hw_stopped = 0;
+							/* start tx ing */
+							port->status |= (ISI_TXOK | ISI_CTS);
+							schedule_work(&port->bh_tqueue);
+						}
+					}
+					else {
+						if (!(header & ISI_CTS)) {
+							port->tty->hw_stopped = 1;
+							/* stop tx ing */
+							port->status &= ~(ISI_TXOK | ISI_CTS);
+						}
+					}
+				}
+				else {
+					if (header & ISI_CTS) 
+						port->status |= ISI_CTS;
+					else
+						port->status &= ~ISI_CTS;
+				}
+				
+				if (header & ISI_DSR) 
+					port->status |= ISI_DSR;
+				else
+					port->status &= ~ISI_DSR;
+				
+				if (header & ISI_RI) 
+					port->status |= ISI_RI;
+				else
+					port->status &= ~ISI_RI;						
+				
+				break;
+				
+			case 1:	/* Received Break !!!	 */
+				if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+					break;
+				*tty->flip.flag_buf_ptr++ = TTY_BREAK;
+				*tty->flip.char_buf_ptr++ = 0;
+				tty->flip.count++;
+				if (port->flags & ASYNC_SAK)
+					do_SAK(tty);
+				schedule_delayed_work(&tty->flip.work, 1);
+				break;
+				
+			case 2:	/* Statistics		 */
+				printk(KERN_DEBUG "ISICOM: isicom_interrupt: stats!!!.\n");			
+				break;
+				
+			default:
+				printk(KERN_WARNING "ISICOM: Intr: Unknown code in status packet.\n");
+				break;
+		}	 
+	}
+	else {				/* Data   Packet */
+		count = min_t(unsigned short, byte_count, (TTY_FLIPBUF_SIZE - tty->flip.count));
+#ifdef ISICOM_DEBUG
+		printk(KERN_DEBUG "ISICOM: Intr: Can rx %d of %d bytes.\n", 
+					count, byte_count);
+#endif			
+		word_count = count >> 1;
+		insw(base, tty->flip.char_buf_ptr, word_count);
+		tty->flip.char_buf_ptr += (word_count << 1);		
+		byte_count -= (word_count << 1);
+		if (count & 0x0001) {
+			*tty->flip.char_buf_ptr++ = (char)(inw(base) & 0xff);
+			byte_count -= 2;
+		}	
+		memset(tty->flip.flag_buf_ptr, 0, count);
+		tty->flip.flag_buf_ptr += count;
+		tty->flip.count += count;
+		
+		if (byte_count > 0) {
+			printk(KERN_DEBUG "ISICOM: Intr(0x%x:%d): Flip buffer overflow! dropping bytes...\n",
+					base, channel+1);
+			while(byte_count > 0) { /* drain out unread xtra data */
+				inw(base);
+				byte_count -= 2;
+			}
+		}
+		schedule_delayed_work(&tty->flip.work, 1);
+	}
+	if (card->isa == YES)
+		ClearInterrupt(base);
+	else
+		outw(0x0000, base+0x04); /* enable interrupts */	
+	return IRQ_HANDLED;
+} 
+
+static void isicom_config_port(struct isi_port * port)
+{
+	struct isi_board * card = port->card;
+	struct tty_struct * tty;
+	unsigned long baud;
+	unsigned short channel_setup, base = card->base;
+	unsigned short channel = port->channel, shift_count = card->shift_count;
+	unsigned char flow_ctrl;
+	
+	if (!(tty = port->tty) || !tty->termios)
+		return;
+	baud = C_BAUD(tty);
+	if (baud & CBAUDEX) {
+		baud &= ~CBAUDEX;
+		
+		/*  if CBAUDEX bit is on and the baud is set to either 50 or 75
+		 *  then the card is programmed for 57.6Kbps or 115Kbps
+		 *  respectively.
+		 */   
+		 
+		if (baud < 1 || baud > 2)
+			port->tty->termios->c_cflag &= ~CBAUDEX;
+		else
+			baud += 15;
+	}	
+	if (baud == 15) {
+	
+		/*  the ASYNC_SPD_HI and ASYNC_SPD_VHI options are set 
+		 *  by the set_serial_info ioctl ... this is done by
+		 *  the 'setserial' utility.
+		 */  
+			
+		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			baud++;     /*  57.6 Kbps */
+		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			baud +=2;   /*  115  Kbps */	 
+	}
+	if (linuxb_to_isib[baud] == -1) {
+		/* hang up */
+	 	drop_dtr(port);
+	 	return;
+	}	
+	else  
+		raise_dtr(port);
+		
+	if (lock_card(card)) {
+		outw(0x8000 | (channel << shift_count) |0x03, base);
+		outw(linuxb_to_isib[baud] << 8 | 0x03, base);
+		channel_setup = 0;
+		switch(C_CSIZE(tty)) {
+			case CS5:
+				channel_setup |= ISICOM_CS5;
+				break;
+			case CS6:
+				channel_setup |= ISICOM_CS6;
+				break;
+			case CS7:
+				channel_setup |= ISICOM_CS7;
+				break;
+			case CS8:
+				channel_setup |= ISICOM_CS8;
+				break;
+		}
+			
+		if (C_CSTOPB(tty))
+			channel_setup |= ISICOM_2SB;
+		if (C_PARENB(tty)) {
+			channel_setup |= ISICOM_EVPAR;
+			if (C_PARODD(tty))
+				channel_setup |= ISICOM_ODPAR;	
+		}
+		outw(channel_setup, base);	
+		InterruptTheCard(base);
+		unlock_card(card);	
+	}	
+	if (C_CLOCAL(tty))
+		port->flags &= ~ASYNC_CHECK_CD;
+	else
+		port->flags |= ASYNC_CHECK_CD;	
+	
+	/* flow control settings ...*/
+	flow_ctrl = 0;
+	port->flags &= ~ASYNC_CTS_FLOW;
+	if (C_CRTSCTS(tty)) {
+		port->flags |= ASYNC_CTS_FLOW;
+		flow_ctrl |= ISICOM_CTSRTS;
+	}	
+	if (I_IXON(tty))	
+		flow_ctrl |= ISICOM_RESPOND_XONXOFF;
+	if (I_IXOFF(tty))
+		flow_ctrl |= ISICOM_INITIATE_XONXOFF;	
+		
+	if (lock_card(card)) {
+		outw(0x8000 | (channel << shift_count) |0x04, base);
+		outw(flow_ctrl << 8 | 0x05, base);
+		outw((STOP_CHAR(tty)) << 8 | (START_CHAR(tty)), base);
+		InterruptTheCard(base);
+		unlock_card(card);
+	}
+	
+	/*	rx enabled -> enable port for rx on the card	*/
+	if (C_CREAD(tty)) {
+		card->port_status |= (1 << channel);
+		outw(card->port_status, base + 0x02);
+	}
+}
+ 
+/* open et all */ 
+
+static inline void isicom_setup_board(struct isi_board * bp)
+{
+	int channel;
+	struct isi_port * port;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&bp->card_lock, flags);
+	if (bp->status & BOARD_ACTIVE) {
+		spin_unlock_irqrestore(&bp->card_lock, flags);
+		return;
+	}
+	port = bp->ports;
+	bp->status |= BOARD_ACTIVE;
+	spin_unlock_irqrestore(&bp->card_lock, flags);
+	for(channel = 0; channel < bp->port_count; channel++, port++)
+		drop_dtr_rts(port);
+	return;
+}
+ 
+static int isicom_setup_port(struct isi_port * port)
+{
+	struct isi_board * card = port->card;
+	unsigned long flags;
+	
+	if (port->flags & ASYNC_INITIALIZED) {
+		return 0;
+	}
+	if (!port->xmit_buf) {
+		unsigned long page;
+		
+		if (!(page = get_zeroed_page(GFP_KERNEL)))
+			return -ENOMEM;
+		
+		if (port->xmit_buf) {
+			free_page(page);
+			return -ERESTARTSYS;
+		}
+		port->xmit_buf = (unsigned char *) page;	
+	}	
+
+	spin_lock_irqsave(&card->card_lock, flags);
+	if (port->tty)
+		clear_bit(TTY_IO_ERROR, &port->tty->flags);
+	if (port->count == 1)
+		card->count++;
+		
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	
+	/*	discard any residual data	*/
+	kill_queue(port, ISICOM_KILLTX | ISICOM_KILLRX);
+	
+	isicom_config_port(port);
+	port->flags |= ASYNC_INITIALIZED;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	
+	return 0;		
+} 
+ 
+static int block_til_ready(struct tty_struct * tty, struct file * filp, struct isi_port * port) 
+{
+	struct isi_board * card = port->card;
+	int do_clocal = 0, retval;
+	unsigned long flags;
+	DECLARE_WAITQUEUE(wait, current);
+
+	/* block if port is in the process of being closed */
+
+	if (tty_hung_up_p(filp) || port->flags & ASYNC_CLOSING) {
+#ifdef ISICOM_DEBUG	
+		printk(KERN_DEBUG "ISICOM: block_til_ready: close in progress.\n");
+#endif		
+		interruptible_sleep_on(&port->close_wait);
+		if (port->flags & ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+	}
+	
+	/* if non-blocking mode is set ... */
+	
+	if ((filp->f_flags & O_NONBLOCK) || (tty->flags & (1 << TTY_IO_ERROR))) {
+#ifdef ISICOM_DEBUG	
+		printk(KERN_DEBUG "ISICOM: block_til_ready: non-block mode.\n");
+#endif		
+		port->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;	
+	}	
+	
+	if (C_CLOCAL(tty))
+		do_clocal = 1;
+	
+	/* block waiting for DCD to be asserted, and while 
+						callout dev is busy */
+	retval = 0;
+	add_wait_queue(&port->open_wait, &wait);
+
+	spin_lock_irqsave(&card->card_lock, flags);
+	if (!tty_hung_up_p(filp))
+		port->count--;
+	port->blocked_open++;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	
+	while (1) {
+		raise_dtr_rts(port);
+
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) || !(port->flags & ASYNC_INITIALIZED)) { 	
+			if (port->flags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;
+			break;
+		}	
+		if (!(port->flags & ASYNC_CLOSING) &&
+		    (do_clocal || (port->status & ISI_DCD))) {
+			break;
+		}	
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();		
+	}
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&port->open_wait, &wait);
+	spin_lock_irqsave(&card->card_lock, flags);
+	if (!tty_hung_up_p(filp))
+		port->count++;
+	port->blocked_open--;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	if (retval)
+		return retval;
+	port->flags |= ASYNC_NORMAL_ACTIVE;
+	return 0;
+}
+ 
+static int isicom_open(struct tty_struct * tty, struct file * filp)
+{
+	struct isi_port * port;
+	struct isi_board * card;
+	unsigned int line, board;
+	int error;
+
+	line = tty->index;
+	if (line < 0 || line > PORT_COUNT-1)
+		return -ENODEV;
+	board = BOARD(line);
+	card = &isi_card[board];
+	
+	if (!(card->status & FIRMWARE_LOADED))
+		return -ENODEV;
+	
+	/*  open on a port greater than the port count for the card !!! */
+	if (line > ((board * 16) + card->port_count - 1))
+		return -ENODEV;
+
+	port = &isi_ports[line];	
+	if (isicom_paranoia_check(port, tty->name, "isicom_open"))
+		return -ENODEV;
+		
+	isicom_setup_board(card);		
+	
+	port->count++;
+	tty->driver_data = port;
+	port->tty = tty;
+	if ((error = isicom_setup_port(port))!=0)
+		return error;
+	if ((error = block_til_ready(tty, filp, port))!=0)
+		return error;
+
+	return 0;      		
+}
+ 
+/* close et all */
+
+static inline void isicom_shutdown_board(struct isi_board * bp)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&bp->card_lock, flags);
+	if (bp->status & BOARD_ACTIVE) {
+		bp->status &= ~BOARD_ACTIVE;
+	}
+	spin_unlock_irqrestore(&bp->card_lock, flags);
+}
+
+static void isicom_shutdown_port(struct isi_port * port)
+{
+	struct isi_board * card = port->card;
+	struct tty_struct * tty;	
+	unsigned long flags;
+	
+	tty = port->tty;
+
+	spin_lock_irqsave(&card->card_lock, flags);	
+	if (!(port->flags & ASYNC_INITIALIZED)) {
+		spin_unlock_irqrestore(&card->card_lock, flags);
+		return;
+	}
+	if (port->xmit_buf) {
+		free_page((unsigned long) port->xmit_buf);
+		port->xmit_buf = NULL;
+	}	
+	port->flags &= ~ASYNC_INITIALIZED;
+	/* 3rd October 2000 : Vinayak P Risbud */
+	port->tty = NULL;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	
+	/*Fix done by Anil .S on 30-04-2001
+	remote login through isi port has dtr toggle problem
+	due to which the carrier drops before the password prompt
+	appears on the remote end. Now we drop the dtr only if the 
+	HUPCL(Hangup on close) flag is set for the tty*/
+	
+	if (C_HUPCL(tty)) 
+		/* drop dtr on this port */
+		drop_dtr(port);
+		
+	/* any other port uninits  */ 
+	if (tty)
+		set_bit(TTY_IO_ERROR, &tty->flags);
+	
+	if (--card->count < 0) {
+		printk(KERN_DEBUG "ISICOM: isicom_shutdown_port: bad board(0x%x) count %d.\n",
+			card->base, card->count);
+		card->count = 0;	
+	}
+	
+	/* last port was closed , shutdown that boad too */
+	if(C_HUPCL(tty)) {
+		if (!card->count)
+			isicom_shutdown_board(card);
+	}
+}
+
+static void isicom_close(struct tty_struct * tty, struct file * filp)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	struct isi_board * card = port->card;
+	unsigned long flags;
+	
+	if (!port)
+		return;
+	if (isicom_paranoia_check(port, tty->name, "isicom_close"))
+		return;
+	
+#ifdef ISICOM_DEBUG		
+	printk(KERN_DEBUG "ISICOM: Close start!!!.\n");
+#endif	
+	
+	spin_lock_irqsave(&card->card_lock, flags);
+	if (tty_hung_up_p(filp)) {
+		spin_unlock_irqrestore(&card->card_lock, flags);
+		return;
+	}
+	
+	if (tty->count == 1 && port->count != 1) {
+		printk(KERN_WARNING "ISICOM:(0x%x) isicom_close: bad port count"
+			"tty->count = 1	port count = %d.\n",
+			card->base, port->count);
+		port->count = 1;
+	}
+	if (--port->count < 0) {
+		printk(KERN_WARNING "ISICOM:(0x%x) isicom_close: bad port count for"
+			"channel%d = %d", card->base, port->channel, 
+			port->count);
+		port->count = 0;	
+	}
+	
+	if (port->count) {
+		spin_unlock_irqrestore(&card->card_lock, flags);
+		return;
+	} 	
+	port->flags |= ASYNC_CLOSING;
+	tty->closing = 1;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	
+	if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, port->closing_wait);
+	/* indicate to the card that no more data can be received 
+	   on this port */
+	spin_lock_irqsave(&card->card_lock, flags);
+	if (port->flags & ASYNC_INITIALIZED) {   
+		card->port_status &= ~(1 << port->channel);
+		outw(card->port_status, card->base + 0x02);
+	}	
+	isicom_shutdown_port(port);
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+	tty_ldisc_flush(tty);
+
+	spin_lock_irqsave(&card->card_lock, flags);
+	tty->closing = 0;
+
+	if (port->blocked_open) {
+		spin_unlock_irqrestore(&card->card_lock, flags);
+		if (port->close_delay) {
+#ifdef ISICOM_DEBUG			
+			printk(KERN_DEBUG "ISICOM: scheduling until time out.\n");
+#endif			
+			msleep_interruptible(jiffies_to_msecs(port->close_delay));
+		}
+		spin_lock_irqsave(&card->card_lock, flags);
+		wake_up_interruptible(&port->open_wait);
+	}	
+	port->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
+	wake_up_interruptible(&port->close_wait);
+	spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/* write et all */
+static int isicom_write(struct tty_struct * tty,
+			const unsigned char * buf, int count)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	struct isi_board * card = port->card;
+	unsigned long flags;
+	int cnt, total = 0;
+
+	if (isicom_paranoia_check(port, tty->name, "isicom_write"))
+		return 0;
+	
+	if (!tty || !port->xmit_buf || !tmp_buf)
+		return 0;
+		
+	spin_lock_irqsave(&card->card_lock, flags);
+	
+	while(1) {	
+		cnt = min_t(int, count, min(SERIAL_XMIT_SIZE - port->xmit_cnt - 1,
+					    SERIAL_XMIT_SIZE - port->xmit_head));
+		if (cnt <= 0) 
+			break;
+		
+		memcpy(port->xmit_buf + port->xmit_head, buf, cnt);
+		port->xmit_head = (port->xmit_head + cnt) & (SERIAL_XMIT_SIZE - 1);
+		port->xmit_cnt += cnt;
+		buf += cnt;
+		count -= cnt;
+		total += cnt;
+	}		
+	if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped)
+		port->status |= ISI_TXOK;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	return total;	
+}
+
+/* put_char et all */
+static void isicom_put_char(struct tty_struct * tty, unsigned char ch)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	struct isi_board * card = port->card;
+	unsigned long flags;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_put_char"))
+		return;
+	
+	if (!tty || !port->xmit_buf)
+		return;
+
+	spin_lock_irqsave(&card->card_lock, flags);
+	if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+		spin_unlock_irqrestore(&card->card_lock, flags);
+		return;
+	}
+	
+	port->xmit_buf[port->xmit_head++] = ch;
+	port->xmit_head &= (SERIAL_XMIT_SIZE - 1);
+	port->xmit_cnt++;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+}
+
+/* flush_chars et all */
+static void isicom_flush_chars(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_flush_chars"))
+		return;
+	
+	if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped || !port->xmit_buf)
+		return;
+		
+	/* this tells the transmitter to consider this port for
+	   data output to the card ... that's the best we can do. */
+	port->status |= ISI_TXOK;	
+}
+
+/* write_room et all */
+static int isicom_write_room(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	int free;
+
+	if (isicom_paranoia_check(port, tty->name, "isicom_write_room"))
+		return 0;
+	
+	free = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+	if (free < 0)
+		free = 0;
+	return free;
+}
+
+/* chars_in_buffer et all */
+static int isicom_chars_in_buffer(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	if (isicom_paranoia_check(port, tty->name, "isicom_chars_in_buffer"))
+		return 0;
+	return port->xmit_cnt;
+}
+
+/* ioctl et all */
+static inline void isicom_send_break(struct isi_port * port, unsigned long length)
+{
+	struct isi_board * card = port->card;
+	unsigned short base = card->base;	
+	
+	if(!lock_card(card))
+		return;
+		
+	outw(0x8000 | ((port->channel) << (card->shift_count)) | 0x3, base);
+	outw((length & 0xff) << 8 | 0x00, base);
+	outw((length & 0xff00), base);
+	InterruptTheCard(base);
+
+	unlock_card(card);
+}
+
+static int isicom_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	/* just send the port status */
+	unsigned short status = port->status;
+
+	if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
+		return -ENODEV;
+	
+	return  ((status & ISI_RTS) ? TIOCM_RTS : 0) |
+		((status & ISI_DTR) ? TIOCM_DTR : 0) |
+		((status & ISI_DCD) ? TIOCM_CAR : 0) |
+		((status & ISI_DSR) ? TIOCM_DSR : 0) |
+		((status & ISI_CTS) ? TIOCM_CTS : 0) |
+		((status & ISI_RI ) ? TIOCM_RI  : 0);
+}
+
+static int isicom_tiocmset(struct tty_struct *tty, struct file *file,
+			   unsigned int set, unsigned int clear)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
+		return -ENODEV;
+	
+	if (set & TIOCM_RTS)
+		raise_rts(port);
+	if (set & TIOCM_DTR)
+		raise_dtr(port);
+
+	if (clear & TIOCM_RTS)
+		drop_rts(port);
+	if (clear & TIOCM_DTR)
+		drop_dtr(port);
+
+	return 0;
+}			
+
+static int isicom_set_serial_info(struct isi_port * port,
+					struct serial_struct __user *info)
+{
+	struct serial_struct newinfo;
+	int reconfig_port;
+
+	if(copy_from_user(&newinfo, info, sizeof(newinfo)))
+		return -EFAULT;
+		
+	reconfig_port = ((port->flags & ASYNC_SPD_MASK) != 
+			 (newinfo.flags & ASYNC_SPD_MASK));
+	
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((newinfo.close_delay != port->close_delay) ||
+		    (newinfo.closing_wait != port->closing_wait) ||
+		    ((newinfo.flags & ~ASYNC_USR_MASK) != 
+		     (port->flags & ~ASYNC_USR_MASK)))
+			return -EPERM;
+		port->flags = ((port->flags & ~ ASYNC_USR_MASK) |
+				(newinfo.flags & ASYNC_USR_MASK));
+	}	
+	else {
+		port->close_delay = newinfo.close_delay;
+		port->closing_wait = newinfo.closing_wait; 
+		port->flags = ((port->flags & ~ASYNC_FLAGS) | 
+				(newinfo.flags & ASYNC_FLAGS));
+	}
+	if (reconfig_port) {
+		isicom_config_port(port);
+	}
+	return 0;		 
+}		
+
+static int isicom_get_serial_info(struct isi_port * port, 
+					struct serial_struct __user *info)
+{
+	struct serial_struct out_info;
+	
+	memset(&out_info, 0, sizeof(out_info));
+/*	out_info.type = ? */
+	out_info.line = port - isi_ports;
+	out_info.port = port->card->base;
+	out_info.irq = port->card->irq;
+	out_info.flags = port->flags;
+/*	out_info.baud_base = ? */
+	out_info.close_delay = port->close_delay;
+	out_info.closing_wait = port->closing_wait;
+	if(copy_to_user(info, &out_info, sizeof(out_info)))
+		return -EFAULT;
+	return 0;
+}					
+
+static int isicom_ioctl(struct tty_struct * tty, struct file * filp,
+			unsigned int cmd, unsigned long arg) 
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	void __user *argp = (void __user *)arg;
+	int retval;
+
+	if (isicom_paranoia_check(port, tty->name, "isicom_ioctl"))
+		return -ENODEV;
+
+	switch(cmd) {
+		case TCSBRK:
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+			tty_wait_until_sent(tty, 0);
+			if (!arg)
+				isicom_send_break(port, HZ/4);
+			return 0;
+			
+		case TCSBRKP:	
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+			tty_wait_until_sent(tty, 0);
+			isicom_send_break(port, arg ? arg * (HZ/10) : HZ/4);
+			return 0;
+			
+		case TIOCGSOFTCAR:
+			return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *)argp);
+			
+		case TIOCSSOFTCAR:
+			if(get_user(arg, (unsigned long __user *) argp))
+				return -EFAULT;
+			tty->termios->c_cflag =
+				((tty->termios->c_cflag & ~CLOCAL) |
+				(arg ? CLOCAL : 0));
+			return 0;	
+			
+		case TIOCGSERIAL:
+			return isicom_get_serial_info(port, argp);
+		
+		case TIOCSSERIAL:
+			return isicom_set_serial_info(port, argp);
+					
+		default:
+			return -ENOIOCTLCMD;						
+	}
+	return 0;
+}
+
+/* set_termios et all */
+static void isicom_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_set_termios"))
+		return;
+	
+	if (tty->termios->c_cflag == old_termios->c_cflag &&
+	    tty->termios->c_iflag == old_termios->c_iflag)
+		return;
+		
+	isicom_config_port(port);
+	
+	if ((old_termios->c_cflag & CRTSCTS) &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {	
+		tty->hw_stopped = 0;
+		isicom_start(tty);   
+	}    
+}
+
+/* throttle et all */
+static void isicom_throttle(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	struct isi_board * card = port->card;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_throttle"))
+		return;
+	
+	/* tell the card that this port cannot handle any more data for now */
+	card->port_status &= ~(1 << port->channel);
+	outw(card->port_status, card->base + 0x02);
+}
+
+/* unthrottle et all */
+static void isicom_unthrottle(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	struct isi_board * card = port->card;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_unthrottle"))
+		return;
+	
+	/* tell the card that this port is ready to accept more data */
+	card->port_status |= (1 << port->channel);
+	outw(card->port_status, card->base + 0x02);
+}
+
+/* stop et all */
+static void isicom_stop(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+
+	if (isicom_paranoia_check(port, tty->name, "isicom_stop"))
+		return;
+	
+	/* this tells the transmitter not to consider this port for
+	   data output to the card. */
+	port->status &= ~ISI_TXOK;
+}
+
+/* start et all */
+static void isicom_start(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_start"))
+		return;
+	
+	/* this tells the transmitter to consider this port for
+	   data output to the card. */
+	port->status |= ISI_TXOK;
+}
+
+/* hangup et all */
+static void do_isicom_hangup(void * data)
+{
+	struct isi_port * port = (struct isi_port *) data;
+	struct tty_struct * tty;
+	
+	tty = port->tty;
+	if (tty)
+		tty_hangup(tty);
+}
+
+static void isicom_hangup(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_hangup"))
+		return;
+	
+	isicom_shutdown_port(port);
+	port->count = 0;
+	port->flags &= ~ASYNC_NORMAL_ACTIVE;
+	port->tty = NULL;
+	wake_up_interruptible(&port->open_wait);
+}
+
+/* flush_buffer et all */
+static void isicom_flush_buffer(struct tty_struct * tty)
+{
+	struct isi_port * port = (struct isi_port *) tty->driver_data;
+	struct isi_board * card = port->card;
+	unsigned long flags;
+	
+	if (isicom_paranoia_check(port, tty->name, "isicom_flush_buffer"))
+		return;
+	
+	spin_lock_irqsave(&card->card_lock, flags);
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	spin_unlock_irqrestore(&card->card_lock, flags);
+	
+	wake_up_interruptible(&tty->write_wait);
+	tty_wakeup(tty);
+}
+
+
+static int __init register_ioregion(void)
+{
+	int count, done=0;
+	for (count=0; count < BOARD_COUNT; count++ ) {
+		if (isi_card[count].base)
+			if (!request_region(isi_card[count].base,16,ISICOM_NAME)) {
+				printk(KERN_DEBUG "ISICOM: I/O Region 0x%x-0x%x is busy. Card%d will be disabled.\n",
+					isi_card[count].base,isi_card[count].base+15,count+1);
+				isi_card[count].base=0;
+				done++;
+			}
+	}
+	return done;
+}
+
+static void __exit unregister_ioregion(void)
+{
+	int count;
+	for (count=0; count < BOARD_COUNT; count++ ) 
+		if (isi_card[count].base) {
+			release_region(isi_card[count].base,16);
+#ifdef ISICOM_DEBUG			
+			printk(KERN_DEBUG "ISICOM: I/O Region 0x%x-0x%x released for Card%d.\n",isi_card[count].base,isi_card[count].base+15,count+1);
+#endif			
+		}
+}
+
+static struct tty_operations isicom_ops = {
+	.open	= isicom_open,
+	.close	= isicom_close,
+	.write	= isicom_write,
+	.put_char	= isicom_put_char,
+	.flush_chars	= isicom_flush_chars,
+	.write_room	= isicom_write_room,
+	.chars_in_buffer	= isicom_chars_in_buffer,
+	.ioctl	= isicom_ioctl,
+	.set_termios	= isicom_set_termios,
+	.throttle	= isicom_throttle,
+	.unthrottle	= isicom_unthrottle,
+	.stop	= isicom_stop,
+	.start	= isicom_start,
+	.hangup	= isicom_hangup,
+	.flush_buffer	= isicom_flush_buffer,
+	.tiocmget	= isicom_tiocmget,
+	.tiocmset	= isicom_tiocmset,
+};
+
+static int __init register_drivers(void)
+{
+	int error;
+
+	/* tty driver structure initialization */
+	isicom_normal = alloc_tty_driver(PORT_COUNT);
+	if (!isicom_normal)
+		return -ENOMEM;
+
+	isicom_normal->owner	= THIS_MODULE;
+	isicom_normal->name 	= "ttyM";
+	isicom_normal->devfs_name = "isicom/";
+	isicom_normal->major	= ISICOM_NMAJOR;
+	isicom_normal->minor_start	= 0;
+	isicom_normal->type	= TTY_DRIVER_TYPE_SERIAL;
+	isicom_normal->subtype	= SERIAL_TYPE_NORMAL;
+	isicom_normal->init_termios	= tty_std_termios;
+	isicom_normal->init_termios.c_cflag	= 
+				B9600 | CS8 | CREAD | HUPCL |CLOCAL;
+	isicom_normal->flags	= TTY_DRIVER_REAL_RAW;
+	tty_set_operations(isicom_normal, &isicom_ops);
+	
+	if ((error=tty_register_driver(isicom_normal))!=0) {
+		printk(KERN_DEBUG "ISICOM: Couldn't register the dialin driver, error=%d\n",
+			error);
+		put_tty_driver(isicom_normal);
+		return error;
+	}
+	return 0;
+}
+
+static void __exit unregister_drivers(void)
+{
+	int error = tty_unregister_driver(isicom_normal);
+	if (error)
+		printk(KERN_DEBUG "ISICOM: couldn't unregister normal driver error=%d.\n",error);
+	put_tty_driver(isicom_normal);
+}
+
+static int __init register_isr(void)
+{
+	int count, done=0;
+	unsigned long irqflags;
+
+	for (count=0; count < BOARD_COUNT; count++ ) {
+		if (isi_card[count].base) {
+			irqflags = (isi_card[count].isa == YES) ? 
+					SA_INTERRUPT : 
+					(SA_INTERRUPT | SA_SHIRQ);
+
+			if (request_irq(isi_card[count].irq, 
+					isicom_interrupt, 
+					irqflags, 
+					ISICOM_NAME, &isi_card[count])) {
+
+				printk(KERN_WARNING "ISICOM: Could not"
+					" install handler at Irq %d."
+					" Card%d will be disabled.\n",
+					isi_card[count].irq, count+1);
+
+				release_region(isi_card[count].base,16);
+				isi_card[count].base=0;
+			}
+			else
+				done++;
+		}	
+	}
+	return done;
+}
+
+static void __exit unregister_isr(void)
+{
+	int count;
+
+	for (count=0; count < BOARD_COUNT; count++ ) {
+		if (isi_card[count].base)
+			free_irq(isi_card[count].irq, &isi_card[count]);
+	}
+}
+
+static int __init isicom_init(void)
+{
+	int card, channel, base;
+	struct isi_port * port;
+	unsigned long page;
+	
+	if (!tmp_buf) { 
+		page = get_zeroed_page(GFP_KERNEL);
+	      	if (!page) {
+#ifdef ISICOM_DEBUG	      	
+	      		printk(KERN_DEBUG "ISICOM: Couldn't allocate page for tmp_buf.\n");
+#else
+			printk(KERN_ERR "ISICOM: Not enough memory...\n");
+#endif	      
+	      		return 0;
+	      	}	
+	      	tmp_buf = (unsigned char *) page;
+	}
+	
+	if (!register_ioregion()) 
+	{
+		printk(KERN_ERR "ISICOM: All required I/O space found busy.\n");
+		free_page((unsigned long)tmp_buf);
+		return 0;
+	}
+	if (register_drivers()) 
+	{
+		unregister_ioregion();
+		free_page((unsigned long)tmp_buf);
+		return 0;
+	}
+	if (!register_isr()) 
+	{
+		unregister_drivers();
+		/*  ioports already uregistered in register_isr */
+		free_page((unsigned long)tmp_buf);
+		return 0;		
+	}
+	
+	memset(isi_ports, 0, sizeof(isi_ports));
+	for (card = 0; card < BOARD_COUNT; card++) {
+		port = &isi_ports[card * 16];
+		isi_card[card].ports = port;
+		spin_lock_init(&isi_card[card].card_lock);
+		base = isi_card[card].base;
+		for (channel = 0; channel < 16; channel++, port++) {
+			port->magic = ISICOM_MAGIC;
+			port->card = &isi_card[card];
+			port->channel = channel;
+		 	port->close_delay = 50 * HZ/100;
+		 	port->closing_wait = 3000 * HZ/100;
+		 	INIT_WORK(&port->hangup_tq, do_isicom_hangup, port);
+		 	INIT_WORK(&port->bh_tqueue, isicom_bottomhalf, port);
+		 	port->status = 0;
+			init_waitqueue_head(&port->open_wait);	 				
+			init_waitqueue_head(&port->close_wait);
+			/*  . . .  */
+ 		}
+	} 
+	
+	return 1;	
+}
+
+/*
+ *	Insmod can set static symbols so keep these static
+ */
+ 
+static int io[4];
+static int irq[4];
+
+MODULE_AUTHOR("MultiTech");
+MODULE_DESCRIPTION("Driver for the ISI series of cards by MultiTech");
+MODULE_LICENSE("GPL");
+module_param_array(io, int, NULL, 0);
+MODULE_PARM_DESC(io, "I/O ports for the cards");
+module_param_array(irq, int, NULL, 0);
+MODULE_PARM_DESC(irq, "Interrupts for the cards");
+
+static int __devinit isicom_setup(void)
+{
+	struct pci_dev *dev = NULL;
+	int retval, card, idx, count;
+	unsigned char pciirq;
+	unsigned int ioaddr;
+	                
+	card = 0;
+	for(idx=0; idx < BOARD_COUNT; idx++) {	
+		if (io[idx]) {
+			isi_card[idx].base=io[idx];
+			isi_card[idx].irq=irq[idx];
+			isi_card[idx].isa=YES;
+			card++;
+		}
+		else {
+			isi_card[idx].base = 0;
+			isi_card[idx].irq = 0;
+		}
+	}
+	
+	for (idx=0 ;idx < card; idx++) {
+		if (!((isi_card[idx].irq==2)||(isi_card[idx].irq==3)||
+		    (isi_card[idx].irq==4)||(isi_card[idx].irq==5)||
+		    (isi_card[idx].irq==7)||(isi_card[idx].irq==10)||
+		    (isi_card[idx].irq==11)||(isi_card[idx].irq==12)||
+		    (isi_card[idx].irq==15))) {
+			
+			if (isi_card[idx].base) {
+				printk(KERN_ERR "ISICOM: Irq %d unsupported. Disabling Card%d...\n",
+					isi_card[idx].irq, idx+1);
+				isi_card[idx].base=0;
+				card--;
+			}	
+		}
+	}	
+	
+	if (card < BOARD_COUNT) {
+		for (idx=0; idx < DEVID_COUNT; idx++) {
+			dev = NULL;
+			for (;;){
+				if (!(dev = pci_find_device(VENDOR_ID, isicom_pci_tbl[idx].device, dev)))
+					break;
+				if (card >= BOARD_COUNT)
+					break;
+					
+				if (pci_enable_device(dev))
+					break;
+
+				/* found a PCI ISI card! */
+				ioaddr = pci_resource_start (dev, 3); /* i.e at offset 0x1c in the
+								       * PCI configuration register
+								       * space.
+								       */
+				pciirq = dev->irq;
+				printk(KERN_INFO "ISI PCI Card(Device ID 0x%x)\n", isicom_pci_tbl[idx].device);
+				/*
+				 * allot the first empty slot in the array
+				 */				
+				for (count=0; count < BOARD_COUNT; count++) {				
+					if (isi_card[count].base == 0) {
+						isi_card[count].base = ioaddr;
+						isi_card[count].irq = pciirq;
+						isi_card[count].isa = NO;
+						card++;
+						break;
+					}
+				}
+			}				
+			if (card >= BOARD_COUNT) break;
+		}
+	}
+	
+	if (!(isi_card[0].base || isi_card[1].base || isi_card[2].base || isi_card[3].base)) {
+		printk(KERN_ERR "ISICOM: No valid card configuration. Driver cannot be initialized...\n"); 
+		return -EIO;
+	}	
+
+	retval = misc_register(&isiloader_device);
+	if (retval < 0) {
+		printk(KERN_ERR "ISICOM: Unable to register firmware loader driver.\n");
+		return retval;
+	}
+	
+	if (!isicom_init()) {
+		if (misc_deregister(&isiloader_device)) 
+			printk(KERN_ERR "ISICOM: Unable to unregister Firmware Loader driver\n");
+		return -EIO;
+	}
+	
+	init_timer(&tx);
+	tx.expires = jiffies + 1;
+	tx.data = 0;
+	tx.function = isicom_tx;
+	re_schedule = 1;
+	add_timer(&tx);
+	
+	return 0;
+}
+
+static void __exit isicom_exit(void)
+{
+	re_schedule = 0;
+	/* FIXME */
+	msleep(1000);
+	unregister_isr();
+	unregister_drivers();
+	unregister_ioregion();	
+	if(tmp_buf)
+		free_page((unsigned long)tmp_buf);
+	if (misc_deregister(&isiloader_device))
+		printk(KERN_ERR "ISICOM: Unable to unregister Firmware Loader driver\n");
+}
+
+module_init(isicom_setup);
+module_exit(isicom_exit);
diff --git a/drivers/char/istallion.c b/drivers/char/istallion.c
new file mode 100644
index 0000000..21aed0e
--- /dev/null
+++ b/drivers/char/istallion.c
@@ -0,0 +1,5276 @@
+/*****************************************************************************/
+
+/*
+ *	istallion.c  -- stallion intelligent multiport serial driver.
+ *
+ *	Copyright (C) 1996-1999  Stallion Technologies
+ *	Copyright (C) 1994-1996  Greg Ungerer.
+ *
+ *	This code is loosely based on the Linux serial driver, written by
+ *	Linus Torvalds, Theodore T'so and others.
+ *
+ *	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.
+ */
+
+/*****************************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/cdk.h>
+#include <linux/comstats.h>
+#include <linux/istallion.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Define different board types. Not all of the following board types
+ *	are supported by this driver. But I will use the standard "assigned"
+ *	board numbers. Currently supported boards are abbreviated as:
+ *	ECP = EasyConnection 8/64, ONB = ONboard, BBY = Brumby and
+ *	STAL = Stallion.
+ */
+#define	BRD_UNKNOWN	0
+#define	BRD_STALLION	1
+#define	BRD_BRUMBY4	2
+#define	BRD_ONBOARD2	3
+#define	BRD_ONBOARD	4
+#define	BRD_BRUMBY8	5
+#define	BRD_BRUMBY16	6
+#define	BRD_ONBOARDE	7
+#define	BRD_ONBOARD32	9
+#define	BRD_ONBOARD2_32	10
+#define	BRD_ONBOARDRS	11
+#define	BRD_EASYIO	20
+#define	BRD_ECH		21
+#define	BRD_ECHMC	22
+#define	BRD_ECP		23
+#define BRD_ECPE	24
+#define	BRD_ECPMC	25
+#define	BRD_ECHPCI	26
+#define	BRD_ECH64PCI	27
+#define	BRD_EASYIOPCI	28
+#define	BRD_ECPPCI	29
+
+#define	BRD_BRUMBY	BRD_BRUMBY4
+
+/*
+ *	Define a configuration structure to hold the board configuration.
+ *	Need to set this up in the code (for now) with the boards that are
+ *	to be configured into the system. This is what needs to be modified
+ *	when adding/removing/modifying boards. Each line entry in the
+ *	stli_brdconf[] array is a board. Each line contains io/irq/memory
+ *	ranges for that board (as well as what type of board it is).
+ *	Some examples:
+ *		{ BRD_ECP, 0x2a0, 0, 0xcc000, 0, 0 },
+ *	This line will configure an EasyConnection 8/64 at io address 2a0,
+ *	and shared memory address of cc000. Multiple EasyConnection 8/64
+ *	boards can share the same shared memory address space. No interrupt
+ *	is required for this board type.
+ *	Another example:
+ *		{ BRD_ECPE, 0x5000, 0, 0x80000000, 0, 0 },
+ *	This line will configure an EasyConnection 8/64 EISA in slot 5 and
+ *	shared memory address of 0x80000000 (2 GByte). Multiple
+ *	EasyConnection 8/64 EISA boards can share the same shared memory
+ *	address space. No interrupt is required for this board type.
+ *	Another example:
+ *		{ BRD_ONBOARD, 0x240, 0, 0xd0000, 0, 0 },
+ *	This line will configure an ONboard (ISA type) at io address 240,
+ *	and shared memory address of d0000. Multiple ONboards can share
+ *	the same shared memory address space. No interrupt required.
+ *	Another example:
+ *		{ BRD_BRUMBY4, 0x360, 0, 0xc8000, 0, 0 },
+ *	This line will configure a Brumby board (any number of ports!) at
+ *	io address 360 and shared memory address of c8000. All Brumby boards
+ *	configured into a system must have their own separate io and memory
+ *	addresses. No interrupt is required.
+ *	Another example:
+ *		{ BRD_STALLION, 0x330, 0, 0xd0000, 0, 0 },
+ *	This line will configure an original Stallion board at io address 330
+ *	and shared memory address d0000 (this would only be valid for a "V4.0"
+ *	or Rev.O Stallion board). All Stallion boards configured into the
+ *	system must have their own separate io and memory addresses. No
+ *	interrupt is required.
+ */
+
+typedef struct {
+	int		brdtype;
+	int		ioaddr1;
+	int		ioaddr2;
+	unsigned long	memaddr;
+	int		irq;
+	int		irqtype;
+} stlconf_t;
+
+static stlconf_t	stli_brdconf[] = {
+	/*{ BRD_ECP, 0x2a0, 0, 0xcc000, 0, 0 },*/
+};
+
+static int	stli_nrbrds = sizeof(stli_brdconf) / sizeof(stlconf_t);
+
+/*
+ *	There is some experimental EISA board detection code in this driver.
+ *	By default it is disabled, but for those that want to try it out,
+ *	then set the define below to be 1.
+ */
+#define	STLI_EISAPROBE	0
+
+/*****************************************************************************/
+
+/*
+ *	Define some important driver characteristics. Device major numbers
+ *	allocated as per Linux Device Registry.
+ */
+#ifndef	STL_SIOMEMMAJOR
+#define	STL_SIOMEMMAJOR		28
+#endif
+#ifndef	STL_SERIALMAJOR
+#define	STL_SERIALMAJOR		24
+#endif
+#ifndef	STL_CALLOUTMAJOR
+#define	STL_CALLOUTMAJOR	25
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Define our local driver identity first. Set up stuff to deal with
+ *	all the local structures required by a serial tty driver.
+ */
+static char	*stli_drvtitle = "Stallion Intelligent Multiport Serial Driver";
+static char	*stli_drvname = "istallion";
+static char	*stli_drvversion = "5.6.0";
+static char	*stli_serialname = "ttyE";
+
+static struct tty_driver	*stli_serial;
+
+/*
+ *	We will need to allocate a temporary write buffer for chars that
+ *	come direct from user space. The problem is that a copy from user
+ *	space might cause a page fault (typically on a system that is
+ *	swapping!). All ports will share one buffer - since if the system
+ *	is already swapping a shared buffer won't make things any worse.
+ */
+static char			*stli_tmpwritebuf;
+static DECLARE_MUTEX(stli_tmpwritesem);
+
+#define	STLI_TXBUFSIZE		4096
+
+/*
+ *	Use a fast local buffer for cooked characters. Typically a whole
+ *	bunch of cooked characters come in for a port, 1 at a time. So we
+ *	save those up into a local buffer, then write out the whole lot
+ *	with a large memcpy. Just use 1 buffer for all ports, since its
+ *	use it is only need for short periods of time by each port.
+ */
+static char			*stli_txcookbuf;
+static int			stli_txcooksize;
+static int			stli_txcookrealsize;
+static struct tty_struct	*stli_txcooktty;
+
+/*
+ *	Define a local default termios struct. All ports will be created
+ *	with this termios initially. Basically all it defines is a raw port
+ *	at 9600 baud, 8 data bits, no parity, 1 stop bit.
+ */
+static struct termios		stli_deftermios = {
+	.c_cflag	= (B9600 | CS8 | CREAD | HUPCL | CLOCAL),
+	.c_cc		= INIT_C_CC,
+};
+
+/*
+ *	Define global stats structures. Not used often, and can be
+ *	re-used for each stats call.
+ */
+static comstats_t	stli_comstats;
+static combrd_t		stli_brdstats;
+static asystats_t	stli_cdkstats;
+static stlibrd_t	stli_dummybrd;
+static stliport_t	stli_dummyport;
+
+/*****************************************************************************/
+
+static stlibrd_t	*stli_brds[STL_MAXBRDS];
+
+static int		stli_shared;
+
+/*
+ *	Per board state flags. Used with the state field of the board struct.
+ *	Not really much here... All we need to do is keep track of whether
+ *	the board has been detected, and whether it is actually running a slave
+ *	or not.
+ */
+#define	BST_FOUND	0x1
+#define	BST_STARTED	0x2
+
+/*
+ *	Define the set of port state flags. These are marked for internal
+ *	state purposes only, usually to do with the state of communications
+ *	with the slave. Most of them need to be updated atomically, so always
+ *	use the bit setting operations (unless protected by cli/sti).
+ */
+#define	ST_INITIALIZING	1
+#define	ST_OPENING	2
+#define	ST_CLOSING	3
+#define	ST_CMDING	4
+#define	ST_TXBUSY	5
+#define	ST_RXING	6
+#define	ST_DOFLUSHRX	7
+#define	ST_DOFLUSHTX	8
+#define	ST_DOSIGS	9
+#define	ST_RXSTOP	10
+#define	ST_GETSIGS	11
+
+/*
+ *	Define an array of board names as printable strings. Handy for
+ *	referencing boards when printing trace and stuff.
+ */
+static char	*stli_brdnames[] = {
+	"Unknown",
+	"Stallion",
+	"Brumby",
+	"ONboard-MC",
+	"ONboard",
+	"Brumby",
+	"Brumby",
+	"ONboard-EI",
+	(char *) NULL,
+	"ONboard",
+	"ONboard-MC",
+	"ONboard-MC",
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	"EasyIO",
+	"EC8/32-AT",
+	"EC8/32-MC",
+	"EC8/64-AT",
+	"EC8/64-EI",
+	"EC8/64-MC",
+	"EC8/32-PCI",
+	"EC8/64-PCI",
+	"EasyIO-PCI",
+	"EC/RA-PCI",
+};
+
+/*****************************************************************************/
+
+#ifdef MODULE
+/*
+ *	Define some string labels for arguments passed from the module
+ *	load line. These allow for easy board definitions, and easy
+ *	modification of the io, memory and irq resoucres.
+ */
+
+static char	*board0[8];
+static char	*board1[8];
+static char	*board2[8];
+static char	*board3[8];
+
+static char	**stli_brdsp[] = {
+	(char **) &board0,
+	(char **) &board1,
+	(char **) &board2,
+	(char **) &board3
+};
+
+/*
+ *	Define a set of common board names, and types. This is used to
+ *	parse any module arguments.
+ */
+
+typedef struct stlibrdtype {
+	char	*name;
+	int	type;
+} stlibrdtype_t;
+
+static stlibrdtype_t	stli_brdstr[] = {
+	{ "stallion", BRD_STALLION },
+	{ "1", BRD_STALLION },
+	{ "brumby", BRD_BRUMBY },
+	{ "brumby4", BRD_BRUMBY },
+	{ "brumby/4", BRD_BRUMBY },
+	{ "brumby-4", BRD_BRUMBY },
+	{ "brumby8", BRD_BRUMBY },
+	{ "brumby/8", BRD_BRUMBY },
+	{ "brumby-8", BRD_BRUMBY },
+	{ "brumby16", BRD_BRUMBY },
+	{ "brumby/16", BRD_BRUMBY },
+	{ "brumby-16", BRD_BRUMBY },
+	{ "2", BRD_BRUMBY },
+	{ "onboard2", BRD_ONBOARD2 },
+	{ "onboard-2", BRD_ONBOARD2 },
+	{ "onboard/2", BRD_ONBOARD2 },
+	{ "onboard-mc", BRD_ONBOARD2 },
+	{ "onboard/mc", BRD_ONBOARD2 },
+	{ "onboard-mca", BRD_ONBOARD2 },
+	{ "onboard/mca", BRD_ONBOARD2 },
+	{ "3", BRD_ONBOARD2 },
+	{ "onboard", BRD_ONBOARD },
+	{ "onboardat", BRD_ONBOARD },
+	{ "4", BRD_ONBOARD },
+	{ "onboarde", BRD_ONBOARDE },
+	{ "onboard-e", BRD_ONBOARDE },
+	{ "onboard/e", BRD_ONBOARDE },
+	{ "onboard-ei", BRD_ONBOARDE },
+	{ "onboard/ei", BRD_ONBOARDE },
+	{ "7", BRD_ONBOARDE },
+	{ "ecp", BRD_ECP },
+	{ "ecpat", BRD_ECP },
+	{ "ec8/64", BRD_ECP },
+	{ "ec8/64-at", BRD_ECP },
+	{ "ec8/64-isa", BRD_ECP },
+	{ "23", BRD_ECP },
+	{ "ecpe", BRD_ECPE },
+	{ "ecpei", BRD_ECPE },
+	{ "ec8/64-e", BRD_ECPE },
+	{ "ec8/64-ei", BRD_ECPE },
+	{ "24", BRD_ECPE },
+	{ "ecpmc", BRD_ECPMC },
+	{ "ec8/64-mc", BRD_ECPMC },
+	{ "ec8/64-mca", BRD_ECPMC },
+	{ "25", BRD_ECPMC },
+	{ "ecppci", BRD_ECPPCI },
+	{ "ec/ra", BRD_ECPPCI },
+	{ "ec/ra-pc", BRD_ECPPCI },
+	{ "ec/ra-pci", BRD_ECPPCI },
+	{ "29", BRD_ECPPCI },
+};
+
+/*
+ *	Define the module agruments.
+ */
+MODULE_AUTHOR("Greg Ungerer");
+MODULE_DESCRIPTION("Stallion Intelligent Multiport Serial Driver");
+MODULE_LICENSE("GPL");
+
+
+MODULE_PARM(board0, "1-3s");
+MODULE_PARM_DESC(board0, "Board 0 config -> name[,ioaddr[,memaddr]");
+MODULE_PARM(board1, "1-3s");
+MODULE_PARM_DESC(board1, "Board 1 config -> name[,ioaddr[,memaddr]");
+MODULE_PARM(board2, "1-3s");
+MODULE_PARM_DESC(board2, "Board 2 config -> name[,ioaddr[,memaddr]");
+MODULE_PARM(board3, "1-3s");
+MODULE_PARM_DESC(board3, "Board 3 config -> name[,ioaddr[,memaddr]");
+
+#endif
+
+/*
+ *	Set up a default memory address table for EISA board probing.
+ *	The default addresses are all bellow 1Mbyte, which has to be the
+ *	case anyway. They should be safe, since we only read values from
+ *	them, and interrupts are disabled while we do it. If the higher
+ *	memory support is compiled in then we also try probing around
+ *	the 1Gb, 2Gb and 3Gb areas as well...
+ */
+static unsigned long	stli_eisamemprobeaddrs[] = {
+	0xc0000,    0xd0000,    0xe0000,    0xf0000,
+	0x80000000, 0x80010000, 0x80020000, 0x80030000,
+	0x40000000, 0x40010000, 0x40020000, 0x40030000,
+	0xc0000000, 0xc0010000, 0xc0020000, 0xc0030000,
+	0xff000000, 0xff010000, 0xff020000, 0xff030000,
+};
+
+static int	stli_eisamempsize = sizeof(stli_eisamemprobeaddrs) / sizeof(unsigned long);
+int		stli_eisaprobe = STLI_EISAPROBE;
+
+/*
+ *	Define the Stallion PCI vendor and device IDs.
+ */
+#ifdef CONFIG_PCI
+#ifndef	PCI_VENDOR_ID_STALLION
+#define	PCI_VENDOR_ID_STALLION		0x124d
+#endif
+#ifndef PCI_DEVICE_ID_ECRA
+#define	PCI_DEVICE_ID_ECRA		0x0004
+#endif
+
+static struct pci_device_id istallion_pci_tbl[] = {
+	{ PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECRA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+	{ 0 }
+};
+MODULE_DEVICE_TABLE(pci, istallion_pci_tbl);
+
+#endif /* CONFIG_PCI */
+
+/*****************************************************************************/
+
+/*
+ *	Hardware configuration info for ECP boards. These defines apply
+ *	to the directly accessible io ports of the ECP. There is a set of
+ *	defines for each ECP board type, ISA, EISA, MCA and PCI.
+ */
+#define	ECP_IOSIZE	4
+
+#define	ECP_MEMSIZE	(128 * 1024)
+#define	ECP_PCIMEMSIZE	(256 * 1024)
+
+#define	ECP_ATPAGESIZE	(4 * 1024)
+#define	ECP_MCPAGESIZE	(4 * 1024)
+#define	ECP_EIPAGESIZE	(64 * 1024)
+#define	ECP_PCIPAGESIZE	(64 * 1024)
+
+#define	STL_EISAID	0x8c4e
+
+/*
+ *	Important defines for the ISA class of ECP board.
+ */
+#define	ECP_ATIREG	0
+#define	ECP_ATCONFR	1
+#define	ECP_ATMEMAR	2
+#define	ECP_ATMEMPR	3
+#define	ECP_ATSTOP	0x1
+#define	ECP_ATINTENAB	0x10
+#define	ECP_ATENABLE	0x20
+#define	ECP_ATDISABLE	0x00
+#define	ECP_ATADDRMASK	0x3f000
+#define	ECP_ATADDRSHFT	12
+
+/*
+ *	Important defines for the EISA class of ECP board.
+ */
+#define	ECP_EIIREG	0
+#define	ECP_EIMEMARL	1
+#define	ECP_EICONFR	2
+#define	ECP_EIMEMARH	3
+#define	ECP_EIENABLE	0x1
+#define	ECP_EIDISABLE	0x0
+#define	ECP_EISTOP	0x4
+#define	ECP_EIEDGE	0x00
+#define	ECP_EILEVEL	0x80
+#define	ECP_EIADDRMASKL	0x00ff0000
+#define	ECP_EIADDRSHFTL	16
+#define	ECP_EIADDRMASKH	0xff000000
+#define	ECP_EIADDRSHFTH	24
+#define	ECP_EIBRDENAB	0xc84
+
+#define	ECP_EISAID	0x4
+
+/*
+ *	Important defines for the Micro-channel class of ECP board.
+ *	(It has a lot in common with the ISA boards.)
+ */
+#define	ECP_MCIREG	0
+#define	ECP_MCCONFR	1
+#define	ECP_MCSTOP	0x20
+#define	ECP_MCENABLE	0x80
+#define	ECP_MCDISABLE	0x00
+
+/*
+ *	Important defines for the PCI class of ECP board.
+ *	(It has a lot in common with the other ECP boards.)
+ */
+#define	ECP_PCIIREG	0
+#define	ECP_PCICONFR	1
+#define	ECP_PCISTOP	0x01
+
+/*
+ *	Hardware configuration info for ONboard and Brumby boards. These
+ *	defines apply to the directly accessible io ports of these boards.
+ */
+#define	ONB_IOSIZE	16
+#define	ONB_MEMSIZE	(64 * 1024)
+#define	ONB_ATPAGESIZE	(64 * 1024)
+#define	ONB_MCPAGESIZE	(64 * 1024)
+#define	ONB_EIMEMSIZE	(128 * 1024)
+#define	ONB_EIPAGESIZE	(64 * 1024)
+
+/*
+ *	Important defines for the ISA class of ONboard board.
+ */
+#define	ONB_ATIREG	0
+#define	ONB_ATMEMAR	1
+#define	ONB_ATCONFR	2
+#define	ONB_ATSTOP	0x4
+#define	ONB_ATENABLE	0x01
+#define	ONB_ATDISABLE	0x00
+#define	ONB_ATADDRMASK	0xff0000
+#define	ONB_ATADDRSHFT	16
+
+#define	ONB_MEMENABLO	0
+#define	ONB_MEMENABHI	0x02
+
+/*
+ *	Important defines for the EISA class of ONboard board.
+ */
+#define	ONB_EIIREG	0
+#define	ONB_EIMEMARL	1
+#define	ONB_EICONFR	2
+#define	ONB_EIMEMARH	3
+#define	ONB_EIENABLE	0x1
+#define	ONB_EIDISABLE	0x0
+#define	ONB_EISTOP	0x4
+#define	ONB_EIEDGE	0x00
+#define	ONB_EILEVEL	0x80
+#define	ONB_EIADDRMASKL	0x00ff0000
+#define	ONB_EIADDRSHFTL	16
+#define	ONB_EIADDRMASKH	0xff000000
+#define	ONB_EIADDRSHFTH	24
+#define	ONB_EIBRDENAB	0xc84
+
+#define	ONB_EISAID	0x1
+
+/*
+ *	Important defines for the Brumby boards. They are pretty simple,
+ *	there is not much that is programmably configurable.
+ */
+#define	BBY_IOSIZE	16
+#define	BBY_MEMSIZE	(64 * 1024)
+#define	BBY_PAGESIZE	(16 * 1024)
+
+#define	BBY_ATIREG	0
+#define	BBY_ATCONFR	1
+#define	BBY_ATSTOP	0x4
+
+/*
+ *	Important defines for the Stallion boards. They are pretty simple,
+ *	there is not much that is programmably configurable.
+ */
+#define	STAL_IOSIZE	16
+#define	STAL_MEMSIZE	(64 * 1024)
+#define	STAL_PAGESIZE	(64 * 1024)
+
+/*
+ *	Define the set of status register values for EasyConnection panels.
+ *	The signature will return with the status value for each panel. From
+ *	this we can determine what is attached to the board - before we have
+ *	actually down loaded any code to it.
+ */
+#define	ECH_PNLSTATUS	2
+#define	ECH_PNL16PORT	0x20
+#define	ECH_PNLIDMASK	0x07
+#define	ECH_PNLXPID	0x40
+#define	ECH_PNLINTRPEND	0x80
+
+/*
+ *	Define some macros to do things to the board. Even those these boards
+ *	are somewhat related there is often significantly different ways of
+ *	doing some operation on it (like enable, paging, reset, etc). So each
+ *	board class has a set of functions which do the commonly required
+ *	operations. The macros below basically just call these functions,
+ *	generally checking for a NULL function - which means that the board
+ *	needs nothing done to it to achieve this operation!
+ */
+#define	EBRDINIT(brdp)						\
+	if (brdp->init != NULL)					\
+		(* brdp->init)(brdp)
+
+#define	EBRDENABLE(brdp)					\
+	if (brdp->enable != NULL)				\
+		(* brdp->enable)(brdp);
+
+#define	EBRDDISABLE(brdp)					\
+	if (brdp->disable != NULL)				\
+		(* brdp->disable)(brdp);
+
+#define	EBRDINTR(brdp)						\
+	if (brdp->intr != NULL)					\
+		(* brdp->intr)(brdp);
+
+#define	EBRDRESET(brdp)						\
+	if (brdp->reset != NULL)				\
+		(* brdp->reset)(brdp);
+
+#define	EBRDGETMEMPTR(brdp,offset)				\
+	(* brdp->getmemptr)(brdp, offset, __LINE__)
+
+/*
+ *	Define the maximal baud rate, and the default baud base for ports.
+ */
+#define	STL_MAXBAUD	460800
+#define	STL_BAUDBASE	115200
+#define	STL_CLOSEDELAY	(5 * HZ / 10)
+
+/*****************************************************************************/
+
+/*
+ *	Define macros to extract a brd or port number from a minor number.
+ */
+#define	MINOR2BRD(min)		(((min) & 0xc0) >> 6)
+#define	MINOR2PORT(min)		((min) & 0x3f)
+
+/*
+ *	Define a baud rate table that converts termios baud rate selector
+ *	into the actual baud rate value. All baud rate calculations are based
+ *	on the actual baud rate required.
+ */
+static unsigned int	stli_baudrates[] = {
+	0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+	9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+};
+
+/*****************************************************************************/
+
+/*
+ *	Define some handy local macros...
+ */
+#undef MIN
+#define	MIN(a,b)	(((a) <= (b)) ? (a) : (b))
+
+#undef	TOLOWER
+#define	TOLOWER(x)	((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 0x20) : (x))
+
+/*****************************************************************************/
+
+/*
+ *	Prototype all functions in this driver!
+ */
+
+#ifdef MODULE
+static void	stli_argbrds(void);
+static int	stli_parsebrd(stlconf_t *confp, char **argp);
+
+static unsigned long	stli_atol(char *str);
+#endif
+
+int		stli_init(void);
+static int	stli_open(struct tty_struct *tty, struct file *filp);
+static void	stli_close(struct tty_struct *tty, struct file *filp);
+static int	stli_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void	stli_putchar(struct tty_struct *tty, unsigned char ch);
+static void	stli_flushchars(struct tty_struct *tty);
+static int	stli_writeroom(struct tty_struct *tty);
+static int	stli_charsinbuffer(struct tty_struct *tty);
+static int	stli_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static void	stli_settermios(struct tty_struct *tty, struct termios *old);
+static void	stli_throttle(struct tty_struct *tty);
+static void	stli_unthrottle(struct tty_struct *tty);
+static void	stli_stop(struct tty_struct *tty);
+static void	stli_start(struct tty_struct *tty);
+static void	stli_flushbuffer(struct tty_struct *tty);
+static void	stli_breakctl(struct tty_struct *tty, int state);
+static void	stli_waituntilsent(struct tty_struct *tty, int timeout);
+static void	stli_sendxchar(struct tty_struct *tty, char ch);
+static void	stli_hangup(struct tty_struct *tty);
+static int	stli_portinfo(stlibrd_t *brdp, stliport_t *portp, int portnr, char *pos);
+
+static int	stli_brdinit(stlibrd_t *brdp);
+static int	stli_startbrd(stlibrd_t *brdp);
+static ssize_t	stli_memread(struct file *fp, char __user *buf, size_t count, loff_t *offp);
+static ssize_t	stli_memwrite(struct file *fp, const char __user *buf, size_t count, loff_t *offp);
+static int	stli_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg);
+static void	stli_brdpoll(stlibrd_t *brdp, volatile cdkhdr_t *hdrp);
+static void	stli_poll(unsigned long arg);
+static int	stli_hostcmd(stlibrd_t *brdp, stliport_t *portp);
+static int	stli_initopen(stlibrd_t *brdp, stliport_t *portp);
+static int	stli_rawopen(stlibrd_t *brdp, stliport_t *portp, unsigned long arg, int wait);
+static int	stli_rawclose(stlibrd_t *brdp, stliport_t *portp, unsigned long arg, int wait);
+static int	stli_waitcarrier(stlibrd_t *brdp, stliport_t *portp, struct file *filp);
+static void	stli_dohangup(void *arg);
+static int	stli_setport(stliport_t *portp);
+static int	stli_cmdwait(stlibrd_t *brdp, stliport_t *portp, unsigned long cmd, void *arg, int size, int copyback);
+static void	stli_sendcmd(stlibrd_t *brdp, stliport_t *portp, unsigned long cmd, void *arg, int size, int copyback);
+static void	stli_dodelaycmd(stliport_t *portp, volatile cdkctrl_t *cp);
+static void	stli_mkasyport(stliport_t *portp, asyport_t *pp, struct termios *tiosp);
+static void	stli_mkasysigs(asysigs_t *sp, int dtr, int rts);
+static long	stli_mktiocm(unsigned long sigvalue);
+static void	stli_read(stlibrd_t *brdp, stliport_t *portp);
+static int	stli_getserial(stliport_t *portp, struct serial_struct __user *sp);
+static int	stli_setserial(stliport_t *portp, struct serial_struct __user *sp);
+static int	stli_getbrdstats(combrd_t __user *bp);
+static int	stli_getportstats(stliport_t *portp, comstats_t __user *cp);
+static int	stli_portcmdstats(stliport_t *portp);
+static int	stli_clrportstats(stliport_t *portp, comstats_t __user *cp);
+static int	stli_getportstruct(stliport_t __user *arg);
+static int	stli_getbrdstruct(stlibrd_t __user *arg);
+static void	*stli_memalloc(int len);
+static stlibrd_t *stli_allocbrd(void);
+
+static void	stli_ecpinit(stlibrd_t *brdp);
+static void	stli_ecpenable(stlibrd_t *brdp);
+static void	stli_ecpdisable(stlibrd_t *brdp);
+static char	*stli_ecpgetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void	stli_ecpreset(stlibrd_t *brdp);
+static void	stli_ecpintr(stlibrd_t *brdp);
+static void	stli_ecpeiinit(stlibrd_t *brdp);
+static void	stli_ecpeienable(stlibrd_t *brdp);
+static void	stli_ecpeidisable(stlibrd_t *brdp);
+static char	*stli_ecpeigetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void	stli_ecpeireset(stlibrd_t *brdp);
+static void	stli_ecpmcenable(stlibrd_t *brdp);
+static void	stli_ecpmcdisable(stlibrd_t *brdp);
+static char	*stli_ecpmcgetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void	stli_ecpmcreset(stlibrd_t *brdp);
+static void	stli_ecppciinit(stlibrd_t *brdp);
+static char	*stli_ecppcigetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void	stli_ecppcireset(stlibrd_t *brdp);
+
+static void	stli_onbinit(stlibrd_t *brdp);
+static void	stli_onbenable(stlibrd_t *brdp);
+static void	stli_onbdisable(stlibrd_t *brdp);
+static char	*stli_onbgetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void	stli_onbreset(stlibrd_t *brdp);
+static void	stli_onbeinit(stlibrd_t *brdp);
+static void	stli_onbeenable(stlibrd_t *brdp);
+static void	stli_onbedisable(stlibrd_t *brdp);
+static char	*stli_onbegetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void	stli_onbereset(stlibrd_t *brdp);
+static void	stli_bbyinit(stlibrd_t *brdp);
+static char	*stli_bbygetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void	stli_bbyreset(stlibrd_t *brdp);
+static void	stli_stalinit(stlibrd_t *brdp);
+static char	*stli_stalgetmemptr(stlibrd_t *brdp, unsigned long offset, int line);
+static void	stli_stalreset(stlibrd_t *brdp);
+
+static stliport_t *stli_getport(int brdnr, int panelnr, int portnr);
+
+static int	stli_initecp(stlibrd_t *brdp);
+static int	stli_initonb(stlibrd_t *brdp);
+static int	stli_eisamemprobe(stlibrd_t *brdp);
+static int	stli_initports(stlibrd_t *brdp);
+
+#ifdef	CONFIG_PCI
+static int	stli_initpcibrd(int brdtype, struct pci_dev *devp);
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Define the driver info for a user level shared memory device. This
+ *	device will work sort of like the /dev/kmem device - except that it
+ *	will give access to the shared memory on the Stallion intelligent
+ *	board. This is also a very useful debugging tool.
+ */
+static struct file_operations	stli_fsiomem = {
+	.owner		= THIS_MODULE,
+	.read		= stli_memread,
+	.write		= stli_memwrite,
+	.ioctl		= stli_memioctl,
+};
+
+/*****************************************************************************/
+
+/*
+ *	Define a timer_list entry for our poll routine. The slave board
+ *	is polled every so often to see if anything needs doing. This is
+ *	much cheaper on host cpu than using interrupts. It turns out to
+ *	not increase character latency by much either...
+ */
+static struct timer_list stli_timerlist = TIMER_INITIALIZER(stli_poll, 0, 0);
+
+static int	stli_timeron;
+
+/*
+ *	Define the calculation for the timeout routine.
+ */
+#define	STLI_TIMEOUT	(jiffies + 1)
+
+/*****************************************************************************/
+
+static struct class_simple *istallion_class;
+
+#ifdef MODULE
+
+/*
+ *	Loadable module initialization stuff.
+ */
+
+static int __init istallion_module_init(void)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("init_module()\n");
+#endif
+
+	save_flags(flags);
+	cli();
+	stli_init();
+	restore_flags(flags);
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+static void __exit istallion_module_exit(void)
+{
+	stlibrd_t	*brdp;
+	stliport_t	*portp;
+	unsigned long	flags;
+	int		i, j;
+
+#ifdef DEBUG
+	printk("cleanup_module()\n");
+#endif
+
+	printk(KERN_INFO "Unloading %s: version %s\n", stli_drvtitle,
+		stli_drvversion);
+
+	save_flags(flags);
+	cli();
+
+/*
+ *	Free up all allocated resources used by the ports. This includes
+ *	memory and interrupts.
+ */
+	if (stli_timeron) {
+		stli_timeron = 0;
+		del_timer(&stli_timerlist);
+	}
+
+	i = tty_unregister_driver(stli_serial);
+	if (i) {
+		printk("STALLION: failed to un-register tty driver, "
+			"errno=%d\n", -i);
+		restore_flags(flags);
+		return;
+	}
+	put_tty_driver(stli_serial);
+	for (i = 0; i < 4; i++) {
+		devfs_remove("staliomem/%d", i);
+		class_simple_device_remove(MKDEV(STL_SIOMEMMAJOR, i));
+	}
+	devfs_remove("staliomem");
+	class_simple_destroy(istallion_class);
+	if ((i = unregister_chrdev(STL_SIOMEMMAJOR, "staliomem")))
+		printk("STALLION: failed to un-register serial memory device, "
+			"errno=%d\n", -i);
+	if (stli_tmpwritebuf != (char *) NULL)
+		kfree(stli_tmpwritebuf);
+	if (stli_txcookbuf != (char *) NULL)
+		kfree(stli_txcookbuf);
+
+	for (i = 0; (i < stli_nrbrds); i++) {
+		if ((brdp = stli_brds[i]) == (stlibrd_t *) NULL)
+			continue;
+		for (j = 0; (j < STL_MAXPORTS); j++) {
+			portp = brdp->ports[j];
+			if (portp != (stliport_t *) NULL) {
+				if (portp->tty != (struct tty_struct *) NULL)
+					tty_hangup(portp->tty);
+				kfree(portp);
+			}
+		}
+
+		iounmap(brdp->membase);
+		if (brdp->iosize > 0)
+			release_region(brdp->iobase, brdp->iosize);
+		kfree(brdp);
+		stli_brds[i] = (stlibrd_t *) NULL;
+	}
+
+	restore_flags(flags);
+}
+
+module_init(istallion_module_init);
+module_exit(istallion_module_exit);
+
+/*****************************************************************************/
+
+/*
+ *	Check for any arguments passed in on the module load command line.
+ */
+
+static void stli_argbrds(void)
+{
+	stlconf_t	conf;
+	stlibrd_t	*brdp;
+	int		nrargs, i;
+
+#ifdef DEBUG
+	printk("stli_argbrds()\n");
+#endif
+
+	nrargs = sizeof(stli_brdsp) / sizeof(char **);
+
+	for (i = stli_nrbrds; (i < nrargs); i++) {
+		memset(&conf, 0, sizeof(conf));
+		if (stli_parsebrd(&conf, stli_brdsp[i]) == 0)
+			continue;
+		if ((brdp = stli_allocbrd()) == (stlibrd_t *) NULL)
+			continue;
+		stli_nrbrds = i + 1;
+		brdp->brdnr = i;
+		brdp->brdtype = conf.brdtype;
+		brdp->iobase = conf.ioaddr1;
+		brdp->memaddr = conf.memaddr;
+		stli_brdinit(brdp);
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Convert an ascii string number into an unsigned long.
+ */
+
+static unsigned long stli_atol(char *str)
+{
+	unsigned long	val;
+	int		base, c;
+	char		*sp;
+
+	val = 0;
+	sp = str;
+	if ((*sp == '0') && (*(sp+1) == 'x')) {
+		base = 16;
+		sp += 2;
+	} else if (*sp == '0') {
+		base = 8;
+		sp++;
+	} else {
+		base = 10;
+	}
+
+	for (; (*sp != 0); sp++) {
+		c = (*sp > '9') ? (TOLOWER(*sp) - 'a' + 10) : (*sp - '0');
+		if ((c < 0) || (c >= base)) {
+			printk("STALLION: invalid argument %s\n", str);
+			val = 0;
+			break;
+		}
+		val = (val * base) + c;
+	}
+	return(val);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Parse the supplied argument string, into the board conf struct.
+ */
+
+static int stli_parsebrd(stlconf_t *confp, char **argp)
+{
+	char	*sp;
+	int	nrbrdnames, i;
+
+#ifdef DEBUG
+	printk("stli_parsebrd(confp=%x,argp=%x)\n", (int) confp, (int) argp);
+#endif
+
+	if ((argp[0] == (char *) NULL) || (*argp[0] == 0))
+		return(0);
+
+	for (sp = argp[0], i = 0; ((*sp != 0) && (i < 25)); sp++, i++)
+		*sp = TOLOWER(*sp);
+
+	nrbrdnames = sizeof(stli_brdstr) / sizeof(stlibrdtype_t);
+	for (i = 0; (i < nrbrdnames); i++) {
+		if (strcmp(stli_brdstr[i].name, argp[0]) == 0)
+			break;
+	}
+	if (i >= nrbrdnames) {
+		printk("STALLION: unknown board name, %s?\n", argp[0]);
+		return(0);
+	}
+
+	confp->brdtype = stli_brdstr[i].type;
+	if ((argp[1] != (char *) NULL) && (*argp[1] != 0))
+		confp->ioaddr1 = stli_atol(argp[1]);
+	if ((argp[2] != (char *) NULL) && (*argp[2] != 0))
+		confp->memaddr = stli_atol(argp[2]);
+	return(1);
+}
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Local driver kernel malloc routine.
+ */
+
+static void *stli_memalloc(int len)
+{
+	return((void *) kmalloc(len, GFP_KERNEL));
+}
+
+/*****************************************************************************/
+
+static int stli_open(struct tty_struct *tty, struct file *filp)
+{
+	stlibrd_t	*brdp;
+	stliport_t	*portp;
+	unsigned int	minordev;
+	int		brdnr, portnr, rc;
+
+#ifdef DEBUG
+	printk("stli_open(tty=%x,filp=%x): device=%s\n", (int) tty,
+		(int) filp, tty->name);
+#endif
+
+	minordev = tty->index;
+	brdnr = MINOR2BRD(minordev);
+	if (brdnr >= stli_nrbrds)
+		return(-ENODEV);
+	brdp = stli_brds[brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(-ENODEV);
+	if ((brdp->state & BST_STARTED) == 0)
+		return(-ENODEV);
+	portnr = MINOR2PORT(minordev);
+	if ((portnr < 0) || (portnr > brdp->nrports))
+		return(-ENODEV);
+
+	portp = brdp->ports[portnr];
+	if (portp == (stliport_t *) NULL)
+		return(-ENODEV);
+	if (portp->devnr < 1)
+		return(-ENODEV);
+
+
+/*
+ *	Check if this port is in the middle of closing. If so then wait
+ *	until it is closed then return error status based on flag settings.
+ *	The sleep here does not need interrupt protection since the wakeup
+ *	for it is done with the same context.
+ */
+	if (portp->flags & ASYNC_CLOSING) {
+		interruptible_sleep_on(&portp->close_wait);
+		if (portp->flags & ASYNC_HUP_NOTIFY)
+			return(-EAGAIN);
+		return(-ERESTARTSYS);
+	}
+
+/*
+ *	On the first open of the device setup the port hardware, and
+ *	initialize the per port data structure. Since initializing the port
+ *	requires several commands to the board we will need to wait for any
+ *	other open that is already initializing the port.
+ */
+	portp->tty = tty;
+	tty->driver_data = portp;
+	portp->refcount++;
+
+	wait_event_interruptible(portp->raw_wait,
+			!test_bit(ST_INITIALIZING, &portp->state));
+	if (signal_pending(current))
+		return(-ERESTARTSYS);
+
+	if ((portp->flags & ASYNC_INITIALIZED) == 0) {
+		set_bit(ST_INITIALIZING, &portp->state);
+		if ((rc = stli_initopen(brdp, portp)) >= 0) {
+			portp->flags |= ASYNC_INITIALIZED;
+			clear_bit(TTY_IO_ERROR, &tty->flags);
+		}
+		clear_bit(ST_INITIALIZING, &portp->state);
+		wake_up_interruptible(&portp->raw_wait);
+		if (rc < 0)
+			return(rc);
+	}
+
+/*
+ *	Check if this port is in the middle of closing. If so then wait
+ *	until it is closed then return error status, based on flag settings.
+ *	The sleep here does not need interrupt protection since the wakeup
+ *	for it is done with the same context.
+ */
+	if (portp->flags & ASYNC_CLOSING) {
+		interruptible_sleep_on(&portp->close_wait);
+		if (portp->flags & ASYNC_HUP_NOTIFY)
+			return(-EAGAIN);
+		return(-ERESTARTSYS);
+	}
+
+/*
+ *	Based on type of open being done check if it can overlap with any
+ *	previous opens still in effect. If we are a normal serial device
+ *	then also we might have to wait for carrier.
+ */
+	if (!(filp->f_flags & O_NONBLOCK)) {
+		if ((rc = stli_waitcarrier(brdp, portp, filp)) != 0)
+			return(rc);
+	}
+	portp->flags |= ASYNC_NORMAL_ACTIVE;
+	return(0);
+}
+
+/*****************************************************************************/
+
+static void stli_close(struct tty_struct *tty, struct file *filp)
+{
+	stlibrd_t	*brdp;
+	stliport_t	*portp;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stli_close(tty=%x,filp=%x)\n", (int) tty, (int) filp);
+#endif
+
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	if (tty_hung_up_p(filp)) {
+		restore_flags(flags);
+		return;
+	}
+	if ((tty->count == 1) && (portp->refcount != 1))
+		portp->refcount = 1;
+	if (portp->refcount-- > 1) {
+		restore_flags(flags);
+		return;
+	}
+
+	portp->flags |= ASYNC_CLOSING;
+
+/*
+ *	May want to wait for data to drain before closing. The BUSY flag
+ *	keeps track of whether we are still transmitting or not. It is
+ *	updated by messages from the slave - indicating when all chars
+ *	really have drained.
+ */
+	if (tty == stli_txcooktty)
+		stli_flushchars(tty);
+	tty->closing = 1;
+	if (portp->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, portp->closing_wait);
+
+	portp->flags &= ~ASYNC_INITIALIZED;
+	brdp = stli_brds[portp->brdnr];
+	stli_rawclose(brdp, portp, 0, 0);
+	if (tty->termios->c_cflag & HUPCL) {
+		stli_mkasysigs(&portp->asig, 0, 0);
+		if (test_bit(ST_CMDING, &portp->state))
+			set_bit(ST_DOSIGS, &portp->state);
+		else
+			stli_sendcmd(brdp, portp, A_SETSIGNALS, &portp->asig,
+				sizeof(asysigs_t), 0);
+	}
+	clear_bit(ST_TXBUSY, &portp->state);
+	clear_bit(ST_RXSTOP, &portp->state);
+	set_bit(TTY_IO_ERROR, &tty->flags);
+	if (tty->ldisc.flush_buffer)
+		(tty->ldisc.flush_buffer)(tty);
+	set_bit(ST_DOFLUSHRX, &portp->state);
+	stli_flushbuffer(tty);
+
+	tty->closing = 0;
+	portp->tty = (struct tty_struct *) NULL;
+
+	if (portp->openwaitcnt) {
+		if (portp->close_delay)
+			msleep_interruptible(jiffies_to_msecs(portp->close_delay));
+		wake_up_interruptible(&portp->open_wait);
+	}
+
+	portp->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+	wake_up_interruptible(&portp->close_wait);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Carry out first open operations on a port. This involves a number of
+ *	commands to be sent to the slave. We need to open the port, set the
+ *	notification events, set the initial port settings, get and set the
+ *	initial signal values. We sleep and wait in between each one. But
+ *	this still all happens pretty quickly.
+ */
+
+static int stli_initopen(stlibrd_t *brdp, stliport_t *portp)
+{
+	struct tty_struct	*tty;
+	asynotify_t		nt;
+	asyport_t		aport;
+	int			rc;
+
+#ifdef DEBUG
+	printk("stli_initopen(brdp=%x,portp=%x)\n", (int) brdp, (int) portp);
+#endif
+
+	if ((rc = stli_rawopen(brdp, portp, 0, 1)) < 0)
+		return(rc);
+
+	memset(&nt, 0, sizeof(asynotify_t));
+	nt.data = (DT_TXLOW | DT_TXEMPTY | DT_RXBUSY | DT_RXBREAK);
+	nt.signal = SG_DCD;
+	if ((rc = stli_cmdwait(brdp, portp, A_SETNOTIFY, &nt,
+	    sizeof(asynotify_t), 0)) < 0)
+		return(rc);
+
+	tty = portp->tty;
+	if (tty == (struct tty_struct *) NULL)
+		return(-ENODEV);
+	stli_mkasyport(portp, &aport, tty->termios);
+	if ((rc = stli_cmdwait(brdp, portp, A_SETPORT, &aport,
+	    sizeof(asyport_t), 0)) < 0)
+		return(rc);
+
+	set_bit(ST_GETSIGS, &portp->state);
+	if ((rc = stli_cmdwait(brdp, portp, A_GETSIGNALS, &portp->asig,
+	    sizeof(asysigs_t), 1)) < 0)
+		return(rc);
+	if (test_and_clear_bit(ST_GETSIGS, &portp->state))
+		portp->sigs = stli_mktiocm(portp->asig.sigvalue);
+	stli_mkasysigs(&portp->asig, 1, 1);
+	if ((rc = stli_cmdwait(brdp, portp, A_SETSIGNALS, &portp->asig,
+	    sizeof(asysigs_t), 0)) < 0)
+		return(rc);
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Send an open message to the slave. This will sleep waiting for the
+ *	acknowledgement, so must have user context. We need to co-ordinate
+ *	with close events here, since we don't want open and close events
+ *	to overlap.
+ */
+
+static int stli_rawopen(stlibrd_t *brdp, stliport_t *portp, unsigned long arg, int wait)
+{
+	volatile cdkhdr_t	*hdrp;
+	volatile cdkctrl_t	*cp;
+	volatile unsigned char	*bits;
+	unsigned long		flags;
+	int			rc;
+
+#ifdef DEBUG
+	printk("stli_rawopen(brdp=%x,portp=%x,arg=%x,wait=%d)\n",
+		(int) brdp, (int) portp, (int) arg, wait);
+#endif
+
+/*
+ *	Send a message to the slave to open this port.
+ */
+	save_flags(flags);
+	cli();
+
+/*
+ *	Slave is already closing this port. This can happen if a hangup
+ *	occurs on this port. So we must wait until it is complete. The
+ *	order of opens and closes may not be preserved across shared
+ *	memory, so we must wait until it is complete.
+ */
+	wait_event_interruptible(portp->raw_wait,
+			!test_bit(ST_CLOSING, &portp->state));
+	if (signal_pending(current)) {
+		restore_flags(flags);
+		return -ERESTARTSYS;
+	}
+
+/*
+ *	Everything is ready now, so write the open message into shared
+ *	memory. Once the message is in set the service bits to say that
+ *	this port wants service.
+ */
+	EBRDENABLE(brdp);
+	cp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->ctrl;
+	cp->openarg = arg;
+	cp->open = 1;
+	hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+	bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+		portp->portidx;
+	*bits |= portp->portbit;
+	EBRDDISABLE(brdp);
+
+	if (wait == 0) {
+		restore_flags(flags);
+		return(0);
+	}
+
+/*
+ *	Slave is in action, so now we must wait for the open acknowledgment
+ *	to come back.
+ */
+	rc = 0;
+	set_bit(ST_OPENING, &portp->state);
+	wait_event_interruptible(portp->raw_wait,
+			!test_bit(ST_OPENING, &portp->state));
+	if (signal_pending(current))
+		rc = -ERESTARTSYS;
+	restore_flags(flags);
+
+	if ((rc == 0) && (portp->rc != 0))
+		rc = -EIO;
+	return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Send a close message to the slave. Normally this will sleep waiting
+ *	for the acknowledgement, but if wait parameter is 0 it will not. If
+ *	wait is true then must have user context (to sleep).
+ */
+
+static int stli_rawclose(stlibrd_t *brdp, stliport_t *portp, unsigned long arg, int wait)
+{
+	volatile cdkhdr_t	*hdrp;
+	volatile cdkctrl_t	*cp;
+	volatile unsigned char	*bits;
+	unsigned long		flags;
+	int			rc;
+
+#ifdef DEBUG
+	printk("stli_rawclose(brdp=%x,portp=%x,arg=%x,wait=%d)\n",
+		(int) brdp, (int) portp, (int) arg, wait);
+#endif
+
+	save_flags(flags);
+	cli();
+
+/*
+ *	Slave is already closing this port. This can happen if a hangup
+ *	occurs on this port.
+ */
+	if (wait) {
+		wait_event_interruptible(portp->raw_wait,
+				!test_bit(ST_CLOSING, &portp->state));
+		if (signal_pending(current)) {
+			restore_flags(flags);
+			return -ERESTARTSYS;
+		}
+	}
+
+/*
+ *	Write the close command into shared memory.
+ */
+	EBRDENABLE(brdp);
+	cp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->ctrl;
+	cp->closearg = arg;
+	cp->close = 1;
+	hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+	bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+		portp->portidx;
+	*bits |= portp->portbit;
+	EBRDDISABLE(brdp);
+
+	set_bit(ST_CLOSING, &portp->state);
+	if (wait == 0) {
+		restore_flags(flags);
+		return(0);
+	}
+
+/*
+ *	Slave is in action, so now we must wait for the open acknowledgment
+ *	to come back.
+ */
+	rc = 0;
+	wait_event_interruptible(portp->raw_wait,
+			!test_bit(ST_CLOSING, &portp->state));
+	if (signal_pending(current))
+		rc = -ERESTARTSYS;
+	restore_flags(flags);
+
+	if ((rc == 0) && (portp->rc != 0))
+		rc = -EIO;
+	return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Send a command to the slave and wait for the response. This must
+ *	have user context (it sleeps). This routine is generic in that it
+ *	can send any type of command. Its purpose is to wait for that command
+ *	to complete (as opposed to initiating the command then returning).
+ */
+
+static int stli_cmdwait(stlibrd_t *brdp, stliport_t *portp, unsigned long cmd, void *arg, int size, int copyback)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stli_cmdwait(brdp=%x,portp=%x,cmd=%x,arg=%x,size=%d,"
+		"copyback=%d)\n", (int) brdp, (int) portp, (int) cmd,
+		(int) arg, size, copyback);
+#endif
+
+	save_flags(flags);
+	cli();
+	wait_event_interruptible(portp->raw_wait,
+			!test_bit(ST_CMDING, &portp->state));
+	if (signal_pending(current)) {
+		restore_flags(flags);
+		return -ERESTARTSYS;
+	}
+
+	stli_sendcmd(brdp, portp, cmd, arg, size, copyback);
+
+	wait_event_interruptible(portp->raw_wait,
+			!test_bit(ST_CMDING, &portp->state));
+	if (signal_pending(current)) {
+		restore_flags(flags);
+		return -ERESTARTSYS;
+	}
+	restore_flags(flags);
+
+	if (portp->rc != 0)
+		return(-EIO);
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Send the termios settings for this port to the slave. This sleeps
+ *	waiting for the command to complete - so must have user context.
+ */
+
+static int stli_setport(stliport_t *portp)
+{
+	stlibrd_t	*brdp;
+	asyport_t	aport;
+
+#ifdef DEBUG
+	printk("stli_setport(portp=%x)\n", (int) portp);
+#endif
+
+	if (portp == (stliport_t *) NULL)
+		return(-ENODEV);
+	if (portp->tty == (struct tty_struct *) NULL)
+		return(-ENODEV);
+	if ((portp->brdnr < 0) && (portp->brdnr >= stli_nrbrds))
+		return(-ENODEV);
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(-ENODEV);
+
+	stli_mkasyport(portp, &aport, portp->tty->termios);
+	return(stli_cmdwait(brdp, portp, A_SETPORT, &aport, sizeof(asyport_t), 0));
+}
+
+/*****************************************************************************/
+
+/*
+ *	Possibly need to wait for carrier (DCD signal) to come high. Say
+ *	maybe because if we are clocal then we don't need to wait...
+ */
+
+static int stli_waitcarrier(stlibrd_t *brdp, stliport_t *portp, struct file *filp)
+{
+	unsigned long	flags;
+	int		rc, doclocal;
+
+#ifdef DEBUG
+	printk("stli_waitcarrier(brdp=%x,portp=%x,filp=%x)\n",
+		(int) brdp, (int) portp, (int) filp);
+#endif
+
+	rc = 0;
+	doclocal = 0;
+
+	if (portp->tty->termios->c_cflag & CLOCAL)
+		doclocal++;
+
+	save_flags(flags);
+	cli();
+	portp->openwaitcnt++;
+	if (! tty_hung_up_p(filp))
+		portp->refcount--;
+
+	for (;;) {
+		stli_mkasysigs(&portp->asig, 1, 1);
+		if ((rc = stli_cmdwait(brdp, portp, A_SETSIGNALS,
+		    &portp->asig, sizeof(asysigs_t), 0)) < 0)
+			break;
+		if (tty_hung_up_p(filp) ||
+		    ((portp->flags & ASYNC_INITIALIZED) == 0)) {
+			if (portp->flags & ASYNC_HUP_NOTIFY)
+				rc = -EBUSY;
+			else
+				rc = -ERESTARTSYS;
+			break;
+		}
+		if (((portp->flags & ASYNC_CLOSING) == 0) &&
+		    (doclocal || (portp->sigs & TIOCM_CD))) {
+			break;
+		}
+		if (signal_pending(current)) {
+			rc = -ERESTARTSYS;
+			break;
+		}
+		interruptible_sleep_on(&portp->open_wait);
+	}
+
+	if (! tty_hung_up_p(filp))
+		portp->refcount++;
+	portp->openwaitcnt--;
+	restore_flags(flags);
+
+	return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Write routine. Take the data and put it in the shared memory ring
+ *	queue. If port is not already sending chars then need to mark the
+ *	service bits for this port.
+ */
+
+static int stli_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	volatile cdkasy_t	*ap;
+	volatile cdkhdr_t	*hdrp;
+	volatile unsigned char	*bits;
+	unsigned char		*shbuf, *chbuf;
+	stliport_t		*portp;
+	stlibrd_t		*brdp;
+	unsigned int		len, stlen, head, tail, size;
+	unsigned long		flags;
+
+#ifdef DEBUG
+	printk("stli_write(tty=%x,buf=%x,count=%d)\n",
+		(int) tty, (int) buf, count);
+#endif
+
+	if ((tty == (struct tty_struct *) NULL) ||
+	    (stli_tmpwritebuf == (char *) NULL))
+		return(0);
+	if (tty == stli_txcooktty)
+		stli_flushchars(tty);
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return(0);
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return(0);
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(0);
+	chbuf = (unsigned char *) buf;
+
+/*
+ *	All data is now local, shove as much as possible into shared memory.
+ */
+	save_flags(flags);
+	cli();
+	EBRDENABLE(brdp);
+	ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+	head = (unsigned int) ap->txq.head;
+	tail = (unsigned int) ap->txq.tail;
+	if (tail != ((unsigned int) ap->txq.tail))
+		tail = (unsigned int) ap->txq.tail;
+	size = portp->txsize;
+	if (head >= tail) {
+		len = size - (head - tail) - 1;
+		stlen = size - head;
+	} else {
+		len = tail - head - 1;
+		stlen = len;
+	}
+
+	len = MIN(len, count);
+	count = 0;
+	shbuf = (char *) EBRDGETMEMPTR(brdp, portp->txoffset);
+
+	while (len > 0) {
+		stlen = MIN(len, stlen);
+		memcpy((shbuf + head), chbuf, stlen);
+		chbuf += stlen;
+		len -= stlen;
+		count += stlen;
+		head += stlen;
+		if (head >= size) {
+			head = 0;
+			stlen = tail;
+		}
+	}
+
+	ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+	ap->txq.head = head;
+	if (test_bit(ST_TXBUSY, &portp->state)) {
+		if (ap->changed.data & DT_TXEMPTY)
+			ap->changed.data &= ~DT_TXEMPTY;
+	}
+	hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+	bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+		portp->portidx;
+	*bits |= portp->portbit;
+	set_bit(ST_TXBUSY, &portp->state);
+	EBRDDISABLE(brdp);
+
+	restore_flags(flags);
+
+	return(count);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Output a single character. We put it into a temporary local buffer
+ *	(for speed) then write out that buffer when the flushchars routine
+ *	is called. There is a safety catch here so that if some other port
+ *	writes chars before the current buffer has been, then we write them
+ *	first them do the new ports.
+ */
+
+static void stli_putchar(struct tty_struct *tty, unsigned char ch)
+{
+#ifdef DEBUG
+	printk("stli_putchar(tty=%x,ch=%x)\n", (int) tty, (int) ch);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	if (tty != stli_txcooktty) {
+		if (stli_txcooktty != (struct tty_struct *) NULL)
+			stli_flushchars(stli_txcooktty);
+		stli_txcooktty = tty;
+	}
+
+	stli_txcookbuf[stli_txcooksize++] = ch;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Transfer characters from the local TX cooking buffer to the board.
+ *	We sort of ignore the tty that gets passed in here. We rely on the
+ *	info stored with the TX cook buffer to tell us which port to flush
+ *	the data on. In any case we clean out the TX cook buffer, for re-use
+ *	by someone else.
+ */
+
+static void stli_flushchars(struct tty_struct *tty)
+{
+	volatile cdkhdr_t	*hdrp;
+	volatile unsigned char	*bits;
+	volatile cdkasy_t	*ap;
+	struct tty_struct	*cooktty;
+	stliport_t		*portp;
+	stlibrd_t		*brdp;
+	unsigned int		len, stlen, head, tail, size, count, cooksize;
+	unsigned char		*buf, *shbuf;
+	unsigned long		flags;
+
+#ifdef DEBUG
+	printk("stli_flushchars(tty=%x)\n", (int) tty);
+#endif
+
+	cooksize = stli_txcooksize;
+	cooktty = stli_txcooktty;
+	stli_txcooksize = 0;
+	stli_txcookrealsize = 0;
+	stli_txcooktty = (struct tty_struct *) NULL;
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	if (cooktty == (struct tty_struct *) NULL)
+		return;
+	if (tty != cooktty)
+		tty = cooktty;
+	if (cooksize == 0)
+		return;
+
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return;
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	EBRDENABLE(brdp);
+
+	ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+	head = (unsigned int) ap->txq.head;
+	tail = (unsigned int) ap->txq.tail;
+	if (tail != ((unsigned int) ap->txq.tail))
+		tail = (unsigned int) ap->txq.tail;
+	size = portp->txsize;
+	if (head >= tail) {
+		len = size - (head - tail) - 1;
+		stlen = size - head;
+	} else {
+		len = tail - head - 1;
+		stlen = len;
+	}
+
+	len = MIN(len, cooksize);
+	count = 0;
+	shbuf = (char *) EBRDGETMEMPTR(brdp, portp->txoffset);
+	buf = stli_txcookbuf;
+
+	while (len > 0) {
+		stlen = MIN(len, stlen);
+		memcpy((shbuf + head), buf, stlen);
+		buf += stlen;
+		len -= stlen;
+		count += stlen;
+		head += stlen;
+		if (head >= size) {
+			head = 0;
+			stlen = tail;
+		}
+	}
+
+	ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+	ap->txq.head = head;
+
+	if (test_bit(ST_TXBUSY, &portp->state)) {
+		if (ap->changed.data & DT_TXEMPTY)
+			ap->changed.data &= ~DT_TXEMPTY;
+	}
+	hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+	bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+		portp->portidx;
+	*bits |= portp->portbit;
+	set_bit(ST_TXBUSY, &portp->state);
+
+	EBRDDISABLE(brdp);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static int stli_writeroom(struct tty_struct *tty)
+{
+	volatile cdkasyrq_t	*rp;
+	stliport_t		*portp;
+	stlibrd_t		*brdp;
+	unsigned int		head, tail, len;
+	unsigned long		flags;
+
+#ifdef DEBUG
+	printk("stli_writeroom(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return(0);
+	if (tty == stli_txcooktty) {
+		if (stli_txcookrealsize != 0) {
+			len = stli_txcookrealsize - stli_txcooksize;
+			return(len);
+		}
+	}
+
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return(0);
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return(0);
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(0);
+
+	save_flags(flags);
+	cli();
+	EBRDENABLE(brdp);
+	rp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->txq;
+	head = (unsigned int) rp->head;
+	tail = (unsigned int) rp->tail;
+	if (tail != ((unsigned int) rp->tail))
+		tail = (unsigned int) rp->tail;
+	len = (head >= tail) ? (portp->txsize - (head - tail)) : (tail - head);
+	len--;
+	EBRDDISABLE(brdp);
+	restore_flags(flags);
+
+	if (tty == stli_txcooktty) {
+		stli_txcookrealsize = len;
+		len -= stli_txcooksize;
+	}
+	return(len);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the number of characters in the transmit buffer. Normally we
+ *	will return the number of chars in the shared memory ring queue.
+ *	We need to kludge around the case where the shared memory buffer is
+ *	empty but not all characters have drained yet, for this case just
+ *	return that there is 1 character in the buffer!
+ */
+
+static int stli_charsinbuffer(struct tty_struct *tty)
+{
+	volatile cdkasyrq_t	*rp;
+	stliport_t		*portp;
+	stlibrd_t		*brdp;
+	unsigned int		head, tail, len;
+	unsigned long		flags;
+
+#ifdef DEBUG
+	printk("stli_charsinbuffer(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return(0);
+	if (tty == stli_txcooktty)
+		stli_flushchars(tty);
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return(0);
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return(0);
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(0);
+
+	save_flags(flags);
+	cli();
+	EBRDENABLE(brdp);
+	rp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->txq;
+	head = (unsigned int) rp->head;
+	tail = (unsigned int) rp->tail;
+	if (tail != ((unsigned int) rp->tail))
+		tail = (unsigned int) rp->tail;
+	len = (head >= tail) ? (head - tail) : (portp->txsize - (tail - head));
+	if ((len == 0) && test_bit(ST_TXBUSY, &portp->state))
+		len = 1;
+	EBRDDISABLE(brdp);
+	restore_flags(flags);
+
+	return(len);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Generate the serial struct info.
+ */
+
+static int stli_getserial(stliport_t *portp, struct serial_struct __user *sp)
+{
+	struct serial_struct	sio;
+	stlibrd_t		*brdp;
+
+#ifdef DEBUG
+	printk("stli_getserial(portp=%x,sp=%x)\n", (int) portp, (int) sp);
+#endif
+
+	memset(&sio, 0, sizeof(struct serial_struct));
+	sio.type = PORT_UNKNOWN;
+	sio.line = portp->portnr;
+	sio.irq = 0;
+	sio.flags = portp->flags;
+	sio.baud_base = portp->baud_base;
+	sio.close_delay = portp->close_delay;
+	sio.closing_wait = portp->closing_wait;
+	sio.custom_divisor = portp->custom_divisor;
+	sio.xmit_fifo_size = 0;
+	sio.hub6 = 0;
+
+	brdp = stli_brds[portp->brdnr];
+	if (brdp != (stlibrd_t *) NULL)
+		sio.port = brdp->iobase;
+		
+	return copy_to_user(sp, &sio, sizeof(struct serial_struct)) ?
+			-EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Set port according to the serial struct info.
+ *	At this point we do not do any auto-configure stuff, so we will
+ *	just quietly ignore any requests to change irq, etc.
+ */
+
+static int stli_setserial(stliport_t *portp, struct serial_struct __user *sp)
+{
+	struct serial_struct	sio;
+	int			rc;
+
+#ifdef DEBUG
+	printk("stli_setserial(portp=%p,sp=%p)\n", portp, sp);
+#endif
+
+	if (copy_from_user(&sio, sp, sizeof(struct serial_struct)))
+		return -EFAULT;
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((sio.baud_base != portp->baud_base) ||
+		    (sio.close_delay != portp->close_delay) ||
+		    ((sio.flags & ~ASYNC_USR_MASK) !=
+		    (portp->flags & ~ASYNC_USR_MASK)))
+			return(-EPERM);
+	} 
+
+	portp->flags = (portp->flags & ~ASYNC_USR_MASK) |
+		(sio.flags & ASYNC_USR_MASK);
+	portp->baud_base = sio.baud_base;
+	portp->close_delay = sio.close_delay;
+	portp->closing_wait = sio.closing_wait;
+	portp->custom_divisor = sio.custom_divisor;
+
+	if ((rc = stli_setport(portp)) < 0)
+		return(rc);
+	return(0);
+}
+
+/*****************************************************************************/
+
+static int stli_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	stliport_t *portp = tty->driver_data;
+	stlibrd_t *brdp;
+	int rc;
+
+	if (portp == (stliport_t *) NULL)
+		return(-ENODEV);
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return(0);
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(0);
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return(-EIO);
+
+	if ((rc = stli_cmdwait(brdp, portp, A_GETSIGNALS,
+			       &portp->asig, sizeof(asysigs_t), 1)) < 0)
+		return(rc);
+
+	return stli_mktiocm(portp->asig.sigvalue);
+}
+
+static int stli_tiocmset(struct tty_struct *tty, struct file *file,
+			 unsigned int set, unsigned int clear)
+{
+	stliport_t *portp = tty->driver_data;
+	stlibrd_t *brdp;
+	int rts = -1, dtr = -1;
+
+	if (portp == (stliport_t *) NULL)
+		return(-ENODEV);
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return(0);
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(0);
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return(-EIO);
+
+	if (set & TIOCM_RTS)
+		rts = 1;
+	if (set & TIOCM_DTR)
+		dtr = 1;
+	if (clear & TIOCM_RTS)
+		rts = 0;
+	if (clear & TIOCM_DTR)
+		dtr = 0;
+
+	stli_mkasysigs(&portp->asig, dtr, rts);
+
+	return stli_cmdwait(brdp, portp, A_SETSIGNALS, &portp->asig,
+			    sizeof(asysigs_t), 0);
+}
+
+static int stli_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	stliport_t	*portp;
+	stlibrd_t	*brdp;
+	unsigned int	ival;
+	int		rc;
+	void __user *argp = (void __user *)arg;
+
+#ifdef DEBUG
+	printk("stli_ioctl(tty=%x,file=%x,cmd=%x,arg=%x)\n",
+		(int) tty, (int) file, cmd, (int) arg);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return(-ENODEV);
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return(-ENODEV);
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return(0);
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(0);
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ 	    (cmd != COM_GETPORTSTATS) && (cmd != COM_CLRPORTSTATS)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+			return(-EIO);
+	}
+
+	rc = 0;
+
+	switch (cmd) {
+	case TIOCGSOFTCAR:
+		rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+			(unsigned __user *) arg);
+		break;
+	case TIOCSSOFTCAR:
+		if ((rc = get_user(ival, (unsigned __user *) arg)) == 0)
+			tty->termios->c_cflag =
+				(tty->termios->c_cflag & ~CLOCAL) |
+				(ival ? CLOCAL : 0);
+		break;
+	case TIOCGSERIAL:
+		rc = stli_getserial(portp, argp);
+		break;
+	case TIOCSSERIAL:
+		rc = stli_setserial(portp, argp);
+		break;
+	case STL_GETPFLAG:
+		rc = put_user(portp->pflag, (unsigned __user *)argp);
+		break;
+	case STL_SETPFLAG:
+		if ((rc = get_user(portp->pflag, (unsigned __user *)argp)) == 0)
+			stli_setport(portp);
+		break;
+	case COM_GETPORTSTATS:
+		rc = stli_getportstats(portp, argp);
+		break;
+	case COM_CLRPORTSTATS:
+		rc = stli_clrportstats(portp, argp);
+		break;
+	case TIOCSERCONFIG:
+	case TIOCSERGWILD:
+	case TIOCSERSWILD:
+	case TIOCSERGETLSR:
+	case TIOCSERGSTRUCT:
+	case TIOCSERGETMULTI:
+	case TIOCSERSETMULTI:
+	default:
+		rc = -ENOIOCTLCMD;
+		break;
+	}
+
+	return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ *	This routine assumes that we have user context and can sleep.
+ *	Looks like it is true for the current ttys implementation..!!
+ */
+
+static void stli_settermios(struct tty_struct *tty, struct termios *old)
+{
+	stliport_t	*portp;
+	stlibrd_t	*brdp;
+	struct termios	*tiosp;
+	asyport_t	aport;
+
+#ifdef DEBUG
+	printk("stli_settermios(tty=%x,old=%x)\n", (int) tty, (int) old);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return;
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return;
+
+	tiosp = tty->termios;
+	if ((tiosp->c_cflag == old->c_cflag) &&
+	    (tiosp->c_iflag == old->c_iflag))
+		return;
+
+	stli_mkasyport(portp, &aport, tiosp);
+	stli_cmdwait(brdp, portp, A_SETPORT, &aport, sizeof(asyport_t), 0);
+	stli_mkasysigs(&portp->asig, ((tiosp->c_cflag & CBAUD) ? 1 : 0), -1);
+	stli_cmdwait(brdp, portp, A_SETSIGNALS, &portp->asig,
+		sizeof(asysigs_t), 0);
+	if ((old->c_cflag & CRTSCTS) && ((tiosp->c_cflag & CRTSCTS) == 0))
+		tty->hw_stopped = 0;
+	if (((old->c_cflag & CLOCAL) == 0) && (tiosp->c_cflag & CLOCAL))
+		wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Attempt to flow control who ever is sending us data. We won't really
+ *	do any flow control action here. We can't directly, and even if we
+ *	wanted to we would have to send a command to the slave. The slave
+ *	knows how to flow control, and will do so when its buffers reach its
+ *	internal high water marks. So what we will do is set a local state
+ *	bit that will stop us sending any RX data up from the poll routine
+ *	(which is the place where RX data from the slave is handled).
+ */
+
+static void stli_throttle(struct tty_struct *tty)
+{
+	stliport_t	*portp;
+
+#ifdef DEBUG
+	printk("stli_throttle(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+
+	set_bit(ST_RXSTOP, &portp->state);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Unflow control the device sending us data... That means that all
+ *	we have to do is clear the RXSTOP state bit. The next poll call
+ *	will then be able to pass the RX data back up.
+ */
+
+static void stli_unthrottle(struct tty_struct *tty)
+{
+	stliport_t	*portp;
+
+#ifdef DEBUG
+	printk("stli_unthrottle(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+
+	clear_bit(ST_RXSTOP, &portp->state);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Stop the transmitter. Basically to do this we will just turn TX
+ *	interrupts off.
+ */
+
+static void stli_stop(struct tty_struct *tty)
+{
+	stlibrd_t	*brdp;
+	stliport_t	*portp;
+	asyctrl_t	actrl;
+
+#ifdef DEBUG
+	printk("stli_stop(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return;
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return;
+
+	memset(&actrl, 0, sizeof(asyctrl_t));
+	actrl.txctrl = CT_STOPFLOW;
+#if 0
+	stli_cmdwait(brdp, portp, A_PORTCTRL, &actrl, sizeof(asyctrl_t), 0);
+#endif
+}
+
+/*****************************************************************************/
+
+/*
+ *	Start the transmitter again. Just turn TX interrupts back on.
+ */
+
+static void stli_start(struct tty_struct *tty)
+{
+	stliport_t	*portp;
+	stlibrd_t	*brdp;
+	asyctrl_t	actrl;
+
+#ifdef DEBUG
+	printk("stli_start(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return;
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return;
+
+	memset(&actrl, 0, sizeof(asyctrl_t));
+	actrl.txctrl = CT_STARTFLOW;
+#if 0
+	stli_cmdwait(brdp, portp, A_PORTCTRL, &actrl, sizeof(asyctrl_t), 0);
+#endif
+}
+
+/*****************************************************************************/
+
+/*
+ *	Scheduler called hang up routine. This is called from the scheduler,
+ *	not direct from the driver "poll" routine. We can't call it there
+ *	since the real local hangup code will enable/disable the board and
+ *	other things that we can't do while handling the poll. Much easier
+ *	to deal with it some time later (don't really care when, hangups
+ *	aren't that time critical).
+ */
+
+static void stli_dohangup(void *arg)
+{
+	stliport_t	*portp;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_dohangup(portp=%x)\n", (int) arg);
+#endif
+
+	/*
+	 * FIXME: There's a module removal race here: tty_hangup
+	 * calls schedule_work which will call into this
+	 * driver later.
+	 */
+	portp = (stliport_t *) arg;
+	if (portp != (stliport_t *) NULL) {
+		if (portp->tty != (struct tty_struct *) NULL) {
+			tty_hangup(portp->tty);
+		}
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Hangup this port. This is pretty much like closing the port, only
+ *	a little more brutal. No waiting for data to drain. Shutdown the
+ *	port and maybe drop signals. This is rather tricky really. We want
+ *	to close the port as well.
+ */
+
+static void stli_hangup(struct tty_struct *tty)
+{
+	stliport_t	*portp;
+	stlibrd_t	*brdp;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_hangup(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return;
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return;
+
+	portp->flags &= ~ASYNC_INITIALIZED;
+
+	save_flags(flags);
+	cli();
+	if (! test_bit(ST_CLOSING, &portp->state))
+		stli_rawclose(brdp, portp, 0, 0);
+	if (tty->termios->c_cflag & HUPCL) {
+		stli_mkasysigs(&portp->asig, 0, 0);
+		if (test_bit(ST_CMDING, &portp->state)) {
+			set_bit(ST_DOSIGS, &portp->state);
+			set_bit(ST_DOFLUSHTX, &portp->state);
+			set_bit(ST_DOFLUSHRX, &portp->state);
+		} else {
+			stli_sendcmd(brdp, portp, A_SETSIGNALSF,
+				&portp->asig, sizeof(asysigs_t), 0);
+		}
+	}
+	restore_flags(flags);
+
+	clear_bit(ST_TXBUSY, &portp->state);
+	clear_bit(ST_RXSTOP, &portp->state);
+	set_bit(TTY_IO_ERROR, &tty->flags);
+	portp->tty = (struct tty_struct *) NULL;
+	portp->flags &= ~ASYNC_NORMAL_ACTIVE;
+	portp->refcount = 0;
+	wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Flush characters from the lower buffer. We may not have user context
+ *	so we cannot sleep waiting for it to complete. Also we need to check
+ *	if there is chars for this port in the TX cook buffer, and flush them
+ *	as well.
+ */
+
+static void stli_flushbuffer(struct tty_struct *tty)
+{
+	stliport_t	*portp;
+	stlibrd_t	*brdp;
+	unsigned long	ftype, flags;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_flushbuffer(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return;
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	if (tty == stli_txcooktty) {
+		stli_txcooktty = (struct tty_struct *) NULL;
+		stli_txcooksize = 0;
+		stli_txcookrealsize = 0;
+	}
+	if (test_bit(ST_CMDING, &portp->state)) {
+		set_bit(ST_DOFLUSHTX, &portp->state);
+	} else {
+		ftype = FLUSHTX;
+		if (test_bit(ST_DOFLUSHRX, &portp->state)) {
+			ftype |= FLUSHRX;
+			clear_bit(ST_DOFLUSHRX, &portp->state);
+		}
+		stli_sendcmd(brdp, portp, A_FLUSH, &ftype,
+			sizeof(unsigned long), 0);
+	}
+	restore_flags(flags);
+
+	wake_up_interruptible(&tty->write_wait);
+	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+	    tty->ldisc.write_wakeup)
+		(tty->ldisc.write_wakeup)(tty);
+}
+
+/*****************************************************************************/
+
+static void stli_breakctl(struct tty_struct *tty, int state)
+{
+	stlibrd_t	*brdp;
+	stliport_t	*portp;
+	long		arg;
+	/* long savestate, savetime; */
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_breakctl(tty=%x,state=%d)\n", (int) tty, state);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return;
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return;
+
+/*
+ *	Due to a bug in the tty send_break() code we need to preserve
+ *	the current process state and timeout...
+	savetime = current->timeout;
+	savestate = current->state;
+ */
+
+	arg = (state == -1) ? BREAKON : BREAKOFF;
+	stli_cmdwait(brdp, portp, A_BREAK, &arg, sizeof(long), 0);
+
+/*
+ *
+	current->timeout = savetime;
+	current->state = savestate;
+ */
+}
+
+/*****************************************************************************/
+
+static void stli_waituntilsent(struct tty_struct *tty, int timeout)
+{
+	stliport_t	*portp;
+	unsigned long	tend;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_waituntilsent(tty=%x,timeout=%x)\n", (int) tty, timeout);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+
+	if (timeout == 0)
+		timeout = HZ;
+	tend = jiffies + timeout;
+
+	while (test_bit(ST_TXBUSY, &portp->state)) {
+		if (signal_pending(current))
+			break;
+		msleep_interruptible(20);
+		if (time_after_eq(jiffies, tend))
+			break;
+	}
+}
+
+/*****************************************************************************/
+
+static void stli_sendxchar(struct tty_struct *tty, char ch)
+{
+	stlibrd_t	*brdp;
+	stliport_t	*portp;
+	asyctrl_t	actrl;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_sendxchar(tty=%x,ch=%x)\n", (int) tty, ch);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stliport_t *) NULL)
+		return;
+	if ((portp->brdnr < 0) || (portp->brdnr >= stli_nrbrds))
+		return;
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return;
+
+	memset(&actrl, 0, sizeof(asyctrl_t));
+	if (ch == STOP_CHAR(tty)) {
+		actrl.rxctrl = CT_STOPFLOW;
+	} else if (ch == START_CHAR(tty)) {
+		actrl.rxctrl = CT_STARTFLOW;
+	} else {
+		actrl.txctrl = CT_SENDCHR;
+		actrl.tximdch = ch;
+	}
+
+	stli_cmdwait(brdp, portp, A_PORTCTRL, &actrl, sizeof(asyctrl_t), 0);
+}
+
+/*****************************************************************************/
+
+#define	MAXLINE		80
+
+/*
+ *	Format info for a specified port. The line is deliberately limited
+ *	to 80 characters. (If it is too long it will be truncated, if too
+ *	short then padded with spaces).
+ */
+
+static int stli_portinfo(stlibrd_t *brdp, stliport_t *portp, int portnr, char *pos)
+{
+	char	*sp, *uart;
+	int	rc, cnt;
+
+	rc = stli_portcmdstats(portp);
+
+	uart = "UNKNOWN";
+	if (brdp->state & BST_STARTED) {
+		switch (stli_comstats.hwid) {
+		case 0:		uart = "2681"; break;
+		case 1:		uart = "SC26198"; break;
+		default:	uart = "CD1400"; break;
+		}
+	}
+
+	sp = pos;
+	sp += sprintf(sp, "%d: uart:%s ", portnr, uart);
+
+	if ((brdp->state & BST_STARTED) && (rc >= 0)) {
+		sp += sprintf(sp, "tx:%d rx:%d", (int) stli_comstats.txtotal,
+			(int) stli_comstats.rxtotal);
+
+		if (stli_comstats.rxframing)
+			sp += sprintf(sp, " fe:%d",
+				(int) stli_comstats.rxframing);
+		if (stli_comstats.rxparity)
+			sp += sprintf(sp, " pe:%d",
+				(int) stli_comstats.rxparity);
+		if (stli_comstats.rxbreaks)
+			sp += sprintf(sp, " brk:%d",
+				(int) stli_comstats.rxbreaks);
+		if (stli_comstats.rxoverrun)
+			sp += sprintf(sp, " oe:%d",
+				(int) stli_comstats.rxoverrun);
+
+		cnt = sprintf(sp, "%s%s%s%s%s ",
+			(stli_comstats.signals & TIOCM_RTS) ? "|RTS" : "",
+			(stli_comstats.signals & TIOCM_CTS) ? "|CTS" : "",
+			(stli_comstats.signals & TIOCM_DTR) ? "|DTR" : "",
+			(stli_comstats.signals & TIOCM_CD) ? "|DCD" : "",
+			(stli_comstats.signals & TIOCM_DSR) ? "|DSR" : "");
+		*sp = ' ';
+		sp += cnt;
+	}
+
+	for (cnt = (sp - pos); (cnt < (MAXLINE - 1)); cnt++)
+		*sp++ = ' ';
+	if (cnt >= MAXLINE)
+		pos[(MAXLINE - 2)] = '+';
+	pos[(MAXLINE - 1)] = '\n';
+
+	return(MAXLINE);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Port info, read from the /proc file system.
+ */
+
+static int stli_readproc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+	stlibrd_t	*brdp;
+	stliport_t	*portp;
+	int		brdnr, portnr, totalport;
+	int		curoff, maxoff;
+	char		*pos;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_readproc(page=%x,start=%x,off=%x,count=%d,eof=%x,"
+		"data=%x\n", (int) page, (int) start, (int) off, count,
+		(int) eof, (int) data);
+#endif
+
+	pos = page;
+	totalport = 0;
+	curoff = 0;
+
+	if (off == 0) {
+		pos += sprintf(pos, "%s: version %s", stli_drvtitle,
+			stli_drvversion);
+		while (pos < (page + MAXLINE - 1))
+			*pos++ = ' ';
+		*pos++ = '\n';
+	}
+	curoff =  MAXLINE;
+
+/*
+ *	We scan through for each board, panel and port. The offset is
+ *	calculated on the fly, and irrelevant ports are skipped.
+ */
+	for (brdnr = 0; (brdnr < stli_nrbrds); brdnr++) {
+		brdp = stli_brds[brdnr];
+		if (brdp == (stlibrd_t *) NULL)
+			continue;
+		if (brdp->state == 0)
+			continue;
+
+		maxoff = curoff + (brdp->nrports * MAXLINE);
+		if (off >= maxoff) {
+			curoff = maxoff;
+			continue;
+		}
+
+		totalport = brdnr * STL_MAXPORTS;
+		for (portnr = 0; (portnr < brdp->nrports); portnr++,
+		    totalport++) {
+			portp = brdp->ports[portnr];
+			if (portp == (stliport_t *) NULL)
+				continue;
+			if (off >= (curoff += MAXLINE))
+				continue;
+			if ((pos - page + MAXLINE) > count)
+				goto stli_readdone;
+			pos += stli_portinfo(brdp, portp, totalport, pos);
+		}
+	}
+
+	*eof = 1;
+
+stli_readdone:
+	*start = page;
+	return(pos - page);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Generic send command routine. This will send a message to the slave,
+ *	of the specified type with the specified argument. Must be very
+ *	careful of data that will be copied out from shared memory -
+ *	containing command results. The command completion is all done from
+ *	a poll routine that does not have user context. Therefore you cannot
+ *	copy back directly into user space, or to the kernel stack of a
+ *	process. This routine does not sleep, so can be called from anywhere.
+ */
+
+static void stli_sendcmd(stlibrd_t *brdp, stliport_t *portp, unsigned long cmd, void *arg, int size, int copyback)
+{
+	volatile cdkhdr_t	*hdrp;
+	volatile cdkctrl_t	*cp;
+	volatile unsigned char	*bits;
+	unsigned long		flags;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_sendcmd(brdp=%x,portp=%x,cmd=%x,arg=%x,size=%d,"
+		"copyback=%d)\n", (int) brdp, (int) portp, (int) cmd,
+		(int) arg, size, copyback);
+#endif
+
+	save_flags(flags);
+	cli();
+
+	if (test_bit(ST_CMDING, &portp->state)) {
+		printk(KERN_ERR "STALLION: command already busy, cmd=%x!\n",
+				(int) cmd);
+		restore_flags(flags);
+		return;
+	}
+
+	EBRDENABLE(brdp);
+	cp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->ctrl;
+	if (size > 0) {
+		memcpy((void *) &(cp->args[0]), arg, size);
+		if (copyback) {
+			portp->argp = arg;
+			portp->argsize = size;
+		}
+	}
+	cp->status = 0;
+	cp->cmd = cmd;
+	hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+	bits = ((volatile unsigned char *) hdrp) + brdp->slaveoffset +
+		portp->portidx;
+	*bits |= portp->portbit;
+	set_bit(ST_CMDING, &portp->state);
+	EBRDDISABLE(brdp);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Read data from shared memory. This assumes that the shared memory
+ *	is enabled and that interrupts are off. Basically we just empty out
+ *	the shared memory buffer into the tty buffer. Must be careful to
+ *	handle the case where we fill up the tty buffer, but still have
+ *	more chars to unload.
+ */
+
+static void stli_read(stlibrd_t *brdp, stliport_t *portp)
+{
+	volatile cdkasyrq_t	*rp;
+	volatile char		*shbuf;
+	struct tty_struct	*tty;
+	unsigned int		head, tail, size;
+	unsigned int		len, stlen;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_read(brdp=%x,portp=%d)\n",
+			(int) brdp, (int) portp);
+#endif
+
+	if (test_bit(ST_RXSTOP, &portp->state))
+		return;
+	tty = portp->tty;
+	if (tty == (struct tty_struct *) NULL)
+		return;
+
+	rp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->rxq;
+	head = (unsigned int) rp->head;
+	if (head != ((unsigned int) rp->head))
+		head = (unsigned int) rp->head;
+	tail = (unsigned int) rp->tail;
+	size = portp->rxsize;
+	if (head >= tail) {
+		len = head - tail;
+		stlen = len;
+	} else {
+		len = size - (tail - head);
+		stlen = size - tail;
+	}
+
+	len = MIN(len, (TTY_FLIPBUF_SIZE - tty->flip.count));
+	shbuf = (volatile char *) EBRDGETMEMPTR(brdp, portp->rxoffset);
+
+	while (len > 0) {
+		stlen = MIN(len, stlen);
+		memcpy(tty->flip.char_buf_ptr, (char *) (shbuf + tail), stlen);
+		memset(tty->flip.flag_buf_ptr, 0, stlen);
+		tty->flip.char_buf_ptr += stlen;
+		tty->flip.flag_buf_ptr += stlen;
+		tty->flip.count += stlen;
+
+		len -= stlen;
+		tail += stlen;
+		if (tail >= size) {
+			tail = 0;
+			stlen = head;
+		}
+	}
+	rp = &((volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr))->rxq;
+	rp->tail = tail;
+
+	if (head != tail)
+		set_bit(ST_RXING, &portp->state);
+
+	tty_schedule_flip(tty);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Set up and carry out any delayed commands. There is only a small set
+ *	of slave commands that can be done "off-level". So it is not too
+ *	difficult to deal with them here.
+ */
+
+static void stli_dodelaycmd(stliport_t *portp, volatile cdkctrl_t *cp)
+{
+	int	cmd;
+
+	if (test_bit(ST_DOSIGS, &portp->state)) {
+		if (test_bit(ST_DOFLUSHTX, &portp->state) &&
+		    test_bit(ST_DOFLUSHRX, &portp->state))
+			cmd = A_SETSIGNALSF;
+		else if (test_bit(ST_DOFLUSHTX, &portp->state))
+			cmd = A_SETSIGNALSFTX;
+		else if (test_bit(ST_DOFLUSHRX, &portp->state))
+			cmd = A_SETSIGNALSFRX;
+		else
+			cmd = A_SETSIGNALS;
+		clear_bit(ST_DOFLUSHTX, &portp->state);
+		clear_bit(ST_DOFLUSHRX, &portp->state);
+		clear_bit(ST_DOSIGS, &portp->state);
+		memcpy((void *) &(cp->args[0]), (void *) &portp->asig,
+			sizeof(asysigs_t));
+		cp->status = 0;
+		cp->cmd = cmd;
+		set_bit(ST_CMDING, &portp->state);
+	} else if (test_bit(ST_DOFLUSHTX, &portp->state) ||
+	    test_bit(ST_DOFLUSHRX, &portp->state)) {
+		cmd = ((test_bit(ST_DOFLUSHTX, &portp->state)) ? FLUSHTX : 0);
+		cmd |= ((test_bit(ST_DOFLUSHRX, &portp->state)) ? FLUSHRX : 0);
+		clear_bit(ST_DOFLUSHTX, &portp->state);
+		clear_bit(ST_DOFLUSHRX, &portp->state);
+		memcpy((void *) &(cp->args[0]), (void *) &cmd, sizeof(int));
+		cp->status = 0;
+		cp->cmd = A_FLUSH;
+		set_bit(ST_CMDING, &portp->state);
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Host command service checking. This handles commands or messages
+ *	coming from the slave to the host. Must have board shared memory
+ *	enabled and interrupts off when called. Notice that by servicing the
+ *	read data last we don't need to change the shared memory pointer
+ *	during processing (which is a slow IO operation).
+ *	Return value indicates if this port is still awaiting actions from
+ *	the slave (like open, command, or even TX data being sent). If 0
+ *	then port is still busy, otherwise no longer busy.
+ */
+
+static int stli_hostcmd(stlibrd_t *brdp, stliport_t *portp)
+{
+	volatile cdkasy_t	*ap;
+	volatile cdkctrl_t	*cp;
+	struct tty_struct	*tty;
+	asynotify_t		nt;
+	unsigned long		oldsigs;
+	int			rc, donerx;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_hostcmd(brdp=%x,channr=%d)\n",
+			(int) brdp, channr);
+#endif
+
+	ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+	cp = &ap->ctrl;
+
+/*
+ *	Check if we are waiting for an open completion message.
+ */
+	if (test_bit(ST_OPENING, &portp->state)) {
+		rc = (int) cp->openarg;
+		if ((cp->open == 0) && (rc != 0)) {
+			if (rc > 0)
+				rc--;
+			cp->openarg = 0;
+			portp->rc = rc;
+			clear_bit(ST_OPENING, &portp->state);
+			wake_up_interruptible(&portp->raw_wait);
+		}
+	}
+
+/*
+ *	Check if we are waiting for a close completion message.
+ */
+	if (test_bit(ST_CLOSING, &portp->state)) {
+		rc = (int) cp->closearg;
+		if ((cp->close == 0) && (rc != 0)) {
+			if (rc > 0)
+				rc--;
+			cp->closearg = 0;
+			portp->rc = rc;
+			clear_bit(ST_CLOSING, &portp->state);
+			wake_up_interruptible(&portp->raw_wait);
+		}
+	}
+
+/*
+ *	Check if we are waiting for a command completion message. We may
+ *	need to copy out the command results associated with this command.
+ */
+	if (test_bit(ST_CMDING, &portp->state)) {
+		rc = cp->status;
+		if ((cp->cmd == 0) && (rc != 0)) {
+			if (rc > 0)
+				rc--;
+			if (portp->argp != (void *) NULL) {
+				memcpy(portp->argp, (void *) &(cp->args[0]),
+					portp->argsize);
+				portp->argp = (void *) NULL;
+			}
+			cp->status = 0;
+			portp->rc = rc;
+			clear_bit(ST_CMDING, &portp->state);
+			stli_dodelaycmd(portp, cp);
+			wake_up_interruptible(&portp->raw_wait);
+		}
+	}
+
+/*
+ *	Check for any notification messages ready. This includes lots of
+ *	different types of events - RX chars ready, RX break received,
+ *	TX data low or empty in the slave, modem signals changed state.
+ */
+	donerx = 0;
+
+	if (ap->notify) {
+		nt = ap->changed;
+		ap->notify = 0;
+		tty = portp->tty;
+
+		if (nt.signal & SG_DCD) {
+			oldsigs = portp->sigs;
+			portp->sigs = stli_mktiocm(nt.sigvalue);
+			clear_bit(ST_GETSIGS, &portp->state);
+			if ((portp->sigs & TIOCM_CD) &&
+			    ((oldsigs & TIOCM_CD) == 0))
+				wake_up_interruptible(&portp->open_wait);
+			if ((oldsigs & TIOCM_CD) &&
+			    ((portp->sigs & TIOCM_CD) == 0)) {
+				if (portp->flags & ASYNC_CHECK_CD) {
+					if (tty)
+						schedule_work(&portp->tqhangup);
+				}
+			}
+		}
+
+		if (nt.data & DT_TXEMPTY)
+			clear_bit(ST_TXBUSY, &portp->state);
+		if (nt.data & (DT_TXEMPTY | DT_TXLOW)) {
+			if (tty != (struct tty_struct *) NULL) {
+				if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+				    tty->ldisc.write_wakeup) {
+					(tty->ldisc.write_wakeup)(tty);
+					EBRDENABLE(brdp);
+				}
+				wake_up_interruptible(&tty->write_wait);
+			}
+		}
+
+		if ((nt.data & DT_RXBREAK) && (portp->rxmarkmsk & BRKINT)) {
+			if (tty != (struct tty_struct *) NULL) {
+				if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+					tty->flip.count++;
+					*tty->flip.flag_buf_ptr++ = TTY_BREAK;
+					*tty->flip.char_buf_ptr++ = 0;
+					if (portp->flags & ASYNC_SAK) {
+						do_SAK(tty);
+						EBRDENABLE(brdp);
+					}
+					tty_schedule_flip(tty);
+				}
+			}
+		}
+
+		if (nt.data & DT_RXBUSY) {
+			donerx++;
+			stli_read(brdp, portp);
+		}
+	}
+
+/*
+ *	It might seem odd that we are checking for more RX chars here.
+ *	But, we need to handle the case where the tty buffer was previously
+ *	filled, but we had more characters to pass up. The slave will not
+ *	send any more RX notify messages until the RX buffer has been emptied.
+ *	But it will leave the service bits on (since the buffer is not empty).
+ *	So from here we can try to process more RX chars.
+ */
+	if ((!donerx) && test_bit(ST_RXING, &portp->state)) {
+		clear_bit(ST_RXING, &portp->state);
+		stli_read(brdp, portp);
+	}
+
+	return((test_bit(ST_OPENING, &portp->state) ||
+		test_bit(ST_CLOSING, &portp->state) ||
+		test_bit(ST_CMDING, &portp->state) ||
+		test_bit(ST_TXBUSY, &portp->state) ||
+		test_bit(ST_RXING, &portp->state)) ? 0 : 1);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Service all ports on a particular board. Assumes that the boards
+ *	shared memory is enabled, and that the page pointer is pointed
+ *	at the cdk header structure.
+ */
+
+static void stli_brdpoll(stlibrd_t *brdp, volatile cdkhdr_t *hdrp)
+{
+	stliport_t	*portp;
+	unsigned char	hostbits[(STL_MAXCHANS / 8) + 1];
+	unsigned char	slavebits[(STL_MAXCHANS / 8) + 1];
+	unsigned char	*slavep;
+	int		bitpos, bitat, bitsize;
+	int 		channr, nrdevs, slavebitchange;
+
+	bitsize = brdp->bitsize;
+	nrdevs = brdp->nrdevs;
+
+/*
+ *	Check if slave wants any service. Basically we try to do as
+ *	little work as possible here. There are 2 levels of service
+ *	bits. So if there is nothing to do we bail early. We check
+ *	8 service bits at a time in the inner loop, so we can bypass
+ *	the lot if none of them want service.
+ */
+	memcpy(&hostbits[0], (((unsigned char *) hdrp) + brdp->hostoffset),
+		bitsize);
+
+	memset(&slavebits[0], 0, bitsize);
+	slavebitchange = 0;
+
+	for (bitpos = 0; (bitpos < bitsize); bitpos++) {
+		if (hostbits[bitpos] == 0)
+			continue;
+		channr = bitpos * 8;
+		for (bitat = 0x1; (channr < nrdevs); channr++, bitat <<= 1) {
+			if (hostbits[bitpos] & bitat) {
+				portp = brdp->ports[(channr - 1)];
+				if (stli_hostcmd(brdp, portp)) {
+					slavebitchange++;
+					slavebits[bitpos] |= bitat;
+				}
+			}
+		}
+	}
+
+/*
+ *	If any of the ports are no longer busy then update them in the
+ *	slave request bits. We need to do this after, since a host port
+ *	service may initiate more slave requests.
+ */
+	if (slavebitchange) {
+		hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+		slavep = ((unsigned char *) hdrp) + brdp->slaveoffset;
+		for (bitpos = 0; (bitpos < bitsize); bitpos++) {
+			if (slavebits[bitpos])
+				slavep[bitpos] &= ~slavebits[bitpos];
+		}
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Driver poll routine. This routine polls the boards in use and passes
+ *	messages back up to host when necessary. This is actually very
+ *	CPU efficient, since we will always have the kernel poll clock, it
+ *	adds only a few cycles when idle (since board service can be
+ *	determined very easily), but when loaded generates no interrupts
+ *	(with their expensive associated context change).
+ */
+
+static void stli_poll(unsigned long arg)
+{
+	volatile cdkhdr_t	*hdrp;
+	stlibrd_t		*brdp;
+	int 			brdnr;
+
+	stli_timerlist.expires = STLI_TIMEOUT;
+	add_timer(&stli_timerlist);
+
+/*
+ *	Check each board and do any servicing required.
+ */
+	for (brdnr = 0; (brdnr < stli_nrbrds); brdnr++) {
+		brdp = stli_brds[brdnr];
+		if (brdp == (stlibrd_t *) NULL)
+			continue;
+		if ((brdp->state & BST_STARTED) == 0)
+			continue;
+
+		EBRDENABLE(brdp);
+		hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+		if (hdrp->hostreq)
+			stli_brdpoll(brdp, hdrp);
+		EBRDDISABLE(brdp);
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Translate the termios settings into the port setting structure of
+ *	the slave.
+ */
+
+static void stli_mkasyport(stliport_t *portp, asyport_t *pp, struct termios *tiosp)
+{
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_mkasyport(portp=%x,pp=%x,tiosp=%d)\n",
+		(int) portp, (int) pp, (int) tiosp);
+#endif
+
+	memset(pp, 0, sizeof(asyport_t));
+
+/*
+ *	Start of by setting the baud, char size, parity and stop bit info.
+ */
+	pp->baudout = tiosp->c_cflag & CBAUD;
+	if (pp->baudout & CBAUDEX) {
+		pp->baudout &= ~CBAUDEX;
+		if ((pp->baudout < 1) || (pp->baudout > 4))
+			tiosp->c_cflag &= ~CBAUDEX;
+		else
+			pp->baudout += 15;
+	}
+	pp->baudout = stli_baudrates[pp->baudout];
+	if ((tiosp->c_cflag & CBAUD) == B38400) {
+		if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			pp->baudout = 57600;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			pp->baudout = 115200;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+			pp->baudout = 230400;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+			pp->baudout = 460800;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+			pp->baudout = (portp->baud_base / portp->custom_divisor);
+	}
+	if (pp->baudout > STL_MAXBAUD)
+		pp->baudout = STL_MAXBAUD;
+	pp->baudin = pp->baudout;
+
+	switch (tiosp->c_cflag & CSIZE) {
+	case CS5:
+		pp->csize = 5;
+		break;
+	case CS6:
+		pp->csize = 6;
+		break;
+	case CS7:
+		pp->csize = 7;
+		break;
+	default:
+		pp->csize = 8;
+		break;
+	}
+
+	if (tiosp->c_cflag & CSTOPB)
+		pp->stopbs = PT_STOP2;
+	else
+		pp->stopbs = PT_STOP1;
+
+	if (tiosp->c_cflag & PARENB) {
+		if (tiosp->c_cflag & PARODD)
+			pp->parity = PT_ODDPARITY;
+		else
+			pp->parity = PT_EVENPARITY;
+	} else {
+		pp->parity = PT_NOPARITY;
+	}
+
+/*
+ *	Set up any flow control options enabled.
+ */
+	if (tiosp->c_iflag & IXON) {
+		pp->flow |= F_IXON;
+		if (tiosp->c_iflag & IXANY)
+			pp->flow |= F_IXANY;
+	}
+	if (tiosp->c_cflag & CRTSCTS)
+		pp->flow |= (F_RTSFLOW | F_CTSFLOW);
+
+	pp->startin = tiosp->c_cc[VSTART];
+	pp->stopin = tiosp->c_cc[VSTOP];
+	pp->startout = tiosp->c_cc[VSTART];
+	pp->stopout = tiosp->c_cc[VSTOP];
+
+/*
+ *	Set up the RX char marking mask with those RX error types we must
+ *	catch. We can get the slave to help us out a little here, it will
+ *	ignore parity errors and breaks for us, and mark parity errors in
+ *	the data stream.
+ */
+	if (tiosp->c_iflag & IGNPAR)
+		pp->iflag |= FI_IGNRXERRS;
+	if (tiosp->c_iflag & IGNBRK)
+		pp->iflag |= FI_IGNBREAK;
+
+	portp->rxmarkmsk = 0;
+	if (tiosp->c_iflag & (INPCK | PARMRK))
+		pp->iflag |= FI_1MARKRXERRS;
+	if (tiosp->c_iflag & BRKINT)
+		portp->rxmarkmsk |= BRKINT;
+
+/*
+ *	Set up clocal processing as required.
+ */
+	if (tiosp->c_cflag & CLOCAL)
+		portp->flags &= ~ASYNC_CHECK_CD;
+	else
+		portp->flags |= ASYNC_CHECK_CD;
+
+/*
+ *	Transfer any persistent flags into the asyport structure.
+ */
+	pp->pflag = (portp->pflag & 0xffff);
+	pp->vmin = (portp->pflag & P_RXIMIN) ? 1 : 0;
+	pp->vtime = (portp->pflag & P_RXITIME) ? 1 : 0;
+	pp->cc[1] = (portp->pflag & P_RXTHOLD) ? 1 : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Construct a slave signals structure for setting the DTR and RTS
+ *	signals as specified.
+ */
+
+static void stli_mkasysigs(asysigs_t *sp, int dtr, int rts)
+{
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_mkasysigs(sp=%x,dtr=%d,rts=%d)\n",
+			(int) sp, dtr, rts);
+#endif
+
+	memset(sp, 0, sizeof(asysigs_t));
+	if (dtr >= 0) {
+		sp->signal |= SG_DTR;
+		sp->sigvalue |= ((dtr > 0) ? SG_DTR : 0);
+	}
+	if (rts >= 0) {
+		sp->signal |= SG_RTS;
+		sp->sigvalue |= ((rts > 0) ? SG_RTS : 0);
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Convert the signals returned from the slave into a local TIOCM type
+ *	signals value. We keep them locally in TIOCM format.
+ */
+
+static long stli_mktiocm(unsigned long sigvalue)
+{
+	long	tiocm;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_mktiocm(sigvalue=%x)\n", (int) sigvalue);
+#endif
+
+	tiocm = 0;
+	tiocm |= ((sigvalue & SG_DCD) ? TIOCM_CD : 0);
+	tiocm |= ((sigvalue & SG_CTS) ? TIOCM_CTS : 0);
+	tiocm |= ((sigvalue & SG_RI) ? TIOCM_RI : 0);
+	tiocm |= ((sigvalue & SG_DSR) ? TIOCM_DSR : 0);
+	tiocm |= ((sigvalue & SG_DTR) ? TIOCM_DTR : 0);
+	tiocm |= ((sigvalue & SG_RTS) ? TIOCM_RTS : 0);
+	return(tiocm);
+}
+
+/*****************************************************************************/
+
+/*
+ *	All panels and ports actually attached have been worked out. All
+ *	we need to do here is set up the appropriate per port data structures.
+ */
+
+static int stli_initports(stlibrd_t *brdp)
+{
+	stliport_t	*portp;
+	int		i, panelnr, panelport;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_initports(brdp=%x)\n", (int) brdp);
+#endif
+
+	for (i = 0, panelnr = 0, panelport = 0; (i < brdp->nrports); i++) {
+		portp = (stliport_t *) stli_memalloc(sizeof(stliport_t));
+		if (portp == (stliport_t *) NULL) {
+			printk("STALLION: failed to allocate port structure\n");
+			continue;
+		}
+
+		memset(portp, 0, sizeof(stliport_t));
+		portp->magic = STLI_PORTMAGIC;
+		portp->portnr = i;
+		portp->brdnr = brdp->brdnr;
+		portp->panelnr = panelnr;
+		portp->baud_base = STL_BAUDBASE;
+		portp->close_delay = STL_CLOSEDELAY;
+		portp->closing_wait = 30 * HZ;
+		INIT_WORK(&portp->tqhangup, stli_dohangup, portp);
+		init_waitqueue_head(&portp->open_wait);
+		init_waitqueue_head(&portp->close_wait);
+		init_waitqueue_head(&portp->raw_wait);
+		panelport++;
+		if (panelport >= brdp->panels[panelnr]) {
+			panelport = 0;
+			panelnr++;
+		}
+		brdp->ports[i] = portp;
+	}
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	All the following routines are board specific hardware operations.
+ */
+
+static void stli_ecpinit(stlibrd_t *brdp)
+{
+	unsigned long	memconf;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecpinit(brdp=%d)\n", (int) brdp);
+#endif
+
+	outb(ECP_ATSTOP, (brdp->iobase + ECP_ATCONFR));
+	udelay(10);
+	outb(ECP_ATDISABLE, (brdp->iobase + ECP_ATCONFR));
+	udelay(100);
+
+	memconf = (brdp->memaddr & ECP_ATADDRMASK) >> ECP_ATADDRSHFT;
+	outb(memconf, (brdp->iobase + ECP_ATMEMAR));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpenable(stlibrd_t *brdp)
+{	
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecpenable(brdp=%x)\n", (int) brdp);
+#endif
+	outb(ECP_ATENABLE, (brdp->iobase + ECP_ATCONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpdisable(stlibrd_t *brdp)
+{	
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecpdisable(brdp=%x)\n", (int) brdp);
+#endif
+	outb(ECP_ATDISABLE, (brdp->iobase + ECP_ATCONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_ecpgetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{	
+	void		*ptr;
+	unsigned char	val;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecpgetmemptr(brdp=%x,offset=%x)\n", (int) brdp,
+		(int) offset);
+#endif
+
+	if (offset > brdp->memsize) {
+		printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+				"range at line=%d(%d), brd=%d\n",
+			(int) offset, line, __LINE__, brdp->brdnr);
+		ptr = NULL;
+		val = 0;
+	} else {
+		ptr = brdp->membase + (offset % ECP_ATPAGESIZE);
+		val = (unsigned char) (offset / ECP_ATPAGESIZE);
+	}
+	outb(val, (brdp->iobase + ECP_ATMEMPR));
+	return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_ecpreset(stlibrd_t *brdp)
+{	
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecpreset(brdp=%x)\n", (int) brdp);
+#endif
+
+	outb(ECP_ATSTOP, (brdp->iobase + ECP_ATCONFR));
+	udelay(10);
+	outb(ECP_ATDISABLE, (brdp->iobase + ECP_ATCONFR));
+	udelay(500);
+}
+
+/*****************************************************************************/
+
+static void stli_ecpintr(stlibrd_t *brdp)
+{	
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecpintr(brdp=%x)\n", (int) brdp);
+#endif
+	outb(0x1, brdp->iobase);
+}
+
+/*****************************************************************************/
+
+/*
+ *	The following set of functions act on ECP EISA boards.
+ */
+
+static void stli_ecpeiinit(stlibrd_t *brdp)
+{
+	unsigned long	memconf;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecpeiinit(brdp=%x)\n", (int) brdp);
+#endif
+
+	outb(0x1, (brdp->iobase + ECP_EIBRDENAB));
+	outb(ECP_EISTOP, (brdp->iobase + ECP_EICONFR));
+	udelay(10);
+	outb(ECP_EIDISABLE, (brdp->iobase + ECP_EICONFR));
+	udelay(500);
+
+	memconf = (brdp->memaddr & ECP_EIADDRMASKL) >> ECP_EIADDRSHFTL;
+	outb(memconf, (brdp->iobase + ECP_EIMEMARL));
+	memconf = (brdp->memaddr & ECP_EIADDRMASKH) >> ECP_EIADDRSHFTH;
+	outb(memconf, (brdp->iobase + ECP_EIMEMARH));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpeienable(stlibrd_t *brdp)
+{	
+	outb(ECP_EIENABLE, (brdp->iobase + ECP_EICONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpeidisable(stlibrd_t *brdp)
+{	
+	outb(ECP_EIDISABLE, (brdp->iobase + ECP_EICONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_ecpeigetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{	
+	void		*ptr;
+	unsigned char	val;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecpeigetmemptr(brdp=%x,offset=%x,line=%d)\n",
+		(int) brdp, (int) offset, line);
+#endif
+
+	if (offset > brdp->memsize) {
+		printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+				"range at line=%d(%d), brd=%d\n",
+			(int) offset, line, __LINE__, brdp->brdnr);
+		ptr = NULL;
+		val = 0;
+	} else {
+		ptr = brdp->membase + (offset % ECP_EIPAGESIZE);
+		if (offset < ECP_EIPAGESIZE)
+			val = ECP_EIENABLE;
+		else
+			val = ECP_EIENABLE | 0x40;
+	}
+	outb(val, (brdp->iobase + ECP_EICONFR));
+	return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_ecpeireset(stlibrd_t *brdp)
+{	
+	outb(ECP_EISTOP, (brdp->iobase + ECP_EICONFR));
+	udelay(10);
+	outb(ECP_EIDISABLE, (brdp->iobase + ECP_EICONFR));
+	udelay(500);
+}
+
+/*****************************************************************************/
+
+/*
+ *	The following set of functions act on ECP MCA boards.
+ */
+
+static void stli_ecpmcenable(stlibrd_t *brdp)
+{	
+	outb(ECP_MCENABLE, (brdp->iobase + ECP_MCCONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_ecpmcdisable(stlibrd_t *brdp)
+{	
+	outb(ECP_MCDISABLE, (brdp->iobase + ECP_MCCONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_ecpmcgetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{	
+	void		*ptr;
+	unsigned char	val;
+
+	if (offset > brdp->memsize) {
+		printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+				"range at line=%d(%d), brd=%d\n",
+			(int) offset, line, __LINE__, brdp->brdnr);
+		ptr = NULL;
+		val = 0;
+	} else {
+		ptr = brdp->membase + (offset % ECP_MCPAGESIZE);
+		val = ((unsigned char) (offset / ECP_MCPAGESIZE)) | ECP_MCENABLE;
+	}
+	outb(val, (brdp->iobase + ECP_MCCONFR));
+	return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_ecpmcreset(stlibrd_t *brdp)
+{	
+	outb(ECP_MCSTOP, (brdp->iobase + ECP_MCCONFR));
+	udelay(10);
+	outb(ECP_MCDISABLE, (brdp->iobase + ECP_MCCONFR));
+	udelay(500);
+}
+
+/*****************************************************************************/
+
+/*
+ *	The following set of functions act on ECP PCI boards.
+ */
+
+static void stli_ecppciinit(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecppciinit(brdp=%x)\n", (int) brdp);
+#endif
+
+	outb(ECP_PCISTOP, (brdp->iobase + ECP_PCICONFR));
+	udelay(10);
+	outb(0, (brdp->iobase + ECP_PCICONFR));
+	udelay(500);
+}
+
+/*****************************************************************************/
+
+static char *stli_ecppcigetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{	
+	void		*ptr;
+	unsigned char	val;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_ecppcigetmemptr(brdp=%x,offset=%x,line=%d)\n",
+		(int) brdp, (int) offset, line);
+#endif
+
+	if (offset > brdp->memsize) {
+		printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+				"range at line=%d(%d), board=%d\n",
+				(int) offset, line, __LINE__, brdp->brdnr);
+		ptr = NULL;
+		val = 0;
+	} else {
+		ptr = brdp->membase + (offset % ECP_PCIPAGESIZE);
+		val = (offset / ECP_PCIPAGESIZE) << 1;
+	}
+	outb(val, (brdp->iobase + ECP_PCICONFR));
+	return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_ecppcireset(stlibrd_t *brdp)
+{	
+	outb(ECP_PCISTOP, (brdp->iobase + ECP_PCICONFR));
+	udelay(10);
+	outb(0, (brdp->iobase + ECP_PCICONFR));
+	udelay(500);
+}
+
+/*****************************************************************************/
+
+/*
+ *	The following routines act on ONboards.
+ */
+
+static void stli_onbinit(stlibrd_t *brdp)
+{
+	unsigned long	memconf;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbinit(brdp=%d)\n", (int) brdp);
+#endif
+
+	outb(ONB_ATSTOP, (brdp->iobase + ONB_ATCONFR));
+	udelay(10);
+	outb(ONB_ATDISABLE, (brdp->iobase + ONB_ATCONFR));
+	mdelay(1000);
+
+	memconf = (brdp->memaddr & ONB_ATADDRMASK) >> ONB_ATADDRSHFT;
+	outb(memconf, (brdp->iobase + ONB_ATMEMAR));
+	outb(0x1, brdp->iobase);
+	mdelay(1);
+}
+
+/*****************************************************************************/
+
+static void stli_onbenable(stlibrd_t *brdp)
+{	
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbenable(brdp=%x)\n", (int) brdp);
+#endif
+	outb((brdp->enabval | ONB_ATENABLE), (brdp->iobase + ONB_ATCONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_onbdisable(stlibrd_t *brdp)
+{	
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbdisable(brdp=%x)\n", (int) brdp);
+#endif
+	outb((brdp->enabval | ONB_ATDISABLE), (brdp->iobase + ONB_ATCONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_onbgetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{	
+	void	*ptr;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbgetmemptr(brdp=%x,offset=%x)\n", (int) brdp,
+		(int) offset);
+#endif
+
+	if (offset > brdp->memsize) {
+		printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+				"range at line=%d(%d), brd=%d\n",
+				(int) offset, line, __LINE__, brdp->brdnr);
+		ptr = NULL;
+	} else {
+		ptr = brdp->membase + (offset % ONB_ATPAGESIZE);
+	}
+	return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_onbreset(stlibrd_t *brdp)
+{	
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbreset(brdp=%x)\n", (int) brdp);
+#endif
+
+	outb(ONB_ATSTOP, (brdp->iobase + ONB_ATCONFR));
+	udelay(10);
+	outb(ONB_ATDISABLE, (brdp->iobase + ONB_ATCONFR));
+	mdelay(1000);
+}
+
+/*****************************************************************************/
+
+/*
+ *	The following routines act on ONboard EISA.
+ */
+
+static void stli_onbeinit(stlibrd_t *brdp)
+{
+	unsigned long	memconf;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbeinit(brdp=%d)\n", (int) brdp);
+#endif
+
+	outb(0x1, (brdp->iobase + ONB_EIBRDENAB));
+	outb(ONB_EISTOP, (brdp->iobase + ONB_EICONFR));
+	udelay(10);
+	outb(ONB_EIDISABLE, (brdp->iobase + ONB_EICONFR));
+	mdelay(1000);
+
+	memconf = (brdp->memaddr & ONB_EIADDRMASKL) >> ONB_EIADDRSHFTL;
+	outb(memconf, (brdp->iobase + ONB_EIMEMARL));
+	memconf = (brdp->memaddr & ONB_EIADDRMASKH) >> ONB_EIADDRSHFTH;
+	outb(memconf, (brdp->iobase + ONB_EIMEMARH));
+	outb(0x1, brdp->iobase);
+	mdelay(1);
+}
+
+/*****************************************************************************/
+
+static void stli_onbeenable(stlibrd_t *brdp)
+{	
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbeenable(brdp=%x)\n", (int) brdp);
+#endif
+	outb(ONB_EIENABLE, (brdp->iobase + ONB_EICONFR));
+}
+
+/*****************************************************************************/
+
+static void stli_onbedisable(stlibrd_t *brdp)
+{	
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbedisable(brdp=%x)\n", (int) brdp);
+#endif
+	outb(ONB_EIDISABLE, (brdp->iobase + ONB_EICONFR));
+}
+
+/*****************************************************************************/
+
+static char *stli_onbegetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{	
+	void		*ptr;
+	unsigned char	val;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_onbegetmemptr(brdp=%x,offset=%x,line=%d)\n",
+		(int) brdp, (int) offset, line);
+#endif
+
+	if (offset > brdp->memsize) {
+		printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+				"range at line=%d(%d), brd=%d\n",
+			(int) offset, line, __LINE__, brdp->brdnr);
+		ptr = NULL;
+		val = 0;
+	} else {
+		ptr = brdp->membase + (offset % ONB_EIPAGESIZE);
+		if (offset < ONB_EIPAGESIZE)
+			val = ONB_EIENABLE;
+		else
+			val = ONB_EIENABLE | 0x40;
+	}
+	outb(val, (brdp->iobase + ONB_EICONFR));
+	return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_onbereset(stlibrd_t *brdp)
+{	
+
+#ifdef DEBUG
+	printk(KERN_ERR "stli_onbereset(brdp=%x)\n", (int) brdp);
+#endif
+
+	outb(ONB_EISTOP, (brdp->iobase + ONB_EICONFR));
+	udelay(10);
+	outb(ONB_EIDISABLE, (brdp->iobase + ONB_EICONFR));
+	mdelay(1000);
+}
+
+/*****************************************************************************/
+
+/*
+ *	The following routines act on Brumby boards.
+ */
+
+static void stli_bbyinit(stlibrd_t *brdp)
+{
+
+#ifdef DEBUG
+	printk(KERN_ERR "stli_bbyinit(brdp=%d)\n", (int) brdp);
+#endif
+
+	outb(BBY_ATSTOP, (brdp->iobase + BBY_ATCONFR));
+	udelay(10);
+	outb(0, (brdp->iobase + BBY_ATCONFR));
+	mdelay(1000);
+	outb(0x1, brdp->iobase);
+	mdelay(1);
+}
+
+/*****************************************************************************/
+
+static char *stli_bbygetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{	
+	void		*ptr;
+	unsigned char	val;
+
+#ifdef DEBUG
+	printk(KERN_ERR "stli_bbygetmemptr(brdp=%x,offset=%x)\n", (int) brdp,
+		(int) offset);
+#endif
+
+	if (offset > brdp->memsize) {
+		printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+				"range at line=%d(%d), brd=%d\n",
+				(int) offset, line, __LINE__, brdp->brdnr);
+		ptr = NULL;
+		val = 0;
+	} else {
+		ptr = brdp->membase + (offset % BBY_PAGESIZE);
+		val = (unsigned char) (offset / BBY_PAGESIZE);
+	}
+	outb(val, (brdp->iobase + BBY_ATCONFR));
+	return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_bbyreset(stlibrd_t *brdp)
+{	
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_bbyreset(brdp=%x)\n", (int) brdp);
+#endif
+
+	outb(BBY_ATSTOP, (brdp->iobase + BBY_ATCONFR));
+	udelay(10);
+	outb(0, (brdp->iobase + BBY_ATCONFR));
+	mdelay(1000);
+}
+
+/*****************************************************************************/
+
+/*
+ *	The following routines act on original old Stallion boards.
+ */
+
+static void stli_stalinit(stlibrd_t *brdp)
+{
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_stalinit(brdp=%d)\n", (int) brdp);
+#endif
+
+	outb(0x1, brdp->iobase);
+	mdelay(1000);
+}
+
+/*****************************************************************************/
+
+static char *stli_stalgetmemptr(stlibrd_t *brdp, unsigned long offset, int line)
+{	
+	void	*ptr;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_stalgetmemptr(brdp=%x,offset=%x)\n", (int) brdp,
+		(int) offset);
+#endif
+
+	if (offset > brdp->memsize) {
+		printk(KERN_ERR "STALLION: shared memory pointer=%x out of "
+				"range at line=%d(%d), brd=%d\n",
+				(int) offset, line, __LINE__, brdp->brdnr);
+		ptr = NULL;
+	} else {
+		ptr = brdp->membase + (offset % STAL_PAGESIZE);
+	}
+	return(ptr);
+}
+
+/*****************************************************************************/
+
+static void stli_stalreset(stlibrd_t *brdp)
+{	
+	volatile unsigned long	*vecp;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_stalreset(brdp=%x)\n", (int) brdp);
+#endif
+
+	vecp = (volatile unsigned long *) (brdp->membase + 0x30);
+	*vecp = 0xffff0000;
+	outb(0, brdp->iobase);
+	mdelay(1000);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Try to find an ECP board and initialize it. This handles only ECP
+ *	board types.
+ */
+
+static int stli_initecp(stlibrd_t *brdp)
+{
+	cdkecpsig_t	sig;
+	cdkecpsig_t	*sigsp;
+	unsigned int	status, nxtid;
+	char		*name;
+	int		panelnr, nrports;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_initecp(brdp=%x)\n", (int) brdp);
+#endif
+
+	if (!request_region(brdp->iobase, brdp->iosize, "istallion"))
+		return -EIO;
+	
+	if ((brdp->iobase == 0) || (brdp->memaddr == 0))
+	{
+		release_region(brdp->iobase, brdp->iosize);
+		return(-ENODEV);
+	}
+
+	brdp->iosize = ECP_IOSIZE;
+
+/*
+ *	Based on the specific board type setup the common vars to access
+ *	and enable shared memory. Set all board specific information now
+ *	as well.
+ */
+	switch (brdp->brdtype) {
+	case BRD_ECP:
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->memsize = ECP_MEMSIZE;
+		brdp->pagesize = ECP_ATPAGESIZE;
+		brdp->init = stli_ecpinit;
+		brdp->enable = stli_ecpenable;
+		brdp->reenable = stli_ecpenable;
+		brdp->disable = stli_ecpdisable;
+		brdp->getmemptr = stli_ecpgetmemptr;
+		brdp->intr = stli_ecpintr;
+		brdp->reset = stli_ecpreset;
+		name = "serial(EC8/64)";
+		break;
+
+	case BRD_ECPE:
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->memsize = ECP_MEMSIZE;
+		brdp->pagesize = ECP_EIPAGESIZE;
+		brdp->init = stli_ecpeiinit;
+		brdp->enable = stli_ecpeienable;
+		brdp->reenable = stli_ecpeienable;
+		brdp->disable = stli_ecpeidisable;
+		brdp->getmemptr = stli_ecpeigetmemptr;
+		brdp->intr = stli_ecpintr;
+		brdp->reset = stli_ecpeireset;
+		name = "serial(EC8/64-EI)";
+		break;
+
+	case BRD_ECPMC:
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->memsize = ECP_MEMSIZE;
+		brdp->pagesize = ECP_MCPAGESIZE;
+		brdp->init = NULL;
+		brdp->enable = stli_ecpmcenable;
+		brdp->reenable = stli_ecpmcenable;
+		brdp->disable = stli_ecpmcdisable;
+		brdp->getmemptr = stli_ecpmcgetmemptr;
+		brdp->intr = stli_ecpintr;
+		brdp->reset = stli_ecpmcreset;
+		name = "serial(EC8/64-MCA)";
+		break;
+
+	case BRD_ECPPCI:
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->memsize = ECP_PCIMEMSIZE;
+		brdp->pagesize = ECP_PCIPAGESIZE;
+		brdp->init = stli_ecppciinit;
+		brdp->enable = NULL;
+		brdp->reenable = NULL;
+		brdp->disable = NULL;
+		brdp->getmemptr = stli_ecppcigetmemptr;
+		brdp->intr = stli_ecpintr;
+		brdp->reset = stli_ecppcireset;
+		name = "serial(EC/RA-PCI)";
+		break;
+
+	default:
+		release_region(brdp->iobase, brdp->iosize);
+		return(-EINVAL);
+	}
+
+/*
+ *	The per-board operations structure is all set up, so now let's go
+ *	and get the board operational. Firstly initialize board configuration
+ *	registers. Set the memory mapping info so we can get at the boards
+ *	shared memory.
+ */
+	EBRDINIT(brdp);
+
+	brdp->membase = ioremap(brdp->memaddr, brdp->memsize);
+	if (brdp->membase == (void *) NULL)
+	{
+		release_region(brdp->iobase, brdp->iosize);
+		return(-ENOMEM);
+	}
+
+/*
+ *	Now that all specific code is set up, enable the shared memory and
+ *	look for the a signature area that will tell us exactly what board
+ *	this is, and what it is connected to it.
+ */
+	EBRDENABLE(brdp);
+	sigsp = (cdkecpsig_t *) EBRDGETMEMPTR(brdp, CDK_SIGADDR);
+	memcpy(&sig, sigsp, sizeof(cdkecpsig_t));
+	EBRDDISABLE(brdp);
+
+#if 0
+	printk("%s(%d): sig-> magic=%x rom=%x panel=%x,%x,%x,%x,%x,%x,%x,%x\n",
+		__FILE__, __LINE__, (int) sig.magic, sig.romver, sig.panelid[0],
+		(int) sig.panelid[1], (int) sig.panelid[2],
+		(int) sig.panelid[3], (int) sig.panelid[4],
+		(int) sig.panelid[5], (int) sig.panelid[6],
+		(int) sig.panelid[7]);
+#endif
+
+	if (sig.magic != ECP_MAGIC)
+	{
+		release_region(brdp->iobase, brdp->iosize);
+		return(-ENODEV);
+	}
+
+/*
+ *	Scan through the signature looking at the panels connected to the
+ *	board. Calculate the total number of ports as we go.
+ */
+	for (panelnr = 0, nxtid = 0; (panelnr < STL_MAXPANELS); panelnr++) {
+		status = sig.panelid[nxtid];
+		if ((status & ECH_PNLIDMASK) != nxtid)
+			break;
+
+		brdp->panelids[panelnr] = status;
+		nrports = (status & ECH_PNL16PORT) ? 16 : 8;
+		if ((nrports == 16) && ((status & ECH_PNLXPID) == 0))
+			nxtid++;
+		brdp->panels[panelnr] = nrports;
+		brdp->nrports += nrports;
+		nxtid++;
+		brdp->nrpanels++;
+	}
+
+
+	brdp->state |= BST_FOUND;
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Try to find an ONboard, Brumby or Stallion board and initialize it.
+ *	This handles only these board types.
+ */
+
+static int stli_initonb(stlibrd_t *brdp)
+{
+	cdkonbsig_t	sig;
+	cdkonbsig_t	*sigsp;
+	char		*name;
+	int		i;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_initonb(brdp=%x)\n", (int) brdp);
+#endif
+
+/*
+ *	Do a basic sanity check on the IO and memory addresses.
+ */
+	if ((brdp->iobase == 0) || (brdp->memaddr == 0))
+		return(-ENODEV);
+
+	brdp->iosize = ONB_IOSIZE;
+	
+	if (!request_region(brdp->iobase, brdp->iosize, "istallion"))
+		return -EIO;
+
+/*
+ *	Based on the specific board type setup the common vars to access
+ *	and enable shared memory. Set all board specific information now
+ *	as well.
+ */
+	switch (brdp->brdtype) {
+	case BRD_ONBOARD:
+	case BRD_ONBOARD32:
+	case BRD_ONBOARD2:
+	case BRD_ONBOARD2_32:
+	case BRD_ONBOARDRS:
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->memsize = ONB_MEMSIZE;
+		brdp->pagesize = ONB_ATPAGESIZE;
+		brdp->init = stli_onbinit;
+		brdp->enable = stli_onbenable;
+		brdp->reenable = stli_onbenable;
+		brdp->disable = stli_onbdisable;
+		brdp->getmemptr = stli_onbgetmemptr;
+		brdp->intr = stli_ecpintr;
+		brdp->reset = stli_onbreset;
+		if (brdp->memaddr > 0x100000)
+			brdp->enabval = ONB_MEMENABHI;
+		else
+			brdp->enabval = ONB_MEMENABLO;
+		name = "serial(ONBoard)";
+		break;
+
+	case BRD_ONBOARDE:
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->memsize = ONB_EIMEMSIZE;
+		brdp->pagesize = ONB_EIPAGESIZE;
+		brdp->init = stli_onbeinit;
+		brdp->enable = stli_onbeenable;
+		brdp->reenable = stli_onbeenable;
+		brdp->disable = stli_onbedisable;
+		brdp->getmemptr = stli_onbegetmemptr;
+		brdp->intr = stli_ecpintr;
+		brdp->reset = stli_onbereset;
+		name = "serial(ONBoard/E)";
+		break;
+
+	case BRD_BRUMBY4:
+	case BRD_BRUMBY8:
+	case BRD_BRUMBY16:
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->memsize = BBY_MEMSIZE;
+		brdp->pagesize = BBY_PAGESIZE;
+		brdp->init = stli_bbyinit;
+		brdp->enable = NULL;
+		brdp->reenable = NULL;
+		brdp->disable = NULL;
+		brdp->getmemptr = stli_bbygetmemptr;
+		brdp->intr = stli_ecpintr;
+		brdp->reset = stli_bbyreset;
+		name = "serial(Brumby)";
+		break;
+
+	case BRD_STALLION:
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->memsize = STAL_MEMSIZE;
+		brdp->pagesize = STAL_PAGESIZE;
+		brdp->init = stli_stalinit;
+		brdp->enable = NULL;
+		brdp->reenable = NULL;
+		brdp->disable = NULL;
+		brdp->getmemptr = stli_stalgetmemptr;
+		brdp->intr = stli_ecpintr;
+		brdp->reset = stli_stalreset;
+		name = "serial(Stallion)";
+		break;
+
+	default:
+		release_region(brdp->iobase, brdp->iosize);
+		return(-EINVAL);
+	}
+
+/*
+ *	The per-board operations structure is all set up, so now let's go
+ *	and get the board operational. Firstly initialize board configuration
+ *	registers. Set the memory mapping info so we can get at the boards
+ *	shared memory.
+ */
+	EBRDINIT(brdp);
+
+	brdp->membase = ioremap(brdp->memaddr, brdp->memsize);
+	if (brdp->membase == (void *) NULL)
+	{
+		release_region(brdp->iobase, brdp->iosize);
+		return(-ENOMEM);
+	}
+
+/*
+ *	Now that all specific code is set up, enable the shared memory and
+ *	look for the a signature area that will tell us exactly what board
+ *	this is, and how many ports.
+ */
+	EBRDENABLE(brdp);
+	sigsp = (cdkonbsig_t *) EBRDGETMEMPTR(brdp, CDK_SIGADDR);
+	memcpy(&sig, sigsp, sizeof(cdkonbsig_t));
+	EBRDDISABLE(brdp);
+
+#if 0
+	printk("%s(%d): sig-> magic=%x:%x:%x:%x romver=%x amask=%x:%x:%x\n",
+		__FILE__, __LINE__, sig.magic0, sig.magic1, sig.magic2,
+		sig.magic3, sig.romver, sig.amask0, sig.amask1, sig.amask2);
+#endif
+
+	if ((sig.magic0 != ONB_MAGIC0) || (sig.magic1 != ONB_MAGIC1) ||
+	    (sig.magic2 != ONB_MAGIC2) || (sig.magic3 != ONB_MAGIC3))
+	{
+		release_region(brdp->iobase, brdp->iosize);
+		return(-ENODEV);
+	}
+
+/*
+ *	Scan through the signature alive mask and calculate how many ports
+ *	there are on this board.
+ */
+	brdp->nrpanels = 1;
+	if (sig.amask1) {
+		brdp->nrports = 32;
+	} else {
+		for (i = 0; (i < 16); i++) {
+			if (((sig.amask0 << i) & 0x8000) == 0)
+				break;
+		}
+		brdp->nrports = i;
+	}
+	brdp->panels[0] = brdp->nrports;
+
+
+	brdp->state |= BST_FOUND;
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Start up a running board. This routine is only called after the
+ *	code has been down loaded to the board and is operational. It will
+ *	read in the memory map, and get the show on the road...
+ */
+
+static int stli_startbrd(stlibrd_t *brdp)
+{
+	volatile cdkhdr_t	*hdrp;
+	volatile cdkmem_t	*memp;
+	volatile cdkasy_t	*ap;
+	unsigned long		flags;
+	stliport_t		*portp;
+	int			portnr, nrdevs, i, rc;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_startbrd(brdp=%x)\n", (int) brdp);
+#endif
+
+	rc = 0;
+
+	save_flags(flags);
+	cli();
+	EBRDENABLE(brdp);
+	hdrp = (volatile cdkhdr_t *) EBRDGETMEMPTR(brdp, CDK_CDKADDR);
+	nrdevs = hdrp->nrdevs;
+
+#if 0
+	printk("%s(%d): CDK version %d.%d.%d --> "
+		"nrdevs=%d memp=%x hostp=%x slavep=%x\n",
+		 __FILE__, __LINE__, hdrp->ver_release, hdrp->ver_modification,
+		 hdrp->ver_fix, nrdevs, (int) hdrp->memp, (int) hdrp->hostp,
+		 (int) hdrp->slavep);
+#endif
+
+	if (nrdevs < (brdp->nrports + 1)) {
+		printk(KERN_ERR "STALLION: slave failed to allocate memory for "
+				"all devices, devices=%d\n", nrdevs);
+		brdp->nrports = nrdevs - 1;
+	}
+	brdp->nrdevs = nrdevs;
+	brdp->hostoffset = hdrp->hostp - CDK_CDKADDR;
+	brdp->slaveoffset = hdrp->slavep - CDK_CDKADDR;
+	brdp->bitsize = (nrdevs + 7) / 8;
+	memp = (volatile cdkmem_t *) hdrp->memp;
+	if (((unsigned long) memp) > brdp->memsize) {
+		printk(KERN_ERR "STALLION: corrupted shared memory region?\n");
+		rc = -EIO;
+		goto stli_donestartup;
+	}
+	memp = (volatile cdkmem_t *) EBRDGETMEMPTR(brdp, (unsigned long) memp);
+	if (memp->dtype != TYP_ASYNCTRL) {
+		printk(KERN_ERR "STALLION: no slave control device found\n");
+		goto stli_donestartup;
+	}
+	memp++;
+
+/*
+ *	Cycle through memory allocation of each port. We are guaranteed to
+ *	have all ports inside the first page of slave window, so no need to
+ *	change pages while reading memory map.
+ */
+	for (i = 1, portnr = 0; (i < nrdevs); i++, portnr++, memp++) {
+		if (memp->dtype != TYP_ASYNC)
+			break;
+		portp = brdp->ports[portnr];
+		if (portp == (stliport_t *) NULL)
+			break;
+		portp->devnr = i;
+		portp->addr = memp->offset;
+		portp->reqbit = (unsigned char) (0x1 << (i * 8 / nrdevs));
+		portp->portidx = (unsigned char) (i / 8);
+		portp->portbit = (unsigned char) (0x1 << (i % 8));
+	}
+
+	hdrp->slavereq = 0xff;
+
+/*
+ *	For each port setup a local copy of the RX and TX buffer offsets
+ *	and sizes. We do this separate from the above, because we need to
+ *	move the shared memory page...
+ */
+	for (i = 1, portnr = 0; (i < nrdevs); i++, portnr++) {
+		portp = brdp->ports[portnr];
+		if (portp == (stliport_t *) NULL)
+			break;
+		if (portp->addr == 0)
+			break;
+		ap = (volatile cdkasy_t *) EBRDGETMEMPTR(brdp, portp->addr);
+		if (ap != (volatile cdkasy_t *) NULL) {
+			portp->rxsize = ap->rxq.size;
+			portp->txsize = ap->txq.size;
+			portp->rxoffset = ap->rxq.offset;
+			portp->txoffset = ap->txq.offset;
+		}
+	}
+
+stli_donestartup:
+	EBRDDISABLE(brdp);
+	restore_flags(flags);
+
+	if (rc == 0)
+		brdp->state |= BST_STARTED;
+
+	if (! stli_timeron) {
+		stli_timeron++;
+		stli_timerlist.expires = STLI_TIMEOUT;
+		add_timer(&stli_timerlist);
+	}
+
+	return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Probe and initialize the specified board.
+ */
+
+static int __init stli_brdinit(stlibrd_t *brdp)
+{
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_brdinit(brdp=%x)\n", (int) brdp);
+#endif
+
+	stli_brds[brdp->brdnr] = brdp;
+
+	switch (brdp->brdtype) {
+	case BRD_ECP:
+	case BRD_ECPE:
+	case BRD_ECPMC:
+	case BRD_ECPPCI:
+		stli_initecp(brdp);
+		break;
+	case BRD_ONBOARD:
+	case BRD_ONBOARDE:
+	case BRD_ONBOARD2:
+	case BRD_ONBOARD32:
+	case BRD_ONBOARD2_32:
+	case BRD_ONBOARDRS:
+	case BRD_BRUMBY4:
+	case BRD_BRUMBY8:
+	case BRD_BRUMBY16:
+	case BRD_STALLION:
+		stli_initonb(brdp);
+		break;
+	case BRD_EASYIO:
+	case BRD_ECH:
+	case BRD_ECHMC:
+	case BRD_ECHPCI:
+		printk(KERN_ERR "STALLION: %s board type not supported in "
+				"this driver\n", stli_brdnames[brdp->brdtype]);
+		return(ENODEV);
+	default:
+		printk(KERN_ERR "STALLION: board=%d is unknown board "
+				"type=%d\n", brdp->brdnr, brdp->brdtype);
+		return(ENODEV);
+	}
+
+	if ((brdp->state & BST_FOUND) == 0) {
+		printk(KERN_ERR "STALLION: %s board not found, board=%d "
+				"io=%x mem=%x\n",
+			stli_brdnames[brdp->brdtype], brdp->brdnr,
+			brdp->iobase, (int) brdp->memaddr);
+		return(ENODEV);
+	}
+
+	stli_initports(brdp);
+	printk(KERN_INFO "STALLION: %s found, board=%d io=%x mem=%x "
+		"nrpanels=%d nrports=%d\n", stli_brdnames[brdp->brdtype],
+		brdp->brdnr, brdp->iobase, (int) brdp->memaddr,
+		brdp->nrpanels, brdp->nrports);
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Probe around trying to find where the EISA boards shared memory
+ *	might be. This is a bit if hack, but it is the best we can do.
+ */
+
+static int stli_eisamemprobe(stlibrd_t *brdp)
+{
+	cdkecpsig_t	ecpsig, *ecpsigp;
+	cdkonbsig_t	onbsig, *onbsigp;
+	int		i, foundit;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_eisamemprobe(brdp=%x)\n", (int) brdp);
+#endif
+
+/*
+ *	First up we reset the board, to get it into a known state. There
+ *	is only 2 board types here we need to worry about. Don;t use the
+ *	standard board init routine here, it programs up the shared
+ *	memory address, and we don't know it yet...
+ */
+	if (brdp->brdtype == BRD_ECPE) {
+		outb(0x1, (brdp->iobase + ECP_EIBRDENAB));
+		outb(ECP_EISTOP, (brdp->iobase + ECP_EICONFR));
+		udelay(10);
+		outb(ECP_EIDISABLE, (brdp->iobase + ECP_EICONFR));
+		udelay(500);
+		stli_ecpeienable(brdp);
+	} else if (brdp->brdtype == BRD_ONBOARDE) {
+		outb(0x1, (brdp->iobase + ONB_EIBRDENAB));
+		outb(ONB_EISTOP, (brdp->iobase + ONB_EICONFR));
+		udelay(10);
+		outb(ONB_EIDISABLE, (brdp->iobase + ONB_EICONFR));
+		mdelay(100);
+		outb(0x1, brdp->iobase);
+		mdelay(1);
+		stli_onbeenable(brdp);
+	} else {
+		return(-ENODEV);
+	}
+
+	foundit = 0;
+	brdp->memsize = ECP_MEMSIZE;
+
+/*
+ *	Board shared memory is enabled, so now we have a poke around and
+ *	see if we can find it.
+ */
+	for (i = 0; (i < stli_eisamempsize); i++) {
+		brdp->memaddr = stli_eisamemprobeaddrs[i];
+		brdp->membase = (void *) brdp->memaddr;
+		brdp->membase = ioremap(brdp->memaddr, brdp->memsize);
+		if (brdp->membase == (void *) NULL)
+			continue;
+
+		if (brdp->brdtype == BRD_ECPE) {
+			ecpsigp = (cdkecpsig_t *) stli_ecpeigetmemptr(brdp,
+				CDK_SIGADDR, __LINE__);
+			memcpy(&ecpsig, ecpsigp, sizeof(cdkecpsig_t));
+			if (ecpsig.magic == ECP_MAGIC)
+				foundit = 1;
+		} else {
+			onbsigp = (cdkonbsig_t *) stli_onbegetmemptr(brdp,
+				CDK_SIGADDR, __LINE__);
+			memcpy(&onbsig, onbsigp, sizeof(cdkonbsig_t));
+			if ((onbsig.magic0 == ONB_MAGIC0) &&
+			    (onbsig.magic1 == ONB_MAGIC1) &&
+			    (onbsig.magic2 == ONB_MAGIC2) &&
+			    (onbsig.magic3 == ONB_MAGIC3))
+				foundit = 1;
+		}
+
+		iounmap(brdp->membase);
+		if (foundit)
+			break;
+	}
+
+/*
+ *	Regardless of whether we found the shared memory or not we must
+ *	disable the region. After that return success or failure.
+ */
+	if (brdp->brdtype == BRD_ECPE)
+		stli_ecpeidisable(brdp);
+	else
+		stli_onbedisable(brdp);
+
+	if (! foundit) {
+		brdp->memaddr = 0;
+		brdp->membase = NULL;
+		printk(KERN_ERR "STALLION: failed to probe shared memory "
+				"region for %s in EISA slot=%d\n",
+			stli_brdnames[brdp->brdtype], (brdp->iobase >> 12));
+		return(-ENODEV);
+	}
+	return(0);
+}
+
+static int stli_getbrdnr(void)
+{
+	int i;
+
+	for (i = 0; i < STL_MAXBRDS; i++) {
+		if (!stli_brds[i]) {
+			if (i >= stli_nrbrds)
+				stli_nrbrds = i + 1;
+			return i;
+		}
+	}
+	return -1;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Probe around and try to find any EISA boards in system. The biggest
+ *	problem here is finding out what memory address is associated with
+ *	an EISA board after it is found. The registers of the ECPE and
+ *	ONboardE are not readable - so we can't read them from there. We
+ *	don't have access to the EISA CMOS (or EISA BIOS) so we don't
+ *	actually have any way to find out the real value. The best we can
+ *	do is go probing around in the usual places hoping we can find it.
+ */
+
+static int stli_findeisabrds(void)
+{
+	stlibrd_t	*brdp;
+	unsigned int	iobase, eid;
+	int		i;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_findeisabrds()\n");
+#endif
+
+/*
+ *	Firstly check if this is an EISA system. Do this by probing for
+ *	the system board EISA ID. If this is not an EISA system then
+ *	don't bother going any further!
+ */
+	outb(0xff, 0xc80);
+	if (inb(0xc80) == 0xff)
+		return(0);
+
+/*
+ *	Looks like an EISA system, so go searching for EISA boards.
+ */
+	for (iobase = 0x1000; (iobase <= 0xc000); iobase += 0x1000) {
+		outb(0xff, (iobase + 0xc80));
+		eid = inb(iobase + 0xc80);
+		eid |= inb(iobase + 0xc81) << 8;
+		if (eid != STL_EISAID)
+			continue;
+
+/*
+ *		We have found a board. Need to check if this board was
+ *		statically configured already (just in case!).
+ */
+		for (i = 0; (i < STL_MAXBRDS); i++) {
+			brdp = stli_brds[i];
+			if (brdp == (stlibrd_t *) NULL)
+				continue;
+			if (brdp->iobase == iobase)
+				break;
+		}
+		if (i < STL_MAXBRDS)
+			continue;
+
+/*
+ *		We have found a Stallion board and it is not configured already.
+ *		Allocate a board structure and initialize it.
+ */
+		if ((brdp = stli_allocbrd()) == (stlibrd_t *) NULL)
+			return(-ENOMEM);
+		if ((brdp->brdnr = stli_getbrdnr()) < 0)
+			return(-ENOMEM);
+		eid = inb(iobase + 0xc82);
+		if (eid == ECP_EISAID)
+			brdp->brdtype = BRD_ECPE;
+		else if (eid == ONB_EISAID)
+			brdp->brdtype = BRD_ONBOARDE;
+		else
+			brdp->brdtype = BRD_UNKNOWN;
+		brdp->iobase = iobase;
+		outb(0x1, (iobase + 0xc84));
+		if (stli_eisamemprobe(brdp))
+			outb(0, (iobase + 0xc84));
+		stli_brdinit(brdp);
+	}
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Find the next available board number that is free.
+ */
+
+/*****************************************************************************/
+
+#ifdef	CONFIG_PCI
+
+/*
+ *	We have a Stallion board. Allocate a board structure and
+ *	initialize it. Read its IO and MEMORY resources from PCI
+ *	configuration space.
+ */
+
+static int stli_initpcibrd(int brdtype, struct pci_dev *devp)
+{
+	stlibrd_t	*brdp;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_initpcibrd(brdtype=%d,busnr=%x,devnr=%x)\n",
+		brdtype, dev->bus->number, dev->devfn);
+#endif
+
+	if (pci_enable_device(devp))
+		return(-EIO);
+	if ((brdp = stli_allocbrd()) == (stlibrd_t *) NULL)
+		return(-ENOMEM);
+	if ((brdp->brdnr = stli_getbrdnr()) < 0) {
+		printk(KERN_INFO "STALLION: too many boards found, "
+			"maximum supported %d\n", STL_MAXBRDS);
+		return(0);
+	}
+	brdp->brdtype = brdtype;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "%s(%d): BAR[]=%lx,%lx,%lx,%lx\n", __FILE__, __LINE__,
+		pci_resource_start(devp, 0),
+		pci_resource_start(devp, 1),
+		pci_resource_start(devp, 2),
+		pci_resource_start(devp, 3));
+#endif
+
+/*
+ *	We have all resources from the board, so lets setup the actual
+ *	board structure now.
+ */
+	brdp->iobase = pci_resource_start(devp, 3);
+	brdp->memaddr = pci_resource_start(devp, 2);
+	stli_brdinit(brdp);
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Find all Stallion PCI boards that might be installed. Initialize each
+ *	one as it is found.
+ */
+
+static int stli_findpcibrds(void)
+{
+	struct pci_dev	*dev = NULL;
+	int		rc;
+
+#ifdef DEBUG
+	printk("stli_findpcibrds()\n");
+#endif
+
+	while ((dev = pci_find_device(PCI_VENDOR_ID_STALLION,
+	    PCI_DEVICE_ID_ECRA, dev))) {
+		if ((rc = stli_initpcibrd(BRD_ECPPCI, dev)))
+			return(rc);
+	}
+
+	return(0);
+}
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Allocate a new board structure. Fill out the basic info in it.
+ */
+
+static stlibrd_t *stli_allocbrd(void)
+{
+	stlibrd_t	*brdp;
+
+	brdp = (stlibrd_t *) stli_memalloc(sizeof(stlibrd_t));
+	if (brdp == (stlibrd_t *) NULL) {
+		printk(KERN_ERR "STALLION: failed to allocate memory "
+				"(size=%d)\n", sizeof(stlibrd_t));
+		return((stlibrd_t *) NULL);
+	}
+
+	memset(brdp, 0, sizeof(stlibrd_t));
+	brdp->magic = STLI_BOARDMAGIC;
+	return(brdp);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Scan through all the boards in the configuration and see what we
+ *	can find.
+ */
+
+static int stli_initbrds(void)
+{
+	stlibrd_t	*brdp, *nxtbrdp;
+	stlconf_t	*confp;
+	int		i, j;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_initbrds()\n");
+#endif
+
+	if (stli_nrbrds > STL_MAXBRDS) {
+		printk(KERN_INFO "STALLION: too many boards in configuration "
+			"table, truncating to %d\n", STL_MAXBRDS);
+		stli_nrbrds = STL_MAXBRDS;
+	}
+
+/*
+ *	Firstly scan the list of static boards configured. Allocate
+ *	resources and initialize the boards as found. If this is a
+ *	module then let the module args override static configuration.
+ */
+	for (i = 0; (i < stli_nrbrds); i++) {
+		confp = &stli_brdconf[i];
+#ifdef MODULE
+		stli_parsebrd(confp, stli_brdsp[i]);
+#endif
+		if ((brdp = stli_allocbrd()) == (stlibrd_t *) NULL)
+			return(-ENOMEM);
+		brdp->brdnr = i;
+		brdp->brdtype = confp->brdtype;
+		brdp->iobase = confp->ioaddr1;
+		brdp->memaddr = confp->memaddr;
+		stli_brdinit(brdp);
+	}
+
+/*
+ *	Static configuration table done, so now use dynamic methods to
+ *	see if any more boards should be configured.
+ */
+#ifdef MODULE
+	stli_argbrds();
+#endif
+	if (stli_eisaprobe)
+		stli_findeisabrds();
+#ifdef CONFIG_PCI
+	stli_findpcibrds();
+#endif
+
+/*
+ *	All found boards are initialized. Now for a little optimization, if
+ *	no boards are sharing the "shared memory" regions then we can just
+ *	leave them all enabled. This is in fact the usual case.
+ */
+	stli_shared = 0;
+	if (stli_nrbrds > 1) {
+		for (i = 0; (i < stli_nrbrds); i++) {
+			brdp = stli_brds[i];
+			if (brdp == (stlibrd_t *) NULL)
+				continue;
+			for (j = i + 1; (j < stli_nrbrds); j++) {
+				nxtbrdp = stli_brds[j];
+				if (nxtbrdp == (stlibrd_t *) NULL)
+					continue;
+				if ((brdp->membase >= nxtbrdp->membase) &&
+				    (brdp->membase <= (nxtbrdp->membase +
+				    nxtbrdp->memsize - 1))) {
+					stli_shared++;
+					break;
+				}
+			}
+		}
+	}
+
+	if (stli_shared == 0) {
+		for (i = 0; (i < stli_nrbrds); i++) {
+			brdp = stli_brds[i];
+			if (brdp == (stlibrd_t *) NULL)
+				continue;
+			if (brdp->state & BST_FOUND) {
+				EBRDENABLE(brdp);
+				brdp->enable = NULL;
+				brdp->disable = NULL;
+			}
+		}
+	}
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Code to handle an "staliomem" read operation. This device is the 
+ *	contents of the board shared memory. It is used for down loading
+ *	the slave image (and debugging :-)
+ */
+
+static ssize_t stli_memread(struct file *fp, char __user *buf, size_t count, loff_t *offp)
+{
+	unsigned long	flags;
+	void		*memptr;
+	stlibrd_t	*brdp;
+	int		brdnr, size, n;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_memread(fp=%x,buf=%x,count=%x,offp=%x)\n",
+			(int) fp, (int) buf, count, (int) offp);
+#endif
+
+	brdnr = iminor(fp->f_dentry->d_inode);
+	if (brdnr >= stli_nrbrds)
+		return(-ENODEV);
+	brdp = stli_brds[brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(-ENODEV);
+	if (brdp->state == 0)
+		return(-ENODEV);
+	if (fp->f_pos >= brdp->memsize)
+		return(0);
+
+	size = MIN(count, (brdp->memsize - fp->f_pos));
+
+	save_flags(flags);
+	cli();
+	EBRDENABLE(brdp);
+	while (size > 0) {
+		memptr = (void *) EBRDGETMEMPTR(brdp, fp->f_pos);
+		n = MIN(size, (brdp->pagesize - (((unsigned long) fp->f_pos) % brdp->pagesize)));
+		if (copy_to_user(buf, memptr, n)) {
+			count = -EFAULT;
+			goto out;
+		}
+		fp->f_pos += n;
+		buf += n;
+		size -= n;
+	}
+out:
+	EBRDDISABLE(brdp);
+	restore_flags(flags);
+
+	return(count);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Code to handle an "staliomem" write operation. This device is the 
+ *	contents of the board shared memory. It is used for down loading
+ *	the slave image (and debugging :-)
+ */
+
+static ssize_t stli_memwrite(struct file *fp, const char __user *buf, size_t count, loff_t *offp)
+{
+	unsigned long	flags;
+	void		*memptr;
+	stlibrd_t	*brdp;
+	char		__user *chbuf;
+	int		brdnr, size, n;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_memwrite(fp=%x,buf=%x,count=%x,offp=%x)\n",
+			(int) fp, (int) buf, count, (int) offp);
+#endif
+
+	brdnr = iminor(fp->f_dentry->d_inode);
+	if (brdnr >= stli_nrbrds)
+		return(-ENODEV);
+	brdp = stli_brds[brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(-ENODEV);
+	if (brdp->state == 0)
+		return(-ENODEV);
+	if (fp->f_pos >= brdp->memsize)
+		return(0);
+
+	chbuf = (char __user *) buf;
+	size = MIN(count, (brdp->memsize - fp->f_pos));
+
+	save_flags(flags);
+	cli();
+	EBRDENABLE(brdp);
+	while (size > 0) {
+		memptr = (void *) EBRDGETMEMPTR(brdp, fp->f_pos);
+		n = MIN(size, (brdp->pagesize - (((unsigned long) fp->f_pos) % brdp->pagesize)));
+		if (copy_from_user(memptr, chbuf, n)) {
+			count = -EFAULT;
+			goto out;
+		}
+		fp->f_pos += n;
+		chbuf += n;
+		size -= n;
+	}
+out:
+	EBRDDISABLE(brdp);
+	restore_flags(flags);
+
+	return(count);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the board stats structure to user app.
+ */
+
+static int stli_getbrdstats(combrd_t __user *bp)
+{
+	stlibrd_t	*brdp;
+	int		i;
+
+	if (copy_from_user(&stli_brdstats, bp, sizeof(combrd_t)))
+		return -EFAULT;
+	if (stli_brdstats.brd >= STL_MAXBRDS)
+		return(-ENODEV);
+	brdp = stli_brds[stli_brdstats.brd];
+	if (brdp == (stlibrd_t *) NULL)
+		return(-ENODEV);
+
+	memset(&stli_brdstats, 0, sizeof(combrd_t));
+	stli_brdstats.brd = brdp->brdnr;
+	stli_brdstats.type = brdp->brdtype;
+	stli_brdstats.hwid = 0;
+	stli_brdstats.state = brdp->state;
+	stli_brdstats.ioaddr = brdp->iobase;
+	stli_brdstats.memaddr = brdp->memaddr;
+	stli_brdstats.nrpanels = brdp->nrpanels;
+	stli_brdstats.nrports = brdp->nrports;
+	for (i = 0; (i < brdp->nrpanels); i++) {
+		stli_brdstats.panels[i].panel = i;
+		stli_brdstats.panels[i].hwid = brdp->panelids[i];
+		stli_brdstats.panels[i].nrports = brdp->panels[i];
+	}
+
+	if (copy_to_user(bp, &stli_brdstats, sizeof(combrd_t)))
+		return -EFAULT;
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Resolve the referenced port number into a port struct pointer.
+ */
+
+static stliport_t *stli_getport(int brdnr, int panelnr, int portnr)
+{
+	stlibrd_t	*brdp;
+	int		i;
+
+	if ((brdnr < 0) || (brdnr >= STL_MAXBRDS))
+		return((stliport_t *) NULL);
+	brdp = stli_brds[brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return((stliport_t *) NULL);
+	for (i = 0; (i < panelnr); i++)
+		portnr += brdp->panels[i];
+	if ((portnr < 0) || (portnr >= brdp->nrports))
+		return((stliport_t *) NULL);
+	return(brdp->ports[portnr]);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the port stats structure to user app. A NULL port struct
+ *	pointer passed in means that we need to find out from the app
+ *	what port to get stats for (used through board control device).
+ */
+
+static int stli_portcmdstats(stliport_t *portp)
+{
+	unsigned long	flags;
+	stlibrd_t	*brdp;
+	int		rc;
+
+	memset(&stli_comstats, 0, sizeof(comstats_t));
+
+	if (portp == (stliport_t *) NULL)
+		return(-ENODEV);
+	brdp = stli_brds[portp->brdnr];
+	if (brdp == (stlibrd_t *) NULL)
+		return(-ENODEV);
+
+	if (brdp->state & BST_STARTED) {
+		if ((rc = stli_cmdwait(brdp, portp, A_GETSTATS,
+		    &stli_cdkstats, sizeof(asystats_t), 1)) < 0)
+			return(rc);
+	} else {
+		memset(&stli_cdkstats, 0, sizeof(asystats_t));
+	}
+
+	stli_comstats.brd = portp->brdnr;
+	stli_comstats.panel = portp->panelnr;
+	stli_comstats.port = portp->portnr;
+	stli_comstats.state = portp->state;
+	stli_comstats.flags = portp->flags;
+
+	save_flags(flags);
+	cli();
+	if (portp->tty != (struct tty_struct *) NULL) {
+		if (portp->tty->driver_data == portp) {
+			stli_comstats.ttystate = portp->tty->flags;
+			stli_comstats.rxbuffered = portp->tty->flip.count;
+			if (portp->tty->termios != (struct termios *) NULL) {
+				stli_comstats.cflags = portp->tty->termios->c_cflag;
+				stli_comstats.iflags = portp->tty->termios->c_iflag;
+				stli_comstats.oflags = portp->tty->termios->c_oflag;
+				stli_comstats.lflags = portp->tty->termios->c_lflag;
+			}
+		}
+	}
+	restore_flags(flags);
+
+	stli_comstats.txtotal = stli_cdkstats.txchars;
+	stli_comstats.rxtotal = stli_cdkstats.rxchars + stli_cdkstats.ringover;
+	stli_comstats.txbuffered = stli_cdkstats.txringq;
+	stli_comstats.rxbuffered += stli_cdkstats.rxringq;
+	stli_comstats.rxoverrun = stli_cdkstats.overruns;
+	stli_comstats.rxparity = stli_cdkstats.parity;
+	stli_comstats.rxframing = stli_cdkstats.framing;
+	stli_comstats.rxlost = stli_cdkstats.ringover;
+	stli_comstats.rxbreaks = stli_cdkstats.rxbreaks;
+	stli_comstats.txbreaks = stli_cdkstats.txbreaks;
+	stli_comstats.txxon = stli_cdkstats.txstart;
+	stli_comstats.txxoff = stli_cdkstats.txstop;
+	stli_comstats.rxxon = stli_cdkstats.rxstart;
+	stli_comstats.rxxoff = stli_cdkstats.rxstop;
+	stli_comstats.rxrtsoff = stli_cdkstats.rtscnt / 2;
+	stli_comstats.rxrtson = stli_cdkstats.rtscnt - stli_comstats.rxrtsoff;
+	stli_comstats.modem = stli_cdkstats.dcdcnt;
+	stli_comstats.hwid = stli_cdkstats.hwid;
+	stli_comstats.signals = stli_mktiocm(stli_cdkstats.signals);
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the port stats structure to user app. A NULL port struct
+ *	pointer passed in means that we need to find out from the app
+ *	what port to get stats for (used through board control device).
+ */
+
+static int stli_getportstats(stliport_t *portp, comstats_t __user *cp)
+{
+	stlibrd_t	*brdp;
+	int		rc;
+
+	if (!portp) {
+		if (copy_from_user(&stli_comstats, cp, sizeof(comstats_t)))
+			return -EFAULT;
+		portp = stli_getport(stli_comstats.brd, stli_comstats.panel,
+			stli_comstats.port);
+		if (!portp)
+			return -ENODEV;
+	}
+
+	brdp = stli_brds[portp->brdnr];
+	if (!brdp)
+		return -ENODEV;
+
+	if ((rc = stli_portcmdstats(portp)) < 0)
+		return rc;
+
+	return copy_to_user(cp, &stli_comstats, sizeof(comstats_t)) ?
+			-EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Clear the port stats structure. We also return it zeroed out...
+ */
+
+static int stli_clrportstats(stliport_t *portp, comstats_t __user *cp)
+{
+	stlibrd_t	*brdp;
+	int		rc;
+
+	if (!portp) {
+		if (copy_from_user(&stli_comstats, cp, sizeof(comstats_t)))
+			return -EFAULT;
+		portp = stli_getport(stli_comstats.brd, stli_comstats.panel,
+			stli_comstats.port);
+		if (!portp)
+			return -ENODEV;
+	}
+
+	brdp = stli_brds[portp->brdnr];
+	if (!brdp)
+		return -ENODEV;
+
+	if (brdp->state & BST_STARTED) {
+		if ((rc = stli_cmdwait(brdp, portp, A_CLEARSTATS, NULL, 0, 0)) < 0)
+			return rc;
+	}
+
+	memset(&stli_comstats, 0, sizeof(comstats_t));
+	stli_comstats.brd = portp->brdnr;
+	stli_comstats.panel = portp->panelnr;
+	stli_comstats.port = portp->portnr;
+
+	if (copy_to_user(cp, &stli_comstats, sizeof(comstats_t)))
+		return -EFAULT;
+	return 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the entire driver ports structure to a user app.
+ */
+
+static int stli_getportstruct(stliport_t __user *arg)
+{
+	stliport_t	*portp;
+
+	if (copy_from_user(&stli_dummyport, arg, sizeof(stliport_t)))
+		return -EFAULT;
+	portp = stli_getport(stli_dummyport.brdnr, stli_dummyport.panelnr,
+		 stli_dummyport.portnr);
+	if (!portp)
+		return -ENODEV;
+	if (copy_to_user(arg, portp, sizeof(stliport_t)))
+		return -EFAULT;
+	return 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the entire driver board structure to a user app.
+ */
+
+static int stli_getbrdstruct(stlibrd_t __user *arg)
+{
+	stlibrd_t	*brdp;
+
+	if (copy_from_user(&stli_dummybrd, arg, sizeof(stlibrd_t)))
+		return -EFAULT;
+	if ((stli_dummybrd.brdnr < 0) || (stli_dummybrd.brdnr >= STL_MAXBRDS))
+		return -ENODEV;
+	brdp = stli_brds[stli_dummybrd.brdnr];
+	if (!brdp)
+		return -ENODEV;
+	if (copy_to_user(arg, brdp, sizeof(stlibrd_t)))
+		return -EFAULT;
+	return 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	The "staliomem" device is also required to do some special operations on
+ *	the board. We need to be able to send an interrupt to the board,
+ *	reset it, and start/stop it.
+ */
+
+static int stli_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	stlibrd_t	*brdp;
+	int		brdnr, rc, done;
+	void __user *argp = (void __user *)arg;
+
+#ifdef DEBUG
+	printk(KERN_DEBUG "stli_memioctl(ip=%x,fp=%x,cmd=%x,arg=%x)\n",
+			(int) ip, (int) fp, cmd, (int) arg);
+#endif
+
+/*
+ *	First up handle the board independent ioctls.
+ */
+	done = 0;
+	rc = 0;
+
+	switch (cmd) {
+	case COM_GETPORTSTATS:
+		rc = stli_getportstats(NULL, argp);
+		done++;
+		break;
+	case COM_CLRPORTSTATS:
+		rc = stli_clrportstats(NULL, argp);
+		done++;
+		break;
+	case COM_GETBRDSTATS:
+		rc = stli_getbrdstats(argp);
+		done++;
+		break;
+	case COM_READPORT:
+		rc = stli_getportstruct(argp);
+		done++;
+		break;
+	case COM_READBOARD:
+		rc = stli_getbrdstruct(argp);
+		done++;
+		break;
+	}
+
+	if (done)
+		return(rc);
+
+/*
+ *	Now handle the board specific ioctls. These all depend on the
+ *	minor number of the device they were called from.
+ */
+	brdnr = iminor(ip);
+	if (brdnr >= STL_MAXBRDS)
+		return(-ENODEV);
+	brdp = stli_brds[brdnr];
+	if (!brdp)
+		return(-ENODEV);
+	if (brdp->state == 0)
+		return(-ENODEV);
+
+	switch (cmd) {
+	case STL_BINTR:
+		EBRDINTR(brdp);
+		break;
+	case STL_BSTART:
+		rc = stli_startbrd(brdp);
+		break;
+	case STL_BSTOP:
+		brdp->state &= ~BST_STARTED;
+		break;
+	case STL_BRESET:
+		brdp->state &= ~BST_STARTED;
+		EBRDRESET(brdp);
+		if (stli_shared == 0) {
+			if (brdp->reenable != NULL)
+				(* brdp->reenable)(brdp);
+		}
+		break;
+	default:
+		rc = -ENOIOCTLCMD;
+		break;
+	}
+
+	return(rc);
+}
+
+static struct tty_operations stli_ops = {
+	.open = stli_open,
+	.close = stli_close,
+	.write = stli_write,
+	.put_char = stli_putchar,
+	.flush_chars = stli_flushchars,
+	.write_room = stli_writeroom,
+	.chars_in_buffer = stli_charsinbuffer,
+	.ioctl = stli_ioctl,
+	.set_termios = stli_settermios,
+	.throttle = stli_throttle,
+	.unthrottle = stli_unthrottle,
+	.stop = stli_stop,
+	.start = stli_start,
+	.hangup = stli_hangup,
+	.flush_buffer = stli_flushbuffer,
+	.break_ctl = stli_breakctl,
+	.wait_until_sent = stli_waituntilsent,
+	.send_xchar = stli_sendxchar,
+	.read_proc = stli_readproc,
+	.tiocmget = stli_tiocmget,
+	.tiocmset = stli_tiocmset,
+};
+
+/*****************************************************************************/
+
+int __init stli_init(void)
+{
+	int i;
+	printk(KERN_INFO "%s: version %s\n", stli_drvtitle, stli_drvversion);
+
+	stli_initbrds();
+
+	stli_serial = alloc_tty_driver(STL_MAXBRDS * STL_MAXPORTS);
+	if (!stli_serial)
+		return -ENOMEM;
+
+/*
+ *	Allocate a temporary write buffer.
+ */
+	stli_tmpwritebuf = (char *) stli_memalloc(STLI_TXBUFSIZE);
+	if (stli_tmpwritebuf == (char *) NULL)
+		printk(KERN_ERR "STALLION: failed to allocate memory "
+				"(size=%d)\n", STLI_TXBUFSIZE);
+	stli_txcookbuf = stli_memalloc(STLI_TXBUFSIZE);
+	if (stli_txcookbuf == (char *) NULL)
+		printk(KERN_ERR "STALLION: failed to allocate memory "
+				"(size=%d)\n", STLI_TXBUFSIZE);
+
+/*
+ *	Set up a character driver for the shared memory region. We need this
+ *	to down load the slave code image. Also it is a useful debugging tool.
+ */
+	if (register_chrdev(STL_SIOMEMMAJOR, "staliomem", &stli_fsiomem))
+		printk(KERN_ERR "STALLION: failed to register serial memory "
+				"device\n");
+
+	devfs_mk_dir("staliomem");
+	istallion_class = class_simple_create(THIS_MODULE, "staliomem");
+	for (i = 0; i < 4; i++) {
+		devfs_mk_cdev(MKDEV(STL_SIOMEMMAJOR, i),
+			       S_IFCHR | S_IRUSR | S_IWUSR,
+			       "staliomem/%d", i);
+		class_simple_device_add(istallion_class, MKDEV(STL_SIOMEMMAJOR, i), 
+				NULL, "staliomem%d", i);
+	}
+
+/*
+ *	Set up the tty driver structure and register us as a driver.
+ */
+	stli_serial->owner = THIS_MODULE;
+	stli_serial->driver_name = stli_drvname;
+	stli_serial->name = stli_serialname;
+	stli_serial->major = STL_SERIALMAJOR;
+	stli_serial->minor_start = 0;
+	stli_serial->type = TTY_DRIVER_TYPE_SERIAL;
+	stli_serial->subtype = SERIAL_TYPE_NORMAL;
+	stli_serial->init_termios = stli_deftermios;
+	stli_serial->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(stli_serial, &stli_ops);
+
+	if (tty_register_driver(stli_serial)) {
+		put_tty_driver(stli_serial);
+		printk(KERN_ERR "STALLION: failed to register serial driver\n");
+		return -EBUSY;
+	}
+	return(0);
+}
+
+/*****************************************************************************/
diff --git a/drivers/char/ite_gpio.c b/drivers/char/ite_gpio.c
new file mode 100644
index 0000000..d1ed6ac
--- /dev/null
+++ b/drivers/char/ite_gpio.c
@@ -0,0 +1,419 @@
+/*
+ * FILE NAME ite_gpio.c
+ *
+ * BRIEF MODULE DESCRIPTION
+ *  API for ITE GPIO device.
+ *  Driver for ITE GPIO device.
+ *
+ *  Author: MontaVista Software, Inc.  <source@mvista.com>
+ *          Hai-Pao Fan <haipao@mvista.com>
+ *
+ * Copyright 2001 MontaVista Software 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.
+ *
+ *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
+ *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ *  NO  EVENT  SHALL   THE AUTHOR  BE	LIABLE FOR ANY   DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
+ *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.
+ */
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <asm/uaccess.h>
+#include <asm/addrspace.h>
+#include <asm/it8172/it8172_int.h>
+#include <linux/sched.h>
+#include <linux/ite_gpio.h>
+
+#define ite_gpio_base 0x14013800
+
+#define	ITE_GPADR	(*(volatile __u8 *)(0x14013800 + KSEG1))
+#define	ITE_GPBDR	(*(volatile __u8 *)(0x14013808 + KSEG1))
+#define	ITE_GPCDR	(*(volatile __u8 *)(0x14013810 + KSEG1))
+#define	ITE_GPACR	(*(volatile __u16 *)(0x14013802 + KSEG1))
+#define	ITE_GPBCR	(*(volatile __u16 *)(0x1401380a + KSEG1))
+#define	ITE_GPCCR	(*(volatile __u16 *)(0x14013812 + KSEG1))
+#define ITE_GPAICR	(*(volatile __u16 *)(0x14013804 + KSEG1))
+#define	ITE_GPBICR	(*(volatile __u16 *)(0x1401380c + KSEG1))
+#define	ITE_GPCICR	(*(volatile __u16 *)(0x14013814 + KSEG1))
+#define	ITE_GPAISR	(*(volatile __u8 *)(0x14013806 + KSEG1))
+#define	ITE_GPBISR	(*(volatile __u8 *)(0x1401380e + KSEG1))
+#define	ITE_GPCISR	(*(volatile __u8 *)(0x14013816 + KSEG1))
+#define	ITE_GCR		(*(volatile __u8 *)(0x14013818 + KSEG1))
+
+#define MAX_GPIO_LINE		21
+static int ite_gpio_irq=IT8172_GPIO_IRQ;
+
+static long ite_irq_counter[MAX_GPIO_LINE];
+wait_queue_head_t ite_gpio_wait[MAX_GPIO_LINE];
+static int ite_gpio_irq_pending[MAX_GPIO_LINE];
+
+static int ite_gpio_debug=0;
+#define DEB(x)  if (ite_gpio_debug>=1) x
+
+int ite_gpio_in(__u32 device, __u32 mask, volatile __u32 *data)
+{
+	DEB(printk("ite_gpio_in mask=0x%x\n",mask)); 
+
+	switch (device) {
+	case ITE_GPIO_PORTA:
+		ITE_GPACR = (__u16)mask;	/* 0xffff */
+		*data = ITE_GPADR;
+		break;
+	case ITE_GPIO_PORTB:
+		ITE_GPBCR = (__u16)mask;	/* 0xffff */
+		*data = ITE_GPBDR;
+		break;
+	case ITE_GPIO_PORTC:
+		ITE_GPCCR = (__u16)mask;	/* 0x03ff */
+		*data = ITE_GPCDR;
+		break;
+	default:
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+
+int ite_gpio_out(__u32 device, __u32 mask, __u32 data)
+{
+	switch (device) {
+	case ITE_GPIO_PORTA:
+		ITE_GPACR = (__u16)mask;	/* 0x5555 */
+		ITE_GPADR = (__u8)data;
+		break;
+	case ITE_GPIO_PORTB:
+		ITE_GPBCR = (__u16)mask;	/* 0x5555 */
+		ITE_GPBDR = (__u8)data;
+		break;
+	case ITE_GPIO_PORTC:
+		ITE_GPCCR = (__u16)mask;	/* 0x0155 */
+		ITE_GPCDR = (__u8)data;
+		break;
+	default:
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+int ite_gpio_int_ctrl(__u32 device, __u32 mask, __u32 data)
+{
+	switch (device) {
+	case ITE_GPIO_PORTA:
+		ITE_GPAICR = (ITE_GPAICR & ~mask) | (data & mask);
+		break;
+	case ITE_GPIO_PORTB:
+		ITE_GPBICR = (ITE_GPBICR & ~mask) | (data & mask);
+		break;
+	case ITE_GPIO_PORTC:
+		ITE_GPCICR = (ITE_GPCICR & ~mask) | (data & mask);
+		break;
+	default:
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+int ite_gpio_in_status(__u32 device, __u32 mask, volatile __u32 *data)
+{
+	int ret=-1;
+
+	if ((MAX_GPIO_LINE > *data) && (*data >= 0)) 
+		ret=ite_gpio_irq_pending[*data];
+ 
+	DEB(printk("ite_gpio_in_status %d ret=%d\n",*data, ret));
+
+	switch (device) {
+	case ITE_GPIO_PORTA:
+		*data = ITE_GPAISR & mask;
+		break;
+	case ITE_GPIO_PORTB:
+		*data = ITE_GPBISR & mask;
+		break;
+	case ITE_GPIO_PORTC:
+		*data = ITE_GPCISR & mask;
+		break;
+	default:
+		return -EFAULT;
+	}
+
+	return ret;
+}
+
+int ite_gpio_out_status(__u32 device, __u32 mask, __u32 data)
+{
+	switch (device) {
+	case ITE_GPIO_PORTA:
+		ITE_GPAISR = (ITE_GPAISR & ~mask) | (data & mask);
+		break;
+	case ITE_GPIO_PORTB:
+		ITE_GPBISR = (ITE_GPBISR & ~mask) | (data & mask);
+		break;
+	case ITE_GPIO_PORTC:
+		ITE_GPCISR = (ITE_GPCISR & ~mask) | (data & mask);
+		break;
+	default:
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+int ite_gpio_gen_ctrl(__u32 device, __u32 mask, __u32 data)
+{
+	ITE_GCR = (ITE_GCR & ~mask) | (data & mask);
+
+	return 0;
+}
+
+int ite_gpio_int_wait (__u32 device, __u32 mask, __u32 data)
+{
+	int i,line=0, ret=0;
+	unsigned long flags;
+
+	switch (device) {
+	case ITE_GPIO_PORTA:
+		line = data & mask;
+		break;
+	case ITE_GPIO_PORTB:
+		line = (data & mask) <<8;
+		break;
+	case ITE_GPIO_PORTC:
+		line = (data & mask) <<16;
+		break;
+	}
+	for (i=MAX_GPIO_LINE-1; i >= 0; i--) {
+		if ( (line) & (1 << i))
+			break;
+	}
+
+	DEB(printk("wait device=0x%d mask=0x%x data=0x%x index %d\n", 
+		device, mask, data, i));
+
+	if (line & ~(1<<i))
+		return -EFAULT;
+
+	if (ite_gpio_irq_pending[i]==1)
+		return -EFAULT;
+
+	save_flags (flags);
+	cli();
+	ite_gpio_irq_pending[i] = 1;
+	ret = interruptible_sleep_on_timeout(&ite_gpio_wait[i], 3*HZ);
+	restore_flags (flags);
+	ite_gpio_irq_pending[i] = 0;
+
+	return ret;
+}
+
+EXPORT_SYMBOL(ite_gpio_in);
+EXPORT_SYMBOL(ite_gpio_out);
+EXPORT_SYMBOL(ite_gpio_int_ctrl);
+EXPORT_SYMBOL(ite_gpio_in_status);
+EXPORT_SYMBOL(ite_gpio_out_status);
+EXPORT_SYMBOL(ite_gpio_gen_ctrl);
+EXPORT_SYMBOL(ite_gpio_int_wait);
+
+static int ite_gpio_open(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+
+static int ite_gpio_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+
+static int ite_gpio_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	static struct ite_gpio_ioctl_data ioctl_data;
+
+	if (copy_from_user(&ioctl_data, (struct ite_gpio_ioctl_data *)arg,
+			sizeof(ioctl_data)))
+		return -EFAULT;
+	if ((ioctl_data.device < ITE_GPIO_PORTA) ||
+			(ioctl_data.device > ITE_GPIO_PORTC) )
+				return -EFAULT;
+
+	switch(cmd) {
+		case ITE_GPIO_IN:
+			if (ite_gpio_in(ioctl_data.device, ioctl_data.mask,
+					   &ioctl_data.data))
+				return -EFAULT;
+
+			if (copy_to_user((struct ite_gpio_ioctl_data *)arg,
+					 &ioctl_data, sizeof(ioctl_data)))
+				return -EFAULT;
+			break;
+
+		case ITE_GPIO_OUT:
+			return ite_gpio_out(ioctl_data.device,
+					ioctl_data.mask, ioctl_data.data);
+			break;
+
+		case ITE_GPIO_INT_CTRL:
+			return ite_gpio_int_ctrl(ioctl_data.device,
+					ioctl_data.mask, ioctl_data.data);
+			break;
+
+		case ITE_GPIO_IN_STATUS:
+			if (ite_gpio_in_status(ioctl_data.device, ioctl_data.mask,
+					&ioctl_data.data))
+				return -EFAULT;
+			if (copy_to_user((struct ite_gpio_ioctl_data *)arg,
+					&ioctl_data, sizeof(ioctl_data))) 
+				return -EFAULT;
+			break;
+
+		case ITE_GPIO_OUT_STATUS:
+			return ite_gpio_out_status(ioctl_data.device,
+					ioctl_data.mask, ioctl_data.data);
+			break;
+
+		case ITE_GPIO_GEN_CTRL:
+			return ite_gpio_gen_ctrl(ioctl_data.device,
+					ioctl_data.mask, ioctl_data.data);
+			break;
+
+		case ITE_GPIO_INT_WAIT:
+			return ite_gpio_int_wait(ioctl_data.device,
+					ioctl_data.mask, ioctl_data.data);
+			break;
+
+		default:
+			return -ENOIOCTLCMD;
+
+	}
+
+	return 0;
+}
+
+static void ite_gpio_irq_handler(int this_irq, void *dev_id,
+	struct pt_regs *regs)
+{
+	int i,line;
+
+	line = ITE_GPCISR & 0x1f;
+	for (i=4; i >=0; i--) {
+		if ( line & (1 << i)) { 
+			++ite_irq_counter[i+16];
+			ite_gpio_irq_pending[i+16] = 2;
+			wake_up_interruptible(&ite_gpio_wait[i+16]);
+
+DEB(printk("interrupt 0x%x %d\n", &ite_gpio_wait[i+16], i+16));
+
+			ITE_GPCISR = ITE_GPCISR & (1<<i);
+			return;
+		}
+	}
+	line = ITE_GPBISR;
+	for (i=7; i >= 0; i--) {
+		if ( line & (1 << i)) {
+			++ite_irq_counter[i+8];
+			ite_gpio_irq_pending[i+8] = 2;
+			wake_up_interruptible(&ite_gpio_wait[i+8]);
+
+DEB(printk("interrupt 0x%x %d\n",ITE_GPBISR, i+8));
+
+			ITE_GPBISR = ITE_GPBISR & (1<<i);
+			return;
+		}
+	}
+	line = ITE_GPAISR;
+	for (i=7; i >= 0; i--) {
+		if ( line & (1 << i)) {
+			++ite_irq_counter[i];
+			ite_gpio_irq_pending[i] = 2;
+			wake_up_interruptible(&ite_gpio_wait[i]);
+
+DEB(printk("interrupt 0x%x %d\n",ITE_GPAISR, i));
+
+			ITE_GPAISR = ITE_GPAISR & (1<<i);
+			return;
+		}
+	}
+}
+
+static struct file_operations ite_gpio_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= ite_gpio_ioctl,
+	.open		= ite_gpio_open,
+	.release	= ite_gpio_release,
+};
+
+static struct miscdevice ite_gpio_miscdev = {
+	MISC_DYNAMIC_MINOR,
+	"ite_gpio",
+	&ite_gpio_fops
+};
+
+int __init ite_gpio_init(void)
+{
+	int i;
+
+	if (misc_register(&ite_gpio_miscdev))
+		return -ENODEV;
+
+	if (!request_region(ite_gpio_base, 0x1c, "ITE GPIO"))
+	{
+		misc_deregister(&ite_gpio_miscdev);
+		return -EIO;
+	}
+
+	/* initialize registers */
+        ITE_GPACR = 0xffff;
+        ITE_GPBCR = 0xffff;
+        ITE_GPCCR = 0xffff;
+        ITE_GPAICR = 0x00ff;
+        ITE_GPBICR = 0x00ff;
+        ITE_GPCICR = 0x00ff;
+        ITE_GCR = 0;
+	
+	for (i = 0; i < MAX_GPIO_LINE; i++) {
+		ite_gpio_irq_pending[i]=0;	
+		init_waitqueue_head(&ite_gpio_wait[i]);
+	}
+
+	if (request_irq(ite_gpio_irq, ite_gpio_irq_handler, SA_SHIRQ, "gpio", 0) < 0) {
+		misc_deregister(&ite_gpio_miscdev);
+		release_region(ite_gpio_base, 0x1c);
+		return 0;
+	}
+
+	printk("GPIO at 0x%x (irq = %d)\n", ite_gpio_base, ite_gpio_irq);
+
+	return 0;
+}	
+
+static void __exit ite_gpio_exit(void)
+{
+	misc_deregister(&ite_gpio_miscdev);
+}
+
+module_init(ite_gpio_init);
+module_exit(ite_gpio_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/keyboard.c b/drivers/char/keyboard.c
new file mode 100644
index 0000000..3ce51c6
--- /dev/null
+++ b/drivers/char/keyboard.c
@@ -0,0 +1,1254 @@
+/*
+ * linux/drivers/char/keyboard.c
+ *
+ * Written for linux by Johan Myreen as a translation from
+ * the assembly version by Linus (with diacriticals added)
+ *
+ * Some additional features added by Christoph Niemann (ChN), March 1993
+ *
+ * Loadable keymaps by Risto Kankkunen, May 1993
+ *
+ * Diacriticals redone & other small changes, aeb@cwi.nl, June 1993
+ * Added decr/incr_console, dynamic keymaps, Unicode support,
+ * dynamic function/string keys, led setting,  Sept 1994
+ * `Sticky' modifier keys, 951006.
+ *
+ * 11-11-96: SAK should now work in the raw mode (Martin Mares)
+ * 
+ * Modified to provide 'generic' keyboard support by Hamish Macdonald
+ * Merge with the m68k keyboard driver and split-off of the PC low-level
+ * parts by Geert Uytterhoeven, May 1997
+ *
+ * 27-05-97: Added support for the Magic SysRq Key (Martin Mares)
+ * 30-07-98: Dead keys redone, aeb@cwi.nl.
+ * 21-08-02: Converted to input API, major cleanup. (Vojtech Pavlik)
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/kbd_diacr.h>
+#include <linux/vt_kern.h>
+#include <linux/sysrq.h>
+#include <linux/input.h>
+
+static void kbd_disconnect(struct input_handle *handle);
+extern void ctrl_alt_del(void);
+
+/*
+ * Exported functions/variables
+ */
+
+#define KBD_DEFMODE ((1 << VC_REPEAT) | (1 << VC_META))
+
+/*
+ * Some laptops take the 789uiojklm,. keys as number pad when NumLock is on.
+ * This seems a good reason to start with NumLock off. On HIL keyboards
+ * of PARISC machines however there is no NumLock key and everyone expects the keypad 
+ * to be used for numbers.
+ */
+
+#if defined(CONFIG_PARISC) && (defined(CONFIG_KEYBOARD_HIL) || defined(CONFIG_KEYBOARD_HIL_OLD))
+#define KBD_DEFLEDS (1 << VC_NUMLOCK)
+#else
+#define KBD_DEFLEDS 0
+#endif
+
+#define KBD_DEFLOCK 0
+
+void compute_shiftstate(void);
+
+/*
+ * Handler Tables.
+ */
+
+#define K_HANDLERS\
+	k_self,		k_fn,		k_spec,		k_pad,\
+	k_dead,		k_cons,		k_cur,		k_shift,\
+	k_meta,		k_ascii,	k_lock,		k_lowercase,\
+	k_slock,	k_dead2,	k_ignore,	k_ignore
+
+typedef void (k_handler_fn)(struct vc_data *vc, unsigned char value, 
+			    char up_flag, struct pt_regs *regs);
+static k_handler_fn K_HANDLERS;
+static k_handler_fn *k_handler[16] = { K_HANDLERS };
+
+#define FN_HANDLERS\
+	fn_null, 	fn_enter,	fn_show_ptregs,	fn_show_mem,\
+	fn_show_state,	fn_send_intr, 	fn_lastcons, 	fn_caps_toggle,\
+	fn_num,		fn_hold, 	fn_scroll_forw,	fn_scroll_back,\
+	fn_boot_it, 	fn_caps_on, 	fn_compose,	fn_SAK,\
+	fn_dec_console, fn_inc_console, fn_spawn_con, 	fn_bare_num
+
+typedef void (fn_handler_fn)(struct vc_data *vc, struct pt_regs *regs);
+static fn_handler_fn FN_HANDLERS;
+static fn_handler_fn *fn_handler[] = { FN_HANDLERS };
+
+/*
+ * Variables exported for vt_ioctl.c
+ */
+
+/* maximum values each key_handler can handle */
+const int max_vals[] = {
+	255, ARRAY_SIZE(func_table) - 1, ARRAY_SIZE(fn_handler) - 1, NR_PAD - 1,
+	NR_DEAD - 1, 255, 3, NR_SHIFT - 1, 255, NR_ASCII - 1, NR_LOCK - 1,
+	255, NR_LOCK - 1, 255
+};
+
+const int NR_TYPES = ARRAY_SIZE(max_vals);
+
+struct kbd_struct kbd_table[MAX_NR_CONSOLES];
+static struct kbd_struct *kbd = kbd_table;
+static struct kbd_struct kbd0;
+
+int spawnpid, spawnsig;
+
+/*
+ * Variables exported for vt.c
+ */
+
+int shift_state = 0;
+
+/*
+ * Internal Data.
+ */
+
+static struct input_handler kbd_handler;
+static unsigned long key_down[NBITS(KEY_MAX)];		/* keyboard key bitmap */
+static unsigned char shift_down[NR_SHIFT];		/* shift state counters.. */
+static int dead_key_next;
+static int npadch = -1;					/* -1 or number assembled on pad */
+static unsigned char diacr;
+static char rep;					/* flag telling character repeat */
+
+static unsigned char ledstate = 0xff;			/* undefined */
+static unsigned char ledioctl;
+
+static struct ledptr {
+	unsigned int *addr;
+	unsigned int mask;
+	unsigned char valid:1;
+} ledptrs[3];
+
+/* Simple translation table for the SysRq keys */
+
+#ifdef CONFIG_MAGIC_SYSRQ
+unsigned char kbd_sysrq_xlate[KEY_MAX + 1] =
+        "\000\0331234567890-=\177\t"                    /* 0x00 - 0x0f */
+        "qwertyuiop[]\r\000as"                          /* 0x10 - 0x1f */
+        "dfghjkl;'`\000\\zxcv"                          /* 0x20 - 0x2f */
+        "bnm,./\000*\000 \000\201\202\203\204\205"      /* 0x30 - 0x3f */
+        "\206\207\210\211\212\000\000789-456+1"         /* 0x40 - 0x4f */
+        "230\177\000\000\213\214\000\000\000\000\000\000\000\000\000\000" /* 0x50 - 0x5f */
+        "\r\000/";                                      /* 0x60 - 0x6f */
+static int sysrq_down;
+#endif
+static int sysrq_alt;
+
+/*
+ * Translation of scancodes to keycodes. We set them on only the first attached
+ * keyboard - for per-keyboard setting, /dev/input/event is more useful.
+ */
+int getkeycode(unsigned int scancode)
+{
+	struct list_head * node;
+	struct input_dev *dev = NULL;
+
+	list_for_each(node,&kbd_handler.h_list) {
+		struct input_handle * handle = to_handle_h(node);
+		if (handle->dev->keycodesize) { 
+			dev = handle->dev; 
+			break;
+		}
+	}
+
+	if (!dev)
+		return -ENODEV;
+
+	if (scancode >= dev->keycodemax)
+		return -EINVAL;
+
+	return INPUT_KEYCODE(dev, scancode);
+}
+
+int setkeycode(unsigned int scancode, unsigned int keycode)
+{
+	struct list_head * node;
+	struct input_dev *dev = NULL;
+	unsigned int i, oldkey;
+
+	list_for_each(node,&kbd_handler.h_list) {
+		struct input_handle *handle = to_handle_h(node);
+		if (handle->dev->keycodesize) { 
+			dev = handle->dev; 
+			break; 
+		}
+	}
+
+	if (!dev)
+		return -ENODEV;
+
+	if (scancode >= dev->keycodemax)
+		return -EINVAL;
+	if (keycode > KEY_MAX)
+		return -EINVAL;
+	if (keycode < 0 || keycode > KEY_MAX)
+		return -EINVAL;
+
+	oldkey = SET_INPUT_KEYCODE(dev, scancode, keycode);
+
+	clear_bit(oldkey, dev->keybit);
+	set_bit(keycode, dev->keybit);
+
+	for (i = 0; i < dev->keycodemax; i++)
+		if (INPUT_KEYCODE(dev,i) == oldkey)
+			set_bit(oldkey, dev->keybit);
+
+	return 0;
+}
+
+/*
+ * Making beeps and bells. 
+ */
+static void kd_nosound(unsigned long ignored)
+{
+	struct list_head * node;
+
+	list_for_each(node,&kbd_handler.h_list) {
+		struct input_handle *handle = to_handle_h(node);
+		if (test_bit(EV_SND, handle->dev->evbit)) {
+			if (test_bit(SND_TONE, handle->dev->sndbit))
+				input_event(handle->dev, EV_SND, SND_TONE, 0);
+			if (test_bit(SND_BELL, handle->dev->sndbit))
+				input_event(handle->dev, EV_SND, SND_BELL, 0);
+		}
+	}
+}
+
+static struct timer_list kd_mksound_timer =
+		TIMER_INITIALIZER(kd_nosound, 0, 0);
+
+void kd_mksound(unsigned int hz, unsigned int ticks)
+{
+	struct list_head * node;
+
+	del_timer(&kd_mksound_timer);
+
+	if (hz) {
+		list_for_each_prev(node,&kbd_handler.h_list) {
+			struct input_handle *handle = to_handle_h(node);
+			if (test_bit(EV_SND, handle->dev->evbit)) {
+				if (test_bit(SND_TONE, handle->dev->sndbit)) {
+					input_event(handle->dev, EV_SND, SND_TONE, hz);
+					break;
+				}
+				if (test_bit(SND_BELL, handle->dev->sndbit)) {
+					input_event(handle->dev, EV_SND, SND_BELL, 1);
+					break;
+				}
+			}
+		}
+		if (ticks)
+			mod_timer(&kd_mksound_timer, jiffies + ticks);
+	} else
+		kd_nosound(0);
+}
+
+/*
+ * Setting the keyboard rate.
+ */
+
+int kbd_rate(struct kbd_repeat *rep)
+{
+	struct list_head *node;
+	unsigned int d = 0;
+	unsigned int p = 0;
+
+	list_for_each(node,&kbd_handler.h_list) {
+		struct input_handle *handle = to_handle_h(node);
+		struct input_dev *dev = handle->dev;
+
+		if (test_bit(EV_REP, dev->evbit)) {
+			if (rep->delay > 0)
+				input_event(dev, EV_REP, REP_DELAY, rep->delay);
+			if (rep->period > 0)
+				input_event(dev, EV_REP, REP_PERIOD, rep->period);
+			d = dev->rep[REP_DELAY];
+			p = dev->rep[REP_PERIOD];
+		}
+	}
+	rep->delay  = d;
+	rep->period = p;
+	return 0;
+}
+
+/*
+ * Helper Functions.
+ */
+static void put_queue(struct vc_data *vc, int ch)
+{
+	struct tty_struct *tty = vc->vc_tty;
+
+	if (tty) {
+		tty_insert_flip_char(tty, ch, 0);
+		con_schedule_flip(tty);
+	}
+}
+
+static void puts_queue(struct vc_data *vc, char *cp)
+{
+	struct tty_struct *tty = vc->vc_tty;
+
+	if (!tty)
+		return;
+
+	while (*cp) {
+		tty_insert_flip_char(tty, *cp, 0);
+		cp++;
+	}
+	con_schedule_flip(tty);
+}
+
+static void applkey(struct vc_data *vc, int key, char mode)
+{
+	static char buf[] = { 0x1b, 'O', 0x00, 0x00 };
+
+	buf[1] = (mode ? 'O' : '[');
+	buf[2] = key;
+	puts_queue(vc, buf);
+}
+
+/*
+ * Many other routines do put_queue, but I think either
+ * they produce ASCII, or they produce some user-assigned
+ * string, and in both cases we might assume that it is
+ * in utf-8 already. UTF-8 is defined for words of up to 31 bits,
+ * but we need only 16 bits here
+ */
+static void to_utf8(struct vc_data *vc, ushort c)
+{
+	if (c < 0x80)
+		/*  0******* */
+		put_queue(vc, c);
+    	else if (c < 0x800) {
+		/* 110***** 10****** */
+		put_queue(vc, 0xc0 | (c >> 6)); 
+		put_queue(vc, 0x80 | (c & 0x3f));
+    	} else {
+		/* 1110**** 10****** 10****** */
+		put_queue(vc, 0xe0 | (c >> 12));
+		put_queue(vc, 0x80 | ((c >> 6) & 0x3f));
+		put_queue(vc, 0x80 | (c & 0x3f));
+    	}
+}
+
+/* 
+ * Called after returning from RAW mode or when changing consoles - recompute
+ * shift_down[] and shift_state from key_down[] maybe called when keymap is
+ * undefined, so that shiftkey release is seen
+ */
+void compute_shiftstate(void)
+{
+	unsigned int i, j, k, sym, val;
+
+	shift_state = 0;
+	memset(shift_down, 0, sizeof(shift_down));
+	
+	for (i = 0; i < ARRAY_SIZE(key_down); i++) {
+
+		if (!key_down[i])
+			continue;
+
+		k = i * BITS_PER_LONG;
+
+		for (j = 0; j < BITS_PER_LONG; j++, k++) {
+
+			if (!test_bit(k, key_down))
+				continue;
+
+			sym = U(key_maps[0][k]);
+			if (KTYP(sym) != KT_SHIFT && KTYP(sym) != KT_SLOCK)
+				continue;
+
+			val = KVAL(sym);
+			if (val == KVAL(K_CAPSSHIFT))
+				val = KVAL(K_SHIFT);
+
+			shift_down[val]++;
+			shift_state |= (1 << val);
+		}
+	}
+}
+
+/*
+ * We have a combining character DIACR here, followed by the character CH.
+ * If the combination occurs in the table, return the corresponding value.
+ * Otherwise, if CH is a space or equals DIACR, return DIACR.
+ * Otherwise, conclude that DIACR was not combining after all,
+ * queue it and return CH.
+ */
+static unsigned char handle_diacr(struct vc_data *vc, unsigned char ch)
+{
+	int d = diacr;
+	unsigned int i;
+
+	diacr = 0;
+
+	for (i = 0; i < accent_table_size; i++) {
+		if (accent_table[i].diacr == d && accent_table[i].base == ch)
+			return accent_table[i].result;
+	}
+
+	if (ch == ' ' || ch == d)
+		return d;
+
+	put_queue(vc, d);
+	return ch;
+}
+
+/*
+ * Special function handlers
+ */
+static void fn_enter(struct vc_data *vc, struct pt_regs *regs)
+{
+	if (diacr) {
+		put_queue(vc, diacr);
+		diacr = 0;
+	}
+	put_queue(vc, 13);
+	if (vc_kbd_mode(kbd, VC_CRLF))
+		put_queue(vc, 10);
+}
+
+static void fn_caps_toggle(struct vc_data *vc, struct pt_regs *regs)
+{
+	if (rep)
+		return;
+	chg_vc_kbd_led(kbd, VC_CAPSLOCK);
+}
+
+static void fn_caps_on(struct vc_data *vc, struct pt_regs *regs)
+{
+	if (rep)
+		return;
+	set_vc_kbd_led(kbd, VC_CAPSLOCK);
+}
+
+static void fn_show_ptregs(struct vc_data *vc, struct pt_regs *regs)
+{
+	if (regs)
+		show_regs(regs);
+}
+
+static void fn_hold(struct vc_data *vc, struct pt_regs *regs)
+{
+	struct tty_struct *tty = vc->vc_tty;
+
+	if (rep || !tty)
+		return;
+
+	/*
+	 * Note: SCROLLOCK will be set (cleared) by stop_tty (start_tty);
+	 * these routines are also activated by ^S/^Q.
+	 * (And SCROLLOCK can also be set by the ioctl KDSKBLED.)
+	 */
+	if (tty->stopped)
+		start_tty(tty);
+	else
+		stop_tty(tty);
+}
+
+static void fn_num(struct vc_data *vc, struct pt_regs *regs)
+{
+	if (vc_kbd_mode(kbd,VC_APPLIC))
+		applkey(vc, 'P', 1);
+	else
+		fn_bare_num(vc, regs);
+}
+
+/*
+ * Bind this to Shift-NumLock if you work in application keypad mode
+ * but want to be able to change the NumLock flag.
+ * Bind this to NumLock if you prefer that the NumLock key always
+ * changes the NumLock flag.
+ */
+static void fn_bare_num(struct vc_data *vc, struct pt_regs *regs)
+{
+	if (!rep)
+		chg_vc_kbd_led(kbd, VC_NUMLOCK);
+}
+
+static void fn_lastcons(struct vc_data *vc, struct pt_regs *regs)
+{
+	/* switch to the last used console, ChN */
+	set_console(last_console);
+}
+
+static void fn_dec_console(struct vc_data *vc, struct pt_regs *regs)
+{
+	int i, cur = fg_console;
+
+	/* Currently switching?  Queue this next switch relative to that. */
+	if (want_console != -1)
+		cur = want_console;
+
+	for (i = cur-1; i != cur; i--) {
+		if (i == -1)
+			i = MAX_NR_CONSOLES-1;
+		if (vc_cons_allocated(i))
+			break;
+	}
+	set_console(i);
+}
+
+static void fn_inc_console(struct vc_data *vc, struct pt_regs *regs)
+{
+	int i, cur = fg_console;
+
+	/* Currently switching?  Queue this next switch relative to that. */
+	if (want_console != -1)
+		cur = want_console;
+
+	for (i = cur+1; i != cur; i++) {
+		if (i == MAX_NR_CONSOLES)
+			i = 0;
+		if (vc_cons_allocated(i))
+			break;
+	}
+	set_console(i);
+}
+
+static void fn_send_intr(struct vc_data *vc, struct pt_regs *regs)
+{
+	struct tty_struct *tty = vc->vc_tty;
+
+	if (!tty)
+		return;
+	tty_insert_flip_char(tty, 0, TTY_BREAK);
+	con_schedule_flip(tty);
+}
+
+static void fn_scroll_forw(struct vc_data *vc, struct pt_regs *regs)
+{
+	scrollfront(vc, 0);
+}
+
+static void fn_scroll_back(struct vc_data *vc, struct pt_regs *regs)
+{
+	scrollback(vc, 0);
+}
+
+static void fn_show_mem(struct vc_data *vc, struct pt_regs *regs)
+{
+	show_mem();
+}
+
+static void fn_show_state(struct vc_data *vc, struct pt_regs *regs)
+{
+	show_state();
+}
+
+static void fn_boot_it(struct vc_data *vc, struct pt_regs *regs)
+{
+	ctrl_alt_del();
+}
+
+static void fn_compose(struct vc_data *vc, struct pt_regs *regs)
+{
+	dead_key_next = 1;
+}
+
+static void fn_spawn_con(struct vc_data *vc, struct pt_regs *regs)
+{
+        if (spawnpid)
+	   if(kill_proc(spawnpid, spawnsig, 1))
+	     spawnpid = 0;
+}
+
+static void fn_SAK(struct vc_data *vc, struct pt_regs *regs)
+{
+	struct tty_struct *tty = vc->vc_tty;
+
+	/*
+	 * SAK should also work in all raw modes and reset
+	 * them properly.
+	 */
+	if (tty)
+		do_SAK(tty);
+	reset_vc(vc);
+}
+
+static void fn_null(struct vc_data *vc, struct pt_regs *regs)
+{
+	compute_shiftstate();
+}
+
+/*
+ * Special key handlers
+ */
+static void k_ignore(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+}
+
+static void k_spec(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	if (up_flag)
+		return;
+	if (value >= ARRAY_SIZE(fn_handler))
+		return;
+	if ((kbd->kbdmode == VC_RAW || 
+	     kbd->kbdmode == VC_MEDIUMRAW) && 
+	     value != KVAL(K_SAK))
+		return;		/* SAK is allowed even in raw mode */
+	fn_handler[value](vc, regs);
+}
+
+static void k_lowercase(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	printk(KERN_ERR "keyboard.c: k_lowercase was called - impossible\n");
+}
+
+static void k_self(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	if (up_flag)
+		return;		/* no action, if this is a key release */
+
+	if (diacr)
+		value = handle_diacr(vc, value);
+
+	if (dead_key_next) {
+		dead_key_next = 0;
+		diacr = value;
+		return;
+	}
+	put_queue(vc, value);
+}
+
+/*
+ * Handle dead key. Note that we now may have several
+ * dead keys modifying the same character. Very useful
+ * for Vietnamese.
+ */
+static void k_dead2(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	if (up_flag)
+		return;
+	diacr = (diacr ? handle_diacr(vc, value) : value);
+}
+
+/*
+ * Obsolete - for backwards compatibility only
+ */
+static void k_dead(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	static unsigned char ret_diacr[NR_DEAD] = {'`', '\'', '^', '~', '"', ',' };
+	value = ret_diacr[value];
+	k_dead2(vc, value, up_flag, regs);
+}
+
+static void k_cons(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	if (up_flag)
+		return;
+	set_console(value);
+}
+
+static void k_fn(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	unsigned v;
+
+	if (up_flag)
+		return;
+	v = value;
+	if (v < ARRAY_SIZE(func_table)) {
+		if (func_table[value])
+			puts_queue(vc, func_table[value]);
+	} else
+		printk(KERN_ERR "k_fn called with value=%d\n", value);
+}
+
+static void k_cur(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	static const char *cur_chars = "BDCA";
+
+	if (up_flag)
+		return;
+	applkey(vc, cur_chars[value], vc_kbd_mode(kbd, VC_CKMODE));
+}
+
+static void k_pad(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	static const char *pad_chars = "0123456789+-*/\015,.?()#";
+	static const char *app_map = "pqrstuvwxylSRQMnnmPQS";
+
+	if (up_flag)
+		return;		/* no action, if this is a key release */
+
+	/* kludge... shift forces cursor/number keys */
+	if (vc_kbd_mode(kbd, VC_APPLIC) && !shift_down[KG_SHIFT]) {
+		applkey(vc, app_map[value], 1);
+		return;
+	}
+
+	if (!vc_kbd_led(kbd, VC_NUMLOCK))
+		switch (value) {
+			case KVAL(K_PCOMMA):
+			case KVAL(K_PDOT):
+				k_fn(vc, KVAL(K_REMOVE), 0, regs);
+				return;
+			case KVAL(K_P0):
+				k_fn(vc, KVAL(K_INSERT), 0, regs);
+				return;
+			case KVAL(K_P1):
+				k_fn(vc, KVAL(K_SELECT), 0, regs);
+				return;
+			case KVAL(K_P2):
+				k_cur(vc, KVAL(K_DOWN), 0, regs);
+				return;
+			case KVAL(K_P3):
+				k_fn(vc, KVAL(K_PGDN), 0, regs);
+				return;
+			case KVAL(K_P4):
+				k_cur(vc, KVAL(K_LEFT), 0, regs);
+				return;
+			case KVAL(K_P6):
+				k_cur(vc, KVAL(K_RIGHT), 0, regs);
+				return;
+			case KVAL(K_P7):
+				k_fn(vc, KVAL(K_FIND), 0, regs);
+				return;
+			case KVAL(K_P8):
+				k_cur(vc, KVAL(K_UP), 0, regs);
+				return;
+			case KVAL(K_P9):
+				k_fn(vc, KVAL(K_PGUP), 0, regs);
+				return;
+			case KVAL(K_P5):
+				applkey(vc, 'G', vc_kbd_mode(kbd, VC_APPLIC));
+				return;
+		}
+
+	put_queue(vc, pad_chars[value]);
+	if (value == KVAL(K_PENTER) && vc_kbd_mode(kbd, VC_CRLF))
+		put_queue(vc, 10);
+}
+
+static void k_shift(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	int old_state = shift_state;
+
+	if (rep)
+		return;
+	/*
+	 * Mimic typewriter:
+	 * a CapsShift key acts like Shift but undoes CapsLock
+	 */
+	if (value == KVAL(K_CAPSSHIFT)) {
+		value = KVAL(K_SHIFT);
+		if (!up_flag)
+			clr_vc_kbd_led(kbd, VC_CAPSLOCK);
+	}
+
+	if (up_flag) {
+		/*
+		 * handle the case that two shift or control
+		 * keys are depressed simultaneously
+		 */
+		if (shift_down[value])
+			shift_down[value]--;
+	} else
+		shift_down[value]++;
+
+	if (shift_down[value])
+		shift_state |= (1 << value);
+	else
+		shift_state &= ~(1 << value);
+
+	/* kludge */
+	if (up_flag && shift_state != old_state && npadch != -1) {
+		if (kbd->kbdmode == VC_UNICODE)
+			to_utf8(vc, npadch & 0xffff);
+		else
+			put_queue(vc, npadch & 0xff);
+		npadch = -1;
+	}
+}
+
+static void k_meta(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	if (up_flag)
+		return;
+
+	if (vc_kbd_mode(kbd, VC_META)) {
+		put_queue(vc, '\033');
+		put_queue(vc, value);
+	} else
+		put_queue(vc, value | 0x80);
+}
+
+static void k_ascii(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	int base;
+
+	if (up_flag)
+		return;
+
+	if (value < 10) {
+		/* decimal input of code, while Alt depressed */
+		base = 10;
+	} else {
+		/* hexadecimal input of code, while AltGr depressed */
+		value -= 10;
+		base = 16;
+	}
+
+	if (npadch == -1)
+		npadch = value;
+	else
+		npadch = npadch * base + value;
+}
+
+static void k_lock(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	if (up_flag || rep)
+		return;
+	chg_vc_kbd_lock(kbd, value);
+}
+
+static void k_slock(struct vc_data *vc, unsigned char value, char up_flag, struct pt_regs *regs)
+{
+	k_shift(vc, value, up_flag, regs);
+	if (up_flag || rep)
+		return;
+	chg_vc_kbd_slock(kbd, value);
+	/* try to make Alt, oops, AltGr and such work */
+	if (!key_maps[kbd->lockstate ^ kbd->slockstate]) {
+		kbd->slockstate = 0;
+		chg_vc_kbd_slock(kbd, value);
+	}
+}
+
+/*
+ * The leds display either (i) the status of NumLock, CapsLock, ScrollLock,
+ * or (ii) whatever pattern of lights people want to show using KDSETLED,
+ * or (iii) specified bits of specified words in kernel memory.
+ */
+unsigned char getledstate(void)
+{
+	return ledstate;
+}
+
+void setledstate(struct kbd_struct *kbd, unsigned int led)
+{
+	if (!(led & ~7)) {
+		ledioctl = led;
+		kbd->ledmode = LED_SHOW_IOCTL;
+	} else
+		kbd->ledmode = LED_SHOW_FLAGS;
+	set_leds();
+}
+
+static inline unsigned char getleds(void)
+{
+	struct kbd_struct *kbd = kbd_table + fg_console;
+	unsigned char leds;
+	int i;
+
+	if (kbd->ledmode == LED_SHOW_IOCTL)
+		return ledioctl;
+
+	leds = kbd->ledflagstate;
+
+	if (kbd->ledmode == LED_SHOW_MEM) {
+		for (i = 0; i < 3; i++)
+			if (ledptrs[i].valid) {
+				if (*ledptrs[i].addr & ledptrs[i].mask)
+					leds |= (1 << i);
+				else
+					leds &= ~(1 << i);
+			}
+	}
+	return leds;
+}
+
+/*
+ * This routine is the bottom half of the keyboard interrupt
+ * routine, and runs with all interrupts enabled. It does
+ * console changing, led setting and copy_to_cooked, which can
+ * take a reasonably long time.
+ *
+ * Aside from timing (which isn't really that important for
+ * keyboard interrupts as they happen often), using the software
+ * interrupt routines for this thing allows us to easily mask
+ * this when we don't want any of the above to happen.
+ * This allows for easy and efficient race-condition prevention
+ * for kbd_refresh_leds => input_event(dev, EV_LED, ...) => ...
+ */
+
+static void kbd_bh(unsigned long dummy)
+{
+	struct list_head * node;
+	unsigned char leds = getleds();
+
+	if (leds != ledstate) {
+		list_for_each(node,&kbd_handler.h_list) {
+			struct input_handle * handle = to_handle_h(node);
+			input_event(handle->dev, EV_LED, LED_SCROLLL, !!(leds & 0x01));
+			input_event(handle->dev, EV_LED, LED_NUML,    !!(leds & 0x02));
+			input_event(handle->dev, EV_LED, LED_CAPSL,   !!(leds & 0x04));
+			input_sync(handle->dev);
+		}
+	}
+
+	ledstate = leds;
+}
+
+DECLARE_TASKLET_DISABLED(keyboard_tasklet, kbd_bh, 0);
+
+/*
+ * This allows a newly plugged keyboard to pick the LED state.
+ */
+static void kbd_refresh_leds(struct input_handle *handle)
+{
+	unsigned char leds = ledstate;
+
+	tasklet_disable(&keyboard_tasklet);
+	if (leds != 0xff) {
+		input_event(handle->dev, EV_LED, LED_SCROLLL, !!(leds & 0x01));
+		input_event(handle->dev, EV_LED, LED_NUML,    !!(leds & 0x02));
+		input_event(handle->dev, EV_LED, LED_CAPSL,   !!(leds & 0x04));
+		input_sync(handle->dev);
+	}
+	tasklet_enable(&keyboard_tasklet);
+}
+
+#if defined(CONFIG_X86) || defined(CONFIG_IA64) || defined(CONFIG_ALPHA) ||\
+    defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_SPARC32) ||\
+    defined(CONFIG_SPARC64) || defined(CONFIG_PARISC) || defined(CONFIG_SUPERH) ||\
+    (defined(CONFIG_ARM) && defined(CONFIG_KEYBOARD_ATKBD) && !defined(CONFIG_ARCH_RPC))
+
+#define HW_RAW(dev) (test_bit(EV_MSC, dev->evbit) && test_bit(MSC_RAW, dev->mscbit) &&\
+			((dev)->id.bustype == BUS_I8042) && ((dev)->id.vendor == 0x0001) && ((dev)->id.product == 0x0001))
+
+static unsigned short x86_keycodes[256] =
+	{ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
+	 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+	 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+	 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+	 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+	 80, 81, 82, 83, 84,118, 86, 87, 88,115,120,119,121,112,123, 92,
+	284,285,309,298,312, 91,327,328,329,331,333,335,336,337,338,339,
+	367,288,302,304,350, 89,334,326,267,126,268,269,125,347,348,349,
+	360,261,262,263,268,376,100,101,321,316,373,286,289,102,351,355,
+	103,104,105,275,287,279,306,106,274,107,294,364,358,363,362,361,
+	291,108,381,281,290,272,292,305,280, 99,112,257,258,359,113,114,
+	264,117,271,374,379,265,266, 93, 94, 95, 85,259,375,260, 90,116,
+	377,109,111,277,278,282,283,295,296,297,299,300,301,293,303,307,
+	308,310,313,314,315,317,318,319,320,357,322,323,324,325,276,330,
+	332,340,365,342,343,344,345,346,356,270,341,368,369,370,371,372 };
+
+#ifdef CONFIG_MAC_EMUMOUSEBTN
+extern int mac_hid_mouse_emulate_buttons(int, int, int);
+#endif /* CONFIG_MAC_EMUMOUSEBTN */
+
+#if defined(CONFIG_SPARC32) || defined(CONFIG_SPARC64)
+static int sparc_l1_a_state = 0;
+extern void sun_do_break(void);
+#endif
+
+static int emulate_raw(struct vc_data *vc, unsigned int keycode, 
+		       unsigned char up_flag)
+{
+	if (keycode > 255 || !x86_keycodes[keycode])
+		return -1; 
+
+	switch (keycode) {
+		case KEY_PAUSE:
+			put_queue(vc, 0xe1);
+			put_queue(vc, 0x1d | up_flag);
+			put_queue(vc, 0x45 | up_flag);
+			return 0;
+		case KEY_HANGUEL:
+			if (!up_flag) put_queue(vc, 0xf1);
+			return 0;
+		case KEY_HANJA:
+			if (!up_flag) put_queue(vc, 0xf2);
+			return 0;
+	} 
+
+	if (keycode == KEY_SYSRQ && sysrq_alt) {
+		put_queue(vc, 0x54 | up_flag);
+		return 0;
+	}
+
+	if (x86_keycodes[keycode] & 0x100)
+		put_queue(vc, 0xe0);
+
+	put_queue(vc, (x86_keycodes[keycode] & 0x7f) | up_flag);
+
+	if (keycode == KEY_SYSRQ) {
+		put_queue(vc, 0xe0);
+		put_queue(vc, 0x37 | up_flag);
+	}
+
+	return 0;
+}
+
+#else
+
+#define HW_RAW(dev)	0
+
+#warning "Cannot generate rawmode keyboard for your architecture yet."
+
+static int emulate_raw(struct vc_data *vc, unsigned int keycode, unsigned char up_flag)
+{
+	if (keycode > 127)
+		return -1;
+
+	put_queue(vc, keycode | up_flag);
+	return 0;
+}
+#endif
+
+static void kbd_rawcode(unsigned char data)
+{
+	struct vc_data *vc = vc_cons[fg_console].d;
+	kbd = kbd_table + fg_console;
+	if (kbd->kbdmode == VC_RAW)
+		put_queue(vc, data);
+}
+
+void kbd_keycode(unsigned int keycode, int down, int hw_raw, struct pt_regs *regs)
+{
+	struct vc_data *vc = vc_cons[fg_console].d;
+	unsigned short keysym, *key_map;
+	unsigned char type, raw_mode;
+	struct tty_struct *tty;
+	int shift_final;
+
+	tty = vc->vc_tty;
+
+	if (tty && (!tty->driver_data)) {
+		/* No driver data? Strange. Okay we fix it then. */
+		tty->driver_data = vc;
+	}
+
+	kbd = kbd_table + fg_console;
+
+	if (keycode == KEY_LEFTALT || keycode == KEY_RIGHTALT)
+		sysrq_alt = down;
+#if defined(CONFIG_SPARC32) || defined(CONFIG_SPARC64)
+	if (keycode == KEY_STOP)
+		sparc_l1_a_state = down;
+#endif
+
+	rep = (down == 2);
+
+#ifdef CONFIG_MAC_EMUMOUSEBTN
+	if (mac_hid_mouse_emulate_buttons(1, keycode, down))
+		return;
+#endif /* CONFIG_MAC_EMUMOUSEBTN */
+
+	if ((raw_mode = (kbd->kbdmode == VC_RAW)) && !hw_raw)
+		if (emulate_raw(vc, keycode, !down << 7))
+			if (keycode < BTN_MISC)
+				printk(KERN_WARNING "keyboard.c: can't emulate rawmode for keycode %d\n", keycode);
+
+#ifdef CONFIG_MAGIC_SYSRQ	       /* Handle the SysRq Hack */
+	if (keycode == KEY_SYSRQ && (sysrq_down || (down == 1 && sysrq_alt))) {
+		sysrq_down = down;
+		return;
+	}
+	if (sysrq_down && down && !rep) {
+		handle_sysrq(kbd_sysrq_xlate[keycode], regs, tty);
+		return;
+	}
+#endif
+#if defined(CONFIG_SPARC32) || defined(CONFIG_SPARC64)
+	if (keycode == KEY_A && sparc_l1_a_state) {
+		sparc_l1_a_state = 0;
+		sun_do_break();
+	}
+#endif
+
+	if (kbd->kbdmode == VC_MEDIUMRAW) {
+		/*
+		 * This is extended medium raw mode, with keys above 127
+		 * encoded as 0, high 7 bits, low 7 bits, with the 0 bearing
+		 * the 'up' flag if needed. 0 is reserved, so this shouldn't
+		 * interfere with anything else. The two bytes after 0 will
+		 * always have the up flag set not to interfere with older
+		 * applications. This allows for 16384 different keycodes,
+		 * which should be enough.
+		 */
+		if (keycode < 128) {
+			put_queue(vc, keycode | (!down << 7));
+		} else {
+			put_queue(vc, !down << 7);
+			put_queue(vc, (keycode >> 7) | 0x80);
+			put_queue(vc, keycode | 0x80);
+		}
+		raw_mode = 1;
+	}
+
+	if (down)
+		set_bit(keycode, key_down);
+	else
+		clear_bit(keycode, key_down);
+
+	if (rep && (!vc_kbd_mode(kbd, VC_REPEAT) || (tty && 
+		(!L_ECHO(tty) && tty->driver->chars_in_buffer(tty))))) {
+		/*
+		 * Don't repeat a key if the input buffers are not empty and the
+		 * characters get aren't echoed locally. This makes key repeat 
+		 * usable with slow applications and under heavy loads.
+		 */
+		return;
+	}
+
+	shift_final = (shift_state | kbd->slockstate) ^ kbd->lockstate;
+	key_map = key_maps[shift_final];
+
+	if (!key_map) {
+		compute_shiftstate();
+		kbd->slockstate = 0;
+		return;
+	}
+
+	if (keycode > NR_KEYS)
+		return;
+
+	keysym = key_map[keycode];
+	type = KTYP(keysym);
+
+	if (type < 0xf0) {
+		if (down && !raw_mode) to_utf8(vc, keysym);
+		return;
+	}
+
+	type -= 0xf0;
+
+	if (raw_mode && type != KT_SPEC && type != KT_SHIFT)
+		return;
+
+	if (type == KT_LETTER) {
+		type = KT_LATIN;
+		if (vc_kbd_led(kbd, VC_CAPSLOCK)) {
+			key_map = key_maps[shift_final ^ (1 << KG_SHIFT)];
+			if (key_map)
+				keysym = key_map[keycode];
+		}
+	}
+
+	(*k_handler[type])(vc, keysym & 0xff, !down, regs);
+
+	if (type != KT_SLOCK)
+		kbd->slockstate = 0;
+}
+
+static void kbd_event(struct input_handle *handle, unsigned int event_type, 
+		      unsigned int event_code, int value)
+{
+	if (event_type == EV_MSC && event_code == MSC_RAW && HW_RAW(handle->dev))
+		kbd_rawcode(value);
+	if (event_type == EV_KEY)
+		kbd_keycode(event_code, value, HW_RAW(handle->dev), handle->dev->regs);
+	tasklet_schedule(&keyboard_tasklet);
+	do_poke_blanked_console = 1;
+	schedule_console_callback();
+}
+
+static char kbd_name[] = "kbd";
+
+/*
+ * When a keyboard (or other input device) is found, the kbd_connect
+ * function is called. The function then looks at the device, and if it
+ * likes it, it can open it and get events from it. In this (kbd_connect)
+ * function, we should decide which VT to bind that keyboard to initially.
+ */
+static struct input_handle *kbd_connect(struct input_handler *handler, 
+					struct input_dev *dev,
+					struct input_device_id *id)
+{
+	struct input_handle *handle;
+	int i;
+
+	for (i = KEY_RESERVED; i < BTN_MISC; i++)
+		if (test_bit(i, dev->keybit)) break;
+
+	if ((i == BTN_MISC) && !test_bit(EV_SND, dev->evbit)) 
+		return NULL;
+
+	if (!(handle = kmalloc(sizeof(struct input_handle), GFP_KERNEL))) 
+		return NULL;
+	memset(handle, 0, sizeof(struct input_handle));
+
+	handle->dev = dev;
+	handle->handler = handler;
+	handle->name = kbd_name;
+
+	input_open_device(handle);
+	kbd_refresh_leds(handle);
+
+	return handle;
+}
+
+static void kbd_disconnect(struct input_handle *handle)
+{
+	input_close_device(handle);
+	kfree(handle);
+}
+
+static struct input_device_id kbd_ids[] = {
+	{
+                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+                .evbit = { BIT(EV_KEY) },
+        },
+	
+	{
+                .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+                .evbit = { BIT(EV_SND) },
+        },	
+
+	{ },    /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(input, kbd_ids);
+
+static struct input_handler kbd_handler = {
+	.event		= kbd_event,
+	.connect	= kbd_connect,
+	.disconnect	= kbd_disconnect,
+	.name		= "kbd",
+	.id_table	= kbd_ids,
+};
+
+int __init kbd_init(void)
+{
+	int i;
+
+        kbd0.ledflagstate = kbd0.default_ledflagstate = KBD_DEFLEDS;
+        kbd0.ledmode = LED_SHOW_FLAGS;
+        kbd0.lockstate = KBD_DEFLOCK;
+        kbd0.slockstate = 0;
+        kbd0.modeflags = KBD_DEFMODE;
+        kbd0.kbdmode = VC_XLATE;
+
+        for (i = 0 ; i < MAX_NR_CONSOLES ; i++)
+                kbd_table[i] = kbd0;
+
+	input_register_handler(&kbd_handler);
+
+	tasklet_enable(&keyboard_tasklet);
+	tasklet_schedule(&keyboard_tasklet);
+
+	return 0;
+}
diff --git a/drivers/char/lcd.c b/drivers/char/lcd.c
new file mode 100644
index 0000000..cf01a72
--- /dev/null
+++ b/drivers/char/lcd.c
@@ -0,0 +1,683 @@
+/*
+ * LCD, LED and Button interface for Cobalt
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 1996, 1997 by Andrew Bose
+ *
+ * Linux kernel version history:
+ *       March 2001: Ported from 2.0.34  by Liam Davies
+ *
+ */
+
+#define RTC_IO_EXTENT	0x10	/*Only really two ports, but... */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/mc146818rtc.h>
+#include <linux/netdevice.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/delay.h>
+
+#include "lcd.h"
+
+static DEFINE_SPINLOCK(lcd_lock);
+
+static int lcd_ioctl(struct inode *inode, struct file *file,
+		     unsigned int cmd, unsigned long arg);
+
+static unsigned int lcd_present = 1;
+
+/* used in arch/mips/cobalt/reset.c */
+int led_state = 0;
+
+#if defined(CONFIG_TULIP) && 0
+
+#define MAX_INTERFACES	8
+static linkcheck_func_t linkcheck_callbacks[MAX_INTERFACES];
+static void *linkcheck_cookies[MAX_INTERFACES];
+
+int lcd_register_linkcheck_func(int iface_num, void *func, void *cookie)
+{
+	if (iface_num < 0 ||
+	    iface_num >= MAX_INTERFACES ||
+	    linkcheck_callbacks[iface_num] != NULL)
+		return -1;
+	linkcheck_callbacks[iface_num] = (linkcheck_func_t) func;
+	linkcheck_cookies[iface_num] = cookie;
+	return 0;
+}
+#endif
+
+static int lcd_ioctl(struct inode *inode, struct file *file,
+		     unsigned int cmd, unsigned long arg)
+{
+	struct lcd_display button_display;
+	unsigned long address, a;
+
+	switch (cmd) {
+	case LCD_On:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x0F);
+		break;
+
+	case LCD_Off:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x08);
+		break;
+
+	case LCD_Reset:
+		udelay(150);
+		LCDWriteInst(0x3F);
+		udelay(150);
+		LCDWriteInst(0x3F);
+		udelay(150);
+		LCDWriteInst(0x3F);
+		udelay(150);
+		LCDWriteInst(0x3F);
+		udelay(150);
+		LCDWriteInst(0x01);
+		udelay(150);
+		LCDWriteInst(0x06);
+		break;
+
+	case LCD_Clear:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x01);
+		break;
+
+	case LCD_Cursor_Left:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x10);
+		break;
+
+	case LCD_Cursor_Right:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x14);
+		break;
+
+	case LCD_Cursor_Off:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x0C);
+		break;
+
+	case LCD_Cursor_On:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x0F);
+		break;
+
+	case LCD_Blink_Off:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x0E);
+		break;
+
+	case LCD_Get_Cursor_Pos:{
+			struct lcd_display display;
+
+			udelay(150);
+			BusyCheck();
+			display.cursor_address = (LCDReadInst);
+			display.cursor_address =
+			    (display.cursor_address & 0x07F);
+			if (copy_to_user
+			    ((struct lcd_display *) arg, &display,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+
+			break;
+		}
+
+
+	case LCD_Set_Cursor_Pos:{
+			struct lcd_display display;
+
+			if (copy_from_user
+			    (&display, (struct lcd_display *) arg,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+
+			a = (display.cursor_address | kLCD_Addr);
+
+			udelay(150);
+			BusyCheck();
+			LCDWriteInst(a);
+
+			break;
+		}
+
+	case LCD_Get_Cursor:{
+			struct lcd_display display;
+
+			udelay(150);
+			BusyCheck();
+			display.character = LCDReadData;
+
+			if (copy_to_user
+			    ((struct lcd_display *) arg, &display,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+			udelay(150);
+			BusyCheck();
+			LCDWriteInst(0x10);
+
+			break;
+		}
+
+	case LCD_Set_Cursor:{
+			struct lcd_display display;
+
+			if (copy_from_user
+			    (&display, (struct lcd_display *) arg,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+
+			udelay(150);
+			BusyCheck();
+			LCDWriteData(display.character);
+			udelay(150);
+			BusyCheck();
+			LCDWriteInst(0x10);
+
+			break;
+		}
+
+
+	case LCD_Disp_Left:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x18);
+		break;
+
+	case LCD_Disp_Right:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x1C);
+		break;
+
+	case LCD_Home:
+		udelay(150);
+		BusyCheck();
+		LCDWriteInst(0x02);
+		break;
+
+	case LCD_Write:{
+			struct lcd_display display;
+			unsigned int index;
+
+
+			if (copy_from_user
+			    (&display, (struct lcd_display *) arg,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+
+			udelay(150);
+			BusyCheck();
+			LCDWriteInst(0x80);
+			udelay(150);
+			BusyCheck();
+
+			for (index = 0; index < (display.size1); index++) {
+				udelay(150);
+				BusyCheck();
+				LCDWriteData(display.line1[index]);
+				BusyCheck();
+			}
+
+			udelay(150);
+			BusyCheck();
+			LCDWriteInst(0xC0);
+			udelay(150);
+			BusyCheck();
+			for (index = 0; index < (display.size2); index++) {
+				udelay(150);
+				BusyCheck();
+				LCDWriteData(display.line2[index]);
+			}
+
+			break;
+		}
+
+	case LCD_Read:{
+			struct lcd_display display;
+
+			BusyCheck();
+			for (address = kDD_R00; address <= kDD_R01;
+			     address++) {
+				a = (address | kLCD_Addr);
+
+				udelay(150);
+				BusyCheck();
+				LCDWriteInst(a);
+				udelay(150);
+				BusyCheck();
+				display.line1[address] = LCDReadData;
+			}
+
+			display.line1[0x27] = '\0';
+
+			for (address = kDD_R10; address <= kDD_R11;
+			     address++) {
+				a = (address | kLCD_Addr);
+
+				udelay(150);
+				BusyCheck();
+				LCDWriteInst(a);
+
+				udelay(150);
+				BusyCheck();
+				display.line2[address - 0x40] =
+				    LCDReadData;
+			}
+
+			display.line2[0x27] = '\0';
+
+			if (copy_to_user
+			    ((struct lcd_display *) arg, &display,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+			break;
+		}
+
+//  set all GPIO leds to led_display.leds
+
+	case LED_Set:{
+			struct lcd_display led_display;
+
+
+			if (copy_from_user
+			    (&led_display, (struct lcd_display *) arg,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+
+			led_state = led_display.leds;
+			LEDSet(led_state);
+
+			break;
+		}
+
+
+//  set only bit led_display.leds
+
+	case LED_Bit_Set:{
+			unsigned int i;
+			int bit = 1;
+			struct lcd_display led_display;
+
+
+			if (copy_from_user
+			    (&led_display, (struct lcd_display *) arg,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+
+			for (i = 0; i < (int) led_display.leds; i++) {
+				bit = 2 * bit;
+			}
+
+			led_state = led_state | bit;
+			LEDSet(led_state);
+			break;
+		}
+
+//  clear only bit led_display.leds
+
+	case LED_Bit_Clear:{
+			unsigned int i;
+			int bit = 1;
+			struct lcd_display led_display;
+
+
+			if (copy_from_user
+			    (&led_display, (struct lcd_display *) arg,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+
+			for (i = 0; i < (int) led_display.leds; i++) {
+				bit = 2 * bit;
+			}
+
+			led_state = led_state & ~bit;
+			LEDSet(led_state);
+			break;
+		}
+
+
+	case BUTTON_Read:{
+			button_display.buttons = GPIRead;
+			if (copy_to_user
+			    ((struct lcd_display *) arg, &button_display,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+			break;
+		}
+
+	case LINK_Check:{
+			button_display.buttons =
+			    *((volatile unsigned long *) (0xB0100060));
+			if (copy_to_user
+			    ((struct lcd_display *) arg, &button_display,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+			break;
+		}
+
+	case LINK_Check_2:{
+			int iface_num;
+
+			/* panel-utils should pass in the desired interface status is wanted for
+			 * in "buttons" of the structure.  We will set this to non-zero if the
+			 * link is in fact up for the requested interface.  --DaveM
+			 */
+			if (copy_from_user
+			    (&button_display, (struct lcd_display *) arg,
+			     sizeof(button_display)))
+				return -EFAULT;
+			iface_num = button_display.buttons;
+#if defined(CONFIG_TULIP) && 0
+			if (iface_num >= 0 &&
+			    iface_num < MAX_INTERFACES &&
+			    linkcheck_callbacks[iface_num] != NULL) {
+				button_display.buttons =
+				    linkcheck_callbacks[iface_num]
+				    (linkcheck_cookies[iface_num]);
+			} else
+#endif
+				button_display.buttons = 0;
+
+			if (__copy_to_user
+			    ((struct lcd_display *) arg, &button_display,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+			break;
+		}
+
+//  Erase the flash
+
+	case FLASH_Erase:{
+
+			int ctr = 0;
+
+			if ( !capable(CAP_SYS_ADMIN) ) return -EPERM;
+
+			pr_info(LCD "Erasing Flash\n");
+
+			// Chip Erase Sequence
+			WRITE_FLASH(kFlash_Addr1, kFlash_Data1);
+			WRITE_FLASH(kFlash_Addr2, kFlash_Data2);
+			WRITE_FLASH(kFlash_Addr1, kFlash_Erase3);
+			WRITE_FLASH(kFlash_Addr1, kFlash_Data1);
+			WRITE_FLASH(kFlash_Addr2, kFlash_Data2);
+			WRITE_FLASH(kFlash_Addr1, kFlash_Erase6);
+
+			while ((!dqpoll(0x00000000, 0xFF))
+			       && (!timeout(0x00000000))) {
+				ctr++;
+			}
+
+			if (READ_FLASH(0x07FFF0) == 0xFF) {
+				pr_info(LCD "Erase Successful\n");
+			} else if (timeout) {
+				pr_info(LCD "Erase Timed Out\n");
+			}
+
+			break;
+		}
+
+// burn the flash
+
+	case FLASH_Burn:{
+
+			volatile unsigned long burn_addr;
+			unsigned long flags;
+			unsigned int i, index;
+			unsigned char *rom;
+
+
+			struct lcd_display display;
+
+			if ( !capable(CAP_SYS_ADMIN) ) return -EPERM;
+
+			if (copy_from_user
+			    (&display, (struct lcd_display *) arg,
+			     sizeof(struct lcd_display)))
+				return -EFAULT;
+			rom = (unsigned char *) kmalloc((128), GFP_ATOMIC);
+			if (rom == NULL) {
+				printk(KERN_ERR LCD "kmalloc() failed in %s\n",
+						__FUNCTION__);
+				return -ENOMEM;
+			}
+
+			pr_info(LCD "Starting Flash burn\n");
+			for (i = 0; i < FLASH_SIZE; i = i + 128) {
+
+				if (copy_from_user
+				    (rom, display.RomImage + i, 128)) {
+					kfree(rom);
+					return -EFAULT;
+				}
+				burn_addr = kFlashBase + i;
+				spin_lock_irqsave(&lcd_lock, flags);
+				for (index = 0; index < (128); index++) {
+
+					WRITE_FLASH(kFlash_Addr1,
+						    kFlash_Data1);
+					WRITE_FLASH(kFlash_Addr2,
+						    kFlash_Data2);
+					WRITE_FLASH(kFlash_Addr1,
+						    kFlash_Prog);
+					*((volatile unsigned char *)burn_addr) =
+					  (volatile unsigned char) rom[index];
+
+					while ((!dqpoll (burn_addr,
+						(volatile unsigned char)
+						rom[index])) &&
+						(!timeout(burn_addr))) { }
+					burn_addr++;
+				}
+				spin_unlock_irqrestore(&lcd_lock, flags);
+				if (* ((volatile unsigned char *)
+					(burn_addr - 1)) ==
+					(volatile unsigned char)
+					rom[index - 1]) {
+				} else if (timeout) {
+					pr_info(LCD "Flash burn timed out\n");
+				}
+
+
+			}
+			kfree(rom);
+
+			pr_info(LCD "Flash successfully burned\n");
+
+			break;
+		}
+
+//  read the flash all at once
+
+	case FLASH_Read:{
+
+			unsigned char *user_bytes;
+			volatile unsigned long read_addr;
+			unsigned int i;
+
+			user_bytes =
+			    &(((struct lcd_display *) arg)->RomImage[0]);
+
+			if (!access_ok
+			    (VERIFY_WRITE, user_bytes, FLASH_SIZE))
+				return -EFAULT;
+
+			pr_info(LCD "Reading Flash");
+			for (i = 0; i < FLASH_SIZE; i++) {
+				unsigned char tmp_byte;
+				read_addr = kFlashBase + i;
+				tmp_byte =
+				    *((volatile unsigned char *)
+				      read_addr);
+				if (__put_user(tmp_byte, &user_bytes[i]))
+					return -EFAULT;
+			}
+
+
+			break;
+		}
+
+	default:
+		return -EINVAL;
+
+	}
+
+	return 0;
+
+}
+
+static int lcd_open(struct inode *inode, struct file *file)
+{
+	if (!lcd_present)
+		return -ENXIO;
+	else
+		return 0;
+}
+
+/* Only RESET or NEXT counts as button pressed */
+
+static inline int button_pressed(void)
+{
+	unsigned long buttons = GPIRead;
+
+	if ((buttons == BUTTON_Next) || (buttons == BUTTON_Next_B)
+	    || (buttons == BUTTON_Reset_B))
+		return buttons;
+	return 0;
+}
+
+/* LED daemon sits on this and we wake him up once a key is pressed. */
+
+static int lcd_waiters = 0;
+
+static long lcd_read(struct inode *inode, struct file *file, char *buf,
+		     unsigned long count)
+{
+	long buttons_now;
+
+	if (lcd_waiters > 0)
+		return -EINVAL;
+
+	lcd_waiters++;
+	while (((buttons_now = (long) button_pressed()) == 0) &&
+	       !(signal_pending(current))) {
+		msleep_interruptible(2000);
+	}
+	lcd_waiters--;
+
+	if (signal_pending(current))
+		return -ERESTARTSYS;
+	return buttons_now;
+}
+
+/*
+ *	The various file operations we support.
+ */
+
+static struct file_operations lcd_fops = {
+	.read = lcd_read,
+	.ioctl = lcd_ioctl,
+	.open = lcd_open,
+};
+
+static struct miscdevice lcd_dev = {
+	MISC_DYNAMIC_MINOR,
+	"lcd",
+	&lcd_fops
+};
+
+static int lcd_init(void)
+{
+	unsigned long data;
+
+	pr_info("%s\n", LCD_DRIVER);
+	misc_register(&lcd_dev);
+
+	/* Check region? Naaah! Just snarf it up. */
+/*	request_region(RTC_PORT(0), RTC_IO_EXTENT, "lcd");*/
+
+	udelay(150);
+	data = LCDReadData;
+	if ((data & 0x000000FF) == (0x00)) {
+		lcd_present = 0;
+		pr_info(LCD "LCD Not Present\n");
+	} else {
+		lcd_present = 1;
+		WRITE_GAL(kGal_DevBank2PReg, kGal_DevBank2Cfg);
+		WRITE_GAL(kGal_DevBank3PReg, kGal_DevBank3Cfg);
+	}
+
+	return 0;
+}
+
+static void __exit lcd_exit(void)
+{
+	misc_deregister(&lcd_dev);
+}
+
+//
+// Function: dqpoll
+//
+// Description:  Polls the data lines to see if the flash is busy
+//
+// In: address, byte data
+//
+// Out: 0 = busy, 1 = write or erase complete
+//
+//
+
+static int dqpoll(volatile unsigned long address, volatile unsigned char data)
+{
+	volatile unsigned char dq7;
+
+	dq7 = data & 0x80;
+
+	return ((READ_FLASH(address) & 0x80) == dq7);
+}
+
+//
+// Function: timeout
+//
+// Description: Checks to see if erase or write has timed out
+//              By polling dq5
+//
+// In: address
+//
+//
+// Out: 0 = not timed out, 1 = timed out
+
+static int timeout(volatile unsigned long address)
+{
+	return (READ_FLASH(address) & 0x20) == 0x20;
+}
+
+module_init(lcd_init);
+module_exit(lcd_exit);
+
+MODULE_AUTHOR("Andrew Bose");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/lcd.h b/drivers/char/lcd.h
new file mode 100644
index 0000000..878a952
--- /dev/null
+++ b/drivers/char/lcd.h
@@ -0,0 +1,186 @@
+/*
+ * LED, LCD and Button panel driver for Cobalt
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 1996, 1997 by Andrew Bose
+ *
+ * Linux kernel version history:
+ *       March 2001: Ported from 2.0.34  by Liam Davies
+ *
+ */
+
+// function headers
+
+static int dqpoll(volatile unsigned long, volatile unsigned char );
+static int timeout(volatile unsigned long);
+
+#define LCD_CHARS_PER_LINE 40
+#define FLASH_SIZE 524288
+#define MAX_IDLE_TIME 120
+
+struct lcd_display {
+        unsigned long buttons;
+        int size1;
+        int size2;
+        unsigned char line1[LCD_CHARS_PER_LINE];
+        unsigned char line2[LCD_CHARS_PER_LINE];
+        unsigned char cursor_address;
+        unsigned char character;
+        unsigned char leds;
+        unsigned char *RomImage;
+};
+
+
+
+#define LCD_DRIVER	"Cobalt LCD Driver v2.10"
+
+#define LCD		"lcd: "
+
+#define kLCD_IR		0x0F000000
+#define kLCD_DR		0x0F000010
+#define kGPI		0x0D000000
+#define kLED		0x0C000000
+
+#define kDD_R00         0x00
+#define kDD_R01         0x27
+#define kDD_R10         0x40
+#define kDD_R11         0x67
+
+#define kLCD_Addr       0x00000080
+
+#define LCDTimeoutValue	0xfff
+
+
+// Flash definitions AMD 29F040
+#define kFlashBase	0x0FC00000
+
+#define kFlash_Addr1    0x5555
+#define kFlash_Addr2    0x2AAA
+#define kFlash_Data1    0xAA
+#define kFlash_Data2    0x55
+#define kFlash_Prog     0xA0
+#define kFlash_Erase3   0x80
+#define kFlash_Erase6   0x10
+#define kFlash_Read     0xF0
+
+#define kFlash_ID       0x90
+#define kFlash_VenAddr  0x00
+#define kFlash_DevAddr  0x01
+#define kFlash_VenID    0x01
+#define kFlash_DevID    0xA4    // 29F040
+//#define kFlash_DevID  0xAD    // 29F016
+
+
+// Macros
+
+#define LCDWriteData(x)	outl((x << 24), kLCD_DR)
+#define LCDWriteInst(x)	outl((x << 24), kLCD_IR)
+
+#define LCDReadData	(inl(kLCD_DR) >> 24)
+#define LCDReadInst	(inl(kLCD_IR) >> 24)
+
+#define GPIRead		(inl(kGPI) >> 24)
+
+#define LEDSet(x)	outb((char)x, kLED)
+
+#define WRITE_GAL(x,y)	outl(y, 0x04000000 | (x))
+#define BusyCheck()	while ((LCDReadInst & 0x80) == 0x80)
+
+#define WRITE_FLASH(x,y) outb((char)y, kFlashBase | (x))
+#define READ_FLASH(x)	(inb(kFlashBase | (x)))
+
+
+
+/*
+ * Function command codes for io_ctl.
+ */
+#define LCD_On			1
+#define LCD_Off			2
+#define LCD_Clear		3
+#define LCD_Reset		4
+#define LCD_Cursor_Left		5
+#define LCD_Cursor_Right	6
+#define LCD_Disp_Left		7
+#define LCD_Disp_Right		8
+#define LCD_Get_Cursor		9
+#define LCD_Set_Cursor		10
+#define LCD_Home		11
+#define LCD_Read		12
+#define LCD_Write		13
+#define LCD_Cursor_Off		14
+#define LCD_Cursor_On		15
+#define LCD_Get_Cursor_Pos	16
+#define LCD_Set_Cursor_Pos	17
+#define LCD_Blink_Off           18
+
+#define LED_Set			40
+#define LED_Bit_Set		41
+#define LED_Bit_Clear		42
+
+
+//  Button defs
+#define BUTTON_Read             50
+
+//  Flash command codes
+#define FLASH_Erase		60
+#define FLASH_Burn		61
+#define FLASH_Read		62
+
+
+// Ethernet LINK check hackaroo
+#define LINK_Check              90
+#define LINK_Check_2		91
+
+//  Button patterns  _B - single layer lcd boards
+
+#define BUTTON_NONE               0x3F
+#define BUTTON_NONE_B             0xFE
+
+#define BUTTON_Left               0x3B
+#define BUTTON_Left_B             0xFA
+
+#define BUTTON_Right              0x37
+#define BUTTON_Right_B            0xDE
+
+#define BUTTON_Up                 0x2F
+#define BUTTON_Up_B               0xF6
+
+#define BUTTON_Down               0x1F
+#define BUTTON_Down_B             0xEE
+
+#define BUTTON_Next               0x3D
+#define BUTTON_Next_B             0x7E
+
+#define BUTTON_Enter              0x3E
+#define BUTTON_Enter_B            0xBE
+
+#define BUTTON_Reset_B            0xFC
+
+
+// debounce constants
+
+#define BUTTON_SENSE            160000
+#define BUTTON_DEBOUNCE		5000
+
+
+//  Galileo register stuff
+
+#define kGal_DevBank2Cfg        0x1466DB33
+#define kGal_DevBank2PReg       0x464
+#define kGal_DevBank3Cfg        0x146FDFFB
+#define kGal_DevBank3PReg       0x468
+
+// Network
+
+#define kIPADDR			1
+#define kNETMASK		2
+#define kGATEWAY		3
+#define kDNS			4
+
+#define kClassA			5
+#define kClassB			6
+#define kClassC			7
+
diff --git a/drivers/char/lp.c b/drivers/char/lp.c
new file mode 100644
index 0000000..4dee945
--- /dev/null
+++ b/drivers/char/lp.c
@@ -0,0 +1,995 @@
+/*
+ * Generic parallel printer driver
+ *
+ * Copyright (C) 1992 by Jim Weigand and Linus Torvalds
+ * Copyright (C) 1992,1993 by Michael K. Johnson
+ * - Thanks much to Gunter Windau for pointing out to me where the error
+ *   checking ought to be.
+ * Copyright (C) 1993 by Nigel Gamble (added interrupt code)
+ * Copyright (C) 1994 by Alan Cox (Modularised it)
+ * LPCAREFUL, LPABORT, LPGETSTATUS added by Chris Metcalf, metcalf@lcs.mit.edu
+ * Statistics and support for slow printers by Rob Janssen, rob@knoware.nl
+ * "lp=" command line parameters added by Grant Guenther, grant@torque.net
+ * lp_read (Status readback) support added by Carsten Gross,
+ *                                             carsten@sol.wohnheim.uni-ulm.de
+ * Support for parport by Philip Blundell <philb@gnu.org>
+ * Parport sharing hacking by Andrea Arcangeli
+ * Fixed kernel_(to/from)_user memory copy to check for errors
+ * 				by Riccardo Facchetti <fizban@tin.it>
+ * 22-JAN-1998  Added support for devfs  Richard Gooch <rgooch@atnf.csiro.au>
+ * Redesigned interrupt handling for handle printers with buggy handshake
+ *				by Andrea Arcangeli, 11 May 1998
+ * Full efficient handling of printer with buggy irq handshake (now I have
+ * understood the meaning of the strange handshake). This is done sending new
+ * characters if the interrupt is just happened, even if the printer say to
+ * be still BUSY. This is needed at least with Epson Stylus Color. To enable
+ * the new TRUST_IRQ mode read the `LP OPTIMIZATION' section below...
+ * Fixed the irq on the rising edge of the strobe case.
+ * Obsoleted the CAREFUL flag since a printer that doesn' t work with
+ * CAREFUL will block a bit after in lp_check_status().
+ *				Andrea Arcangeli, 15 Oct 1998
+ * Obsoleted and removed all the lowlevel stuff implemented in the last
+ * month to use the IEEE1284 functions (that handle the _new_ compatibilty
+ * mode fine).
+ */
+
+/* This driver should, in theory, work with any parallel port that has an
+ * appropriate low-level driver; all I/O is done through the parport
+ * abstraction layer.
+ *
+ * If this driver is built into the kernel, you can configure it using the
+ * kernel command-line.  For example:
+ *
+ *	lp=parport1,none,parport2	(bind lp0 to parport1, disable lp1 and
+ *					 bind lp2 to parport2)
+ *
+ *	lp=auto				(assign lp devices to all ports that
+ *				         have printers attached, as determined
+ *					 by the IEEE-1284 autoprobe)
+ * 
+ *	lp=reset			(reset the printer during 
+ *					 initialisation)
+ *
+ *	lp=off				(disable the printer driver entirely)
+ *
+ * If the driver is loaded as a module, similar functionality is available
+ * using module parameters.  The equivalent of the above commands would be:
+ *
+ *	# insmod lp.o parport=1,none,2
+ *
+ *	# insmod lp.o parport=auto
+ *
+ *	# insmod lp.o reset=1
+ */
+
+/* COMPATIBILITY WITH OLD KERNELS
+ *
+ * Under Linux 2.0 and previous versions, lp devices were bound to ports at
+ * particular I/O addresses, as follows:
+ *
+ *	lp0		0x3bc
+ *	lp1		0x378
+ *	lp2		0x278
+ *
+ * The new driver, by default, binds lp devices to parport devices as it
+ * finds them.  This means that if you only have one port, it will be bound
+ * to lp0 regardless of its I/O address.  If you need the old behaviour, you
+ * can force it using the parameters described above.
+ */
+
+/*
+ * The new interrupt handling code take care of the buggy handshake
+ * of some HP and Epson printer:
+ * ___
+ * ACK    _______________    ___________
+ *                       |__|
+ * ____
+ * BUSY   _________              _______
+ *                 |____________|
+ *
+ * I discovered this using the printer scanner that you can find at:
+ *
+ *	ftp://e-mind.com/pub/linux/pscan/
+ *
+ *					11 May 98, Andrea Arcangeli
+ *
+ * My printer scanner run on an Epson Stylus Color show that such printer
+ * generates the irq on the _rising_ edge of the STROBE. Now lp handle
+ * this case fine too.
+ *
+ *					15 Oct 1998, Andrea Arcangeli
+ *
+ * The so called `buggy' handshake is really the well documented
+ * compatibility mode IEEE1284 handshake. They changed the well known
+ * Centronics handshake acking in the middle of busy expecting to not
+ * break drivers or legacy application, while they broken linux lp
+ * until I fixed it reverse engineering the protocol by hand some
+ * month ago...
+ *
+ *                                     14 Dec 1998, Andrea Arcangeli
+ *
+ * Copyright (C) 2000 by Tim Waugh (added LPSETTIMEOUT ioctl)
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/console.h>
+#include <linux/device.h>
+#include <linux/wait.h>
+
+#include <linux/parport.h>
+#undef LP_STATS
+#include <linux/lp.h>
+
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+/* if you have more than 8 printers, remember to increase LP_NO */
+#define LP_NO 8
+
+/* ROUND_UP macro from fs/select.c */
+#define ROUND_UP(x,y) (((x)+(y)-1)/(y))
+
+static struct lp_struct lp_table[LP_NO];
+
+static unsigned int lp_count = 0;
+static struct class_simple *lp_class;
+
+#ifdef CONFIG_LP_CONSOLE
+static struct parport *console_registered; // initially NULL
+#endif /* CONFIG_LP_CONSOLE */
+
+#undef LP_DEBUG
+
+/* Bits used to manage claiming the parport device */
+#define LP_PREEMPT_REQUEST 1
+#define LP_PARPORT_CLAIMED 2
+
+/* --- low-level port access ----------------------------------- */
+
+#define r_dtr(x)	(parport_read_data(lp_table[(x)].dev->port))
+#define r_str(x)	(parport_read_status(lp_table[(x)].dev->port))
+#define w_ctr(x,y)	do { parport_write_control(lp_table[(x)].dev->port, (y)); } while (0)
+#define w_dtr(x,y)	do { parport_write_data(lp_table[(x)].dev->port, (y)); } while (0)
+
+/* Claim the parport or block trying unless we've already claimed it */
+static void lp_claim_parport_or_block(struct lp_struct *this_lp)
+{
+	if (!test_and_set_bit(LP_PARPORT_CLAIMED, &this_lp->bits)) {
+		parport_claim_or_block (this_lp->dev);
+	}
+}
+
+/* Claim the parport or block trying unless we've already claimed it */
+static void lp_release_parport(struct lp_struct *this_lp)
+{
+	if (test_and_clear_bit(LP_PARPORT_CLAIMED, &this_lp->bits)) {
+		parport_release (this_lp->dev);
+	}
+}
+
+
+
+static int lp_preempt(void *handle)
+{
+	struct lp_struct *this_lp = (struct lp_struct *)handle;
+	set_bit(LP_PREEMPT_REQUEST, &this_lp->bits);
+	return (1);
+}
+
+
+/* 
+ * Try to negotiate to a new mode; if unsuccessful negotiate to
+ * compatibility mode.  Return the mode we ended up in.
+ */
+static int lp_negotiate(struct parport * port, int mode)
+{
+	if (parport_negotiate (port, mode) != 0) {
+		mode = IEEE1284_MODE_COMPAT;
+		parport_negotiate (port, mode);
+	}
+
+	return (mode);
+}
+
+static int lp_reset(int minor)
+{
+	int retval;
+	lp_claim_parport_or_block (&lp_table[minor]);
+	w_ctr(minor, LP_PSELECP);
+	udelay (LP_DELAY);
+	w_ctr(minor, LP_PSELECP | LP_PINITP);
+	retval = r_str(minor);
+	lp_release_parport (&lp_table[minor]);
+	return retval;
+}
+
+static void lp_error (int minor)
+{
+	DEFINE_WAIT(wait);
+	int polling;
+
+	if (LP_F(minor) & LP_ABORT)
+		return;
+
+	polling = lp_table[minor].dev->port->irq == PARPORT_IRQ_NONE;
+	if (polling) lp_release_parport (&lp_table[minor]);
+	prepare_to_wait(&lp_table[minor].waitq, &wait, TASK_INTERRUPTIBLE);
+	schedule_timeout(LP_TIMEOUT_POLLED);
+	finish_wait(&lp_table[minor].waitq, &wait);
+	if (polling) lp_claim_parport_or_block (&lp_table[minor]);
+	else parport_yield_blocking (lp_table[minor].dev);
+}
+
+static int lp_check_status(int minor)
+{
+	int error = 0;
+	unsigned int last = lp_table[minor].last_error;
+	unsigned char status = r_str(minor);
+	if ((status & LP_PERRORP) && !(LP_F(minor) & LP_CAREFUL))
+		/* No error. */
+		last = 0;
+	else if ((status & LP_POUTPA)) {
+		if (last != LP_POUTPA) {
+			last = LP_POUTPA;
+			printk(KERN_INFO "lp%d out of paper\n", minor);
+		}
+		error = -ENOSPC;
+	} else if (!(status & LP_PSELECD)) {
+		if (last != LP_PSELECD) {
+			last = LP_PSELECD;
+			printk(KERN_INFO "lp%d off-line\n", minor);
+		}
+		error = -EIO;
+	} else if (!(status & LP_PERRORP)) {
+		if (last != LP_PERRORP) {
+			last = LP_PERRORP;
+			printk(KERN_INFO "lp%d on fire\n", minor);
+		}
+		error = -EIO;
+	} else {
+		last = 0; /* Come here if LP_CAREFUL is set and no
+                             errors are reported. */
+	}
+
+	lp_table[minor].last_error = last;
+
+	if (last != 0)
+		lp_error(minor);
+
+	return error;
+}
+
+static int lp_wait_ready(int minor, int nonblock)
+{
+	int error = 0;
+
+	/* If we're not in compatibility mode, we're ready now! */
+	if (lp_table[minor].current_mode != IEEE1284_MODE_COMPAT) {
+	  return (0);
+	}
+
+	do {
+		error = lp_check_status (minor);
+		if (error && (nonblock || (LP_F(minor) & LP_ABORT)))
+			break;
+		if (signal_pending (current)) {
+			error = -EINTR;
+			break;
+		}
+	} while (error);
+	return error;
+}
+
+static ssize_t lp_write(struct file * file, const char __user * buf,
+		        size_t count, loff_t *ppos)
+{
+	unsigned int minor = iminor(file->f_dentry->d_inode);
+	struct parport *port = lp_table[minor].dev->port;
+	char *kbuf = lp_table[minor].lp_buffer;
+	ssize_t retv = 0;
+	ssize_t written;
+	size_t copy_size = count;
+	int nonblock = ((file->f_flags & O_NONBLOCK) ||
+			(LP_F(minor) & LP_ABORT));
+
+#ifdef LP_STATS
+	if (jiffies-lp_table[minor].lastcall > LP_TIME(minor))
+		lp_table[minor].runchars = 0;
+
+	lp_table[minor].lastcall = jiffies;
+#endif
+
+	/* Need to copy the data from user-space. */
+	if (copy_size > LP_BUFFER_SIZE)
+		copy_size = LP_BUFFER_SIZE;
+
+	if (down_interruptible (&lp_table[minor].port_mutex))
+		return -EINTR;
+
+	if (copy_from_user (kbuf, buf, copy_size)) {
+		retv = -EFAULT;
+		goto out_unlock;
+	}
+
+ 	/* Claim Parport or sleep until it becomes available
+ 	 */
+	lp_claim_parport_or_block (&lp_table[minor]);
+	/* Go to the proper mode. */
+	lp_table[minor].current_mode = lp_negotiate (port, 
+						     lp_table[minor].best_mode);
+
+	parport_set_timeout (lp_table[minor].dev,
+			     (nonblock ? PARPORT_INACTIVITY_O_NONBLOCK
+			      : lp_table[minor].timeout));
+
+	if ((retv = lp_wait_ready (minor, nonblock)) == 0)
+	do {
+		/* Write the data. */
+		written = parport_write (port, kbuf, copy_size);
+		if (written > 0) {
+			copy_size -= written;
+			count -= written;
+			buf  += written;
+			retv += written;
+		}
+
+		if (signal_pending (current)) {
+			if (retv == 0)
+				retv = -EINTR;
+
+			break;
+		}
+
+		if (copy_size > 0) {
+			/* incomplete write -> check error ! */
+			int error;
+
+			parport_negotiate (lp_table[minor].dev->port, 
+					   IEEE1284_MODE_COMPAT);
+			lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+
+			error = lp_wait_ready (minor, nonblock);
+
+			if (error) {
+				if (retv == 0)
+					retv = error;
+				break;
+			} else if (nonblock) {
+				if (retv == 0)
+					retv = -EAGAIN;
+				break;
+			}
+
+			parport_yield_blocking (lp_table[minor].dev);
+			lp_table[minor].current_mode 
+			  = lp_negotiate (port, 
+					  lp_table[minor].best_mode);
+
+		} else if (need_resched())
+			schedule ();
+
+		if (count) {
+			copy_size = count;
+			if (copy_size > LP_BUFFER_SIZE)
+				copy_size = LP_BUFFER_SIZE;
+
+			if (copy_from_user(kbuf, buf, copy_size)) {
+				if (retv == 0)
+					retv = -EFAULT;
+				break;
+			}
+		}	
+	} while (count > 0);
+
+	if (test_and_clear_bit(LP_PREEMPT_REQUEST, 
+			       &lp_table[minor].bits)) {
+		printk(KERN_INFO "lp%d releasing parport\n", minor);
+		parport_negotiate (lp_table[minor].dev->port, 
+				   IEEE1284_MODE_COMPAT);
+		lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+		lp_release_parport (&lp_table[minor]);
+	}
+out_unlock:
+	up (&lp_table[minor].port_mutex);
+
+ 	return retv;
+}
+
+#ifdef CONFIG_PARPORT_1284
+
+/* Status readback conforming to ieee1284 */
+static ssize_t lp_read(struct file * file, char __user * buf,
+		       size_t count, loff_t *ppos)
+{
+	DEFINE_WAIT(wait);
+	unsigned int minor=iminor(file->f_dentry->d_inode);
+	struct parport *port = lp_table[minor].dev->port;
+	ssize_t retval = 0;
+	char *kbuf = lp_table[minor].lp_buffer;
+	int nonblock = ((file->f_flags & O_NONBLOCK) ||
+			(LP_F(minor) & LP_ABORT));
+
+	if (count > LP_BUFFER_SIZE)
+		count = LP_BUFFER_SIZE;
+
+	if (down_interruptible (&lp_table[minor].port_mutex))
+		return -EINTR;
+
+	lp_claim_parport_or_block (&lp_table[minor]);
+
+	parport_set_timeout (lp_table[minor].dev,
+			     (nonblock ? PARPORT_INACTIVITY_O_NONBLOCK
+			      : lp_table[minor].timeout));
+
+	parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
+	if (parport_negotiate (lp_table[minor].dev->port,
+			       IEEE1284_MODE_NIBBLE)) {
+		retval = -EIO;
+		goto out;
+	}
+
+	while (retval == 0) {
+		retval = parport_read (port, kbuf, count);
+
+		if (retval > 0)
+			break;
+
+		if (nonblock) {
+			retval = -EAGAIN;
+			break;
+		}
+
+		/* Wait for data. */
+
+		if (lp_table[minor].dev->port->irq == PARPORT_IRQ_NONE) {
+			parport_negotiate (lp_table[minor].dev->port,
+					   IEEE1284_MODE_COMPAT);
+			lp_error (minor);
+			if (parport_negotiate (lp_table[minor].dev->port,
+					       IEEE1284_MODE_NIBBLE)) {
+				retval = -EIO;
+				goto out;
+			}
+		} else {
+			prepare_to_wait(&lp_table[minor].waitq, &wait, TASK_INTERRUPTIBLE);
+			schedule_timeout(LP_TIMEOUT_POLLED);
+			finish_wait(&lp_table[minor].waitq, &wait);
+		}
+
+		if (signal_pending (current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+
+		cond_resched ();
+	}
+	parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
+ out:
+	lp_release_parport (&lp_table[minor]);
+
+	if (retval > 0 && copy_to_user (buf, kbuf, retval))
+		retval = -EFAULT;
+
+	up (&lp_table[minor].port_mutex);
+
+	return retval;
+}
+
+#endif /* IEEE 1284 support */
+
+static int lp_open(struct inode * inode, struct file * file)
+{
+	unsigned int minor = iminor(inode);
+
+	if (minor >= LP_NO)
+		return -ENXIO;
+	if ((LP_F(minor) & LP_EXIST) == 0)
+		return -ENXIO;
+	if (test_and_set_bit(LP_BUSY_BIT_POS, &LP_F(minor)))
+		return -EBUSY;
+
+	/* If ABORTOPEN is set and the printer is offline or out of paper,
+	   we may still want to open it to perform ioctl()s.  Therefore we
+	   have commandeered O_NONBLOCK, even though it is being used in
+	   a non-standard manner.  This is strictly a Linux hack, and
+	   should most likely only ever be used by the tunelp application. */
+	if ((LP_F(minor) & LP_ABORTOPEN) && !(file->f_flags & O_NONBLOCK)) {
+		int status;
+		lp_claim_parport_or_block (&lp_table[minor]);
+		status = r_str(minor);
+		lp_release_parport (&lp_table[minor]);
+		if (status & LP_POUTPA) {
+			printk(KERN_INFO "lp%d out of paper\n", minor);
+			LP_F(minor) &= ~LP_BUSY;
+			return -ENOSPC;
+		} else if (!(status & LP_PSELECD)) {
+			printk(KERN_INFO "lp%d off-line\n", minor);
+			LP_F(minor) &= ~LP_BUSY;
+			return -EIO;
+		} else if (!(status & LP_PERRORP)) {
+			printk(KERN_ERR "lp%d printer error\n", minor);
+			LP_F(minor) &= ~LP_BUSY;
+			return -EIO;
+		}
+	}
+	lp_table[minor].lp_buffer = (char *) kmalloc(LP_BUFFER_SIZE, GFP_KERNEL);
+	if (!lp_table[minor].lp_buffer) {
+		LP_F(minor) &= ~LP_BUSY;
+		return -ENOMEM;
+	}
+	/* Determine if the peripheral supports ECP mode */
+	lp_claim_parport_or_block (&lp_table[minor]);
+	if ( (lp_table[minor].dev->port->modes & PARPORT_MODE_ECP) &&
+             !parport_negotiate (lp_table[minor].dev->port, 
+                                 IEEE1284_MODE_ECP)) {
+		printk (KERN_INFO "lp%d: ECP mode\n", minor);
+		lp_table[minor].best_mode = IEEE1284_MODE_ECP;
+	} else {
+		lp_table[minor].best_mode = IEEE1284_MODE_COMPAT;
+	}
+	/* Leave peripheral in compatibility mode */
+	parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
+	lp_release_parport (&lp_table[minor]);
+	lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+	return 0;
+}
+
+static int lp_release(struct inode * inode, struct file * file)
+{
+	unsigned int minor = iminor(inode);
+
+	lp_claim_parport_or_block (&lp_table[minor]);
+	parport_negotiate (lp_table[minor].dev->port, IEEE1284_MODE_COMPAT);
+	lp_table[minor].current_mode = IEEE1284_MODE_COMPAT;
+	lp_release_parport (&lp_table[minor]);
+	kfree(lp_table[minor].lp_buffer);
+	lp_table[minor].lp_buffer = NULL;
+	LP_F(minor) &= ~LP_BUSY;
+	return 0;
+}
+
+static int lp_ioctl(struct inode *inode, struct file *file,
+		    unsigned int cmd, unsigned long arg)
+{
+	unsigned int minor = iminor(inode);
+	int status;
+	int retval = 0;
+	void __user *argp = (void __user *)arg;
+
+#ifdef LP_DEBUG
+	printk(KERN_DEBUG "lp%d ioctl, cmd: 0x%x, arg: 0x%lx\n", minor, cmd, arg);
+#endif
+	if (minor >= LP_NO)
+		return -ENODEV;
+	if ((LP_F(minor) & LP_EXIST) == 0)
+		return -ENODEV;
+	switch ( cmd ) {
+		struct timeval par_timeout;
+		long to_jiffies;
+
+		case LPTIME:
+			LP_TIME(minor) = arg * HZ/100;
+			break;
+		case LPCHAR:
+			LP_CHAR(minor) = arg;
+			break;
+		case LPABORT:
+			if (arg)
+				LP_F(minor) |= LP_ABORT;
+			else
+				LP_F(minor) &= ~LP_ABORT;
+			break;
+		case LPABORTOPEN:
+			if (arg)
+				LP_F(minor) |= LP_ABORTOPEN;
+			else
+				LP_F(minor) &= ~LP_ABORTOPEN;
+			break;
+		case LPCAREFUL:
+			if (arg)
+				LP_F(minor) |= LP_CAREFUL;
+			else
+				LP_F(minor) &= ~LP_CAREFUL;
+			break;
+		case LPWAIT:
+			LP_WAIT(minor) = arg;
+			break;
+		case LPSETIRQ: 
+			return -EINVAL;
+			break;
+		case LPGETIRQ:
+			if (copy_to_user(argp, &LP_IRQ(minor),
+					sizeof(int)))
+				return -EFAULT;
+			break;
+		case LPGETSTATUS:
+			lp_claim_parport_or_block (&lp_table[minor]);
+			status = r_str(minor);
+			lp_release_parport (&lp_table[minor]);
+
+			if (copy_to_user(argp, &status, sizeof(int)))
+				return -EFAULT;
+			break;
+		case LPRESET:
+			lp_reset(minor);
+			break;
+#ifdef LP_STATS
+		case LPGETSTATS:
+			if (copy_to_user(argp, &LP_STAT(minor),
+					sizeof(struct lp_stats)))
+				return -EFAULT;
+			if (capable(CAP_SYS_ADMIN))
+				memset(&LP_STAT(minor), 0,
+						sizeof(struct lp_stats));
+			break;
+#endif
+ 		case LPGETFLAGS:
+ 			status = LP_F(minor);
+			if (copy_to_user(argp, &status, sizeof(int)))
+				return -EFAULT;
+			break;
+
+		case LPSETTIMEOUT:
+			if (copy_from_user (&par_timeout, argp,
+					    sizeof (struct timeval))) {
+				return -EFAULT;
+			}
+			/* Convert to jiffies, place in lp_table */
+			if ((par_timeout.tv_sec < 0) ||
+			    (par_timeout.tv_usec < 0)) {
+				return -EINVAL;
+			}
+			to_jiffies = ROUND_UP(par_timeout.tv_usec, 1000000/HZ);
+			to_jiffies += par_timeout.tv_sec * (long) HZ;
+			if (to_jiffies <= 0) {
+				return -EINVAL;
+			}
+			lp_table[minor].timeout = to_jiffies;
+			break;
+
+		default:
+			retval = -EINVAL;
+	}
+	return retval;
+}
+
+static struct file_operations lp_fops = {
+	.owner		= THIS_MODULE,
+	.write		= lp_write,
+	.ioctl		= lp_ioctl,
+	.open		= lp_open,
+	.release	= lp_release,
+#ifdef CONFIG_PARPORT_1284
+	.read		= lp_read,
+#endif
+};
+
+/* --- support for console on the line printer ----------------- */
+
+#ifdef CONFIG_LP_CONSOLE
+
+#define CONSOLE_LP 0
+
+/* If the printer is out of paper, we can either lose the messages or
+ * stall until the printer is happy again.  Define CONSOLE_LP_STRICT
+ * non-zero to get the latter behaviour. */
+#define CONSOLE_LP_STRICT 1
+
+/* The console must be locked when we get here. */
+
+static void lp_console_write (struct console *co, const char *s,
+			      unsigned count)
+{
+	struct pardevice *dev = lp_table[CONSOLE_LP].dev;
+	struct parport *port = dev->port;
+	ssize_t written;
+
+	if (parport_claim (dev))
+		/* Nothing we can do. */
+		return;
+
+	parport_set_timeout (dev, 0);
+
+	/* Go to compatibility mode. */
+	parport_negotiate (port, IEEE1284_MODE_COMPAT);
+
+	do {
+		/* Write the data, converting LF->CRLF as we go. */
+		ssize_t canwrite = count;
+		char *lf = memchr (s, '\n', count);
+		if (lf)
+			canwrite = lf - s;
+
+		if (canwrite > 0) {
+			written = parport_write (port, s, canwrite);
+
+			if (written <= 0)
+				continue;
+
+			s += written;
+			count -= written;
+			canwrite -= written;
+		}
+
+		if (lf && canwrite <= 0) {
+			const char *crlf = "\r\n";
+			int i = 2;
+
+			/* Dodge the original '\n', and put '\r\n' instead. */
+			s++;
+			count--;
+			do {
+				written = parport_write (port, crlf, i);
+				if (written > 0)
+					i -= written, crlf += written;
+			} while (i > 0 && (CONSOLE_LP_STRICT || written > 0));
+		}
+	} while (count > 0 && (CONSOLE_LP_STRICT || written > 0));
+
+	parport_release (dev);
+}
+
+static struct console lpcons = {
+	.name		= "lp",
+	.write		= lp_console_write,
+	.flags		= CON_PRINTBUFFER,
+};
+
+#endif /* console on line printer */
+
+/* --- initialisation code ------------------------------------- */
+
+static int parport_nr[LP_NO] = { [0 ... LP_NO-1] = LP_PARPORT_UNSPEC };
+static char *parport[LP_NO] = { NULL,  };
+static int reset = 0;
+
+module_param_array(parport, charp, NULL, 0);
+module_param(reset, bool, 0);
+
+#ifndef MODULE
+static int __init lp_setup (char *str)
+{
+	static int parport_ptr; // initially zero
+	int x;
+
+	if (get_option (&str, &x)) {
+		if (x == 0) {
+			/* disable driver on "lp=" or "lp=0" */
+			parport_nr[0] = LP_PARPORT_OFF;
+		} else {
+			printk(KERN_WARNING "warning: 'lp=0x%x' is deprecated, ignored\n", x);
+			return 0;
+		}
+	} else if (!strncmp(str, "parport", 7)) {
+		int n = simple_strtoul(str+7, NULL, 10);
+		if (parport_ptr < LP_NO)
+			parport_nr[parport_ptr++] = n;
+		else
+			printk(KERN_INFO "lp: too many ports, %s ignored.\n",
+			       str);
+	} else if (!strcmp(str, "auto")) {
+		parport_nr[0] = LP_PARPORT_AUTO;
+	} else if (!strcmp(str, "none")) {
+		parport_nr[parport_ptr++] = LP_PARPORT_NONE;
+	} else if (!strcmp(str, "reset")) {
+		reset = 1;
+	}
+	return 1;
+}
+#endif
+
+static int lp_register(int nr, struct parport *port)
+{
+	lp_table[nr].dev = parport_register_device(port, "lp", 
+						   lp_preempt, NULL, NULL, 0,
+						   (void *) &lp_table[nr]);
+	if (lp_table[nr].dev == NULL)
+		return 1;
+	lp_table[nr].flags |= LP_EXIST;
+
+	if (reset)
+		lp_reset(nr);
+
+	class_simple_device_add(lp_class, MKDEV(LP_MAJOR, nr), NULL,
+				"lp%d", nr);
+	devfs_mk_cdev(MKDEV(LP_MAJOR, nr), S_IFCHR | S_IRUGO | S_IWUGO,
+			"printers/%d", nr);
+
+	printk(KERN_INFO "lp%d: using %s (%s).\n", nr, port->name, 
+	       (port->irq == PARPORT_IRQ_NONE)?"polling":"interrupt-driven");
+
+#ifdef CONFIG_LP_CONSOLE
+	if (!nr) {
+		if (port->modes & PARPORT_MODE_SAFEININT) {
+			register_console (&lpcons);
+			console_registered = port;
+			printk (KERN_INFO "lp%d: console ready\n", CONSOLE_LP);
+		} else
+			printk (KERN_ERR "lp%d: cannot run console on %s\n",
+				CONSOLE_LP, port->name);
+	}
+#endif
+
+	return 0;
+}
+
+static void lp_attach (struct parport *port)
+{
+	unsigned int i;
+
+	switch (parport_nr[0])
+	{
+	case LP_PARPORT_UNSPEC:
+	case LP_PARPORT_AUTO:
+		if (parport_nr[0] == LP_PARPORT_AUTO &&
+		    port->probe_info[0].class != PARPORT_CLASS_PRINTER)
+			return;
+		if (lp_count == LP_NO) {
+			printk(KERN_INFO "lp: ignoring parallel port (max. %d)\n",LP_NO);
+			return;
+		}
+		if (!lp_register(lp_count, port))
+			lp_count++;
+		break;
+
+	default:
+		for (i = 0; i < LP_NO; i++) {
+			if (port->number == parport_nr[i]) {
+				if (!lp_register(i, port))
+					lp_count++;
+				break;
+			}
+		}
+		break;
+	}
+}
+
+static void lp_detach (struct parport *port)
+{
+	/* Write this some day. */
+#ifdef CONFIG_LP_CONSOLE
+	if (console_registered == port) {
+		unregister_console (&lpcons);
+		console_registered = NULL;
+	}
+#endif /* CONFIG_LP_CONSOLE */
+}
+
+static struct parport_driver lp_driver = {
+	.name = "lp",
+	.attach = lp_attach,
+	.detach = lp_detach,
+};
+
+static int __init lp_init (void)
+{
+	int i, err = 0;
+
+	if (parport_nr[0] == LP_PARPORT_OFF)
+		return 0;
+
+	for (i = 0; i < LP_NO; i++) {
+		lp_table[i].dev = NULL;
+		lp_table[i].flags = 0;
+		lp_table[i].chars = LP_INIT_CHAR;
+		lp_table[i].time = LP_INIT_TIME;
+		lp_table[i].wait = LP_INIT_WAIT;
+		lp_table[i].lp_buffer = NULL;
+#ifdef LP_STATS
+		lp_table[i].lastcall = 0;
+		lp_table[i].runchars = 0;
+		memset (&lp_table[i].stats, 0, sizeof (struct lp_stats));
+#endif
+		lp_table[i].last_error = 0;
+		init_waitqueue_head (&lp_table[i].waitq);
+		init_waitqueue_head (&lp_table[i].dataq);
+		init_MUTEX (&lp_table[i].port_mutex);
+		lp_table[i].timeout = 10 * HZ;
+	}
+
+	if (register_chrdev (LP_MAJOR, "lp", &lp_fops)) {
+		printk (KERN_ERR "lp: unable to get major %d\n", LP_MAJOR);
+		return -EIO;
+	}
+
+	devfs_mk_dir("printers");
+	lp_class = class_simple_create(THIS_MODULE, "printer");
+	if (IS_ERR(lp_class)) {
+		err = PTR_ERR(lp_class);
+		goto out_devfs;
+	}
+
+	if (parport_register_driver (&lp_driver)) {
+		printk (KERN_ERR "lp: unable to register with parport\n");
+		err = -EIO;
+		goto out_class;
+	}
+
+	if (!lp_count) {
+		printk (KERN_INFO "lp: driver loaded but no devices found\n");
+#ifndef CONFIG_PARPORT_1284
+		if (parport_nr[0] == LP_PARPORT_AUTO)
+			printk (KERN_INFO "lp: (is IEEE 1284 support enabled?)\n");
+#endif
+	}
+
+	return 0;
+
+out_class:
+	class_simple_destroy(lp_class);
+out_devfs:
+	devfs_remove("printers");
+	unregister_chrdev(LP_MAJOR, "lp");
+	return err;
+}
+
+static int __init lp_init_module (void)
+{
+	if (parport[0]) {
+		/* The user gave some parameters.  Let's see what they were.  */
+		if (!strncmp(parport[0], "auto", 4))
+			parport_nr[0] = LP_PARPORT_AUTO;
+		else {
+			int n;
+			for (n = 0; n < LP_NO && parport[n]; n++) {
+				if (!strncmp(parport[n], "none", 4))
+					parport_nr[n] = LP_PARPORT_NONE;
+				else {
+					char *ep;
+					unsigned long r = simple_strtoul(parport[n], &ep, 0);
+					if (ep != parport[n]) 
+						parport_nr[n] = r;
+					else {
+						printk(KERN_ERR "lp: bad port specifier `%s'\n", parport[n]);
+						return -ENODEV;
+					}
+				}
+			}
+		}
+	}
+
+	return lp_init();
+}
+
+static void lp_cleanup_module (void)
+{
+	unsigned int offset;
+
+	parport_unregister_driver (&lp_driver);
+
+#ifdef CONFIG_LP_CONSOLE
+	unregister_console (&lpcons);
+#endif
+
+	unregister_chrdev(LP_MAJOR, "lp");
+	for (offset = 0; offset < LP_NO; offset++) {
+		if (lp_table[offset].dev == NULL)
+			continue;
+		parport_unregister_device(lp_table[offset].dev);
+		devfs_remove("printers/%d", offset);
+		class_simple_device_remove(MKDEV(LP_MAJOR, offset));
+	}
+	devfs_remove("printers");
+	class_simple_destroy(lp_class);
+}
+
+__setup("lp=", lp_setup);
+module_init(lp_init_module);
+module_exit(lp_cleanup_module);
+
+MODULE_ALIAS_CHARDEV_MAJOR(LP_MAJOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/mem.c b/drivers/char/mem.c
new file mode 100644
index 0000000..947cb3c
--- /dev/null
+++ b/drivers/char/mem.c
@@ -0,0 +1,880 @@
+/*
+ *  linux/drivers/char/mem.c
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ *
+ *  Added devfs support. 
+ *    Jan-11-1998, C. Scott Ananian <cananian@alumni.princeton.edu>
+ *  Shared /dev/zero mmaping support, Feb 2000, Kanoj Sarcar <kanoj@sgi.com>
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/mman.h>
+#include <linux/random.h>
+#include <linux/init.h>
+#include <linux/raw.h>
+#include <linux/tty.h>
+#include <linux/capability.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/ptrace.h>
+#include <linux/device.h>
+#include <linux/backing-dev.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#ifdef CONFIG_IA64
+# include <linux/efi.h>
+#endif
+
+#if defined(CONFIG_S390_TAPE) && defined(CONFIG_S390_TAPE_CHAR)
+extern void tapechar_init(void);
+#endif
+
+/*
+ * Architectures vary in how they handle caching for addresses
+ * outside of main memory.
+ *
+ */
+static inline int uncached_access(struct file *file, unsigned long addr)
+{
+#if defined(__i386__)
+	/*
+	 * On the PPro and successors, the MTRRs are used to set
+	 * memory types for physical addresses outside main memory,
+	 * so blindly setting PCD or PWT on those pages is wrong.
+	 * For Pentiums and earlier, the surround logic should disable
+	 * caching for the high addresses through the KEN pin, but
+	 * we maintain the tradition of paranoia in this code.
+	 */
+	if (file->f_flags & O_SYNC)
+		return 1;
+ 	return !( test_bit(X86_FEATURE_MTRR, boot_cpu_data.x86_capability) ||
+		  test_bit(X86_FEATURE_K6_MTRR, boot_cpu_data.x86_capability) ||
+		  test_bit(X86_FEATURE_CYRIX_ARR, boot_cpu_data.x86_capability) ||
+		  test_bit(X86_FEATURE_CENTAUR_MCR, boot_cpu_data.x86_capability) )
+	  && addr >= __pa(high_memory);
+#elif defined(__x86_64__)
+	/* 
+	 * This is broken because it can generate memory type aliases,
+	 * which can cause cache corruptions
+	 * But it is only available for root and we have to be bug-to-bug
+	 * compatible with i386.
+	 */
+	if (file->f_flags & O_SYNC)
+		return 1;
+	/* same behaviour as i386. PAT always set to cached and MTRRs control the
+	   caching behaviour. 
+	   Hopefully a full PAT implementation will fix that soon. */	   
+	return 0;
+#elif defined(CONFIG_IA64)
+	/*
+	 * On ia64, we ignore O_SYNC because we cannot tolerate memory attribute aliases.
+	 */
+	return !(efi_mem_attributes(addr) & EFI_MEMORY_WB);
+#else
+	/*
+	 * Accessing memory above the top the kernel knows about or through a file pointer
+	 * that was marked O_SYNC will be done non-cached.
+	 */
+	if (file->f_flags & O_SYNC)
+		return 1;
+	return addr >= __pa(high_memory);
+#endif
+}
+
+#ifndef ARCH_HAS_VALID_PHYS_ADDR_RANGE
+static inline int valid_phys_addr_range(unsigned long addr, size_t *count)
+{
+	unsigned long end_mem;
+
+	end_mem = __pa(high_memory);
+	if (addr >= end_mem)
+		return 0;
+
+	if (*count > end_mem - addr)
+		*count = end_mem - addr;
+
+	return 1;
+}
+#endif
+
+/*
+ * This funcion reads the *physical* memory. The f_pos points directly to the 
+ * memory location. 
+ */
+static ssize_t read_mem(struct file * file, char __user * buf,
+			size_t count, loff_t *ppos)
+{
+	unsigned long p = *ppos;
+	ssize_t read, sz;
+	char *ptr;
+
+	if (!valid_phys_addr_range(p, &count))
+		return -EFAULT;
+	read = 0;
+#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
+	/* we don't have page 0 mapped on sparc and m68k.. */
+	if (p < PAGE_SIZE) {
+		sz = PAGE_SIZE - p;
+		if (sz > count) 
+			sz = count; 
+		if (sz > 0) {
+			if (clear_user(buf, sz))
+				return -EFAULT;
+			buf += sz; 
+			p += sz; 
+			count -= sz; 
+			read += sz; 
+		}
+	}
+#endif
+
+	while (count > 0) {
+		/*
+		 * Handle first page in case it's not aligned
+		 */
+		if (-p & (PAGE_SIZE - 1))
+			sz = -p & (PAGE_SIZE - 1);
+		else
+			sz = PAGE_SIZE;
+
+		sz = min_t(unsigned long, sz, count);
+
+		/*
+		 * On ia64 if a page has been mapped somewhere as
+		 * uncached, then it must also be accessed uncached
+		 * by the kernel or data corruption may occur
+		 */
+		ptr = xlate_dev_mem_ptr(p);
+
+		if (copy_to_user(buf, ptr, sz))
+			return -EFAULT;
+		buf += sz;
+		p += sz;
+		count -= sz;
+		read += sz;
+	}
+
+	*ppos += read;
+	return read;
+}
+
+static ssize_t write_mem(struct file * file, const char __user * buf, 
+			 size_t count, loff_t *ppos)
+{
+	unsigned long p = *ppos;
+	ssize_t written, sz;
+	unsigned long copied;
+	void *ptr;
+
+	if (!valid_phys_addr_range(p, &count))
+		return -EFAULT;
+
+	written = 0;
+
+#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
+	/* we don't have page 0 mapped on sparc and m68k.. */
+	if (p < PAGE_SIZE) {
+		unsigned long sz = PAGE_SIZE - p;
+		if (sz > count)
+			sz = count;
+		/* Hmm. Do something? */
+		buf += sz;
+		p += sz;
+		count -= sz;
+		written += sz;
+	}
+#endif
+
+	while (count > 0) {
+		/*
+		 * Handle first page in case it's not aligned
+		 */
+		if (-p & (PAGE_SIZE - 1))
+			sz = -p & (PAGE_SIZE - 1);
+		else
+			sz = PAGE_SIZE;
+
+		sz = min_t(unsigned long, sz, count);
+
+		/*
+		 * On ia64 if a page has been mapped somewhere as
+		 * uncached, then it must also be accessed uncached
+		 * by the kernel or data corruption may occur
+		 */
+		ptr = xlate_dev_mem_ptr(p);
+
+		copied = copy_from_user(ptr, buf, sz);
+		if (copied) {
+			ssize_t ret;
+
+			ret = written + (sz - copied);
+			if (ret)
+				return ret;
+			return -EFAULT;
+		}
+		buf += sz;
+		p += sz;
+		count -= sz;
+		written += sz;
+	}
+
+	*ppos += written;
+	return written;
+}
+
+static int mmap_mem(struct file * file, struct vm_area_struct * vma)
+{
+#if defined(__HAVE_PHYS_MEM_ACCESS_PROT)
+	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+	vma->vm_page_prot = phys_mem_access_prot(file, offset,
+						 vma->vm_end - vma->vm_start,
+						 vma->vm_page_prot);
+#elif defined(pgprot_noncached)
+	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+	int uncached;
+
+	uncached = uncached_access(file, offset);
+	if (uncached)
+		vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+#endif
+
+	/* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */
+	if (remap_pfn_range(vma,
+			    vma->vm_start,
+			    vma->vm_pgoff,
+			    vma->vm_end-vma->vm_start,
+			    vma->vm_page_prot))
+		return -EAGAIN;
+	return 0;
+}
+
+static int mmap_kmem(struct file * file, struct vm_area_struct * vma)
+{
+        unsigned long long val;
+	/*
+	 * RED-PEN: on some architectures there is more mapped memory
+	 * than available in mem_map which pfn_valid checks
+	 * for. Perhaps should add a new macro here.
+	 *
+	 * RED-PEN: vmalloc is not supported right now.
+	 */
+	if (!pfn_valid(vma->vm_pgoff))
+		return -EIO;
+	val = (u64)vma->vm_pgoff << PAGE_SHIFT;
+	vma->vm_pgoff = __pa(val) >> PAGE_SHIFT;
+	return mmap_mem(file, vma);
+}
+
+extern long vread(char *buf, char *addr, unsigned long count);
+extern long vwrite(char *buf, char *addr, unsigned long count);
+
+/*
+ * This function reads the *virtual* memory as seen by the kernel.
+ */
+static ssize_t read_kmem(struct file *file, char __user *buf, 
+			 size_t count, loff_t *ppos)
+{
+	unsigned long p = *ppos;
+	ssize_t low_count, read, sz;
+	char * kbuf; /* k-addr because vread() takes vmlist_lock rwlock */
+
+	read = 0;
+	if (p < (unsigned long) high_memory) {
+		low_count = count;
+		if (count > (unsigned long) high_memory - p)
+			low_count = (unsigned long) high_memory - p;
+
+#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
+		/* we don't have page 0 mapped on sparc and m68k.. */
+		if (p < PAGE_SIZE && low_count > 0) {
+			size_t tmp = PAGE_SIZE - p;
+			if (tmp > low_count) tmp = low_count;
+			if (clear_user(buf, tmp))
+				return -EFAULT;
+			buf += tmp;
+			p += tmp;
+			read += tmp;
+			low_count -= tmp;
+			count -= tmp;
+		}
+#endif
+		while (low_count > 0) {
+			/*
+			 * Handle first page in case it's not aligned
+			 */
+			if (-p & (PAGE_SIZE - 1))
+				sz = -p & (PAGE_SIZE - 1);
+			else
+				sz = PAGE_SIZE;
+
+			sz = min_t(unsigned long, sz, low_count);
+
+			/*
+			 * On ia64 if a page has been mapped somewhere as
+			 * uncached, then it must also be accessed uncached
+			 * by the kernel or data corruption may occur
+			 */
+			kbuf = xlate_dev_kmem_ptr((char *)p);
+
+			if (copy_to_user(buf, kbuf, sz))
+				return -EFAULT;
+			buf += sz;
+			p += sz;
+			read += sz;
+			low_count -= sz;
+			count -= sz;
+		}
+	}
+
+	if (count > 0) {
+		kbuf = (char *)__get_free_page(GFP_KERNEL);
+		if (!kbuf)
+			return -ENOMEM;
+		while (count > 0) {
+			int len = count;
+
+			if (len > PAGE_SIZE)
+				len = PAGE_SIZE;
+			len = vread(kbuf, (char *)p, len);
+			if (!len)
+				break;
+			if (copy_to_user(buf, kbuf, len)) {
+				free_page((unsigned long)kbuf);
+				return -EFAULT;
+			}
+			count -= len;
+			buf += len;
+			read += len;
+			p += len;
+		}
+		free_page((unsigned long)kbuf);
+	}
+ 	*ppos = p;
+ 	return read;
+}
+
+
+static inline ssize_t
+do_write_kmem(void *p, unsigned long realp, const char __user * buf,
+	      size_t count, loff_t *ppos)
+{
+	ssize_t written, sz;
+	unsigned long copied;
+
+	written = 0;
+#ifdef __ARCH_HAS_NO_PAGE_ZERO_MAPPED
+	/* we don't have page 0 mapped on sparc and m68k.. */
+	if (realp < PAGE_SIZE) {
+		unsigned long sz = PAGE_SIZE - realp;
+		if (sz > count)
+			sz = count;
+		/* Hmm. Do something? */
+		buf += sz;
+		p += sz;
+		realp += sz;
+		count -= sz;
+		written += sz;
+	}
+#endif
+
+	while (count > 0) {
+		char *ptr;
+		/*
+		 * Handle first page in case it's not aligned
+		 */
+		if (-realp & (PAGE_SIZE - 1))
+			sz = -realp & (PAGE_SIZE - 1);
+		else
+			sz = PAGE_SIZE;
+
+		sz = min_t(unsigned long, sz, count);
+
+		/*
+		 * On ia64 if a page has been mapped somewhere as
+		 * uncached, then it must also be accessed uncached
+		 * by the kernel or data corruption may occur
+		 */
+		ptr = xlate_dev_kmem_ptr(p);
+
+		copied = copy_from_user(ptr, buf, sz);
+		if (copied) {
+			ssize_t ret;
+
+			ret = written + (sz - copied);
+			if (ret)
+				return ret;
+			return -EFAULT;
+		}
+		buf += sz;
+		p += sz;
+		realp += sz;
+		count -= sz;
+		written += sz;
+	}
+
+	*ppos += written;
+	return written;
+}
+
+
+/*
+ * This function writes to the *virtual* memory as seen by the kernel.
+ */
+static ssize_t write_kmem(struct file * file, const char __user * buf, 
+			  size_t count, loff_t *ppos)
+{
+	unsigned long p = *ppos;
+	ssize_t wrote = 0;
+	ssize_t virtr = 0;
+	ssize_t written;
+	char * kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */
+
+	if (p < (unsigned long) high_memory) {
+
+		wrote = count;
+		if (count > (unsigned long) high_memory - p)
+			wrote = (unsigned long) high_memory - p;
+
+		written = do_write_kmem((void*)p, p, buf, wrote, ppos);
+		if (written != wrote)
+			return written;
+		wrote = written;
+		p += wrote;
+		buf += wrote;
+		count -= wrote;
+	}
+
+	if (count > 0) {
+		kbuf = (char *)__get_free_page(GFP_KERNEL);
+		if (!kbuf)
+			return wrote ? wrote : -ENOMEM;
+		while (count > 0) {
+			int len = count;
+
+			if (len > PAGE_SIZE)
+				len = PAGE_SIZE;
+			if (len) {
+				written = copy_from_user(kbuf, buf, len);
+				if (written) {
+					ssize_t ret;
+
+					free_page((unsigned long)kbuf);
+					ret = wrote + virtr + (len - written);
+					return ret ? ret : -EFAULT;
+				}
+			}
+			len = vwrite(kbuf, (char *)p, len);
+			count -= len;
+			buf += len;
+			virtr += len;
+			p += len;
+		}
+		free_page((unsigned long)kbuf);
+	}
+
+ 	*ppos = p;
+ 	return virtr + wrote;
+}
+
+#if defined(CONFIG_ISA) || !defined(__mc68000__)
+static ssize_t read_port(struct file * file, char __user * buf,
+			 size_t count, loff_t *ppos)
+{
+	unsigned long i = *ppos;
+	char __user *tmp = buf;
+
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT; 
+	while (count-- > 0 && i < 65536) {
+		if (__put_user(inb(i),tmp) < 0) 
+			return -EFAULT;  
+		i++;
+		tmp++;
+	}
+	*ppos = i;
+	return tmp-buf;
+}
+
+static ssize_t write_port(struct file * file, const char __user * buf,
+			  size_t count, loff_t *ppos)
+{
+	unsigned long i = *ppos;
+	const char __user * tmp = buf;
+
+	if (!access_ok(VERIFY_READ,buf,count))
+		return -EFAULT;
+	while (count-- > 0 && i < 65536) {
+		char c;
+		if (__get_user(c, tmp)) 
+			return -EFAULT; 
+		outb(c,i);
+		i++;
+		tmp++;
+	}
+	*ppos = i;
+	return tmp-buf;
+}
+#endif
+
+static ssize_t read_null(struct file * file, char __user * buf,
+			 size_t count, loff_t *ppos)
+{
+	return 0;
+}
+
+static ssize_t write_null(struct file * file, const char __user * buf,
+			  size_t count, loff_t *ppos)
+{
+	return count;
+}
+
+#ifdef CONFIG_MMU
+/*
+ * For fun, we are using the MMU for this.
+ */
+static inline size_t read_zero_pagealigned(char __user * buf, size_t size)
+{
+	struct mm_struct *mm;
+	struct vm_area_struct * vma;
+	unsigned long addr=(unsigned long)buf;
+
+	mm = current->mm;
+	/* Oops, this was forgotten before. -ben */
+	down_read(&mm->mmap_sem);
+
+	/* For private mappings, just map in zero pages. */
+	for (vma = find_vma(mm, addr); vma; vma = vma->vm_next) {
+		unsigned long count;
+
+		if (vma->vm_start > addr || (vma->vm_flags & VM_WRITE) == 0)
+			goto out_up;
+		if (vma->vm_flags & (VM_SHARED | VM_HUGETLB))
+			break;
+		count = vma->vm_end - addr;
+		if (count > size)
+			count = size;
+
+		zap_page_range(vma, addr, count, NULL);
+        	zeromap_page_range(vma, addr, count, PAGE_COPY);
+
+		size -= count;
+		buf += count;
+		addr += count;
+		if (size == 0)
+			goto out_up;
+	}
+
+	up_read(&mm->mmap_sem);
+	
+	/* The shared case is hard. Let's do the conventional zeroing. */ 
+	do {
+		unsigned long unwritten = clear_user(buf, PAGE_SIZE);
+		if (unwritten)
+			return size + unwritten - PAGE_SIZE;
+		cond_resched();
+		buf += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	} while (size);
+
+	return size;
+out_up:
+	up_read(&mm->mmap_sem);
+	return size;
+}
+
+static ssize_t read_zero(struct file * file, char __user * buf, 
+			 size_t count, loff_t *ppos)
+{
+	unsigned long left, unwritten, written = 0;
+
+	if (!count)
+		return 0;
+
+	if (!access_ok(VERIFY_WRITE, buf, count))
+		return -EFAULT;
+
+	left = count;
+
+	/* do we want to be clever? Arbitrary cut-off */
+	if (count >= PAGE_SIZE*4) {
+		unsigned long partial;
+
+		/* How much left of the page? */
+		partial = (PAGE_SIZE-1) & -(unsigned long) buf;
+		unwritten = clear_user(buf, partial);
+		written = partial - unwritten;
+		if (unwritten)
+			goto out;
+		left -= partial;
+		buf += partial;
+		unwritten = read_zero_pagealigned(buf, left & PAGE_MASK);
+		written += (left & PAGE_MASK) - unwritten;
+		if (unwritten)
+			goto out;
+		buf += left & PAGE_MASK;
+		left &= ~PAGE_MASK;
+	}
+	unwritten = clear_user(buf, left);
+	written += left - unwritten;
+out:
+	return written ? written : -EFAULT;
+}
+
+static int mmap_zero(struct file * file, struct vm_area_struct * vma)
+{
+	if (vma->vm_flags & VM_SHARED)
+		return shmem_zero_setup(vma);
+	if (zeromap_page_range(vma, vma->vm_start, vma->vm_end - vma->vm_start, vma->vm_page_prot))
+		return -EAGAIN;
+	return 0;
+}
+#else /* CONFIG_MMU */
+static ssize_t read_zero(struct file * file, char * buf, 
+			 size_t count, loff_t *ppos)
+{
+	size_t todo = count;
+
+	while (todo) {
+		size_t chunk = todo;
+
+		if (chunk > 4096)
+			chunk = 4096;	/* Just for latency reasons */
+		if (clear_user(buf, chunk))
+			return -EFAULT;
+		buf += chunk;
+		todo -= chunk;
+		cond_resched();
+	}
+	return count;
+}
+
+static int mmap_zero(struct file * file, struct vm_area_struct * vma)
+{
+	return -ENOSYS;
+}
+#endif /* CONFIG_MMU */
+
+static ssize_t write_full(struct file * file, const char __user * buf,
+			  size_t count, loff_t *ppos)
+{
+	return -ENOSPC;
+}
+
+/*
+ * Special lseek() function for /dev/null and /dev/zero.  Most notably, you
+ * can fopen() both devices with "a" now.  This was previously impossible.
+ * -- SRB.
+ */
+
+static loff_t null_lseek(struct file * file, loff_t offset, int orig)
+{
+	return file->f_pos = 0;
+}
+
+/*
+ * The memory devices use the full 32/64 bits of the offset, and so we cannot
+ * check against negative addresses: they are ok. The return value is weird,
+ * though, in that case (0).
+ *
+ * also note that seeking relative to the "end of file" isn't supported:
+ * it has no meaning, so it returns -EINVAL.
+ */
+static loff_t memory_lseek(struct file * file, loff_t offset, int orig)
+{
+	loff_t ret;
+
+	down(&file->f_dentry->d_inode->i_sem);
+	switch (orig) {
+		case 0:
+			file->f_pos = offset;
+			ret = file->f_pos;
+			force_successful_syscall_return();
+			break;
+		case 1:
+			file->f_pos += offset;
+			ret = file->f_pos;
+			force_successful_syscall_return();
+			break;
+		default:
+			ret = -EINVAL;
+	}
+	up(&file->f_dentry->d_inode->i_sem);
+	return ret;
+}
+
+static int open_port(struct inode * inode, struct file * filp)
+{
+	return capable(CAP_SYS_RAWIO) ? 0 : -EPERM;
+}
+
+#define zero_lseek	null_lseek
+#define full_lseek      null_lseek
+#define write_zero	write_null
+#define read_full       read_zero
+#define open_mem	open_port
+#define open_kmem	open_mem
+
+static struct file_operations mem_fops = {
+	.llseek		= memory_lseek,
+	.read		= read_mem,
+	.write		= write_mem,
+	.mmap		= mmap_mem,
+	.open		= open_mem,
+};
+
+static struct file_operations kmem_fops = {
+	.llseek		= memory_lseek,
+	.read		= read_kmem,
+	.write		= write_kmem,
+	.mmap		= mmap_kmem,
+	.open		= open_kmem,
+};
+
+static struct file_operations null_fops = {
+	.llseek		= null_lseek,
+	.read		= read_null,
+	.write		= write_null,
+};
+
+#if defined(CONFIG_ISA) || !defined(__mc68000__)
+static struct file_operations port_fops = {
+	.llseek		= memory_lseek,
+	.read		= read_port,
+	.write		= write_port,
+	.open		= open_port,
+};
+#endif
+
+static struct file_operations zero_fops = {
+	.llseek		= zero_lseek,
+	.read		= read_zero,
+	.write		= write_zero,
+	.mmap		= mmap_zero,
+};
+
+static struct backing_dev_info zero_bdi = {
+	.capabilities	= BDI_CAP_MAP_COPY,
+};
+
+static struct file_operations full_fops = {
+	.llseek		= full_lseek,
+	.read		= read_full,
+	.write		= write_full,
+};
+
+static ssize_t kmsg_write(struct file * file, const char __user * buf,
+			  size_t count, loff_t *ppos)
+{
+	char *tmp;
+	int ret;
+
+	tmp = kmalloc(count + 1, GFP_KERNEL);
+	if (tmp == NULL)
+		return -ENOMEM;
+	ret = -EFAULT;
+	if (!copy_from_user(tmp, buf, count)) {
+		tmp[count] = 0;
+		ret = printk("%s", tmp);
+	}
+	kfree(tmp);
+	return ret;
+}
+
+static struct file_operations kmsg_fops = {
+	.write =	kmsg_write,
+};
+
+static int memory_open(struct inode * inode, struct file * filp)
+{
+	switch (iminor(inode)) {
+		case 1:
+			filp->f_op = &mem_fops;
+			break;
+		case 2:
+			filp->f_op = &kmem_fops;
+			break;
+		case 3:
+			filp->f_op = &null_fops;
+			break;
+#if defined(CONFIG_ISA) || !defined(__mc68000__)
+		case 4:
+			filp->f_op = &port_fops;
+			break;
+#endif
+		case 5:
+			filp->f_mapping->backing_dev_info = &zero_bdi;
+			filp->f_op = &zero_fops;
+			break;
+		case 7:
+			filp->f_op = &full_fops;
+			break;
+		case 8:
+			filp->f_op = &random_fops;
+			break;
+		case 9:
+			filp->f_op = &urandom_fops;
+			break;
+		case 11:
+			filp->f_op = &kmsg_fops;
+			break;
+		default:
+			return -ENXIO;
+	}
+	if (filp->f_op && filp->f_op->open)
+		return filp->f_op->open(inode,filp);
+	return 0;
+}
+
+static struct file_operations memory_fops = {
+	.open		= memory_open,	/* just a selector for the real open */
+};
+
+static const struct {
+	unsigned int		minor;
+	char			*name;
+	umode_t			mode;
+	struct file_operations	*fops;
+} devlist[] = { /* list of minor devices */
+	{1, "mem",     S_IRUSR | S_IWUSR | S_IRGRP, &mem_fops},
+	{2, "kmem",    S_IRUSR | S_IWUSR | S_IRGRP, &kmem_fops},
+	{3, "null",    S_IRUGO | S_IWUGO,           &null_fops},
+#if defined(CONFIG_ISA) || !defined(__mc68000__)
+	{4, "port",    S_IRUSR | S_IWUSR | S_IRGRP, &port_fops},
+#endif
+	{5, "zero",    S_IRUGO | S_IWUGO,           &zero_fops},
+	{7, "full",    S_IRUGO | S_IWUGO,           &full_fops},
+	{8, "random",  S_IRUGO | S_IWUSR,           &random_fops},
+	{9, "urandom", S_IRUGO | S_IWUSR,           &urandom_fops},
+	{11,"kmsg",    S_IRUGO | S_IWUSR,           &kmsg_fops},
+};
+
+static struct class_simple *mem_class;
+
+static int __init chr_dev_init(void)
+{
+	int i;
+
+	if (register_chrdev(MEM_MAJOR,"mem",&memory_fops))
+		printk("unable to get major %d for memory devs\n", MEM_MAJOR);
+
+	mem_class = class_simple_create(THIS_MODULE, "mem");
+	for (i = 0; i < ARRAY_SIZE(devlist); i++) {
+		class_simple_device_add(mem_class,
+					MKDEV(MEM_MAJOR, devlist[i].minor),
+					NULL, devlist[i].name);
+		devfs_mk_cdev(MKDEV(MEM_MAJOR, devlist[i].minor),
+				S_IFCHR | devlist[i].mode, devlist[i].name);
+	}
+	
+	return 0;
+}
+
+fs_initcall(chr_dev_init);
diff --git a/drivers/char/misc.c b/drivers/char/misc.c
new file mode 100644
index 0000000..0937544
--- /dev/null
+++ b/drivers/char/misc.c
@@ -0,0 +1,331 @@
+/*
+ * linux/drivers/char/misc.c
+ *
+ * Generic misc open routine by Johan Myreen
+ *
+ * Based on code from Linus
+ *
+ * Teemu Rantanen's Microsoft Busmouse support and Derrick Cole's
+ *   changes incorporated into 0.97pl4
+ *   by Peter Cervasio (pete%q106fm.uucp@wupost.wustl.edu) (08SEP92)
+ *   See busmouse.c for particulars.
+ *
+ * Made things a lot mode modular - easy to compile in just one or two
+ * of the misc drivers, as they are now completely independent. Linus.
+ *
+ * Support for loadable modules. 8-Sep-95 Philip Blundell <pjb27@cam.ac.uk>
+ *
+ * Fixed a failing symbol register to free the device registration
+ *		Alan Cox <alan@lxorguk.ukuu.org.uk> 21-Jan-96
+ *
+ * Dynamic minors and /proc/mice by Alessandro Rubini. 26-Mar-96
+ *
+ * Renamed to misc and miscdevice to be more accurate. Alan Cox 26-Mar-96
+ *
+ * Handling of mouse minor numbers for kerneld:
+ *  Idea by Jacques Gelinas <jack@solucorp.qc.ca>,
+ *  adapted by Bjorn Ekwall <bj0rn@blox.se>
+ *  corrected by Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ * Changes for kmod (from kerneld):
+ *	Cyrus Durgin <cider@speakeasy.org>
+ *
+ * Added devfs support. Richard Gooch <rgooch@atnf.csiro.au>  10-Jan-1998
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/tty.h>
+#include <linux/kmod.h>
+
+/*
+ * Head entry for the doubly linked miscdevice list
+ */
+static LIST_HEAD(misc_list);
+static DECLARE_MUTEX(misc_sem);
+
+/*
+ * Assigned numbers, used for dynamic minors
+ */
+#define DYNAMIC_MINORS 64 /* like dynamic majors */
+static unsigned char misc_minors[DYNAMIC_MINORS / 8];
+
+extern int rtc_DP8570A_init(void);
+extern int rtc_MK48T08_init(void);
+extern int pmu_device_init(void);
+extern int tosh_init(void);
+extern int i8k_init(void);
+
+#ifdef CONFIG_PROC_FS
+static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	struct miscdevice *p;
+	loff_t off = 0;
+
+	down(&misc_sem);
+	list_for_each_entry(p, &misc_list, list) {
+		if (*pos == off++) 
+			return p;
+	}
+	return NULL;
+}
+
+static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	struct list_head *n = ((struct miscdevice *)v)->list.next;
+
+	++*pos;
+
+	return (n != &misc_list) ? list_entry(n, struct miscdevice, list)
+		 : NULL;
+}
+
+static void misc_seq_stop(struct seq_file *seq, void *v)
+{
+	up(&misc_sem);
+}
+
+static int misc_seq_show(struct seq_file *seq, void *v)
+{
+	const struct miscdevice *p = v;
+
+	seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
+	return 0;
+}
+
+
+static struct seq_operations misc_seq_ops = {
+	.start = misc_seq_start,
+	.next  = misc_seq_next,
+	.stop  = misc_seq_stop,
+	.show  = misc_seq_show,
+};
+
+static int misc_seq_open(struct inode *inode, struct file *file)
+{
+	return seq_open(file, &misc_seq_ops);
+}
+
+static struct file_operations misc_proc_fops = {
+	.owner	 = THIS_MODULE,
+	.open    = misc_seq_open,
+	.read    = seq_read,
+	.llseek  = seq_lseek,
+	.release = seq_release,
+};
+#endif
+
+static int misc_open(struct inode * inode, struct file * file)
+{
+	int minor = iminor(inode);
+	struct miscdevice *c;
+	int err = -ENODEV;
+	struct file_operations *old_fops, *new_fops = NULL;
+	
+	down(&misc_sem);
+	
+	list_for_each_entry(c, &misc_list, list) {
+		if (c->minor == minor) {
+			new_fops = fops_get(c->fops);		
+			break;
+		}
+	}
+		
+	if (!new_fops) {
+		up(&misc_sem);
+		request_module("char-major-%d-%d", MISC_MAJOR, minor);
+		down(&misc_sem);
+
+		list_for_each_entry(c, &misc_list, list) {
+			if (c->minor == minor) {
+				new_fops = fops_get(c->fops);
+				break;
+			}
+		}
+		if (!new_fops)
+			goto fail;
+	}
+
+	err = 0;
+	old_fops = file->f_op;
+	file->f_op = new_fops;
+	if (file->f_op->open) {
+		err=file->f_op->open(inode,file);
+		if (err) {
+			fops_put(file->f_op);
+			file->f_op = fops_get(old_fops);
+		}
+	}
+	fops_put(old_fops);
+fail:
+	up(&misc_sem);
+	return err;
+}
+
+/* 
+ * TODO for 2.7:
+ *  - add a struct class_device to struct miscdevice and make all usages of
+ *    them dynamic.
+ */
+static struct class_simple *misc_class;
+
+static struct file_operations misc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= misc_open,
+};
+
+
+/**
+ *	misc_register	-	register a miscellaneous device
+ *	@misc: device structure
+ *	
+ *	Register a miscellaneous device with the kernel. If the minor
+ *	number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
+ *	and placed in the minor field of the structure. For other cases
+ *	the minor number requested is used.
+ *
+ *	The structure passed is linked into the kernel and may not be
+ *	destroyed until it has been unregistered.
+ *
+ *	A zero is returned on success and a negative errno code for
+ *	failure.
+ */
+ 
+int misc_register(struct miscdevice * misc)
+{
+	struct miscdevice *c;
+	dev_t dev;
+	int err;
+
+	down(&misc_sem);
+	list_for_each_entry(c, &misc_list, list) {
+		if (c->minor == misc->minor) {
+			up(&misc_sem);
+			return -EBUSY;
+		}
+	}
+
+	if (misc->minor == MISC_DYNAMIC_MINOR) {
+		int i = DYNAMIC_MINORS;
+		while (--i >= 0)
+			if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
+				break;
+		if (i<0) {
+			up(&misc_sem);
+			return -EBUSY;
+		}
+		misc->minor = i;
+	}
+
+	if (misc->minor < DYNAMIC_MINORS)
+		misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
+	if (misc->devfs_name[0] == '\0') {
+		snprintf(misc->devfs_name, sizeof(misc->devfs_name),
+				"misc/%s", misc->name);
+	}
+	dev = MKDEV(MISC_MAJOR, misc->minor);
+
+	misc->class = class_simple_device_add(misc_class, dev,
+					      misc->dev, misc->name);
+	if (IS_ERR(misc->class)) {
+		err = PTR_ERR(misc->class);
+		goto out;
+	}
+
+	err = devfs_mk_cdev(dev, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, 
+			    misc->devfs_name);
+	if (err) {
+		class_simple_device_remove(dev);
+		goto out;
+	}
+
+	/*
+	 * Add it to the front, so that later devices can "override"
+	 * earlier defaults
+	 */
+	list_add(&misc->list, &misc_list);
+ out:
+	up(&misc_sem);
+	return err;
+}
+
+/**
+ *	misc_deregister - unregister a miscellaneous device
+ *	@misc: device to unregister
+ *
+ *	Unregister a miscellaneous device that was previously
+ *	successfully registered with misc_register(). Success
+ *	is indicated by a zero return, a negative errno code
+ *	indicates an error.
+ */
+
+int misc_deregister(struct miscdevice * misc)
+{
+	int i = misc->minor;
+
+	if (list_empty(&misc->list))
+		return -EINVAL;
+
+	down(&misc_sem);
+	list_del(&misc->list);
+	class_simple_device_remove(MKDEV(MISC_MAJOR, misc->minor));
+	devfs_remove(misc->devfs_name);
+	if (i < DYNAMIC_MINORS && i>0) {
+		misc_minors[i>>3] &= ~(1 << (misc->minor & 7));
+	}
+	up(&misc_sem);
+	return 0;
+}
+
+EXPORT_SYMBOL(misc_register);
+EXPORT_SYMBOL(misc_deregister);
+
+static int __init misc_init(void)
+{
+#ifdef CONFIG_PROC_FS
+	struct proc_dir_entry *ent;
+
+	ent = create_proc_entry("misc", 0, NULL);
+	if (ent)
+		ent->proc_fops = &misc_proc_fops;
+#endif
+	misc_class = class_simple_create(THIS_MODULE, "misc");
+	if (IS_ERR(misc_class))
+		return PTR_ERR(misc_class);
+#ifdef CONFIG_MVME16x
+	rtc_MK48T08_init();
+#endif
+#ifdef CONFIG_BVME6000
+	rtc_DP8570A_init();
+#endif
+#ifdef CONFIG_PMAC_PBOOK
+	pmu_device_init();
+#endif
+#ifdef CONFIG_TOSHIBA
+	tosh_init();
+#endif
+#ifdef CONFIG_I8K
+	i8k_init();
+#endif
+	if (register_chrdev(MISC_MAJOR,"misc",&misc_fops)) {
+		printk("unable to get major %d for misc devices\n",
+		       MISC_MAJOR);
+		class_simple_destroy(misc_class);
+		return -EIO;
+	}
+	return 0;
+}
+subsys_initcall(misc_init);
diff --git a/drivers/char/mmtimer.c b/drivers/char/mmtimer.c
new file mode 100644
index 0000000..58eddfd
--- /dev/null
+++ b/drivers/char/mmtimer.c
@@ -0,0 +1,725 @@
+/*
+ * Intel Multimedia Timer device implementation for SGI SN platforms.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2001-2004 Silicon Graphics, Inc.  All rights reserved.
+ *
+ * This driver exports an API that should be supportable by any HPET or IA-PC
+ * multimedia timer.  The code below is currently specific to the SGI Altix
+ * SHub RTC, however.
+ *
+ * 11/01/01 - jbarnes - initial revision
+ * 9/10/04 - Christoph Lameter - remove interrupt support for kernel inclusion
+ * 10/1/04 - Christoph Lameter - provide posix clock CLOCK_SGI_CYCLE
+ * 10/13/04 - Christoph Lameter, Dimitri Sivanich - provide timer interrupt
+ *		support via the posix timer interface
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/mmtimer.h>
+#include <linux/miscdevice.h>
+#include <linux/posix-timers.h>
+#include <linux/interrupt.h>
+
+#include <asm/uaccess.h>
+#include <asm/sn/addrs.h>
+#include <asm/sn/intr.h>
+#include <asm/sn/shub_mmr.h>
+#include <asm/sn/nodepda.h>
+#include <asm/sn/shubio.h>
+
+MODULE_AUTHOR("Jesse Barnes <jbarnes@sgi.com>");
+MODULE_DESCRIPTION("SGI Altix RTC Timer");
+MODULE_LICENSE("GPL");
+
+/* name of the device, usually in /dev */
+#define MMTIMER_NAME "mmtimer"
+#define MMTIMER_DESC "SGI Altix RTC Timer"
+#define MMTIMER_VERSION "2.0"
+
+#define RTC_BITS 55 /* 55 bits for this implementation */
+
+extern unsigned long sn_rtc_cycles_per_second;
+
+#define RTC_COUNTER_ADDR        ((long *)LOCAL_MMR_ADDR(SH_RTC))
+
+#define rtc_time()              (*RTC_COUNTER_ADDR)
+
+static int mmtimer_ioctl(struct inode *inode, struct file *file,
+			 unsigned int cmd, unsigned long arg);
+static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma);
+
+/*
+ * Period in femtoseconds (10^-15 s)
+ */
+static unsigned long mmtimer_femtoperiod = 0;
+
+static struct file_operations mmtimer_fops = {
+	.owner =	THIS_MODULE,
+	.mmap =		mmtimer_mmap,
+	.ioctl =	mmtimer_ioctl,
+};
+
+/*
+ * We only have comparison registers RTC1-4 currently available per
+ * node.  RTC0 is used by SAL.
+ */
+#define NUM_COMPARATORS 3
+/* Check for an RTC interrupt pending */
+static int inline mmtimer_int_pending(int comparator)
+{
+	if (HUB_L((unsigned long *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED)) &
+			SH_EVENT_OCCURRED_RTC1_INT_MASK << comparator)
+		return 1;
+	else
+		return 0;
+}
+/* Clear the RTC interrupt pending bit */
+static void inline mmtimer_clr_int_pending(int comparator)
+{
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_EVENT_OCCURRED_ALIAS),
+		SH_EVENT_OCCURRED_RTC1_INT_MASK << comparator);
+}
+
+/* Setup timer on comparator RTC1 */
+static void inline mmtimer_setup_int_0(u64 expires)
+{
+	u64 val;
+
+	/* Disable interrupt */
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 0UL);
+
+	/* Initialize comparator value */
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPB), -1L);
+
+	/* Clear pending bit */
+	mmtimer_clr_int_pending(0);
+
+	val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC1_INT_CONFIG_IDX_SHFT) |
+		((u64)cpu_physical_id(smp_processor_id()) <<
+			SH_RTC1_INT_CONFIG_PID_SHFT);
+
+	/* Set configuration */
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_CONFIG), val);
+
+	/* Enable RTC interrupts */
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE), 1UL);
+
+	/* Initialize comparator value */
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPB), expires);
+
+
+}
+
+/* Setup timer on comparator RTC2 */
+static void inline mmtimer_setup_int_1(u64 expires)
+{
+	u64 val;
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE), 0UL);
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPC), -1L);
+
+	mmtimer_clr_int_pending(1);
+
+	val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC2_INT_CONFIG_IDX_SHFT) |
+		((u64)cpu_physical_id(smp_processor_id()) <<
+			SH_RTC2_INT_CONFIG_PID_SHFT);
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_CONFIG), val);
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE), 1UL);
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPC), expires);
+}
+
+/* Setup timer on comparator RTC3 */
+static void inline mmtimer_setup_int_2(u64 expires)
+{
+	u64 val;
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE), 0UL);
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPD), -1L);
+
+	mmtimer_clr_int_pending(2);
+
+	val = ((u64)SGI_MMTIMER_VECTOR << SH_RTC3_INT_CONFIG_IDX_SHFT) |
+		((u64)cpu_physical_id(smp_processor_id()) <<
+			SH_RTC3_INT_CONFIG_PID_SHFT);
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_CONFIG), val);
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE), 1UL);
+
+	HUB_S((u64 *)LOCAL_MMR_ADDR(SH_INT_CMPD), expires);
+}
+
+/*
+ * This function must be called with interrupts disabled and preemption off
+ * in order to insure that the setup succeeds in a deterministic time frame.
+ * It will check if the interrupt setup succeeded.
+ */
+static int inline mmtimer_setup(int comparator, unsigned long expires)
+{
+
+	switch (comparator) {
+	case 0:
+		mmtimer_setup_int_0(expires);
+		break;
+	case 1:
+		mmtimer_setup_int_1(expires);
+		break;
+	case 2:
+		mmtimer_setup_int_2(expires);
+		break;
+	}
+	/* We might've missed our expiration time */
+	if (rtc_time() < expires)
+		return 1;
+
+	/*
+	 * If an interrupt is already pending then its okay
+	 * if not then we failed
+	 */
+	return mmtimer_int_pending(comparator);
+}
+
+static int inline mmtimer_disable_int(long nasid, int comparator)
+{
+	switch (comparator) {
+	case 0:
+		nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC1_INT_ENABLE),
+			0UL) : REMOTE_HUB_S(nasid, SH_RTC1_INT_ENABLE, 0UL);
+		break;
+	case 1:
+		nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC2_INT_ENABLE),
+			0UL) : REMOTE_HUB_S(nasid, SH_RTC2_INT_ENABLE, 0UL);
+		break;
+	case 2:
+		nasid == -1 ? HUB_S((u64 *)LOCAL_MMR_ADDR(SH_RTC3_INT_ENABLE),
+			0UL) : REMOTE_HUB_S(nasid, SH_RTC3_INT_ENABLE, 0UL);
+		break;
+	default:
+		return -EFAULT;
+	}
+	return 0;
+}
+
+#define TIMER_OFF 0xbadcabLL
+
+/* There is one of these for each comparator */
+typedef struct mmtimer {
+	spinlock_t lock ____cacheline_aligned;
+	struct k_itimer *timer;
+	int i;
+	int cpu;
+	struct tasklet_struct tasklet;
+} mmtimer_t;
+
+/*
+ * Total number of comparators is comparators/node * MAX nodes/running kernel
+ */
+static mmtimer_t timers[NUM_COMPARATORS*MAX_COMPACT_NODES];
+
+/**
+ * mmtimer_ioctl - ioctl interface for /dev/mmtimer
+ * @inode: inode of the device
+ * @file: file structure for the device
+ * @cmd: command to execute
+ * @arg: optional argument to command
+ *
+ * Executes the command specified by @cmd.  Returns 0 for success, < 0 for
+ * failure.
+ *
+ * Valid commands:
+ *
+ * %MMTIMER_GETOFFSET - Should return the offset (relative to the start
+ * of the page where the registers are mapped) for the counter in question.
+ *
+ * %MMTIMER_GETRES - Returns the resolution of the clock in femto (10^-15)
+ * seconds
+ *
+ * %MMTIMER_GETFREQ - Copies the frequency of the clock in Hz to the address
+ * specified by @arg
+ *
+ * %MMTIMER_GETBITS - Returns the number of bits in the clock's counter
+ *
+ * %MMTIMER_MMAPAVAIL - Returns 1 if the registers can be mmap'd into userspace
+ *
+ * %MMTIMER_GETCOUNTER - Gets the current value in the counter and places it
+ * in the address specified by @arg.
+ */
+static int mmtimer_ioctl(struct inode *inode, struct file *file,
+			 unsigned int cmd, unsigned long arg)
+{
+	int ret = 0;
+
+	switch (cmd) {
+	case MMTIMER_GETOFFSET:	/* offset of the counter */
+		/*
+		 * SN RTC registers are on their own 64k page
+		 */
+		if(PAGE_SIZE <= (1 << 16))
+			ret = (((long)RTC_COUNTER_ADDR) & (PAGE_SIZE-1)) / 8;
+		else
+			ret = -ENOSYS;
+		break;
+
+	case MMTIMER_GETRES: /* resolution of the clock in 10^-15 s */
+		if(copy_to_user((unsigned long __user *)arg,
+				&mmtimer_femtoperiod, sizeof(unsigned long)))
+			return -EFAULT;
+		break;
+
+	case MMTIMER_GETFREQ: /* frequency in Hz */
+		if(copy_to_user((unsigned long __user *)arg,
+				&sn_rtc_cycles_per_second,
+				sizeof(unsigned long)))
+			return -EFAULT;
+		ret = 0;
+		break;
+
+	case MMTIMER_GETBITS: /* number of bits in the clock */
+		ret = RTC_BITS;
+		break;
+
+	case MMTIMER_MMAPAVAIL: /* can we mmap the clock into userspace? */
+		ret = (PAGE_SIZE <= (1 << 16)) ? 1 : 0;
+		break;
+
+	case MMTIMER_GETCOUNTER:
+		if(copy_to_user((unsigned long __user *)arg,
+				RTC_COUNTER_ADDR, sizeof(unsigned long)))
+			return -EFAULT;
+		break;
+	default:
+		ret = -ENOSYS;
+		break;
+	}
+
+	return ret;
+}
+
+/**
+ * mmtimer_mmap - maps the clock's registers into userspace
+ * @file: file structure for the device
+ * @vma: VMA to map the registers into
+ *
+ * Calls remap_pfn_range() to map the clock's registers into
+ * the calling process' address space.
+ */
+static int mmtimer_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	unsigned long mmtimer_addr;
+
+	if (vma->vm_end - vma->vm_start != PAGE_SIZE)
+		return -EINVAL;
+
+	if (vma->vm_flags & VM_WRITE)
+		return -EPERM;
+
+	if (PAGE_SIZE > (1 << 16))
+		return -ENOSYS;
+
+	vma->vm_flags |= (VM_IO | VM_SHM | VM_LOCKED );
+	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+	mmtimer_addr = __pa(RTC_COUNTER_ADDR);
+	mmtimer_addr &= ~(PAGE_SIZE - 1);
+	mmtimer_addr &= 0xfffffffffffffffUL;
+
+	if (remap_pfn_range(vma, vma->vm_start, mmtimer_addr >> PAGE_SHIFT,
+					PAGE_SIZE, vma->vm_page_prot)) {
+		printk(KERN_ERR "remap_pfn_range failed in mmtimer.c\n");
+		return -EAGAIN;
+	}
+
+	return 0;
+}
+
+static struct miscdevice mmtimer_miscdev = {
+	SGI_MMTIMER,
+	MMTIMER_NAME,
+	&mmtimer_fops
+};
+
+static struct timespec sgi_clock_offset;
+static int sgi_clock_period;
+
+/*
+ * Posix Timer Interface
+ */
+
+static struct timespec sgi_clock_offset;
+static int sgi_clock_period;
+
+static int sgi_clock_get(clockid_t clockid, struct timespec *tp)
+{
+	u64 nsec;
+
+	nsec = rtc_time() * sgi_clock_period
+			+ sgi_clock_offset.tv_nsec;
+	tp->tv_sec = div_long_long_rem(nsec, NSEC_PER_SEC, &tp->tv_nsec)
+			+ sgi_clock_offset.tv_sec;
+	return 0;
+};
+
+static int sgi_clock_set(clockid_t clockid, struct timespec *tp)
+{
+
+	u64 nsec;
+	u64 rem;
+
+	nsec = rtc_time() * sgi_clock_period;
+
+	sgi_clock_offset.tv_sec = tp->tv_sec - div_long_long_rem(nsec, NSEC_PER_SEC, &rem);
+
+	if (rem <= tp->tv_nsec)
+		sgi_clock_offset.tv_nsec = tp->tv_sec - rem;
+	else {
+		sgi_clock_offset.tv_nsec = tp->tv_sec + NSEC_PER_SEC - rem;
+		sgi_clock_offset.tv_sec--;
+	}
+	return 0;
+}
+
+/*
+ * Schedule the next periodic interrupt. This function will attempt
+ * to schedule a periodic interrupt later if necessary. If the scheduling
+ * of an interrupt fails then the time to skip is lengthened
+ * exponentially in order to ensure that the next interrupt
+ * can be properly scheduled..
+ */
+static int inline reschedule_periodic_timer(mmtimer_t *x)
+{
+	int n;
+	struct k_itimer *t = x->timer;
+
+	t->it.mmtimer.clock = x->i;
+	t->it_overrun--;
+
+	n = 0;
+	do {
+
+		t->it.mmtimer.expires += t->it.mmtimer.incr << n;
+		t->it_overrun += 1 << n;
+		n++;
+		if (n > 20)
+			return 1;
+
+	} while (!mmtimer_setup(x->i, t->it.mmtimer.expires));
+
+	return 0;
+}
+
+/**
+ * mmtimer_interrupt - timer interrupt handler
+ * @irq: irq received
+ * @dev_id: device the irq came from
+ * @regs: register state upon receipt of the interrupt
+ *
+ * Called when one of the comarators matches the counter, This
+ * routine will send signals to processes that have requested
+ * them.
+ *
+ * This interrupt is run in an interrupt context
+ * by the SHUB. It is therefore safe to locally access SHub
+ * registers.
+ */
+static irqreturn_t
+mmtimer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	int i;
+	mmtimer_t *base = timers + cpuid_to_cnodeid(smp_processor_id()) *
+						NUM_COMPARATORS;
+	unsigned long expires = 0;
+	int result = IRQ_NONE;
+
+	/*
+	 * Do this once for each comparison register
+	 */
+	for (i = 0; i < NUM_COMPARATORS; i++) {
+		/* Make sure this doesn't get reused before tasklet_sched */
+		spin_lock(&base[i].lock);
+		if (base[i].cpu == smp_processor_id()) {
+			if (base[i].timer)
+				expires = base[i].timer->it.mmtimer.expires;
+			/* expires test won't work with shared irqs */
+			if ((mmtimer_int_pending(i) > 0) ||
+				(expires && (expires < rtc_time()))) {
+				mmtimer_clr_int_pending(i);
+				tasklet_schedule(&base[i].tasklet);
+				result = IRQ_HANDLED;
+			}
+		}
+		spin_unlock(&base[i].lock);
+		expires = 0;
+	}
+	return result;
+}
+
+void mmtimer_tasklet(unsigned long data) {
+	mmtimer_t *x = (mmtimer_t *)data;
+	struct k_itimer *t = x->timer;
+	unsigned long flags;
+
+	if (t == NULL)
+		return;
+
+	/* Send signal and deal with periodic signals */
+	spin_lock_irqsave(&t->it_lock, flags);
+	spin_lock(&x->lock);
+	/* If timer was deleted between interrupt and here, leave */
+	if (t != x->timer)
+		goto out;
+	t->it_overrun = 0;
+
+	if (tasklist_lock.write_lock || posix_timer_event(t, 0) != 0) {
+
+		// printk(KERN_WARNING "mmtimer: cannot deliver signal.\n");
+
+		t->it_overrun++;
+	}
+	if(t->it.mmtimer.incr) {
+		/* Periodic timer */
+		if (reschedule_periodic_timer(x)) {
+			printk(KERN_WARNING "mmtimer: unable to reschedule\n");
+			x->timer = NULL;
+		}
+	} else {
+		/* Ensure we don't false trigger in mmtimer_interrupt */
+		t->it.mmtimer.expires = 0;
+	}
+	t->it_overrun_last = t->it_overrun;
+out:
+	spin_unlock(&x->lock);
+	spin_unlock_irqrestore(&t->it_lock, flags);
+}
+
+static int sgi_timer_create(struct k_itimer *timer)
+{
+	/* Insure that a newly created timer is off */
+	timer->it.mmtimer.clock = TIMER_OFF;
+	return 0;
+}
+
+/* This does not really delete a timer. It just insures
+ * that the timer is not active
+ *
+ * Assumption: it_lock is already held with irq's disabled
+ */
+static int sgi_timer_del(struct k_itimer *timr)
+{
+	int i = timr->it.mmtimer.clock;
+	cnodeid_t nodeid = timr->it.mmtimer.node;
+	mmtimer_t *t = timers + nodeid * NUM_COMPARATORS +i;
+	unsigned long irqflags;
+
+	if (i != TIMER_OFF) {
+		spin_lock_irqsave(&t->lock, irqflags);
+		mmtimer_disable_int(cnodeid_to_nasid(nodeid),i);
+		t->timer = NULL;
+		timr->it.mmtimer.clock = TIMER_OFF;
+		timr->it.mmtimer.expires = 0;
+		spin_unlock_irqrestore(&t->lock, irqflags);
+	}
+	return 0;
+}
+
+#define timespec_to_ns(x) ((x).tv_nsec + (x).tv_sec * NSEC_PER_SEC)
+#define ns_to_timespec(ts, nsec) (ts).tv_sec = div_long_long_rem(nsec, NSEC_PER_SEC, &(ts).tv_nsec)
+
+/* Assumption: it_lock is already held with irq's disabled */
+static void sgi_timer_get(struct k_itimer *timr, struct itimerspec *cur_setting)
+{
+
+	if (timr->it.mmtimer.clock == TIMER_OFF) {
+		cur_setting->it_interval.tv_nsec = 0;
+		cur_setting->it_interval.tv_sec = 0;
+		cur_setting->it_value.tv_nsec = 0;
+		cur_setting->it_value.tv_sec =0;
+		return;
+	}
+
+	ns_to_timespec(cur_setting->it_interval, timr->it.mmtimer.incr * sgi_clock_period);
+	ns_to_timespec(cur_setting->it_value, (timr->it.mmtimer.expires - rtc_time())* sgi_clock_period);
+	return;
+}
+
+
+static int sgi_timer_set(struct k_itimer *timr, int flags,
+	struct itimerspec * new_setting,
+	struct itimerspec * old_setting)
+{
+
+	int i;
+	unsigned long when, period, irqflags;
+	int err = 0;
+	cnodeid_t nodeid;
+	mmtimer_t *base;
+
+	if (old_setting)
+		sgi_timer_get(timr, old_setting);
+
+	sgi_timer_del(timr);
+	when = timespec_to_ns(new_setting->it_value);
+	period = timespec_to_ns(new_setting->it_interval);
+
+	if (when == 0)
+		/* Clear timer */
+		return 0;
+
+	if (flags & TIMER_ABSTIME) {
+		struct timespec n;
+		unsigned long now;
+
+		getnstimeofday(&n);
+		now = timespec_to_ns(n);
+		if (when > now)
+			when -= now;
+		else
+			/* Fire the timer immediately */
+			when = 0;
+	}
+
+	/*
+	 * Convert to sgi clock period. Need to keep rtc_time() as near as possible
+	 * to getnstimeofday() in order to be as faithful as possible to the time
+	 * specified.
+	 */
+	when = (when + sgi_clock_period - 1) / sgi_clock_period + rtc_time();
+	period = (period + sgi_clock_period - 1)  / sgi_clock_period;
+
+	/*
+	 * We are allocating a local SHub comparator. If we would be moved to another
+	 * cpu then another SHub may be local to us. Prohibit that by switching off
+	 * preemption.
+	 */
+	preempt_disable();
+
+	nodeid =  cpuid_to_cnodeid(smp_processor_id());
+	base = timers + nodeid * NUM_COMPARATORS;
+retry:
+	/* Don't use an allocated timer, or a deleted one that's pending */
+	for(i = 0; i< NUM_COMPARATORS; i++) {
+		if (!base[i].timer && !base[i].tasklet.state) {
+			break;
+		}
+	}
+
+	if (i == NUM_COMPARATORS) {
+		preempt_enable();
+		return -EBUSY;
+	}
+
+	spin_lock_irqsave(&base[i].lock, irqflags);
+
+	if (base[i].timer || base[i].tasklet.state != 0) {
+		spin_unlock_irqrestore(&base[i].lock, irqflags);
+		goto retry;
+	}
+	base[i].timer = timr;
+	base[i].cpu = smp_processor_id();
+
+	timr->it.mmtimer.clock = i;
+	timr->it.mmtimer.node = nodeid;
+	timr->it.mmtimer.incr = period;
+	timr->it.mmtimer.expires = when;
+
+	if (period == 0) {
+		if (!mmtimer_setup(i, when)) {
+			mmtimer_disable_int(-1, i);
+			posix_timer_event(timr, 0);
+			timr->it.mmtimer.expires = 0;
+		}
+	} else {
+		timr->it.mmtimer.expires -= period;
+		if (reschedule_periodic_timer(base+i))
+			err = -EINVAL;
+	}
+
+	spin_unlock_irqrestore(&base[i].lock, irqflags);
+
+	preempt_enable();
+
+	return err;
+}
+
+static struct k_clock sgi_clock = {
+	.res = 0,
+	.clock_set = sgi_clock_set,
+	.clock_get = sgi_clock_get,
+	.timer_create = sgi_timer_create,
+	.nsleep = do_posix_clock_nonanosleep,
+	.timer_set = sgi_timer_set,
+	.timer_del = sgi_timer_del,
+	.timer_get = sgi_timer_get
+};
+
+/**
+ * mmtimer_init - device initialization routine
+ *
+ * Does initial setup for the mmtimer device.
+ */
+static int __init mmtimer_init(void)
+{
+	unsigned i;
+
+	if (!ia64_platform_is("sn2"))
+		return -1;
+
+	/*
+	 * Sanity check the cycles/sec variable
+	 */
+	if (sn_rtc_cycles_per_second < 100000) {
+		printk(KERN_ERR "%s: unable to determine clock frequency\n",
+		       MMTIMER_NAME);
+		return -1;
+	}
+
+	mmtimer_femtoperiod = ((unsigned long)1E15 + sn_rtc_cycles_per_second /
+			       2) / sn_rtc_cycles_per_second;
+
+	for (i=0; i< NUM_COMPARATORS*MAX_COMPACT_NODES; i++) {
+		spin_lock_init(&timers[i].lock);
+		timers[i].timer = NULL;
+		timers[i].cpu = 0;
+		timers[i].i = i % NUM_COMPARATORS;
+		tasklet_init(&timers[i].tasklet, mmtimer_tasklet, (unsigned long) (timers+i));
+	}
+
+	if (request_irq(SGI_MMTIMER_VECTOR, mmtimer_interrupt, SA_PERCPU_IRQ, MMTIMER_NAME, NULL)) {
+		printk(KERN_WARNING "%s: unable to allocate interrupt.",
+			MMTIMER_NAME);
+		return -1;
+	}
+
+	strcpy(mmtimer_miscdev.devfs_name, MMTIMER_NAME);
+	if (misc_register(&mmtimer_miscdev)) {
+		printk(KERN_ERR "%s: failed to register device\n",
+		       MMTIMER_NAME);
+		return -1;
+	}
+
+	sgi_clock_period = sgi_clock.res = NSEC_PER_SEC / sn_rtc_cycles_per_second;
+	register_posix_clock(CLOCK_SGI_CYCLE, &sgi_clock);
+
+	printk(KERN_INFO "%s: v%s, %ld MHz\n", MMTIMER_DESC, MMTIMER_VERSION,
+	       sn_rtc_cycles_per_second/(unsigned long)1E6);
+
+	return 0;
+}
+
+module_init(mmtimer_init);
+
diff --git a/drivers/char/moxa.c b/drivers/char/moxa.c
new file mode 100644
index 0000000..7c24fbe
--- /dev/null
+++ b/drivers/char/moxa.c
@@ -0,0 +1,3243 @@
+/*****************************************************************************/
+/*
+ *           moxa.c  -- MOXA Intellio family multiport serial driver.
+ *
+ *      Copyright (C) 1999-2000  Moxa Technologies (support@moxa.com.tw).
+ *
+ *      This code is loosely based on the Linux serial driver, written by
+ *      Linus Torvalds, Theodore T'so and others.
+ *
+ *      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.
+ */
+
+/*
+ *    MOXA Intellio Series Driver
+ *      for             : LINUX
+ *      date            : 1999/1/7
+ *      version         : 5.1
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/serial.h>
+#include <linux/tty_driver.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define		MOXA_VERSION		"5.1k"
+
+#define MOXAMAJOR       172
+#define MOXACUMAJOR     173
+
+#define put_to_user(arg1, arg2) put_user(arg1, (unsigned long *)arg2)
+#define get_from_user(arg1, arg2) get_user(arg1, (unsigned int *)arg2)
+
+#define MAX_BOARDS 		4	/* Don't change this value */
+#define MAX_PORTS_PER_BOARD	32	/* Don't change this value */
+#define MAX_PORTS 		128	/* Don't change this value */
+
+/*
+ *    Define the Moxa PCI vendor and device IDs.
+ */
+#define MOXA_BUS_TYPE_ISA		0
+#define MOXA_BUS_TYPE_PCI		1
+
+#ifndef	PCI_VENDOR_ID_MOXA
+#define	PCI_VENDOR_ID_MOXA	0x1393
+#endif
+#ifndef PCI_DEVICE_ID_CP204J
+#define PCI_DEVICE_ID_CP204J	0x2040
+#endif
+#ifndef PCI_DEVICE_ID_C218
+#define PCI_DEVICE_ID_C218	0x2180
+#endif
+#ifndef PCI_DEVICE_ID_C320
+#define PCI_DEVICE_ID_C320	0x3200
+#endif
+
+enum {
+	MOXA_BOARD_C218_PCI = 1,
+	MOXA_BOARD_C218_ISA,
+	MOXA_BOARD_C320_PCI,
+	MOXA_BOARD_C320_ISA,
+	MOXA_BOARD_CP204J,
+};
+
+static char *moxa_brdname[] =
+{
+	"C218 Turbo PCI series",
+	"C218 Turbo ISA series",
+	"C320 Turbo PCI series",
+	"C320 Turbo ISA series",
+	"CP-204J series",
+};
+
+#ifdef CONFIG_PCI
+static struct pci_device_id moxa_pcibrds[] = {
+	{ PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_C218, PCI_ANY_ID, PCI_ANY_ID, 
+	  0, 0, MOXA_BOARD_C218_PCI },
+	{ PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_C320, PCI_ANY_ID, PCI_ANY_ID, 
+	  0, 0, MOXA_BOARD_C320_PCI },
+	{ PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_CP204J, PCI_ANY_ID, PCI_ANY_ID, 
+	  0, 0, MOXA_BOARD_CP204J },
+	{ 0 }
+};
+MODULE_DEVICE_TABLE(pci, moxa_pcibrds);
+#endif /* CONFIG_PCI */
+
+typedef struct _moxa_isa_board_conf {
+	int boardType;
+	int numPorts;
+	unsigned long baseAddr;
+} moxa_isa_board_conf;
+
+static moxa_isa_board_conf moxa_isa_boards[] =
+{
+/*       {MOXA_BOARD_C218_ISA,8,0xDC000}, */
+};
+
+typedef struct _moxa_pci_devinfo {
+	ushort busNum;
+	ushort devNum;
+} moxa_pci_devinfo;
+
+typedef struct _moxa_board_conf {
+	int boardType;
+	int numPorts;
+	unsigned long baseAddr;
+	int busType;
+	moxa_pci_devinfo pciInfo;
+} moxa_board_conf;
+
+static moxa_board_conf moxa_boards[MAX_BOARDS];
+static void __iomem *moxaBaseAddr[MAX_BOARDS];
+
+struct moxa_str {
+	int type;
+	int port;
+	int close_delay;
+	unsigned short closing_wait;
+	int count;
+	int blocked_open;
+	long event; /* long req'd for set_bit --RR */
+	int asyncflags;
+	unsigned long statusflags;
+	struct tty_struct *tty;
+	int cflag;
+	wait_queue_head_t open_wait;
+	wait_queue_head_t close_wait;
+	struct work_struct tqueue;
+};
+
+struct mxser_mstatus {
+	tcflag_t cflag;
+	int cts;
+	int dsr;
+	int ri;
+	int dcd;
+};
+
+static struct mxser_mstatus GMStatus[MAX_PORTS];
+
+/* statusflags */
+#define TXSTOPPED	0x1
+#define LOWWAIT 	0x2
+#define EMPTYWAIT	0x4
+#define THROTTLE	0x8
+
+/* event */
+#define MOXA_EVENT_HANGUP	1
+
+#define SERIAL_DO_RESTART
+
+
+#define SERIAL_TYPE_NORMAL	1
+
+#define WAKEUP_CHARS		256
+
+#define PORTNO(x)		((x)->index)
+
+static int verbose = 0;
+static int ttymajor = MOXAMAJOR;
+/* Variables for insmod */
+#ifdef MODULE
+static int baseaddr[] 	= 	{0, 0, 0, 0};
+static int type[]	=	{0, 0, 0, 0};
+static int numports[] 	=	{0, 0, 0, 0};
+#endif
+
+MODULE_AUTHOR("William Chen");
+MODULE_DESCRIPTION("MOXA Intellio Family Multiport Board Device Driver");
+MODULE_LICENSE("GPL");
+#ifdef MODULE
+module_param_array(type, int, NULL, 0);
+module_param_array(baseaddr, int, NULL, 0);
+module_param_array(numports, int, NULL, 0);
+#endif
+module_param(ttymajor, int, 0);
+module_param(verbose, bool, 0644);
+
+static struct tty_driver *moxaDriver;
+static struct moxa_str moxaChannels[MAX_PORTS];
+static unsigned char *moxaXmitBuff;
+static int moxaTimer_on;
+static struct timer_list moxaTimer;
+static int moxaEmptyTimer_on[MAX_PORTS];
+static struct timer_list moxaEmptyTimer[MAX_PORTS];
+static struct semaphore moxaBuffSem;
+
+/*
+ * static functions:
+ */
+static void do_moxa_softint(void *);
+static int moxa_open(struct tty_struct *, struct file *);
+static void moxa_close(struct tty_struct *, struct file *);
+static int moxa_write(struct tty_struct *, const unsigned char *, int);
+static int moxa_write_room(struct tty_struct *);
+static void moxa_flush_buffer(struct tty_struct *);
+static int moxa_chars_in_buffer(struct tty_struct *);
+static void moxa_flush_chars(struct tty_struct *);
+static void moxa_put_char(struct tty_struct *, unsigned char);
+static int moxa_ioctl(struct tty_struct *, struct file *, unsigned int, unsigned long);
+static void moxa_throttle(struct tty_struct *);
+static void moxa_unthrottle(struct tty_struct *);
+static void moxa_set_termios(struct tty_struct *, struct termios *);
+static void moxa_stop(struct tty_struct *);
+static void moxa_start(struct tty_struct *);
+static void moxa_hangup(struct tty_struct *);
+static int moxa_tiocmget(struct tty_struct *tty, struct file *file);
+static int moxa_tiocmset(struct tty_struct *tty, struct file *file,
+			 unsigned int set, unsigned int clear);
+static void moxa_poll(unsigned long);
+static void set_tty_param(struct tty_struct *);
+static int block_till_ready(struct tty_struct *, struct file *,
+			    struct moxa_str *);
+static void setup_empty_event(struct tty_struct *);
+static void check_xmit_empty(unsigned long);
+static void shut_down(struct moxa_str *);
+static void receive_data(struct moxa_str *);
+/*
+ * moxa board interface functions:
+ */
+static void MoxaDriverInit(void);
+static int MoxaDriverIoctl(unsigned int, unsigned long, int);
+static int MoxaDriverPoll(void);
+static int MoxaPortsOfCard(int);
+static int MoxaPortIsValid(int);
+static void MoxaPortEnable(int);
+static void MoxaPortDisable(int);
+static long MoxaPortGetMaxBaud(int);
+static long MoxaPortSetBaud(int, long);
+static int MoxaPortSetTermio(int, struct termios *);
+static int MoxaPortGetLineOut(int, int *, int *);
+static void MoxaPortLineCtrl(int, int, int);
+static void MoxaPortFlowCtrl(int, int, int, int, int, int);
+static int MoxaPortLineStatus(int);
+static int MoxaPortDCDChange(int);
+static int MoxaPortDCDON(int);
+static void MoxaPortFlushData(int, int);
+static int MoxaPortWriteData(int, unsigned char *, int);
+static int MoxaPortReadData(int, unsigned char *, int);
+static int MoxaPortTxQueue(int);
+static int MoxaPortRxQueue(int);
+static int MoxaPortTxFree(int);
+static void MoxaPortTxDisable(int);
+static void MoxaPortTxEnable(int);
+static int MoxaPortResetBrkCnt(int);
+static void MoxaPortSendBreak(int, int);
+static int moxa_get_serial_info(struct moxa_str *, struct serial_struct __user *);
+static int moxa_set_serial_info(struct moxa_str *, struct serial_struct __user *);
+static void MoxaSetFifo(int port, int enable);
+
+static struct tty_operations moxa_ops = {
+	.open = moxa_open,
+	.close = moxa_close,
+	.write = moxa_write,
+	.write_room = moxa_write_room,
+	.flush_buffer = moxa_flush_buffer,
+	.chars_in_buffer = moxa_chars_in_buffer,
+	.flush_chars = moxa_flush_chars,
+	.put_char = moxa_put_char,
+	.ioctl = moxa_ioctl,
+	.throttle = moxa_throttle,
+	.unthrottle = moxa_unthrottle,
+	.set_termios = moxa_set_termios,
+	.stop = moxa_stop,
+	.start = moxa_start,
+	.hangup = moxa_hangup,
+	.tiocmget = moxa_tiocmget,
+	.tiocmset = moxa_tiocmset,
+};
+
+#ifdef CONFIG_PCI
+static int moxa_get_PCI_conf(struct pci_dev *p, int board_type, moxa_board_conf * board)
+{
+	board->baseAddr = pci_resource_start (p, 2);
+	board->boardType = board_type;
+	switch (board_type) {
+	case MOXA_BOARD_C218_ISA:
+	case MOXA_BOARD_C218_PCI:
+		board->numPorts = 8;
+		break;
+
+	case MOXA_BOARD_CP204J:
+		board->numPorts = 4;
+		break;
+	default:
+		board->numPorts = 0;
+		break;
+	}
+	board->busType = MOXA_BUS_TYPE_PCI;
+	board->pciInfo.busNum = p->bus->number;
+	board->pciInfo.devNum = p->devfn >> 3;
+
+	return (0);
+}
+#endif /* CONFIG_PCI */
+
+static int __init moxa_init(void)
+{
+	int i, numBoards;
+	struct moxa_str *ch;
+
+	printk(KERN_INFO "MOXA Intellio family driver version %s\n", MOXA_VERSION);
+	moxaDriver = alloc_tty_driver(MAX_PORTS + 1);
+	if (!moxaDriver)
+		return -ENOMEM;
+
+	init_MUTEX(&moxaBuffSem);
+	moxaDriver->owner = THIS_MODULE;
+	moxaDriver->name = "ttya";
+	moxaDriver->devfs_name = "tts/a";
+	moxaDriver->major = ttymajor;
+	moxaDriver->minor_start = 0;
+	moxaDriver->type = TTY_DRIVER_TYPE_SERIAL;
+	moxaDriver->subtype = SERIAL_TYPE_NORMAL;
+	moxaDriver->init_termios = tty_std_termios;
+	moxaDriver->init_termios.c_iflag = 0;
+	moxaDriver->init_termios.c_oflag = 0;
+	moxaDriver->init_termios.c_cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+	moxaDriver->init_termios.c_lflag = 0;
+	moxaDriver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(moxaDriver, &moxa_ops);
+
+	moxaXmitBuff = NULL;
+
+	for (i = 0, ch = moxaChannels; i < MAX_PORTS; i++, ch++) {
+		ch->type = PORT_16550A;
+		ch->port = i;
+		INIT_WORK(&ch->tqueue, do_moxa_softint, ch);
+		ch->tty = NULL;
+		ch->close_delay = 5 * HZ / 10;
+		ch->closing_wait = 30 * HZ;
+		ch->count = 0;
+		ch->blocked_open = 0;
+		ch->cflag = B9600 | CS8 | CREAD | CLOCAL | HUPCL;
+		init_waitqueue_head(&ch->open_wait);
+		init_waitqueue_head(&ch->close_wait);
+	}
+
+	for (i = 0; i < MAX_BOARDS; i++) {
+		moxa_boards[i].boardType = 0;
+		moxa_boards[i].numPorts = 0;
+		moxa_boards[i].baseAddr = 0;
+		moxa_boards[i].busType = 0;
+		moxa_boards[i].pciInfo.busNum = 0;
+		moxa_boards[i].pciInfo.devNum = 0;
+	}
+	MoxaDriverInit();
+	printk("Tty devices major number = %d\n", ttymajor);
+
+	if (tty_register_driver(moxaDriver)) {
+		printk(KERN_ERR "Couldn't install MOXA Smartio family driver !\n");
+		put_tty_driver(moxaDriver);
+		return -1;
+	}
+	for (i = 0; i < MAX_PORTS; i++) {
+		init_timer(&moxaEmptyTimer[i]);
+		moxaEmptyTimer[i].function = check_xmit_empty;
+		moxaEmptyTimer[i].data = (unsigned long) & moxaChannels[i];
+		moxaEmptyTimer_on[i] = 0;
+	}
+
+	init_timer(&moxaTimer);
+	moxaTimer.function = moxa_poll;
+	moxaTimer.expires = jiffies + (HZ / 50);
+	moxaTimer_on = 1;
+	add_timer(&moxaTimer);
+
+	/* Find the boards defined in source code */
+	numBoards = 0;
+	for (i = 0; i < MAX_BOARDS; i++) {
+		if ((moxa_isa_boards[i].boardType == MOXA_BOARD_C218_ISA) ||
+		 (moxa_isa_boards[i].boardType == MOXA_BOARD_C320_ISA)) {
+			moxa_boards[numBoards].boardType = moxa_isa_boards[i].boardType;
+			if (moxa_isa_boards[i].boardType == MOXA_BOARD_C218_ISA)
+				moxa_boards[numBoards].numPorts = 8;
+			else
+				moxa_boards[numBoards].numPorts = moxa_isa_boards[i].numPorts;
+			moxa_boards[numBoards].busType = MOXA_BUS_TYPE_ISA;
+			moxa_boards[numBoards].baseAddr = moxa_isa_boards[i].baseAddr;
+			if (verbose)
+				printk("Board %2d: %s board(baseAddr=%lx)\n",
+				       numBoards + 1,
+				       moxa_brdname[moxa_boards[numBoards].boardType - 1],
+				       moxa_boards[numBoards].baseAddr);
+			numBoards++;
+		}
+	}
+	/* Find the boards defined form module args. */
+#ifdef MODULE
+	for (i = 0; i < MAX_BOARDS; i++) {
+		if ((type[i] == MOXA_BOARD_C218_ISA) ||
+		    (type[i] == MOXA_BOARD_C320_ISA)) {
+			if (verbose)
+				printk("Board %2d: %s board(baseAddr=%lx)\n",
+				       numBoards + 1,
+				       moxa_brdname[type[i] - 1],
+				       (unsigned long) baseaddr[i]);
+			if (numBoards >= MAX_BOARDS) {
+				if (verbose)
+					printk("More than %d MOXA Intellio family boards found. Board is ignored.", MAX_BOARDS);
+				continue;
+			}
+			moxa_boards[numBoards].boardType = type[i];
+			if (moxa_isa_boards[i].boardType == MOXA_BOARD_C218_ISA)
+				moxa_boards[numBoards].numPorts = 8;
+			else
+				moxa_boards[numBoards].numPorts = numports[i];
+			moxa_boards[numBoards].busType = MOXA_BUS_TYPE_ISA;
+			moxa_boards[numBoards].baseAddr = baseaddr[i];
+			numBoards++;
+		}
+	}
+#endif
+	/* Find PCI boards here */
+#ifdef CONFIG_PCI
+	{
+		struct pci_dev *p = NULL;
+		int n = (sizeof(moxa_pcibrds) / sizeof(moxa_pcibrds[0])) - 1;
+		i = 0;
+		while (i < n) {
+			while ((p = pci_find_device(moxa_pcibrds[i].vendor, moxa_pcibrds[i].device, p))!=NULL)
+			{
+				if (pci_enable_device(p))
+					continue;
+				if (numBoards >= MAX_BOARDS) {
+					if (verbose)
+						printk("More than %d MOXA Intellio family boards found. Board is ignored.", MAX_BOARDS);
+				} else {
+					moxa_get_PCI_conf(p, moxa_pcibrds[i].driver_data,
+						&moxa_boards[numBoards]);
+					numBoards++;
+				}
+			}
+			i++;
+		}
+	}
+#endif
+	for (i = 0; i < numBoards; i++) {
+		moxaBaseAddr[i] = ioremap((unsigned long) moxa_boards[i].baseAddr, 0x4000);
+	}
+
+	return (0);
+}
+
+static void __exit moxa_exit(void)
+{
+	int i;
+
+	if (verbose)
+		printk("Unloading module moxa ...\n");
+
+	if (moxaTimer_on)
+		del_timer(&moxaTimer);
+
+	for (i = 0; i < MAX_PORTS; i++)
+		if (moxaEmptyTimer_on[i])
+			del_timer(&moxaEmptyTimer[i]);
+
+	if (tty_unregister_driver(moxaDriver))
+		printk("Couldn't unregister MOXA Intellio family serial driver\n");
+	put_tty_driver(moxaDriver);
+	if (verbose)
+		printk("Done\n");
+}
+
+module_init(moxa_init);
+module_exit(moxa_exit);
+
+static void do_moxa_softint(void *private_)
+{
+	struct moxa_str *ch = (struct moxa_str *) private_;
+	struct tty_struct *tty;
+
+	if (ch && (tty = ch->tty)) {
+		if (test_and_clear_bit(MOXA_EVENT_HANGUP, &ch->event)) {
+			tty_hangup(tty);	/* FIXME: module removal race here - AKPM */
+			wake_up_interruptible(&ch->open_wait);
+			ch->asyncflags &= ~ASYNC_NORMAL_ACTIVE;
+		}
+	}
+}
+
+static int moxa_open(struct tty_struct *tty, struct file *filp)
+{
+	struct moxa_str *ch;
+	int port;
+	int retval;
+	unsigned long page;
+
+	port = PORTNO(tty);
+	if (port == MAX_PORTS) {
+		return (0);
+	}
+	if (!MoxaPortIsValid(port)) {
+		tty->driver_data = NULL;
+		return (-ENODEV);
+	}
+	down(&moxaBuffSem);
+	if (!moxaXmitBuff) {
+		page = get_zeroed_page(GFP_KERNEL);
+		if (!page) {
+			up(&moxaBuffSem);
+			return (-ENOMEM);
+		}
+		/* This test is guarded by the BuffSem so no longer needed
+		   delete me in 2.5 */
+		if (moxaXmitBuff)
+			free_page(page);
+		else
+			moxaXmitBuff = (unsigned char *) page;
+	}
+	up(&moxaBuffSem);
+
+	ch = &moxaChannels[port];
+	ch->count++;
+	tty->driver_data = ch;
+	ch->tty = tty;
+	if (!(ch->asyncflags & ASYNC_INITIALIZED)) {
+		ch->statusflags = 0;
+		set_tty_param(tty);
+		MoxaPortLineCtrl(ch->port, 1, 1);
+		MoxaPortEnable(ch->port);
+		ch->asyncflags |= ASYNC_INITIALIZED;
+	}
+	retval = block_till_ready(tty, filp, ch);
+
+	moxa_unthrottle(tty);
+
+	if (ch->type == PORT_16550A) {
+		MoxaSetFifo(ch->port, 1);
+	} else {
+		MoxaSetFifo(ch->port, 0);
+	}
+
+	return (retval);
+}
+
+static void moxa_close(struct tty_struct *tty, struct file *filp)
+{
+	struct moxa_str *ch;
+	int port;
+
+	port = PORTNO(tty);
+	if (port == MAX_PORTS) {
+		return;
+	}
+	if (!MoxaPortIsValid(port)) {
+#ifdef SERIAL_DEBUG_CLOSE
+		printk("Invalid portno in moxa_close\n");
+#endif
+		tty->driver_data = NULL;
+		return;
+	}
+	if (tty->driver_data == NULL) {
+		return;
+	}
+	if (tty_hung_up_p(filp)) {
+		return;
+	}
+	ch = (struct moxa_str *) tty->driver_data;
+
+	if ((tty->count == 1) && (ch->count != 1)) {
+		printk("moxa_close: bad serial port count; tty->count is 1, "
+		       "ch->count is %d\n", ch->count);
+		ch->count = 1;
+	}
+	if (--ch->count < 0) {
+		printk("moxa_close: bad serial port count, device=%s\n",
+		       tty->name);
+		ch->count = 0;
+	}
+	if (ch->count) {
+		return;
+	}
+	ch->asyncflags |= ASYNC_CLOSING;
+
+	ch->cflag = tty->termios->c_cflag;
+	if (ch->asyncflags & ASYNC_INITIALIZED) {
+		setup_empty_event(tty);
+		tty_wait_until_sent(tty, 30 * HZ);	/* 30 seconds timeout */
+		moxaEmptyTimer_on[ch->port] = 0;
+		del_timer(&moxaEmptyTimer[ch->port]);
+	}
+	shut_down(ch);
+	MoxaPortFlushData(port, 2);
+
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+	tty_ldisc_flush(tty);
+			
+	tty->closing = 0;
+	ch->event = 0;
+	ch->tty = NULL;
+	if (ch->blocked_open) {
+		if (ch->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(ch->close_delay));
+		}
+		wake_up_interruptible(&ch->open_wait);
+	}
+	ch->asyncflags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
+	wake_up_interruptible(&ch->close_wait);
+}
+
+static int moxa_write(struct tty_struct *tty,
+		      const unsigned char *buf, int count)
+{
+	struct moxa_str *ch;
+	int len, port;
+	unsigned long flags;
+
+	ch = (struct moxa_str *) tty->driver_data;
+	if (ch == NULL)
+		return (0);
+	port = ch->port;
+	save_flags(flags);
+	cli();
+	len = MoxaPortWriteData(port, (unsigned char *) buf, count);
+	restore_flags(flags);
+
+	/*********************************************
+	if ( !(ch->statusflags & LOWWAIT) &&
+	     ((len != count) || (MoxaPortTxFree(port) <= 100)) )
+	************************************************/
+	ch->statusflags |= LOWWAIT;
+	return (len);
+}
+
+static int moxa_write_room(struct tty_struct *tty)
+{
+	struct moxa_str *ch;
+
+	if (tty->stopped)
+		return (0);
+	ch = (struct moxa_str *) tty->driver_data;
+	if (ch == NULL)
+		return (0);
+	return (MoxaPortTxFree(ch->port));
+}
+
+static void moxa_flush_buffer(struct tty_struct *tty)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+	if (ch == NULL)
+		return;
+	MoxaPortFlushData(ch->port, 1);
+	tty_wakeup(tty);
+}
+
+static int moxa_chars_in_buffer(struct tty_struct *tty)
+{
+	int chars;
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+	/*
+	 * Sigh...I have to check if driver_data is NULL here, because
+	 * if an open() fails, the TTY subsystem eventually calls
+	 * tty_wait_until_sent(), which calls the driver's chars_in_buffer()
+	 * routine.  And since the open() failed, we return 0 here.  TDJ
+	 */
+	if (ch == NULL)
+		return (0);
+	chars = MoxaPortTxQueue(ch->port);
+	if (chars) {
+		/*
+		 * Make it possible to wakeup anything waiting for output
+		 * in tty_ioctl.c, etc.
+		 */
+		if (!(ch->statusflags & EMPTYWAIT))
+			setup_empty_event(tty);
+	}
+	return (chars);
+}
+
+static void moxa_flush_chars(struct tty_struct *tty)
+{
+	/*
+	 * Don't think I need this, because this is called to empty the TX
+	 * buffer for the 16450, 16550, etc.
+	 */
+}
+
+static void moxa_put_char(struct tty_struct *tty, unsigned char c)
+{
+	struct moxa_str *ch;
+	int port;
+	unsigned long flags;
+
+	ch = (struct moxa_str *) tty->driver_data;
+	if (ch == NULL)
+		return;
+	port = ch->port;
+	save_flags(flags);
+	cli();
+	moxaXmitBuff[0] = c;
+	MoxaPortWriteData(port, moxaXmitBuff, 1);
+	restore_flags(flags);
+	/************************************************
+	if ( !(ch->statusflags & LOWWAIT) && (MoxaPortTxFree(port) <= 100) )
+	*************************************************/
+	ch->statusflags |= LOWWAIT;
+}
+
+static int moxa_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+	int port;
+	int flag = 0, dtr, rts;
+
+	port = PORTNO(tty);
+	if ((port != MAX_PORTS) && (!ch))
+		return (-EINVAL);
+
+	MoxaPortGetLineOut(ch->port, &dtr, &rts);
+	if (dtr)
+		flag |= TIOCM_DTR;
+	if (rts)
+		flag |= TIOCM_RTS;
+	dtr = MoxaPortLineStatus(ch->port);
+	if (dtr & 1)
+		flag |= TIOCM_CTS;
+	if (dtr & 2)
+		flag |= TIOCM_DSR;
+	if (dtr & 4)
+		flag |= TIOCM_CD;
+	return flag;
+}
+
+static int moxa_tiocmset(struct tty_struct *tty, struct file *file,
+			 unsigned int set, unsigned int clear)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+	int port;
+	int dtr, rts;
+
+	port = PORTNO(tty);
+	if ((port != MAX_PORTS) && (!ch))
+		return (-EINVAL);
+
+	MoxaPortGetLineOut(ch->port, &dtr, &rts);
+	if (set & TIOCM_RTS)
+		rts = 1;
+	if (set & TIOCM_DTR)
+		dtr = 1;
+	if (clear & TIOCM_RTS)
+		rts = 0;
+	if (clear & TIOCM_DTR)
+		dtr = 0;
+	MoxaPortLineCtrl(ch->port, dtr, rts);
+	return 0;
+}
+
+static int moxa_ioctl(struct tty_struct *tty, struct file *file,
+		      unsigned int cmd, unsigned long arg)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+	register int port;
+	void __user *argp = (void __user *)arg;
+	int retval;
+
+	port = PORTNO(tty);
+	if ((port != MAX_PORTS) && (!ch))
+		return (-EINVAL);
+
+	switch (cmd) {
+	case TCSBRK:		/* SVID version: non-zero arg --> no break */
+		retval = tty_check_change(tty);
+		if (retval)
+			return (retval);
+		setup_empty_event(tty);
+		tty_wait_until_sent(tty, 0);
+		if (!arg)
+			MoxaPortSendBreak(ch->port, 0);
+		return (0);
+	case TCSBRKP:		/* support for POSIX tcsendbreak() */
+		retval = tty_check_change(tty);
+		if (retval)
+			return (retval);
+		setup_empty_event(tty);
+		tty_wait_until_sent(tty, 0);
+		MoxaPortSendBreak(ch->port, arg);
+		return (0);
+	case TIOCGSOFTCAR:
+		return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) argp);
+	case TIOCSSOFTCAR:
+		if(get_user(retval, (unsigned long __user *) argp))
+			return -EFAULT;
+		arg = retval;
+		tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL) |
+					 (arg ? CLOCAL : 0));
+		if (C_CLOCAL(tty))
+			ch->asyncflags &= ~ASYNC_CHECK_CD;
+		else
+			ch->asyncflags |= ASYNC_CHECK_CD;
+		return (0);
+	case TIOCGSERIAL:
+		return moxa_get_serial_info(ch, argp);
+
+	case TIOCSSERIAL:
+		return moxa_set_serial_info(ch, argp);
+	default:
+		retval = MoxaDriverIoctl(cmd, arg, port);
+	}
+	return (retval);
+}
+
+static void moxa_throttle(struct tty_struct *tty)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+	ch->statusflags |= THROTTLE;
+}
+
+static void moxa_unthrottle(struct tty_struct *tty)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+	ch->statusflags &= ~THROTTLE;
+}
+
+static void moxa_set_termios(struct tty_struct *tty,
+			     struct termios *old_termios)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+	if (ch == NULL)
+		return;
+	set_tty_param(tty);
+	if (!(old_termios->c_cflag & CLOCAL) &&
+	    (tty->termios->c_cflag & CLOCAL))
+		wake_up_interruptible(&ch->open_wait);
+}
+
+static void moxa_stop(struct tty_struct *tty)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+	if (ch == NULL)
+		return;
+	MoxaPortTxDisable(ch->port);
+	ch->statusflags |= TXSTOPPED;
+}
+
+
+static void moxa_start(struct tty_struct *tty)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+	if (ch == NULL)
+		return;
+
+	if (!(ch->statusflags & TXSTOPPED))
+		return;
+
+	MoxaPortTxEnable(ch->port);
+	ch->statusflags &= ~TXSTOPPED;
+}
+
+static void moxa_hangup(struct tty_struct *tty)
+{
+	struct moxa_str *ch = (struct moxa_str *) tty->driver_data;
+
+	moxa_flush_buffer(tty);
+	shut_down(ch);
+	ch->event = 0;
+	ch->count = 0;
+	ch->asyncflags &= ~ASYNC_NORMAL_ACTIVE;
+	ch->tty = NULL;
+	wake_up_interruptible(&ch->open_wait);
+}
+
+static void moxa_poll(unsigned long ignored)
+{
+	register int card;
+	struct moxa_str *ch;
+	struct tty_struct *tp;
+	int i, ports;
+
+	moxaTimer_on = 0;
+	del_timer(&moxaTimer);
+
+	if (MoxaDriverPoll() < 0) {
+		moxaTimer.function = moxa_poll;
+		moxaTimer.expires = jiffies + (HZ / 50);
+		moxaTimer_on = 1;
+		add_timer(&moxaTimer);
+		return;
+	}
+	for (card = 0; card < MAX_BOARDS; card++) {
+		if ((ports = MoxaPortsOfCard(card)) <= 0)
+			continue;
+		ch = &moxaChannels[card * MAX_PORTS_PER_BOARD];
+		for (i = 0; i < ports; i++, ch++) {
+			if ((ch->asyncflags & ASYNC_INITIALIZED) == 0)
+				continue;
+			if (!(ch->statusflags & THROTTLE) &&
+			    (MoxaPortRxQueue(ch->port) > 0))
+				receive_data(ch);
+			if ((tp = ch->tty) == 0)
+				continue;
+			if (ch->statusflags & LOWWAIT) {
+				if (MoxaPortTxQueue(ch->port) <= WAKEUP_CHARS) {
+					if (!tp->stopped) {
+						ch->statusflags &= ~LOWWAIT;
+						tty_wakeup(tp);
+					}
+				}
+			}
+			if (!I_IGNBRK(tp) && (MoxaPortResetBrkCnt(ch->port) > 0)) {
+				tty_insert_flip_char(tp, 0, TTY_BREAK);
+				tty_schedule_flip(tp);
+			}
+			if (MoxaPortDCDChange(ch->port)) {
+				if (ch->asyncflags & ASYNC_CHECK_CD) {
+					if (MoxaPortDCDON(ch->port))
+						wake_up_interruptible(&ch->open_wait);
+					else {
+						set_bit(MOXA_EVENT_HANGUP, &ch->event);
+						schedule_work(&ch->tqueue);
+					}
+				}
+			}
+		}
+	}
+
+	moxaTimer.function = moxa_poll;
+	moxaTimer.expires = jiffies + (HZ / 50);
+	moxaTimer_on = 1;
+	add_timer(&moxaTimer);
+}
+
+/******************************************************************************/
+
+static void set_tty_param(struct tty_struct *tty)
+{
+	register struct termios *ts;
+	struct moxa_str *ch;
+	int rts, cts, txflow, rxflow, xany;
+
+	ch = (struct moxa_str *) tty->driver_data;
+	ts = tty->termios;
+	if (ts->c_cflag & CLOCAL)
+		ch->asyncflags &= ~ASYNC_CHECK_CD;
+	else
+		ch->asyncflags |= ASYNC_CHECK_CD;
+	rts = cts = txflow = rxflow = xany = 0;
+	if (ts->c_cflag & CRTSCTS)
+		rts = cts = 1;
+	if (ts->c_iflag & IXON)
+		txflow = 1;
+	if (ts->c_iflag & IXOFF)
+		rxflow = 1;
+	if (ts->c_iflag & IXANY)
+		xany = 1;
+	MoxaPortFlowCtrl(ch->port, rts, cts, txflow, rxflow, xany);
+	MoxaPortSetTermio(ch->port, ts);
+}
+
+static int block_till_ready(struct tty_struct *tty, struct file *filp,
+			    struct moxa_str *ch)
+{
+	DECLARE_WAITQUEUE(wait,current);
+	unsigned long flags;
+	int retval;
+	int do_clocal = C_CLOCAL(tty);
+
+	/*
+	 * 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) || (ch->asyncflags & ASYNC_CLOSING)) {
+		if (ch->asyncflags & ASYNC_CLOSING)
+			interruptible_sleep_on(&ch->close_wait);
+#ifdef SERIAL_DO_RESTART
+		if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+			return (-EAGAIN);
+		else
+			return (-ERESTARTSYS);
+#else
+		return (-EAGAIN);
+#endif
+	}
+	/*
+	 * If non-blocking mode is set, then make the check up front
+	 * and then exit.
+	 */
+	if (filp->f_flags & O_NONBLOCK) {
+		ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+		return (0);
+	}
+	/*
+	 * Block waiting for the carrier detect and the line to become free
+	 */
+	retval = 0;
+	add_wait_queue(&ch->open_wait, &wait);
+#ifdef SERIAL_DEBUG_OPEN
+	printk("block_til_ready before block: ttys%d, count = %d\n",
+	       ch->line, ch->count);
+#endif
+	save_flags(flags);
+	cli();
+	if (!tty_hung_up_p(filp))
+		ch->count--;
+	restore_flags(flags);
+	ch->blocked_open++;
+	while (1) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) ||
+		    !(ch->asyncflags & ASYNC_INITIALIZED)) {
+#ifdef SERIAL_DO_RESTART
+			if (ch->asyncflags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;
+#else
+			retval = -EAGAIN;
+#endif
+			break;
+		}
+		if (!(ch->asyncflags & ASYNC_CLOSING) && (do_clocal ||
+						MoxaPortDCDON(ch->port)))
+			break;
+
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();
+	}
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&ch->open_wait, &wait);
+	if (!tty_hung_up_p(filp))
+		ch->count++;
+	ch->blocked_open--;
+#ifdef SERIAL_DEBUG_OPEN
+	printk("block_til_ready after blocking: ttys%d, count = %d\n",
+	       ch->line, ch->count);
+#endif
+	if (retval)
+		return (retval);
+	ch->asyncflags |= ASYNC_NORMAL_ACTIVE;
+	return (0);
+}
+
+static void setup_empty_event(struct tty_struct *tty)
+{
+	struct moxa_str *ch = tty->driver_data;
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	ch->statusflags |= EMPTYWAIT;
+	moxaEmptyTimer_on[ch->port] = 0;
+	del_timer(&moxaEmptyTimer[ch->port]);
+	moxaEmptyTimer[ch->port].expires = jiffies + HZ;
+	moxaEmptyTimer_on[ch->port] = 1;
+	add_timer(&moxaEmptyTimer[ch->port]);
+	restore_flags(flags);
+}
+
+static void check_xmit_empty(unsigned long data)
+{
+	struct moxa_str *ch;
+
+	ch = (struct moxa_str *) data;
+	moxaEmptyTimer_on[ch->port] = 0;
+	del_timer(&moxaEmptyTimer[ch->port]);
+	if (ch->tty && (ch->statusflags & EMPTYWAIT)) {
+		if (MoxaPortTxQueue(ch->port) == 0) {
+			ch->statusflags &= ~EMPTYWAIT;
+			tty_wakeup(ch->tty);
+			return;
+		}
+		moxaEmptyTimer[ch->port].expires = jiffies + HZ;
+		moxaEmptyTimer_on[ch->port] = 1;
+		add_timer(&moxaEmptyTimer[ch->port]);
+	} else
+		ch->statusflags &= ~EMPTYWAIT;
+}
+
+static void shut_down(struct moxa_str *ch)
+{
+	struct tty_struct *tp;
+
+	if (!(ch->asyncflags & ASYNC_INITIALIZED))
+		return;
+
+	tp = ch->tty;
+
+	MoxaPortDisable(ch->port);
+
+	/*
+	 * If we're a modem control device and HUPCL is on, drop RTS & DTR.
+	 */
+	if (tp->termios->c_cflag & HUPCL)
+		MoxaPortLineCtrl(ch->port, 0, 0);
+
+	ch->asyncflags &= ~ASYNC_INITIALIZED;
+}
+
+static void receive_data(struct moxa_str *ch)
+{
+	struct tty_struct *tp;
+	struct termios *ts;
+	int i, count, rc, space;
+	unsigned char *charptr, *flagptr;
+	unsigned long flags;
+
+	ts = NULL;
+	tp = ch->tty;
+	if (tp)
+		ts = tp->termios;
+	/**************************************************
+	if ( !tp || !ts || !(ts->c_cflag & CREAD) ) {
+	*****************************************************/
+	if (!tp || !ts) {
+		MoxaPortFlushData(ch->port, 0);
+		return;
+	}
+	space = TTY_FLIPBUF_SIZE - tp->flip.count;
+	if (space <= 0)
+		return;
+	charptr = tp->flip.char_buf_ptr;
+	flagptr = tp->flip.flag_buf_ptr;
+	rc = tp->flip.count;
+	save_flags(flags);
+	cli();
+	count = MoxaPortReadData(ch->port, charptr, space);
+	restore_flags(flags);
+	for (i = 0; i < count; i++)
+		*flagptr++ = 0;
+	charptr += count;
+	rc += count;
+	tp->flip.count = rc;
+	tp->flip.char_buf_ptr = charptr;
+	tp->flip.flag_buf_ptr = flagptr;
+	tty_schedule_flip(ch->tty);
+}
+
+#define Magic_code	0x404
+
+/*
+ *    System Configuration
+ */
+/*
+ *    for C218 BIOS initialization
+ */
+#define C218_ConfBase	0x800
+#define C218_status	(C218_ConfBase + 0)	/* BIOS running status    */
+#define C218_diag	(C218_ConfBase + 2)	/* diagnostic status      */
+#define C218_key	(C218_ConfBase + 4)	/* WORD (0x218 for C218) */
+#define C218DLoad_len	(C218_ConfBase + 6)	/* WORD           */
+#define C218check_sum	(C218_ConfBase + 8)	/* BYTE           */
+#define C218chksum_ok	(C218_ConfBase + 0x0a)	/* BYTE (1:ok)            */
+#define C218_TestRx	(C218_ConfBase + 0x10)	/* 8 bytes for 8 ports    */
+#define C218_TestTx	(C218_ConfBase + 0x18)	/* 8 bytes for 8 ports    */
+#define C218_RXerr	(C218_ConfBase + 0x20)	/* 8 bytes for 8 ports    */
+#define C218_ErrFlag	(C218_ConfBase + 0x28)	/* 8 bytes for 8 ports    */
+
+#define C218_LoadBuf	0x0F00
+#define C218_KeyCode	0x218
+#define CP204J_KeyCode	0x204
+
+/*
+ *    for C320 BIOS initialization
+ */
+#define C320_ConfBase	0x800
+#define C320_LoadBuf	0x0f00
+#define STS_init	0x05	/* for C320_status        */
+
+#define C320_status	C320_ConfBase + 0	/* BIOS running status    */
+#define C320_diag	C320_ConfBase + 2	/* diagnostic status      */
+#define C320_key	C320_ConfBase + 4	/* WORD (0320H for C320) */
+#define C320DLoad_len	C320_ConfBase + 6	/* WORD           */
+#define C320check_sum	C320_ConfBase + 8	/* WORD           */
+#define C320chksum_ok	C320_ConfBase + 0x0a	/* WORD (1:ok)            */
+#define C320bapi_len	C320_ConfBase + 0x0c	/* WORD           */
+#define C320UART_no	C320_ConfBase + 0x0e	/* WORD           */
+
+#define C320_KeyCode	0x320
+
+#define FixPage_addr	0x0000	/* starting addr of static page  */
+#define DynPage_addr	0x2000	/* starting addr of dynamic page */
+#define C218_start	0x3000	/* starting addr of C218 BIOS prg */
+#define Control_reg	0x1ff0	/* select page and reset control */
+#define HW_reset	0x80
+
+/*
+ *    Function Codes
+ */
+#define FC_CardReset	0x80
+#define FC_ChannelReset 1	/* C320 firmware not supported */
+#define FC_EnableCH	2
+#define FC_DisableCH	3
+#define FC_SetParam	4
+#define FC_SetMode	5
+#define FC_SetRate	6
+#define FC_LineControl	7
+#define FC_LineStatus	8
+#define FC_XmitControl	9
+#define FC_FlushQueue	10
+#define FC_SendBreak	11
+#define FC_StopBreak	12
+#define FC_LoopbackON	13
+#define FC_LoopbackOFF	14
+#define FC_ClrIrqTable	15
+#define FC_SendXon	16
+#define FC_SetTermIrq	17	/* C320 firmware not supported */
+#define FC_SetCntIrq	18	/* C320 firmware not supported */
+#define FC_SetBreakIrq	19
+#define FC_SetLineIrq	20
+#define FC_SetFlowCtl	21
+#define FC_GenIrq	22
+#define FC_InCD180	23
+#define FC_OutCD180	24
+#define FC_InUARTreg	23
+#define FC_OutUARTreg	24
+#define FC_SetXonXoff	25
+#define FC_OutCD180CCR	26
+#define FC_ExtIQueue	27
+#define FC_ExtOQueue	28
+#define FC_ClrLineIrq	29
+#define FC_HWFlowCtl	30
+#define FC_GetClockRate 35
+#define FC_SetBaud	36
+#define FC_SetDataMode  41
+#define FC_GetCCSR      43
+#define FC_GetDataError 45
+#define FC_RxControl	50
+#define FC_ImmSend	51
+#define FC_SetXonState	52
+#define FC_SetXoffState	53
+#define FC_SetRxFIFOTrig 54
+#define FC_SetTxFIFOCnt 55
+#define FC_UnixRate	56
+#define FC_UnixResetTimer 57
+
+#define	RxFIFOTrig1	0
+#define	RxFIFOTrig4	1
+#define	RxFIFOTrig8	2
+#define	RxFIFOTrig14	3
+
+/*
+ *    Dual-Ported RAM
+ */
+#define DRAM_global	0
+#define INT_data	(DRAM_global + 0)
+#define Config_base	(DRAM_global + 0x108)
+
+#define IRQindex	(INT_data + 0)
+#define IRQpending	(INT_data + 4)
+#define IRQtable	(INT_data + 8)
+
+/*
+ *    Interrupt Status
+ */
+#define IntrRx		0x01	/* receiver data O.K.             */
+#define IntrTx		0x02	/* transmit buffer empty  */
+#define IntrFunc	0x04	/* function complete              */
+#define IntrBreak	0x08	/* received break         */
+#define IntrLine	0x10	/* line status change
+				   for transmitter                */
+#define IntrIntr	0x20	/* received INTR code             */
+#define IntrQuit	0x40	/* received QUIT code             */
+#define IntrEOF 	0x80	/* received EOF code              */
+
+#define IntrRxTrigger 	0x100	/* rx data count reach tigger value */
+#define IntrTxTrigger 	0x200	/* tx data count below trigger value */
+
+#define Magic_no	(Config_base + 0)
+#define Card_model_no	(Config_base + 2)
+#define Total_ports	(Config_base + 4)
+#define Module_cnt	(Config_base + 8)
+#define Module_no	(Config_base + 10)
+#define Timer_10ms	(Config_base + 14)
+#define Disable_IRQ	(Config_base + 20)
+#define TMS320_PORT1	(Config_base + 22)
+#define TMS320_PORT2	(Config_base + 24)
+#define TMS320_CLOCK	(Config_base + 26)
+
+/*
+ *    DATA BUFFER in DRAM
+ */
+#define Extern_table	0x400	/* Base address of the external table
+				   (24 words *    64) total 3K bytes
+				   (24 words * 128) total 6K bytes */
+#define Extern_size	0x60	/* 96 bytes                       */
+#define RXrptr		0x00	/* read pointer for RX buffer     */
+#define RXwptr		0x02	/* write pointer for RX buffer    */
+#define TXrptr		0x04	/* read pointer for TX buffer     */
+#define TXwptr		0x06	/* write pointer for TX buffer    */
+#define HostStat	0x08	/* IRQ flag and general flag      */
+#define FlagStat	0x0A
+#define FlowControl	0x0C	/* B7 B6 B5 B4 B3 B2 B1 B0              */
+					/*  x  x  x  x  |  |  |  |            */
+					/*              |  |  |  + CTS flow   */
+					/*              |  |  +--- RTS flow   */
+					/*              |  +------ TX Xon/Xoff */
+					/*              +--------- RX Xon/Xoff */
+#define Break_cnt	0x0E	/* received break count   */
+#define CD180TXirq	0x10	/* if non-0: enable TX irq        */
+#define RX_mask 	0x12
+#define TX_mask 	0x14
+#define Ofs_rxb 	0x16
+#define Ofs_txb 	0x18
+#define Page_rxb	0x1A
+#define Page_txb	0x1C
+#define EndPage_rxb	0x1E
+#define EndPage_txb	0x20
+#define Data_error	0x22
+#define RxTrigger	0x28
+#define TxTrigger	0x2a
+
+#define rRXwptr 	0x34
+#define Low_water	0x36
+
+#define FuncCode	0x40
+#define FuncArg 	0x42
+#define FuncArg1	0x44
+
+#define C218rx_size	0x2000	/* 8K bytes */
+#define C218tx_size	0x8000	/* 32K bytes */
+
+#define C218rx_mask	(C218rx_size - 1)
+#define C218tx_mask	(C218tx_size - 1)
+
+#define C320p8rx_size	0x2000
+#define C320p8tx_size	0x8000
+#define C320p8rx_mask	(C320p8rx_size - 1)
+#define C320p8tx_mask	(C320p8tx_size - 1)
+
+#define C320p16rx_size	0x2000
+#define C320p16tx_size	0x4000
+#define C320p16rx_mask	(C320p16rx_size - 1)
+#define C320p16tx_mask	(C320p16tx_size - 1)
+
+#define C320p24rx_size	0x2000
+#define C320p24tx_size	0x2000
+#define C320p24rx_mask	(C320p24rx_size - 1)
+#define C320p24tx_mask	(C320p24tx_size - 1)
+
+#define C320p32rx_size	0x1000
+#define C320p32tx_size	0x1000
+#define C320p32rx_mask	(C320p32rx_size - 1)
+#define C320p32tx_mask	(C320p32tx_size - 1)
+
+#define Page_size	0x2000
+#define Page_mask	(Page_size - 1)
+#define C218rx_spage	3
+#define C218tx_spage	4
+#define C218rx_pageno	1
+#define C218tx_pageno	4
+#define C218buf_pageno	5
+
+#define C320p8rx_spage	3
+#define C320p8tx_spage	4
+#define C320p8rx_pgno	1
+#define C320p8tx_pgno	4
+#define C320p8buf_pgno	5
+
+#define C320p16rx_spage 3
+#define C320p16tx_spage 4
+#define C320p16rx_pgno	1
+#define C320p16tx_pgno	2
+#define C320p16buf_pgno 3
+
+#define C320p24rx_spage 3
+#define C320p24tx_spage 4
+#define C320p24rx_pgno	1
+#define C320p24tx_pgno	1
+#define C320p24buf_pgno 2
+
+#define C320p32rx_spage 3
+#define C320p32tx_ofs	C320p32rx_size
+#define C320p32tx_spage 3
+#define C320p32buf_pgno 1
+
+/*
+ *    Host Status
+ */
+#define WakeupRx	0x01
+#define WakeupTx	0x02
+#define WakeupBreak	0x08
+#define WakeupLine	0x10
+#define WakeupIntr	0x20
+#define WakeupQuit	0x40
+#define WakeupEOF	0x80	/* used in VTIME control */
+#define WakeupRxTrigger	0x100
+#define WakeupTxTrigger	0x200
+/*
+ *    Flag status
+ */
+#define Rx_over		0x01
+#define Xoff_state	0x02
+#define Tx_flowOff	0x04
+#define Tx_enable	0x08
+#define CTS_state	0x10
+#define DSR_state	0x20
+#define DCD_state	0x80
+/*
+ *    FlowControl
+ */
+#define CTS_FlowCtl	1
+#define RTS_FlowCtl	2
+#define Tx_FlowCtl	4
+#define Rx_FlowCtl	8
+#define IXM_IXANY	0x10
+
+#define LowWater	128
+
+#define DTR_ON		1
+#define RTS_ON		2
+#define CTS_ON		1
+#define DSR_ON		2
+#define DCD_ON		8
+
+/* mode definition */
+#define	MX_CS8		0x03
+#define	MX_CS7		0x02
+#define	MX_CS6		0x01
+#define	MX_CS5		0x00
+
+#define	MX_STOP1	0x00
+#define	MX_STOP15	0x04
+#define	MX_STOP2	0x08
+
+#define	MX_PARNONE	0x00
+#define	MX_PAREVEN	0x40
+#define	MX_PARODD	0xC0
+
+/*
+ *    Query
+ */
+#define QueryPort	MAX_PORTS
+
+
+
+struct mon_str {
+	int tick;
+	int rxcnt[MAX_PORTS];
+	int txcnt[MAX_PORTS];
+};
+typedef struct mon_str mon_st;
+
+#define 	DCD_changed	0x01
+#define 	DCD_oldstate	0x80
+
+static unsigned char moxaBuff[10240];
+static void __iomem *moxaIntNdx[MAX_BOARDS];
+static void __iomem *moxaIntPend[MAX_BOARDS];
+static void __iomem *moxaIntTable[MAX_BOARDS];
+static char moxaChkPort[MAX_PORTS];
+static char moxaLineCtrl[MAX_PORTS];
+static void __iomem *moxaTableAddr[MAX_PORTS];
+static long moxaCurBaud[MAX_PORTS];
+static char moxaDCDState[MAX_PORTS];
+static char moxaLowChkFlag[MAX_PORTS];
+static int moxaLowWaterChk;
+static int moxaCard;
+static mon_st moxaLog;
+static int moxaFuncTout;
+static ushort moxaBreakCnt[MAX_PORTS];
+
+static void moxadelay(int);
+static void moxafunc(void __iomem *, int, ushort);
+static void wait_finish(void __iomem *);
+static void low_water_check(void __iomem *);
+static int moxaloadbios(int, unsigned char __user *, int);
+static int moxafindcard(int);
+static int moxaload320b(int, unsigned char __user *, int);
+static int moxaloadcode(int, unsigned char __user *, int);
+static int moxaloadc218(int, void __iomem *, int);
+static int moxaloadc320(int, void __iomem *, int, int *);
+
+/*****************************************************************************
+ *	Driver level functions: 					     *
+ *	1. MoxaDriverInit(void);					     *
+ *	2. MoxaDriverIoctl(unsigned int cmd, unsigned long arg, int port);   *
+ *	3. MoxaDriverPoll(void);					     *
+ *****************************************************************************/
+void MoxaDriverInit(void)
+{
+	int i;
+
+	moxaFuncTout = HZ / 2;	/* 500 mini-seconds */
+	moxaCard = 0;
+	moxaLog.tick = 0;
+	moxaLowWaterChk = 0;
+	for (i = 0; i < MAX_PORTS; i++) {
+		moxaChkPort[i] = 0;
+		moxaLowChkFlag[i] = 0;
+		moxaLineCtrl[i] = 0;
+		moxaLog.rxcnt[i] = 0;
+		moxaLog.txcnt[i] = 0;
+	}
+}
+
+#define	MOXA		0x400
+#define MOXA_GET_IQUEUE 	(MOXA + 1)	/* get input buffered count */
+#define MOXA_GET_OQUEUE 	(MOXA + 2)	/* get output buffered count */
+#define MOXA_INIT_DRIVER	(MOXA + 6)	/* moxaCard=0 */
+#define MOXA_LOAD_BIOS		(MOXA + 9)	/* download BIOS */
+#define MOXA_FIND_BOARD		(MOXA + 10)	/* Check if MOXA card exist? */
+#define MOXA_LOAD_C320B		(MOXA + 11)	/* download 320B firmware */
+#define MOXA_LOAD_CODE		(MOXA + 12)	/* download firmware */
+#define MOXA_GETDATACOUNT       (MOXA + 23)
+#define MOXA_GET_IOQUEUE	(MOXA + 27)
+#define MOXA_FLUSH_QUEUE	(MOXA + 28)
+#define MOXA_GET_CONF		(MOXA + 35)	/* configuration */
+#define MOXA_GET_MAJOR          (MOXA + 63)
+#define MOXA_GET_CUMAJOR        (MOXA + 64)
+#define MOXA_GETMSTATUS         (MOXA + 65)
+
+
+struct moxaq_str {
+	int inq;
+	int outq;
+};
+
+struct dl_str {
+	char __user *buf;
+	int len;
+	int cardno;
+};
+
+static struct moxaq_str temp_queue[MAX_PORTS];
+static struct dl_str dltmp;
+
+void MoxaPortFlushData(int port, int mode)
+{
+	void __iomem *ofsAddr;
+	if ((mode < 0) || (mode > 2))
+		return;
+	ofsAddr = moxaTableAddr[port];
+	moxafunc(ofsAddr, FC_FlushQueue, mode);
+	if (mode != 1) {
+		moxaLowChkFlag[port] = 0;
+		low_water_check(ofsAddr);
+	}
+}
+
+int MoxaDriverIoctl(unsigned int cmd, unsigned long arg, int port)
+{
+	int i;
+	int status;
+	int MoxaPortTxQueue(int), MoxaPortRxQueue(int);
+	void __user *argp = (void __user *)arg;
+
+	if (port == QueryPort) {
+		if ((cmd != MOXA_GET_CONF) && (cmd != MOXA_INIT_DRIVER) &&
+		    (cmd != MOXA_LOAD_BIOS) && (cmd != MOXA_FIND_BOARD) && (cmd != MOXA_LOAD_C320B) &&
+		 (cmd != MOXA_LOAD_CODE) && (cmd != MOXA_GETDATACOUNT) &&
+		  (cmd != MOXA_GET_IOQUEUE) && (cmd != MOXA_GET_MAJOR) &&
+		    (cmd != MOXA_GET_CUMAJOR) && (cmd != MOXA_GETMSTATUS))
+			return (-EINVAL);
+	}
+	switch (cmd) {
+	case MOXA_GET_CONF:
+		if(copy_to_user(argp, &moxa_boards, MAX_BOARDS * sizeof(moxa_board_conf)))
+			return -EFAULT;
+		return (0);
+	case MOXA_INIT_DRIVER:
+		if ((int) arg == 0x404)
+			MoxaDriverInit();
+		return (0);
+	case MOXA_GETDATACOUNT:
+		moxaLog.tick = jiffies;
+		if(copy_to_user(argp, &moxaLog, sizeof(mon_st)))
+			return -EFAULT;
+		return (0);
+	case MOXA_FLUSH_QUEUE:
+		MoxaPortFlushData(port, arg);
+		return (0);
+	case MOXA_GET_IOQUEUE:
+		for (i = 0; i < MAX_PORTS; i++) {
+			if (moxaChkPort[i]) {
+				temp_queue[i].inq = MoxaPortRxQueue(i);
+				temp_queue[i].outq = MoxaPortTxQueue(i);
+			}
+		}
+		if(copy_to_user(argp, temp_queue, sizeof(struct moxaq_str) * MAX_PORTS))
+			return -EFAULT;
+		return (0);
+	case MOXA_GET_OQUEUE:
+		i = MoxaPortTxQueue(port);
+		return put_user(i, (unsigned long __user *)argp);
+	case MOXA_GET_IQUEUE:
+		i = MoxaPortRxQueue(port);
+		return put_user(i, (unsigned long __user *)argp);
+	case MOXA_GET_MAJOR:
+		if(copy_to_user(argp, &ttymajor, sizeof(int)))
+			return -EFAULT;
+		return 0;
+	case MOXA_GET_CUMAJOR:
+		i = 0;
+		if(copy_to_user(argp, &i, sizeof(int)))
+			return -EFAULT;
+		return 0;
+	case MOXA_GETMSTATUS:
+		for (i = 0; i < MAX_PORTS; i++) {
+			GMStatus[i].ri = 0;
+			GMStatus[i].dcd = 0;
+			GMStatus[i].dsr = 0;
+			GMStatus[i].cts = 0;
+			if (!moxaChkPort[i]) {
+				continue;
+			} else {
+				status = MoxaPortLineStatus(moxaChannels[i].port);
+				if (status & 1)
+					GMStatus[i].cts = 1;
+				if (status & 2)
+					GMStatus[i].dsr = 1;
+				if (status & 4)
+					GMStatus[i].dcd = 1;
+			}
+
+			if (!moxaChannels[i].tty || !moxaChannels[i].tty->termios)
+				GMStatus[i].cflag = moxaChannels[i].cflag;
+			else
+				GMStatus[i].cflag = moxaChannels[i].tty->termios->c_cflag;
+		}
+		if(copy_to_user(argp, GMStatus, sizeof(struct mxser_mstatus) * MAX_PORTS))
+			return -EFAULT;
+		return 0;
+	default:
+		return (-ENOIOCTLCMD);
+	case MOXA_LOAD_BIOS:
+	case MOXA_FIND_BOARD:
+	case MOXA_LOAD_C320B:
+	case MOXA_LOAD_CODE:
+		break;
+	}
+
+	if(copy_from_user(&dltmp, argp, sizeof(struct dl_str)))
+		return -EFAULT;
+	if(dltmp.cardno < 0 || dltmp.cardno >= MAX_BOARDS)
+		return -EINVAL;
+
+	switch(cmd)
+	{
+	case MOXA_LOAD_BIOS:
+		i = moxaloadbios(dltmp.cardno, dltmp.buf, dltmp.len);
+		return (i);
+	case MOXA_FIND_BOARD:
+		return moxafindcard(dltmp.cardno);
+	case MOXA_LOAD_C320B:
+		moxaload320b(dltmp.cardno, dltmp.buf, dltmp.len);
+	default: /* to keep gcc happy */
+		return (0);
+	case MOXA_LOAD_CODE:
+		i = moxaloadcode(dltmp.cardno, dltmp.buf, dltmp.len);
+		if (i == -1)
+			return (-EFAULT);
+		return (i);
+
+	}
+}
+
+int MoxaDriverPoll(void)
+{
+	register ushort temp;
+	register int card;
+	void __iomem *ofsAddr;
+	void __iomem *ip;
+	int port, p, ports;
+
+	if (moxaCard == 0)
+		return (-1);
+	for (card = 0; card < MAX_BOARDS; card++) {
+		if ((ports = moxa_boards[card].numPorts) == 0)
+			continue;
+		if (readb(moxaIntPend[card]) == 0xff) {
+			ip = moxaIntTable[card] + readb(moxaIntNdx[card]);
+			p = card * MAX_PORTS_PER_BOARD;
+			ports <<= 1;
+			for (port = 0; port < ports; port += 2, p++) {
+				if ((temp = readw(ip + port)) != 0) {
+					writew(0, ip + port);
+					ofsAddr = moxaTableAddr[p];
+					if (temp & IntrTx)
+						writew(readw(ofsAddr + HostStat) & ~WakeupTx, ofsAddr + HostStat);
+					if (temp & IntrBreak) {
+						moxaBreakCnt[p]++;
+					}
+					if (temp & IntrLine) {
+						if (readb(ofsAddr + FlagStat) & DCD_state) {
+							if ((moxaDCDState[p] & DCD_oldstate) == 0)
+								moxaDCDState[p] = (DCD_oldstate |
+										   DCD_changed);
+						} else {
+							if (moxaDCDState[p] & DCD_oldstate)
+								moxaDCDState[p] = DCD_changed;
+						}
+					}
+				}
+			}
+			writeb(0, moxaIntPend[card]);
+		}
+		if (moxaLowWaterChk) {
+			p = card * MAX_PORTS_PER_BOARD;
+			for (port = 0; port < ports; port++, p++) {
+				if (moxaLowChkFlag[p]) {
+					moxaLowChkFlag[p] = 0;
+					ofsAddr = moxaTableAddr[p];
+					low_water_check(ofsAddr);
+				}
+			}
+		}
+	}
+	moxaLowWaterChk = 0;
+	return (0);
+}
+
+/*****************************************************************************
+ *	Card level function:						     *
+ *	1. MoxaPortsOfCard(int cardno); 				     *
+ *****************************************************************************/
+int MoxaPortsOfCard(int cardno)
+{
+
+	if (moxa_boards[cardno].boardType == 0)
+		return (0);
+	return (moxa_boards[cardno].numPorts);
+}
+
+/*****************************************************************************
+ *	Port level functions:						     *
+ *	1.  MoxaPortIsValid(int port);					     *
+ *	2.  MoxaPortEnable(int port);					     *
+ *	3.  MoxaPortDisable(int port);					     *
+ *	4.  MoxaPortGetMaxBaud(int port);				     *
+ *	5.  MoxaPortGetCurBaud(int port);				     *
+ *	6.  MoxaPortSetBaud(int port, long baud);			     *
+ *	7.  MoxaPortSetMode(int port, int databit, int stopbit, int parity); *
+ *	8.  MoxaPortSetTermio(int port, unsigned char *termio); 	     *
+ *	9.  MoxaPortGetLineOut(int port, int *dtrState, int *rtsState);      *
+ *	10. MoxaPortLineCtrl(int port, int dtrState, int rtsState);	     *
+ *	11. MoxaPortFlowCtrl(int port, int rts, int cts, int rx, int tx,int xany);    *
+ *	12. MoxaPortLineStatus(int port);				     *
+ *	13. MoxaPortDCDChange(int port);				     *
+ *	14. MoxaPortDCDON(int port);					     *
+ *	15. MoxaPortFlushData(int port, int mode);	                     *
+ *	16. MoxaPortWriteData(int port, unsigned char * buffer, int length); *
+ *	17. MoxaPortReadData(int port, unsigned char * buffer, int length);  *
+ *	18. MoxaPortTxBufSize(int port);				     *
+ *	19. MoxaPortRxBufSize(int port);				     *
+ *	20. MoxaPortTxQueue(int port);					     *
+ *	21. MoxaPortTxFree(int port);					     *
+ *	22. MoxaPortRxQueue(int port);					     *
+ *	23. MoxaPortRxFree(int port);					     *
+ *	24. MoxaPortTxDisable(int port);				     *
+ *	25. MoxaPortTxEnable(int port); 				     *
+ *	26. MoxaPortGetBrkCnt(int port);				     *
+ *	27. MoxaPortResetBrkCnt(int port);				     *
+ *	28. MoxaPortSetXonXoff(int port, int xonValue, int xoffValue);	     *
+ *	29. MoxaPortIsTxHold(int port); 				     *
+ *	30. MoxaPortSendBreak(int port, int ticks);			     *
+ *****************************************************************************/
+/*
+ *    Moxa Port Number Description:
+ *
+ *      MOXA serial driver supports up to 4 MOXA-C218/C320 boards. And,
+ *      the port number using in MOXA driver functions will be 0 to 31 for
+ *      first MOXA board, 32 to 63 for second, 64 to 95 for third and 96
+ *      to 127 for fourth. For example, if you setup three MOXA boards,
+ *      first board is C218, second board is C320-16 and third board is
+ *      C320-32. The port number of first board (C218 - 8 ports) is from
+ *      0 to 7. The port number of second board (C320 - 16 ports) is form
+ *      32 to 47. The port number of third board (C320 - 32 ports) is from
+ *      64 to 95. And those port numbers form 8 to 31, 48 to 63 and 96 to
+ *      127 will be invalid.
+ *
+ *
+ *      Moxa Functions Description:
+ *
+ *      Function 1:     Driver initialization routine, this routine must be
+ *                      called when initialized driver.
+ *      Syntax:
+ *      void MoxaDriverInit();
+ *
+ *
+ *      Function 2:     Moxa driver private IOCTL command processing.
+ *      Syntax:
+ *      int  MoxaDriverIoctl(unsigned int cmd, unsigned long arg, int port);
+ *
+ *           unsigned int cmd   : IOCTL command
+ *           unsigned long arg  : IOCTL argument
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    0  (OK)
+ *                      -EINVAL
+ *                      -ENOIOCTLCMD
+ *
+ *
+ *      Function 3:     Moxa driver polling process routine.
+ *      Syntax:
+ *      int  MoxaDriverPoll(void);
+ *
+ *           return:    0       ; polling O.K.
+ *                      -1      : no any Moxa card.             
+ *
+ *
+ *      Function 4:     Get the ports of this card.
+ *      Syntax:
+ *      int  MoxaPortsOfCard(int cardno);
+ *
+ *           int cardno         : card number (0 - 3)
+ *
+ *           return:    0       : this card is invalid
+ *                      8/16/24/32
+ *
+ *
+ *      Function 5:     Check this port is valid or invalid
+ *      Syntax:
+ *      int  MoxaPortIsValid(int port);
+ *           int port           : port number (0 - 127, ref port description)
+ *
+ *           return:    0       : this port is invalid
+ *                      1       : this port is valid
+ *
+ *
+ *      Function 6:     Enable this port to start Tx/Rx data.
+ *      Syntax:
+ *      void MoxaPortEnable(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *
+ *      Function 7:     Disable this port
+ *      Syntax:
+ *      void MoxaPortDisable(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *
+ *      Function 8:     Get the maximun available baud rate of this port.
+ *      Syntax:
+ *      long MoxaPortGetMaxBaud(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    0       : this port is invalid
+ *                      38400/57600/115200 bps
+ *
+ *
+ *      Function 9:     Get the current baud rate of this port.
+ *      Syntax:
+ *      long MoxaPortGetCurBaud(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    0       : this port is invalid
+ *                      50 - 115200 bps
+ *
+ *
+ *      Function 10:    Setting baud rate of this port.
+ *      Syntax:
+ *      long MoxaPortSetBaud(int port, long baud);
+ *           int port           : port number (0 - 127)
+ *           long baud          : baud rate (50 - 115200)
+ *
+ *           return:    0       : this port is invalid or baud < 50
+ *                      50 - 115200 : the real baud rate set to the port, if
+ *                                    the argument baud is large than maximun
+ *                                    available baud rate, the real setting
+ *                                    baud rate will be the maximun baud rate.
+ *
+ *
+ *      Function 11:    Setting the data-bits/stop-bits/parity of this port
+ *      Syntax:
+ *      int  MoxaPortSetMode(int port, int databits, int stopbits, int parity);
+ *           int port           : port number (0 - 127)
+ *           int databits       : data bits (8/7/6/5)
+ *           int stopbits       : stop bits (2/1/0, 0 show 1.5 stop bits)
+ int parity     : parity (0:None,1:Odd,2:Even,3:Mark,4:Space)
+ *
+ *           return:    -1      : invalid parameter
+ *                      0       : setting O.K.
+ *
+ *
+ *      Function 12:    Configure the port.
+ *      Syntax:
+ *      int  MoxaPortSetTermio(int port, struct termios *termio);
+ *           int port           : port number (0 - 127)
+ *           struct termios * termio : termio structure pointer
+ *
+ *           return:    -1      : this port is invalid or termio == NULL
+ *                      0       : setting O.K.
+ *
+ *
+ *      Function 13:    Get the DTR/RTS state of this port.
+ *      Syntax:
+ *      int  MoxaPortGetLineOut(int port, int *dtrState, int *rtsState);
+ *           int port           : port number (0 - 127)
+ *           int * dtrState     : pointer to INT to receive the current DTR
+ *                                state. (if NULL, this function will not
+ *                                write to this address)
+ *           int * rtsState     : pointer to INT to receive the current RTS
+ *                                state. (if NULL, this function will not
+ *                                write to this address)
+ *
+ *           return:    -1      : this port is invalid
+ *                      0       : O.K.
+ *
+ *
+ *      Function 14:    Setting the DTR/RTS output state of this port.
+ *      Syntax:
+ *      void MoxaPortLineCtrl(int port, int dtrState, int rtsState);
+ *           int port           : port number (0 - 127)
+ *           int dtrState       : DTR output state (0: off, 1: on)
+ *           int rtsState       : RTS output state (0: off, 1: on)
+ *
+ *
+ *      Function 15:    Setting the flow control of this port.
+ *      Syntax:
+ *      void MoxaPortFlowCtrl(int port, int rtsFlow, int ctsFlow, int rxFlow,
+ *                            int txFlow,int xany);
+ *           int port           : port number (0 - 127)
+ *           int rtsFlow        : H/W RTS flow control (0: no, 1: yes)
+ *           int ctsFlow        : H/W CTS flow control (0: no, 1: yes)
+ *           int rxFlow         : S/W Rx XON/XOFF flow control (0: no, 1: yes)
+ *           int txFlow         : S/W Tx XON/XOFF flow control (0: no, 1: yes)
+ *           int xany           : S/W XANY flow control (0: no, 1: yes)
+ *
+ *
+ *      Function 16:    Get ths line status of this port
+ *      Syntax:
+ *      int  MoxaPortLineStatus(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    Bit 0 - CTS state (0: off, 1: on)
+ *                      Bit 1 - DSR state (0: off, 1: on)
+ *                      Bit 2 - DCD state (0: off, 1: on)
+ *
+ *
+ *      Function 17:    Check the DCD state has changed since the last read
+ *                      of this function.
+ *      Syntax:
+ *      int  MoxaPortDCDChange(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    0       : no changed
+ *                      1       : DCD has changed
+ *
+ *
+ *      Function 18:    Check ths current DCD state is ON or not.
+ *      Syntax:
+ *      int  MoxaPortDCDON(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    0       : DCD off
+ *                      1       : DCD on
+ *
+ *
+ *      Function 19:    Flush the Rx/Tx buffer data of this port.
+ *      Syntax:
+ *      void MoxaPortFlushData(int port, int mode);
+ *           int port           : port number (0 - 127)
+ *           int mode    
+ *                      0       : flush the Rx buffer 
+ *                      1       : flush the Tx buffer 
+ *                      2       : flush the Rx and Tx buffer 
+ *
+ *
+ *      Function 20:    Write data.
+ *      Syntax:
+ *      int  MoxaPortWriteData(int port, unsigned char * buffer, int length);
+ *           int port           : port number (0 - 127)
+ *           unsigned char * buffer     : pointer to write data buffer.
+ *           int length         : write data length
+ *
+ *           return:    0 - length      : real write data length
+ *
+ *
+ *      Function 21:    Read data.
+ *      Syntax:
+ *      int  MoxaPortReadData(int port, unsigned char * buffer, int length);
+ *           int port           : port number (0 - 127)
+ *           unsigned char * buffer     : pointer to read data buffer.
+ *           int length         : read data buffer length
+ *
+ *           return:    0 - length      : real read data length
+ *
+ *
+ *      Function 22:    Get the Tx buffer size of this port
+ *      Syntax:
+ *      int  MoxaPortTxBufSize(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    ..      : Tx buffer size
+ *
+ *
+ *      Function 23:    Get the Rx buffer size of this port
+ *      Syntax:
+ *      int  MoxaPortRxBufSize(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    ..      : Rx buffer size
+ *
+ *
+ *      Function 24:    Get the Tx buffer current queued data bytes
+ *      Syntax:
+ *      int  MoxaPortTxQueue(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    ..      : Tx buffer current queued data bytes
+ *
+ *
+ *      Function 25:    Get the Tx buffer current free space
+ *      Syntax:
+ *      int  MoxaPortTxFree(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    ..      : Tx buffer current free space
+ *
+ *
+ *      Function 26:    Get the Rx buffer current queued data bytes
+ *      Syntax:
+ *      int  MoxaPortRxQueue(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    ..      : Rx buffer current queued data bytes
+ *
+ *
+ *      Function 27:    Get the Rx buffer current free space
+ *      Syntax:
+ *      int  MoxaPortRxFree(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    ..      : Rx buffer current free space
+ *
+ *
+ *      Function 28:    Disable port data transmission.
+ *      Syntax:
+ *      void MoxaPortTxDisable(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *
+ *      Function 29:    Enable port data transmission.
+ *      Syntax:
+ *      void MoxaPortTxEnable(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *
+ *      Function 30:    Get the received BREAK signal count.
+ *      Syntax:
+ *      int  MoxaPortGetBrkCnt(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    0 - ..  : BREAK signal count
+ *
+ *
+ *      Function 31:    Get the received BREAK signal count and reset it.
+ *      Syntax:
+ *      int  MoxaPortResetBrkCnt(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    0 - ..  : BREAK signal count
+ *
+ *
+ *      Function 32:    Set the S/W flow control new XON/XOFF value, default
+ *                      XON is 0x11 & XOFF is 0x13.
+ *      Syntax:
+ *      void MoxaPortSetXonXoff(int port, int xonValue, int xoffValue);
+ *           int port           : port number (0 - 127)
+ *           int xonValue       : new XON value (0 - 255)
+ *           int xoffValue      : new XOFF value (0 - 255)
+ *
+ *
+ *      Function 33:    Check this port's transmission is hold by remote site
+ *                      because the flow control.
+ *      Syntax:
+ *      int  MoxaPortIsTxHold(int port);
+ *           int port           : port number (0 - 127)
+ *
+ *           return:    0       : normal
+ *                      1       : hold by remote site
+ *
+ *
+ *      Function 34:    Send out a BREAK signal.
+ *      Syntax:
+ *      void MoxaPortSendBreak(int port, int ms100);
+ *           int port           : port number (0 - 127)
+ *           int ms100          : break signal time interval.
+ *                                unit: 100 mini-second. if ms100 == 0, it will
+ *                                send out a about 250 ms BREAK signal.
+ *
+ */
+int MoxaPortIsValid(int port)
+{
+
+	if (moxaCard == 0)
+		return (0);
+	if (moxaChkPort[port] == 0)
+		return (0);
+	return (1);
+}
+
+void MoxaPortEnable(int port)
+{
+	void __iomem *ofsAddr;
+	int MoxaPortLineStatus(int);
+	short lowwater = 512;
+
+	ofsAddr = moxaTableAddr[port];
+	writew(lowwater, ofsAddr + Low_water);
+	moxaBreakCnt[port] = 0;
+	if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+	    (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI)) {
+		moxafunc(ofsAddr, FC_SetBreakIrq, 0);
+	} else {
+		writew(readw(ofsAddr + HostStat) | WakeupBreak, ofsAddr + HostStat);
+	}
+
+	moxafunc(ofsAddr, FC_SetLineIrq, Magic_code);
+	moxafunc(ofsAddr, FC_FlushQueue, 2);
+
+	moxafunc(ofsAddr, FC_EnableCH, Magic_code);
+	MoxaPortLineStatus(port);
+}
+
+void MoxaPortDisable(int port)
+{
+	void __iomem *ofsAddr = moxaTableAddr[port];
+
+	moxafunc(ofsAddr, FC_SetFlowCtl, 0);	/* disable flow control */
+	moxafunc(ofsAddr, FC_ClrLineIrq, Magic_code);
+	writew(0, ofsAddr + HostStat);
+	moxafunc(ofsAddr, FC_DisableCH, Magic_code);
+}
+
+long MoxaPortGetMaxBaud(int port)
+{
+	if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+	    (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI))
+		return (460800L);
+	else
+		return (921600L);
+}
+
+
+long MoxaPortSetBaud(int port, long baud)
+{
+	void __iomem *ofsAddr;
+	long max, clock;
+	unsigned int val;
+
+	if ((baud < 50L) || ((max = MoxaPortGetMaxBaud(port)) == 0))
+		return (0);
+	ofsAddr = moxaTableAddr[port];
+	if (baud > max)
+		baud = max;
+	if (max == 38400L)
+		clock = 614400L;	/* for 9.8304 Mhz : max. 38400 bps */
+	else if (max == 57600L)
+		clock = 691200L;	/* for 11.0592 Mhz : max. 57600 bps */
+	else
+		clock = 921600L;	/* for 14.7456 Mhz : max. 115200 bps */
+	val = clock / baud;
+	moxafunc(ofsAddr, FC_SetBaud, val);
+	baud = clock / val;
+	moxaCurBaud[port] = baud;
+	return (baud);
+}
+
+int MoxaPortSetTermio(int port, struct termios *termio)
+{
+	void __iomem *ofsAddr;
+	tcflag_t cflag;
+	long baud;
+	tcflag_t mode = 0;
+
+	if (moxaChkPort[port] == 0 || termio == 0)
+		return (-1);
+	ofsAddr = moxaTableAddr[port];
+	cflag = termio->c_cflag;	/* termio->c_cflag */
+
+	mode = termio->c_cflag & CSIZE;
+	if (mode == CS5)
+		mode = MX_CS5;
+	else if (mode == CS6)
+		mode = MX_CS6;
+	else if (mode == CS7)
+		mode = MX_CS7;
+	else if (mode == CS8)
+		mode = MX_CS8;
+
+	if (termio->c_cflag & CSTOPB) {
+		if (mode == MX_CS5)
+			mode |= MX_STOP15;
+		else
+			mode |= MX_STOP2;
+	} else
+		mode |= MX_STOP1;
+
+	if (termio->c_cflag & PARENB) {
+		if (termio->c_cflag & PARODD)
+			mode |= MX_PARODD;
+		else
+			mode |= MX_PAREVEN;
+	} else
+		mode |= MX_PARNONE;
+
+	moxafunc(ofsAddr, FC_SetDataMode, (ushort) mode);
+
+	cflag &= (CBAUD | CBAUDEX);
+#ifndef B921600
+#define	B921600	(B460800+1)
+#endif
+	switch (cflag) {
+	case B921600:
+		baud = 921600L;
+		break;
+	case B460800:
+		baud = 460800L;
+		break;
+	case B230400:
+		baud = 230400L;
+		break;
+	case B115200:
+		baud = 115200L;
+		break;
+	case B57600:
+		baud = 57600L;
+		break;
+	case B38400:
+		baud = 38400L;
+		break;
+	case B19200:
+		baud = 19200L;
+		break;
+	case B9600:
+		baud = 9600L;
+		break;
+	case B4800:
+		baud = 4800L;
+		break;
+	case B2400:
+		baud = 2400L;
+		break;
+	case B1800:
+		baud = 1800L;
+		break;
+	case B1200:
+		baud = 1200L;
+		break;
+	case B600:
+		baud = 600L;
+		break;
+	case B300:
+		baud = 300L;
+		break;
+	case B200:
+		baud = 200L;
+		break;
+	case B150:
+		baud = 150L;
+		break;
+	case B134:
+		baud = 134L;
+		break;
+	case B110:
+		baud = 110L;
+		break;
+	case B75:
+		baud = 75L;
+		break;
+	case B50:
+		baud = 50L;
+		break;
+	default:
+		baud = 0;
+	}
+	if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+	    (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI)) {
+		if (baud == 921600L)
+			return (-1);
+	}
+	MoxaPortSetBaud(port, baud);
+
+	if (termio->c_iflag & (IXON | IXOFF | IXANY)) {
+		writeb(termio->c_cc[VSTART], ofsAddr + FuncArg);
+		writeb(termio->c_cc[VSTOP], ofsAddr + FuncArg1);
+		writeb(FC_SetXonXoff, ofsAddr + FuncCode);
+		wait_finish(ofsAddr);
+
+	}
+	return (0);
+}
+
+int MoxaPortGetLineOut(int port, int *dtrState, int *rtsState)
+{
+
+	if (!MoxaPortIsValid(port))
+		return (-1);
+	if (dtrState) {
+		if (moxaLineCtrl[port] & DTR_ON)
+			*dtrState = 1;
+		else
+			*dtrState = 0;
+	}
+	if (rtsState) {
+		if (moxaLineCtrl[port] & RTS_ON)
+			*rtsState = 1;
+		else
+			*rtsState = 0;
+	}
+	return (0);
+}
+
+void MoxaPortLineCtrl(int port, int dtr, int rts)
+{
+	void __iomem *ofsAddr;
+	int mode;
+
+	ofsAddr = moxaTableAddr[port];
+	mode = 0;
+	if (dtr)
+		mode |= DTR_ON;
+	if (rts)
+		mode |= RTS_ON;
+	moxaLineCtrl[port] = mode;
+	moxafunc(ofsAddr, FC_LineControl, mode);
+}
+
+void MoxaPortFlowCtrl(int port, int rts, int cts, int txflow, int rxflow, int txany)
+{
+	void __iomem *ofsAddr;
+	int mode;
+
+	ofsAddr = moxaTableAddr[port];
+	mode = 0;
+	if (rts)
+		mode |= RTS_FlowCtl;
+	if (cts)
+		mode |= CTS_FlowCtl;
+	if (txflow)
+		mode |= Tx_FlowCtl;
+	if (rxflow)
+		mode |= Rx_FlowCtl;
+	if (txany)
+		mode |= IXM_IXANY;
+	moxafunc(ofsAddr, FC_SetFlowCtl, mode);
+}
+
+int MoxaPortLineStatus(int port)
+{
+	void __iomem *ofsAddr;
+	int val;
+
+	ofsAddr = moxaTableAddr[port];
+	if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+	    (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI)) {
+		moxafunc(ofsAddr, FC_LineStatus, 0);
+		val = readw(ofsAddr + FuncArg);
+	} else {
+		val = readw(ofsAddr + FlagStat) >> 4;
+	}
+	val &= 0x0B;
+	if (val & 8) {
+		val |= 4;
+		if ((moxaDCDState[port] & DCD_oldstate) == 0)
+			moxaDCDState[port] = (DCD_oldstate | DCD_changed);
+	} else {
+		if (moxaDCDState[port] & DCD_oldstate)
+			moxaDCDState[port] = DCD_changed;
+	}
+	val &= 7;
+	return (val);
+}
+
+int MoxaPortDCDChange(int port)
+{
+	int n;
+
+	if (moxaChkPort[port] == 0)
+		return (0);
+	n = moxaDCDState[port];
+	moxaDCDState[port] &= ~DCD_changed;
+	n &= DCD_changed;
+	return (n);
+}
+
+int MoxaPortDCDON(int port)
+{
+	int n;
+
+	if (moxaChkPort[port] == 0)
+		return (0);
+	if (moxaDCDState[port] & DCD_oldstate)
+		n = 1;
+	else
+		n = 0;
+	return (n);
+}
+
+
+/*
+   int MoxaDumpMem(int port, unsigned char * buffer, int len)
+   {
+   int          i;
+   unsigned long                baseAddr,ofsAddr,ofs;
+
+   baseAddr = moxaBaseAddr[port / MAX_PORTS_PER_BOARD];
+   ofs = baseAddr + DynPage_addr + pageofs;
+   if (len > 0x2000L)
+   len = 0x2000L;
+   for (i = 0; i < len; i++)
+   buffer[i] = readb(ofs+i);
+   }
+ */
+
+
+int MoxaPortWriteData(int port, unsigned char * buffer, int len)
+{
+	int c, total, i;
+	ushort tail;
+	int cnt;
+	ushort head, tx_mask, spage, epage;
+	ushort pageno, pageofs, bufhead;
+	void __iomem *baseAddr, *ofsAddr, *ofs;
+
+	ofsAddr = moxaTableAddr[port];
+	baseAddr = moxaBaseAddr[port / MAX_PORTS_PER_BOARD];
+	tx_mask = readw(ofsAddr + TX_mask);
+	spage = readw(ofsAddr + Page_txb);
+	epage = readw(ofsAddr + EndPage_txb);
+	tail = readw(ofsAddr + TXwptr);
+	head = readw(ofsAddr + TXrptr);
+	c = (head > tail) ? (head - tail - 1)
+	    : (head - tail + tx_mask);
+	if (c > len)
+		c = len;
+	moxaLog.txcnt[port] += c;
+	total = c;
+	if (spage == epage) {
+		bufhead = readw(ofsAddr + Ofs_txb);
+		writew(spage, baseAddr + Control_reg);
+		while (c > 0) {
+			if (head > tail)
+				len = head - tail - 1;
+			else
+				len = tx_mask + 1 - tail;
+			len = (c > len) ? len : c;
+			ofs = baseAddr + DynPage_addr + bufhead + tail;
+			for (i = 0; i < len; i++)
+				writeb(*buffer++, ofs + i);
+			tail = (tail + len) & tx_mask;
+			c -= len;
+		}
+		writew(tail, ofsAddr + TXwptr);
+	} else {
+		len = c;
+		pageno = spage + (tail >> 13);
+		pageofs = tail & Page_mask;
+		do {
+			cnt = Page_size - pageofs;
+			if (cnt > c)
+				cnt = c;
+			c -= cnt;
+			writeb(pageno, baseAddr + Control_reg);
+			ofs = baseAddr + DynPage_addr + pageofs;
+			for (i = 0; i < cnt; i++)
+				writeb(*buffer++, ofs + i);
+			if (c == 0) {
+				writew((tail + len) & tx_mask, ofsAddr + TXwptr);
+				break;
+			}
+			if (++pageno == epage)
+				pageno = spage;
+			pageofs = 0;
+		} while (1);
+	}
+	writeb(1, ofsAddr + CD180TXirq);	/* start to send */
+	return (total);
+}
+
+int MoxaPortReadData(int port, unsigned char * buffer, int space)
+{
+	register ushort head, pageofs;
+	int i, count, cnt, len, total, remain;
+	ushort tail, rx_mask, spage, epage;
+	ushort pageno, bufhead;
+	void __iomem *baseAddr, *ofsAddr, *ofs;
+
+	ofsAddr = moxaTableAddr[port];
+	baseAddr = moxaBaseAddr[port / MAX_PORTS_PER_BOARD];
+	head = readw(ofsAddr + RXrptr);
+	tail = readw(ofsAddr + RXwptr);
+	rx_mask = readw(ofsAddr + RX_mask);
+	spage = readw(ofsAddr + Page_rxb);
+	epage = readw(ofsAddr + EndPage_rxb);
+	count = (tail >= head) ? (tail - head)
+	    : (tail - head + rx_mask + 1);
+	if (count == 0)
+		return (0);
+
+	total = (space > count) ? count : space;
+	remain = count - total;
+	moxaLog.rxcnt[port] += total;
+	count = total;
+	if (spage == epage) {
+		bufhead = readw(ofsAddr + Ofs_rxb);
+		writew(spage, baseAddr + Control_reg);
+		while (count > 0) {
+			if (tail >= head)
+				len = tail - head;
+			else
+				len = rx_mask + 1 - head;
+			len = (count > len) ? len : count;
+			ofs = baseAddr + DynPage_addr + bufhead + head;
+			for (i = 0; i < len; i++)
+				*buffer++ = readb(ofs + i);
+			head = (head + len) & rx_mask;
+			count -= len;
+		}
+		writew(head, ofsAddr + RXrptr);
+	} else {
+		len = count;
+		pageno = spage + (head >> 13);
+		pageofs = head & Page_mask;
+		do {
+			cnt = Page_size - pageofs;
+			if (cnt > count)
+				cnt = count;
+			count -= cnt;
+			writew(pageno, baseAddr + Control_reg);
+			ofs = baseAddr + DynPage_addr + pageofs;
+			for (i = 0; i < cnt; i++)
+				*buffer++ = readb(ofs + i);
+			if (count == 0) {
+				writew((head + len) & rx_mask, ofsAddr + RXrptr);
+				break;
+			}
+			if (++pageno == epage)
+				pageno = spage;
+			pageofs = 0;
+		} while (1);
+	}
+	if ((readb(ofsAddr + FlagStat) & Xoff_state) && (remain < LowWater)) {
+		moxaLowWaterChk = 1;
+		moxaLowChkFlag[port] = 1;
+	}
+	return (total);
+}
+
+
+int MoxaPortTxQueue(int port)
+{
+	void __iomem *ofsAddr;
+	ushort rptr, wptr, mask;
+	int len;
+
+	ofsAddr = moxaTableAddr[port];
+	rptr = readw(ofsAddr + TXrptr);
+	wptr = readw(ofsAddr + TXwptr);
+	mask = readw(ofsAddr + TX_mask);
+	len = (wptr - rptr) & mask;
+	return (len);
+}
+
+int MoxaPortTxFree(int port)
+{
+	void __iomem *ofsAddr;
+	ushort rptr, wptr, mask;
+	int len;
+
+	ofsAddr = moxaTableAddr[port];
+	rptr = readw(ofsAddr + TXrptr);
+	wptr = readw(ofsAddr + TXwptr);
+	mask = readw(ofsAddr + TX_mask);
+	len = mask - ((wptr - rptr) & mask);
+	return (len);
+}
+
+int MoxaPortRxQueue(int port)
+{
+	void __iomem *ofsAddr;
+	ushort rptr, wptr, mask;
+	int len;
+
+	ofsAddr = moxaTableAddr[port];
+	rptr = readw(ofsAddr + RXrptr);
+	wptr = readw(ofsAddr + RXwptr);
+	mask = readw(ofsAddr + RX_mask);
+	len = (wptr - rptr) & mask;
+	return (len);
+}
+
+
+void MoxaPortTxDisable(int port)
+{
+	void __iomem *ofsAddr;
+
+	ofsAddr = moxaTableAddr[port];
+	moxafunc(ofsAddr, FC_SetXoffState, Magic_code);
+}
+
+void MoxaPortTxEnable(int port)
+{
+	void __iomem *ofsAddr;
+
+	ofsAddr = moxaTableAddr[port];
+	moxafunc(ofsAddr, FC_SetXonState, Magic_code);
+}
+
+
+int MoxaPortResetBrkCnt(int port)
+{
+	ushort cnt;
+	cnt = moxaBreakCnt[port];
+	moxaBreakCnt[port] = 0;
+	return (cnt);
+}
+
+
+void MoxaPortSendBreak(int port, int ms100)
+{
+	void __iomem *ofsAddr;
+
+	ofsAddr = moxaTableAddr[port];
+	if (ms100) {
+		moxafunc(ofsAddr, FC_SendBreak, Magic_code);
+		moxadelay(ms100 * (HZ / 10));
+	} else {
+		moxafunc(ofsAddr, FC_SendBreak, Magic_code);
+		moxadelay(HZ / 4);	/* 250 ms */
+	}
+	moxafunc(ofsAddr, FC_StopBreak, Magic_code);
+}
+
+static int moxa_get_serial_info(struct moxa_str *info,
+				struct serial_struct __user *retinfo)
+{
+	struct serial_struct tmp;
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.type = info->type;
+	tmp.line = info->port;
+	tmp.port = 0;
+	tmp.irq = 0;
+	tmp.flags = info->asyncflags;
+	tmp.baud_base = 921600;
+	tmp.close_delay = info->close_delay;
+	tmp.closing_wait = info->closing_wait;
+	tmp.custom_divisor = 0;
+	tmp.hub6 = 0;
+	if(copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+		return -EFAULT;
+	return (0);
+}
+
+
+static int moxa_set_serial_info(struct moxa_str *info,
+				struct serial_struct __user *new_info)
+{
+	struct serial_struct new_serial;
+
+	if(copy_from_user(&new_serial, new_info, sizeof(new_serial)))
+		return -EFAULT;
+
+	if ((new_serial.irq != 0) ||
+	    (new_serial.port != 0) ||
+//           (new_serial.type != info->type) ||
+	    (new_serial.custom_divisor != 0) ||
+	    (new_serial.baud_base != 921600))
+		return (-EPERM);
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if (((new_serial.flags & ~ASYNC_USR_MASK) !=
+		     (info->asyncflags & ~ASYNC_USR_MASK)))
+			return (-EPERM);
+	} else {
+		info->close_delay = new_serial.close_delay * HZ / 100;
+		info->closing_wait = new_serial.closing_wait * HZ / 100;
+	}
+
+	new_serial.flags = (new_serial.flags & ~ASYNC_FLAGS);
+	new_serial.flags |= (info->asyncflags & ASYNC_FLAGS);
+
+	if (new_serial.type == PORT_16550A) {
+		MoxaSetFifo(info->port, 1);
+	} else {
+		MoxaSetFifo(info->port, 0);
+	}
+
+	info->type = new_serial.type;
+	return (0);
+}
+
+
+
+/*****************************************************************************
+ *	Static local functions: 					     *
+ *****************************************************************************/
+/*
+ * moxadelay - delays a specified number ticks
+ */
+static void moxadelay(int tick)
+{
+	unsigned long st, et;
+
+	st = jiffies;
+	et = st + tick;
+	while (time_before(jiffies, et));
+}
+
+static void moxafunc(void __iomem *ofsAddr, int cmd, ushort arg)
+{
+
+	writew(arg, ofsAddr + FuncArg);
+	writew(cmd, ofsAddr + FuncCode);
+	wait_finish(ofsAddr);
+}
+
+static void wait_finish(void __iomem *ofsAddr)
+{
+	unsigned long i, j;
+
+	i = jiffies;
+	while (readw(ofsAddr + FuncCode) != 0) {
+		j = jiffies;
+		if ((j - i) > moxaFuncTout) {
+			return;
+		}
+	}
+}
+
+static void low_water_check(void __iomem *ofsAddr)
+{
+	int len;
+	ushort rptr, wptr, mask;
+
+	if (readb(ofsAddr + FlagStat) & Xoff_state) {
+		rptr = readw(ofsAddr + RXrptr);
+		wptr = readw(ofsAddr + RXwptr);
+		mask = readw(ofsAddr + RX_mask);
+		len = (wptr - rptr) & mask;
+		if (len <= Low_water)
+			moxafunc(ofsAddr, FC_SendXon, 0);
+	}
+}
+
+static int moxaloadbios(int cardno, unsigned char __user *tmp, int len)
+{
+	void __iomem *baseAddr;
+	int i;
+
+	if(copy_from_user(moxaBuff, tmp, len))
+		return -EFAULT;
+	baseAddr = moxaBaseAddr[cardno];
+	writeb(HW_reset, baseAddr + Control_reg);	/* reset */
+	moxadelay(1);		/* delay 10 ms */
+	for (i = 0; i < 4096; i++)
+		writeb(0, baseAddr + i);	/* clear fix page */
+	for (i = 0; i < len; i++)
+		writeb(moxaBuff[i], baseAddr + i);	/* download BIOS */
+	writeb(0, baseAddr + Control_reg);	/* restart */
+	return (0);
+}
+
+static int moxafindcard(int cardno)
+{
+	void __iomem *baseAddr;
+	ushort tmp;
+
+	baseAddr = moxaBaseAddr[cardno];
+	switch (moxa_boards[cardno].boardType) {
+	case MOXA_BOARD_C218_ISA:
+	case MOXA_BOARD_C218_PCI:
+		if ((tmp = readw(baseAddr + C218_key)) != C218_KeyCode) {
+			return (-1);
+		}
+		break;
+	case MOXA_BOARD_CP204J:
+		if ((tmp = readw(baseAddr + C218_key)) != CP204J_KeyCode) {
+			return (-1);
+		}
+		break;
+	default:
+		if ((tmp = readw(baseAddr + C320_key)) != C320_KeyCode) {
+			return (-1);
+		}
+		if ((tmp = readw(baseAddr + C320_status)) != STS_init) {
+			return (-2);
+		}
+	}
+	return (0);
+}
+
+static int moxaload320b(int cardno, unsigned char __user *tmp, int len)
+{
+	void __iomem *baseAddr;
+	int i;
+
+	if(len > sizeof(moxaBuff))
+		return -EINVAL;
+	if(copy_from_user(moxaBuff, tmp, len))
+		return -EFAULT;
+	baseAddr = moxaBaseAddr[cardno];
+	writew(len - 7168 - 2, baseAddr + C320bapi_len);
+	writeb(1, baseAddr + Control_reg);	/* Select Page 1 */
+	for (i = 0; i < 7168; i++)
+		writeb(moxaBuff[i], baseAddr + DynPage_addr + i);
+	writeb(2, baseAddr + Control_reg);	/* Select Page 2 */
+	for (i = 0; i < (len - 7168); i++)
+		writeb(moxaBuff[i + 7168], baseAddr + DynPage_addr + i);
+	return (0);
+}
+
+static int moxaloadcode(int cardno, unsigned char __user *tmp, int len)
+{
+	void __iomem *baseAddr, *ofsAddr;
+	int retval, port, i;
+
+	if(copy_from_user(moxaBuff, tmp, len))
+		return -EFAULT;
+	baseAddr = moxaBaseAddr[cardno];
+	switch (moxa_boards[cardno].boardType) {
+	case MOXA_BOARD_C218_ISA:
+	case MOXA_BOARD_C218_PCI:
+	case MOXA_BOARD_CP204J:
+		retval = moxaloadc218(cardno, baseAddr, len);
+		if (retval)
+			return (retval);
+		port = cardno * MAX_PORTS_PER_BOARD;
+		for (i = 0; i < moxa_boards[cardno].numPorts; i++, port++) {
+			moxaChkPort[port] = 1;
+			moxaCurBaud[port] = 9600L;
+			moxaDCDState[port] = 0;
+			moxaTableAddr[port] = baseAddr + Extern_table + Extern_size * i;
+			ofsAddr = moxaTableAddr[port];
+			writew(C218rx_mask, ofsAddr + RX_mask);
+			writew(C218tx_mask, ofsAddr + TX_mask);
+			writew(C218rx_spage + i * C218buf_pageno, ofsAddr + Page_rxb);
+			writew(readw(ofsAddr + Page_rxb) + C218rx_pageno, ofsAddr + EndPage_rxb);
+
+			writew(C218tx_spage + i * C218buf_pageno, ofsAddr + Page_txb);
+			writew(readw(ofsAddr + Page_txb) + C218tx_pageno, ofsAddr + EndPage_txb);
+
+		}
+		break;
+	default:
+		retval = moxaloadc320(cardno, baseAddr, len,
+				      &moxa_boards[cardno].numPorts);
+		if (retval)
+			return (retval);
+		port = cardno * MAX_PORTS_PER_BOARD;
+		for (i = 0; i < moxa_boards[cardno].numPorts; i++, port++) {
+			moxaChkPort[port] = 1;
+			moxaCurBaud[port] = 9600L;
+			moxaDCDState[port] = 0;
+			moxaTableAddr[port] = baseAddr + Extern_table + Extern_size * i;
+			ofsAddr = moxaTableAddr[port];
+			if (moxa_boards[cardno].numPorts == 8) {
+				writew(C320p8rx_mask, ofsAddr + RX_mask);
+				writew(C320p8tx_mask, ofsAddr + TX_mask);
+				writew(C320p8rx_spage + i * C320p8buf_pgno, ofsAddr + Page_rxb);
+				writew(readw(ofsAddr + Page_rxb) + C320p8rx_pgno, ofsAddr + EndPage_rxb);
+				writew(C320p8tx_spage + i * C320p8buf_pgno, ofsAddr + Page_txb);
+				writew(readw(ofsAddr + Page_txb) + C320p8tx_pgno, ofsAddr + EndPage_txb);
+
+			} else if (moxa_boards[cardno].numPorts == 16) {
+				writew(C320p16rx_mask, ofsAddr + RX_mask);
+				writew(C320p16tx_mask, ofsAddr + TX_mask);
+				writew(C320p16rx_spage + i * C320p16buf_pgno, ofsAddr + Page_rxb);
+				writew(readw(ofsAddr + Page_rxb) + C320p16rx_pgno, ofsAddr + EndPage_rxb);
+				writew(C320p16tx_spage + i * C320p16buf_pgno, ofsAddr + Page_txb);
+				writew(readw(ofsAddr + Page_txb) + C320p16tx_pgno, ofsAddr + EndPage_txb);
+
+			} else if (moxa_boards[cardno].numPorts == 24) {
+				writew(C320p24rx_mask, ofsAddr + RX_mask);
+				writew(C320p24tx_mask, ofsAddr + TX_mask);
+				writew(C320p24rx_spage + i * C320p24buf_pgno, ofsAddr + Page_rxb);
+				writew(readw(ofsAddr + Page_rxb) + C320p24rx_pgno, ofsAddr + EndPage_rxb);
+				writew(C320p24tx_spage + i * C320p24buf_pgno, ofsAddr + Page_txb);
+				writew(readw(ofsAddr + Page_txb), ofsAddr + EndPage_txb);
+			} else if (moxa_boards[cardno].numPorts == 32) {
+				writew(C320p32rx_mask, ofsAddr + RX_mask);
+				writew(C320p32tx_mask, ofsAddr + TX_mask);
+				writew(C320p32tx_ofs, ofsAddr + Ofs_txb);
+				writew(C320p32rx_spage + i * C320p32buf_pgno, ofsAddr + Page_rxb);
+				writew(readb(ofsAddr + Page_rxb), ofsAddr + EndPage_rxb);
+				writew(C320p32tx_spage + i * C320p32buf_pgno, ofsAddr + Page_txb);
+				writew(readw(ofsAddr + Page_txb), ofsAddr + EndPage_txb);
+			}
+		}
+		break;
+	}
+	return (0);
+}
+
+static int moxaloadc218(int cardno, void __iomem *baseAddr, int len)
+{
+	char retry;
+	int i, j, len1, len2;
+	ushort usum, *ptr, keycode;
+
+	if (moxa_boards[cardno].boardType == MOXA_BOARD_CP204J)
+		keycode = CP204J_KeyCode;
+	else
+		keycode = C218_KeyCode;
+	usum = 0;
+	len1 = len >> 1;
+	ptr = (ushort *) moxaBuff;
+	for (i = 0; i < len1; i++)
+		usum += *(ptr + i);
+	retry = 0;
+	do {
+		len1 = len >> 1;
+		j = 0;
+		while (len1) {
+			len2 = (len1 > 2048) ? 2048 : len1;
+			len1 -= len2;
+			for (i = 0; i < len2 << 1; i++)
+				writeb(moxaBuff[i + j], baseAddr + C218_LoadBuf + i);
+			j += i;
+
+			writew(len2, baseAddr + C218DLoad_len);
+			writew(0, baseAddr + C218_key);
+			for (i = 0; i < 100; i++) {
+				if (readw(baseAddr + C218_key) == keycode)
+					break;
+				moxadelay(1);	/* delay 10 ms */
+			}
+			if (readw(baseAddr + C218_key) != keycode) {
+				return (-1);
+			}
+		}
+		writew(0, baseAddr + C218DLoad_len);
+		writew(usum, baseAddr + C218check_sum);
+		writew(0, baseAddr + C218_key);
+		for (i = 0; i < 100; i++) {
+			if (readw(baseAddr + C218_key) == keycode)
+				break;
+			moxadelay(1);	/* delay 10 ms */
+		}
+		retry++;
+	} while ((readb(baseAddr + C218chksum_ok) != 1) && (retry < 3));
+	if (readb(baseAddr + C218chksum_ok) != 1) {
+		return (-1);
+	}
+	writew(0, baseAddr + C218_key);
+	for (i = 0; i < 100; i++) {
+		if (readw(baseAddr + Magic_no) == Magic_code)
+			break;
+		moxadelay(1);	/* delay 10 ms */
+	}
+	if (readw(baseAddr + Magic_no) != Magic_code) {
+		return (-1);
+	}
+	writew(1, baseAddr + Disable_IRQ);
+	writew(0, baseAddr + Magic_no);
+	for (i = 0; i < 100; i++) {
+		if (readw(baseAddr + Magic_no) == Magic_code)
+			break;
+		moxadelay(1);	/* delay 10 ms */
+	}
+	if (readw(baseAddr + Magic_no) != Magic_code) {
+		return (-1);
+	}
+	moxaCard = 1;
+	moxaIntNdx[cardno] = baseAddr + IRQindex;
+	moxaIntPend[cardno] = baseAddr + IRQpending;
+	moxaIntTable[cardno] = baseAddr + IRQtable;
+	return (0);
+}
+
+static int moxaloadc320(int cardno, void __iomem *baseAddr, int len, int *numPorts)
+{
+	ushort usum;
+	int i, j, wlen, len2, retry;
+	ushort *uptr;
+
+	usum = 0;
+	wlen = len >> 1;
+	uptr = (ushort *) moxaBuff;
+	for (i = 0; i < wlen; i++)
+		usum += uptr[i];
+	retry = 0;
+	j = 0;
+	do {
+		while (wlen) {
+			if (wlen > 2048)
+				len2 = 2048;
+			else
+				len2 = wlen;
+			wlen -= len2;
+			len2 <<= 1;
+			for (i = 0; i < len2; i++)
+				writeb(moxaBuff[j + i], baseAddr + C320_LoadBuf + i);
+			len2 >>= 1;
+			j += i;
+			writew(len2, baseAddr + C320DLoad_len);
+			writew(0, baseAddr + C320_key);
+			for (i = 0; i < 10; i++) {
+				if (readw(baseAddr + C320_key) == C320_KeyCode)
+					break;
+				moxadelay(1);
+			}
+			if (readw(baseAddr + C320_key) != C320_KeyCode)
+				return (-1);
+		}
+		writew(0, baseAddr + C320DLoad_len);
+		writew(usum, baseAddr + C320check_sum);
+		writew(0, baseAddr + C320_key);
+		for (i = 0; i < 10; i++) {
+			if (readw(baseAddr + C320_key) == C320_KeyCode)
+				break;
+			moxadelay(1);
+		}
+		retry++;
+	} while ((readb(baseAddr + C320chksum_ok) != 1) && (retry < 3));
+	if (readb(baseAddr + C320chksum_ok) != 1)
+		return (-1);
+	writew(0, baseAddr + C320_key);
+	for (i = 0; i < 600; i++) {
+		if (readw(baseAddr + Magic_no) == Magic_code)
+			break;
+		moxadelay(1);
+	}
+	if (readw(baseAddr + Magic_no) != Magic_code)
+		return (-100);
+
+	if (moxa_boards[cardno].busType == MOXA_BUS_TYPE_PCI) {		/* ASIC board */
+		writew(0x3800, baseAddr + TMS320_PORT1);
+		writew(0x3900, baseAddr + TMS320_PORT2);
+		writew(28499, baseAddr + TMS320_CLOCK);
+	} else {
+		writew(0x3200, baseAddr + TMS320_PORT1);
+		writew(0x3400, baseAddr + TMS320_PORT2);
+		writew(19999, baseAddr + TMS320_CLOCK);
+	}
+	writew(1, baseAddr + Disable_IRQ);
+	writew(0, baseAddr + Magic_no);
+	for (i = 0; i < 500; i++) {
+		if (readw(baseAddr + Magic_no) == Magic_code)
+			break;
+		moxadelay(1);
+	}
+	if (readw(baseAddr + Magic_no) != Magic_code)
+		return (-102);
+
+	j = readw(baseAddr + Module_cnt);
+	if (j <= 0)
+		return (-101);
+	*numPorts = j * 8;
+	writew(j, baseAddr + Module_no);
+	writew(0, baseAddr + Magic_no);
+	for (i = 0; i < 600; i++) {
+		if (readw(baseAddr + Magic_no) == Magic_code)
+			break;
+		moxadelay(1);
+	}
+	if (readw(baseAddr + Magic_no) != Magic_code)
+		return (-102);
+	moxaCard = 1;
+	moxaIntNdx[cardno] = baseAddr + IRQindex;
+	moxaIntPend[cardno] = baseAddr + IRQpending;
+	moxaIntTable[cardno] = baseAddr + IRQtable;
+	return (0);
+}
+
+#if 0
+long MoxaPortGetCurBaud(int port)
+{
+
+	if (moxaChkPort[port] == 0)
+		return (0);
+	return (moxaCurBaud[port]);
+}
+#endif  /*  0  */
+
+static void MoxaSetFifo(int port, int enable)
+{
+	void __iomem *ofsAddr = moxaTableAddr[port];
+
+	if (!enable) {
+		moxafunc(ofsAddr, FC_SetRxFIFOTrig, 0);
+		moxafunc(ofsAddr, FC_SetTxFIFOCnt, 1);
+	} else {
+		moxafunc(ofsAddr, FC_SetRxFIFOTrig, 3);
+		moxafunc(ofsAddr, FC_SetTxFIFOCnt, 16);
+	}
+}
+
+#if 0
+int MoxaPortSetMode(int port, int databits, int stopbits, int parity)
+{
+	void __iomem *ofsAddr;
+	int val;
+
+	val = 0;
+	switch (databits) {
+	case 5:
+		val |= 0;
+		break;
+	case 6:
+		val |= 1;
+		break;
+	case 7:
+		val |= 2;
+		break;
+	case 8:
+		val |= 3;
+		break;
+	default:
+		return (-1);
+	}
+	switch (stopbits) {
+	case 0:
+		val |= 0;
+		break;		/* stop bits 1.5 */
+	case 1:
+		val |= 0;
+		break;
+	case 2:
+		val |= 4;
+		break;
+	default:
+		return (-1);
+	}
+	switch (parity) {
+	case 0:
+		val |= 0x00;
+		break;		/* None  */
+	case 1:
+		val |= 0x08;
+		break;		/* Odd   */
+	case 2:
+		val |= 0x18;
+		break;		/* Even  */
+	case 3:
+		val |= 0x28;
+		break;		/* Mark  */
+	case 4:
+		val |= 0x38;
+		break;		/* Space */
+	default:
+		return (-1);
+	}
+	ofsAddr = moxaTableAddr[port];
+	moxafunc(ofsAddr, FC_SetMode, val);
+	return (0);
+}
+
+int MoxaPortTxBufSize(int port)
+{
+	void __iomem *ofsAddr;
+	int size;
+
+	ofsAddr = moxaTableAddr[port];
+	size = readw(ofsAddr + TX_mask);
+	return (size);
+}
+
+int MoxaPortRxBufSize(int port)
+{
+	void __iomem *ofsAddr;
+	int size;
+
+	ofsAddr = moxaTableAddr[port];
+	size = readw(ofsAddr + RX_mask);
+	return (size);
+}
+
+int MoxaPortRxFree(int port)
+{
+	void __iomem *ofsAddr;
+	ushort rptr, wptr, mask;
+	int len;
+
+	ofsAddr = moxaTableAddr[port];
+	rptr = readw(ofsAddr + RXrptr);
+	wptr = readw(ofsAddr + RXwptr);
+	mask = readw(ofsAddr + RX_mask);
+	len = mask - ((wptr - rptr) & mask);
+	return (len);
+}
+int MoxaPortGetBrkCnt(int port)
+{
+	return (moxaBreakCnt[port]);
+}
+
+void MoxaPortSetXonXoff(int port, int xonValue, int xoffValue)
+{
+	void __iomem *ofsAddr;
+
+	ofsAddr = moxaTableAddr[port];
+	writew(xonValue, ofsAddr + FuncArg);
+	writew(xoffValue, ofsAddr + FuncArg1);
+	writew(FC_SetXonXoff, ofsAddr + FuncCode);
+	wait_finish(ofsAddr);
+}
+
+int MoxaPortIsTxHold(int port)
+{
+	void __iomem *ofsAddr;
+	int val;
+
+	ofsAddr = moxaTableAddr[port];
+	if ((moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_ISA) ||
+	    (moxa_boards[port / MAX_PORTS_PER_BOARD].boardType == MOXA_BOARD_C320_PCI)) {
+		moxafunc(ofsAddr, FC_GetCCSR, 0);
+		val = readw(ofsAddr + FuncArg);
+		if (val & 0x04)
+			return (1);
+	} else {
+		if (readw(ofsAddr + FlagStat) & Tx_flowOff)
+			return (1);
+	}
+	return (0);
+}
+#endif
diff --git a/drivers/char/mwave/3780i.c b/drivers/char/mwave/3780i.c
new file mode 100644
index 0000000..ab00f51
--- /dev/null
+++ b/drivers/char/mwave/3780i.c
@@ -0,0 +1,727 @@
+/*
+*
+* 3780i.c -- helper routines for the 3780i DSP
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/unistd.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+#include "smapi.h"
+#include "mwavedd.h"
+#include "3780i.h"
+
+static DEFINE_SPINLOCK(dsp_lock);
+static unsigned long flags;
+
+
+static void PaceMsaAccess(unsigned short usDspBaseIO)
+{
+	cond_resched();
+	udelay(100);
+	cond_resched();
+}
+
+unsigned short dsp3780I_ReadMsaCfg(unsigned short usDspBaseIO,
+                                   unsigned long ulMsaAddr)
+{
+	unsigned short val;
+
+	PRINTK_3(TRACE_3780I,
+		"3780i::dsp3780I_ReadMsaCfg entry usDspBaseIO %x ulMsaAddr %lx\n",
+		usDspBaseIO, ulMsaAddr);
+
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulMsaAddr);
+	OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulMsaAddr >> 16));
+	val = InWordDsp(DSP_MsaDataDSISHigh);
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+	PRINTK_2(TRACE_3780I, "3780i::dsp3780I_ReadMsaCfg exit val %x\n", val);
+
+	return val;
+}
+
+void dsp3780I_WriteMsaCfg(unsigned short usDspBaseIO,
+                          unsigned long ulMsaAddr, unsigned short usValue)
+{
+
+	PRINTK_4(TRACE_3780I,
+		"3780i::dsp3780i_WriteMsaCfg entry usDspBaseIO %x ulMsaAddr %lx usValue %x\n",
+		usDspBaseIO, ulMsaAddr, usValue);
+
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulMsaAddr);
+	OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulMsaAddr >> 16));
+	OutWordDsp(DSP_MsaDataDSISHigh, usValue);
+	spin_unlock_irqrestore(&dsp_lock, flags);
+}
+
+void dsp3780I_WriteGenCfg(unsigned short usDspBaseIO, unsigned uIndex,
+                          unsigned char ucValue)
+{
+	DSP_ISA_SLAVE_CONTROL rSlaveControl;
+	DSP_ISA_SLAVE_CONTROL rSlaveControl_Save;
+
+
+	PRINTK_4(TRACE_3780I,
+		"3780i::dsp3780i_WriteGenCfg entry usDspBaseIO %x uIndex %x ucValue %x\n",
+		usDspBaseIO, uIndex, ucValue);
+
+	MKBYTE(rSlaveControl) = InByteDsp(DSP_IsaSlaveControl);
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780i_WriteGenCfg rSlaveControl %x\n",
+		MKBYTE(rSlaveControl));
+
+	rSlaveControl_Save = rSlaveControl;
+	rSlaveControl.ConfigMode = TRUE;
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780i_WriteGenCfg entry rSlaveControl+ConfigMode %x\n",
+		MKBYTE(rSlaveControl));
+
+	OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl));
+	OutByteDsp(DSP_ConfigAddress, (unsigned char) uIndex);
+	OutByteDsp(DSP_ConfigData, ucValue);
+	OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl_Save));
+
+	PRINTK_1(TRACE_3780I, "3780i::dsp3780i_WriteGenCfg exit\n");
+
+
+}
+
+unsigned char dsp3780I_ReadGenCfg(unsigned short usDspBaseIO,
+                                  unsigned uIndex)
+{
+	DSP_ISA_SLAVE_CONTROL rSlaveControl;
+	DSP_ISA_SLAVE_CONTROL rSlaveControl_Save;
+	unsigned char ucValue;
+
+
+	PRINTK_3(TRACE_3780I,
+		"3780i::dsp3780i_ReadGenCfg entry usDspBaseIO %x uIndex %x\n",
+		usDspBaseIO, uIndex);
+
+	MKBYTE(rSlaveControl) = InByteDsp(DSP_IsaSlaveControl);
+	rSlaveControl_Save = rSlaveControl;
+	rSlaveControl.ConfigMode = TRUE;
+	OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl));
+	OutByteDsp(DSP_ConfigAddress, (unsigned char) uIndex);
+	ucValue = InByteDsp(DSP_ConfigData);
+	OutByteDsp(DSP_IsaSlaveControl, MKBYTE(rSlaveControl_Save));
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780i_ReadGenCfg exit ucValue %x\n", ucValue);
+
+
+	return ucValue;
+}
+
+int dsp3780I_EnableDSP(DSP_3780I_CONFIG_SETTINGS * pSettings,
+                       unsigned short *pIrqMap,
+                       unsigned short *pDmaMap)
+{
+	unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+	int i;
+	DSP_UART_CFG_1 rUartCfg1;
+	DSP_UART_CFG_2 rUartCfg2;
+	DSP_HBRIDGE_CFG_1 rHBridgeCfg1;
+	DSP_HBRIDGE_CFG_2 rHBridgeCfg2;
+	DSP_BUSMASTER_CFG_1 rBusmasterCfg1;
+	DSP_BUSMASTER_CFG_2 rBusmasterCfg2;
+	DSP_ISA_PROT_CFG rIsaProtCfg;
+	DSP_POWER_MGMT_CFG rPowerMgmtCfg;
+	DSP_HBUS_TIMER_CFG rHBusTimerCfg;
+	DSP_LBUS_TIMEOUT_DISABLE rLBusTimeoutDisable;
+	DSP_CHIP_RESET rChipReset;
+	DSP_CLOCK_CONTROL_1 rClockControl1;
+	DSP_CLOCK_CONTROL_2 rClockControl2;
+	DSP_ISA_SLAVE_CONTROL rSlaveControl;
+	DSP_HBRIDGE_CONTROL rHBridgeControl;
+	unsigned short ChipID = 0;
+	unsigned short tval;
+
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780I_EnableDSP entry pSettings->bDSPEnabled %x\n",
+		pSettings->bDSPEnabled);
+
+
+	if (!pSettings->bDSPEnabled) {
+		PRINTK_ERROR( KERN_ERR "3780i::dsp3780I_EnableDSP: Error: DSP not enabled. Aborting.\n" );
+		return -EIO;
+	}
+
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780i_EnableDSP entry pSettings->bModemEnabled %x\n",
+		pSettings->bModemEnabled);
+
+	if (pSettings->bModemEnabled) {
+		rUartCfg1.Reserved = rUartCfg2.Reserved = 0;
+		rUartCfg1.IrqActiveLow = pSettings->bUartIrqActiveLow;
+		rUartCfg1.IrqPulse = pSettings->bUartIrqPulse;
+		rUartCfg1.Irq =
+			(unsigned char) pIrqMap[pSettings->usUartIrq];
+		switch (pSettings->usUartBaseIO) {
+		case 0x03F8:
+			rUartCfg1.BaseIO = 0;
+			break;
+		case 0x02F8:
+			rUartCfg1.BaseIO = 1;
+			break;
+		case 0x03E8:
+			rUartCfg1.BaseIO = 2;
+			break;
+		case 0x02E8:
+			rUartCfg1.BaseIO = 3;
+			break;
+		}
+		rUartCfg2.Enable = TRUE;
+	}
+
+	rHBridgeCfg1.Reserved = rHBridgeCfg2.Reserved = 0;
+	rHBridgeCfg1.IrqActiveLow = pSettings->bDspIrqActiveLow;
+	rHBridgeCfg1.IrqPulse = pSettings->bDspIrqPulse;
+	rHBridgeCfg1.Irq = (unsigned char) pIrqMap[pSettings->usDspIrq];
+	rHBridgeCfg1.AccessMode = 1;
+	rHBridgeCfg2.Enable = TRUE;
+
+
+	rBusmasterCfg2.Reserved = 0;
+	rBusmasterCfg1.Dma = (unsigned char) pDmaMap[pSettings->usDspDma];
+	rBusmasterCfg1.NumTransfers =
+		(unsigned char) pSettings->usNumTransfers;
+	rBusmasterCfg1.ReRequest = (unsigned char) pSettings->usReRequest;
+	rBusmasterCfg1.MEMCS16 = pSettings->bEnableMEMCS16;
+	rBusmasterCfg2.IsaMemCmdWidth =
+		(unsigned char) pSettings->usIsaMemCmdWidth;
+
+
+	rIsaProtCfg.Reserved = 0;
+	rIsaProtCfg.GateIOCHRDY = pSettings->bGateIOCHRDY;
+
+	rPowerMgmtCfg.Reserved = 0;
+	rPowerMgmtCfg.Enable = pSettings->bEnablePwrMgmt;
+
+	rHBusTimerCfg.LoadValue =
+		(unsigned char) pSettings->usHBusTimerLoadValue;
+
+	rLBusTimeoutDisable.Reserved = 0;
+	rLBusTimeoutDisable.DisableTimeout =
+		pSettings->bDisableLBusTimeout;
+
+	MKWORD(rChipReset) = ~pSettings->usChipletEnable;
+
+	rClockControl1.Reserved1 = rClockControl1.Reserved2 = 0;
+	rClockControl1.N_Divisor = pSettings->usN_Divisor;
+	rClockControl1.M_Multiplier = pSettings->usM_Multiplier;
+
+	rClockControl2.Reserved = 0;
+	rClockControl2.PllBypass = pSettings->bPllBypass;
+
+	/* Issue a soft reset to the chip */
+	/* Note: Since we may be coming in with 3780i clocks suspended, we must keep
+	* soft-reset active for 10ms.
+	*/
+	rSlaveControl.ClockControl = 0;
+	rSlaveControl.SoftReset = TRUE;
+	rSlaveControl.ConfigMode = FALSE;
+	rSlaveControl.Reserved = 0;
+
+	PRINTK_4(TRACE_3780I,
+		"3780i::dsp3780i_EnableDSP usDspBaseIO %x index %x taddr %x\n",
+		usDspBaseIO, DSP_IsaSlaveControl,
+		usDspBaseIO + DSP_IsaSlaveControl);
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780i_EnableDSP rSlaveContrl %x\n",
+		MKWORD(rSlaveControl));
+
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl));
+	MKWORD(tval) = InWordDsp(DSP_IsaSlaveControl);
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780i_EnableDSP rSlaveControl 2 %x\n", tval);
+
+
+	for (i = 0; i < 11; i++)
+		udelay(2000);
+
+	rSlaveControl.SoftReset = FALSE;
+	OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl));
+
+	MKWORD(tval) = InWordDsp(DSP_IsaSlaveControl);
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780i_EnableDSP rSlaveControl 3 %x\n", tval);
+
+
+	/* Program our general configuration registers */
+	WriteGenCfg(DSP_HBridgeCfg1Index, MKBYTE(rHBridgeCfg1));
+	WriteGenCfg(DSP_HBridgeCfg2Index, MKBYTE(rHBridgeCfg2));
+	WriteGenCfg(DSP_BusMasterCfg1Index, MKBYTE(rBusmasterCfg1));
+	WriteGenCfg(DSP_BusMasterCfg2Index, MKBYTE(rBusmasterCfg2));
+	WriteGenCfg(DSP_IsaProtCfgIndex, MKBYTE(rIsaProtCfg));
+	WriteGenCfg(DSP_PowerMgCfgIndex, MKBYTE(rPowerMgmtCfg));
+	WriteGenCfg(DSP_HBusTimerCfgIndex, MKBYTE(rHBusTimerCfg));
+
+	if (pSettings->bModemEnabled) {
+		WriteGenCfg(DSP_UartCfg1Index, MKBYTE(rUartCfg1));
+		WriteGenCfg(DSP_UartCfg2Index, MKBYTE(rUartCfg2));
+	}
+
+
+	rHBridgeControl.EnableDspInt = FALSE;
+	rHBridgeControl.MemAutoInc = TRUE;
+	rHBridgeControl.IoAutoInc = FALSE;
+	rHBridgeControl.DiagnosticMode = FALSE;
+
+	PRINTK_3(TRACE_3780I,
+		"3780i::dsp3780i_EnableDSP DSP_HBridgeControl %x rHBridgeControl %x\n",
+		DSP_HBridgeControl, MKWORD(rHBridgeControl));
+
+	OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+	WriteMsaCfg(DSP_LBusTimeoutDisable, MKWORD(rLBusTimeoutDisable));
+	WriteMsaCfg(DSP_ClockControl_1, MKWORD(rClockControl1));
+	WriteMsaCfg(DSP_ClockControl_2, MKWORD(rClockControl2));
+	WriteMsaCfg(DSP_ChipReset, MKWORD(rChipReset));
+
+	ChipID = ReadMsaCfg(DSP_ChipID);
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780I_EnableDSP exiting bRC=TRUE, ChipID %x\n",
+		ChipID);
+
+	return 0;
+}
+
+int dsp3780I_DisableDSP(DSP_3780I_CONFIG_SETTINGS * pSettings)
+{
+	unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+	DSP_ISA_SLAVE_CONTROL rSlaveControl;
+
+
+	PRINTK_1(TRACE_3780I, "3780i::dsp3780i_DisableDSP entry\n");
+
+	rSlaveControl.ClockControl = 0;
+	rSlaveControl.SoftReset = TRUE;
+	rSlaveControl.ConfigMode = FALSE;
+	rSlaveControl.Reserved = 0;
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl));
+
+	udelay(5);
+
+	rSlaveControl.ClockControl = 1;
+	OutWordDsp(DSP_IsaSlaveControl, MKWORD(rSlaveControl));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+	udelay(5);
+
+
+	PRINTK_1(TRACE_3780I, "3780i::dsp3780i_DisableDSP exit\n");
+
+	return 0;
+}
+
+int dsp3780I_Reset(DSP_3780I_CONFIG_SETTINGS * pSettings)
+{
+	unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+	DSP_BOOT_DOMAIN rBootDomain;
+	DSP_HBRIDGE_CONTROL rHBridgeControl;
+
+
+	PRINTK_1(TRACE_3780I, "3780i::dsp3780i_Reset entry\n");
+
+	spin_lock_irqsave(&dsp_lock, flags);
+	/* Mask DSP to PC interrupt */
+	MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl);
+
+	PRINTK_2(TRACE_3780I, "3780i::dsp3780i_Reset rHBridgeControl %x\n",
+		MKWORD(rHBridgeControl));
+
+	rHBridgeControl.EnableDspInt = FALSE;
+	OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+	/* Reset the core via the boot domain register */
+	rBootDomain.ResetCore = TRUE;
+	rBootDomain.Halt = TRUE;
+	rBootDomain.NMI = TRUE;
+	rBootDomain.Reserved = 0;
+
+	PRINTK_2(TRACE_3780I, "3780i::dsp3780i_Reset rBootDomain %x\n",
+		MKWORD(rBootDomain));
+
+	WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain));
+
+	/* Reset all the chiplets and then reactivate them */
+	WriteMsaCfg(DSP_ChipReset, 0xFFFF);
+	udelay(5);
+	WriteMsaCfg(DSP_ChipReset,
+			(unsigned short) (~pSettings->usChipletEnable));
+
+
+	PRINTK_1(TRACE_3780I, "3780i::dsp3780i_Reset exit bRC=0\n");
+
+	return 0;
+}
+
+
+int dsp3780I_Run(DSP_3780I_CONFIG_SETTINGS * pSettings)
+{
+	unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+	DSP_BOOT_DOMAIN rBootDomain;
+	DSP_HBRIDGE_CONTROL rHBridgeControl;
+
+
+	PRINTK_1(TRACE_3780I, "3780i::dsp3780i_Run entry\n");
+
+
+	/* Transition the core to a running state */
+	rBootDomain.ResetCore = TRUE;
+	rBootDomain.Halt = FALSE;
+	rBootDomain.NMI = TRUE;
+	rBootDomain.Reserved = 0;
+	WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain));
+
+	udelay(5);
+
+	rBootDomain.ResetCore = FALSE;
+	WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain));
+	udelay(5);
+
+	rBootDomain.NMI = FALSE;
+	WriteMsaCfg(DSP_MspBootDomain, MKWORD(rBootDomain));
+	udelay(5);
+
+	/* Enable DSP to PC interrupt */
+	spin_lock_irqsave(&dsp_lock, flags);
+	MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl);
+	rHBridgeControl.EnableDspInt = TRUE;
+
+	PRINTK_2(TRACE_3780I, "3780i::dsp3780i_Run rHBridgeControl %x\n",
+		MKWORD(rHBridgeControl));
+
+	OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+
+	PRINTK_1(TRACE_3780I, "3780i::dsp3780i_Run exit bRC=TRUE\n");
+
+	return 0;
+}
+
+
+int dsp3780I_ReadDStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+                        unsigned uCount, unsigned long ulDSPAddr)
+{
+	unsigned short __user *pusBuffer = pvBuffer;
+	unsigned short val;
+
+
+	PRINTK_5(TRACE_3780I,
+		"3780i::dsp3780I_ReadDStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+		usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+
+	/* Set the initial MSA address. No adjustments need to be made to data store addresses */
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+	OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+	/* Transfer the memory block */
+	while (uCount-- != 0) {
+		spin_lock_irqsave(&dsp_lock, flags);
+		val = InWordDsp(DSP_MsaDataDSISHigh);
+		spin_unlock_irqrestore(&dsp_lock, flags);
+		if(put_user(val, pusBuffer++))
+			return -EFAULT;
+
+		PRINTK_3(TRACE_3780I,
+			"3780I::dsp3780I_ReadDStore uCount %x val %x\n",
+			uCount, val);
+
+		PaceMsaAccess(usDspBaseIO);
+	}
+
+
+	PRINTK_1(TRACE_3780I,
+		"3780I::dsp3780I_ReadDStore exit bRC=TRUE\n");
+
+	return 0;
+}
+
+int dsp3780I_ReadAndClearDStore(unsigned short usDspBaseIO,
+                                void __user *pvBuffer, unsigned uCount,
+                                unsigned long ulDSPAddr)
+{
+	unsigned short __user *pusBuffer = pvBuffer;
+	unsigned short val;
+
+
+	PRINTK_5(TRACE_3780I,
+		"3780i::dsp3780I_ReadAndDStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+		usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+
+	/* Set the initial MSA address. No adjustments need to be made to data store addresses */
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+	OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+	/* Transfer the memory block */
+	while (uCount-- != 0) {
+		spin_lock_irqsave(&dsp_lock, flags);
+		val = InWordDsp(DSP_ReadAndClear);
+		spin_unlock_irqrestore(&dsp_lock, flags);
+		if(put_user(val, pusBuffer++))
+			return -EFAULT;
+
+		PRINTK_3(TRACE_3780I,
+			"3780I::dsp3780I_ReadAndCleanDStore uCount %x val %x\n",
+			uCount, val);
+
+		PaceMsaAccess(usDspBaseIO);
+	}
+
+
+	PRINTK_1(TRACE_3780I,
+		"3780I::dsp3780I_ReadAndClearDStore exit bRC=TRUE\n");
+
+	return 0;
+}
+
+
+int dsp3780I_WriteDStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+                         unsigned uCount, unsigned long ulDSPAddr)
+{
+	unsigned short __user *pusBuffer = pvBuffer;
+
+
+	PRINTK_5(TRACE_3780I,
+		"3780i::dsp3780D_WriteDStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+		usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+
+	/* Set the initial MSA address. No adjustments need to be made to data store addresses */
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+	OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+	/* Transfer the memory block */
+	while (uCount-- != 0) {
+		unsigned short val;
+		if(get_user(val, pusBuffer++))
+			return -EFAULT;
+		spin_lock_irqsave(&dsp_lock, flags);
+		OutWordDsp(DSP_MsaDataDSISHigh, val);
+		spin_unlock_irqrestore(&dsp_lock, flags);
+
+		PRINTK_3(TRACE_3780I,
+			"3780I::dsp3780I_WriteDStore uCount %x val %x\n",
+			uCount, val);
+
+		PaceMsaAccess(usDspBaseIO);
+	}
+
+
+	PRINTK_1(TRACE_3780I,
+		"3780I::dsp3780D_WriteDStore exit bRC=TRUE\n");
+
+	return 0;
+}
+
+
+int dsp3780I_ReadIStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+                        unsigned uCount, unsigned long ulDSPAddr)
+{
+	unsigned short __user *pusBuffer = pvBuffer;
+
+	PRINTK_5(TRACE_3780I,
+		"3780i::dsp3780I_ReadIStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+		usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+	/*
+	* Set the initial MSA address. To convert from an instruction store
+	* address to an MSA address
+	* shift the address two bits to the left and set bit 22
+	*/
+	ulDSPAddr = (ulDSPAddr << 2) | (1 << 22);
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+	OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+	/* Transfer the memory block */
+	while (uCount-- != 0) {
+		unsigned short val_lo, val_hi;
+		spin_lock_irqsave(&dsp_lock, flags);
+		val_lo = InWordDsp(DSP_MsaDataISLow);
+		val_hi = InWordDsp(DSP_MsaDataDSISHigh);
+		spin_unlock_irqrestore(&dsp_lock, flags);
+		if(put_user(val_lo, pusBuffer++))
+			return -EFAULT;
+		if(put_user(val_hi, pusBuffer++))
+			return -EFAULT;
+
+		PRINTK_4(TRACE_3780I,
+			"3780I::dsp3780I_ReadIStore uCount %x val_lo %x val_hi %x\n",
+			uCount, val_lo, val_hi);
+
+		PaceMsaAccess(usDspBaseIO);
+
+	}
+
+	PRINTK_1(TRACE_3780I,
+		"3780I::dsp3780I_ReadIStore exit bRC=TRUE\n");
+
+	return 0;
+}
+
+
+int dsp3780I_WriteIStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+                         unsigned uCount, unsigned long ulDSPAddr)
+{
+	unsigned short __user *pusBuffer = pvBuffer;
+
+	PRINTK_5(TRACE_3780I,
+		"3780i::dsp3780I_WriteIStore entry usDspBaseIO %x, pusBuffer %p, uCount %x, ulDSPAddr %lx\n",
+		usDspBaseIO, pusBuffer, uCount, ulDSPAddr);
+
+
+	/*
+	* Set the initial MSA address. To convert from an instruction store
+	* address to an MSA address
+	* shift the address two bits to the left and set bit 22
+	*/
+	ulDSPAddr = (ulDSPAddr << 2) | (1 << 22);
+	spin_lock_irqsave(&dsp_lock, flags);
+	OutWordDsp(DSP_MsaAddrLow, (unsigned short) ulDSPAddr);
+	OutWordDsp(DSP_MsaAddrHigh, (unsigned short) (ulDSPAddr >> 16));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+	/* Transfer the memory block */
+	while (uCount-- != 0) {
+		unsigned short val_lo, val_hi;
+		if(get_user(val_lo, pusBuffer++))
+			return -EFAULT;
+		if(get_user(val_hi, pusBuffer++))
+			return -EFAULT;
+		spin_lock_irqsave(&dsp_lock, flags);
+		OutWordDsp(DSP_MsaDataISLow, val_lo);
+		OutWordDsp(DSP_MsaDataDSISHigh, val_hi);
+		spin_unlock_irqrestore(&dsp_lock, flags);
+
+		PRINTK_4(TRACE_3780I,
+			"3780I::dsp3780I_WriteIStore uCount %x val_lo %x val_hi %x\n",
+			uCount, val_lo, val_hi);
+
+		PaceMsaAccess(usDspBaseIO);
+
+	}
+
+	PRINTK_1(TRACE_3780I,
+		"3780I::dsp3780I_WriteIStore exit bRC=TRUE\n");
+
+	return 0;
+}
+
+
+int dsp3780I_GetIPCSource(unsigned short usDspBaseIO,
+                          unsigned short *pusIPCSource)
+{
+	DSP_HBRIDGE_CONTROL rHBridgeControl;
+	unsigned short temp;
+
+
+	PRINTK_3(TRACE_3780I,
+		"3780i::dsp3780I_GetIPCSource entry usDspBaseIO %x pusIPCSource %p\n",
+		usDspBaseIO, pusIPCSource);
+
+	/*
+	* Disable DSP to PC interrupts, read the interrupt register,
+	* clear the pending IPC bits, and reenable DSP to PC interrupts
+	*/
+	spin_lock_irqsave(&dsp_lock, flags);
+	MKWORD(rHBridgeControl) = InWordDsp(DSP_HBridgeControl);
+	rHBridgeControl.EnableDspInt = FALSE;
+	OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+
+	*pusIPCSource = InWordDsp(DSP_Interrupt);
+	temp = (unsigned short) ~(*pusIPCSource);
+
+	PRINTK_3(TRACE_3780I,
+		"3780i::dsp3780I_GetIPCSource, usIPCSource %x ~ %x\n",
+		*pusIPCSource, temp);
+
+	OutWordDsp(DSP_Interrupt, (unsigned short) ~(*pusIPCSource));
+
+	rHBridgeControl.EnableDspInt = TRUE;
+	OutWordDsp(DSP_HBridgeControl, MKWORD(rHBridgeControl));
+	spin_unlock_irqrestore(&dsp_lock, flags);
+
+
+	PRINTK_2(TRACE_3780I,
+		"3780i::dsp3780I_GetIPCSource exit usIPCSource %x\n",
+		*pusIPCSource);
+
+	return 0;
+}
diff --git a/drivers/char/mwave/3780i.h b/drivers/char/mwave/3780i.h
new file mode 100644
index 0000000..3e7d020
--- /dev/null
+++ b/drivers/char/mwave/3780i.h
@@ -0,0 +1,362 @@
+/*
+*
+* 3780i.h -- declarations for 3780i.c
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#ifndef _LINUX_3780I_H
+#define _LINUX_3780I_H
+
+#include <asm/io.h>
+
+/* DSP I/O port offsets and definitions */
+#define DSP_IsaSlaveControl        0x0000	/* ISA slave control register */
+#define DSP_IsaSlaveStatus         0x0001	/* ISA slave status register */
+#define DSP_ConfigAddress          0x0002	/* General config address register */
+#define DSP_ConfigData             0x0003	/* General config data register */
+#define DSP_HBridgeControl         0x0002	/* HBridge control register */
+#define DSP_MsaAddrLow             0x0004	/* MSP System Address, low word */
+#define DSP_MsaAddrHigh            0x0006	/* MSP System Address, high word */
+#define DSP_MsaDataDSISHigh        0x0008	/* MSA data register: d-store word or high byte of i-store */
+#define DSP_MsaDataISLow           0x000A	/* MSA data register: low word of i-store */
+#define DSP_ReadAndClear           0x000C	/* MSA read and clear data register */
+#define DSP_Interrupt              0x000E	/* Interrupt register (IPC source) */
+
+typedef struct {
+	unsigned char ClockControl:1;	/* RW: Clock control: 0=normal, 1=stop 3780i clocks */
+	unsigned char SoftReset:1;	/* RW: Soft reset 0=normal, 1=soft reset active */
+	unsigned char ConfigMode:1;	/* RW: Configuration mode, 0=normal, 1=config mode */
+	unsigned char Reserved:5;	/* 0: Reserved */
+} DSP_ISA_SLAVE_CONTROL;
+
+
+typedef struct {
+	unsigned short EnableDspInt:1;	/* RW: Enable DSP to X86 ISA interrupt 0=mask it, 1=enable it */
+	unsigned short MemAutoInc:1;	/* RW: Memory address auto increment, 0=disable, 1=enable */
+	unsigned short IoAutoInc:1;	/* RW: I/O address auto increment, 0=disable, 1=enable */
+	unsigned short DiagnosticMode:1;	/* RW: Disgnostic mode 0=nromal, 1=diagnostic mode */
+	unsigned short IsaPacingTimer:12;	/* R: ISA access pacing timer: count of core cycles stolen */
+} DSP_HBRIDGE_CONTROL;
+
+
+/* DSP register indexes used with the configuration register address (index) register */
+#define DSP_UartCfg1Index          0x0003	/* UART config register 1 */
+#define DSP_UartCfg2Index          0x0004	/* UART config register 2 */
+#define DSP_HBridgeCfg1Index       0x0007	/* HBridge config register 1 */
+#define DSP_HBridgeCfg2Index       0x0008	/* HBridge config register 2 */
+#define DSP_BusMasterCfg1Index     0x0009	/* ISA bus master config register 1 */
+#define DSP_BusMasterCfg2Index     0x000A	/* ISA bus master config register 2 */
+#define DSP_IsaProtCfgIndex        0x000F	/* ISA protocol control register */
+#define DSP_PowerMgCfgIndex        0x0010	/* Low poser suspend/resume enable */
+#define DSP_HBusTimerCfgIndex      0x0011	/* HBUS timer load value */
+
+typedef struct {
+	unsigned char IrqActiveLow:1;	/* RW: IRQ active high or low: 0=high, 1=low */
+	unsigned char IrqPulse:1;	/* RW: IRQ pulse or level: 0=level, 1=pulse  */
+	unsigned char Irq:3;	/* RW: IRQ selection */
+	unsigned char BaseIO:2;	/* RW: Base I/O selection */
+	unsigned char Reserved:1;	/* 0: Reserved */
+} DSP_UART_CFG_1;
+
+typedef struct {
+	unsigned char Enable:1;	/* RW: Enable I/O and IRQ: 0=FALSE, 1=TRUE */
+	unsigned char Reserved:7;	/* 0: Reserved */
+} DSP_UART_CFG_2;
+
+typedef struct {
+	unsigned char IrqActiveLow:1;	/* RW: IRQ active high=0 or low=1 */
+	unsigned char IrqPulse:1;	/* RW: IRQ pulse=1 or level=0 */
+	unsigned char Irq:3;	/* RW: IRQ selection */
+	unsigned char AccessMode:1;	/* RW: 16-bit register access method 0=byte, 1=word */
+	unsigned char Reserved:2;	/* 0: Reserved */
+} DSP_HBRIDGE_CFG_1;
+
+typedef struct {
+	unsigned char Enable:1;	/* RW: enable I/O and IRQ: 0=FALSE, 1=TRUE */
+	unsigned char Reserved:7;	/* 0: Reserved */
+} DSP_HBRIDGE_CFG_2;
+
+
+typedef struct {
+	unsigned char Dma:3;	/* RW: DMA channel selection */
+	unsigned char NumTransfers:2;	/* RW: Maximum # of transfers once being granted the ISA bus */
+	unsigned char ReRequest:2;	/* RW: Minumum delay between releasing the ISA bus and requesting it again */
+	unsigned char MEMCS16:1;	/* RW: ISA signal MEMCS16: 0=disabled, 1=enabled */
+} DSP_BUSMASTER_CFG_1;
+
+typedef struct {
+	unsigned char IsaMemCmdWidth:2;	/* RW: ISA memory command width */
+	unsigned char Reserved:6;	/* 0: Reserved */
+} DSP_BUSMASTER_CFG_2;
+
+
+typedef struct {
+	unsigned char GateIOCHRDY:1;	/* RW: Enable IOCHRDY gating: 0=FALSE, 1=TRUE */
+	unsigned char Reserved:7;	/* 0: Reserved */
+} DSP_ISA_PROT_CFG;
+
+typedef struct {
+	unsigned char Enable:1;	/* RW: Enable low power suspend/resume 0=FALSE, 1=TRUE */
+	unsigned char Reserved:7;	/* 0: Reserved */
+} DSP_POWER_MGMT_CFG;
+
+typedef struct {
+	unsigned char LoadValue:8;	/* RW: HBUS timer load value */
+} DSP_HBUS_TIMER_CFG;
+
+
+
+/* DSP registers that exist in MSA I/O space */
+#define DSP_ChipID                 0x80000000
+#define DSP_MspBootDomain          0x80000580
+#define DSP_LBusTimeoutDisable     0x80000580
+#define DSP_ClockControl_1         0x8000058A
+#define DSP_ClockControl_2         0x8000058C
+#define DSP_ChipReset              0x80000588
+#define DSP_GpioModeControl_15_8   0x80000082
+#define DSP_GpioDriverEnable_15_8  0x80000076
+#define DSP_GpioOutputData_15_8    0x80000072
+
+typedef struct {
+	unsigned short NMI:1;	/* RW: non maskable interrupt */
+	unsigned short Halt:1;	/* RW: Halt MSP clock */
+	unsigned short ResetCore:1;	/* RW: Reset MSP core interface */
+	unsigned short Reserved:13;	/* 0: Reserved */
+} DSP_BOOT_DOMAIN;
+
+typedef struct {
+	unsigned short DisableTimeout:1;	/* RW: Disable LBus timeout */
+	unsigned short Reserved:15;	/* 0: Reserved */
+} DSP_LBUS_TIMEOUT_DISABLE;
+
+typedef struct {
+	unsigned short Memory:1;	/* RW: Reset memory interface */
+	unsigned short SerialPort1:1;	/* RW: Reset serial port 1 interface */
+	unsigned short SerialPort2:1;	/* RW: Reset serial port 2 interface */
+	unsigned short SerialPort3:1;	/* RW: Reset serial port 3 interface */
+	unsigned short Gpio:1;	/* RW: Reset GPIO interface */
+	unsigned short Dma:1;	/* RW: Reset DMA interface */
+	unsigned short SoundBlaster:1;	/* RW: Reset soundblaster interface */
+	unsigned short Uart:1;	/* RW: Reset UART interface */
+	unsigned short Midi:1;	/* RW: Reset MIDI interface */
+	unsigned short IsaMaster:1;	/* RW: Reset ISA master interface */
+	unsigned short Reserved:6;	/* 0: Reserved */
+} DSP_CHIP_RESET;
+
+typedef struct {
+	unsigned short N_Divisor:6;	/* RW: (N) PLL output clock divisor */
+	unsigned short Reserved1:2;	/* 0: reserved */
+	unsigned short M_Multiplier:6;	/* RW: (M) PLL feedback clock multiplier */
+	unsigned short Reserved2:2;	/* 0: reserved */
+} DSP_CLOCK_CONTROL_1;
+
+typedef struct {
+	unsigned short PllBypass:1;	/* RW: PLL Bypass */
+	unsigned short Reserved:15;	/* 0: Reserved */
+} DSP_CLOCK_CONTROL_2;
+
+typedef struct {
+	unsigned short Latch8:1;
+	unsigned short Latch9:1;
+	unsigned short Latch10:1;
+	unsigned short Latch11:1;
+	unsigned short Latch12:1;
+	unsigned short Latch13:1;
+	unsigned short Latch14:1;
+	unsigned short Latch15:1;
+	unsigned short Mask8:1;
+	unsigned short Mask9:1;
+	unsigned short Mask10:1;
+	unsigned short Mask11:1;
+	unsigned short Mask12:1;
+	unsigned short Mask13:1;
+	unsigned short Mask14:1;
+	unsigned short Mask15:1;
+} DSP_GPIO_OUTPUT_DATA_15_8;
+
+typedef struct {
+	unsigned short Enable8:1;
+	unsigned short Enable9:1;
+	unsigned short Enable10:1;
+	unsigned short Enable11:1;
+	unsigned short Enable12:1;
+	unsigned short Enable13:1;
+	unsigned short Enable14:1;
+	unsigned short Enable15:1;
+	unsigned short Mask8:1;
+	unsigned short Mask9:1;
+	unsigned short Mask10:1;
+	unsigned short Mask11:1;
+	unsigned short Mask12:1;
+	unsigned short Mask13:1;
+	unsigned short Mask14:1;
+	unsigned short Mask15:1;
+} DSP_GPIO_DRIVER_ENABLE_15_8;
+
+typedef struct {
+	unsigned short GpioMode8:2;
+	unsigned short GpioMode9:2;
+	unsigned short GpioMode10:2;
+	unsigned short GpioMode11:2;
+	unsigned short GpioMode12:2;
+	unsigned short GpioMode13:2;
+	unsigned short GpioMode14:2;
+	unsigned short GpioMode15:2;
+} DSP_GPIO_MODE_15_8;
+
+/* Component masks that are defined in dspmgr.h */
+#define MW_ADC_MASK    0x0001
+#define MW_AIC2_MASK   0x0006
+#define MW_MIDI_MASK   0x0008
+#define MW_CDDAC_MASK  0x8001
+#define MW_AIC1_MASK   0xE006
+#define MW_UART_MASK   0xE00A
+#define MW_ACI_MASK    0xE00B
+
+/*
+* Definition of 3780i configuration structure.  Unless otherwise stated,
+* these values are provided as input to the 3780i support layer.  At present,
+* the only values maintained by the 3780i support layer are the saved UART
+* registers.
+*/
+typedef struct _DSP_3780I_CONFIG_SETTINGS {
+
+	/* Location of base configuration register */
+	unsigned short usBaseConfigIO;
+
+	/* Enables for various DSP components */
+	int bDSPEnabled;
+	int bModemEnabled;
+	int bInterruptClaimed;
+
+	/* IRQ, DMA, and Base I/O addresses for various DSP components */
+	unsigned short usDspIrq;
+	unsigned short usDspDma;
+	unsigned short usDspBaseIO;
+	unsigned short usUartIrq;
+	unsigned short usUartBaseIO;
+
+	/* IRQ modes for various DSP components */
+	int bDspIrqActiveLow;
+	int bUartIrqActiveLow;
+	int bDspIrqPulse;
+	int bUartIrqPulse;
+
+	/* Card abilities */
+	unsigned uIps;
+	unsigned uDStoreSize;
+	unsigned uIStoreSize;
+	unsigned uDmaBandwidth;
+
+	/* Adapter specific 3780i settings */
+	unsigned short usNumTransfers;
+	unsigned short usReRequest;
+	int bEnableMEMCS16;
+	unsigned short usIsaMemCmdWidth;
+	int bGateIOCHRDY;
+	int bEnablePwrMgmt;
+	unsigned short usHBusTimerLoadValue;
+	int bDisableLBusTimeout;
+	unsigned short usN_Divisor;
+	unsigned short usM_Multiplier;
+	int bPllBypass;
+	unsigned short usChipletEnable;	/* Used with the chip reset register to enable specific chiplets */
+
+	/* Saved UART registers. These are maintained by the 3780i support layer. */
+	int bUartSaved;		/* True after a successful save of the UART registers */
+	unsigned char ucIER;	/* Interrupt enable register */
+	unsigned char ucFCR;	/* FIFO control register */
+	unsigned char ucLCR;	/* Line control register */
+	unsigned char ucMCR;	/* Modem control register */
+	unsigned char ucSCR;	/* Scratch register */
+	unsigned char ucDLL;	/* Divisor latch, low byte */
+	unsigned char ucDLM;	/* Divisor latch, high byte */
+} DSP_3780I_CONFIG_SETTINGS;
+
+
+/* 3780i support functions */
+int dsp3780I_EnableDSP(DSP_3780I_CONFIG_SETTINGS * pSettings,
+                       unsigned short *pIrqMap,
+                       unsigned short *pDmaMap);
+int dsp3780I_DisableDSP(DSP_3780I_CONFIG_SETTINGS * pSettings);
+int dsp3780I_Reset(DSP_3780I_CONFIG_SETTINGS * pSettings);
+int dsp3780I_Run(DSP_3780I_CONFIG_SETTINGS * pSettings);
+int dsp3780I_ReadDStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+                        unsigned uCount, unsigned long ulDSPAddr);
+int dsp3780I_ReadAndClearDStore(unsigned short usDspBaseIO,
+                                void __user *pvBuffer, unsigned uCount,
+                                unsigned long ulDSPAddr);
+int dsp3780I_WriteDStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+                         unsigned uCount, unsigned long ulDSPAddr);
+int dsp3780I_ReadIStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+                        unsigned uCount, unsigned long ulDSPAddr);
+int dsp3780I_WriteIStore(unsigned short usDspBaseIO, void __user *pvBuffer,
+                         unsigned uCount, unsigned long ulDSPAddr);
+unsigned short dsp3780I_ReadMsaCfg(unsigned short usDspBaseIO,
+                                   unsigned long ulMsaAddr);
+void dsp3780I_WriteMsaCfg(unsigned short usDspBaseIO,
+                          unsigned long ulMsaAddr, unsigned short usValue);
+void dsp3780I_WriteGenCfg(unsigned short usDspBaseIO, unsigned uIndex,
+                          unsigned char ucValue);
+unsigned char dsp3780I_ReadGenCfg(unsigned short usDspBaseIO,
+                                  unsigned uIndex);
+int dsp3780I_GetIPCSource(unsigned short usDspBaseIO,
+                          unsigned short *pusIPCSource);
+
+/* I/O port access macros */
+#define MKWORD(var) (*((unsigned short *)(&var)))
+#define MKBYTE(var) (*((unsigned char *)(&var)))
+
+#define WriteMsaCfg(addr,value) dsp3780I_WriteMsaCfg(usDspBaseIO,addr,value)
+#define ReadMsaCfg(addr) dsp3780I_ReadMsaCfg(usDspBaseIO,addr)
+#define WriteGenCfg(index,value) dsp3780I_WriteGenCfg(usDspBaseIO,index,value)
+#define ReadGenCfg(index) dsp3780I_ReadGenCfg(usDspBaseIO,index)
+
+#define InWordDsp(index)          inw(usDspBaseIO+index)
+#define InByteDsp(index)          inb(usDspBaseIO+index)
+#define OutWordDsp(index,value)   outw(value,usDspBaseIO+index)
+#define OutByteDsp(index,value)   outb(value,usDspBaseIO+index)
+
+#endif
diff --git a/drivers/char/mwave/Makefile b/drivers/char/mwave/Makefile
new file mode 100644
index 0000000..754c9e2
--- /dev/null
+++ b/drivers/char/mwave/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for ACP Modem (Mwave).
+#
+# See the README file in this directory for more info. <paulsch@us.ibm.com>
+#
+
+obj-$(CONFIG_MWAVE) += mwave.o
+
+mwave-objs := mwavedd.o smapi.o tp3780i.o 3780i.o
+
+# To have the mwave driver disable other uarts if necessary
+# EXTRA_CFLAGS += -DMWAVE_FUTZ_WITH_OTHER_DEVICES
+
+# To compile in lots (~20 KiB) of run-time enablable printk()s for debugging:
+EXTRA_CFLAGS += -DMW_TRACE
diff --git a/drivers/char/mwave/README b/drivers/char/mwave/README
new file mode 100644
index 0000000..70f8d19
--- /dev/null
+++ b/drivers/char/mwave/README
@@ -0,0 +1,50 @@
+Module options
+--------------
+
+The mwave module takes the following options.  Note that these options
+are not saved by the BIOS and so do not persist after unload and reload.
+
+  mwave_debug=value, where value is bitwise OR of trace flags:
+	0x0001 mwavedd api tracing
+	0x0002 smapi api tracing
+	0x0004 3780i tracing
+	0x0008 tp3780i tracing
+
+        Tracing only occurs if the driver has been compiled with the
+        MW_TRACE macro #defined  (i.e. let EXTRA_CFLAGS += -DMW_TRACE
+        in the Makefile).
+
+  mwave_3780i_irq=5/7/10/11/15
+	If the dsp irq has not been setup and stored in bios by the 
+	thinkpad configuration utility then this parameter allows the
+	irq used by the dsp to be configured.
+
+  mwave_3780i_io=0x130/0x350/0x0070/0xDB0
+	If the dsp io range has not been setup and stored in bios by the 
+	thinkpad configuration utility then this parameter allows the
+	io range used by the dsp to be configured.
+
+  mwave_uart_irq=3/4
+	If the mwave's uart irq has not been setup and stored in bios by the 
+	thinkpad configuration utility then this parameter allows the
+	irq used by the mwave uart to be configured.
+
+  mwave_uart_io=0x3f8/0x2f8/0x3E8/0x2E8
+	If the uart io range has not been setup and stored in bios by the 
+	thinkpad configuration utility then this parameter allows the
+	io range used by the mwave uart to be configured.
+
+Example to enable the 3780i DSP using ttyS1 resources:
+	
+  insmod mwave mwave_3780i_irq=10 mwave_3780i_io=0x0130 mwave_uart_irq=3 mwave_uart_io=0x2f8
+
+Accessing the driver
+--------------------
+
+You must also create a node for the driver.  Without devfs:
+  mkdir -p /dev/modems
+  mknod --mode=660 /dev/modems/mwave c 10 219
+With devfs:
+  mkdir -p /dev/modems
+  ln -s ../misc/mwave /dev/modems/mwave
+
diff --git a/drivers/char/mwave/mwavedd.c b/drivers/char/mwave/mwavedd.c
new file mode 100644
index 0000000..d37625d
--- /dev/null
+++ b/drivers/char/mwave/mwavedd.c
@@ -0,0 +1,674 @@
+/*
+*
+* mwavedd.c -- mwave device driver
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/major.h>
+#include <linux/miscdevice.h>
+#include <linux/device.h>
+#include <linux/serial.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include "smapi.h"
+#include "mwavedd.h"
+#include "3780i.h"
+#include "tp3780i.h"
+
+MODULE_DESCRIPTION("3780i Advanced Communications Processor (Mwave) driver");
+MODULE_AUTHOR("Mike Sullivan and Paul Schroeder");
+MODULE_LICENSE("GPL");
+
+/*
+* These parameters support the setting of MWave resources. Note that no
+* checks are made against other devices (ie. superio) for conflicts.
+* We'll depend on users using the tpctl utility to do that for now
+*/
+int mwave_debug = 0;
+int mwave_3780i_irq = 0;
+int mwave_3780i_io = 0;
+int mwave_uart_irq = 0;
+int mwave_uart_io = 0;
+module_param(mwave_debug, int, 0);
+module_param(mwave_3780i_irq, int, 0);
+module_param(mwave_3780i_io, int, 0);
+module_param(mwave_uart_irq, int, 0);
+module_param(mwave_uart_io, int, 0);
+
+static int mwave_open(struct inode *inode, struct file *file);
+static int mwave_close(struct inode *inode, struct file *file);
+static int mwave_ioctl(struct inode *inode, struct file *filp,
+                       unsigned int iocmd, unsigned long ioarg);
+
+MWAVE_DEVICE_DATA mwave_s_mdd;
+
+static int mwave_open(struct inode *inode, struct file *file)
+{
+	unsigned int retval = 0;
+
+	PRINTK_3(TRACE_MWAVE,
+		"mwavedd::mwave_open, entry inode %p file %p\n",
+		 inode, file);
+	PRINTK_2(TRACE_MWAVE,
+		"mwavedd::mwave_open, exit return retval %x\n", retval);
+
+	return retval;
+}
+
+static int mwave_close(struct inode *inode, struct file *file)
+{
+	unsigned int retval = 0;
+
+	PRINTK_3(TRACE_MWAVE,
+		"mwavedd::mwave_close, entry inode %p file %p\n",
+		 inode,  file);
+
+	PRINTK_2(TRACE_MWAVE, "mwavedd::mwave_close, exit retval %x\n",
+		retval);
+
+	return retval;
+}
+
+static int mwave_ioctl(struct inode *inode, struct file *file,
+                       unsigned int iocmd, unsigned long ioarg)
+{
+	unsigned int retval = 0;
+	pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
+	void __user *arg = (void __user *)ioarg;
+
+	PRINTK_5(TRACE_MWAVE,
+		"mwavedd::mwave_ioctl, entry inode %p file %p cmd %x arg %x\n",
+		 inode,  file, iocmd, (int) ioarg);
+
+	switch (iocmd) {
+
+		case IOCTL_MW_RESET:
+			PRINTK_1(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl, IOCTL_MW_RESET"
+				" calling tp3780I_ResetDSP\n");
+			retval = tp3780I_ResetDSP(&pDrvData->rBDData);
+			PRINTK_2(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl, IOCTL_MW_RESET"
+				" retval %x from tp3780I_ResetDSP\n",
+				retval);
+			break;
+	
+		case IOCTL_MW_RUN:
+			PRINTK_1(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl, IOCTL_MW_RUN"
+				" calling tp3780I_StartDSP\n");
+			retval = tp3780I_StartDSP(&pDrvData->rBDData);
+			PRINTK_2(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl, IOCTL_MW_RUN"
+				" retval %x from tp3780I_StartDSP\n",
+				retval);
+			break;
+	
+		case IOCTL_MW_DSP_ABILITIES: {
+			MW_ABILITIES rAbilities;
+	
+			PRINTK_1(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl,"
+				" IOCTL_MW_DSP_ABILITIES calling"
+				" tp3780I_QueryAbilities\n");
+			retval = tp3780I_QueryAbilities(&pDrvData->rBDData,
+					&rAbilities);
+			PRINTK_2(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl, IOCTL_MW_DSP_ABILITIES"
+				" retval %x from tp3780I_QueryAbilities\n",
+				retval);
+			if (retval == 0) {
+				if( copy_to_user(arg, &rAbilities,
+							sizeof(MW_ABILITIES)) )
+					return -EFAULT;
+			}
+			PRINTK_2(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl, IOCTL_MW_DSP_ABILITIES"
+				" exit retval %x\n",
+				retval);
+		}
+			break;
+	
+		case IOCTL_MW_READ_DATA:
+		case IOCTL_MW_READCLEAR_DATA: {
+			MW_READWRITE rReadData;
+			unsigned short __user *pusBuffer = NULL;
+	
+			if( copy_from_user(&rReadData, arg,
+						sizeof(MW_READWRITE)) )
+				return -EFAULT;
+			pusBuffer = (unsigned short __user *) (rReadData.pBuf);
+	
+			PRINTK_4(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl IOCTL_MW_READ_DATA,"
+				" size %lx, ioarg %lx pusBuffer %p\n",
+				rReadData.ulDataLength, ioarg, pusBuffer);
+			retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
+					iocmd,
+					pusBuffer,
+					rReadData.ulDataLength,
+					rReadData.usDspAddress);
+		}
+			break;
+	
+		case IOCTL_MW_READ_INST: {
+			MW_READWRITE rReadData;
+			unsigned short __user *pusBuffer = NULL;
+	
+			if( copy_from_user(&rReadData, arg,
+						sizeof(MW_READWRITE)) )
+				return -EFAULT;
+			pusBuffer = (unsigned short __user *) (rReadData.pBuf);
+	
+			PRINTK_4(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl IOCTL_MW_READ_INST,"
+				" size %lx, ioarg %lx pusBuffer %p\n",
+				rReadData.ulDataLength / 2, ioarg,
+				pusBuffer);
+			retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
+				iocmd, pusBuffer,
+				rReadData.ulDataLength / 2,
+				rReadData.usDspAddress);
+		}
+			break;
+	
+		case IOCTL_MW_WRITE_DATA: {
+			MW_READWRITE rWriteData;
+			unsigned short __user *pusBuffer = NULL;
+	
+			if( copy_from_user(&rWriteData, arg,
+						sizeof(MW_READWRITE)) )
+				return -EFAULT;
+			pusBuffer = (unsigned short __user *) (rWriteData.pBuf);
+	
+			PRINTK_4(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl IOCTL_MW_WRITE_DATA,"
+				" size %lx, ioarg %lx pusBuffer %p\n",
+				rWriteData.ulDataLength, ioarg,
+				pusBuffer);
+			retval = tp3780I_ReadWriteDspDStore(&pDrvData->rBDData,
+					iocmd, pusBuffer,
+					rWriteData.ulDataLength,
+					rWriteData.usDspAddress);
+		}
+			break;
+	
+		case IOCTL_MW_WRITE_INST: {
+			MW_READWRITE rWriteData;
+			unsigned short __user *pusBuffer = NULL;
+	
+			if( copy_from_user(&rWriteData, arg,
+						sizeof(MW_READWRITE)) )
+				return -EFAULT;
+			pusBuffer = (unsigned short __user *)(rWriteData.pBuf);
+	
+			PRINTK_4(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl IOCTL_MW_WRITE_INST,"
+				" size %lx, ioarg %lx pusBuffer %p\n",
+				rWriteData.ulDataLength, ioarg,
+				pusBuffer);
+			retval = tp3780I_ReadWriteDspIStore(&pDrvData->rBDData,
+					iocmd, pusBuffer,
+					rWriteData.ulDataLength,
+					rWriteData.usDspAddress);
+		}
+			break;
+	
+		case IOCTL_MW_REGISTER_IPC: {
+			unsigned int ipcnum = (unsigned int) ioarg;
+	
+			PRINTK_3(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl IOCTL_MW_REGISTER_IPC"
+				" ipcnum %x entry usIntCount %x\n",
+				ipcnum,
+				pDrvData->IPCs[ipcnum].usIntCount);
+	
+			if (ipcnum > ARRAY_SIZE(pDrvData->IPCs)) {
+				PRINTK_ERROR(KERN_ERR_MWAVE
+						"mwavedd::mwave_ioctl:"
+						" IOCTL_MW_REGISTER_IPC:"
+						" Error: Invalid ipcnum %x\n",
+						ipcnum);
+				return -EINVAL;
+			}
+			pDrvData->IPCs[ipcnum].bIsHere = FALSE;
+			pDrvData->IPCs[ipcnum].bIsEnabled = TRUE;
+	
+			PRINTK_2(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl IOCTL_MW_REGISTER_IPC"
+				" ipcnum %x exit\n",
+				ipcnum);
+		}
+			break;
+	
+		case IOCTL_MW_GET_IPC: {
+			unsigned int ipcnum = (unsigned int) ioarg;
+	
+			PRINTK_3(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl IOCTL_MW_GET_IPC"
+				" ipcnum %x, usIntCount %x\n",
+				ipcnum,
+				pDrvData->IPCs[ipcnum].usIntCount);
+			if (ipcnum > ARRAY_SIZE(pDrvData->IPCs)) {
+				PRINTK_ERROR(KERN_ERR_MWAVE
+						"mwavedd::mwave_ioctl:"
+						" IOCTL_MW_GET_IPC: Error:"
+						" Invalid ipcnum %x\n", ipcnum);
+				return -EINVAL;
+			}
+	
+			if (pDrvData->IPCs[ipcnum].bIsEnabled == TRUE) {
+				DECLARE_WAITQUEUE(wait, current);
+
+				PRINTK_2(TRACE_MWAVE,
+					"mwavedd::mwave_ioctl, thread for"
+					" ipc %x going to sleep\n",
+					ipcnum);
+				add_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait);
+				pDrvData->IPCs[ipcnum].bIsHere = TRUE;
+				set_current_state(TASK_INTERRUPTIBLE);
+				/* check whether an event was signalled by */
+				/* the interrupt handler while we were gone */
+				if (pDrvData->IPCs[ipcnum].usIntCount == 1) {	/* first int has occurred (race condition) */
+					pDrvData->IPCs[ipcnum].usIntCount = 2;	/* first int has been handled */
+					PRINTK_2(TRACE_MWAVE,
+						"mwavedd::mwave_ioctl"
+						" IOCTL_MW_GET_IPC ipcnum %x"
+						" handling first int\n",
+						ipcnum);
+				} else {	/* either 1st int has not yet occurred, or we have already handled the first int */
+					schedule();
+					if (pDrvData->IPCs[ipcnum].usIntCount == 1) {
+						pDrvData->IPCs[ipcnum].usIntCount = 2;
+					}
+					PRINTK_2(TRACE_MWAVE,
+						"mwavedd::mwave_ioctl"
+						" IOCTL_MW_GET_IPC ipcnum %x"
+						" woke up and returning to"
+						" application\n",
+						ipcnum);
+				}
+				pDrvData->IPCs[ipcnum].bIsHere = FALSE;
+				remove_wait_queue(&pDrvData->IPCs[ipcnum].ipc_wait_queue, &wait);
+				set_current_state(TASK_RUNNING);
+				PRINTK_2(TRACE_MWAVE,
+					"mwavedd::mwave_ioctl IOCTL_MW_GET_IPC,"
+					" returning thread for ipc %x"
+					" processing\n",
+					ipcnum);
+			}
+		}
+			break;
+	
+		case IOCTL_MW_UNREGISTER_IPC: {
+			unsigned int ipcnum = (unsigned int) ioarg;
+	
+			PRINTK_2(TRACE_MWAVE,
+				"mwavedd::mwave_ioctl IOCTL_MW_UNREGISTER_IPC"
+				" ipcnum %x\n",
+				ipcnum);
+			if (ipcnum > ARRAY_SIZE(pDrvData->IPCs)) {
+				PRINTK_ERROR(KERN_ERR_MWAVE
+						"mwavedd::mwave_ioctl:"
+						" IOCTL_MW_UNREGISTER_IPC:"
+						" Error: Invalid ipcnum %x\n",
+						ipcnum);
+				return -EINVAL;
+			}
+			if (pDrvData->IPCs[ipcnum].bIsEnabled == TRUE) {
+				pDrvData->IPCs[ipcnum].bIsEnabled = FALSE;
+				if (pDrvData->IPCs[ipcnum].bIsHere == TRUE) {
+					wake_up_interruptible(&pDrvData->IPCs[ipcnum].ipc_wait_queue);
+				}
+			}
+		}
+			break;
+	
+		default:
+			PRINTK_ERROR(KERN_ERR_MWAVE "mwavedd::mwave_ioctl:"
+					" Error: Unrecognized iocmd %x\n",
+					iocmd);
+			return -ENOTTY;
+			break;
+	} /* switch */
+
+	PRINTK_2(TRACE_MWAVE, "mwavedd::mwave_ioctl, exit retval %x\n", retval);
+
+	return retval;
+}
+
+
+static ssize_t mwave_read(struct file *file, char __user *buf, size_t count,
+                          loff_t * ppos)
+{
+	PRINTK_5(TRACE_MWAVE,
+		"mwavedd::mwave_read entry file %p, buf %p, count %zx ppos %p\n",
+		file, buf, count, ppos);
+
+	return -EINVAL;
+}
+
+
+static ssize_t mwave_write(struct file *file, const char __user *buf,
+                           size_t count, loff_t * ppos)
+{
+	PRINTK_5(TRACE_MWAVE,
+		"mwavedd::mwave_write entry file %p, buf %p,"
+		" count %zx ppos %p\n",
+		file, buf, count, ppos);
+
+	return -EINVAL;
+}
+
+
+static int register_serial_portandirq(unsigned int port, int irq)
+{
+	struct serial_struct serial;
+
+	switch ( port ) {
+		case 0x3f8:
+		case 0x2f8:
+		case 0x3e8:
+		case 0x2e8:
+			/* OK */
+			break;
+		default:
+			PRINTK_ERROR(KERN_ERR_MWAVE
+					"mwavedd::register_serial_portandirq:"
+					" Error: Illegal port %x\n", port );
+			return -1;
+	} /* switch */
+	/* port is okay */
+
+	switch ( irq ) {
+		case 3:
+		case 4:
+		case 5:
+		case 7:
+			/* OK */
+			break;
+		default:
+			PRINTK_ERROR(KERN_ERR_MWAVE
+					"mwavedd::register_serial_portandirq:"
+					" Error: Illegal irq %x\n", irq );
+			return -1;
+	} /* switch */
+	/* irq is okay */
+
+	memset(&serial, 0, sizeof(serial));
+	serial.port = port;
+	serial.irq = irq;
+	serial.flags = ASYNC_SHARE_IRQ;
+
+	return register_serial(&serial);
+}
+
+
+static struct file_operations mwave_fops = {
+	.owner		= THIS_MODULE,
+	.read		= mwave_read,
+	.write		= mwave_write,
+	.ioctl		= mwave_ioctl,
+	.open		= mwave_open,
+	.release	= mwave_close
+};
+
+
+static struct miscdevice mwave_misc_dev = { MWAVE_MINOR, "mwave", &mwave_fops };
+
+#if 0 /* totally b0rked */
+/*
+ * sysfs support <paulsch@us.ibm.com>
+ */
+
+struct device mwave_device;
+
+/* Prevent code redundancy, create a macro for mwave_show_* functions. */
+#define mwave_show_function(attr_name, format_string, field)		\
+static ssize_t mwave_show_##attr_name(struct device *dev, char *buf)	\
+{									\
+	DSP_3780I_CONFIG_SETTINGS *pSettings =				\
+		&mwave_s_mdd.rBDData.rDspSettings;			\
+        return sprintf(buf, format_string, pSettings->field);		\
+}
+
+/* All of our attributes are read attributes. */
+#define mwave_dev_rd_attr(attr_name, format_string, field)		\
+	mwave_show_function(attr_name, format_string, field)		\
+static DEVICE_ATTR(attr_name, S_IRUGO, mwave_show_##attr_name, NULL)
+
+mwave_dev_rd_attr (3780i_dma, "%i\n", usDspDma);
+mwave_dev_rd_attr (3780i_irq, "%i\n", usDspIrq);
+mwave_dev_rd_attr (3780i_io, "%#.4x\n", usDspBaseIO);
+mwave_dev_rd_attr (uart_irq, "%i\n", usUartIrq);
+mwave_dev_rd_attr (uart_io, "%#.4x\n", usUartBaseIO);
+
+static struct device_attribute * const mwave_dev_attrs[] = {
+	&dev_attr_3780i_dma,
+	&dev_attr_3780i_irq,
+	&dev_attr_3780i_io,
+	&dev_attr_uart_irq,
+	&dev_attr_uart_io,
+};
+#endif
+
+/*
+* mwave_init is called on module load
+*
+* mwave_exit is called on module unload
+* mwave_exit is also used to clean up after an aborted mwave_init
+*/
+static void mwave_exit(void)
+{
+	pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
+
+	PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_exit entry\n");
+
+#if 0
+	for (i = 0; i < pDrvData->nr_registered_attrs; i++)
+		device_remove_file(&mwave_device, mwave_dev_attrs[i]);
+	pDrvData->nr_registered_attrs = 0;
+
+	if (pDrvData->device_registered) {
+		device_unregister(&mwave_device);
+		pDrvData->device_registered = FALSE;
+	}
+#endif
+
+	if ( pDrvData->sLine >= 0 ) {
+		unregister_serial(pDrvData->sLine);
+	}
+	if (pDrvData->bMwaveDevRegistered) {
+		misc_deregister(&mwave_misc_dev);
+	}
+	if (pDrvData->bDSPEnabled) {
+		tp3780I_DisableDSP(&pDrvData->rBDData);
+	}
+	if (pDrvData->bResourcesClaimed) {
+		tp3780I_ReleaseResources(&pDrvData->rBDData);
+	}
+	if (pDrvData->bBDInitialized) {
+		tp3780I_Cleanup(&pDrvData->rBDData);
+	}
+
+	PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_exit exit\n");
+}
+
+module_exit(mwave_exit);
+
+static int __init mwave_init(void)
+{
+	int i;
+	int retval = 0;
+	pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
+
+	PRINTK_1(TRACE_MWAVE, "mwavedd::mwave_init entry\n");
+
+	memset(&mwave_s_mdd, 0, sizeof(MWAVE_DEVICE_DATA));
+
+	pDrvData->bBDInitialized = FALSE;
+	pDrvData->bResourcesClaimed = FALSE;
+	pDrvData->bDSPEnabled = FALSE;
+	pDrvData->bDSPReset = FALSE;
+	pDrvData->bMwaveDevRegistered = FALSE;
+	pDrvData->sLine = -1;
+
+	for (i = 0; i < ARRAY_SIZE(pDrvData->IPCs); i++) {
+		pDrvData->IPCs[i].bIsEnabled = FALSE;
+		pDrvData->IPCs[i].bIsHere = FALSE;
+		pDrvData->IPCs[i].usIntCount = 0;	/* no ints received yet */
+		init_waitqueue_head(&pDrvData->IPCs[i].ipc_wait_queue);
+	}
+
+	retval = tp3780I_InitializeBoardData(&pDrvData->rBDData);
+	PRINTK_2(TRACE_MWAVE,
+		"mwavedd::mwave_init, return from tp3780I_InitializeBoardData"
+		" retval %x\n",
+		retval);
+	if (retval) {
+		PRINTK_ERROR(KERN_ERR_MWAVE
+				"mwavedd::mwave_init: Error:"
+				" Failed to initialize board data\n");
+		goto cleanup_error;
+	}
+	pDrvData->bBDInitialized = TRUE;
+
+	retval = tp3780I_CalcResources(&pDrvData->rBDData);
+	PRINTK_2(TRACE_MWAVE,
+		"mwavedd::mwave_init, return from tp3780I_CalcResources"
+		" retval %x\n",
+		retval);
+	if (retval) {
+		PRINTK_ERROR(KERN_ERR_MWAVE
+				"mwavedd:mwave_init: Error:"
+				" Failed to calculate resources\n");
+		goto cleanup_error;
+	}
+
+	retval = tp3780I_ClaimResources(&pDrvData->rBDData);
+	PRINTK_2(TRACE_MWAVE,
+		"mwavedd::mwave_init, return from tp3780I_ClaimResources"
+		" retval %x\n",
+		retval);
+	if (retval) {
+		PRINTK_ERROR(KERN_ERR_MWAVE
+				"mwavedd:mwave_init: Error:"
+				" Failed to claim resources\n");
+		goto cleanup_error;
+	}
+	pDrvData->bResourcesClaimed = TRUE;
+
+	retval = tp3780I_EnableDSP(&pDrvData->rBDData);
+	PRINTK_2(TRACE_MWAVE,
+		"mwavedd::mwave_init, return from tp3780I_EnableDSP"
+		" retval %x\n",
+		retval);
+	if (retval) {
+		PRINTK_ERROR(KERN_ERR_MWAVE
+				"mwavedd:mwave_init: Error:"
+				" Failed to enable DSP\n");
+		goto cleanup_error;
+	}
+	pDrvData->bDSPEnabled = TRUE;
+
+	if (misc_register(&mwave_misc_dev) < 0) {
+		PRINTK_ERROR(KERN_ERR_MWAVE
+				"mwavedd:mwave_init: Error:"
+				" Failed to register misc device\n");
+		goto cleanup_error;
+	}
+	pDrvData->bMwaveDevRegistered = TRUE;
+
+	pDrvData->sLine = register_serial_portandirq(
+		pDrvData->rBDData.rDspSettings.usUartBaseIO,
+		pDrvData->rBDData.rDspSettings.usUartIrq
+	);
+	if (pDrvData->sLine < 0) {
+		PRINTK_ERROR(KERN_ERR_MWAVE
+				"mwavedd:mwave_init: Error:"
+				" Failed to register serial driver\n");
+		goto cleanup_error;
+	}
+	/* uart is registered */
+
+#if 0
+	/* sysfs */
+	memset(&mwave_device, 0, sizeof (struct device));
+	snprintf(mwave_device.bus_id, BUS_ID_SIZE, "mwave");
+
+	if (device_register(&mwave_device))
+		goto cleanup_error;
+	pDrvData->device_registered = TRUE;
+	for (i = 0; i < ARRAY_SIZE(mwave_dev_attrs); i++) {
+		if(device_create_file(&mwave_device, mwave_dev_attrs[i])) {
+			PRINTK_ERROR(KERN_ERR_MWAVE
+					"mwavedd:mwave_init: Error:"
+					" Failed to create sysfs file %s\n",
+					mwave_dev_attrs[i]->attr.name);
+			goto cleanup_error;
+		}
+		pDrvData->nr_registered_attrs++;
+	}
+#endif
+
+	/* SUCCESS! */
+	return 0;
+
+cleanup_error:
+	PRINTK_ERROR(KERN_ERR_MWAVE
+			"mwavedd::mwave_init: Error:"
+			" Failed to initialize\n");
+	mwave_exit(); /* clean up */
+
+	return -EIO;
+}
+
+module_init(mwave_init);
+
diff --git a/drivers/char/mwave/mwavedd.h b/drivers/char/mwave/mwavedd.h
new file mode 100644
index 0000000..8eca61e
--- /dev/null
+++ b/drivers/char/mwave/mwavedd.h
@@ -0,0 +1,150 @@
+/*
+*
+* mwavedd.h -- declarations for mwave device driver
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#ifndef _LINUX_MWAVEDD_H
+#define _LINUX_MWAVEDD_H
+#include "3780i.h"
+#include "tp3780i.h"
+#include "smapi.h"
+#include "mwavepub.h"
+#include <linux/ioctl.h>
+#include <asm/uaccess.h>
+#include <linux/wait.h>
+
+extern int mwave_debug;
+extern int mwave_3780i_irq;
+extern int mwave_3780i_io;
+extern int mwave_uart_irq;
+extern int mwave_uart_io;
+
+#define PRINTK_ERROR printk
+#define KERN_ERR_MWAVE KERN_ERR "mwave: "
+
+#define TRACE_MWAVE     0x0001
+#define TRACE_SMAPI     0x0002
+#define TRACE_3780I     0x0004
+#define TRACE_TP3780I   0x0008
+
+#ifdef MW_TRACE
+#define PRINTK_1(f,s)                       \
+  if (f & (mwave_debug)) {                  \
+    printk(s);                              \
+  }
+
+#define PRINTK_2(f,s,v1)                    \
+  if (f & (mwave_debug)) {                  \
+    printk(s,v1);                           \
+  }
+
+#define PRINTK_3(f,s,v1,v2)                 \
+  if (f & (mwave_debug)) {                  \
+    printk(s,v1,v2);                        \
+  }
+
+#define PRINTK_4(f,s,v1,v2,v3)              \
+  if (f & (mwave_debug)) {                  \
+    printk(s,v1,v2,v3);                     \
+  }
+
+#define PRINTK_5(f,s,v1,v2,v3,v4)           \
+  if (f & (mwave_debug)) {                  \
+    printk(s,v1,v2,v3,v4);                  \
+  }
+
+#define PRINTK_6(f,s,v1,v2,v3,v4,v5)        \
+  if (f & (mwave_debug)) {                  \
+    printk(s,v1,v2,v3,v4,v5);               \
+  }
+
+#define PRINTK_7(f,s,v1,v2,v3,v4,v5,v6)     \
+  if (f & (mwave_debug)) {                  \
+    printk(s,v1,v2,v3,v4,v5,v6);            \
+  }
+
+#define PRINTK_8(f,s,v1,v2,v3,v4,v5,v6,v7)  \
+  if (f & (mwave_debug)) {                  \
+    printk(s,v1,v2,v3,v4,v5,v6,v7);         \
+  }
+
+#else
+#define PRINTK_1(f,s)
+#define PRINTK_2(f,s,v1)
+#define PRINTK_3(f,s,v1,v2)
+#define PRINTK_4(f,s,v1,v2,v3)
+#define PRINTK_5(f,s,v1,v2,v3,v4)
+#define PRINTK_6(f,s,v1,v2,v3,v4,v5)
+#define PRINTK_7(f,s,v1,v2,v3,v4,v5,v6)
+#define PRINTK_8(f,s,v1,v2,v3,v4,v5,v6,v7)
+#endif
+
+
+typedef struct _MWAVE_IPC {
+	unsigned short usIntCount;	/* 0=none, 1=first, 2=greater than 1st */
+	BOOLEAN bIsEnabled;
+	BOOLEAN bIsHere;
+	/* entry spin lock */
+	wait_queue_head_t ipc_wait_queue;
+} MWAVE_IPC;
+
+typedef struct _MWAVE_DEVICE_DATA {
+	THINKPAD_BD_DATA rBDData;	/* board driver's data area */
+	unsigned long ulIPCSource_ISR;	/* IPC source bits for recently processed intr, set during ISR processing */
+	unsigned long ulIPCSource_DPC;	/* IPC source bits for recently processed intr, set during DPC processing */
+	BOOLEAN bBDInitialized;
+	BOOLEAN bResourcesClaimed;
+	BOOLEAN bDSPEnabled;
+	BOOLEAN bDSPReset;
+	MWAVE_IPC IPCs[16];
+	BOOLEAN bMwaveDevRegistered;
+	short sLine;
+	int nr_registered_attrs;
+	int device_registered;
+
+} MWAVE_DEVICE_DATA, *pMWAVE_DEVICE_DATA;
+
+#endif
diff --git a/drivers/char/mwave/mwavepub.h b/drivers/char/mwave/mwavepub.h
new file mode 100644
index 0000000..f1f9da7
--- /dev/null
+++ b/drivers/char/mwave/mwavepub.h
@@ -0,0 +1,89 @@
+/*
+*
+* mwavepub.h -- PUBLIC declarations for the mwave driver
+*               and applications using it
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#ifndef _LINUX_MWAVEPUB_H
+#define _LINUX_MWAVEPUB_H
+
+#include <linux/miscdevice.h>
+
+
+typedef struct _MW_ABILITIES {
+	unsigned long instr_per_sec;
+	unsigned long data_size;
+	unsigned long inst_size;
+	unsigned long bus_dma_bw;
+	unsigned short uart_enable;
+	short component_count;
+	unsigned long component_list[7];
+	char mwave_os_name[16];
+	char bios_task_name[16];
+} MW_ABILITIES, *pMW_ABILITIES;
+
+
+typedef struct _MW_READWRITE {
+	unsigned short usDspAddress;	/* The dsp address */
+	unsigned long ulDataLength;	/* The size in bytes of the data or user buffer */
+	void *pBuf;		/* Input:variable sized buffer */
+} MW_READWRITE, *pMW_READWRITE;
+
+#define IOCTL_MW_RESET           _IO(MWAVE_MINOR,1)
+#define IOCTL_MW_RUN             _IO(MWAVE_MINOR,2)
+#define IOCTL_MW_DSP_ABILITIES   _IOR(MWAVE_MINOR,3,MW_ABILITIES)
+#define IOCTL_MW_READ_DATA       _IOR(MWAVE_MINOR,4,MW_READWRITE)
+#define IOCTL_MW_READCLEAR_DATA  _IOR(MWAVE_MINOR,5,MW_READWRITE)
+#define IOCTL_MW_READ_INST       _IOR(MWAVE_MINOR,6,MW_READWRITE)
+#define IOCTL_MW_WRITE_DATA      _IOW(MWAVE_MINOR,7,MW_READWRITE)
+#define IOCTL_MW_WRITE_INST      _IOW(MWAVE_MINOR,8,MW_READWRITE)
+#define IOCTL_MW_REGISTER_IPC    _IOW(MWAVE_MINOR,9,int)
+#define IOCTL_MW_UNREGISTER_IPC  _IOW(MWAVE_MINOR,10,int)
+#define IOCTL_MW_GET_IPC         _IOW(MWAVE_MINOR,11,int)
+#define IOCTL_MW_TRACE           _IOR(MWAVE_MINOR,12,MW_READWRITE)
+
+
+#endif
diff --git a/drivers/char/mwave/smapi.c b/drivers/char/mwave/smapi.c
new file mode 100644
index 0000000..6187fd1
--- /dev/null
+++ b/drivers/char/mwave/smapi.c
@@ -0,0 +1,570 @@
+/*
+*
+* smapi.c -- SMAPI interface routines
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#include <linux/kernel.h>
+#include <linux/mc146818rtc.h>	/* CMOS defines */
+#include "smapi.h"
+#include "mwavedd.h"
+
+static unsigned short g_usSmapiPort = 0;
+
+
+static int smapi_request(unsigned short inBX, unsigned short inCX,
+			 unsigned short inDI, unsigned short inSI,
+			 unsigned short *outAX, unsigned short *outBX,
+			 unsigned short *outCX, unsigned short *outDX,
+			 unsigned short *outDI, unsigned short *outSI)
+{
+	unsigned short myoutAX = 2, *pmyoutAX = &myoutAX;
+	unsigned short myoutBX = 3, *pmyoutBX = &myoutBX;
+	unsigned short myoutCX = 4, *pmyoutCX = &myoutCX;
+	unsigned short myoutDX = 5, *pmyoutDX = &myoutDX;
+	unsigned short myoutDI = 6, *pmyoutDI = &myoutDI;
+	unsigned short myoutSI = 7, *pmyoutSI = &myoutSI;
+	unsigned short usSmapiOK = -EIO, *pusSmapiOK = &usSmapiOK;
+	unsigned int inBXCX = (inBX << 16) | inCX;
+	unsigned int inDISI = (inDI << 16) | inSI;
+	int retval = 0;
+
+	PRINTK_5(TRACE_SMAPI, "inBX %x inCX %x inDI %x inSI %x\n",
+		inBX, inCX, inDI, inSI);
+
+	__asm__ __volatile__("movw  $0x5380,%%ax\n\t"
+			    "movl  %7,%%ebx\n\t"
+			    "shrl  $16, %%ebx\n\t"
+			    "movw  %7,%%cx\n\t"
+			    "movl  %8,%%edi\n\t"
+			    "shrl  $16,%%edi\n\t"
+			    "movw  %8,%%si\n\t"
+			    "movw  %9,%%dx\n\t"
+			    "out   %%al,%%dx\n\t"
+			    "out   %%al,$0x4F\n\t"
+			    "cmpb  $0x53,%%ah\n\t"
+			    "je    2f\n\t"
+			    "1:\n\t"
+			    "orb   %%ah,%%ah\n\t"
+			    "jnz   2f\n\t"
+			    "movw  %%ax,%0\n\t"
+			    "movw  %%bx,%1\n\t"
+			    "movw  %%cx,%2\n\t"
+			    "movw  %%dx,%3\n\t"
+			    "movw  %%di,%4\n\t"
+			    "movw  %%si,%5\n\t"
+			    "movw  $1,%6\n\t"
+			    "2:\n\t":"=m"(*(unsigned short *) pmyoutAX),
+			    "=m"(*(unsigned short *) pmyoutBX),
+			    "=m"(*(unsigned short *) pmyoutCX),
+			    "=m"(*(unsigned short *) pmyoutDX),
+			    "=m"(*(unsigned short *) pmyoutDI),
+			    "=m"(*(unsigned short *) pmyoutSI),
+			    "=m"(*(unsigned short *) pusSmapiOK)
+			    :"m"(inBXCX), "m"(inDISI), "m"(g_usSmapiPort)
+			    :"%eax", "%ebx", "%ecx", "%edx", "%edi",
+			    "%esi");
+
+	PRINTK_8(TRACE_SMAPI,
+		"myoutAX %x myoutBX %x myoutCX %x myoutDX %x myoutDI %x myoutSI %x usSmapiOK %x\n",
+		myoutAX, myoutBX, myoutCX, myoutDX, myoutDI, myoutSI,
+		usSmapiOK);
+	*outAX = myoutAX;
+	*outBX = myoutBX;
+	*outCX = myoutCX;
+	*outDX = myoutDX;
+	*outDI = myoutDI;
+	*outSI = myoutSI;
+
+	retval = (usSmapiOK == 1) ? 0 : -EIO;
+	PRINTK_2(TRACE_SMAPI, "smapi::smapi_request exit retval %x\n", retval);
+	return retval;
+}
+
+
+int smapi_query_DSP_cfg(SMAPI_DSP_SETTINGS * pSettings)
+{
+	int bRC = -EIO;
+	unsigned short usAX, usBX, usCX, usDX, usDI, usSI;
+	unsigned short ausDspBases[] = { 0x0030, 0x4E30, 0x8E30, 0xCE30, 0x0130, 0x0350, 0x0070, 0x0DB0 };
+	unsigned short ausUartBases[] = { 0x03F8, 0x02F8, 0x03E8, 0x02E8 };
+	unsigned short numDspBases = 8;
+	unsigned short numUartBases = 4;
+
+	PRINTK_1(TRACE_SMAPI, "smapi::smapi_query_DSP_cfg entry\n");
+
+	bRC = smapi_request(0x1802, 0x0000, 0, 0,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+	if (bRC) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Error: Could not get DSP Settings. Aborting.\n");
+		return bRC;
+	}
+
+	PRINTK_1(TRACE_SMAPI, "smapi::smapi_query_DSP_cfg, smapi_request OK\n");
+
+	pSettings->bDSPPresent = ((usBX & 0x0100) != 0);
+	pSettings->bDSPEnabled = ((usCX & 0x0001) != 0);
+	pSettings->usDspIRQ = usSI & 0x00FF;
+	pSettings->usDspDMA = (usSI & 0xFF00) >> 8;
+	if ((usDI & 0x00FF) < numDspBases) {
+		pSettings->usDspBaseIO = ausDspBases[usDI & 0x00FF];
+	} else {
+		pSettings->usDspBaseIO = 0;
+	}
+	PRINTK_6(TRACE_SMAPI,
+		"smapi::smapi_query_DSP_cfg get DSP Settings bDSPPresent %x bDSPEnabled %x usDspIRQ %x usDspDMA %x usDspBaseIO %x\n",
+		pSettings->bDSPPresent, pSettings->bDSPEnabled,
+		pSettings->usDspIRQ, pSettings->usDspDMA,
+		pSettings->usDspBaseIO);
+
+	/* check for illegal values */
+	if ( pSettings->usDspBaseIO == 0 ) 
+		PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Worry: DSP base I/O address is 0\n");
+	if ( pSettings->usDspIRQ == 0 )
+		PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Worry: DSP IRQ line is 0\n");
+
+	bRC = smapi_request(0x1804, 0x0000, 0, 0,
+	   	&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+	if (bRC) {
+		PRINTK_ERROR("smapi::smapi_query_DSP_cfg: Error: Could not get DSP modem settings. Aborting.\n");
+		return bRC;
+	} 
+
+	PRINTK_1(TRACE_SMAPI, "smapi::smapi_query_DSP_cfg, smapi_request OK\n");
+
+	pSettings->bModemEnabled = ((usCX & 0x0001) != 0);
+	pSettings->usUartIRQ = usSI & 0x000F;
+	if (((usSI & 0xFF00) >> 8) < numUartBases) {
+		pSettings->usUartBaseIO = ausUartBases[(usSI & 0xFF00) >> 8];
+	} else {
+		pSettings->usUartBaseIO = 0;
+	}
+
+	PRINTK_4(TRACE_SMAPI,
+		"smapi::smapi_query_DSP_cfg get DSP modem settings bModemEnabled %x usUartIRQ %x usUartBaseIO %x\n",
+		pSettings->bModemEnabled,
+		pSettings->usUartIRQ,
+		pSettings->usUartBaseIO);
+
+	/* check for illegal values */
+	if ( pSettings->usUartBaseIO == 0 ) 
+		PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Worry: UART base I/O address is 0\n");
+	if ( pSettings->usUartIRQ == 0 )
+		PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_query_DSP_cfg: Worry: UART IRQ line is 0\n");
+
+	PRINTK_2(TRACE_SMAPI, "smapi::smapi_query_DSP_cfg exit bRC %x\n", bRC);
+
+	return bRC;
+}
+
+
+int smapi_set_DSP_cfg(void)
+{
+	int bRC = -EIO;
+	int i;
+	unsigned short usAX, usBX, usCX, usDX, usDI, usSI;
+	unsigned short ausDspBases[] = { 0x0030, 0x4E30, 0x8E30, 0xCE30, 0x0130, 0x0350, 0x0070, 0x0DB0 };
+	unsigned short ausUartBases[] = { 0x03F8, 0x02F8, 0x03E8, 0x02E8 };
+	unsigned short ausDspIrqs[] = { 5, 7, 10, 11, 15 };
+	unsigned short ausUartIrqs[] = { 3, 4 };
+
+	unsigned short numDspBases = 8;
+	unsigned short numUartBases = 4;
+	unsigned short numDspIrqs = 5;
+	unsigned short numUartIrqs = 2;
+	unsigned short dspio_index = 0, uartio_index = 0;
+
+	PRINTK_5(TRACE_SMAPI,
+		"smapi::smapi_set_DSP_cfg entry mwave_3780i_irq %x mwave_3780i_io %x mwave_uart_irq %x mwave_uart_io %x\n",
+		mwave_3780i_irq, mwave_3780i_io, mwave_uart_irq, mwave_uart_io);
+
+	if (mwave_3780i_io) {
+		for (i = 0; i < numDspBases; i++) {
+			if (mwave_3780i_io == ausDspBases[i])
+				break;
+		}
+		if (i == numDspBases) {
+			PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg: Error: Invalid mwave_3780i_io address %x. Aborting.\n", mwave_3780i_io);
+			return bRC;
+		}
+		dspio_index = i;
+	}
+
+	if (mwave_3780i_irq) {
+		for (i = 0; i < numDspIrqs; i++) {
+			if (mwave_3780i_irq == ausDspIrqs[i])
+				break;
+		}
+		if (i == numDspIrqs) {
+			PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg: Error: Invalid mwave_3780i_irq %x. Aborting.\n", mwave_3780i_irq);
+			return bRC;
+		}
+	}
+
+	if (mwave_uart_io) {
+		for (i = 0; i < numUartBases; i++) {
+			if (mwave_uart_io == ausUartBases[i])
+				break;
+		}
+		if (i == numUartBases) {
+			PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg: Error: Invalid mwave_uart_io address %x. Aborting.\n", mwave_uart_io);
+			return bRC;
+		}
+		uartio_index = i;
+	}
+
+
+	if (mwave_uart_irq) {
+		for (i = 0; i < numUartIrqs; i++) {
+			if (mwave_uart_irq == ausUartIrqs[i])
+				break;
+		}
+		if (i == numUartIrqs) {
+			PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg: Error: Invalid mwave_uart_irq %x. Aborting.\n", mwave_uart_irq);
+			return bRC;
+		}
+	}
+
+	if (mwave_uart_irq || mwave_uart_io) {
+
+		/* Check serial port A */
+		bRC = smapi_request(0x1402, 0x0000, 0, 0,
+			&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+		if (bRC) goto exit_smapi_request_error;
+		/* bRC == 0 */
+		if (usBX & 0x0100) {	/* serial port A is present */
+			if (usCX & 1) {	/* serial port is enabled */
+				if ((usSI & 0xFF) == mwave_uart_irq) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+					PRINTK_ERROR(KERN_ERR_MWAVE
+						"smapi::smapi_set_DSP_cfg: Serial port A irq %x conflicts with mwave_uart_irq %x\n", usSI & 0xFF, mwave_uart_irq);
+#else
+					PRINTK_3(TRACE_SMAPI,
+						"smapi::smapi_set_DSP_cfg: Serial port A irq %x conflicts with mwave_uart_irq %x\n", usSI & 0xFF, mwave_uart_irq);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+					PRINTK_1(TRACE_SMAPI,
+						"smapi::smapi_set_DSP_cfg Disabling conflicting serial port\n");
+					bRC = smapi_request(0x1403, 0x0100, 0, usSI,
+						&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+					if (bRC) goto exit_smapi_request_error;
+					bRC = smapi_request(0x1402, 0x0000, 0, 0,
+						&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+					if (bRC) goto exit_smapi_request_error;
+#else
+					goto exit_conflict;
+#endif
+				} else {
+					if ((usSI >> 8) == uartio_index) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+						PRINTK_ERROR(KERN_ERR_MWAVE
+							"smapi::smapi_set_DSP_cfg: Serial port A base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI >> 8], ausUartBases[uartio_index]);
+#else
+						PRINTK_3(TRACE_SMAPI,
+							"smapi::smapi_set_DSP_cfg: Serial port A base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI >> 8], ausUartBases[uartio_index]);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+						PRINTK_1(TRACE_SMAPI,
+							"smapi::smapi_set_DSP_cfg Disabling conflicting serial port A\n");
+						bRC = smapi_request (0x1403, 0x0100, 0, usSI,
+							&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+						if (bRC) goto exit_smapi_request_error;
+						bRC = smapi_request (0x1402, 0x0000, 0, 0,
+							&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+						if (bRC) goto exit_smapi_request_error;
+#else
+						goto exit_conflict;
+#endif
+					}
+				}
+			}
+		}
+
+		/* Check serial port B */
+		bRC = smapi_request(0x1404, 0x0000, 0, 0,
+			&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+		if (bRC) goto exit_smapi_request_error;
+		/* bRC == 0 */
+		if (usBX & 0x0100) {	/* serial port B is present */
+			if (usCX & 1) {	/* serial port is enabled */
+				if ((usSI & 0xFF) == mwave_uart_irq) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+					PRINTK_ERROR(KERN_ERR_MWAVE
+						"smapi::smapi_set_DSP_cfg: Serial port B irq %x conflicts with mwave_uart_irq %x\n", usSI & 0xFF, mwave_uart_irq);
+#else
+					PRINTK_3(TRACE_SMAPI,
+						"smapi::smapi_set_DSP_cfg: Serial port B irq %x conflicts with mwave_uart_irq %x\n", usSI & 0xFF, mwave_uart_irq);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+					PRINTK_1(TRACE_SMAPI,
+						"smapi::smapi_set_DSP_cfg Disabling conflicting serial port B\n");
+					bRC = smapi_request(0x1405, 0x0100, 0, usSI,
+						&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+					if (bRC) goto exit_smapi_request_error;
+					bRC = smapi_request(0x1404, 0x0000, 0, 0,
+						&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+					if (bRC) goto exit_smapi_request_error;
+#else
+					goto exit_conflict;
+#endif
+				} else {
+					if ((usSI >> 8) == uartio_index) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+						PRINTK_ERROR(KERN_ERR_MWAVE
+							"smapi::smapi_set_DSP_cfg: Serial port B base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI >> 8], ausUartBases[uartio_index]);
+#else
+						PRINTK_3(TRACE_SMAPI,
+							"smapi::smapi_set_DSP_cfg: Serial port B base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI >> 8], ausUartBases[uartio_index]);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+						PRINTK_1 (TRACE_SMAPI,
+						    "smapi::smapi_set_DSP_cfg Disabling conflicting serial port B\n");
+						bRC = smapi_request (0x1405, 0x0100, 0, usSI,
+							&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+						if (bRC) goto exit_smapi_request_error;
+						bRC = smapi_request (0x1404, 0x0000, 0, 0,
+							&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+						if (bRC) goto exit_smapi_request_error;
+#else
+						goto exit_conflict;
+#endif
+					}
+				}
+			}
+		}
+
+		/* Check IR port */
+		bRC = smapi_request(0x1700, 0x0000, 0, 0,
+			&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+		if (bRC) goto exit_smapi_request_error;
+		bRC = smapi_request(0x1704, 0x0000, 0, 0,
+			&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+		if (bRC) goto exit_smapi_request_error;
+		/* bRC == 0 */
+		if ((usCX & 0xff) != 0xff) { /* IR port not disabled */
+			if ((usCX & 0xff) == mwave_uart_irq) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+				PRINTK_ERROR(KERN_ERR_MWAVE
+					"smapi::smapi_set_DSP_cfg: IR port irq %x conflicts with mwave_uart_irq %x\n", usCX & 0xff, mwave_uart_irq);
+#else
+				PRINTK_3(TRACE_SMAPI,
+					"smapi::smapi_set_DSP_cfg: IR port irq %x conflicts with mwave_uart_irq %x\n", usCX & 0xff, mwave_uart_irq);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+				PRINTK_1(TRACE_SMAPI,
+					"smapi::smapi_set_DSP_cfg Disabling conflicting IR port\n");
+				bRC = smapi_request(0x1701, 0x0100, 0, 0,
+					&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+				if (bRC) goto exit_smapi_request_error;
+				bRC = smapi_request(0x1700, 0, 0, 0,
+					&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+				if (bRC) goto exit_smapi_request_error;
+				bRC = smapi_request(0x1705, 0x01ff, 0, usSI,
+					&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+				if (bRC) goto exit_smapi_request_error;
+				bRC = smapi_request(0x1704, 0x0000, 0, 0,
+					&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+				if (bRC) goto exit_smapi_request_error;
+#else
+				goto exit_conflict;
+#endif
+			} else {
+				if ((usSI & 0xff) == uartio_index) {
+#ifndef MWAVE_FUTZ_WITH_OTHER_DEVICES
+					PRINTK_ERROR(KERN_ERR_MWAVE
+						"smapi::smapi_set_DSP_cfg: IR port base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI & 0xff], ausUartBases[uartio_index]);
+#else
+					PRINTK_3(TRACE_SMAPI,
+						"smapi::smapi_set_DSP_cfg: IR port base I/O address %x conflicts with mwave uart I/O %x\n", ausUartBases[usSI & 0xff], ausUartBases[uartio_index]);
+#endif
+#ifdef MWAVE_FUTZ_WITH_OTHER_DEVICES
+					PRINTK_1(TRACE_SMAPI,
+						"smapi::smapi_set_DSP_cfg Disabling conflicting IR port\n");
+					bRC = smapi_request(0x1701, 0x0100, 0, 0,
+						&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+					if (bRC) goto exit_smapi_request_error;
+					bRC = smapi_request(0x1700, 0, 0, 0,
+						&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+					if (bRC) goto exit_smapi_request_error;
+					bRC = smapi_request(0x1705, 0x01ff, 0, usSI,
+						&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+					if (bRC) goto exit_smapi_request_error;
+					bRC = smapi_request(0x1704, 0x0000, 0, 0,
+						&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+					if (bRC) goto exit_smapi_request_error;
+#else
+					goto exit_conflict;
+#endif
+				}
+			}
+		}
+	}
+
+	bRC = smapi_request(0x1802, 0x0000, 0, 0,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+	if (bRC) goto exit_smapi_request_error;
+
+	if (mwave_3780i_io) {
+		usDI = dspio_index;
+	}
+	if (mwave_3780i_irq) {
+		usSI = (usSI & 0xff00) | mwave_3780i_irq;
+	}
+
+	bRC = smapi_request(0x1803, 0x0101, usDI, usSI,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+	if (bRC) goto exit_smapi_request_error;
+
+	bRC = smapi_request(0x1804, 0x0000, 0, 0,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+	if (bRC) goto exit_smapi_request_error;
+
+	if (mwave_uart_io) {
+		usSI = (usSI & 0x00ff) | (uartio_index << 8);
+	}
+	if (mwave_uart_irq) {
+		usSI = (usSI & 0xff00) | mwave_uart_irq;
+	}
+	bRC = smapi_request(0x1805, 0x0101, 0, usSI,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+	if (bRC) goto exit_smapi_request_error;
+
+	bRC = smapi_request(0x1802, 0x0000, 0, 0,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+	if (bRC) goto exit_smapi_request_error;
+
+	bRC = smapi_request(0x1804, 0x0000, 0, 0,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+	if (bRC) goto exit_smapi_request_error;
+
+/* normal exit: */
+	PRINTK_1(TRACE_SMAPI, "smapi::smapi_set_DSP_cfg exit\n");
+	return 0;
+
+exit_conflict:
+	/* Message has already been printed */
+	return -EIO;
+
+exit_smapi_request_error:
+	PRINTK_ERROR(KERN_ERR_MWAVE "smapi::smapi_set_DSP_cfg exit on smapi_request error bRC %x\n", bRC);
+	return bRC;
+}
+
+
+int smapi_set_DSP_power_state(BOOLEAN bOn)
+{
+	int bRC = -EIO;
+	unsigned short usAX, usBX, usCX, usDX, usDI, usSI;
+	unsigned short usPowerFunction;
+
+	PRINTK_2(TRACE_SMAPI, "smapi::smapi_set_DSP_power_state entry bOn %x\n", bOn);
+
+	usPowerFunction = (bOn) ? 1 : 0;
+
+	bRC = smapi_request(0x4901, 0x0000, 0, usPowerFunction,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+
+	PRINTK_2(TRACE_SMAPI, "smapi::smapi_set_DSP_power_state exit bRC %x\n", bRC);
+
+	return bRC;
+}
+
+#if 0
+static int SmapiQuerySystemID(void)
+{
+	int bRC = -EIO;
+	unsigned short usAX = 0xffff, usBX = 0xffff, usCX = 0xffff,
+		usDX = 0xffff, usDI = 0xffff, usSI = 0xffff;
+
+	printk("smapi::SmapiQUerySystemID entry\n");
+	bRC = smapi_request(0x0000, 0, 0, 0,
+		&usAX, &usBX, &usCX, &usDX, &usDI, &usSI);
+
+	if (bRC == 0) {
+		printk("AX=%x, BX=%x, CX=%x, DX=%x, DI=%x, SI=%x\n",
+			usAX, usBX, usCX, usDX, usDI, usSI);
+	} else {
+		printk("smapi::SmapiQuerySystemID smapi_request error\n");
+	}
+
+	return bRC;
+}
+#endif  /*  0  */
+
+int smapi_init(void)
+{
+	int retval = -EIO;
+	unsigned short usSmapiID = 0;
+	unsigned long flags;
+
+	PRINTK_1(TRACE_SMAPI, "smapi::smapi_init entry\n");
+
+	spin_lock_irqsave(&rtc_lock, flags);
+	usSmapiID = CMOS_READ(0x7C);
+	usSmapiID |= (CMOS_READ(0x7D) << 8);
+	spin_unlock_irqrestore(&rtc_lock, flags);
+	PRINTK_2(TRACE_SMAPI, "smapi::smapi_init usSmapiID %x\n", usSmapiID);
+
+	if (usSmapiID == 0x5349) {
+		spin_lock_irqsave(&rtc_lock, flags);
+		g_usSmapiPort = CMOS_READ(0x7E);
+		g_usSmapiPort |= (CMOS_READ(0x7F) << 8);
+		spin_unlock_irqrestore(&rtc_lock, flags);
+		if (g_usSmapiPort == 0) {
+			PRINTK_ERROR("smapi::smapi_init, ERROR unable to read from SMAPI port\n");
+		} else {
+			PRINTK_2(TRACE_SMAPI,
+				"smapi::smapi_init, exit TRUE g_usSmapiPort %x\n",
+				g_usSmapiPort);
+			retval = 0;
+			//SmapiQuerySystemID();
+		}
+	} else {
+		PRINTK_ERROR("smapi::smapi_init, ERROR invalid usSmapiID\n");
+		retval = -ENXIO;
+	}
+
+	return retval;
+}
diff --git a/drivers/char/mwave/smapi.h b/drivers/char/mwave/smapi.h
new file mode 100644
index 0000000..64b2ec1
--- /dev/null
+++ b/drivers/char/mwave/smapi.h
@@ -0,0 +1,80 @@
+/*
+*
+* smapi.h -- declarations for SMAPI interface routines
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#ifndef _LINUX_SMAPI_H
+#define _LINUX_SMAPI_H
+
+#define TRUE 1
+#define FALSE 0
+#define BOOLEAN int
+
+typedef struct {
+	int bDSPPresent;
+	int bDSPEnabled;
+	int bModemEnabled;
+	int bMIDIEnabled;
+	int bSblstEnabled;
+	unsigned short usDspIRQ;
+	unsigned short usDspDMA;
+	unsigned short usDspBaseIO;
+	unsigned short usUartIRQ;
+	unsigned short usUartBaseIO;
+	unsigned short usMidiIRQ;
+	unsigned short usMidiBaseIO;
+	unsigned short usSndblstIRQ;
+	unsigned short usSndblstDMA;
+	unsigned short usSndblstBaseIO;
+} SMAPI_DSP_SETTINGS;
+
+int smapi_init(void);
+int smapi_query_DSP_cfg(SMAPI_DSP_SETTINGS * pSettings);
+int smapi_set_DSP_cfg(void);
+int smapi_set_DSP_power_state(BOOLEAN bOn);
+
+
+#endif
diff --git a/drivers/char/mwave/tp3780i.c b/drivers/char/mwave/tp3780i.c
new file mode 100644
index 0000000..ab650cd
--- /dev/null
+++ b/drivers/char/mwave/tp3780i.c
@@ -0,0 +1,592 @@
+/*
+*
+* tp3780i.c -- board driver for 3780i on ThinkPads
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#include <linux/version.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include "smapi.h"
+#include "mwavedd.h"
+#include "tp3780i.h"
+#include "3780i.h"
+#include "mwavepub.h"
+
+extern MWAVE_DEVICE_DATA mwave_s_mdd;
+
+static unsigned short s_ausThinkpadIrqToField[16] =
+	{ 0xFFFF, 0xFFFF, 0xFFFF, 0x0001, 0x0002, 0x0003, 0xFFFF, 0x0004,
+	0xFFFF, 0xFFFF, 0x0005, 0x0006, 0xFFFF, 0xFFFF, 0xFFFF, 0x0007 };
+static unsigned short s_ausThinkpadDmaToField[8] =
+	{ 0x0001, 0x0002, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0003, 0x0004 };
+static unsigned short s_numIrqs = 16, s_numDmas = 8;
+
+
+static void EnableSRAM(THINKPAD_BD_DATA * pBDData)
+{
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+	unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+	DSP_GPIO_OUTPUT_DATA_15_8 rGpioOutputData;
+	DSP_GPIO_DRIVER_ENABLE_15_8 rGpioDriverEnable;
+	DSP_GPIO_MODE_15_8 rGpioMode;
+
+	PRINTK_1(TRACE_TP3780I, "tp3780i::EnableSRAM, entry\n");
+
+	MKWORD(rGpioMode) = ReadMsaCfg(DSP_GpioModeControl_15_8);
+	rGpioMode.GpioMode10 = 0;
+	WriteMsaCfg(DSP_GpioModeControl_15_8, MKWORD(rGpioMode));
+
+	MKWORD(rGpioDriverEnable) = 0;
+	rGpioDriverEnable.Enable10 = TRUE;
+	rGpioDriverEnable.Mask10 = TRUE;
+	WriteMsaCfg(DSP_GpioDriverEnable_15_8, MKWORD(rGpioDriverEnable));
+
+	MKWORD(rGpioOutputData) = 0;
+	rGpioOutputData.Latch10 = 0;
+	rGpioOutputData.Mask10 = TRUE;
+	WriteMsaCfg(DSP_GpioOutputData_15_8, MKWORD(rGpioOutputData));
+
+	PRINTK_1(TRACE_TP3780I, "tp3780i::EnableSRAM exit\n");
+}
+
+
+static irqreturn_t UartInterrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	PRINTK_3(TRACE_TP3780I,
+		"tp3780i::UartInterrupt entry irq %x dev_id %p\n", irq, dev_id);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t DspInterrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	pMWAVE_DEVICE_DATA pDrvData = &mwave_s_mdd;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pDrvData->rBDData.rDspSettings;
+	unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+	unsigned short usIPCSource = 0, usIsolationMask, usPCNum;
+
+	PRINTK_3(TRACE_TP3780I,
+		"tp3780i::DspInterrupt entry irq %x dev_id %p\n", irq, dev_id);
+
+	if (dsp3780I_GetIPCSource(usDspBaseIO, &usIPCSource) == 0) {
+		PRINTK_2(TRACE_TP3780I,
+			"tp3780i::DspInterrupt, return from dsp3780i_GetIPCSource, usIPCSource %x\n",
+			usIPCSource);
+		usIsolationMask = 1;
+		for (usPCNum = 1; usPCNum <= 16; usPCNum++) {
+			if (usIPCSource & usIsolationMask) {
+				usIPCSource &= ~usIsolationMask;
+				PRINTK_3(TRACE_TP3780I,
+					"tp3780i::DspInterrupt usPCNum %x usIPCSource %x\n",
+					usPCNum, usIPCSource);
+				if (pDrvData->IPCs[usPCNum - 1].usIntCount == 0) {
+					pDrvData->IPCs[usPCNum - 1].usIntCount = 1;
+				}
+				PRINTK_2(TRACE_TP3780I,
+					"tp3780i::DspInterrupt usIntCount %x\n",
+					pDrvData->IPCs[usPCNum - 1].usIntCount);
+				if (pDrvData->IPCs[usPCNum - 1].bIsEnabled == TRUE) {
+					PRINTK_2(TRACE_TP3780I,
+						"tp3780i::DspInterrupt, waking up usPCNum %x\n",
+						usPCNum - 1);
+					wake_up_interruptible(&pDrvData->IPCs[usPCNum - 1].ipc_wait_queue);
+				} else {
+					PRINTK_2(TRACE_TP3780I,
+						"tp3780i::DspInterrupt, no one waiting for IPC %x\n",
+						usPCNum - 1);
+				}
+			}
+			if (usIPCSource == 0)
+				break;
+			/* try next IPC */
+			usIsolationMask = usIsolationMask << 1;
+		}
+	} else {
+		PRINTK_1(TRACE_TP3780I,
+			"tp3780i::DspInterrupt, return false from dsp3780i_GetIPCSource\n");
+	}
+	PRINTK_1(TRACE_TP3780I, "tp3780i::DspInterrupt exit\n");
+	return IRQ_HANDLED;
+}
+
+
+int tp3780I_InitializeBoardData(THINKPAD_BD_DATA * pBDData)
+{
+	int retval = 0;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_InitializeBoardData entry pBDData %p\n", pBDData);
+
+	pBDData->bDSPEnabled = FALSE;
+	pSettings->bInterruptClaimed = FALSE;
+
+	retval = smapi_init();
+	if (retval) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_InitializeBoardData: Error: SMAPI is not available on this machine\n");
+	} else {
+		if (mwave_3780i_irq || mwave_3780i_io || mwave_uart_irq || mwave_uart_io) {
+			retval = smapi_set_DSP_cfg();
+		}
+	}
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_InitializeBoardData exit retval %x\n", retval);
+
+	return retval;
+}
+
+int tp3780I_Cleanup(THINKPAD_BD_DATA * pBDData)
+{
+	int retval = 0;
+
+	PRINTK_2(TRACE_TP3780I,
+		"tp3780i::tp3780I_Cleanup entry and exit pBDData %p\n", pBDData);
+
+	return retval;
+}
+
+int tp3780I_CalcResources(THINKPAD_BD_DATA * pBDData)
+{
+	SMAPI_DSP_SETTINGS rSmapiInfo;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+	PRINTK_2(TRACE_TP3780I,
+		"tp3780i::tp3780I_CalcResources entry pBDData %p\n", pBDData);
+
+	if (smapi_query_DSP_cfg(&rSmapiInfo)) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_CalcResources: Error: Could not query DSP config. Aborting.\n");
+		return -EIO;
+	}
+
+	/* Sanity check */
+	if (
+		( rSmapiInfo.usDspIRQ == 0 )
+		|| ( rSmapiInfo.usDspBaseIO ==  0 )
+		|| ( rSmapiInfo.usUartIRQ ==  0 )
+		|| ( rSmapiInfo.usUartBaseIO ==  0 )
+	) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_CalcResources: Error: Illegal resource setting. Aborting.\n");
+		return -EIO;
+	}
+
+	pSettings->bDSPEnabled = (rSmapiInfo.bDSPEnabled && rSmapiInfo.bDSPPresent);
+	pSettings->bModemEnabled = rSmapiInfo.bModemEnabled;
+	pSettings->usDspIrq = rSmapiInfo.usDspIRQ;
+	pSettings->usDspDma = rSmapiInfo.usDspDMA;
+	pSettings->usDspBaseIO = rSmapiInfo.usDspBaseIO;
+	pSettings->usUartIrq = rSmapiInfo.usUartIRQ;
+	pSettings->usUartBaseIO = rSmapiInfo.usUartBaseIO;
+
+	pSettings->uDStoreSize = TP_ABILITIES_DATA_SIZE;
+	pSettings->uIStoreSize = TP_ABILITIES_INST_SIZE;
+	pSettings->uIps = TP_ABILITIES_INTS_PER_SEC;
+
+	if (pSettings->bDSPEnabled && pSettings->bModemEnabled && pSettings->usDspIrq == pSettings->usUartIrq) {
+		pBDData->bShareDspIrq = pBDData->bShareUartIrq = 1;
+	} else {
+		pBDData->bShareDspIrq = pBDData->bShareUartIrq = 0;
+	}
+
+	PRINTK_1(TRACE_TP3780I, "tp3780i::tp3780I_CalcResources exit\n");
+
+	return 0;
+}
+
+
+int tp3780I_ClaimResources(THINKPAD_BD_DATA * pBDData)
+{
+	int retval = 0;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
+	struct resource *pres;
+#endif
+
+	PRINTK_2(TRACE_TP3780I,
+		"tp3780i::tp3780I_ClaimResources entry pBDData %p\n", pBDData);
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
+	pres = request_region(pSettings->usDspBaseIO, 16, "mwave_3780i");
+	if ( pres == NULL ) retval = -EIO;
+#else
+	retval = check_region(pSettings->usDspBaseIO, 16);
+	if (!retval) request_region(pSettings->usDspBaseIO, 16, "mwave_3780i");
+#endif
+	if (retval) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_ClaimResources: Error: Could not claim I/O region starting at %x\n", pSettings->usDspBaseIO);
+		retval = -EIO;
+	}
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_ClaimResources exit retval %x\n", retval);
+
+	return retval;
+}
+
+int tp3780I_ReleaseResources(THINKPAD_BD_DATA * pBDData)
+{
+	int retval = 0;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+	PRINTK_2(TRACE_TP3780I,
+		"tp3780i::tp3780I_ReleaseResources entry pBDData %p\n", pBDData);
+
+	release_region(pSettings->usDspBaseIO & (~3), 16);
+
+	if (pSettings->bInterruptClaimed) {
+		free_irq(pSettings->usDspIrq, NULL);
+		pSettings->bInterruptClaimed = FALSE;
+	}
+
+	PRINTK_2(TRACE_TP3780I,
+		"tp3780i::tp3780I_ReleaseResources exit retval %x\n", retval);
+
+	return retval;
+}
+
+
+
+int tp3780I_EnableDSP(THINKPAD_BD_DATA * pBDData)
+{
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+	BOOLEAN bDSPPoweredUp = FALSE, bDSPEnabled = FALSE, bInterruptAllocated = FALSE;
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_EnableDSP entry pBDData %p\n", pBDData);
+
+	if (pBDData->bDSPEnabled) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: DSP already enabled!\n");
+		goto exit_cleanup;
+	}
+
+	if (!pSettings->bDSPEnabled) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780::tp3780I_EnableDSP: Error: pSettings->bDSPEnabled not set\n");
+		goto exit_cleanup;
+	}
+
+	if (
+		(pSettings->usDspIrq >= s_numIrqs)
+		|| (pSettings->usDspDma >= s_numDmas)
+		|| (s_ausThinkpadIrqToField[pSettings->usDspIrq] == 0xFFFF)
+		|| (s_ausThinkpadDmaToField[pSettings->usDspDma] == 0xFFFF)
+	) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: invalid irq %x\n", pSettings->usDspIrq);
+		goto exit_cleanup;
+	}
+
+	if (
+		((pSettings->usDspBaseIO & 0xF00F) != 0)
+		|| (pSettings->usDspBaseIO & 0x0FF0) == 0
+	) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: Invalid DSP base I/O address %x\n", pSettings->usDspBaseIO);
+		goto exit_cleanup;
+	}
+
+	if (pSettings->bModemEnabled) {
+		if (
+			pSettings->usUartIrq >= s_numIrqs
+			|| s_ausThinkpadIrqToField[pSettings->usUartIrq] == 0xFFFF
+		) {
+			PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: Invalid UART IRQ %x\n", pSettings->usUartIrq);
+			goto exit_cleanup;
+		}
+		switch (pSettings->usUartBaseIO) {
+			case 0x03F8:
+			case 0x02F8:
+			case 0x03E8:
+			case 0x02E8:
+				break;
+
+			default:
+				PRINTK_ERROR("tp3780i::tp3780I_EnableDSP: Error: Invalid UART base I/O address %x\n", pSettings->usUartBaseIO);
+				goto exit_cleanup;
+		}
+	}
+
+	pSettings->bDspIrqActiveLow = pSettings->bDspIrqPulse = TRUE;
+	pSettings->bUartIrqActiveLow = pSettings->bUartIrqPulse = TRUE;
+
+	if (pBDData->bShareDspIrq) {
+		pSettings->bDspIrqActiveLow = FALSE;
+	}
+	if (pBDData->bShareUartIrq) {
+		pSettings->bUartIrqActiveLow = FALSE;
+	}
+
+	pSettings->usNumTransfers = TP_CFG_NumTransfers;
+	pSettings->usReRequest = TP_CFG_RerequestTimer;
+	pSettings->bEnableMEMCS16 = TP_CFG_MEMCS16;
+	pSettings->usIsaMemCmdWidth = TP_CFG_IsaMemCmdWidth;
+	pSettings->bGateIOCHRDY = TP_CFG_GateIOCHRDY;
+	pSettings->bEnablePwrMgmt = TP_CFG_EnablePwrMgmt;
+	pSettings->usHBusTimerLoadValue = TP_CFG_HBusTimerValue;
+	pSettings->bDisableLBusTimeout = TP_CFG_DisableLBusTimeout;
+	pSettings->usN_Divisor = TP_CFG_N_Divisor;
+	pSettings->usM_Multiplier = TP_CFG_M_Multiplier;
+	pSettings->bPllBypass = TP_CFG_PllBypass;
+	pSettings->usChipletEnable = TP_CFG_ChipletEnable;
+
+	if (request_irq(pSettings->usUartIrq, &UartInterrupt, 0, "mwave_uart", NULL)) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: Could not get UART IRQ %x\n", pSettings->usUartIrq);
+		goto exit_cleanup;
+	} else {		/* no conflict just release */
+		free_irq(pSettings->usUartIrq, NULL);
+	}
+
+	if (request_irq(pSettings->usDspIrq, &DspInterrupt, 0, "mwave_3780i", NULL)) {
+		PRINTK_ERROR("tp3780i::tp3780I_EnableDSP: Error: Could not get 3780i IRQ %x\n", pSettings->usDspIrq);
+		goto exit_cleanup;
+	} else {
+		PRINTK_3(TRACE_TP3780I,
+			"tp3780i::tp3780I_EnableDSP, got interrupt %x bShareDspIrq %x\n",
+			pSettings->usDspIrq, pBDData->bShareDspIrq);
+		bInterruptAllocated = TRUE;
+		pSettings->bInterruptClaimed = TRUE;
+	}
+
+	smapi_set_DSP_power_state(FALSE);
+	if (smapi_set_DSP_power_state(TRUE)) {
+		PRINTK_ERROR(KERN_ERR_MWAVE "tp3780i::tp3780I_EnableDSP: Error: smapi_set_DSP_power_state(TRUE) failed\n");
+		goto exit_cleanup;
+	} else {
+		bDSPPoweredUp = TRUE;
+	}
+
+	if (dsp3780I_EnableDSP(pSettings, s_ausThinkpadIrqToField, s_ausThinkpadDmaToField)) {
+		PRINTK_ERROR("tp3780i::tp3780I_EnableDSP: Error: dsp7880I_EnableDSP() failed\n");
+		goto exit_cleanup;
+	} else {
+		bDSPEnabled = TRUE;
+	}
+
+	EnableSRAM(pBDData);
+
+	pBDData->bDSPEnabled = TRUE;
+
+	PRINTK_1(TRACE_TP3780I, "tp3780i::tp3780I_EnableDSP exit\n");
+
+	return 0;
+
+exit_cleanup:
+	PRINTK_ERROR("tp3780i::tp3780I_EnableDSP: Cleaning up\n");
+	if (bDSPEnabled)
+		dsp3780I_DisableDSP(pSettings);
+	if (bDSPPoweredUp)
+		smapi_set_DSP_power_state(FALSE);
+	if (bInterruptAllocated) {
+		free_irq(pSettings->usDspIrq, NULL);
+		pSettings->bInterruptClaimed = FALSE;
+	}
+	return -EIO;
+}
+
+
+int tp3780I_DisableDSP(THINKPAD_BD_DATA * pBDData)
+{
+	int retval = 0;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_DisableDSP entry pBDData %p\n", pBDData);
+
+	if (pBDData->bDSPEnabled) {
+		dsp3780I_DisableDSP(&pBDData->rDspSettings);
+		if (pSettings->bInterruptClaimed) {
+			free_irq(pSettings->usDspIrq, NULL);
+			pSettings->bInterruptClaimed = FALSE;
+		}
+		smapi_set_DSP_power_state(FALSE);
+		pBDData->bDSPEnabled = FALSE;
+	}
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_DisableDSP exit retval %x\n", retval);
+
+	return retval;
+}
+
+
+int tp3780I_ResetDSP(THINKPAD_BD_DATA * pBDData)
+{
+	int retval = 0;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_ResetDSP entry pBDData %p\n",
+		pBDData);
+
+	if (dsp3780I_Reset(pSettings) == 0) {
+		EnableSRAM(pBDData);
+	} else {
+		retval = -EIO;
+	}
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_ResetDSP exit retval %x\n", retval);
+
+	return retval;
+}
+
+
+int tp3780I_StartDSP(THINKPAD_BD_DATA * pBDData)
+{
+	int retval = 0;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_StartDSP entry pBDData %p\n", pBDData);
+
+	if (dsp3780I_Run(pSettings) == 0) {
+		// @BUG @TBD EnableSRAM(pBDData);
+	} else {
+		retval = -EIO;
+	}
+
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_StartDSP exit retval %x\n", retval);
+
+	return retval;
+}
+
+
+int tp3780I_QueryAbilities(THINKPAD_BD_DATA * pBDData, MW_ABILITIES * pAbilities)
+{
+	int retval = 0;
+
+	PRINTK_2(TRACE_TP3780I,
+		"tp3780i::tp3780I_QueryAbilities entry pBDData %p\n", pBDData);
+
+	/* fill out standard constant fields */
+	pAbilities->instr_per_sec = pBDData->rDspSettings.uIps;
+	pAbilities->data_size = pBDData->rDspSettings.uDStoreSize;
+	pAbilities->inst_size = pBDData->rDspSettings.uIStoreSize;
+	pAbilities->bus_dma_bw = pBDData->rDspSettings.uDmaBandwidth;
+
+	/* fill out dynamically determined fields */
+	pAbilities->component_list[0] = 0x00010000 | MW_ADC_MASK;
+	pAbilities->component_list[1] = 0x00010000 | MW_ACI_MASK;
+	pAbilities->component_list[2] = 0x00010000 | MW_AIC1_MASK;
+	pAbilities->component_list[3] = 0x00010000 | MW_AIC2_MASK;
+	pAbilities->component_list[4] = 0x00010000 | MW_CDDAC_MASK;
+	pAbilities->component_list[5] = 0x00010000 | MW_MIDI_MASK;
+	pAbilities->component_list[6] = 0x00010000 | MW_UART_MASK;
+	pAbilities->component_count = 7;
+
+	/* Fill out Mwave OS and BIOS task names */
+
+	memcpy(pAbilities->mwave_os_name, TP_ABILITIES_MWAVEOS_NAME,
+		sizeof(TP_ABILITIES_MWAVEOS_NAME));
+	memcpy(pAbilities->bios_task_name, TP_ABILITIES_BIOSTASK_NAME,
+		sizeof(TP_ABILITIES_BIOSTASK_NAME));
+
+	PRINTK_1(TRACE_TP3780I,
+		"tp3780i::tp3780I_QueryAbilities exit retval=SUCCESSFUL\n");
+
+	return retval;
+}
+
+int tp3780I_ReadWriteDspDStore(THINKPAD_BD_DATA * pBDData, unsigned int uOpcode,
+                               void __user *pvBuffer, unsigned int uCount,
+                               unsigned long ulDSPAddr)
+{
+	int retval = 0;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+	unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+	BOOLEAN bRC = 0;
+
+	PRINTK_6(TRACE_TP3780I,
+		"tp3780i::tp3780I_ReadWriteDspDStore entry pBDData %p, uOpcode %x, pvBuffer %p, uCount %x, ulDSPAddr %lx\n",
+		pBDData, uOpcode, pvBuffer, uCount, ulDSPAddr);
+
+	if (pBDData->bDSPEnabled) {
+		switch (uOpcode) {
+		case IOCTL_MW_READ_DATA:
+			bRC = dsp3780I_ReadDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+			break;
+
+		case IOCTL_MW_READCLEAR_DATA:
+			bRC = dsp3780I_ReadAndClearDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+			break;
+
+		case IOCTL_MW_WRITE_DATA:
+			bRC = dsp3780I_WriteDStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+			break;
+		}
+	}
+
+	retval = (bRC) ? -EIO : 0;
+	PRINTK_2(TRACE_TP3780I, "tp3780i::tp3780I_ReadWriteDspDStore exit retval %x\n", retval);
+
+	return retval;
+}
+
+
+int tp3780I_ReadWriteDspIStore(THINKPAD_BD_DATA * pBDData, unsigned int uOpcode,
+                               void __user *pvBuffer, unsigned int uCount,
+                               unsigned long ulDSPAddr)
+{
+	int retval = 0;
+	DSP_3780I_CONFIG_SETTINGS *pSettings = &pBDData->rDspSettings;
+	unsigned short usDspBaseIO = pSettings->usDspBaseIO;
+	BOOLEAN bRC = 0;
+
+	PRINTK_6(TRACE_TP3780I,
+		"tp3780i::tp3780I_ReadWriteDspIStore entry pBDData %p, uOpcode %x, pvBuffer %p, uCount %x, ulDSPAddr %lx\n",
+		pBDData, uOpcode, pvBuffer, uCount, ulDSPAddr);
+
+	if (pBDData->bDSPEnabled) {
+		switch (uOpcode) {
+		case IOCTL_MW_READ_INST:
+			bRC = dsp3780I_ReadIStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+			break;
+
+		case IOCTL_MW_WRITE_INST:
+			bRC = dsp3780I_WriteIStore(usDspBaseIO, pvBuffer, uCount, ulDSPAddr);
+			break;
+		}
+	}
+
+	retval = (bRC) ? -EIO : 0;
+
+	PRINTK_2(TRACE_TP3780I,
+		"tp3780i::tp3780I_ReadWriteDspIStore exit retval %x\n", retval);
+
+	return retval;
+}
+
diff --git a/drivers/char/mwave/tp3780i.h b/drivers/char/mwave/tp3780i.h
new file mode 100644
index 0000000..07685b6
--- /dev/null
+++ b/drivers/char/mwave/tp3780i.h
@@ -0,0 +1,103 @@
+/*
+*
+* tp3780i.h -- declarations for tp3780i.c
+*
+*
+* Written By: Mike Sullivan IBM Corporation
+*
+* Copyright (C) 1999 IBM Corporation
+*
+* 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.
+*
+* NO WARRANTY
+* THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+* CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+* LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+* solely responsible for determining the appropriateness of using and
+* distributing the Program and assumes all risks associated with its
+* exercise of rights under this Agreement, including but not limited to
+* the risks and costs of program errors, damage to or loss of data,
+* programs or equipment, and unavailability or interruption of operations.
+*
+* DISCLAIMER OF LIABILITY
+* NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+* DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+* USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+* HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+*
+* 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*
+*
+* 10/23/2000 - Alpha Release
+*	First release to the public
+*/
+
+#ifndef _LINUX_TP3780I_H
+#define _LINUX_TP3780I_H
+
+#include <asm/io.h>
+#include "mwavepub.h"
+
+
+/* DSP abilities constants for 3780i based Thinkpads */
+#define TP_ABILITIES_INTS_PER_SEC       39160800
+#define TP_ABILITIES_DATA_SIZE          32768
+#define TP_ABILITIES_INST_SIZE          32768
+#define TP_ABILITIES_MWAVEOS_NAME       "mwaveos0700.dsp"
+#define TP_ABILITIES_BIOSTASK_NAME      "mwbio701.dsp"
+
+
+/* DSP configuration values for 3780i based Thinkpads */
+#define TP_CFG_NumTransfers     3	/* 16 transfers */
+#define TP_CFG_RerequestTimer   1	/* 2 usec */
+#define TP_CFG_MEMCS16          0	/* Disabled, 16-bit memory assumed */
+#define TP_CFG_IsaMemCmdWidth   3	/* 295 nsec (16-bit) */
+#define TP_CFG_GateIOCHRDY      0	/* No IOCHRDY gating */
+#define TP_CFG_EnablePwrMgmt    1	/* Enable low poser suspend/resume */
+#define TP_CFG_HBusTimerValue 255	/* HBus timer load value */
+#define TP_CFG_DisableLBusTimeout 0	/* Enable LBus timeout */
+#define TP_CFG_N_Divisor       32	/* Clock = 39.1608 Mhz */
+#define TP_CFG_M_Multiplier    37	/* " */
+#define TP_CFG_PllBypass        0	/* don't bypass */
+#define TP_CFG_ChipletEnable 0xFFFF	/* Enable all chiplets */
+
+typedef struct {
+	int bDSPEnabled;
+	int bShareDspIrq;
+	int bShareUartIrq;
+	DSP_3780I_CONFIG_SETTINGS rDspSettings;
+} THINKPAD_BD_DATA;
+
+int tp3780I_InitializeBoardData(THINKPAD_BD_DATA * pBDData);
+int tp3780I_CalcResources(THINKPAD_BD_DATA * pBDData);
+int tp3780I_ClaimResources(THINKPAD_BD_DATA * pBDData);
+int tp3780I_ReleaseResources(THINKPAD_BD_DATA * pBDData);
+int tp3780I_EnableDSP(THINKPAD_BD_DATA * pBDData);
+int tp3780I_DisableDSP(THINKPAD_BD_DATA * pBDData);
+int tp3780I_ResetDSP(THINKPAD_BD_DATA * pBDData);
+int tp3780I_StartDSP(THINKPAD_BD_DATA * pBDData);
+int tp3780I_QueryAbilities(THINKPAD_BD_DATA * pBDData, MW_ABILITIES * pAbilities);
+int tp3780I_Cleanup(THINKPAD_BD_DATA * pBDData);
+int tp3780I_ReadWriteDspDStore(THINKPAD_BD_DATA * pBDData, unsigned int uOpcode,
+                               void __user *pvBuffer, unsigned int uCount,
+                               unsigned long ulDSPAddr);
+int tp3780I_ReadWriteDspIStore(THINKPAD_BD_DATA * pBDData, unsigned int uOpcode,
+                               void __user *pvBuffer, unsigned int uCount,
+                               unsigned long ulDSPAddr);
+
+
+#endif
diff --git a/drivers/char/mxser.c b/drivers/char/mxser.c
new file mode 100644
index 0000000..7a24506
--- /dev/null
+++ b/drivers/char/mxser.c
@@ -0,0 +1,3170 @@
+/*
+ *          mxser.c  -- MOXA Smartio/Industio family multiport serial driver.
+ *
+ *      Copyright (C) 1999-2001  Moxa Technologies (support@moxa.com.tw).
+ *
+ *      This code is loosely based on the Linux serial driver, written by
+ *      Linus Torvalds, Theodore T'so and others.
+ *
+ *      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.
+ *
+ *	Original release	10/26/00
+ *
+ *	02/06/01	Support MOXA Industio family boards.
+ *	02/06/01	Support TIOCGICOUNT.
+ *	02/06/01	Fix the problem for connecting to serial mouse.
+ *	02/06/01	Fix the problem for H/W flow control.
+ *	02/06/01	Fix the compling warning when CONFIG_PCI
+ *			don't be defined.
+ *
+ *	Fed through a cleanup, indent and remove of non 2.6 code by Alan Cox
+ *	<alan@redhat.com>. The original 1.8 code is available on www.moxa.com.
+ *	- Fixed x86_64 cleanness
+ *	- Fixed sleep with spinlock held in mxser_send_break
+ */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/autoconf.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/serial_reg.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/gfp.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/segment.h>
+#include <asm/bitops.h>
+#include <asm/uaccess.h>
+
+#include "mxser.h"
+
+#define	MXSER_VERSION	"1.8"
+#define	MXSERMAJOR	 174
+#define	MXSERCUMAJOR	 175
+
+#define	MXSER_EVENT_TXLOW	 1
+#define	MXSER_EVENT_HANGUP	 2
+
+#define MXSER_BOARDS		4	/* Max. boards */
+#define MXSER_PORTS		32	/* Max. ports */
+#define MXSER_PORTS_PER_BOARD	8	/* Max. ports per board */
+#define MXSER_ISR_PASS_LIMIT	256
+
+#define	MXSER_ERR_IOADDR	-1
+#define	MXSER_ERR_IRQ		-2
+#define	MXSER_ERR_IRQ_CONFLIT	-3
+#define	MXSER_ERR_VECTOR	-4
+
+#define SERIAL_TYPE_NORMAL	1
+#define SERIAL_TYPE_CALLOUT	2
+
+#define WAKEUP_CHARS		256
+
+#define UART_MCR_AFE		0x20
+#define UART_LSR_SPECIAL	0x1E
+
+#define RELEVANT_IFLAG(iflag)	(iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK|IXON|IXOFF))
+
+#define IRQ_T(info) ((info->flags & ASYNC_SHARE_IRQ) ? SA_SHIRQ : SA_INTERRUPT)
+
+#define C168_ASIC_ID    1
+#define C104_ASIC_ID    2
+#define C102_ASIC_ID	0xB
+#define CI132_ASIC_ID	4
+#define CI134_ASIC_ID	3
+#define CI104J_ASIC_ID  5
+
+enum {
+	MXSER_BOARD_C168_ISA = 1,
+	MXSER_BOARD_C104_ISA,
+	MXSER_BOARD_CI104J,
+	MXSER_BOARD_C168_PCI,
+	MXSER_BOARD_C104_PCI,
+	MXSER_BOARD_C102_ISA,
+	MXSER_BOARD_CI132,
+	MXSER_BOARD_CI134,
+	MXSER_BOARD_CP132,
+	MXSER_BOARD_CP114,
+	MXSER_BOARD_CT114,
+	MXSER_BOARD_CP102,
+	MXSER_BOARD_CP104U,
+	MXSER_BOARD_CP168U,
+	MXSER_BOARD_CP132U,
+	MXSER_BOARD_CP134U,
+	MXSER_BOARD_CP104JU,
+	MXSER_BOARD_RC7000,
+	MXSER_BOARD_CP118U,
+	MXSER_BOARD_CP102UL,
+	MXSER_BOARD_CP102U,
+};
+
+static char *mxser_brdname[] = {
+	"C168 series",
+	"C104 series",
+	"CI-104J series",
+	"C168H/PCI series",
+	"C104H/PCI series",
+	"C102 series",
+	"CI-132 series",
+	"CI-134 series",
+	"CP-132 series",
+	"CP-114 series",
+	"CT-114 series",
+	"CP-102 series",
+	"CP-104U series",
+	"CP-168U series",
+	"CP-132U series",
+	"CP-134U series",
+	"CP-104JU series",
+	"Moxa UC7000 Serial",
+	"CP-118U series",
+	"CP-102UL series",
+	"CP-102U series",
+};
+
+static int mxser_numports[] = {
+	8,			// C168-ISA
+	4,			// C104-ISA
+	4,			// CI104J
+	8,			// C168-PCI
+	4,			// C104-PCI
+	2,			// C102-ISA
+	2,			// CI132
+	4,			// CI134
+	2,			// CP132
+	4,			// CP114
+	4,			// CT114
+	2,			// CP102
+	4,			// CP104U
+	8,			// CP168U
+	2,			// CP132U
+	4,			// CP134U
+	4,			// CP104JU
+	8,			// RC7000
+	8,			// CP118U 
+	2,			// CP102UL 
+	2,			// CP102U
+};
+
+#define UART_TYPE_NUM	2
+
+static const unsigned int Gmoxa_uart_id[UART_TYPE_NUM] = {
+	MOXA_MUST_MU150_HWID,
+	MOXA_MUST_MU860_HWID
+};
+
+// This is only for PCI
+#define UART_INFO_NUM	3
+struct mxpciuart_info {
+	int type;
+	int tx_fifo;
+	int rx_fifo;
+	int xmit_fifo_size;
+	int rx_high_water;
+	int rx_trigger;
+	int rx_low_water;
+	long max_baud;
+};
+
+static const struct mxpciuart_info Gpci_uart_info[UART_INFO_NUM] = {
+	{MOXA_OTHER_UART, 16, 16, 16, 14, 14, 1, 921600L},
+	{MOXA_MUST_MU150_HWID, 64, 64, 64, 48, 48, 16, 230400L},
+	{MOXA_MUST_MU860_HWID, 128, 128, 128, 96, 96, 32, 921600L}
+};
+
+
+#ifdef CONFIG_PCI
+
+static struct pci_device_id mxser_pcibrds[] = {
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_C168, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_C168_PCI},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_C104, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_C104_PCI},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP132, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP132},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP114, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP114},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CT114, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CT114},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP102, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP102},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP104U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP104U},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP168U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP168U},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP132U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP132U},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP134U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP134U},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP104JU, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP104JU},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_RC7000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_RC7000},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP118U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP118U},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP102UL, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP102UL},
+	{PCI_VENDOR_ID_MOXA, PCI_DEVICE_ID_MOXA_CP102U, PCI_ANY_ID, PCI_ANY_ID, 0, 0, MXSER_BOARD_CP102U},
+	{0}
+};
+
+MODULE_DEVICE_TABLE(pci, mxser_pcibrds);
+
+
+#endif
+
+typedef struct _moxa_pci_info {
+	unsigned short busNum;
+	unsigned short devNum;
+	struct pci_dev *pdev;	// add by Victor Yu. 06-23-2003
+} moxa_pci_info;
+
+static int ioaddr[MXSER_BOARDS] = { 0, 0, 0, 0 };
+static int ttymajor = MXSERMAJOR;
+static int calloutmajor = MXSERCUMAJOR;
+static int verbose = 0;
+
+/* Variables for insmod */
+
+MODULE_AUTHOR("Casper Yang");
+MODULE_DESCRIPTION("MOXA Smartio/Industio Family Multiport Board Device Driver");
+MODULE_PARM(ioaddr, "1-4i");
+MODULE_PARM(ttymajor, "i");
+MODULE_PARM(calloutmajor, "i");
+MODULE_PARM(verbose, "i");
+MODULE_LICENSE("GPL");
+
+struct mxser_log {
+	int tick;
+	unsigned long rxcnt[MXSER_PORTS];
+	unsigned long txcnt[MXSER_PORTS];
+};
+
+
+struct mxser_mon {
+	unsigned long rxcnt;
+	unsigned long txcnt;
+	unsigned long up_rxcnt;
+	unsigned long up_txcnt;
+	int modem_status;
+	unsigned char hold_reason;
+};
+
+struct mxser_mon_ext {
+	unsigned long rx_cnt[32];
+	unsigned long tx_cnt[32];
+	unsigned long up_rxcnt[32];
+	unsigned long up_txcnt[32];
+	int modem_status[32];
+
+	long baudrate[32];
+	int databits[32];
+	int stopbits[32];
+	int parity[32];
+	int flowctrl[32];
+	int fifo[32];
+	int iftype[32];
+};
+struct mxser_hwconf {
+	int board_type;
+	int ports;
+	int irq;
+	int vector;
+	int vector_mask;
+	int uart_type;
+	int ioaddr[MXSER_PORTS_PER_BOARD];
+	int baud_base[MXSER_PORTS_PER_BOARD];
+	moxa_pci_info pciInfo;
+	int IsMoxaMustChipFlag;	// add by Victor Yu. 08-30-2002
+	int MaxCanSetBaudRate[MXSER_PORTS_PER_BOARD];	// add by Victor Yu. 09-04-2002
+	int opmode_ioaddr[MXSER_PORTS_PER_BOARD];	// add by Victor Yu. 01-05-2004
+};
+
+struct mxser_struct {
+	int port;
+	int base;		/* port base address */
+	int irq;		/* port using irq no. */
+	int vector;		/* port irq vector */
+	int vectormask;		/* port vector mask */
+	int rx_high_water;
+	int rx_trigger;		/* Rx fifo trigger level */
+	int rx_low_water;
+	int baud_base;		/* max. speed */
+	int flags;		/* defined in tty.h */
+	int type;		/* UART type */
+	struct tty_struct *tty;
+	int read_status_mask;
+	int ignore_status_mask;
+	int xmit_fifo_size;
+	int custom_divisor;
+	int x_char;		/* xon/xoff character */
+	int close_delay;
+	unsigned short closing_wait;
+	int IER;		/* Interrupt Enable Register */
+	int MCR;		/* Modem control register */
+	unsigned long event;
+	int count;		/* # of fd on device */
+	int blocked_open;	/* # of blocked opens */
+	long session;		/* Session of opening process */
+	long pgrp;		/* pgrp of opening process */
+	unsigned char *xmit_buf;
+	int xmit_head;
+	int xmit_tail;
+	int xmit_cnt;
+	struct work_struct tqueue;
+	struct termios normal_termios;
+	struct termios callout_termios;
+	wait_queue_head_t open_wait;
+	wait_queue_head_t close_wait;
+	wait_queue_head_t delta_msr_wait;
+	struct async_icount icount;	/* kernel counters for the 4 input interrupts */
+	int timeout;
+	int IsMoxaMustChipFlag;	// add by Victor Yu. 08-30-2002
+	int MaxCanSetBaudRate;	// add by Victor Yu. 09-04-2002
+	int opmode_ioaddr;	// add by Victor Yu. 01-05-2004
+	unsigned char stop_rx;
+	unsigned char ldisc_stop_rx;
+	long realbaud;
+	struct mxser_mon mon_data;
+	unsigned char err_shadow;
+	spinlock_t slock;
+};
+
+
+struct mxser_mstatus {
+	tcflag_t cflag;
+	int cts;
+	int dsr;
+	int ri;
+	int dcd;
+};
+
+static struct mxser_mstatus GMStatus[MXSER_PORTS];
+
+static int mxserBoardCAP[MXSER_BOARDS] = {
+	0, 0, 0, 0
+	    /*  0x180, 0x280, 0x200, 0x320   */
+};
+
+static struct tty_driver *mxvar_sdriver;
+static struct mxser_struct mxvar_table[MXSER_PORTS];
+static struct tty_struct *mxvar_tty[MXSER_PORTS + 1];
+static struct termios *mxvar_termios[MXSER_PORTS + 1];
+static struct termios *mxvar_termios_locked[MXSER_PORTS + 1];
+static struct mxser_log mxvar_log;
+static int mxvar_diagflag;
+static unsigned char mxser_msr[MXSER_PORTS + 1];
+static struct mxser_mon_ext mon_data_ext;
+static int mxser_set_baud_method[MXSER_PORTS + 1];
+static spinlock_t gm_lock;
+
+/*
+ * This is used to figure out the divisor speeds and the timeouts
+ */
+
+static struct mxser_hwconf mxsercfg[MXSER_BOARDS];
+
+/*
+ * static functions:
+ */
+
+static void mxser_getcfg(int board, struct mxser_hwconf *hwconf);
+static int mxser_init(void);
+
+//static void   mxser_poll(unsigned long);
+static int mxser_get_ISA_conf(int, struct mxser_hwconf *);
+static int mxser_get_PCI_conf(int, int, int, struct mxser_hwconf *);
+static void mxser_do_softint(void *);
+static int mxser_open(struct tty_struct *, struct file *);
+static void mxser_close(struct tty_struct *, struct file *);
+static int mxser_write(struct tty_struct *, const unsigned char *, int);
+static int mxser_write_room(struct tty_struct *);
+static void mxser_flush_buffer(struct tty_struct *);
+static int mxser_chars_in_buffer(struct tty_struct *);
+static void mxser_flush_chars(struct tty_struct *);
+static void mxser_put_char(struct tty_struct *, unsigned char);
+static int mxser_ioctl(struct tty_struct *, struct file *, uint, ulong);
+static int mxser_ioctl_special(unsigned int, void __user *);
+static void mxser_throttle(struct tty_struct *);
+static void mxser_unthrottle(struct tty_struct *);
+static void mxser_set_termios(struct tty_struct *, struct termios *);
+static void mxser_stop(struct tty_struct *);
+static void mxser_start(struct tty_struct *);
+static void mxser_hangup(struct tty_struct *);
+static void mxser_rs_break(struct tty_struct *, int);
+static irqreturn_t mxser_interrupt(int, void *, struct pt_regs *);
+static void mxser_receive_chars(struct mxser_struct *, int *);
+static void mxser_transmit_chars(struct mxser_struct *);
+static void mxser_check_modem_status(struct mxser_struct *, int);
+static int mxser_block_til_ready(struct tty_struct *, struct file *, struct mxser_struct *);
+static int mxser_startup(struct mxser_struct *);
+static void mxser_shutdown(struct mxser_struct *);
+static int mxser_change_speed(struct mxser_struct *, struct termios *old_termios);
+static int mxser_get_serial_info(struct mxser_struct *, struct serial_struct __user *);
+static int mxser_set_serial_info(struct mxser_struct *, struct serial_struct __user *);
+static int mxser_get_lsr_info(struct mxser_struct *, unsigned int __user *);
+static void mxser_send_break(struct mxser_struct *, int);
+static int mxser_tiocmget(struct tty_struct *, struct file *);
+static int mxser_tiocmset(struct tty_struct *, struct file *, unsigned int, unsigned int);
+static int mxser_set_baud(struct mxser_struct *info, long newspd);
+static void mxser_wait_until_sent(struct tty_struct *tty, int timeout);
+
+static void mxser_startrx(struct tty_struct *tty);
+static void mxser_stoprx(struct tty_struct *tty);
+
+
+static int CheckIsMoxaMust(int io)
+{
+	u8 oldmcr, hwid;
+	int i;
+
+	outb(0, io + UART_LCR);
+	DISABLE_MOXA_MUST_ENCHANCE_MODE(io);
+	oldmcr = inb(io + UART_MCR);
+	outb(0, io + UART_MCR);
+	SET_MOXA_MUST_XON1_VALUE(io, 0x11);
+	if ((hwid = inb(io + UART_MCR)) != 0) {
+		outb(oldmcr, io + UART_MCR);
+		return (MOXA_OTHER_UART);
+	}
+
+	GET_MOXA_MUST_HARDWARE_ID(io, &hwid);
+	for (i = 0; i < UART_TYPE_NUM; i++) {
+		if (hwid == Gmoxa_uart_id[i])
+			return (int) hwid;
+	}
+	return MOXA_OTHER_UART;
+}
+
+// above is modified by Victor Yu. 08-15-2002
+
+static struct tty_operations mxser_ops = {
+	.open = mxser_open,
+	.close = mxser_close,
+	.write = mxser_write,
+	.put_char = mxser_put_char,
+	.flush_chars = mxser_flush_chars,
+	.write_room = mxser_write_room,
+	.chars_in_buffer = mxser_chars_in_buffer,
+	.flush_buffer = mxser_flush_buffer,
+	.ioctl = mxser_ioctl,
+	.throttle = mxser_throttle,
+	.unthrottle = mxser_unthrottle,
+	.set_termios = mxser_set_termios,
+	.stop = mxser_stop,
+	.start = mxser_start,
+	.hangup = mxser_hangup,
+	.tiocmget = mxser_tiocmget,
+	.tiocmset = mxser_tiocmset,
+};
+
+/*
+ * The MOXA Smartio/Industio serial driver boot-time initialization code!
+ */
+
+static int __init mxser_module_init(void)
+{
+	int ret;
+
+	if (verbose)
+		printk(KERN_DEBUG "Loading module mxser ...\n");
+	ret = mxser_init();
+	if (verbose)
+		printk(KERN_DEBUG "Done.\n");
+	return ret;
+}
+
+static void __exit mxser_module_exit(void)
+{
+	int i, err = 0;
+
+	if (verbose)
+		printk(KERN_DEBUG "Unloading module mxser ...\n");
+
+	if ((err |= tty_unregister_driver(mxvar_sdriver)))
+		printk(KERN_ERR "Couldn't unregister MOXA Smartio/Industio family serial driver\n");
+
+	for (i = 0; i < MXSER_BOARDS; i++) {
+		struct pci_dev *pdev;
+
+		if (mxsercfg[i].board_type == -1)
+			continue;
+		else {
+			pdev = mxsercfg[i].pciInfo.pdev;
+			free_irq(mxsercfg[i].irq, &mxvar_table[i * MXSER_PORTS_PER_BOARD]);
+			if (pdev != NULL) {	//PCI
+				release_region(pci_resource_start(pdev, 2), pci_resource_len(pdev, 2));
+				release_region(pci_resource_start(pdev, 3), pci_resource_len(pdev, 3));
+			} else {
+				release_region(mxsercfg[i].ioaddr[0], 8 * mxsercfg[i].ports);
+				release_region(mxsercfg[i].vector, 1);
+			}
+		}
+	}
+	if (verbose)
+		printk(KERN_DEBUG "Done.\n");
+
+}
+
+static void process_txrx_fifo(struct mxser_struct *info)
+{
+	int i;
+
+	if ((info->type == PORT_16450) || (info->type == PORT_8250)) {
+		info->rx_trigger = 1;
+		info->rx_high_water = 1;
+		info->rx_low_water = 1;
+		info->xmit_fifo_size = 1;
+	} else {
+		for (i = 0; i < UART_INFO_NUM; i++) {
+			if (info->IsMoxaMustChipFlag == Gpci_uart_info[i].type) {
+				info->rx_trigger = Gpci_uart_info[i].rx_trigger;
+				info->rx_low_water = Gpci_uart_info[i].rx_low_water;
+				info->rx_high_water = Gpci_uart_info[i].rx_high_water;
+				info->xmit_fifo_size = Gpci_uart_info[i].xmit_fifo_size;
+				break;
+			}
+		}
+	}
+}
+
+static int mxser_initbrd(int board, struct mxser_hwconf *hwconf)
+{
+	struct mxser_struct *info;
+	int retval;
+	int i, n;
+
+	n = board * MXSER_PORTS_PER_BOARD;
+	info = &mxvar_table[n];
+	/*if (verbose) */  {
+		printk(KERN_DEBUG "        ttyM%d - ttyM%d ", n, n + hwconf->ports - 1);
+		printk(" max. baud rate = %d bps.\n", hwconf->MaxCanSetBaudRate[0]);
+	}
+
+	for (i = 0; i < hwconf->ports; i++, n++, info++) {
+		info->port = n;
+		info->base = hwconf->ioaddr[i];
+		info->irq = hwconf->irq;
+		info->vector = hwconf->vector;
+		info->vectormask = hwconf->vector_mask;
+		info->opmode_ioaddr = hwconf->opmode_ioaddr[i];	// add by Victor Yu. 01-05-2004
+		info->stop_rx = 0;
+		info->ldisc_stop_rx = 0;
+
+		info->IsMoxaMustChipFlag = hwconf->IsMoxaMustChipFlag;
+		//Enhance mode enabled here
+		if (info->IsMoxaMustChipFlag != MOXA_OTHER_UART) {
+			ENABLE_MOXA_MUST_ENCHANCE_MODE(info->base);
+		}
+
+		info->flags = ASYNC_SHARE_IRQ;
+		info->type = hwconf->uart_type;
+		info->baud_base = hwconf->baud_base[i];
+
+		info->MaxCanSetBaudRate = hwconf->MaxCanSetBaudRate[i];
+
+		process_txrx_fifo(info);
+
+
+		info->custom_divisor = hwconf->baud_base[i] * 16;
+		info->close_delay = 5 * HZ / 10;
+		info->closing_wait = 30 * HZ;
+		INIT_WORK(&info->tqueue, mxser_do_softint, info);
+		info->normal_termios = mxvar_sdriver->init_termios;
+		init_waitqueue_head(&info->open_wait);
+		init_waitqueue_head(&info->close_wait);
+		init_waitqueue_head(&info->delta_msr_wait);
+		memset(&info->mon_data, 0, sizeof(struct mxser_mon));
+		info->err_shadow = 0;
+		spin_lock_init(&info->slock);
+	}
+	/*
+	 * Allocate the IRQ if necessary
+	 */
+
+
+	/* before set INT ISR, disable all int */
+	for (i = 0; i < hwconf->ports; i++) {
+		outb(inb(hwconf->ioaddr[i] + UART_IER) & 0xf0, hwconf->ioaddr[i] + UART_IER);
+	}
+
+	n = board * MXSER_PORTS_PER_BOARD;
+	info = &mxvar_table[n];
+
+	retval = request_irq(hwconf->irq, mxser_interrupt, IRQ_T(info), "mxser", info);
+	if (retval) {
+		printk(KERN_ERR "Board %d: %s", board, mxser_brdname[hwconf->board_type - 1]);
+		printk("  Request irq fail,IRQ (%d) may be conflit with another device.\n", info->irq);
+		return retval;
+	}
+	return 0;
+}
+
+
+static void mxser_getcfg(int board, struct mxser_hwconf *hwconf)
+{
+	mxsercfg[board] = *hwconf;
+}
+
+#ifdef CONFIG_PCI
+static int mxser_get_PCI_conf(int busnum, int devnum, int board_type, struct mxser_hwconf *hwconf)
+{
+	int i, j;
+//      unsigned int    val;
+	unsigned int ioaddress;
+	struct pci_dev *pdev = hwconf->pciInfo.pdev;
+
+	//io address
+	hwconf->board_type = board_type;
+	hwconf->ports = mxser_numports[board_type - 1];
+	ioaddress = pci_resource_start(pdev, 2);
+	request_region(pci_resource_start(pdev, 2), pci_resource_len(pdev, 2), "mxser(IO)");
+
+	for (i = 0; i < hwconf->ports; i++) {
+		hwconf->ioaddr[i] = ioaddress + 8 * i;
+	}
+
+	//vector
+	ioaddress = pci_resource_start(pdev, 3);
+	request_region(pci_resource_start(pdev, 3), pci_resource_len(pdev, 3), "mxser(vector)");
+	hwconf->vector = ioaddress;
+
+	//irq
+	hwconf->irq = hwconf->pciInfo.pdev->irq;
+
+	hwconf->IsMoxaMustChipFlag = CheckIsMoxaMust(hwconf->ioaddr[0]);
+	hwconf->uart_type = PORT_16550A;
+	hwconf->vector_mask = 0;
+
+
+	for (i = 0; i < hwconf->ports; i++) {
+		for (j = 0; j < UART_INFO_NUM; j++) {
+			if (Gpci_uart_info[j].type == hwconf->IsMoxaMustChipFlag) {
+				hwconf->MaxCanSetBaudRate[i] = Gpci_uart_info[j].max_baud;
+
+				//exception....CP-102
+				if (board_type == MXSER_BOARD_CP102)
+					hwconf->MaxCanSetBaudRate[i] = 921600;
+				break;
+			}
+		}
+	}
+
+	if (hwconf->IsMoxaMustChipFlag == MOXA_MUST_MU860_HWID) {
+		for (i = 0; i < hwconf->ports; i++) {
+			if (i < 4)
+				hwconf->opmode_ioaddr[i] = ioaddress + 4;
+			else
+				hwconf->opmode_ioaddr[i] = ioaddress + 0x0c;
+		}
+		outb(0, ioaddress + 4);	// default set to RS232 mode
+		outb(0, ioaddress + 0x0c);	//default set to RS232 mode
+	}
+
+	for (i = 0; i < hwconf->ports; i++) {
+		hwconf->vector_mask |= (1 << i);
+		hwconf->baud_base[i] = 921600;
+	}
+	return (0);
+}
+#endif
+
+static int mxser_init(void)
+{
+	int i, m, retval, b, n;
+	int ret1;
+	struct pci_dev *pdev = NULL;
+	int index;
+	unsigned char busnum, devnum;
+	struct mxser_hwconf hwconf;
+
+	mxvar_sdriver = alloc_tty_driver(MXSER_PORTS + 1);
+	if (!mxvar_sdriver)
+		return -ENOMEM;
+	spin_lock_init(&gm_lock);
+
+	for (i = 0; i < MXSER_BOARDS; i++) {
+		mxsercfg[i].board_type = -1;
+	}
+
+	printk(KERN_INFO "MOXA Smartio/Industio family driver version %s\n", MXSER_VERSION);
+
+	/* Initialize the tty_driver structure */
+	memset(mxvar_sdriver, 0, sizeof(struct tty_driver));
+	mxvar_sdriver->magic = TTY_DRIVER_MAGIC;
+	mxvar_sdriver->name = "ttyM";
+	mxvar_sdriver->major = ttymajor;
+	mxvar_sdriver->minor_start = 0;
+	mxvar_sdriver->num = MXSER_PORTS + 1;
+	mxvar_sdriver->type = TTY_DRIVER_TYPE_SERIAL;
+	mxvar_sdriver->subtype = SERIAL_TYPE_NORMAL;
+	mxvar_sdriver->init_termios = tty_std_termios;
+	mxvar_sdriver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	mxvar_sdriver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(mxvar_sdriver, &mxser_ops);
+	mxvar_sdriver->ttys = mxvar_tty;
+	mxvar_sdriver->termios = mxvar_termios;
+	mxvar_sdriver->termios_locked = mxvar_termios_locked;
+
+	mxvar_sdriver->open = mxser_open;
+	mxvar_sdriver->close = mxser_close;
+	mxvar_sdriver->write = mxser_write;
+	mxvar_sdriver->put_char = mxser_put_char;
+	mxvar_sdriver->flush_chars = mxser_flush_chars;
+	mxvar_sdriver->write_room = mxser_write_room;
+	mxvar_sdriver->chars_in_buffer = mxser_chars_in_buffer;
+	mxvar_sdriver->flush_buffer = mxser_flush_buffer;
+	mxvar_sdriver->ioctl = mxser_ioctl;
+	mxvar_sdriver->throttle = mxser_throttle;
+	mxvar_sdriver->unthrottle = mxser_unthrottle;
+	mxvar_sdriver->set_termios = mxser_set_termios;
+	mxvar_sdriver->stop = mxser_stop;
+	mxvar_sdriver->start = mxser_start;
+	mxvar_sdriver->hangup = mxser_hangup;
+	mxvar_sdriver->break_ctl = mxser_rs_break;
+	mxvar_sdriver->wait_until_sent = mxser_wait_until_sent;
+
+	mxvar_diagflag = 0;
+	memset(mxvar_table, 0, MXSER_PORTS * sizeof(struct mxser_struct));
+	memset(&mxvar_log, 0, sizeof(struct mxser_log));
+
+	memset(&mxser_msr, 0, sizeof(unsigned char) * (MXSER_PORTS + 1));
+	memset(&mon_data_ext, 0, sizeof(struct mxser_mon_ext));
+	memset(&mxser_set_baud_method, 0, sizeof(int) * (MXSER_PORTS + 1));
+	memset(&hwconf, 0, sizeof(struct mxser_hwconf));
+
+	m = 0;
+	/* Start finding ISA boards here */
+	for (b = 0; b < MXSER_BOARDS && m < MXSER_BOARDS; b++) {
+		int cap;
+		if (!(cap = mxserBoardCAP[b]))
+			continue;
+
+		retval = mxser_get_ISA_conf(cap, &hwconf);
+
+		if (retval != 0)
+			printk(KERN_INFO "Found MOXA %s board (CAP=0x%x)\n", mxser_brdname[hwconf.board_type - 1], ioaddr[b]);
+
+		if (retval <= 0) {
+			if (retval == MXSER_ERR_IRQ)
+				printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+			else if (retval == MXSER_ERR_IRQ_CONFLIT)
+				printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+			else if (retval == MXSER_ERR_VECTOR)
+				printk(KERN_ERR "Invalid interrupt vector,board not configured\n");
+			else if (retval == MXSER_ERR_IOADDR)
+				printk(KERN_ERR "Invalid I/O address,board not configured\n");
+
+			continue;
+		}
+
+		hwconf.pciInfo.busNum = 0;
+		hwconf.pciInfo.devNum = 0;
+		hwconf.pciInfo.pdev = NULL;
+
+		mxser_getcfg(m, &hwconf);
+		//init mxsercfg first, or mxsercfg data is not correct on ISR.
+		//mxser_initbrd will hook ISR.
+		if (mxser_initbrd(m, &hwconf) < 0)
+			continue;
+
+
+		m++;
+	}
+
+	/* Start finding ISA boards from module arg */
+	for (b = 0; b < MXSER_BOARDS && m < MXSER_BOARDS; b++) {
+		int cap;
+		if (!(cap = ioaddr[b]))
+			continue;
+
+		retval = mxser_get_ISA_conf(cap, &hwconf);
+
+		if (retval != 0)
+			printk(KERN_INFO "Found MOXA %s board (CAP=0x%x)\n", mxser_brdname[hwconf.board_type - 1], ioaddr[b]);
+
+		if (retval <= 0) {
+			if (retval == MXSER_ERR_IRQ)
+				printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+			else if (retval == MXSER_ERR_IRQ_CONFLIT)
+				printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+			else if (retval == MXSER_ERR_VECTOR)
+				printk(KERN_ERR "Invalid interrupt vector,board not configured\n");
+			else if (retval == MXSER_ERR_IOADDR)
+				printk(KERN_ERR "Invalid I/O address,board not configured\n");
+
+			continue;
+		}
+
+		hwconf.pciInfo.busNum = 0;
+		hwconf.pciInfo.devNum = 0;
+		hwconf.pciInfo.pdev = NULL;
+
+		mxser_getcfg(m, &hwconf);
+		//init mxsercfg first, or mxsercfg data is not correct on ISR.
+		//mxser_initbrd will hook ISR.
+		if (mxser_initbrd(m, &hwconf) < 0)
+			continue;
+
+		m++;
+	}
+
+	/* start finding PCI board here */
+#ifdef CONFIG_PCI
+	n = (sizeof(mxser_pcibrds) / sizeof(mxser_pcibrds[0])) - 1;
+	index = 0;
+	b = 0;
+	while (b < n) {
+		pdev = pci_find_device(mxser_pcibrds[b].vendor, mxser_pcibrds[b].device, pdev);
+			if (pdev == NULL) {
+			b++;
+			continue;
+		}
+		hwconf.pciInfo.busNum = busnum = pdev->bus->number;
+		hwconf.pciInfo.devNum = devnum = PCI_SLOT(pdev->devfn) << 3;
+		hwconf.pciInfo.pdev = pdev;
+		printk(KERN_INFO "Found MOXA %s board(BusNo=%d,DevNo=%d)\n", mxser_brdname[(int) (mxser_pcibrds[b].driver_data) - 1], busnum, devnum >> 3);
+		index++;
+		if (m >= MXSER_BOARDS) {
+			printk(KERN_ERR "Too many Smartio/Industio family boards find (maximum %d),board not configured\n", MXSER_BOARDS);
+		} else {
+			if (pci_enable_device(pdev)) {
+				printk(KERN_ERR "Moxa SmartI/O PCI enable fail !\n");
+				continue;
+			}
+			retval = mxser_get_PCI_conf(busnum, devnum, (int) mxser_pcibrds[b].driver_data, &hwconf);
+			if (retval < 0) {
+				if (retval == MXSER_ERR_IRQ)
+					printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+				else if (retval == MXSER_ERR_IRQ_CONFLIT)
+					printk(KERN_ERR "Invalid interrupt number,board not configured\n");
+				else if (retval == MXSER_ERR_VECTOR)
+					printk(KERN_ERR "Invalid interrupt vector,board not configured\n");
+				else if (retval == MXSER_ERR_IOADDR)
+					printk(KERN_ERR "Invalid I/O address,board not configured\n");
+				continue;
+			}
+			mxser_getcfg(m, &hwconf);
+			//init mxsercfg first, or mxsercfg data is not correct on ISR.
+			//mxser_initbrd will hook ISR.
+			if (mxser_initbrd(m, &hwconf) < 0)
+				continue;
+			m++;
+		}
+	}
+#endif
+
+	ret1 = 0;
+	if (!(ret1 = tty_register_driver(mxvar_sdriver))) {
+		return 0;
+	} else
+		printk(KERN_ERR "Couldn't install MOXA Smartio/Industio family driver !\n");
+
+
+	if (ret1) {
+		for (i = 0; i < MXSER_BOARDS; i++) {
+			if (mxsercfg[i].board_type == -1)
+				continue;
+			else {
+				free_irq(mxsercfg[i].irq, &mxvar_table[i * MXSER_PORTS_PER_BOARD]);
+				//todo: release io, vector
+			}
+		}
+		return -1;
+	}
+
+	return (0);
+}
+
+static void mxser_do_softint(void *private_)
+{
+	struct mxser_struct *info = (struct mxser_struct *) private_;
+	struct tty_struct *tty;
+
+	tty = info->tty;
+
+	if (tty) {
+		if (test_and_clear_bit(MXSER_EVENT_TXLOW, &info->event))
+			tty_wakeup(tty);
+		if (test_and_clear_bit(MXSER_EVENT_HANGUP, &info->event))
+			tty_hangup(tty);
+	}
+}
+
+static unsigned char mxser_get_msr(int baseaddr, int mode, int port, struct mxser_struct *info)
+{
+	unsigned char status = 0;
+
+	status = inb(baseaddr + UART_MSR);
+
+	mxser_msr[port] &= 0x0F;
+	mxser_msr[port] |= status;
+	status = mxser_msr[port];
+	if (mode)
+		mxser_msr[port] = 0;
+
+	return status;
+}
+
+/*
+ * This routine is called whenever a serial port is opened.  It
+ * enables interrupts for a serial port, linking in its async structure into
+ * the IRQ chain.   It also performs the serial-specific
+ * initialization for the tty structure.
+ */
+static int mxser_open(struct tty_struct *tty, struct file *filp)
+{
+	struct mxser_struct *info;
+	int retval, line;
+
+	line = tty->index;
+	if (line == MXSER_PORTS)
+		return 0;
+	if (line < 0 || line > MXSER_PORTS)
+		return -ENODEV;
+	info = mxvar_table + line;
+	if (!info->base)
+		return (-ENODEV);
+
+	tty->driver_data = info;
+	info->tty = tty;
+	/*
+	 * Start up serial port
+	 */
+	retval = mxser_startup(info);
+	if (retval)
+		return (retval);
+
+	retval = mxser_block_til_ready(tty, filp, info);
+	if (retval)
+		return (retval);
+
+	info->count++;
+
+	if ((info->count == 1) && (info->flags & ASYNC_SPLIT_TERMIOS)) {
+		if (tty->driver->subtype == SERIAL_TYPE_NORMAL)
+			*tty->termios = info->normal_termios;
+		else
+			*tty->termios = info->callout_termios;
+		mxser_change_speed(info, NULL);
+	}
+
+	info->session = current->signal->session;
+	info->pgrp = process_group(current);
+	clear_bit(TTY_DONT_FLIP, &tty->flags);
+
+	//status = mxser_get_msr(info->base, 0, info->port);
+	//mxser_check_modem_status(info, status);
+
+/* unmark here for very high baud rate (ex. 921600 bps) used
+*/
+	tty->low_latency = 1;
+	return 0;
+}
+
+/*
+ * This routine is called when the serial port gets closed.  First, we
+ * wait for the last remaining data to be sent.  Then, we unlink its
+ * async structure from the interrupt chain if necessary, and we free
+ * that IRQ if nothing is left in the chain.
+ */
+static void mxser_close(struct tty_struct *tty, struct file *filp)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+
+	unsigned long timeout;
+	unsigned long flags;
+	struct tty_ldisc *ld;
+
+	if (tty->index == MXSER_PORTS)
+		return;
+	if (!info)
+		BUG();
+
+	spin_lock_irqsave(&info->slock, flags);
+
+	if (tty_hung_up_p(filp)) {
+		spin_unlock_irqrestore(&info->slock, flags);
+		return;
+	}
+	if ((tty->count == 1) && (info->count != 1)) {
+		/*
+		 * Uh, oh.  tty->count is 1, which means that the tty
+		 * structure will be freed.  Info->count should always
+		 * be one in these conditions.  If it's greater than
+		 * one, we've got real problems, since it means the
+		 * serial port won't be shutdown.
+		 */
+		printk(KERN_ERR "mxser_close: bad serial port count; tty->count is 1, " "info->count is %d\n", info->count);
+		info->count = 1;
+	}
+	if (--info->count < 0) {
+		printk(KERN_ERR "mxser_close: bad serial port count for ttys%d: %d\n", info->port, info->count);
+		info->count = 0;
+	}
+	if (info->count) {
+		spin_unlock_irqrestore(&info->slock, flags);
+		return;
+	}
+	info->flags |= ASYNC_CLOSING;
+	spin_unlock_irqrestore(&info->slock, flags);
+	/*
+	 * Save the termios structure, since this port may have
+	 * separate termios for callout and dialin.
+	 */
+	if (info->flags & ASYNC_NORMAL_ACTIVE)
+		info->normal_termios = *tty->termios;
+	/*
+	 * Now we wait for the transmit buffer to clear; and we notify
+	 * the line discipline to only process XON/XOFF characters.
+	 */
+	tty->closing = 1;
+	if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, info->closing_wait);
+	/*
+	 * At this point we stop accepting input.  To do this, we
+	 * disable the receive line status interrupts, and tell the
+	 * interrupt driver to stop checking the data ready bit in the
+	 * line status register.
+	 */
+	info->IER &= ~UART_IER_RLSI;
+	if (info->IsMoxaMustChipFlag)
+		info->IER &= ~MOXA_MUST_RECV_ISR;
+/* by William
+	info->read_status_mask &= ~UART_LSR_DR;
+*/
+	if (info->flags & ASYNC_INITIALIZED) {
+		outb(info->IER, info->base + UART_IER);
+		/*
+		 * Before we drop DTR, make sure the UART transmitter
+		 * has completely drained; this is especially
+		 * important if there is a transmit FIFO!
+		 */
+		timeout = jiffies + HZ;
+		while (!(inb(info->base + UART_LSR) & UART_LSR_TEMT)) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(5);
+			if (time_after(jiffies, timeout))
+				break;
+		}
+	}
+	mxser_shutdown(info);
+
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+		
+	ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if(ld->flush_buffer)
+			ld->flush_buffer(tty);
+		tty_ldisc_deref(ld);
+	}
+		
+	tty->closing = 0;
+	info->event = 0;
+	info->tty = NULL;
+	if (info->blocked_open) {
+		if (info->close_delay) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			schedule_timeout(info->close_delay);
+		}
+		wake_up_interruptible(&info->open_wait);
+	}
+
+	info->flags &= ~(ASYNC_NORMAL_ACTIVE | ASYNC_CLOSING);
+	wake_up_interruptible(&info->close_wait);
+
+}
+
+static int mxser_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	int c, total = 0;
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long flags;
+
+	if (!tty || !info->xmit_buf)
+		return (0);
+
+	while (1) {
+		c = min_t(int, count, min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1, SERIAL_XMIT_SIZE - info->xmit_head));
+		if (c <= 0)
+			break;
+
+		memcpy(info->xmit_buf + info->xmit_head, buf, c);
+		spin_lock_irqsave(&info->slock, flags);
+		info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE - 1);
+		info->xmit_cnt += c;
+		spin_unlock_irqrestore(&info->slock, flags);
+
+		buf += c;
+		count -= c;
+		total += c;
+
+	}
+
+	if (info->xmit_cnt && !tty->stopped && !(info->IER & UART_IER_THRI)) {
+		if (!tty->hw_stopped || (info->type == PORT_16550A) || (info->IsMoxaMustChipFlag)) {
+			spin_lock_irqsave(&info->slock, flags);
+			info->IER |= UART_IER_THRI;
+			outb(info->IER, info->base + UART_IER);
+			spin_unlock_irqrestore(&info->slock, flags);
+		}
+	}
+	return total;
+}
+
+static void mxser_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long flags;
+
+	if (!tty || !info->xmit_buf)
+		return;
+
+	if (info->xmit_cnt >= SERIAL_XMIT_SIZE - 1)
+		return;
+
+	spin_lock_irqsave(&info->slock, flags);
+	info->xmit_buf[info->xmit_head++] = ch;
+	info->xmit_head &= SERIAL_XMIT_SIZE - 1;
+	info->xmit_cnt++;
+	spin_unlock_irqrestore(&info->slock, flags);
+	if (!tty->stopped && !(info->IER & UART_IER_THRI)) {
+		if (!tty->hw_stopped || (info->type == PORT_16550A) || info->IsMoxaMustChipFlag) {
+			spin_lock_irqsave(&info->slock, flags);
+			info->IER |= UART_IER_THRI;
+			outb(info->IER, info->base + UART_IER);
+			spin_unlock_irqrestore(&info->slock, flags);
+		}
+	}
+}
+
+
+static void mxser_flush_chars(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long flags;
+
+	if (info->xmit_cnt <= 0 || tty->stopped || !info->xmit_buf || (tty->hw_stopped && (info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)))
+		return;
+
+	spin_lock_irqsave(&info->slock, flags);
+
+	info->IER |= UART_IER_THRI;
+	outb(info->IER, info->base + UART_IER);
+
+	spin_unlock_irqrestore(&info->slock, flags);
+}
+
+static int mxser_write_room(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	int ret;
+
+	ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+	return (ret);
+}
+
+static int mxser_chars_in_buffer(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	return info->xmit_cnt;
+}
+
+static void mxser_flush_buffer(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	char fcr;
+	unsigned long flags;
+
+
+	spin_lock_irqsave(&info->slock, flags);
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+	/* below added by shinhay */
+	fcr = inb(info->base + UART_FCR);
+	outb((fcr | UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT), info->base + UART_FCR);
+	outb(fcr, info->base + UART_FCR);
+
+	spin_unlock_irqrestore(&info->slock, flags);
+	/* above added by shinhay */
+
+	wake_up_interruptible(&tty->write_wait);
+	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup)
+		(tty->ldisc.write_wakeup) (tty);
+}
+
+static int mxser_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	int retval;
+	struct async_icount cprev, cnow;	/* kernel counter temps */
+	struct serial_icounter_struct __user *p_cuser;
+	unsigned long templ;
+	unsigned long flags;
+	void __user *argp = (void __user *)arg;
+
+	if (tty->index == MXSER_PORTS)
+		return (mxser_ioctl_special(cmd, argp));
+
+	// following add by Victor Yu. 01-05-2004
+	if (cmd == MOXA_SET_OP_MODE || cmd == MOXA_GET_OP_MODE) {
+		int opmode, p;
+		static unsigned char ModeMask[] = { 0xfc, 0xf3, 0xcf, 0x3f };
+		int shiftbit;
+		unsigned char val, mask;
+
+		p = info->port % 4;
+		if (cmd == MOXA_SET_OP_MODE) {
+			if (get_user(opmode, (int __user *) argp))
+				return -EFAULT;
+			if (opmode != RS232_MODE && opmode != RS485_2WIRE_MODE && opmode != RS422_MODE && opmode != RS485_4WIRE_MODE)
+				return -EFAULT;
+			mask = ModeMask[p];
+			shiftbit = p * 2;
+			val = inb(info->opmode_ioaddr);
+			val &= mask;
+			val |= (opmode << shiftbit);
+			outb(val, info->opmode_ioaddr);
+		} else {
+			shiftbit = p * 2;
+			opmode = inb(info->opmode_ioaddr) >> shiftbit;
+			opmode &= OP_MODE_MASK;
+			if (copy_to_user(argp, &opmode, sizeof(int)))
+				return -EFAULT;
+		}
+		return 0;
+	}
+	// above add by Victor Yu. 01-05-2004
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+			return (-EIO);
+	}
+	switch (cmd) {
+	case TCSBRK:		/* SVID version: non-zero arg --> no break */
+		retval = tty_check_change(tty);
+		if (retval)
+			return (retval);
+		tty_wait_until_sent(tty, 0);
+		if (!arg)
+			mxser_send_break(info, HZ / 4);	/* 1/4 second */
+		return (0);
+	case TCSBRKP:		/* support for POSIX tcsendbreak() */
+		retval = tty_check_change(tty);
+		if (retval)
+			return (retval);
+		tty_wait_until_sent(tty, 0);
+		mxser_send_break(info, arg ? arg * (HZ / 10) : HZ / 4);
+		return (0);
+	case TIOCGSOFTCAR:
+		return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long __user *) argp);
+	case TIOCSSOFTCAR:
+		if (get_user(templ, (unsigned long __user *) argp))
+			return -EFAULT;
+		arg = templ;
+		tty->termios->c_cflag = ((tty->termios->c_cflag & ~CLOCAL) | (arg ? CLOCAL : 0));
+		return (0);
+	case TIOCGSERIAL:
+		return mxser_get_serial_info(info, argp);
+	case TIOCSSERIAL:
+		return mxser_set_serial_info(info, argp);
+	case TIOCSERGETLSR:	/* Get line status register */
+		return mxser_get_lsr_info(info, argp);
+		/*
+		 * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change
+		 * - mask passed in arg for lines of interest
+		 *   (use |'ed TIOCM_RNG/DSR/CD/CTS for masking)
+		 * Caller should use TIOCGICOUNT to see which one it was
+		 */
+	case TIOCMIWAIT:{
+			DECLARE_WAITQUEUE(wait, current);
+			int ret;
+			spin_lock_irqsave(&info->slock, flags);
+			cprev = info->icount;	/* note the counters on entry */
+			spin_unlock_irqrestore(&info->slock, flags);
+
+			add_wait_queue(&info->delta_msr_wait, &wait);
+			while (1) {
+				spin_lock_irqsave(&info->slock, flags);
+				cnow = info->icount;	/* atomic copy */
+				spin_unlock_irqrestore(&info->slock, flags);
+
+				set_current_state(TASK_INTERRUPTIBLE);
+				if (((arg & TIOCM_RNG) && (cnow.rng != cprev.rng)) || ((arg & TIOCM_DSR) && (cnow.dsr != cprev.dsr)) || ((arg & TIOCM_CD) && (cnow.dcd != cprev.dcd)) || ((arg & TIOCM_CTS) && (cnow.cts != cprev.cts))) {
+					ret = 0;
+					break;
+				}
+				/* see if a signal did it */
+				if (signal_pending(current)) {
+					ret = -ERESTARTSYS;
+					break;
+				}
+				cprev = cnow;
+			}
+			current->state = TASK_RUNNING;
+			remove_wait_queue(&info->delta_msr_wait, &wait);
+			break;
+		}
+		/* NOTREACHED */
+		/*
+		 * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+		 * Return: write counters to the user passed counter struct
+		 * NB: both 1->0 and 0->1 transitions are counted except for
+		 *     RI where only 0->1 is counted.
+		 */
+	case TIOCGICOUNT:
+		spin_lock_irqsave(&info->slock, flags);
+		cnow = info->icount;
+		spin_unlock_irqrestore(&info->slock, flags);
+		p_cuser = argp;
+		/* modified by casper 1/11/2000 */
+		if (put_user(cnow.frame, &p_cuser->frame))
+			return -EFAULT;
+		if (put_user(cnow.brk, &p_cuser->brk))
+			return -EFAULT;
+		if (put_user(cnow.overrun, &p_cuser->overrun))
+			return -EFAULT;
+		if (put_user(cnow.buf_overrun, &p_cuser->buf_overrun))
+			return -EFAULT;
+		if (put_user(cnow.parity, &p_cuser->parity))
+			return -EFAULT;
+		if (put_user(cnow.rx, &p_cuser->rx))
+			return -EFAULT;
+		if (put_user(cnow.tx, &p_cuser->tx))
+			return -EFAULT;
+		put_user(cnow.cts, &p_cuser->cts);
+		put_user(cnow.dsr, &p_cuser->dsr);
+		put_user(cnow.rng, &p_cuser->rng);
+		put_user(cnow.dcd, &p_cuser->dcd);
+
+/* */
+		return 0;
+	case MOXA_HighSpeedOn:
+		return put_user(info->baud_base != 115200 ? 1 : 0, (int __user *) argp);
+
+	case MOXA_SDS_RSTICOUNTER:{
+			info->mon_data.rxcnt = 0;
+			info->mon_data.txcnt = 0;
+			return 0;
+		}
+// (above) added by James.
+	case MOXA_ASPP_SETBAUD:{
+			long baud;
+			if (get_user(baud, (long __user *) argp))
+				return -EFAULT;
+			mxser_set_baud(info, baud);
+			return 0;
+		}
+	case MOXA_ASPP_GETBAUD:
+		if (copy_to_user(argp, &info->realbaud, sizeof(long)))
+			return -EFAULT;
+
+		return 0;
+
+	case MOXA_ASPP_OQUEUE:{
+			int len, lsr;
+
+			len = mxser_chars_in_buffer(tty);
+
+			lsr = inb(info->base + UART_LSR) & UART_LSR_TEMT;
+
+			len += (lsr ? 0 : 1);
+
+			if (copy_to_user(argp, &len, sizeof(int)))
+				return -EFAULT;
+
+			return 0;
+		}
+	case MOXA_ASPP_MON:{
+			int mcr, status;
+//      info->mon_data.ser_param = tty->termios->c_cflag;
+
+			status = mxser_get_msr(info->base, 1, info->port, info);
+			mxser_check_modem_status(info, status);
+
+			mcr = inb(info->base + UART_MCR);
+			if (mcr & MOXA_MUST_MCR_XON_FLAG)
+				info->mon_data.hold_reason &= ~NPPI_NOTIFY_XOFFHOLD;
+			else
+				info->mon_data.hold_reason |= NPPI_NOTIFY_XOFFHOLD;
+
+			if (mcr & MOXA_MUST_MCR_TX_XON)
+				info->mon_data.hold_reason &= ~NPPI_NOTIFY_XOFFXENT;
+			else
+				info->mon_data.hold_reason |= NPPI_NOTIFY_XOFFXENT;
+
+			if (info->tty->hw_stopped)
+				info->mon_data.hold_reason |= NPPI_NOTIFY_CTSHOLD;
+			else
+				info->mon_data.hold_reason &= ~NPPI_NOTIFY_CTSHOLD;
+
+
+			if (copy_to_user(argp, &info->mon_data, sizeof(struct mxser_mon)))
+				return -EFAULT;
+
+			return 0;
+
+		}
+
+	case MOXA_ASPP_LSTATUS:{
+			if (copy_to_user(argp, &info->err_shadow, sizeof(unsigned char)))
+				return -EFAULT;
+
+			info->err_shadow = 0;
+			return 0;
+
+		}
+	case MOXA_SET_BAUD_METHOD:{
+			int method;
+			if (get_user(method, (int __user *) argp))
+				return -EFAULT;
+			mxser_set_baud_method[info->port] = method;
+			if (copy_to_user(argp, &method, sizeof(int)))
+				return -EFAULT;
+
+			return 0;
+		}
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+#ifndef CMSPAR
+#define	CMSPAR 010000000000
+#endif
+
+static int mxser_ioctl_special(unsigned int cmd, void __user *argp)
+{
+	int i, result, status;
+
+	switch (cmd) {
+	case MOXA_GET_CONF:
+		if (copy_to_user(argp, mxsercfg, sizeof(struct mxser_hwconf) * 4))
+			return -EFAULT;
+		return 0;
+	case MOXA_GET_MAJOR:
+		if (copy_to_user(argp, &ttymajor, sizeof(int)))
+			return -EFAULT;
+		return 0;
+
+	case MOXA_GET_CUMAJOR:
+		if (copy_to_user(argp, &calloutmajor, sizeof(int)))
+			return -EFAULT;
+		return 0;
+
+	case MOXA_CHKPORTENABLE:
+		result = 0;
+		for (i = 0; i < MXSER_PORTS; i++) {
+			if (mxvar_table[i].base)
+				result |= (1 << i);
+		}
+		return put_user(result, (unsigned long __user *) argp);
+	case MOXA_GETDATACOUNT:
+		if (copy_to_user(argp, &mxvar_log, sizeof(mxvar_log)))
+			return -EFAULT;
+		return (0);
+	case MOXA_GETMSTATUS:
+		for (i = 0; i < MXSER_PORTS; i++) {
+			GMStatus[i].ri = 0;
+			if (!mxvar_table[i].base) {
+				GMStatus[i].dcd = 0;
+				GMStatus[i].dsr = 0;
+				GMStatus[i].cts = 0;
+				continue;
+			}
+
+			if (!mxvar_table[i].tty || !mxvar_table[i].tty->termios)
+				GMStatus[i].cflag = mxvar_table[i].normal_termios.c_cflag;
+			else
+				GMStatus[i].cflag = mxvar_table[i].tty->termios->c_cflag;
+
+			status = inb(mxvar_table[i].base + UART_MSR);
+			if (status & 0x80 /*UART_MSR_DCD */ )
+				GMStatus[i].dcd = 1;
+			else
+				GMStatus[i].dcd = 0;
+
+			if (status & 0x20 /*UART_MSR_DSR */ )
+				GMStatus[i].dsr = 1;
+			else
+				GMStatus[i].dsr = 0;
+
+
+			if (status & 0x10 /*UART_MSR_CTS */ )
+				GMStatus[i].cts = 1;
+			else
+				GMStatus[i].cts = 0;
+		}
+		if (copy_to_user(argp, GMStatus, sizeof(struct mxser_mstatus) * MXSER_PORTS))
+			return -EFAULT;
+		return 0;
+	case MOXA_ASPP_MON_EXT:{
+			int status;
+			int opmode, p;
+			int shiftbit;
+			unsigned cflag, iflag;
+
+			for (i = 0; i < MXSER_PORTS; i++) {
+
+				if (!mxvar_table[i].base)
+					continue;
+
+				status = mxser_get_msr(mxvar_table[i].base, 0, i, &(mxvar_table[i]));
+//                      mxser_check_modem_status(&mxvar_table[i], status);
+				if (status & UART_MSR_TERI)
+					mxvar_table[i].icount.rng++;
+				if (status & UART_MSR_DDSR)
+					mxvar_table[i].icount.dsr++;
+				if (status & UART_MSR_DDCD)
+					mxvar_table[i].icount.dcd++;
+				if (status & UART_MSR_DCTS)
+					mxvar_table[i].icount.cts++;
+
+				mxvar_table[i].mon_data.modem_status = status;
+				mon_data_ext.rx_cnt[i] = mxvar_table[i].mon_data.rxcnt;
+				mon_data_ext.tx_cnt[i] = mxvar_table[i].mon_data.txcnt;
+				mon_data_ext.up_rxcnt[i] = mxvar_table[i].mon_data.up_rxcnt;
+				mon_data_ext.up_txcnt[i] = mxvar_table[i].mon_data.up_txcnt;
+				mon_data_ext.modem_status[i] = mxvar_table[i].mon_data.modem_status;
+				mon_data_ext.baudrate[i] = mxvar_table[i].realbaud;
+
+				if (!mxvar_table[i].tty || !mxvar_table[i].tty->termios) {
+					cflag = mxvar_table[i].normal_termios.c_cflag;
+					iflag = mxvar_table[i].normal_termios.c_iflag;
+				} else {
+					cflag = mxvar_table[i].tty->termios->c_cflag;
+					iflag = mxvar_table[i].tty->termios->c_iflag;
+				}
+
+				mon_data_ext.databits[i] = cflag & CSIZE;
+
+				mon_data_ext.stopbits[i] = cflag & CSTOPB;
+
+				mon_data_ext.parity[i] = cflag & (PARENB | PARODD | CMSPAR);
+
+				mon_data_ext.flowctrl[i] = 0x00;
+
+				if (cflag & CRTSCTS)
+					mon_data_ext.flowctrl[i] |= 0x03;
+
+				if (iflag & (IXON | IXOFF))
+					mon_data_ext.flowctrl[i] |= 0x0C;
+
+				if (mxvar_table[i].type == PORT_16550A)
+					mon_data_ext.fifo[i] = 1;
+				else
+					mon_data_ext.fifo[i] = 0;
+
+				p = i % 4;
+				shiftbit = p * 2;
+				opmode = inb(mxvar_table[i].opmode_ioaddr) >> shiftbit;
+				opmode &= OP_MODE_MASK;
+
+				mon_data_ext.iftype[i] = opmode;
+
+			}
+			if (copy_to_user(argp, &mon_data_ext, sizeof(struct mxser_mon_ext)))
+				return -EFAULT;
+
+			return 0;
+
+		}
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+
+static void mxser_stoprx(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	//unsigned long flags;
+
+
+	info->ldisc_stop_rx = 1;
+	if (I_IXOFF(tty)) {
+
+		//MX_LOCK(&info->slock);
+		// following add by Victor Yu. 09-02-2002
+		if (info->IsMoxaMustChipFlag) {
+			info->IER &= ~MOXA_MUST_RECV_ISR;
+			outb(info->IER, info->base + UART_IER);
+		} else {
+			// above add by Victor Yu. 09-02-2002
+
+			info->x_char = STOP_CHAR(tty);
+			//      outb(info->IER, 0); // mask by Victor Yu. 09-02-2002
+			outb(0, info->base + UART_IER);
+			info->IER |= UART_IER_THRI;
+			outb(info->IER, info->base + UART_IER);	/* force Tx interrupt */
+		}		// add by Victor Yu. 09-02-2002
+		//MX_UNLOCK(&info->slock);
+	}
+
+	if (info->tty->termios->c_cflag & CRTSCTS) {
+		//MX_LOCK(&info->slock);
+		info->MCR &= ~UART_MCR_RTS;
+		outb(info->MCR, info->base + UART_MCR);
+		//MX_UNLOCK(&info->slock);
+	}
+}
+
+static void mxser_startrx(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	//unsigned long flags;
+
+	info->ldisc_stop_rx = 0;
+	if (I_IXOFF(tty)) {
+		if (info->x_char)
+			info->x_char = 0;
+		else {
+			//MX_LOCK(&info->slock);
+
+			// following add by Victor Yu. 09-02-2002
+			if (info->IsMoxaMustChipFlag) {
+				info->IER |= MOXA_MUST_RECV_ISR;
+				outb(info->IER, info->base + UART_IER);
+			} else {
+				// above add by Victor Yu. 09-02-2002
+
+				info->x_char = START_CHAR(tty);
+				//          outb(info->IER, 0); // mask by Victor Yu. 09-02-2002
+				outb(0, info->base + UART_IER);	// add by Victor Yu. 09-02-2002
+				info->IER |= UART_IER_THRI;	/* force Tx interrupt */
+				outb(info->IER, info->base + UART_IER);
+			}	// add by Victor Yu. 09-02-2002
+			//MX_UNLOCK(&info->slock);
+		}
+	}
+
+	if (info->tty->termios->c_cflag & CRTSCTS) {
+		//MX_LOCK(&info->slock);
+		info->MCR |= UART_MCR_RTS;
+		outb(info->MCR, info->base + UART_MCR);
+		//MX_UNLOCK(&info->slock);
+	}
+}
+
+/*
+ * This routine is called by the upper-layer tty layer to signal that
+ * incoming characters should be throttled.
+ */
+static void mxser_throttle(struct tty_struct *tty)
+{
+	//struct mxser_struct *info = (struct mxser_struct *)tty->driver_data;
+	//unsigned long flags;
+	//MX_LOCK(&info->slock);
+	mxser_stoprx(tty);
+	//MX_UNLOCK(&info->slock);
+}
+
+static void mxser_unthrottle(struct tty_struct *tty)
+{
+	//struct mxser_struct *info = (struct mxser_struct *)tty->driver_data;
+	//unsigned long flags;
+	//MX_LOCK(&info->slock);
+	mxser_startrx(tty);
+	//MX_UNLOCK(&info->slock);
+}
+
+static void mxser_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long flags;
+
+	if ((tty->termios->c_cflag != old_termios->c_cflag) || (RELEVANT_IFLAG(tty->termios->c_iflag) != RELEVANT_IFLAG(old_termios->c_iflag))) {
+
+		mxser_change_speed(info, old_termios);
+
+		if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) {
+			tty->hw_stopped = 0;
+			mxser_start(tty);
+		}
+	}
+
+/* Handle sw stopped */
+	if ((old_termios->c_iflag & IXON) && !(tty->termios->c_iflag & IXON)) {
+		tty->stopped = 0;
+
+		// following add by Victor Yu. 09-02-2002
+		if (info->IsMoxaMustChipFlag) {
+			spin_lock_irqsave(&info->slock, flags);
+			DISABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(info->base);
+			spin_unlock_irqrestore(&info->slock, flags);
+		}
+		// above add by Victor Yu. 09-02-2002
+
+		mxser_start(tty);
+	}
+}
+
+/*
+ * mxser_stop() and mxser_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * They enable or disable transmitter interrupts, as necessary.
+ */
+static void mxser_stop(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->slock, flags);
+	if (info->IER & UART_IER_THRI) {
+		info->IER &= ~UART_IER_THRI;
+		outb(info->IER, info->base + UART_IER);
+	}
+	spin_unlock_irqrestore(&info->slock, flags);
+}
+
+static void mxser_start(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->slock, flags);
+	if (info->xmit_cnt && info->xmit_buf && !(info->IER & UART_IER_THRI)) {
+		info->IER |= UART_IER_THRI;
+		outb(info->IER, info->base + UART_IER);
+	}
+	spin_unlock_irqrestore(&info->slock, flags);
+}
+
+/*
+ * mxser_wait_until_sent() --- wait until the transmitter is empty
+ */
+static void mxser_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long orig_jiffies, char_time;
+	int lsr;
+
+	if (info->type == PORT_UNKNOWN)
+		return;
+
+	if (info->xmit_fifo_size == 0)
+		return;		/* Just in case.... */
+
+	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 = (info->timeout - HZ / 50) / info->xmit_fifo_size;
+	char_time = char_time / 5;
+	if (char_time == 0)
+		char_time = 1;
+	if (timeout && timeout < char_time)
+		char_time = timeout;
+	/*
+	 * If the transmitter hasn't cleared in twice the approximate
+	 * amount of time to send the entire FIFO, it probably won't
+	 * ever clear.  This assumes the UART isn't doing flow
+	 * control, which is currently the case.  Hence, if it ever
+	 * takes longer than info->timeout, this is probably due to a
+	 * UART bug of some kind.  So, we clamp the timeout parameter at
+	 * 2*info->timeout.
+	 */
+	if (!timeout || timeout > 2 * info->timeout)
+		timeout = 2 * info->timeout;
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+	printk(KERN_DEBUG "In rs_wait_until_sent(%d) check=%lu...", timeout, char_time);
+	printk("jiff=%lu...", jiffies);
+#endif
+	while (!((lsr = inb(info->base + UART_LSR)) & UART_LSR_TEMT)) {
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+		printk("lsr = %d (jiff=%lu)...", lsr, jiffies);
+#endif
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(char_time);
+		if (signal_pending(current))
+			break;
+		if (timeout && time_after(jiffies, orig_jiffies + timeout))
+			break;
+	}
+	set_current_state(TASK_RUNNING);
+
+#ifdef SERIAL_DEBUG_RS_WAIT_UNTIL_SENT
+	printk("lsr = %d (jiff=%lu)...done\n", lsr, jiffies);
+#endif
+}
+
+
+/*
+ * This routine is called by tty_hangup() when a hangup is signaled.
+ */
+void mxser_hangup(struct tty_struct *tty)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+
+	mxser_flush_buffer(tty);
+	mxser_shutdown(info);
+	info->event = 0;
+	info->count = 0;
+	info->flags &= ~ASYNC_NORMAL_ACTIVE;
+	info->tty = NULL;
+	wake_up_interruptible(&info->open_wait);
+}
+
+
+// added by James 03-12-2004.
+/*
+ * mxser_rs_break() --- routine which turns the break handling on or off
+ */
+static void mxser_rs_break(struct tty_struct *tty, int break_state)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->slock, flags);
+	if (break_state == -1)
+		outb(inb(info->base + UART_LCR) | UART_LCR_SBC, info->base + UART_LCR);
+	else
+		outb(inb(info->base + UART_LCR) & ~UART_LCR_SBC, info->base + UART_LCR);
+	spin_unlock_irqrestore(&info->slock, flags);
+}
+
+// (above) added by James.
+
+
+/*
+ * This is the serial driver's generic interrupt routine
+ */
+static irqreturn_t mxser_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	int status, iir, i;
+	struct mxser_struct *info;
+	struct mxser_struct *port;
+	int max, irqbits, bits, msr;
+	int pass_counter = 0;
+	int handled = IRQ_NONE;
+
+	port = NULL;
+	//spin_lock(&gm_lock);
+
+	for (i = 0; i < MXSER_BOARDS; i++) {
+		if (dev_id == &(mxvar_table[i * MXSER_PORTS_PER_BOARD])) {
+			port = dev_id;
+			break;
+		}
+	}
+
+	if (i == MXSER_BOARDS) {
+		goto irq_stop;
+	}
+	if (port == 0) {
+		goto irq_stop;
+	}
+	max = mxser_numports[mxsercfg[i].board_type - 1];
+	while (1) {
+		irqbits = inb(port->vector) & port->vectormask;
+		if (irqbits == port->vectormask) {
+			break;
+		}
+
+		handled = IRQ_HANDLED;
+		for (i = 0, bits = 1; i < max; i++, irqbits |= bits, bits <<= 1) {
+			if (irqbits == port->vectormask) {
+				break;
+			}
+			if (bits & irqbits)
+				continue;
+			info = port + i;
+
+			// following add by Victor Yu. 09-13-2002
+			iir = inb(info->base + UART_IIR);
+			if (iir & UART_IIR_NO_INT)
+				continue;
+			iir &= MOXA_MUST_IIR_MASK;
+			if (!info->tty) {
+				status = inb(info->base + UART_LSR);
+				outb(0x27, info->base + UART_FCR);
+				inb(info->base + UART_MSR);
+				continue;
+			}
+			// above add by Victor Yu. 09-13-2002
+			/*
+			   if ( info->tty->flip.count < TTY_FLIPBUF_SIZE/4 ){
+			   info->IER |= MOXA_MUST_RECV_ISR;
+			   outb(info->IER, info->base + UART_IER);
+			   }
+			 */
+
+
+			/* mask by Victor Yu. 09-13-2002
+			   if ( !info->tty ||
+			   (inb(info->base + UART_IIR) & UART_IIR_NO_INT) )
+			   continue;
+			 */
+			/* mask by Victor Yu. 09-02-2002
+			   status = inb(info->base + UART_LSR) & info->read_status_mask;
+			 */
+
+			// following add by Victor Yu. 09-02-2002
+			status = inb(info->base + UART_LSR);
+
+			if (status & UART_LSR_PE) {
+				info->err_shadow |= NPPI_NOTIFY_PARITY;
+			}
+			if (status & UART_LSR_FE) {
+				info->err_shadow |= NPPI_NOTIFY_FRAMING;
+			}
+			if (status & UART_LSR_OE) {
+				info->err_shadow |= NPPI_NOTIFY_HW_OVERRUN;
+			}
+			if (status & UART_LSR_BI)
+				info->err_shadow |= NPPI_NOTIFY_BREAK;
+
+			if (info->IsMoxaMustChipFlag) {
+				/*
+				   if ( (status & 0x02) && !(status & 0x01) ) {
+				   outb(info->base+UART_FCR,  0x23);
+				   continue;
+				   }
+				 */
+				if (iir == MOXA_MUST_IIR_GDA || iir == MOXA_MUST_IIR_RDA || iir == MOXA_MUST_IIR_RTO || iir == MOXA_MUST_IIR_LSR)
+					mxser_receive_chars(info, &status);
+
+			} else {
+				// above add by Victor Yu. 09-02-2002
+
+				status &= info->read_status_mask;
+				if (status & UART_LSR_DR)
+					mxser_receive_chars(info, &status);
+			}
+			msr = inb(info->base + UART_MSR);
+			if (msr & UART_MSR_ANY_DELTA) {
+				mxser_check_modem_status(info, msr);
+			}
+			// following add by Victor Yu. 09-13-2002
+			if (info->IsMoxaMustChipFlag) {
+				if ((iir == 0x02) && (status & UART_LSR_THRE)) {
+					mxser_transmit_chars(info);
+				}
+			} else {
+				// above add by Victor Yu. 09-13-2002
+
+				if (status & UART_LSR_THRE) {
+/* 8-2-99 by William
+			    if ( info->x_char || (info->xmit_cnt > 0) )
+*/
+					mxser_transmit_chars(info);
+				}
+			}
+		}
+		if (pass_counter++ > MXSER_ISR_PASS_LIMIT) {
+			break;	/* Prevent infinite loops */
+		}
+	}
+
+      irq_stop:
+	//spin_unlock(&gm_lock);
+	return handled;
+}
+
+static void mxser_receive_chars(struct mxser_struct *info, int *status)
+{
+	struct tty_struct *tty = info->tty;
+	unsigned char ch, gdl;
+	int ignored = 0;
+	int cnt = 0;
+	unsigned char *cp;
+	char *fp;
+	int count;
+	int recv_room;
+	int max = 256;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->slock, flags);
+
+	recv_room = tty->ldisc.receive_room(tty);
+	if ((recv_room == 0) && (!info->ldisc_stop_rx)) {
+		//mxser_throttle(tty);
+		mxser_stoprx(tty);
+		//return;
+	}
+
+	cp = tty->flip.char_buf;
+	fp = tty->flip.flag_buf;
+	count = 0;
+
+	// following add by Victor Yu. 09-02-2002
+	if (info->IsMoxaMustChipFlag != MOXA_OTHER_UART) {
+
+		if (*status & UART_LSR_SPECIAL) {
+			goto intr_old;
+		}
+		// following add by Victor Yu. 02-11-2004
+		if (info->IsMoxaMustChipFlag == MOXA_MUST_MU860_HWID && (*status & MOXA_MUST_LSR_RERR))
+			goto intr_old;
+		// above add by Victor Yu. 02-14-2004
+		if (*status & MOXA_MUST_LSR_RERR)
+			goto intr_old;
+
+		gdl = inb(info->base + MOXA_MUST_GDL_REGISTER);
+
+		if (info->IsMoxaMustChipFlag == MOXA_MUST_MU150_HWID)	// add by Victor Yu. 02-11-2004
+			gdl &= MOXA_MUST_GDL_MASK;
+		if (gdl >= recv_room) {
+			if (!info->ldisc_stop_rx) {
+				//mxser_throttle(tty);
+				mxser_stoprx(tty);
+			}
+			//return;
+		}
+		while (gdl--) {
+			ch = inb(info->base + UART_RX);
+			count++;
+			*cp++ = ch;
+			*fp++ = 0;
+			cnt++;
+			/*
+			   if((count>=HI_WATER) && (info->stop_rx==0)){
+			   mxser_stoprx(tty);
+			   info->stop_rx=1;
+			   break;
+			   } */
+		}
+		goto end_intr;
+	}
+intr_old:
+	// above add by Victor Yu. 09-02-2002
+
+	do {
+		if (max-- < 0)
+			break;
+		/*
+		   if((count>=HI_WATER) && (info->stop_rx==0)){
+		   mxser_stoprx(tty);
+		   info->stop_rx=1;
+		   break;
+		   }
+		 */
+
+		ch = inb(info->base + UART_RX);
+		// following add by Victor Yu. 09-02-2002
+		if (info->IsMoxaMustChipFlag && (*status & UART_LSR_OE) /*&& !(*status&UART_LSR_DR) */ )
+			outb(0x23, info->base + UART_FCR);
+		*status &= info->read_status_mask;
+		// above add by Victor Yu. 09-02-2002
+		if (*status & info->ignore_status_mask) {
+			if (++ignored > 100)
+				break;
+		} else {
+			count++;
+			if (*status & UART_LSR_SPECIAL) {
+				if (*status & UART_LSR_BI) {
+					*fp++ = TTY_BREAK;
+/* added by casper 1/11/2000 */
+					info->icount.brk++;
+
+/* */
+					if (info->flags & ASYNC_SAK)
+						do_SAK(tty);
+				} else if (*status & UART_LSR_PE) {
+					*fp++ = TTY_PARITY;
+/* added by casper 1/11/2000 */
+					info->icount.parity++;
+/* */
+				} else if (*status & UART_LSR_FE) {
+					*fp++ = TTY_FRAME;
+/* added by casper 1/11/2000 */
+					info->icount.frame++;
+/* */
+				} else if (*status & UART_LSR_OE) {
+					*fp++ = TTY_OVERRUN;
+/* added by casper 1/11/2000 */
+					info->icount.overrun++;
+/* */
+				} else
+					*fp++ = 0;
+			} else
+				*fp++ = 0;
+			*cp++ = ch;
+			cnt++;
+			if (cnt >= recv_room) {
+				if (!info->ldisc_stop_rx) {
+					//mxser_throttle(tty);
+					mxser_stoprx(tty);
+				}
+				break;
+			}
+
+		}
+
+		// following add by Victor Yu. 09-02-2002
+		if (info->IsMoxaMustChipFlag)
+			break;
+		// above add by Victor Yu. 09-02-2002
+
+		/* mask by Victor Yu. 09-02-2002
+		 *status = inb(info->base + UART_LSR) & info->read_status_mask;
+		 */
+		// following add by Victor Yu. 09-02-2002
+		*status = inb(info->base + UART_LSR);
+		// above add by Victor Yu. 09-02-2002
+	} while (*status & UART_LSR_DR);
+
+      end_intr:		// add by Victor Yu. 09-02-2002
+
+	mxvar_log.rxcnt[info->port] += cnt;
+	info->mon_data.rxcnt += cnt;
+	info->mon_data.up_rxcnt += cnt;
+	spin_unlock_irqrestore(&info->slock, flags);
+	
+	tty_flip_buffer_push(tty);
+}
+
+static void mxser_transmit_chars(struct mxser_struct *info)
+{
+	int count, cnt;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->slock, flags);
+
+	if (info->x_char) {
+		outb(info->x_char, info->base + UART_TX);
+		info->x_char = 0;
+		mxvar_log.txcnt[info->port]++;
+		info->mon_data.txcnt++;
+		info->mon_data.up_txcnt++;
+
+/* added by casper 1/11/2000 */
+		info->icount.tx++;
+/* */
+		spin_unlock_irqrestore(&info->slock, flags);
+		return;
+	}
+
+	if (info->xmit_buf == 0) {
+		spin_unlock_irqrestore(&info->slock, flags);
+		return;
+	}
+
+	if ((info->xmit_cnt <= 0) || info->tty->stopped || (info->tty->hw_stopped && (info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag))) {
+		info->IER &= ~UART_IER_THRI;
+		outb(info->IER, info->base + UART_IER);
+		spin_unlock_irqrestore(&info->slock, flags);
+		return;
+	}
+
+	cnt = info->xmit_cnt;
+	count = info->xmit_fifo_size;
+	do {
+		outb(info->xmit_buf[info->xmit_tail++], info->base + UART_TX);
+		info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE - 1);
+		if (--info->xmit_cnt <= 0)
+			break;
+	} while (--count > 0);
+	mxvar_log.txcnt[info->port] += (cnt - info->xmit_cnt);
+
+// added by James 03-12-2004.
+	info->mon_data.txcnt += (cnt - info->xmit_cnt);
+	info->mon_data.up_txcnt += (cnt - info->xmit_cnt);
+// (above) added by James.
+
+/* added by casper 1/11/2000 */
+	info->icount.tx += (cnt - info->xmit_cnt);
+/* */
+
+	if (info->xmit_cnt < WAKEUP_CHARS) {
+		set_bit(MXSER_EVENT_TXLOW, &info->event);
+		schedule_work(&info->tqueue);
+	}
+	if (info->xmit_cnt <= 0) {
+		info->IER &= ~UART_IER_THRI;
+		outb(info->IER, info->base + UART_IER);
+	}
+	spin_unlock_irqrestore(&info->slock, flags);
+}
+
+static void mxser_check_modem_status(struct mxser_struct *info, int status)
+{
+	/* update input line counters */
+	if (status & UART_MSR_TERI)
+		info->icount.rng++;
+	if (status & UART_MSR_DDSR)
+		info->icount.dsr++;
+	if (status & UART_MSR_DDCD)
+		info->icount.dcd++;
+	if (status & UART_MSR_DCTS)
+		info->icount.cts++;
+	info->mon_data.modem_status = status;
+	wake_up_interruptible(&info->delta_msr_wait);
+
+
+	if ((info->flags & ASYNC_CHECK_CD) && (status & UART_MSR_DDCD)) {
+		if (status & UART_MSR_DCD)
+			wake_up_interruptible(&info->open_wait);
+		schedule_work(&info->tqueue);
+	}
+
+	if (info->flags & ASYNC_CTS_FLOW) {
+		if (info->tty->hw_stopped) {
+			if (status & UART_MSR_CTS) {
+				info->tty->hw_stopped = 0;
+
+				if ((info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)) {
+					info->IER |= UART_IER_THRI;
+					outb(info->IER, info->base + UART_IER);
+				}
+				set_bit(MXSER_EVENT_TXLOW, &info->event);
+				schedule_work(&info->tqueue);			}
+		} else {
+			if (!(status & UART_MSR_CTS)) {
+				info->tty->hw_stopped = 1;
+				if ((info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)) {
+					info->IER &= ~UART_IER_THRI;
+					outb(info->IER, info->base + UART_IER);
+				}
+			}
+		}
+	}
+}
+
+static int mxser_block_til_ready(struct tty_struct *tty, struct file *filp, struct mxser_struct *info)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	int retval;
+	int do_clocal = 0;
+	unsigned long flags;
+
+	/*
+	 * 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))) {
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+		return (0);
+	}
+
+	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, info->count is dropped by one, so that
+	 * mxser_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	retval = 0;
+	add_wait_queue(&info->open_wait, &wait);
+
+	spin_lock_irqsave(&info->slock, flags);
+	if (!tty_hung_up_p(filp))
+		info->count--;
+	spin_unlock_irqrestore(&info->slock, flags);
+	info->blocked_open++;
+	while (1) {
+		spin_lock_irqsave(&info->slock, flags);
+		outb(inb(info->base + UART_MCR) | UART_MCR_DTR | UART_MCR_RTS, info->base + UART_MCR);
+		spin_unlock_irqrestore(&info->slock, flags);
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)) {
+			if (info->flags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;
+			break;
+		}
+		if (!(info->flags & ASYNC_CLOSING) && (do_clocal || (inb(info->base + UART_MSR) & UART_MSR_DCD)))
+			break;
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();
+	}
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&info->open_wait, &wait);
+	if (!tty_hung_up_p(filp))
+		info->count++;
+	info->blocked_open--;
+	if (retval)
+		return (retval);
+	info->flags |= ASYNC_NORMAL_ACTIVE;
+	return (0);
+}
+
+static int mxser_startup(struct mxser_struct *info)
+{
+
+	unsigned long page;
+	unsigned long flags;
+
+	page = __get_free_page(GFP_KERNEL);
+	if (!page)
+		return (-ENOMEM);
+
+	spin_lock_irqsave(&info->slock, flags);
+
+	if (info->flags & ASYNC_INITIALIZED) {
+		free_page(page);
+		spin_unlock_irqrestore(&info->slock, flags);
+		return (0);
+	}
+
+	if (!info->base || !info->type) {
+		if (info->tty)
+			set_bit(TTY_IO_ERROR, &info->tty->flags);
+		free_page(page);
+		spin_unlock_irqrestore(&info->slock, flags);
+		return (0);
+	}
+	if (info->xmit_buf)
+		free_page(page);
+	else
+		info->xmit_buf = (unsigned char *) page;
+
+	/*
+	 * Clear the FIFO buffers and disable them
+	 * (they will be reenabled in mxser_change_speed())
+	 */
+	if (info->IsMoxaMustChipFlag)
+		outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT | MOXA_MUST_FCR_GDA_MODE_ENABLE), info->base + UART_FCR);
+	else
+		outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT), info->base + UART_FCR);
+
+	/*
+	 * At this point there's no way the LSR could still be 0xFF;
+	 * if it is, then bail out, because there's likely no UART
+	 * here.
+	 */
+	if (inb(info->base + UART_LSR) == 0xff) {
+		spin_unlock_irqrestore(&info->slock, flags);
+		if (capable(CAP_SYS_ADMIN)) {
+			if (info->tty)
+				set_bit(TTY_IO_ERROR, &info->tty->flags);
+			return (0);
+		} else
+			return (-ENODEV);
+	}
+
+	/*
+	 * Clear the interrupt registers.
+	 */
+	(void) inb(info->base + UART_LSR);
+	(void) inb(info->base + UART_RX);
+	(void) inb(info->base + UART_IIR);
+	(void) inb(info->base + UART_MSR);
+
+	/*
+	 * Now, initialize the UART
+	 */
+	outb(UART_LCR_WLEN8, info->base + UART_LCR);	/* reset DLAB */
+	info->MCR = UART_MCR_DTR | UART_MCR_RTS;
+	outb(info->MCR, info->base + UART_MCR);
+
+	/*
+	 * Finally, enable interrupts
+	 */
+	info->IER = UART_IER_MSI | UART_IER_RLSI | UART_IER_RDI;
+//      info->IER = UART_IER_RLSI | UART_IER_RDI;
+
+	// following add by Victor Yu. 08-30-2002
+	if (info->IsMoxaMustChipFlag)
+		info->IER |= MOXA_MUST_IER_EGDAI;
+	// above add by Victor Yu. 08-30-2002
+	outb(info->IER, info->base + UART_IER);	/* enable interrupts */
+
+	/*
+	 * And clear the interrupt registers again for luck.
+	 */
+	(void) inb(info->base + UART_LSR);
+	(void) inb(info->base + UART_RX);
+	(void) inb(info->base + UART_IIR);
+	(void) inb(info->base + UART_MSR);
+
+	if (info->tty)
+		clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+	/*
+	 * and set the speed of the serial port
+	 */
+	spin_unlock_irqrestore(&info->slock, flags);
+	mxser_change_speed(info, NULL);
+
+	info->flags |= ASYNC_INITIALIZED;
+	return (0);
+}
+
+/*
+ * This routine will shutdown a serial port; interrupts maybe disabled, and
+ * DTR is dropped if the hangup on close termio flag is on.
+ */
+static void mxser_shutdown(struct mxser_struct *info)
+{
+	unsigned long flags;
+
+	if (!(info->flags & ASYNC_INITIALIZED))
+		return;
+
+	spin_lock_irqsave(&info->slock, flags);
+
+	/*
+	 * 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(&info->delta_msr_wait);
+
+	/*
+	 * Free the IRQ, if necessary
+	 */
+	if (info->xmit_buf) {
+		free_page((unsigned long) info->xmit_buf);
+		info->xmit_buf = NULL;
+	}
+
+	info->IER = 0;
+	outb(0x00, info->base + UART_IER);
+
+	if (!info->tty || (info->tty->termios->c_cflag & HUPCL))
+		info->MCR &= ~(UART_MCR_DTR | UART_MCR_RTS);
+	outb(info->MCR, info->base + UART_MCR);
+
+	/* clear Rx/Tx FIFO's */
+	// following add by Victor Yu. 08-30-2002
+	if (info->IsMoxaMustChipFlag)
+		outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT | MOXA_MUST_FCR_GDA_MODE_ENABLE), info->base + UART_FCR);
+	else
+		// above add by Victor Yu. 08-30-2002
+		outb((UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT), info->base + UART_FCR);
+
+	/* read data port to reset things */
+	(void) inb(info->base + UART_RX);
+
+	if (info->tty)
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	info->flags &= ~ASYNC_INITIALIZED;
+
+	// following add by Victor Yu. 09-23-2002
+	if (info->IsMoxaMustChipFlag) {
+		SET_MOXA_MUST_NO_SOFTWARE_FLOW_CONTROL(info->base);
+	}
+	// above add by Victor Yu. 09-23-2002
+
+	spin_unlock_irqrestore(&info->slock, flags);
+}
+
+/*
+ * This routine is called to set the UART divisor registers to match
+ * the specified baud rate for a serial port.
+ */
+static int mxser_change_speed(struct mxser_struct *info, struct termios *old_termios)
+{
+	unsigned cflag, cval, fcr;
+	int ret = 0;
+	unsigned char status;
+	long baud;
+	unsigned long flags;
+
+
+	if (!info->tty || !info->tty->termios)
+		return ret;
+	cflag = info->tty->termios->c_cflag;
+	if (!(info->base))
+		return ret;
+
+
+#ifndef B921600
+#define B921600 (B460800 +1)
+#endif
+	if (mxser_set_baud_method[info->port] == 0) {
+		switch (cflag & (CBAUD | CBAUDEX)) {
+		case B921600:
+			baud = 921600;
+			break;
+		case B460800:
+			baud = 460800;
+			break;
+		case B230400:
+			baud = 230400;
+			break;
+		case B115200:
+			baud = 115200;
+			break;
+		case B57600:
+			baud = 57600;
+			break;
+		case B38400:
+			baud = 38400;
+			break;
+		case B19200:
+			baud = 19200;
+			break;
+		case B9600:
+			baud = 9600;
+			break;
+		case B4800:
+			baud = 4800;
+			break;
+		case B2400:
+			baud = 2400;
+			break;
+		case B1800:
+			baud = 1800;
+			break;
+		case B1200:
+			baud = 1200;
+			break;
+		case B600:
+			baud = 600;
+			break;
+		case B300:
+			baud = 300;
+			break;
+		case B200:
+			baud = 200;
+			break;
+		case B150:
+			baud = 150;
+			break;
+		case B134:
+			baud = 134;
+			break;
+		case B110:
+			baud = 110;
+			break;
+		case B75:
+			baud = 75;
+			break;
+		case B50:
+			baud = 50;
+			break;
+		default:
+			baud = 0;
+			break;
+		}
+		mxser_set_baud(info, baud);
+	}
+
+	/* byte size and parity */
+	switch (cflag & CSIZE) {
+	case CS5:
+		cval = 0x00;
+		break;
+	case CS6:
+		cval = 0x01;
+		break;
+	case CS7:
+		cval = 0x02;
+		break;
+	case CS8:
+		cval = 0x03;
+		break;
+	default:
+		cval = 0x00;
+		break;		/* too keep GCC shut... */
+	}
+	if (cflag & CSTOPB)
+		cval |= 0x04;
+	if (cflag & PARENB)
+		cval |= UART_LCR_PARITY;
+	if (!(cflag & PARODD)) {
+		cval |= UART_LCR_EPAR;
+	}
+	if (cflag & CMSPAR)
+		cval |= UART_LCR_SPAR;
+
+	if ((info->type == PORT_8250) || (info->type == PORT_16450)) {
+		if (info->IsMoxaMustChipFlag) {
+			fcr = UART_FCR_ENABLE_FIFO;
+			fcr |= MOXA_MUST_FCR_GDA_MODE_ENABLE;
+			SET_MOXA_MUST_FIFO_VALUE(info);
+		} else
+			fcr = 0;
+	} else {
+		fcr = UART_FCR_ENABLE_FIFO;
+		// following add by Victor Yu. 08-30-2002
+		if (info->IsMoxaMustChipFlag) {
+			fcr |= MOXA_MUST_FCR_GDA_MODE_ENABLE;
+			SET_MOXA_MUST_FIFO_VALUE(info);
+		} else {
+			// above add by Victor Yu. 08-30-2002
+
+			switch (info->rx_trigger) {
+			case 1:
+				fcr |= UART_FCR_TRIGGER_1;
+				break;
+			case 4:
+				fcr |= UART_FCR_TRIGGER_4;
+				break;
+			case 8:
+				fcr |= UART_FCR_TRIGGER_8;
+				break;
+			default:
+				fcr |= UART_FCR_TRIGGER_14;
+				break;
+			}
+		}
+	}
+
+	/* CTS flow control flag and modem status interrupts */
+	info->IER &= ~UART_IER_MSI;
+	info->MCR &= ~UART_MCR_AFE;
+	if (cflag & CRTSCTS) {
+		info->flags |= ASYNC_CTS_FLOW;
+		info->IER |= UART_IER_MSI;
+		if ((info->type == PORT_16550A) || (info->IsMoxaMustChipFlag)) {
+			info->MCR |= UART_MCR_AFE;
+			//status = mxser_get_msr(info->base, 0, info->port);
+/*	save_flags(flags);
+	cli();
+	status = inb(baseaddr + UART_MSR);
+	restore_flags(flags);*/
+			//mxser_check_modem_status(info, status);
+		} else {
+			//status = mxser_get_msr(info->base, 0, info->port);
+
+			//MX_LOCK(&info->slock);
+			status = inb(info->base + UART_MSR);
+			//MX_UNLOCK(&info->slock);
+			if (info->tty->hw_stopped) {
+				if (status & UART_MSR_CTS) {
+					info->tty->hw_stopped = 0;
+					if ((info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)) {
+						info->IER |= UART_IER_THRI;
+						outb(info->IER, info->base + UART_IER);
+					}
+					set_bit(MXSER_EVENT_TXLOW, &info->event);
+					schedule_work(&info->tqueue);				}
+			} else {
+				if (!(status & UART_MSR_CTS)) {
+					info->tty->hw_stopped = 1;
+					if ((info->type != PORT_16550A) && (!info->IsMoxaMustChipFlag)) {
+						info->IER &= ~UART_IER_THRI;
+						outb(info->IER, info->base + UART_IER);
+					}
+				}
+			}
+		}
+	} else {
+		info->flags &= ~ASYNC_CTS_FLOW;
+	}
+	outb(info->MCR, info->base + UART_MCR);
+	if (cflag & CLOCAL) {
+		info->flags &= ~ASYNC_CHECK_CD;
+	} else {
+		info->flags |= ASYNC_CHECK_CD;
+		info->IER |= UART_IER_MSI;
+	}
+	outb(info->IER, info->base + UART_IER);
+
+	/*
+	 * Set up parity check flag
+	 */
+	info->read_status_mask = UART_LSR_OE | UART_LSR_THRE | UART_LSR_DR;
+	if (I_INPCK(info->tty))
+		info->read_status_mask |= UART_LSR_FE | UART_LSR_PE;
+	if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+		info->read_status_mask |= UART_LSR_BI;
+
+	info->ignore_status_mask = 0;
+
+	if (I_IGNBRK(info->tty)) {
+		info->ignore_status_mask |= UART_LSR_BI;
+		info->read_status_mask |= UART_LSR_BI;
+		/*
+		 * If we're ignore parity and break indicators, ignore
+		 * overruns too.  (For real raw support).
+		 */
+		if (I_IGNPAR(info->tty)) {
+			info->ignore_status_mask |= UART_LSR_OE | UART_LSR_PE | UART_LSR_FE;
+			info->read_status_mask |= UART_LSR_OE | UART_LSR_PE | UART_LSR_FE;
+		}
+	}
+	// following add by Victor Yu. 09-02-2002
+	if (info->IsMoxaMustChipFlag) {
+		spin_lock_irqsave(&info->slock, flags);
+		SET_MOXA_MUST_XON1_VALUE(info->base, START_CHAR(info->tty));
+		SET_MOXA_MUST_XOFF1_VALUE(info->base, STOP_CHAR(info->tty));
+		if (I_IXON(info->tty)) {
+			ENABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(info->base);
+		} else {
+			DISABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(info->base);
+		}
+		if (I_IXOFF(info->tty)) {
+			ENABLE_MOXA_MUST_TX_SOFTWARE_FLOW_CONTROL(info->base);
+		} else {
+			DISABLE_MOXA_MUST_TX_SOFTWARE_FLOW_CONTROL(info->base);
+		}
+		/*
+		   if ( I_IXANY(info->tty) ) {
+		   info->MCR |= MOXA_MUST_MCR_XON_ANY;
+		   ENABLE_MOXA_MUST_XON_ANY_FLOW_CONTROL(info->base);
+		   } else {
+		   info->MCR &= ~MOXA_MUST_MCR_XON_ANY;
+		   DISABLE_MOXA_MUST_XON_ANY_FLOW_CONTROL(info->base);
+		   }
+		 */
+		spin_unlock_irqrestore(&info->slock, flags);
+	}
+	// above add by Victor Yu. 09-02-2002
+
+
+	outb(fcr, info->base + UART_FCR);	/* set fcr */
+	outb(cval, info->base + UART_LCR);
+
+	return ret;
+}
+
+
+static int mxser_set_baud(struct mxser_struct *info, long newspd)
+{
+	int quot = 0;
+	unsigned char cval;
+	int ret = 0;
+	unsigned long flags;
+
+	if (!info->tty || !info->tty->termios)
+		return ret;
+
+	if (!(info->base))
+		return ret;
+
+	if (newspd > info->MaxCanSetBaudRate)
+		return 0;
+
+	info->realbaud = newspd;
+	if (newspd == 134) {
+		quot = (2 * info->baud_base / 269);
+	} else if (newspd) {
+		quot = info->baud_base / newspd;
+
+		if (quot == 0)
+			quot = 1;
+
+	} else {
+		quot = 0;
+	}
+
+	info->timeout = ((info->xmit_fifo_size * HZ * 10 * quot) / info->baud_base);
+	info->timeout += HZ / 50;	/* Add .02 seconds of slop */
+
+	if (quot) {
+		spin_lock_irqsave(&info->slock, flags);
+		info->MCR |= UART_MCR_DTR;
+		outb(info->MCR, info->base + UART_MCR);
+		spin_unlock_irqrestore(&info->slock, flags);
+	} else {
+		spin_lock_irqsave(&info->slock, flags);
+		info->MCR &= ~UART_MCR_DTR;
+		outb(info->MCR, info->base + UART_MCR);
+		spin_unlock_irqrestore(&info->slock, flags);
+		return ret;
+	}
+
+	cval = inb(info->base + UART_LCR);
+
+	outb(cval | UART_LCR_DLAB, info->base + UART_LCR);	/* set DLAB */
+
+	outb(quot & 0xff, info->base + UART_DLL);	/* LS of divisor */
+	outb(quot >> 8, info->base + UART_DLM);	/* MS of divisor */
+	outb(cval, info->base + UART_LCR);	/* reset DLAB */
+
+
+	return ret;
+}
+
+
+
+/*
+ * ------------------------------------------------------------
+ * friends of mxser_ioctl()
+ * ------------------------------------------------------------
+ */
+static int mxser_get_serial_info(struct mxser_struct *info, struct serial_struct __user *retinfo)
+{
+	struct serial_struct tmp;
+
+	if (!retinfo)
+		return (-EFAULT);
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.type = info->type;
+	tmp.line = info->port;
+	tmp.port = info->base;
+	tmp.irq = info->irq;
+	tmp.flags = info->flags;
+	tmp.baud_base = info->baud_base;
+	tmp.close_delay = info->close_delay;
+	tmp.closing_wait = info->closing_wait;
+	tmp.custom_divisor = info->custom_divisor;
+	tmp.hub6 = 0;
+	if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
+		return -EFAULT;
+	return (0);
+}
+
+static int mxser_set_serial_info(struct mxser_struct *info, struct serial_struct __user *new_info)
+{
+	struct serial_struct new_serial;
+	unsigned int flags;
+	int retval = 0;
+
+	if (!new_info || !info->base)
+		return (-EFAULT);
+	if (copy_from_user(&new_serial, new_info, sizeof(new_serial)))
+		return -EFAULT;
+
+	if ((new_serial.irq != info->irq) || (new_serial.port != info->base) || (new_serial.custom_divisor != info->custom_divisor) || (new_serial.baud_base != info->baud_base))
+		return (-EPERM);
+
+	flags = info->flags & ASYNC_SPD_MASK;
+
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((new_serial.baud_base != info->baud_base) || (new_serial.close_delay != info->close_delay) || ((new_serial.flags & ~ASYNC_USR_MASK) != (info->flags & ~ASYNC_USR_MASK)))
+			return (-EPERM);
+		info->flags = ((info->flags & ~ASYNC_USR_MASK) | (new_serial.flags & ASYNC_USR_MASK));
+	} else {
+		/*
+		 * OK, past this point, all the error checking has been done.
+		 * At this point, we start making changes.....
+		 */
+		info->flags = ((info->flags & ~ASYNC_FLAGS) | (new_serial.flags & ASYNC_FLAGS));
+		info->close_delay = new_serial.close_delay * HZ / 100;
+		info->closing_wait = new_serial.closing_wait * HZ / 100;
+		info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+		info->tty->low_latency = 0;	//(info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+	}
+
+	/* added by casper, 3/17/2000, for mouse */
+	info->type = new_serial.type;
+
+	process_txrx_fifo(info);
+
+	/* */
+	if (info->flags & ASYNC_INITIALIZED) {
+		if (flags != (info->flags & ASYNC_SPD_MASK)) {
+			mxser_change_speed(info, NULL);
+		}
+	} else {
+		retval = mxser_startup(info);
+	}
+	return (retval);
+}
+
+/*
+ * mxser_get_lsr_info - get line status register info
+ *
+ * Purpose: Let user call ioctl() to get info when the UART physically
+ *	    is emptied.  On bus types like RS485, the transmitter must
+ *	    release the bus after transmitting. This must be done when
+ *	    the transmit shift register is empty, not be done when the
+ *	    transmit holding register is empty.  This functionality
+ *	    allows an RS485 driver to be written in user space.
+ */
+static int mxser_get_lsr_info(struct mxser_struct *info, unsigned int __user *value)
+{
+	unsigned char status;
+	unsigned int result;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->slock, flags);
+	status = inb(info->base + UART_LSR);
+	spin_unlock_irqrestore(&info->slock, flags);
+	result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
+	return put_user(result, value);
+}
+
+/*
+ * This routine sends a break character out the serial port.
+ */
+static void mxser_send_break(struct mxser_struct *info, int duration)
+{
+	unsigned long flags;
+
+	if (!info->base)
+		return;
+	set_current_state(TASK_INTERRUPTIBLE);
+	spin_lock_irqsave(&info->slock, flags);
+	outb(inb(info->base + UART_LCR) | UART_LCR_SBC, info->base + UART_LCR);
+	spin_unlock_irqrestore(&info->slock, flags);
+	schedule_timeout(duration);
+	spin_lock_irqsave(&info->slock, flags);
+	outb(inb(info->base + UART_LCR) & ~UART_LCR_SBC, info->base + UART_LCR);
+	spin_unlock_irqrestore(&info->slock, flags);
+}
+
+static int mxser_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned char control, status;
+	unsigned long flags;
+
+
+	if (tty->index == MXSER_PORTS)
+		return (-ENOIOCTLCMD);
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return (-EIO);
+
+	control = info->MCR;
+
+	spin_lock_irqsave(&info->slock, flags);
+	status = inb(info->base + UART_MSR);
+	if (status & UART_MSR_ANY_DELTA)
+		mxser_check_modem_status(info, status);
+	spin_unlock_irqrestore(&info->slock, flags);
+	return ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) |
+	    ((control & UART_MCR_DTR) ? TIOCM_DTR : 0) | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0) | ((status & UART_MSR_RI) ? TIOCM_RNG : 0) | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0) | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0);
+}
+
+static int mxser_tiocmset(struct tty_struct *tty, struct file *file, unsigned int set, unsigned int clear)
+{
+	struct mxser_struct *info = (struct mxser_struct *) tty->driver_data;
+	unsigned long flags;
+
+
+	if (tty->index == MXSER_PORTS)
+		return -ENOIOCTLCMD;
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return -EIO;
+
+	spin_lock_irqsave(&info->slock, flags);
+
+	if (set & TIOCM_RTS)
+		info->MCR |= UART_MCR_RTS;
+	if (set & TIOCM_DTR)
+		info->MCR |= UART_MCR_DTR;
+
+	if (clear & TIOCM_RTS)
+		info->MCR &= ~UART_MCR_RTS;
+	if (clear & TIOCM_DTR)
+		info->MCR &= ~UART_MCR_DTR;
+
+	outb(info->MCR, info->base + UART_MCR);
+	spin_unlock_irqrestore(&info->slock, flags);
+	return 0;
+}
+
+
+static int mxser_read_register(int, unsigned short *);
+static int mxser_program_mode(int);
+static void mxser_normal_mode(int);
+
+static int mxser_get_ISA_conf(int cap, struct mxser_hwconf *hwconf)
+{
+	int id, i, bits;
+	unsigned short regs[16], irq;
+	unsigned char scratch, scratch2;
+
+	hwconf->IsMoxaMustChipFlag = MOXA_OTHER_UART;
+
+	id = mxser_read_register(cap, regs);
+	if (id == C168_ASIC_ID) {
+		hwconf->board_type = MXSER_BOARD_C168_ISA;
+		hwconf->ports = 8;
+	} else if (id == C104_ASIC_ID) {
+		hwconf->board_type = MXSER_BOARD_C104_ISA;
+		hwconf->ports = 4;
+	} else if (id == C102_ASIC_ID) {
+		hwconf->board_type = MXSER_BOARD_C102_ISA;
+		hwconf->ports = 2;
+	} else if (id == CI132_ASIC_ID) {
+		hwconf->board_type = MXSER_BOARD_CI132;
+		hwconf->ports = 2;
+	} else if (id == CI134_ASIC_ID) {
+		hwconf->board_type = MXSER_BOARD_CI134;
+		hwconf->ports = 4;
+	} else if (id == CI104J_ASIC_ID) {
+		hwconf->board_type = MXSER_BOARD_CI104J;
+		hwconf->ports = 4;
+	} else
+		return (0);
+
+	irq = 0;
+	if (hwconf->ports == 2) {
+		irq = regs[9] & 0xF000;
+		irq = irq | (irq >> 4);
+		if (irq != (regs[9] & 0xFF00))
+			return (MXSER_ERR_IRQ_CONFLIT);
+	} else if (hwconf->ports == 4) {
+		irq = regs[9] & 0xF000;
+		irq = irq | (irq >> 4);
+		irq = irq | (irq >> 8);
+		if (irq != regs[9])
+			return (MXSER_ERR_IRQ_CONFLIT);
+	} else if (hwconf->ports == 8) {
+		irq = regs[9] & 0xF000;
+		irq = irq | (irq >> 4);
+		irq = irq | (irq >> 8);
+		if ((irq != regs[9]) || (irq != regs[10]))
+			return (MXSER_ERR_IRQ_CONFLIT);
+	}
+
+	if (!irq) {
+		return (MXSER_ERR_IRQ);
+	}
+	hwconf->irq = ((int) (irq & 0xF000) >> 12);
+	for (i = 0; i < 8; i++)
+		hwconf->ioaddr[i] = (int) regs[i + 1] & 0xFFF8;
+	if ((regs[12] & 0x80) == 0) {
+		return (MXSER_ERR_VECTOR);
+	}
+	hwconf->vector = (int) regs[11];	/* interrupt vector */
+	if (id == 1)
+		hwconf->vector_mask = 0x00FF;
+	else
+		hwconf->vector_mask = 0x000F;
+	for (i = 7, bits = 0x0100; i >= 0; i--, bits <<= 1) {
+		if (regs[12] & bits) {
+			hwconf->baud_base[i] = 921600;
+			hwconf->MaxCanSetBaudRate[i] = 921600;	// add by Victor Yu. 09-04-2002
+		} else {
+			hwconf->baud_base[i] = 115200;
+			hwconf->MaxCanSetBaudRate[i] = 115200;	// add by Victor Yu. 09-04-2002
+		}
+	}
+	scratch2 = inb(cap + UART_LCR) & (~UART_LCR_DLAB);
+	outb(scratch2 | UART_LCR_DLAB, cap + UART_LCR);
+	outb(0, cap + UART_EFR);	/* EFR is the same as FCR */
+	outb(scratch2, cap + UART_LCR);
+	outb(UART_FCR_ENABLE_FIFO, cap + UART_FCR);
+	scratch = inb(cap + UART_IIR);
+
+	if (scratch & 0xC0)
+		hwconf->uart_type = PORT_16550A;
+	else
+		hwconf->uart_type = PORT_16450;
+	if (id == 1)
+		hwconf->ports = 8;
+	else
+		hwconf->ports = 4;
+	request_region(hwconf->ioaddr[0], 8 * hwconf->ports, "mxser(IO)");
+	request_region(hwconf->vector, 1, "mxser(vector)");
+	return (hwconf->ports);
+}
+
+#define CHIP_SK 	0x01	/* Serial Data Clock  in Eprom */
+#define CHIP_DO 	0x02	/* Serial Data Output in Eprom */
+#define CHIP_CS 	0x04	/* Serial Chip Select in Eprom */
+#define CHIP_DI 	0x08	/* Serial Data Input  in Eprom */
+#define EN_CCMD 	0x000	/* Chip's command register     */
+#define EN0_RSARLO	0x008	/* Remote start address reg 0  */
+#define EN0_RSARHI	0x009	/* Remote start address reg 1  */
+#define EN0_RCNTLO	0x00A	/* Remote byte count reg WR    */
+#define EN0_RCNTHI	0x00B	/* Remote byte count reg WR    */
+#define EN0_DCFG	0x00E	/* Data configuration reg WR   */
+#define EN0_PORT	0x010	/* Rcv missed frame error counter RD */
+#define ENC_PAGE0	0x000	/* Select page 0 of chip registers   */
+#define ENC_PAGE3	0x0C0	/* Select page 3 of chip registers   */
+static int mxser_read_register(int port, unsigned short *regs)
+{
+	int i, k, value, id;
+	unsigned int j;
+
+	id = mxser_program_mode(port);
+	if (id < 0)
+		return (id);
+	for (i = 0; i < 14; i++) {
+		k = (i & 0x3F) | 0x180;
+		for (j = 0x100; j > 0; j >>= 1) {
+			outb(CHIP_CS, port);
+			if (k & j) {
+				outb(CHIP_CS | CHIP_DO, port);
+				outb(CHIP_CS | CHIP_DO | CHIP_SK, port);	/* A? bit of read */
+			} else {
+				outb(CHIP_CS, port);
+				outb(CHIP_CS | CHIP_SK, port);	/* A? bit of read */
+			}
+		}
+		(void) inb(port);
+		value = 0;
+		for (k = 0, j = 0x8000; k < 16; k++, j >>= 1) {
+			outb(CHIP_CS, port);
+			outb(CHIP_CS | CHIP_SK, port);
+			if (inb(port) & CHIP_DI)
+				value |= j;
+		}
+		regs[i] = value;
+		outb(0, port);
+	}
+	mxser_normal_mode(port);
+	return (id);
+}
+
+static int mxser_program_mode(int port)
+{
+	int id, i, j, n;
+	//unsigned long flags;
+
+	spin_lock(&gm_lock);
+	outb(0, port);
+	outb(0, port);
+	outb(0, port);
+	(void) inb(port);
+	(void) inb(port);
+	outb(0, port);
+	(void) inb(port);
+	//restore_flags(flags);
+	spin_unlock(&gm_lock);
+
+	id = inb(port + 1) & 0x1F;
+	if ((id != C168_ASIC_ID) && (id != C104_ASIC_ID) && (id != C102_ASIC_ID) && (id != CI132_ASIC_ID) && (id != CI134_ASIC_ID) && (id != CI104J_ASIC_ID))
+		return (-1);
+	for (i = 0, j = 0; i < 4; i++) {
+		n = inb(port + 2);
+		if (n == 'M') {
+			j = 1;
+		} else if ((j == 1) && (n == 1)) {
+			j = 2;
+			break;
+		} else
+			j = 0;
+	}
+	if (j != 2)
+		id = -2;
+	return (id);
+}
+
+static void mxser_normal_mode(int port)
+{
+	int i, n;
+
+	outb(0xA5, port + 1);
+	outb(0x80, port + 3);
+	outb(12, port + 0);	/* 9600 bps */
+	outb(0, port + 1);
+	outb(0x03, port + 3);	/* 8 data bits */
+	outb(0x13, port + 4);	/* loop back mode */
+	for (i = 0; i < 16; i++) {
+		n = inb(port + 5);
+		if ((n & 0x61) == 0x60)
+			break;
+		if ((n & 1) == 1)
+			(void) inb(port);
+	}
+	outb(0x00, port + 4);
+}
+
+module_init(mxser_module_init);
+module_exit(mxser_module_exit);
diff --git a/drivers/char/mxser.h b/drivers/char/mxser.h
new file mode 100644
index 0000000..e7fd0b0
--- /dev/null
+++ b/drivers/char/mxser.h
@@ -0,0 +1,450 @@
+#ifndef _MXSER_H
+#define _MXSER_H
+
+/*
+ *	Semi-public control interfaces
+ */
+ 
+/*
+ *	MOXA ioctls
+ */
+
+#define MOXA			0x400
+#define MOXA_GETDATACOUNT	(MOXA + 23)
+#define	MOXA_GET_CONF		(MOXA + 35)
+#define MOXA_DIAGNOSE		(MOXA + 50)
+#define MOXA_CHKPORTENABLE	(MOXA + 60)
+#define MOXA_HighSpeedOn	(MOXA + 61)
+#define MOXA_GET_MAJOR		(MOXA + 63)
+#define MOXA_GET_CUMAJOR	(MOXA + 64)
+#define MOXA_GETMSTATUS		(MOXA + 65)
+#define MOXA_SET_OP_MODE	(MOXA + 66)
+#define MOXA_GET_OP_MODE	(MOXA + 67)
+
+#define RS232_MODE		0
+#define RS485_2WIRE_MODE	1
+#define RS422_MODE		2
+#define RS485_4WIRE_MODE	3
+#define OP_MODE_MASK		3
+// above add by Victor Yu. 01-05-2004
+
+#define TTY_THRESHOLD_THROTTLE  128
+
+#define LO_WATER	 	(TTY_FLIPBUF_SIZE)
+#define HI_WATER		(TTY_FLIPBUF_SIZE*2*3/4)
+
+// added by James. 03-11-2004.
+#define MOXA_SDS_GETICOUNTER  	(MOXA + 68)
+#define MOXA_SDS_RSTICOUNTER  	(MOXA + 69)
+// (above) added by James.
+
+#define MOXA_ASPP_OQUEUE  	(MOXA + 70)
+#define MOXA_ASPP_SETBAUD 	(MOXA + 71)
+#define MOXA_ASPP_GETBAUD 	(MOXA + 72)
+#define MOXA_ASPP_MON     	(MOXA + 73)
+#define MOXA_ASPP_LSTATUS 	(MOXA + 74)
+#define MOXA_ASPP_MON_EXT 	(MOXA + 75)
+#define MOXA_SET_BAUD_METHOD	(MOXA + 76)
+
+
+/* --------------------------------------------------- */
+
+#define NPPI_NOTIFY_PARITY	0x01
+#define NPPI_NOTIFY_FRAMING	0x02
+#define NPPI_NOTIFY_HW_OVERRUN	0x04
+#define NPPI_NOTIFY_SW_OVERRUN	0x08
+#define NPPI_NOTIFY_BREAK	0x10
+
+#define NPPI_NOTIFY_CTSHOLD         0x01	// Tx hold by CTS low
+#define NPPI_NOTIFY_DSRHOLD         0x02	// Tx hold by DSR low
+#define NPPI_NOTIFY_XOFFHOLD        0x08	// Tx hold by Xoff received
+#define NPPI_NOTIFY_XOFFXENT        0x10	// Xoff Sent
+
+//CheckIsMoxaMust return value
+#define MOXA_OTHER_UART			0x00
+#define MOXA_MUST_MU150_HWID		0x01
+#define MOXA_MUST_MU860_HWID		0x02
+
+// follow just for Moxa Must chip define.
+//
+// when LCR register (offset 0x03) write following value,
+// the Must chip will enter enchance mode. And write value
+// on EFR (offset 0x02) bit 6,7 to change bank.
+#define MOXA_MUST_ENTER_ENCHANCE	0xBF
+
+// when enhance mode enable, access on general bank register
+#define MOXA_MUST_GDL_REGISTER		0x07
+#define MOXA_MUST_GDL_MASK		0x7F
+#define MOXA_MUST_GDL_HAS_BAD_DATA	0x80
+
+#define MOXA_MUST_LSR_RERR		0x80	// error in receive FIFO
+// enchance register bank select and enchance mode setting register
+// when LCR register equal to 0xBF
+#define MOXA_MUST_EFR_REGISTER		0x02
+// enchance mode enable
+#define MOXA_MUST_EFR_EFRB_ENABLE	0x10
+// enchance reister bank set 0, 1, 2
+#define MOXA_MUST_EFR_BANK0		0x00
+#define MOXA_MUST_EFR_BANK1		0x40
+#define MOXA_MUST_EFR_BANK2		0x80
+#define MOXA_MUST_EFR_BANK3		0xC0
+#define MOXA_MUST_EFR_BANK_MASK		0xC0
+
+// set XON1 value register, when LCR=0xBF and change to bank0
+#define MOXA_MUST_XON1_REGISTER		0x04
+
+// set XON2 value register, when LCR=0xBF and change to bank0
+#define MOXA_MUST_XON2_REGISTER		0x05
+
+// set XOFF1 value register, when LCR=0xBF and change to bank0
+#define MOXA_MUST_XOFF1_REGISTER	0x06
+
+// set XOFF2 value register, when LCR=0xBF and change to bank0
+#define MOXA_MUST_XOFF2_REGISTER	0x07
+
+#define MOXA_MUST_RBRTL_REGISTER	0x04
+#define MOXA_MUST_RBRTH_REGISTER	0x05
+#define MOXA_MUST_RBRTI_REGISTER	0x06
+#define MOXA_MUST_THRTL_REGISTER	0x07
+#define MOXA_MUST_ENUM_REGISTER		0x04
+#define MOXA_MUST_HWID_REGISTER		0x05
+#define MOXA_MUST_ECR_REGISTER		0x06
+#define MOXA_MUST_CSR_REGISTER		0x07
+
+// good data mode enable
+#define MOXA_MUST_FCR_GDA_MODE_ENABLE	0x20
+// only good data put into RxFIFO
+#define MOXA_MUST_FCR_GDA_ONLY_ENABLE	0x10
+
+// enable CTS interrupt
+#define MOXA_MUST_IER_ECTSI		0x80
+// eanble RTS interrupt
+#define MOXA_MUST_IER_ERTSI		0x40
+// enable Xon/Xoff interrupt
+#define MOXA_MUST_IER_XINT		0x20
+// enable GDA interrupt
+#define MOXA_MUST_IER_EGDAI		0x10
+
+#define MOXA_MUST_RECV_ISR		(UART_IER_RDI | MOXA_MUST_IER_EGDAI)
+
+// GDA interrupt pending
+#define MOXA_MUST_IIR_GDA		0x1C
+#define MOXA_MUST_IIR_RDA		0x04
+#define MOXA_MUST_IIR_RTO		0x0C
+#define MOXA_MUST_IIR_LSR		0x06
+
+// recieved Xon/Xoff or specical interrupt pending
+#define MOXA_MUST_IIR_XSC		0x10
+
+// RTS/CTS change state interrupt pending
+#define MOXA_MUST_IIR_RTSCTS		0x20
+#define MOXA_MUST_IIR_MASK		0x3E
+
+#define MOXA_MUST_MCR_XON_FLAG		0x40
+#define MOXA_MUST_MCR_XON_ANY		0x80
+#define MOXA_MUST_MCR_TX_XON		0x08
+
+
+// software flow control on chip mask value
+#define MOXA_MUST_EFR_SF_MASK		0x0F
+// send Xon1/Xoff1
+#define MOXA_MUST_EFR_SF_TX1		0x08
+// send Xon2/Xoff2
+#define MOXA_MUST_EFR_SF_TX2		0x04
+// send Xon1,Xon2/Xoff1,Xoff2
+#define MOXA_MUST_EFR_SF_TX12		0x0C
+// don't send Xon/Xoff
+#define MOXA_MUST_EFR_SF_TX_NO		0x00
+// Tx software flow control mask
+#define MOXA_MUST_EFR_SF_TX_MASK	0x0C
+// don't receive Xon/Xoff
+#define MOXA_MUST_EFR_SF_RX_NO		0x00
+// receive Xon1/Xoff1
+#define MOXA_MUST_EFR_SF_RX1		0x02
+// receive Xon2/Xoff2
+#define MOXA_MUST_EFR_SF_RX2		0x01
+// receive Xon1,Xon2/Xoff1,Xoff2
+#define MOXA_MUST_EFR_SF_RX12		0x03
+// Rx software flow control mask
+#define MOXA_MUST_EFR_SF_RX_MASK	0x03
+
+//#define MOXA_MUST_MIN_XOFFLIMIT               66
+//#define MOXA_MUST_MIN_XONLIMIT                20
+//#define ID1_RX_TRIG                   120
+
+
+#define CHECK_MOXA_MUST_XOFFLIMIT(info) { 	\
+	if ( (info)->IsMoxaMustChipFlag && 	\
+	 (info)->HandFlow.XoffLimit < MOXA_MUST_MIN_XOFFLIMIT ) {	\
+		(info)->HandFlow.XoffLimit = MOXA_MUST_MIN_XOFFLIMIT;	\
+		(info)->HandFlow.XonLimit = MOXA_MUST_MIN_XONLIMIT;	\
+	}	\
+}
+
+#define ENABLE_MOXA_MUST_ENCHANCE_MODE(baseio) { \
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr |= MOXA_MUST_EFR_EFRB_ENABLE;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define DISABLE_MOXA_MUST_ENCHANCE_MODE(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_EFRB_ENABLE;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_XON1_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK0;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_XON1_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_XON2_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK0;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_XON2_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_XOFF1_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK0;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_XOFF1_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_XOFF2_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK0;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_XOFF2_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_RBRTL_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK1;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_RBRTL_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_RBRTH_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK1;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_RBRTH_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_RBRTI_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK1;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_RBRTI_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_THRTL_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK1;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_THRTL_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+//#define MOXA_MUST_RBRL_VALUE  4
+#define SET_MOXA_MUST_FIFO_VALUE(info) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((info)->base+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (info)->base+UART_LCR);	\
+	__efr = inb((info)->base+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK1;	\
+	outb(__efr, (info)->base+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)((info)->rx_high_water), (info)->base+MOXA_MUST_RBRTH_REGISTER);	\
+	outb((u8)((info)->rx_trigger), (info)->base+MOXA_MUST_RBRTI_REGISTER);	\
+	outb((u8)((info)->rx_low_water), (info)->base+MOXA_MUST_RBRTL_REGISTER);	\
+	outb(__oldlcr, (info)->base+UART_LCR);	\
+}
+
+
+
+#define SET_MOXA_MUST_ENUM_VALUE(baseio, Value) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK2;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb((u8)(Value), (baseio)+MOXA_MUST_ENUM_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define GET_MOXA_MUST_HARDWARE_ID(baseio, pId) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_BANK_MASK;	\
+	__efr |= MOXA_MUST_EFR_BANK2;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	*pId = inb((baseio)+MOXA_MUST_HWID_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_NO_SOFTWARE_FLOW_CONTROL(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_SF_MASK;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_JUST_TX_SOFTWARE_FLOW_CONTROL(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_SF_MASK;	\
+	__efr |= MOXA_MUST_EFR_SF_TX1;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define ENABLE_MOXA_MUST_TX_SOFTWARE_FLOW_CONTROL(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_SF_TX_MASK;	\
+	__efr |= MOXA_MUST_EFR_SF_TX1;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define DISABLE_MOXA_MUST_TX_SOFTWARE_FLOW_CONTROL(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_SF_TX_MASK;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define SET_MOXA_MUST_JUST_RX_SOFTWARE_FLOW_CONTROL(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_SF_MASK;	\
+	__efr |= MOXA_MUST_EFR_SF_RX1;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define ENABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_SF_RX_MASK;	\
+	__efr |= MOXA_MUST_EFR_SF_RX1;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define DISABLE_MOXA_MUST_RX_SOFTWARE_FLOW_CONTROL(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_SF_RX_MASK;	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define ENABLE_MOXA_MUST_TX_RX_SOFTWARE_FLOW_CONTROL(baseio) {	\
+	u8	__oldlcr, __efr;	\
+	__oldlcr = inb((baseio)+UART_LCR);	\
+	outb(MOXA_MUST_ENTER_ENCHANCE, (baseio)+UART_LCR);	\
+	__efr = inb((baseio)+MOXA_MUST_EFR_REGISTER);	\
+	__efr &= ~MOXA_MUST_EFR_SF_MASK;	\
+	__efr |= (MOXA_MUST_EFR_SF_RX1|MOXA_MUST_EFR_SF_TX1);	\
+	outb(__efr, (baseio)+MOXA_MUST_EFR_REGISTER);	\
+	outb(__oldlcr, (baseio)+UART_LCR);	\
+}
+
+#define ENABLE_MOXA_MUST_XON_ANY_FLOW_CONTROL(baseio) {	\
+	u8	__oldmcr;	\
+	__oldmcr = inb((baseio)+UART_MCR);	\
+	__oldmcr |= MOXA_MUST_MCR_XON_ANY;	\
+	outb(__oldmcr, (baseio)+UART_MCR);	\
+}
+
+#define DISABLE_MOXA_MUST_XON_ANY_FLOW_CONTROL(baseio) {	\
+	u8	__oldmcr;	\
+	__oldmcr = inb((baseio)+UART_MCR);	\
+	__oldmcr &= ~MOXA_MUST_MCR_XON_ANY;	\
+	outb(__oldmcr, (baseio)+UART_MCR);	\
+}
+
+#define READ_MOXA_MUST_GDL(baseio)	inb((baseio)+MOXA_MUST_GDL_REGISTER)
+
+
+#ifndef INIT_WORK
+#define INIT_WORK(_work, _func, _data){	\
+	_data->tqueue.routine = _func;\
+	_data->tqueue.data = _data;\
+	}
+#endif
+
+#endif
diff --git a/drivers/char/n_hdlc.c b/drivers/char/n_hdlc.c
new file mode 100644
index 0000000..b3dbff1
--- /dev/null
+++ b/drivers/char/n_hdlc.c
@@ -0,0 +1,978 @@
+/* generic HDLC line discipline for Linux
+ *
+ * Written by Paul Fulghum paulkf@microgate.com
+ * for Microgate Corporation
+ *
+ * Microgate and SyncLink are registered trademarks of Microgate Corporation
+ *
+ * Adapted from ppp.c, written by Michael Callahan <callahan@maths.ox.ac.uk>,
+ *	Al Longyear <longyear@netcom.com>,
+ *	Paul Mackerras <Paul.Mackerras@cs.anu.edu.au>
+ *
+ * Original release 01/11/99
+ * $Id: n_hdlc.c,v 4.8 2003/05/06 21:18:51 paulkf Exp $
+ *
+ * This code is released under the GNU General Public License (GPL)
+ *
+ * This module implements the tty line discipline N_HDLC for use with
+ * tty device drivers that support bit-synchronous HDLC communications.
+ *
+ * All HDLC data is frame oriented which means:
+ *
+ * 1. tty write calls represent one complete transmit frame of data
+ *    The device driver should accept the complete frame or none of 
+ *    the frame (busy) in the write method. Each write call should have
+ *    a byte count in the range of 2-65535 bytes (2 is min HDLC frame
+ *    with 1 addr byte and 1 ctrl byte). The max byte count of 65535
+ *    should include any crc bytes required. For example, when using
+ *    CCITT CRC32, 4 crc bytes are required, so the maximum size frame
+ *    the application may transmit is limited to 65531 bytes. For CCITT
+ *    CRC16, the maximum application frame size would be 65533.
+ *
+ *
+ * 2. receive callbacks from the device driver represents
+ *    one received frame. The device driver should bypass
+ *    the tty flip buffer and call the line discipline receive
+ *    callback directly to avoid fragmenting or concatenating
+ *    multiple frames into a single receive callback.
+ *
+ *    The HDLC line discipline queues the receive frames in separate
+ *    buffers so complete receive frames can be returned by the
+ *    tty read calls.
+ *
+ * 3. tty read calls returns an entire frame of data or nothing.
+ *    
+ * 4. all send and receive data is considered raw. No processing
+ *    or translation is performed by the line discipline, regardless
+ *    of the tty flags
+ *
+ * 5. When line discipline is queried for the amount of receive
+ *    data available (FIOC), 0 is returned if no data available,
+ *    otherwise the count of the next available frame is returned.
+ *    (instead of the sum of all received frame counts).
+ *
+ * These conventions allow the standard tty programming interface
+ * to be used for synchronous HDLC applications when used with
+ * this line discipline (or another line discipline that is frame
+ * oriented such as N_PPP).
+ *
+ * The SyncLink driver (synclink.c) implements both asynchronous
+ * (using standard line discipline N_TTY) and synchronous HDLC
+ * (using N_HDLC) communications, with the latter using the above
+ * conventions.
+ *
+ * This implementation is very basic and does not maintain
+ * any statistics. The main point is to enforce the raw data
+ * and frame orientation of HDLC communications.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define HDLC_MAGIC 0x239e
+#define HDLC_VERSION "$Revision: 4.8 $"
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+
+#undef VERSION
+#define VERSION(major,minor,patch) (((((major)<<8)+(minor))<<8)+(patch))
+
+#include <linux/poll.h>
+#include <linux/in.h>
+#include <linux/ioctl.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/string.h>	/* used in new tty drivers */
+#include <linux/signal.h>	/* used in new tty drivers */
+#include <linux/if.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/termios.h>
+#include <asm/uaccess.h>
+
+/*
+ * Buffers for individual HDLC frames
+ */
+#define MAX_HDLC_FRAME_SIZE 65535 
+#define DEFAULT_RX_BUF_COUNT 10
+#define MAX_RX_BUF_COUNT 60
+#define DEFAULT_TX_BUF_COUNT 1
+
+struct n_hdlc_buf {
+	struct n_hdlc_buf *link;
+	int		  count;
+	char		  buf[1];
+};
+
+#define	N_HDLC_BUF_SIZE	(sizeof(struct n_hdlc_buf) + maxframe)
+
+struct n_hdlc_buf_list {
+	struct n_hdlc_buf *head;
+	struct n_hdlc_buf *tail;
+	int		  count;
+	spinlock_t	  spinlock;
+};
+
+/**
+ * struct n_hdlc - per device instance data structure
+ * @magic - magic value for structure
+ * @flags - miscellaneous control flags
+ * @tty - ptr to TTY structure
+ * @backup_tty - TTY to use if tty gets closed
+ * @tbusy - reentrancy flag for tx wakeup code
+ * @woke_up - FIXME: describe this field
+ * @tbuf - currently transmitting tx buffer
+ * @tx_buf_list - list of pending transmit frame buffers
+ * @rx_buf_list - list of received frame buffers
+ * @tx_free_buf_list - list unused transmit frame buffers
+ * @rx_free_buf_list - list unused received frame buffers
+ */
+struct n_hdlc {
+	int			magic;
+	__u32			flags;
+	struct tty_struct	*tty;
+	struct tty_struct	*backup_tty;
+	int			tbusy;
+	int			woke_up;
+	struct n_hdlc_buf	*tbuf;
+	struct n_hdlc_buf_list	tx_buf_list;
+	struct n_hdlc_buf_list	rx_buf_list;
+	struct n_hdlc_buf_list	tx_free_buf_list;
+	struct n_hdlc_buf_list	rx_free_buf_list;
+};
+
+/*
+ * HDLC buffer list manipulation functions
+ */
+static void n_hdlc_buf_list_init(struct n_hdlc_buf_list *list);
+static void n_hdlc_buf_put(struct n_hdlc_buf_list *list,
+			   struct n_hdlc_buf *buf);
+static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *list);
+
+/* Local functions */
+
+static struct n_hdlc *n_hdlc_alloc (void);
+
+/* debug level can be set by insmod for debugging purposes */
+#define DEBUG_LEVEL_INFO	1
+static int debuglevel;
+
+/* max frame size for memory allocations */
+static int maxframe = 4096;
+
+/* TTY callbacks */
+
+static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,
+			   __u8 __user *buf, size_t nr);
+static ssize_t n_hdlc_tty_write(struct tty_struct *tty, struct file *file,
+			    const unsigned char *buf, size_t nr);
+static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file,
+			    unsigned int cmd, unsigned long arg);
+static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,
+				    poll_table *wait);
+static int n_hdlc_tty_open(struct tty_struct *tty);
+static void n_hdlc_tty_close(struct tty_struct *tty);
+static int n_hdlc_tty_room(struct tty_struct *tty);
+static void n_hdlc_tty_receive(struct tty_struct *tty, const __u8 *cp,
+			       char *fp, int count);
+static void n_hdlc_tty_wakeup(struct tty_struct *tty);
+
+#define bset(p,b)	((p)[(b) >> 5] |= (1 << ((b) & 0x1f)))
+
+#define tty2n_hdlc(tty)	((struct n_hdlc *) ((tty)->disc_data))
+#define n_hdlc2tty(n_hdlc)	((n_hdlc)->tty)
+
+static struct tty_ldisc n_hdlc_ldisc = {
+	.owner		= THIS_MODULE,
+	.magic		= TTY_LDISC_MAGIC,
+	.name		= "hdlc",
+	.open		= n_hdlc_tty_open,
+	.close		= n_hdlc_tty_close,
+	.read		= n_hdlc_tty_read,
+	.write		= n_hdlc_tty_write,
+	.ioctl		= n_hdlc_tty_ioctl,
+	.poll		= n_hdlc_tty_poll,
+	.receive_buf	= n_hdlc_tty_receive,
+	.receive_room	= n_hdlc_tty_room,
+	.write_wakeup	= n_hdlc_tty_wakeup,
+};
+
+/**
+ * n_hdlc_release - release an n_hdlc per device line discipline info structure
+ * @n_hdlc - per device line discipline info structure
+ */
+static void n_hdlc_release(struct n_hdlc *n_hdlc)
+{
+	struct tty_struct *tty = n_hdlc2tty (n_hdlc);
+	struct n_hdlc_buf *buf;
+	
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_release() called\n",__FILE__,__LINE__);
+		
+	/* Ensure that the n_hdlcd process is not hanging on select()/poll() */
+	wake_up_interruptible (&tty->read_wait);
+	wake_up_interruptible (&tty->write_wait);
+
+	if (tty != NULL && tty->disc_data == n_hdlc)
+		tty->disc_data = NULL;	/* Break the tty->n_hdlc link */
+
+	/* Release transmit and receive buffers */
+	for(;;) {
+		buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list);
+		if (buf) {
+			kfree(buf);
+		} else
+			break;
+	}
+	for(;;) {
+		buf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list);
+		if (buf) {
+			kfree(buf);
+		} else
+			break;
+	}
+	for(;;) {
+		buf = n_hdlc_buf_get(&n_hdlc->rx_buf_list);
+		if (buf) {
+			kfree(buf);
+		} else
+			break;
+	}
+	for(;;) {
+		buf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
+		if (buf) {
+			kfree(buf);
+		} else
+			break;
+	}
+	if (n_hdlc->tbuf)
+		kfree(n_hdlc->tbuf);
+	kfree(n_hdlc);
+	
+}	/* end of n_hdlc_release() */
+
+/**
+ * n_hdlc_tty_close - line discipline close
+ * @tty - pointer to tty info structure
+ *
+ * Called when the line discipline is changed to something
+ * else, the tty is closed, or the tty detects a hangup.
+ */
+static void n_hdlc_tty_close(struct tty_struct *tty)
+{
+	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_close() called\n",__FILE__,__LINE__);
+		
+	if (n_hdlc != NULL) {
+		if (n_hdlc->magic != HDLC_MAGIC) {
+			printk (KERN_WARNING"n_hdlc: trying to close unopened tty!\n");
+			return;
+		}
+#if defined(TTY_NO_WRITE_SPLIT)
+		clear_bit(TTY_NO_WRITE_SPLIT,&tty->flags);
+#endif
+		tty->disc_data = NULL;
+		if (tty == n_hdlc->backup_tty)
+			n_hdlc->backup_tty = NULL;
+		if (tty != n_hdlc->tty)
+			return;
+		if (n_hdlc->backup_tty) {
+			n_hdlc->tty = n_hdlc->backup_tty;
+		} else {
+			n_hdlc_release (n_hdlc);
+		}
+	}
+	
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_close() success\n",__FILE__,__LINE__);
+		
+}	/* end of n_hdlc_tty_close() */
+
+/**
+ * n_hdlc_tty_open - called when line discipline changed to n_hdlc
+ * @tty - pointer to tty info structure
+ *
+ * Returns 0 if success, otherwise error code
+ */
+static int n_hdlc_tty_open (struct tty_struct *tty)
+{
+	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_open() called (device=%s)\n",
+		__FILE__,__LINE__,
+		tty->name);
+		
+	/* There should not be an existing table for this slot. */
+	if (n_hdlc) {
+		printk (KERN_ERR"n_hdlc_tty_open:tty already associated!\n" );
+		return -EEXIST;
+	}
+	
+	n_hdlc = n_hdlc_alloc();
+	if (!n_hdlc) {
+		printk (KERN_ERR "n_hdlc_alloc failed\n");
+		return -ENFILE;
+	}
+		
+	tty->disc_data = n_hdlc;
+	n_hdlc->tty    = tty;
+	
+#if defined(TTY_NO_WRITE_SPLIT)
+	/* change tty_io write() to not split large writes into 8K chunks */
+	set_bit(TTY_NO_WRITE_SPLIT,&tty->flags);
+#endif
+	
+	/* Flush any pending characters in the driver and discipline. */
+	
+	if (tty->ldisc.flush_buffer)
+		tty->ldisc.flush_buffer (tty);
+
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer (tty);
+		
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_open() success\n",__FILE__,__LINE__);
+		
+	return 0;
+	
+}	/* end of n_tty_hdlc_open() */
+
+/**
+ * n_hdlc_send_frames - send frames on pending send buffer list
+ * @n_hdlc - pointer to ldisc instance data
+ * @tty - pointer to tty instance data
+ *
+ * Send frames on pending send buffer list until the driver does not accept a
+ * frame (busy) this function is called after adding a frame to the send buffer
+ * list and by the tty wakeup callback.
+ */
+static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty)
+{
+	register int actual;
+	unsigned long flags;
+	struct n_hdlc_buf *tbuf;
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_send_frames() called\n",__FILE__,__LINE__);
+ check_again:
+		
+ 	spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
+	if (n_hdlc->tbusy) {
+		n_hdlc->woke_up = 1;
+ 		spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
+		return;
+	}
+	n_hdlc->tbusy = 1;
+	n_hdlc->woke_up = 0;
+	spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
+
+	/* get current transmit buffer or get new transmit */
+	/* buffer from list of pending transmit buffers */
+		
+	tbuf = n_hdlc->tbuf;
+	if (!tbuf)
+		tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
+		
+	while (tbuf) {
+		if (debuglevel >= DEBUG_LEVEL_INFO)	
+			printk("%s(%d)sending frame %p, count=%d\n",
+				__FILE__,__LINE__,tbuf,tbuf->count);
+			
+		/* Send the next block of data to device */
+		tty->flags |= (1 << TTY_DO_WRITE_WAKEUP);
+		actual = tty->driver->write(tty, tbuf->buf, tbuf->count);
+		    
+		/* if transmit error, throw frame away by */
+		/* pretending it was accepted by driver */
+		if (actual < 0)
+			actual = tbuf->count;
+		
+		if (actual == tbuf->count) {
+			if (debuglevel >= DEBUG_LEVEL_INFO)	
+				printk("%s(%d)frame %p completed\n",
+					__FILE__,__LINE__,tbuf);
+					
+			/* free current transmit buffer */
+			n_hdlc_buf_put(&n_hdlc->tx_free_buf_list, tbuf);
+			
+			/* this tx buffer is done */
+			n_hdlc->tbuf = NULL;
+			
+			/* wait up sleeping writers */
+			wake_up_interruptible(&tty->write_wait);
+	
+			/* get next pending transmit buffer */
+			tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
+		} else {
+			if (debuglevel >= DEBUG_LEVEL_INFO)	
+				printk("%s(%d)frame %p pending\n",
+					__FILE__,__LINE__,tbuf);
+					
+			/* buffer not accepted by driver */
+			/* set this buffer as pending buffer */
+			n_hdlc->tbuf = tbuf;
+			break;
+		}
+	}
+	
+	if (!tbuf)
+		tty->flags  &= ~(1 << TTY_DO_WRITE_WAKEUP);
+	
+	/* Clear the re-entry flag */
+	spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags);
+	n_hdlc->tbusy = 0;
+	spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); 
+	
+        if (n_hdlc->woke_up)
+	  goto check_again;
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_send_frames() exit\n",__FILE__,__LINE__);
+		
+}	/* end of n_hdlc_send_frames() */
+
+/**
+ * n_hdlc_tty_wakeup - Callback for transmit wakeup
+ * @tty	- pointer to associated tty instance data
+ *
+ * Called when low level device driver can accept more send data.
+ */
+static void n_hdlc_tty_wakeup(struct tty_struct *tty)
+{
+	struct n_hdlc *n_hdlc = tty2n_hdlc(tty);
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_wakeup() called\n",__FILE__,__LINE__);
+		
+	if (!n_hdlc)
+		return;
+
+	if (tty != n_hdlc->tty) {
+		tty->flags &= ~(1 << TTY_DO_WRITE_WAKEUP);
+		return;
+	}
+
+	n_hdlc_send_frames (n_hdlc, tty);
+		
+}	/* end of n_hdlc_tty_wakeup() */
+
+/**
+ * n_hdlc_tty_room - Return the amount of space left in the receiver's buffer
+ * @tty	- pointer to associated tty instance data
+ *
+ * Callback function from tty driver. Return the amount of space left in the
+ * receiver's buffer to decide if remote transmitter is to be throttled.
+ */
+static int n_hdlc_tty_room(struct tty_struct *tty)
+{
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_room() called\n",__FILE__,__LINE__);
+	/* always return a larger number to prevent */
+	/* throttling of remote transmitter. */
+	return 65536;
+}	/* end of n_hdlc_tty_root() */
+
+/**
+ * n_hdlc_tty_receive - Called by tty driver when receive data is available
+ * @tty	- pointer to tty instance data
+ * @data - pointer to received data
+ * @flags - pointer to flags for data
+ * @count - count of received data in bytes
+ *
+ * Called by tty low level driver when receive data is available. Data is
+ * interpreted as one HDLC frame.
+ */
+static void n_hdlc_tty_receive(struct tty_struct *tty, const __u8 *data,
+			       char *flags, int count)
+{
+	register struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+	register struct n_hdlc_buf *buf;
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_receive() called count=%d\n",
+			__FILE__,__LINE__, count);
+		
+	/* This can happen if stuff comes in on the backup tty */
+	if (n_hdlc == 0 || tty != n_hdlc->tty)
+		return;
+		
+	/* verify line is using HDLC discipline */
+	if (n_hdlc->magic != HDLC_MAGIC) {
+		printk("%s(%d) line not using HDLC discipline\n",
+			__FILE__,__LINE__);
+		return;
+	}
+	
+	if ( count>maxframe ) {
+		if (debuglevel >= DEBUG_LEVEL_INFO)	
+			printk("%s(%d) rx count>maxframesize, data discarded\n",
+			       __FILE__,__LINE__);
+		return;
+	}
+
+	/* get a free HDLC buffer */	
+	buf = n_hdlc_buf_get(&n_hdlc->rx_free_buf_list);
+	if (!buf) {
+		/* no buffers in free list, attempt to allocate another rx buffer */
+		/* unless the maximum count has been reached */
+		if (n_hdlc->rx_buf_list.count < MAX_RX_BUF_COUNT)
+			buf = kmalloc(N_HDLC_BUF_SIZE, GFP_ATOMIC);
+	}
+	
+	if (!buf) {
+		if (debuglevel >= DEBUG_LEVEL_INFO)	
+			printk("%s(%d) no more rx buffers, data discarded\n",
+			       __FILE__,__LINE__);
+		return;
+	}
+		
+	/* copy received data to HDLC buffer */
+	memcpy(buf->buf,data,count);
+	buf->count=count;
+
+	/* add HDLC buffer to list of received frames */
+	n_hdlc_buf_put(&n_hdlc->rx_buf_list, buf);
+	
+	/* wake up any blocked reads and perform async signalling */
+	wake_up_interruptible (&tty->read_wait);
+	if (n_hdlc->tty->fasync != NULL)
+		kill_fasync (&n_hdlc->tty->fasync, SIGIO, POLL_IN);
+
+}	/* end of n_hdlc_tty_receive() */
+
+/**
+ * n_hdlc_tty_read - Called to retreive one frame of data (if available)
+ * @tty - pointer to tty instance data
+ * @file - pointer to open file object
+ * @buf - pointer to returned data buffer
+ * @nr - size of returned data buffer
+ * 	
+ * Returns the number of bytes returned or error code.
+ */
+static ssize_t n_hdlc_tty_read(struct tty_struct *tty, struct file *file,
+			   __u8 __user *buf, size_t nr)
+{
+	struct n_hdlc *n_hdlc = tty2n_hdlc(tty);
+	int ret;
+	struct n_hdlc_buf *rbuf;
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_read() called\n",__FILE__,__LINE__);
+		
+	/* Validate the pointers */
+	if (!n_hdlc)
+		return -EIO;
+
+	/* verify user access to buffer */
+	if (!access_ok(VERIFY_WRITE, buf, nr)) {
+		printk(KERN_WARNING "%s(%d) n_hdlc_tty_read() can't verify user "
+		"buffer\n", __FILE__, __LINE__);
+		return -EFAULT;
+	}
+
+	for (;;) {
+		if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+			return -EIO;
+
+		n_hdlc = tty2n_hdlc (tty);
+		if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC ||
+			 tty != n_hdlc->tty)
+			return 0;
+
+		rbuf = n_hdlc_buf_get(&n_hdlc->rx_buf_list);
+		if (rbuf)
+			break;
+			
+		/* no data */
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+			
+		interruptible_sleep_on (&tty->read_wait);
+		if (signal_pending(current))
+			return -EINTR;
+	}
+		
+	if (rbuf->count > nr)
+		/* frame too large for caller's buffer (discard frame) */
+		ret = -EOVERFLOW;
+	else {
+		/* Copy the data to the caller's buffer */
+		if (copy_to_user(buf, rbuf->buf, rbuf->count))
+			ret = -EFAULT;
+		else
+			ret = rbuf->count;
+	}
+	
+	/* return HDLC buffer to free list unless the free list */
+	/* count has exceeded the default value, in which case the */
+	/* buffer is freed back to the OS to conserve memory */
+	if (n_hdlc->rx_free_buf_list.count > DEFAULT_RX_BUF_COUNT)
+		kfree(rbuf);
+	else	
+		n_hdlc_buf_put(&n_hdlc->rx_free_buf_list,rbuf);
+	
+	return ret;
+	
+}	/* end of n_hdlc_tty_read() */
+
+/**
+ * n_hdlc_tty_write - write a single frame of data to device
+ * @tty	- pointer to associated tty device instance data
+ * @file - pointer to file object data
+ * @data - pointer to transmit data (one frame)
+ * @count - size of transmit frame in bytes
+ * 		
+ * Returns the number of bytes written (or error code).
+ */
+static ssize_t n_hdlc_tty_write(struct tty_struct *tty, struct file *file,
+			    const unsigned char *data, size_t count)
+{
+	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+	int error = 0;
+	DECLARE_WAITQUEUE(wait, current);
+	struct n_hdlc_buf *tbuf;
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_write() called count=%Zd\n",
+			__FILE__,__LINE__,count);
+		
+	/* Verify pointers */
+	if (!n_hdlc)
+		return -EIO;
+
+	if (n_hdlc->magic != HDLC_MAGIC)
+		return -EIO;
+
+	/* verify frame size */
+	if (count > maxframe ) {
+		if (debuglevel & DEBUG_LEVEL_INFO)
+			printk (KERN_WARNING
+				"n_hdlc_tty_write: truncating user packet "
+				"from %lu to %d\n", (unsigned long) count,
+				maxframe );
+		count = maxframe;
+	}
+	
+	add_wait_queue(&tty->write_wait, &wait);
+	set_current_state(TASK_INTERRUPTIBLE);
+	
+	/* Allocate transmit buffer */
+	/* sleep until transmit buffer available */		
+	while (!(tbuf = n_hdlc_buf_get(&n_hdlc->tx_free_buf_list))) {
+		schedule();
+			
+		n_hdlc = tty2n_hdlc (tty);
+		if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC || 
+		    tty != n_hdlc->tty) {
+			printk("n_hdlc_tty_write: %p invalid after wait!\n", n_hdlc);
+			error = -EIO;
+			break;
+		}
+			
+		if (signal_pending(current)) {
+			error = -EINTR;
+			break;
+		}
+	}
+
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&tty->write_wait, &wait);
+
+	if (!error) {		
+		/* Retrieve the user's buffer */
+		memcpy(tbuf->buf, data, count);
+
+		/* Send the data */
+		tbuf->count = error = count;
+		n_hdlc_buf_put(&n_hdlc->tx_buf_list,tbuf);
+		n_hdlc_send_frames(n_hdlc,tty);
+	}
+
+	return error;
+	
+}	/* end of n_hdlc_tty_write() */
+
+/**
+ * n_hdlc_tty_ioctl - process IOCTL system call for the tty device.
+ * @tty - pointer to tty instance data
+ * @file - pointer to open file object for device
+ * @cmd - IOCTL command code
+ * @arg - argument for IOCTL call (cmd dependent)
+ *
+ * Returns command dependent result.
+ */
+static int n_hdlc_tty_ioctl(struct tty_struct *tty, struct file *file,
+			    unsigned int cmd, unsigned long arg)
+{
+	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+	int error = 0;
+	int count;
+	unsigned long flags;
+	
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_ioctl() called %d\n",
+			__FILE__,__LINE__,cmd);
+		
+	/* Verify the status of the device */
+	if (!n_hdlc || n_hdlc->magic != HDLC_MAGIC)
+		return -EBADF;
+
+	switch (cmd) {
+	case FIONREAD:
+		/* report count of read data available */
+		/* in next available frame (if any) */
+		spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock,flags);
+		if (n_hdlc->rx_buf_list.head)
+			count = n_hdlc->rx_buf_list.head->count;
+		else
+			count = 0;
+		spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock,flags);
+		error = put_user(count, (int __user *)arg);
+		break;
+
+	case TIOCOUTQ:
+		/* get the pending tx byte count in the driver */
+		count = tty->driver->chars_in_buffer ?
+				tty->driver->chars_in_buffer(tty) : 0;
+		/* add size of next output frame in queue */
+		spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock,flags);
+		if (n_hdlc->tx_buf_list.head)
+			count += n_hdlc->tx_buf_list.head->count;
+		spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock,flags);
+		error = put_user(count, (int __user *)arg);
+		break;
+
+	default:
+		error = n_tty_ioctl (tty, file, cmd, arg);
+		break;
+	}
+	return error;
+	
+}	/* end of n_hdlc_tty_ioctl() */
+
+/**
+ * n_hdlc_tty_poll - TTY callback for poll system call
+ * @tty - pointer to tty instance data
+ * @filp - pointer to open file object for device
+ * @poll_table - wait queue for operations
+ * 
+ * Determine which operations (read/write) will not block and return info
+ * to caller.
+ * Returns a bit mask containing info on which ops will not block.
+ */
+static unsigned int n_hdlc_tty_poll(struct tty_struct *tty, struct file *filp,
+				    poll_table *wait)
+{
+	struct n_hdlc *n_hdlc = tty2n_hdlc (tty);
+	unsigned int mask = 0;
+
+	if (debuglevel >= DEBUG_LEVEL_INFO)	
+		printk("%s(%d)n_hdlc_tty_poll() called\n",__FILE__,__LINE__);
+		
+	if (n_hdlc && n_hdlc->magic == HDLC_MAGIC && tty == n_hdlc->tty) {
+		/* queue current process into any wait queue that */
+		/* may awaken in the future (read and write) */
+
+		poll_wait(filp, &tty->read_wait, wait);
+		poll_wait(filp, &tty->write_wait, wait);
+
+		/* set bits for operations that won't block */
+		if(n_hdlc->rx_buf_list.head)
+			mask |= POLLIN | POLLRDNORM;	/* readable */
+		if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+			mask |= POLLHUP;
+		if(tty_hung_up_p(filp))
+			mask |= POLLHUP;
+		if(n_hdlc->tx_free_buf_list.head)
+			mask |= POLLOUT | POLLWRNORM;	/* writable */
+	}
+	return mask;
+}	/* end of n_hdlc_tty_poll() */
+
+/**
+ * n_hdlc_alloc - allocate an n_hdlc instance data structure
+ *
+ * Returns a pointer to newly created structure if success, otherwise %NULL
+ */
+static struct n_hdlc *n_hdlc_alloc(void)
+{
+	struct n_hdlc_buf *buf;
+	int i;
+	struct n_hdlc *n_hdlc = kmalloc(sizeof(*n_hdlc), GFP_KERNEL);
+
+	if (!n_hdlc)
+		return NULL;
+
+	memset(n_hdlc, 0, sizeof(*n_hdlc));
+
+	n_hdlc_buf_list_init(&n_hdlc->rx_free_buf_list);
+	n_hdlc_buf_list_init(&n_hdlc->tx_free_buf_list);
+	n_hdlc_buf_list_init(&n_hdlc->rx_buf_list);
+	n_hdlc_buf_list_init(&n_hdlc->tx_buf_list);
+	
+	/* allocate free rx buffer list */
+	for(i=0;i<DEFAULT_RX_BUF_COUNT;i++) {
+		buf = kmalloc(N_HDLC_BUF_SIZE, GFP_KERNEL);
+		if (buf)
+			n_hdlc_buf_put(&n_hdlc->rx_free_buf_list,buf);
+		else if (debuglevel >= DEBUG_LEVEL_INFO)	
+			printk("%s(%d)n_hdlc_alloc(), kalloc() failed for rx buffer %d\n",__FILE__,__LINE__, i);
+	}
+	
+	/* allocate free tx buffer list */
+	for(i=0;i<DEFAULT_TX_BUF_COUNT;i++) {
+		buf = kmalloc(N_HDLC_BUF_SIZE, GFP_KERNEL);
+		if (buf)
+			n_hdlc_buf_put(&n_hdlc->tx_free_buf_list,buf);
+		else if (debuglevel >= DEBUG_LEVEL_INFO)	
+			printk("%s(%d)n_hdlc_alloc(), kalloc() failed for tx buffer %d\n",__FILE__,__LINE__, i);
+	}
+	
+	/* Initialize the control block */
+	n_hdlc->magic  = HDLC_MAGIC;
+	n_hdlc->flags  = 0;
+	
+	return n_hdlc;
+	
+}	/* end of n_hdlc_alloc() */
+
+/**
+ * n_hdlc_buf_list_init - initialize specified HDLC buffer list
+ * @list - pointer to buffer list
+ */
+static void n_hdlc_buf_list_init(struct n_hdlc_buf_list *list)
+{
+	memset(list, 0, sizeof(*list));
+	spin_lock_init(&list->spinlock);
+}	/* end of n_hdlc_buf_list_init() */
+
+/**
+ * n_hdlc_buf_put - add specified HDLC buffer to tail of specified list
+ * @list - pointer to buffer list
+ * @buf	- pointer to buffer
+ */
+static void n_hdlc_buf_put(struct n_hdlc_buf_list *list,
+			   struct n_hdlc_buf *buf)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&list->spinlock,flags);
+	
+	buf->link=NULL;
+	if(list->tail)
+		list->tail->link = buf;
+	else
+		list->head = buf;
+	list->tail = buf;
+	(list->count)++;
+	
+	spin_unlock_irqrestore(&list->spinlock,flags);
+	
+}	/* end of n_hdlc_buf_put() */
+
+/**
+ * n_hdlc_buf_get - remove and return an HDLC buffer from list
+ * @list - pointer to HDLC buffer list
+ * 
+ * Remove and return an HDLC buffer from the head of the specified HDLC buffer
+ * list.
+ * Returns a pointer to HDLC buffer if available, otherwise %NULL.
+ */
+static struct n_hdlc_buf* n_hdlc_buf_get(struct n_hdlc_buf_list *list)
+{
+	unsigned long flags;
+	struct n_hdlc_buf *buf;
+	spin_lock_irqsave(&list->spinlock,flags);
+	
+	buf = list->head;
+	if (buf) {
+		list->head = buf->link;
+		(list->count)--;
+	}
+	if (!list->head)
+		list->tail = NULL;
+	
+	spin_unlock_irqrestore(&list->spinlock,flags);
+	return buf;
+	
+}	/* end of n_hdlc_buf_get() */
+
+static char hdlc_banner[] __initdata =
+	KERN_INFO "HDLC line discipline: version " HDLC_VERSION
+	", maxframe=%u\n";
+static char hdlc_register_ok[] __initdata =
+	KERN_INFO "N_HDLC line discipline registered.\n";
+static char hdlc_register_fail[] __initdata =
+	KERN_ERR "error registering line discipline: %d\n";
+static char hdlc_init_fail[] __initdata =
+	KERN_INFO "N_HDLC: init failure %d\n";
+
+static int __init n_hdlc_init(void)
+{
+	int status;
+
+	/* range check maxframe arg */
+	if (maxframe < 4096)
+		maxframe = 4096;
+	else if (maxframe > 65535)
+		maxframe = 65535;
+
+	printk(hdlc_banner, maxframe);
+
+	status = tty_register_ldisc(N_HDLC, &n_hdlc_ldisc);
+	if (!status)
+		printk(hdlc_register_ok);
+	else
+		printk(hdlc_register_fail, status);
+
+	if (status)
+		printk(hdlc_init_fail, status);
+	return status;
+	
+}	/* end of init_module() */
+
+static char hdlc_unregister_ok[] __exitdata =
+	KERN_INFO "N_HDLC: line discipline unregistered\n";
+static char hdlc_unregister_fail[] __exitdata =
+	KERN_ERR "N_HDLC: can't unregister line discipline (err = %d)\n";
+
+static void __exit n_hdlc_exit(void)
+{
+	/* Release tty registration of line discipline */
+	int status = tty_register_ldisc(N_HDLC, NULL);
+
+	if (status)
+		printk(hdlc_unregister_fail, status);
+	else
+		printk(hdlc_unregister_ok);
+}
+
+module_init(n_hdlc_init);
+module_exit(n_hdlc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Paul Fulghum paulkf@microgate.com");
+module_param(debuglevel, int, 0);
+module_param(maxframe, int, 0);
+MODULE_ALIAS_LDISC(N_HDLC);
diff --git a/drivers/char/n_r3964.c b/drivers/char/n_r3964.c
new file mode 100644
index 0000000..3883073
--- /dev/null
+++ b/drivers/char/n_r3964.c
@@ -0,0 +1,1416 @@
+/* r3964 linediscipline for linux
+ *
+ * -----------------------------------------------------------
+ * Copyright by 
+ * Philips Automation Projects
+ * Kassel (Germany)
+ * http://www.pap-philips.de
+ * -----------------------------------------------------------
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ *
+ * Author:
+ * L. Haag
+ *
+ * $Log: n_r3964.c,v $
+ * Revision 1.10  2001/03/18 13:02:24  dwmw2
+ * Fix timer usage, use spinlocks properly.
+ *
+ * Revision 1.9  2001/03/18 12:52:14  dwmw2
+ * Merge changes in 2.4.2
+ *
+ * Revision 1.8  2000/03/23 14:14:54  dwmw2
+ * Fix race in sleeping in r3964_read()
+ *
+ * Revision 1.7  1999/28/08 11:41:50  dwmw2
+ * Port to 2.3 kernel
+ *
+ * Revision 1.6  1998/09/30 00:40:40  dwmw2
+ * Fixed compilation on 2.0.x kernels
+ * Updated to newly registered tty-ldisc number 9
+ *
+ * Revision 1.5  1998/09/04 21:57:36  dwmw2
+ * Signal handling bug fixes, port to 2.1.x.
+ *
+ * Revision 1.4  1998/04/02 20:26:59  lhaag
+ * select, blocking, ...
+ *
+ * Revision 1.3  1998/02/12 18:58:43  root
+ * fixed some memory leaks
+ * calculation of checksum characters
+ *
+ * Revision 1.2  1998/02/07 13:03:34  root
+ * ioctl read_telegram
+ *
+ * Revision 1.1  1998/02/06 19:21:03  root
+ * Initial revision
+ *
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/errno.h>
+#include <linux/string.h>   /* used in new tty drivers */
+#include <linux/signal.h>   /* used in new tty drivers */
+#include <linux/ioctl.h>
+#include <linux/n_r3964.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+
+//#define DEBUG_QUEUE
+
+/* Log successful handshake and protocol operations  */
+//#define DEBUG_PROTO_S
+
+/* Log handshake and protocol errors: */
+//#define DEBUG_PROTO_E
+
+/* Log Linediscipline operations (open, close, read, write...): */
+//#define DEBUG_LDISC
+
+/* Log module and memory operations (init, cleanup; kmalloc, kfree): */
+//#define DEBUG_MODUL
+
+/* Macro helpers for debug output: */
+#define TRACE(format, args...) printk("r3964: " format "\n" , ## args);
+
+#ifdef DEBUG_MODUL
+#define TRACE_M(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_M(fmt, arg...) /**/
+#endif
+
+#ifdef DEBUG_PROTO_S
+#define TRACE_PS(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_PS(fmt, arg...) /**/
+#endif
+
+#ifdef DEBUG_PROTO_E
+#define TRACE_PE(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_PE(fmt, arg...) /**/
+#endif
+
+#ifdef DEBUG_LDISC
+#define TRACE_L(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_L(fmt, arg...) /**/
+#endif
+
+#ifdef DEBUG_QUEUE
+#define TRACE_Q(format, args...) printk("r3964: " format "\n" , ## args);
+#else
+#define TRACE_Q(fmt, arg...) /**/
+#endif
+
+static void add_tx_queue(struct r3964_info *, struct r3964_block_header *);
+static void remove_from_tx_queue(struct r3964_info *pInfo, int error_code);
+static void put_char(struct r3964_info *pInfo, unsigned char ch);
+static void trigger_transmit(struct r3964_info *pInfo);
+static void retry_transmit(struct r3964_info *pInfo);
+static void transmit_block(struct r3964_info *pInfo);
+static void receive_char(struct r3964_info *pInfo, const unsigned char c);
+static void receive_error(struct r3964_info *pInfo, const char flag);
+static void on_timeout(unsigned long priv);
+static int enable_signals(struct r3964_info *pInfo, pid_t pid, int arg);
+static int read_telegram(struct r3964_info *pInfo, pid_t pid, unsigned char __user *buf);
+static void add_msg(struct r3964_client_info *pClient, int msg_id, int arg,
+             int error_code, struct r3964_block_header *pBlock);
+static struct r3964_message* remove_msg(struct r3964_info *pInfo, 
+             struct r3964_client_info *pClient);
+static void remove_client_block(struct r3964_info *pInfo, 
+                struct r3964_client_info *pClient);
+
+static int  r3964_open(struct tty_struct *tty);
+static void r3964_close(struct tty_struct *tty);
+static ssize_t r3964_read(struct tty_struct *tty, struct file *file,
+                     unsigned char __user *buf, size_t nr);
+static ssize_t r3964_write(struct tty_struct * tty, struct file * file,
+                      const unsigned char * buf, size_t nr);
+static int r3964_ioctl(struct tty_struct * tty, struct file * file,
+                       unsigned int cmd, unsigned long arg);
+static void r3964_set_termios(struct tty_struct *tty, struct termios * old);
+static unsigned int r3964_poll(struct tty_struct * tty, struct file * file,
+		      struct poll_table_struct  *wait);
+static void r3964_receive_buf(struct tty_struct *tty, const unsigned char *cp,
+                              char *fp, int count);
+static int  r3964_receive_room(struct tty_struct *tty);
+
+static struct tty_ldisc tty_ldisc_N_R3964 = {
+	.owner	 = THIS_MODULE,
+	.magic	= TTY_LDISC_MAGIC, 
+	.name	= "R3964",
+	.open	= r3964_open,
+	.close	= r3964_close,
+	.read	= r3964_read,
+	.write	= r3964_write,
+	.ioctl	= r3964_ioctl,
+	.set_termios = r3964_set_termios,
+	.poll	= r3964_poll,            
+	.receive_buf = r3964_receive_buf,
+	.receive_room = r3964_receive_room,
+};
+
+
+
+static void dump_block(const unsigned char *block, unsigned int length)
+{
+   unsigned int i,j;
+   char linebuf[16*3+1];
+   
+   for(i=0;i<length;i+=16)
+   {
+      for(j=0;(j<16) && (j+i<length);j++)
+      {
+         sprintf(linebuf+3*j,"%02x ",block[i+j]);
+      }
+      linebuf[3*j]='\0';
+      TRACE_PS("%s",linebuf);
+   }
+}
+
+         
+
+
+/*************************************************************
+ * Driver initialisation
+ *************************************************************/
+
+
+/*************************************************************
+ * Module support routines
+ *************************************************************/
+
+static void __exit r3964_exit(void)
+{
+   int status;
+   
+   TRACE_M ("cleanup_module()");
+
+   status=tty_register_ldisc(N_R3964, NULL);
+   
+   if(status!=0)
+   {
+      printk(KERN_ERR "r3964: error unregistering linediscipline: %d\n", status);
+   }
+   else
+   {
+      TRACE_L("linediscipline successfully unregistered");
+   }
+   
+}
+
+static int __init r3964_init(void)
+{
+   int status;
+   
+   printk ("r3964: Philips r3964 Driver $Revision: 1.10 $\n");
+
+   /*
+    * Register the tty line discipline
+    */
+   
+   status = tty_register_ldisc (N_R3964, &tty_ldisc_N_R3964);
+   if (status == 0)
+     {
+       TRACE_L("line discipline %d registered", N_R3964);
+       TRACE_L("flags=%x num=%x", tty_ldisc_N_R3964.flags, 
+               tty_ldisc_N_R3964.num);
+       TRACE_L("open=%x", (int)tty_ldisc_N_R3964.open);
+       TRACE_L("tty_ldisc_N_R3964 = %x", (int)&tty_ldisc_N_R3964);
+     }
+   else
+     {
+       printk (KERN_ERR "r3964: error registering line discipline: %d\n", status);
+     }
+   return status;
+}
+
+module_init(r3964_init);
+module_exit(r3964_exit);
+
+
+/*************************************************************
+ * Protocol implementation routines
+ *************************************************************/
+
+static void add_tx_queue(struct r3964_info *pInfo, struct r3964_block_header *pHeader)
+{
+   unsigned long flags;
+   
+   spin_lock_irqsave(&pInfo->lock, flags);
+
+   pHeader->next = NULL;
+
+   if(pInfo->tx_last == NULL)
+   {
+      pInfo->tx_first = pInfo->tx_last = pHeader;
+   }
+   else
+   {
+      pInfo->tx_last->next = pHeader;
+      pInfo->tx_last = pHeader;
+   }
+   
+   spin_unlock_irqrestore(&pInfo->lock, flags);
+
+   TRACE_Q("add_tx_queue %x, length %d, tx_first = %x", 
+          (int)pHeader, pHeader->length, (int)pInfo->tx_first );
+}
+
+static void remove_from_tx_queue(struct r3964_info *pInfo, int error_code)
+{
+   struct r3964_block_header *pHeader;
+   unsigned long flags;
+#ifdef DEBUG_QUEUE
+   struct r3964_block_header *pDump;
+#endif
+   
+   pHeader = pInfo->tx_first;
+
+   if(pHeader==NULL)
+      return;
+
+#ifdef DEBUG_QUEUE
+   printk("r3964: remove_from_tx_queue: %x, length %d - ",
+          (int)pHeader, (int)pHeader->length );
+   for(pDump=pHeader;pDump;pDump=pDump->next)
+	 printk("%x ", (int)pDump);
+   printk("\n");
+#endif
+
+
+   if(pHeader->owner)
+   {
+      if(error_code)
+      {
+          add_msg(pHeader->owner, R3964_MSG_ACK, 0, 
+                  error_code, NULL);
+      }
+      else
+      {
+          add_msg(pHeader->owner, R3964_MSG_ACK, pHeader->length, 
+                  error_code, NULL);
+      }
+      wake_up_interruptible (&pInfo->read_wait);
+   }
+
+   spin_lock_irqsave(&pInfo->lock, flags);
+
+   pInfo->tx_first = pHeader->next;
+   if(pInfo->tx_first==NULL)
+   {
+      pInfo->tx_last = NULL;
+   }
+
+   spin_unlock_irqrestore(&pInfo->lock, flags);
+
+   kfree(pHeader);
+   TRACE_M("remove_from_tx_queue - kfree %x",(int)pHeader);
+
+   TRACE_Q("remove_from_tx_queue: tx_first = %x, tx_last = %x",
+          (int)pInfo->tx_first, (int)pInfo->tx_last );
+}
+
+static void add_rx_queue(struct r3964_info *pInfo, struct r3964_block_header *pHeader)
+{
+   unsigned long flags;
+   
+   spin_lock_irqsave(&pInfo->lock, flags);
+
+   pHeader->next = NULL;
+
+   if(pInfo->rx_last == NULL)
+   {
+      pInfo->rx_first = pInfo->rx_last = pHeader;
+   }
+   else
+   {
+      pInfo->rx_last->next = pHeader;
+      pInfo->rx_last = pHeader;
+   }
+   pInfo->blocks_in_rx_queue++;
+   
+   spin_unlock_irqrestore(&pInfo->lock, flags);
+
+   TRACE_Q("add_rx_queue: %x, length = %d, rx_first = %x, count = %d",
+          (int)pHeader, pHeader->length,
+          (int)pInfo->rx_first, pInfo->blocks_in_rx_queue);
+}
+
+static void remove_from_rx_queue(struct r3964_info *pInfo,
+                 struct r3964_block_header *pHeader)
+{
+   unsigned long flags;
+   struct r3964_block_header *pFind;
+   
+   if(pHeader==NULL)
+      return;
+
+   TRACE_Q("remove_from_rx_queue: rx_first = %x, rx_last = %x, count = %d",
+          (int)pInfo->rx_first, (int)pInfo->rx_last, pInfo->blocks_in_rx_queue );
+   TRACE_Q("remove_from_rx_queue: %x, length %d",
+          (int)pHeader, (int)pHeader->length );
+
+   spin_lock_irqsave(&pInfo->lock, flags);
+
+   if(pInfo->rx_first == pHeader)
+   {
+      /* Remove the first block in the linked list: */
+      pInfo->rx_first = pHeader->next;
+      
+      if(pInfo->rx_first==NULL)
+      {
+         pInfo->rx_last = NULL;
+      }
+      pInfo->blocks_in_rx_queue--;
+   }
+   else 
+   {
+      /* Find block to remove: */
+      for(pFind=pInfo->rx_first; pFind; pFind=pFind->next)
+      {
+         if(pFind->next == pHeader) 
+         {
+            /* Got it. */
+            pFind->next = pHeader->next;
+            pInfo->blocks_in_rx_queue--;
+            if(pFind->next==NULL)
+            {
+               /* Oh, removed the last one! */
+               pInfo->rx_last = pFind;
+            }
+            break;
+         }
+      }
+   }
+
+   spin_unlock_irqrestore(&pInfo->lock, flags);
+
+   kfree(pHeader);
+   TRACE_M("remove_from_rx_queue - kfree %x",(int)pHeader);
+
+   TRACE_Q("remove_from_rx_queue: rx_first = %x, rx_last = %x, count = %d",
+          (int)pInfo->rx_first, (int)pInfo->rx_last, pInfo->blocks_in_rx_queue );
+}
+
+static void put_char(struct r3964_info *pInfo, unsigned char ch)
+{
+   struct tty_struct *tty = pInfo->tty;
+
+   if(tty==NULL)
+      return;
+
+   if(tty->driver->put_char)
+   {
+      tty->driver->put_char(tty, ch);
+   }
+   pInfo->bcc ^= ch;
+}
+
+static void flush(struct r3964_info *pInfo)
+{
+   struct tty_struct *tty = pInfo->tty;
+
+   if(tty==NULL)
+      return;
+
+   if(tty->driver->flush_chars)
+   {
+      tty->driver->flush_chars(tty);
+   }
+}
+
+static void trigger_transmit(struct r3964_info *pInfo)
+{
+   unsigned long flags;
+   
+
+   spin_lock_irqsave(&pInfo->lock, flags);
+
+   if((pInfo->state == R3964_IDLE) && (pInfo->tx_first!=NULL))
+   {
+      pInfo->state = R3964_TX_REQUEST;
+      pInfo->nRetry=0;
+      pInfo->flags &= ~R3964_ERROR;
+      mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ);
+
+      spin_unlock_irqrestore(&pInfo->lock, flags);
+
+      TRACE_PS("trigger_transmit - sent STX");
+
+      put_char(pInfo, STX);
+      flush(pInfo);
+
+      pInfo->bcc = 0;
+   }
+   else
+   {
+      spin_unlock_irqrestore(&pInfo->lock, flags);
+   }
+}
+
+static void retry_transmit(struct r3964_info *pInfo)
+{
+   if(pInfo->nRetry<R3964_MAX_RETRIES)
+   {
+      TRACE_PE("transmission failed. Retry #%d", 
+             pInfo->nRetry);
+      pInfo->bcc = 0;
+      put_char(pInfo, STX);
+      flush(pInfo);
+      pInfo->state = R3964_TX_REQUEST;
+      pInfo->nRetry++;
+      mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ);
+   }
+   else
+   {
+      TRACE_PE("transmission failed after %d retries", 
+             R3964_MAX_RETRIES);
+
+      remove_from_tx_queue(pInfo, R3964_TX_FAIL);
+      
+      put_char(pInfo, NAK);
+      flush(pInfo);
+      pInfo->state = R3964_IDLE;
+
+      trigger_transmit(pInfo);
+   }
+}
+
+
+static void transmit_block(struct r3964_info *pInfo)
+{
+   struct tty_struct *tty = pInfo->tty;
+   struct r3964_block_header *pBlock = pInfo->tx_first;
+   int room=0;
+
+   if((tty==NULL) || (pBlock==NULL))
+   {
+      return;
+   }
+
+   if(tty->driver->write_room)
+      room=tty->driver->write_room(tty);
+
+   TRACE_PS("transmit_block %x, room %d, length %d", 
+          (int)pBlock, room, pBlock->length);
+   
+   while(pInfo->tx_position < pBlock->length)
+   {
+      if(room<2)
+         break;
+ 
+      if(pBlock->data[pInfo->tx_position]==DLE)
+      {
+         /* send additional DLE char: */
+         put_char(pInfo, DLE);
+      }
+      put_char(pInfo, pBlock->data[pInfo->tx_position++]);
+      
+      room--;
+   }
+
+   if((pInfo->tx_position == pBlock->length) && (room>=3))
+   {
+      put_char(pInfo, DLE);
+      put_char(pInfo, ETX);
+      if(pInfo->flags & R3964_BCC)
+      {
+         put_char(pInfo, pInfo->bcc);
+      }
+      pInfo->state = R3964_WAIT_FOR_TX_ACK;
+      mod_timer(&pInfo->tmr, jiffies + R3964_TO_QVZ);
+   }
+   flush(pInfo);
+}
+
+static void on_receive_block(struct r3964_info *pInfo)
+{
+   unsigned int length;
+   struct r3964_client_info *pClient;
+   struct r3964_block_header *pBlock;
+   
+   length=pInfo->rx_position;
+
+   /* compare byte checksum characters: */
+   if(pInfo->flags & R3964_BCC)
+   {
+      if(pInfo->bcc!=pInfo->last_rx)
+      {
+         TRACE_PE("checksum error - got %x but expected %x",
+                pInfo->last_rx, pInfo->bcc);
+         pInfo->flags |= R3964_CHECKSUM;
+      }
+   }
+
+   /* check for errors (parity, overrun,...): */
+   if(pInfo->flags & R3964_ERROR)
+   {
+      TRACE_PE("on_receive_block - transmission failed error %x",
+             pInfo->flags & R3964_ERROR);
+      
+      put_char(pInfo, NAK);
+      flush(pInfo);
+      if(pInfo->nRetry<R3964_MAX_RETRIES)
+      {
+         pInfo->state=R3964_WAIT_FOR_RX_REPEAT;
+         pInfo->nRetry++;
+	 mod_timer(&pInfo->tmr, jiffies + R3964_TO_RX_PANIC);
+      }
+      else
+      {
+         TRACE_PE("on_receive_block - failed after max retries");
+         pInfo->state=R3964_IDLE;
+      }
+      return;
+   }
+
+   
+   /* received block; submit DLE: */
+   put_char(pInfo, DLE);
+   flush(pInfo);
+   del_timer_sync(&pInfo->tmr);
+   TRACE_PS(" rx success: got %d chars", length);
+
+   /* prepare struct r3964_block_header: */
+   pBlock = kmalloc(length+sizeof(struct r3964_block_header), GFP_KERNEL);
+   TRACE_M("on_receive_block - kmalloc %x",(int)pBlock);
+
+   if(pBlock==NULL)
+      return;
+
+   pBlock->length = length;
+   pBlock->data   = ((unsigned char*)pBlock)+sizeof(struct r3964_block_header);
+   pBlock->locks  = 0;
+   pBlock->next   = NULL;
+   pBlock->owner  = NULL;
+
+   memcpy(pBlock->data, pInfo->rx_buf, length);
+
+   /* queue block into rx_queue: */
+   add_rx_queue(pInfo, pBlock);
+
+   /* notify attached client processes: */
+   for(pClient=pInfo->firstClient; pClient; pClient=pClient->next)
+   {
+      if(pClient->sig_flags & R3964_SIG_DATA)
+      {
+         add_msg(pClient, R3964_MSG_DATA, length, R3964_OK, pBlock);
+      }
+   }
+   wake_up_interruptible (&pInfo->read_wait);
+   
+   pInfo->state = R3964_IDLE;
+
+   trigger_transmit(pInfo);
+}
+
+
+static void receive_char(struct r3964_info *pInfo, const unsigned char c)
+{
+   switch(pInfo->state)
+   {
+      case R3964_TX_REQUEST:
+         if(c==DLE)
+         {
+            TRACE_PS("TX_REQUEST - got DLE");
+
+            pInfo->state = R3964_TRANSMITTING;
+            pInfo->tx_position = 0;
+            
+            transmit_block(pInfo);
+         }
+         else if(c==STX)
+         {
+            if(pInfo->nRetry==0)
+            {
+               TRACE_PE("TX_REQUEST - init conflict");
+               if(pInfo->priority == R3964_SLAVE)
+               {
+                  goto start_receiving;
+               }
+            } 
+            else 
+            {
+               TRACE_PE("TX_REQUEST - secondary init conflict!?"
+                        " Switching to SLAVE mode for next rx.");
+               goto start_receiving;
+            }
+         }
+         else
+         {
+            TRACE_PE("TX_REQUEST - char != DLE: %x", c);
+            retry_transmit(pInfo);
+         }
+         break;
+      case R3964_TRANSMITTING:
+         if(c==NAK)
+         {
+            TRACE_PE("TRANSMITTING - got NAK");
+            retry_transmit(pInfo);
+         }
+         else
+         {
+            TRACE_PE("TRANSMITTING - got invalid char");
+ 
+            pInfo->state = R3964_WAIT_ZVZ_BEFORE_TX_RETRY;
+	    mod_timer(&pInfo->tmr, jiffies + R3964_TO_ZVZ);
+         }
+         break;
+      case R3964_WAIT_FOR_TX_ACK:
+         if(c==DLE)
+         {
+            TRACE_PS("WAIT_FOR_TX_ACK - got DLE");
+            remove_from_tx_queue(pInfo, R3964_OK);
+            
+            pInfo->state = R3964_IDLE;
+            trigger_transmit(pInfo);
+         }
+         else
+         {
+            retry_transmit(pInfo);
+         }
+         break;
+      case R3964_WAIT_FOR_RX_REPEAT:
+         /* FALLTROUGH */
+      case R3964_IDLE:
+         if(c==STX)
+         {
+            /* Prevent rx_queue from overflow: */
+            if(pInfo->blocks_in_rx_queue >= R3964_MAX_BLOCKS_IN_RX_QUEUE)
+            {
+               TRACE_PE("IDLE - got STX but no space in rx_queue!");
+               pInfo->state=R3964_WAIT_FOR_RX_BUF;
+	       mod_timer(&pInfo->tmr, R3964_TO_NO_BUF);
+               break;
+            }
+start_receiving:
+            /* Ok, start receiving: */
+            TRACE_PS("IDLE - got STX");
+            pInfo->rx_position = 0;
+            pInfo->last_rx = 0;
+            pInfo->flags &= ~R3964_ERROR;
+            pInfo->state=R3964_RECEIVING;
+	    mod_timer(&pInfo->tmr, R3964_TO_ZVZ);
+	    pInfo->nRetry = 0;
+            put_char(pInfo, DLE);
+            flush(pInfo);
+            pInfo->bcc = 0;
+         }
+         break;
+      case R3964_RECEIVING:
+         if(pInfo->rx_position < RX_BUF_SIZE)
+         {
+            pInfo->bcc ^= c;
+            
+            if(c==DLE)
+            {
+               if(pInfo->last_rx==DLE)
+               {
+                  pInfo->last_rx = 0;
+                  goto char_to_buf;
+               }
+               pInfo->last_rx = DLE;
+               break;
+            } 
+            else if((c==ETX) && (pInfo->last_rx==DLE))
+            {
+               if(pInfo->flags & R3964_BCC)
+               {
+                  pInfo->state = R3964_WAIT_FOR_BCC;
+		  mod_timer(&pInfo->tmr, R3964_TO_ZVZ);
+               }
+               else 
+               {
+                  on_receive_block(pInfo);
+               }
+            }
+            else
+            {
+               pInfo->last_rx = c;
+char_to_buf:
+               pInfo->rx_buf[pInfo->rx_position++] = c;
+	       mod_timer(&pInfo->tmr, R3964_TO_ZVZ);
+            }
+         }
+        /* else: overflow-msg? BUF_SIZE>MTU; should not happen? */ 
+         break;
+      case R3964_WAIT_FOR_BCC:
+         pInfo->last_rx = c;
+         on_receive_block(pInfo);
+         break;
+   }
+}
+
+static void receive_error(struct r3964_info *pInfo, const char flag)
+{
+    switch (flag) 
+    {
+    case TTY_NORMAL:
+        break;
+    case TTY_BREAK:
+        TRACE_PE("received break")
+        pInfo->flags |= R3964_BREAK;
+        break;
+    case TTY_PARITY:
+        TRACE_PE("parity error")
+        pInfo->flags |= R3964_PARITY;
+        break;
+    case TTY_FRAME:
+        TRACE_PE("frame error")
+        pInfo->flags |= R3964_FRAME;
+        break;
+    case TTY_OVERRUN:
+        TRACE_PE("frame overrun")
+        pInfo->flags |= R3964_OVERRUN;
+        break;
+    default:
+        TRACE_PE("receive_error - unknown flag %d", flag);
+        pInfo->flags |= R3964_UNKNOWN;
+        break;
+    }
+}
+
+static void on_timeout(unsigned long priv)
+{
+   struct r3964_info *pInfo = (void *)priv;
+
+   switch(pInfo->state)
+   {
+      case R3964_TX_REQUEST:
+         TRACE_PE("TX_REQUEST - timeout");
+         retry_transmit(pInfo);
+         break;
+      case R3964_WAIT_ZVZ_BEFORE_TX_RETRY:
+         put_char(pInfo, NAK);
+         flush(pInfo);
+         retry_transmit(pInfo);
+         break;
+      case R3964_WAIT_FOR_TX_ACK:
+         TRACE_PE("WAIT_FOR_TX_ACK - timeout");
+         retry_transmit(pInfo);
+         break;
+      case R3964_WAIT_FOR_RX_BUF:
+         TRACE_PE("WAIT_FOR_RX_BUF - timeout");
+         put_char(pInfo, NAK);
+         flush(pInfo);
+         pInfo->state=R3964_IDLE;
+         break;
+      case R3964_RECEIVING:
+         TRACE_PE("RECEIVING - timeout after %d chars", 
+                  pInfo->rx_position);
+         put_char(pInfo, NAK);
+         flush(pInfo);
+         pInfo->state=R3964_IDLE;
+         break;
+      case R3964_WAIT_FOR_RX_REPEAT:
+         TRACE_PE("WAIT_FOR_RX_REPEAT - timeout");
+         pInfo->state=R3964_IDLE;
+         break;
+      case R3964_WAIT_FOR_BCC:
+         TRACE_PE("WAIT_FOR_BCC - timeout");
+         put_char(pInfo, NAK);
+         flush(pInfo);
+         pInfo->state=R3964_IDLE;
+         break;
+   }
+}
+
+static struct r3964_client_info *findClient(
+  struct r3964_info *pInfo, pid_t pid)
+{
+   struct r3964_client_info *pClient;
+   
+   for(pClient=pInfo->firstClient; pClient; pClient=pClient->next)
+   {
+      if(pClient->pid == pid)
+      {
+         return pClient;
+      }
+   }
+   return NULL;
+}
+
+static int enable_signals(struct r3964_info *pInfo, pid_t pid, int arg)
+{
+   struct r3964_client_info *pClient;
+   struct r3964_client_info **ppClient;
+   struct r3964_message *pMsg;
+   
+   if((arg & R3964_SIG_ALL)==0)
+   {
+      /* Remove client from client list */
+      for(ppClient=&pInfo->firstClient; *ppClient; ppClient=&(*ppClient)->next)
+      {
+         pClient = *ppClient;
+         
+         if(pClient->pid == pid)
+         {
+            TRACE_PS("removing client %d from client list", pid);
+            *ppClient = pClient->next;
+            while(pClient->msg_count)
+            {
+               pMsg=remove_msg(pInfo, pClient);
+               if(pMsg)
+               {
+                  kfree(pMsg);
+                  TRACE_M("enable_signals - msg kfree %x",(int)pMsg);
+               }
+            }
+            kfree(pClient);
+            TRACE_M("enable_signals - kfree %x",(int)pClient);
+            return 0;
+         }
+      }
+      return -EINVAL;
+   }
+   else
+   {
+      pClient=findClient(pInfo, pid);
+      if(pClient)
+      {
+         /* update signal options */
+         pClient->sig_flags=arg;
+      } 
+      else 
+      {
+         /* add client to client list */
+         pClient=kmalloc(sizeof(struct r3964_client_info), GFP_KERNEL);
+         TRACE_M("enable_signals - kmalloc %x",(int)pClient);
+         if(pClient==NULL)
+            return -ENOMEM;
+
+         TRACE_PS("add client %d to client list", pid);
+	 spin_lock_init(&pClient->lock);
+         pClient->sig_flags=arg;
+         pClient->pid = pid;
+         pClient->next=pInfo->firstClient;
+         pClient->first_msg = NULL;
+         pClient->last_msg = NULL;
+         pClient->next_block_to_read = NULL;
+         pClient->msg_count = 0;
+         pInfo->firstClient=pClient;
+      }
+   }
+
+   return 0;
+}
+
+static int read_telegram(struct r3964_info *pInfo, pid_t pid, unsigned char __user *buf)
+{
+    struct r3964_client_info *pClient;
+    struct r3964_block_header *block;
+
+    if(!buf)
+    {
+        return -EINVAL;
+    }
+
+    pClient=findClient(pInfo,pid);
+    if(pClient==NULL)
+    {
+       return -EINVAL;
+    }
+    
+    block=pClient->next_block_to_read;
+    if(!block)
+    {
+       return 0;
+    }
+    else
+    {
+      if (copy_to_user (buf, block->data, block->length))
+	return -EFAULT;
+
+       remove_client_block(pInfo, pClient);
+       return block->length;
+    }
+
+    return -EINVAL;
+}
+
+static void add_msg(struct r3964_client_info *pClient, int msg_id, int arg,
+             int error_code, struct r3964_block_header *pBlock)
+{
+   struct r3964_message *pMsg;
+   unsigned long flags;
+   
+   if(pClient->msg_count<R3964_MAX_MSG_COUNT-1)
+   {
+queue_the_message:
+
+      pMsg = kmalloc(sizeof(struct r3964_message), GFP_KERNEL);
+      TRACE_M("add_msg - kmalloc %x",(int)pMsg);
+      if(pMsg==NULL) {
+         return;
+      }
+
+      spin_lock_irqsave(&pClient->lock, flags);
+
+      pMsg->msg_id = msg_id;
+      pMsg->arg    = arg;
+      pMsg->error_code = error_code;
+      pMsg->block  = pBlock;
+      pMsg->next   = NULL;
+      
+      if(pClient->last_msg==NULL)
+      {
+         pClient->first_msg=pClient->last_msg=pMsg;
+      }
+      else
+      {
+         pClient->last_msg->next = pMsg;
+         pClient->last_msg=pMsg;
+      }
+
+      pClient->msg_count++;
+
+      if(pBlock!=NULL)
+      {
+         pBlock->locks++;
+      }
+      spin_unlock_irqrestore(&pClient->lock, flags);
+   }
+   else
+   {
+      if((pClient->last_msg->msg_id == R3964_MSG_ACK)
+		 && (pClient->last_msg->error_code==R3964_OVERFLOW))
+      {
+         pClient->last_msg->arg++;
+		 TRACE_PE("add_msg - inc prev OVERFLOW-msg");
+      }
+      else
+      {
+         msg_id = R3964_MSG_ACK;
+         arg = 0;
+		 error_code = R3964_OVERFLOW;
+         pBlock = NULL;
+		 TRACE_PE("add_msg - queue OVERFLOW-msg");
+         goto queue_the_message;
+      }
+   }
+   /* Send SIGIO signal to client process: */
+   if(pClient->sig_flags & R3964_USE_SIGIO)
+   {
+      kill_proc(pClient->pid, SIGIO, 1);
+   }
+}
+
+static struct r3964_message *remove_msg(struct r3964_info *pInfo,
+                       struct r3964_client_info *pClient)
+{
+   struct r3964_message *pMsg=NULL;
+   unsigned long flags;
+
+   if(pClient->first_msg)
+   {
+      spin_lock_irqsave(&pClient->lock, flags);
+
+      pMsg = pClient->first_msg;
+      pClient->first_msg = pMsg->next;
+      if(pClient->first_msg==NULL)
+      {
+         pClient->last_msg = NULL;
+      }
+      
+      pClient->msg_count--;
+      if(pMsg->block)
+      {
+        remove_client_block(pInfo, pClient);
+        pClient->next_block_to_read = pMsg->block;
+      }
+      spin_unlock_irqrestore(&pClient->lock, flags);
+   }
+   return pMsg;
+}
+
+static void remove_client_block(struct r3964_info *pInfo, 
+                struct r3964_client_info *pClient)
+{
+    struct r3964_block_header *block;
+
+    TRACE_PS("remove_client_block PID %d", pClient->pid);
+
+    block=pClient->next_block_to_read;
+    if(block)
+    {
+        block->locks--;
+        if(block->locks==0)
+        {
+            remove_from_rx_queue(pInfo, block);
+        }
+    }
+    pClient->next_block_to_read = NULL;
+}
+
+
+/*************************************************************
+ * Line discipline routines
+ *************************************************************/
+
+static int r3964_open(struct tty_struct *tty)
+{
+   struct r3964_info *pInfo;
+   
+   TRACE_L("open");
+   TRACE_L("tty=%x, PID=%d, disc_data=%x", 
+          (int)tty, current->pid, (int)tty->disc_data);
+   
+   pInfo=kmalloc(sizeof(struct r3964_info), GFP_KERNEL); 
+   TRACE_M("r3964_open - info kmalloc %x",(int)pInfo);
+
+   if(!pInfo)
+   {
+      printk(KERN_ERR "r3964: failed to alloc info structure\n");
+      return -ENOMEM;
+   }
+
+   pInfo->rx_buf = kmalloc(RX_BUF_SIZE, GFP_KERNEL);
+   TRACE_M("r3964_open - rx_buf kmalloc %x",(int)pInfo->rx_buf);
+
+   if(!pInfo->rx_buf)
+   {
+      printk(KERN_ERR "r3964: failed to alloc receive buffer\n");
+      kfree(pInfo);
+      TRACE_M("r3964_open - info kfree %x",(int)pInfo);
+      return -ENOMEM;
+   }
+   
+   pInfo->tx_buf = kmalloc(TX_BUF_SIZE, GFP_KERNEL);
+   TRACE_M("r3964_open - tx_buf kmalloc %x",(int)pInfo->tx_buf);
+
+   if(!pInfo->tx_buf)
+   {
+      printk(KERN_ERR "r3964: failed to alloc transmit buffer\n");
+      kfree(pInfo->rx_buf);
+      TRACE_M("r3964_open - rx_buf kfree %x",(int)pInfo->rx_buf);
+      kfree(pInfo);
+      TRACE_M("r3964_open - info kfree %x",(int)pInfo);
+      return -ENOMEM;
+   }
+
+   spin_lock_init(&pInfo->lock);
+   pInfo->tty = tty;
+   init_waitqueue_head (&pInfo->read_wait);
+   pInfo->priority = R3964_MASTER;
+   pInfo->rx_first = pInfo->rx_last = NULL;
+   pInfo->tx_first = pInfo->tx_last = NULL;
+   pInfo->rx_position = 0;
+   pInfo->tx_position = 0;
+   pInfo->last_rx = 0;
+   pInfo->blocks_in_rx_queue = 0;
+   pInfo->firstClient=NULL;
+   pInfo->state=R3964_IDLE;
+   pInfo->flags = R3964_DEBUG;
+   pInfo->nRetry = 0;
+   
+   tty->disc_data = pInfo;
+
+   init_timer(&pInfo->tmr);
+   pInfo->tmr.data = (unsigned long)pInfo;
+   pInfo->tmr.function = on_timeout;
+
+   return 0;
+}
+
+static void r3964_close(struct tty_struct *tty)
+{
+   struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+   struct r3964_client_info *pClient, *pNext;
+   struct r3964_message *pMsg;
+   struct r3964_block_header *pHeader, *pNextHeader;
+   unsigned long flags;
+
+   TRACE_L("close");
+
+    /*
+     * Make sure that our task queue isn't activated.  If it
+     * is, take it out of the linked list.
+     */
+    del_timer_sync(&pInfo->tmr);
+
+   /* Remove client-structs and message queues: */
+    pClient=pInfo->firstClient;
+    while(pClient)
+    {
+       pNext=pClient->next;
+       while(pClient->msg_count)
+       {
+          pMsg=remove_msg(pInfo, pClient);
+          if(pMsg)
+          {
+             kfree(pMsg);
+             TRACE_M("r3964_close - msg kfree %x",(int)pMsg);
+          }
+       }
+       kfree(pClient);
+       TRACE_M("r3964_close - client kfree %x",(int)pClient);
+       pClient=pNext;
+    }
+    /* Remove jobs from tx_queue: */
+        spin_lock_irqsave(&pInfo->lock, flags);
+	pHeader=pInfo->tx_first;
+	pInfo->tx_first=pInfo->tx_last=NULL;
+	spin_unlock_irqrestore(&pInfo->lock, flags);
+	
+    while(pHeader)
+	{
+	   pNextHeader=pHeader->next;
+	   kfree(pHeader);
+	   pHeader=pNextHeader;
+	}
+
+    /* Free buffers: */
+    wake_up_interruptible(&pInfo->read_wait);
+    kfree(pInfo->rx_buf);
+    TRACE_M("r3964_close - rx_buf kfree %x",(int)pInfo->rx_buf);
+    kfree(pInfo->tx_buf);
+    TRACE_M("r3964_close - tx_buf kfree %x",(int)pInfo->tx_buf);
+    kfree(pInfo);
+    TRACE_M("r3964_close - info kfree %x",(int)pInfo);
+}
+
+static ssize_t r3964_read(struct tty_struct *tty, struct file *file,
+			  unsigned char __user *buf, size_t nr)
+{
+   struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+   struct r3964_client_info *pClient;
+   struct r3964_message *pMsg;
+   struct r3964_client_message theMsg;
+   DECLARE_WAITQUEUE (wait, current);
+   
+   int pid = current->pid;
+   int count;
+   
+   TRACE_L("read()");
+ 
+   pClient=findClient(pInfo, pid);
+   if(pClient)
+   {
+      pMsg = remove_msg(pInfo, pClient);
+      if(pMsg==NULL)
+      {
+		 /* no messages available. */
+         if (file->f_flags & O_NONBLOCK)
+		 {
+            return -EAGAIN;
+		 }
+         /* block until there is a message: */
+         add_wait_queue(&pInfo->read_wait, &wait);
+repeat:
+         current->state = TASK_INTERRUPTIBLE;
+         pMsg = remove_msg(pInfo, pClient);
+	 if (!pMsg && !signal_pending(current))
+		 {
+            schedule();
+            goto repeat;
+         }
+         current->state = TASK_RUNNING;
+         remove_wait_queue(&pInfo->read_wait, &wait);
+      }
+      
+      /* If we still haven't got a message, we must have been signalled */
+
+      if (!pMsg) return -EINTR;
+
+      /* deliver msg to client process: */
+      theMsg.msg_id = pMsg->msg_id;
+      theMsg.arg    = pMsg->arg;
+      theMsg.error_code = pMsg->error_code;
+      count = sizeof(struct r3964_client_message);
+
+      kfree(pMsg);
+      TRACE_M("r3964_read - msg kfree %x",(int)pMsg);
+
+      if (copy_to_user(buf,&theMsg, count))
+	return -EFAULT;
+
+      TRACE_PS("read - return %d", count);
+      return count;
+   }
+   return -EPERM;
+}
+
+static ssize_t r3964_write(struct tty_struct * tty, struct file * file,
+			   const unsigned char *data, size_t count)
+{
+   struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+   struct r3964_block_header *pHeader;
+   struct r3964_client_info *pClient;
+   unsigned char *new_data;
+   int pid;
+   
+   TRACE_L("write request, %d characters", count);
+/* 
+ * Verify the pointers 
+ */
+
+   if(!pInfo)
+      return -EIO;
+
+/*
+ * Ensure that the caller does not wish to send too much.
+ */
+   if (count > R3964_MTU) 
+   {
+      if (pInfo->flags & R3964_DEBUG)
+      {
+         TRACE_L (KERN_WARNING
+                 "r3964_write: truncating user packet "
+                 "from %u to mtu %d", count, R3964_MTU);
+      }
+      count = R3964_MTU;
+   }
+/*
+ * Allocate a buffer for the data and copy it from the buffer with header prepended
+ */
+   new_data = kmalloc (count+sizeof(struct r3964_block_header), GFP_KERNEL);
+   TRACE_M("r3964_write - kmalloc %x",(int)new_data);
+   if (new_data == NULL) {
+      if (pInfo->flags & R3964_DEBUG)
+      {
+         printk (KERN_ERR
+               "r3964_write: no memory\n");
+      }
+      return -ENOSPC;
+   }
+   
+   pHeader = (struct r3964_block_header *)new_data;
+   pHeader->data = new_data + sizeof(struct r3964_block_header);
+   pHeader->length = count;
+   pHeader->locks = 0;
+   pHeader->owner = NULL;
+   
+   pid=current->pid;
+   
+   pClient=findClient(pInfo, pid);
+   if(pClient)
+   {
+      pHeader->owner = pClient;
+   }
+
+   memcpy(pHeader->data, data, count); /* We already verified this */
+
+   if(pInfo->flags & R3964_DEBUG)
+   {
+      dump_block(pHeader->data, count);
+   }
+
+/*
+ * Add buffer to transmit-queue:
+ */
+   add_tx_queue(pInfo, pHeader);
+   trigger_transmit(pInfo);
+   
+   return 0;
+}
+
+static int r3964_ioctl(struct tty_struct * tty, struct file * file,
+               unsigned int cmd, unsigned long arg)
+{
+   struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+   if(pInfo==NULL)
+      return -EINVAL;
+   switch(cmd)
+   {
+      case R3964_ENABLE_SIGNALS:
+         return enable_signals(pInfo, current->pid, arg);
+      case R3964_SETPRIORITY:
+         if(arg<R3964_MASTER || arg>R3964_SLAVE)
+            return -EINVAL;
+         pInfo->priority = arg & 0xff;
+         return 0;
+      case R3964_USE_BCC:
+             if(arg)
+            pInfo->flags |= R3964_BCC;
+         else
+            pInfo->flags &= ~R3964_BCC;
+         return 0;
+      case R3964_READ_TELEGRAM:
+         return read_telegram(pInfo, current->pid, (unsigned char __user *)arg);
+      default:
+         return -ENOIOCTLCMD;
+   }
+}
+
+static void r3964_set_termios(struct tty_struct *tty, struct termios * old)
+{
+   TRACE_L("set_termios");
+}
+
+/* Called without the kernel lock held - fine */
+static unsigned int r3964_poll(struct tty_struct * tty, struct file * file,
+		      struct poll_table_struct *wait)
+{
+   struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+   int pid=current->pid;
+   struct r3964_client_info *pClient;
+   struct r3964_message *pMsg=NULL;
+   unsigned long flags;
+   int result = POLLOUT;
+
+   TRACE_L("POLL");
+
+   pClient=findClient(pInfo,pid);
+   if(pClient)
+     {
+       poll_wait(file, &pInfo->read_wait, wait);
+       spin_lock_irqsave(&pInfo->lock, flags);
+       pMsg=pClient->first_msg;
+       spin_unlock_irqrestore(&pInfo->lock, flags);
+       if(pMsg)
+	   result |= POLLIN | POLLRDNORM;
+     }
+   else
+     {
+       result = -EINVAL;
+     }
+   return result;
+}
+
+static void r3964_receive_buf(struct tty_struct *tty, const unsigned char *cp,
+                              char *fp, int count)
+{
+   struct r3964_info *pInfo=(struct r3964_info*)tty->disc_data;
+    const unsigned char *p;
+    char *f, flags = 0;
+    int i;
+
+    for (i=count, p = cp, f = fp; i; i--, p++) {
+        if (f)
+            flags = *f++;
+        if(flags==TTY_NORMAL)
+        {
+            receive_char(pInfo, *p);
+        }
+        else
+        {
+            receive_error(pInfo, flags);
+        }
+        
+    }
+}
+
+static int r3964_receive_room(struct tty_struct *tty)
+{
+   TRACE_L("receive_room");
+   return -1;
+}
+
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_R3964);
diff --git a/drivers/char/n_tty.c b/drivers/char/n_tty.c
new file mode 100644
index 0000000..edba5a3
--- /dev/null
+++ b/drivers/char/n_tty.c
@@ -0,0 +1,1562 @@
+/*
+ * n_tty.c --- implements the N_TTY line discipline.
+ * 
+ * This code used to be in tty_io.c, but things are getting hairy
+ * enough that it made sense to split things off.  (The N_TTY
+ * processing has changed so much that it's hardly recognizable,
+ * anyway...)
+ *
+ * Note that the open routine for N_TTY is guaranteed never to return
+ * an error.  This is because Linux will fall back to setting a line
+ * to N_TTY if it can not switch to any other line discipline.  
+ *
+ * Written by Theodore Ts'o, Copyright 1994.
+ * 
+ * This file also contains code originally written by Linus Torvalds,
+ * Copyright 1991, 1992, 1993, and by Julian Cowley, Copyright 1994.
+ * 
+ * This file may be redistributed under the terms of the GNU General Public
+ * License.
+ *
+ * Reduced memory usage for older ARM systems  - Russell King.
+ *
+ * 2000/01/20   Fixed SMP locking on put_tty_queue using bits of 
+ *		the patch by Andrew J. Kroll <ag784@freenet.buffalo.edu>
+ *		who actually finally proved there really was a race.
+ *
+ * 2002/03/18   Implemented n_tty_wakeup to send SIGIO POLL_OUTs to
+ *		waiting writing processes-Sapan Bhatia <sapan@corewars.org>.
+ *		Also fixed a bug in BLOCKING mode where write_chan returns
+ *		EAGAIN
+ */
+
+#include <linux/types.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/timer.h>
+#include <linux/ctype.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/bitops.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+/* number of characters left in xmit buffer before select has we have room */
+#define WAKEUP_CHARS 256
+
+/*
+ * This defines the low- and high-watermarks for throttling and
+ * unthrottling the TTY driver.  These watermarks are used for
+ * controlling the space in the read buffer.
+ */
+#define TTY_THRESHOLD_THROTTLE		128 /* now based on remaining room */
+#define TTY_THRESHOLD_UNTHROTTLE 	128
+
+static inline unsigned char *alloc_buf(void)
+{
+	int prio = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL;
+
+	if (PAGE_SIZE != N_TTY_BUF_SIZE)
+		return kmalloc(N_TTY_BUF_SIZE, prio);
+	else
+		return (unsigned char *)__get_free_page(prio);
+}
+
+static inline void free_buf(unsigned char *buf)
+{
+	if (PAGE_SIZE != N_TTY_BUF_SIZE)
+		kfree(buf);
+	else
+		free_page((unsigned long) buf);
+}
+
+static inline void put_tty_queue_nolock(unsigned char c, struct tty_struct *tty)
+{
+	if (tty->read_cnt < N_TTY_BUF_SIZE) {
+		tty->read_buf[tty->read_head] = c;
+		tty->read_head = (tty->read_head + 1) & (N_TTY_BUF_SIZE-1);
+		tty->read_cnt++;
+	}
+}
+
+static inline void put_tty_queue(unsigned char c, struct tty_struct *tty)
+{
+	unsigned long flags;
+	/*
+	 *	The problem of stomping on the buffers ends here.
+	 *	Why didn't anyone see this one coming? --AJK
+	*/
+	spin_lock_irqsave(&tty->read_lock, flags);
+	put_tty_queue_nolock(c, tty);
+	spin_unlock_irqrestore(&tty->read_lock, flags);
+}
+
+/**
+ *	check_unthrottle	-	allow new receive data
+ *	@tty; tty device
+ *
+ *	Check whether to call the driver.unthrottle function.
+ *	We test the TTY_THROTTLED bit first so that it always
+ *	indicates the current state. The decision about whether
+ *	it is worth allowing more input has been taken by the caller.
+ *	Can sleep, may be called under the atomic_read semaphore but
+ *	this is not guaranteed.
+ */
+ 
+static void check_unthrottle(struct tty_struct * tty)
+{
+	if (tty->count &&
+	    test_and_clear_bit(TTY_THROTTLED, &tty->flags) && 
+	    tty->driver->unthrottle)
+		tty->driver->unthrottle(tty);
+}
+
+/**
+ *	reset_buffer_flags	-	reset buffer state
+ *	@tty: terminal to reset
+ *
+ *	Reset the read buffer counters, clear the flags, 
+ *	and make sure the driver is unthrottled. Called
+ *	from n_tty_open() and n_tty_flush_buffer().
+ */
+static void reset_buffer_flags(struct tty_struct *tty)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&tty->read_lock, flags);
+	tty->read_head = tty->read_tail = tty->read_cnt = 0;
+	spin_unlock_irqrestore(&tty->read_lock, flags);
+	tty->canon_head = tty->canon_data = tty->erasing = 0;
+	memset(&tty->read_flags, 0, sizeof tty->read_flags);
+	check_unthrottle(tty);
+}
+
+/**
+ *	n_tty_flush_buffer	-	clean input queue
+ *	@tty:	terminal device
+ *
+ *	Flush the input buffer. Called when the line discipline is
+ *	being closed, when the tty layer wants the buffer flushed (eg
+ *	at hangup) or when the N_TTY line discipline internally has to
+ *	clean the pending queue (for example some signals).
+ *
+ *	FIXME: tty->ctrl_status is not spinlocked and relies on
+ *	lock_kernel() still.
+ */
+ 
+static void n_tty_flush_buffer(struct tty_struct * tty)
+{
+	/* clear everything and unthrottle the driver */
+	reset_buffer_flags(tty);
+	
+	if (!tty->link)
+		return;
+
+	if (tty->link->packet) {
+		tty->ctrl_status |= TIOCPKT_FLUSHREAD;
+		wake_up_interruptible(&tty->link->read_wait);
+	}
+}
+
+/**
+ *	n_tty_chars_in_buffer	-	report available bytes
+ *	@tty: tty device
+ *
+ *	Report the number of characters buffered to be delivered to user
+ *	at this instant in time. 
+ */
+ 
+static ssize_t n_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	unsigned long flags;
+	ssize_t n = 0;
+
+	spin_lock_irqsave(&tty->read_lock, flags);
+	if (!tty->icanon) {
+		n = tty->read_cnt;
+	} else if (tty->canon_data) {
+		n = (tty->canon_head > tty->read_tail) ?
+			tty->canon_head - tty->read_tail :
+			tty->canon_head + (N_TTY_BUF_SIZE - tty->read_tail);
+	}
+	spin_unlock_irqrestore(&tty->read_lock, flags);
+	return n;
+}
+
+/**
+ *	is_utf8_continuation	-	utf8 multibyte check
+ *	@c: byte to check
+ *
+ *	Returns true if the utf8 character 'c' is a multibyte continuation
+ *	character. We use this to correctly compute the on screen size
+ *	of the character when printing
+ */
+ 
+static inline int is_utf8_continuation(unsigned char c)
+{
+	return (c & 0xc0) == 0x80;
+}
+
+/**
+ *	is_continuation		-	multibyte check
+ *	@c: byte to check
+ *
+ *	Returns true if the utf8 character 'c' is a multibyte continuation
+ *	character and the terminal is in unicode mode.
+ */
+ 
+static inline int is_continuation(unsigned char c, struct tty_struct *tty)
+{
+	return I_IUTF8(tty) && is_utf8_continuation(c);
+}
+
+/**
+ *	opost			-	output post processor
+ *	@c: character (or partial unicode symbol)
+ *	@tty: terminal device
+ *
+ *	Perform OPOST processing.  Returns -1 when the output device is
+ *	full and the character must be retried. Note that Linux currently
+ *	ignores TABDLY, CRDLY, VTDLY, FFDLY and NLDLY. They simply aren't
+ *	relevant in the world today. If you ever need them, add them here.
+ *
+ *	Called from both the receive and transmit sides and can be called
+ *	re-entrantly. Relies on lock_kernel() still.
+ */
+ 
+static int opost(unsigned char c, struct tty_struct *tty)
+{
+	int	space, spaces;
+
+	space = tty->driver->write_room(tty);
+	if (!space)
+		return -1;
+
+	if (O_OPOST(tty)) {
+		switch (c) {
+		case '\n':
+			if (O_ONLRET(tty))
+				tty->column = 0;
+			if (O_ONLCR(tty)) {
+				if (space < 2)
+					return -1;
+				tty->driver->put_char(tty, '\r');
+				tty->column = 0;
+			}
+			tty->canon_column = tty->column;
+			break;
+		case '\r':
+			if (O_ONOCR(tty) && tty->column == 0)
+				return 0;
+			if (O_OCRNL(tty)) {
+				c = '\n';
+				if (O_ONLRET(tty))
+					tty->canon_column = tty->column = 0;
+				break;
+			}
+			tty->canon_column = tty->column = 0;
+			break;
+		case '\t':
+			spaces = 8 - (tty->column & 7);
+			if (O_TABDLY(tty) == XTABS) {
+				if (space < spaces)
+					return -1;
+				tty->column += spaces;
+				tty->driver->write(tty, "        ", spaces);
+				return 0;
+			}
+			tty->column += spaces;
+			break;
+		case '\b':
+			if (tty->column > 0)
+				tty->column--;
+			break;
+		default:
+			if (O_OLCUC(tty))
+				c = toupper(c);
+			if (!iscntrl(c) && !is_continuation(c, tty))
+				tty->column++;
+			break;
+		}
+	}
+	tty->driver->put_char(tty, c);
+	return 0;
+}
+
+/**
+ *	opost_block		-	block postprocess
+ *	@tty: terminal device
+ *	@inbuf: user buffer
+ *	@nr: number of bytes
+ *
+ *	This path is used to speed up block console writes, among other
+ *	things when processing blocks of output data. It handles only
+ *	the simple cases normally found and helps to generate blocks of
+ *	symbols for the console driver and thus improve performance.
+ *
+ *	Called from write_chan under the tty layer write lock.
+ */
+ 
+static ssize_t opost_block(struct tty_struct * tty,
+		       const unsigned char * buf, unsigned int nr)
+{
+	int	space;
+	int 	i;
+	const unsigned char *cp;
+
+	space = tty->driver->write_room(tty);
+	if (!space)
+		return 0;
+	if (nr > space)
+		nr = space;
+
+	for (i = 0, cp = buf; i < nr; i++, cp++) {
+		switch (*cp) {
+		case '\n':
+			if (O_ONLRET(tty))
+				tty->column = 0;
+			if (O_ONLCR(tty))
+				goto break_out;
+			tty->canon_column = tty->column;
+			break;
+		case '\r':
+			if (O_ONOCR(tty) && tty->column == 0)
+				goto break_out;
+			if (O_OCRNL(tty))
+				goto break_out;
+			tty->canon_column = tty->column = 0;
+			break;
+		case '\t':
+			goto break_out;
+		case '\b':
+			if (tty->column > 0)
+				tty->column--;
+			break;
+		default:
+			if (O_OLCUC(tty))
+				goto break_out;
+			if (!iscntrl(*cp))
+				tty->column++;
+			break;
+		}
+	}
+break_out:
+	if (tty->driver->flush_chars)
+		tty->driver->flush_chars(tty);
+	i = tty->driver->write(tty, buf, i);	
+	return i;
+}
+
+
+/**
+ *	put_char	-	write character to driver
+ *	@c: character (or part of unicode symbol)
+ *	@tty: terminal device
+ *
+ *	Queue a byte to the driver layer for output
+ */
+ 
+static inline void put_char(unsigned char c, struct tty_struct *tty)
+{
+	tty->driver->put_char(tty, c);
+}
+
+/**
+ *	echo_char	-	echo characters
+ *	@c: unicode byte to echo
+ *	@tty: terminal device
+ *
+ *	Echo user input back onto the screen. This must be called only when 
+ *	L_ECHO(tty) is true. Called from the driver receive_buf path.
+ */
+
+static void echo_char(unsigned char c, struct tty_struct *tty)
+{
+	if (L_ECHOCTL(tty) && iscntrl(c) && c != '\t') {
+		put_char('^', tty);
+		put_char(c ^ 0100, tty);
+		tty->column += 2;
+	} else
+		opost(c, tty);
+}
+
+static inline void finish_erasing(struct tty_struct *tty)
+{
+	if (tty->erasing) {
+		put_char('/', tty);
+		tty->column++;
+		tty->erasing = 0;
+	}
+}
+
+/**
+ *	eraser		-	handle erase function
+ *	@c: character input
+ *	@tty: terminal device
+ *
+ *	Perform erase and neccessary output when an erase character is
+ *	present in the stream from the driver layer. Handles the complexities
+ *	of UTF-8 multibyte symbols.
+ */
+ 
+static void eraser(unsigned char c, struct tty_struct *tty)
+{
+	enum { ERASE, WERASE, KILL } kill_type;
+	int head, seen_alnums, cnt;
+	unsigned long flags;
+
+	if (tty->read_head == tty->canon_head) {
+		/* opost('\a', tty); */		/* what do you think? */
+		return;
+	}
+	if (c == ERASE_CHAR(tty))
+		kill_type = ERASE;
+	else if (c == WERASE_CHAR(tty))
+		kill_type = WERASE;
+	else {
+		if (!L_ECHO(tty)) {
+			spin_lock_irqsave(&tty->read_lock, flags);
+			tty->read_cnt -= ((tty->read_head - tty->canon_head) &
+					  (N_TTY_BUF_SIZE - 1));
+			tty->read_head = tty->canon_head;
+			spin_unlock_irqrestore(&tty->read_lock, flags);
+			return;
+		}
+		if (!L_ECHOK(tty) || !L_ECHOKE(tty) || !L_ECHOE(tty)) {
+			spin_lock_irqsave(&tty->read_lock, flags);
+			tty->read_cnt -= ((tty->read_head - tty->canon_head) &
+					  (N_TTY_BUF_SIZE - 1));
+			tty->read_head = tty->canon_head;
+			spin_unlock_irqrestore(&tty->read_lock, flags);
+			finish_erasing(tty);
+			echo_char(KILL_CHAR(tty), tty);
+			/* Add a newline if ECHOK is on and ECHOKE is off. */
+			if (L_ECHOK(tty))
+				opost('\n', tty);
+			return;
+		}
+		kill_type = KILL;
+	}
+
+	seen_alnums = 0;
+	while (tty->read_head != tty->canon_head) {
+		head = tty->read_head;
+
+		/* erase a single possibly multibyte character */
+		do {
+			head = (head - 1) & (N_TTY_BUF_SIZE-1);
+			c = tty->read_buf[head];
+		} while (is_continuation(c, tty) && head != tty->canon_head);
+
+		/* do not partially erase */
+		if (is_continuation(c, tty))
+			break;
+
+		if (kill_type == WERASE) {
+			/* Equivalent to BSD's ALTWERASE. */
+			if (isalnum(c) || c == '_')
+				seen_alnums++;
+			else if (seen_alnums)
+				break;
+		}
+		cnt = (tty->read_head - head) & (N_TTY_BUF_SIZE-1);
+		spin_lock_irqsave(&tty->read_lock, flags);
+		tty->read_head = head;
+		tty->read_cnt -= cnt;
+		spin_unlock_irqrestore(&tty->read_lock, flags);
+		if (L_ECHO(tty)) {
+			if (L_ECHOPRT(tty)) {
+				if (!tty->erasing) {
+					put_char('\\', tty);
+					tty->column++;
+					tty->erasing = 1;
+				}
+				/* if cnt > 1, output a multi-byte character */
+				echo_char(c, tty);
+				while (--cnt > 0) {
+					head = (head+1) & (N_TTY_BUF_SIZE-1);
+					put_char(tty->read_buf[head], tty);
+				}
+			} else if (kill_type == ERASE && !L_ECHOE(tty)) {
+				echo_char(ERASE_CHAR(tty), tty);
+			} else if (c == '\t') {
+				unsigned int col = tty->canon_column;
+				unsigned long tail = tty->canon_head;
+
+				/* Find the column of the last char. */
+				while (tail != tty->read_head) {
+					c = tty->read_buf[tail];
+					if (c == '\t')
+						col = (col | 7) + 1;
+					else if (iscntrl(c)) {
+						if (L_ECHOCTL(tty))
+							col += 2;
+					} else if (!is_continuation(c, tty))
+						col++;
+					tail = (tail+1) & (N_TTY_BUF_SIZE-1);
+				}
+
+				/* should never happen */
+				if (tty->column > 0x80000000)
+					tty->column = 0; 
+
+				/* Now backup to that column. */
+				while (tty->column > col) {
+					/* Can't use opost here. */
+					put_char('\b', tty);
+					if (tty->column > 0)
+						tty->column--;
+				}
+			} else {
+				if (iscntrl(c) && L_ECHOCTL(tty)) {
+					put_char('\b', tty);
+					put_char(' ', tty);
+					put_char('\b', tty);
+					if (tty->column > 0)
+						tty->column--;
+				}
+				if (!iscntrl(c) || L_ECHOCTL(tty)) {
+					put_char('\b', tty);
+					put_char(' ', tty);
+					put_char('\b', tty);
+					if (tty->column > 0)
+						tty->column--;
+				}
+			}
+		}
+		if (kill_type == ERASE)
+			break;
+	}
+	if (tty->read_head == tty->canon_head)
+		finish_erasing(tty);
+}
+
+/**
+ *	isig		-	handle the ISIG optio
+ *	@sig: signal
+ *	@tty: terminal
+ *	@flush: force flush
+ *
+ *	Called when a signal is being sent due to terminal input. This
+ *	may caus terminal flushing to take place according to the termios
+ *	settings and character used. Called from the driver receive_buf
+ *	path so serialized.
+ */
+ 
+static inline void isig(int sig, struct tty_struct *tty, int flush)
+{
+	if (tty->pgrp > 0)
+		kill_pg(tty->pgrp, sig, 1);
+	if (flush || !L_NOFLSH(tty)) {
+		n_tty_flush_buffer(tty);
+		if (tty->driver->flush_buffer)
+			tty->driver->flush_buffer(tty);
+	}
+}
+
+/**
+ *	n_tty_receive_break	-	handle break
+ *	@tty: terminal
+ *
+ *	An RS232 break event has been hit in the incoming bitstream. This
+ *	can cause a variety of events depending upon the termios settings.
+ *
+ *	Called from the receive_buf path so single threaded.
+ */
+ 
+static inline void n_tty_receive_break(struct tty_struct *tty)
+{
+	if (I_IGNBRK(tty))
+		return;
+	if (I_BRKINT(tty)) {
+		isig(SIGINT, tty, 1);
+		return;
+	}
+	if (I_PARMRK(tty)) {
+		put_tty_queue('\377', tty);
+		put_tty_queue('\0', tty);
+	}
+	put_tty_queue('\0', tty);
+	wake_up_interruptible(&tty->read_wait);
+}
+
+/**
+ *	n_tty_receive_overrun	-	handle overrun reporting
+ *	@tty: terminal
+ *
+ *	Data arrived faster than we could process it. While the tty
+ *	driver has flagged this the bits that were missed are gone
+ *	forever.
+ *
+ *	Called from the receive_buf path so single threaded. Does not
+ *	need locking as num_overrun and overrun_time are function
+ *	private.
+ */
+ 
+static inline void n_tty_receive_overrun(struct tty_struct *tty)
+{
+	char buf[64];
+
+	tty->num_overrun++;
+	if (time_before(tty->overrun_time, jiffies - HZ) ||
+			time_after(tty->overrun_time, jiffies)) {
+		printk(KERN_WARNING "%s: %d input overrun(s)\n",
+			tty_name(tty, buf),
+			tty->num_overrun);
+		tty->overrun_time = jiffies;
+		tty->num_overrun = 0;
+	}
+}
+
+/**
+ *	n_tty_receive_parity_error	-	error notifier
+ *	@tty: terminal device
+ *	@c: character
+ *
+ *	Process a parity error and queue the right data to indicate
+ *	the error case if neccessary. Locking as per n_tty_receive_buf.
+ */
+static inline void n_tty_receive_parity_error(struct tty_struct *tty,
+					      unsigned char c)
+{
+	if (I_IGNPAR(tty)) {
+		return;
+	}
+	if (I_PARMRK(tty)) {
+		put_tty_queue('\377', tty);
+		put_tty_queue('\0', tty);
+		put_tty_queue(c, tty);
+	} else	if (I_INPCK(tty))
+		put_tty_queue('\0', tty);
+	else
+		put_tty_queue(c, tty);
+	wake_up_interruptible(&tty->read_wait);
+}
+
+/**
+ *	n_tty_receive_char	-	perform processing
+ *	@tty: terminal device
+ *	@c: character
+ *
+ *	Process an individual character of input received from the driver.
+ *	This is serialized with respect to itself by the rules for the 
+ *	driver above.
+ */
+
+static inline void n_tty_receive_char(struct tty_struct *tty, unsigned char c)
+{
+	unsigned long flags;
+
+	if (tty->raw) {
+		put_tty_queue(c, tty);
+		return;
+	}
+	
+	if (tty->stopped && !tty->flow_stopped &&
+	    I_IXON(tty) && I_IXANY(tty)) {
+		start_tty(tty);
+		return;
+	}
+	
+	if (I_ISTRIP(tty))
+		c &= 0x7f;
+	if (I_IUCLC(tty) && L_IEXTEN(tty))
+		c=tolower(c);
+
+	if (tty->closing) {
+		if (I_IXON(tty)) {
+			if (c == START_CHAR(tty))
+				start_tty(tty);
+			else if (c == STOP_CHAR(tty))
+				stop_tty(tty);
+		}
+		return;
+	}
+
+	/*
+	 * If the previous character was LNEXT, or we know that this
+	 * character is not one of the characters that we'll have to
+	 * handle specially, do shortcut processing to speed things
+	 * up.
+	 */
+	if (!test_bit(c, tty->process_char_map) || tty->lnext) {
+		finish_erasing(tty);
+		tty->lnext = 0;
+		if (L_ECHO(tty)) {
+			if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+				put_char('\a', tty); /* beep if no space */
+				return;
+			}
+			/* Record the column of first canon char. */
+			if (tty->canon_head == tty->read_head)
+				tty->canon_column = tty->column;
+			echo_char(c, tty);
+		}
+		if (I_PARMRK(tty) && c == (unsigned char) '\377')
+			put_tty_queue(c, tty);
+		put_tty_queue(c, tty);
+		return;
+	}
+		
+	if (c == '\r') {
+		if (I_IGNCR(tty))
+			return;
+		if (I_ICRNL(tty))
+			c = '\n';
+	} else if (c == '\n' && I_INLCR(tty))
+		c = '\r';
+	if (I_IXON(tty)) {
+		if (c == START_CHAR(tty)) {
+			start_tty(tty);
+			return;
+		}
+		if (c == STOP_CHAR(tty)) {
+			stop_tty(tty);
+			return;
+		}
+	}
+	if (L_ISIG(tty)) {
+		int signal;
+		signal = SIGINT;
+		if (c == INTR_CHAR(tty))
+			goto send_signal;
+		signal = SIGQUIT;
+		if (c == QUIT_CHAR(tty))
+			goto send_signal;
+		signal = SIGTSTP;
+		if (c == SUSP_CHAR(tty)) {
+send_signal:
+			isig(signal, tty, 0);
+			return;
+		}
+	}
+	if (tty->icanon) {
+		if (c == ERASE_CHAR(tty) || c == KILL_CHAR(tty) ||
+		    (c == WERASE_CHAR(tty) && L_IEXTEN(tty))) {
+			eraser(c, tty);
+			return;
+		}
+		if (c == LNEXT_CHAR(tty) && L_IEXTEN(tty)) {
+			tty->lnext = 1;
+			if (L_ECHO(tty)) {
+				finish_erasing(tty);
+				if (L_ECHOCTL(tty)) {
+					put_char('^', tty);
+					put_char('\b', tty);
+				}
+			}
+			return;
+		}
+		if (c == REPRINT_CHAR(tty) && L_ECHO(tty) &&
+		    L_IEXTEN(tty)) {
+			unsigned long tail = tty->canon_head;
+
+			finish_erasing(tty);
+			echo_char(c, tty);
+			opost('\n', tty);
+			while (tail != tty->read_head) {
+				echo_char(tty->read_buf[tail], tty);
+				tail = (tail+1) & (N_TTY_BUF_SIZE-1);
+			}
+			return;
+		}
+		if (c == '\n') {
+			if (L_ECHO(tty) || L_ECHONL(tty)) {
+				if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+					put_char('\a', tty);
+					return;
+				}
+				opost('\n', tty);
+			}
+			goto handle_newline;
+		}
+		if (c == EOF_CHAR(tty)) {
+		        if (tty->canon_head != tty->read_head)
+			        set_bit(TTY_PUSH, &tty->flags);
+			c = __DISABLED_CHAR;
+			goto handle_newline;
+		}
+		if ((c == EOL_CHAR(tty)) ||
+		    (c == EOL2_CHAR(tty) && L_IEXTEN(tty))) {
+			/*
+			 * XXX are EOL_CHAR and EOL2_CHAR echoed?!?
+			 */
+			if (L_ECHO(tty)) {
+				if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+					put_char('\a', tty);
+					return;
+				}
+				/* Record the column of first canon char. */
+				if (tty->canon_head == tty->read_head)
+					tty->canon_column = tty->column;
+				echo_char(c, tty);
+			}
+			/*
+			 * XXX does PARMRK doubling happen for
+			 * EOL_CHAR and EOL2_CHAR?
+			 */
+			if (I_PARMRK(tty) && c == (unsigned char) '\377')
+				put_tty_queue(c, tty);
+
+		handle_newline:
+			spin_lock_irqsave(&tty->read_lock, flags);
+			set_bit(tty->read_head, tty->read_flags);
+			put_tty_queue_nolock(c, tty);
+			tty->canon_head = tty->read_head;
+			tty->canon_data++;
+			spin_unlock_irqrestore(&tty->read_lock, flags);
+			kill_fasync(&tty->fasync, SIGIO, POLL_IN);
+			if (waitqueue_active(&tty->read_wait))
+				wake_up_interruptible(&tty->read_wait);
+			return;
+		}
+	}
+	
+	finish_erasing(tty);
+	if (L_ECHO(tty)) {
+		if (tty->read_cnt >= N_TTY_BUF_SIZE-1) {
+			put_char('\a', tty); /* beep if no space */
+			return;
+		}
+		if (c == '\n')
+			opost('\n', tty);
+		else {
+			/* Record the column of first canon char. */
+			if (tty->canon_head == tty->read_head)
+				tty->canon_column = tty->column;
+			echo_char(c, tty);
+		}
+	}
+
+	if (I_PARMRK(tty) && c == (unsigned char) '\377')
+		put_tty_queue(c, tty);
+
+	put_tty_queue(c, tty);
+}	
+
+/**
+ *	n_tty_receive_room	-	receive space
+ *	@tty: terminal
+ *
+ *	Called by the driver to find out how much data it is
+ *	permitted to feed to the line discipline without any being lost
+ *	and thus to manage flow control. Not serialized. Answers for the
+ *	"instant".
+ */
+ 
+static int n_tty_receive_room(struct tty_struct *tty)
+{
+	int	left = N_TTY_BUF_SIZE - tty->read_cnt - 1;
+
+	/*
+	 * If we are doing input canonicalization, and there are no
+	 * pending newlines, let characters through without limit, so
+	 * that erase characters will be handled.  Other excess
+	 * characters will be beeped.
+	 */
+	if (tty->icanon && !tty->canon_data)
+		return N_TTY_BUF_SIZE;
+
+	if (left > 0)
+		return left;
+	return 0;
+}
+
+/**
+ *	n_tty_write_wakeup	-	asynchronous I/O notifier
+ *	@tty: tty device
+ *
+ *	Required for the ptys, serial driver etc. since processes
+ *	that attach themselves to the master and rely on ASYNC
+ *	IO must be woken up
+ */
+
+static void n_tty_write_wakeup(struct tty_struct *tty)
+{
+	if (tty->fasync)
+	{
+ 		set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+		kill_fasync(&tty->fasync, SIGIO, POLL_OUT);
+	}
+	return;
+}
+
+/**
+ *	n_tty_receive_buf	-	data receive
+ *	@tty: terminal device
+ *	@cp: buffer
+ *	@fp: flag buffer
+ *	@count: characters
+ *
+ *	Called by the terminal driver when a block of characters has
+ *	been received. This function must be called from soft contexts
+ *	not from interrupt context. The driver is responsible for making
+ *	calls one at a time and in order (or using flush_to_ldisc)
+ */
+ 
+static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp,
+			      char *fp, int count)
+{
+	const unsigned char *p;
+	char *f, flags = TTY_NORMAL;
+	int	i;
+	char	buf[64];
+	unsigned long cpuflags;
+
+	if (!tty->read_buf)
+		return;
+
+	if (tty->real_raw) {
+		spin_lock_irqsave(&tty->read_lock, cpuflags);
+		i = min(N_TTY_BUF_SIZE - tty->read_cnt,
+			N_TTY_BUF_SIZE - tty->read_head);
+		i = min(count, i);
+		memcpy(tty->read_buf + tty->read_head, cp, i);
+		tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
+		tty->read_cnt += i;
+		cp += i;
+		count -= i;
+
+		i = min(N_TTY_BUF_SIZE - tty->read_cnt,
+			N_TTY_BUF_SIZE - tty->read_head);
+		i = min(count, i);
+		memcpy(tty->read_buf + tty->read_head, cp, i);
+		tty->read_head = (tty->read_head + i) & (N_TTY_BUF_SIZE-1);
+		tty->read_cnt += i;
+		spin_unlock_irqrestore(&tty->read_lock, cpuflags);
+	} else {
+		for (i=count, p = cp, f = fp; i; i--, p++) {
+			if (f)
+				flags = *f++;
+			switch (flags) {
+			case TTY_NORMAL:
+				n_tty_receive_char(tty, *p);
+				break;
+			case TTY_BREAK:
+				n_tty_receive_break(tty);
+				break;
+			case TTY_PARITY:
+			case TTY_FRAME:
+				n_tty_receive_parity_error(tty, *p);
+				break;
+			case TTY_OVERRUN:
+				n_tty_receive_overrun(tty);
+				break;
+			default:
+				printk("%s: unknown flag %d\n",
+				       tty_name(tty, buf), flags);
+				break;
+			}
+		}
+		if (tty->driver->flush_chars)
+			tty->driver->flush_chars(tty);
+	}
+
+	if (!tty->icanon && (tty->read_cnt >= tty->minimum_to_wake)) {
+		kill_fasync(&tty->fasync, SIGIO, POLL_IN);
+		if (waitqueue_active(&tty->read_wait))
+			wake_up_interruptible(&tty->read_wait);
+	}
+
+	/*
+	 * Check the remaining room for the input canonicalization
+	 * mode.  We don't want to throttle the driver if we're in
+	 * canonical mode and don't have a newline yet!
+	 */
+	if (n_tty_receive_room(tty) < TTY_THRESHOLD_THROTTLE) {
+		/* check TTY_THROTTLED first so it indicates our state */
+		if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) &&
+		    tty->driver->throttle)
+			tty->driver->throttle(tty);
+	}
+}
+
+int is_ignored(int sig)
+{
+	return (sigismember(&current->blocked, sig) ||
+	        current->sighand->action[sig-1].sa.sa_handler == SIG_IGN);
+}
+
+/**
+ *	n_tty_set_termios	-	termios data changed
+ *	@tty: terminal
+ *	@old: previous data
+ *
+ *	Called by the tty layer when the user changes termios flags so
+ *	that the line discipline can plan ahead. This function cannot sleep
+ *	and is protected from re-entry by the tty layer. The user is 
+ *	guaranteed that this function will not be re-entered or in progress
+ *	when the ldisc is closed.
+ */
+ 
+static void n_tty_set_termios(struct tty_struct *tty, struct termios * old)
+{
+	if (!tty)
+		return;
+	
+	tty->icanon = (L_ICANON(tty) != 0);
+	if (test_bit(TTY_HW_COOK_IN, &tty->flags)) {
+		tty->raw = 1;
+		tty->real_raw = 1;
+		return;
+	}
+	if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) ||
+	    I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) ||
+	    I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) ||
+	    I_PARMRK(tty)) {
+		memset(tty->process_char_map, 0, 256/8);
+
+		if (I_IGNCR(tty) || I_ICRNL(tty))
+			set_bit('\r', tty->process_char_map);
+		if (I_INLCR(tty))
+			set_bit('\n', tty->process_char_map);
+
+		if (L_ICANON(tty)) {
+			set_bit(ERASE_CHAR(tty), tty->process_char_map);
+			set_bit(KILL_CHAR(tty), tty->process_char_map);
+			set_bit(EOF_CHAR(tty), tty->process_char_map);
+			set_bit('\n', tty->process_char_map);
+			set_bit(EOL_CHAR(tty), tty->process_char_map);
+			if (L_IEXTEN(tty)) {
+				set_bit(WERASE_CHAR(tty),
+					tty->process_char_map);
+				set_bit(LNEXT_CHAR(tty),
+					tty->process_char_map);
+				set_bit(EOL2_CHAR(tty),
+					tty->process_char_map);
+				if (L_ECHO(tty))
+					set_bit(REPRINT_CHAR(tty),
+						tty->process_char_map);
+			}
+		}
+		if (I_IXON(tty)) {
+			set_bit(START_CHAR(tty), tty->process_char_map);
+			set_bit(STOP_CHAR(tty), tty->process_char_map);
+		}
+		if (L_ISIG(tty)) {
+			set_bit(INTR_CHAR(tty), tty->process_char_map);
+			set_bit(QUIT_CHAR(tty), tty->process_char_map);
+			set_bit(SUSP_CHAR(tty), tty->process_char_map);
+		}
+		clear_bit(__DISABLED_CHAR, tty->process_char_map);
+		tty->raw = 0;
+		tty->real_raw = 0;
+	} else {
+		tty->raw = 1;
+		if ((I_IGNBRK(tty) || (!I_BRKINT(tty) && !I_PARMRK(tty))) &&
+		    (I_IGNPAR(tty) || !I_INPCK(tty)) &&
+		    (tty->driver->flags & TTY_DRIVER_REAL_RAW))
+			tty->real_raw = 1;
+		else
+			tty->real_raw = 0;
+	}
+}
+
+/**
+ *	n_tty_close		-	close the ldisc for this tty
+ *	@tty: device
+ *
+ *	Called from the terminal layer when this line discipline is 
+ *	being shut down, either because of a close or becsuse of a 
+ *	discipline change. The function will not be called while other
+ *	ldisc methods are in progress.
+ */
+ 
+static void n_tty_close(struct tty_struct *tty)
+{
+	n_tty_flush_buffer(tty);
+	if (tty->read_buf) {
+		free_buf(tty->read_buf);
+		tty->read_buf = NULL;
+	}
+}
+
+/**
+ *	n_tty_open		-	open an ldisc
+ *	@tty: terminal to open
+ *
+ *	Called when this line discipline is being attached to the 
+ *	terminal device. Can sleep. Called serialized so that no
+ *	other events will occur in parallel. No further open will occur
+ *	until a close.
+ */
+
+static int n_tty_open(struct tty_struct *tty)
+{
+	if (!tty)
+		return -EINVAL;
+
+	/* This one is ugly. Currently a malloc failure here can panic */
+	if (!tty->read_buf) {
+		tty->read_buf = alloc_buf();
+		if (!tty->read_buf)
+			return -ENOMEM;
+	}
+	memset(tty->read_buf, 0, N_TTY_BUF_SIZE);
+	reset_buffer_flags(tty);
+	tty->column = 0;
+	n_tty_set_termios(tty, NULL);
+	tty->minimum_to_wake = 1;
+	tty->closing = 0;
+	return 0;
+}
+
+static inline int input_available_p(struct tty_struct *tty, int amt)
+{
+	if (tty->icanon) {
+		if (tty->canon_data)
+			return 1;
+	} else if (tty->read_cnt >= (amt ? amt : 1))
+		return 1;
+
+	return 0;
+}
+
+/**
+ * 	copy_from_read_buf	-	copy read data directly
+ *	@tty: terminal device
+ *	@b: user data
+ *	@nr: size of data
+ *
+ *	Helper function to speed up read_chan.  It is only called when
+ *	ICANON is off; it copies characters straight from the tty queue to
+ *	user space directly.  It can be profitably called twice; once to
+ *	drain the space from the tail pointer to the (physical) end of the
+ *	buffer, and once to drain the space from the (physical) beginning of
+ *	the buffer to head pointer.
+ *
+ *	Called under the tty->atomic_read sem and with TTY_DONT_FLIP set
+ *
+ */
+ 
+static inline int copy_from_read_buf(struct tty_struct *tty,
+				      unsigned char __user **b,
+				      size_t *nr)
+
+{
+	int retval;
+	size_t n;
+	unsigned long flags;
+
+	retval = 0;
+	spin_lock_irqsave(&tty->read_lock, flags);
+	n = min(tty->read_cnt, N_TTY_BUF_SIZE - tty->read_tail);
+	n = min(*nr, n);
+	spin_unlock_irqrestore(&tty->read_lock, flags);
+	if (n) {
+		mb();
+		retval = copy_to_user(*b, &tty->read_buf[tty->read_tail], n);
+		n -= retval;
+		spin_lock_irqsave(&tty->read_lock, flags);
+		tty->read_tail = (tty->read_tail + n) & (N_TTY_BUF_SIZE-1);
+		tty->read_cnt -= n;
+		spin_unlock_irqrestore(&tty->read_lock, flags);
+		*b += n;
+		*nr -= n;
+	}
+	return retval;
+}
+
+extern ssize_t redirected_tty_write(struct file *,const char *,size_t,loff_t *);
+
+/**
+ *	job_control		-	check job control
+ *	@tty: tty
+ *	@file: file handle
+ *
+ *	Perform job control management checks on this file/tty descriptor
+ *	and if appropriate send any needed signals and return a negative 
+ *	error code if action should be taken.
+ */
+ 
+static int job_control(struct tty_struct *tty, struct file *file)
+{
+	/* Job control check -- must be done at start and after
+	   every sleep (POSIX.1 7.1.1.4). */
+	/* NOTE: not yet done after every sleep pending a thorough
+	   check of the logic of this change. -- jlc */
+	/* don't stop on /dev/console */
+	if (file->f_op->write != redirected_tty_write &&
+	    current->signal->tty == tty) {
+		if (tty->pgrp <= 0)
+			printk("read_chan: tty->pgrp <= 0!\n");
+		else if (process_group(current) != tty->pgrp) {
+			if (is_ignored(SIGTTIN) ||
+			    is_orphaned_pgrp(process_group(current)))
+				return -EIO;
+			kill_pg(process_group(current), SIGTTIN, 1);
+			return -ERESTARTSYS;
+		}
+	}
+	return 0;
+}
+ 
+
+/**
+ *	read_chan		-	read function for tty
+ *	@tty: tty device
+ *	@file: file object
+ *	@buf: userspace buffer pointer
+ *	@nr: size of I/O
+ *
+ *	Perform reads for the line discipline. We are guaranteed that the
+ *	line discipline will not be closed under us but we may get multiple
+ *	parallel readers and must handle this ourselves. We may also get
+ *	a hangup. Always called in user context, may sleep.
+ *
+ *	This code must be sure never to sleep through a hangup.
+ */
+ 
+static ssize_t read_chan(struct tty_struct *tty, struct file *file,
+			 unsigned char __user *buf, size_t nr)
+{
+	unsigned char __user *b = buf;
+	DECLARE_WAITQUEUE(wait, current);
+	int c;
+	int minimum, time;
+	ssize_t retval = 0;
+	ssize_t size;
+	long timeout;
+	unsigned long flags;
+
+do_it_again:
+
+	if (!tty->read_buf) {
+		printk("n_tty_read_chan: called with read_buf == NULL?!?\n");
+		return -EIO;
+	}
+
+	c = job_control(tty, file);
+	if(c < 0)
+		return c;
+	
+	minimum = time = 0;
+	timeout = MAX_SCHEDULE_TIMEOUT;
+	if (!tty->icanon) {
+		time = (HZ / 10) * TIME_CHAR(tty);
+		minimum = MIN_CHAR(tty);
+		if (minimum) {
+			if (time)
+				tty->minimum_to_wake = 1;
+			else if (!waitqueue_active(&tty->read_wait) ||
+				 (tty->minimum_to_wake > minimum))
+				tty->minimum_to_wake = minimum;
+		} else {
+			timeout = 0;
+			if (time) {
+				timeout = time;
+				time = 0;
+			}
+			tty->minimum_to_wake = minimum = 1;
+		}
+	}
+
+	/*
+	 *	Internal serialization of reads.
+	 */
+	if (file->f_flags & O_NONBLOCK) {
+		if (down_trylock(&tty->atomic_read))
+			return -EAGAIN;
+	}
+	else {
+		if (down_interruptible(&tty->atomic_read))
+			return -ERESTARTSYS;
+	}
+
+	add_wait_queue(&tty->read_wait, &wait);
+	set_bit(TTY_DONT_FLIP, &tty->flags);
+	while (nr) {
+		/* First test for status change. */
+		if (tty->packet && tty->link->ctrl_status) {
+			unsigned char cs;
+			if (b != buf)
+				break;
+			cs = tty->link->ctrl_status;
+			tty->link->ctrl_status = 0;
+			if (put_user(cs, b++)) {
+				retval = -EFAULT;
+				b--;
+				break;
+			}
+			nr--;
+			break;
+		}
+		/* This statement must be first before checking for input
+		   so that any interrupt will set the state back to
+		   TASK_RUNNING. */
+		set_current_state(TASK_INTERRUPTIBLE);
+		
+		if (((minimum - (b - buf)) < tty->minimum_to_wake) &&
+		    ((minimum - (b - buf)) >= 1))
+			tty->minimum_to_wake = (minimum - (b - buf));
+		
+		if (!input_available_p(tty, 0)) {
+			if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) {
+				retval = -EIO;
+				break;
+			}
+			if (tty_hung_up_p(file))
+				break;
+			if (!timeout)
+				break;
+			if (file->f_flags & O_NONBLOCK) {
+				retval = -EAGAIN;
+				break;
+			}
+			if (signal_pending(current)) {
+				retval = -ERESTARTSYS;
+				break;
+			}
+			clear_bit(TTY_DONT_FLIP, &tty->flags);
+			timeout = schedule_timeout(timeout);
+			set_bit(TTY_DONT_FLIP, &tty->flags);
+			continue;
+		}
+		__set_current_state(TASK_RUNNING);
+
+		/* Deal with packet mode. */
+		if (tty->packet && b == buf) {
+			if (put_user(TIOCPKT_DATA, b++)) {
+				retval = -EFAULT;
+				b--;
+				break;
+			}
+			nr--;
+		}
+
+		if (tty->icanon) {
+			/* N.B. avoid overrun if nr == 0 */
+			while (nr && tty->read_cnt) {
+ 				int eol;
+
+				eol = test_and_clear_bit(tty->read_tail,
+						tty->read_flags);
+				c = tty->read_buf[tty->read_tail];
+				spin_lock_irqsave(&tty->read_lock, flags);
+				tty->read_tail = ((tty->read_tail+1) &
+						  (N_TTY_BUF_SIZE-1));
+				tty->read_cnt--;
+				if (eol) {
+					/* this test should be redundant:
+					 * we shouldn't be reading data if
+					 * canon_data is 0
+					 */
+					if (--tty->canon_data < 0)
+						tty->canon_data = 0;
+				}
+				spin_unlock_irqrestore(&tty->read_lock, flags);
+
+				if (!eol || (c != __DISABLED_CHAR)) {
+					if (put_user(c, b++)) {
+						retval = -EFAULT;
+						b--;
+						break;
+					}
+					nr--;
+				}
+				if (eol)
+					break;
+			}
+			if (retval)
+				break;
+		} else {
+			int uncopied;
+			uncopied = copy_from_read_buf(tty, &b, &nr);
+			uncopied += copy_from_read_buf(tty, &b, &nr);
+			if (uncopied) {
+				retval = -EFAULT;
+				break;
+			}
+		}
+
+		/* If there is enough space in the read buffer now, let the
+		 * low-level driver know. We use n_tty_chars_in_buffer() to
+		 * check the buffer, as it now knows about canonical mode.
+		 * Otherwise, if the driver is throttled and the line is
+		 * longer than TTY_THRESHOLD_UNTHROTTLE in canonical mode,
+		 * we won't get any more characters.
+		 */
+		if (n_tty_chars_in_buffer(tty) <= TTY_THRESHOLD_UNTHROTTLE)
+			check_unthrottle(tty);
+
+		if (b - buf >= minimum)
+			break;
+		if (time)
+			timeout = time;
+	}
+	clear_bit(TTY_DONT_FLIP, &tty->flags);
+	up(&tty->atomic_read);
+	remove_wait_queue(&tty->read_wait, &wait);
+
+	if (!waitqueue_active(&tty->read_wait))
+		tty->minimum_to_wake = minimum;
+
+	__set_current_state(TASK_RUNNING);
+	size = b - buf;
+	if (size) {
+		retval = size;
+		if (nr)
+	       		clear_bit(TTY_PUSH, &tty->flags);
+	} else if (test_and_clear_bit(TTY_PUSH, &tty->flags))
+		 goto do_it_again;
+
+	return retval;
+}
+
+/**
+ *	write_chan		-	write function for tty
+ *	@tty: tty device
+ *	@file: file object
+ *	@buf: userspace buffer pointer
+ *	@nr: size of I/O
+ *
+ *	Write function of the terminal device. This is serialized with
+ *	respect to other write callers but not to termios changes, reads
+ *	and other such events. We must be careful with N_TTY as the receive
+ *	code will echo characters, thus calling driver write methods.
+ *
+ *	This code must be sure never to sleep through a hangup.
+ */
+ 
+static ssize_t write_chan(struct tty_struct * tty, struct file * file,
+			  const unsigned char * buf, size_t nr)
+{
+	const unsigned char *b = buf;
+	DECLARE_WAITQUEUE(wait, current);
+	int c;
+	ssize_t retval = 0;
+
+	/* Job control check -- must be done at start (POSIX.1 7.1.1.4). */
+	if (L_TOSTOP(tty) && file->f_op->write != redirected_tty_write) {
+		retval = tty_check_change(tty);
+		if (retval)
+			return retval;
+	}
+
+	add_wait_queue(&tty->write_wait, &wait);
+	while (1) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		if (tty_hung_up_p(file) || (tty->link && !tty->link->count)) {
+			retval = -EIO;
+			break;
+		}
+		if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) {
+			while (nr > 0) {
+				ssize_t num = opost_block(tty, b, nr);
+				if (num < 0) {
+					if (num == -EAGAIN)
+						break;
+					retval = num;
+					goto break_out;
+				}
+				b += num;
+				nr -= num;
+				if (nr == 0)
+					break;
+				c = *b;
+				if (opost(c, tty) < 0)
+					break;
+				b++; nr--;
+			}
+			if (tty->driver->flush_chars)
+				tty->driver->flush_chars(tty);
+		} else {
+			c = tty->driver->write(tty, b, nr);
+			if (c < 0) {
+				retval = c;
+				goto break_out;
+			}
+			b += c;
+			nr -= c;
+		}
+		if (!nr)
+			break;
+		if (file->f_flags & O_NONBLOCK) {
+			retval = -EAGAIN;
+			break;
+		}
+		schedule();
+	}
+break_out:
+	__set_current_state(TASK_RUNNING);
+	remove_wait_queue(&tty->write_wait, &wait);
+	return (b - buf) ? b - buf : retval;
+}
+
+/**
+ *	normal_poll		-	poll method for N_TTY
+ *	@tty: terminal device
+ *	@file: file accessing it
+ *	@wait: poll table
+ *
+ *	Called when the line discipline is asked to poll() for data or
+ *	for special events. This code is not serialized with respect to
+ *	other events save open/close.
+ *
+ *	This code must be sure never to sleep through a hangup.
+ *	Called without the kernel lock held - fine
+ *
+ *	FIXME: if someone changes the VMIN or discipline settings for the
+ *	terminal while another process is in poll() the poll does not
+ *	recompute the new limits. Possibly set_termios should issue
+ *	a read wakeup to fix this bug.
+ */
+ 
+static unsigned int normal_poll(struct tty_struct * tty, struct file * file, poll_table *wait)
+{
+	unsigned int mask = 0;
+
+	poll_wait(file, &tty->read_wait, wait);
+	poll_wait(file, &tty->write_wait, wait);
+	if (input_available_p(tty, TIME_CHAR(tty) ? 0 : MIN_CHAR(tty)))
+		mask |= POLLIN | POLLRDNORM;
+	if (tty->packet && tty->link->ctrl_status)
+		mask |= POLLPRI | POLLIN | POLLRDNORM;
+	if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+		mask |= POLLHUP;
+	if (tty_hung_up_p(file))
+		mask |= POLLHUP;
+	if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) {
+		if (MIN_CHAR(tty) && !TIME_CHAR(tty))
+			tty->minimum_to_wake = MIN_CHAR(tty);
+		else
+			tty->minimum_to_wake = 1;
+	}
+	if (tty->driver->chars_in_buffer(tty) < WAKEUP_CHARS &&
+			tty->driver->write_room(tty) > 0)
+		mask |= POLLOUT | POLLWRNORM;
+	return mask;
+}
+
+struct tty_ldisc tty_ldisc_N_TTY = {
+	TTY_LDISC_MAGIC,	/* magic */
+	"n_tty",		/* name */
+	0,			/* num */
+	0,			/* flags */
+	n_tty_open,		/* open */
+	n_tty_close,		/* close */
+	n_tty_flush_buffer,	/* flush_buffer */
+	n_tty_chars_in_buffer,	/* chars_in_buffer */
+	read_chan,		/* read */
+	write_chan,		/* write */
+	n_tty_ioctl,		/* ioctl */
+	n_tty_set_termios,	/* set_termios */
+	normal_poll,		/* poll */
+	NULL,			/* hangup */
+	n_tty_receive_buf,	/* receive_buf */
+	n_tty_receive_room,	/* receive_room */
+	n_tty_write_wakeup	/* write_wakeup */
+};
+
diff --git a/drivers/char/nvram.c b/drivers/char/nvram.c
new file mode 100644
index 0000000..f63a3fd
--- /dev/null
+++ b/drivers/char/nvram.c
@@ -0,0 +1,926 @@
+/*
+ * CMOS/NV-RAM driver for Linux
+ *
+ * Copyright (C) 1997 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+ * idea by and with help from Richard Jelinek <rj@suse.de>
+ * Portions copyright (c) 2001,2002 Sun Microsystems (thockin@sun.com)
+ *
+ * This driver allows you to access the contents of the non-volatile memory in
+ * the mc146818rtc.h real-time clock. This chip is built into all PCs and into
+ * many Atari machines. In the former it's called "CMOS-RAM", in the latter
+ * "NVRAM" (NV stands for non-volatile).
+ *
+ * The data are supplied as a (seekable) character device, /dev/nvram. The
+ * size of this file is dependent on the controller.  The usual size is 114,
+ * the number of freely available bytes in the memory (i.e., not used by the
+ * RTC itself).
+ *
+ * Checksums over the NVRAM contents are managed by this driver. In case of a
+ * bad checksum, reads and writes return -EIO. The checksum can be initialized
+ * to a sane state either by ioctl(NVRAM_INIT) (clear whole NVRAM) or
+ * ioctl(NVRAM_SETCKS) (doesn't change contents, just makes checksum valid
+ * again; use with care!)
+ *
+ * This file also provides some functions for other parts of the kernel that
+ * want to access the NVRAM: nvram_{read,write,check_checksum,set_checksum}.
+ * Obviously this can be used only if this driver is always configured into
+ * the kernel and is not a module. Since the functions are used by some Atari
+ * drivers, this is the case on the Atari.
+ *
+ *
+ * 	1.1	Cesar Barros: SMP locking fixes
+ * 		added changelog
+ * 	1.2	Erik Gilling: Cobalt Networks support
+ * 		Tim Hockin: general cleanup, Cobalt support
+ */
+
+#define NVRAM_VERSION	"1.2"
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <linux/nvram.h>
+
+#define PC		1
+#define ATARI		2
+#define COBALT		3
+
+/* select machine configuration */
+#if defined(CONFIG_ATARI)
+#  define MACH ATARI
+#elif defined(__i386__) || defined(__x86_64__) || defined(__arm__)  /* and others?? */
+#define MACH PC
+#  if defined(CONFIG_COBALT)
+#    include <linux/cobalt-nvram.h>
+#    define MACH COBALT
+#  else
+#    define MACH PC
+#  endif
+#else
+#  error Cannot build nvram driver for this machine configuration.
+#endif
+
+#if MACH == PC
+
+/* RTC in a PC */
+#define CHECK_DRIVER_INIT()	1
+
+/* On PCs, the checksum is built only over bytes 2..31 */
+#define PC_CKS_RANGE_START	2
+#define PC_CKS_RANGE_END	31
+#define PC_CKS_LOC		32
+#define NVRAM_BYTES		(128-NVRAM_FIRST_BYTE)
+
+#define mach_check_checksum	pc_check_checksum
+#define mach_set_checksum	pc_set_checksum
+#define mach_proc_infos		pc_proc_infos
+
+#endif
+
+#if MACH == COBALT
+
+#define CHECK_DRIVER_INIT()     1
+
+#define NVRAM_BYTES		(128-NVRAM_FIRST_BYTE)
+
+#define mach_check_checksum	cobalt_check_checksum
+#define mach_set_checksum	cobalt_set_checksum
+#define mach_proc_infos		cobalt_proc_infos
+
+#endif
+
+#if MACH == ATARI
+
+/* Special parameters for RTC in Atari machines */
+#include <asm/atarihw.h>
+#include <asm/atariints.h>
+#define RTC_PORT(x)		(TT_RTC_BAS + 2*(x))
+#define CHECK_DRIVER_INIT()	(MACH_IS_ATARI && ATARIHW_PRESENT(TT_CLK))
+
+#define NVRAM_BYTES		50
+
+/* On Ataris, the checksum is over all bytes except the checksum bytes
+ * themselves; these are at the very end */
+#define ATARI_CKS_RANGE_START	0
+#define ATARI_CKS_RANGE_END	47
+#define ATARI_CKS_LOC		48
+
+#define mach_check_checksum	atari_check_checksum
+#define mach_set_checksum	atari_set_checksum
+#define mach_proc_infos		atari_proc_infos
+
+#endif
+
+/* Note that *all* calls to CMOS_READ and CMOS_WRITE must be done with
+ * rtc_lock held. Due to the index-port/data-port design of the RTC, we
+ * don't want two different things trying to get to it at once. (e.g. the
+ * periodic 11 min sync from time.c vs. this driver.)
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/mc146818rtc.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+static DEFINE_SPINLOCK(nvram_state_lock);
+static int nvram_open_cnt;	/* #times opened */
+static int nvram_open_mode;	/* special open modes */
+#define NVRAM_WRITE		1 /* opened for writing (exclusive) */
+#define NVRAM_EXCL		2 /* opened with O_EXCL */
+
+static int mach_check_checksum(void);
+static void mach_set_checksum(void);
+
+#ifdef CONFIG_PROC_FS
+static int mach_proc_infos(unsigned char *contents, char *buffer, int *len,
+    off_t *begin, off_t offset, int size);
+#endif
+
+/*
+ * These functions are provided to be called internally or by other parts of
+ * the kernel. It's up to the caller to ensure correct checksum before reading
+ * or after writing (needs to be done only once).
+ *
+ * It is worth noting that these functions all access bytes of general
+ * purpose memory in the NVRAM - that is to say, they all add the
+ * NVRAM_FIRST_BYTE offset.  Pass them offsets into NVRAM as if you did not 
+ * know about the RTC cruft.
+ */
+
+unsigned char
+__nvram_read_byte(int i)
+{
+	return CMOS_READ(NVRAM_FIRST_BYTE + i);
+}
+
+unsigned char
+nvram_read_byte(int i)
+{
+	unsigned long flags;
+	unsigned char c;
+
+	spin_lock_irqsave(&rtc_lock, flags);
+	c = __nvram_read_byte(i);
+	spin_unlock_irqrestore(&rtc_lock, flags);
+	return c;
+}
+
+/* This races nicely with trying to read with checksum checking (nvram_read) */
+void
+__nvram_write_byte(unsigned char c, int i)
+{
+	CMOS_WRITE(c, NVRAM_FIRST_BYTE + i);
+}
+
+void
+nvram_write_byte(unsigned char c, int i)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&rtc_lock, flags);
+	__nvram_write_byte(c, i);
+	spin_unlock_irqrestore(&rtc_lock, flags);
+}
+
+int
+__nvram_check_checksum(void)
+{
+	return mach_check_checksum();
+}
+
+int
+nvram_check_checksum(void)
+{
+	unsigned long flags;
+	int rv;
+
+	spin_lock_irqsave(&rtc_lock, flags);
+	rv = __nvram_check_checksum();
+	spin_unlock_irqrestore(&rtc_lock, flags);
+	return rv;
+}
+
+void
+__nvram_set_checksum(void)
+{
+	mach_set_checksum();
+}
+
+void
+nvram_set_checksum(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&rtc_lock, flags);
+	__nvram_set_checksum();
+	spin_unlock_irqrestore(&rtc_lock, flags);
+}
+
+/*
+ * The are the file operation function for user access to /dev/nvram
+ */
+
+static loff_t nvram_llseek(struct file *file,loff_t offset, int origin )
+{
+	lock_kernel();
+	switch (origin) {
+	case 0:
+		/* nothing to do */
+		break;
+	case 1:
+		offset += file->f_pos;
+		break;
+	case 2:
+		offset += NVRAM_BYTES;
+		break;
+	}
+	unlock_kernel();
+	return (offset >= 0) ? (file->f_pos = offset) : -EINVAL;
+}
+
+static ssize_t
+nvram_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	unsigned char contents[NVRAM_BYTES];
+	unsigned i = *ppos;
+	unsigned char *tmp;
+
+	spin_lock_irq(&rtc_lock);
+
+	if (!__nvram_check_checksum())
+		goto checksum_err;
+
+	for (tmp = contents; count-- > 0 && i < NVRAM_BYTES; ++i, ++tmp)
+		*tmp = __nvram_read_byte(i);
+
+	spin_unlock_irq(&rtc_lock);
+
+	if (copy_to_user(buf, contents, tmp - contents))
+		return -EFAULT;
+
+	*ppos = i;
+
+	return tmp - contents;
+
+      checksum_err:
+	spin_unlock_irq(&rtc_lock);
+	return -EIO;
+}
+
+static ssize_t
+nvram_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	unsigned char contents[NVRAM_BYTES];
+	unsigned i = *ppos;
+	unsigned char *tmp;
+	int len;
+
+	len = (NVRAM_BYTES - i) < count ? (NVRAM_BYTES - i) : count;
+	if (copy_from_user(contents, buf, len))
+		return -EFAULT;
+
+	spin_lock_irq(&rtc_lock);
+
+	if (!__nvram_check_checksum())
+		goto checksum_err;
+
+	for (tmp = contents; count-- > 0 && i < NVRAM_BYTES; ++i, ++tmp)
+		__nvram_write_byte(*tmp, i);
+
+	__nvram_set_checksum();
+
+	spin_unlock_irq(&rtc_lock);
+
+	*ppos = i;
+
+	return tmp - contents;
+
+      checksum_err:
+	spin_unlock_irq(&rtc_lock);
+	return -EIO;
+}
+
+static int
+nvram_ioctl(struct inode *inode, struct file *file,
+    unsigned int cmd, unsigned long arg)
+{
+	int i;
+
+	switch (cmd) {
+
+	case NVRAM_INIT:
+		/* initialize NVRAM contents and checksum */
+		if (!capable(CAP_SYS_ADMIN))
+			return -EACCES;
+
+		spin_lock_irq(&rtc_lock);
+
+		for (i = 0; i < NVRAM_BYTES; ++i)
+			__nvram_write_byte(0, i);
+		__nvram_set_checksum();
+
+		spin_unlock_irq(&rtc_lock);
+		return 0;
+
+	case NVRAM_SETCKS:
+		/* just set checksum, contents unchanged (maybe useful after 
+		 * checksum garbaged somehow...) */
+		if (!capable(CAP_SYS_ADMIN))
+			return -EACCES;
+
+		spin_lock_irq(&rtc_lock);
+		__nvram_set_checksum();
+		spin_unlock_irq(&rtc_lock);
+		return 0;
+
+	default:
+		return -ENOTTY;
+	}
+}
+
+static int
+nvram_open(struct inode *inode, struct file *file)
+{
+	spin_lock(&nvram_state_lock);
+
+	if ((nvram_open_cnt && (file->f_flags & O_EXCL)) ||
+	    (nvram_open_mode & NVRAM_EXCL) ||
+	    ((file->f_mode & 2) && (nvram_open_mode & NVRAM_WRITE))) {
+		spin_unlock(&nvram_state_lock);
+		return -EBUSY;
+	}
+
+	if (file->f_flags & O_EXCL)
+		nvram_open_mode |= NVRAM_EXCL;
+	if (file->f_mode & 2)
+		nvram_open_mode |= NVRAM_WRITE;
+	nvram_open_cnt++;
+
+	spin_unlock(&nvram_state_lock);
+
+	return 0;
+}
+
+static int
+nvram_release(struct inode *inode, struct file *file)
+{
+	spin_lock(&nvram_state_lock);
+
+	nvram_open_cnt--;
+
+	/* if only one instance is open, clear the EXCL bit */
+	if (nvram_open_mode & NVRAM_EXCL)
+		nvram_open_mode &= ~NVRAM_EXCL;
+	if (file->f_mode & 2)
+		nvram_open_mode &= ~NVRAM_WRITE;
+
+	spin_unlock(&nvram_state_lock);
+
+	return 0;
+}
+
+#ifndef CONFIG_PROC_FS
+static int
+nvram_read_proc(char *buffer, char **start, off_t offset,
+    int size, int *eof, void *data)
+{
+	return 0;
+}
+#else
+
+static int
+nvram_read_proc(char *buffer, char **start, off_t offset,
+    int size, int *eof, void *data)
+{
+	unsigned char contents[NVRAM_BYTES];
+	int i, len = 0;
+	off_t begin = 0;
+
+	spin_lock_irq(&rtc_lock);
+	for (i = 0; i < NVRAM_BYTES; ++i)
+		contents[i] = __nvram_read_byte(i);
+	spin_unlock_irq(&rtc_lock);
+
+	*eof = mach_proc_infos(contents, buffer, &len, &begin, offset, size);
+
+	if (offset >= begin + len)
+		return 0;
+	*start = buffer + (offset - begin);
+	return (size < begin + len - offset) ? size : begin + len - offset;
+
+}
+
+/* This macro frees the machine specific function from bounds checking and
+ * this like that... */
+#define PRINT_PROC(fmt,args...)					\
+	do {							\
+		*len += sprintf(buffer+*len, fmt, ##args);	\
+		if (*begin + *len > offset + size)		\
+			return 0;				\
+		if (*begin + *len < offset) {			\
+			*begin += *len;				\
+			*len = 0;				\
+		}						\
+	} while(0)
+
+#endif /* CONFIG_PROC_FS */
+
+static struct file_operations nvram_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= nvram_llseek,
+	.read		= nvram_read,
+	.write		= nvram_write,
+	.ioctl		= nvram_ioctl,
+	.open		= nvram_open,
+	.release	= nvram_release,
+};
+
+static struct miscdevice nvram_dev = {
+	NVRAM_MINOR,
+	"nvram",
+	&nvram_fops
+};
+
+static int __init
+nvram_init(void)
+{
+	int ret;
+
+	/* First test whether the driver should init at all */
+	if (!CHECK_DRIVER_INIT())
+		return -ENXIO;
+
+	ret = misc_register(&nvram_dev);
+	if (ret) {
+		printk(KERN_ERR "nvram: can't misc_register on minor=%d\n",
+		    NVRAM_MINOR);
+		goto out;
+	}
+	if (!create_proc_read_entry("driver/nvram", 0, NULL, nvram_read_proc,
+		NULL)) {
+		printk(KERN_ERR "nvram: can't create /proc/driver/nvram\n");
+		ret = -ENOMEM;
+		goto outmisc;
+	}
+	ret = 0;
+	printk(KERN_INFO "Non-volatile memory driver v" NVRAM_VERSION "\n");
+      out:
+	return ret;
+      outmisc:
+	misc_deregister(&nvram_dev);
+	goto out;
+}
+
+static void __exit
+nvram_cleanup_module(void)
+{
+	remove_proc_entry("driver/nvram", NULL);
+	misc_deregister(&nvram_dev);
+}
+
+module_init(nvram_init);
+module_exit(nvram_cleanup_module);
+
+/*
+ * Machine specific functions
+ */
+
+#if MACH == PC
+
+static int
+pc_check_checksum(void)
+{
+	int i;
+	unsigned short sum = 0;
+	unsigned short expect;
+
+	for (i = PC_CKS_RANGE_START; i <= PC_CKS_RANGE_END; ++i)
+		sum += __nvram_read_byte(i);
+	expect = __nvram_read_byte(PC_CKS_LOC)<<8 |
+	    __nvram_read_byte(PC_CKS_LOC+1);
+	return ((sum & 0xffff) == expect);
+}
+
+static void
+pc_set_checksum(void)
+{
+	int i;
+	unsigned short sum = 0;
+
+	for (i = PC_CKS_RANGE_START; i <= PC_CKS_RANGE_END; ++i)
+		sum += __nvram_read_byte(i);
+	__nvram_write_byte(sum >> 8, PC_CKS_LOC);
+	__nvram_write_byte(sum & 0xff, PC_CKS_LOC + 1);
+}
+
+#ifdef CONFIG_PROC_FS
+
+static char *floppy_types[] = {
+	"none", "5.25'' 360k", "5.25'' 1.2M", "3.5'' 720k", "3.5'' 1.44M",
+	"3.5'' 2.88M", "3.5'' 2.88M"
+};
+
+static char *gfx_types[] = {
+	"EGA, VGA, ... (with BIOS)",
+	"CGA (40 cols)",
+	"CGA (80 cols)",
+	"monochrome",
+};
+
+static int
+pc_proc_infos(unsigned char *nvram, char *buffer, int *len,
+    off_t *begin, off_t offset, int size)
+{
+	int checksum;
+	int type;
+
+	spin_lock_irq(&rtc_lock);
+	checksum = __nvram_check_checksum();
+	spin_unlock_irq(&rtc_lock);
+
+	PRINT_PROC("Checksum status: %svalid\n", checksum ? "" : "not ");
+
+	PRINT_PROC("# floppies     : %d\n",
+	    (nvram[6] & 1) ? (nvram[6] >> 6) + 1 : 0);
+	PRINT_PROC("Floppy 0 type  : ");
+	type = nvram[2] >> 4;
+	if (type < sizeof (floppy_types) / sizeof (*floppy_types))
+		PRINT_PROC("%s\n", floppy_types[type]);
+	else
+		PRINT_PROC("%d (unknown)\n", type);
+	PRINT_PROC("Floppy 1 type  : ");
+	type = nvram[2] & 0x0f;
+	if (type < sizeof (floppy_types) / sizeof (*floppy_types))
+		PRINT_PROC("%s\n", floppy_types[type]);
+	else
+		PRINT_PROC("%d (unknown)\n", type);
+
+	PRINT_PROC("HD 0 type      : ");
+	type = nvram[4] >> 4;
+	if (type)
+		PRINT_PROC("%02x\n", type == 0x0f ? nvram[11] : type);
+	else
+		PRINT_PROC("none\n");
+
+	PRINT_PROC("HD 1 type      : ");
+	type = nvram[4] & 0x0f;
+	if (type)
+		PRINT_PROC("%02x\n", type == 0x0f ? nvram[12] : type);
+	else
+		PRINT_PROC("none\n");
+
+	PRINT_PROC("HD type 48 data: %d/%d/%d C/H/S, precomp %d, lz %d\n",
+	    nvram[18] | (nvram[19] << 8),
+	    nvram[20], nvram[25],
+	    nvram[21] | (nvram[22] << 8), nvram[23] | (nvram[24] << 8));
+	PRINT_PROC("HD type 49 data: %d/%d/%d C/H/S, precomp %d, lz %d\n",
+	    nvram[39] | (nvram[40] << 8),
+	    nvram[41], nvram[46],
+	    nvram[42] | (nvram[43] << 8), nvram[44] | (nvram[45] << 8));
+
+	PRINT_PROC("DOS base memory: %d kB\n", nvram[7] | (nvram[8] << 8));
+	PRINT_PROC("Extended memory: %d kB (configured), %d kB (tested)\n",
+	    nvram[9] | (nvram[10] << 8), nvram[34] | (nvram[35] << 8));
+
+	PRINT_PROC("Gfx adapter    : %s\n", gfx_types[(nvram[6] >> 4) & 3]);
+
+	PRINT_PROC("FPU            : %sinstalled\n",
+	    (nvram[6] & 2) ? "" : "not ");
+
+	return 1;
+}
+#endif
+
+#endif /* MACH == PC */
+
+#if MACH == COBALT
+
+/* the cobalt CMOS has a wider range of its checksum */
+static int cobalt_check_checksum(void)
+{
+	int i;
+	unsigned short sum = 0;
+	unsigned short expect;
+
+	for (i = COBT_CMOS_CKS_START; i <= COBT_CMOS_CKS_END; ++i) {
+		if ((i == COBT_CMOS_CHECKSUM) || (i == (COBT_CMOS_CHECKSUM+1)))
+			continue;
+
+		sum += __nvram_read_byte(i);
+	}
+	expect = __nvram_read_byte(COBT_CMOS_CHECKSUM) << 8 |
+	    __nvram_read_byte(COBT_CMOS_CHECKSUM+1);
+	return ((sum & 0xffff) == expect);
+}
+
+static void cobalt_set_checksum(void)
+{
+	int i;
+	unsigned short sum = 0;
+
+	for (i = COBT_CMOS_CKS_START; i <= COBT_CMOS_CKS_END; ++i) {
+		if ((i == COBT_CMOS_CHECKSUM) || (i == (COBT_CMOS_CHECKSUM+1)))
+			continue;
+
+		sum += __nvram_read_byte(i);
+	}
+
+	__nvram_write_byte(sum >> 8, COBT_CMOS_CHECKSUM);
+	__nvram_write_byte(sum & 0xff, COBT_CMOS_CHECKSUM+1);
+}
+
+#ifdef CONFIG_PROC_FS
+
+static int cobalt_proc_infos(unsigned char *nvram, char *buffer, int *len,
+	off_t *begin, off_t offset, int size)
+{
+	int i;
+	unsigned int checksum;
+	unsigned int flags;
+	char sernum[14];
+	char *key = "cNoEbTaWlOtR!";
+	unsigned char bto_csum;
+
+	spin_lock_irq(&rtc_lock);
+	checksum = __nvram_check_checksum();
+	spin_unlock_irq(&rtc_lock);
+
+	PRINT_PROC("Checksum status: %svalid\n", checksum ? "" : "not ");
+
+	flags = nvram[COBT_CMOS_FLAG_BYTE_0] << 8 
+	    | nvram[COBT_CMOS_FLAG_BYTE_1];
+
+	PRINT_PROC("Console: %s\n",
+		flags & COBT_CMOS_CONSOLE_FLAG ?  "on": "off");
+
+	PRINT_PROC("Firmware Debug Messages: %s\n",
+		flags & COBT_CMOS_DEBUG_FLAG ? "on": "off");
+
+	PRINT_PROC("Auto Prompt: %s\n",
+		flags & COBT_CMOS_AUTO_PROMPT_FLAG ? "on": "off");
+
+	PRINT_PROC("Shutdown Status: %s\n",
+		flags & COBT_CMOS_CLEAN_BOOT_FLAG ? "clean": "dirty");
+
+	PRINT_PROC("Hardware Probe: %s\n",
+		flags & COBT_CMOS_HW_NOPROBE_FLAG ? "partial": "full");
+
+	PRINT_PROC("System Fault: %sdetected\n",
+		flags & COBT_CMOS_SYSFAULT_FLAG ? "": "not ");
+
+	PRINT_PROC("Panic on OOPS: %s\n",
+		flags & COBT_CMOS_OOPSPANIC_FLAG ? "yes": "no");
+
+	PRINT_PROC("Delayed Cache Initialization: %s\n",
+		flags & COBT_CMOS_DELAY_CACHE_FLAG ? "yes": "no");
+
+	PRINT_PROC("Show Logo at Boot: %s\n",
+		flags & COBT_CMOS_NOLOGO_FLAG ? "no": "yes");
+
+	PRINT_PROC("Boot Method: ");
+	switch (nvram[COBT_CMOS_BOOT_METHOD]) {
+	case COBT_CMOS_BOOT_METHOD_DISK:
+		PRINT_PROC("disk\n");
+		break;
+
+	case COBT_CMOS_BOOT_METHOD_ROM:
+		PRINT_PROC("rom\n");
+		break;
+
+	case COBT_CMOS_BOOT_METHOD_NET:
+		PRINT_PROC("net\n");
+		break;
+
+	default:
+		PRINT_PROC("unknown\n");
+		break;
+	}
+
+	PRINT_PROC("Primary Boot Device: %d:%d\n",
+		nvram[COBT_CMOS_BOOT_DEV0_MAJ],
+		nvram[COBT_CMOS_BOOT_DEV0_MIN] );
+	PRINT_PROC("Secondary Boot Device: %d:%d\n",
+		nvram[COBT_CMOS_BOOT_DEV1_MAJ],
+		nvram[COBT_CMOS_BOOT_DEV1_MIN] );
+	PRINT_PROC("Tertiary Boot Device: %d:%d\n",
+		nvram[COBT_CMOS_BOOT_DEV2_MAJ],
+		nvram[COBT_CMOS_BOOT_DEV2_MIN] );
+
+	PRINT_PROC("Uptime: %d\n",
+		nvram[COBT_CMOS_UPTIME_0] << 24 |
+		nvram[COBT_CMOS_UPTIME_1] << 16 |
+		nvram[COBT_CMOS_UPTIME_2] << 8  |
+		nvram[COBT_CMOS_UPTIME_3]);
+
+	PRINT_PROC("Boot Count: %d\n",
+		nvram[COBT_CMOS_BOOTCOUNT_0] << 24 |
+		nvram[COBT_CMOS_BOOTCOUNT_1] << 16 |
+		nvram[COBT_CMOS_BOOTCOUNT_2] << 8  |
+		nvram[COBT_CMOS_BOOTCOUNT_3]);
+
+	/* 13 bytes of serial num */
+	for (i=0 ; i<13 ; i++) {
+		sernum[i] = nvram[COBT_CMOS_SYS_SERNUM_0 + i];
+	}
+	sernum[13] = '\0';
+
+	checksum = 0;
+	for (i=0 ; i<13 ; i++) {
+		checksum += sernum[i] ^ key[i];
+	}
+	checksum = ((checksum & 0x7f) ^ (0xd6)) & 0xff;
+
+	PRINT_PROC("Serial Number: %s", sernum);
+	if (checksum != nvram[COBT_CMOS_SYS_SERNUM_CSUM]) {
+		PRINT_PROC(" (invalid checksum)");
+	}
+	PRINT_PROC("\n");
+
+	PRINT_PROC("Rom Revison: %d.%d.%d\n", nvram[COBT_CMOS_ROM_REV_MAJ],
+		nvram[COBT_CMOS_ROM_REV_MIN], nvram[COBT_CMOS_ROM_REV_REV]);
+
+	PRINT_PROC("BTO Server: %d.%d.%d.%d", nvram[COBT_CMOS_BTO_IP_0],
+		nvram[COBT_CMOS_BTO_IP_1], nvram[COBT_CMOS_BTO_IP_2],
+		nvram[COBT_CMOS_BTO_IP_3]);
+	bto_csum = nvram[COBT_CMOS_BTO_IP_0] + nvram[COBT_CMOS_BTO_IP_1]
+		+ nvram[COBT_CMOS_BTO_IP_2] + nvram[COBT_CMOS_BTO_IP_3];
+	if (bto_csum != nvram[COBT_CMOS_BTO_IP_CSUM]) {
+		PRINT_PROC(" (invalid checksum)");
+	}
+	PRINT_PROC("\n");
+
+	if (flags & COBT_CMOS_VERSION_FLAG
+	 && nvram[COBT_CMOS_VERSION] >= COBT_CMOS_VER_BTOCODE) {
+		PRINT_PROC("BTO Code: 0x%x\n",
+			nvram[COBT_CMOS_BTO_CODE_0] << 24 |
+			nvram[COBT_CMOS_BTO_CODE_1] << 16 |
+			nvram[COBT_CMOS_BTO_CODE_2] << 8 |
+			nvram[COBT_CMOS_BTO_CODE_3]);
+	}
+
+	return 1;
+}
+#endif /* CONFIG_PROC_FS */
+
+#endif /* MACH == COBALT */
+
+#if MACH == ATARI
+
+static int
+atari_check_checksum(void)
+{
+	int i;
+	unsigned char sum = 0;
+
+	for (i = ATARI_CKS_RANGE_START; i <= ATARI_CKS_RANGE_END; ++i)
+		sum += __nvram_read_byte(i);
+	return (__nvram_read_byte(ATARI_CKS_LOC) == (~sum & 0xff) &&
+	    __nvram_read_byte(ATARI_CKS_LOC + 1) == (sum & 0xff));
+}
+
+static void
+atari_set_checksum(void)
+{
+	int i;
+	unsigned char sum = 0;
+
+	for (i = ATARI_CKS_RANGE_START; i <= ATARI_CKS_RANGE_END; ++i)
+		sum += __nvram_read_byte(i);
+	__nvram_write_byte(~sum, ATARI_CKS_LOC);
+	__nvram_write_byte(sum, ATARI_CKS_LOC + 1);
+}
+
+#ifdef CONFIG_PROC_FS
+
+static struct {
+	unsigned char val;
+	char *name;
+} boot_prefs[] = {
+	{ 0x80, "TOS" },
+	{ 0x40, "ASV" },
+	{ 0x20, "NetBSD (?)" },
+	{ 0x10, "Linux" },
+	{ 0x00, "unspecified" }
+};
+
+static char *languages[] = {
+	"English (US)",
+	"German",
+	"French",
+	"English (UK)",
+	"Spanish",
+	"Italian",
+	"6 (undefined)",
+	"Swiss (French)",
+	"Swiss (German)"
+};
+
+static char *dateformat[] = {
+	"MM%cDD%cYY",
+	"DD%cMM%cYY",
+	"YY%cMM%cDD",
+	"YY%cDD%cMM",
+	"4 (undefined)",
+	"5 (undefined)",
+	"6 (undefined)",
+	"7 (undefined)"
+};
+
+static char *colors[] = {
+	"2", "4", "16", "256", "65536", "??", "??", "??"
+};
+
+#define fieldsize(a)	(sizeof(a)/sizeof(*a))
+
+static int
+atari_proc_infos(unsigned char *nvram, char *buffer, int *len,
+    off_t *begin, off_t offset, int size)
+{
+	int checksum = nvram_check_checksum();
+	int i;
+	unsigned vmode;
+
+	PRINT_PROC("Checksum status  : %svalid\n", checksum ? "" : "not ");
+
+	PRINT_PROC("Boot preference  : ");
+	for (i = fieldsize(boot_prefs) - 1; i >= 0; --i) {
+		if (nvram[1] == boot_prefs[i].val) {
+			PRINT_PROC("%s\n", boot_prefs[i].name);
+			break;
+		}
+	}
+	if (i < 0)
+		PRINT_PROC("0x%02x (undefined)\n", nvram[1]);
+
+	PRINT_PROC("SCSI arbitration : %s\n",
+	    (nvram[16] & 0x80) ? "on" : "off");
+	PRINT_PROC("SCSI host ID     : ");
+	if (nvram[16] & 0x80)
+		PRINT_PROC("%d\n", nvram[16] & 7);
+	else
+		PRINT_PROC("n/a\n");
+
+	/* the following entries are defined only for the Falcon */
+	if ((atari_mch_cookie >> 16) != ATARI_MCH_FALCON)
+		return 1;
+
+	PRINT_PROC("OS language      : ");
+	if (nvram[6] < fieldsize(languages))
+		PRINT_PROC("%s\n", languages[nvram[6]]);
+	else
+		PRINT_PROC("%u (undefined)\n", nvram[6]);
+	PRINT_PROC("Keyboard language: ");
+	if (nvram[7] < fieldsize(languages))
+		PRINT_PROC("%s\n", languages[nvram[7]]);
+	else
+		PRINT_PROC("%u (undefined)\n", nvram[7]);
+	PRINT_PROC("Date format      : ");
+	PRINT_PROC(dateformat[nvram[8] & 7],
+	    nvram[9] ? nvram[9] : '/', nvram[9] ? nvram[9] : '/');
+	PRINT_PROC(", %dh clock\n", nvram[8] & 16 ? 24 : 12);
+	PRINT_PROC("Boot delay       : ");
+	if (nvram[10] == 0)
+		PRINT_PROC("default");
+	else
+		PRINT_PROC("%ds%s\n", nvram[10],
+		    nvram[10] < 8 ? ", no memory test" : "");
+
+	vmode = (nvram[14] << 8) || nvram[15];
+	PRINT_PROC("Video mode       : %s colors, %d columns, %s %s monitor\n",
+	    colors[vmode & 7],
+	    vmode & 8 ? 80 : 40,
+	    vmode & 16 ? "VGA" : "TV", vmode & 32 ? "PAL" : "NTSC");
+	PRINT_PROC("                   %soverscan, compat. mode %s%s\n",
+	    vmode & 64 ? "" : "no ",
+	    vmode & 128 ? "on" : "off",
+	    vmode & 256 ?
+	    (vmode & 16 ? ", line doubling" : ", half screen") : "");
+
+	return 1;
+}
+#endif
+
+#endif /* MACH == ATARI */
+
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(__nvram_read_byte);
+EXPORT_SYMBOL(nvram_read_byte);
+EXPORT_SYMBOL(__nvram_write_byte);
+EXPORT_SYMBOL(nvram_write_byte);
+EXPORT_SYMBOL(__nvram_check_checksum);
+EXPORT_SYMBOL(nvram_check_checksum);
+EXPORT_SYMBOL(__nvram_set_checksum);
+EXPORT_SYMBOL(nvram_set_checksum);
+MODULE_ALIAS_MISCDEV(NVRAM_MINOR);
diff --git a/drivers/char/nwbutton.c b/drivers/char/nwbutton.c
new file mode 100644
index 0000000..4083b78
--- /dev/null
+++ b/drivers/char/nwbutton.c
@@ -0,0 +1,248 @@
+/*
+ * 	NetWinder Button Driver-
+ *	Copyright (C) Alex Holden <alex@linuxhacker.org> 1998, 1999.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/time.h>
+#include <linux/timer.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/mach-types.h>
+
+#define __NWBUTTON_C		/* Tell the header file who we are */
+#include "nwbutton.h"
+
+static int button_press_count;		/* The count of button presses */
+static struct timer_list button_timer;	/* Times for the end of a sequence */ 
+static DECLARE_WAIT_QUEUE_HEAD(button_wait_queue); /* Used for blocking read */
+static char button_output_buffer[32];	/* Stores data to write out of device */
+static int bcount;			/* The number of bytes in the buffer */
+static int bdelay = BUTTON_DELAY;	/* The delay, in jiffies */
+static struct button_callback button_callback_list[32]; /* The callback list */
+static int callback_count;		/* The number of callbacks registered */
+static int reboot_count = NUM_PRESSES_REBOOT; /* Number of presses to reboot */
+
+/*
+ * This function is called by other drivers to register a callback function
+ * to be called when a particular number of button presses occurs.
+ * The callback list is a static array of 32 entries (I somehow doubt many
+ * people are ever going to want to register more than 32 different actions
+ * to be performed by the kernel on different numbers of button presses ;).
+ * However, if an attempt to register a 33rd entry (perhaps a stuck loop
+ * somewhere registering the same entry over and over?) it will fail to
+ * do so and return -ENOMEM. If an attempt is made to register a null pointer,
+ * it will fail to do so and return -EINVAL.
+ * Because callbacks can be unregistered at random the list can become
+ * fragmented, so we need to search through the list until we find the first
+ * free entry.
+ *
+ * FIXME: Has anyone spotted any locking functions int his code recently ??
+ */
+
+int button_add_callback (void (*callback) (void), int count)
+{
+	int lp = 0;
+	if (callback_count == 32) {
+		return -ENOMEM;
+	}
+	if (!callback) {
+		return -EINVAL;
+	}
+	callback_count++;
+	for (; (button_callback_list [lp].callback); lp++);
+	button_callback_list [lp].callback = callback;
+	button_callback_list [lp].count = count;
+	return 0;
+}
+
+/*
+ * This function is called by other drivers to deregister a callback function.
+ * If you attempt to unregister a callback which does not exist, it will fail
+ * with -EINVAL. If there is more than one entry with the same address,
+ * because it searches the list from end to beginning, it will unregister the
+ * last one to be registered first (FILO- First In Last Out).
+ * Note that this is not neccessarily true if the entries are not submitted
+ * at the same time, because another driver could have unregistered a callback
+ * between the submissions creating a gap earlier in the list, which would
+ * be filled first at submission time.
+ */
+
+int button_del_callback (void (*callback) (void))
+{
+	int lp = 31;
+	if (!callback) {
+		return -EINVAL;
+	}
+	while (lp >= 0) {
+		if ((button_callback_list [lp].callback) == callback) {
+			button_callback_list [lp].callback = NULL;
+			button_callback_list [lp].count = 0;
+			callback_count--;
+			return 0;
+		};
+		lp--;
+	};
+	return -EINVAL;
+}
+
+/*
+ * This function is called by button_sequence_finished to search through the
+ * list of callback functions, and call any of them whose count argument
+ * matches the current count of button presses. It starts at the beginning
+ * of the list and works up to the end. It will refuse to follow a null
+ * pointer (which should never happen anyway).
+ */
+
+static void button_consume_callbacks (int bpcount)
+{
+	int lp = 0;
+	for (; lp <= 31; lp++) {
+		if ((button_callback_list [lp].count) == bpcount) {
+			if (button_callback_list [lp].callback) {
+				button_callback_list[lp].callback();
+			}
+		}
+	}
+}
+
+/* 
+ * This function is called when the button_timer times out.
+ * ie. When you don't press the button for bdelay jiffies, this is taken to
+ * mean you have ended the sequence of key presses, and this function is
+ * called to wind things up (write the press_count out to /dev/button, call
+ * any matching registered function callbacks, initiate reboot, etc.).
+ */
+
+static void button_sequence_finished (unsigned long parameters)
+{
+#ifdef CONFIG_NWBUTTON_REBOOT		/* Reboot using button is enabled */
+	if (button_press_count == reboot_count) {
+		kill_proc (1, SIGINT, 1);	/* Ask init to reboot us */
+	}
+#endif /* CONFIG_NWBUTTON_REBOOT */
+	button_consume_callbacks (button_press_count);
+	bcount = sprintf (button_output_buffer, "%d\n", button_press_count);
+	button_press_count = 0;		/* Reset the button press counter */
+	wake_up_interruptible (&button_wait_queue);
+}
+
+/* 
+ *  This handler is called when the orange button is pressed (GPIO 10 of the
+ *  SuperIO chip, which maps to logical IRQ 26). If the press_count is 0,
+ *  this is the first press, so it starts a timer and increments the counter.
+ *  If it is higher than 0, it deletes the old timer, starts a new one, and
+ *  increments the counter.
+ */ 
+
+static irqreturn_t button_handler (int irq, void *dev_id, struct pt_regs *regs)
+{
+	if (button_press_count) {
+		del_timer (&button_timer);
+	}
+	button_press_count++;
+	init_timer (&button_timer);
+	button_timer.function = button_sequence_finished;
+	button_timer.expires = (jiffies + bdelay);
+	add_timer (&button_timer);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * This function is called when a user space program attempts to read
+ * /dev/nwbutton. It puts the device to sleep on the wait queue until
+ * button_sequence_finished writes some data to the buffer and flushes
+ * the queue, at which point it writes the data out to the device and
+ * returns the number of characters it has written. This function is
+ * reentrant, so that many processes can be attempting to read from the
+ * device at any one time.
+ */
+
+static int button_read (struct file *filp, char __user *buffer,
+			size_t count, loff_t *ppos)
+{
+	interruptible_sleep_on (&button_wait_queue);
+	return (copy_to_user (buffer, &button_output_buffer, bcount))
+		 ? -EFAULT : bcount;
+}
+
+/* 
+ * This structure is the file operations structure, which specifies what
+ * callbacks functions the kernel should call when a user mode process
+ * attempts to perform these operations on the device.
+ */
+
+static struct file_operations button_fops = {
+	.owner		= THIS_MODULE,
+	.read		= button_read,
+};
+
+/* 
+ * This structure is the misc device structure, which specifies the minor
+ * device number (158 in this case), the name of the device (for /proc/misc),
+ * and the address of the above file operations structure.
+ */
+
+static struct miscdevice button_misc_device = {
+	BUTTON_MINOR,
+	"nwbutton",
+	&button_fops,
+};
+
+/*
+ * This function is called to initialise the driver, either from misc.c at
+ * bootup if the driver is compiled into the kernel, or from init_module
+ * below at module insert time. It attempts to register the device node
+ * and the IRQ and fails with a warning message if either fails, though
+ * neither ever should because the device number and IRQ are unique to
+ * this driver.
+ */
+
+static int __init nwbutton_init(void)
+{
+	if (!machine_is_netwinder())
+		return -ENODEV;
+
+	printk (KERN_INFO "NetWinder Button Driver Version %s (C) Alex Holden "
+			"<alex@linuxhacker.org> 1998.\n", VERSION);
+
+	if (misc_register (&button_misc_device)) {
+		printk (KERN_WARNING "nwbutton: Couldn't register device 10, "
+				"%d.\n", BUTTON_MINOR);
+		return -EBUSY;
+	}
+
+	if (request_irq (IRQ_NETWINDER_BUTTON, button_handler, SA_INTERRUPT,
+			"nwbutton", NULL)) {
+		printk (KERN_WARNING "nwbutton: IRQ %d is not free.\n",
+				IRQ_NETWINDER_BUTTON);
+		misc_deregister (&button_misc_device);
+		return -EIO;
+	}
+	return 0;
+}
+
+static void __exit nwbutton_exit (void) 
+{
+	free_irq (IRQ_NETWINDER_BUTTON, NULL);
+	misc_deregister (&button_misc_device);
+}
+
+
+MODULE_AUTHOR("Alex Holden");
+MODULE_LICENSE("GPL");
+
+module_init(nwbutton_init);
+module_exit(nwbutton_exit);
diff --git a/drivers/char/nwbutton.h b/drivers/char/nwbutton.h
new file mode 100644
index 0000000..ddb7b92
--- /dev/null
+++ b/drivers/char/nwbutton.h
@@ -0,0 +1,40 @@
+#ifndef __NWBUTTON_H
+#define __NWBUTTON_H
+
+/*
+ * 	NetWinder Button Driver-
+ *	Copyright (C) Alex Holden <alex@linuxhacker.org> 1998, 1999.
+ */
+
+#ifdef __NWBUTTON_C	/* Actually compiling the driver itself */
+
+/* Various defines: */
+
+#define NUM_PRESSES_REBOOT 2	/* How many presses to activate shutdown */
+#define BUTTON_DELAY 30 	/* How many jiffies for sequence to end */
+#define VERSION "0.3"		/* Driver version number */
+#define BUTTON_MINOR 158	/* Major 10, Minor 158, /dev/nwbutton */
+
+/* Structure definitions: */
+
+struct button_callback {
+	void (*callback) (void);
+	int count;
+};
+
+/* Function prototypes: */
+
+static void button_sequence_finished (unsigned long parameters);
+static irqreturn_t button_handler (int irq, void *dev_id, struct pt_regs *regs);
+int button_init (void);
+int button_add_callback (void (*callback) (void), int count);
+int button_del_callback (void (*callback) (void));
+static void button_consume_callbacks (int bpcount);
+
+#else /* Not compiling the driver itself */
+
+extern int button_add_callback (void (*callback) (void), int count);
+extern int button_del_callback (void (*callback) (void));
+
+#endif /* __NWBUTTON_C */
+#endif /* __NWBUTTON_H */
diff --git a/drivers/char/nwflash.c b/drivers/char/nwflash.c
new file mode 100644
index 0000000..ca41d62
--- /dev/null
+++ b/drivers/char/nwflash.c
@@ -0,0 +1,702 @@
+/*
+ * Flash memory interface rev.5 driver for the Intel
+ * Flash chips used on the NetWinder.
+ *
+ * 20/08/2000	RMK	use __ioremap to map flash into virtual memory
+ *			make a few more places use "volatile"
+ * 22/05/2001	RMK	- Lock read against write
+ *			- merge printk level changes (with mods) from Alan Cox.
+ *			- use *ppos as the file position, not file->f_pos.
+ *			- fix check for out of range pos and r/w size
+ *
+ * Please note that we are tampering with the only flash chip in the
+ * machine, which contains the bootup code.  We therefore have the
+ * power to convert these machines into doorstops...
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/sched.h>
+#include <linux/miscdevice.h>
+#include <linux/spinlock.h>
+#include <linux/rwsem.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+
+#include <asm/hardware/dec21285.h>
+#include <asm/io.h>
+#include <asm/leds.h>
+#include <asm/mach-types.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+/*****************************************************************************/
+#include <asm/nwflash.h>
+
+#define	NWFLASH_VERSION "6.4"
+
+static void kick_open(void);
+static int get_flash_id(void);
+static int erase_block(int nBlock);
+static int write_block(unsigned long p, const char __user *buf, int count);
+
+#define KFLASH_SIZE	1024*1024	//1 Meg
+#define KFLASH_SIZE4	4*1024*1024	//4 Meg
+#define KFLASH_ID	0x89A6		//Intel flash
+#define KFLASH_ID4	0xB0D4		//Intel flash 4Meg
+
+static int flashdebug;		//if set - we will display progress msgs
+
+static int gbWriteEnable;
+static int gbWriteBase64Enable;
+static volatile unsigned char *FLASH_BASE;
+static int gbFlashSize = KFLASH_SIZE;
+static DECLARE_MUTEX(nwflash_sem);
+
+extern spinlock_t gpio_lock;
+
+static int get_flash_id(void)
+{
+	volatile unsigned int c1, c2;
+
+	/*
+	 * try to get flash chip ID
+	 */
+	kick_open();
+	c2 = inb(0x80);
+	*(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x90;
+	udelay(15);
+	c1 = *(volatile unsigned char *) FLASH_BASE;
+	c2 = inb(0x80);
+
+	/*
+	 * on 4 Meg flash the second byte is actually at offset 2...
+	 */
+	if (c1 == 0xB0)
+		c2 = *(volatile unsigned char *) (FLASH_BASE + 2);
+	else
+		c2 = *(volatile unsigned char *) (FLASH_BASE + 1);
+
+	c2 += (c1 << 8);
+
+	/*
+	 * set it back to read mode
+	 */
+	*(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0xFF;
+
+	if (c2 == KFLASH_ID4)
+		gbFlashSize = KFLASH_SIZE4;
+
+	return c2;
+}
+
+static int flash_ioctl(struct inode *inodep, struct file *filep, unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case CMD_WRITE_DISABLE:
+		gbWriteBase64Enable = 0;
+		gbWriteEnable = 0;
+		break;
+
+	case CMD_WRITE_ENABLE:
+		gbWriteEnable = 1;
+		break;
+
+	case CMD_WRITE_BASE64K_ENABLE:
+		gbWriteBase64Enable = 1;
+		break;
+
+	default:
+		gbWriteBase64Enable = 0;
+		gbWriteEnable = 0;
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static ssize_t flash_read(struct file *file, char __user *buf, size_t size,
+			  loff_t *ppos)
+{
+	unsigned long p = *ppos;
+	unsigned int count = size;
+	int ret = 0;
+
+	if (flashdebug)
+		printk(KERN_DEBUG "flash_read: flash_read: offset=0x%lX, "
+		       "buffer=%p, count=0x%X.\n", p, buf, count);
+
+	if (count)
+		ret = -ENXIO;
+
+	if (p < gbFlashSize) {
+		if (count > gbFlashSize - p)
+			count = gbFlashSize - p;
+
+		/*
+		 * We now lock against reads and writes. --rmk
+		 */
+		if (down_interruptible(&nwflash_sem))
+			return -ERESTARTSYS;
+
+		ret = copy_to_user(buf, (void *)(FLASH_BASE + p), count);
+		if (ret == 0) {
+			ret = count;
+			*ppos += count;
+		} else
+			ret = -EFAULT;
+		up(&nwflash_sem);
+	}
+	return ret;
+}
+
+static ssize_t flash_write(struct file *file, const char __user *buf,
+			   size_t size, loff_t * ppos)
+{
+	unsigned long p = *ppos;
+	unsigned int count = size;
+	int written;
+	int nBlock, temp, rc;
+	int i, j;
+
+	if (flashdebug)
+		printk("flash_write: offset=0x%lX, buffer=0x%p, count=0x%X.\n",
+		       p, buf, count);
+
+	if (!gbWriteEnable)
+		return -EINVAL;
+
+	if (p < 64 * 1024 && (!gbWriteBase64Enable))
+		return -EINVAL;
+
+	/*
+	 * check for out of range pos or count
+	 */
+	if (p >= gbFlashSize)
+		return count ? -ENXIO : 0;
+
+	if (count > gbFlashSize - p)
+		count = gbFlashSize - p;
+			
+	if (!access_ok(VERIFY_READ, buf, count))
+		return -EFAULT;
+
+	/*
+	 * We now lock against reads and writes. --rmk
+	 */
+	if (down_interruptible(&nwflash_sem))
+		return -ERESTARTSYS;
+
+	written = 0;
+
+	leds_event(led_claim);
+	leds_event(led_green_on);
+
+	nBlock = (int) p >> 16;	//block # of 64K bytes
+
+	/*
+	 * # of 64K blocks to erase and write
+	 */
+	temp = ((int) (p + count) >> 16) - nBlock + 1;
+
+	/*
+	 * write ends at exactly 64k boundary?
+	 */
+	if (((int) (p + count) & 0xFFFF) == 0)
+		temp -= 1;
+
+	if (flashdebug)
+		printk(KERN_DEBUG "flash_write: writing %d block(s) "
+			"starting at %d.\n", temp, nBlock);
+
+	for (; temp; temp--, nBlock++) {
+		if (flashdebug)
+			printk(KERN_DEBUG "flash_write: erasing block %d.\n", nBlock);
+
+		/*
+		 * first we have to erase the block(s), where we will write...
+		 */
+		i = 0;
+		j = 0;
+	  RetryBlock:
+		do {
+			rc = erase_block(nBlock);
+			i++;
+		} while (rc && i < 10);
+
+		if (rc) {
+			printk(KERN_ERR "flash_write: erase error %x\n", rc);
+			break;
+		}
+		if (flashdebug)
+			printk(KERN_DEBUG "flash_write: writing offset %lX, "
+			       "from buf %p, bytes left %X.\n", p, buf,
+			       count - written);
+
+		/*
+		 * write_block will limit write to space left in this block
+		 */
+		rc = write_block(p, buf, count - written);
+		j++;
+
+		/*
+		 * if somehow write verify failed? Can't happen??
+		 */
+		if (!rc) {
+			/*
+			 * retry up to 10 times
+			 */
+			if (j < 10)
+				goto RetryBlock;
+			else
+				/*
+				 * else quit with error...
+				 */
+				rc = -1;
+
+		}
+		if (rc < 0) {
+			printk(KERN_ERR "flash_write: write error %X\n", rc);
+			break;
+		}
+		p += rc;
+		buf += rc;
+		written += rc;
+		*ppos += rc;
+
+		if (flashdebug)
+			printk(KERN_DEBUG "flash_write: written 0x%X bytes OK.\n", written);
+	}
+
+	/*
+	 * restore reg on exit
+	 */
+	leds_event(led_release);
+
+	up(&nwflash_sem);
+
+	return written;
+}
+
+
+/*
+ * The memory devices use the full 32/64 bits of the offset, and so we cannot
+ * check against negative addresses: they are ok. The return value is weird,
+ * though, in that case (0).
+ *
+ * also note that seeking relative to the "end of file" isn't supported:
+ * it has no meaning, so it returns -EINVAL.
+ */
+static loff_t flash_llseek(struct file *file, loff_t offset, int orig)
+{
+	loff_t ret;
+
+	lock_kernel();
+	if (flashdebug)
+		printk(KERN_DEBUG "flash_llseek: offset=0x%X, orig=0x%X.\n",
+		       (unsigned int) offset, orig);
+
+	switch (orig) {
+	case 0:
+		if (offset < 0) {
+			ret = -EINVAL;
+			break;
+		}
+
+		if ((unsigned int) offset > gbFlashSize) {
+			ret = -EINVAL;
+			break;
+		}
+
+		file->f_pos = (unsigned int) offset;
+		ret = file->f_pos;
+		break;
+	case 1:
+		if ((file->f_pos + offset) > gbFlashSize) {
+			ret = -EINVAL;
+			break;
+		}
+		if ((file->f_pos + offset) < 0) {
+			ret = -EINVAL;
+			break;
+		}
+		file->f_pos += offset;
+		ret = file->f_pos;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	unlock_kernel();
+	return ret;
+}
+
+
+/*
+ * assume that main Write routine did the parameter checking...
+ * so just go ahead and erase, what requested!
+ */
+
+static int erase_block(int nBlock)
+{
+	volatile unsigned int c1;
+	volatile unsigned char *pWritePtr;
+	unsigned long timeout;
+	int temp, temp1;
+
+	/*
+	 * orange LED == erase
+	 */
+	leds_event(led_amber_on);
+
+	/*
+	 * reset footbridge to the correct offset 0 (...0..3)
+	 */
+	*CSR_ROMWRITEREG = 0;
+
+	/*
+	 * dummy ROM read
+	 */
+	c1 = *(volatile unsigned char *) (FLASH_BASE + 0x8000);
+
+	kick_open();
+	/*
+	 * reset status if old errors
+	 */
+	*(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x50;
+
+	/*
+	 * erase a block...
+	 * aim at the middle of a current block...
+	 */
+	pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + 0x8000 + (nBlock << 16)));
+	/*
+	 * dummy read
+	 */
+	c1 = *pWritePtr;
+
+	kick_open();
+	/*
+	 * erase
+	 */
+	*(volatile unsigned char *) pWritePtr = 0x20;
+
+	/*
+	 * confirm
+	 */
+	*(volatile unsigned char *) pWritePtr = 0xD0;
+
+	/*
+	 * wait 10 ms
+	 */
+	msleep(10);
+
+	/*
+	 * wait while erasing in process (up to 10 sec)
+	 */
+	timeout = jiffies + 10 * HZ;
+	c1 = 0;
+	while (!(c1 & 0x80) && time_before(jiffies, timeout)) {
+		msleep(10);
+		/*
+		 * read any address
+		 */
+		c1 = *(volatile unsigned char *) (pWritePtr);
+		//              printk("Flash_erase: status=%X.\n",c1);
+	}
+
+	/*
+	 * set flash for normal read access
+	 */
+	kick_open();
+//      *(volatile unsigned char*)(FLASH_BASE+0x8000) = 0xFF;
+	*(volatile unsigned char *) pWritePtr = 0xFF;	//back to normal operation
+
+	/*
+	 * check if erase errors were reported
+	 */
+	if (c1 & 0x20) {
+		printk(KERN_ERR "flash_erase: err at %p\n", pWritePtr);
+
+		/*
+		 * reset error
+		 */
+		*(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x50;
+		return -2;
+	}
+
+	/*
+	 * just to make sure - verify if erased OK...
+	 */
+	msleep(10);
+
+	pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + (nBlock << 16)));
+
+	for (temp = 0; temp < 16 * 1024; temp++, pWritePtr += 4) {
+		if ((temp1 = *(volatile unsigned int *) pWritePtr) != 0xFFFFFFFF) {
+			printk(KERN_ERR "flash_erase: verify err at %p = %X\n",
+			       pWritePtr, temp1);
+			return -1;
+		}
+	}
+
+	return 0;
+
+}
+
+/*
+ * write_block will limit number of bytes written to the space in this block
+ */
+static int write_block(unsigned long p, const char __user *buf, int count)
+{
+	volatile unsigned int c1;
+	volatile unsigned int c2;
+	unsigned char *pWritePtr;
+	unsigned int uAddress;
+	unsigned int offset;
+	unsigned long timeout;
+	unsigned long timeout1;
+
+	/*
+	 * red LED == write
+	 */
+	leds_event(led_amber_off);
+	leds_event(led_red_on);
+
+	pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + p));
+
+	/*
+	 * check if write will end in this block....
+	 */
+	offset = p & 0xFFFF;
+
+	if (offset + count > 0x10000)
+		count = 0x10000 - offset;
+
+	/*
+	 * wait up to 30 sec for this block
+	 */
+	timeout = jiffies + 30 * HZ;
+
+	for (offset = 0; offset < count; offset++, pWritePtr++) {
+		uAddress = (unsigned int) pWritePtr;
+		uAddress &= 0xFFFFFFFC;
+		if (__get_user(c2, buf + offset))
+			return -EFAULT;
+
+	  WriteRetry:
+	  	/*
+	  	 * dummy read
+	  	 */
+		c1 = *(volatile unsigned char *) (FLASH_BASE + 0x8000);
+
+		/*
+		 * kick open the write gate
+		 */
+		kick_open();
+
+		/*
+		 * program footbridge to the correct offset...0..3
+		 */
+		*CSR_ROMWRITEREG = (unsigned int) pWritePtr & 3;
+
+		/*
+		 * write cmd
+		 */
+		*(volatile unsigned char *) (uAddress) = 0x40;
+
+		/*
+		 * data to write
+		 */
+		*(volatile unsigned char *) (uAddress) = c2;
+
+		/*
+		 * get status
+		 */
+		*(volatile unsigned char *) (FLASH_BASE + 0x10000) = 0x70;
+
+		c1 = 0;
+
+		/*
+		 * wait up to 1 sec for this byte
+		 */
+		timeout1 = jiffies + 1 * HZ;
+
+		/*
+		 * while not ready...
+		 */
+		while (!(c1 & 0x80) && time_before(jiffies, timeout1))
+			c1 = *(volatile unsigned char *) (FLASH_BASE + 0x8000);
+
+		/*
+		 * if timeout getting status
+		 */
+		if (time_after_eq(jiffies, timeout1)) {
+			kick_open();
+			/*
+			 * reset err
+			 */
+			*(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x50;
+
+			goto WriteRetry;
+		}
+		/*
+		 * switch on read access, as a default flash operation mode
+		 */
+		kick_open();
+		/*
+		 * read access
+		 */
+		*(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0xFF;
+
+		/*
+		 * if hardware reports an error writing, and not timeout - 
+		 * reset the chip and retry
+		 */
+		if (c1 & 0x10) {
+			kick_open();
+			/*
+			 * reset err
+			 */
+			*(volatile unsigned char *) (FLASH_BASE + 0x8000) = 0x50;
+
+			/*
+			 * before timeout?
+			 */
+			if (time_before(jiffies, timeout)) {
+				if (flashdebug)
+					printk(KERN_DEBUG "write_block: Retrying write at 0x%X)n",
+					       pWritePtr - FLASH_BASE);
+
+				/*
+				 * no LED == waiting
+				 */
+				leds_event(led_amber_off);
+				/*
+				 * wait couple ms
+				 */
+				msleep(10);
+				/*
+				 * red LED == write
+				 */
+				leds_event(led_red_on);
+
+				goto WriteRetry;
+			} else {
+				printk(KERN_ERR "write_block: timeout at 0x%X\n",
+				       pWritePtr - FLASH_BASE);
+				/*
+				 * return error -2
+				 */
+				return -2;
+
+			}
+		}
+	}
+
+	/*
+	 * green LED == read/verify
+	 */
+	leds_event(led_amber_off);
+	leds_event(led_green_on);
+
+	msleep(10);
+
+	pWritePtr = (unsigned char *) ((unsigned int) (FLASH_BASE + p));
+
+	for (offset = 0; offset < count; offset++) {
+		char c, c1;
+		if (__get_user(c, buf))
+			return -EFAULT;
+		buf++;
+		if ((c1 = *pWritePtr++) != c) {
+			printk(KERN_ERR "write_block: verify error at 0x%X (%02X!=%02X)\n",
+			       pWritePtr - FLASH_BASE, c1, c);
+			return 0;
+		}
+	}
+
+	return count;
+}
+
+
+static void kick_open(void)
+{
+	unsigned long flags;
+
+	/*
+	 * we want to write a bit pattern XXX1 to Xilinx to enable
+	 * the write gate, which will be open for about the next 2ms.
+	 */
+	spin_lock_irqsave(&gpio_lock, flags);
+	cpld_modify(1, 1);
+	spin_unlock_irqrestore(&gpio_lock, flags);
+
+	/*
+	 * let the ISA bus to catch on...
+	 */
+	udelay(25);
+}
+
+static struct file_operations flash_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= flash_llseek,
+	.read		= flash_read,
+	.write		= flash_write,
+	.ioctl		= flash_ioctl,
+};
+
+static struct miscdevice flash_miscdev =
+{
+	FLASH_MINOR,
+	"nwflash",
+	&flash_fops
+};
+
+static int __init nwflash_init(void)
+{
+	int ret = -ENODEV;
+
+	if (machine_is_netwinder()) {
+		int id;
+
+		FLASH_BASE = ioremap(DC21285_FLASH, KFLASH_SIZE4);
+		if (!FLASH_BASE)
+			goto out;
+
+		id = get_flash_id();
+		if ((id != KFLASH_ID) && (id != KFLASH_ID4)) {
+			ret = -ENXIO;
+			iounmap((void *)FLASH_BASE);
+			printk("Flash: incorrect ID 0x%04X.\n", id);
+			goto out;
+		}
+
+		printk("Flash ROM driver v.%s, flash device ID 0x%04X, size %d Mb.\n",
+		       NWFLASH_VERSION, id, gbFlashSize / (1024 * 1024));
+
+		ret = misc_register(&flash_miscdev);
+		if (ret < 0) {
+			iounmap((void *)FLASH_BASE);
+		}
+	}
+out:
+	return ret;
+}
+
+static void __exit nwflash_exit(void)
+{
+	misc_deregister(&flash_miscdev);
+	iounmap((void *)FLASH_BASE);
+}
+
+MODULE_LICENSE("GPL");
+
+module_param(flashdebug, bool, 0644);
+
+module_init(nwflash_init);
+module_exit(nwflash_exit);
diff --git a/drivers/char/pcmcia/Kconfig b/drivers/char/pcmcia/Kconfig
new file mode 100644
index 0000000..d22bfdc
--- /dev/null
+++ b/drivers/char/pcmcia/Kconfig
@@ -0,0 +1,22 @@
+#
+# PCMCIA character device configuration
+#
+
+menu "PCMCIA character devices"
+	depends on HOTPLUG && PCMCIA!=n
+
+config SYNCLINK_CS
+	tristate "SyncLink PC Card support"
+	depends on PCMCIA
+	help
+	  Enable support for the SyncLink PC Card serial adapter, running
+	  asynchronous and HDLC communications up to 512Kbps. The port is
+	  selectable for RS-232, V.35, RS-449, RS-530, and X.21
+
+	  This driver may be built as a module ( = code which can be
+	  inserted in and removed from the running kernel whenever you want).
+	  The module will be called synclinkmp.  If you want to do that, say M
+	  here.
+
+endmenu
+
diff --git a/drivers/char/pcmcia/Makefile b/drivers/char/pcmcia/Makefile
new file mode 100644
index 0000000..1fcd4c5
--- /dev/null
+++ b/drivers/char/pcmcia/Makefile
@@ -0,0 +1,7 @@
+#
+# drivers/char/pcmcia/Makefile
+#
+# Makefile for the Linux PCMCIA char device drivers.
+#
+
+obj-$(CONFIG_SYNCLINK_CS) += synclink_cs.o
diff --git a/drivers/char/pcmcia/synclink_cs.c b/drivers/char/pcmcia/synclink_cs.c
new file mode 100644
index 0000000..1c8d866
--- /dev/null
+++ b/drivers/char/pcmcia/synclink_cs.c
@@ -0,0 +1,4611 @@
+/*
+ * linux/drivers/char/pcmcia/synclink_cs.c
+ *
+ * $Id: synclink_cs.c,v 4.26 2004/08/11 19:30:02 paulkf Exp $
+ *
+ * Device driver for Microgate SyncLink PC Card
+ * multiprotocol serial adapter.
+ *
+ * written by Paul Fulghum for Microgate Corporation
+ * paulkf@microgate.com
+ *
+ * Microgate and SyncLink are trademarks of Microgate Corporation
+ *
+ * This code is released under the GNU General Public License (GPL)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define VERSION(ver,rel,seq) (((ver)<<16) | ((rel)<<8) | (seq))
+#if defined(__i386__)
+#  define BREAKPOINT() asm("   int $3");
+#else
+#  define BREAKPOINT() { }
+#endif
+
+#define MAX_DEVICE_COUNT 4
+
+#include <linux/config.h>	
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/time.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <asm/serial.h>
+#include <linux/delay.h>
+#include <linux/ioctl.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <linux/bitops.h>
+#include <asm/types.h>
+#include <linux/termios.h>
+#include <linux/workqueue.h>
+#include <linux/hdlc.h>
+
+#include <pcmcia/version.h>
+#include <pcmcia/cs_types.h>
+#include <pcmcia/cs.h>
+#include <pcmcia/cistpl.h>
+#include <pcmcia/cisreg.h>
+#include <pcmcia/ds.h>
+
+#ifdef CONFIG_HDLC_MODULE
+#define CONFIG_HDLC 1
+#endif
+
+#define GET_USER(error,value,addr) error = get_user(value,addr)
+#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0
+#define PUT_USER(error,value,addr) error = put_user(value,addr)
+#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0
+
+#include <asm/uaccess.h>
+
+#include "linux/synclink.h"
+
+static MGSL_PARAMS default_params = {
+	MGSL_MODE_HDLC,			/* unsigned long mode */
+	0,				/* unsigned char loopback; */
+	HDLC_FLAG_UNDERRUN_ABORT15,	/* unsigned short flags; */
+	HDLC_ENCODING_NRZI_SPACE,	/* unsigned char encoding; */
+	0,				/* unsigned long clock_speed; */
+	0xff,				/* unsigned char addr_filter; */
+	HDLC_CRC_16_CCITT,		/* unsigned short crc_type; */
+	HDLC_PREAMBLE_LENGTH_8BITS,	/* unsigned char preamble_length; */
+	HDLC_PREAMBLE_PATTERN_NONE,	/* unsigned char preamble; */
+	9600,				/* unsigned long data_rate; */
+	8,				/* unsigned char data_bits; */
+	1,				/* unsigned char stop_bits; */
+	ASYNC_PARITY_NONE		/* unsigned char parity; */
+};
+
+typedef struct
+{
+	int count;
+	unsigned char status;
+	char data[1];
+} RXBUF;
+
+/* The queue of BH actions to be performed */
+
+#define BH_RECEIVE  1
+#define BH_TRANSMIT 2
+#define BH_STATUS   4
+
+#define IO_PIN_SHUTDOWN_LIMIT 100
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+struct _input_signal_events {
+	int	ri_up;	
+	int	ri_down;
+	int	dsr_up;
+	int	dsr_down;
+	int	dcd_up;
+	int	dcd_down;
+	int	cts_up;
+	int	cts_down;
+};
+
+
+/*
+ * Device instance data structure
+ */
+ 
+typedef struct _mgslpc_info {
+	void *if_ptr;	/* General purpose pointer (used by SPPP) */
+	int			magic;
+	int			flags;
+	int			count;		/* count of opens */
+	int			line;
+	unsigned short		close_delay;
+	unsigned short		closing_wait;	/* time to wait before closing */
+	
+	struct mgsl_icount	icount;
+	
+	struct tty_struct 	*tty;
+	int			timeout;
+	int			x_char;		/* xon/xoff character */
+	int			blocked_open;	/* # of blocked opens */
+	unsigned char		read_status_mask;
+	unsigned char		ignore_status_mask;	
+
+	unsigned char *tx_buf;
+	int            tx_put;
+	int            tx_get;
+	int            tx_count;
+
+	/* circular list of fixed length rx buffers */
+
+	unsigned char  *rx_buf;        /* memory allocated for all rx buffers */
+	int            rx_buf_total_size; /* size of memory allocated for rx buffers */
+	int            rx_put;         /* index of next empty rx buffer */
+	int            rx_get;         /* index of next full rx buffer */
+	int            rx_buf_size;    /* size in bytes of single rx buffer */
+	int            rx_buf_count;   /* total number of rx buffers */
+	int            rx_frame_count; /* number of full rx buffers */
+	
+	wait_queue_head_t	open_wait;
+	wait_queue_head_t	close_wait;
+	
+	wait_queue_head_t	status_event_wait_q;
+	wait_queue_head_t	event_wait_q;
+	struct timer_list	tx_timer;	/* HDLC transmit timeout timer */
+	struct _mgslpc_info	*next_device;	/* device list link */
+
+	unsigned short imra_value;
+	unsigned short imrb_value;
+	unsigned char  pim_value;
+
+	spinlock_t lock;
+	struct work_struct task;		/* task structure for scheduling bh */
+
+	u32 max_frame_size;
+
+	u32 pending_bh;
+
+	int bh_running;
+	int bh_requested;
+	
+	int dcd_chkcount; /* check counts to prevent */
+	int cts_chkcount; /* too many IRQs if a signal */
+	int dsr_chkcount; /* is floating */
+	int ri_chkcount;
+
+	int rx_enabled;
+	int rx_overflow;
+
+	int tx_enabled;
+	int tx_active;
+	int tx_aborting;
+	u32 idle_mode;
+
+	int if_mode; /* serial interface selection (RS-232, v.35 etc) */
+
+	char device_name[25];		/* device instance name */
+
+	unsigned int io_base;	/* base I/O address of adapter */
+	unsigned int irq_level;
+	
+	MGSL_PARAMS params;		/* communications parameters */
+
+	unsigned char serial_signals;	/* current serial signal states */
+
+	char irq_occurred;		/* for diagnostics use */
+	char testing_irq;
+	unsigned int init_error;	/* startup error (DIAGS)	*/
+
+	char flag_buf[MAX_ASYNC_BUFFER_SIZE];
+	BOOLEAN drop_rts_on_tx_done;
+
+	struct	_input_signal_events	input_signal_events;
+
+	/* PCMCIA support */
+	dev_link_t	      link;
+	dev_node_t	      node;
+	int		      stop;
+
+	/* SPPP/Cisco HDLC device parts */
+	int netcount;
+	int dosyncppp;
+	spinlock_t netlock;
+
+#ifdef CONFIG_HDLC
+	struct net_device *netdev;
+#endif
+
+} MGSLPC_INFO;
+
+#define MGSLPC_MAGIC 0x5402
+
+/*
+ * The size of the serial xmit buffer is 1 page, or 4096 bytes
+ */
+#define TXBUFSIZE 4096
+
+    
+#define CHA     0x00   /* channel A offset */
+#define CHB     0x40   /* channel B offset */
+
+/*
+ *  FIXME: PPC has PVR defined in asm/reg.h.  For now we just undef it.
+ */
+#undef PVR
+
+#define RXFIFO  0
+#define TXFIFO  0
+#define STAR    0x20
+#define CMDR    0x20
+#define RSTA    0x21
+#define PRE     0x21
+#define MODE    0x22
+#define TIMR    0x23
+#define XAD1    0x24
+#define XAD2    0x25
+#define RAH1    0x26
+#define RAH2    0x27
+#define DAFO    0x27
+#define RAL1    0x28
+#define RFC     0x28
+#define RHCR    0x29
+#define RAL2    0x29
+#define RBCL    0x2a
+#define XBCL    0x2a
+#define RBCH    0x2b
+#define XBCH    0x2b
+#define CCR0    0x2c
+#define CCR1    0x2d
+#define CCR2    0x2e
+#define CCR3    0x2f
+#define VSTR    0x34
+#define BGR     0x34
+#define RLCR    0x35
+#define AML     0x36
+#define AMH     0x37
+#define GIS     0x38
+#define IVA     0x38
+#define IPC     0x39
+#define ISR     0x3a
+#define IMR     0x3a
+#define PVR     0x3c
+#define PIS     0x3d
+#define PIM     0x3d
+#define PCR     0x3e
+#define CCR4    0x3f
+    
+// IMR/ISR
+    
+#define IRQ_BREAK_ON    BIT15   // rx break detected
+#define IRQ_DATAOVERRUN BIT14	// receive data overflow
+#define IRQ_ALLSENT     BIT13	// all sent
+#define IRQ_UNDERRUN    BIT12	// transmit data underrun
+#define IRQ_TIMER       BIT11	// timer interrupt
+#define IRQ_CTS         BIT10	// CTS status change
+#define IRQ_TXREPEAT    BIT9	// tx message repeat
+#define IRQ_TXFIFO      BIT8	// transmit pool ready
+#define IRQ_RXEOM       BIT7	// receive message end
+#define IRQ_EXITHUNT    BIT6	// receive frame start
+#define IRQ_RXTIME      BIT6    // rx char timeout
+#define IRQ_DCD         BIT2	// carrier detect status change
+#define IRQ_OVERRUN     BIT1	// receive frame overflow
+#define IRQ_RXFIFO      BIT0	// receive pool full
+    
+// STAR
+    
+#define XFW   BIT6		// transmit FIFO write enable
+#define CEC   BIT2		// command executing
+#define CTS   BIT1		// CTS state
+    
+#define PVR_DTR      BIT0
+#define PVR_DSR      BIT1
+#define PVR_RI       BIT2
+#define PVR_AUTOCTS  BIT3
+#define PVR_RS232    0x20   /* 0010b */
+#define PVR_V35      0xe0   /* 1110b */
+#define PVR_RS422    0x40   /* 0100b */
+    
+/* Register access functions */ 
+    
+#define write_reg(info, reg, val) outb((val),(info)->io_base + (reg))
+#define read_reg(info, reg) inb((info)->io_base + (reg))
+
+#define read_reg16(info, reg) inw((info)->io_base + (reg))  
+#define write_reg16(info, reg, val) outw((val), (info)->io_base + (reg))
+    
+#define set_reg_bits(info, reg, mask) \
+    write_reg(info, (reg), \
+		 (unsigned char) (read_reg(info, (reg)) | (mask)))  
+#define clear_reg_bits(info, reg, mask) \
+    write_reg(info, (reg), \
+		 (unsigned char) (read_reg(info, (reg)) & ~(mask)))  
+/*
+ * interrupt enable/disable routines
+ */ 
+static void irq_disable(MGSLPC_INFO *info, unsigned char channel, unsigned short mask) 
+{
+	if (channel == CHA) {
+		info->imra_value |= mask;
+		write_reg16(info, CHA + IMR, info->imra_value);
+	} else {
+		info->imrb_value |= mask;
+		write_reg16(info, CHB + IMR, info->imrb_value);
+	}
+}
+static void irq_enable(MGSLPC_INFO *info, unsigned char channel, unsigned short mask) 
+{
+	if (channel == CHA) {
+		info->imra_value &= ~mask;
+		write_reg16(info, CHA + IMR, info->imra_value);
+	} else {
+		info->imrb_value &= ~mask;
+		write_reg16(info, CHB + IMR, info->imrb_value);
+	}
+}
+
+#define port_irq_disable(info, mask) \
+  { info->pim_value |= (mask); write_reg(info, PIM, info->pim_value); }
+
+#define port_irq_enable(info, mask) \
+  { info->pim_value &= ~(mask); write_reg(info, PIM, info->pim_value); }
+
+static void rx_start(MGSLPC_INFO *info);
+static void rx_stop(MGSLPC_INFO *info);
+
+static void tx_start(MGSLPC_INFO *info);
+static void tx_stop(MGSLPC_INFO *info);
+static void tx_set_idle(MGSLPC_INFO *info);
+
+static void get_signals(MGSLPC_INFO *info);
+static void set_signals(MGSLPC_INFO *info);
+
+static void reset_device(MGSLPC_INFO *info);
+
+static void hdlc_mode(MGSLPC_INFO *info);
+static void async_mode(MGSLPC_INFO *info);
+
+static void tx_timeout(unsigned long context);
+
+static int ioctl_common(MGSLPC_INFO *info, unsigned int cmd, unsigned long arg);
+
+#ifdef CONFIG_HDLC
+#define dev_to_port(D) (dev_to_hdlc(D)->priv)
+static void hdlcdev_tx_done(MGSLPC_INFO *info);
+static void hdlcdev_rx(MGSLPC_INFO *info, char *buf, int size);
+static int  hdlcdev_init(MGSLPC_INFO *info);
+static void hdlcdev_exit(MGSLPC_INFO *info);
+#endif
+
+static void trace_block(MGSLPC_INFO *info,const char* data, int count, int xmit);
+
+static BOOLEAN register_test(MGSLPC_INFO *info);
+static BOOLEAN irq_test(MGSLPC_INFO *info);
+static int adapter_test(MGSLPC_INFO *info);
+
+static int claim_resources(MGSLPC_INFO *info);
+static void release_resources(MGSLPC_INFO *info);
+static void mgslpc_add_device(MGSLPC_INFO *info);
+static void mgslpc_remove_device(MGSLPC_INFO *info);
+
+static int  rx_get_frame(MGSLPC_INFO *info);
+static void rx_reset_buffers(MGSLPC_INFO *info);
+static int  rx_alloc_buffers(MGSLPC_INFO *info);
+static void rx_free_buffers(MGSLPC_INFO *info);
+
+static irqreturn_t mgslpc_isr(int irq, void *dev_id, struct pt_regs * regs);
+
+/*
+ * Bottom half interrupt handlers
+ */
+static void bh_handler(void* Context);
+static void bh_transmit(MGSLPC_INFO *info);
+static void bh_status(MGSLPC_INFO *info);
+
+/*
+ * ioctl handlers
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file);
+static int tiocmset(struct tty_struct *tty, struct file *file,
+		    unsigned int set, unsigned int clear);
+static int get_stats(MGSLPC_INFO *info, struct mgsl_icount __user *user_icount);
+static int get_params(MGSLPC_INFO *info, MGSL_PARAMS __user *user_params);
+static int set_params(MGSLPC_INFO *info, MGSL_PARAMS __user *new_params);
+static int get_txidle(MGSLPC_INFO *info, int __user *idle_mode);
+static int set_txidle(MGSLPC_INFO *info, int idle_mode);
+static int set_txenable(MGSLPC_INFO *info, int enable);
+static int tx_abort(MGSLPC_INFO *info);
+static int set_rxenable(MGSLPC_INFO *info, int enable);
+static int wait_events(MGSLPC_INFO *info, int __user *mask);
+
+static MGSLPC_INFO *mgslpc_device_list = NULL;
+static int mgslpc_device_count = 0;
+
+/*
+ * Set this param to non-zero to load eax with the
+ * .text section address and breakpoint on module load.
+ * This is useful for use with gdb and add-symbol-file command.
+ */
+static int break_on_load=0;
+
+/*
+ * Driver major number, defaults to zero to get auto
+ * assigned major number. May be forced as module parameter.
+ */
+static int ttymajor=0;
+
+static int debug_level = 0;
+static int maxframe[MAX_DEVICE_COUNT] = {0,};
+static int dosyncppp[MAX_DEVICE_COUNT] = {1,1,1,1};
+
+module_param(break_on_load, bool, 0);
+module_param(ttymajor, int, 0);
+module_param(debug_level, int, 0);
+module_param_array(maxframe, int, NULL, 0);
+module_param_array(dosyncppp, int, NULL, 0);
+
+MODULE_LICENSE("GPL");
+
+static char *driver_name = "SyncLink PC Card driver";
+static char *driver_version = "$Revision: 4.26 $";
+
+static struct tty_driver *serial_driver;
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+static void mgslpc_change_params(MGSLPC_INFO *info);
+static void mgslpc_wait_until_sent(struct tty_struct *tty, int timeout);
+
+/* PCMCIA prototypes */
+
+static void mgslpc_config(dev_link_t *link);
+static void mgslpc_release(u_long arg);
+static int  mgslpc_event(event_t event, int priority,
+			 event_callback_args_t *args);
+static dev_link_t *mgslpc_attach(void);
+static void mgslpc_detach(dev_link_t *);
+
+static dev_info_t dev_info = "synclink_cs";
+static dev_link_t *dev_list = NULL;
+
+/*
+ * 1st function defined in .text section. Calling this function in
+ * init_module() followed by a breakpoint allows a remote debugger
+ * (gdb) to get the .text address for the add-symbol-file command.
+ * This allows remote debugging of dynamically loadable modules.
+ */
+static void* mgslpc_get_text_ptr(void)
+{
+	return mgslpc_get_text_ptr;
+}
+
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_flush_buffer - flush line discipline receive buffers
+ * ldisc_receive_buf  - pass receive data to line discipline
+ */
+
+static void ldisc_flush_buffer(struct tty_struct *tty)
+{
+	struct tty_ldisc *ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->flush_buffer)
+			ld->flush_buffer(tty);
+		tty_ldisc_deref(ld);
+	}
+}
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+			      const __u8 *data, char *flags, int count)
+{
+	struct tty_ldisc *ld;
+	if (!tty)
+		return;
+	ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->receive_buf)
+			ld->receive_buf(tty, data, flags, count);
+		tty_ldisc_deref(ld);
+	}
+}
+
+static dev_link_t *mgslpc_attach(void)
+{
+    MGSLPC_INFO *info;
+    dev_link_t *link;
+    client_reg_t client_reg;
+    int ret;
+    
+    if (debug_level >= DEBUG_LEVEL_INFO)
+	    printk("mgslpc_attach\n");
+	
+    info = (MGSLPC_INFO *)kmalloc(sizeof(MGSLPC_INFO), GFP_KERNEL);
+    if (!info) {
+	    printk("Error can't allocate device instance data\n");
+	    return NULL;
+    }
+
+    memset(info, 0, sizeof(MGSLPC_INFO));
+    info->magic = MGSLPC_MAGIC;
+    INIT_WORK(&info->task, bh_handler, info);
+    info->max_frame_size = 4096;
+    info->close_delay = 5*HZ/10;
+    info->closing_wait = 30*HZ;
+    init_waitqueue_head(&info->open_wait);
+    init_waitqueue_head(&info->close_wait);
+    init_waitqueue_head(&info->status_event_wait_q);
+    init_waitqueue_head(&info->event_wait_q);
+    spin_lock_init(&info->lock);
+    spin_lock_init(&info->netlock);
+    memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
+    info->idle_mode = HDLC_TXIDLE_FLAGS;		
+    info->imra_value = 0xffff;
+    info->imrb_value = 0xffff;
+    info->pim_value = 0xff;
+
+    link = &info->link;
+    link->priv = info;
+    
+    /* Initialize the dev_link_t structure */
+
+    /* Interrupt setup */
+    link->irq.Attributes = IRQ_TYPE_EXCLUSIVE;
+    link->irq.IRQInfo1   = IRQ_INFO2_VALID | IRQ_LEVEL_ID;
+    link->irq.Handler = NULL;
+    
+    link->conf.Attributes = 0;
+    link->conf.Vcc = 50;
+    link->conf.IntType = INT_MEMORY_AND_IO;
+
+    /* Register with Card Services */
+    link->next = dev_list;
+    dev_list = link;
+
+    client_reg.dev_info = &dev_info;
+    client_reg.EventMask =
+	    CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
+	    CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET |
+	    CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
+    client_reg.event_handler = &mgslpc_event;
+    client_reg.Version = 0x0210;
+    client_reg.event_callback_args.client_data = link;
+
+    ret = pcmcia_register_client(&link->handle, &client_reg);
+    if (ret != CS_SUCCESS) {
+	    cs_error(link->handle, RegisterClient, ret);
+	    mgslpc_detach(link);
+	    return NULL;
+    }
+
+    mgslpc_add_device(info);
+
+    return link;
+}
+
+/* Card has been inserted.
+ */
+
+#define CS_CHECK(fn, ret) \
+do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0)
+
+static void mgslpc_config(dev_link_t *link)
+{
+    client_handle_t handle = link->handle;
+    MGSLPC_INFO *info = link->priv;
+    tuple_t tuple;
+    cisparse_t parse;
+    int last_fn, last_ret;
+    u_char buf[64];
+    config_info_t conf;
+    cistpl_cftable_entry_t dflt = { 0 };
+    cistpl_cftable_entry_t *cfg;
+    
+    if (debug_level >= DEBUG_LEVEL_INFO)
+	    printk("mgslpc_config(0x%p)\n", link);
+
+    /* read CONFIG tuple to find its configuration registers */
+    tuple.DesiredTuple = CISTPL_CONFIG;
+    tuple.Attributes = 0;
+    tuple.TupleData = buf;
+    tuple.TupleDataMax = sizeof(buf);
+    tuple.TupleOffset = 0;
+    CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple));
+    CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple));
+    CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse));
+    link->conf.ConfigBase = parse.config.base;
+    link->conf.Present = parse.config.rmask[0];
+    
+    /* Configure card */
+    link->state |= DEV_CONFIG;
+
+    /* Look up the current Vcc */
+    CS_CHECK(GetConfigurationInfo, pcmcia_get_configuration_info(handle, &conf));
+    link->conf.Vcc = conf.Vcc;
+
+    /* get CIS configuration entry */
+
+    tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
+    CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(handle, &tuple));
+
+    cfg = &(parse.cftable_entry);
+    CS_CHECK(GetTupleData, pcmcia_get_tuple_data(handle, &tuple));
+    CS_CHECK(ParseTuple, pcmcia_parse_tuple(handle, &tuple, &parse));
+
+    if (cfg->flags & CISTPL_CFTABLE_DEFAULT) dflt = *cfg;
+    if (cfg->index == 0)
+	    goto cs_failed;
+
+    link->conf.ConfigIndex = cfg->index;
+    link->conf.Attributes |= CONF_ENABLE_IRQ;
+	
+    /* IO window settings */
+    link->io.NumPorts1 = 0;
+    if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0)) {
+	    cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io;
+	    link->io.Attributes1 = IO_DATA_PATH_WIDTH_AUTO;
+	    if (!(io->flags & CISTPL_IO_8BIT))
+		    link->io.Attributes1 = IO_DATA_PATH_WIDTH_16;
+	    if (!(io->flags & CISTPL_IO_16BIT))
+		    link->io.Attributes1 = IO_DATA_PATH_WIDTH_8;
+	    link->io.IOAddrLines = io->flags & CISTPL_IO_LINES_MASK;
+	    link->io.BasePort1 = io->win[0].base;
+	    link->io.NumPorts1 = io->win[0].len;
+	    CS_CHECK(RequestIO, pcmcia_request_io(link->handle, &link->io));
+    }
+
+    link->conf.Attributes = CONF_ENABLE_IRQ;
+    link->conf.Vcc = 50;
+    link->conf.IntType = INT_MEMORY_AND_IO;
+    link->conf.ConfigIndex = 8;
+    link->conf.Present = PRESENT_OPTION;
+    
+    link->irq.Attributes |= IRQ_HANDLE_PRESENT;
+    link->irq.Handler     = mgslpc_isr;
+    link->irq.Instance    = info;
+    CS_CHECK(RequestIRQ, pcmcia_request_irq(link->handle, &link->irq));
+
+    CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link->handle, &link->conf));
+
+    info->io_base = link->io.BasePort1;
+    info->irq_level = link->irq.AssignedIRQ;
+
+    /* add to linked list of devices */
+    sprintf(info->node.dev_name, "mgslpc0");
+    info->node.major = info->node.minor = 0;
+    link->dev = &info->node;
+
+    printk(KERN_INFO "%s: index 0x%02x:",
+	   info->node.dev_name, link->conf.ConfigIndex);
+    if (link->conf.Attributes & CONF_ENABLE_IRQ)
+	    printk(", irq %d", link->irq.AssignedIRQ);
+    if (link->io.NumPorts1)
+	    printk(", io 0x%04x-0x%04x", link->io.BasePort1,
+		   link->io.BasePort1+link->io.NumPorts1-1);
+    printk("\n");
+    
+    link->state &= ~DEV_CONFIG_PENDING;
+    return;
+
+cs_failed:
+    cs_error(link->handle, last_fn, last_ret);
+    mgslpc_release((u_long)link);
+}
+
+/* Card has been removed.
+ * Unregister device and release PCMCIA configuration.
+ * If device is open, postpone until it is closed.
+ */
+static void mgslpc_release(u_long arg)
+{
+    dev_link_t *link = (dev_link_t *)arg;
+
+    if (debug_level >= DEBUG_LEVEL_INFO)
+	    printk("mgslpc_release(0x%p)\n", link);
+
+    /* Unlink the device chain */
+    link->dev = NULL;
+    link->state &= ~DEV_CONFIG;
+
+    pcmcia_release_configuration(link->handle);
+    if (link->io.NumPorts1)
+	    pcmcia_release_io(link->handle, &link->io);
+    if (link->irq.AssignedIRQ)
+	    pcmcia_release_irq(link->handle, &link->irq);
+    if (link->state & DEV_STALE_LINK)
+	    mgslpc_detach(link);
+}
+
+static void mgslpc_detach(dev_link_t *link)
+{
+    dev_link_t **linkp;
+
+    if (debug_level >= DEBUG_LEVEL_INFO)
+	    printk("mgslpc_detach(0x%p)\n", link);
+    
+    /* find device */
+    for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next)
+	    if (*linkp == link) break;
+    if (*linkp == NULL)
+	    return;
+
+    if (link->state & DEV_CONFIG) {
+	    /* device is configured/active, mark it so when
+	     * release() is called a proper detach() occurs.
+	     */
+	    if (debug_level >= DEBUG_LEVEL_INFO)
+		    printk(KERN_DEBUG "synclinkpc: detach postponed, '%s' "
+			   "still locked\n", link->dev->dev_name);
+	    link->state |= DEV_STALE_LINK;
+	    return;
+    }
+
+    /* Break the link with Card Services */
+    if (link->handle)
+	    pcmcia_deregister_client(link->handle);
+    
+    /* Unlink device structure, and free it */
+    *linkp = link->next;
+    mgslpc_remove_device((MGSLPC_INFO *)link->priv);
+}
+
+static int mgslpc_event(event_t event, int priority,
+			event_callback_args_t *args)
+{
+    dev_link_t *link = args->client_data;
+    MGSLPC_INFO *info = link->priv;
+    
+    if (debug_level >= DEBUG_LEVEL_INFO)
+	    printk("mgslpc_event(0x%06x)\n", event);
+    
+    switch (event) {
+    case CS_EVENT_CARD_REMOVAL:
+	    link->state &= ~DEV_PRESENT;
+	    if (link->state & DEV_CONFIG) {
+		    ((MGSLPC_INFO *)link->priv)->stop = 1;
+		    mgslpc_release((u_long)link);
+	    }
+	    break;
+    case CS_EVENT_CARD_INSERTION:
+	    link->state |= DEV_PRESENT | DEV_CONFIG_PENDING;
+	    mgslpc_config(link);
+	    break;
+    case CS_EVENT_PM_SUSPEND:
+	    link->state |= DEV_SUSPEND;
+	    /* Fall through... */
+    case CS_EVENT_RESET_PHYSICAL:
+	    /* Mark the device as stopped, to block IO until later */
+	    info->stop = 1;
+	    if (link->state & DEV_CONFIG)
+		    pcmcia_release_configuration(link->handle);
+	    break;
+    case CS_EVENT_PM_RESUME:
+	    link->state &= ~DEV_SUSPEND;
+	    /* Fall through... */
+    case CS_EVENT_CARD_RESET:
+	    if (link->state & DEV_CONFIG)
+		    pcmcia_request_configuration(link->handle, &link->conf);
+	    info->stop = 0;
+	    break;
+    }
+    return 0;
+}
+
+static inline int mgslpc_paranoia_check(MGSLPC_INFO *info,
+					char *name, const char *routine)
+{
+#ifdef MGSLPC_PARANOIA_CHECK
+	static const char *badmagic =
+		"Warning: bad magic number for mgsl struct (%s) in %s\n";
+	static const char *badinfo =
+		"Warning: null mgslpc_info for (%s) in %s\n";
+
+	if (!info) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (info->magic != MGSLPC_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+#else
+	if (!info)
+		return 1;
+#endif
+	return 0;
+}
+
+
+#define CMD_RXFIFO      BIT7	// release current rx FIFO
+#define CMD_RXRESET     BIT6	// receiver reset
+#define CMD_RXFIFO_READ BIT5
+#define CMD_START_TIMER BIT4
+#define CMD_TXFIFO      BIT3	// release current tx FIFO
+#define CMD_TXEOM       BIT1	// transmit end message
+#define CMD_TXRESET     BIT0	// transmit reset
+
+static BOOLEAN wait_command_complete(MGSLPC_INFO *info, unsigned char channel) 
+{
+	int i = 0;
+	/* wait for command completion */ 
+	while (read_reg(info, (unsigned char)(channel+STAR)) & BIT2) {
+		udelay(1);
+		if (i++ == 1000)
+			return FALSE;
+	}
+	return TRUE;
+}
+
+static void issue_command(MGSLPC_INFO *info, unsigned char channel, unsigned char cmd) 
+{
+	wait_command_complete(info, channel);
+	write_reg(info, (unsigned char) (channel + CMDR), cmd);
+}
+
+static void tx_pause(struct tty_struct *tty)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+	
+	if (mgslpc_paranoia_check(info, tty->name, "tx_pause"))
+		return;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("tx_pause(%s)\n",info->device_name);	
+		
+	spin_lock_irqsave(&info->lock,flags);
+	if (info->tx_enabled)
+	 	tx_stop(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+static void tx_release(struct tty_struct *tty)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+	
+	if (mgslpc_paranoia_check(info, tty->name, "tx_release"))
+		return;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("tx_release(%s)\n",info->device_name);	
+		
+	spin_lock_irqsave(&info->lock,flags);
+	if (!info->tx_enabled)
+	 	tx_start(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Return next bottom half action to perform.
+ * or 0 if nothing to do.
+ */
+static int bh_action(MGSLPC_INFO *info)
+{
+	unsigned long flags;
+	int rc = 0;
+	
+	spin_lock_irqsave(&info->lock,flags);
+
+	if (info->pending_bh & BH_RECEIVE) {
+		info->pending_bh &= ~BH_RECEIVE;
+		rc = BH_RECEIVE;
+	} else if (info->pending_bh & BH_TRANSMIT) {
+		info->pending_bh &= ~BH_TRANSMIT;
+		rc = BH_TRANSMIT;
+	} else if (info->pending_bh & BH_STATUS) {
+		info->pending_bh &= ~BH_STATUS;
+		rc = BH_STATUS;
+	}
+
+	if (!rc) {
+		/* Mark BH routine as complete */
+		info->bh_running   = 0;
+		info->bh_requested = 0;
+	}
+	
+	spin_unlock_irqrestore(&info->lock,flags);
+	
+	return rc;
+}
+
+void bh_handler(void* Context)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO*)Context;
+	int action;
+
+	if (!info)
+		return;
+		
+	if (debug_level >= DEBUG_LEVEL_BH)
+		printk( "%s(%d):bh_handler(%s) entry\n",
+			__FILE__,__LINE__,info->device_name);
+	
+	info->bh_running = 1;
+
+	while((action = bh_action(info)) != 0) {
+	
+		/* Process work item */
+		if ( debug_level >= DEBUG_LEVEL_BH )
+			printk( "%s(%d):bh_handler() work item action=%d\n",
+				__FILE__,__LINE__,action);
+
+		switch (action) {
+		
+		case BH_RECEIVE:
+			while(rx_get_frame(info));
+			break;
+		case BH_TRANSMIT:
+			bh_transmit(info);
+			break;
+		case BH_STATUS:
+			bh_status(info);
+			break;
+		default:
+			/* unknown work item ID */
+			printk("Unknown work item ID=%08X!\n", action);
+			break;
+		}
+	}
+
+	if (debug_level >= DEBUG_LEVEL_BH)
+		printk( "%s(%d):bh_handler(%s) exit\n",
+			__FILE__,__LINE__,info->device_name);
+}
+
+void bh_transmit(MGSLPC_INFO *info)
+{
+	struct tty_struct *tty = info->tty;
+	if (debug_level >= DEBUG_LEVEL_BH)
+		printk("bh_transmit() entry on %s\n", info->device_name);
+
+	if (tty) {
+		tty_wakeup(tty);
+		wake_up_interruptible(&tty->write_wait);
+	}
+}
+
+void bh_status(MGSLPC_INFO *info)
+{
+	info->ri_chkcount = 0;
+	info->dsr_chkcount = 0;
+	info->dcd_chkcount = 0;
+	info->cts_chkcount = 0;
+}
+
+/* eom: non-zero = end of frame */ 
+static void rx_ready_hdlc(MGSLPC_INFO *info, int eom)
+{
+	unsigned char data[2];
+	unsigned char fifo_count, read_count, i;
+	RXBUF *buf = (RXBUF*)(info->rx_buf + (info->rx_put * info->rx_buf_size));
+
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):rx_ready_hdlc(eom=%d)\n",__FILE__,__LINE__,eom);
+	
+	if (!info->rx_enabled)
+		return;
+
+	if (info->rx_frame_count >= info->rx_buf_count) {
+		/* no more free buffers */
+		issue_command(info, CHA, CMD_RXRESET);
+		info->pending_bh |= BH_RECEIVE;
+		info->rx_overflow = 1;
+		info->icount.buf_overrun++;
+		return;
+	}
+
+	if (eom) {
+		/* end of frame, get FIFO count from RBCL register */ 
+		if (!(fifo_count = (unsigned char)(read_reg(info, CHA+RBCL) & 0x1f)))
+			fifo_count = 32;
+	} else
+		fifo_count = 32;
+	
+	do {
+		if (fifo_count == 1) {
+			read_count = 1;
+			data[0] = read_reg(info, CHA + RXFIFO);
+		} else {
+			read_count = 2;
+			*((unsigned short *) data) = read_reg16(info, CHA + RXFIFO);
+		}
+		fifo_count -= read_count;
+		if (!fifo_count && eom)
+			buf->status = data[--read_count];
+
+		for (i = 0; i < read_count; i++) {
+			if (buf->count >= info->max_frame_size) {
+				/* frame too large, reset receiver and reset current buffer */
+				issue_command(info, CHA, CMD_RXRESET);
+				buf->count = 0;
+				return;
+			}
+			*(buf->data + buf->count) = data[i];
+			buf->count++;
+		}
+	} while (fifo_count);
+
+	if (eom) {
+		info->pending_bh |= BH_RECEIVE;
+		info->rx_frame_count++;
+		info->rx_put++;
+		if (info->rx_put >= info->rx_buf_count)
+			info->rx_put = 0;
+	}
+	issue_command(info, CHA, CMD_RXFIFO);
+}
+
+static void rx_ready_async(MGSLPC_INFO *info, int tcd)
+{
+	unsigned char data, status;
+	int fifo_count;
+ 	struct tty_struct *tty = info->tty;
+ 	struct mgsl_icount *icount = &info->icount;
+
+	if (tcd) {
+		/* early termination, get FIFO count from RBCL register */ 
+		fifo_count = (unsigned char)(read_reg(info, CHA+RBCL) & 0x1f);
+
+		/* Zero fifo count could mean 0 or 32 bytes available.
+		 * If BIT5 of STAR is set then at least 1 byte is available.
+		 */
+		if (!fifo_count && (read_reg(info,CHA+STAR) & BIT5))
+			fifo_count = 32;
+	} else
+		fifo_count = 32;
+	
+	/* Flush received async data to receive data buffer. */ 
+	while (fifo_count) {
+		data   = read_reg(info, CHA + RXFIFO);
+		status = read_reg(info, CHA + RXFIFO);
+		fifo_count -= 2;
+
+		if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+			break;
+			
+		*tty->flip.char_buf_ptr = data;
+		icount->rx++;
+		
+		*tty->flip.flag_buf_ptr = 0;
+
+		// if no frameing/crc error then save data
+		// BIT7:parity error
+		// BIT6:framing error
+
+		if (status & (BIT7 + BIT6)) {
+			if (status & BIT7) 
+				icount->parity++;
+			else
+				icount->frame++;
+
+			/* discard char if tty control flags say so */
+			if (status & info->ignore_status_mask)
+				continue;
+				
+			status &= info->read_status_mask;
+
+			if (status & BIT7)
+				*tty->flip.flag_buf_ptr = TTY_PARITY;
+			else if (status & BIT6)
+				*tty->flip.flag_buf_ptr = TTY_FRAME;
+		}
+		
+		tty->flip.flag_buf_ptr++;
+		tty->flip.char_buf_ptr++;
+		tty->flip.count++;
+	}
+	issue_command(info, CHA, CMD_RXFIFO);
+
+	if (debug_level >= DEBUG_LEVEL_ISR) {
+		printk("%s(%d):rx_ready_async count=%d\n",
+			__FILE__,__LINE__,tty->flip.count);
+		printk("%s(%d):rx=%d brk=%d parity=%d frame=%d overrun=%d\n",
+			__FILE__,__LINE__,icount->rx,icount->brk,
+			icount->parity,icount->frame,icount->overrun);
+	}
+			
+	if (tty->flip.count)
+		tty_flip_buffer_push(tty);
+}
+
+
+static void tx_done(MGSLPC_INFO *info)
+{
+	if (!info->tx_active)
+		return;
+			
+	info->tx_active = 0;
+	info->tx_aborting = 0;
+
+	if (info->params.mode == MGSL_MODE_ASYNC)
+		return;
+
+	info->tx_count = info->tx_put = info->tx_get = 0;
+	del_timer(&info->tx_timer);	
+	
+	if (info->drop_rts_on_tx_done) {
+		get_signals(info);
+		if (info->serial_signals & SerialSignal_RTS) {
+			info->serial_signals &= ~SerialSignal_RTS;
+			set_signals(info);
+		}
+		info->drop_rts_on_tx_done = 0;
+	}
+
+#ifdef CONFIG_HDLC
+	if (info->netcount)
+		hdlcdev_tx_done(info);
+	else 
+#endif
+	{
+		if (info->tty->stopped || info->tty->hw_stopped) {
+			tx_stop(info);
+			return;
+		}
+		info->pending_bh |= BH_TRANSMIT;
+	}
+}
+
+static void tx_ready(MGSLPC_INFO *info)
+{
+	unsigned char fifo_count = 32;
+	int c;
+
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):tx_ready(%s)\n", __FILE__,__LINE__,info->device_name);
+
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		if (!info->tx_active)
+			return;
+	} else {
+		if (info->tty->stopped || info->tty->hw_stopped) {
+			tx_stop(info);
+			return;
+		}
+		if (!info->tx_count)
+			info->tx_active = 0;
+	}
+
+	if (!info->tx_count)
+		return;
+
+	while (info->tx_count && fifo_count) {
+		c = min(2, min_t(int, fifo_count, min(info->tx_count, TXBUFSIZE - info->tx_get)));
+		
+		if (c == 1) {
+			write_reg(info, CHA + TXFIFO, *(info->tx_buf + info->tx_get));
+		} else {
+			write_reg16(info, CHA + TXFIFO,
+					  *((unsigned short*)(info->tx_buf + info->tx_get)));
+		}
+		info->tx_count -= c;
+		info->tx_get = (info->tx_get + c) & (TXBUFSIZE - 1);
+		fifo_count -= c;
+	}
+
+	if (info->params.mode == MGSL_MODE_ASYNC) {
+		if (info->tx_count < WAKEUP_CHARS)
+			info->pending_bh |= BH_TRANSMIT;
+		issue_command(info, CHA, CMD_TXFIFO);
+	} else {
+		if (info->tx_count)
+			issue_command(info, CHA, CMD_TXFIFO);
+		else
+			issue_command(info, CHA, CMD_TXFIFO + CMD_TXEOM);
+	}
+}
+
+static void cts_change(MGSLPC_INFO *info)
+{
+	get_signals(info);
+	if ((info->cts_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+		irq_disable(info, CHB, IRQ_CTS);
+	info->icount.cts++;
+	if (info->serial_signals & SerialSignal_CTS)
+		info->input_signal_events.cts_up++;
+	else
+		info->input_signal_events.cts_down++;
+	wake_up_interruptible(&info->status_event_wait_q);
+	wake_up_interruptible(&info->event_wait_q);
+
+	if (info->flags & ASYNC_CTS_FLOW) {
+		if (info->tty->hw_stopped) {
+			if (info->serial_signals & SerialSignal_CTS) {
+				if (debug_level >= DEBUG_LEVEL_ISR)
+					printk("CTS tx start...");
+				if (info->tty)
+					info->tty->hw_stopped = 0;
+				tx_start(info);
+				info->pending_bh |= BH_TRANSMIT;
+				return;
+			}
+		} else {
+			if (!(info->serial_signals & SerialSignal_CTS)) {
+				if (debug_level >= DEBUG_LEVEL_ISR)
+					printk("CTS tx stop...");
+				if (info->tty)
+					info->tty->hw_stopped = 1;
+				tx_stop(info);
+			}
+		}
+	}
+	info->pending_bh |= BH_STATUS;
+}
+
+static void dcd_change(MGSLPC_INFO *info)
+{
+	get_signals(info);
+	if ((info->dcd_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+		irq_disable(info, CHB, IRQ_DCD);
+	info->icount.dcd++;
+	if (info->serial_signals & SerialSignal_DCD) {
+		info->input_signal_events.dcd_up++;
+	}
+	else
+		info->input_signal_events.dcd_down++;
+#ifdef CONFIG_HDLC
+	if (info->netcount)
+		hdlc_set_carrier(info->serial_signals & SerialSignal_DCD, info->netdev);
+#endif
+	wake_up_interruptible(&info->status_event_wait_q);
+	wake_up_interruptible(&info->event_wait_q);
+
+	if (info->flags & ASYNC_CHECK_CD) {
+		if (debug_level >= DEBUG_LEVEL_ISR)
+			printk("%s CD now %s...", info->device_name,
+			       (info->serial_signals & SerialSignal_DCD) ? "on" : "off");
+		if (info->serial_signals & SerialSignal_DCD)
+			wake_up_interruptible(&info->open_wait);
+		else {
+			if (debug_level >= DEBUG_LEVEL_ISR)
+				printk("doing serial hangup...");
+			if (info->tty)
+				tty_hangup(info->tty);
+		}
+	}
+	info->pending_bh |= BH_STATUS;
+}
+
+static void dsr_change(MGSLPC_INFO *info)
+{
+	get_signals(info);
+	if ((info->dsr_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+		port_irq_disable(info, PVR_DSR);
+	info->icount.dsr++;
+	if (info->serial_signals & SerialSignal_DSR)
+		info->input_signal_events.dsr_up++;
+	else
+		info->input_signal_events.dsr_down++;
+	wake_up_interruptible(&info->status_event_wait_q);
+	wake_up_interruptible(&info->event_wait_q);
+	info->pending_bh |= BH_STATUS;
+}
+
+static void ri_change(MGSLPC_INFO *info)
+{
+	get_signals(info);
+	if ((info->ri_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+		port_irq_disable(info, PVR_RI);
+	info->icount.rng++;
+	if (info->serial_signals & SerialSignal_RI)
+		info->input_signal_events.ri_up++;
+	else
+		info->input_signal_events.ri_down++;
+	wake_up_interruptible(&info->status_event_wait_q);
+	wake_up_interruptible(&info->event_wait_q);
+	info->pending_bh |= BH_STATUS;
+}
+
+/* Interrupt service routine entry point.
+ * 	
+ * Arguments:
+ * 
+ * irq     interrupt number that caused interrupt
+ * dev_id  device ID supplied during interrupt registration
+ * regs    interrupted processor context
+ */
+static irqreturn_t mgslpc_isr(int irq, void *dev_id, struct pt_regs * regs)
+{
+	MGSLPC_INFO * info = (MGSLPC_INFO *)dev_id;
+	unsigned short isr;
+	unsigned char gis, pis;
+	int count=0;
+
+	if (debug_level >= DEBUG_LEVEL_ISR)	
+		printk("mgslpc_isr(%d) entry.\n", irq);
+	if (!info)
+		return IRQ_NONE;
+		
+	if (!(info->link.state & DEV_CONFIG))
+		return IRQ_HANDLED;
+
+	spin_lock(&info->lock);
+
+	while ((gis = read_reg(info, CHA + GIS))) {
+		if (debug_level >= DEBUG_LEVEL_ISR)	
+			printk("mgslpc_isr %s gis=%04X\n", info->device_name,gis);
+
+		if ((gis & 0x70) || count > 1000) {
+			printk("synclink_cs:hardware failed or ejected\n");
+			break;
+		}
+		count++;
+
+		if (gis & (BIT1 + BIT0)) {
+			isr = read_reg16(info, CHB + ISR);
+			if (isr & IRQ_DCD)
+				dcd_change(info);
+			if (isr & IRQ_CTS)
+				cts_change(info);
+		}
+		if (gis & (BIT3 + BIT2))
+		{
+			isr = read_reg16(info, CHA + ISR);
+			if (isr & IRQ_TIMER) {
+				info->irq_occurred = 1;
+				irq_disable(info, CHA, IRQ_TIMER);
+			}
+
+			/* receive IRQs */ 
+			if (isr & IRQ_EXITHUNT) {
+				info->icount.exithunt++;
+				wake_up_interruptible(&info->event_wait_q);
+			}
+			if (isr & IRQ_BREAK_ON) {
+				info->icount.brk++;
+				if (info->flags & ASYNC_SAK)
+					do_SAK(info->tty);
+			}
+			if (isr & IRQ_RXTIME) {
+				issue_command(info, CHA, CMD_RXFIFO_READ);
+			}
+			if (isr & (IRQ_RXEOM + IRQ_RXFIFO)) {
+				if (info->params.mode == MGSL_MODE_HDLC)
+					rx_ready_hdlc(info, isr & IRQ_RXEOM); 
+				else
+					rx_ready_async(info, isr & IRQ_RXEOM);
+			}
+
+			/* transmit IRQs */ 
+			if (isr & IRQ_UNDERRUN) {
+				if (info->tx_aborting)
+					info->icount.txabort++;
+				else
+					info->icount.txunder++;
+				tx_done(info);
+			}
+			else if (isr & IRQ_ALLSENT) {
+				info->icount.txok++;
+				tx_done(info);
+			}
+			else if (isr & IRQ_TXFIFO)
+				tx_ready(info);
+		}
+		if (gis & BIT7) {
+			pis = read_reg(info, CHA + PIS);
+			if (pis & BIT1)
+				dsr_change(info);
+			if (pis & BIT2)
+				ri_change(info);
+		}
+	}
+	
+	/* Request bottom half processing if there's something 
+	 * for it to do and the bh is not already running
+	 */
+
+	if (info->pending_bh && !info->bh_running && !info->bh_requested) {
+		if ( debug_level >= DEBUG_LEVEL_ISR )	
+			printk("%s(%d):%s queueing bh task.\n",
+				__FILE__,__LINE__,info->device_name);
+		schedule_work(&info->task);
+		info->bh_requested = 1;
+	}
+
+	spin_unlock(&info->lock);
+	
+	if (debug_level >= DEBUG_LEVEL_ISR)	
+		printk("%s(%d):mgslpc_isr(%d)exit.\n",
+		       __FILE__,__LINE__,irq);
+
+	return IRQ_HANDLED;
+}
+
+/* Initialize and start device.
+ */
+static int startup(MGSLPC_INFO * info)
+{
+	int retval = 0;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):startup(%s)\n",__FILE__,__LINE__,info->device_name);
+		
+	if (info->flags & ASYNC_INITIALIZED)
+		return 0;
+	
+	if (!info->tx_buf) {
+		/* allocate a page of memory for a transmit buffer */
+		info->tx_buf = (unsigned char *)get_zeroed_page(GFP_KERNEL);
+		if (!info->tx_buf) {
+			printk(KERN_ERR"%s(%d):%s can't allocate transmit buffer\n",
+				__FILE__,__LINE__,info->device_name);
+			return -ENOMEM;
+		}
+	}
+
+	info->pending_bh = 0;
+	
+	init_timer(&info->tx_timer);
+	info->tx_timer.data = (unsigned long)info;
+	info->tx_timer.function = tx_timeout;
+
+	/* Allocate and claim adapter resources */
+	retval = claim_resources(info);
+	
+	/* perform existance check and diagnostics */
+	if ( !retval )
+		retval = adapter_test(info);
+		
+	if ( retval ) {
+  		if (capable(CAP_SYS_ADMIN) && info->tty)
+			set_bit(TTY_IO_ERROR, &info->tty->flags);
+		release_resources(info);
+  		return retval;
+  	}
+
+	/* program hardware for current parameters */
+	mgslpc_change_params(info);
+	
+	if (info->tty)
+		clear_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	info->flags |= ASYNC_INITIALIZED;
+	
+	return 0;
+}
+
+/* Called by mgslpc_close() and mgslpc_hangup() to shutdown hardware
+ */
+static void shutdown(MGSLPC_INFO * info)
+{
+	unsigned long flags;
+	
+	if (!(info->flags & ASYNC_INITIALIZED))
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_shutdown(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	/* clear status wait queue because status changes */
+	/* can't happen after shutting down the hardware */
+	wake_up_interruptible(&info->status_event_wait_q);
+	wake_up_interruptible(&info->event_wait_q);
+
+	del_timer(&info->tx_timer);	
+
+	if (info->tx_buf) {
+		free_page((unsigned long) info->tx_buf);
+		info->tx_buf = NULL;
+	}
+
+	spin_lock_irqsave(&info->lock,flags);
+
+	rx_stop(info);
+	tx_stop(info);
+
+	/* TODO:disable interrupts instead of reset to preserve signal states */
+	reset_device(info);
+	
+ 	if (!info->tty || info->tty->termios->c_cflag & HUPCL) {
+ 		info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
+		set_signals(info);
+	}
+	
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	release_resources(info);	
+	
+	if (info->tty)
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	info->flags &= ~ASYNC_INITIALIZED;
+}
+
+static void mgslpc_program_hw(MGSLPC_INFO *info)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->lock,flags);
+	
+	rx_stop(info);
+	tx_stop(info);
+	info->tx_count = info->tx_put = info->tx_get = 0;
+	
+	if (info->params.mode == MGSL_MODE_HDLC || info->netcount)
+		hdlc_mode(info);
+	else
+		async_mode(info);
+		
+	set_signals(info);
+	
+	info->dcd_chkcount = 0;
+	info->cts_chkcount = 0;
+	info->ri_chkcount = 0;
+	info->dsr_chkcount = 0;
+
+	irq_enable(info, CHB, IRQ_DCD | IRQ_CTS);
+	port_irq_enable(info, (unsigned char) PVR_DSR | PVR_RI);
+	get_signals(info);
+		
+	if (info->netcount || info->tty->termios->c_cflag & CREAD)
+		rx_start(info);
+		
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Reconfigure adapter based on new parameters
+ */
+static void mgslpc_change_params(MGSLPC_INFO *info)
+{
+	unsigned cflag;
+	int bits_per_char;
+
+	if (!info->tty || !info->tty->termios)
+		return;
+		
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_change_params(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	cflag = info->tty->termios->c_cflag;
+
+	/* if B0 rate (hangup) specified then negate DTR and RTS */
+	/* otherwise assert DTR and RTS */
+ 	if (cflag & CBAUD)
+		info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+	else
+		info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+	
+	/* byte size and parity */
+	
+	switch (cflag & CSIZE) {
+	case CS5: info->params.data_bits = 5; break;
+	case CS6: info->params.data_bits = 6; break;
+	case CS7: info->params.data_bits = 7; break;
+	case CS8: info->params.data_bits = 8; break;
+	default:  info->params.data_bits = 7; break;
+	}
+	      
+	if (cflag & CSTOPB)
+		info->params.stop_bits = 2;
+	else
+		info->params.stop_bits = 1;
+
+	info->params.parity = ASYNC_PARITY_NONE;
+	if (cflag & PARENB) {
+		if (cflag & PARODD)
+			info->params.parity = ASYNC_PARITY_ODD;
+		else
+			info->params.parity = ASYNC_PARITY_EVEN;
+#ifdef CMSPAR
+		if (cflag & CMSPAR)
+			info->params.parity = ASYNC_PARITY_SPACE;
+#endif
+	}
+
+	/* calculate number of jiffies to transmit a full
+	 * FIFO (32 bytes) at specified data rate
+	 */
+	bits_per_char = info->params.data_bits + 
+			info->params.stop_bits + 1;
+
+	/* if port data rate is set to 460800 or less then
+	 * allow tty settings to override, otherwise keep the
+	 * current data rate.
+	 */
+	if (info->params.data_rate <= 460800) {
+		info->params.data_rate = tty_get_baud_rate(info->tty);
+	}
+	
+	if ( info->params.data_rate ) {
+		info->timeout = (32*HZ*bits_per_char) / 
+				info->params.data_rate;
+	}
+	info->timeout += HZ/50;		/* Add .02 seconds of slop */
+
+	if (cflag & CRTSCTS)
+		info->flags |= ASYNC_CTS_FLOW;
+	else
+		info->flags &= ~ASYNC_CTS_FLOW;
+		
+	if (cflag & CLOCAL)
+		info->flags &= ~ASYNC_CHECK_CD;
+	else
+		info->flags |= ASYNC_CHECK_CD;
+
+	/* process tty input control flags */
+	
+	info->read_status_mask = 0;
+	if (I_INPCK(info->tty))
+		info->read_status_mask |= BIT7 | BIT6;
+	if (I_IGNPAR(info->tty))
+		info->ignore_status_mask |= BIT7 | BIT6;
+
+	mgslpc_program_hw(info);
+}
+
+/* Add a character to the transmit buffer
+ */
+static void mgslpc_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO) {
+		printk( "%s(%d):mgslpc_put_char(%d) on %s\n",
+			__FILE__,__LINE__,ch,info->device_name);
+	}
+
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_put_char"))
+		return;
+
+	if (!tty || !info->tx_buf)
+		return;
+
+	spin_lock_irqsave(&info->lock,flags);
+	
+	if (info->params.mode == MGSL_MODE_ASYNC || !info->tx_active) {
+		if (info->tx_count < TXBUFSIZE - 1) {
+			info->tx_buf[info->tx_put++] = ch;
+			info->tx_put &= TXBUFSIZE-1;
+			info->tx_count++;
+		}
+	}
+	
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Enable transmitter so remaining characters in the
+ * transmit buffer are sent.
+ */
+static void mgslpc_flush_chars(struct tty_struct *tty)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+				
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk( "%s(%d):mgslpc_flush_chars() entry on %s tx_count=%d\n",
+			__FILE__,__LINE__,info->device_name,info->tx_count);
+	
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_flush_chars"))
+		return;
+
+	if (info->tx_count <= 0 || tty->stopped ||
+	    tty->hw_stopped || !info->tx_buf)
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk( "%s(%d):mgslpc_flush_chars() entry on %s starting transmitter\n",
+			__FILE__,__LINE__,info->device_name);
+
+	spin_lock_irqsave(&info->lock,flags);
+	if (!info->tx_active)
+	 	tx_start(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Send a block of data
+ * 	
+ * Arguments:
+ * 
+ * tty        pointer to tty information structure
+ * buf	      pointer to buffer containing send data
+ * count      size of send data in bytes
+ * 	
+ * Returns: number of characters written
+ */
+static int mgslpc_write(struct tty_struct * tty,
+			const unsigned char *buf, int count)
+{
+	int c, ret = 0;
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk( "%s(%d):mgslpc_write(%s) count=%d\n",
+			__FILE__,__LINE__,info->device_name,count);
+	
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_write") ||
+	    !tty || !info->tx_buf)
+		goto cleanup;
+
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		if (count > TXBUFSIZE) {
+			ret = -EIO;
+			goto cleanup;
+		}
+		if (info->tx_active)
+			goto cleanup;
+		else if (info->tx_count)
+			goto start;
+	}
+
+	for (;;) {
+		c = min(count,
+			min(TXBUFSIZE - info->tx_count - 1,
+			    TXBUFSIZE - info->tx_put));
+		if (c <= 0)
+			break;
+			
+		memcpy(info->tx_buf + info->tx_put, buf, c);
+
+		spin_lock_irqsave(&info->lock,flags);
+		info->tx_put = (info->tx_put + c) & (TXBUFSIZE-1);
+		info->tx_count += c;
+		spin_unlock_irqrestore(&info->lock,flags);
+
+		buf += c;
+		count -= c;
+		ret += c;
+	}
+start:
+ 	if (info->tx_count && !tty->stopped && !tty->hw_stopped) {
+		spin_lock_irqsave(&info->lock,flags);
+		if (!info->tx_active)
+		 	tx_start(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+ 	}
+cleanup:	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk( "%s(%d):mgslpc_write(%s) returning=%d\n",
+			__FILE__,__LINE__,info->device_name,ret);
+	return ret;
+}
+
+/* Return the count of free bytes in transmit buffer
+ */
+static int mgslpc_write_room(struct tty_struct *tty)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	int ret;
+				
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_write_room"))
+		return 0;
+
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		/* HDLC (frame oriented) mode */
+		if (info->tx_active)
+			return 0;
+		else
+			return HDLC_MAX_FRAME_SIZE;
+	} else {
+		ret = TXBUFSIZE - info->tx_count - 1;
+		if (ret < 0)
+			ret = 0;
+	}
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_write_room(%s)=%d\n",
+			 __FILE__,__LINE__, info->device_name, ret);
+	return ret;
+}
+
+/* Return the count of bytes in transmit buffer
+ */
+static int mgslpc_chars_in_buffer(struct tty_struct *tty)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	int rc;
+		 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_chars_in_buffer(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_chars_in_buffer"))
+		return 0;
+		
+	if (info->params.mode == MGSL_MODE_HDLC)
+		rc = info->tx_active ? info->max_frame_size : 0;
+	else
+		rc = info->tx_count;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_chars_in_buffer(%s)=%d\n",
+			 __FILE__,__LINE__, info->device_name, rc);
+			 
+	return rc;
+}
+
+/* Discard all data in the send buffer
+ */
+static void mgslpc_flush_buffer(struct tty_struct *tty)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_flush_buffer(%s) entry\n",
+			 __FILE__,__LINE__, info->device_name );
+	
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_flush_buffer"))
+		return;
+		
+	spin_lock_irqsave(&info->lock,flags); 
+	info->tx_count = info->tx_put = info->tx_get = 0;
+	del_timer(&info->tx_timer);	
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	wake_up_interruptible(&tty->write_wait);
+	tty_wakeup(tty);
+}
+
+/* Send a high-priority XON/XOFF character
+ */
+static void mgslpc_send_xchar(struct tty_struct *tty, char ch)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_send_xchar(%s,%d)\n",
+			 __FILE__,__LINE__, info->device_name, ch );
+			 
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_send_xchar"))
+		return;
+
+	info->x_char = ch;
+	if (ch) {
+		spin_lock_irqsave(&info->lock,flags);
+		if (!info->tx_enabled)
+		 	tx_start(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+}
+
+/* Signal remote device to throttle send data (our receive data)
+ */
+static void mgslpc_throttle(struct tty_struct * tty)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_throttle(%s) entry\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_throttle"))
+		return;
+	
+	if (I_IXOFF(tty))
+		mgslpc_send_xchar(tty, STOP_CHAR(tty));
+ 
+ 	if (tty->termios->c_cflag & CRTSCTS) {
+		spin_lock_irqsave(&info->lock,flags);
+		info->serial_signals &= ~SerialSignal_RTS;
+	 	set_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+}
+
+/* Signal remote device to stop throttling send data (our receive data)
+ */
+static void mgslpc_unthrottle(struct tty_struct * tty)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_unthrottle(%s) entry\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_unthrottle"))
+		return;
+	
+	if (I_IXOFF(tty)) {
+		if (info->x_char)
+			info->x_char = 0;
+		else
+			mgslpc_send_xchar(tty, START_CHAR(tty));
+	}
+	
+ 	if (tty->termios->c_cflag & CRTSCTS) {
+		spin_lock_irqsave(&info->lock,flags);
+		info->serial_signals |= SerialSignal_RTS;
+	 	set_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+}
+
+/* get the current serial statistics
+ */
+static int get_stats(MGSLPC_INFO * info, struct mgsl_icount __user *user_icount)
+{
+	int err;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("get_params(%s)\n", info->device_name);
+	COPY_TO_USER(err,user_icount, &info->icount, sizeof(struct mgsl_icount));
+	if (err)
+		return -EFAULT;
+	return 0;
+}
+
+/* get the current serial parameters
+ */
+static int get_params(MGSLPC_INFO * info, MGSL_PARAMS __user *user_params)
+{
+	int err;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("get_params(%s)\n", info->device_name);
+	COPY_TO_USER(err,user_params, &info->params, sizeof(MGSL_PARAMS));
+	if (err)
+		return -EFAULT;
+	return 0;
+}
+
+/* set the serial parameters
+ * 	
+ * Arguments:
+ * 
+ * 	info		pointer to device instance data
+ * 	new_params	user buffer containing new serial params
+ *
+ * Returns:	0 if success, otherwise error code
+ */
+static int set_params(MGSLPC_INFO * info, MGSL_PARAMS __user *new_params)
+{
+ 	unsigned long flags;
+	MGSL_PARAMS tmp_params;
+	int err;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):set_params %s\n", __FILE__,__LINE__,
+			info->device_name );
+	COPY_FROM_USER(err,&tmp_params, new_params, sizeof(MGSL_PARAMS));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):set_params(%s) user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+	
+	spin_lock_irqsave(&info->lock,flags);
+	memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
+	spin_unlock_irqrestore(&info->lock,flags);
+	
+ 	mgslpc_change_params(info);
+	
+	return 0;
+}
+
+static int get_txidle(MGSLPC_INFO * info, int __user *idle_mode)
+{
+	int err;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("get_txidle(%s)=%d\n", info->device_name, info->idle_mode);
+	COPY_TO_USER(err,idle_mode, &info->idle_mode, sizeof(int));
+	if (err)
+		return -EFAULT;
+	return 0;
+}
+
+static int set_txidle(MGSLPC_INFO * info, int idle_mode)
+{
+ 	unsigned long flags;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("set_txidle(%s,%d)\n", info->device_name, idle_mode);
+	spin_lock_irqsave(&info->lock,flags);
+	info->idle_mode = idle_mode;
+	tx_set_idle(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+static int get_interface(MGSLPC_INFO * info, int __user *if_mode)
+{
+	int err;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("get_interface(%s)=%d\n", info->device_name, info->if_mode);
+	COPY_TO_USER(err,if_mode, &info->if_mode, sizeof(int));
+	if (err)
+		return -EFAULT;
+	return 0;
+}
+
+static int set_interface(MGSLPC_INFO * info, int if_mode)
+{
+ 	unsigned long flags;
+	unsigned char val;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("set_interface(%s,%d)\n", info->device_name, if_mode);
+	spin_lock_irqsave(&info->lock,flags);
+	info->if_mode = if_mode;
+
+	val = read_reg(info, PVR) & 0x0f;
+	switch (info->if_mode)
+	{
+	case MGSL_INTERFACE_RS232: val |= PVR_RS232; break;
+	case MGSL_INTERFACE_V35:   val |= PVR_V35;   break;
+	case MGSL_INTERFACE_RS422: val |= PVR_RS422; break;
+	}
+	write_reg(info, PVR, val);
+
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+static int set_txenable(MGSLPC_INFO * info, int enable)
+{
+ 	unsigned long flags;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("set_txenable(%s,%d)\n", info->device_name, enable);
+			
+	spin_lock_irqsave(&info->lock,flags);
+	if (enable) {
+		if (!info->tx_enabled)
+			tx_start(info);
+	} else {
+		if (info->tx_enabled)
+			tx_stop(info);
+	}
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+static int tx_abort(MGSLPC_INFO * info)
+{
+ 	unsigned long flags;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("tx_abort(%s)\n", info->device_name);
+			
+	spin_lock_irqsave(&info->lock,flags);
+	if (info->tx_active && info->tx_count &&
+	    info->params.mode == MGSL_MODE_HDLC) {
+		/* clear data count so FIFO is not filled on next IRQ.
+		 * This results in underrun and abort transmission.
+		 */
+		info->tx_count = info->tx_put = info->tx_get = 0;
+		info->tx_aborting = TRUE;
+	}
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+static int set_rxenable(MGSLPC_INFO * info, int enable)
+{
+ 	unsigned long flags;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("set_rxenable(%s,%d)\n", info->device_name, enable);
+			
+	spin_lock_irqsave(&info->lock,flags);
+	if (enable) {
+		if (!info->rx_enabled)
+			rx_start(info);
+	} else {
+		if (info->rx_enabled)
+			rx_stop(info);
+	}
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+/* wait for specified event to occur
+ * 	
+ * Arguments:	 	info	pointer to device instance data
+ * 			mask	pointer to bitmask of events to wait for
+ * Return Value:	0 	if successful and bit mask updated with
+ *				of events triggerred,
+ * 			otherwise error code
+ */
+static int wait_events(MGSLPC_INFO * info, int __user *mask_ptr)
+{
+ 	unsigned long flags;
+	int s;
+	int rc=0;
+	struct mgsl_icount cprev, cnow;
+	int events;
+	int mask;
+	struct	_input_signal_events oldsigs, newsigs;
+	DECLARE_WAITQUEUE(wait, current);
+
+	COPY_FROM_USER(rc,&mask, mask_ptr, sizeof(int));
+	if (rc)
+		return  -EFAULT;
+		 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("wait_events(%s,%d)\n", info->device_name, mask);
+
+	spin_lock_irqsave(&info->lock,flags);
+
+	/* return immediately if state matches requested events */
+	get_signals(info);
+	s = info->serial_signals;
+	events = mask &
+		( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) +
+ 		  ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) +
+		  ((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) +
+		  ((s & SerialSignal_RI)  ? MgslEvent_RiActive :MgslEvent_RiInactive) );
+	if (events) {
+		spin_unlock_irqrestore(&info->lock,flags);
+		goto exit;
+	}
+
+	/* save current irq counts */
+	cprev = info->icount;
+	oldsigs = info->input_signal_events;
+	
+	if ((info->params.mode == MGSL_MODE_HDLC) &&
+	    (mask & MgslEvent_ExitHuntMode))
+		irq_enable(info, CHA, IRQ_EXITHUNT);
+	
+	set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&info->event_wait_q, &wait);
+	
+	spin_unlock_irqrestore(&info->lock,flags);
+	
+	
+	for(;;) {
+		schedule();
+		if (signal_pending(current)) {
+			rc = -ERESTARTSYS;
+			break;
+		}
+			
+		/* get current irq counts */
+		spin_lock_irqsave(&info->lock,flags);
+		cnow = info->icount;
+		newsigs = info->input_signal_events;
+		set_current_state(TASK_INTERRUPTIBLE);
+		spin_unlock_irqrestore(&info->lock,flags);
+
+		/* if no change, wait aborted for some reason */
+		if (newsigs.dsr_up   == oldsigs.dsr_up   &&
+		    newsigs.dsr_down == oldsigs.dsr_down &&
+		    newsigs.dcd_up   == oldsigs.dcd_up   &&
+		    newsigs.dcd_down == oldsigs.dcd_down &&
+		    newsigs.cts_up   == oldsigs.cts_up   &&
+		    newsigs.cts_down == oldsigs.cts_down &&
+		    newsigs.ri_up    == oldsigs.ri_up    &&
+		    newsigs.ri_down  == oldsigs.ri_down  &&
+		    cnow.exithunt    == cprev.exithunt   &&
+		    cnow.rxidle      == cprev.rxidle) {
+			rc = -EIO;
+			break;
+		}
+
+		events = mask &
+			( (newsigs.dsr_up   != oldsigs.dsr_up   ? MgslEvent_DsrActive:0)   +
+			  (newsigs.dsr_down != oldsigs.dsr_down ? MgslEvent_DsrInactive:0) +
+			  (newsigs.dcd_up   != oldsigs.dcd_up   ? MgslEvent_DcdActive:0)   +
+			  (newsigs.dcd_down != oldsigs.dcd_down ? MgslEvent_DcdInactive:0) +
+			  (newsigs.cts_up   != oldsigs.cts_up   ? MgslEvent_CtsActive:0)   +
+			  (newsigs.cts_down != oldsigs.cts_down ? MgslEvent_CtsInactive:0) +
+			  (newsigs.ri_up    != oldsigs.ri_up    ? MgslEvent_RiActive:0)    +
+			  (newsigs.ri_down  != oldsigs.ri_down  ? MgslEvent_RiInactive:0)  +
+			  (cnow.exithunt    != cprev.exithunt   ? MgslEvent_ExitHuntMode:0) +
+			  (cnow.rxidle      != cprev.rxidle     ? MgslEvent_IdleReceived:0) );
+		if (events)
+			break;
+		
+		cprev = cnow;
+		oldsigs = newsigs;
+	}
+	
+	remove_wait_queue(&info->event_wait_q, &wait);
+	set_current_state(TASK_RUNNING);
+
+	if (mask & MgslEvent_ExitHuntMode) {
+		spin_lock_irqsave(&info->lock,flags);
+		if (!waitqueue_active(&info->event_wait_q))
+			irq_disable(info, CHA, IRQ_EXITHUNT);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+exit:
+	if (rc == 0)
+		PUT_USER(rc, events, mask_ptr);
+	return rc;
+}
+
+static int modem_input_wait(MGSLPC_INFO *info,int arg)
+{
+ 	unsigned long flags;
+	int rc;
+	struct mgsl_icount cprev, cnow;
+	DECLARE_WAITQUEUE(wait, current);
+
+	/* save current irq counts */
+	spin_lock_irqsave(&info->lock,flags);
+	cprev = info->icount;
+	add_wait_queue(&info->status_event_wait_q, &wait);
+	set_current_state(TASK_INTERRUPTIBLE);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	for(;;) {
+		schedule();
+		if (signal_pending(current)) {
+			rc = -ERESTARTSYS;
+			break;
+		}
+
+		/* get new irq counts */
+		spin_lock_irqsave(&info->lock,flags);
+		cnow = info->icount;
+		set_current_state(TASK_INTERRUPTIBLE);
+		spin_unlock_irqrestore(&info->lock,flags);
+
+		/* if no change, wait aborted for some reason */
+		if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+		    cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+			rc = -EIO;
+			break;
+		}
+
+		/* check for change in caller specified modem input */
+		if ((arg & TIOCM_RNG && cnow.rng != cprev.rng) ||
+		    (arg & TIOCM_DSR && cnow.dsr != cprev.dsr) ||
+		    (arg & TIOCM_CD  && cnow.dcd != cprev.dcd) ||
+		    (arg & TIOCM_CTS && cnow.cts != cprev.cts)) {
+			rc = 0;
+			break;
+		}
+
+		cprev = cnow;
+	}
+	remove_wait_queue(&info->status_event_wait_q, &wait);
+	set_current_state(TASK_RUNNING);
+	return rc;
+}
+
+/* return the state of the serial control and status signals
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned int result;
+ 	unsigned long flags;
+
+	spin_lock_irqsave(&info->lock,flags);
+ 	get_signals(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	result = ((info->serial_signals & SerialSignal_RTS) ? TIOCM_RTS:0) +
+		((info->serial_signals & SerialSignal_DTR) ? TIOCM_DTR:0) +
+		((info->serial_signals & SerialSignal_DCD) ? TIOCM_CAR:0) +
+		((info->serial_signals & SerialSignal_RI)  ? TIOCM_RNG:0) +
+		((info->serial_signals & SerialSignal_DSR) ? TIOCM_DSR:0) +
+		((info->serial_signals & SerialSignal_CTS) ? TIOCM_CTS:0);
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s tiocmget() value=%08X\n",
+			 __FILE__,__LINE__, info->device_name, result );
+	return result;
+}
+
+/* set modem control signals (DTR/RTS)
+ */
+static int tiocmset(struct tty_struct *tty, struct file *file,
+		    unsigned int set, unsigned int clear)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+ 	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s tiocmset(%x,%x)\n",
+			__FILE__,__LINE__,info->device_name, set, clear);
+
+	if (set & TIOCM_RTS)
+		info->serial_signals |= SerialSignal_RTS;
+	if (set & TIOCM_DTR)
+		info->serial_signals |= SerialSignal_DTR;
+	if (clear & TIOCM_RTS)
+		info->serial_signals &= ~SerialSignal_RTS;
+	if (clear & TIOCM_DTR)
+		info->serial_signals &= ~SerialSignal_DTR;
+
+	spin_lock_irqsave(&info->lock,flags);
+ 	set_signals(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	return 0;
+}
+
+/* Set or clear transmit break condition
+ *
+ * Arguments:		tty		pointer to tty instance data
+ *			break_state	-1=set break condition, 0=clear
+ */
+static void mgslpc_break(struct tty_struct *tty, int break_state)
+{
+	MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_break(%s,%d)\n",
+			 __FILE__,__LINE__, info->device_name, break_state);
+			 
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_break"))
+		return;
+
+	spin_lock_irqsave(&info->lock,flags);
+ 	if (break_state == -1)
+		set_reg_bits(info, CHA+DAFO, BIT6);
+	else 
+		clear_reg_bits(info, CHA+DAFO, BIT6);
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Service an IOCTL request
+ * 	
+ * Arguments:
+ * 
+ * 	tty	pointer to tty instance data
+ * 	file	pointer to associated file object for device
+ * 	cmd	IOCTL command code
+ * 	arg	command argument/context
+ * 	
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgslpc_ioctl(struct tty_struct *tty, struct file * file,
+			unsigned int cmd, unsigned long arg)
+{
+	MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_ioctl %s cmd=%08X\n", __FILE__,__LINE__,
+			info->device_name, cmd );
+	
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_ioctl"))
+		return -ENODEV;
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+	    (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+		    return -EIO;
+	}
+
+	return ioctl_common(info, cmd, arg);
+}
+
+int ioctl_common(MGSLPC_INFO *info, unsigned int cmd, unsigned long arg)
+{
+	int error;
+	struct mgsl_icount cnow;	/* kernel counter temps */
+	struct serial_icounter_struct __user *p_cuser;	/* user space */
+	void __user *argp = (void __user *)arg;
+	unsigned long flags;
+	
+	switch (cmd) {
+	case MGSL_IOCGPARAMS:
+		return get_params(info, argp);
+	case MGSL_IOCSPARAMS:
+		return set_params(info, argp);
+	case MGSL_IOCGTXIDLE:
+		return get_txidle(info, argp);
+	case MGSL_IOCSTXIDLE:
+		return set_txidle(info, (int)arg);
+	case MGSL_IOCGIF:
+		return get_interface(info, argp);
+	case MGSL_IOCSIF:
+		return set_interface(info,(int)arg);
+	case MGSL_IOCTXENABLE:
+		return set_txenable(info,(int)arg);
+	case MGSL_IOCRXENABLE:
+		return set_rxenable(info,(int)arg);
+	case MGSL_IOCTXABORT:
+		return tx_abort(info);
+	case MGSL_IOCGSTATS:
+		return get_stats(info, argp);
+	case MGSL_IOCWAITEVENT:
+		return wait_events(info, argp);
+	case TIOCMIWAIT:
+		return modem_input_wait(info,(int)arg);
+	case TIOCGICOUNT:
+		spin_lock_irqsave(&info->lock,flags);
+		cnow = info->icount;
+		spin_unlock_irqrestore(&info->lock,flags);
+		p_cuser = argp;
+		PUT_USER(error,cnow.cts, &p_cuser->cts);
+		if (error) return error;
+		PUT_USER(error,cnow.dsr, &p_cuser->dsr);
+		if (error) return error;
+		PUT_USER(error,cnow.rng, &p_cuser->rng);
+		if (error) return error;
+		PUT_USER(error,cnow.dcd, &p_cuser->dcd);
+		if (error) return error;
+		PUT_USER(error,cnow.rx, &p_cuser->rx);
+		if (error) return error;
+		PUT_USER(error,cnow.tx, &p_cuser->tx);
+		if (error) return error;
+		PUT_USER(error,cnow.frame, &p_cuser->frame);
+		if (error) return error;
+		PUT_USER(error,cnow.overrun, &p_cuser->overrun);
+		if (error) return error;
+		PUT_USER(error,cnow.parity, &p_cuser->parity);
+		if (error) return error;
+		PUT_USER(error,cnow.brk, &p_cuser->brk);
+		if (error) return error;
+		PUT_USER(error,cnow.buf_overrun, &p_cuser->buf_overrun);
+		if (error) return error;
+		return 0;
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+/* Set new termios settings
+ * 	
+ * Arguments:
+ * 
+ * 	tty		pointer to tty structure
+ * 	termios		pointer to buffer to hold returned old termios
+ */
+static void mgslpc_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_set_termios %s\n", __FILE__,__LINE__,
+			tty->driver->name );
+	
+	/* just return if nothing has changed */
+	if ((tty->termios->c_cflag == old_termios->c_cflag)
+	    && (RELEVANT_IFLAG(tty->termios->c_iflag) 
+		== RELEVANT_IFLAG(old_termios->c_iflag)))
+	  return;
+
+	mgslpc_change_params(info);
+
+	/* Handle transition to B0 status */
+	if (old_termios->c_cflag & CBAUD &&
+	    !(tty->termios->c_cflag & CBAUD)) {
+		info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+		spin_lock_irqsave(&info->lock,flags);
+	 	set_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+	
+	/* Handle transition away from B0 status */
+	if (!(old_termios->c_cflag & CBAUD) &&
+	    tty->termios->c_cflag & CBAUD) {
+		info->serial_signals |= SerialSignal_DTR;
+ 		if (!(tty->termios->c_cflag & CRTSCTS) || 
+ 		    !test_bit(TTY_THROTTLED, &tty->flags)) {
+			info->serial_signals |= SerialSignal_RTS;
+ 		}
+		spin_lock_irqsave(&info->lock,flags);
+	 	set_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+	
+	/* Handle turning off CRTSCTS */
+	if (old_termios->c_cflag & CRTSCTS &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {
+		tty->hw_stopped = 0;
+		tx_release(tty);
+	}
+}
+
+static void mgslpc_close(struct tty_struct *tty, struct file * filp)
+{
+	MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_close"))
+		return;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_close(%s) entry, count=%d\n",
+			 __FILE__,__LINE__, info->device_name, info->count);
+			 
+	if (!info->count)
+		return;
+
+	if (tty_hung_up_p(filp))
+		goto cleanup;
+			
+	if ((tty->count == 1) && (info->count != 1)) {
+		/*
+		 * tty->count is 1 and the tty structure will be freed.
+		 * info->count should be one in this case.
+		 * if it's not, correct it so that the port is shutdown.
+		 */
+		printk("mgslpc_close: bad refcount; tty->count is 1, "
+		       "info->count is %d\n", info->count);
+		info->count = 1;
+	}
+	
+	info->count--;
+	
+	/* if at least one open remaining, leave hardware active */
+	if (info->count)
+		goto cleanup;
+	
+	info->flags |= ASYNC_CLOSING;
+	
+	/* set tty->closing to notify line discipline to 
+	 * only process XON/XOFF characters. Only the N_TTY
+	 * discipline appears to use this (ppp does not).
+	 */
+	tty->closing = 1;
+	
+	/* wait for transmit data to clear all layers */
+	
+	if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):mgslpc_close(%s) calling tty_wait_until_sent\n",
+				 __FILE__,__LINE__, info->device_name );
+		tty_wait_until_sent(tty, info->closing_wait);
+	}
+		
+ 	if (info->flags & ASYNC_INITIALIZED)
+ 		mgslpc_wait_until_sent(tty, info->timeout);
+
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+
+	ldisc_flush_buffer(tty);
+		
+	shutdown(info);
+	
+	tty->closing = 0;
+	info->tty = NULL;
+	
+	if (info->blocked_open) {
+		if (info->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(info->close_delay));
+		}
+		wake_up_interruptible(&info->open_wait);
+	}
+	
+	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+			 
+	wake_up_interruptible(&info->close_wait);
+	
+cleanup:			
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_close(%s) exit, count=%d\n", __FILE__,__LINE__,
+			tty->driver->name, info->count);
+}
+
+/* Wait until the transmitter is empty.
+ */
+static void mgslpc_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+	unsigned long orig_jiffies, char_time;
+
+	if (!info )
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_wait_until_sent(%s) entry\n",
+			 __FILE__,__LINE__, info->device_name );
+      
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_wait_until_sent"))
+		return;
+
+	if (!(info->flags & ASYNC_INITIALIZED))
+		goto exit;
+	 
+	orig_jiffies = jiffies;
+      
+	/* Set check interval to 1/5 of estimated time to
+	 * send a character, and make it at least 1. The check
+	 * interval should also be less than the timeout.
+	 * Note: use tight timings here to satisfy the NIST-PCTS.
+	 */ 
+       
+	if ( info->params.data_rate ) {
+	       	char_time = info->timeout/(32 * 5);
+		if (!char_time)
+			char_time++;
+	} else
+		char_time = 1;
+		
+	if (timeout)
+		char_time = min_t(unsigned long, char_time, timeout);
+		
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		while (info->tx_active) {
+			msleep_interruptible(jiffies_to_msecs(char_time));
+			if (signal_pending(current))
+				break;
+			if (timeout && time_after(jiffies, orig_jiffies + timeout))
+				break;
+		}
+	} else {
+		while ((info->tx_count || info->tx_active) &&
+			info->tx_enabled) {
+			msleep_interruptible(jiffies_to_msecs(char_time));
+			if (signal_pending(current))
+				break;
+			if (timeout && time_after(jiffies, orig_jiffies + timeout))
+				break;
+		}
+	}
+      
+exit:
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_wait_until_sent(%s) exit\n",
+			 __FILE__,__LINE__, info->device_name );
+}
+
+/* Called by tty_hangup() when a hangup is signaled.
+ * This is the same as closing all open files for the port.
+ */
+static void mgslpc_hangup(struct tty_struct *tty)
+{
+	MGSLPC_INFO * info = (MGSLPC_INFO *)tty->driver_data;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_hangup(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_hangup"))
+		return;
+
+	mgslpc_flush_buffer(tty);
+	shutdown(info);
+	
+	info->count = 0;	
+	info->flags &= ~ASYNC_NORMAL_ACTIVE;
+	info->tty = NULL;
+
+	wake_up_interruptible(&info->open_wait);
+}
+
+/* Block the current process until the specified port
+ * is ready to be opened.
+ */
+static int block_til_ready(struct tty_struct *tty, struct file *filp,
+			   MGSLPC_INFO *info)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	int		retval;
+	int		do_clocal = 0, extra_count = 0;
+	unsigned long	flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):block_til_ready on %s\n",
+			 __FILE__,__LINE__, tty->driver->name );
+
+	if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){
+		/* nonblock mode is set or port is not enabled */
+		/* just verify that callout device is not active */
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	if (tty->termios->c_cflag & CLOCAL)
+		do_clocal = 1;
+
+	/* Wait for carrier detect and the line to become
+	 * free (i.e., not in use by the callout).  While we are in
+	 * this loop, info->count is dropped by one, so that
+	 * mgslpc_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	 
+	retval = 0;
+	add_wait_queue(&info->open_wait, &wait);
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):block_til_ready before block on %s count=%d\n",
+			 __FILE__,__LINE__, tty->driver->name, info->count );
+
+	spin_lock_irqsave(&info->lock, flags);
+	if (!tty_hung_up_p(filp)) {
+		extra_count = 1;
+		info->count--;
+	}
+	spin_unlock_irqrestore(&info->lock, flags);
+	info->blocked_open++;
+	
+	while (1) {
+		if ((tty->termios->c_cflag & CBAUD)) {
+			spin_lock_irqsave(&info->lock,flags);
+			info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+		 	set_signals(info);
+			spin_unlock_irqrestore(&info->lock,flags);
+		}
+		
+		set_current_state(TASK_INTERRUPTIBLE);
+		
+		if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)){
+			retval = (info->flags & ASYNC_HUP_NOTIFY) ?
+					-EAGAIN : -ERESTARTSYS;
+			break;
+		}
+		
+		spin_lock_irqsave(&info->lock,flags);
+	 	get_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+		
+ 		if (!(info->flags & ASYNC_CLOSING) &&
+ 		    (do_clocal || (info->serial_signals & SerialSignal_DCD)) ) {
+ 			break;
+		}
+			
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):block_til_ready blocking on %s count=%d\n",
+				 __FILE__,__LINE__, tty->driver->name, info->count );
+				 
+		schedule();
+	}
+	
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&info->open_wait, &wait);
+	
+	if (extra_count)
+		info->count++;
+	info->blocked_open--;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):block_til_ready after blocking on %s count=%d\n",
+			 __FILE__,__LINE__, tty->driver->name, info->count );
+			 
+	if (!retval)
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+		
+	return retval;
+}
+
+static int mgslpc_open(struct tty_struct *tty, struct file * filp)
+{
+	MGSLPC_INFO	*info;
+	int 			retval, line;
+	unsigned long flags;
+
+	/* verify range of specified line number */	
+	line = tty->index;
+	if ((line < 0) || (line >= mgslpc_device_count)) {
+		printk("%s(%d):mgslpc_open with invalid line #%d.\n",
+			__FILE__,__LINE__,line);
+		return -ENODEV;
+	}
+
+	/* find the info structure for the specified line */
+	info = mgslpc_device_list;
+	while(info && info->line != line)
+		info = info->next_device;
+	if (mgslpc_paranoia_check(info, tty->name, "mgslpc_open"))
+		return -ENODEV;
+	
+	tty->driver_data = info;
+	info->tty = tty;
+		
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_open(%s), old ref count = %d\n",
+			 __FILE__,__LINE__,tty->driver->name, info->count);
+
+	/* If port is closing, signal caller to try again */
+	if (tty_hung_up_p(filp) || info->flags & ASYNC_CLOSING){
+		if (info->flags & ASYNC_CLOSING)
+			interruptible_sleep_on(&info->close_wait);
+		retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+			-EAGAIN : -ERESTARTSYS);
+		goto cleanup;
+	}
+	
+	info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+	spin_lock_irqsave(&info->netlock, flags);
+	if (info->netcount) {
+		retval = -EBUSY;
+		spin_unlock_irqrestore(&info->netlock, flags);
+		goto cleanup;
+	}
+	info->count++;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	if (info->count == 1) {
+		/* 1st open on this device, init hardware */
+		retval = startup(info);
+		if (retval < 0)
+			goto cleanup;
+	}
+
+	retval = block_til_ready(tty, filp, info);
+	if (retval) {
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):block_til_ready(%s) returned %d\n",
+				 __FILE__,__LINE__, info->device_name, retval);
+		goto cleanup;
+	}
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgslpc_open(%s) success\n",
+			 __FILE__,__LINE__, info->device_name);
+	retval = 0;
+	
+cleanup:			
+	if (retval) {
+		if (tty->count == 1)
+			info->tty = NULL; /* tty layer will release tty struct */
+		if(info->count)
+			info->count--;
+	}
+	
+	return retval;
+}
+
+/*
+ * /proc fs routines....
+ */
+
+static inline int line_info(char *buf, MGSLPC_INFO *info)
+{
+	char	stat_buf[30];
+	int	ret;
+	unsigned long flags;
+
+	ret = sprintf(buf, "%s:io:%04X irq:%d",
+		      info->device_name, info->io_base, info->irq_level);
+
+	/* output current serial signal states */
+	spin_lock_irqsave(&info->lock,flags);
+ 	get_signals(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+	
+	stat_buf[0] = 0;
+	stat_buf[1] = 0;
+	if (info->serial_signals & SerialSignal_RTS)
+		strcat(stat_buf, "|RTS");
+	if (info->serial_signals & SerialSignal_CTS)
+		strcat(stat_buf, "|CTS");
+	if (info->serial_signals & SerialSignal_DTR)
+		strcat(stat_buf, "|DTR");
+	if (info->serial_signals & SerialSignal_DSR)
+		strcat(stat_buf, "|DSR");
+	if (info->serial_signals & SerialSignal_DCD)
+		strcat(stat_buf, "|CD");
+	if (info->serial_signals & SerialSignal_RI)
+		strcat(stat_buf, "|RI");
+
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		ret += sprintf(buf+ret, " HDLC txok:%d rxok:%d",
+			      info->icount.txok, info->icount.rxok);
+		if (info->icount.txunder)
+			ret += sprintf(buf+ret, " txunder:%d", info->icount.txunder);
+		if (info->icount.txabort)
+			ret += sprintf(buf+ret, " txabort:%d", info->icount.txabort);
+		if (info->icount.rxshort)
+			ret += sprintf(buf+ret, " rxshort:%d", info->icount.rxshort);	
+		if (info->icount.rxlong)
+			ret += sprintf(buf+ret, " rxlong:%d", info->icount.rxlong);
+		if (info->icount.rxover)
+			ret += sprintf(buf+ret, " rxover:%d", info->icount.rxover);
+		if (info->icount.rxcrc)
+			ret += sprintf(buf+ret, " rxcrc:%d", info->icount.rxcrc);
+	} else {
+		ret += sprintf(buf+ret, " ASYNC tx:%d rx:%d",
+			      info->icount.tx, info->icount.rx);
+		if (info->icount.frame)
+			ret += sprintf(buf+ret, " fe:%d", info->icount.frame);
+		if (info->icount.parity)
+			ret += sprintf(buf+ret, " pe:%d", info->icount.parity);
+		if (info->icount.brk)
+			ret += sprintf(buf+ret, " brk:%d", info->icount.brk);	
+		if (info->icount.overrun)
+			ret += sprintf(buf+ret, " oe:%d", info->icount.overrun);
+	}
+	
+	/* Append serial signal status to end */
+	ret += sprintf(buf+ret, " %s\n", stat_buf+1);
+	
+	ret += sprintf(buf+ret, "txactive=%d bh_req=%d bh_run=%d pending_bh=%x\n",
+		       info->tx_active,info->bh_requested,info->bh_running,
+		       info->pending_bh);
+	
+	return ret;
+}
+
+/* Called to print information about devices
+ */
+static int mgslpc_read_proc(char *page, char **start, off_t off, int count,
+		 int *eof, void *data)
+{
+	int len = 0, l;
+	off_t	begin = 0;
+	MGSLPC_INFO *info;
+	
+	len += sprintf(page, "synclink driver:%s\n", driver_version);
+	
+	info = mgslpc_device_list;
+	while( info ) {
+		l = line_info(page + len, info);
+		len += l;
+		if (len+begin > off+count)
+			goto done;
+		if (len+begin < off) {
+			begin += len;
+			len = 0;
+		}
+		info = info->next_device;
+	}
+
+	*eof = 1;
+done:
+	if (off >= len+begin)
+		return 0;
+	*start = page + (off-begin);
+	return ((count < begin+len-off) ? count : begin+len-off);
+}
+
+int rx_alloc_buffers(MGSLPC_INFO *info)
+{
+	/* each buffer has header and data */
+	info->rx_buf_size = sizeof(RXBUF) + info->max_frame_size;
+
+	/* calculate total allocation size for 8 buffers */
+	info->rx_buf_total_size = info->rx_buf_size * 8;
+
+	/* limit total allocated memory */
+	if (info->rx_buf_total_size > 0x10000)
+		info->rx_buf_total_size = 0x10000;
+
+	/* calculate number of buffers */
+	info->rx_buf_count = info->rx_buf_total_size / info->rx_buf_size;
+
+	info->rx_buf = kmalloc(info->rx_buf_total_size, GFP_KERNEL);
+	if (info->rx_buf == NULL)
+		return -ENOMEM;
+
+	rx_reset_buffers(info);
+	return 0;
+}
+
+void rx_free_buffers(MGSLPC_INFO *info)
+{
+	if (info->rx_buf)
+		kfree(info->rx_buf);
+	info->rx_buf = NULL;
+}
+
+int claim_resources(MGSLPC_INFO *info)
+{
+	if (rx_alloc_buffers(info) < 0 ) {
+		printk( "Cant allocate rx buffer %s\n", info->device_name);
+		release_resources(info);
+		return -ENODEV;
+	}	
+	return 0;
+}
+
+void release_resources(MGSLPC_INFO *info)
+{
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("release_resources(%s)\n", info->device_name);
+	rx_free_buffers(info);
+}
+
+/* Add the specified device instance data structure to the
+ * global linked list of devices and increment the device count.
+ * 	
+ * Arguments:		info	pointer to device instance data
+ */
+void mgslpc_add_device(MGSLPC_INFO *info)
+{
+	info->next_device = NULL;
+	info->line = mgslpc_device_count;
+	sprintf(info->device_name,"ttySLP%d",info->line);
+	
+	if (info->line < MAX_DEVICE_COUNT) {
+		if (maxframe[info->line])
+			info->max_frame_size = maxframe[info->line];
+		info->dosyncppp = dosyncppp[info->line];
+	}
+
+	mgslpc_device_count++;
+	
+	if (!mgslpc_device_list)
+		mgslpc_device_list = info;
+	else {	
+		MGSLPC_INFO *current_dev = mgslpc_device_list;
+		while( current_dev->next_device )
+			current_dev = current_dev->next_device;
+		current_dev->next_device = info;
+	}
+	
+	if (info->max_frame_size < 4096)
+		info->max_frame_size = 4096;
+	else if (info->max_frame_size > 65535)
+		info->max_frame_size = 65535;
+	
+	printk( "SyncLink PC Card %s:IO=%04X IRQ=%d\n",
+		info->device_name, info->io_base, info->irq_level);
+
+#ifdef CONFIG_HDLC
+	hdlcdev_init(info);
+#endif
+}
+
+void mgslpc_remove_device(MGSLPC_INFO *remove_info)
+{
+	MGSLPC_INFO *info = mgslpc_device_list;
+	MGSLPC_INFO *last = NULL;
+
+	while(info) {
+		if (info == remove_info) {
+			if (last)
+				last->next_device = info->next_device;
+			else
+				mgslpc_device_list = info->next_device;
+#ifdef CONFIG_HDLC
+			hdlcdev_exit(info);
+#endif
+			release_resources(info);
+			kfree(info);
+			mgslpc_device_count--;
+			return;
+		}
+		last = info;
+		info = info->next_device;
+	}
+}
+
+static struct pcmcia_driver mgslpc_driver = {
+	.owner		= THIS_MODULE,
+	.drv		= {
+		.name	= "synclink_cs",
+	},
+	.attach		= mgslpc_attach,
+	.detach		= mgslpc_detach,
+};
+
+static struct tty_operations mgslpc_ops = {
+	.open = mgslpc_open,
+	.close = mgslpc_close,
+	.write = mgslpc_write,
+	.put_char = mgslpc_put_char,
+	.flush_chars = mgslpc_flush_chars,
+	.write_room = mgslpc_write_room,
+	.chars_in_buffer = mgslpc_chars_in_buffer,
+	.flush_buffer = mgslpc_flush_buffer,
+	.ioctl = mgslpc_ioctl,
+	.throttle = mgslpc_throttle,
+	.unthrottle = mgslpc_unthrottle,
+	.send_xchar = mgslpc_send_xchar,
+	.break_ctl = mgslpc_break,
+	.wait_until_sent = mgslpc_wait_until_sent,
+	.read_proc = mgslpc_read_proc,
+	.set_termios = mgslpc_set_termios,
+	.stop = tx_pause,
+	.start = tx_release,
+	.hangup = mgslpc_hangup,
+	.tiocmget = tiocmget,
+	.tiocmset = tiocmset,
+};
+
+static void synclink_cs_cleanup(void)
+{
+	int rc;
+
+	printk("Unloading %s: version %s\n", driver_name, driver_version);
+
+	while(mgslpc_device_list)
+		mgslpc_remove_device(mgslpc_device_list);
+
+	if (serial_driver) {
+		if ((rc = tty_unregister_driver(serial_driver)))
+			printk("%s(%d) failed to unregister tty driver err=%d\n",
+			       __FILE__,__LINE__,rc);
+		put_tty_driver(serial_driver);
+	}
+
+	pcmcia_unregister_driver(&mgslpc_driver);
+	BUG_ON(dev_list != NULL);
+}
+
+static int __init synclink_cs_init(void)
+{
+    int rc;
+
+    if (break_on_load) {
+	    mgslpc_get_text_ptr();
+	    BREAKPOINT();
+    }
+
+    printk("%s %s\n", driver_name, driver_version);
+
+    if ((rc = pcmcia_register_driver(&mgslpc_driver)) < 0)
+	    return rc;
+
+    serial_driver = alloc_tty_driver(MAX_DEVICE_COUNT);
+    if (!serial_driver) {
+	    rc = -ENOMEM;
+	    goto error;
+    }
+
+    /* Initialize the tty_driver structure */
+	
+    serial_driver->owner = THIS_MODULE;
+    serial_driver->driver_name = "synclink_cs";
+    serial_driver->name = "ttySLP";
+    serial_driver->major = ttymajor;
+    serial_driver->minor_start = 64;
+    serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+    serial_driver->subtype = SERIAL_TYPE_NORMAL;
+    serial_driver->init_termios = tty_std_termios;
+    serial_driver->init_termios.c_cflag =
+	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+    serial_driver->flags = TTY_DRIVER_REAL_RAW;
+    tty_set_operations(serial_driver, &mgslpc_ops);
+
+    if ((rc = tty_register_driver(serial_driver)) < 0) {
+	    printk("%s(%d):Couldn't register serial driver\n",
+		   __FILE__,__LINE__);
+	    put_tty_driver(serial_driver);
+	    serial_driver = NULL;
+	    goto error;
+    }
+			
+    printk("%s %s, tty major#%d\n",
+	   driver_name, driver_version,
+	   serial_driver->major);
+	
+    return 0;
+
+error:
+    synclink_cs_cleanup();
+    return rc;
+}
+
+static void __exit synclink_cs_exit(void) 
+{
+	synclink_cs_cleanup();
+}
+
+module_init(synclink_cs_init);
+module_exit(synclink_cs_exit);
+
+static void mgslpc_set_rate(MGSLPC_INFO *info, unsigned char channel, unsigned int rate)
+{
+	unsigned int M, N;
+	unsigned char val;
+
+	/* note:standard BRG mode is broken in V3.2 chip 
+	 * so enhanced mode is always used 
+	 */
+
+	if (rate) {
+		N = 3686400 / rate;
+		if (!N)
+			N = 1;
+		N >>= 1;
+		for (M = 1; N > 64 && M < 16; M++)
+			N >>= 1;
+		N--;
+
+		/* BGR[5..0] = N
+		 * BGR[9..6] = M
+		 * BGR[7..0] contained in BGR register
+		 * BGR[9..8] contained in CCR2[7..6]
+		 * divisor = (N+1)*2^M
+		 *
+		 * Note: M *must* not be zero (causes asymetric duty cycle)
+		 */ 
+		write_reg(info, (unsigned char) (channel + BGR),
+				  (unsigned char) ((M << 6) + N));
+		val = read_reg(info, (unsigned char) (channel + CCR2)) & 0x3f;
+		val |= ((M << 4) & 0xc0);
+		write_reg(info, (unsigned char) (channel + CCR2), val);
+	}
+}
+
+/* Enabled the AUX clock output at the specified frequency.
+ */
+static void enable_auxclk(MGSLPC_INFO *info)
+{
+	unsigned char val;
+	
+	/* MODE
+	 *
+	 * 07..06  MDS[1..0] 10 = transparent HDLC mode
+	 * 05      ADM Address Mode, 0 = no addr recognition
+	 * 04      TMD Timer Mode, 0 = external
+	 * 03      RAC Receiver Active, 0 = inactive
+	 * 02      RTS 0=RTS active during xmit, 1=RTS always active
+	 * 01      TRS Timer Resolution, 1=512
+	 * 00      TLP Test Loop, 0 = no loop
+	 *
+	 * 1000 0010
+	 */ 
+	val = 0x82;
+	
+	/* channel B RTS is used to enable AUXCLK driver on SP505 */ 
+	if (info->params.mode == MGSL_MODE_HDLC && info->params.clock_speed)
+		val |= BIT2;
+	write_reg(info, CHB + MODE, val);
+	
+	/* CCR0
+	 *
+	 * 07      PU Power Up, 1=active, 0=power down
+	 * 06      MCE Master Clock Enable, 1=enabled
+	 * 05      Reserved, 0
+	 * 04..02  SC[2..0] Encoding
+	 * 01..00  SM[1..0] Serial Mode, 00=HDLC
+	 *
+	 * 11000000
+	 */ 
+	write_reg(info, CHB + CCR0, 0xc0);
+	
+	/* CCR1
+	 *
+	 * 07      SFLG Shared Flag, 0 = disable shared flags
+	 * 06      GALP Go Active On Loop, 0 = not used
+	 * 05      GLP Go On Loop, 0 = not used
+	 * 04      ODS Output Driver Select, 1=TxD is push-pull output
+	 * 03      ITF Interframe Time Fill, 0=mark, 1=flag
+	 * 02..00  CM[2..0] Clock Mode
+	 *
+	 * 0001 0111
+	 */ 
+	write_reg(info, CHB + CCR1, 0x17);
+	
+	/* CCR2 (Channel B)
+	 *
+	 * 07..06  BGR[9..8] Baud rate bits 9..8
+	 * 05      BDF Baud rate divisor factor, 0=1, 1=BGR value
+	 * 04      SSEL Clock source select, 1=submode b
+	 * 03      TOE 0=TxCLK is input, 1=TxCLK is output
+	 * 02      RWX Read/Write Exchange 0=disabled
+	 * 01      C32, CRC select, 0=CRC-16, 1=CRC-32
+	 * 00      DIV, data inversion 0=disabled, 1=enabled
+	 *
+	 * 0011 1000
+	 */ 
+	if (info->params.mode == MGSL_MODE_HDLC && info->params.clock_speed)
+		write_reg(info, CHB + CCR2, 0x38);
+	else
+		write_reg(info, CHB + CCR2, 0x30);
+	
+	/* CCR4
+	 *
+	 * 07      MCK4 Master Clock Divide by 4, 1=enabled
+	 * 06      EBRG Enhanced Baud Rate Generator Mode, 1=enabled
+	 * 05      TST1 Test Pin, 0=normal operation
+	 * 04      ICD Ivert Carrier Detect, 1=enabled (active low)
+	 * 03..02  Reserved, must be 0
+	 * 01..00  RFT[1..0] RxFIFO Threshold 00=32 bytes
+	 *
+	 * 0101 0000
+	 */ 
+	write_reg(info, CHB + CCR4, 0x50);
+	
+	/* if auxclk not enabled, set internal BRG so
+	 * CTS transitions can be detected (requires TxC)
+	 */ 
+	if (info->params.mode == MGSL_MODE_HDLC && info->params.clock_speed)
+		mgslpc_set_rate(info, CHB, info->params.clock_speed);
+	else
+		mgslpc_set_rate(info, CHB, 921600);
+}
+
+static void loopback_enable(MGSLPC_INFO *info) 
+{
+	unsigned char val;
+	
+	/* CCR1:02..00  CM[2..0] Clock Mode = 111 (clock mode 7) */ 
+	val = read_reg(info, CHA + CCR1) | (BIT2 + BIT1 + BIT0);
+	write_reg(info, CHA + CCR1, val);
+	
+	/* CCR2:04 SSEL Clock source select, 1=submode b */ 
+	val = read_reg(info, CHA + CCR2) | (BIT4 + BIT5);
+	write_reg(info, CHA + CCR2, val);
+	
+	/* set LinkSpeed if available, otherwise default to 2Mbps */ 
+	if (info->params.clock_speed)
+		mgslpc_set_rate(info, CHA, info->params.clock_speed);
+	else
+		mgslpc_set_rate(info, CHA, 1843200);
+	
+	/* MODE:00 TLP Test Loop, 1=loopback enabled */ 
+	val = read_reg(info, CHA + MODE) | BIT0;
+	write_reg(info, CHA + MODE, val);
+}
+
+void hdlc_mode(MGSLPC_INFO *info)
+{
+	unsigned char val;
+	unsigned char clkmode, clksubmode;
+
+	/* disable all interrupts */ 
+	irq_disable(info, CHA, 0xffff);
+	irq_disable(info, CHB, 0xffff);
+	port_irq_disable(info, 0xff);
+	
+	/* assume clock mode 0a, rcv=RxC xmt=TxC */ 
+	clkmode = clksubmode = 0;
+	if (info->params.flags & HDLC_FLAG_RXC_DPLL
+	    && info->params.flags & HDLC_FLAG_TXC_DPLL) {
+		/* clock mode 7a, rcv = DPLL, xmt = DPLL */ 
+		clkmode = 7;
+	} else if (info->params.flags & HDLC_FLAG_RXC_BRG
+		 && info->params.flags & HDLC_FLAG_TXC_BRG) {
+		/* clock mode 7b, rcv = BRG, xmt = BRG */ 
+		clkmode = 7;
+		clksubmode = 1;
+	} else if (info->params.flags & HDLC_FLAG_RXC_DPLL) {
+		if (info->params.flags & HDLC_FLAG_TXC_BRG) {
+			/* clock mode 6b, rcv = DPLL, xmt = BRG/16 */ 
+			clkmode = 6;
+			clksubmode = 1;
+		} else {
+			/* clock mode 6a, rcv = DPLL, xmt = TxC */ 
+			clkmode = 6;
+		}
+	} else if (info->params.flags & HDLC_FLAG_TXC_BRG) {
+		/* clock mode 0b, rcv = RxC, xmt = BRG */ 
+		clksubmode = 1;
+	}
+	
+	/* MODE
+	 *
+	 * 07..06  MDS[1..0] 10 = transparent HDLC mode
+	 * 05      ADM Address Mode, 0 = no addr recognition
+	 * 04      TMD Timer Mode, 0 = external
+	 * 03      RAC Receiver Active, 0 = inactive
+	 * 02      RTS 0=RTS active during xmit, 1=RTS always active
+	 * 01      TRS Timer Resolution, 1=512
+	 * 00      TLP Test Loop, 0 = no loop
+	 *
+	 * 1000 0010
+	 */ 
+	val = 0x82;
+	if (info->params.loopback)
+		val |= BIT0;
+	
+	/* preserve RTS state */ 
+	if (info->serial_signals & SerialSignal_RTS)
+		val |= BIT2;
+	write_reg(info, CHA + MODE, val);
+	
+	/* CCR0
+	 *
+	 * 07      PU Power Up, 1=active, 0=power down
+	 * 06      MCE Master Clock Enable, 1=enabled
+	 * 05      Reserved, 0
+	 * 04..02  SC[2..0] Encoding
+	 * 01..00  SM[1..0] Serial Mode, 00=HDLC
+	 *
+	 * 11000000
+	 */ 
+	val = 0xc0;
+	switch (info->params.encoding)
+	{
+	case HDLC_ENCODING_NRZI:
+		val |= BIT3;
+		break;
+	case HDLC_ENCODING_BIPHASE_SPACE:
+		val |= BIT4;
+		break;		// FM0
+	case HDLC_ENCODING_BIPHASE_MARK:
+		val |= BIT4 + BIT2;
+		break;		// FM1
+	case HDLC_ENCODING_BIPHASE_LEVEL:
+		val |= BIT4 + BIT3;
+		break;		// Manchester
+	}
+	write_reg(info, CHA + CCR0, val);
+	
+	/* CCR1
+	 *
+	 * 07      SFLG Shared Flag, 0 = disable shared flags
+	 * 06      GALP Go Active On Loop, 0 = not used
+	 * 05      GLP Go On Loop, 0 = not used
+	 * 04      ODS Output Driver Select, 1=TxD is push-pull output
+	 * 03      ITF Interframe Time Fill, 0=mark, 1=flag
+	 * 02..00  CM[2..0] Clock Mode
+	 *
+	 * 0001 0000
+	 */ 
+	val = 0x10 + clkmode;
+	write_reg(info, CHA + CCR1, val);
+	
+	/* CCR2
+	 *
+	 * 07..06  BGR[9..8] Baud rate bits 9..8
+	 * 05      BDF Baud rate divisor factor, 0=1, 1=BGR value
+	 * 04      SSEL Clock source select, 1=submode b
+	 * 03      TOE 0=TxCLK is input, 0=TxCLK is input
+	 * 02      RWX Read/Write Exchange 0=disabled
+	 * 01      C32, CRC select, 0=CRC-16, 1=CRC-32
+	 * 00      DIV, data inversion 0=disabled, 1=enabled
+	 *
+	 * 0000 0000
+	 */ 
+	val = 0x00;
+	if (clkmode == 2 || clkmode == 3 || clkmode == 6
+	    || clkmode == 7 || (clkmode == 0 && clksubmode == 1))
+		val |= BIT5;
+	if (clksubmode)
+		val |= BIT4;
+	if (info->params.crc_type == HDLC_CRC_32_CCITT)
+		val |= BIT1;
+	if (info->params.encoding == HDLC_ENCODING_NRZB)
+		val |= BIT0;
+	write_reg(info, CHA + CCR2, val);
+	
+	/* CCR3
+	 *
+	 * 07..06  PRE[1..0] Preamble count 00=1, 01=2, 10=4, 11=8
+	 * 05      EPT Enable preamble transmission, 1=enabled
+	 * 04      RADD Receive address pushed to FIFO, 0=disabled
+	 * 03      CRL CRC Reset Level, 0=FFFF
+	 * 02      RCRC Rx CRC 0=On 1=Off
+	 * 01      TCRC Tx CRC 0=On 1=Off
+	 * 00      PSD DPLL Phase Shift Disable
+	 *
+	 * 0000 0000
+	 */ 
+	val = 0x00;
+	if (info->params.crc_type == HDLC_CRC_NONE)
+		val |= BIT2 + BIT1;
+	if (info->params.preamble != HDLC_PREAMBLE_PATTERN_NONE)
+		val |= BIT5;
+	switch (info->params.preamble_length)
+	{
+	case HDLC_PREAMBLE_LENGTH_16BITS:
+		val |= BIT6;
+		break;
+	case HDLC_PREAMBLE_LENGTH_32BITS:
+		val |= BIT6;
+		break;
+	case HDLC_PREAMBLE_LENGTH_64BITS:
+		val |= BIT7 + BIT6;
+		break;
+	}
+	write_reg(info, CHA + CCR3, val);
+	
+	/* PRE - Preamble pattern */ 
+	val = 0;
+	switch (info->params.preamble)
+	{
+	case HDLC_PREAMBLE_PATTERN_FLAGS: val = 0x7e; break;
+	case HDLC_PREAMBLE_PATTERN_10:    val = 0xaa; break;
+	case HDLC_PREAMBLE_PATTERN_01:    val = 0x55; break;
+	case HDLC_PREAMBLE_PATTERN_ONES:  val = 0xff; break;
+	}
+	write_reg(info, CHA + PRE, val);
+	
+	/* CCR4
+	 *
+	 * 07      MCK4 Master Clock Divide by 4, 1=enabled
+	 * 06      EBRG Enhanced Baud Rate Generator Mode, 1=enabled
+	 * 05      TST1 Test Pin, 0=normal operation
+	 * 04      ICD Ivert Carrier Detect, 1=enabled (active low)
+	 * 03..02  Reserved, must be 0
+	 * 01..00  RFT[1..0] RxFIFO Threshold 00=32 bytes
+	 *
+	 * 0101 0000
+	 */ 
+	val = 0x50;
+	write_reg(info, CHA + CCR4, val);
+	if (info->params.flags & HDLC_FLAG_RXC_DPLL)
+		mgslpc_set_rate(info, CHA, info->params.clock_speed * 16);
+	else
+		mgslpc_set_rate(info, CHA, info->params.clock_speed);
+	
+	/* RLCR Receive length check register
+	 *
+	 * 7     1=enable receive length check
+	 * 6..0  Max frame length = (RL + 1) * 32
+	 */ 
+	write_reg(info, CHA + RLCR, 0);
+	
+	/* XBCH Transmit Byte Count High
+	 *
+	 * 07      DMA mode, 0 = interrupt driven
+	 * 06      NRM, 0=ABM (ignored)
+	 * 05      CAS Carrier Auto Start
+	 * 04      XC Transmit Continuously (ignored)
+	 * 03..00  XBC[10..8] Transmit byte count bits 10..8
+	 *
+	 * 0000 0000
+	 */ 
+	val = 0x00;
+	if (info->params.flags & HDLC_FLAG_AUTO_DCD)
+		val |= BIT5;
+	write_reg(info, CHA + XBCH, val);
+	enable_auxclk(info);
+	if (info->params.loopback || info->testing_irq)
+		loopback_enable(info);
+	if (info->params.flags & HDLC_FLAG_AUTO_CTS)
+	{
+		irq_enable(info, CHB, IRQ_CTS);
+		/* PVR[3] 1=AUTO CTS active */ 
+		set_reg_bits(info, CHA + PVR, BIT3);
+	} else
+		clear_reg_bits(info, CHA + PVR, BIT3);
+
+	irq_enable(info, CHA,
+			 IRQ_RXEOM + IRQ_RXFIFO + IRQ_ALLSENT +
+			 IRQ_UNDERRUN + IRQ_TXFIFO);
+	issue_command(info, CHA, CMD_TXRESET + CMD_RXRESET);
+	wait_command_complete(info, CHA);
+	read_reg16(info, CHA + ISR);	/* clear pending IRQs */
+	
+	/* Master clock mode enabled above to allow reset commands
+	 * to complete even if no data clocks are present.
+	 *
+	 * Disable master clock mode for normal communications because
+	 * V3.2 of the ESCC2 has a bug that prevents the transmit all sent
+	 * IRQ when in master clock mode.
+	 *
+	 * Leave master clock mode enabled for IRQ test because the
+	 * timer IRQ used by the test can only happen in master clock mode.
+	 */ 
+	if (!info->testing_irq)
+		clear_reg_bits(info, CHA + CCR0, BIT6);
+
+	tx_set_idle(info);
+
+	tx_stop(info);
+	rx_stop(info);
+}
+
+void rx_stop(MGSLPC_INFO *info)
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):rx_stop(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	/* MODE:03 RAC Receiver Active, 0=inactive */ 
+	clear_reg_bits(info, CHA + MODE, BIT3);
+
+	info->rx_enabled = 0;
+	info->rx_overflow = 0;
+}
+
+void rx_start(MGSLPC_INFO *info)
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):rx_start(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	rx_reset_buffers(info);
+	info->rx_enabled = 0;
+	info->rx_overflow = 0;
+
+	/* MODE:03 RAC Receiver Active, 1=active */ 
+	set_reg_bits(info, CHA + MODE, BIT3);
+
+	info->rx_enabled = 1;
+}
+
+void tx_start(MGSLPC_INFO *info)
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):tx_start(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	if (info->tx_count) {
+		/* If auto RTS enabled and RTS is inactive, then assert */
+		/* RTS and set a flag indicating that the driver should */
+		/* negate RTS when the transmission completes. */
+		info->drop_rts_on_tx_done = 0;
+
+		if (info->params.flags & HDLC_FLAG_AUTO_RTS) {
+			get_signals(info);
+			if (!(info->serial_signals & SerialSignal_RTS)) {
+				info->serial_signals |= SerialSignal_RTS;
+				set_signals(info);
+				info->drop_rts_on_tx_done = 1;
+			}
+		}
+
+		if (info->params.mode == MGSL_MODE_ASYNC) {
+			if (!info->tx_active) {
+				info->tx_active = 1;
+				tx_ready(info);
+			}
+		} else {
+			info->tx_active = 1;
+			tx_ready(info);
+			info->tx_timer.expires = jiffies + msecs_to_jiffies(5000);
+			add_timer(&info->tx_timer);	
+		}
+	}
+
+	if (!info->tx_enabled)
+		info->tx_enabled = 1;
+}
+
+void tx_stop(MGSLPC_INFO *info)
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):tx_stop(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	del_timer(&info->tx_timer);	
+
+	info->tx_enabled = 0;
+	info->tx_active  = 0;
+}
+
+/* Reset the adapter to a known state and prepare it for further use.
+ */
+void reset_device(MGSLPC_INFO *info)
+{
+	/* power up both channels (set BIT7) */ 
+	write_reg(info, CHA + CCR0, 0x80);
+	write_reg(info, CHB + CCR0, 0x80);
+	write_reg(info, CHA + MODE, 0);
+	write_reg(info, CHB + MODE, 0);
+	
+	/* disable all interrupts */ 
+	irq_disable(info, CHA, 0xffff);
+	irq_disable(info, CHB, 0xffff);
+	port_irq_disable(info, 0xff);
+	
+	/* PCR Port Configuration Register
+	 *
+	 * 07..04  DEC[3..0] Serial I/F select outputs
+	 * 03      output, 1=AUTO CTS control enabled
+	 * 02      RI Ring Indicator input 0=active
+	 * 01      DSR input 0=active
+	 * 00      DTR output 0=active
+	 *
+	 * 0000 0110
+	 */ 
+	write_reg(info, PCR, 0x06);
+	
+	/* PVR Port Value Register
+	 *
+	 * 07..04  DEC[3..0] Serial I/F select (0000=disabled)
+	 * 03      AUTO CTS output 1=enabled
+	 * 02      RI Ring Indicator input
+	 * 01      DSR input
+	 * 00      DTR output (1=inactive)
+	 *
+	 * 0000 0001
+	 */
+//	write_reg(info, PVR, PVR_DTR);
+	
+	/* IPC Interrupt Port Configuration
+	 *
+	 * 07      VIS 1=Masked interrupts visible
+	 * 06..05  Reserved, 0
+	 * 04..03  SLA Slave address, 00 ignored
+	 * 02      CASM Cascading Mode, 1=daisy chain
+	 * 01..00  IC[1..0] Interrupt Config, 01=push-pull output, active low
+	 *
+	 * 0000 0101
+	 */ 
+	write_reg(info, IPC, 0x05);
+}
+
+void async_mode(MGSLPC_INFO *info)
+{
+	unsigned char val;
+
+	/* disable all interrupts */ 
+	irq_disable(info, CHA, 0xffff);
+	irq_disable(info, CHB, 0xffff);
+	port_irq_disable(info, 0xff);
+	
+	/* MODE
+	 *
+	 * 07      Reserved, 0
+	 * 06      FRTS RTS State, 0=active
+	 * 05      FCTS Flow Control on CTS
+	 * 04      FLON Flow Control Enable
+	 * 03      RAC Receiver Active, 0 = inactive
+	 * 02      RTS 0=Auto RTS, 1=manual RTS
+	 * 01      TRS Timer Resolution, 1=512
+	 * 00      TLP Test Loop, 0 = no loop
+	 *
+	 * 0000 0110
+	 */ 
+	val = 0x06;
+	if (info->params.loopback)
+		val |= BIT0;
+	
+	/* preserve RTS state */ 
+	if (!(info->serial_signals & SerialSignal_RTS))
+		val |= BIT6;
+	write_reg(info, CHA + MODE, val);
+	
+	/* CCR0
+	 *
+	 * 07      PU Power Up, 1=active, 0=power down
+	 * 06      MCE Master Clock Enable, 1=enabled
+	 * 05      Reserved, 0
+	 * 04..02  SC[2..0] Encoding, 000=NRZ
+	 * 01..00  SM[1..0] Serial Mode, 11=Async
+	 *
+	 * 1000 0011
+	 */ 
+	write_reg(info, CHA + CCR0, 0x83);
+	
+	/* CCR1
+	 *
+	 * 07..05  Reserved, 0
+	 * 04      ODS Output Driver Select, 1=TxD is push-pull output
+	 * 03      BCR Bit Clock Rate, 1=16x
+	 * 02..00  CM[2..0] Clock Mode, 111=BRG
+	 *
+	 * 0001 1111
+	 */ 
+	write_reg(info, CHA + CCR1, 0x1f);
+	
+	/* CCR2 (channel A)
+	 *
+	 * 07..06  BGR[9..8] Baud rate bits 9..8
+	 * 05      BDF Baud rate divisor factor, 0=1, 1=BGR value
+	 * 04      SSEL Clock source select, 1=submode b
+	 * 03      TOE 0=TxCLK is input, 0=TxCLK is input
+	 * 02      RWX Read/Write Exchange 0=disabled
+	 * 01      Reserved, 0
+	 * 00      DIV, data inversion 0=disabled, 1=enabled
+	 *
+	 * 0001 0000
+	 */ 
+	write_reg(info, CHA + CCR2, 0x10);
+	
+	/* CCR3
+	 *
+	 * 07..01  Reserved, 0
+	 * 00      PSD DPLL Phase Shift Disable
+	 *
+	 * 0000 0000
+	 */ 
+	write_reg(info, CHA + CCR3, 0);
+	
+	/* CCR4
+	 *
+	 * 07      MCK4 Master Clock Divide by 4, 1=enabled
+	 * 06      EBRG Enhanced Baud Rate Generator Mode, 1=enabled
+	 * 05      TST1 Test Pin, 0=normal operation
+	 * 04      ICD Ivert Carrier Detect, 1=enabled (active low)
+	 * 03..00  Reserved, must be 0
+	 *
+	 * 0101 0000
+	 */ 
+	write_reg(info, CHA + CCR4, 0x50);
+	mgslpc_set_rate(info, CHA, info->params.data_rate * 16);
+	
+	/* DAFO Data Format
+	 *
+	 * 07      Reserved, 0
+	 * 06      XBRK transmit break, 0=normal operation
+	 * 05      Stop bits (0=1, 1=2)
+	 * 04..03  PAR[1..0] Parity (01=odd, 10=even)
+	 * 02      PAREN Parity Enable
+	 * 01..00  CHL[1..0] Character Length (00=8, 01=7)
+	 *
+	 */ 
+	val = 0x00;
+	if (info->params.data_bits != 8)
+		val |= BIT0;	/* 7 bits */
+	if (info->params.stop_bits != 1)
+		val |= BIT5;
+	if (info->params.parity != ASYNC_PARITY_NONE)
+	{
+		val |= BIT2;	/* Parity enable */
+		if (info->params.parity == ASYNC_PARITY_ODD)
+			val |= BIT3;
+		else
+			val |= BIT4;
+	}
+	write_reg(info, CHA + DAFO, val);
+	
+	/* RFC Rx FIFO Control
+	 *
+	 * 07      Reserved, 0
+	 * 06      DPS, 1=parity bit not stored in data byte
+	 * 05      DXS, 0=all data stored in FIFO (including XON/XOFF)
+	 * 04      RFDF Rx FIFO Data Format, 1=status byte stored in FIFO
+	 * 03..02  RFTH[1..0], rx threshold, 11=16 status + 16 data byte
+	 * 01      Reserved, 0
+	 * 00      TCDE Terminate Char Detect Enable, 0=disabled
+	 *
+	 * 0101 1100
+	 */ 
+	write_reg(info, CHA + RFC, 0x5c);
+	
+	/* RLCR Receive length check register
+	 *
+	 * Max frame length = (RL + 1) * 32
+	 */ 
+	write_reg(info, CHA + RLCR, 0);
+	
+	/* XBCH Transmit Byte Count High
+	 *
+	 * 07      DMA mode, 0 = interrupt driven
+	 * 06      NRM, 0=ABM (ignored)
+	 * 05      CAS Carrier Auto Start
+	 * 04      XC Transmit Continuously (ignored)
+	 * 03..00  XBC[10..8] Transmit byte count bits 10..8
+	 *
+	 * 0000 0000
+	 */ 
+	val = 0x00;
+	if (info->params.flags & HDLC_FLAG_AUTO_DCD)
+		val |= BIT5;
+	write_reg(info, CHA + XBCH, val);
+	if (info->params.flags & HDLC_FLAG_AUTO_CTS)
+		irq_enable(info, CHA, IRQ_CTS);
+	
+	/* MODE:03 RAC Receiver Active, 1=active */ 
+	set_reg_bits(info, CHA + MODE, BIT3);
+	enable_auxclk(info);
+	if (info->params.flags & HDLC_FLAG_AUTO_CTS) {
+		irq_enable(info, CHB, IRQ_CTS);
+		/* PVR[3] 1=AUTO CTS active */ 
+		set_reg_bits(info, CHA + PVR, BIT3);
+	} else
+		clear_reg_bits(info, CHA + PVR, BIT3);
+	irq_enable(info, CHA,
+			  IRQ_RXEOM + IRQ_RXFIFO + IRQ_BREAK_ON + IRQ_RXTIME +
+			  IRQ_ALLSENT + IRQ_TXFIFO);
+	issue_command(info, CHA, CMD_TXRESET + CMD_RXRESET);
+	wait_command_complete(info, CHA);
+	read_reg16(info, CHA + ISR);	/* clear pending IRQs */
+}
+
+/* Set the HDLC idle mode for the transmitter.
+ */
+void tx_set_idle(MGSLPC_INFO *info)
+{
+	/* Note: ESCC2 only supports flags and one idle modes */ 
+	if (info->idle_mode == HDLC_TXIDLE_FLAGS)
+		set_reg_bits(info, CHA + CCR1, BIT3);
+	else
+		clear_reg_bits(info, CHA + CCR1, BIT3);
+}
+
+/* get state of the V24 status (input) signals.
+ */
+void get_signals(MGSLPC_INFO *info)
+{
+	unsigned char status = 0;
+	
+	/* preserve DTR and RTS */ 
+	info->serial_signals &= SerialSignal_DTR + SerialSignal_RTS;
+
+	if (read_reg(info, CHB + VSTR) & BIT7)
+		info->serial_signals |= SerialSignal_DCD;
+	if (read_reg(info, CHB + STAR) & BIT1)
+		info->serial_signals |= SerialSignal_CTS;
+
+	status = read_reg(info, CHA + PVR);
+	if (!(status & PVR_RI))
+		info->serial_signals |= SerialSignal_RI;
+	if (!(status & PVR_DSR))
+		info->serial_signals |= SerialSignal_DSR;
+}
+
+/* Set the state of DTR and RTS based on contents of
+ * serial_signals member of device extension.
+ */
+void set_signals(MGSLPC_INFO *info)
+{
+	unsigned char val;
+
+	val = read_reg(info, CHA + MODE);
+	if (info->params.mode == MGSL_MODE_ASYNC) {
+		if (info->serial_signals & SerialSignal_RTS)
+			val &= ~BIT6;
+		else
+			val |= BIT6;
+	} else {
+		if (info->serial_signals & SerialSignal_RTS)
+			val |= BIT2;
+		else
+			val &= ~BIT2;
+	}
+	write_reg(info, CHA + MODE, val);
+
+	if (info->serial_signals & SerialSignal_DTR)
+		clear_reg_bits(info, CHA + PVR, PVR_DTR);
+	else
+		set_reg_bits(info, CHA + PVR, PVR_DTR);
+}
+
+void rx_reset_buffers(MGSLPC_INFO *info)
+{
+	RXBUF *buf;
+	int i;
+
+	info->rx_put = 0;
+	info->rx_get = 0;
+	info->rx_frame_count = 0;
+	for (i=0 ; i < info->rx_buf_count ; i++) {
+		buf = (RXBUF*)(info->rx_buf + (i * info->rx_buf_size));
+		buf->status = buf->count = 0;
+	}
+}
+
+/* Attempt to return a received HDLC frame
+ * Only frames received without errors are returned.
+ *
+ * Returns 1 if frame returned, otherwise 0
+ */
+int rx_get_frame(MGSLPC_INFO *info)
+{
+	unsigned short status;
+	RXBUF *buf;
+	unsigned int framesize = 0;
+	unsigned long flags;
+	struct tty_struct *tty = info->tty;
+	int return_frame = 0;
+	
+	if (info->rx_frame_count == 0)
+		return 0;
+
+	buf = (RXBUF*)(info->rx_buf + (info->rx_get * info->rx_buf_size));
+
+	status = buf->status;
+
+	/* 07  VFR  1=valid frame
+	 * 06  RDO  1=data overrun
+	 * 05  CRC  1=OK, 0=error
+	 * 04  RAB  1=frame aborted
+	 */
+	if ((status & 0xf0) != 0xA0) {
+		if (!(status & BIT7) || (status & BIT4))
+			info->icount.rxabort++;
+		else if (status & BIT6)
+			info->icount.rxover++;
+		else if (!(status & BIT5)) {
+			info->icount.rxcrc++;
+			if (info->params.crc_type & HDLC_CRC_RETURN_EX)
+				return_frame = 1;
+		}
+		framesize = 0;
+#ifdef CONFIG_HDLC
+		{
+			struct net_device_stats *stats = hdlc_stats(info->netdev);
+			stats->rx_errors++;
+			stats->rx_frame_errors++;
+		}
+#endif
+	} else
+		return_frame = 1;
+
+	if (return_frame)
+		framesize = buf->count;
+
+	if (debug_level >= DEBUG_LEVEL_BH)
+		printk("%s(%d):rx_get_frame(%s) status=%04X size=%d\n",
+			__FILE__,__LINE__,info->device_name,status,framesize);
+			
+	if (debug_level >= DEBUG_LEVEL_DATA)
+		trace_block(info, buf->data, framesize, 0);	
+		
+	if (framesize) {
+		if ((info->params.crc_type & HDLC_CRC_RETURN_EX &&
+		      framesize+1 > info->max_frame_size) ||
+		    framesize > info->max_frame_size)
+			info->icount.rxlong++;
+		else {
+			if (status & BIT5)
+				info->icount.rxok++;
+
+			if (info->params.crc_type & HDLC_CRC_RETURN_EX) {
+				*(buf->data + framesize) = status & BIT5 ? RX_OK:RX_CRC_ERROR;
+				++framesize;
+			}
+
+#ifdef CONFIG_HDLC
+			if (info->netcount)
+				hdlcdev_rx(info, buf->data, framesize);
+			else
+#endif
+				ldisc_receive_buf(tty, buf->data, info->flag_buf, framesize);
+		}
+	}
+
+	spin_lock_irqsave(&info->lock,flags);
+	buf->status = buf->count = 0;
+	info->rx_frame_count--;
+	info->rx_get++;
+	if (info->rx_get >= info->rx_buf_count)
+		info->rx_get = 0;
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	return 1;
+}
+
+BOOLEAN register_test(MGSLPC_INFO *info)
+{
+	static unsigned char patterns[] = 
+	    { 0x00, 0xff, 0xaa, 0x55, 0x69, 0x96, 0x0f };
+	static unsigned int count = sizeof(patterns) / sizeof(patterns[0]);
+	unsigned int i;
+	BOOLEAN rc = TRUE;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->lock,flags);
+	reset_device(info);
+
+	for (i = 0; i < count; i++) {
+		write_reg(info, XAD1, patterns[i]);
+		write_reg(info, XAD2, patterns[(i + 1) % count]);
+		if ((read_reg(info, XAD1) != patterns[i]) || 
+		    (read_reg(info, XAD2) != patterns[(i + 1) % count])) {
+			rc = FALSE;
+			break;
+		}
+	}
+
+	spin_unlock_irqrestore(&info->lock,flags);
+	return rc;
+}
+
+BOOLEAN irq_test(MGSLPC_INFO *info)
+{
+	unsigned long end_time;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->lock,flags);
+	reset_device(info);
+
+	info->testing_irq = TRUE;
+	hdlc_mode(info);
+
+	info->irq_occurred = FALSE;
+
+	/* init hdlc mode */
+
+	irq_enable(info, CHA, IRQ_TIMER);
+	write_reg(info, CHA + TIMR, 0);	/* 512 cycles */
+	issue_command(info, CHA, CMD_START_TIMER);
+
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	end_time=100;
+	while(end_time-- && !info->irq_occurred) {
+		msleep_interruptible(10);
+	}
+	
+	info->testing_irq = FALSE;
+
+	spin_lock_irqsave(&info->lock,flags);
+	reset_device(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+	
+	return info->irq_occurred ? TRUE : FALSE;
+}
+
+int adapter_test(MGSLPC_INFO *info)
+{
+	if (!register_test(info)) {
+		info->init_error = DiagStatus_AddressFailure;
+		printk( "%s(%d):Register test failure for device %s Addr=%04X\n",
+			__FILE__,__LINE__,info->device_name, (unsigned short)(info->io_base) );
+		return -ENODEV;
+	}
+
+	if (!irq_test(info)) {
+		info->init_error = DiagStatus_IrqFailure;
+		printk( "%s(%d):Interrupt test failure for device %s IRQ=%d\n",
+			__FILE__,__LINE__,info->device_name, (unsigned short)(info->irq_level) );
+		return -ENODEV;
+	}
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):device %s passed diagnostics\n",
+			__FILE__,__LINE__,info->device_name);
+	return 0;
+}
+
+void trace_block(MGSLPC_INFO *info,const char* data, int count, int xmit)
+{
+	int i;
+	int linecount;
+	if (xmit)
+		printk("%s tx data:\n",info->device_name);
+	else
+		printk("%s rx data:\n",info->device_name);
+		
+	while(count) {
+		if (count > 16)
+			linecount = 16;
+		else
+			linecount = count;
+			
+		for(i=0;i<linecount;i++)
+			printk("%02X ",(unsigned char)data[i]);
+		for(;i<17;i++)
+			printk("   ");
+		for(i=0;i<linecount;i++) {
+			if (data[i]>=040 && data[i]<=0176)
+				printk("%c",data[i]);
+			else
+				printk(".");
+		}
+		printk("\n");
+		
+		data  += linecount;
+		count -= linecount;
+	}
+}
+
+/* HDLC frame time out
+ * update stats and do tx completion processing
+ */
+void tx_timeout(unsigned long context)
+{
+	MGSLPC_INFO *info = (MGSLPC_INFO*)context;
+	unsigned long flags;
+	
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):tx_timeout(%s)\n",
+			__FILE__,__LINE__,info->device_name);
+	if(info->tx_active &&
+	   info->params.mode == MGSL_MODE_HDLC) {
+		info->icount.txtimeout++;
+	}
+	spin_lock_irqsave(&info->lock,flags);
+	info->tx_active = 0;
+	info->tx_count = info->tx_put = info->tx_get = 0;
+
+	spin_unlock_irqrestore(&info->lock,flags);
+	
+#ifdef CONFIG_HDLC
+	if (info->netcount)
+		hdlcdev_tx_done(info);
+	else
+#endif
+		bh_transmit(info);
+}
+
+#ifdef CONFIG_HDLC
+
+/**
+ * called by generic HDLC layer when protocol selected (PPP, frame relay, etc.)
+ * set encoding and frame check sequence (FCS) options
+ *
+ * dev       pointer to network device structure
+ * encoding  serial encoding setting
+ * parity    FCS setting
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_attach(struct net_device *dev, unsigned short encoding,
+			  unsigned short parity)
+{
+	MGSLPC_INFO *info = dev_to_port(dev);
+	unsigned char  new_encoding;
+	unsigned short new_crctype;
+
+	/* return error if TTY interface open */
+	if (info->count)
+		return -EBUSY;
+
+	switch (encoding)
+	{
+	case ENCODING_NRZ:        new_encoding = HDLC_ENCODING_NRZ; break;
+	case ENCODING_NRZI:       new_encoding = HDLC_ENCODING_NRZI_SPACE; break;
+	case ENCODING_FM_MARK:    new_encoding = HDLC_ENCODING_BIPHASE_MARK; break;
+	case ENCODING_FM_SPACE:   new_encoding = HDLC_ENCODING_BIPHASE_SPACE; break;
+	case ENCODING_MANCHESTER: new_encoding = HDLC_ENCODING_BIPHASE_LEVEL; break;
+	default: return -EINVAL;
+	}
+
+	switch (parity)
+	{
+	case PARITY_NONE:            new_crctype = HDLC_CRC_NONE; break;
+	case PARITY_CRC16_PR1_CCITT: new_crctype = HDLC_CRC_16_CCITT; break;
+	case PARITY_CRC32_PR1_CCITT: new_crctype = HDLC_CRC_32_CCITT; break;
+	default: return -EINVAL;
+	}
+
+	info->params.encoding = new_encoding;
+	info->params.crc_type = new_crctype;;
+
+	/* if network interface up, reprogram hardware */
+	if (info->netcount)
+		mgslpc_program_hw(info);
+
+	return 0;
+}
+
+/**
+ * called by generic HDLC layer to send frame
+ *
+ * skb  socket buffer containing HDLC frame
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	MGSLPC_INFO *info = dev_to_port(dev);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk(KERN_INFO "%s:hdlc_xmit(%s)\n",__FILE__,dev->name);
+
+	/* stop sending until this frame completes */
+	netif_stop_queue(dev);
+
+	/* copy data to device buffers */
+	memcpy(info->tx_buf, skb->data, skb->len);
+	info->tx_get = 0;
+	info->tx_put = info->tx_count = skb->len;
+
+	/* update network statistics */
+	stats->tx_packets++;
+	stats->tx_bytes += skb->len;
+
+	/* done with socket buffer, so free it */
+	dev_kfree_skb(skb);
+
+	/* save start time for transmit timeout detection */
+	dev->trans_start = jiffies;
+
+	/* start hardware transmitter if necessary */
+	spin_lock_irqsave(&info->lock,flags);
+	if (!info->tx_active)
+	 	tx_start(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	return 0;
+}
+
+/**
+ * called by network layer when interface enabled
+ * claim resources and initialize hardware
+ *
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_open(struct net_device *dev)
+{
+	MGSLPC_INFO *info = dev_to_port(dev);
+	int rc;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_open(%s)\n",__FILE__,dev->name);
+
+	/* generic HDLC layer open processing */
+	if ((rc = hdlc_open(dev)))
+		return rc;
+
+	/* arbitrate between network and tty opens */
+	spin_lock_irqsave(&info->netlock, flags);
+	if (info->count != 0 || info->netcount != 0) {
+		printk(KERN_WARNING "%s: hdlc_open returning busy\n", dev->name);
+		spin_unlock_irqrestore(&info->netlock, flags);
+		return -EBUSY;
+	}
+	info->netcount=1;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	/* claim resources and init adapter */
+	if ((rc = startup(info)) != 0) {
+		spin_lock_irqsave(&info->netlock, flags);
+		info->netcount=0;
+		spin_unlock_irqrestore(&info->netlock, flags);
+		return rc;
+	}
+
+	/* assert DTR and RTS, apply hardware settings */
+	info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+	mgslpc_program_hw(info);
+
+	/* enable network layer transmit */
+	dev->trans_start = jiffies;
+	netif_start_queue(dev);
+
+	/* inform generic HDLC layer of current DCD status */
+	spin_lock_irqsave(&info->lock, flags);
+	get_signals(info);
+	spin_unlock_irqrestore(&info->lock, flags);
+	hdlc_set_carrier(info->serial_signals & SerialSignal_DCD, dev);
+
+	return 0;
+}
+
+/**
+ * called by network layer when interface is disabled
+ * shutdown hardware and release resources
+ *
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_close(struct net_device *dev)
+{
+	MGSLPC_INFO *info = dev_to_port(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_close(%s)\n",__FILE__,dev->name);
+
+	netif_stop_queue(dev);
+
+	/* shutdown adapter and release resources */
+	shutdown(info);
+
+	hdlc_close(dev);
+
+	spin_lock_irqsave(&info->netlock, flags);
+	info->netcount=0;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	return 0;
+}
+
+/**
+ * called by network layer to process IOCTL call to network device
+ *
+ * dev  pointer to network device structure
+ * ifr  pointer to network interface request structure
+ * cmd  IOCTL command code
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	const size_t size = sizeof(sync_serial_settings);
+	sync_serial_settings new_line;
+	sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+	MGSLPC_INFO *info = dev_to_port(dev);
+	unsigned int flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_ioctl(%s)\n",__FILE__,dev->name);
+
+	/* return error if TTY interface open */
+	if (info->count)
+		return -EBUSY;
+
+	if (cmd != SIOCWANDEV)
+		return hdlc_ioctl(dev, ifr, cmd);
+
+	switch(ifr->ifr_settings.type) {
+	case IF_GET_IFACE: /* return current sync_serial_settings */
+
+		ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+
+		flags = info->params.flags & (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					      HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					      HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					      HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN);
+
+		switch (flags){
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN): new_line.clock_type = CLOCK_EXT; break;
+		case (HDLC_FLAG_RXC_BRG    | HDLC_FLAG_TXC_BRG):    new_line.clock_type = CLOCK_INT; break;
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG):    new_line.clock_type = CLOCK_TXINT; break;
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN): new_line.clock_type = CLOCK_TXFROMRX; break;
+		default: new_line.clock_type = CLOCK_DEFAULT;
+		}
+
+		new_line.clock_rate = info->params.clock_speed;
+		new_line.loopback   = info->params.loopback ? 1:0;
+
+		if (copy_to_user(line, &new_line, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_IFACE_SYNC_SERIAL: /* set sync_serial_settings */
+
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		if (copy_from_user(&new_line, line, size))
+			return -EFAULT;
+
+		switch (new_line.clock_type)
+		{
+		case CLOCK_EXT:      flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN; break;
+		case CLOCK_TXFROMRX: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN; break;
+		case CLOCK_INT:      flags = HDLC_FLAG_RXC_BRG    | HDLC_FLAG_TXC_BRG;    break;
+		case CLOCK_TXINT:    flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG;    break;
+		case CLOCK_DEFAULT:  flags = info->params.flags &
+					     (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					      HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					      HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					      HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN); break;
+		default: return -EINVAL;
+		}
+
+		if (new_line.loopback != 0 && new_line.loopback != 1)
+			return -EINVAL;
+
+		info->params.flags &= ~(HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN);
+		info->params.flags |= flags;
+
+		info->params.loopback = new_line.loopback;
+
+		if (flags & (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG))
+			info->params.clock_speed = new_line.clock_rate;
+		else
+			info->params.clock_speed = 0;
+
+		/* if network interface up, reprogram hardware */
+		if (info->netcount)
+			mgslpc_program_hw(info);
+		return 0;
+
+	default:
+		return hdlc_ioctl(dev, ifr, cmd);
+	}
+}
+
+/**
+ * called by network layer when transmit timeout is detected
+ *
+ * dev  pointer to network device structure
+ */
+static void hdlcdev_tx_timeout(struct net_device *dev)
+{
+	MGSLPC_INFO *info = dev_to_port(dev);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("hdlcdev_tx_timeout(%s)\n",dev->name);
+
+	stats->tx_errors++;
+	stats->tx_aborted_errors++;
+
+	spin_lock_irqsave(&info->lock,flags);
+	tx_stop(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	netif_wake_queue(dev);
+}
+
+/**
+ * called by device driver when transmit completes
+ * reenable network layer transmit if stopped
+ *
+ * info  pointer to device instance information
+ */
+static void hdlcdev_tx_done(MGSLPC_INFO *info)
+{
+	if (netif_queue_stopped(info->netdev))
+		netif_wake_queue(info->netdev);
+}
+
+/**
+ * called by device driver when frame received
+ * pass frame to network layer
+ *
+ * info  pointer to device instance information
+ * buf   pointer to buffer contianing frame data
+ * size  count of data bytes in buf
+ */
+static void hdlcdev_rx(MGSLPC_INFO *info, char *buf, int size)
+{
+	struct sk_buff *skb = dev_alloc_skb(size);
+	struct net_device *dev = info->netdev;
+	struct net_device_stats *stats = hdlc_stats(dev);
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("hdlcdev_rx(%s)\n",dev->name);
+
+	if (skb == NULL) {
+		printk(KERN_NOTICE "%s: can't alloc skb, dropping packet\n", dev->name);
+		stats->rx_dropped++;
+		return;
+	}
+
+	memcpy(skb_put(skb, size),buf,size);
+
+	skb->protocol = hdlc_type_trans(skb, info->netdev);
+
+	stats->rx_packets++;
+	stats->rx_bytes += size;
+
+	netif_rx(skb);
+
+	info->netdev->last_rx = jiffies;
+}
+
+/**
+ * called by device driver when adding device instance
+ * do generic HDLC initialization
+ *
+ * info  pointer to device instance information
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_init(MGSLPC_INFO *info)
+{
+	int rc;
+	struct net_device *dev;
+	hdlc_device *hdlc;
+
+	/* allocate and initialize network and HDLC layer objects */
+
+	if (!(dev = alloc_hdlcdev(info))) {
+		printk(KERN_ERR "%s:hdlc device allocation failure\n",__FILE__);
+		return -ENOMEM;
+	}
+
+	/* for network layer reporting purposes only */
+	dev->base_addr = info->io_base;
+	dev->irq       = info->irq_level;
+
+	/* network layer callbacks and settings */
+	dev->do_ioctl       = hdlcdev_ioctl;
+	dev->open           = hdlcdev_open;
+	dev->stop           = hdlcdev_close;
+	dev->tx_timeout     = hdlcdev_tx_timeout;
+	dev->watchdog_timeo = 10*HZ;
+	dev->tx_queue_len   = 50;
+
+	/* generic HDLC layer callbacks and settings */
+	hdlc         = dev_to_hdlc(dev);
+	hdlc->attach = hdlcdev_attach;
+	hdlc->xmit   = hdlcdev_xmit;
+
+	/* register objects with HDLC layer */
+	if ((rc = register_hdlc_device(dev))) {
+		printk(KERN_WARNING "%s:unable to register hdlc device\n",__FILE__);
+		free_netdev(dev);
+		return rc;
+	}
+
+	info->netdev = dev;
+	return 0;
+}
+
+/**
+ * called by device driver when removing device instance
+ * do generic HDLC cleanup
+ *
+ * info  pointer to device instance information
+ */
+static void hdlcdev_exit(MGSLPC_INFO *info)
+{
+	unregister_hdlc_device(info->netdev);
+	free_netdev(info->netdev);
+	info->netdev = NULL;
+}
+
+#endif /* CONFIG_HDLC */
+
diff --git a/drivers/char/ppdev.c b/drivers/char/ppdev.c
new file mode 100644
index 0000000..5eda075
--- /dev/null
+++ b/drivers/char/ppdev.c
@@ -0,0 +1,824 @@
+/*
+ * linux/drivers/char/ppdev.c
+ *
+ * This is the code behind /dev/parport* -- it allows a user-space
+ * application to use the parport subsystem.
+ *
+ * Copyright (C) 1998-2000, 2002 Tim Waugh <tim@cyberelk.net>
+ *
+ * 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.
+ *
+ * A /dev/parportx device node represents an arbitrary device
+ * on port 'x'.  The following operations are possible:
+ *
+ * open		do nothing, set up default IEEE 1284 protocol to be COMPAT
+ * close	release port and unregister device (if necessary)
+ * ioctl
+ *   EXCL	register device exclusively (may fail)
+ *   CLAIM	(register device first time) parport_claim_or_block
+ *   RELEASE	parport_release
+ *   SETMODE	set the IEEE 1284 protocol to use for read/write
+ *   SETPHASE	set the IEEE 1284 phase of a particular mode.  Not to be
+ *              confused with ioctl(fd, SETPHASER, &stun). ;-)
+ *   DATADIR	data_forward / data_reverse
+ *   WDATA	write_data
+ *   RDATA	read_data
+ *   WCONTROL	write_control
+ *   RCONTROL	read_control
+ *   FCONTROL	frob_control
+ *   RSTATUS	read_status
+ *   NEGOT	parport_negotiate
+ *   YIELD	parport_yield_blocking
+ *   WCTLONIRQ	on interrupt, set control lines
+ *   CLRIRQ	clear (and return) interrupt count
+ *   SETTIME	sets device timeout (struct timeval)
+ *   GETTIME	gets device timeout (struct timeval)
+ *   GETMODES	gets hardware supported modes (unsigned int)
+ *   GETMODE	gets the current IEEE1284 mode
+ *   GETPHASE   gets the current IEEE1284 phase
+ *   GETFLAGS   gets current (user-visible) flags
+ *   SETFLAGS   sets current (user-visible) flags
+ * read/write	read or write in current IEEE 1284 protocol
+ * select	wait for interrupt (in readfds)
+ *
+ * Changes:
+ * Added SETTIME/GETTIME ioctl, Fred Barnes, 1999.
+ *
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> 2000/08/25
+ * - On error, copy_from_user and copy_to_user do not return -EFAULT,
+ *   They return the positive number of bytes *not* copied due to address
+ *   space errors.
+ *
+ * Added GETMODES/GETMODE/GETPHASE ioctls, Fred Barnes <frmb2@ukc.ac.uk>, 03/01/2001.
+ * Added GETFLAGS/SETFLAGS ioctls, Fred Barnes, 04/2001
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/device.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/ioctl.h>
+#include <linux/parport.h>
+#include <linux/ctype.h>
+#include <linux/poll.h>
+#include <asm/uaccess.h>
+#include <linux/ppdev.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+
+#define PP_VERSION "ppdev: user-space parallel port driver"
+#define CHRDEV "ppdev"
+
+struct pp_struct {
+	struct pardevice * pdev;
+	wait_queue_head_t irq_wait;
+	atomic_t irqc;
+	unsigned int flags;
+	int irqresponse;
+	unsigned char irqctl;
+	struct ieee1284_info state;
+	struct ieee1284_info saved_state;
+	long default_inactivity;
+};
+
+/* pp_struct.flags bitfields */
+#define PP_CLAIMED    (1<<0)
+#define PP_EXCL       (1<<1)
+
+/* Other constants */
+#define PP_INTERRUPT_TIMEOUT (10 * HZ) /* 10s */
+#define PP_BUFFER_SIZE 1024
+#define PARDEVICE_MAX 8
+
+/* ROUND_UP macro from fs/select.c */
+#define ROUND_UP(x,y) (((x)+(y)-1)/(y))
+
+static inline void pp_enable_irq (struct pp_struct *pp)
+{
+	struct parport *port = pp->pdev->port;
+	port->ops->enable_irq (port);
+}
+
+static ssize_t pp_read (struct file * file, char __user * buf, size_t count,
+			loff_t * ppos)
+{
+	unsigned int minor = iminor(file->f_dentry->d_inode);
+	struct pp_struct *pp = file->private_data;
+	char * kbuffer;
+	ssize_t bytes_read = 0;
+	struct parport *pport;
+	int mode;
+
+	if (!(pp->flags & PP_CLAIMED)) {
+		/* Don't have the port claimed */
+		printk (KERN_DEBUG CHRDEV "%x: claim the port first\n",
+			minor);
+		return -EINVAL;
+	}
+
+	/* Trivial case. */
+	if (count == 0)
+		return 0;
+
+	kbuffer = kmalloc(min_t(size_t, count, PP_BUFFER_SIZE), GFP_KERNEL);
+	if (!kbuffer) {
+		return -ENOMEM;
+	}
+	pport = pp->pdev->port;
+	mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);
+
+	parport_set_timeout (pp->pdev,
+			     (file->f_flags & O_NONBLOCK) ?
+			     PARPORT_INACTIVITY_O_NONBLOCK :
+			     pp->default_inactivity);
+
+	while (bytes_read == 0) {
+		ssize_t need = min_t(unsigned long, count, PP_BUFFER_SIZE);
+
+		if (mode == IEEE1284_MODE_EPP) {
+			/* various specials for EPP mode */
+			int flags = 0;
+			size_t (*fn)(struct parport *, void *, size_t, int);
+
+			if (pp->flags & PP_W91284PIC) {
+				flags |= PARPORT_W91284PIC;
+			}
+			if (pp->flags & PP_FASTREAD) {
+				flags |= PARPORT_EPP_FAST;
+			}
+			if (pport->ieee1284.mode & IEEE1284_ADDR) {
+				fn = pport->ops->epp_read_addr;
+			} else {
+				fn = pport->ops->epp_read_data;
+			}
+			bytes_read = (*fn)(pport, kbuffer, need, flags);
+		} else {
+			bytes_read = parport_read (pport, kbuffer, need);
+		}
+
+		if (bytes_read != 0)
+			break;
+
+		if (file->f_flags & O_NONBLOCK) {
+			bytes_read = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending (current)) {
+			bytes_read = -ERESTARTSYS;
+			break;
+		}
+
+		cond_resched();
+	}
+
+	parport_set_timeout (pp->pdev, pp->default_inactivity);
+
+	if (bytes_read > 0 && copy_to_user (buf, kbuffer, bytes_read))
+		bytes_read = -EFAULT;
+
+	kfree (kbuffer);
+	pp_enable_irq (pp);
+	return bytes_read;
+}
+
+static ssize_t pp_write (struct file * file, const char __user * buf,
+			 size_t count, loff_t * ppos)
+{
+	unsigned int minor = iminor(file->f_dentry->d_inode);
+	struct pp_struct *pp = file->private_data;
+	char * kbuffer;
+	ssize_t bytes_written = 0;
+	ssize_t wrote;
+	int mode;
+	struct parport *pport;
+
+	if (!(pp->flags & PP_CLAIMED)) {
+		/* Don't have the port claimed */
+		printk (KERN_DEBUG CHRDEV "%x: claim the port first\n",
+			minor);
+		return -EINVAL;
+	}
+
+	kbuffer = kmalloc(min_t(size_t, count, PP_BUFFER_SIZE), GFP_KERNEL);
+	if (!kbuffer) {
+		return -ENOMEM;
+	}
+	pport = pp->pdev->port;
+	mode = pport->ieee1284.mode & ~(IEEE1284_DEVICEID | IEEE1284_ADDR);
+
+	parport_set_timeout (pp->pdev,
+			     (file->f_flags & O_NONBLOCK) ?
+			     PARPORT_INACTIVITY_O_NONBLOCK :
+			     pp->default_inactivity);
+
+	while (bytes_written < count) {
+		ssize_t n = min_t(unsigned long, count - bytes_written, PP_BUFFER_SIZE);
+
+		if (copy_from_user (kbuffer, buf + bytes_written, n)) {
+			bytes_written = -EFAULT;
+			break;
+		}
+
+		if ((pp->flags & PP_FASTWRITE) && (mode == IEEE1284_MODE_EPP)) {
+			/* do a fast EPP write */
+			if (pport->ieee1284.mode & IEEE1284_ADDR) {
+				wrote = pport->ops->epp_write_addr (pport,
+					kbuffer, n, PARPORT_EPP_FAST);
+			} else {
+				wrote = pport->ops->epp_write_data (pport,
+					kbuffer, n, PARPORT_EPP_FAST);
+			}
+		} else {
+			wrote = parport_write (pp->pdev->port, kbuffer, n);
+		}
+
+		if (wrote <= 0) {
+			if (!bytes_written) {
+				bytes_written = wrote;
+			}
+			break;
+		}
+
+		bytes_written += wrote;
+
+		if (file->f_flags & O_NONBLOCK) {
+			if (!bytes_written)
+				bytes_written = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending (current)) {
+			if (!bytes_written) {
+				bytes_written = -EINTR;
+			}
+			break;
+		}
+
+		cond_resched();
+	}
+
+	parport_set_timeout (pp->pdev, pp->default_inactivity);
+
+	kfree (kbuffer);
+	pp_enable_irq (pp);
+	return bytes_written;
+}
+
+static void pp_irq (int irq, void * private, struct pt_regs * unused)
+{
+	struct pp_struct * pp = (struct pp_struct *) private;
+
+	if (pp->irqresponse) {
+		parport_write_control (pp->pdev->port, pp->irqctl);
+		pp->irqresponse = 0;
+	}
+
+	atomic_inc (&pp->irqc);
+	wake_up_interruptible (&pp->irq_wait);
+}
+
+static int register_device (int minor, struct pp_struct *pp)
+{
+	struct parport *port;
+	struct pardevice * pdev = NULL;
+	char *name;
+	int fl;
+
+	name = kmalloc (strlen (CHRDEV) + 3, GFP_KERNEL);
+	if (name == NULL)
+		return -ENOMEM;
+
+	sprintf (name, CHRDEV "%x", minor);
+
+	port = parport_find_number (minor);
+	if (!port) {
+		printk (KERN_WARNING "%s: no associated port!\n", name);
+		kfree (name);
+		return -ENXIO;
+	}
+
+	fl = (pp->flags & PP_EXCL) ? PARPORT_FLAG_EXCL : 0;
+	pdev = parport_register_device (port, name, NULL,
+					NULL, pp_irq, fl, pp);
+	parport_put_port (port);
+
+	if (!pdev) {
+		printk (KERN_WARNING "%s: failed to register device!\n", name);
+		kfree (name);
+		return -ENXIO;
+	}
+
+	pp->pdev = pdev;
+	printk (KERN_DEBUG "%s: registered pardevice\n", name);
+	return 0;
+}
+
+static enum ieee1284_phase init_phase (int mode)
+{
+	switch (mode & ~(IEEE1284_DEVICEID
+			 | IEEE1284_ADDR)) {
+	case IEEE1284_MODE_NIBBLE:
+	case IEEE1284_MODE_BYTE:
+		return IEEE1284_PH_REV_IDLE;
+	}
+	return IEEE1284_PH_FWD_IDLE;
+}
+
+static int pp_ioctl(struct inode *inode, struct file *file,
+		    unsigned int cmd, unsigned long arg)
+{
+	unsigned int minor = iminor(inode);
+	struct pp_struct *pp = file->private_data;
+	struct parport * port;
+	void __user *argp = (void __user *)arg;
+
+	/* First handle the cases that don't take arguments. */
+	switch (cmd) {
+	case PPCLAIM:
+	    {
+		struct ieee1284_info *info;
+		int ret;
+
+		if (pp->flags & PP_CLAIMED) {
+			printk (KERN_DEBUG CHRDEV
+				"%x: you've already got it!\n", minor);
+			return -EINVAL;
+		}
+
+		/* Deferred device registration. */
+		if (!pp->pdev) {
+			int err = register_device (minor, pp);
+			if (err) {
+				return err;
+			}
+		}
+
+		ret = parport_claim_or_block (pp->pdev);
+		if (ret < 0)
+			return ret;
+
+		pp->flags |= PP_CLAIMED;
+
+		/* For interrupt-reporting to work, we need to be
+		 * informed of each interrupt. */
+		pp_enable_irq (pp);
+
+		/* We may need to fix up the state machine. */
+		info = &pp->pdev->port->ieee1284;
+		pp->saved_state.mode = info->mode;
+		pp->saved_state.phase = info->phase;
+		info->mode = pp->state.mode;
+		info->phase = pp->state.phase;
+		pp->default_inactivity = parport_set_timeout (pp->pdev, 0);
+		parport_set_timeout (pp->pdev, pp->default_inactivity);
+
+		return 0;
+	    }
+	case PPEXCL:
+		if (pp->pdev) {
+			printk (KERN_DEBUG CHRDEV "%x: too late for PPEXCL; "
+				"already registered\n", minor);
+			if (pp->flags & PP_EXCL)
+				/* But it's not really an error. */
+				return 0;
+			/* There's no chance of making the driver happy. */
+			return -EINVAL;
+		}
+
+		/* Just remember to register the device exclusively
+		 * when we finally do the registration. */
+		pp->flags |= PP_EXCL;
+		return 0;
+	case PPSETMODE:
+	    {
+		int mode;
+		if (copy_from_user (&mode, argp, sizeof (mode)))
+			return -EFAULT;
+		/* FIXME: validate mode */
+		pp->state.mode = mode;
+		pp->state.phase = init_phase (mode);
+
+		if (pp->flags & PP_CLAIMED) {
+			pp->pdev->port->ieee1284.mode = mode;
+			pp->pdev->port->ieee1284.phase = pp->state.phase;
+		}
+
+		return 0;
+	    }
+	case PPGETMODE:
+	    {
+		int mode;
+
+		if (pp->flags & PP_CLAIMED) {
+			mode = pp->pdev->port->ieee1284.mode;
+		} else {
+			mode = pp->state.mode;
+		}
+		if (copy_to_user (argp, &mode, sizeof (mode))) {
+			return -EFAULT;
+		}
+		return 0;
+	    }
+	case PPSETPHASE:
+	    {
+		int phase;
+		if (copy_from_user (&phase, argp, sizeof (phase))) {
+			return -EFAULT;
+		}
+		/* FIXME: validate phase */
+		pp->state.phase = phase;
+
+		if (pp->flags & PP_CLAIMED) {
+			pp->pdev->port->ieee1284.phase = phase;
+		}
+
+		return 0;
+	    }
+	case PPGETPHASE:
+	    {
+		int phase;
+
+		if (pp->flags & PP_CLAIMED) {
+			phase = pp->pdev->port->ieee1284.phase;
+		} else {
+			phase = pp->state.phase;
+		}
+		if (copy_to_user (argp, &phase, sizeof (phase))) {
+			return -EFAULT;
+		}
+		return 0;
+	    }
+	case PPGETMODES:
+	    {
+		unsigned int modes;
+
+		port = parport_find_number (minor);
+		if (!port)
+			return -ENODEV;
+
+		modes = port->modes;
+		if (copy_to_user (argp, &modes, sizeof (modes))) {
+			return -EFAULT;
+		}
+		return 0;
+	    }
+	case PPSETFLAGS:
+	    {
+		int uflags;
+
+		if (copy_from_user (&uflags, argp, sizeof (uflags))) {
+			return -EFAULT;
+		}
+		pp->flags &= ~PP_FLAGMASK;
+		pp->flags |= (uflags & PP_FLAGMASK);
+		return 0;
+	    }
+	case PPGETFLAGS:
+	    {
+		int uflags;
+
+		uflags = pp->flags & PP_FLAGMASK;
+		if (copy_to_user (argp, &uflags, sizeof (uflags))) {
+			return -EFAULT;
+		}
+		return 0;
+	    }
+	}	/* end switch() */
+
+	/* Everything else requires the port to be claimed, so check
+	 * that now. */
+	if ((pp->flags & PP_CLAIMED) == 0) {
+		printk (KERN_DEBUG CHRDEV "%x: claim the port first\n",
+			minor);
+		return -EINVAL;
+	}
+
+	port = pp->pdev->port;
+	switch (cmd) {
+		struct ieee1284_info *info;
+		unsigned char reg;
+		unsigned char mask;
+		int mode;
+		int ret;
+		struct timeval par_timeout;
+		long to_jiffies;
+
+	case PPRSTATUS:
+		reg = parport_read_status (port);
+		if (copy_to_user (argp, &reg, sizeof (reg)))
+			return -EFAULT;
+		return 0;
+	case PPRDATA:
+		reg = parport_read_data (port);
+		if (copy_to_user (argp, &reg, sizeof (reg)))
+			return -EFAULT;
+		return 0;
+	case PPRCONTROL:
+		reg = parport_read_control (port);
+		if (copy_to_user (argp, &reg, sizeof (reg)))
+			return -EFAULT;
+		return 0;
+	case PPYIELD:
+		parport_yield_blocking (pp->pdev);
+		return 0;
+
+	case PPRELEASE:
+		/* Save the state machine's state. */
+		info = &pp->pdev->port->ieee1284;
+		pp->state.mode = info->mode;
+		pp->state.phase = info->phase;
+		info->mode = pp->saved_state.mode;
+		info->phase = pp->saved_state.phase;
+		parport_release (pp->pdev);
+		pp->flags &= ~PP_CLAIMED;
+		return 0;
+
+	case PPWCONTROL:
+		if (copy_from_user (&reg, argp, sizeof (reg)))
+			return -EFAULT;
+		parport_write_control (port, reg);
+		return 0;
+
+	case PPWDATA:
+		if (copy_from_user (&reg, argp, sizeof (reg)))
+			return -EFAULT;
+		parport_write_data (port, reg);
+		return 0;
+
+	case PPFCONTROL:
+		if (copy_from_user (&mask, argp,
+				    sizeof (mask)))
+			return -EFAULT;
+		if (copy_from_user (&reg, 1 + (unsigned char __user *) arg,
+				    sizeof (reg)))
+			return -EFAULT;
+		parport_frob_control (port, mask, reg);
+		return 0;
+
+	case PPDATADIR:
+		if (copy_from_user (&mode, argp, sizeof (mode)))
+			return -EFAULT;
+		if (mode)
+			port->ops->data_reverse (port);
+		else
+			port->ops->data_forward (port);
+		return 0;
+
+	case PPNEGOT:
+		if (copy_from_user (&mode, argp, sizeof (mode)))
+			return -EFAULT;
+		switch ((ret = parport_negotiate (port, mode))) {
+		case 0: break;
+		case -1: /* handshake failed, peripheral not IEEE 1284 */
+			ret = -EIO;
+			break;
+		case 1:  /* handshake succeeded, peripheral rejected mode */
+			ret = -ENXIO;
+			break;
+		}
+		pp_enable_irq (pp);
+		return ret;
+
+	case PPWCTLONIRQ:
+		if (copy_from_user (&reg, argp, sizeof (reg)))
+			return -EFAULT;
+
+		/* Remember what to set the control lines to, for next
+		 * time we get an interrupt. */
+		pp->irqctl = reg;
+		pp->irqresponse = 1;
+		return 0;
+
+	case PPCLRIRQ:
+		ret = atomic_read (&pp->irqc);
+		if (copy_to_user (argp, &ret, sizeof (ret)))
+			return -EFAULT;
+		atomic_sub (ret, &pp->irqc);
+		return 0;
+
+	case PPSETTIME:
+		if (copy_from_user (&par_timeout, argp, sizeof(struct timeval))) {
+			return -EFAULT;
+		}
+		/* Convert to jiffies, place in pp->pdev->timeout */
+		if ((par_timeout.tv_sec < 0) || (par_timeout.tv_usec < 0)) {
+			return -EINVAL;
+		}
+		to_jiffies = ROUND_UP(par_timeout.tv_usec, 1000000/HZ);
+		to_jiffies += par_timeout.tv_sec * (long)HZ;
+		if (to_jiffies <= 0) {
+			return -EINVAL;
+		}
+		pp->pdev->timeout = to_jiffies;
+		return 0;
+
+	case PPGETTIME:
+		to_jiffies = pp->pdev->timeout;
+		par_timeout.tv_sec = to_jiffies / HZ;
+		par_timeout.tv_usec = (to_jiffies % (long)HZ) * (1000000/HZ);
+		if (copy_to_user (argp, &par_timeout, sizeof(struct timeval)))
+			return -EFAULT;
+		return 0;
+
+	default:
+		printk (KERN_DEBUG CHRDEV "%x: What? (cmd=0x%x)\n", minor,
+			cmd);
+		return -EINVAL;
+	}
+
+	/* Keep the compiler happy */
+	return 0;
+}
+
+static int pp_open (struct inode * inode, struct file * file)
+{
+	unsigned int minor = iminor(inode);
+	struct pp_struct *pp;
+
+	if (minor >= PARPORT_MAX)
+		return -ENXIO;
+
+	pp = kmalloc (sizeof (struct pp_struct), GFP_KERNEL);
+	if (!pp)
+		return -ENOMEM;
+
+	pp->state.mode = IEEE1284_MODE_COMPAT;
+	pp->state.phase = init_phase (pp->state.mode);
+	pp->flags = 0;
+	pp->irqresponse = 0;
+	atomic_set (&pp->irqc, 0);
+	init_waitqueue_head (&pp->irq_wait);
+
+	/* Defer the actual device registration until the first claim.
+	 * That way, we know whether or not the driver wants to have
+	 * exclusive access to the port (PPEXCL).
+	 */
+	pp->pdev = NULL;
+	file->private_data = pp;
+
+	return 0;
+}
+
+static int pp_release (struct inode * inode, struct file * file)
+{
+	unsigned int minor = iminor(inode);
+	struct pp_struct *pp = file->private_data;
+	int compat_negot;
+
+	compat_negot = 0;
+	if (!(pp->flags & PP_CLAIMED) && pp->pdev &&
+	    (pp->state.mode != IEEE1284_MODE_COMPAT)) {
+	    	struct ieee1284_info *info;
+
+		/* parport released, but not in compatibility mode */
+		parport_claim_or_block (pp->pdev);
+		pp->flags |= PP_CLAIMED;
+		info = &pp->pdev->port->ieee1284;
+		pp->saved_state.mode = info->mode;
+		pp->saved_state.phase = info->phase;
+		info->mode = pp->state.mode;
+		info->phase = pp->state.phase;
+		compat_negot = 1;
+	} else if ((pp->flags & PP_CLAIMED) && pp->pdev &&
+	    (pp->pdev->port->ieee1284.mode != IEEE1284_MODE_COMPAT)) {
+		compat_negot = 2;
+	}
+	if (compat_negot) {
+		parport_negotiate (pp->pdev->port, IEEE1284_MODE_COMPAT);
+		printk (KERN_DEBUG CHRDEV
+			"%x: negotiated back to compatibility mode because "
+			"user-space forgot\n", minor);
+	}
+
+	if (pp->flags & PP_CLAIMED) {
+		struct ieee1284_info *info;
+
+		info = &pp->pdev->port->ieee1284;
+		pp->state.mode = info->mode;
+		pp->state.phase = info->phase;
+		info->mode = pp->saved_state.mode;
+		info->phase = pp->saved_state.phase;
+		parport_release (pp->pdev);
+		if (compat_negot != 1) {
+			printk (KERN_DEBUG CHRDEV "%x: released pardevice "
+				"because user-space forgot\n", minor);
+		}
+	}
+
+	if (pp->pdev) {
+		const char *name = pp->pdev->name;
+		parport_unregister_device (pp->pdev);
+		kfree (name);
+		pp->pdev = NULL;
+		printk (KERN_DEBUG CHRDEV "%x: unregistered pardevice\n",
+			minor);
+	}
+
+	kfree (pp);
+
+	return 0;
+}
+
+/* No kernel lock held - fine */
+static unsigned int pp_poll (struct file * file, poll_table * wait)
+{
+	struct pp_struct *pp = file->private_data;
+	unsigned int mask = 0;
+
+	poll_wait (file, &pp->irq_wait, wait);
+	if (atomic_read (&pp->irqc))
+		mask |= POLLIN | POLLRDNORM;
+
+	return mask;
+}
+
+static struct class_simple *ppdev_class;
+
+static struct file_operations pp_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= pp_read,
+	.write		= pp_write,
+	.poll		= pp_poll,
+	.ioctl		= pp_ioctl,
+	.open		= pp_open,
+	.release	= pp_release,
+};
+
+static void pp_attach(struct parport *port)
+{
+	class_simple_device_add(ppdev_class, MKDEV(PP_MAJOR, port->number),
+			NULL, "parport%d", port->number);
+}
+
+static void pp_detach(struct parport *port)
+{
+	class_simple_device_remove(MKDEV(PP_MAJOR, port->number));
+}
+
+static struct parport_driver pp_driver = {
+	.name		= CHRDEV,
+	.attach		= pp_attach,
+	.detach		= pp_detach,
+};
+
+static int __init ppdev_init (void)
+{
+	int i, err = 0;
+
+	if (register_chrdev (PP_MAJOR, CHRDEV, &pp_fops)) {
+		printk (KERN_WARNING CHRDEV ": unable to get major %d\n",
+			PP_MAJOR);
+		return -EIO;
+	}
+	ppdev_class = class_simple_create(THIS_MODULE, CHRDEV);
+	if (IS_ERR(ppdev_class)) {
+		err = PTR_ERR(ppdev_class);
+		goto out_chrdev;
+	}
+	devfs_mk_dir("parports");
+	for (i = 0; i < PARPORT_MAX; i++) {
+		devfs_mk_cdev(MKDEV(PP_MAJOR, i),
+				S_IFCHR | S_IRUGO | S_IWUGO, "parports/%d", i);
+	}
+	if (parport_register_driver(&pp_driver)) {
+		printk (KERN_WARNING CHRDEV ": unable to register with parport\n");
+		goto out_class;
+	}
+
+	printk (KERN_INFO PP_VERSION "\n");
+	goto out;
+
+out_class:
+	for (i = 0; i < PARPORT_MAX; i++)
+		devfs_remove("parports/%d", i);
+	devfs_remove("parports");
+	class_simple_destroy(ppdev_class);
+out_chrdev:
+	unregister_chrdev(PP_MAJOR, CHRDEV);
+out:
+	return err;
+}
+
+static void __exit ppdev_cleanup (void)
+{
+	int i;
+	/* Clean up all parport stuff */
+	for (i = 0; i < PARPORT_MAX; i++)
+		devfs_remove("parports/%d", i);
+	parport_unregister_driver(&pp_driver);
+	devfs_remove("parports");
+	class_simple_destroy(ppdev_class);
+	unregister_chrdev (PP_MAJOR, CHRDEV);
+}
+
+module_init(ppdev_init);
+module_exit(ppdev_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_CHARDEV_MAJOR(PP_MAJOR);
diff --git a/drivers/char/pty.c b/drivers/char/pty.c
new file mode 100644
index 0000000..da32889
--- /dev/null
+++ b/drivers/char/pty.c
@@ -0,0 +1,412 @@
+/*
+ *  linux/drivers/char/pty.c
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ *
+ *  Added support for a Unix98-style ptmx device.
+ *    -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998
+ *  Added TTY_DO_WRITE_WAKEUP to enable n_tty to send POLL_OUT to
+ *      waiting writers -- Sapan Bhatia <sapan@corewars.org>
+ *
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>	/* For EXPORT_SYMBOL */
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/sysctl.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/bitops.h>
+#include <linux/devpts_fs.h>
+
+/* These are global because they are accessed in tty_io.c */
+#ifdef CONFIG_UNIX98_PTYS
+struct tty_driver *ptm_driver;
+static struct tty_driver *pts_driver;
+#endif
+
+static void pty_close(struct tty_struct * tty, struct file * filp)
+{
+	if (!tty)
+		return;
+	if (tty->driver->subtype == PTY_TYPE_MASTER) {
+		if (tty->count > 1)
+			printk("master pty_close: count = %d!!\n", tty->count);
+	} else {
+		if (tty->count > 2)
+			return;
+	}
+	wake_up_interruptible(&tty->read_wait);
+	wake_up_interruptible(&tty->write_wait);
+	tty->packet = 0;
+	if (!tty->link)
+		return;
+	tty->link->packet = 0;
+	set_bit(TTY_OTHER_CLOSED, &tty->link->flags);
+	wake_up_interruptible(&tty->link->read_wait);
+	wake_up_interruptible(&tty->link->write_wait);
+	if (tty->driver->subtype == PTY_TYPE_MASTER) {
+		set_bit(TTY_OTHER_CLOSED, &tty->flags);
+#ifdef CONFIG_UNIX98_PTYS
+		if (tty->driver == ptm_driver)
+			devpts_pty_kill(tty->index);
+#endif
+		tty_vhangup(tty->link);
+	}
+}
+
+/*
+ * The unthrottle routine is called by the line discipline to signal
+ * that it can receive more characters.  For PTY's, the TTY_THROTTLED
+ * flag is always set, to force the line discipline to always call the
+ * unthrottle routine when there are fewer than TTY_THRESHOLD_UNTHROTTLE 
+ * characters in the queue.  This is necessary since each time this
+ * happens, we need to wake up any sleeping processes that could be
+ * (1) trying to send data to the pty, or (2) waiting in wait_until_sent()
+ * for the pty buffer to be drained.
+ */
+static void pty_unthrottle(struct tty_struct * tty)
+{
+	struct tty_struct *o_tty = tty->link;
+
+	if (!o_tty)
+		return;
+
+	tty_wakeup(o_tty);
+	set_bit(TTY_THROTTLED, &tty->flags);
+}
+
+/*
+ * WSH 05/24/97: modified to 
+ *   (1) use space in tty->flip instead of a shared temp buffer
+ *	 The flip buffers aren't being used for a pty, so there's lots
+ *	 of space available.  The buffer is protected by a per-pty
+ *	 semaphore that should almost never come under contention.
+ *   (2) avoid redundant copying for cases where count >> receive_room
+ * N.B. Calls from user space may now return an error code instead of
+ * a count.
+ *
+ * FIXME: Our pty_write method is called with our ldisc lock held but
+ * not our partners. We can't just take the other one blindly without
+ * risking deadlocks.  There is also the small matter of TTY_DONT_FLIP
+ */
+static int pty_write(struct tty_struct * tty, const unsigned char *buf, int count)
+{
+	struct tty_struct *to = tty->link;
+	int	c;
+
+	if (!to || tty->stopped)
+		return 0;
+
+	c = to->ldisc.receive_room(to);
+	if (c > count)
+		c = count;
+	to->ldisc.receive_buf(to, buf, NULL, c);
+	
+	return c;
+}
+
+static int pty_write_room(struct tty_struct *tty)
+{
+	struct tty_struct *to = tty->link;
+
+	if (!to || tty->stopped)
+		return 0;
+
+	return to->ldisc.receive_room(to);
+}
+
+/*
+ *	WSH 05/24/97:  Modified for asymmetric MASTER/SLAVE behavior
+ *	The chars_in_buffer() value is used by the ldisc select() function 
+ *	to hold off writing when chars_in_buffer > WAKEUP_CHARS (== 256).
+ *	The pty driver chars_in_buffer() Master/Slave must behave differently:
+ *
+ *      The Master side needs to allow typed-ahead commands to accumulate
+ *      while being canonicalized, so we report "our buffer" as empty until
+ *	some threshold is reached, and then report the count. (Any count >
+ *	WAKEUP_CHARS is regarded by select() as "full".)  To avoid deadlock 
+ *	the count returned must be 0 if no canonical data is available to be 
+ *	read. (The N_TTY ldisc.chars_in_buffer now knows this.)
+ *  
+ *	The Slave side passes all characters in raw mode to the Master side's
+ *	buffer where they can be read immediately, so in this case we can
+ *	return the true count in the buffer.
+ */
+static int pty_chars_in_buffer(struct tty_struct *tty)
+{
+	struct tty_struct *to = tty->link;
+	ssize_t (*chars_in_buffer)(struct tty_struct *);
+	int count;
+
+	/* We should get the line discipline lock for "tty->link" */
+	if (!to || !(chars_in_buffer = to->ldisc.chars_in_buffer))
+		return 0;
+
+	/* The ldisc must report 0 if no characters available to be read */
+	count = chars_in_buffer(to);
+
+	if (tty->driver->subtype == PTY_TYPE_SLAVE) return count;
+
+	/* Master side driver ... if the other side's read buffer is less than 
+	 * half full, return 0 to allow writers to proceed; otherwise return
+	 * the count.  This leaves a comfortable margin to avoid overflow, 
+	 * and still allows half a buffer's worth of typed-ahead commands.
+	 */
+	return ((count < N_TTY_BUF_SIZE/2) ? 0 : count);
+}
+
+/* Set the lock flag on a pty */
+static int pty_set_lock(struct tty_struct *tty, int __user * arg)
+{
+	int val;
+	if (get_user(val,arg))
+		return -EFAULT;
+	if (val)
+		set_bit(TTY_PTY_LOCK, &tty->flags);
+	else
+		clear_bit(TTY_PTY_LOCK, &tty->flags);
+	return 0;
+}
+
+static void pty_flush_buffer(struct tty_struct *tty)
+{
+	struct tty_struct *to = tty->link;
+	
+	if (!to)
+		return;
+	
+	if (to->ldisc.flush_buffer)
+		to->ldisc.flush_buffer(to);
+	
+	if (to->packet) {
+		tty->ctrl_status |= TIOCPKT_FLUSHWRITE;
+		wake_up_interruptible(&to->read_wait);
+	}
+}
+
+static int pty_open(struct tty_struct *tty, struct file * filp)
+{
+	int	retval = -ENODEV;
+
+	if (!tty || !tty->link)
+		goto out;
+
+	retval = -EIO;
+	if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
+		goto out;
+	if (test_bit(TTY_PTY_LOCK, &tty->link->flags))
+		goto out;
+	if (tty->link->count != 1)
+		goto out;
+
+	clear_bit(TTY_OTHER_CLOSED, &tty->link->flags);
+	set_bit(TTY_THROTTLED, &tty->flags);
+	set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+	retval = 0;
+out:
+	return retval;
+}
+
+static void pty_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+        tty->termios->c_cflag &= ~(CSIZE | PARENB);
+        tty->termios->c_cflag |= (CS8 | CREAD);
+}
+
+static struct tty_operations pty_ops = {
+	.open = pty_open,
+	.close = pty_close,
+	.write = pty_write,
+	.write_room = pty_write_room,
+	.flush_buffer = pty_flush_buffer,
+	.chars_in_buffer = pty_chars_in_buffer,
+	.unthrottle = pty_unthrottle,
+	.set_termios = pty_set_termios,
+};
+
+/* Traditional BSD devices */
+#ifdef CONFIG_LEGACY_PTYS
+static struct tty_driver *pty_driver, *pty_slave_driver;
+
+static int pty_bsd_ioctl(struct tty_struct *tty, struct file *file,
+			 unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
+		return pty_set_lock(tty, (int __user *) arg);
+	}
+	return -ENOIOCTLCMD;
+}
+
+static void __init legacy_pty_init(void)
+{
+
+	pty_driver = alloc_tty_driver(NR_PTYS);
+	if (!pty_driver)
+		panic("Couldn't allocate pty driver");
+
+	pty_slave_driver = alloc_tty_driver(NR_PTYS);
+	if (!pty_slave_driver)
+		panic("Couldn't allocate pty slave driver");
+
+	pty_driver->owner = THIS_MODULE;
+	pty_driver->driver_name = "pty_master";
+	pty_driver->name = "pty";
+	pty_driver->devfs_name = "pty/m";
+	pty_driver->major = PTY_MASTER_MAJOR;
+	pty_driver->minor_start = 0;
+	pty_driver->type = TTY_DRIVER_TYPE_PTY;
+	pty_driver->subtype = PTY_TYPE_MASTER;
+	pty_driver->init_termios = tty_std_termios;
+	pty_driver->init_termios.c_iflag = 0;
+	pty_driver->init_termios.c_oflag = 0;
+	pty_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+	pty_driver->init_termios.c_lflag = 0;
+	pty_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW;
+	pty_driver->other = pty_slave_driver;
+	tty_set_operations(pty_driver, &pty_ops);
+	pty_driver->ioctl = pty_bsd_ioctl;
+
+	pty_slave_driver->owner = THIS_MODULE;
+	pty_slave_driver->driver_name = "pty_slave";
+	pty_slave_driver->name = "ttyp";
+	pty_slave_driver->devfs_name = "pty/s";
+	pty_slave_driver->major = PTY_SLAVE_MAJOR;
+	pty_slave_driver->minor_start = 0;
+	pty_slave_driver->type = TTY_DRIVER_TYPE_PTY;
+	pty_slave_driver->subtype = PTY_TYPE_SLAVE;
+	pty_slave_driver->init_termios = tty_std_termios;
+	pty_slave_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+	pty_slave_driver->flags = TTY_DRIVER_RESET_TERMIOS |
+					TTY_DRIVER_REAL_RAW;
+	pty_slave_driver->other = pty_driver;
+	tty_set_operations(pty_slave_driver, &pty_ops);
+
+	if (tty_register_driver(pty_driver))
+		panic("Couldn't register pty driver");
+	if (tty_register_driver(pty_slave_driver))
+		panic("Couldn't register pty slave driver");
+}
+#else
+static inline void legacy_pty_init(void) { }
+#endif
+
+/* Unix98 devices */
+#ifdef CONFIG_UNIX98_PTYS
+/*
+ * sysctl support for setting limits on the number of Unix98 ptys allocated.
+ * Otherwise one can eat up all kernel memory by opening /dev/ptmx repeatedly.
+ */
+int pty_limit = NR_UNIX98_PTY_DEFAULT;
+static int pty_limit_min = 0;
+static int pty_limit_max = NR_UNIX98_PTY_MAX;
+
+ctl_table pty_table[] = {
+	{
+		.ctl_name	= PTY_MAX,
+		.procname	= "max",
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.data		= &pty_limit,
+		.proc_handler	= &proc_dointvec_minmax,
+		.strategy	= &sysctl_intvec,
+		.extra1		= &pty_limit_min,
+		.extra2		= &pty_limit_max,
+	}, {
+		.ctl_name	= PTY_NR,
+		.procname	= "nr",
+		.maxlen		= sizeof(int),
+		.mode		= 0444,
+		.proc_handler	= &proc_dointvec,
+	}, {
+		.ctl_name	= 0
+	}
+};
+
+static int pty_unix98_ioctl(struct tty_struct *tty, struct file *file,
+			    unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case TIOCSPTLCK: /* Set PT Lock (disallow slave open) */
+		return pty_set_lock(tty, (int __user *)arg);
+	case TIOCGPTN: /* Get PT Number */
+		return put_user(tty->index, (unsigned int __user *)arg);
+	}
+
+	return -ENOIOCTLCMD;
+}
+
+static void __init unix98_pty_init(void)
+{
+	devfs_mk_dir("pts");
+	ptm_driver = alloc_tty_driver(NR_UNIX98_PTY_MAX);
+	if (!ptm_driver)
+		panic("Couldn't allocate Unix98 ptm driver");
+	pts_driver = alloc_tty_driver(NR_UNIX98_PTY_MAX);
+	if (!pts_driver)
+		panic("Couldn't allocate Unix98 pts driver");
+
+	ptm_driver->owner = THIS_MODULE;
+	ptm_driver->driver_name = "pty_master";
+	ptm_driver->name = "ptm";
+	ptm_driver->major = UNIX98_PTY_MASTER_MAJOR;
+	ptm_driver->minor_start = 0;
+	ptm_driver->type = TTY_DRIVER_TYPE_PTY;
+	ptm_driver->subtype = PTY_TYPE_MASTER;
+	ptm_driver->init_termios = tty_std_termios;
+	ptm_driver->init_termios.c_iflag = 0;
+	ptm_driver->init_termios.c_oflag = 0;
+	ptm_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+	ptm_driver->init_termios.c_lflag = 0;
+	ptm_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW |
+		TTY_DRIVER_NO_DEVFS | TTY_DRIVER_DEVPTS_MEM;
+	ptm_driver->other = pts_driver;
+	tty_set_operations(ptm_driver, &pty_ops);
+	ptm_driver->ioctl = pty_unix98_ioctl;
+
+	pts_driver->owner = THIS_MODULE;
+	pts_driver->driver_name = "pty_slave";
+	pts_driver->name = "pts";
+	pts_driver->major = UNIX98_PTY_SLAVE_MAJOR;
+	pts_driver->minor_start = 0;
+	pts_driver->type = TTY_DRIVER_TYPE_PTY;
+	pts_driver->subtype = PTY_TYPE_SLAVE;
+	pts_driver->init_termios = tty_std_termios;
+	pts_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
+	pts_driver->flags = TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW |
+		TTY_DRIVER_NO_DEVFS | TTY_DRIVER_DEVPTS_MEM;
+	pts_driver->other = ptm_driver;
+	tty_set_operations(pts_driver, &pty_ops);
+	
+	if (tty_register_driver(ptm_driver))
+		panic("Couldn't register Unix98 ptm driver");
+	if (tty_register_driver(pts_driver))
+		panic("Couldn't register Unix98 pts driver");
+
+	pty_table[1].data = &ptm_driver->refcount;
+}
+#else
+static inline void unix98_pty_init(void) { }
+#endif
+
+static int __init pty_init(void)
+{
+	legacy_pty_init();
+	unix98_pty_init();
+	return 0;
+}
+module_init(pty_init);
diff --git a/drivers/char/qtronix.c b/drivers/char/qtronix.c
new file mode 100644
index 0000000..40a3cf6
--- /dev/null
+++ b/drivers/char/qtronix.c
@@ -0,0 +1,601 @@
+/*
+ *
+ * BRIEF MODULE DESCRIPTION
+ *	Qtronix 990P infrared keyboard driver.
+ *
+ *
+ * Copyright 2001 MontaVista Software Inc.
+ * Author: MontaVista Software, Inc.
+ *         	ppopov@mvista.com or source@mvista.com
+ *
+ *
+ *  The bottom portion of this driver was take from 
+ *  pc_keyb.c  Please see that file for copyrights.
+ *
+ *  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  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
+ *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
+ *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.
+ */
+
+#include <linux/config.h>
+
+/* 
+ * NOTE:  
+ *
+ *	This driver has only been tested with the Consumer IR
+ *	port of the ITE 8172 system controller.
+ *
+ *	You do not need this driver if you are using the ps/2 or
+ *	USB adapter that the keyboard ships with.  You only need 
+ *	this driver if your board has a IR port and the keyboard
+ *	data is being sent directly to the IR.  In that case,
+ *	you also need some low-level IR support. See it8172_cir.c.
+ *	
+ */
+
+#ifdef CONFIG_QTRONIX_KEYBOARD
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+
+#include <asm/it8172/it8172.h>
+#include <asm/it8172/it8172_int.h>
+#include <asm/it8172/it8172_cir.h>
+
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/kbd_ll.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/kbd_kern.h>
+#include <linux/smp_lock.h>
+#include <asm/io.h>
+#include <linux/pc_keyb.h>
+
+#include <asm/keyboard.h>
+#include <linux/bitops.h>
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+
+#define leading1 0
+#define leading2 0xF
+
+#define KBD_CIR_PORT 0
+#define AUX_RECONNECT 170 /* scancode when ps2 device is plugged (back) in */
+
+static int data_index;
+struct cir_port *cir;
+static unsigned char kbdbytes[5];
+static unsigned char cir_data[32]; /* we only need 16 chars */
+
+static void kbd_int_handler(int irq, void *dev_id, struct pt_regs *regs);
+static int handle_data(unsigned char *p_data);
+static inline void handle_mouse_event(unsigned char scancode);
+static inline void handle_keyboard_event(unsigned char scancode, int down);
+static int __init psaux_init(void);
+
+static struct aux_queue *queue;	/* Mouse data buffer. */
+static int aux_count = 0;
+
+/*
+ * Keys accessed through the 'Fn' key
+ * The Fn key does not produce a key-up sequence. So, the first
+ * time the user presses it, it will be key-down event. The key
+ * stays down until the user presses it again.
+ */
+#define NUM_FN_KEYS 56
+static unsigned char fn_keys[NUM_FN_KEYS] = {
+	0,0,0,0,0,0,0,0,        /* 0 7   */
+	8,9,10,93,0,0,0,0,      /* 8 15  */
+	0,0,0,0,0,0,0,5,        /* 16 23 */
+	6,7,91,0,0,0,0,0,       /* 24 31 */
+	0,0,0,0,0,2,3,4,        /* 32 39 */
+	92,0,0,0,0,0,0,0,       /* 40 47 */
+	0,0,0,0,11,0,94,95        /* 48 55 */
+
+};
+
+void __init init_qtronix_990P_kbd(void)
+{
+	int retval;
+
+	cir = (struct cir_port *)kmalloc(sizeof(struct cir_port), GFP_KERNEL);
+	if (!cir) {
+		printk("Unable to initialize Qtronix keyboard\n");
+		return;
+	}
+
+	/* 
+	 * revisit
+	 * this should be programmable, somehow by the, by the user.
+	 */
+	cir->port = KBD_CIR_PORT;
+	cir->baud_rate = 0x1d;
+	cir->rdwos = 0;
+	cir->rxdcr = 0x3;
+	cir->hcfs = 0;
+	cir->fifo_tl = 0;
+	cir->cfq = 0x1d;
+	cir_port_init(cir);
+
+	retval = request_irq(IT8172_CIR0_IRQ, kbd_int_handler, 
+			(unsigned long )(SA_INTERRUPT|SA_SHIRQ), 
+			(const char *)"Qtronix IR Keyboard", (void *)cir);
+
+	if (retval) {
+		printk("unable to allocate cir %d irq %d\n", 
+				cir->port, IT8172_CIR0_IRQ);
+	}
+#ifdef CONFIG_PSMOUSE
+	psaux_init();
+#endif
+}
+
+static inline unsigned char BitReverse(unsigned short key)
+{
+	unsigned char rkey = 0;
+	rkey |= (key & 0x1) << 7;
+	rkey |= (key & 0x2) << 5;
+	rkey |= (key & 0x4) << 3;
+	rkey |= (key & 0x8) << 1;
+	rkey |= (key & 0x10) >> 1;
+	rkey |= (key & 0x20) >> 3;
+	rkey |= (key & 0x40) >> 5;
+	rkey |= (key & 0x80) >> 7;
+	return rkey;
+
+}
+
+
+static inline u_int8_t UpperByte(u_int8_t data)
+{
+	return (data >> 4);
+}
+
+
+static inline u_int8_t LowerByte(u_int8_t data)
+{
+	return (data & 0xF);
+}
+
+
+int CheckSumOk(u_int8_t byte1, u_int8_t byte2, 
+		u_int8_t byte3, u_int8_t byte4, u_int8_t byte5)
+{
+	u_int8_t CheckSum;
+
+	CheckSum = (byte1 & 0x0F) + byte2 + byte3 + byte4 + byte5;
+	if ( LowerByte(UpperByte(CheckSum) + LowerByte(CheckSum)) != UpperByte(byte1) )
+		return 0;
+	else
+		return 1;
+}
+
+
+static void kbd_int_handler(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct cir_port *cir;
+	int j;
+	unsigned char int_status;
+
+	cir = (struct cir_port *)dev_id;
+	int_status = get_int_status(cir);
+	if (int_status & 0x4) {
+		clear_fifo(cir);
+		return;
+	}
+
+	while (cir_get_rx_count(cir)) {
+
+		cir_data[data_index] = cir_read_data(cir);
+
+		if (data_index == 0) {/* expecting first byte */
+			if (cir_data[data_index] != leading1) {
+				//printk("!leading byte %x\n", cir_data[data_index]);
+				set_rx_active(cir);
+				clear_fifo(cir);
+				continue;
+			}
+		}
+		if (data_index == 1) {
+			if ((cir_data[data_index] & 0xf) != leading2) {
+				set_rx_active(cir);
+				data_index = 0; /* start over */
+				clear_fifo(cir);
+				continue;
+			}
+		}
+
+		if ( (cir_data[data_index] == 0xff)) { /* last byte */
+			//printk("data_index %d\n", data_index);
+			set_rx_active(cir);
+#if 0
+			for (j=0; j<=data_index; j++) {
+				printk("rx_data %d:  %x\n", j, cir_data[j]);
+			}
+#endif
+			data_index = 0;
+			handle_data(cir_data);
+			return;
+		}
+		else if (data_index>16) {
+			set_rx_active(cir);
+#if 0
+			printk("warning: data_index %d\n", data_index);
+			for (j=0; j<=data_index; j++) {
+				printk("rx_data %d:  %x\n", j, cir_data[j]);
+			}
+#endif
+			data_index = 0;
+			clear_fifo(cir);
+			return;
+		}
+		data_index++;
+	}
+}
+
+
+#define NUM_KBD_BYTES 5
+static int handle_data(unsigned char *p_data)
+{
+	u_int32_t bit_bucket;
+	u_int32_t i, j;
+	u_int32_t got_bits, next_byte;
+	int down = 0;
+
+	/* Reorganize the bit stream */
+	for (i=0; i<16; i++)
+		p_data[i] = BitReverse(~p_data[i]);
+
+	/* 
+	 * We've already previously checked that p_data[0]
+	 * is equal to leading1 and that (p_data[1] & 0xf)
+	 * is equal to leading2. These twelve bits are the
+	 * leader code.  We can now throw them away (the 12
+	 * bits) and continue parsing the stream.
+	 */
+	bit_bucket = p_data[1] << 12;
+	got_bits = 4;
+	next_byte = 2;
+
+	/* 
+	 * Process four bits at a time
+	 */
+	for (i=0; i<NUM_KBD_BYTES; i++) {
+
+		kbdbytes[i]=0;
+
+		for (j=0; j<8; j++) /* 8 bits per byte */
+		{
+			if (got_bits < 4) {
+				bit_bucket |= (p_data[next_byte++] << (8 - got_bits));
+				got_bits += 8;
+			}
+
+			if ((bit_bucket & 0xF000) == 0x8000) { 
+				/* Convert 1000b to 1 */
+				kbdbytes[i] = 0x80 | (kbdbytes[i] >> 1);
+				got_bits -= 4;
+				bit_bucket = bit_bucket << 4;
+			}
+			else if ((bit_bucket & 0xC000) == 0x8000) {
+				/* Convert 10b to 0 */
+				kbdbytes[i] =  kbdbytes[i] >> 1;
+				got_bits -= 2;
+				bit_bucket = bit_bucket << 2;
+			}
+			else {
+				/* bad serial stream */
+				return 1;
+			}
+
+			if (next_byte > 16) {
+				//printk("error: too many bytes\n");
+				return 1;
+			}
+		}
+	}
+
+
+	if (!CheckSumOk(kbdbytes[0], kbdbytes[1], 
+				kbdbytes[2], kbdbytes[3], kbdbytes[4])) {
+		//printk("checksum failed\n");
+		return 1;
+	}
+
+	if (kbdbytes[1] & 0x08) {
+		//printk("m: %x %x %x\n", kbdbytes[1], kbdbytes[2], kbdbytes[3]);
+		handle_mouse_event(kbdbytes[1]);
+		handle_mouse_event(kbdbytes[2]);
+		handle_mouse_event(kbdbytes[3]);
+	}
+	else {
+		if (kbdbytes[2] == 0) down = 1;
+#if 0
+		if (down)
+			printk("down %d\n", kbdbytes[3]);
+		else
+			printk("up %d\n", kbdbytes[3]);
+#endif
+		handle_keyboard_event(kbdbytes[3], down);
+	}
+	return 0;
+}
+
+
+DEFINE_SPINLOCK(kbd_controller_lock);
+static unsigned char handle_kbd_event(void);
+
+
+int kbd_setkeycode(unsigned int scancode, unsigned int keycode)
+{
+	printk("kbd_setkeycode scancode %x keycode %x\n", scancode, keycode);
+	return 0;
+}
+
+int kbd_getkeycode(unsigned int scancode)
+{
+	return scancode;
+}
+
+
+int kbd_translate(unsigned char scancode, unsigned char *keycode,
+		    char raw_mode)
+{
+	static int prev_scancode = 0;
+
+	if (scancode == 0x00 || scancode == 0xff) {
+		prev_scancode = 0;
+		return 0;
+	}
+
+	/* todo */
+	if (!prev_scancode && scancode == 160) { /* Fn key down */
+		//printk("Fn key down\n");
+		prev_scancode = 160;
+		return 0;
+	}
+	else if (prev_scancode && scancode == 160) { /* Fn key up */
+		//printk("Fn key up\n");
+		prev_scancode = 0;
+		return 0;
+	}
+
+	/* todo */
+	if (prev_scancode == 160) {
+		if (scancode <= NUM_FN_KEYS) {
+			*keycode = fn_keys[scancode];
+			//printk("fn keycode %d\n", *keycode);
+		}
+		else
+			return 0;
+	} 
+	else if (scancode <= 127) {
+		*keycode = scancode;
+	}
+	else
+		return 0;
+
+
+ 	return 1;
+}
+
+char kbd_unexpected_up(unsigned char keycode)
+{
+	//printk("kbd_unexpected_up\n");
+	return 0;
+}
+
+static unsigned char kbd_exists = 1;
+
+static inline void handle_keyboard_event(unsigned char scancode, int down)
+{
+	kbd_exists = 1;
+	handle_scancode(scancode, down);
+	tasklet_schedule(&keyboard_tasklet);
+}	
+
+
+void kbd_leds(unsigned char leds)
+{
+}
+
+/* dummy */
+void kbd_init_hw(void)
+{
+}
+
+
+
+static inline void handle_mouse_event(unsigned char scancode)
+{
+	if(scancode == AUX_RECONNECT){
+		queue->head = queue->tail = 0;  /* Flush input queue */
+	//	__aux_write_ack(AUX_ENABLE_DEV);  /* ping the mouse :) */
+		return;
+	}
+
+	if (aux_count) {
+		int head = queue->head;
+
+		queue->buf[head] = scancode;
+		head = (head + 1) & (AUX_BUF_SIZE-1);
+		if (head != queue->tail) {
+			queue->head = head;
+			kill_fasync(&queue->fasync, SIGIO, POLL_IN);
+			wake_up_interruptible(&queue->proc_list);
+		}
+	}
+}
+
+static unsigned char get_from_queue(void)
+{
+	unsigned char result;
+	unsigned long flags;
+
+	spin_lock_irqsave(&kbd_controller_lock, flags);
+	result = queue->buf[queue->tail];
+	queue->tail = (queue->tail + 1) & (AUX_BUF_SIZE-1);
+	spin_unlock_irqrestore(&kbd_controller_lock, flags);
+	return result;
+}
+
+
+static inline int queue_empty(void)
+{
+	return queue->head == queue->tail;
+}
+
+static int fasync_aux(int fd, struct file *filp, int on)
+{
+	int retval;
+
+	//printk("fasync_aux\n");
+	retval = fasync_helper(fd, filp, on, &queue->fasync);
+	if (retval < 0)
+		return retval;
+	return 0;
+}
+
+
+/*
+ * Random magic cookie for the aux device
+ */
+#define AUX_DEV ((void *)queue)
+
+static int release_aux(struct inode * inode, struct file * file)
+{
+	fasync_aux(-1, file, 0);
+	aux_count--;
+	return 0;
+}
+
+static int open_aux(struct inode * inode, struct file * file)
+{
+	if (aux_count++) {
+		return 0;
+	}
+	queue->head = queue->tail = 0;		/* Flush input queue */
+	return 0;
+}
+
+/*
+ * Put bytes from input queue to buffer.
+ */
+
+static ssize_t read_aux(struct file * file, char * buffer,
+			size_t count, loff_t *ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	ssize_t i = count;
+	unsigned char c;
+
+	if (queue_empty()) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+		add_wait_queue(&queue->proc_list, &wait);
+repeat:
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (queue_empty() && !signal_pending(current)) {
+			schedule();
+			goto repeat;
+		}
+		current->state = TASK_RUNNING;
+		remove_wait_queue(&queue->proc_list, &wait);
+	}
+	while (i > 0 && !queue_empty()) {
+		c = get_from_queue();
+		put_user(c, buffer++);
+		i--;
+	}
+	if (count-i) {
+		struct inode *inode = file->f_dentry->d_inode;
+		inode->i_atime = current_fs_time(inode->i_sb);
+		return count-i;
+	}
+	if (signal_pending(current))
+		return -ERESTARTSYS;
+	return 0;
+}
+
+/*
+ * Write to the aux device.
+ */
+
+static ssize_t write_aux(struct file * file, const char * buffer,
+			 size_t count, loff_t *ppos)
+{
+	/*
+	 * The ITE boards this was tested on did not have the
+	 * transmit wires connected.
+	 */
+	return count;
+}
+
+static unsigned int aux_poll(struct file *file, poll_table * wait)
+{
+	poll_wait(file, &queue->proc_list, wait);
+	if (!queue_empty())
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+struct file_operations psaux_fops = {
+	.read		= read_aux,
+	.write		= write_aux,
+	.poll		= aux_poll,
+	.open		= open_aux,
+	.release	= release_aux,
+	.fasync		= fasync_aux,
+};
+
+/*
+ * Initialize driver.
+ */
+static struct miscdevice psaux_mouse = {
+	PSMOUSE_MINOR, "psaux", &psaux_fops
+};
+
+static int __init psaux_init(void)
+{
+	int retval;
+
+	retval = misc_register(&psaux_mouse);
+	if(retval < 0)
+		return retval;
+
+	queue = (struct aux_queue *) kmalloc(sizeof(*queue), GFP_KERNEL);
+	memset(queue, 0, sizeof(*queue));
+	queue->head = queue->tail = 0;
+	init_waitqueue_head(&queue->proc_list);
+
+	return 0;
+}
+module_init(init_qtronix_990P_kbd);
+#endif
diff --git a/drivers/char/qtronixmap.c_shipped b/drivers/char/qtronixmap.c_shipped
new file mode 100644
index 0000000..1e2b92b
--- /dev/null
+++ b/drivers/char/qtronixmap.c_shipped
@@ -0,0 +1,265 @@
+
+/* Do not edit this file! It was automatically generated by   */
+/*    loadkeys --mktable defkeymap.map > defkeymap.c          */
+
+#include <linux/types.h>
+#include <linux/keyboard.h>
+#include <linux/kd.h>
+
+u_short plain_map[NR_KEYS] = {
+	0xf200,	0xf060,	0xf031,	0xf032,	0xf033,	0xf034,	0xf035,	0xf036,
+	0xf037,	0xf038,	0xf039,	0xf030,	0xf02d,	0xf03d,	0xf200,	0xf07f,
+	0xf009,	0xfb71,	0xfb77,	0xfb65,	0xfb72,	0xfb74,	0xfb79,	0xfb75,
+	0xfb69,	0xfb6f,	0xfb70,	0xf05b,	0xf05d,	0xf05c,	0xf207,	0xfb61,
+	0xfb73,	0xfb64,	0xfb66,	0xfb67,	0xfb68,	0xfb6a,	0xfb6b,	0xfb6c,
+	0xf03b,	0xf027,	0xf060,	0xf201,	0xf700,	0xf200,	0xfb7a,	0xfb78,
+	0xfb63,	0xfb76,	0xfb62,	0xfb6e,	0xfb6d,	0xf02c,	0xf02e,	0xf02f,
+	0xf200,	0xf700,	0xf702,	0xf200,	0xf703,	0xf020,	0xf703,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf115,	0xf07f,	0xf200,	0xf200,	0xf601,
+	0xf200,	0xf200,	0xf200,	0xf603,	0xf600,	0xf118,	0xf119,	0xf200,
+	0xf200,	0xf602,	0xf208,	0xf02d,	0xf02b,	0xf30c,	0xf02e,	0xf30d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf117,	0xf600,	0xf200,	0xf01b,	0xf200,
+	0xf100,	0xf101,	0xf102,	0xf103,	0xf104,	0xf105,	0xf106,	0xf107,
+	0xf108,	0xf109,	0xf200,	0xf200,	0xf200,	0xf200,	0xf11d,	0xf200,
+};
+
+u_short shift_map[NR_KEYS] = {
+	0xf200,	0xf07e,	0xf021,	0xf040,	0xf023,	0xf024,	0xf025,	0xf05e,
+	0xf026,	0xf02a,	0xf028,	0xf029,	0xf05f,	0xf02b,	0xf200,	0xf07f,
+	0xf009,	0xfb51,	0xfb57,	0xfb45,	0xfb52,	0xfb54,	0xfb59,	0xfb55,
+	0xfb49,	0xfb4f,	0xfb50,	0xf07b,	0xf07d,	0xf07c,	0xf207,	0xfb41,
+	0xfb53,	0xfb44,	0xfb46,	0xfb47,	0xfb48,	0xfb4a,	0xfb4b,	0xfb4c,
+	0xf03a,	0xf022,	0xf07e,	0xf201,	0xf700,	0xf200,	0xfb5a,	0xfb58,
+	0xfb43,	0xfb56,	0xfb42,	0xfb4e,	0xfb4d,	0xf03c,	0xf03e,	0xf03f,
+	0xf200,	0xf700,	0xf702,	0xf200,	0xf703,	0xf020,	0xf703,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf115,	0xf07f,	0xf200,	0xf200,	0xf601,
+	0xf200,	0xf200,	0xf200,	0xf603,	0xf600,	0xf20b,	0xf20a,	0xf200,
+	0xf200,	0xf602,	0xf213,	0xf02d,	0xf02b,	0xf30c,	0xf02e,	0xf30d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf117,	0xf600,	0xf200,	0xf01b,	0xf200,
+	0xf10a,	0xf10b,	0xf10c,	0xf10d,	0xf10e,	0xf10f,	0xf110,	0xf111,
+	0xf112,	0xf113,	0xf200,	0xf200,	0xf200,	0xf200,	0xf11d,	0xf200,
+};
+
+u_short altgr_map[NR_KEYS] = {
+	0xf200,	0xf200,	0xf200,	0xf040,	0xf200,	0xf024,	0xf200,	0xf200,
+	0xf07b,	0xf05b,	0xf05d,	0xf07d,	0xf05c,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xfb71,	0xfb77,	0xfb65,	0xfb72,	0xfb74,	0xfb79,	0xfb75,
+	0xfb69,	0xfb6f,	0xfb70,	0xf200,	0xf200,	0xf200,	0xf207,	0xfb61,
+	0xfb73,	0xfb64,	0xfb66,	0xfb67,	0xfb68,	0xfb6a,	0xfb6b,	0xfb6c,
+	0xf200,	0xf200,	0xf200,	0xf201,	0xf700,	0xf200,	0xfb7a,	0xfb78,
+	0xfb63,	0xfb76,	0xfb62,	0xfb6e,	0xfb6d,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf700,	0xf702,	0xf200,	0xf703,	0xf200,	0xf703,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf115,	0xf07f,	0xf200,	0xf200,	0xf601,
+	0xf200,	0xf200,	0xf200,	0xf603,	0xf600,	0xf118,	0xf119,	0xf200,
+	0xf200,	0xf602,	0xf208,	0xf02d,	0xf02b,	0xf30c,	0xf02e,	0xf30d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf117,	0xf600,	0xf200,	0xf200,	0xf200,
+	0xf50c,	0xf50d,	0xf50e,	0xf50f,	0xf510,	0xf511,	0xf512,	0xf513,
+	0xf514,	0xf515,	0xf200,	0xf200,	0xf200,	0xf200,	0xf11d,	0xf200,
+};
+
+u_short ctrl_map[NR_KEYS] = {
+	0xf200,	0xf200,	0xf200,	0xf000,	0xf01b,	0xf01c,	0xf01d,	0xf01e,
+	0xf01f,	0xf07f,	0xf200,	0xf200,	0xf01f,	0xf200,	0xf200,	0xf008,
+	0xf200,	0xf011,	0xf017,	0xf005,	0xf012,	0xf014,	0xf019,	0xf015,
+	0xf009,	0xf00f,	0xf010,	0xf01b,	0xf01d,	0xf01c,	0xf207,	0xf001,
+	0xf013,	0xf004,	0xf006,	0xf007,	0xf008,	0xf00a,	0xf00b,	0xf00c,
+	0xf007,	0xf000,	0xf200,	0xf201,	0xf700,	0xf200,	0xf01a,	0xf018,
+	0xf003,	0xf016,	0xf002,	0xf00e,	0xf20e,	0xf07f,	0xf200,	0xf200,
+	0xf200,	0xf700,	0xf702,	0xf200,	0xf703,	0xf000,	0xf703,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf115,	0xf07f,	0xf200,	0xf200,	0xf601,
+	0xf200,	0xf200,	0xf200,	0xf603,	0xf600,	0xf118,	0xf119,	0xf200,
+	0xf200,	0xf602,	0xf208,	0xf02d,	0xf02b,	0xf30c,	0xf02e,	0xf30d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf117,	0xf600,	0xf200,	0xf200,	0xf200,
+	0xf100,	0xf101,	0xf102,	0xf103,	0xf104,	0xf105,	0xf106,	0xf107,
+	0xf108,	0xf109,	0xf200,	0xf200,	0xf200,	0xf200,	0xf11d,	0xf200,
+};
+
+u_short shift_ctrl_map[NR_KEYS] = {
+	0xf200,	0xf200,	0xf200,	0xf000,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf01f,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf011,	0xf017,	0xf005,	0xf012,	0xf014,	0xf019,	0xf015,
+	0xf009,	0xf00f,	0xf010,	0xf200,	0xf200,	0xf200,	0xf207,	0xf001,
+	0xf013,	0xf004,	0xf006,	0xf007,	0xf008,	0xf00a,	0xf00b,	0xf00c,
+	0xf200,	0xf200,	0xf200,	0xf201,	0xf700,	0xf200,	0xf01a,	0xf018,
+	0xf003,	0xf016,	0xf002,	0xf00e,	0xf00d,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf700,	0xf702,	0xf200,	0xf703,	0xf200,	0xf703,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf115,	0xf07f,	0xf200,	0xf200,	0xf601,
+	0xf200,	0xf200,	0xf200,	0xf603,	0xf600,	0xf118,	0xf119,	0xf200,
+	0xf200,	0xf602,	0xf208,	0xf02d,	0xf02b,	0xf30c,	0xf02e,	0xf30d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf117,	0xf600,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf11d,	0xf200,
+};
+
+u_short alt_map[NR_KEYS] = {
+	0xf200,	0xf81b,	0xf831,	0xf832,	0xf833,	0xf834,	0xf835,	0xf836,
+	0xf837,	0xf838,	0xf839,	0xf830,	0xf82d,	0xf83d,	0xf200,	0xf87f,
+	0xf809,	0xf871,	0xf877,	0xf865,	0xf872,	0xf874,	0xf879,	0xf875,
+	0xf869,	0xf86f,	0xf870,	0xf85b,	0xf85d,	0xf85c,	0xf207,	0xf861,
+	0xf873,	0xf864,	0xf866,	0xf867,	0xf868,	0xf86a,	0xf86b,	0xf83b,
+	0xf827,	0xf860,	0xf200,	0xf80d,	0xf700,	0xf200,	0xf87a,	0xf878,
+	0xf863,	0xf876,	0xf862,	0xf82c,	0xf82e,	0xf82f,	0xf200,	0xf200,
+	0xf200,	0xf700,	0xf702,	0xf200,	0xf703,	0xf820,	0xf703,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf115,	0xf07f,	0xf200,	0xf200,	0xf210,
+	0xf200,	0xf200,	0xf200,	0xf603,	0xf600,	0xf118,	0xf119,	0xf200,
+	0xf200,	0xf211,	0xf208,	0xf02d,	0xf02b,	0xf30c,	0xf02e,	0xf30d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf117,	0xf600,	0xf200,	0xf200,	0xf200,
+	0xf500,	0xf501,	0xf502,	0xf503,	0xf504,	0xf505,	0xf506,	0xf507,
+	0xf508,	0xf509,	0xf200,	0xf200,	0xf200,	0xf200,	0xf11d,	0xf200,
+};
+
+u_short ctrl_alt_map[NR_KEYS] = {
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf811,	0xf817,	0xf805,	0xf812,	0xf814,	0xf819,	0xf815,
+	0xf809,	0xf80f,	0xf810,	0xf200,	0xf200,	0xf200,	0xf207,	0xf801,
+	0xf813,	0xf804,	0xf806,	0xf807,	0xf808,	0xf80a,	0xf80b,	0xf80c,
+	0xf200,	0xf200,	0xf200,	0xf201,	0xf700,	0xf200,	0xf81a,	0xf818,
+	0xf803,	0xf816,	0xf802,	0xf80e,	0xf80d,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf700,	0xf702,	0xf200,	0xf703,	0xf200,	0xf703,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf115,	0xf07f,	0xf200,	0xf200,	0xf601,
+	0xf200,	0xf200,	0xf200,	0xf603,	0xf600,	0xf118,	0xf119,	0xf200,
+	0xf200,	0xf602,	0xf208,	0xf02d,	0xf02b,	0xf30c,	0xf02e,	0xf30d,
+	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,	0xf200,
+	0xf200,	0xf200,	0xf200,	0xf117,	0xf600,	0xf200,	0xf200,	0xf200,
+	0xf500,	0xf501,	0xf502,	0xf503,	0xf504,	0xf505,	0xf506,	0xf507,
+	0xf508,	0xf509,	0xf200,	0xf200,	0xf200,	0xf200,	0xf11d,	0xf200,
+};
+
+ushort *key_maps[MAX_NR_KEYMAPS] = {
+	plain_map, shift_map, altgr_map, 0,
+	ctrl_map, shift_ctrl_map, 0, 0,
+	alt_map, 0, 0, 0,
+	ctrl_alt_map,	0
+};
+
+unsigned int keymap_count = 7;
+
+
+/*
+ * Philosophy: most people do not define more strings, but they who do
+ * often want quite a lot of string space. So, we statically allocate
+ * the default and allocate dynamically in chunks of 512 bytes.
+ */
+
+char func_buf[] = {
+	'\033', '[', '[', 'A', 0, 
+	'\033', '[', '[', 'B', 0, 
+	'\033', '[', '[', 'C', 0, 
+	'\033', '[', '[', 'D', 0, 
+	'\033', '[', '[', 'E', 0, 
+	'\033', '[', '1', '7', '~', 0, 
+	'\033', '[', '1', '8', '~', 0, 
+	'\033', '[', '1', '9', '~', 0, 
+	'\033', '[', '2', '0', '~', 0, 
+	'\033', '[', '2', '1', '~', 0, 
+	'\033', '[', '2', '3', '~', 0, 
+	'\033', '[', '2', '4', '~', 0, 
+	'\033', '[', '2', '5', '~', 0, 
+	'\033', '[', '2', '6', '~', 0, 
+	'\033', '[', '2', '8', '~', 0, 
+	'\033', '[', '2', '9', '~', 0, 
+	'\033', '[', '3', '1', '~', 0, 
+	'\033', '[', '3', '2', '~', 0, 
+	'\033', '[', '3', '3', '~', 0, 
+	'\033', '[', '3', '4', '~', 0, 
+	'\033', '[', '1', '~', 0, 
+	'\033', '[', '2', '~', 0, 
+	'\033', '[', '3', '~', 0, 
+	'\033', '[', '4', '~', 0, 
+	'\033', '[', '5', '~', 0, 
+	'\033', '[', '6', '~', 0, 
+	'\033', '[', 'M', 0, 
+	'\033', '[', 'P', 0, 
+};
+
+
+char *funcbufptr = func_buf;
+int funcbufsize = sizeof(func_buf);
+int funcbufleft = 0;          /* space left */
+
+char *func_table[MAX_NR_FUNC] = {
+	func_buf + 0,
+	func_buf + 5,
+	func_buf + 10,
+	func_buf + 15,
+	func_buf + 20,
+	func_buf + 25,
+	func_buf + 31,
+	func_buf + 37,
+	func_buf + 43,
+	func_buf + 49,
+	func_buf + 55,
+	func_buf + 61,
+	func_buf + 67,
+	func_buf + 73,
+	func_buf + 79,
+	func_buf + 85,
+	func_buf + 91,
+	func_buf + 97,
+	func_buf + 103,
+	func_buf + 109,
+	func_buf + 115,
+	func_buf + 120,
+	func_buf + 125,
+	func_buf + 130,
+	func_buf + 135,
+	func_buf + 140,
+	func_buf + 145,
+	0,
+	0,
+	func_buf + 149,
+	0,
+};
+
+struct kbdiacr accent_table[MAX_DIACR] = {
+	{'`', 'A', 'À'},	{'`', 'a', 'à'},
+	{'\'', 'A', 'Á'},	{'\'', 'a', 'á'},
+	{'^', 'A', 'Â'},	{'^', 'a', 'â'},
+	{'~', 'A', 'Ã'},	{'~', 'a', 'ã'},
+	{'"', 'A', 'Ä'},	{'"', 'a', 'ä'},
+	{'O', 'A', 'Å'},	{'o', 'a', 'å'},
+	{'0', 'A', 'Å'},	{'0', 'a', 'å'},
+	{'A', 'A', 'Å'},	{'a', 'a', 'å'},
+	{'A', 'E', 'Æ'},	{'a', 'e', 'æ'},
+	{',', 'C', 'Ç'},	{',', 'c', 'ç'},
+	{'`', 'E', 'È'},	{'`', 'e', 'è'},
+	{'\'', 'E', 'É'},	{'\'', 'e', 'é'},
+	{'^', 'E', 'Ê'},	{'^', 'e', 'ê'},
+	{'"', 'E', 'Ë'},	{'"', 'e', 'ë'},
+	{'`', 'I', 'Ì'},	{'`', 'i', 'ì'},
+	{'\'', 'I', 'Í'},	{'\'', 'i', 'í'},
+	{'^', 'I', 'Î'},	{'^', 'i', 'î'},
+	{'"', 'I', 'Ï'},	{'"', 'i', 'ï'},
+	{'-', 'D', 'Ð'},	{'-', 'd', 'ð'},
+	{'~', 'N', 'Ñ'},	{'~', 'n', 'ñ'},
+	{'`', 'O', 'Ò'},	{'`', 'o', 'ò'},
+	{'\'', 'O', 'Ó'},	{'\'', 'o', 'ó'},
+	{'^', 'O', 'Ô'},	{'^', 'o', 'ô'},
+	{'~', 'O', 'Õ'},	{'~', 'o', 'õ'},
+	{'"', 'O', 'Ö'},	{'"', 'o', 'ö'},
+	{'/', 'O', 'Ø'},	{'/', 'o', 'ø'},
+	{'`', 'U', 'Ù'},	{'`', 'u', 'ù'},
+	{'\'', 'U', 'Ú'},	{'\'', 'u', 'ú'},
+	{'^', 'U', 'Û'},	{'^', 'u', 'û'},
+	{'"', 'U', 'Ü'},	{'"', 'u', 'ü'},
+	{'\'', 'Y', 'Ý'},	{'\'', 'y', 'ý'},
+	{'T', 'H', 'Þ'},	{'t', 'h', 'þ'},
+	{'s', 's', 'ß'},	{'"', 'y', 'ÿ'},
+	{'s', 'z', 'ß'},	{'i', 'j', 'ÿ'},
+};
+
+unsigned int accent_table_size = 68;
diff --git a/drivers/char/qtronixmap.map b/drivers/char/qtronixmap.map
new file mode 100644
index 0000000..8d1ff5c
--- /dev/null
+++ b/drivers/char/qtronixmap.map
@@ -0,0 +1,287 @@
+# Default kernel keymap. This uses 7 modifier combinations.
+keymaps 0-2,4-5,8,12
+# Change the above line into
+#	keymaps 0-2,4-6,8,12
+# in case you want the entries
+#	altgr   control keycode  83 = Boot            
+#	altgr   control keycode 111 = Boot            
+# below.
+#
+# In fact AltGr is used very little, and one more keymap can
+# be saved by mapping AltGr to Alt (and adapting a few entries):
+# keycode 100 = Alt
+#
+keycode   1 = grave        asciitilde
+	alt     keycode   1 = Meta_Escape     
+keycode   2 = one              exclam          
+	alt     keycode   2 = Meta_one        
+keycode   3 = two              at               at              
+	control	keycode   3 = nul             
+	shift	control	keycode   3 = nul             
+	alt	keycode   3 = Meta_two        
+keycode   4 = three            numbersign      
+	control keycode   4 = Escape          
+	alt     keycode   4 = Meta_three      
+keycode   5 = four             dollar           dollar          
+	control keycode   5 = Control_backslash
+	alt     keycode   5 = Meta_four       
+keycode   6 = five             percent         
+	control keycode   6 = Control_bracketright
+	alt     keycode   6 = Meta_five       
+keycode   7 = six              asciicircum     
+	control keycode   7 = Control_asciicircum
+	alt     keycode   7 = Meta_six        
+keycode   8 = seven            ampersand        braceleft       
+	control keycode   8 = Control_underscore
+	alt     keycode   8 = Meta_seven      
+keycode   9 = eight            asterisk         bracketleft     
+	control keycode   9 = Delete          
+	alt     keycode   9 = Meta_eight      
+keycode  10 = nine             parenleft        bracketright    
+	alt     keycode  10 = Meta_nine       
+keycode  11 = zero             parenright       braceright      
+	alt     keycode  11 = Meta_zero       
+keycode  12 = minus            underscore       backslash       
+	control	keycode  12 = Control_underscore
+	shift	control	keycode  12 = Control_underscore
+	alt	keycode  12 = Meta_minus      
+keycode  13 = equal            plus            
+	alt     keycode  13 = Meta_equal      
+keycode  15 = Delete           Delete          
+	control keycode  15 = BackSpace
+	alt     keycode  15 = Meta_Delete     
+keycode  16 = Tab              Tab             
+	alt     keycode  16 = Meta_Tab        
+keycode  17 = q               
+keycode  18 = w               
+keycode  19 = e
+keycode  20 = r               
+keycode  21 = t               
+keycode  22 = y               
+keycode  23 = u	 
+keycode  24 = i               
+keycode  25 = o               
+keycode  26 = p               
+keycode  27 = bracketleft      braceleft       
+	control keycode  27 = Escape          
+	alt     keycode  27 = Meta_bracketleft
+keycode  28 = bracketright     braceright
+	control keycode  28 = Control_bracketright
+	alt     keycode  28 = Meta_bracketright
+keycode  29 = backslash        bar             
+	control keycode  29 = Control_backslash
+	alt     keycode  29 = Meta_backslash  
+keycode  30 = Caps_Lock
+keycode  31 = a               
+keycode  32 = s
+keycode  33 = d
+keycode  34 = f               
+keycode  35 = g               
+keycode  36 = h               
+keycode  37 = j               
+keycode  38 = k               
+keycode  39 = l               
+keycode  40 = semicolon        colon           
+	alt     keycode  39 = Meta_semicolon  
+keycode  41 = apostrophe       quotedbl        
+	control keycode  40 = Control_g       
+	alt     keycode  40 = Meta_apostrophe 
+keycode  42 = grave            asciitilde      
+	control keycode  41 = nul             
+	alt     keycode  41 = Meta_grave      
+keycode  43 = Return          
+	alt     keycode  43 = Meta_Control_m  
+keycode  44 = Shift               
+keycode  46 = z
+keycode  47 = x               
+keycode  48 = c
+keycode  49 = v               
+keycode  50 = b               
+keycode  51 = n
+keycode  52 = m
+keycode  53 = comma            less            
+	alt     keycode  51 = Meta_comma      
+keycode  54 = period           greater         
+	control keycode  52 = Compose         
+	alt     keycode  52 = Meta_period     
+keycode  55 = slash            question        
+	control keycode  53 = Delete          
+	alt     keycode  53 = Meta_slash      
+keycode  57 = Shift
+keycode  58 = Control
+keycode  60 = Alt
+keycode  61 = space            space           
+	control keycode  61 = nul             
+	alt     keycode  61 = Meta_space      
+keycode  62 = Alt
+
+keycode 75 = Insert          
+keycode 76 = Delete          
+
+keycode 83 = Up              
+keycode 84 = Down              
+
+keycode 85 = Prior           
+	shift   keycode 85 = Scroll_Backward 
+keycode 86 = Next            
+	shift   keycode 86 = Scroll_Forward  
+keycode 89 = Right           
+	alt     keycode 89 = Incr_Console
+keycode 79 = Left            
+	alt     keycode 79 = Decr_Console
+
+keycode  90 = Num_Lock
+	shift   keycode  90 = Bare_Num_Lock
+
+keycode 91 = minus
+keycode 92 = plus
+keycode 93 = KP_Multiply
+keycode 94 = period
+keycode 95 = KP_Divide
+
+keycode 107 = Select          
+keycode 108 = Down            
+
+keycode 110 = Escape           Escape          
+	alt     keycode   1 = Meta_Escape     
+
+keycode  112 = F1              F11              Console_13      
+	control keycode  112 = F1              
+	alt     keycode  112 = Console_1       
+	control alt     keycode  112 = Console_1       
+keycode  113 = F2              F12              Console_14      
+	control keycode  113 = F2              
+	alt     keycode  113 = Console_2       
+	control alt     keycode  113 = Console_2       
+keycode  114 = F3              F13              Console_15      
+	control keycode  114 = F3              
+	alt     keycode  114 = Console_3       
+	control alt     keycode  114 = Console_3       
+keycode  115 = F4              F14              Console_16      
+	control keycode  115 = F4              
+	alt     keycode  115 = Console_4       
+	control alt     keycode  115 = Console_4       
+keycode  116 = F5              F15              Console_17      
+	control keycode  116 = F5              
+	alt     keycode  116 = Console_5       
+	control alt     keycode  116 = Console_5       
+keycode  117 = F6              F16              Console_18      
+	control keycode  117 = F6              
+	alt     keycode  117 = Console_6       
+	control alt     keycode  117 = Console_6       
+keycode  118 = F7              F17              Console_19      
+	control keycode  118 = F7              
+	alt     keycode  118 = Console_7       
+	control alt     keycode  118 = Console_7       
+keycode  119 = F8              F18              Console_20      
+	control keycode  119 = F8              
+	alt     keycode  119 = Console_8       
+	control alt     keycode  119 = Console_8       
+keycode  120 = F9              F19              Console_21      
+	control keycode  120 = F9              
+	alt     keycode  120 = Console_9       
+	control alt     keycode  120 = Console_9       
+keycode  121 = F10             F20              Console_22      
+	control keycode  121 = F10             
+	alt     keycode  121 = Console_10      
+	control alt     keycode  121 = Console_10      
+	
+keycode 126 = Pause           
+
+
+string F1 = "\033[[A"
+string F2 = "\033[[B"
+string F3 = "\033[[C"
+string F4 = "\033[[D"
+string F5 = "\033[[E"
+string F6 = "\033[17~"
+string F7 = "\033[18~"
+string F8 = "\033[19~"
+string F9 = "\033[20~"
+string F10 = "\033[21~"
+string F11 = "\033[23~"
+string F12 = "\033[24~"
+string F13 = "\033[25~"
+string F14 = "\033[26~"
+string F15 = "\033[28~"
+string F16 = "\033[29~"
+string F17 = "\033[31~"
+string F18 = "\033[32~"
+string F19 = "\033[33~"
+string F20 = "\033[34~"
+string Find = "\033[1~"
+string Insert = "\033[2~"
+string Remove = "\033[3~"
+string Select = "\033[4~"
+string Prior = "\033[5~"
+string Next = "\033[6~"
+string Macro = "\033[M"
+string Pause = "\033[P"
+compose '`' 'A' to 'À'
+compose '`' 'a' to 'à'
+compose '\'' 'A' to 'Á'
+compose '\'' 'a' to 'á'
+compose '^' 'A' to 'Â'
+compose '^' 'a' to 'â'
+compose '~' 'A' to 'Ã'
+compose '~' 'a' to 'ã'
+compose '"' 'A' to 'Ä'
+compose '"' 'a' to 'ä'
+compose 'O' 'A' to 'Å'
+compose 'o' 'a' to 'å'
+compose '0' 'A' to 'Å'
+compose '0' 'a' to 'å'
+compose 'A' 'A' to 'Å'
+compose 'a' 'a' to 'å'
+compose 'A' 'E' to 'Æ'
+compose 'a' 'e' to 'æ'
+compose ',' 'C' to 'Ç'
+compose ',' 'c' to 'ç'
+compose '`' 'E' to 'È'
+compose '`' 'e' to 'è'
+compose '\'' 'E' to 'É'
+compose '\'' 'e' to 'é'
+compose '^' 'E' to 'Ê'
+compose '^' 'e' to 'ê'
+compose '"' 'E' to 'Ë'
+compose '"' 'e' to 'ë'
+compose '`' 'I' to 'Ì'
+compose '`' 'i' to 'ì'
+compose '\'' 'I' to 'Í'
+compose '\'' 'i' to 'í'
+compose '^' 'I' to 'Î'
+compose '^' 'i' to 'î'
+compose '"' 'I' to 'Ï'
+compose '"' 'i' to 'ï'
+compose '-' 'D' to 'Ð'
+compose '-' 'd' to 'ð'
+compose '~' 'N' to 'Ñ'
+compose '~' 'n' to 'ñ'
+compose '`' 'O' to 'Ò'
+compose '`' 'o' to 'ò'
+compose '\'' 'O' to 'Ó'
+compose '\'' 'o' to 'ó'
+compose '^' 'O' to 'Ô'
+compose '^' 'o' to 'ô'
+compose '~' 'O' to 'Õ'
+compose '~' 'o' to 'õ'
+compose '"' 'O' to 'Ö'
+compose '"' 'o' to 'ö'
+compose '/' 'O' to 'Ø'
+compose '/' 'o' to 'ø'
+compose '`' 'U' to 'Ù'
+compose '`' 'u' to 'ù'
+compose '\'' 'U' to 'Ú'
+compose '\'' 'u' to 'ú'
+compose '^' 'U' to 'Û'
+compose '^' 'u' to 'û'
+compose '"' 'U' to 'Ü'
+compose '"' 'u' to 'ü'
+compose '\'' 'Y' to 'Ý'
+compose '\'' 'y' to 'ý'
+compose 'T' 'H' to 'Þ'
+compose 't' 'h' to 'þ'
+compose 's' 's' to 'ß'
+compose '"' 'y' to 'ÿ'
+compose 's' 'z' to 'ß'
+compose 'i' 'j' to 'ÿ'
diff --git a/drivers/char/random.c b/drivers/char/random.c
new file mode 100644
index 0000000..ad9b52c
--- /dev/null
+++ b/drivers/char/random.c
@@ -0,0 +1,1629 @@
+/*
+ * random.c -- A strong random number generator
+ *
+ * Version 1.89, last modified 19-Sep-99
+ *
+ * Copyright Theodore Ts'o, 1994, 1995, 1996, 1997, 1998, 1999.  All
+ * rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU General Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions.  (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
+
+/*
+ * (now, with legal B.S. out of the way.....)
+ *
+ * This routine gathers environmental noise from device drivers, etc.,
+ * and returns good random numbers, suitable for cryptographic use.
+ * Besides the obvious cryptographic uses, these numbers are also good
+ * for seeding TCP sequence numbers, and other places where it is
+ * desirable to have numbers which are not only random, but hard to
+ * predict by an attacker.
+ *
+ * Theory of operation
+ * ===================
+ *
+ * Computers are very predictable devices.  Hence it is extremely hard
+ * to produce truly random numbers on a computer --- as opposed to
+ * pseudo-random numbers, which can easily generated by using a
+ * algorithm.  Unfortunately, it is very easy for attackers to guess
+ * the sequence of pseudo-random number generators, and for some
+ * applications this is not acceptable.  So instead, we must try to
+ * gather "environmental noise" from the computer's environment, which
+ * must be hard for outside attackers to observe, and use that to
+ * generate random numbers.  In a Unix environment, this is best done
+ * from inside the kernel.
+ *
+ * Sources of randomness from the environment include inter-keyboard
+ * timings, inter-interrupt timings from some interrupts, and other
+ * events which are both (a) non-deterministic and (b) hard for an
+ * outside observer to measure.  Randomness from these sources are
+ * added to an "entropy pool", which is mixed using a CRC-like function.
+ * This is not cryptographically strong, but it is adequate assuming
+ * the randomness is not chosen maliciously, and it is fast enough that
+ * the overhead of doing it on every interrupt is very reasonable.
+ * As random bytes are mixed into the entropy pool, the routines keep
+ * an *estimate* of how many bits of randomness have been stored into
+ * the random number generator's internal state.
+ *
+ * When random bytes are desired, they are obtained by taking the SHA
+ * hash of the contents of the "entropy pool".  The SHA hash avoids
+ * exposing the internal state of the entropy pool.  It is believed to
+ * be computationally infeasible to derive any useful information
+ * about the input of SHA from its output.  Even if it is possible to
+ * analyze SHA in some clever way, as long as the amount of data
+ * returned from the generator is less than the inherent entropy in
+ * the pool, the output data is totally unpredictable.  For this
+ * reason, the routine decreases its internal estimate of how many
+ * bits of "true randomness" are contained in the entropy pool as it
+ * outputs random numbers.
+ *
+ * If this estimate goes to zero, the routine can still generate
+ * random numbers; however, an attacker may (at least in theory) be
+ * able to infer the future output of the generator from prior
+ * outputs.  This requires successful cryptanalysis of SHA, which is
+ * not believed to be feasible, but there is a remote possibility.
+ * Nonetheless, these numbers should be useful for the vast majority
+ * of purposes.
+ *
+ * Exported interfaces ---- output
+ * ===============================
+ *
+ * There are three exported interfaces; the first is one designed to
+ * be used from within the kernel:
+ *
+ * 	void get_random_bytes(void *buf, int nbytes);
+ *
+ * This interface will return the requested number of random bytes,
+ * and place it in the requested buffer.
+ *
+ * The two other interfaces are two character devices /dev/random and
+ * /dev/urandom.  /dev/random is suitable for use when very high
+ * quality randomness is desired (for example, for key generation or
+ * one-time pads), as it will only return a maximum of the number of
+ * bits of randomness (as estimated by the random number generator)
+ * contained in the entropy pool.
+ *
+ * The /dev/urandom device does not have this limit, and will return
+ * as many bytes as are requested.  As more and more random bytes are
+ * requested without giving time for the entropy pool to recharge,
+ * this will result in random numbers that are merely cryptographically
+ * strong.  For many applications, however, this is acceptable.
+ *
+ * Exported interfaces ---- input
+ * ==============================
+ *
+ * The current exported interfaces for gathering environmental noise
+ * from the devices are:
+ *
+ * 	void add_input_randomness(unsigned int type, unsigned int code,
+ *                                unsigned int value);
+ * 	void add_interrupt_randomness(int irq);
+ *
+ * add_input_randomness() uses the input layer interrupt timing, as well as
+ * the event type information from the hardware.
+ *
+ * add_interrupt_randomness() uses the inter-interrupt timing as random
+ * inputs to the entropy pool.  Note that not all interrupts are good
+ * sources of randomness!  For example, the timer interrupts is not a
+ * good choice, because the periodicity of the interrupts is too
+ * regular, and hence predictable to an attacker.  Disk interrupts are
+ * a better measure, since the timing of the disk interrupts are more
+ * unpredictable.
+ *
+ * All of these routines try to estimate how many bits of randomness a
+ * particular randomness source.  They do this by keeping track of the
+ * first and second order deltas of the event timings.
+ *
+ * Ensuring unpredictability at system startup
+ * ============================================
+ *
+ * When any operating system starts up, it will go through a sequence
+ * of actions that are fairly predictable by an adversary, especially
+ * if the start-up does not involve interaction with a human operator.
+ * This reduces the actual number of bits of unpredictability in the
+ * entropy pool below the value in entropy_count.  In order to
+ * counteract this effect, it helps to carry information in the
+ * entropy pool across shut-downs and start-ups.  To do this, put the
+ * following lines an appropriate script which is run during the boot
+ * sequence:
+ *
+ *	echo "Initializing random number generator..."
+ *	random_seed=/var/run/random-seed
+ *	# Carry a random seed from start-up to start-up
+ *	# Load and then save the whole entropy pool
+ *	if [ -f $random_seed ]; then
+ *		cat $random_seed >/dev/urandom
+ *	else
+ *		touch $random_seed
+ *	fi
+ *	chmod 600 $random_seed
+ *	dd if=/dev/urandom of=$random_seed count=1 bs=512
+ *
+ * and the following lines in an appropriate script which is run as
+ * the system is shutdown:
+ *
+ *	# Carry a random seed from shut-down to start-up
+ *	# Save the whole entropy pool
+ *	echo "Saving random seed..."
+ *	random_seed=/var/run/random-seed
+ *	touch $random_seed
+ *	chmod 600 $random_seed
+ *	dd if=/dev/urandom of=$random_seed count=1 bs=512
+ *
+ * For example, on most modern systems using the System V init
+ * scripts, such code fragments would be found in
+ * /etc/rc.d/init.d/random.  On older Linux systems, the correct script
+ * location might be in /etc/rcb.d/rc.local or /etc/rc.d/rc.0.
+ *
+ * Effectively, these commands cause the contents of the entropy pool
+ * to be saved at shut-down time and reloaded into the entropy pool at
+ * start-up.  (The 'dd' in the addition to the bootup script is to
+ * make sure that /etc/random-seed is different for every start-up,
+ * even if the system crashes without executing rc.0.)  Even with
+ * complete knowledge of the start-up activities, predicting the state
+ * of the entropy pool requires knowledge of the previous history of
+ * the system.
+ *
+ * Configuring the /dev/random driver under Linux
+ * ==============================================
+ *
+ * The /dev/random driver under Linux uses minor numbers 8 and 9 of
+ * the /dev/mem major number (#1).  So if your system does not have
+ * /dev/random and /dev/urandom created already, they can be created
+ * by using the commands:
+ *
+ * 	mknod /dev/random c 1 8
+ * 	mknod /dev/urandom c 1 9
+ *
+ * Acknowledgements:
+ * =================
+ *
+ * Ideas for constructing this random number generator were derived
+ * from Pretty Good Privacy's random number generator, and from private
+ * discussions with Phil Karn.  Colin Plumb provided a faster random
+ * number generator, which speed up the mixing function of the entropy
+ * pool, taken from PGPfone.  Dale Worley has also contributed many
+ * useful ideas and suggestions to improve this driver.
+ *
+ * Any flaws in the design are solely my responsibility, and should
+ * not be attributed to the Phil, Colin, or any of authors of PGP.
+ *
+ * Further background information on this topic may be obtained from
+ * RFC 1750, "Randomness Recommendations for Security", by Donald
+ * Eastlake, Steve Crocker, and Jeff Schiller.
+ */
+
+#include <linux/utsname.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/genhd.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/percpu.h>
+#include <linux/cryptohash.h>
+
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+
+/*
+ * Configuration information
+ */
+#define INPUT_POOL_WORDS 128
+#define OUTPUT_POOL_WORDS 32
+#define SEC_XFER_SIZE 512
+
+/*
+ * The minimum number of bits of entropy before we wake up a read on
+ * /dev/random.  Should be enough to do a significant reseed.
+ */
+static int random_read_wakeup_thresh = 64;
+
+/*
+ * If the entropy count falls under this number of bits, then we
+ * should wake up processes which are selecting or polling on write
+ * access to /dev/random.
+ */
+static int random_write_wakeup_thresh = 128;
+
+/*
+ * When the input pool goes over trickle_thresh, start dropping most
+ * samples to avoid wasting CPU time and reduce lock contention.
+ */
+
+static int trickle_thresh = INPUT_POOL_WORDS * 28;
+
+static DEFINE_PER_CPU(int, trickle_count) = 0;
+
+/*
+ * A pool of size .poolwords is stirred with a primitive polynomial
+ * of degree .poolwords over GF(2).  The taps for various sizes are
+ * defined below.  They are chosen to be evenly spaced (minimum RMS
+ * distance from evenly spaced; the numbers in the comments are a
+ * scaled squared error sum) except for the last tap, which is 1 to
+ * get the twisting happening as fast as possible.
+ */
+static struct poolinfo {
+	int poolwords;
+	int tap1, tap2, tap3, tap4, tap5;
+} poolinfo_table[] = {
+	/* x^128 + x^103 + x^76 + x^51 +x^25 + x + 1 -- 105 */
+	{ 128,	103,	76,	51,	25,	1 },
+	/* x^32 + x^26 + x^20 + x^14 + x^7 + x + 1 -- 15 */
+	{ 32,	26,	20,	14,	7,	1 },
+#if 0
+	/* x^2048 + x^1638 + x^1231 + x^819 + x^411 + x + 1  -- 115 */
+	{ 2048,	1638,	1231,	819,	411,	1 },
+
+	/* x^1024 + x^817 + x^615 + x^412 + x^204 + x + 1 -- 290 */
+	{ 1024,	817,	615,	412,	204,	1 },
+
+	/* x^1024 + x^819 + x^616 + x^410 + x^207 + x^2 + 1 -- 115 */
+	{ 1024,	819,	616,	410,	207,	2 },
+
+	/* x^512 + x^411 + x^308 + x^208 + x^104 + x + 1 -- 225 */
+	{ 512,	411,	308,	208,	104,	1 },
+
+	/* x^512 + x^409 + x^307 + x^206 + x^102 + x^2 + 1 -- 95 */
+	{ 512,	409,	307,	206,	102,	2 },
+	/* x^512 + x^409 + x^309 + x^205 + x^103 + x^2 + 1 -- 95 */
+	{ 512,	409,	309,	205,	103,	2 },
+
+	/* x^256 + x^205 + x^155 + x^101 + x^52 + x + 1 -- 125 */
+	{ 256,	205,	155,	101,	52,	1 },
+
+	/* x^128 + x^103 + x^78 + x^51 + x^27 + x^2 + 1 -- 70 */
+	{ 128,	103,	78,	51,	27,	2 },
+
+	/* x^64 + x^52 + x^39 + x^26 + x^14 + x + 1 -- 15 */
+	{ 64,	52,	39,	26,	14,	1 },
+#endif
+};
+
+#define POOLBITS	poolwords*32
+#define POOLBYTES	poolwords*4
+
+/*
+ * For the purposes of better mixing, we use the CRC-32 polynomial as
+ * well to make a twisted Generalized Feedback Shift Reigster
+ *
+ * (See M. Matsumoto & Y. Kurita, 1992.  Twisted GFSR generators.  ACM
+ * Transactions on Modeling and Computer Simulation 2(3):179-194.
+ * Also see M. Matsumoto & Y. Kurita, 1994.  Twisted GFSR generators
+ * II.  ACM Transactions on Mdeling and Computer Simulation 4:254-266)
+ *
+ * Thanks to Colin Plumb for suggesting this.
+ *
+ * We have not analyzed the resultant polynomial to prove it primitive;
+ * in fact it almost certainly isn't.  Nonetheless, the irreducible factors
+ * of a random large-degree polynomial over GF(2) are more than large enough
+ * that periodicity is not a concern.
+ *
+ * The input hash is much less sensitive than the output hash.  All
+ * that we want of it is that it be a good non-cryptographic hash;
+ * i.e. it not produce collisions when fed "random" data of the sort
+ * we expect to see.  As long as the pool state differs for different
+ * inputs, we have preserved the input entropy and done a good job.
+ * The fact that an intelligent attacker can construct inputs that
+ * will produce controlled alterations to the pool's state is not
+ * important because we don't consider such inputs to contribute any
+ * randomness.  The only property we need with respect to them is that
+ * the attacker can't increase his/her knowledge of the pool's state.
+ * Since all additions are reversible (knowing the final state and the
+ * input, you can reconstruct the initial state), if an attacker has
+ * any uncertainty about the initial state, he/she can only shuffle
+ * that uncertainty about, but never cause any collisions (which would
+ * decrease the uncertainty).
+ *
+ * The chosen system lets the state of the pool be (essentially) the input
+ * modulo the generator polymnomial.  Now, for random primitive polynomials,
+ * this is a universal class of hash functions, meaning that the chance
+ * of a collision is limited by the attacker's knowledge of the generator
+ * polynomail, so if it is chosen at random, an attacker can never force
+ * a collision.  Here, we use a fixed polynomial, but we *can* assume that
+ * ###--> it is unknown to the processes generating the input entropy. <-###
+ * Because of this important property, this is a good, collision-resistant
+ * hash; hash collisions will occur no more often than chance.
+ */
+
+/*
+ * Static global variables
+ */
+static DECLARE_WAIT_QUEUE_HEAD(random_read_wait);
+static DECLARE_WAIT_QUEUE_HEAD(random_write_wait);
+
+#if 0
+static int debug = 0;
+module_param(debug, bool, 0644);
+#define DEBUG_ENT(fmt, arg...) do { if (debug) \
+	printk(KERN_DEBUG "random %04d %04d %04d: " \
+	fmt,\
+	input_pool.entropy_count,\
+	blocking_pool.entropy_count,\
+	nonblocking_pool.entropy_count,\
+	## arg); } while (0)
+#else
+#define DEBUG_ENT(fmt, arg...) do {} while (0)
+#endif
+
+/**********************************************************************
+ *
+ * OS independent entropy store.   Here are the functions which handle
+ * storing entropy in an entropy pool.
+ *
+ **********************************************************************/
+
+struct entropy_store;
+struct entropy_store {
+	/* mostly-read data: */
+	struct poolinfo *poolinfo;
+	__u32 *pool;
+	const char *name;
+	int limit;
+	struct entropy_store *pull;
+
+	/* read-write data: */
+	spinlock_t lock ____cacheline_aligned_in_smp;
+	unsigned add_ptr;
+	int entropy_count;
+	int input_rotate;
+};
+
+static __u32 input_pool_data[INPUT_POOL_WORDS];
+static __u32 blocking_pool_data[OUTPUT_POOL_WORDS];
+static __u32 nonblocking_pool_data[OUTPUT_POOL_WORDS];
+
+static struct entropy_store input_pool = {
+	.poolinfo = &poolinfo_table[0],
+	.name = "input",
+	.limit = 1,
+	.lock = SPIN_LOCK_UNLOCKED,
+	.pool = input_pool_data
+};
+
+static struct entropy_store blocking_pool = {
+	.poolinfo = &poolinfo_table[1],
+	.name = "blocking",
+	.limit = 1,
+	.pull = &input_pool,
+	.lock = SPIN_LOCK_UNLOCKED,
+	.pool = blocking_pool_data
+};
+
+static struct entropy_store nonblocking_pool = {
+	.poolinfo = &poolinfo_table[1],
+	.name = "nonblocking",
+	.pull = &input_pool,
+	.lock = SPIN_LOCK_UNLOCKED,
+	.pool = nonblocking_pool_data
+};
+
+/*
+ * This function adds a byte into the entropy "pool".  It does not
+ * update the entropy estimate.  The caller should call
+ * credit_entropy_store if this is appropriate.
+ *
+ * The pool is stirred with a primitive polynomial of the appropriate
+ * degree, and then twisted.  We twist by three bits at a time because
+ * it's cheap to do so and helps slightly in the expected case where
+ * the entropy is concentrated in the low-order bits.
+ */
+static void __add_entropy_words(struct entropy_store *r, const __u32 *in,
+				int nwords, __u32 out[16])
+{
+	static __u32 const twist_table[8] = {
+		0x00000000, 0x3b6e20c8, 0x76dc4190, 0x4db26158,
+		0xedb88320, 0xd6d6a3e8, 0x9b64c2b0, 0xa00ae278 };
+	unsigned long i, add_ptr, tap1, tap2, tap3, tap4, tap5;
+	int new_rotate, input_rotate;
+	int wordmask = r->poolinfo->poolwords - 1;
+	__u32 w, next_w;
+	unsigned long flags;
+
+	/* Taps are constant, so we can load them without holding r->lock.  */
+	tap1 = r->poolinfo->tap1;
+	tap2 = r->poolinfo->tap2;
+	tap3 = r->poolinfo->tap3;
+	tap4 = r->poolinfo->tap4;
+	tap5 = r->poolinfo->tap5;
+	next_w = *in++;
+
+	spin_lock_irqsave(&r->lock, flags);
+	prefetch_range(r->pool, wordmask);
+	input_rotate = r->input_rotate;
+	add_ptr = r->add_ptr;
+
+	while (nwords--) {
+		w = rol32(next_w, input_rotate);
+		if (nwords > 0)
+			next_w = *in++;
+		i = add_ptr = (add_ptr - 1) & wordmask;
+		/*
+		 * Normally, we add 7 bits of rotation to the pool.
+		 * At the beginning of the pool, add an extra 7 bits
+		 * rotation, so that successive passes spread the
+		 * input bits across the pool evenly.
+		 */
+		new_rotate = input_rotate + 14;
+		if (i)
+			new_rotate = input_rotate + 7;
+		input_rotate = new_rotate & 31;
+
+		/* XOR in the various taps */
+		w ^= r->pool[(i + tap1) & wordmask];
+		w ^= r->pool[(i + tap2) & wordmask];
+		w ^= r->pool[(i + tap3) & wordmask];
+		w ^= r->pool[(i + tap4) & wordmask];
+		w ^= r->pool[(i + tap5) & wordmask];
+		w ^= r->pool[i];
+		r->pool[i] = (w >> 3) ^ twist_table[w & 7];
+	}
+
+	r->input_rotate = input_rotate;
+	r->add_ptr = add_ptr;
+
+	if (out) {
+		for (i = 0; i < 16; i++) {
+			out[i] = r->pool[add_ptr];
+			add_ptr = (add_ptr - 1) & wordmask;
+		}
+	}
+
+	spin_unlock_irqrestore(&r->lock, flags);
+}
+
+static inline void add_entropy_words(struct entropy_store *r, const __u32 *in,
+				     int nwords)
+{
+	__add_entropy_words(r, in, nwords, NULL);
+}
+
+/*
+ * Credit (or debit) the entropy store with n bits of entropy
+ */
+static void credit_entropy_store(struct entropy_store *r, int nbits)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&r->lock, flags);
+
+	if (r->entropy_count + nbits < 0) {
+		DEBUG_ENT("negative entropy/overflow (%d+%d)\n",
+			  r->entropy_count, nbits);
+		r->entropy_count = 0;
+	} else if (r->entropy_count + nbits > r->poolinfo->POOLBITS) {
+		r->entropy_count = r->poolinfo->POOLBITS;
+	} else {
+		r->entropy_count += nbits;
+		if (nbits)
+			DEBUG_ENT("added %d entropy credits to %s\n",
+				  nbits, r->name);
+	}
+
+	spin_unlock_irqrestore(&r->lock, flags);
+}
+
+/*********************************************************************
+ *
+ * Entropy input management
+ *
+ *********************************************************************/
+
+/* There is one of these per entropy source */
+struct timer_rand_state {
+	cycles_t last_time;
+	long last_delta,last_delta2;
+	unsigned dont_count_entropy:1;
+};
+
+static struct timer_rand_state input_timer_state;
+static struct timer_rand_state *irq_timer_state[NR_IRQS];
+
+/*
+ * This function adds entropy to the entropy "pool" by using timing
+ * delays.  It uses the timer_rand_state structure to make an estimate
+ * of how many bits of entropy this call has added to the pool.
+ *
+ * The number "num" is also added to the pool - it should somehow describe
+ * the type of event which just happened.  This is currently 0-255 for
+ * keyboard scan codes, and 256 upwards for interrupts.
+ *
+ */
+static void add_timer_randomness(struct timer_rand_state *state, unsigned num)
+{
+	struct {
+		cycles_t cycles;
+		long jiffies;
+		unsigned num;
+	} sample;
+	long delta, delta2, delta3;
+
+	preempt_disable();
+	/* if over the trickle threshold, use only 1 in 4096 samples */
+	if (input_pool.entropy_count > trickle_thresh &&
+	    (__get_cpu_var(trickle_count)++ & 0xfff))
+		goto out;
+
+	sample.jiffies = jiffies;
+	sample.cycles = get_cycles();
+	sample.num = num;
+	add_entropy_words(&input_pool, (u32 *)&sample, sizeof(sample)/4);
+
+	/*
+	 * Calculate number of bits of randomness we probably added.
+	 * We take into account the first, second and third-order deltas
+	 * in order to make our estimate.
+	 */
+
+	if (!state->dont_count_entropy) {
+		delta = sample.jiffies - state->last_time;
+		state->last_time = sample.jiffies;
+
+		delta2 = delta - state->last_delta;
+		state->last_delta = delta;
+
+		delta3 = delta2 - state->last_delta2;
+		state->last_delta2 = delta2;
+
+		if (delta < 0)
+			delta = -delta;
+		if (delta2 < 0)
+			delta2 = -delta2;
+		if (delta3 < 0)
+			delta3 = -delta3;
+		if (delta > delta2)
+			delta = delta2;
+		if (delta > delta3)
+			delta = delta3;
+
+		/*
+		 * delta is now minimum absolute delta.
+		 * Round down by 1 bit on general principles,
+		 * and limit entropy entimate to 12 bits.
+		 */
+		credit_entropy_store(&input_pool,
+				     min_t(int, fls(delta>>1), 11));
+	}
+
+	if(input_pool.entropy_count >= random_read_wakeup_thresh)
+		wake_up_interruptible(&random_read_wait);
+
+out:
+	preempt_enable();
+}
+
+extern void add_input_randomness(unsigned int type, unsigned int code,
+				 unsigned int value)
+{
+	static unsigned char last_value;
+
+	/* ignore autorepeat and the like */
+	if (value == last_value)
+		return;
+
+	DEBUG_ENT("input event\n");
+	last_value = value;
+	add_timer_randomness(&input_timer_state,
+			     (type << 4) ^ code ^ (code >> 4) ^ value);
+}
+
+void add_interrupt_randomness(int irq)
+{
+	if (irq >= NR_IRQS || irq_timer_state[irq] == 0)
+		return;
+
+	DEBUG_ENT("irq event %d\n", irq);
+	add_timer_randomness(irq_timer_state[irq], 0x100 + irq);
+}
+
+void add_disk_randomness(struct gendisk *disk)
+{
+	if (!disk || !disk->random)
+		return;
+	/* first major is 1, so we get >= 0x200 here */
+	DEBUG_ENT("disk event %d:%d\n", disk->major, disk->first_minor);
+
+	add_timer_randomness(disk->random,
+			     0x100 + MKDEV(disk->major, disk->first_minor));
+}
+
+EXPORT_SYMBOL(add_disk_randomness);
+
+#define EXTRACT_SIZE 10
+
+/*********************************************************************
+ *
+ * Entropy extraction routines
+ *
+ *********************************************************************/
+
+static ssize_t extract_entropy(struct entropy_store *r, void * buf,
+			       size_t nbytes, int min, int rsvd);
+
+/*
+ * This utility inline function is responsible for transfering entropy
+ * from the primary pool to the secondary extraction pool. We make
+ * sure we pull enough for a 'catastrophic reseed'.
+ */
+static void xfer_secondary_pool(struct entropy_store *r, size_t nbytes)
+{
+	__u32 tmp[OUTPUT_POOL_WORDS];
+
+	if (r->pull && r->entropy_count < nbytes * 8 &&
+	    r->entropy_count < r->poolinfo->POOLBITS) {
+		int bytes = max_t(int, random_read_wakeup_thresh / 8,
+				min_t(int, nbytes, sizeof(tmp)));
+		int rsvd = r->limit ? 0 : random_read_wakeup_thresh/4;
+
+		DEBUG_ENT("going to reseed %s with %d bits "
+			  "(%d of %d requested)\n",
+			  r->name, bytes * 8, nbytes * 8, r->entropy_count);
+
+		bytes=extract_entropy(r->pull, tmp, bytes,
+				      random_read_wakeup_thresh / 8, rsvd);
+		add_entropy_words(r, tmp, (bytes + 3) / 4);
+		credit_entropy_store(r, bytes*8);
+	}
+}
+
+/*
+ * These functions extracts randomness from the "entropy pool", and
+ * returns it in a buffer.
+ *
+ * The min parameter specifies the minimum amount we can pull before
+ * failing to avoid races that defeat catastrophic reseeding while the
+ * reserved parameter indicates how much entropy we must leave in the
+ * pool after each pull to avoid starving other readers.
+ *
+ * Note: extract_entropy() assumes that .poolwords is a multiple of 16 words.
+ */
+
+static size_t account(struct entropy_store *r, size_t nbytes, int min,
+		      int reserved)
+{
+	unsigned long flags;
+
+	BUG_ON(r->entropy_count > r->poolinfo->POOLBITS);
+
+	/* Hold lock while accounting */
+	spin_lock_irqsave(&r->lock, flags);
+
+	DEBUG_ENT("trying to extract %d bits from %s\n",
+		  nbytes * 8, r->name);
+
+	/* Can we pull enough? */
+	if (r->entropy_count / 8 < min + reserved) {
+		nbytes = 0;
+	} else {
+		/* If limited, never pull more than available */
+		if (r->limit && nbytes + reserved >= r->entropy_count / 8)
+			nbytes = r->entropy_count/8 - reserved;
+
+		if(r->entropy_count / 8 >= nbytes + reserved)
+			r->entropy_count -= nbytes*8;
+		else
+			r->entropy_count = reserved;
+
+		if (r->entropy_count < random_write_wakeup_thresh)
+			wake_up_interruptible(&random_write_wait);
+	}
+
+	DEBUG_ENT("debiting %d entropy credits from %s%s\n",
+		  nbytes * 8, r->name, r->limit ? "" : " (unlimited)");
+
+	spin_unlock_irqrestore(&r->lock, flags);
+
+	return nbytes;
+}
+
+static void extract_buf(struct entropy_store *r, __u8 *out)
+{
+	int i, x;
+	__u32 data[16], buf[5 + SHA_WORKSPACE_WORDS];
+
+	sha_init(buf);
+	/*
+	 * As we hash the pool, we mix intermediate values of
+	 * the hash back into the pool.  This eliminates
+	 * backtracking attacks (where the attacker knows
+	 * the state of the pool plus the current outputs, and
+	 * attempts to find previous ouputs), unless the hash
+	 * function can be inverted.
+	 */
+	for (i = 0, x = 0; i < r->poolinfo->poolwords; i += 16, x+=2) {
+		sha_transform(buf, (__u8 *)r->pool+i, buf + 5);
+		add_entropy_words(r, &buf[x % 5], 1);
+	}
+
+	/*
+	 * To avoid duplicates, we atomically extract a
+	 * portion of the pool while mixing, and hash one
+	 * final time.
+	 */
+	__add_entropy_words(r, &buf[x % 5], 1, data);
+	sha_transform(buf, (__u8 *)data, buf + 5);
+
+	/*
+	 * In case the hash function has some recognizable
+	 * output pattern, we fold it in half.
+	 */
+
+	buf[0] ^= buf[3];
+	buf[1] ^= buf[4];
+	buf[0] ^= rol32(buf[3], 16);
+	memcpy(out, buf, EXTRACT_SIZE);
+	memset(buf, 0, sizeof(buf));
+}
+
+static ssize_t extract_entropy(struct entropy_store *r, void * buf,
+			       size_t nbytes, int min, int reserved)
+{
+	ssize_t ret = 0, i;
+	__u8 tmp[EXTRACT_SIZE];
+
+	xfer_secondary_pool(r, nbytes);
+	nbytes = account(r, nbytes, min, reserved);
+
+	while (nbytes) {
+		extract_buf(r, tmp);
+		i = min_t(int, nbytes, EXTRACT_SIZE);
+		memcpy(buf, tmp, i);
+		nbytes -= i;
+		buf += i;
+		ret += i;
+	}
+
+	/* Wipe data just returned from memory */
+	memset(tmp, 0, sizeof(tmp));
+
+	return ret;
+}
+
+static ssize_t extract_entropy_user(struct entropy_store *r, void __user *buf,
+				    size_t nbytes)
+{
+	ssize_t ret = 0, i;
+	__u8 tmp[EXTRACT_SIZE];
+
+	xfer_secondary_pool(r, nbytes);
+	nbytes = account(r, nbytes, 0, 0);
+
+	while (nbytes) {
+		if (need_resched()) {
+			if (signal_pending(current)) {
+				if (ret == 0)
+					ret = -ERESTARTSYS;
+				break;
+			}
+			schedule();
+		}
+
+		extract_buf(r, tmp);
+		i = min_t(int, nbytes, EXTRACT_SIZE);
+		if (copy_to_user(buf, tmp, i)) {
+			ret = -EFAULT;
+			break;
+		}
+
+		nbytes -= i;
+		buf += i;
+		ret += i;
+	}
+
+	/* Wipe data just returned from memory */
+	memset(tmp, 0, sizeof(tmp));
+
+	return ret;
+}
+
+/*
+ * This function is the exported kernel interface.  It returns some
+ * number of good random numbers, suitable for seeding TCP sequence
+ * numbers, etc.
+ */
+void get_random_bytes(void *buf, int nbytes)
+{
+	extract_entropy(&nonblocking_pool, buf, nbytes, 0, 0);
+}
+
+EXPORT_SYMBOL(get_random_bytes);
+
+/*
+ * init_std_data - initialize pool with system data
+ *
+ * @r: pool to initialize
+ *
+ * This function clears the pool's entropy count and mixes some system
+ * data into the pool to prepare it for use. The pool is not cleared
+ * as that can only decrease the entropy in the pool.
+ */
+static void init_std_data(struct entropy_store *r)
+{
+	struct timeval tv;
+	unsigned long flags;
+
+	spin_lock_irqsave(&r->lock, flags);
+	r->entropy_count = 0;
+	spin_unlock_irqrestore(&r->lock, flags);
+
+	do_gettimeofday(&tv);
+	add_entropy_words(r, (__u32 *)&tv, sizeof(tv)/4);
+	add_entropy_words(r, (__u32 *)&system_utsname,
+			  sizeof(system_utsname)/4);
+}
+
+static int __init rand_initialize(void)
+{
+	init_std_data(&input_pool);
+	init_std_data(&blocking_pool);
+	init_std_data(&nonblocking_pool);
+	return 0;
+}
+module_init(rand_initialize);
+
+void rand_initialize_irq(int irq)
+{
+	struct timer_rand_state *state;
+
+	if (irq >= NR_IRQS || irq_timer_state[irq])
+		return;
+
+	/*
+	 * If kmalloc returns null, we just won't use that entropy
+	 * source.
+	 */
+	state = kmalloc(sizeof(struct timer_rand_state), GFP_KERNEL);
+	if (state) {
+		memset(state, 0, sizeof(struct timer_rand_state));
+		irq_timer_state[irq] = state;
+	}
+}
+
+void rand_initialize_disk(struct gendisk *disk)
+{
+	struct timer_rand_state *state;
+
+	/*
+	 * If kmalloc returns null, we just won't use that entropy
+	 * source.
+	 */
+	state = kmalloc(sizeof(struct timer_rand_state), GFP_KERNEL);
+	if (state) {
+		memset(state, 0, sizeof(struct timer_rand_state));
+		disk->random = state;
+	}
+}
+
+static ssize_t
+random_read(struct file * file, char __user * buf, size_t nbytes, loff_t *ppos)
+{
+	ssize_t n, retval = 0, count = 0;
+
+	if (nbytes == 0)
+		return 0;
+
+	while (nbytes > 0) {
+		n = nbytes;
+		if (n > SEC_XFER_SIZE)
+			n = SEC_XFER_SIZE;
+
+		DEBUG_ENT("reading %d bits\n", n*8);
+
+		n = extract_entropy_user(&blocking_pool, buf, n);
+
+		DEBUG_ENT("read got %d bits (%d still needed)\n",
+			  n*8, (nbytes-n)*8);
+
+		if (n == 0) {
+			if (file->f_flags & O_NONBLOCK) {
+				retval = -EAGAIN;
+				break;
+			}
+
+			DEBUG_ENT("sleeping?\n");
+
+			wait_event_interruptible(random_read_wait,
+				input_pool.entropy_count >=
+						 random_read_wakeup_thresh);
+
+			DEBUG_ENT("awake\n");
+
+			if (signal_pending(current)) {
+				retval = -ERESTARTSYS;
+				break;
+			}
+
+			continue;
+		}
+
+		if (n < 0) {
+			retval = n;
+			break;
+		}
+		count += n;
+		buf += n;
+		nbytes -= n;
+		break;		/* This break makes the device work */
+				/* like a named pipe */
+	}
+
+	/*
+	 * If we gave the user some bytes, update the access time.
+	 */
+	if (count)
+		file_accessed(file);
+
+	return (count ? count : retval);
+}
+
+static ssize_t
+urandom_read(struct file * file, char __user * buf,
+		      size_t nbytes, loff_t *ppos)
+{
+	return extract_entropy_user(&nonblocking_pool, buf, nbytes);
+}
+
+static unsigned int
+random_poll(struct file *file, poll_table * wait)
+{
+	unsigned int mask;
+
+	poll_wait(file, &random_read_wait, wait);
+	poll_wait(file, &random_write_wait, wait);
+	mask = 0;
+	if (input_pool.entropy_count >= random_read_wakeup_thresh)
+		mask |= POLLIN | POLLRDNORM;
+	if (input_pool.entropy_count < random_write_wakeup_thresh)
+		mask |= POLLOUT | POLLWRNORM;
+	return mask;
+}
+
+static ssize_t
+random_write(struct file * file, const char __user * buffer,
+	     size_t count, loff_t *ppos)
+{
+	int ret = 0;
+	size_t bytes;
+	__u32 buf[16];
+	const char __user *p = buffer;
+	size_t c = count;
+
+	while (c > 0) {
+		bytes = min(c, sizeof(buf));
+
+		bytes -= copy_from_user(&buf, p, bytes);
+		if (!bytes) {
+			ret = -EFAULT;
+			break;
+		}
+		c -= bytes;
+		p += bytes;
+
+		add_entropy_words(&input_pool, buf, (bytes + 3) / 4);
+	}
+	if (p == buffer) {
+		return (ssize_t)ret;
+	} else {
+		struct inode *inode = file->f_dentry->d_inode;
+	        inode->i_mtime = current_fs_time(inode->i_sb);
+		mark_inode_dirty(inode);
+		return (ssize_t)(p - buffer);
+	}
+}
+
+static int
+random_ioctl(struct inode * inode, struct file * file,
+	     unsigned int cmd, unsigned long arg)
+{
+	int size, ent_count;
+	int __user *p = (int __user *)arg;
+	int retval;
+
+	switch (cmd) {
+	case RNDGETENTCNT:
+		ent_count = input_pool.entropy_count;
+		if (put_user(ent_count, p))
+			return -EFAULT;
+		return 0;
+	case RNDADDTOENTCNT:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (get_user(ent_count, p))
+			return -EFAULT;
+		credit_entropy_store(&input_pool, ent_count);
+		/*
+		 * Wake up waiting processes if we have enough
+		 * entropy.
+		 */
+		if (input_pool.entropy_count >= random_read_wakeup_thresh)
+			wake_up_interruptible(&random_read_wait);
+		return 0;
+	case RNDADDENTROPY:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (get_user(ent_count, p++))
+			return -EFAULT;
+		if (ent_count < 0)
+			return -EINVAL;
+		if (get_user(size, p++))
+			return -EFAULT;
+		retval = random_write(file, (const char __user *) p,
+				      size, &file->f_pos);
+		if (retval < 0)
+			return retval;
+		credit_entropy_store(&input_pool, ent_count);
+		/*
+		 * Wake up waiting processes if we have enough
+		 * entropy.
+		 */
+		if (input_pool.entropy_count >= random_read_wakeup_thresh)
+			wake_up_interruptible(&random_read_wait);
+		return 0;
+	case RNDZAPENTCNT:
+	case RNDCLEARPOOL:
+		/* Clear the entropy pool counters. */
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		init_std_data(&input_pool);
+		init_std_data(&blocking_pool);
+		init_std_data(&nonblocking_pool);
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+struct file_operations random_fops = {
+	.read  = random_read,
+	.write = random_write,
+	.poll  = random_poll,
+	.ioctl = random_ioctl,
+};
+
+struct file_operations urandom_fops = {
+	.read  = urandom_read,
+	.write = random_write,
+	.ioctl = random_ioctl,
+};
+
+/***************************************************************
+ * Random UUID interface
+ *
+ * Used here for a Boot ID, but can be useful for other kernel
+ * drivers.
+ ***************************************************************/
+
+/*
+ * Generate random UUID
+ */
+void generate_random_uuid(unsigned char uuid_out[16])
+{
+	get_random_bytes(uuid_out, 16);
+	/* Set UUID version to 4 --- truely random generation */
+	uuid_out[6] = (uuid_out[6] & 0x0F) | 0x40;
+	/* Set the UUID variant to DCE */
+	uuid_out[8] = (uuid_out[8] & 0x3F) | 0x80;
+}
+
+EXPORT_SYMBOL(generate_random_uuid);
+
+/********************************************************************
+ *
+ * Sysctl interface
+ *
+ ********************************************************************/
+
+#ifdef CONFIG_SYSCTL
+
+#include <linux/sysctl.h>
+
+static int min_read_thresh = 8, min_write_thresh;
+static int max_read_thresh = INPUT_POOL_WORDS * 32;
+static int max_write_thresh = INPUT_POOL_WORDS * 32;
+static char sysctl_bootid[16];
+
+/*
+ * These functions is used to return both the bootid UUID, and random
+ * UUID.  The difference is in whether table->data is NULL; if it is,
+ * then a new UUID is generated and returned to the user.
+ *
+ * If the user accesses this via the proc interface, it will be returned
+ * as an ASCII string in the standard UUID format.  If accesses via the
+ * sysctl system call, it is returned as 16 bytes of binary data.
+ */
+static int proc_do_uuid(ctl_table *table, int write, struct file *filp,
+			void __user *buffer, size_t *lenp, loff_t *ppos)
+{
+	ctl_table fake_table;
+	unsigned char buf[64], tmp_uuid[16], *uuid;
+
+	uuid = table->data;
+	if (!uuid) {
+		uuid = tmp_uuid;
+		uuid[8] = 0;
+	}
+	if (uuid[8] == 0)
+		generate_random_uuid(uuid);
+
+	sprintf(buf, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-"
+		"%02x%02x%02x%02x%02x%02x",
+		uuid[0],  uuid[1],  uuid[2],  uuid[3],
+		uuid[4],  uuid[5],  uuid[6],  uuid[7],
+		uuid[8],  uuid[9],  uuid[10], uuid[11],
+		uuid[12], uuid[13], uuid[14], uuid[15]);
+	fake_table.data = buf;
+	fake_table.maxlen = sizeof(buf);
+
+	return proc_dostring(&fake_table, write, filp, buffer, lenp, ppos);
+}
+
+static int uuid_strategy(ctl_table *table, int __user *name, int nlen,
+			 void __user *oldval, size_t __user *oldlenp,
+			 void __user *newval, size_t newlen, void **context)
+{
+	unsigned char tmp_uuid[16], *uuid;
+	unsigned int len;
+
+	if (!oldval || !oldlenp)
+		return 1;
+
+	uuid = table->data;
+	if (!uuid) {
+		uuid = tmp_uuid;
+		uuid[8] = 0;
+	}
+	if (uuid[8] == 0)
+		generate_random_uuid(uuid);
+
+	if (get_user(len, oldlenp))
+		return -EFAULT;
+	if (len) {
+		if (len > 16)
+			len = 16;
+		if (copy_to_user(oldval, uuid, len) ||
+		    put_user(len, oldlenp))
+			return -EFAULT;
+	}
+	return 1;
+}
+
+static int sysctl_poolsize = INPUT_POOL_WORDS * 32;
+ctl_table random_table[] = {
+	{
+		.ctl_name 	= RANDOM_POOLSIZE,
+		.procname	= "poolsize",
+		.data		= &sysctl_poolsize,
+		.maxlen		= sizeof(int),
+		.mode		= 0444,
+		.proc_handler	= &proc_dointvec,
+	},
+	{
+		.ctl_name	= RANDOM_ENTROPY_COUNT,
+		.procname	= "entropy_avail",
+		.maxlen		= sizeof(int),
+		.mode		= 0444,
+		.proc_handler	= &proc_dointvec,
+		.data		= &input_pool.entropy_count,
+	},
+	{
+		.ctl_name	= RANDOM_READ_THRESH,
+		.procname	= "read_wakeup_threshold",
+		.data		= &random_read_wakeup_thresh,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec_minmax,
+		.strategy	= &sysctl_intvec,
+		.extra1		= &min_read_thresh,
+		.extra2		= &max_read_thresh,
+	},
+	{
+		.ctl_name	= RANDOM_WRITE_THRESH,
+		.procname	= "write_wakeup_threshold",
+		.data		= &random_write_wakeup_thresh,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec_minmax,
+		.strategy	= &sysctl_intvec,
+		.extra1		= &min_write_thresh,
+		.extra2		= &max_write_thresh,
+	},
+	{
+		.ctl_name	= RANDOM_BOOT_ID,
+		.procname	= "boot_id",
+		.data		= &sysctl_bootid,
+		.maxlen		= 16,
+		.mode		= 0444,
+		.proc_handler	= &proc_do_uuid,
+		.strategy	= &uuid_strategy,
+	},
+	{
+		.ctl_name	= RANDOM_UUID,
+		.procname	= "uuid",
+		.maxlen		= 16,
+		.mode		= 0444,
+		.proc_handler	= &proc_do_uuid,
+		.strategy	= &uuid_strategy,
+	},
+	{ .ctl_name = 0 }
+};
+#endif 	/* CONFIG_SYSCTL */
+
+/********************************************************************
+ *
+ * Random funtions for networking
+ *
+ ********************************************************************/
+
+/*
+ * TCP initial sequence number picking.  This uses the random number
+ * generator to pick an initial secret value.  This value is hashed
+ * along with the TCP endpoint information to provide a unique
+ * starting point for each pair of TCP endpoints.  This defeats
+ * attacks which rely on guessing the initial TCP sequence number.
+ * This algorithm was suggested by Steve Bellovin.
+ *
+ * Using a very strong hash was taking an appreciable amount of the total
+ * TCP connection establishment time, so this is a weaker hash,
+ * compensated for by changing the secret periodically.
+ */
+
+/* F, G and H are basic MD4 functions: selection, majority, parity */
+#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))
+#define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z)))
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+
+/*
+ * The generic round function.  The application is so specific that
+ * we don't bother protecting all the arguments with parens, as is generally
+ * good macro practice, in favor of extra legibility.
+ * Rotation is separate from addition to prevent recomputation
+ */
+#define ROUND(f, a, b, c, d, x, s)	\
+	(a += f(b, c, d) + x, a = (a << s) | (a >> (32 - s)))
+#define K1 0
+#define K2 013240474631UL
+#define K3 015666365641UL
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+
+static __u32 twothirdsMD4Transform (__u32 const buf[4], __u32 const in[12])
+{
+	__u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3];
+
+	/* Round 1 */
+	ROUND(F, a, b, c, d, in[ 0] + K1,  3);
+	ROUND(F, d, a, b, c, in[ 1] + K1,  7);
+	ROUND(F, c, d, a, b, in[ 2] + K1, 11);
+	ROUND(F, b, c, d, a, in[ 3] + K1, 19);
+	ROUND(F, a, b, c, d, in[ 4] + K1,  3);
+	ROUND(F, d, a, b, c, in[ 5] + K1,  7);
+	ROUND(F, c, d, a, b, in[ 6] + K1, 11);
+	ROUND(F, b, c, d, a, in[ 7] + K1, 19);
+	ROUND(F, a, b, c, d, in[ 8] + K1,  3);
+	ROUND(F, d, a, b, c, in[ 9] + K1,  7);
+	ROUND(F, c, d, a, b, in[10] + K1, 11);
+	ROUND(F, b, c, d, a, in[11] + K1, 19);
+
+	/* Round 2 */
+	ROUND(G, a, b, c, d, in[ 1] + K2,  3);
+	ROUND(G, d, a, b, c, in[ 3] + K2,  5);
+	ROUND(G, c, d, a, b, in[ 5] + K2,  9);
+	ROUND(G, b, c, d, a, in[ 7] + K2, 13);
+	ROUND(G, a, b, c, d, in[ 9] + K2,  3);
+	ROUND(G, d, a, b, c, in[11] + K2,  5);
+	ROUND(G, c, d, a, b, in[ 0] + K2,  9);
+	ROUND(G, b, c, d, a, in[ 2] + K2, 13);
+	ROUND(G, a, b, c, d, in[ 4] + K2,  3);
+	ROUND(G, d, a, b, c, in[ 6] + K2,  5);
+	ROUND(G, c, d, a, b, in[ 8] + K2,  9);
+	ROUND(G, b, c, d, a, in[10] + K2, 13);
+
+	/* Round 3 */
+	ROUND(H, a, b, c, d, in[ 3] + K3,  3);
+	ROUND(H, d, a, b, c, in[ 7] + K3,  9);
+	ROUND(H, c, d, a, b, in[11] + K3, 11);
+	ROUND(H, b, c, d, a, in[ 2] + K3, 15);
+	ROUND(H, a, b, c, d, in[ 6] + K3,  3);
+	ROUND(H, d, a, b, c, in[10] + K3,  9);
+	ROUND(H, c, d, a, b, in[ 1] + K3, 11);
+	ROUND(H, b, c, d, a, in[ 5] + K3, 15);
+	ROUND(H, a, b, c, d, in[ 9] + K3,  3);
+	ROUND(H, d, a, b, c, in[ 0] + K3,  9);
+	ROUND(H, c, d, a, b, in[ 4] + K3, 11);
+	ROUND(H, b, c, d, a, in[ 8] + K3, 15);
+
+	return buf[1] + b; /* "most hashed" word */
+	/* Alternative: return sum of all words? */
+}
+#endif
+
+#undef ROUND
+#undef F
+#undef G
+#undef H
+#undef K1
+#undef K2
+#undef K3
+
+/* This should not be decreased so low that ISNs wrap too fast. */
+#define REKEY_INTERVAL (300 * HZ)
+/*
+ * Bit layout of the tcp sequence numbers (before adding current time):
+ * bit 24-31: increased after every key exchange
+ * bit 0-23: hash(source,dest)
+ *
+ * The implementation is similar to the algorithm described
+ * in the Appendix of RFC 1185, except that
+ * - it uses a 1 MHz clock instead of a 250 kHz clock
+ * - it performs a rekey every 5 minutes, which is equivalent
+ * 	to a (source,dest) tulple dependent forward jump of the
+ * 	clock by 0..2^(HASH_BITS+1)
+ *
+ * Thus the average ISN wraparound time is 68 minutes instead of
+ * 4.55 hours.
+ *
+ * SMP cleanup and lock avoidance with poor man's RCU.
+ * 			Manfred Spraul <manfred@colorfullife.com>
+ *
+ */
+#define COUNT_BITS 8
+#define COUNT_MASK ((1 << COUNT_BITS) - 1)
+#define HASH_BITS 24
+#define HASH_MASK ((1 << HASH_BITS) - 1)
+
+static struct keydata {
+	__u32 count; /* already shifted to the final position */
+	__u32 secret[12];
+} ____cacheline_aligned ip_keydata[2];
+
+static unsigned int ip_cnt;
+
+static void rekey_seq_generator(void *private_);
+
+static DECLARE_WORK(rekey_work, rekey_seq_generator, NULL);
+
+/*
+ * Lock avoidance:
+ * The ISN generation runs lockless - it's just a hash over random data.
+ * State changes happen every 5 minutes when the random key is replaced.
+ * Synchronization is performed by having two copies of the hash function
+ * state and rekey_seq_generator always updates the inactive copy.
+ * The copy is then activated by updating ip_cnt.
+ * The implementation breaks down if someone blocks the thread
+ * that processes SYN requests for more than 5 minutes. Should never
+ * happen, and even if that happens only a not perfectly compliant
+ * ISN is generated, nothing fatal.
+ */
+static void rekey_seq_generator(void *private_)
+{
+	struct keydata *keyptr = &ip_keydata[1 ^ (ip_cnt & 1)];
+
+	get_random_bytes(keyptr->secret, sizeof(keyptr->secret));
+	keyptr->count = (ip_cnt & COUNT_MASK) << HASH_BITS;
+	smp_wmb();
+	ip_cnt++;
+	schedule_delayed_work(&rekey_work, REKEY_INTERVAL);
+}
+
+static inline struct keydata *get_keyptr(void)
+{
+	struct keydata *keyptr = &ip_keydata[ip_cnt & 1];
+
+	smp_rmb();
+
+	return keyptr;
+}
+
+static __init int seqgen_init(void)
+{
+	rekey_seq_generator(NULL);
+	return 0;
+}
+late_initcall(seqgen_init);
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+__u32 secure_tcpv6_sequence_number(__u32 *saddr, __u32 *daddr,
+				   __u16 sport, __u16 dport)
+{
+	struct timeval tv;
+	__u32 seq;
+	__u32 hash[12];
+	struct keydata *keyptr = get_keyptr();
+
+	/* The procedure is the same as for IPv4, but addresses are longer.
+	 * Thus we must use twothirdsMD4Transform.
+	 */
+
+	memcpy(hash, saddr, 16);
+	hash[4]=(sport << 16) + dport;
+	memcpy(&hash[5],keyptr->secret,sizeof(__u32) * 7);
+
+	seq = twothirdsMD4Transform(daddr, hash) & HASH_MASK;
+	seq += keyptr->count;
+
+	do_gettimeofday(&tv);
+	seq += tv.tv_usec + tv.tv_sec * 1000000;
+
+	return seq;
+}
+EXPORT_SYMBOL(secure_tcpv6_sequence_number);
+#endif
+
+/*  The code below is shamelessly stolen from secure_tcp_sequence_number().
+ *  All blames to Andrey V. Savochkin <saw@msu.ru>.
+ */
+__u32 secure_ip_id(__u32 daddr)
+{
+	struct keydata *keyptr;
+	__u32 hash[4];
+
+	keyptr = get_keyptr();
+
+	/*
+	 *  Pick a unique starting offset for each IP destination.
+	 *  The dest ip address is placed in the starting vector,
+	 *  which is then hashed with random data.
+	 */
+	hash[0] = daddr;
+	hash[1] = keyptr->secret[9];
+	hash[2] = keyptr->secret[10];
+	hash[3] = keyptr->secret[11];
+
+	return half_md4_transform(hash, keyptr->secret);
+}
+
+#ifdef CONFIG_INET
+
+__u32 secure_tcp_sequence_number(__u32 saddr, __u32 daddr,
+				 __u16 sport, __u16 dport)
+{
+	struct timeval tv;
+	__u32 seq;
+	__u32 hash[4];
+	struct keydata *keyptr = get_keyptr();
+
+	/*
+	 *  Pick a unique starting offset for each TCP connection endpoints
+	 *  (saddr, daddr, sport, dport).
+	 *  Note that the words are placed into the starting vector, which is
+	 *  then mixed with a partial MD4 over random data.
+	 */
+	hash[0]=saddr;
+	hash[1]=daddr;
+	hash[2]=(sport << 16) + dport;
+	hash[3]=keyptr->secret[11];
+
+	seq = half_md4_transform(hash, keyptr->secret) & HASH_MASK;
+	seq += keyptr->count;
+	/*
+	 *	As close as possible to RFC 793, which
+	 *	suggests using a 250 kHz clock.
+	 *	Further reading shows this assumes 2 Mb/s networks.
+	 *	For 10 Mb/s Ethernet, a 1 MHz clock is appropriate.
+	 *	That's funny, Linux has one built in!  Use it!
+	 *	(Networks are faster now - should this be increased?)
+	 */
+	do_gettimeofday(&tv);
+	seq += tv.tv_usec + tv.tv_sec * 1000000;
+#if 0
+	printk("init_seq(%lx, %lx, %d, %d) = %d\n",
+	       saddr, daddr, sport, dport, seq);
+#endif
+	return seq;
+}
+
+EXPORT_SYMBOL(secure_tcp_sequence_number);
+
+
+
+/* Generate secure starting point for ephemeral TCP port search */
+u32 secure_tcp_port_ephemeral(__u32 saddr, __u32 daddr, __u16 dport)
+{
+	struct keydata *keyptr = get_keyptr();
+	u32 hash[4];
+
+	/*
+	 *  Pick a unique starting offset for each ephemeral port search
+	 *  (saddr, daddr, dport) and 48bits of random data.
+	 */
+	hash[0] = saddr;
+	hash[1] = daddr;
+	hash[2] = dport ^ keyptr->secret[10];
+	hash[3] = keyptr->secret[11];
+
+	return half_md4_transform(hash, keyptr->secret);
+}
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+u32 secure_tcpv6_port_ephemeral(const __u32 *saddr, const __u32 *daddr, __u16 dport)
+{
+	struct keydata *keyptr = get_keyptr();
+	u32 hash[12];
+
+	memcpy(hash, saddr, 16);
+	hash[4] = dport;
+	memcpy(&hash[5],keyptr->secret,sizeof(__u32) * 7);
+
+	return twothirdsMD4Transform(daddr, hash);
+}
+EXPORT_SYMBOL(secure_tcpv6_port_ephemeral);
+#endif
+
+#endif /* CONFIG_INET */
+
+
+/*
+ * Get a random word for internal kernel use only. Similar to urandom but
+ * with the goal of minimal entropy pool depletion. As a result, the random
+ * value is not cryptographically secure but for several uses the cost of
+ * depleting entropy is too high
+ */
+unsigned int get_random_int(void)
+{
+	/*
+	 * Use IP's RNG. It suits our purpose perfectly: it re-keys itself
+	 * every second, from the entropy pool (and thus creates a limited
+	 * drain on it), and uses halfMD4Transform within the second. We
+	 * also mix it with jiffies and the PID:
+	 */
+	return secure_ip_id(current->pid + jiffies);
+}
+
+/*
+ * randomize_range() returns a start address such that
+ *
+ *    [...... <range> .....]
+ *  start                  end
+ *
+ * a <range> with size "len" starting at the return value is inside in the
+ * area defined by [start, end], but is otherwise randomized.
+ */
+unsigned long
+randomize_range(unsigned long start, unsigned long end, unsigned long len)
+{
+	unsigned long range = end - len - start;
+
+	if (end <= start + len)
+		return 0;
+	return PAGE_ALIGN(get_random_int() % range + start);
+}
diff --git a/drivers/char/raw.c b/drivers/char/raw.c
new file mode 100644
index 0000000..a2e33ec
--- /dev/null
+++ b/drivers/char/raw.c
@@ -0,0 +1,342 @@
+/*
+ * linux/drivers/char/raw.c
+ *
+ * Front-end raw character devices.  These can be bound to any block
+ * devices to provide genuine Unix raw character device semantics.
+ *
+ * We reserve minor number 0 for a control interface.  ioctl()s on this
+ * device are used to bind the other minor numbers to block devices.
+ */
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/major.h>
+#include <linux/blkdev.h>
+#include <linux/module.h>
+#include <linux/raw.h>
+#include <linux/capability.h>
+#include <linux/uio.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+
+#include <asm/uaccess.h>
+
+struct raw_device_data {
+	struct block_device *binding;
+	int inuse;
+};
+
+static struct class_simple *raw_class;
+static struct raw_device_data raw_devices[MAX_RAW_MINORS];
+static DECLARE_MUTEX(raw_mutex);
+static struct file_operations raw_ctl_fops;	     /* forward declaration */
+
+/*
+ * Open/close code for raw IO.
+ *
+ * We just rewrite the i_mapping for the /dev/raw/rawN file descriptor to
+ * point at the blockdev's address_space and set the file handle to use
+ * O_DIRECT.
+ *
+ * Set the device's soft blocksize to the minimum possible.  This gives the
+ * finest possible alignment and has no adverse impact on performance.
+ */
+static int raw_open(struct inode *inode, struct file *filp)
+{
+	const int minor = iminor(inode);
+	struct block_device *bdev;
+	int err;
+
+	if (minor == 0) {	/* It is the control device */
+		filp->f_op = &raw_ctl_fops;
+		return 0;
+	}
+
+	down(&raw_mutex);
+
+	/*
+	 * All we need to do on open is check that the device is bound.
+	 */
+	bdev = raw_devices[minor].binding;
+	err = -ENODEV;
+	if (!bdev)
+		goto out;
+	igrab(bdev->bd_inode);
+	err = blkdev_get(bdev, filp->f_mode, 0);
+	if (err)
+		goto out;
+	err = bd_claim(bdev, raw_open);
+	if (err)
+		goto out1;
+	err = set_blocksize(bdev, bdev_hardsect_size(bdev));
+	if (err)
+		goto out2;
+	filp->f_flags |= O_DIRECT;
+	filp->f_mapping = bdev->bd_inode->i_mapping;
+	if (++raw_devices[minor].inuse == 1)
+		filp->f_dentry->d_inode->i_mapping =
+			bdev->bd_inode->i_mapping;
+	filp->private_data = bdev;
+	up(&raw_mutex);
+	return 0;
+
+out2:
+	bd_release(bdev);
+out1:
+	blkdev_put(bdev);
+out:
+	up(&raw_mutex);
+	return err;
+}
+
+/*
+ * When the final fd which refers to this character-special node is closed, we
+ * make its ->mapping point back at its own i_data.
+ */
+static int raw_release(struct inode *inode, struct file *filp)
+{
+	const int minor= iminor(inode);
+	struct block_device *bdev;
+
+	down(&raw_mutex);
+	bdev = raw_devices[minor].binding;
+	if (--raw_devices[minor].inuse == 0) {
+		/* Here  inode->i_mapping == bdev->bd_inode->i_mapping  */
+		inode->i_mapping = &inode->i_data;
+		inode->i_mapping->backing_dev_info = &default_backing_dev_info;
+	}
+	up(&raw_mutex);
+
+	bd_release(bdev);
+	blkdev_put(bdev);
+	return 0;
+}
+
+/*
+ * Forward ioctls to the underlying block device.
+ */
+static int
+raw_ioctl(struct inode *inode, struct file *filp,
+		  unsigned int command, unsigned long arg)
+{
+	struct block_device *bdev = filp->private_data;
+
+	return ioctl_by_bdev(bdev, command, arg);
+}
+
+static void bind_device(struct raw_config_request *rq)
+{
+	class_simple_device_remove(MKDEV(RAW_MAJOR, rq->raw_minor));
+	class_simple_device_add(raw_class, MKDEV(RAW_MAJOR, rq->raw_minor),
+				      NULL, "raw%d", rq->raw_minor);
+}
+
+/*
+ * Deal with ioctls against the raw-device control interface, to bind
+ * and unbind other raw devices.
+ */
+static int raw_ctl_ioctl(struct inode *inode, struct file *filp,
+			unsigned int command, unsigned long arg)
+{
+	struct raw_config_request rq;
+	struct raw_device_data *rawdev;
+	int err = 0;
+
+	switch (command) {
+	case RAW_SETBIND:
+	case RAW_GETBIND:
+
+		/* First, find out which raw minor we want */
+
+		if (copy_from_user(&rq, (void __user *) arg, sizeof(rq))) {
+			err = -EFAULT;
+			goto out;
+		}
+
+		if (rq.raw_minor < 0 || rq.raw_minor >= MAX_RAW_MINORS) {
+			err = -EINVAL;
+			goto out;
+		}
+		rawdev = &raw_devices[rq.raw_minor];
+
+		if (command == RAW_SETBIND) {
+			dev_t dev;
+
+			/*
+			 * This is like making block devices, so demand the
+			 * same capability
+			 */
+			if (!capable(CAP_SYS_ADMIN)) {
+				err = -EPERM;
+				goto out;
+			}
+
+			/*
+			 * For now, we don't need to check that the underlying
+			 * block device is present or not: we can do that when
+			 * the raw device is opened.  Just check that the
+			 * major/minor numbers make sense.
+			 */
+
+			dev = MKDEV(rq.block_major, rq.block_minor);
+			if ((rq.block_major == 0 && rq.block_minor != 0) ||
+					MAJOR(dev) != rq.block_major ||
+					MINOR(dev) != rq.block_minor) {
+				err = -EINVAL;
+				goto out;
+			}
+
+			down(&raw_mutex);
+			if (rawdev->inuse) {
+				up(&raw_mutex);
+				err = -EBUSY;
+				goto out;
+			}
+			if (rawdev->binding) {
+				bdput(rawdev->binding);
+				module_put(THIS_MODULE);
+			}
+			if (rq.block_major == 0 && rq.block_minor == 0) {
+				/* unbind */
+				rawdev->binding = NULL;
+				class_simple_device_remove(MKDEV(RAW_MAJOR,
+								rq.raw_minor));
+			} else {
+				rawdev->binding = bdget(dev);
+				if (rawdev->binding == NULL)
+					err = -ENOMEM;
+				else {
+					__module_get(THIS_MODULE);
+					bind_device(&rq);
+				}
+			}
+			up(&raw_mutex);
+		} else {
+			struct block_device *bdev;
+
+			down(&raw_mutex);
+			bdev = rawdev->binding;
+			if (bdev) {
+				rq.block_major = MAJOR(bdev->bd_dev);
+				rq.block_minor = MINOR(bdev->bd_dev);
+			} else {
+				rq.block_major = rq.block_minor = 0;
+			}
+			up(&raw_mutex);
+			if (copy_to_user((void __user *)arg, &rq, sizeof(rq))) {
+				err = -EFAULT;
+				goto out;
+			}
+		}
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+out:
+	return err;
+}
+
+static ssize_t raw_file_write(struct file *file, const char __user *buf,
+				   size_t count, loff_t *ppos)
+{
+	struct iovec local_iov = {
+		.iov_base = (char __user *)buf,
+		.iov_len = count
+	};
+
+	return generic_file_write_nolock(file, &local_iov, 1, ppos);
+}
+
+static ssize_t raw_file_aio_write(struct kiocb *iocb, const char __user *buf,
+					size_t count, loff_t pos)
+{
+	struct iovec local_iov = {
+		.iov_base = (char __user *)buf,
+		.iov_len = count
+	};
+
+	return generic_file_aio_write_nolock(iocb, &local_iov, 1, &iocb->ki_pos);
+}
+
+
+static struct file_operations raw_fops = {
+	.read	=	generic_file_read,
+	.aio_read = 	generic_file_aio_read,
+	.write	=	raw_file_write,
+	.aio_write = 	raw_file_aio_write,
+	.open	=	raw_open,
+	.release=	raw_release,
+	.ioctl	=	raw_ioctl,
+	.readv	= 	generic_file_readv,
+	.writev	= 	generic_file_writev,
+	.owner	=	THIS_MODULE,
+};
+
+static struct file_operations raw_ctl_fops = {
+	.ioctl	=	raw_ctl_ioctl,
+	.open	=	raw_open,
+	.owner	=	THIS_MODULE,
+};
+
+static struct cdev raw_cdev = {
+	.kobj	=	{.name = "raw", },
+	.owner	=	THIS_MODULE,
+};
+
+static int __init raw_init(void)
+{
+	int i;
+	dev_t dev = MKDEV(RAW_MAJOR, 0);
+
+	if (register_chrdev_region(dev, MAX_RAW_MINORS, "raw"))
+		goto error;
+
+	cdev_init(&raw_cdev, &raw_fops);
+	if (cdev_add(&raw_cdev, dev, MAX_RAW_MINORS)) {
+		kobject_put(&raw_cdev.kobj);
+		unregister_chrdev_region(dev, MAX_RAW_MINORS);
+		goto error;
+	}
+
+	raw_class = class_simple_create(THIS_MODULE, "raw");
+	if (IS_ERR(raw_class)) {
+		printk(KERN_ERR "Error creating raw class.\n");
+		cdev_del(&raw_cdev);
+		unregister_chrdev_region(dev, MAX_RAW_MINORS);
+		goto error;
+	}
+	class_simple_device_add(raw_class, MKDEV(RAW_MAJOR, 0), NULL, "rawctl");
+
+	devfs_mk_cdev(MKDEV(RAW_MAJOR, 0),
+		      S_IFCHR | S_IRUGO | S_IWUGO,
+		      "raw/rawctl");
+	for (i = 1; i < MAX_RAW_MINORS; i++)
+		devfs_mk_cdev(MKDEV(RAW_MAJOR, i),
+			      S_IFCHR | S_IRUGO | S_IWUGO,
+			      "raw/raw%d", i);
+	return 0;
+
+error:
+	printk(KERN_ERR "error register raw device\n");
+	return 1;
+}
+
+static void __exit raw_exit(void)
+{
+	int i;
+
+	for (i = 1; i < MAX_RAW_MINORS; i++)
+		devfs_remove("raw/raw%d", i);
+	devfs_remove("raw/rawctl");
+	devfs_remove("raw");
+	class_simple_device_remove(MKDEV(RAW_MAJOR, 0));
+	class_simple_destroy(raw_class);
+	cdev_del(&raw_cdev);
+	unregister_chrdev_region(MKDEV(RAW_MAJOR, 0), MAX_RAW_MINORS);
+}
+
+module_init(raw_init);
+module_exit(raw_exit);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/rio/Makefile b/drivers/char/rio/Makefile
new file mode 100644
index 0000000..bce2bd1
--- /dev/null
+++ b/drivers/char/rio/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for the linux rio-subsystem.
+#
+# (C) R.E.Wolff@BitWizard.nl 
+# 
+# This file is GPL. See other files for the full Blurb. I'm lazy today. 
+#
+
+obj-$(CONFIG_RIO) += rio.o
+
+rio-objs := rio_linux.o rioinit.o rioboot.o riocmd.o rioctrl.o riointr.o \
+            rioparam.o riopcicopy.o rioroute.o riotable.o riotty.o
diff --git a/drivers/char/rio/board.h b/drivers/char/rio/board.h
new file mode 100644
index 0000000..0b397e1
--- /dev/null
+++ b/drivers/char/rio/board.h
@@ -0,0 +1,143 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources. 
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: board.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:07
+**	Retrieved	: 11/6/98 11:34:20
+**
+**  ident @(#)board.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef	__rio_board_h__
+#define	__rio_board_h__
+
+#ifdef SCCS_LABELS
+static char *_board_h_sccs_ = "@(#)board.h	1.2";
+#endif
+
+/*
+** board.h contains the definitions for the *hardware* of the host cards.
+** It describes the memory overlay for the dual port RAM area.
+*/
+
+#define	DP_SRAM1_SIZE	0x7C00
+#define	DP_SRAM2_SIZE	0x0200
+#define	DP_SRAM3_SIZE	0x7000
+#define	DP_SCRATCH_SIZE	0x1000
+#define	DP_PARMMAP_ADDR	0x01FE	/* offset into SRAM2 */
+#define	DP_STARTUP_ADDR	0x01F8	/* offset into SRAM2 */
+
+/*
+**	The shape of the Host Control area, at offset 0x7C00, Write Only
+*/
+struct s_Ctrl
+{
+	BYTE	DpCtl;				/* 7C00 */
+	BYTE	Dp_Unused2_[127];
+	BYTE	DpIntSet;			/* 7C80 */
+	BYTE	Dp_Unused3_[127];
+	BYTE	DpTpuReset;			/* 7D00 */
+	BYTE	Dp_Unused4_[127];
+	BYTE	DpIntReset;			/* 7D80 */
+	BYTE	Dp_Unused5_[127];
+};
+
+/*
+** The PROM data area on the host (0x7C00), Read Only
+*/
+struct s_Prom
+{
+	WORD	DpSlxCode[2];
+	WORD	DpRev;
+	WORD	Dp_Unused6_;
+	WORD	DpUniq[4];
+	WORD	DpJahre;
+	WORD	DpWoche;
+	WORD	DpHwFeature[5];
+	WORD	DpOemId;
+	WORD	DpSiggy[16];
+};
+
+/*
+** Union of the Ctrl and Prom areas
+*/
+union u_CtrlProm	/* This is the control/PROM area (0x7C00) */
+{
+	struct s_Ctrl	DpCtrl;
+	struct s_Prom	DpProm;
+};
+
+/*
+** The top end of memory!
+*/
+struct s_ParmMapS		/* Area containing Parm Map Pointer */
+{
+	BYTE	Dp_Unused8_[DP_PARMMAP_ADDR];
+	WORD	DpParmMapAd;
+};
+
+struct s_StartUpS
+{
+	BYTE    Dp_Unused9_[DP_STARTUP_ADDR];
+	BYTE	Dp_LongJump[0x4];
+	BYTE	Dp_Unused10_[2];
+	BYTE	Dp_ShortJump[0x2];
+};
+
+union u_Sram2ParmMap	/* This is the top of memory (0x7E00-0x7FFF) */
+{
+	BYTE	DpSramMem[DP_SRAM2_SIZE];
+	struct s_ParmMapS DpParmMapS;
+	struct s_StartUpS DpStartUpS;
+};
+
+/*
+**	This is the DP RAM overlay.
+*/
+struct DpRam
+{
+    BYTE 		 DpSram1[DP_SRAM1_SIZE];     /* 0000 - 7BFF */
+    union u_CtrlProm     DpCtrlProm;                 /* 7C00 - 7DFF */
+    union u_Sram2ParmMap DpSram2ParmMap;             /* 7E00 - 7FFF */
+    BYTE		 DpScratch[DP_SCRATCH_SIZE]; /* 8000 - 8FFF */
+    BYTE		 DpSram3[DP_SRAM3_SIZE];     /* 9000 - FFFF */
+};
+
+#define	DpControl	DpCtrlProm.DpCtrl.DpCtl
+#define	DpSetInt	DpCtrlProm.DpCtrl.DpIntSet
+#define	DpResetTpu	DpCtrlProm.DpCtrl.DpTpuReset
+#define	DpResetInt	DpCtrlProm.DpCtrl.DpIntReset
+
+#define	DpSlx		DpCtrlProm.DpProm.DpSlxCode
+#define	DpRevision	DpCtrlProm.DpProm.DpRev
+#define	DpUnique	DpCtrlProm.DpProm.DpUniq
+#define	DpYear		DpCtrlProm.DpProm.DpJahre
+#define	DpWeek		DpCtrlProm.DpProm.DpWoche
+#define	DpSignature	DpCtrlProm.DpProm.DpSiggy
+
+#define	DpParmMapR	DpSram2ParmMap.DpParmMapS.DpParmMapAd
+#define	DpSram2		DpSram2ParmMap.DpSramMem
+
+#endif
diff --git a/drivers/char/rio/bootpkt.h b/drivers/char/rio/bootpkt.h
new file mode 100644
index 0000000..c329aeb
--- /dev/null
+++ b/drivers/char/rio/bootpkt.h
@@ -0,0 +1,62 @@
+
+
+/****************************************************************************
+ *******                                                              *******
+ *******        B O O T    P A C K E T   H E A D E R   F I L E
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _pkt_h
+#define _pkt_h 1
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_bootpkt_h_sccs = "@(#)bootpkt.h	1.1" ;
+#endif
+#endif
+
+    /*************************************************
+     * Overlayed onto the Data fields of a regular
+     * Packet
+     ************************************************/
+typedef struct BOOT_PKT BOOT_PKT ;
+struct BOOT_PKT {
+                    short     seq_num ;
+                    char      data[10] ;
+                } ;
+
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/brates.h b/drivers/char/rio/brates.h
new file mode 100644
index 0000000..bd4fc84
--- /dev/null
+++ b/drivers/char/rio/brates.h
@@ -0,0 +1,107 @@
+/****************************************************************************
+ *******                                                              *******
+ *******		BRATES.H				      *******
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Jeremy Rolls
+ Date    : 1 Nov 1990
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _brates_h
+#ifndef lint
+/* static char * _brates_h_sccs = "@(#)brates.h	1.4"; */
+#endif
+#define _brates_h 1
+/* List of baud rate defines. Most are borrowed from /usr/include/sys/termio.h
+*/
+#ifndef INKERNEL
+
+#define	B0	0x00
+#define	B50	0x01
+#define	B75	0x02
+#define	B110	0x03
+#define	B134	0x04
+#define	B150	0x05
+#define	B200	0x06
+#define	B300	0x07
+#define	B600	0x08
+#define	B1200	0x09
+#define	B1800	0x0a
+#define	B2400	0x0b
+#define	B4800	0x0c
+#define	B9600	0x0d
+#define	B19200	0x0e
+#define	B38400	0x0f
+
+#endif
+
+/*
+** The following baudrates may or may not be defined
+** on various UNIX systems.
+** If they are not then we define them.
+** If they are then we do not define them ;-)
+**
+** This is appalling that we use same definitions as UNIX
+** for our own download code as there is no garuntee that
+** B57600 will be defined as 0x11 by a UNIX system....
+** Arghhhhh!!!!!!!!!!!!!!
+*/
+#if !defined(B56000)
+#define	B56000	0x10
+#endif
+
+#if !defined(B57600)
+#define	B57600	0x11
+#endif
+
+#if !defined(B64000)
+#define	B64000	0x12
+#endif
+
+#if !defined(B115200)
+#define	B115200	0x13
+#endif
+
+
+#if !defined(B2000)
+#define B2000	0x14
+#endif
+
+
+#define MAX_RATE B2000
+
+struct    baud_rate            /* Tag for baud rates */
+{
+     /* short    host_rate,*/        /* As passed by the driver */
+     short    divisor,          /* The divisor */
+              prescaler;        /* The pre-scaler */
+};
+
+#endif
diff --git a/drivers/char/rio/chan.h b/drivers/char/rio/chan.h
new file mode 100644
index 0000000..5b30654
--- /dev/null
+++ b/drivers/char/rio/chan.h
@@ -0,0 +1,33 @@
+/*
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+*/
+#ifndef _chan_h
+#define _chan_h
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_chan_h_sccs = "@(#)chan.h	1.1" ;
+#endif
+#endif
+
+#define Link0   0
+#define Link1   1
+#define Link2   2
+#define Link3   3
+
+#endif
diff --git a/drivers/char/rio/cirrus.h b/drivers/char/rio/cirrus.h
new file mode 100644
index 0000000..cf056a9
--- /dev/null
+++ b/drivers/char/rio/cirrus.h
@@ -0,0 +1,463 @@
+/****************************************************************************
+ *******                                                              *******
+ *******		CIRRUS.H				      *******
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Jeremy Rolls
+ Date    : 3 Aug 1990
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _cirrus_h
+#ifndef lint
+/* static char* _cirrus_h_sccs = "@(#)cirrus.h	1.16"; */
+#endif
+#define _cirrus_h 1
+
+#ifdef RTA
+#define	TO_UART	RX
+#define TO_DRIVER TX
+#endif
+
+#ifdef HOST
+#define	TO_UART	TX
+#define TO_DRIVER RX
+#endif
+#ifdef RTA
+/* Miscellaneous defines for CIRRUS addresses and related logic for
+   interrupts etc.
+*/
+#define	MAP(a)		((short *)(cirrus_base + (a)))
+#define outp(a,b)	(*MAP (a) =(b))
+#define inp(a)		((*MAP (a)) & 0xff)
+#define	CIRRUS_FIRST	(short*)0x7300
+#define	CIRRUS_SECOND	(short*)0x7200
+#define	CIRRUS_THIRD	(short*)0x7100
+#define	CIRRUS_FOURTH	(short*)0x7000
+#define	PORTS_ON_CIRRUS	4
+#define	CIRRUS_FIFO_SIZE	12
+#define	SPACE		0x20
+#define	TAB		0x09
+#define	LINE_FEED	0x0a
+#define	CARRIAGE_RETURN	0x0d
+#define	BACKSPACE	0x08
+#define	SPACES_IN_TABS	8
+#define	SEND_ESCAPE	0x00
+#define START_BREAK	0x81
+#define	TIMER_TICK	0x82
+#define STOP_BREAK	0x83
+#define BASE(a) ((a) < 4 ? (short*)CIRRUS_FIRST : ((a) < 8 ? (short *)CIRRUS_SECOND : ((a) < 12 ? (short*)CIRRUS_THIRD : (short *)CIRRUS_FOURTH)))
+#define txack1	((short *)0x7104) 
+#define rxack1	((short *)0x7102) 
+#define mdack1  ((short *)0x7106)
+#define txack2  ((short *)0x7006) 
+#define rxack2	((short *)0x7004) 
+#define mdack2  ((short *)0x7100) 
+#define int_latch       ((short *) 0x7800)
+#define int_status      ((short *) 0x7c00) 
+#define tx1_pending     0x20 
+#define rx1_pending     0x10 
+#define md1_pending     0x40 
+#define tx2_pending     0x02 
+#define rx2_pending     0x01 
+#define md2_pending     0x40 
+#define module1_bits	0x07
+#define module1_modern	0x08
+#define module2_bits	0x70
+#define module2_modern	0x80
+#define module_blank	0xf
+#define rs232_d25	0x0
+#define	rs232_rj45	0x1
+#define rs422_d25	0x3
+#define parallel	0x5
+
+#define	CLK0	0x00
+#define CLK1	0x01
+#define CLK2	0x02
+#define CLK3	0x03
+#define CLK4	0x04
+
+#define CIRRUS_REVC    0x42
+#define CIRRUS_REVE    0x44
+
+#define	TURNON	1
+#define TURNOFF 0
+
+/* The list of CIRRUS registers. 
+   NB. These registers are relative values on 8 bit boundaries whereas
+   on the RTA's the CIRRUS registers are on word boundaries. Use pointer
+   arithmetic (short *) to obtain the real addresses required */
+#define ccr	0x05	/* Channel Command Register     */
+#define ier	0x06	/* Interrupt Enable Register    */
+#define cor1	0x08	/* Channel Option Register 1    */
+#define cor2	0x09	/* Channel Option Register 2    */
+#define cor3	0x0a	/* Channel Option Register 3    */
+#define cor4	0x1e	/* Channel Option Register 4    */
+#define	cor5	0x1f	/* Channel Option Register 5	*/
+
+#define ccsr	0x0b	/* Channel Control Status Register */
+#define rdcr	0x0e	/* Receive Data Count Register  */
+#define tdcr	0x12	/* Transmit Data Count Register */
+#define mcor1	0x15	/* Modem Change Option Register 1 */
+#define mcor2	0x16	/* Modem Change Option Regsiter 2 */
+
+#define livr	0x18	/* Local Interrupt Vector Register */
+#define schr1	0x1a	/* Special Character Register 1 */
+#define schr2	0x1b	/* Special Character Register 2 */
+#define schr3	0x1c	/* Special Character Register 3 */
+#define schr4	0x1d	/* Special Character Register 4 */
+
+#define rtr	0x20    /* Receive Timer Register */
+#define rtpr	0x21	/* Receive Timeout Period Register */
+#define lnc	0x24	/* Lnext character */
+
+#define rivr	0x43	/* Receive Interrupt Vector Register    */
+#define tivr	0x42	/* Transmit Interrupt Vector Register   */
+#define mivr	0x41	/* Modem Interrupt Vector Register      */
+#define gfrcr	0x40	/* Global Firmware Revision code Reg    */
+#define ricr	0x44	/* Receive Interrupting Channel Reg     */
+#define ticr	0x45	/* Transmit Interrupting Channel Reg    */
+#define micr	0x46	/* Modem Interrupting Channel Register  */
+
+#define gcr	0x4b	/* Global configuration register*/
+#define misr    0x4c    /* Modem interrupt status register */
+
+#define rbusr	0x59
+#define tbusr	0x5a
+#define mbusr	0x5b
+
+#define eoir	0x60	/* End Of Interrupt Register */
+#define rdsr	0x62	/* Receive Data / Status Register */
+#define tdr	0x63	/* Transmit Data Register */
+#define svrr	0x67	/* Service Request Register */
+
+#define car	0x68	/* Channel Access Register */
+#define mir	0x69	/* Modem Interrupt Register */
+#define tir	0x6a	/* Transmit Interrupt Register */
+#define rir	0x6b	/* Receive Interrupt Register */
+#define msvr1	0x6c	/* Modem Signal Value Register 1 */
+#define msvr2	0x6d	/* Modem Signal Value Register 2*/
+#define psvr	0x6f	/* Printer Signal Value Register*/
+
+#define tbpr	0x72	/* Transmit Baud Rate Period Register */
+#define tcor	0x76	/* Transmit Clock Option Register */
+
+#define rbpr	0x78	/* Receive Baud Rate Period Register */
+#define rber	0x7a	/* Receive Baud Rate Extension Register */
+#define rcor	0x7c	/* Receive Clock Option Register*/
+#define ppr	0x7e	/* Prescalar Period Register    */
+
+/* Misc registers used for forcing the 1400 out of its reset woes */
+#define airl	0x6d
+#define airm	0x6e
+#define airh	0x6f
+#define btcr	0x66
+#define mtcr	0x6c
+#define tber	0x74
+
+#endif				/* #ifdef RTA */
+
+
+/* Bit fields for particular registers */
+
+/* GCR */
+#define GCR_SERIAL	0x00	/* Configure as serial channel */
+#define GCR_PARALLEL	0x80	/* Configure as parallel channel */
+
+/* RDSR - when status read from FIFO */
+#define	RDSR_BREAK		0x08	/* Break received */
+#define RDSR_TIMEOUT    	0x80    /* No new data timeout */
+#define RDSR_SC1  	  	0x10    /* Special char 1 (tx XON) matched */
+#define RDSR_SC2  	  	0x20    /* Special char 2 (tx XOFF) matched */
+#define RDSR_SC12_MASK	  	0x30    /* Mask for special chars 1 and 2 */
+
+/* PPR */
+#define PPR_DEFAULT	0x31	/* Default value - for a 25Mhz clock gives
+				   a timeout period of 1ms */
+
+/* LIVR */
+#define	LIVR_EXCEPTION	0x07	/* Receive exception interrupt */
+
+/* CCR */
+#define	CCR_RESET	0x80	/* Reset channel */
+#define	CCR_CHANGE	0x4e	/* COR's have changed - NB always change all
+				   COR's */
+#define	CCR_WFLUSH	0x82	/* Flush transmit FIFO and TSR / THR */
+
+#define	CCR_SENDSC1	0x21	/* Send special character one */
+#define CCR_SENDSC2	0x22	/* Send special character two */
+#define CCR_SENDSC3	0x23	/* Send special character three */
+#define CCR_SENDSC4	0x24	/* Send special character four */
+
+#define CCR_TENABLE	0x18	/* Enable transmitter */
+#define	CCR_TDISABLE	0x14	/* Disable transmitter */
+#define CCR_RENABLE	0x12	/* Enable receiver */
+#define CCR_RDISABLE	0x11	/* Disable receiver */
+
+#define	CCR_READY	0x00	/* CCR is ready for another command */
+
+/* CCSR */
+#define CCSR_TXENABLE	0x08	/* Transmitter enable */
+#define CCSR_RXENABLE	0x80	/* Receiver enable */
+#define CCSR_TXFLOWOFF	0x04	/* Transmit flow off */
+#define CCSR_TXFLOWON	0x02	/* Transmit flow on */
+
+/* SVRR */
+#define	SVRR_RECEIVE	0x01	/* Receive interrupt pending */
+#define	SVRR_TRANSMIT	0x02	/* Transmit interrupt pending */
+#define	SVRR_MODEM	0x04	/* Modem interrupt pending */
+
+/* CAR */
+#define CAR_PORTS	0x03	/* Bit fields for ports */
+
+/* IER */
+#define	IER_MODEM	0x80	/* Change in modem status */
+#define	IER_RECEIVE	0x10	/* Good data / data exception */
+#define IER_TRANSMITR	0x04	/* Transmit ready (FIFO empty) */
+#define	IER_TRANSMITE	0x02	/* Transmit empty */
+#define IER_TIMEOUT	0x01	/* Timeout on no data */
+
+#define	IER_DEFAULT	0x94	/* Default values */
+#define IER_PARALLEL    0x84    /* Default for Parallel */
+#define	IER_EMPTY	0x92	/* Transmitter empty rather than ready */
+
+/* COR1 - Driver only */
+#define	COR1_INPCK	0x10	/* Check parity of received characters */
+
+/* COR1 - driver and RTA */
+#define	COR1_ODD	0x80	/* Odd parity */
+#define COR1_EVEN	0x00	/* Even parity */
+#define	COR1_NOP	0x00	/* No parity */
+#define	COR1_FORCE	0x20	/* Force parity */
+#define	COR1_NORMAL	0x40	/* With parity */
+#define	COR1_1STOP	0x00	/* 1 stop bit */
+#define	COR1_15STOP	0x04	/* 1.5 stop bits */
+#define	COR1_2STOP	0x08	/* 2 stop bits */
+#define	COR1_5BITS	0x00	/* 5 data bits */
+#define	COR1_6BITS	0x01	/* 6 data bits */
+#define	COR1_7BITS	0x02	/* 7 data bits */
+#define	COR1_8BITS	0x03	/* 8 data bits */
+
+#define COR1_HOST       0xef    /* Safe host bits */
+
+/* RTA only */
+#define COR1_CINPCK     0x00    /* Check parity of received characters */
+#define COR1_CNINPCK    0x10    /* Don't check parity */
+
+/* COR2 bits for both RTA and driver use */
+#define	COR2_IXANY	0x80	/* IXANY - any character is XON */
+#define	COR2_IXON	0x40	/* IXON - enable tx soft flowcontrol */
+#define	COR2_RTSFLOW	0x02	/* Enable tx hardware flow control */
+
+/* Additional driver bits */
+#define	COR2_HUPCL	0x20	/* Hang up on close */
+#define	COR2_CTSFLOW	0x04	/* Enable rx hardware flow control */
+#define	COR2_IXOFF	0x01	/* Enable rx software flow control */
+#define COR2_DTRFLOW	0x08	/* Enable tx hardware flow control */
+
+/* RTA use only */
+#define COR2_ETC	0x20	/* Embedded transmit options */
+#define	COR2_LOCAL	0x10	/* Local loopback mode */
+#define	COR2_REMOTE	0x08	/* Remote loopback mode */
+#define	COR2_HOST	0xc2	/* Safe host bits */
+
+/* COR3 - RTA use only */
+#define	COR3_SCDRNG	0x80	/* Enable special char detect for range */
+#define	COR3_SCD34	0x40	/* Special character detect for SCHR's 3 + 4 */
+#define	COR3_FCT	0x20	/* Flow control transparency */
+#define	COR3_SCD12	0x10	/* Special character detect for SCHR's 1 + 2 */
+#define	COR3_FIFO12	0x0c	/* 12 chars for receive FIFO threshold */
+#define COR3_FIFO10     0x0a    /* 10 chars for receive FIFO threshold */
+#define COR3_FIFO8      0x08    /* 8 chars for receive FIFO threshold */
+#define COR3_FIFO6      0x06    /* 6 chars for receive FIFO threshold */
+
+#define COR3_THRESHOLD  COR3_FIFO8	/* MUST BE LESS THAN MCOR_THRESHOLD */
+
+#define	COR3_DEFAULT	(COR3_FCT | COR3_THRESHOLD)
+				/* Default bits for COR3 */
+
+/* COR4 driver and RTA use */
+#define	COR4_IGNCR	0x80	/* Throw away CR's on input */
+#define	COR4_ICRNL	0x40	/* Map CR -> NL on input */
+#define	COR4_INLCR	0x20	/* Map NL -> CR on input */
+#define	COR4_IGNBRK	0x10	/* Ignore Break */
+#define	COR4_NBRKINT	0x08	/* No interrupt on break (-BRKINT) */
+#define COR4_RAISEMOD	0x01	/* Raise modem output lines on non-zero baud */
+
+
+/* COR4 driver only */
+#define COR4_IGNPAR	0x04	/* IGNPAR (ignore characters with errors) */
+#define COR4_PARMRK	0x02	/* PARMRK */
+
+#define COR4_HOST	0xf8	/* Safe host bits */
+
+/* COR4 RTA only */
+#define COR4_CIGNPAR	0x02	/* Thrown away bad characters */
+#define COR4_CPARMRK	0x04	/* PARMRK characters */
+#define COR4_CNPARMRK	0x03	/* Don't PARMRK */
+
+/* COR5 driver and RTA use */
+#define	COR5_ISTRIP	0x80	/* Strip input chars to 7 bits */
+#define	COR5_LNE	0x40	/* Enable LNEXT processing */
+#define	COR5_CMOE	0x20	/* Match good and errored characters */
+#define	COR5_ONLCR	0x02	/* NL -> CR NL on output */
+#define	COR5_OCRNL	0x01	/* CR -> NL on output */
+
+/*
+** Spare bits - these are not used in the CIRRUS registers, so we use
+** them to set various other features.
+*/
+/*
+** tstop and tbusy indication
+*/
+#define	COR5_TSTATE_ON	0x08	/* Turn on monitoring of tbusy and tstop */
+#define	COR5_TSTATE_OFF	0x04	/* Turn off monitoring of tbusy and tstop */
+/*
+** TAB3
+*/
+#define	COR5_TAB3	0x10	/* TAB3 mode */
+
+#define	COR5_HOST	0xc3	/* Safe host bits */
+
+/* CCSR */
+#define	CCSR_TXFLOFF	0x04	/* Tx is xoffed */
+
+/* MSVR1 */
+/* NB. DTR / CD swapped from Cirrus spec as the pins are also reversed on the
+   RTA. This is because otherwise DCD would get lost on the 1 parallel / 3
+   serial option.
+*/
+#define	MSVR1_CD	0x80	/* CD (DSR on Cirrus) */
+#define	MSVR1_RTS	0x40	/* RTS (CTS on Cirrus) */
+#define	MSVR1_RI	0x20	/* RI */
+#define	MSVR1_DTR	0x10	/* DTR (CD on Cirrus) */
+#define	MSVR1_CTS	0x01	/* CTS output pin (RTS on Cirrus) */
+/* Next two used to indicate state of tbusy and tstop to driver */
+#define	MSVR1_TSTOP	0x08	/* Set if port flow controlled */
+#define	MSVR1_TEMPTY	0x04	/* Set if port tx buffer empty */
+
+#define	MSVR1_HOST	0xf3	/* The bits the host wants */
+
+/* MSVR2 */
+#define	MSVR2_DSR	0x02	/* DSR output pin (DTR on Cirrus) */
+
+/* MCOR */
+#define	MCOR_CD	        0x80	/* CD (DSR on Cirrus) */
+#define	MCOR_RTS	0x40	/* RTS (CTS on Cirrus) */
+#define	MCOR_RI	        0x20	/* RI */
+#define	MCOR_DTR	0x10	/* DTR (CD on Cirrus) */
+
+#define MCOR_DEFAULT    (MCOR_CD | MCOR_RTS | MCOR_RI | MCOR_DTR)
+#define MCOR_FULLMODEM  MCOR_DEFAULT
+#define MCOR_RJ45       (MCOR_CD | MCOR_RTS | MCOR_DTR)
+#define MCOR_RESTRICTED (MCOR_CD | MCOR_RTS)
+
+/* More MCOR - H/W Handshake (flowcontrol) stuff */
+#define	MCOR_THRESH8	0x08	/* eight characters then we stop */
+#define	MCOR_THRESH9	0x09	/* nine characters then we stop */
+#define	MCOR_THRESH10	0x0A	/* ten characters then we stop */
+#define	MCOR_THRESH11	0x0B	/* eleven characters then we stop */
+
+#define	MCOR_THRESHBITS 0x0F	/* mask for ANDing out the above */
+
+#define	MCOR_THRESHOLD	MCOR_THRESH9 /* MUST BE GREATER THAN COR3_THRESHOLD */
+
+
+/* RTPR */
+#define RTPR_DEFAULT	0x02	/* Default */
+
+
+/* Defines for the subscripts of a CONFIG packet */
+#define	CONFIG_COR1	1	/* Option register 1 */
+#define	CONFIG_COR2	2	/* Option register 2 */
+#define	CONFIG_COR4	3	/* Option register 4 */
+#define	CONFIG_COR5	4	/* Option register 5 */
+#define	CONFIG_TXXON	5	/* Tx XON character */
+#define	CONFIG_TXXOFF	6	/* Tx XOFF character */
+#define	CONFIG_RXXON	7	/* Rx XON character */
+#define	CONFIG_RXXOFF	8	/* Rx XOFF character */
+#define CONFIG_LNEXT	9	/* LNEXT character */
+#define	CONFIG_TXBAUD	10	/* Tx baud rate */
+#define	CONFIG_RXBAUD	11	/* Rx baud rate */
+
+/* Port status stuff */
+#define	IDLE_CLOSED	0	/* Closed */
+#define IDLE_OPEN	1	/* Idle open */
+#define IDLE_BREAK	2	/* Idle on break */
+
+/* Subscript of MODEM STATUS packet */
+#define	MODEM_VALUE	3	/* Current values of handshake pins */
+/* Subscript of SBREAK packet */
+#define BREAK_LENGTH	1	/* Length of a break in slices of 0.01 seconds
+				   0 = stay on break until an EBREAK command
+				   is sent */
+
+
+#define	PRE_EMPTIVE	0x80	/* Pre-emptive bit in command field */
+
+/* Packet types going from Host to remote - with the exception of OPEN, MOPEN,
+   CONFIG, SBREAK and MEMDUMP the remaining bytes of the data array will not
+   be used 
+*/
+#define	OPEN		0x00	/* Open a port */
+#define CONFIG		0x01	/* Configure a port */
+#define	MOPEN		0x02	/* Modem open (block for DCD) */
+#define	CLOSE		0x03	/* Close a port */
+#define	WFLUSH		(0x04 | PRE_EMPTIVE) /* Write flush */
+#define	RFLUSH		(0x05 | PRE_EMPTIVE) /* Read flush */
+#define	RESUME		(0x06 | PRE_EMPTIVE) /* Resume if xoffed */
+#define	SBREAK		0x07 	/* Start break */
+#define	EBREAK		0x08	/* End break */
+#define	SUSPEND		(0x09 | PRE_EMPTIVE) /* Susp op (behave as tho xoffed) */
+#define FCLOSE          (0x0a | PRE_EMPTIVE) /* Force close */
+#define XPRINT          0x0b    /* Xprint packet */
+#define MBIS		(0x0c | PRE_EMPTIVE) /* Set modem lines */
+#define MBIC		(0x0d | PRE_EMPTIVE) /* Clear modem lines */
+#define MSET		(0x0e | PRE_EMPTIVE) /* Set modem lines */
+#define PCLOSE		0x0f	/* Pseudo close - Leaves rx/tx enabled */
+#define MGET		(0x10 | PRE_EMPTIVE) /* Force update of modem status */
+#define MEMDUMP		(0x11 | PRE_EMPTIVE) /* Send back mem from addr supplied */
+#define	READ_REGISTER	(0x12 | PRE_EMPTIVE) /* Read CD1400 register (debug) */
+
+/* "Command" packets going from remote to host COMPLETE and MODEM_STATUS
+   use data[4] / data[3] to indicate current state and modem status respectively
+*/ 
+
+#define	COMPLETE	(0x20 | PRE_EMPTIVE)
+				/* Command complete */
+#define BREAK_RECEIVED	(0x21 | PRE_EMPTIVE)
+				/* Break received */
+#define MODEM_STATUS	(0x22 | PRE_EMPTIVE)
+				/* Change in modem status */
+
+/* "Command" packet that could go either way - handshake wake-up */
+#define HANDSHAKE	(0x23 | PRE_EMPTIVE)
+				/* Wake-up to HOST / RTA */
+
+#endif
diff --git a/drivers/char/rio/cmd.h b/drivers/char/rio/cmd.h
new file mode 100644
index 0000000..c369eda
--- /dev/null
+++ b/drivers/char/rio/cmd.h
@@ -0,0 +1,84 @@
+
+
+/****************************************************************************
+ *******                                                              *******
+ *******           C O M M A N D   P A C K E T   H E A D E R S
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+
+#ifndef _cmd_h
+#define _cmd_h
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_cmd_h_sccs = "@(#)cmd.h	1.1" ;
+#endif
+#endif
+
+
+#define PRE_EMPTIVE_CMD         0x80
+#define INLINE_CMD              ~PRE_EMPTIVE_CMD
+
+#define CMD_IGNORE_PKT          ( (ushort) 0)
+#define CMD_STATUS_REQ          ( (ushort) 1)
+#define CMD_UNIT_STATUS_REQ     ( (ushort) 2)     /* Is this needed ??? */
+#define CMD_CONF_PORT           ( (ushort) 3)
+#define CMD_CONF_UNIT           ( (ushort) 4)
+#define CMD_ROUTE_MAP_REQ       ( (ushort) 5)
+#define CMD_FLUSH_TX            ( (ushort) 6)
+#define CMD_FLUSH_RX            ( (ushort) 7)
+#define CMD_PARTION_PORT        ( (ushort) 8)
+#define CMD_RESET_PORT          ( (ushort) 0x0a)
+#define CMD_BOOT_UNIT           ( (ushort) 0x0b)
+#define CMD_FOUND_UNIT          ( (ushort) 0x0c)
+#define CMD_ATTACHED_RTA_2      ( (ushort) 0x0d)
+#define CMD_PROVIDE_BOOT        ( (ushort) 0x0e)
+#define CMD_CIRRUS              ( (ushort) 0x0f)
+
+#define FORM_STATUS_PKT         ( (ushort) 1 )
+#define FORM_POLL_PKT           ( (ushort) 2 )
+#define FORM_LINK_STATUS_PKT    ( (ushort) 3 )
+
+
+#define CMD_DATA_PORT           ( (ushort) 1 )
+#define CMD_DATA                ( (ushort) 2 )
+
+#define CMD_TX_PART             ( (ushort) 2 )
+#define CMD_RX_PART             ( (ushort) 3 )
+#define CMD_RX_LIMIT            ( (ushort) 4 )
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/cmdblk.h b/drivers/char/rio/cmdblk.h
new file mode 100644
index 0000000..2b8efbd
--- /dev/null
+++ b/drivers/char/rio/cmdblk.h
@@ -0,0 +1,60 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: cmdblk.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:09
+**	Retrieved	: 11/6/98 11:34:20
+**
+**  ident @(#)cmdblk.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_cmdblk_h__
+#define __rio_cmdblk_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_cmdblk_h_sccs_ = "@(#)cmdblk.h	1.2";
+#endif
+#endif
+
+/*
+** the structure of a command block, used to queue commands destined for
+** a rup.
+*/
+
+struct CmdBlk
+{
+ struct CmdBlk *NextP;          /* Pointer to next command block */
+ struct	PKT     Packet;         /* A packet, to copy to the rup */
+    	                        /* The func to call to check if OK */
+    	int     (*PreFuncP)(int, struct CmdBlk *);
+    	int     PreArg;         /* The arg for the func */
+    	                        /* The func to call when completed */
+    	int     (*PostFuncP)(int, struct CmdBlk *);
+    	int     PostArg;        /* The arg for the func */
+};
+
+#define NUM_RIO_CMD_BLKS (3 * (MAX_RUP * 4 + LINKS_PER_UNIT * 4))
+#endif
diff --git a/drivers/char/rio/cmdpkt.h b/drivers/char/rio/cmdpkt.h
new file mode 100644
index 0000000..46befd3
--- /dev/null
+++ b/drivers/char/rio/cmdpkt.h
@@ -0,0 +1,206 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: cmdpkt.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:09
+**	Retrieved	: 11/6/98 11:34:20
+**
+**  ident @(#)cmdpkt.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+#ifndef __rio_cmdpkt_h__
+#define __rio_cmdpkt_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_cmdpkt_h_sccs_ = "@(#)cmdpkt.h	1.2";
+#endif
+#endif
+
+/*
+** overlays for the data area of a packet. Used in both directions
+** (to build a packet to send, and to interpret a packet that arrives)
+** and is very inconvenient for MIPS, so they appear as two separate
+** structures - those used for modifying/reading packets on the card
+** and those for modifying/reading packets in real memory, which have an _M
+** suffix.
+*/
+
+#define	RTA_BOOT_DATA_SIZE (PKT_MAX_DATA_LEN-2)
+
+/*
+** The boot information packet looks like this:
+** This structure overlays a PktCmd->CmdData structure, and so starts
+** at Data[2] in the actual pkt!
+*/
+struct BootSequence
+{
+    WORD	NumPackets;
+    WORD	LoadBase;
+    WORD	CodeSize;
+};
+
+#define	BOOT_SEQUENCE_LEN	8
+
+struct SamTop
+{
+    BYTE Unit;
+    BYTE Link;
+};
+
+struct CmdHdr
+{
+    BYTE PcCommand;
+    union
+    {
+    BYTE PcPhbNum;
+    BYTE PcLinkNum;
+    BYTE PcIDNum;
+    } U0;
+};
+
+
+struct PktCmd
+{
+    union
+    {
+	struct 
+	{
+	    struct CmdHdr CmdHdr;
+	    struct BootSequence PcBootSequence;
+	} S1;
+	struct
+	{
+	    WORD PcSequence;
+	    BYTE PcBootData[RTA_BOOT_DATA_SIZE];
+	} S2;
+	struct
+	{
+	    WORD  __crud__;
+	    BYTE  PcUniqNum[4];	        /* this is really a uint. */
+	    BYTE  PcModuleTypes;  	/* what modules are fitted */
+	} S3;
+	struct
+	{
+	    struct CmdHdr CmdHdr;
+	    BYTE   __undefined__;
+	    BYTE   PcModemStatus;
+	    BYTE   PcPortStatus;
+	    BYTE   PcSubCommand;	/* commands like mem or register dump */
+	    WORD   PcSubAddr;		/* Address for command */
+	    BYTE   PcSubData[64];	/* Date area for command */
+	} S4;
+	struct
+	{
+	    struct CmdHdr CmdHdr;
+	    BYTE   PcCommandText[1];
+	    BYTE   __crud__[20];
+	    BYTE   PcIDNum2;		/* It had to go somewhere! */
+	} S5;
+	struct
+	{
+	    struct CmdHdr CmdHdr;
+	    struct SamTop    Topology[LINKS_PER_UNIT];
+	} S6;
+    } U1;
+};
+
+struct PktCmd_M
+{
+    union
+    {
+	struct 
+	{
+	    struct
+	    {
+    		uchar PcCommand;
+    		union
+    		{
+    		    uchar PcPhbNum;
+    		    uchar PcLinkNum;
+    		    uchar PcIDNum;
+    		} U0;
+	    } CmdHdr;
+	    struct
+	    {
+                ushort	NumPackets;
+                ushort	LoadBase;
+                ushort	CodeSize;
+            } PcBootSequence;
+	} S1;
+	struct
+	{
+	    ushort PcSequence;
+	    uchar PcBootData[RTA_BOOT_DATA_SIZE];
+	} S2;
+	struct
+	{
+	    ushort  __crud__;
+	    uchar  PcUniqNum[4];	        /* this is really a uint. */
+	    uchar  PcModuleTypes;  	/* what modules are fitted */
+	} S3;
+	struct
+	{
+	    ushort  __cmd_hdr__;
+	    uchar   __undefined__;
+	    uchar   PcModemStatus;
+	    uchar   PcPortStatus;
+	    uchar   PcSubCommand;
+	    ushort  PcSubAddr;
+	    uchar   PcSubData[64];
+	} S4;
+	struct
+	{
+	    ushort  __cmd_hdr__;
+	    uchar   PcCommandText[1];
+	    uchar   __crud__[20];
+	    uchar   PcIDNum2;		/* Tacked on end */
+	} S5;
+	struct
+	{
+	    ushort  __cmd_hdr__;
+	    struct Top Topology[LINKS_PER_UNIT];
+	} S6;
+    } U1;
+};
+
+#define Command		U1.S1.CmdHdr.PcCommand
+#define PhbNum		U1.S1.CmdHdr.U0.PcPhbNum
+#define IDNum		U1.S1.CmdHdr.U0.PcIDNum
+#define IDNum2		U1.S5.PcIDNum2
+#define LinkNum		U1.S1.CmdHdr.U0.PcLinkNum
+#define Sequence	U1.S2.PcSequence
+#define BootData	U1.S2.PcBootData
+#define BootSequence	U1.S1.PcBootSequence
+#define UniqNum		U1.S3.PcUniqNum
+#define ModemStatus	U1.S4.PcModemStatus
+#define PortStatus	U1.S4.PcPortStatus
+#define SubCommand	U1.S4.PcSubCommand
+#define SubAddr		U1.S4.PcSubAddr
+#define SubData		U1.S4.PcSubData
+#define CommandText	U1.S5.PcCommandText
+#define RouteTopology	U1.S6.Topology
+#define ModuleTypes	U1.S3.PcModuleTypes
+
+#endif
diff --git a/drivers/char/rio/control.h b/drivers/char/rio/control.h
new file mode 100644
index 0000000..1712f62
--- /dev/null
+++ b/drivers/char/rio/control.h
@@ -0,0 +1,62 @@
+
+
+/****************************************************************************
+ *******                                                              *******
+ *******           C O N T R O L   P A C K E T   H E A D E R S
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Jon Brawn
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+
+#ifndef _control_h
+#define _control_h
+
+#ifndef lint
+/* static char *_rio_control_h_sccs = "@(#)control.h	1.4"; */
+#endif
+
+#define	CONTROL		'^'
+#define IFOAD		( CONTROL + 1 )
+#define	IDENTIFY	( CONTROL + 2 )
+#define	ZOMBIE		( CONTROL + 3 )
+#define	UFOAD		( CONTROL + 4 )
+#define IWAIT		( CONTROL + 5 )
+
+#define	IFOAD_MAGIC	0xF0AD		/* of course */
+#define	ZOMBIE_MAGIC	(~0xDEAD)	/* not dead -> zombie */
+#define	UFOAD_MAGIC	0xD1E		/* kill-your-neighbour */
+#define	IWAIT_MAGIC	0xB1DE		/* Bide your time */
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/daemon.h b/drivers/char/rio/daemon.h
new file mode 100644
index 0000000..62dba0e
--- /dev/null
+++ b/drivers/char/rio/daemon.h
@@ -0,0 +1,334 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: daemon.h
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 11:34:09
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)daemon.h	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef	__rio_daemon_h__
+#define	__rio_daemon_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_daemon_h_sccs_ = "@(#)daemon.h	1.3";
+#endif
+#endif
+
+
+/*
+** structures used on /dev/rio
+*/
+
+struct Error
+{
+	uint	Error;
+	uint	Entry;
+	uint	Other;
+};
+
+struct DownLoad
+{
+	char	*DataP;
+	uint	Count;
+	uint	ProductCode;
+};
+
+/*
+** A few constants....
+*/
+#ifndef MAX_VERSION_LEN
+#define	MAX_VERSION_LEN	256
+#endif
+
+#ifndef MAX_XP_CTRL_LEN
+#define	MAX_XP_CTRL_LEN 16		/* ALSO IN PORT.H */
+#endif
+
+struct	PortSetup
+{
+	uint	From;	/* Set/Clear XP & IXANY Control from this port.... */
+	uint	To;	/* .... to this port */
+	uint	XpCps;			/* at this speed */
+	char	XpOn[MAX_XP_CTRL_LEN];	/* this is the start string */
+	char	XpOff[MAX_XP_CTRL_LEN];	/* this is the stop string */
+	uchar	IxAny;			/* enable/disable IXANY */
+	uchar	IxOn;			/* enable/disable IXON */
+	uchar	Lock;			/* lock port params */
+	uchar	Store;			/* store params across closes */
+	uchar	Drain;			/* close only when drained */
+};
+
+struct	LpbReq
+{
+	uint	Host;
+	uint	Link;
+	struct  LPB     *LpbP;
+};
+
+struct	RupReq
+{
+	uint	HostNum;
+	uint	RupNum;
+	struct  RUP 	*RupP;
+};
+
+struct	PortReq
+{
+	uint	SysPort;
+	struct  Port 	*PortP;
+};
+
+struct  StreamInfo
+{
+	uint	SysPort;
+#if 0
+	queue_t RQueue;
+	queue_t WQueue;
+#else
+	int RQueue;
+	int WQueue;
+#endif
+};
+
+struct	HostReq
+{
+	uint	HostNum;
+	struct  Host 	*HostP;
+};
+
+struct	HostDpRam
+{
+	uint	HostNum;
+	struct	DpRam	*DpRamP;
+};
+
+struct	DebugCtrl
+{
+	uint	SysPort;
+	uint	Debug;
+	uint	Wait;
+};
+
+struct	MapInfo
+{
+	uint	FirstPort;	/* 8 ports, starting from this (tty) number */
+	uint	RtaUnique;	/* reside on this RTA (unique number) */
+};
+
+struct	MapIn
+{
+	uint	NumEntries;		/* How many port sets are we mapping? */
+	struct  MapInfo	*MapInfoP;	/* Pointer to (user space) info */
+};
+
+struct  SendPack
+{
+        unsigned int	PortNum;
+	unsigned char	Len;
+	unsigned char	Data[PKT_MAX_DATA_LEN];
+};
+
+struct SpecialRupCmd
+{
+	struct	PKT		Packet;
+	unsigned short	Host;
+	unsigned short	RupNum;
+};
+
+struct	IdentifyRta
+{
+	ulong	RtaUnique;
+	uchar	ID;
+};
+
+struct	KillNeighbour
+{
+	ulong	UniqueNum;
+	uchar	Link;
+};
+
+struct rioVersion {
+    char        version[MAX_VERSION_LEN];
+    char        relid[MAX_VERSION_LEN];
+    int         buildLevel;
+    char        buildDate[MAX_VERSION_LEN];
+};
+
+
+/*
+**	RIOC commands are for the daemon type operations
+**
+** 09.12.1998 ARG - ESIL 0776 part fix
+** Definition for 'RIOC' also appears in rioioctl.h, so we'd better do a
+** #ifndef here first.
+** rioioctl.h also now has #define 'RIO_QUICK_CHECK' as this ioctl is now
+** allowed to be used by customers.
+*/
+#ifndef RIOC
+#define	RIOC	('R'<<8)|('i'<<16)|('o'<<24)
+#endif
+
+/*
+** Boot stuff
+*/
+#define	RIO_GET_TABLE     (RIOC | 100)
+#define RIO_PUT_TABLE     (RIOC | 101)
+#define RIO_ASSIGN_RTA    (RIOC | 102)
+#define RIO_DELETE_RTA    (RIOC | 103)
+#define	RIO_HOST_FOAD	  (RIOC | 104)
+#define	RIO_QUICK_CHECK	  (RIOC | 105)
+#define RIO_SIGNALS_ON    (RIOC | 106)
+#define RIO_SIGNALS_OFF   (RIOC | 107)
+#define	RIO_CHANGE_NAME   (RIOC | 108)
+#define RIO_DOWNLOAD      (RIOC | 109)
+#define	RIO_GET_LOG	  (RIOC | 110)
+#define	RIO_SETUP_PORTS   (RIOC | 111)
+#define RIO_ALL_MODEM     (RIOC | 112)
+
+/*
+** card state, debug stuff
+*/
+#define	RIO_NUM_HOSTS	  (RIOC | 120)
+#define	RIO_HOST_LPB	  (RIOC | 121)
+#define	RIO_HOST_RUP	  (RIOC | 122)
+#define	RIO_HOST_PORT	  (RIOC | 123)
+#define	RIO_PARMS 	  (RIOC | 124)
+#define RIO_HOST_REQ	  (RIOC | 125)
+#define	RIO_READ_CONFIG	  (RIOC | 126)
+#define	RIO_SET_CONFIG	  (RIOC | 127)
+#define	RIO_VERSID	  (RIOC | 128)
+#define	RIO_FLAGS	  (RIOC | 129)
+#define	RIO_SETDEBUG	  (RIOC | 130)
+#define	RIO_GETDEBUG	  (RIOC | 131)
+#define	RIO_READ_LEVELS   (RIOC | 132)
+#define	RIO_SET_FAST_BUS  (RIOC | 133)
+#define	RIO_SET_SLOW_BUS  (RIOC | 134)
+#define	RIO_SET_BYTE_MODE (RIOC | 135)
+#define	RIO_SET_WORD_MODE (RIOC | 136)
+#define RIO_STREAM_INFO   (RIOC | 137)
+#define	RIO_START_POLLER  (RIOC | 138)
+#define	RIO_STOP_POLLER   (RIOC | 139)
+#define	RIO_LAST_ERROR    (RIOC | 140)
+#define	RIO_TICK	  (RIOC | 141)
+#define	RIO_TOCK	  (RIOC | 241)	/* I did this on purpose, you know. */
+#define	RIO_SEND_PACKET   (RIOC | 142)
+#define	RIO_SET_BUSY	  (RIOC | 143)
+#define	SPECIAL_RUP_CMD   (RIOC | 144)
+#define	RIO_FOAD_RTA      (RIOC | 145)
+#define	RIO_ZOMBIE_RTA    (RIOC | 146)
+#define RIO_IDENTIFY_RTA  (RIOC | 147)
+#define RIO_KILL_NEIGHBOUR (RIOC | 148)
+#define RIO_DEBUG_MEM     (RIOC | 149)
+/*
+** 150 - 167 used.....   See below
+*/
+#define RIO_GET_PORT_SETUP (RIOC | 168)
+#define RIO_RESUME        (RIOC | 169)
+#define	RIO_MESG	(RIOC | 170)
+#define	RIO_NO_MESG	(RIOC | 171)
+#define	RIO_WHAT_MESG	(RIOC | 172)
+#define RIO_HOST_DPRAM	(RIOC | 173)
+#define RIO_MAP_B50_TO_50	(RIOC | 174)
+#define RIO_MAP_B50_TO_57600	(RIOC | 175)
+#define RIO_MAP_B110_TO_110	(RIOC | 176)
+#define RIO_MAP_B110_TO_115200	(RIOC | 177)
+#define RIO_GET_PORT_PARAMS	(RIOC | 178)
+#define RIO_SET_PORT_PARAMS	(RIOC | 179)
+#define RIO_GET_PORT_TTY	(RIOC | 180)
+#define RIO_SET_PORT_TTY	(RIOC | 181)
+#define RIO_SYSLOG_ONLY	(RIOC | 182)
+#define RIO_SYSLOG_CONS	(RIOC | 183)
+#define RIO_CONS_ONLY	(RIOC | 184)
+#define RIO_BLOCK_OPENS	(RIOC | 185)
+
+/*
+** 02.03.1999 ARG - ESIL 0820 fix :
+** RIOBootMode is no longer use by the driver, so these ioctls
+** are now obsolete :
+**
+#define RIO_GET_BOOT_MODE	(RIOC | 186)
+#define RIO_SET_BOOT_MODE	(RIOC | 187)
+**
+*/
+
+#define RIO_MEM_DUMP	(RIOC | 189)
+#define RIO_READ_REGISTER	(RIOC | 190)
+#define RIO_GET_MODTYPE	(RIOC | 191)
+#define RIO_SET_TIMER	(RIOC | 192)
+#define RIO_READ_CHECK	(RIOC | 196)
+#define RIO_WAITING_FOR_RESTART	(RIOC | 197)
+#define RIO_BIND_RTA	(RIOC | 198)
+#define RIO_GET_BINDINGS	(RIOC | 199)
+#define RIO_PUT_BINDINGS	(RIOC | 200)
+
+#define	RIO_MAKE_DEV		(RIOC | 201)
+#define	RIO_MINOR		(RIOC | 202)
+
+#define	RIO_IDENTIFY_DRIVER	(RIOC | 203)
+#define	RIO_DISPLAY_HOST_CFG	(RIOC | 204)
+
+
+/*
+** MAKE_DEV / MINOR stuff
+*/
+#define	RIO_DEV_DIRECT		0x0000
+#define	RIO_DEV_MODEM		0x0200
+#define	RIO_DEV_XPRINT		0x0400
+#define	RIO_DEV_MASK		0x0600
+
+/*
+** port management, xprint stuff
+*/
+#define	rIOCN(N)	(RIOC|(N))
+#define	rIOCR(N,T)	(RIOC|(N))
+#define	rIOCW(N,T)	(RIOC|(N))
+
+#define	RIO_GET_XP_ON     rIOCR(150,char[16])	/* start xprint string */
+#define	RIO_SET_XP_ON     rIOCW(151,char[16])
+#define	RIO_GET_XP_OFF    rIOCR(152,char[16])	/* finish xprint string */
+#define	RIO_SET_XP_OFF    rIOCW(153,char[16])
+#define	RIO_GET_XP_CPS    rIOCR(154,int)	/* xprint CPS */
+#define	RIO_SET_XP_CPS    rIOCW(155,int)
+#define RIO_GET_IXANY     rIOCR(156,int)	/* ixany allowed? */
+#define RIO_SET_IXANY     rIOCW(157,int)
+#define RIO_SET_IXANY_ON  rIOCN(158)		/* allow ixany */
+#define RIO_SET_IXANY_OFF rIOCN(159)		/* disallow ixany */
+#define RIO_GET_MODEM     rIOCR(160,int)	/* port is modem/direct line? */
+#define RIO_SET_MODEM     rIOCW(161,int)
+#define RIO_SET_MODEM_ON  rIOCN(162)		/* port is a modem */
+#define RIO_SET_MODEM_OFF rIOCN(163)		/* port is direct */
+#define RIO_GET_IXON      rIOCR(164,int)	/* ixon allowed? */
+#define RIO_SET_IXON      rIOCW(165,int)
+#define RIO_SET_IXON_ON   rIOCN(166)		/* allow ixon */
+#define RIO_SET_IXON_OFF  rIOCN(167)		/* disallow ixon */
+
+#define RIO_GET_SIVIEW	  ((('s')<<8) | 106)	/* backwards compatible with SI */
+
+#define	RIO_IOCTL_UNKNOWN	-2
+
+#endif
diff --git a/drivers/char/rio/data.h b/drivers/char/rio/data.h
new file mode 100644
index 0000000..dabc2d1
--- /dev/null
+++ b/drivers/char/rio/data.h
@@ -0,0 +1,40 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: data.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:09
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)data.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_datadex__
+#define __rio_datadex__
+
+#ifndef lint
+static char *_data_h_sccs_ = "@(#)data.h	1.2";
+#endif
+
+#endif
diff --git a/drivers/char/rio/debug.h b/drivers/char/rio/debug.h
new file mode 100644
index 0000000..b6e0d09
--- /dev/null
+++ b/drivers/char/rio/debug.h
@@ -0,0 +1,39 @@
+/*
+** File:		debug.h
+**
+** Author:		David Dix
+**
+** Created:		12th March 1993
+**
+** Last modified:	93/04/27
+**
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+*/
+
+#ifndef _debug_h_
+#define _debug_h_
+
+
+#if defined(DCIRRUS)
+#define	DBPACKET(pkt, opt, str, chn) 	debug_packet((pkt), (opt), (str), (chn))
+#else
+#define	DBPACKET(pkt, opt, str, c)
+#endif	/* DCIRRUS */
+
+
+#endif	/* _debug_h_ */
diff --git a/drivers/char/rio/defaults.h b/drivers/char/rio/defaults.h
new file mode 100644
index 0000000..2e7309e
--- /dev/null
+++ b/drivers/char/rio/defaults.h
@@ -0,0 +1,59 @@
+
+/****************************************************************************
+ *******                                                              *******
+ *******                     D E F A U L T S
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_defaults_h_sccs = "@(#)defaults.h	1.1" ;
+#endif
+#endif
+
+
+#define MILLISECOND           (int) (1000/64)   /* 15.625 low ticks */
+#define SECOND                (int) 15625       /* Low priority ticks */
+
+#ifdef RTA
+#define RX_LIMIT       (ushort) 3
+#endif
+#ifdef HOST
+#define RX_LIMIT       (ushort) 1
+#endif
+
+#define LINK_TIMEOUT          (int) (POLL_PERIOD / 2)
+
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/eisa.h b/drivers/char/rio/eisa.h
new file mode 100644
index 0000000..59371b0
--- /dev/null
+++ b/drivers/char/rio/eisa.h
@@ -0,0 +1,104 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: eisa.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:10
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)eisa.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_eisa_h__
+#define __rio_eisa_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_eisa_h_sccs_ = "@(#)eisa.h	1.2";
+#endif
+#endif
+
+/*
+** things to do with the EISA bus
+*/
+
+#define RIO_EISA_STRING_ADDRESS 	0xfffd9	/* where EISA is stored */
+
+#define	RIO_MAX_EISA_SLOTS		16	/* how many EISA slots? */
+
+#define	RIO_EISA_IDENT			0x984D	/* Specialix */
+#define	RIO_EISA_PRODUCT_CODE		0x14	/* Code 14 */
+#define	RIO_EISA_ENABLE_BIT		0x01	/* To enable card */
+
+#define	EISA_MEMORY_BASE_LO		0xC00	/* A16-A23 */
+#define	EISA_MEMORY_BASE_HI		0xC01	/* A24-A31 */
+#define	EISA_INTERRUPT_VEC		0xC02	/* see below */
+#define	EISA_CONTROL_PORT		0xC02	/* see below */
+#define	EISA_INTERRUPT_RESET		0xC03	/* read to clear IRQ */
+
+#define	EISA_PRODUCT_IDENT_LO		0xC80	/* where RIO_EISA_IDENT is */
+#define	EISA_PRODUCT_IDENT_HI		0xC81
+#define	EISA_PRODUCT_NUMBER		0xC82   /* where PROD_CODE is */
+#define	EISA_REVISION_NUMBER		0xC83	/* revision (1dp) */
+#define	EISA_ENABLE			0xC84	/* set LSB to enable card */
+#define	EISA_UNIQUE_NUM_0		0xC88	/* vomit */
+#define	EISA_UNIQUE_NUM_1		0xC8A
+#define	EISA_UNIQUE_NUM_2		0xC90	/* bit strangely arranged */
+#define	EISA_UNIQUE_NUM_3		0xC92
+#define	EISA_MANUF_YEAR			0xC98	/* when */
+#define	EISA_MANUF_WEEK			0xC9A	/* more when */
+
+#define	EISA_TP_BOOT_FROM_RAM	0x01
+#define	EISA_TP_BOOT_FROM_LINK	0x00
+#define	EISA_TP_FAST_LINKS	0x02
+#define	EISA_TP_SLOW_LINKS	0x00
+#define	EISA_TP_BUS_ENABLE	0x04
+#define	EISA_TP_BUS_DISABLE	0x00
+#define	EISA_TP_RUN		0x08
+#define	EISA_TP_RESET		0x00
+#define	EISA_POLLED		0x00
+#define	EISA_IRQ_3		0x30
+#define	EISA_IRQ_4		0x40
+#define	EISA_IRQ_5		0x50
+#define	EISA_IRQ_6		0x60
+#define	EISA_IRQ_7		0x70
+#define	EISA_IRQ_9		0x90
+#define	EISA_IRQ_10		0xA0
+#define	EISA_IRQ_11		0xB0
+#define	EISA_IRQ_12		0xC0
+#define	EISA_IRQ_14		0xE0
+#define	EISA_IRQ_15		0xF0
+
+#define	EISA_INTERRUPT_MASK	0xF0
+#define	EISA_CONTROL_MASK	0x0F
+
+#define	RIO_EISA_DEFAULT_MODE	EISA_TP_SLOW_LINKS
+
+#define	RIOEisaToIvec(X)	(uchar )((uchar)((X) & EISA_INTERRUPT_MASK)>>4)
+
+#define	INBZ(z,x)	inb(((z)<<12) | (x))
+#define	OUTBZ(z,x,y)	outb((((z)<<12) | (x)), y)
+
+#endif /* __rio_eisa_h__ */
diff --git a/drivers/char/rio/enable.h b/drivers/char/rio/enable.h
new file mode 100644
index 0000000..8e9a419
--- /dev/null
+++ b/drivers/char/rio/enable.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+ *******                                                              *******
+ *******               E N A B L E   H E A D E R S
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_enable_h_sccs = "@(#)enable.h	1.1" ;
+#endif
+#endif
+
+
+#define ENABLE_LTT  TRUE
+#define ENABLE_LRT  TRUE
+
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/error.h b/drivers/char/rio/error.h
new file mode 100644
index 0000000..229438e
--- /dev/null
+++ b/drivers/char/rio/error.h
@@ -0,0 +1,85 @@
+
+/****************************************************************************
+ *******                                                              *******
+ *******     E R R O R  H E A D E R   F I L E
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+/* static char *_rio_error_h_sccs = "@(#)error.h	1.3"; */
+#endif
+
+#define E_NO_ERROR                       ((ushort) 0)
+#define E_PROCESS_NOT_INIT               ((ushort) 1)
+#define E_LINK_TIMEOUT                   ((ushort) 2)
+#define E_NO_ROUTE                       ((ushort) 3)
+#define E_CONFUSED                       ((ushort) 4)
+#define E_HOME                           ((ushort) 5)
+#define E_CSUM_FAIL                      ((ushort) 6)
+#define E_DISCONNECTED                   ((ushort) 7)
+#define E_BAD_RUP                        ((ushort) 8)
+#define E_NO_VIRGIN                      ((ushort) 9)
+#define E_BOOT_RUP_BUSY                  ((ushort) 10)
+
+
+
+    /*************************************************
+     * Parsed to mem_halt()
+     ************************************************/
+#define E_CHANALLOC                      ((ushort) 0x80)
+#define E_POLL_ALLOC                     ((ushort) 0x81)
+#define E_LTTWAKE                        ((ushort) 0x82)
+#define E_LTT_ALLOC                      ((ushort) 0x83)
+#define E_LRT_ALLOC                      ((ushort) 0x84)
+#define E_CIRRUS                         ((ushort) 0x85)
+#define E_MONITOR                        ((ushort) 0x86)
+#define E_PHB_ALLOC                      ((ushort) 0x87)
+#define E_ARRAY_ALLOC                    ((ushort) 0x88)
+#define E_QBUF_ALLOC                     ((ushort) 0x89)
+#define E_PKT_ALLOC                      ((ushort) 0x8a)
+#define E_GET_TX_Q_BUF                   ((ushort) 0x8b)
+#define E_GET_RX_Q_BUF                   ((ushort) 0x8c)
+#define E_MEM_OUT                        ((ushort) 0x8d)
+#define E_MMU_INIT                       ((ushort) 0x8e)
+#define E_LTT_INIT                       ((ushort) 0x8f)
+#define E_LRT_INIT                       ((ushort) 0x90)
+#define E_LINK_RUN                       ((ushort) 0x91)
+#define E_MONITOR_ALLOC                  ((ushort) 0x92)
+#define E_MONITOR_INIT                   ((ushort) 0x93)
+#define E_POLL_INIT                      ((ushort) 0x94)
+
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/errors.h b/drivers/char/rio/errors.h
new file mode 100644
index 0000000..f920b9f
--- /dev/null
+++ b/drivers/char/rio/errors.h
@@ -0,0 +1,104 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: errors.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:10
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)errors.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef	__rio_errors_h__
+#define	__rio_errors_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_errors_h_sccs_ = "@(#)errors.h	1.2";
+#endif
+#endif
+
+/*
+** error codes
+*/
+
+#define	NOTHING_WRONG_AT_ALL		0
+#define	BAD_CHARACTER_IN_NAME		1
+#define	TABLE_ENTRY_ISNT_PROPERLY_NULL	2
+#define	UNKNOWN_HOST_NUMBER		3
+#define	ZERO_RTA_ID			4
+#define	BAD_RTA_ID			5
+#define	DUPLICATED_RTA_ID		6
+#define	DUPLICATE_UNIQUE_NUMBER		7
+#define	BAD_TTY_NUMBER			8
+#define	TTY_NUMBER_IN_USE		9
+#define	NAME_USED_TWICE			10
+#define	HOST_ID_NOT_ZERO		11
+#define	BOOT_IN_PROGRESS		12
+#define	COPYIN_FAILED			13
+#define	HOST_FILE_TOO_LARGE		14
+#define	COPYOUT_FAILED			15
+#define	NOT_SUPER_USER			16
+#define	RIO_ALREADY_POLLING		17
+
+#define	ID_NUMBER_OUT_OF_RANGE		18
+#define PORT_NUMBER_OUT_OF_RANGE	19
+#define	HOST_NUMBER_OUT_OF_RANGE	20
+#define	RUP_NUMBER_OUT_OF_RANGE		21
+#define	TTY_NUMBER_OUT_OF_RANGE		22
+#define	LINK_NUMBER_OUT_OF_RANGE	23
+
+#define	HOST_NOT_RUNNING		24
+#define	IOCTL_COMMAND_UNKNOWN		25
+#define	RIO_SYSTEM_HALTED		26
+#define	WAIT_FOR_DRAIN_BROKEN		27
+#define	PORT_NOT_MAPPED_INTO_SYSTEM	28
+#define	EXCLUSIVE_USE_SET		29
+#define	WAIT_FOR_NOT_CLOSING_BROKEN	30
+#define	WAIT_FOR_PORT_TO_OPEN_BROKEN	31
+#define	WAIT_FOR_CARRIER_BROKEN		32
+#define	WAIT_FOR_NOT_IN_USE_BROKEN	33
+#define	WAIT_FOR_CAN_ADD_COMMAND_BROKEN	34
+#define	WAIT_FOR_ADD_COMMAND_BROKEN	35
+#define	WAIT_FOR_NOT_PARAM_BROKEN	36
+#define	WAIT_FOR_RETRY_BROKEN		37
+#define	HOST_HAS_ALREADY_BEEN_BOOTED	38
+#define	UNIT_IS_IN_USE			39
+#define	COULDNT_FIND_ENTRY		40
+#define	RTA_UNIQUE_NUMBER_ZERO		41
+#define	CLOSE_COMMAND_FAILED		42
+#define	WAIT_FOR_CLOSE_BROKEN		43
+#define	CPS_VALUE_OUT_OF_RANGE		44
+#define	ID_ALREADY_IN_USE		45
+#define	SIGNALS_ALREADY_SET		46
+#define	NOT_RECEIVING_PROCESS		47
+#define	RTA_NUMBER_WRONG		48
+#define NO_SUCH_PRODUCT			49
+#define	HOST_SYSPORT_BAD		50
+#define	ID_NOT_TENTATIVE		51
+#define XPRINT_CPS_OUT_OF_RANGE		52
+#define	NOT_ENOUGH_CORE_FOR_PCI_COPY	53
+
+
+#endif /* __rio_errors_h__ */
diff --git a/drivers/char/rio/formpkt.h b/drivers/char/rio/formpkt.h
new file mode 100644
index 0000000..a8b65ae
--- /dev/null
+++ b/drivers/char/rio/formpkt.h
@@ -0,0 +1,154 @@
+
+
+/****************************************************************************
+ *******                                                              *******
+ *******         F O R M   P A C K E T   H E A D E R   F I L E
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _formpkt_h
+#define _formpkt_h 1
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_formpkt_h_sccs = "@(#)formpkt.h	1.1" ;
+#endif
+#endif
+
+typedef struct FORM_BOOT_PKT_1 FORM_BOOT_PKT_1 ;
+struct FORM_BOOT_PKT_1 {
+                           ushort pkt_number ;
+                           ushort pkt_total ;
+                           ushort boot_top ;
+                       } ;
+
+typedef struct FORM_BOOT_PKT_2 FORM_BOOT_PKT_2 ;
+struct FORM_BOOT_PKT_2 {
+                           ushort pkt_number ;
+                           char   boot_data[10] ;
+                       } ;
+
+
+typedef struct FORM_ATTACH_RTA   FORM_ATTACH_RTA ;
+struct FORM_ATTACH_RTA  {
+                       char    cmd_code ;
+                       char    booter_serial[4] ;
+                       char    booter_link ;
+                       char    bootee_serial[4] ;
+                       char    bootee_link ;
+                   } ;
+
+
+typedef struct FORM_BOOT_ID   FORM_BOOT_ID ;
+struct FORM_BOOT_ID  {
+                       char    cmd_code ;
+                       char    bootee_serial[4] ;
+                       char    bootee_prod_id ;
+                       char    bootee_link ;
+                   } ;
+
+
+
+typedef struct FORM_ROUTE_1   FORM_ROUTE_1 ;
+struct FORM_ROUTE_1 {
+                        char     cmd_code ;
+                        char     pkt_number ;
+                        char     total_in_sequence ;
+                        char     unit_id ;
+                        char     host_unit_id ;
+                    } ;
+
+typedef struct FORM_ROUTE_2   FORM_ROUTE_2 ;
+struct FORM_ROUTE_2 {
+                        char   cmd_code ;
+                        char   pkt_number ;
+                        char   total_in_sequence ;
+                        char   route_data[9] ;
+                    } ;
+
+typedef struct FORM_ROUTE_REQ   FORM_ROUTE_REQ ;
+struct FORM_ROUTE_REQ {
+                          char   cmd_code ;
+                          char   pkt_number ;
+                          char   total_in_sequence ;
+                          char   route_data[10] ;
+                      } ;
+
+
+typedef struct FORM_ERROR   FORM_ERROR ;
+struct FORM_ERROR {
+                        char   cmd_code ;
+                        char   error_code ;
+
+                    } ;
+
+typedef struct FORM_STATUS   FORM_STATUS ;
+struct FORM_STATUS {
+                        char   cmd_code ;
+                        char   status_code ;
+                        char   last_packet_valid ;
+                        char   tx_buffer ;
+                        char   rx_buffer ;
+                        char   port_status ;
+                        char   phb_status ;
+                    } ;
+
+
+typedef struct FORM_LINK_STATUS   FORM_LINK_STATUS ;
+struct FORM_LINK_STATUS {
+                        char    cmd_code ;
+                        char    status_code ;
+                        char    link_number ;
+                        ushort  rx_errors ;
+                        ushort  tx_errors ;
+                        ushort  csum_errors ;
+                        ushort  disconnects ;
+                    } ;
+
+
+
+typedef struct FORM_PARTITION FORM_PARTITION ;
+struct FORM_PARTITION {
+                        char    cmd_code ;
+                        char    status_code ;
+                        char    port_number ;
+                        char    tx_max ;
+                        char    rx_max ;
+                        char    rx_limit ;
+                      } ;
+
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/func.h b/drivers/char/rio/func.h
new file mode 100644
index 0000000..e8f3860
--- /dev/null
+++ b/drivers/char/rio/func.h
@@ -0,0 +1,154 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: func.h
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 11:34:10
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)func.h	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __func_h_def
+#define __func_h_def
+
+#include <linux/kdev_t.h>
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_func_h_sccs_ = "@(#)func.h	1.3";
+#endif
+#endif
+
+/* rioboot.c */
+int RIOBootCodeRTA(struct rio_info *, struct DownLoad *);
+int RIOBootCodeHOST(struct rio_info *, register struct DownLoad *);
+int RIOBootCodeUNKNOWN(struct rio_info *, struct DownLoad *);
+void msec_timeout(struct Host *);
+int RIOBootRup(struct rio_info *, uint, struct Host *, struct PKT *);
+int RIOBootOk(struct rio_info *,struct Host *, ulong);
+int RIORtaBound(struct rio_info *, uint); 
+void FillSlot(int, int, uint, struct Host *);
+
+/* riocmd.c */
+int RIOFoadRta(struct Host *, struct Map *);
+int RIOZombieRta(struct Host *, struct Map *);
+int RIOCommandRta(struct rio_info *, uint, int (* func)( struct Host *, 
+								struct Map *));
+int RIOIdentifyRta(struct rio_info *, caddr_t); 
+int RIOKillNeighbour(struct rio_info *, caddr_t);
+int RIOSuspendBootRta(struct Host *, int, int);
+int RIOFoadWakeup(struct rio_info *);
+struct CmdBlk * RIOGetCmdBlk(void);
+void RIOFreeCmdBlk(struct CmdBlk *);
+int RIOQueueCmdBlk(struct Host *, uint, struct CmdBlk *);
+void RIOPollHostCommands(struct rio_info *, struct Host *);
+int RIOWFlushMark(int, struct CmdBlk *);
+int RIORFlushEnable(int, struct CmdBlk *);
+int RIOUnUse(int, struct CmdBlk *);
+void ShowPacket(uint, struct PKT *);
+
+/* rioctrl.c */
+int copyin(int, caddr_t, int);
+int riocontrol(struct rio_info *, dev_t,int,caddr_t,int); 
+int RIOPreemptiveCmd(struct rio_info *,struct Port *,uchar);
+
+/* rioinit.c */
+void rioinit(struct rio_info *, struct RioHostInfo *);
+void RIOInitHosts(struct rio_info *, struct RioHostInfo *);
+void RIOISAinit(struct rio_info *,int);
+int RIODoAT(struct rio_info *, int, int);
+caddr_t RIOCheckForATCard(int);
+int RIOAssignAT(struct rio_info *, int, caddr_t, int);
+int RIOBoardTest(paddr_t, caddr_t, uchar, int);
+void RIOAllocDataStructs(struct rio_info *);
+void RIOSetupDataStructs(struct rio_info *);
+int RIODefaultName(struct rio_info *, struct Host *, uint);
+struct rioVersion * RIOVersid(void);
+int RIOMapin(paddr_t, int, caddr_t *);
+void RIOMapout(paddr_t, long, caddr_t);
+void RIOHostReset(uint, volatile struct DpRam *, uint);
+
+/* riointr.c */
+void RIOTxEnable(char *);
+void RIOServiceHost(struct rio_info *, struct Host *, int);
+int riotproc(struct rio_info *, register struct ttystatics *, int, int);
+
+/* rioparam.c */
+int RIOParam(struct Port *, int, int, int);
+int RIODelay(struct Port *PortP, int);
+int RIODelay_ni(struct Port *PortP, int);
+void ms_timeout(struct Port *);
+int can_add_transmit(struct PKT **, struct Port *);
+void add_transmit(struct Port *);
+void put_free_end(struct Host *, struct PKT *);
+int can_remove_receive(struct PKT **, struct Port *);
+void remove_receive(struct Port *);
+
+/* rioroute.c */
+int RIORouteRup(struct rio_info *, uint, struct Host *, struct PKT *);
+void RIOFixPhbs(struct rio_info *, struct Host *, uint); 
+uint GetUnitType(uint);
+int RIOSetChange(struct rio_info *);
+int RIOFindFreeID(struct rio_info *, struct Host *, uint *, uint *);
+
+
+/* riotty.c */
+
+int riotopen(struct tty_struct * tty, struct file * filp);
+int riotclose(void  *ptr);
+int riotioctl(struct rio_info *, struct tty_struct *, register int, register caddr_t); 
+void ttyseth(struct Port *, struct ttystatics *, struct old_sgttyb *sg);
+
+/* riotable.c */
+int RIONewTable(struct rio_info *);
+int RIOApel(struct rio_info *);
+int RIODeleteRta(struct rio_info *, struct Map *);
+int RIOAssignRta(struct rio_info *, struct Map *);
+int RIOReMapPorts(struct rio_info *, struct Host *, struct Map *);
+int RIOChangeName(struct rio_info *, struct Map*);
+
+#if 0
+/* riodrvr.c */
+struct rio_info * rio_install(struct RioHostInfo *);
+int rio_uninstall(register struct rio_info *);
+int rio_open(struct rio_info *, int, struct file *);
+int rio_close(struct rio_info *, struct file *);
+int rio_read(struct rio_info *, struct file *, char *, int);
+int rio_write(struct rio_info *, struct file *	f, char *, int);
+int rio_ioctl(struct rio_info *, struct file *, int, char *);
+int rio_select(struct rio_info *, struct file *	f, int, struct sel *);
+int	rio_intr(char *);
+int rio_isr_thread(char  *);
+struct rio_info * rio_info_store( int cmd, struct rio_info * p);
+#endif
+
+extern int    rio_pcicopy(char *src, char *dst, int n);
+extern int rio_minor (struct tty_struct *tty);
+extern int rio_ismodem (struct tty_struct *tty);
+extern void rio_udelay (int usecs);
+
+extern void rio_start_card_running (struct Host * HostP);
+
+#endif	/* __func_h_def */
diff --git a/drivers/char/rio/host.h b/drivers/char/rio/host.h
new file mode 100644
index 0000000..4c65963
--- /dev/null
+++ b/drivers/char/rio/host.h
@@ -0,0 +1,134 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: host.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:10
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)host.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_host_h__
+#define __rio_host_h__
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_host_h_sccs_ = "@(#)host.h	1.2";
+#endif
+#endif
+
+/*
+** the host structure - one per host card in the system.
+*/
+
+#define	MAX_EXTRA_UNITS	64
+
+/*
+**    Host data structure. This is used for the software equiv. of
+**    the host.
+*/
+struct    Host
+{
+    uchar             	    Type;      /* RIO_EISA, RIO_MCA, ... */
+    uchar             	    Ivec;      /* POLLED or ivec number */
+    uchar             	    Mode;      /* Control stuff */
+    uchar                   Slot;      /* Slot */
+    volatile caddr_t        Caddr;     /* KV address of DPRAM */
+    volatile struct DpRam  *CardP;     /* KV address of DPRAM, with overlay */
+    paddr_t          	    PaddrP;    /* Phys. address of DPRAM */
+    char                    Name[MAX_NAME_LEN];  /* The name of the host */
+    uint            	    UniqueNum; /* host unique number */
+    spinlock_t	            HostLock;  /* Lock structure for MPX */
+    /*struct pci_devinfo    PciDevInfo; *//* PCI Bus/Device/Function stuff */
+    /*struct lockb	    HostLock;  *//* Lock structure for MPX */
+    uint                    WorkToBeDone; /* set to true each interrupt */
+    uint                    InIntr;    /* Being serviced? */
+    uint                    IntSrvDone;/* host's interrupt has been serviced */
+    int			    (*Copy)( caddr_t, caddr_t, int ); /* copy func */
+    struct timer_list timer;
+    /*
+    **               I M P O R T A N T !
+    **
+    ** The rest of this data structure is cleared to zero after
+    ** a RIO_HOST_FOAD command.
+    */
+    
+    ulong                   Flags;     /* Whats going down */
+#define RC_WAITING            0
+#define RC_STARTUP            1
+#define RC_RUNNING            2
+#define RC_STUFFED            3
+#define RC_SOMETHING          4
+#define RC_SOMETHING_NEW      5
+#define RC_SOMETHING_ELSE     6
+#define RC_READY              7
+#define RUN_STATE             7
+/*
+** Boot mode applies to the way in which hosts in this system will
+** boot RTAs
+*/
+#define RC_BOOT_ALL           0x8	/* Boot all RTAs attached */
+#define RC_BOOT_OWN           0x10	/* Only boot RTAs bound to this system */
+#define RC_BOOT_NONE          0x20	/* Don't boot any RTAs (slave mode) */
+
+    struct Top		    Topology[LINKS_PER_UNIT]; /* one per link */
+    struct Map              Mapping[MAX_RUP];     /* Mappings for host */
+    struct PHB		    *PhbP;                /* Pointer to the PHB array */
+    ushort           	    *PhbNumP;             /* Ptr to Number of PHB's */
+    struct LPB 	            *LinkStrP ;           /* Link Structure Array */
+    struct RUP       	    *RupP;                /* Sixteen real rups here */
+    struct PARM_MAP  	    *ParmMapP;            /* points to the parmmap */
+    uint                    ExtraUnits[MAX_EXTRA_UNITS]; /* unknown things */
+    uint                    NumExtraBooted;       /* how many of the above */
+    /*
+    ** Twenty logical rups.
+    ** The first sixteen are the real Rup entries (above), the last four
+    ** are the link RUPs.
+    */
+    struct UnixRup	    UnixRups[MAX_RUP+LINKS_PER_UNIT];
+	int				timeout_id;	/* For calling 100 ms delays */
+	int				timeout_sem;/* For calling 100 ms delays */
+    long locks; /* long req'd for set_bit --RR */
+    char             	    ____end_marker____;
+};
+#define Control      CardP->DpControl
+#define SetInt       CardP->DpSetInt
+#define ResetTpu     CardP->DpResetTpu
+#define ResetInt     CardP->DpResetInt
+#define Signature    CardP->DpSignature
+#define Sram1        CardP->DpSram1
+#define Sram2        CardP->DpSram2
+#define Sram3        CardP->DpSram3
+#define Scratch      CardP->DpScratch
+#define __ParmMapR   CardP->DpParmMapR
+#define SLX          CardP->DpSlx
+#define Revision     CardP->DpRevision
+#define Unique       CardP->DpUnique
+#define Year         CardP->DpYear
+#define Week         CardP->DpWeek
+
+#define RIO_DUMBPARM 0x0860    /* what not to expect */
+
+#endif
diff --git a/drivers/char/rio/hosthw.h b/drivers/char/rio/hosthw.h
new file mode 100644
index 0000000..f6f31ec
--- /dev/null
+++ b/drivers/char/rio/hosthw.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                H O S T      H A R D W A R E
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_hosthw_h_sccs = "@(#)hosthw.h	1.2" ;
+#endif
+#endif
+
+#define SET_OTHER_INTERRUPT  ( (volatile u_short *) 0x7c80 )
+#define SET_EISA_INTERRUPT  ( (volatile u_short *) 0x7ef0 )
+
+#define EISA_HOST    0x30
+#define AT_HOST      0xa0
+#define MCA_HOST     0xb0
+#define PCI_HOST     0xd0
+
+#define PRODUCT_MASK 0xf0
+
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/link.h b/drivers/char/rio/link.h
new file mode 100644
index 0000000..9722503
--- /dev/null
+++ b/drivers/char/rio/link.h
@@ -0,0 +1,188 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                      L I N K
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _link_h
+#define _link_h 1
+
+#ifndef lint
+#ifdef SCCS_LABELS
+/* static char *_rio_link_h_sccs = "@(#)link.h	1.15"; */
+#endif
+#endif
+
+
+
+/*************************************************
+ * Define the Link Status stuff
+ ************************************************/
+#define LRT_ACTIVE         ((ushort) 0x01)
+#define LRT_SPARE1         ((ushort) 0x02)
+#define INTRO_RCVD         ((ushort) 0x04)
+#define FORCED_DISCONNECT  ((ushort) 0x08)
+#define LRT_SPARE2	   ((ushort) 0x80)
+
+#define TOP_OF_RTA_RAM     ((ushort) 0x7000)
+#define HOST_SERIAL_POINTER (unsigned char **) (TOP_OF_RTA_RAM - 2 * sizeof (ushort))
+
+/* Flags for ltt_status */
+#define  WAITING_ACK		(ushort) 0x0001
+#define  DATA_SENT		(ushort) 0x0002
+#define  WAITING_RUP		(ushort) 0x0004
+#define  WAITING_RETRY		(ushort) 0x0008
+#define  WAITING_TOPOLOGY	(ushort) 0x0010
+#define  SEND_SYNC		(ushort) 0x0020
+#define  FOAD_THIS_LINK		(ushort) 0x0040
+#define  REQUEST_SYNC		(ushort) 0x0080
+#define  REMOTE_DYING		(ushort) 0x0100
+#define  DIE_NOW		(ushort) 0x0200
+
+/* Boot request stuff */
+#define BOOT_REQUEST       ((ushort) 0)    /* Request for a boot */
+#define BOOT_ABORT         ((ushort) 1)    /* Abort a boot */
+#define BOOT_SEQUENCE      ((ushort) 2)    /* Packet with the number of packets
+                                              and load address */
+#define BOOT_COMPLETED     ((ushort) 3)    /* Boot completed */
+
+/* States that a link can be in */
+#define	LINK_DISCONNECTED  ((ushort) 0)    /* Disconnected */
+#define LINK_BOOT1         ((ushort) 1)    /* Trying to send 1st stage boot */
+#define LINK_BOOT2         ((ushort) 2)    /* Trying to send 2nd stage boot */
+#define LINK_BOOT2WAIT     ((ushort) 3)    /* Waiting for selftest results */
+#define LINK_BOOT3         ((ushort) 4)    /* Trying to send 3rd stage boots */
+#define LINK_SYNC          ((ushort) 5)    /* Syncing */
+
+#define LINK_INTRO         ((ushort) 10)    /* Introductory packet */
+#define LINK_SUPPLYID      ((ushort) 11)    /* Trying to supply an ID */
+#define LINK_TOPOLOGY      ((ushort) 12)    /* Send a topology update */
+#define LINK_REQUESTID     ((ushort) 13)    /* Waiting for an ID */
+#define LINK_CONNECTED     ((ushort) 14)    /* Connected */
+
+#define LINK_INTERCONNECT  ((ushort) 20)   /* Subnets interconnected */
+
+#define LINK_SPARE	   ((ushort) 40)
+
+/*
+** Set the default timeout for link communications.
+*/
+#define	LINKTIMEOUT		(400 * MILLISECOND)
+
+/*
+** LED stuff
+*/
+#if defined(RTA)
+#define LED_OFF            ((ushort) 0)    /* LED off */
+#define LED_RED            ((ushort) 1)    /* LED Red */
+#define LED_GREEN          ((ushort) 2)    /* LED Green */
+#define LED_ORANGE         ((ushort) 4)    /* LED Orange */
+#define LED_1TO8_OPEN      ((ushort) 1)    /* Port 1->8 LED on */
+#define LED_9TO16_OPEN     ((ushort) 2)    /* Port 9->16 LED on */
+#define LED_SET_COLOUR(colour)	(link->led = (colour))
+#define LED_OR_COLOUR(colour)	(link->led |= (colour))
+#define LED_TIMEOUT(time)    (link->led_timeout = RioTimePlus(RioTime(),(time)))
+#else
+#define LED_SET_COLOUR(colour)
+#define LED_OR_COLOUR(colour)
+#define LED_TIMEOUT(time)
+#endif /* RTA */
+
+struct LPB {
+               WORD          link_number ;       /* Link Number */
+               Channel_ptr   in_ch ;             /* Link In Channel */
+               Channel_ptr   out_ch ;            /* Link Out Channel */
+#ifdef RTA
+               uchar        stat_led ;          /* Port open leds */
+               uchar        led ;               /* True, light led! */
+#endif
+               BYTE attached_serial[4]; /* Attached serial number */
+               BYTE attached_host_serial[4];
+                                                 /* Serial number of Host who
+                                                    booted the other end */
+               WORD          descheduled ;       /* Currently Descheduled */
+               WORD          state;              /* Current state */
+               WORD          send_poll ;         /* Send a Poll Packet */
+               Process_ptr   ltt_p ;             /* Process Descriptor */
+               Process_ptr   lrt_p ;             /* Process Descriptor */
+               WORD          lrt_status ;        /* Current lrt status */
+               WORD          ltt_status ;        /* Current ltt status */
+               WORD          timeout ;           /* Timeout value */
+               WORD          topology;           /* Topology bits */
+               WORD          mon_ltt ;
+               WORD          mon_lrt ;
+               WORD          WaitNoBoot ;	 /* Secs to hold off booting */
+               PKT_ptr       add_packet_list;    /* Add packets to here */
+               PKT_ptr       remove_packet_list; /* Send packets from here */
+#ifdef RTA
+#ifdef DCIRRUS
+#define    QBUFS_PER_REDIRECT (4 / PKTS_PER_BUFFER + 1) 
+#else
+#define    QBUFS_PER_REDIRECT (8 / PKTS_PER_BUFFER + 1) 
+#endif
+               PKT_ptr_ptr   rd_add ;            /* Add a new Packet here */
+               Q_BUF_ptr     rd_add_qb;          /* Pointer to the add Q buf */
+               PKT_ptr_ptr   rd_add_st_qbb ;     /* Pointer to start of the Q's buf */
+               PKT_ptr_ptr   rd_add_end_qbb ;    /* Pointer to the end of the Q's buf */
+               PKT_ptr_ptr   rd_remove ;         /* Remove a Packet here */
+               Q_BUF_ptr     rd_remove_qb ;      /* Pointer to the remove Q buf */
+               PKT_ptr_ptr   rd_remove_st_qbb ;  /* Pointer to the start of the Q buf */
+               PKT_ptr_ptr   rd_remove_end_qbb ; /* Pointer to the end of the Q buf */
+               ushort        pkts_in_q ;         /* Packets in queue */
+#endif
+
+               Channel_ptr   lrt_fail_chan ;     /* Lrt's failure channel */
+               Channel_ptr   ltt_fail_chan ;     /* Ltt's failure channel */
+
+#if defined (HOST) || defined (INKERNEL)
+ /* RUP structure for HOST to driver communications */
+               struct RUP           rup ;              
+#endif
+               struct RUP           link_rup;           /* RUP for the link (POLL,
+                                                    topology etc.) */
+               WORD          attached_link ;     /* Number of attached link */
+               WORD          csum_errors ;       /* csum errors */
+               WORD          num_disconnects ;   /* number of disconnects */
+               WORD          num_sync_rcvd ;     /* # sync's received */
+               WORD          num_sync_rqst ;     /* # sync requests */
+               WORD          num_tx ;            /* Num pkts sent */
+               WORD          num_rx ;            /* Num pkts received */
+               WORD          module_attached;    /* Module tpyes of attached */
+               WORD          led_timeout;        /* LED timeout */
+               WORD          first_port;         /* First port to service */
+               WORD          last_port;          /* Last port to service */
+           } ;
+
+#endif
+
+/*********** end of file ***********/
diff --git a/drivers/char/rio/linux_compat.h b/drivers/char/rio/linux_compat.h
new file mode 100644
index 0000000..d53843a
--- /dev/null
+++ b/drivers/char/rio/linux_compat.h
@@ -0,0 +1,122 @@
+/*
+ * (C) 2000 R.E.Wolff@BitWizard.nl
+ *
+ *      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.
+ */
+
+#include <linux/interrupt.h>
+
+
+#define disable(oldspl) save_flags (oldspl)
+#define restore(oldspl) restore_flags (oldspl)
+
+#define sysbrk(x) kmalloc ((x),in_interrupt()? GFP_ATOMIC : GFP_KERNEL)
+#define sysfree(p,size) kfree ((p))
+
+#define WBYTE(p,v) writeb(v, &p)
+#define RBYTE(p)   readb (&p)
+#define WWORD(p,v) writew(v, &p)
+#define RWORD(p)   readw(&p)
+#define WINDW(p,v) writew(v, p)
+#define RINDW(p)   readw(p)
+
+#define DEBUG_ALL
+
+#define cprintf printk
+
+#ifdef __KERNEL__
+#define INKERNEL
+#endif
+
+struct ttystatics {
+  struct termios tm;
+};
+
+#define bzero(d, n)         memset((d), 0, (n))
+#define bcopy(src, dest, n) memcpy ((dest), (src), (n))
+
+#define SEM_SIGIGNORE 0x1234
+
+#ifdef DEBUG_SEM
+#define swait(a,b)      printk ("waiting:    " __FILE__ " line %d\n", __LINE__)
+#define ssignal(sem)    printk ("signalling: " __FILE__ " line %d\n", __LINE__)
+
+#define sreset(sem)     printk ("sreset:     " __FILE__ "\n")
+#define sem_init(sem,v) printk ("sreset:     " __FILE__ "\n")
+#endif
+
+
+#define getpid()    (current->pid)
+
+#define QSIZE SERIAL_XMIT_SIZE
+
+#define pseterr(errno) return (- errno)
+
+#define V_CBAUD CBAUD
+
+/* For one reason or another rioboot.c uses delay instead of RIODelay. */
+#define delay(x,y) RIODelay(NULL, y)
+
+extern int rio_debug;
+
+#define RIO_DEBUG_INIT         0x000001
+#define RIO_DEBUG_BOOT         0x000002
+#define RIO_DEBUG_CMD          0x000004
+#define RIO_DEBUG_CTRL         0x000008
+#define RIO_DEBUG_INTR         0x000010
+#define RIO_DEBUG_PARAM        0x000020
+#define RIO_DEBUG_ROUTE        0x000040
+#define RIO_DEBUG_TABLE        0x000080
+#define RIO_DEBUG_TTY          0x000100
+#define RIO_DEBUG_FLOW         0x000200
+#define RIO_DEBUG_MODEMSIGNALS 0x000400
+#define RIO_DEBUG_PROBE        0x000800
+#define RIO_DEBUG_CLEANUP      0x001000
+#define RIO_DEBUG_IFLOW        0x002000
+#define RIO_DEBUG_PFE          0x004000
+#define RIO_DEBUG_REC          0x008000
+#define RIO_DEBUG_SPINLOCK     0x010000
+#define RIO_DEBUG_DELAY        0x020000
+#define RIO_DEBUG_MOD_COUNT    0x040000
+
+/* Copied over from riowinif.h . This is ugly. The winif file declares
+also much other stuff which is incompatible with the headers from
+the older driver. The older driver includes "brates.h" which shadows
+the definitions from Linux, and is incompatible... */
+
+/* RxBaud and TxBaud definitions... */
+#define	RIO_B0			0x00			/* RTS / DTR signals dropped */
+#define	RIO_B50			0x01			/* 50 baud */
+#define	RIO_B75			0x02			/* 75 baud */
+#define	RIO_B110		0x03			/* 110 baud */
+#define	RIO_B134		0x04			/* 134.5 baud */
+#define	RIO_B150		0x05			/* 150 baud */
+#define	RIO_B200		0x06			/* 200 baud */
+#define	RIO_B300		0x07			/* 300 baud */
+#define	RIO_B600		0x08			/* 600 baud */
+#define	RIO_B1200		0x09			/* 1200 baud */
+#define	RIO_B1800		0x0A			/* 1800 baud */
+#define	RIO_B2400		0x0B			/* 2400 baud */
+#define	RIO_B4800		0x0C			/* 4800 baud */
+#define	RIO_B9600		0x0D			/* 9600 baud */
+#define	RIO_B19200		0x0E			/* 19200 baud */
+#define	RIO_B38400		0x0F			/* 38400 baud */
+#define	RIO_B56000		0x10			/* 56000 baud */
+#define	RIO_B57600		0x11			/* 57600 baud */
+#define	RIO_B64000		0x12			/* 64000 baud */
+#define	RIO_B115200		0x13			/* 115200 baud */
+#define	RIO_B2000		0x14			/* 2000 baud */
+
+
diff --git a/drivers/char/rio/list.h b/drivers/char/rio/list.h
new file mode 100644
index 0000000..a4f7f1f
--- /dev/null
+++ b/drivers/char/rio/list.h
@@ -0,0 +1,196 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                      L I S T                                 *******
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Jeremy Rolls.
+ Date    : 04-Nov-1990
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+ ***************************************************************************/
+
+#ifndef _list_h
+#define _list_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+static char *_rio_list_h_sccs = "@(#)list.h	1.9" ;
+#endif
+#endif
+
+#define PKT_IN_USE    0x1
+
+#ifdef INKERNEL
+
+#define ZERO_PTR (ushort) 0x8000
+#define	CaD	PortP->Caddr
+
+/*
+** We can add another packet to a transmit queue if the packet pointer pointed
+** to by the TxAdd pointer has PKT_IN_USE clear in its address.
+*/
+
+#ifndef linux
+#if defined( MIPS ) && !defined( MIPSEISA )
+/* May the shoes of the Devil dance on your grave for creating this */
+#define   can_add_transmit(PacketP,PortP) \
+          (!((uint)(PacketP = (struct PKT *)RIO_PTR(CaD,RINDW(PortP->TxAdd))) \
+          & (PKT_IN_USE<<2)))
+
+#elif  defined(MIPSEISA) || defined(nx6000) || \
+       defined(drs6000)  || defined(UWsparc)
+
+#define   can_add_transmit(PacketP,PortP) \
+          (!((uint)(PacketP = (struct PKT *)RIO_PTR(CaD,RINDW(PortP->TxAdd))) \
+	  & PKT_IN_USE))
+
+#else
+#define   can_add_transmit(PacketP,PortP) \
+          (!((uint)(PacketP = (struct PKT *)RIO_PTR(CaD,*PortP->TxAdd)) \
+	  & PKT_IN_USE))
+#endif
+
+/*
+** To add a packet to the queue, you set the PKT_IN_USE bit in the address,
+** and then move the TxAdd pointer along one position to point to the next
+** packet pointer. You must wrap the pointer from the end back to the start.
+*/
+#if defined(MIPS) || defined(nx6000) || defined(drs6000) || defined(UWsparc)
+#   define add_transmit(PortP)  \
+	WINDW(PortP->TxAdd,RINDW(PortP->TxAdd) | PKT_IN_USE);\
+	if (PortP->TxAdd == PortP->TxEnd)\
+	    PortP->TxAdd = PortP->TxStart;\
+	else\
+	    PortP->TxAdd++;\
+	WWORD(PortP->PhbP->tx_add , RIO_OFF(CaD,PortP->TxAdd));
+#elif defined(AIX)
+#   define add_transmit(PortP)  \
+	{\
+	    register ushort *TxAddP = (ushort *)RIO_PTR(Cad,PortP->TxAddO);\
+	    WINDW( TxAddP, RINDW( TxAddP ) | PKT_IN_USE );\
+	    if (PortP->TxAddO == PortP->TxEndO )\
+		PortP->TxAddO = PortP->TxStartO;\
+	    else\
+		PortP->TxAddO += sizeof(ushort);\
+	    WWORD(((PHB *)RIO_PTR(Cad,PortP->PhbO))->tx_add , PortP->TxAddO );\
+	}
+#else
+#   define add_transmit(PortP)  \
+	*PortP->TxAdd |= PKT_IN_USE;\
+	if (PortP->TxAdd == PortP->TxEnd)\
+	    PortP->TxAdd = PortP->TxStart;\
+	else\
+	    PortP->TxAdd++;\
+	PortP->PhbP->tx_add = RIO_OFF(CaD,PortP->TxAdd);
+#endif
+
+/*
+** can_remove_receive( PacketP, PortP ) returns non-zero if PKT_IN_USE is set
+** for the next packet on the queue. It will also set PacketP to point to the
+** relevant packet, [having cleared the PKT_IN_USE bit]. If PKT_IN_USE is clear,
+** then can_remove_receive() returns 0.
+*/
+#if defined(MIPS) || defined(nx6000) || defined(drs6000) || defined(UWsparc)
+#   define can_remove_receive(PacketP,PortP) \
+	((RINDW(PortP->RxRemove) & PKT_IN_USE) ? \
+	(PacketP=(struct PKT *)RIO_PTR(CaD,(RINDW(PortP->RxRemove) & ~PKT_IN_USE))):0)
+#elif defined(AIX)
+#   define can_remove_receive(PacketP,PortP) \
+	((RINDW((ushort *)RIO_PTR(Cad,PortP->RxRemoveO)) & PKT_IN_USE) ? \
+	(PacketP=(struct PKT *)RIO_PTR(Cad,RINDW((ushort *)RIO_PTR(Cad,PortP->RxRemoveO)) & ~PKT_IN_USE)):0)
+#else
+#   define can_remove_receive(PacketP,PortP) \
+	((*PortP->RxRemove & PKT_IN_USE) ? \
+	(PacketP=(struct PKT *)RIO_PTR(CaD,(*PortP->RxRemove & ~PKT_IN_USE))):0)
+#endif
+
+
+/*
+** Will God see it within his heart to forgive us for this thing that
+** we have created? To remove a packet from the receive queue you clear
+** its PKT_IN_USE bit, and then bump the pointers. Once the pointers
+** get to the end, they must be wrapped back to the start.
+*/
+#if defined(MIPS) || defined(nx6000) || defined(drs6000) || defined(UWsparc)
+#   define remove_receive(PortP) \
+	WINDW(PortP->RxRemove, (RINDW(PortP->RxRemove) & ~PKT_IN_USE));\
+	if (PortP->RxRemove == PortP->RxEnd)\
+	    PortP->RxRemove = PortP->RxStart;\
+	else\
+	    PortP->RxRemove++;\
+	WWORD(PortP->PhbP->rx_remove , RIO_OFF(CaD,PortP->RxRemove));
+#elif defined(AIX)
+#   define remove_receive(PortP) \
+    {\
+        register ushort *RxRemoveP = (ushort *)RIO_PTR(Cad,PortP->RxRemoveO);\
+        WINDW( RxRemoveP, RINDW( RxRemoveP ) & ~PKT_IN_USE );\
+        if (PortP->RxRemoveO == PortP->RxEndO)\
+            PortP->RxRemoveO = PortP->RxStartO;\
+        else\
+            PortP->RxRemoveO += sizeof(ushort);\
+        WWORD(((PHB *)RIO_PTR(Cad,PortP->PhbO))->rx_remove, PortP->RxRemoveO );\
+    }
+#else
+#   define remove_receive(PortP) \
+	*PortP->RxRemove &= ~PKT_IN_USE;\
+	if (PortP->RxRemove == PortP->RxEnd)\
+	    PortP->RxRemove = PortP->RxStart;\
+	else\
+	    PortP->RxRemove++;\
+	PortP->PhbP->rx_remove = RIO_OFF(CaD,PortP->RxRemove);
+#endif
+#endif
+
+
+#else /* !IN_KERNEL */
+
+#define ZERO_PTR NULL
+
+
+#ifdef HOST
+/* #define can_remove_transmit(pkt,phb) ((((char*)pkt = (*(char**)(phb->tx_remove))-1) || 1)) && (*phb->u3.s2.tx_remove_ptr & PKT_IN_USE))   */
+#define remove_transmit(phb) *phb->u3.s2.tx_remove_ptr &= ~(ushort)PKT_IN_USE;\
+                             if (phb->tx_remove == phb->tx_end)\
+                                phb->tx_remove = phb->tx_start;\
+                             else\
+                                phb->tx_remove++;
+#define can_add_receive(phb) !(*phb->u4.s2.rx_add_ptr & PKT_IN_USE)
+#define add_receive(pkt,phb) *phb->rx_add = pkt;\
+                             *phb->u4.s2.rx_add_ptr |= PKT_IN_USE;\
+                             if (phb->rx_add == phb->rx_end)\
+                                phb->rx_add = phb->rx_start;\
+                             else\
+                                phb->rx_add++;
+#endif
+#endif
+
+#ifdef RTA
+#define splx(oldspl)    if ((oldspl) == 0) spl0()
+#endif
+
+#endif /* ifndef _list.h */
+/*********** end of file ***********/
diff --git a/drivers/char/rio/lrt.h b/drivers/char/rio/lrt.h
new file mode 100644
index 0000000..bbac8fa
--- /dev/null
+++ b/drivers/char/rio/lrt.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                      L R T
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_lrt_h_sccs = "@(#)lrt.h	1.1" ;
+#endif
+#endif
+
+
+#ifdef DCIRRUS
+#define LRT_STACK       (unsigned short) 600
+#else
+#define LRT_STACK        (ushort) 200
+#endif
+
+
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/ltt.h b/drivers/char/rio/ltt.h
new file mode 100644
index 0000000..f27dcec
--- /dev/null
+++ b/drivers/char/rio/ltt.h
@@ -0,0 +1,55 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                      L T T
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_ltt_h_sccs = "@(#)ltt.h	1.1" ;
+#endif
+#endif
+
+#ifdef DCIRRUS
+#define LTT_STACK       (unsigned short)  600
+#else
+#define LTT_STACK       (ushort) 200
+#endif
+ 
+
+
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/lttwake.h b/drivers/char/rio/lttwake.h
new file mode 100644
index 0000000..fe17d0e
--- /dev/null
+++ b/drivers/char/rio/lttwake.h
@@ -0,0 +1,53 @@
+
+
+
+/****************************************************************************
+ *******                                                              *******
+ *******            L T T    W A K E U P    H E A D E R
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_lttwake_h_sccs = "@(#)lttwake.h	1.1" ;
+#endif
+#endif
+
+#define LTT_WAKEUP_STACK          500
+#define LTT_WAKEUP_INTERVAL       (int) (500 * MILLISECOND)
+
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/map.h b/drivers/char/rio/map.h
new file mode 100644
index 0000000..400645a
--- /dev/null
+++ b/drivers/char/rio/map.h
@@ -0,0 +1,103 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: map.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:11
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)map.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_map_h__
+#define __rio_map_h__
+
+#ifdef SCCS_LABELS
+static char *_map_h_sccs_ = "@(#)map.h	1.2";
+#endif
+
+/*
+** mapping structure passed to and from the config.rio program to
+** determine the current topology of the world
+*/
+
+#define MAX_MAP_ENTRY 17
+#define	TOTAL_MAP_ENTRIES (MAX_MAP_ENTRY*RIO_SLOTS)
+#define	MAX_NAME_LEN 32
+
+struct Map
+{
+	uint	HostUniqueNum;	        /* Supporting hosts unique number */
+	uint	RtaUniqueNum;	        /* Unique number */
+	/*
+	** The next two IDs must be swapped on big-endian architectures
+	** when using a v2.04 /etc/rio/config with a v3.00 driver (when
+	** upgrading for example).
+	*/
+	ushort	ID;			/* ID used in the subnet */
+	ushort	ID2;			/* ID of 2nd block of 8 for 16 port */
+	ulong	Flags;			/* Booted, ID Given, Disconnected */
+	ulong	SysPort;		/* First tty mapped to this port */
+	struct Top   Topology[LINKS_PER_UNIT];	/* ID connected to each link */
+	char	Name[MAX_NAME_LEN];        /* Cute name by which RTA is known */
+};
+
+/*
+** Flag values:
+*/
+#define	RTA_BOOTED		0x00000001
+#define RTA_NEWBOOT		0x00000010
+#define	MSG_DONE		0x00000020
+#define	RTA_INTERCONNECT	0x00000040
+#define	RTA16_SECOND_SLOT	0x00000080
+#define	BEEN_HERE		0x00000100
+#define SLOT_TENTATIVE		0x40000000
+#define SLOT_IN_USE		0x80000000
+
+/*
+** HostUniqueNum is the unique number from the host card that this RTA
+** is to be connected to.
+** RtaUniqueNum is the unique number of the RTA concerned. It will be ZERO
+** if the slot in the table is unused. If it is the same as the HostUniqueNum
+** then this slot represents a host card.
+** Flags contains current boot/route state info
+** SysPort is a value in the range 0-504, being the number of the first tty
+** on this RTA. Each RTA supports 8 ports. The SysPort value must be modulo 8.
+** SysPort 0-127 correspond to /dev/ttyr001 to /dev/ttyr128, with minor
+** numbers 0-127. SysPort 128-255 correspond to /dev/ttyr129 to /dev/ttyr256,
+** again with minor numbers 0-127, and so on for SysPorts 256-383 and 384-511
+** ID will be in the range 0-16 for a `known' RTA. ID will be 0xFFFF for an
+** unused slot/unknown ID etc.
+** The Topology array contains the ID of the unit connected to each of the
+** four links on this unit. The entry will be 0xFFFF if NOTHING is connected
+** to the link, or will be 0xFF00 if an UNKNOWN unit is connected to the link.
+** The Name field is a null-terminated string, upto 31 characters, containing
+** the 'cute' name that the sysadmin/users know the RTA by. It is permissible
+** for this string to contain any character in the range \040 to \176 inclusive.
+** In particular, ctrl sequences and DEL (0x7F, \177) are not allowed. The
+** special character '%' IS allowable, and needs no special action.
+**
+*/
+
+#endif
diff --git a/drivers/char/rio/mca.h b/drivers/char/rio/mca.h
new file mode 100644
index 0000000..08a327e
--- /dev/null
+++ b/drivers/char/rio/mca.h
@@ -0,0 +1,73 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: mca.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:11
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)mca.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_mca_h__
+#define	__rio_mca_h__
+
+#ifdef SCCS_LABELS
+static char *_mca_h_sccs_ = "@(#)mca.h	1.2";
+#endif
+
+/*
+** Micro Channel stuff
+*/
+
+#define	McaMaxSlots	8
+#define McaSlotSelect	0x96
+#define	McaSlotEnable	0x08
+#define	McaIdLow	0x100
+#define	McaIdHigh	0x101
+#define	McaIrqEnable	0x102
+#define	McaMemory	0x103
+#define McaRIOId	0x6a5c
+#define	McaIrq9		0x00
+#define	McaIrq3		0x02
+#define	McaIrq4		0x04
+#define	McaIrq7		0x06
+#define	McaIrq10	0x08
+#define	McaIrq11	0x0A
+#define	McaIrq12	0x0C
+#define	McaIrq15	0x0E
+#define McaIrqMask	0x0E
+#define	McaCardEnable	0x01
+#define	McaAddress(X)	(((X)&0xFF)<<16)
+
+#define	McaTpFastLinks	        0x40
+#define	McaTpSlowLinks	        0x00
+#define	McaTpBootFromRam	0x01
+#define	McaTpBootFromLink	0x00
+#define	McaTpBusEnable		0x02
+#define	McaTpBusDisable		0x00
+
+#define	RIO_MCA_DEFAULT_MODE	SLOW_LINKS
+
+#endif	/* __rio_mca_h__ */
diff --git a/drivers/char/rio/mesg.h b/drivers/char/rio/mesg.h
new file mode 100644
index 0000000..9cf6c0b
--- /dev/null
+++ b/drivers/char/rio/mesg.h
@@ -0,0 +1,41 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: mesg.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:12
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)mesg.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_mesg_h__
+#define	__rio_mesg_h__
+
+#ifdef SCCS_LABELS
+static char *_mesg_h_sccs_ = "@(#)mesg.h	1.2";
+#endif
+
+
+#endif /* __rio_mesg_h__ */
diff --git a/drivers/char/rio/param.h b/drivers/char/rio/param.h
new file mode 100644
index 0000000..2dc30b9
--- /dev/null
+++ b/drivers/char/rio/param.h
@@ -0,0 +1,61 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: param.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:12
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)param.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_param_h__
+#define __rio_param_h__
+
+#ifdef SCCS_LABELS
+static char *_param_h_sccs_ = "@(#)param.h	1.2";
+#endif
+
+
+/*
+** the param command block, as used in OPEN and PARAM calls.
+*/
+
+struct phb_param
+{
+    BYTE    Cmd;        /* It is very important that these line up */
+    BYTE    Cor1;       /* with what is expected at the other end. */
+    BYTE    Cor2;       /* to confirm that you've got it right,    */
+    BYTE    Cor4;       /* check with cirrus/cirrus.h              */
+    BYTE    Cor5;
+    BYTE    TxXon;	/* Transmit X-On character */
+    BYTE    TxXoff;	/* Transmit X-Off character */
+    BYTE    RxXon;	/* Receive X-On character */
+    BYTE    RxXoff;	/* Receive X-Off character */
+    BYTE    LNext;	/* Literal-next character */
+    BYTE    TxBaud;	/* Transmit baudrate */
+    BYTE    RxBaud;	/* Receive baudrate */
+};
+
+#endif
diff --git a/drivers/char/rio/parmmap.h b/drivers/char/rio/parmmap.h
new file mode 100644
index 0000000..46f99df
--- /dev/null
+++ b/drivers/char/rio/parmmap.h
@@ -0,0 +1,96 @@
+/****************************************************************************
+ *******                                                              *******
+ *******               H O S T   M E M O R Y  M A P
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+6/4/1991   jonb		     Made changes to accommodate Mips R3230 bus
+ ***************************************************************************/
+
+#ifndef _parmap_h
+#define _parmap_h
+
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_parmmap_h_sccs = "@(#)parmmap.h	1.4"; */
+#endif
+#endif
+
+typedef struct PARM_MAP PARM_MAP ;
+
+struct PARM_MAP
+{
+PHB_ptr           phb_ptr ;              /* Pointer to the PHB array */
+WORD_ptr          phb_num_ptr ;          /* Ptr to Number of PHB's */
+FREE_LIST_ptr     free_list;             /* Free List pointer */
+FREE_LIST_ptr     free_list_end;         /* Free List End pointer */
+Q_BUF_ptr_ptr     q_free_list_ptr ;      /* Ptr to Q_BUF variable */
+BYTE_ptr          unit_id_ptr ;          /* Unit Id */
+LPB_ptr           link_str_ptr ;         /* Link Structure Array */
+BYTE_ptr          bootloader_1 ;         /* 1st Stage Boot Loader */
+BYTE_ptr          bootloader_2 ;         /* 2nd Stage Boot Loader */
+WORD_ptr          port_route_map_ptr ;   /* Port Route Map */
+ROUTE_STR_ptr     route_ptr ;            /* Unit Route Map */
+NUMBER_ptr        map_present ;          /* Route Map present */
+NUMBER            pkt_num ;               /* Total number of packets */
+NUMBER            q_num ;                 /* Total number of Q packets */
+WORD              buffers_per_port ;      /* Number of buffers per port */
+WORD              heap_size ;             /* Initial size of heap */
+WORD              heap_left ;             /* Current Heap left */
+WORD              error ;                 /* Error code */
+WORD              tx_max;                 /* Max number of tx pkts per phb */
+WORD              rx_max;                 /* Max number of rx pkts per phb */
+WORD              rx_limit;               /* For high / low watermarks */
+NUMBER            links ;                 /* Links to use */
+NUMBER            timer ;                 /* Interrupts per second */
+RUP_ptr           rups ;                 /* Pointer to the RUPs */
+WORD              max_phb ;              /* Mostly for debugging */
+WORD              living ;               /* Just increments!! */
+WORD              init_done ;            /* Initialisation over */
+WORD              booting_link ;
+WORD              idle_count ;           /* Idle time counter */
+WORD              busy_count ;           /* Busy counter */
+WORD              idle_control ;         /* Control Idle Process */
+#if defined(HOST) || defined(INKERNEL)
+WORD              tx_intr;               /* TX interrupt pending */
+WORD              rx_intr;               /* RX interrupt pending */
+WORD              rup_intr;              /* RUP interrupt pending */
+#endif
+#if defined(RTA)
+WORD		  dying_count;		/* Count of processes dead */
+#endif
+} ;
+
+#endif
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/pci.h b/drivers/char/rio/pci.h
new file mode 100644
index 0000000..dc635bd
--- /dev/null
+++ b/drivers/char/rio/pci.h
@@ -0,0 +1,76 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: pci.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:12
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)pci.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_pci_h__
+#define	__rio_pci_h__
+
+#ifdef SCCS_LABELS
+static char *_pci_h_sccs_ = "@(#)pci.h	1.2";
+#endif
+
+/*
+** PCI stuff
+*/
+
+#define	PCITpFastClock		0x80
+#define	PCITpSlowClock		0x00
+#define	PCITpFastLinks	        0x40
+#define	PCITpSlowLinks	        0x00
+#define	PCITpIntEnable		0x04
+#define	PCITpIntDisable		0x00
+#define	PCITpBusEnable		0x02
+#define	PCITpBusDisable		0x00
+#define	PCITpBootFromRam	0x01
+#define	PCITpBootFromLink	0x00
+
+#define	RIO_PCI_VENDOR		0x11CB
+#define	RIO_PCI_DEVICE		0x8000
+#define	RIO_PCI_BASE_CLASS	0x02
+#define	RIO_PCI_SUB_CLASS	0x80
+#define	RIO_PCI_PROG_IFACE	0x00
+
+#define RIO_PCI_RID		0x0008
+#define RIO_PCI_BADR0		0x0010
+#define RIO_PCI_INTLN		0x003C
+#define RIO_PCI_INTPIN		0x003D
+
+#define	RIO_PCI_MEM_SIZE	65536
+
+#define	RIO_PCI_TURBO_TP	0x80
+#define	RIO_PCI_FAST_LINKS	0x40
+#define	RIO_PCI_INT_ENABLE	0x04
+#define	RIO_PCI_TP_BUS_ENABLE	0x02
+#define	RIO_PCI_BOOT_FROM_RAM	0x01
+
+#define	RIO_PCI_DEFAULT_MODE	0x05
+
+#endif	/* __rio_pci_h__ */
diff --git a/drivers/char/rio/phb.h b/drivers/char/rio/phb.h
new file mode 100644
index 0000000..e1483a0
--- /dev/null
+++ b/drivers/char/rio/phb.h
@@ -0,0 +1,293 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                 P H B     H E A D E R                        *******
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra, Jeremy Rolls
+ Date    : 
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _phb_h
+#define _phb_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_phb_h_sccs = "@(#)phb.h	1.12"; */
+#endif
+#endif
+
+
+ /*************************************************
+  * Set the LIMIT values.
+  ************************************************/
+#ifdef RTA
+#define RX_LIMIT       (ushort) 3
+#endif
+#ifdef HOST
+#define RX_LIMIT       (ushort) 1
+#endif
+
+
+/*************************************************
+ * Handshake asserted. Deasserted by the LTT(s)
+ ************************************************/
+#define PHB_HANDSHAKE_SET      ((ushort) 0x001) /* Set by LRT */
+
+#define PHB_HANDSHAKE_RESET     ((ushort) 0x002) /* Set by ISR / driver */
+
+#define PHB_HANDSHAKE_FLAGS     (PHB_HANDSHAKE_RESET | PHB_HANDSHAKE_SET)
+                                                /* Reset by ltt */
+
+
+/*************************************************
+ * Maximum number of PHB's
+ ************************************************/
+#if defined (HOST) || defined (INKERNEL)
+#define MAX_PHB               ((ushort) 128)  /* range 0-127 */
+#else
+#define MAX_PHB               ((ushort) 8)    /* range 0-7 */
+#endif
+
+/*************************************************
+ * Defines for the mode fields
+ ************************************************/
+#define TXPKT_INCOMPLETE        0x0001  /* Previous tx packet not completed */
+#define TXINTR_ENABLED          0x0002  /* Tx interrupt is enabled */
+#define TX_TAB3                 0x0004  /* TAB3 mode */
+#define TX_OCRNL                0x0008  /* OCRNL mode */
+#define TX_ONLCR                0x0010  /* ONLCR mode */
+#define TX_SENDSPACES           0x0020  /* Send n spaces command needs 
+                                           completing */
+#define TX_SENDNULL             0x0040  /* Escaping NULL needs completing */
+#define TX_SENDLF               0x0080  /* LF -> CR LF needs completing */
+#define TX_PARALLELBUG          0x0100  /* CD1400 LF -> CR LF bug on parallel
+                                           port */
+#define TX_HANGOVER             (TX_SENDSPACES | TX_SENDLF | TX_SENDNULL)
+#define TX_DTRFLOW		0x0200	/* DTR tx flow control */
+#define	TX_DTRFLOWED		0x0400	/* DTR is low - don't allow more data
+					   into the FIFO */
+#define	TX_DATAINFIFO		0x0800	/* There is data in the FIFO */
+#define	TX_BUSY			0x1000	/* Data in FIFO, shift or holding regs */
+
+#define RX_SPARE	        0x0001   /* SPARE */
+#define RXINTR_ENABLED          0x0002   /* Rx interrupt enabled */
+#define RX_ICRNL                0x0008   /* ICRNL mode */
+#define RX_INLCR                0x0010   /* INLCR mode */
+#define RX_IGNCR                0x0020   /* IGNCR mode */
+#define RX_CTSFLOW              0x0040   /* CTSFLOW enabled */
+#define RX_IXOFF                0x0080   /* IXOFF enabled */
+#define RX_CTSFLOWED            0x0100   /* CTSFLOW and CTS dropped */
+#define RX_IXOFFED              0x0200   /* IXOFF and xoff sent */
+#define RX_BUFFERED		0x0400	 /* Try and pass on complete packets */
+
+#define PORT_ISOPEN             0x0001  /* Port open? */
+#define PORT_HUPCL              0x0002  /* Hangup on close? */
+#define PORT_MOPENPEND          0x0004  /* Modem open pending */
+#define PORT_ISPARALLEL         0x0008  /* Parallel port */
+#define PORT_BREAK              0x0010  /* Port on break */
+#define PORT_STATUSPEND		0x0020  /* Status packet pending */
+#define PORT_BREAKPEND          0x0040  /* Break packet pending */
+#define PORT_MODEMPEND          0x0080  /* Modem status packet pending */
+#define PORT_PARALLELBUG        0x0100  /* CD1400 LF -> CR LF bug on parallel
+                                           port */
+#define PORT_FULLMODEM          0x0200  /* Full modem signals */
+#define PORT_RJ45               0x0400  /* RJ45 connector - no RI signal */
+#define PORT_RESTRICTED         0x0600  /* Restricted connector - no RI / DTR */
+
+#define PORT_MODEMBITS          0x0600  /* Mask for modem fields */
+
+#define PORT_WCLOSE             0x0800  /* Waiting for close */
+#define	PORT_HANDSHAKEFIX	0x1000	/* Port has H/W flow control fix */
+#define	PORT_WASPCLOSED		0x2000	/* Port closed with PCLOSE */
+#define	DUMPMODE		0x4000	/* Dump RTA mem */
+#define	READ_REG		0x8000	/* Read CD1400 register */
+
+
+
+/**************************************************************************
+ * PHB Structure
+ * A  few words.
+ *
+ * Normally Packets are added to the end of the list and removed from
+ * the start. The pointer tx_add points to a SPACE to put a Packet.
+ * The pointer tx_remove points to the next Packet to remove
+ *************************************************************************/
+#ifndef INKERNEL
+#define src_unit     u2.s2.unit
+#define src_port     u2.s2.port
+#define dest_unit    u1.s1.unit
+#define dest_port    u1.s1.port
+#endif
+#ifdef HOST
+#define tx_start     u3.s1.tx_start_ptr_ptr
+#define tx_add       u3.s1.tx_add_ptr_ptr
+#define tx_end       u3.s1.tx_end_ptr_ptr
+#define tx_remove    u3.s1.tx_remove_ptr_ptr
+#define rx_start     u4.s1.rx_start_ptr_ptr
+#define rx_add       u4.s1.rx_add_ptr_ptr
+#define rx_end       u4.s1.rx_end_ptr_ptr
+#define rx_remove    u4.s1.rx_remove_ptr_ptr
+#endif
+typedef struct PHB PHB ;
+struct PHB {
+#ifdef RTA
+        ushort      port;
+#endif
+#ifdef INKERNEL
+        WORD      source;
+#else
+        union       
+        {
+            ushort source;              /* Complete source */
+            struct
+            {
+                unsigned char unit;     /* Source unit */
+                unsigned char port;     /* Source port */
+            } s2;
+        } u2;
+#endif
+        WORD      handshake ;
+        WORD      status ;
+        NUMBER       timeout ;           /* Maximum of 1.9 seconds */
+        WORD      link ;              /* Send down this link */
+#ifdef INKERNEL
+        WORD      destination;
+#else
+        union       
+        {
+            ushort destination;         /* Complete destination */
+            struct
+            {
+                unsigned char unit;     /* Destination unit */
+                unsigned char port;     /* Destination port */
+            } s1;
+        } u1;
+#endif
+#ifdef RTA
+        ushort      tx_pkts_added;
+        ushort      tx_pkts_removed;
+        Q_BUF_ptr   tx_q_start ;        /* Start of the Q list chain */
+        short       num_tx_q_bufs ;     /* Number of Q buffers in the chain */
+        PKT_ptr_ptr tx_add ;            /* Add a new Packet here */
+        Q_BUF_ptr   tx_add_qb;          /* Pointer to the add Q buf */
+        PKT_ptr_ptr tx_add_st_qbb ;     /* Pointer to start of the Q's buf */
+        PKT_ptr_ptr tx_add_end_qbb ;    /* Pointer to the end of the Q's buf */
+        PKT_ptr_ptr tx_remove ;         /* Remove a Packet here */
+        Q_BUF_ptr   tx_remove_qb ;      /* Pointer to the remove Q buf */
+        PKT_ptr_ptr tx_remove_st_qbb ;  /* Pointer to the start of the Q buf */
+        PKT_ptr_ptr tx_remove_end_qbb ; /* Pointer to the end of the Q buf */
+#endif
+#ifdef INKERNEL
+        PKT_ptr_ptr tx_start ;
+        PKT_ptr_ptr tx_end ;
+        PKT_ptr_ptr tx_add ;
+        PKT_ptr_ptr tx_remove ;
+#endif
+#ifdef HOST
+        union
+        {
+            struct
+            {
+                PKT_ptr_ptr tx_start_ptr_ptr;
+                PKT_ptr_ptr tx_end_ptr_ptr;
+                PKT_ptr_ptr tx_add_ptr_ptr;
+                PKT_ptr_ptr tx_remove_ptr_ptr;
+            } s1;
+            struct
+            {
+                ushort * tx_start_ptr;
+                ushort * tx_end_ptr;
+                ushort * tx_add_ptr;
+                ushort * tx_remove_ptr;
+            } s2;
+        } u3;
+#endif
+
+#ifdef  RTA
+        ushort      rx_pkts_added;
+        ushort      rx_pkts_removed;
+        Q_BUF_ptr   rx_q_start ;        /* Start of the Q list chain */
+        short       num_rx_q_bufs ;     /* Number of Q buffers in the chain */
+        PKT_ptr_ptr rx_add ;            /* Add a new Packet here */
+        Q_BUF_ptr   rx_add_qb ;         /* Pointer to the add Q buf */
+        PKT_ptr_ptr rx_add_st_qbb ;     /* Pointer to start of the Q's buf */
+        PKT_ptr_ptr rx_add_end_qbb ;    /* Pointer to the end of the Q's buf */
+        PKT_ptr_ptr rx_remove ;         /* Remove a Packet here */
+        Q_BUF_ptr   rx_remove_qb ;      /* Pointer to the remove Q buf */
+        PKT_ptr_ptr rx_remove_st_qbb ;  /* Pointer to the start of the Q buf */
+        PKT_ptr_ptr rx_remove_end_qbb ; /* Pointer to the end of the Q buf */
+#endif
+#ifdef INKERNEL
+        PKT_ptr_ptr rx_start ;
+        PKT_ptr_ptr rx_end ;
+        PKT_ptr_ptr rx_add ;
+        PKT_ptr_ptr rx_remove ;
+#endif
+#ifdef HOST
+        union
+        {
+            struct
+            {
+                PKT_ptr_ptr rx_start_ptr_ptr;
+                PKT_ptr_ptr rx_end_ptr_ptr;
+                PKT_ptr_ptr rx_add_ptr_ptr;
+                PKT_ptr_ptr rx_remove_ptr_ptr;
+            } s1;
+            struct
+            {
+                ushort * rx_start_ptr;
+                ushort * rx_end_ptr;
+                ushort * rx_add_ptr;
+                ushort * rx_remove_ptr;
+            } s2;
+        } u4;
+#endif
+
+#ifdef RTA                              /* some fields for the remotes */
+        ushort     flush_count;		/* Count of write flushes */
+        ushort     txmode;		/* Modes for tx */
+        ushort     rxmode;		/* Modes for rx */
+        ushort     portmode;		/* Generic modes */
+        ushort     column;		/* TAB3 column count */
+        ushort     tx_subscript;	/* (TX) Subscript into data field */
+        ushort     rx_subscript;	/* (RX) Subscript into data field */
+        PKT_ptr    rx_incomplete;	/* Hold an incomplete packet here */
+        ushort     modem_bits;		/* Modem bits to mask */
+	ushort	   lastModem;		/* Modem control lines. */
+        ushort     addr;		/* Address for sub commands */
+        ushort     MonitorTstate;	/* TRUE if monitoring tstop */
+#endif
+
+        } ;
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/pkt.h b/drivers/char/rio/pkt.h
new file mode 100644
index 0000000..66bb2ff
--- /dev/null
+++ b/drivers/char/rio/pkt.h
@@ -0,0 +1,120 @@
+/****************************************************************************
+ *******                                                              *******
+ *******            P A C K E T   H E A D E R   F I L E
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _pkt_h
+#define _pkt_h 1
+
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_pkt_h_sccs = "@(#)pkt.h	1.8"; */
+#endif
+#endif
+
+#define MAX_TTL         0xf
+#define PKT_CMD_BIT     ((ushort) 0x080)
+#define PKT_CMD_DATA    ((ushort) 0x080)
+
+#define PKT_ACK         ((ushort) 0x040)
+
+#define PKT_TGL         ((ushort) 0x020)
+
+#define PKT_LEN_MASK    ((ushort) 0x07f)
+
+#define DATA_WNDW       ((ushort) 0x10)
+#define PKT_TTL_MASK    ((ushort) 0x0f)
+
+#define PKT_MAX_DATA_LEN   72
+
+#define PKT_LENGTH         sizeof(struct PKT)
+#define SYNC_PKT_LENGTH    (PKT_LENGTH + 4)
+
+#define CONTROL_PKT_LEN_MASK PKT_LEN_MASK
+#define CONTROL_PKT_CMD_BIT  PKT_CMD_BIT
+#define CONTROL_PKT_ACK (PKT_ACK << 8)
+#define CONTROL_PKT_TGL (PKT_TGL << 8)
+#define CONTROL_PKT_TTL_MASK (PKT_TTL_MASK << 8)
+#define CONTROL_DATA_WNDW  (DATA_WNDW << 8)
+
+struct PKT    {
+#ifdef INKERNEL
+                   BYTE    dest_unit ;    /* Destination Unit Id */
+                   BYTE    dest_port ;    /* Destination POrt */
+                   BYTE    src_unit ;     /* Source Unit Id */
+                   BYTE    src_port ;     /* Source POrt */
+#else
+                   union       
+                   {
+                       ushort destination;         /* Complete destination */
+                       struct
+                       {
+                           unsigned char unit;     /* Destination unit */
+                           unsigned char port;     /* Destination port */
+                       } s1;
+                   } u1;
+                   union       
+                   {
+                       ushort source;              /* Complete source */
+                       struct
+                       {
+                           unsigned char unit;     /* Source unit */
+                           unsigned char port;     /* Source port */
+                       } s2;
+                   } u2;
+#endif
+#ifdef INKERNEL
+                   BYTE len ;
+                   BYTE control;
+#else
+                   union
+                   {
+                        ushort      control;
+                        struct
+                        {
+                            unsigned char len;
+                            unsigned char control;
+                        } s3;
+                    } u3;
+#endif
+                   BYTE    data[PKT_MAX_DATA_LEN] ;     
+                                                   /* Actual data :-) */
+                   WORD  csum ;                  /* C-SUM */
+               } ;
+#endif
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/poll.h b/drivers/char/rio/poll.h
new file mode 100644
index 0000000..d9b8e98
--- /dev/null
+++ b/drivers/char/rio/poll.h
@@ -0,0 +1,76 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                      P O L L
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _poll_h
+#define _poll_h
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_poll_h_sccs = "@(#)poll.h	1.2" ;
+#endif
+#endif
+
+
+#ifdef HOST
+#define POLL_STACK            100
+#endif
+#ifdef RTA
+#define POLL_STACK            200
+#endif
+
+#define POLL_PERIOD           (int) SECOND
+
+/* The various poll commands */
+#define POLL_POLL             0            /* We are connected and happy.. */
+#define POLL_INTRO            1            /* Introduction packet */
+#define POLL_TOPOLOGY         2            /* Topology update */
+#define POLL_ASSIGN           3            /* ID assign */
+#define POLL_FOAD             4            /* F*** Off And Die */
+#define POLL_LMD	      5		   /* Let Me Die */
+#define POLL_DYB	      6		   /* Die You Ba***** */
+
+/* The way data fields are split up for POLL packets */
+#define POLL_HOST_SERIAL      2            /* Host who booted me */
+#define POLL_MY_SERIAL        6            /* My serial number */
+#define POLL_YOUR_ID          1            /* Your ID number */
+#define POLL_TOPOLOGY_FIELDS  2            /* Topology maps */
+
+#endif
+
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/port.h b/drivers/char/rio/port.h
new file mode 100644
index 0000000..8506af0
--- /dev/null
+++ b/drivers/char/rio/port.h
@@ -0,0 +1,245 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: port.h
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 11:34:12
+**	Retrieved	: 11/6/98 11:34:21
+**
+**  ident @(#)port.h	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef	__rio_port_h__
+#define	__rio_port_h__
+
+#ifdef SCCS_LABELS
+static char *_port_h_sccs_ = "@(#)port.h	1.3";
+#endif
+
+
+#undef VPIX
+
+
+/*
+** the port data structure - one per port in the system
+*/
+
+#ifdef STATS
+struct RIOStats
+{
+	/*
+	** interrupt statistics
+	*/
+	uint	BreakIntCnt;
+	uint	ModemOffCnt;
+	uint	ModemOnCnt;
+	uint	RxIntCnt;
+	uint	TxIntCnt;
+	/*
+	** throughput statistics
+	*/
+	uint	RxCharCnt;
+	uint	RxPktCnt;
+	uint	RxSaveCnt;
+	uint	TxCharCnt;
+	uint	TxPktCnt;
+	/*
+	** driver entry statistics
+	*/
+	uint	CloseCnt;
+	uint	IoctlCnt;
+	uint	OpenCnt;
+	uint	ReadCnt;
+	uint	WriteCnt;
+	/*
+	** proc statistics
+	*/
+	uint	BlockCnt;
+	uint	OutputCnt;
+	uint	ResumeCnt;
+	uint	RflushCnt;
+	uint	SuspendCnt;
+	uint	TbreakCnt;
+	uint	TimeoutCnt;
+	uint	UnblockCnt;
+	uint	WflushCnt;
+	uint	WFBodgeCnt;
+};
+#endif
+
+/*
+**	Port data structure
+*/
+struct	Port
+{
+  struct gs_port gs; 
+  int				PortNum;	/* RIO port no., 0-511 */
+  struct Host	*HostP;
+  volatile caddr_t		Caddr;
+  ushort			HostPort;  /* Port number on host card */
+  uchar			RupNum;	/* Number of RUP for port */
+  uchar			ID2;	/* Second ID of RTA for port */
+  ulong			State;	/* FLAGS for open & xopen */
+#define	RIO_LOPEN	0x00001		/* Local open */
+#define	RIO_MOPEN	0x00002		/* Modem open */
+#define	RIO_WOPEN	0x00004		/* Waiting for open */
+#define	RIO_CLOSING	0x00008		/* The port is being close */
+#define	RIO_XPBUSY	0x00010		/* Transparent printer busy */
+#define	RIO_BREAKING	0x00020		/* Break in progress */
+#define	RIO_DIRECT	0x00040		/* Doing Direct output */
+#define	RIO_EXCLUSIVE	0x00080		/* Stream open for exclusive use */
+#define	RIO_NDELAY	0x00100		/* Stream is open FNDELAY */
+#define	RIO_CARR_ON	0x00200		/* Stream has carrier present */
+#define	RIO_XPWANTR	0x00400		/* Stream wanted by Xprint */
+#define	RIO_RBLK	0x00800		/* Stream is read-blocked */
+#define	RIO_BUSY	0x01000		/* Stream is BUSY for write */
+#define	RIO_TIMEOUT	0x02000		/* Stream timeout in progress */
+#define	RIO_TXSTOP	0x04000		/* Stream output is stopped */
+#define	RIO_WAITFLUSH	0x08000		/* Stream waiting for flush */
+#define	RIO_DYNOROD	0x10000		/* Drain failed */
+#define	RIO_DELETED	0x20000		/* RTA has been deleted */
+#define RIO_ISSCANCODE	0x40000		/* This line is in scancode mode */
+#define	RIO_USING_EUC	0x100000	/* Using extended Unix chars */
+#define	RIO_CAN_COOK	0x200000	/* This line can do cooking */
+#define RIO_TRIAD_MODE  0x400000        /* Enable TRIAD special ops. */
+#define RIO_TRIAD_BLOCK 0x800000        /* Next read will block */
+#define RIO_TRIAD_FUNC  0x1000000       /* Seen a function key coming in */
+#define RIO_THROTTLE_RX 0x2000000       /* RX needs to be throttled. */
+
+    ulong			Config;	/* FLAGS for NOREAD.... */
+#define	RIO_NOREAD	0x0001		/* Are not allowed to read port */
+#define	RIO_NOWRITE	0x0002		/* Are not allowed to write port */
+#define	RIO_NOXPRINT	0x0004		/* Are not allowed to xprint port */
+#define	RIO_NOMASK	0x0007		/* All not allowed things */
+#define RIO_IXANY	0x0008          /* Port is allowed ixany */
+#define	RIO_MODEM	0x0010		/* Stream is a modem device */
+#define	RIO_IXON	0x0020		/* Port is allowed ixon */
+#define RIO_WAITDRAIN	0x0040		/* Wait for port to completely drain */
+#define RIO_MAP_50_TO_50	0x0080	/* Map 50 baud to 50 baud */
+#define RIO_MAP_110_TO_110	0x0100	/* Map 110 baud to 110 baud */
+
+/*
+** 15.10.1998 ARG - ESIL 0761 prt fix
+** As LynxOS does not appear to support Hardware Flow Control .....
+** Define our own flow control flags in 'Config'.
+*/
+#define RIO_CTSFLOW	0x0200		/* RIO's own CTSFLOW flag */
+#define RIO_RTSFLOW	0x0400		/* RIO's own RTSFLOW flag */
+
+
+    struct PHB			*PhbP;	  /* pointer to PHB for port */
+    WORD                        *TxAdd;   /* Add packets here */
+    WORD                        *TxStart; /* Start of add array */
+    WORD                        *TxEnd;         /* End of add array */
+    WORD                        *RxRemove;      /* Remove packets here */
+    WORD                        *RxStart;       /* Start of remove array */
+    WORD                        *RxEnd;         /* End of remove array */
+    uint			RtaUniqueNum;	/* Unique number of RTA */
+    ushort			PortState;	/* status of port */
+    ushort			ModemState;	/* status of modem lines */
+    ulong			ModemLines;	/* Modem bits sent to RTA */
+    uchar			CookMode;	/* who expands CR/LF? */
+    uchar			ParamSem;	/* Prevent write during param */
+    uchar			Mapped;		/* if port mapped onto host */
+    uchar			SecondBlock;	/* if port belongs to 2nd block
+						   of 16 port RTA */
+    uchar			InUse;		/* how many pre-emptive cmds */
+    uchar			Lock;		/* if params locked */
+    uchar			Store;	/* if params stored across closes */
+    uchar			FirstOpen; /* TRUE if first time port opened */
+    uchar			FlushCmdBodge;	/* if doing a (non)flush */
+    uchar			MagicFlags;	/* require intr processing */
+#define	MAGIC_FLUSH	0x01	/* mirror of WflushFlag */
+#define	MAGIC_REBOOT	0x02	/* RTA re-booted, re-open ports */
+#define	MORE_OUTPUT_EYGOR 0x04	/* riotproc failed to empty clists */
+    uchar			WflushFlag;	/* 1 How many WFLUSHs active */
+/*
+** Transparent print stuff
+*/
+    struct Xprint
+    {
+#ifndef MAX_XP_CTRL_LEN
+#define MAX_XP_CTRL_LEN		16		/* ALSO IN DAEMON.H */
+#endif
+	uint			XpCps;
+	char			XpOn[MAX_XP_CTRL_LEN];
+	char			XpOff[MAX_XP_CTRL_LEN];
+	ushort			XpLen;		/* strlen(XpOn)+strlen(XpOff) */
+	uchar			XpActive;
+	uchar			XpLastTickOk;	/* TRUE if we can process */
+#define	XP_OPEN		00001
+#define	XP_RUNABLE	00002
+	struct ttystatics 		*XttyP;
+    } Xprint;
+#ifdef VPIX
+    v86_t			*StashP;
+    uint			IntMask;
+    struct termss 		VpixSs;
+    uchar			ModemStatusReg;	/* Modem status register */
+#endif
+    uchar			RxDataStart;
+    uchar			Cor2Copy;	/* copy of COR2 */
+    char			*Name;		/* points to the Rta's name */
+#ifdef STATS
+    struct RIOStats 		Stat;		/* ports statistics */
+#endif
+    char			*TxRingBuffer;
+    ushort			TxBufferIn;	/* New data arrives here */
+    ushort			TxBufferOut;	/* Intr removes data here */
+    ushort			OldTxBufferOut;	/* Indicates if draining */
+    int				TimeoutId;	/* Timeout ID */
+    uint			Debug;
+    uchar			WaitUntilBooted; /* True if open should block */
+    uint			statsGather;	/* True if gathering stats */
+    ulong			txchars;	/* Chars transmitted */
+    ulong			rxchars;	/* Chars received */
+    ulong			opens;		/* port open count */
+    ulong			closes;		/* port close count */
+    ulong			ioctls;		/* ioctl count */
+    uchar			LastRxTgl;	/* Last state of rx toggle bit */
+  spinlock_t				portSem;	/* Lock using this sem */
+	int				MonitorTstate;	/* Monitoring ? */
+	int				timeout_id;	/* For calling 100 ms delays */
+	int				timeout_sem;/* For calling 100 ms delays */
+	int				firstOpen;	/* First time open ? */
+	char *			p;			/* save the global struc here .. */
+};
+
+struct ModuleInfo
+{
+	char	*Name;
+	uint	Flags[4];	/* one per port on a module */
+};
+#endif
+
+/*
+** This struct is required because trying to grab an entire Port structure
+** runs into problems with differing struct sizes between driver and config.
+*/
+struct PortParams {
+	uint	Port;
+	ulong	Config;
+	ulong	State;
+	struct ttystatics	*TtyP;
+};
diff --git a/drivers/char/rio/proto.h b/drivers/char/rio/proto.h
new file mode 100644
index 0000000..ddff0ef
--- /dev/null
+++ b/drivers/char/rio/proto.h
@@ -0,0 +1,244 @@
+/*
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+ */
+#ifndef	_prototypes_h
+#define _prototypes_h
+
+
+/*
+** boot.c
+*/
+void init_boot( char *p, short stage);
+
+/*
+** disconct.c
+*/
+void kill_boot ( LPB *link );
+void disconnected( LPB *link );
+short boot_3( LPB *link, PKT *pkt );
+short send_3_pkt( LPB *link, PKT *pkt);
+
+/*
+** error.c
+*/
+void du_error(void);
+
+/*
+** formpkt.c
+*/
+ushort sum_it( PKT *pkt ) ;
+void form_rup_pkt( RUP *form_rup, PKT *pkt );
+void form_poll_pkt ( int type, LPB *link, int node );
+void form_route_pkt ( int type, PKT *pkt, LPB *link );
+
+/*
+** idle.c
+*/
+void idle( Process *idle_p );
+
+/*
+** init.c
+*/
+void general_init(void);
+void mem_halt( int error);
+
+/*
+** linkinit.c
+*/
+void initlink( u_short number, LPB *link);
+void runlink( LPB *link);
+
+/*
+** list.c
+*/
+PKT *get_free_start(void);
+void put_free_start( PKT *pkt);
+
+#ifdef HOST
+int can_remove_transmit ( PKT **pkt, PKT *pointer );
+#endif
+
+#ifdef RTA
+int spl7 ( void );
+int spl0 ( void );
+Q_BUF *get_free_q( void );
+PKT *get_free_end(void);
+int add_end( PKT *pkt, PHB *phb, int type);
+unsigned short free_packets( PHB *phb, int type);
+int can_remove_start( PKT **pkt, PHB *phb, int type);
+int can_add_start( PHB *phb, int type);
+int can_add_end( PHB *phb, int type);
+void put_free_end( PKT *pkt);
+int remove_start( PKT **pkt, PHB *phb, int type);
+#endif
+
+/*
+** Lrt.c
+*/
+void lrt( Process *lrt_p, LPB *link );
+
+#ifdef RTA
+void set_led_red ( LPB *link );
+#endif
+
+/*
+** ltt.c
+*/
+void ltt( Process *ltt_p, LPB *link, PHB *phb_ptr[] );
+void send_poll ( LPB *link );
+void request_id ( LPB *link );
+void send_topology_update ( LPB *link );
+void send_topology ( LPB *link );
+void supply_id ( LPB *link );
+
+#ifdef RTA
+void redirect_queue ( LPB *link, ushort flush );
+int obtain_rup ( int rup_number, PKT **pkt_address, LPB *link );
+#endif
+
+#ifdef TESTING_PERF
+int consume_cpu( void );
+#endif
+
+/*
+** lttwake.c
+*/
+#ifdef HOST
+void ltt_wakeup( Process *ltt_wakeup_p );
+#endif
+
+/*
+** mapgen.c
+*/
+void generate_id_map( short mapping, ROUTE_STR route[] );
+void gen_map( int mapping, int looking_at, int come_from, ROUTE_STR route[], int link, int *ttl );
+void adjust_ttl( int mapping, int looking_at, int come_from, ROUTE_STR route[], int link, int *ttl);
+void init_sys_map(void);
+
+/*
+** mmu.c
+*/
+char *rio_malloc( unsigned int amount);
+char *rio_calloc( unsigned int num, unsigned int size);
+ERROR rio_mmu_init( uint total_mem );
+
+/*
+** partn.c
+*/
+void partition_tx( struct PHB *phb, u_short tx_size, u_short rx_size, u_short rx_limit);
+
+/*
+** poll.c
+*/
+void tx_poll( Process *tx_poll_p);
+
+/*
+** process.c
+*/
+int  get_proc_space( Process **pd, int **pws, int wssize);
+
+/*
+** readrom.c
+*/
+void read_serial_number(char *buf);
+
+/*
+** rio.c
+*/
+int main( void );
+
+/*
+** route.c
+*/
+void route_update ( PKT *pkt, LPB *link);
+
+/*
+** rtainit.c
+*/
+#if defined(RTA)
+void rta_init(ushort RtaType);
+#endif /* defined(RTA) */
+
+/*
+** rupboot.c
+*/
+void rup_boot( PKT *pkt, RUP *this_rup, LPB *link);
+
+#ifdef RTA
+void kill_your_neighbour( int link_to_kill );
+#endif
+
+/*
+** rupcmd.c
+*/
+void rup_command( PKT *pkt, struct RUP *this_rup, LPB *link);
+
+/*
+** ruperr.c
+*/
+void rup_error( PKT *pkt, RUP *this_rup, LPB *link );
+void illegal_cmd( PKT *src_pkt );
+
+/*
+** ruppoll.c
+*/
+void rup_poll( PKT *pkt, RUP *this_rup, LPB *link );
+
+/*
+** ruppower.c
+*/
+void rup_power( PKT *pkt, RUP *this_rup, LPB *link );
+
+/*
+** ruprm.c
+*/
+void rup_route_map( PKT *pkt, RUP *this_rup, LPB *link);
+
+/*
+** rupstat.c
+*/
+void rup_status( PKT *pkt, RUP *this_rup, LPB *link);
+
+/*
+** rupsync.c
+*/
+void rup_sync( PKT *pkt);
+
+/*
+** rxpkt.c
+*/
+ERROR  rx_pkt( PKT_ptr_ptr pkt_address, LPB *link);
+
+/*
+** sendsts.c
+*/
+void send_status( PKT *requesting_pkt, RUP *this_rup);
+
+/*
+** serial.c
+*/
+void assign_serial ( char *ser_in, char *ser_out);
+int cmp_serial ( char *ser_1, char *ser_2);
+
+/*
+** txpkt.c
+*/
+ERROR  tx_pkt( PKT *pkt, LPB *link);
+short send_sync( LPB *link);
+
+#endif	/* _prototypes_h */
diff --git a/drivers/char/rio/protsts.h b/drivers/char/rio/protsts.h
new file mode 100644
index 0000000..848111a
--- /dev/null
+++ b/drivers/char/rio/protsts.h
@@ -0,0 +1,119 @@
+/****************************************************************************
+ *******                                                              *******
+ *******      P R O T O C O L    S T A T U S   S T R U C T U R E      *******
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _protsts_h
+#define _protsts_h 1
+
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_protsts_h_sccs = "@(#)protsts.h	1.4"; */
+#endif
+#endif
+
+/*************************************************
+ * ACK bit. Last Packet received OK. Set by
+ * rxpkt to indicate that the Packet has been
+ * received OK and that the LTT must set the ACK
+ * bit in the next outward bound Packet
+ * and re-set by LTT's after xmit.
+ *
+ * Gets shoved into rx_status
+ ************************************************/
+#define PHB_RX_LAST_PKT_ACKED    ((ushort) 0x080)
+
+/*******************************************************
+ * The Rx TOGGLE bit.
+ * Stuffed into rx_status by RXPKT
+ ******************************************************/
+#define PHB_RX_DATA_WNDW         ((ushort) 0x040)
+
+/*******************************************************
+ * The Rx TOGGLE bit. Matches the setting in PKT.H
+ * Stuffed into rx_status
+ ******************************************************/
+#define PHB_RX_TGL               ((ushort) 0x2000)
+
+
+/*************************************************
+ * This bit is set by the LRT to indicate that
+ * an ACK (packet) must be returned.
+ *
+ * Gets shoved into tx_status
+ ************************************************/
+#define PHB_TX_SEND_PKT_ACK      ((ushort) 0x08)
+
+/*************************************************
+ * Set by LTT to indicate that an ACK is required
+ *************************************************/
+#define PHB_TX_ACK_RQRD         ((ushort) 0x01)
+
+
+/*******************************************************
+ * The Tx TOGGLE bit.
+ * Stuffed into tx_status by RXPKT from the PKT WndW
+ * field. Looked by the LTT when the NEXT Packet
+ * is going to be sent.
+ ******************************************************/
+#define PHB_TX_DATA_WNDW         ((ushort) 0x04)
+
+
+/*******************************************************
+ * The Tx TOGGLE bit. Matches the setting in PKT.H
+ * Stuffed into tx_status
+ ******************************************************/
+#define PHB_TX_TGL               ((ushort) 0x02)
+
+/*******************************************************
+ * Request intr bit. Set when the queue has gone quiet
+ * and the PHB has requested an interrupt.
+ ******************************************************/
+#define PHB_TX_INTR             ((ushort) 0x100)
+
+/*******************************************************
+ * SET if the PHB cannot send any more data down the
+ * Link
+ ******************************************************/
+#define PHB_TX_HANDSHAKE         ((ushort) 0x010)
+
+
+#define RUP_SEND_WNDW		 ((ushort) 0x08) ;
+
+#endif
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/qbuf.h b/drivers/char/rio/qbuf.h
new file mode 100644
index 0000000..1fce02f
--- /dev/null
+++ b/drivers/char/rio/qbuf.h
@@ -0,0 +1,67 @@
+
+/****************************************************************************
+ *******                                                              *******
+ *******       Q U E U E    B U F F E R   S T R U C T U R E S
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _qbuf_h
+#define _qbuf_h 1
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_qbuf_h_sccs = "@(#)qbuf.h	1.1" ;
+#endif
+#endif
+
+
+
+#ifdef HOST
+#define PKTS_PER_BUFFER    1
+#else
+#define PKTS_PER_BUFFER    (220 / PKT_LENGTH)
+#endif
+
+typedef struct Q_BUF Q_BUF ;
+struct Q_BUF  {
+                  Q_BUF_ptr next ;
+                  Q_BUF_ptr prev ;
+                  PKT_ptr buf[PKTS_PER_BUFFER] ;
+              } ;
+
+
+#endif
+
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/rio.h b/drivers/char/rio/rio.h
new file mode 100644
index 0000000..13a9931
--- /dev/null
+++ b/drivers/char/rio/rio.h
@@ -0,0 +1,294 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 1998 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: rio.h
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 11:34:13
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)rio.h	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef	__rio_rio_h__
+#define	__rio_rio_h__
+
+#ifdef SCCS_LABELS
+static char *_rio_h_sccs_ = "@(#)rio.h	1.3";
+#endif
+
+/*
+** 30.09.1998 ARG -
+** Introduced driver version and host card type strings
+*/
+#define RIO_DRV_STR "Specialix RIO Driver"
+#define RIO_AT_HOST_STR "ISA"
+#define RIO_PCI_HOST_STR "PCI"
+
+
+/*
+** rio_info_store() commands (arbitary values) :
+*/
+#define RIO_INFO_PUT	0xA4B3C2D1
+#define RIO_INFO_GET	0xF1E2D3C4
+
+
+/*
+** anything that I couldn't cram in somewhere else
+*/
+/*
+#ifndef RIODEBUG
+#define debug
+#else
+#define debug rioprint
+#endif
+*/
+
+
+/*
+**	Maximum numbers of things
+*/
+#define	RIO_SLOTS	4	/* number of configuration slots */
+#define	RIO_HOSTS	4	/* number of hosts that can be found */
+#define	PORTS_PER_HOST	128	/* number of ports per host */
+#define	LINKS_PER_UNIT	4	/* number of links from a host */
+#define	RIO_PORTS	(PORTS_PER_HOST * RIO_HOSTS) /* max. no. of ports */
+#define	RTAS_PER_HOST	(MAX_RUP) /* number of RTAs per host */
+#define	PORTS_PER_RTA	(PORTS_PER_HOST/RTAS_PER_HOST)	/* ports on a rta */
+#define	PORTS_PER_MODULE 4	/* number of ports on a plug-in module */
+				/* number of modules on an RTA */
+#define	MODULES_PER_RTA	 (PORTS_PER_RTA/PORTS_PER_MODULE)
+#define MAX_PRODUCT	16	/* numbr of different product codes */
+#define MAX_MODULE_TYPES 16	/* number of different types of module */
+
+#define RIO_CONTROL_DEV	128	/* minor number of host/control device */
+#define RIO_INVALID_MAJOR 0	/* test first host card's major no for validity */
+
+/*
+** number of RTAs that can be bound to a master
+*/
+#define MAX_RTA_BINDINGS (MAX_RUP * RIO_HOSTS)
+
+/*
+**	Unit types
+*/
+#define PC_RTA16	0x90000000
+#define PC_RTA8		0xe0000000
+#define TYPE_HOST	0
+#define TYPE_RTA8	1
+#define TYPE_RTA16	2
+
+/*
+**	Flag values returned by functions
+*/
+#define	RIO_FAIL	-1
+#define	RIO_SUCCESS	0
+#define	COPYFAIL	-1	/* copy[in|out] failed */
+
+/*
+** SysPort value for something that hasn't any ports
+*/
+#define	NO_PORT	0xFFFFFFFF
+
+/*
+** Unit ID Of all hosts
+*/
+#define	HOST_ID	0
+
+/*
+** Break bytes into nybles
+*/
+#define	LONYBLE(X)	((X) & 0xF)
+#define	HINYBLE(X)	(((X)>>4) & 0xF)
+
+/*
+** Flag values passed into some functions
+*/
+#define	DONT_SLEEP	0
+#define	OK_TO_SLEEP	1
+
+#define	DONT_PRINT	1
+#define	DO_PRINT	0
+
+#define PRINT_TO_LOG_CONS	0
+#define PRINT_TO_CONS	1
+#define PRINT_TO_LOG	2
+
+/*
+** Timeout has trouble with times of less than 3 ticks...
+*/
+#define	MIN_TIMEOUT	3
+
+/*
+**	Generally useful constants
+*/
+#define	HALF_A_SECOND		((HZ)>>1)
+#define	A_SECOND		(HZ)
+#define	HUNDRED_HZ		((HZ/100)?(HZ/100):1)
+#define	FIFTY_HZ		((HZ/50)?(HZ/50):1)
+#define	TWENTY_HZ		((HZ/20)?(HZ/20):1)
+#define	TEN_HZ			((HZ/10)?(HZ/10):1)
+#define	FIVE_HZ			((HZ/5)?(HZ/5):1)
+#define	HUNDRED_MS		TEN_HZ
+#define	FIFTY_MS		TWENTY_HZ
+#define	TWENTY_MS		FIFTY_HZ
+#define	TEN_MS			HUNDRED_HZ
+#define	TWO_SECONDS		((A_SECOND)*2)
+#define	FIVE_SECONDS		((A_SECOND)*5)
+#define	TEN_SECONDS		((A_SECOND)*10)
+#define	FIFTEEN_SECONDS		((A_SECOND)*15)
+#define	TWENTY_SECONDS		((A_SECOND)*20)
+#define	HALF_A_MINUTE		(A_MINUTE>>1)
+#define	A_MINUTE		(A_SECOND*60)
+#define	FIVE_MINUTES		(A_MINUTE*5)
+#define	QUARTER_HOUR		(A_MINUTE*15)
+#define	HALF_HOUR		(A_MINUTE*30)
+#define	HOUR			(A_MINUTE*60)
+
+#define	SIXTEEN_MEG		0x1000000
+#define	ONE_MEG			0x100000
+#define	SIXTY_FOUR_K		0x10000
+
+#define	RIO_AT_MEM_SIZE		SIXTY_FOUR_K
+#define	RIO_EISA_MEM_SIZE	SIXTY_FOUR_K
+#define	RIO_MCA_MEM_SIZE	SIXTY_FOUR_K
+
+#define	POLL_VECTOR		0x100
+
+#define	COOK_WELL		0
+#define	COOK_MEDIUM		1
+#define	COOK_RAW		2
+
+/*
+**	Pointer manipulation stuff
+**	RIO_PTR takes hostp->Caddr and the offset into the DP RAM area
+**	and produces a UNIX caddr_t (pointer) to the object
+**	RIO_OBJ takes hostp->Caddr and a UNIX pointer to an object and
+**	returns the offset into the DP RAM area.
+*/
+#define	RIO_PTR(C,O) (((caddr_t)(C))+(0xFFFF&(O)))
+#define	RIO_OFF(C,O) ((int)(O)-(int)(C))
+
+/*
+**	How to convert from various different device number formats:
+**	DEV is a dev number, as passed to open, close etc - NOT a minor
+**	number!
+**
+**	Note:	LynxOS only gives us 8 bits for the device minor number,
+**		so all this crap here to deal with 'modem' bits etc. is
+**		just a load of irrelevant old bunkum!
+**		This however does not stop us needing to define a value
+**		for RIO_MODEMOFFSET which is required by the 'riomkdev'
+**		utility in the New Config Utilities suite.
+*/
+/* 0-511: direct 512-1023: modem */
+#define	RIO_MODEMOFFSET		0x200	/* doesn't mean anything */
+#define	RIO_MODEM_MASK		0x1FF
+#define	RIO_MODEM_BIT		0x200
+#define	RIO_UNMODEM(DEV)	(MINOR(DEV) & RIO_MODEM_MASK)
+#define	RIO_ISMODEM(DEV)	(MINOR(DEV) & RIO_MODEM_BIT)
+#define RIO_PORT(DEV,FIRST_MAJ)	( (MAJOR(DEV) - FIRST_MAJ) * PORTS_PER_HOST) \
+					+ MINOR(DEV)
+
+#define	splrio	spltty
+
+#define	RIO_IPL	5
+#define	RIO_PRI	(PZERO+10)
+#define RIO_CLOSE_PRI	PZERO-1	/* uninterruptible sleeps for close */
+
+typedef struct DbInf
+{
+	uint	Flag;
+	char	Name[8];
+} DbInf;
+
+#ifndef TRUE
+#define	TRUE (1==1)
+#endif
+#ifndef FALSE
+#define	FALSE	(!TRUE)
+#endif
+
+#define CSUM(pkt_ptr)  (((ushort *)(pkt_ptr))[0] + ((ushort *)(pkt_ptr))[1] + \
+			((ushort *)(pkt_ptr))[2] + ((ushort *)(pkt_ptr))[3] + \
+			((ushort *)(pkt_ptr))[4] + ((ushort *)(pkt_ptr))[5] + \
+			((ushort *)(pkt_ptr))[6] + ((ushort *)(pkt_ptr))[7] + \
+			((ushort *)(pkt_ptr))[8] + ((ushort *)(pkt_ptr))[9] )
+
+/*
+** This happy little macro copies SIZE bytes of data from FROM to TO
+** quite well. SIZE must be a constant.
+*/
+#define CCOPY( FROM, TO, SIZE ) { *(struct s { char data[SIZE]; } *)(TO) = *(struct s *)(FROM); }
+
+/*
+** increment a buffer pointer modulo the size of the buffer...
+*/
+#define	BUMP( P, I )	((P) = (((P)+(I)) & RIOBufferMask))
+
+#define INIT_PACKET( PK, PP ) \
+{ \
+	*((uint *)PK)    = PP->PacketInfo; \
+}
+
+#define	RIO_LINK_ENABLE	0x80FF /* FF is a hack, mainly for Mips, to        */
+			       /* prevent a really stupid race condition.  */
+
+#define	NOT_INITIALISED	0
+#define	INITIALISED	1
+
+#define	NOT_POLLING	0
+#define	POLLING		1
+
+#define	NOT_CHANGED	0
+#define	CHANGED		1
+
+#define	NOT_INUSE	0
+
+#define	DISCONNECT	0
+#define	CONNECT		1
+
+
+/*
+** Machine types - these must NOT overlap with product codes 0-15
+*/
+#define	RIO_MIPS_R3230	31
+#define	RIO_MIPS_R4030	32
+
+#define	RIO_IO_UNKNOWN	-2
+
+#undef	MODERN
+#define	ERROR( E )	do { u.u_error = E; return OPENFAIL } while ( 0 )
+
+/* Defines for MPX line discipline routines */
+
+#define DIST_LINESW_OPEN	0x01
+#define DIST_LINESW_CLOSE	0x02
+#define DIST_LINESW_READ	0x04
+#define DIST_LINESW_WRITE	0x08
+#define DIST_LINESW_IOCTL	0x10
+#define DIST_LINESW_INPUT	0x20
+#define DIST_LINESW_OUTPUT	0x40
+#define DIST_LINESW_MDMINT	0x80
+
+#endif /* __rio_h__ */
diff --git a/drivers/char/rio/rio_linux.c b/drivers/char/rio/rio_linux.c
new file mode 100644
index 0000000..a91ae27
--- /dev/null
+++ b/drivers/char/rio/rio_linux.c
@@ -0,0 +1,1380 @@
+
+/* rio_linux.c -- Linux driver for the Specialix RIO series cards. 
+ *
+ *
+ *   (C) 1999 R.E.Wolff@BitWizard.nl
+ *
+ * Specialix pays for the development and support of this driver.
+ * Please DO contact support@specialix.co.uk if you require
+ * support. But please read the documentation (rio.txt) first.
+ *
+ *
+ *
+ *      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.
+ *
+ * Revision history:
+ * $Log: rio.c,v $
+ * Revision 1.1  1999/07/11 10:13:54  wolff
+ * Initial revision
+ *
+ * */
+
+#include <linux/module.h>
+#include <linux/config.h> 
+#include <linux/kdev_t.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+
+#include <linux/generic_serial.h>
+#include <asm/uaccess.h>
+
+#if BITS_PER_LONG != 32
+#  error FIXME: this driver only works on 32-bit platforms
+#endif
+
+#include "linux_compat.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+#include "protsts.h"
+#include "rioboard.h"
+
+
+#include "rio_linux.h"
+
+/* I don't think that this driver can handle more than 512 ports on
+one machine.  Specialix specifies max 4 boards in one machine. I don't
+know why. If you want to try anyway you'll have to increase the number
+of boards in rio.h.  You'll have to allocate more majors if you need
+more than 512 ports.... */
+
+#ifndef RIO_NORMAL_MAJOR0
+/* This allows overriding on the compiler commandline, or in a "major.h" 
+   include or something like that */
+#define RIO_NORMAL_MAJOR0  154
+#define RIO_NORMAL_MAJOR1  156
+#endif
+
+#ifndef PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8
+#define PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8 0x2000
+#endif
+
+#ifndef RIO_WINDOW_LEN 
+#define RIO_WINDOW_LEN 0x10000
+#endif
+
+
+/* Configurable options: 
+   (Don't be too sure that it'll work if you toggle them) */
+
+/* Am I paranoid or not ? ;-) */
+#undef RIO_PARANOIA_CHECK
+
+
+/* 20 -> 2000 per second. The card should rate-limit interrupts at 1000
+   Hz, but it is user configurable. I don't recommend going above 1000
+   Hz. The interrupt ratelimit might trigger if the interrupt is
+   shared with a very active other device. 
+   undef this if you want to disable the check....
+*/
+#define IRQ_RATE_LIMIT 200
+
+#if 0
+/* Not implemented */
+/* 
+ * The following defines are mostly for testing purposes. But if you need
+ * some nice reporting in your syslog, you can define them also.
+ */
+#define RIO_REPORT_FIFO
+#define RIO_REPORT_OVERRUN
+#endif 
+
+
+/* These constants are derived from SCO Source */
+static struct Conf
+RIOConf =
+{
+  /* locator */         "RIO Config here",
+  /* startuptime */     HZ*2,           /* how long to wait for card to run */
+  /* slowcook */        0,              /* TRUE -> always use line disc. */
+  /* intrpolltime */    1,              /* The frequency of OUR polls */
+  /* breakinterval */   25,             /* x10 mS XXX: units seem to be 1ms not 10! -- REW*/
+  /* timer */           10,             /* mS */
+  /* RtaLoadBase */     0x7000,
+  /* HostLoadBase */    0x7C00,
+  /* XpHz */            5,              /* number of Xprint hits per second */
+  /* XpCps */           120,            /* Xprint characters per second */
+  /* XpOn */            "\033d#",       /* start Xprint for a wyse 60 */
+  /* XpOff */           "\024",         /* end Xprint for a wyse 60 */
+  /* MaxXpCps */        2000,           /* highest Xprint speed */
+  /* MinXpCps */        10,             /* slowest Xprint speed */
+  /* SpinCmds */        1,              /* non-zero for mega fast boots */
+  /* First Addr */      0x0A0000,       /* First address to look at */
+  /* Last Addr */       0xFF0000,       /* Last address looked at */
+  /* BufferSize */      1024,           /* Bytes per port of buffering */
+  /* LowWater */        256,            /* how much data left before wakeup */
+  /* LineLength */      80,             /* how wide is the console? */
+  /* CmdTimeout */      HZ,             /* how long a close command may take */
+};
+
+
+
+
+/* Function prototypes */
+
+static void rio_disable_tx_interrupts (void * ptr); 
+static void rio_enable_tx_interrupts (void * ptr); 
+static void rio_disable_rx_interrupts (void * ptr); 
+static void rio_enable_rx_interrupts (void * ptr); 
+static int  rio_get_CD (void * ptr); 
+static void rio_shutdown_port (void * ptr);
+static int  rio_set_real_termios (void  *ptr);
+static void rio_hungup (void  *ptr);
+static void rio_close (void  *ptr);
+static int rio_chars_in_buffer (void * ptr);
+static int rio_fw_ioctl (struct inode *inode, struct file *filp,
+		         unsigned int cmd, unsigned long arg);
+static int rio_init_drivers(void);
+
+static void my_hd (void *addr, int len);
+
+static struct tty_driver *rio_driver, *rio_driver2;
+
+/* The name "p" is a bit non-descript. But that's what the rio-lynxos
+sources use all over the place. */
+struct rio_info *p;
+
+int rio_debug;
+
+
+/* You can have the driver poll your card. 
+    - Set rio_poll to 1 to poll every timer tick (10ms on Intel). 
+      This is used when the card cannot use an interrupt for some reason.
+*/
+static int rio_poll = 1;
+
+
+/* These are the only open spaces in my computer. Yours may have more
+   or less.... */
+static int rio_probe_addrs[]= {0xc0000, 0xd0000, 0xe0000};
+
+#define NR_RIO_ADDRS (sizeof(rio_probe_addrs)/sizeof (int))
+
+
+/* Set the mask to all-ones. This alas, only supports 32 interrupts. 
+   Some architectures may need more. -- Changed to LONG to
+   support up to 64 bits on 64bit architectures. -- REW 20/06/99 */
+long rio_irqmask = -1;
+
+MODULE_AUTHOR("Rogier Wolff <R.E.Wolff@bitwizard.nl>, Patrick van de Lageweg <patrick@bitwizard.nl>");
+MODULE_DESCRIPTION("RIO driver");
+MODULE_LICENSE("GPL");
+module_param(rio_poll, int, 0);
+module_param(rio_debug, int, 0644);
+module_param(rio_irqmask, long, 0);
+
+static struct real_driver rio_real_driver = {
+  rio_disable_tx_interrupts,
+  rio_enable_tx_interrupts,
+  rio_disable_rx_interrupts,
+  rio_enable_rx_interrupts,
+  rio_get_CD,
+  rio_shutdown_port, 
+  rio_set_real_termios, 
+  rio_chars_in_buffer,
+  rio_close,
+  rio_hungup,
+  NULL
+};
+
+/* 
+ *  Firmware loader driver specific routines
+ *
+ */
+
+static struct file_operations rio_fw_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= rio_fw_ioctl,
+};
+
+static struct miscdevice rio_fw_device = {
+	RIOCTL_MISC_MINOR, "rioctl", &rio_fw_fops
+};
+
+
+
+
+
+#ifdef RIO_PARANOIA_CHECK
+
+/* This doesn't work. Who's paranoid around here? Not me! */
+
+static inline int rio_paranoia_check(struct rio_port const * port,
+				    char *name, const char *routine)
+{
+
+  static const char *badmagic =
+    KERN_ERR "rio: Warning: bad rio port magic number for device %s in %s\n";
+  static const char *badinfo =
+    KERN_ERR "rio: Warning: null rio port for device %s in %s\n";
+ 
+  if (!port) {
+    printk (badinfo, name, routine);
+    return 1;
+  }
+  if (port->magic != RIO_MAGIC) {
+    printk (badmagic, name, routine);
+    return 1;
+  }
+
+  return 0;
+}
+#else
+#define rio_paranoia_check(a,b,c) 0
+#endif
+
+
+#ifdef DEBUG
+static void my_hd (void *ad, int len)
+{
+  int i, j, ch;
+  unsigned char *addr = ad;
+  
+  for (i=0;i<len;i+=16) {
+    rio_dprintk (RIO_DEBUG_PARAM, "%08x ", (int) addr+i);
+    for (j=0;j<16;j++) {
+      rio_dprintk (RIO_DEBUG_PARAM, "%02x %s", addr[j+i], (j==7)?" ":"");
+    }
+    for (j=0;j<16;j++) {
+      ch = addr[j+i];
+      rio_dprintk (RIO_DEBUG_PARAM, "%c", (ch < 0x20)?'.':((ch > 0x7f)?'.':ch));
+    }
+    rio_dprintk (RIO_DEBUG_PARAM, "\n");
+  }
+}
+#else
+#define my_hd(ad,len) do{/* nothing*/ } while (0)
+#endif
+
+
+/* Delay a number of jiffies, allowing a signal to interrupt */ 
+int RIODelay (struct Port *PortP, int njiffies)
+{
+  func_enter ();
+
+  rio_dprintk (RIO_DEBUG_DELAY, "delaying %d jiffies\n", njiffies);  
+  msleep_interruptible(jiffies_to_msecs(njiffies));
+  func_exit();
+
+  if (signal_pending(current))
+    return RIO_FAIL;
+  else
+    return !RIO_FAIL;
+}
+
+
+/* Delay a number of jiffies, disallowing a signal to interrupt */ 
+int RIODelay_ni (struct Port *PortP, int njiffies)
+{
+  func_enter ();
+
+  rio_dprintk (RIO_DEBUG_DELAY, "delaying %d jiffies (ni)\n", njiffies);  
+  msleep(jiffies_to_msecs(njiffies));
+  func_exit();
+  return !RIO_FAIL;
+}
+
+
+int rio_minor(struct tty_struct *tty)
+{
+	return tty->index + (tty->driver == rio_driver) ? 0 : 256;
+}
+
+
+int rio_ismodem(struct tty_struct *tty)
+{
+	return 1;
+}
+
+
+void rio_udelay (int usecs)
+{
+  udelay (usecs);
+}
+
+static int rio_set_real_termios (void *ptr)
+{
+  int rv, modem;
+  struct tty_struct *tty;
+  func_enter();
+
+  tty = ((struct Port *)ptr)->gs.tty;
+
+  modem = rio_ismodem(tty);
+
+  rv = RIOParam( (struct Port *) ptr, CONFIG, modem, 1);
+
+  func_exit ();
+
+  return rv;
+}
+
+
+static void rio_reset_interrupt (struct Host *HostP)
+{
+  func_enter();
+
+  switch( HostP->Type ) {
+  case RIO_AT:
+  case RIO_MCA:
+  case RIO_PCI:
+    WBYTE(HostP->ResetInt , 0xff);
+  }
+
+  func_exit();
+}
+
+
+static irqreturn_t rio_interrupt (int irq, void *ptr, struct pt_regs *regs)
+{
+  struct Host *HostP;
+  func_enter ();
+
+  HostP = (struct Host*)ptr; /* &p->RIOHosts[(long)ptr]; */
+  rio_dprintk (RIO_DEBUG_IFLOW, "rio: enter rio_interrupt (%d/%d)\n", 
+               irq, HostP->Ivec); 
+
+  /* AAargh! The order in which to do these things is essential and
+     not trivial. 
+     
+     - Rate limit goes before "recursive". Otherwise a series of
+       recursive calls will hang the machine in the interrupt routine. 
+
+     - hardware twiddling goes before "recursive". Otherwise when we
+       poll the card, and a recursive interrupt happens, we won't
+       ack the card, so it might keep on interrupting us. (especially
+       level sensitive interrupt systems like PCI).
+
+     - Rate limit goes before hardware twiddling. Otherwise we won't
+       catch a card that has gone bonkers.
+
+     - The "initialized" test goes after the hardware twiddling. Otherwise
+       the card will stick us in the interrupt routine again.
+
+     - The initialized test goes before recursive. 
+  */
+
+
+
+#ifdef IRQ_RATE_LIMIT
+  /* Aaargh! I'm ashamed. This costs more lines-of-code than the
+     actual interrupt routine!. (Well, used to when I wrote that comment) */
+  {
+    static int lastjif;
+    static int nintr=0;
+
+    if (lastjif == jiffies) {
+      if (++nintr > IRQ_RATE_LIMIT) {
+        free_irq (HostP->Ivec, ptr);
+        printk (KERN_ERR "rio: Too many interrupts. Turning off interrupt %d.\n", 
+                HostP->Ivec);
+      }
+    } else {
+      lastjif = jiffies;
+      nintr = 0;
+    }
+  }
+#endif
+  rio_dprintk (RIO_DEBUG_IFLOW, "rio: We've have noticed the interrupt\n"); 
+  if (HostP->Ivec == irq) {
+    /* Tell the card we've noticed the interrupt. */
+    rio_reset_interrupt (HostP);
+  }
+
+  if ((HostP->Flags & RUN_STATE) != RC_RUNNING)
+  	return IRQ_HANDLED;
+
+  if (test_and_set_bit (RIO_BOARD_INTR_LOCK, &HostP->locks)) {
+    printk (KERN_ERR "Recursive interrupt! (host %d/irq%d)\n", 
+            (int) ptr, HostP->Ivec);
+    return IRQ_HANDLED;
+  }
+
+  RIOServiceHost(p, HostP, irq);
+
+  rio_dprintk ( RIO_DEBUG_IFLOW, "riointr() doing host %d type %d\n", 
+                (int) ptr, HostP->Type);
+
+  clear_bit (RIO_BOARD_INTR_LOCK, &HostP->locks);
+  rio_dprintk (RIO_DEBUG_IFLOW, "rio: exit rio_interrupt (%d/%d)\n", 
+               irq, HostP->Ivec); 
+  func_exit ();
+  return IRQ_HANDLED;
+}
+
+
+static void rio_pollfunc (unsigned long data)
+{
+  func_enter ();
+
+  rio_interrupt (0, &p->RIOHosts[data], NULL);
+  p->RIOHosts[data].timer.expires = jiffies + rio_poll;
+  add_timer (&p->RIOHosts[data].timer);
+
+  func_exit ();
+}
+
+
+/* ********************************************************************** *
+ *                Here are the routines that actually                     *
+ *              interface with the generic_serial driver                  *
+ * ********************************************************************** */
+
+/* Ehhm. I don't know how to fiddle with interrupts on the Specialix 
+   cards. ....   Hmm. Ok I figured it out. You don't.  -- REW */
+
+static void rio_disable_tx_interrupts (void * ptr) 
+{
+  func_enter();
+
+  /*  port->gs.flags &= ~GS_TX_INTEN; */
+
+  func_exit();
+}
+
+
+static void rio_enable_tx_interrupts (void * ptr) 
+{
+  struct Port *PortP = ptr;
+  /* int hn; */
+
+  func_enter();
+
+  /* hn = PortP->HostP - p->RIOHosts;
+
+     rio_dprintk (RIO_DEBUG_TTY, "Pushing host %d\n", hn);
+     rio_interrupt (-1,(void *) hn, NULL); */
+
+  RIOTxEnable((char *) PortP);
+
+  /* 
+   * In general we cannot count on "tx empty" interrupts, although
+   * the interrupt routine seems to be able to tell the difference. 
+   */
+  PortP->gs.flags &= ~GS_TX_INTEN;
+
+  func_exit();
+}
+
+
+static void rio_disable_rx_interrupts (void * ptr) 
+{
+  func_enter();
+  func_exit();
+}
+
+static void rio_enable_rx_interrupts (void * ptr) 
+{
+  /*  struct rio_port *port = ptr; */
+  func_enter();
+  func_exit();
+}
+
+
+/* Jeez. Isn't this simple?  */
+static int rio_get_CD (void * ptr) 
+{
+  struct Port *PortP = ptr;
+  int rv;
+
+  func_enter();
+  rv = (PortP->ModemState & MSVR1_CD) != 0;
+
+  rio_dprintk (RIO_DEBUG_INIT, "Getting CD status: %d\n", rv);
+  
+  func_exit();  
+  return rv;
+}
+
+
+/* Jeez. Isn't this simple? Actually, we can sync with the actual port
+   by just pushing stuff into the queue going to the port... */
+static int rio_chars_in_buffer (void * ptr) 
+{
+  func_enter();
+
+  func_exit();  
+  return 0;
+}
+
+
+/* Nothing special here... */
+static void rio_shutdown_port (void * ptr) 
+{
+  struct Port *PortP;
+
+  func_enter();
+
+  PortP = (struct Port *)ptr;
+  PortP->gs.tty = NULL;
+#if 0
+  port->gs.flags &= ~ GS_ACTIVE;
+  if (!port->gs.tty) {
+    rio_dprintk (RIO_DBUG_TTY, "No tty.\n");
+    return;
+  }
+  if (!port->gs.tty->termios) {
+    rio_dprintk (RIO_DEBUG_TTY, "No termios.\n");
+    return;
+  }
+  if (port->gs.tty->termios->c_cflag & HUPCL) {
+    rio_setsignals (port, 0, 0);
+  }
+#endif
+
+  func_exit();
+}
+
+
+/* I haven't the foggiest why the decrement use count has to happen
+   here. The whole linux serial drivers stuff needs to be redesigned.
+   My guess is that this is a hack to minimize the impact of a bug
+   elsewhere. Thinking about it some more. (try it sometime) Try
+   running minicom on a serial port that is driven by a modularized
+   driver. Have the modem hangup. Then remove the driver module. Then
+   exit minicom.  I expect an "oops".  -- REW */
+static void rio_hungup (void *ptr)
+{
+  struct Port *PortP;
+
+  func_enter();
+  
+  PortP = (struct Port *)ptr;
+  PortP->gs.tty = NULL;
+
+  func_exit ();
+}
+
+
+/* The standard serial_close would become shorter if you'd wrap it like
+   this. 
+   rs_close (...){save_flags;cli;real_close();dec_use_count;restore_flags;}
+ */
+static void rio_close (void *ptr)
+{
+  struct Port *PortP;
+
+  func_enter ();
+
+  PortP = (struct Port *)ptr;
+
+  riotclose (ptr);
+
+  if(PortP->gs.count) {
+    printk (KERN_ERR "WARNING port count:%d\n", PortP->gs.count);
+    PortP->gs.count = 0; 
+  }                
+
+  PortP->gs.tty = NULL;
+  func_exit ();
+}
+
+
+
+static int rio_fw_ioctl (struct inode *inode, struct file *filp,
+		         unsigned int cmd, unsigned long arg)
+{
+  int rc = 0;
+  func_enter();
+
+  /* The "dev" argument isn't used. */
+  rc = riocontrol (p, 0, cmd, (void *)arg, capable(CAP_SYS_ADMIN));
+
+  func_exit ();
+  return rc;
+}
+
+extern int RIOShortCommand(struct rio_info *p, struct Port *PortP,
+               int command, int len, int arg);
+
+static int rio_ioctl (struct tty_struct * tty, struct file * filp, 
+                     unsigned int cmd, unsigned long arg)
+{
+  int rc;
+  struct Port *PortP;
+  int ival;
+
+  func_enter();
+
+  PortP = (struct Port *)tty->driver_data;
+
+  rc  = 0;
+  switch (cmd) {
+#if 0
+  case TIOCGSOFTCAR:
+    rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+                  (unsigned int *) arg);
+    break;
+#endif
+  case TIOCSSOFTCAR:
+    if ((rc = get_user(ival, (unsigned int *) arg)) == 0) {
+      tty->termios->c_cflag =
+        (tty->termios->c_cflag & ~CLOCAL) |
+        (ival ? CLOCAL : 0);
+    }
+    break;
+  case TIOCGSERIAL:
+    rc = -EFAULT;
+    if (access_ok(VERIFY_WRITE, (void *) arg,
+                          sizeof(struct serial_struct)))
+      rc = gs_getserial(&PortP->gs, (struct serial_struct *) arg);
+    break;
+  case TCSBRK:
+    if ( PortP->State & RIO_DELETED ) {
+      rio_dprintk (RIO_DEBUG_TTY, "BREAK on deleted RTA\n");
+      rc = -EIO;      
+    } else {
+      if (RIOShortCommand(p, PortP, SBREAK, 2, 250) == RIO_FAIL) {
+         rio_dprintk (RIO_DEBUG_INTR, "SBREAK RIOShortCommand failed\n");
+         rc = -EIO;
+      }          
+    }
+    break;
+  case TCSBRKP:
+    if ( PortP->State & RIO_DELETED ) {
+      rio_dprintk (RIO_DEBUG_TTY, "BREAK on deleted RTA\n");
+      rc = -EIO;      
+    } else {
+      int l;
+      l = arg?arg*100:250;
+      if (l > 255) l = 255;
+      if (RIOShortCommand(p, PortP, SBREAK, 2, arg?arg*100:250) == RIO_FAIL) {
+         rio_dprintk (RIO_DEBUG_INTR, "SBREAK RIOShortCommand failed\n");
+         rc = -EIO;
+      }          
+    }
+    break;
+  case TIOCSSERIAL:
+    rc = -EFAULT;
+    if (access_ok(VERIFY_READ, (void *) arg,
+                          sizeof(struct serial_struct)))
+      rc = gs_setserial(&PortP->gs, (struct serial_struct *) arg);
+    break;
+#if 0
+  /*
+   * note: these IOCTLs no longer reach here.  Use
+   * tiocmset/tiocmget driver methods instead.  The
+   * #if 0 disablement predates this comment.
+   */
+  case TIOCMGET:
+    rc = -EFAULT;
+    if (access_ok(VERIFY_WRITE, (void *) arg,
+                          sizeof(unsigned int))) {
+      rc = 0;
+      ival = rio_getsignals(port);
+      put_user(ival, (unsigned int *) arg);
+    }
+    break;
+  case TIOCMBIS:
+    if ((rc = get_user(ival, (unsigned int *) arg)) == 0) {
+      rio_setsignals(port, ((ival & TIOCM_DTR) ? 1 : -1),
+                           ((ival & TIOCM_RTS) ? 1 : -1));
+    }
+    break;
+  case TIOCMBIC:
+    if ((rc = get_user(ival, (unsigned int *) arg)) == 0) {
+      rio_setsignals(port, ((ival & TIOCM_DTR) ? 0 : -1),
+                           ((ival & TIOCM_RTS) ? 0 : -1));
+    }
+    break;
+  case TIOCMSET:
+    if ((rc = get_user(ival, (unsigned int *) arg)) == 0) {
+      rio_setsignals(port, ((ival & TIOCM_DTR) ? 1 : 0),
+                           ((ival & TIOCM_RTS) ? 1 : 0));
+    }
+    break;
+#endif
+  default:
+    rc = -ENOIOCTLCMD;
+    break;
+  }
+  func_exit();
+  return rc;
+}
+
+
+/* The throttle/unthrottle scheme for the Specialix card is different
+ * from other drivers and deserves some explanation. 
+ * The Specialix hardware takes care of XON/XOFF
+ * and CTS/RTS flow control itself.  This means that all we have to
+ * do when signalled by the upper tty layer to throttle/unthrottle is
+ * to make a note of it here.  When we come to read characters from the
+ * rx buffers on the card (rio_receive_chars()) we look to see if the
+ * upper layer can accept more (as noted here in rio_rx_throt[]). 
+ * If it can't we simply don't remove chars from the cards buffer. 
+ * When the tty layer can accept chars, we again note that here and when
+ * rio_receive_chars() is called it will remove them from the cards buffer.
+ * The card will notice that a ports buffer has drained below some low
+ * water mark and will unflow control the line itself, using whatever
+ * flow control scheme is in use for that port. -- Simon Allen
+ */
+
+static void rio_throttle (struct tty_struct * tty)
+{
+  struct Port *port = (struct Port *)tty->driver_data;
+  
+  func_enter();
+  /* If the port is using any type of input flow
+   * control then throttle the port.
+   */
+
+  if((tty->termios->c_cflag & CRTSCTS) || (I_IXOFF(tty)) ) {
+    port->State |= RIO_THROTTLE_RX;
+  }
+
+  func_exit();
+}
+
+
+static void rio_unthrottle (struct tty_struct * tty)
+{
+  struct Port *port = (struct Port *)tty->driver_data;
+
+  func_enter();
+  /* Always unthrottle even if flow control is not enabled on
+   * this port in case we disabled flow control while the port
+   * was throttled
+   */
+
+  port->State &= ~RIO_THROTTLE_RX;
+
+  func_exit();
+  return;
+}
+
+
+
+
+
+/* ********************************************************************** *
+ *                    Here are the initialization routines.               *
+ * ********************************************************************** */
+
+
+static struct vpd_prom *get_VPD_PROM (struct Host *hp)
+{
+  static struct vpd_prom vpdp;
+  char *p;
+  int i;
+
+  func_enter();
+  rio_dprintk (RIO_DEBUG_PROBE, "Going to verify vpd prom at %p.\n", 
+              hp->Caddr + RIO_VPD_ROM);
+
+  p = (char *) &vpdp;
+  for (i=0;i< sizeof (struct vpd_prom);i++)
+    *p++ = readb (hp->Caddr+RIO_VPD_ROM + i*2);
+      /* read_rio_byte (hp, RIO_VPD_ROM + i*2); */
+
+  /* Terminate the identifier string. 
+     *** requires one extra byte in struct vpd_prom *** */
+  *p++=0; 
+
+  if (rio_debug & RIO_DEBUG_PROBE)
+    my_hd ((char *)&vpdp, 0x20);
+  
+  func_exit();
+
+  return &vpdp;
+}
+
+static struct tty_operations rio_ops = {
+	.open  = riotopen,
+	.close = gs_close,
+	.write = gs_write,
+	.put_char = gs_put_char,
+	.flush_chars = gs_flush_chars,
+	.write_room = gs_write_room,
+	.chars_in_buffer = gs_chars_in_buffer,
+	.flush_buffer = gs_flush_buffer,
+	.ioctl = rio_ioctl,
+	.throttle = rio_throttle,
+	.unthrottle = rio_unthrottle,
+	.set_termios = gs_set_termios,
+	.stop = gs_stop,
+	.start = gs_start,
+	.hangup = gs_hangup,
+};
+
+static int rio_init_drivers(void)
+{
+	int error = -ENOMEM;
+
+	rio_driver = alloc_tty_driver(256);
+	if (!rio_driver)
+		goto out;
+	rio_driver2 = alloc_tty_driver(256);
+	if (!rio_driver2)
+		goto out1;
+
+	func_enter();
+
+	rio_driver->owner = THIS_MODULE;
+	rio_driver->driver_name = "specialix_rio";
+	rio_driver->name = "ttySR";
+	rio_driver->major = RIO_NORMAL_MAJOR0;
+	rio_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	rio_driver->subtype = SERIAL_TYPE_NORMAL;
+	rio_driver->init_termios = tty_std_termios;
+	rio_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	rio_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(rio_driver, &rio_ops);
+
+	rio_driver2->owner = THIS_MODULE;
+	rio_driver2->driver_name = "specialix_rio";
+	rio_driver2->name = "ttySR";
+	rio_driver2->major = RIO_NORMAL_MAJOR1;
+	rio_driver2->type = TTY_DRIVER_TYPE_SERIAL;
+	rio_driver2->subtype = SERIAL_TYPE_NORMAL;
+	rio_driver2->init_termios = tty_std_termios;
+	rio_driver2->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	rio_driver2->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(rio_driver2, &rio_ops);
+
+	rio_dprintk (RIO_DEBUG_INIT, "set_termios = %p\n", gs_set_termios);
+
+	if ((error = tty_register_driver(rio_driver)))
+		goto out2;
+	if ((error = tty_register_driver(rio_driver2)))
+		goto out3;
+	func_exit();
+	return 0;
+out3:
+	tty_unregister_driver(rio_driver);
+out2:
+	put_tty_driver(rio_driver2);
+out1:
+	put_tty_driver(rio_driver);
+out:
+	printk(KERN_ERR "rio: Couldn't register a rio driver, error = %d\n",
+	     error);
+	return 1;
+}
+
+
+static void * ckmalloc (int size)
+{
+  void *p;
+
+  p = kmalloc(size, GFP_KERNEL);
+  if (p) 
+    memset(p, 0, size);
+  return p;
+}
+
+
+
+static int rio_init_datastructures (void)
+{
+  int i;
+  struct Port *port;
+  func_enter();
+
+  /* Many drivers statically allocate the maximum number of ports
+     There is no reason not to allocate them dynamically. Is there? -- REW */
+  /* However, the RIO driver allows users to configure their first
+     RTA as the ports numbered 504-511. We therefore need to allocate 
+     the whole range. :-(   -- REW */
+  
+#define RI_SZ   sizeof(struct rio_info)
+#define HOST_SZ sizeof(struct Host)
+#define PORT_SZ sizeof(struct Port *)
+#define TMIO_SZ sizeof(struct termios *)
+  rio_dprintk (RIO_DEBUG_INIT, "getting : %d %d %d %d %d bytes\n", 
+               RI_SZ, 
+               RIO_HOSTS * HOST_SZ,
+               RIO_PORTS * PORT_SZ,
+               RIO_PORTS * TMIO_SZ,
+               RIO_PORTS * TMIO_SZ);
+  
+  if (!(p                  = ckmalloc (              RI_SZ))) goto free0;
+  if (!(p->RIOHosts        = ckmalloc (RIO_HOSTS * HOST_SZ))) goto free1;
+  if (!(p->RIOPortp        = ckmalloc (RIO_PORTS * PORT_SZ))) goto free2;
+  p->RIOConf = RIOConf;
+  rio_dprintk (RIO_DEBUG_INIT, "Got : %p %p %p\n", 
+               p, p->RIOHosts, p->RIOPortp);
+
+#if 1
+  for (i = 0; i < RIO_PORTS; i++) {
+    port = p->RIOPortp[i] = ckmalloc (sizeof (struct Port));
+    if (!port) {
+      goto free6;
+    }
+    rio_dprintk (RIO_DEBUG_INIT, "initing port %d (%d)\n", i, port->Mapped);
+    port->PortNum = i;
+    port->gs.magic = RIO_MAGIC;
+    port->gs.close_delay = HZ/2;
+    port->gs.closing_wait = 30 * HZ;
+    port->gs.rd = &rio_real_driver;
+    spin_lock_init(&port->portSem);
+    /*
+     * Initializing wait queue
+     */
+    init_waitqueue_head(&port->gs.open_wait);
+    init_waitqueue_head(&port->gs.close_wait);
+  }
+#else
+  /* We could postpone initializing them to when they are configured. */
+#endif
+
+
+  
+  if (rio_debug & RIO_DEBUG_INIT) {
+    my_hd (&rio_real_driver, sizeof (rio_real_driver));
+  }
+
+  
+  func_exit();
+  return 0;
+
+ free6:for (i--;i>=0;i--)
+        kfree (p->RIOPortp[i]);
+/*free5:
+ free4:
+ free3:*/kfree (p->RIOPortp);
+ free2:kfree (p->RIOHosts);
+ free1:
+  rio_dprintk (RIO_DEBUG_INIT, "Not enough memory! %p %p %p\n", 
+        	       p, p->RIOHosts, p->RIOPortp);
+  kfree(p);        	      
+ free0:
+  return -ENOMEM;
+}
+
+static void  __exit rio_release_drivers(void)
+{
+  func_enter();
+  tty_unregister_driver(rio_driver2);
+  tty_unregister_driver(rio_driver);
+  put_tty_driver(rio_driver2);
+  put_tty_driver(rio_driver);
+  func_exit();
+}
+
+
+#ifdef CONFIG_PCI
+ /* This was written for SX, but applies to RIO too...
+    (including bugs....)
+
+    There is another bit besides Bit 17. Turning that bit off
+    (on boards shipped with the fix in the eeprom) results in a 
+    hang on the next access to the card. 
+ */
+
+ /******************************************************** 
+ * Setting bit 17 in the CNTRL register of the PLX 9050  * 
+ * chip forces a retry on writes while a read is pending.*
+ * This is to prevent the card locking up on Intel Xeon  *
+ * multiprocessor systems with the NX chipset.    -- NV  *
+ ********************************************************/
+
+/* Newer cards are produced with this bit set from the configuration
+   EEprom.  As the bit is read/write for the CPU, we can fix it here,
+   if we detect that it isn't set correctly. -- REW */
+
+static void fix_rio_pci (struct pci_dev *pdev)
+{
+  unsigned int hwbase;
+  unsigned long rebase;
+  unsigned int t;
+
+#define CNTRL_REG_OFFSET        0x50
+#define CNTRL_REG_GOODVALUE     0x18260000
+
+  pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &hwbase);
+  hwbase &= PCI_BASE_ADDRESS_MEM_MASK;
+  rebase =  (ulong) ioremap(hwbase, 0x80);
+  t = readl (rebase + CNTRL_REG_OFFSET);
+  if (t != CNTRL_REG_GOODVALUE) {
+    printk (KERN_DEBUG "rio: performing cntrl reg fix: %08x -> %08x\n", 
+            t, CNTRL_REG_GOODVALUE); 
+    writel (CNTRL_REG_GOODVALUE, rebase + CNTRL_REG_OFFSET);  
+  }
+  iounmap((char*) rebase);
+}
+#endif
+
+
+static int __init rio_init(void) 
+{
+  int found = 0;
+  int i;
+  struct Host *hp;
+  int retval;
+  struct vpd_prom *vpdp;
+  int okboard;
+
+#ifdef CONFIG_PCI
+  struct pci_dev *pdev = NULL;
+  unsigned int tint;
+  unsigned short tshort;
+#endif
+
+  func_enter();
+  rio_dprintk (RIO_DEBUG_INIT, "Initing rio module... (rio_debug=%d)\n", 
+	       rio_debug);
+
+  if (abs ((long) (&rio_debug) - rio_debug) < 0x10000) {
+    printk (KERN_WARNING "rio: rio_debug is an address, instead of a value. "
+            "Assuming -1. Was %x/%p.\n", rio_debug, &rio_debug);
+    rio_debug=-1;
+  }
+
+  if (misc_register(&rio_fw_device) < 0) {
+    printk(KERN_ERR "RIO: Unable to register firmware loader driver.\n");
+    return -EIO;
+  }
+
+  retval = rio_init_datastructures ();
+  if (retval < 0) {
+    misc_deregister(&rio_fw_device);
+    return retval;
+  }
+
+#ifdef CONFIG_PCI
+    /* First look for the JET devices: */
+    while ((pdev = pci_find_device (PCI_VENDOR_ID_SPECIALIX, 
+                                    PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8, 
+                                    pdev))) {
+       if (pci_enable_device(pdev)) continue;
+
+      /* Specialix has a whole bunch of cards with
+         0x2000 as the device ID. They say its because
+         the standard requires it. Stupid standard. */
+      /* It seems that reading a word doesn't work reliably on 2.0.
+         Also, reading a non-aligned dword doesn't work. So we read the
+         whole dword at 0x2c and extract the word at 0x2e (SUBSYSTEM_ID)
+         ourselves */
+      /* I don't know why the define doesn't work, constant 0x2c does --REW */ 
+      pci_read_config_dword (pdev, 0x2c, &tint);
+      tshort = (tint >> 16) & 0xffff;
+      rio_dprintk (RIO_DEBUG_PROBE, "Got a specialix card: %x.\n", tint);
+      if (tshort != 0x0100) {
+        rio_dprintk (RIO_DEBUG_PROBE, "But it's not a RIO card (%d)...\n", 
+                    tshort);
+        continue;
+      }
+      rio_dprintk (RIO_DEBUG_PROBE, "cp1\n");
+
+      pci_read_config_dword(pdev, PCI_BASE_ADDRESS_2, &tint);
+
+      hp = &p->RIOHosts[p->RIONumHosts];
+      hp->PaddrP =  tint & PCI_BASE_ADDRESS_MEM_MASK;
+      hp->Ivec = pdev->irq;
+      if (((1 << hp->Ivec) & rio_irqmask) == 0)
+              hp->Ivec = 0;
+      hp->Caddr = ioremap(p->RIOHosts[p->RIONumHosts].PaddrP, RIO_WINDOW_LEN);
+      hp->CardP	= (struct DpRam *) hp->Caddr;
+      hp->Type  = RIO_PCI;
+      hp->Copy  = rio_pcicopy; 
+      hp->Mode  = RIO_PCI_BOOT_FROM_RAM;
+      spin_lock_init(&hp->HostLock);
+      rio_reset_interrupt (hp);
+      rio_start_card_running (hp);
+
+      rio_dprintk (RIO_DEBUG_PROBE, "Going to test it (%p/%p).\n",
+                   (void *)p->RIOHosts[p->RIONumHosts].PaddrP,
+                   p->RIOHosts[p->RIONumHosts].Caddr);
+      if (RIOBoardTest( p->RIOHosts[p->RIONumHosts].PaddrP,
+                        p->RIOHosts[p->RIONumHosts].Caddr, 
+                        RIO_PCI, 0 ) == RIO_SUCCESS) {
+              rio_dprintk (RIO_DEBUG_INIT, "Done RIOBoardTest\n");
+              WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt, 0xff);
+              p->RIOHosts[p->RIONumHosts].UniqueNum  =
+                      ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[0]) &0xFF)<< 0)|
+                      ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[1]) &0xFF)<< 8)|
+                      ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[2]) &0xFF)<<16)|
+                      ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[3]) &0xFF)<<24);
+              rio_dprintk (RIO_DEBUG_PROBE, "Hmm Tested ok, uniqid = %x.\n",
+                           p->RIOHosts[p->RIONumHosts].UniqueNum);
+              
+              fix_rio_pci (pdev);
+              p->RIOLastPCISearch = RIO_SUCCESS;
+              p->RIONumHosts++;
+              found++;
+      } else {
+              iounmap((char*) (p->RIOHosts[p->RIONumHosts].Caddr));
+      }
+    }
+    
+    /* Then look for the older PCI card.... : */
+
+  /* These older PCI cards have problems (only byte-mode access is
+     supported), which makes them a bit awkward to support. 
+     They also have problems sharing interrupts. Be careful. 
+     (The driver now refuses to share interrupts for these
+     cards. This should be sufficient).
+  */
+
+    /* Then look for the older RIO/PCI devices: */
+    while ((pdev = pci_find_device (PCI_VENDOR_ID_SPECIALIX, 
+                                    PCI_DEVICE_ID_SPECIALIX_RIO, 
+                                    pdev))) {
+       if (pci_enable_device(pdev)) continue;
+
+#ifdef CONFIG_RIO_OLDPCI
+      pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &tint);
+
+      hp = &p->RIOHosts[p->RIONumHosts];
+      hp->PaddrP =  tint & PCI_BASE_ADDRESS_MEM_MASK;
+      hp->Ivec = pdev->irq;
+      if (((1 << hp->Ivec) & rio_irqmask) == 0) 
+      	hp->Ivec = 0;
+      hp->Ivec |= 0x8000; /* Mark as non-sharable */
+      hp->Caddr = ioremap(p->RIOHosts[p->RIONumHosts].PaddrP, RIO_WINDOW_LEN);
+      hp->CardP	= (struct DpRam *) hp->Caddr;
+      hp->Type  = RIO_PCI;
+      hp->Copy  = rio_pcicopy;
+      hp->Mode  = RIO_PCI_BOOT_FROM_RAM;
+      spin_lock_init(&hp->HostLock);
+
+      rio_dprintk (RIO_DEBUG_PROBE, "Ivec: %x\n", hp->Ivec);
+      rio_dprintk (RIO_DEBUG_PROBE, "Mode: %x\n", hp->Mode);
+
+      rio_reset_interrupt (hp);
+      rio_start_card_running (hp);
+       rio_dprintk (RIO_DEBUG_PROBE, "Going to test it (%p/%p).\n",
+                   (void *)p->RIOHosts[p->RIONumHosts].PaddrP,
+                   p->RIOHosts[p->RIONumHosts].Caddr);
+      if (RIOBoardTest( p->RIOHosts[p->RIONumHosts].PaddrP,
+                        p->RIOHosts[p->RIONumHosts].Caddr, 
+                        RIO_PCI, 0 ) == RIO_SUCCESS) {
+        WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt, 0xff);
+        p->RIOHosts[p->RIONumHosts].UniqueNum  =
+          ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[0]) &0xFF)<< 0)|
+          ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[1]) &0xFF)<< 8)|
+          ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[2]) &0xFF)<<16)|
+          ((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[3]) &0xFF)<<24);
+        rio_dprintk (RIO_DEBUG_PROBE, "Hmm Tested ok, uniqid = %x.\n",
+                   p->RIOHosts[p->RIONumHosts].UniqueNum);
+
+        p->RIOLastPCISearch = RIO_SUCCESS;
+        p->RIONumHosts++;
+        found++;
+      } else {
+        iounmap((char*) (p->RIOHosts[p->RIONumHosts].Caddr));
+      }
+#else
+      printk (KERN_ERR "Found an older RIO PCI card, but the driver is not "
+              "compiled to support it.\n");
+#endif
+    }
+#endif /* PCI */
+
+  /* Now probe for ISA cards... */
+  for (i=0;i<NR_RIO_ADDRS;i++) {
+    hp = &p->RIOHosts[p->RIONumHosts];
+    hp->PaddrP = rio_probe_addrs[i];
+    /* There was something about the IRQs of these cards. 'Forget what.--REW */
+    hp->Ivec = 0;
+    hp->Caddr = ioremap(p->RIOHosts[p->RIONumHosts].PaddrP, RIO_WINDOW_LEN);
+    hp->CardP = (struct DpRam *) hp->Caddr;
+    hp->Type = RIO_AT;
+    hp->Copy = rio_pcicopy; /* AT card PCI???? - PVDL
+                             * -- YES! this is now a normal copy. Only the 
+                             * old PCI card uses the special PCI copy. 
+                             * Moreover, the ISA card will work with the 
+                             * special PCI copy anyway. -- REW */
+    hp->Mode = 0;
+    spin_lock_init(&hp->HostLock);
+
+    vpdp = get_VPD_PROM (hp);
+    rio_dprintk (RIO_DEBUG_PROBE, "Got VPD ROM\n");
+    okboard = 0;
+    if ((strncmp (vpdp->identifier, RIO_ISA_IDENT, 16) == 0) ||
+        (strncmp (vpdp->identifier, RIO_ISA2_IDENT, 16) == 0) ||
+        (strncmp (vpdp->identifier, RIO_ISA3_IDENT, 16) == 0)) {
+      /* Board is present... */
+      if (RIOBoardTest (hp->PaddrP, 
+                        hp->Caddr, RIO_AT, 0) == RIO_SUCCESS) {
+        /* ... and feeling fine!!!! */
+        rio_dprintk (RIO_DEBUG_PROBE, "Hmm Tested ok, uniqid = %x.\n",
+                   p->RIOHosts[p->RIONumHosts].UniqueNum);
+        if (RIOAssignAT(p, hp->PaddrP, hp->Caddr, 0)) {        
+          rio_dprintk (RIO_DEBUG_PROBE, "Hmm Tested ok, host%d uniqid = %x.\n",
+                       p->RIONumHosts, 
+                       p->RIOHosts[p->RIONumHosts-1].UniqueNum);
+          okboard++;
+          found++;
+        }
+      }
+
+    if (!okboard)
+      iounmap ((char*) (hp->Caddr));
+    }
+  }
+
+
+  for (i=0;i<p->RIONumHosts;i++) {
+    hp = &p->RIOHosts[i];
+    if (hp->Ivec) {
+      int mode = SA_SHIRQ;
+      if (hp->Ivec & 0x8000) {mode = 0; hp->Ivec &= 0x7fff;}
+      rio_dprintk (RIO_DEBUG_INIT, "Requesting interrupt hp: %p rio_interrupt: %d Mode: %x\n", hp,hp->Ivec, hp->Mode);
+      retval = request_irq (hp->Ivec, rio_interrupt, mode, "rio", hp);
+      rio_dprintk (RIO_DEBUG_INIT, "Return value from request_irq: %d\n", retval);
+      if (retval) {
+              printk(KERN_ERR "rio: Cannot allocate irq %d.\n", hp->Ivec);
+              hp->Ivec = 0;
+      }
+      rio_dprintk (RIO_DEBUG_INIT, "Got irq %d.\n", hp->Ivec);
+      if (hp->Ivec != 0){
+              rio_dprintk (RIO_DEBUG_INIT, "Enabling interrupts on rio card.\n"); 
+              hp->Mode |= RIO_PCI_INT_ENABLE;
+      } else
+              hp->Mode &= !RIO_PCI_INT_ENABLE;
+      rio_dprintk (RIO_DEBUG_INIT, "New Mode: %x\n", hp->Mode);
+      rio_start_card_running (hp);
+    }
+    /* Init the timer "always" to make sure that it can safely be 
+       deleted when we unload... */
+
+    init_timer (&hp->timer);
+    if (!hp->Ivec) {
+      rio_dprintk (RIO_DEBUG_INIT, "Starting polling at %dj intervals.\n", 
+                   rio_poll);
+      hp->timer.data = i;
+      hp->timer.function = rio_pollfunc;
+      hp->timer.expires = jiffies + rio_poll;
+      add_timer (&hp->timer);
+    }
+  }
+
+  if (found) {
+    rio_dprintk (RIO_DEBUG_INIT, "rio: total of %d boards detected.\n", found);
+    rio_init_drivers ();
+  } else {
+    /* deregister the misc device we created earlier */
+    misc_deregister(&rio_fw_device);
+  }
+
+  func_exit();
+  return found?0:-EIO;
+}
+
+
+static void __exit rio_exit (void)
+{
+  int i; 
+  struct Host *hp;
+  
+  func_enter();
+
+  for (i=0,hp=p->RIOHosts;i<p->RIONumHosts;i++, hp++) {
+    RIOHostReset (hp->Type, hp->CardP, hp->Slot);
+    if (hp->Ivec) {
+      free_irq (hp->Ivec, hp);
+      rio_dprintk (RIO_DEBUG_INIT, "freed irq %d.\n", hp->Ivec);
+    }
+    /* It is safe/allowed to del_timer a non-active timer */
+    del_timer (&hp->timer);
+  }
+
+  if (misc_deregister(&rio_fw_device) < 0) {
+    printk (KERN_INFO "rio: couldn't deregister control-device\n");
+  }
+
+
+  rio_dprintk (RIO_DEBUG_CLEANUP, "Cleaning up drivers\n");
+
+  rio_release_drivers ();
+
+  /* Release dynamically allocated memory */
+  kfree (p->RIOPortp);
+  kfree (p->RIOHosts);
+  kfree (p);
+
+  func_exit();
+}
+
+module_init(rio_init);
+module_exit(rio_exit);
+
+/*
+ * Anybody who knows why this doesn't work for me, please tell me -- REW.
+ * Snatched from scsi.c (fixed one spelling error):
+ * Overrides for Emacs so that we follow Linus' tabbing style.
+ * Emacs will notice this stuff at the end of the file and automatically
+ * adjust the settings for this buffer only.  This must remain at the end
+ * of the file.
+ * ---------------------------------------------------------------------------
+ * Local Variables:
+ * c-indent-level: 4
+ * c-brace-imaginary-offset: 0
+ * c-brace-offset: -4
+ * c-argdecl-indent: 4
+ * c-label-offset: -4
+ * c-continued-statement-offset: 4
+ * c-continued-brace-offset: 0
+ * indent-tabs-mode: nil
+ * tab-width: 8
+ * End:
+ */
+
diff --git a/drivers/char/rio/rio_linux.h b/drivers/char/rio/rio_linux.h
new file mode 100644
index 0000000..1fba19d5
--- /dev/null
+++ b/drivers/char/rio/rio_linux.h
@@ -0,0 +1,187 @@
+
+/*
+ *  rio_linux.h
+ *
+ *  Copyright (C) 1998,1999,2000 R.E.Wolff@BitWizard.nl
+ *
+ *      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.
+ *
+ *  RIO serial driver.
+ *
+ *  Version 1.0 -- July, 1999. 
+ * 
+ */
+#include <linux/config.h>
+
+#define RIO_NBOARDS        4
+#define RIO_PORTSPERBOARD 128
+#define RIO_NPORTS        (RIO_NBOARDS * RIO_PORTSPERBOARD)
+
+#define MODEM_SUPPORT
+
+#ifdef __KERNEL__
+
+#define RIO_MAGIC 0x12345678
+
+
+struct vpd_prom {
+  unsigned short id;
+  char hwrev;
+  char hwass;
+  int uniqid;
+  char myear;
+  char mweek;
+  char hw_feature[5];
+  char oem_id;
+  char identifier[16];
+};
+
+
+#define RIO_DEBUG_ALL           0xffffffff
+
+#define O_OTHER(tty)    \
+      ((O_OLCUC(tty))  ||\
+      (O_ONLCR(tty))   ||\
+      (O_OCRNL(tty))   ||\
+      (O_ONOCR(tty))   ||\
+      (O_ONLRET(tty))  ||\
+      (O_OFILL(tty))   ||\
+      (O_OFDEL(tty))   ||\
+      (O_NLDLY(tty))   ||\
+      (O_CRDLY(tty))   ||\
+      (O_TABDLY(tty))  ||\
+      (O_BSDLY(tty))   ||\
+      (O_VTDLY(tty))   ||\
+      (O_FFDLY(tty)))
+
+/* Same for input. */
+#define I_OTHER(tty)    \
+      ((I_INLCR(tty))  ||\
+      (I_IGNCR(tty))   ||\
+      (I_ICRNL(tty))   ||\
+      (I_IUCLC(tty))   ||\
+      (L_ISIG(tty)))
+
+
+#endif /* __KERNEL__ */
+
+
+#define RIO_BOARD_INTR_LOCK  1
+
+
+#ifndef RIOCTL_MISC_MINOR 
+/* Allow others to gather this into "major.h" or something like that */
+#define RIOCTL_MISC_MINOR    169
+#endif
+
+
+/* Allow us to debug "in the field" without requiring clients to
+   recompile.... */
+#if 1
+#define rio_spin_lock_irqsave(sem, flags) do { \
+	rio_dprintk (RIO_DEBUG_SPINLOCK, "spinlockirqsave: %p %s:%d\n", \
+	                                sem, __FILE__, __LINE__);\
+	spin_lock_irqsave(sem, flags);\
+	} while (0)
+
+#define rio_spin_unlock_irqrestore(sem, flags) do { \
+	rio_dprintk (RIO_DEBUG_SPINLOCK, "spinunlockirqrestore: %p %s:%d\n",\
+	                                sem, __FILE__, __LINE__);\
+	spin_unlock_irqrestore(sem, flags);\
+	} while (0)
+
+#define rio_spin_lock(sem) do { \
+	rio_dprintk (RIO_DEBUG_SPINLOCK, "spinlock: %p %s:%d\n",\
+	                                sem, __FILE__, __LINE__);\
+	spin_lock(sem);\
+	} while (0)
+
+#define rio_spin_unlock(sem) do { \
+	rio_dprintk (RIO_DEBUG_SPINLOCK, "spinunlock: %p %s:%d\n",\
+	                                sem, __FILE__, __LINE__);\
+	spin_unlock(sem);\
+	} while (0)
+#else
+#define rio_spin_lock_irqsave(sem, flags) \
+            spin_lock_irqsave(sem, flags)
+
+#define rio_spin_unlock_irqrestore(sem, flags) \
+            spin_unlock_irqrestore(sem, flags)
+
+#define rio_spin_lock(sem) \
+            spin_lock(sem) 
+
+#define rio_spin_unlock(sem) \
+            spin_unlock(sem) 
+
+#endif
+
+
+
+#ifdef CONFIG_RIO_OLDPCI
+static inline void *rio_memcpy_toio (void *dummy, void *dest, void *source, int n)
+{
+  char *dst = dest;
+  char *src = source;
+
+  while (n--) {
+    writeb (*src++, dst++);
+    (void) readb (dummy);
+  }
+
+  return dest;
+}
+
+
+static inline void *rio_memcpy_fromio (void *dest, void *source, int n)
+{
+  char *dst = dest;
+  char *src = source;
+
+  while (n--) 
+    *dst++ = readb (src++);
+
+  return dest;
+}
+
+#else
+#define rio_memcpy_toio(dummy,dest,source,n)   memcpy_toio(dest, source, n)
+#define rio_memcpy_fromio                      memcpy_fromio
+#endif
+
+#define DEBUG 1
+
+
+/* 
+   This driver can spew a whole lot of debugging output at you. If you
+   need maximum performance, you should disable the DEBUG define. To
+   aid in debugging in the field, I'm leaving the compile-time debug
+   features enabled, and disable them "runtime". That allows me to
+   instruct people with problems to enable debugging without requiring
+   them to recompile... 
+*/
+
+#ifdef DEBUG
+#define rio_dprintk(f, str...) do { if (rio_debug & f) printk (str);} while (0)
+#define func_enter() rio_dprintk (RIO_DEBUG_FLOW, "rio: enter %s\n", __FUNCTION__)
+#define func_exit()  rio_dprintk (RIO_DEBUG_FLOW, "rio: exit  %s\n", __FUNCTION__)
+#define func_enter2() rio_dprintk (RIO_DEBUG_FLOW, "rio: enter %s (port %d)\n",__FUNCTION__, port->line)
+#else
+#define rio_dprintk(f, str...) /* nothing */
+#define func_enter()
+#define func_exit()
+#define func_enter2()
+#endif
+
diff --git a/drivers/char/rio/rioboard.h b/drivers/char/rio/rioboard.h
new file mode 100644
index 0000000..cc6ac6a
--- /dev/null
+++ b/drivers/char/rio/rioboard.h
@@ -0,0 +1,281 @@
+/************************************************************************/
+/*									*/
+/*	Title		:	RIO Host Card Hardware Definitions	*/
+/*									*/
+/*	Author		:	N.P.Vassallo				*/
+/*									*/
+/*	Creation	:	26th April 1999				*/
+/*									*/
+/*	Version		:	1.0.0					*/
+/*									*/
+/*	Copyright	:	(c) Specialix International Ltd. 1999	*
+ *
+ *      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.
+ *									*/
+/*	Description	:	Prototypes, structures and definitions	*/
+/*				describing the RIO board hardware	*/
+/*									*/
+/************************************************************************/
+
+/* History...
+
+1.0.0	26/04/99 NPV	Creation.
+
+*/
+
+#ifndef	_rioboard_h				/* If RIOBOARD.H not already defined */
+#define	_rioboard_h    1
+
+/*****************************************************************************
+***********************                                ***********************
+***********************   Hardware Control Registers   ***********************
+***********************                                ***********************
+*****************************************************************************/
+
+/* Hardware Registers... */
+
+#define	RIO_REG_BASE	0x7C00			/* Base of control registers */
+
+#define	RIO_CONFIG	RIO_REG_BASE + 0x0000	/* WRITE: Configuration Register */
+#define	RIO_INTSET	RIO_REG_BASE + 0x0080	/* WRITE: Interrupt Set */
+#define	RIO_RESET	RIO_REG_BASE + 0x0100	/* WRITE: Host Reset */
+#define	RIO_INTRESET	RIO_REG_BASE + 0x0180	/* WRITE: Interrupt Reset */
+
+#define	RIO_VPD_ROM	RIO_REG_BASE + 0x0000	/* READ: Vital Product Data ROM */
+#define	RIO_INTSTAT	RIO_REG_BASE + 0x0080	/* READ: Interrupt Status (Jet boards only) */
+#define	RIO_RESETSTAT	RIO_REG_BASE + 0x0100	/* READ: Reset Status (Jet boards only) */
+
+/* RIO_VPD_ROM definitions... */
+#define	VPD_SLX_ID1	0x00			/* READ: Specialix Identifier #1 */
+#define	VPD_SLX_ID2	0x01			/* READ: Specialix Identifier #2 */
+#define	VPD_HW_REV	0x02			/* READ: Hardware Revision */
+#define	VPD_HW_ASSEM	0x03			/* READ: Hardware Assembly Level */
+#define	VPD_UNIQUEID4	0x04			/* READ: Unique Identifier #4 */
+#define	VPD_UNIQUEID3	0x05			/* READ: Unique Identifier #3 */
+#define	VPD_UNIQUEID2	0x06			/* READ: Unique Identifier #2 */
+#define	VPD_UNIQUEID1	0x07			/* READ: Unique Identifier #1 */
+#define	VPD_MANU_YEAR	0x08			/* READ: Year Of Manufacture (0 = 1970) */
+#define	VPD_MANU_WEEK	0x09			/* READ: Week Of Manufacture (0 = week 1 Jan) */
+#define	VPD_HWFEATURE1	0x0A			/* READ: Hardware Feature Byte 1 */
+#define	VPD_HWFEATURE2	0x0B			/* READ: Hardware Feature Byte 2 */
+#define	VPD_HWFEATURE3	0x0C			/* READ: Hardware Feature Byte 3 */
+#define	VPD_HWFEATURE4	0x0D			/* READ: Hardware Feature Byte 4 */
+#define	VPD_HWFEATURE5	0x0E			/* READ: Hardware Feature Byte 5 */
+#define	VPD_OEMID	0x0F			/* READ: OEM Identifier */
+#define	VPD_IDENT	0x10			/* READ: Identifier string (16 bytes) */
+#define	VPD_IDENT_LEN	0x10
+
+/* VPD ROM Definitions... */
+#define	SLX_ID1		0x4D
+#define	SLX_ID2		0x98
+
+#define	PRODUCT_ID(a)	((a>>4)&0xF)		/* Use to obtain Product ID from VPD_UNIQUEID1 */
+
+#define	ID_SX_ISA	0x2
+#define	ID_RIO_EISA	0x3
+#define	ID_SX_PCI	0x5
+#define	ID_SX_EISA	0x7
+#define	ID_RIO_RTA16	0x9
+#define	ID_RIO_ISA	0xA
+#define	ID_RIO_MCA	0xB
+#define	ID_RIO_SBUS	0xC
+#define	ID_RIO_PCI	0xD
+#define	ID_RIO_RTA8	0xE
+
+/* Transputer bootstrap definitions... */
+
+#define	BOOTLOADADDR		(0x8000 - 6)
+#define	BOOTINDICATE		(0x8000 - 2)
+
+/* Firmware load position... */
+
+#define	FIRMWARELOADADDR	0x7C00		/* Firmware is loaded _before_ this address */
+
+/*****************************************************************************
+*****************************                    *****************************
+*****************************   RIO (Rev1) ISA   *****************************
+*****************************                    *****************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define	RIO_ISA_IDENT	"JBJGPGGHINSMJPJR"
+
+#define	RIO_ISA_CFG_BOOTRAM	0x01		/* Boot from RAM, else Link */
+#define	RIO_ISA_CFG_BUSENABLE	0x02		/* Enable processor bus */
+#define	RIO_ISA_CFG_IRQMASK	0x30		/* Interrupt mask */
+#define	  RIO_ISA_CFG_IRQ12	0x10		/* Interrupt Level 12 */
+#define	  RIO_ISA_CFG_IRQ11	0x20		/* Interrupt Level 11 */
+#define	  RIO_ISA_CFG_IRQ9	0x30		/* Interrupt Level 9 */
+#define	RIO_ISA_CFG_LINK20	0x40		/* 20Mbps link, else 10Mbps */
+#define	RIO_ISA_CFG_WAITSTATE0	0x80		/* 0 waitstates, else 1 */
+
+/*****************************************************************************
+*****************************                    *****************************
+*****************************   RIO (Rev2) ISA   *****************************
+*****************************                    *****************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define	RIO_ISA2_IDENT	"JBJGPGGHINSMJPJR"
+
+#define	RIO_ISA2_CFG_BOOTRAM	0x01		/* Boot from RAM, else Link */
+#define	RIO_ISA2_CFG_BUSENABLE	0x02		/* Enable processor bus */
+#define	RIO_ISA2_CFG_INTENABLE	0x04		/* Interrupt enable, else disable */
+#define	RIO_ISA2_CFG_16BIT	0x08		/* 16bit mode, else 8bit */
+#define	RIO_ISA2_CFG_IRQMASK	0x30		/* Interrupt mask */
+#define	  RIO_ISA2_CFG_IRQ15	0x00		/* Interrupt Level 15 */
+#define	  RIO_ISA2_CFG_IRQ12	0x10		/* Interrupt Level 12 */
+#define	  RIO_ISA2_CFG_IRQ11	0x20		/* Interrupt Level 11 */
+#define	  RIO_ISA2_CFG_IRQ9	0x30		/* Interrupt Level 9 */
+#define	RIO_ISA2_CFG_LINK20	0x40		/* 20Mbps link, else 10Mbps */
+#define	RIO_ISA2_CFG_WAITSTATE0	0x80		/* 0 waitstates, else 1 */
+
+/*****************************************************************************
+*****************************                   ******************************
+*****************************   RIO (Jet) ISA   ******************************
+*****************************                   ******************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define	RIO_ISA3_IDENT	"JET HOST BY KEV#"
+
+#define	RIO_ISA3_CFG_BUSENABLE	0x02		/* Enable processor bus */
+#define	RIO_ISA3_CFG_INTENABLE	0x04		/* Interrupt enable, else disable */
+#define	RIO_ISA32_CFG_IRQMASK	0xF30		/* Interrupt mask */
+#define	  RIO_ISA3_CFG_IRQ15	0xF0		/* Interrupt Level 15 */
+#define	  RIO_ISA3_CFG_IRQ12	0xC0		/* Interrupt Level 12 */
+#define	  RIO_ISA3_CFG_IRQ11	0xB0		/* Interrupt Level 11 */
+#define	  RIO_ISA3_CFG_IRQ10	0xA0		/* Interrupt Level 10 */
+#define	  RIO_ISA3_CFG_IRQ9	0x90		/* Interrupt Level 9 */
+
+/*****************************************************************************
+*********************************             ********************************
+*********************************   RIO MCA   ********************************
+*********************************             ********************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define	RIO_MCA_IDENT	"JBJGPGGHINSMJPJR"
+
+#define	RIO_MCA_CFG_BOOTRAM	0x01		/* Boot from RAM, else Link */
+#define	RIO_MCA_CFG_BUSENABLE	0x02		/* Enable processor bus */
+#define	RIO_MCA_CFG_LINK20	0x40		/* 20Mbps link, else 10Mbps */
+
+/*****************************************************************************
+********************************              ********************************
+********************************   RIO EISA   ********************************
+********************************              ********************************
+*****************************************************************************/
+
+/* EISA Configuration Space Definitions... */
+#define	EISA_PRODUCT_ID1	0xC80
+#define	EISA_PRODUCT_ID2	0xC81
+#define	EISA_PRODUCT_NUMBER	0xC82
+#define	EISA_REVISION_NUMBER	0xC83
+#define	EISA_CARD_ENABLE	0xC84
+#define	EISA_VPD_UNIQUEID4	0xC88		/* READ: Unique Identifier #4 */
+#define	EISA_VPD_UNIQUEID3	0xC8A		/* READ: Unique Identifier #3 */
+#define	EISA_VPD_UNIQUEID2	0xC90		/* READ: Unique Identifier #2 */
+#define	EISA_VPD_UNIQUEID1	0xC92		/* READ: Unique Identifier #1 */
+#define	EISA_VPD_MANU_YEAR	0xC98		/* READ: Year Of Manufacture (0 = 1970) */
+#define	EISA_VPD_MANU_WEEK	0xC9A		/* READ: Week Of Manufacture (0 = week 1 Jan) */
+#define	EISA_MEM_ADDR_23_16	0xC00
+#define	EISA_MEM_ADDR_31_24	0xC01
+#define	EISA_RIO_CONFIG		0xC02		/* WRITE: Configuration Register */
+#define	EISA_RIO_INTSET		0xC03		/* WRITE: Interrupt Set */
+#define	EISA_RIO_INTRESET	0xC03		/* READ:  Interrupt Reset */
+
+/* Control Register Definitions... */
+#define	RIO_EISA_CFG_BOOTRAM	0x01		/* Boot from RAM, else Link */
+#define	RIO_EISA_CFG_LINK20	0x02		/* 20Mbps link, else 10Mbps */
+#define	RIO_EISA_CFG_BUSENABLE	0x04		/* Enable processor bus */
+#define	RIO_EISA_CFG_PROCRUN	0x08		/* Processor running, else reset */
+#define	RIO_EISA_CFG_IRQMASK	0xF0		/* Interrupt mask */
+#define	  RIO_EISA_CFG_IRQ15	0xF0		/* Interrupt Level 15 */
+#define	  RIO_EISA_CFG_IRQ14	0xE0		/* Interrupt Level 14 */
+#define	  RIO_EISA_CFG_IRQ12	0xC0		/* Interrupt Level 12 */
+#define	  RIO_EISA_CFG_IRQ11	0xB0		/* Interrupt Level 11 */
+#define	  RIO_EISA_CFG_IRQ10	0xA0		/* Interrupt Level 10 */
+#define	  RIO_EISA_CFG_IRQ9	0x90		/* Interrupt Level 9 */
+#define	  RIO_EISA_CFG_IRQ7	0x70		/* Interrupt Level 7 */
+#define	  RIO_EISA_CFG_IRQ6	0x60		/* Interrupt Level 6 */
+#define	  RIO_EISA_CFG_IRQ5	0x50		/* Interrupt Level 5 */
+#define	  RIO_EISA_CFG_IRQ4	0x40		/* Interrupt Level 4 */
+#define	  RIO_EISA_CFG_IRQ3	0x30		/* Interrupt Level 3 */
+
+/*****************************************************************************
+********************************              ********************************
+********************************   RIO SBus   ********************************
+********************************              ********************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define	RIO_SBUS_IDENT	"JBPGK#\0\0\0\0\0\0\0\0\0\0"
+
+#define	RIO_SBUS_CFG_BOOTRAM	0x01		/* Boot from RAM, else Link */
+#define	RIO_SBUS_CFG_BUSENABLE	0x02		/* Enable processor bus */
+#define	RIO_SBUS_CFG_INTENABLE	0x04		/* Interrupt enable, else disable */
+#define	RIO_SBUS_CFG_IRQMASK	0x38		/* Interrupt mask */
+#define	  RIO_SBUS_CFG_IRQNONE	0x00		/* No Interrupt */
+#define	  RIO_SBUS_CFG_IRQ7	0x38		/* Interrupt Level 7 */
+#define	  RIO_SBUS_CFG_IRQ6	0x30		/* Interrupt Level 6 */
+#define	  RIO_SBUS_CFG_IRQ5	0x28		/* Interrupt Level 5 */
+#define	  RIO_SBUS_CFG_IRQ4	0x20		/* Interrupt Level 4 */
+#define	  RIO_SBUS_CFG_IRQ3	0x18		/* Interrupt Level 3 */
+#define	  RIO_SBUS_CFG_IRQ2	0x10		/* Interrupt Level 2 */
+#define	  RIO_SBUS_CFG_IRQ1	0x08		/* Interrupt Level 1 */
+#define	RIO_SBUS_CFG_LINK20	0x40		/* 20Mbps link, else 10Mbps */
+#define	RIO_SBUS_CFG_PROC25	0x80		/* 25Mhz processor clock, else 20Mhz */
+
+/*****************************************************************************
+*********************************             ********************************
+*********************************   RIO PCI   ********************************
+*********************************             ********************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define	RIO_PCI_IDENT	"ECDDPGJGJHJRGSK#"
+
+#define	RIO_PCI_CFG_BOOTRAM	0x01		/* Boot from RAM, else Link */
+#define	RIO_PCI_CFG_BUSENABLE	0x02		/* Enable processor bus */
+#define	RIO_PCI_CFG_INTENABLE	0x04		/* Interrupt enable, else disable */
+#define	RIO_PCI_CFG_LINK20	0x40		/* 20Mbps link, else 10Mbps */
+#define	RIO_PCI_CFG_PROC25	0x80		/* 25Mhz processor clock, else 20Mhz */
+
+/* PCI Definitions... */
+#define	SPX_VENDOR_ID		0x11CB		/* Assigned by the PCI SIG */
+#define	SPX_DEVICE_ID		0x8000		/* RIO bridge boards */
+#define	SPX_PLXDEVICE_ID	0x2000		/* PLX bridge boards */
+#define	SPX_SUB_VENDOR_ID	SPX_VENDOR_ID	/* Same as vendor id */
+#define	RIO_SUB_SYS_ID		0x0800		/* RIO PCI board */
+
+/*****************************************************************************
+*****************************                   ******************************
+*****************************   RIO (Jet) PCI   ******************************
+*****************************                   ******************************
+*****************************************************************************/
+
+/* Control Register Definitions... */
+#define	RIO_PCI2_IDENT	"JET HOST BY KEV#"
+
+#define	RIO_PCI2_CFG_BUSENABLE	0x02		/* Enable processor bus */
+#define	RIO_PCI2_CFG_INTENABLE	0x04		/* Interrupt enable, else disable */
+
+/* PCI Definitions... */
+#define	RIO2_SUB_SYS_ID		0x0100		/* RIO (Jet) PCI board */
+
+#endif						/*_rioboard_h */
+
+/* End of RIOBOARD.H */
diff --git a/drivers/char/rio/rioboot.c b/drivers/char/rio/rioboot.c
new file mode 100644
index 0000000..a8be11d
--- /dev/null
+++ b/drivers/char/rio/rioboot.c
@@ -0,0 +1,1360 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: rioboot.c
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 10:33:36
+**	Retrieved	: 11/6/98 10:33:48
+**
+**  ident @(#)rioboot.c	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifdef SCCS_LABELS
+static char *_rioboot_c_sccs_ = "@(#)rioboot.c	1.3";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+
+static int RIOBootComplete( struct rio_info *p, struct Host *HostP, uint Rup, struct PktCmd *PktCmdP );
+
+static uchar
+RIOAtVec2Ctrl[] =
+{
+	/* 0 */  INTERRUPT_DISABLE,
+	/* 1 */  INTERRUPT_DISABLE,
+	/* 2 */  INTERRUPT_DISABLE,
+	/* 3 */  INTERRUPT_DISABLE,
+	/* 4 */  INTERRUPT_DISABLE,
+	/* 5 */  INTERRUPT_DISABLE,
+	/* 6 */  INTERRUPT_DISABLE,
+	/* 7 */  INTERRUPT_DISABLE,
+	/* 8 */  INTERRUPT_DISABLE,
+	/* 9 */  IRQ_9|INTERRUPT_ENABLE,
+	/* 10 */ INTERRUPT_DISABLE,
+	/* 11 */ IRQ_11|INTERRUPT_ENABLE,
+	/* 12 */ IRQ_12|INTERRUPT_ENABLE,
+	/* 13 */ INTERRUPT_DISABLE,
+	/* 14 */ INTERRUPT_DISABLE,
+	/* 15 */ IRQ_15|INTERRUPT_ENABLE
+};
+
+/*
+** Load in the RTA boot code.
+*/
+int
+RIOBootCodeRTA(p, rbp)
+struct rio_info *	p;
+struct DownLoad *	rbp; 
+{
+	int offset;
+
+	func_enter ();
+
+	/* Linux doesn't allow you to disable interrupts during a
+	   "copyin". (Crash when a pagefault occurs). */
+	/* disable(oldspl); */
+	
+	rio_dprintk (RIO_DEBUG_BOOT, "Data at user address 0x%x\n",(int)rbp->DataP);
+
+	/*
+	** Check that we have set asside enough memory for this
+	*/
+	if ( rbp->Count > SIXTY_FOUR_K ) {
+		rio_dprintk (RIO_DEBUG_BOOT, "RTA Boot Code Too Large!\n");
+		p->RIOError.Error = HOST_FILE_TOO_LARGE;
+		/* restore(oldspl); */
+		func_exit ();
+		return -ENOMEM;
+	}
+
+	if ( p->RIOBooting ) {
+		rio_dprintk (RIO_DEBUG_BOOT, "RTA Boot Code : BUSY BUSY BUSY!\n");
+		p->RIOError.Error = BOOT_IN_PROGRESS;
+		/* restore(oldspl); */
+		func_exit ();
+		return -EBUSY;
+	}
+
+	/*
+	** The data we load in must end on a (RTA_BOOT_DATA_SIZE) byte boundary,
+	** so calculate how far we have to move the data up the buffer
+	** to achieve this.
+	*/
+	offset = (RTA_BOOT_DATA_SIZE - (rbp->Count % RTA_BOOT_DATA_SIZE)) % 
+							RTA_BOOT_DATA_SIZE;
+
+	/*
+	** Be clean, and clear the 'unused' portion of the boot buffer,
+	** because it will (eventually) be part of the Rta run time environment
+	** and so should be zeroed.
+	*/
+	bzero( (caddr_t)p->RIOBootPackets, offset );
+
+	/*
+	** Copy the data from user space.
+	*/
+
+	if ( copyin((int)rbp->DataP,((caddr_t)(p->RIOBootPackets))+offset,
+				rbp->Count) ==COPYFAIL ) {
+		rio_dprintk (RIO_DEBUG_BOOT, "Bad data copy from user space\n");
+		p->RIOError.Error = COPYIN_FAILED;
+		/* restore(oldspl); */
+		func_exit ();
+		return -EFAULT;
+	}
+
+	/*
+	** Make sure that our copy of the size includes that offset we discussed
+	** earlier.
+	*/
+	p->RIONumBootPkts = (rbp->Count+offset)/RTA_BOOT_DATA_SIZE;
+	p->RIOBootCount   = rbp->Count;
+
+	/* restore(oldspl); */
+	func_exit();
+	return 0;
+}
+
+void rio_start_card_running (struct Host * HostP)
+{
+	func_enter ();
+
+	switch ( HostP->Type ) {
+	case RIO_AT:
+		rio_dprintk (RIO_DEBUG_BOOT, "Start ISA card running\n");
+		WBYTE(HostP->Control, 
+		      BOOT_FROM_RAM | EXTERNAL_BUS_ON
+		      | HostP->Mode
+		      | RIOAtVec2Ctrl[HostP->Ivec & 0xF] );
+		break;
+		
+#ifdef FUTURE_RELEASE
+	case RIO_MCA:
+				/*
+				** MCA handles IRQ vectors differently, so we don't write 
+				** them to this register.
+				*/
+		rio_dprintk (RIO_DEBUG_BOOT, "Start MCA card running\n");
+		WBYTE(HostP->Control, McaTpBootFromRam | McaTpBusEnable | HostP->Mode);
+		break;
+
+	case RIO_EISA:
+				/*
+				** EISA is totally different and expects OUTBZs to turn it on.
+				*/
+		rio_dprintk (RIO_DEBUG_BOOT, "Start EISA card running\n");
+		OUTBZ( HostP->Slot, EISA_CONTROL_PORT, HostP->Mode | RIOEisaVec2Ctrl[HostP->Ivec] | EISA_TP_RUN | EISA_TP_BUS_ENABLE | EISA_TP_BOOT_FROM_RAM );
+		break;
+#endif
+
+	case RIO_PCI:
+				/*
+				** PCI is much the same as MCA. Everything is once again memory
+				** mapped, so we are writing to memory registers instead of io
+				** ports.
+				*/
+		rio_dprintk (RIO_DEBUG_BOOT, "Start PCI card running\n");
+		WBYTE(HostP->Control, PCITpBootFromRam | PCITpBusEnable | HostP->Mode);
+		break;
+	default:
+		rio_dprintk (RIO_DEBUG_BOOT, "Unknown host type %d\n", HostP->Type);
+		break;
+	}
+/* 
+	printk (KERN_INFO "Done with starting the card\n");
+	func_exit ();
+*/
+	return;
+}
+
+/*
+** Load in the host boot code - load it directly onto all halted hosts
+** of the correct type.
+**
+** Put your rubber pants on before messing with this code - even the magic
+** numbers have trouble understanding what they are doing here.
+*/
+int
+RIOBootCodeHOST(p, rbp)
+struct rio_info *	p;
+register struct DownLoad *rbp;
+{
+	register struct Host *HostP;
+	register caddr_t Cad;
+	register PARM_MAP *ParmMapP;
+	register int RupN;
+	int PortN;
+	uint host;
+	caddr_t StartP;
+	BYTE *DestP;
+	int wait_count;
+	ushort OldParmMap;
+	ushort offset;	/* It is very important that this is a ushort */
+	/* uint byte; */
+	caddr_t DownCode = NULL;
+	unsigned long flags;
+
+	HostP = NULL; /* Assure the compiler we've initialized it */
+	for ( host=0; host<p->RIONumHosts; host++ ) {
+		rio_dprintk (RIO_DEBUG_BOOT, "Attempt to boot host %d\n",host);
+		HostP = &p->RIOHosts[host];
+		
+		rio_dprintk (RIO_DEBUG_BOOT,  "Host Type = 0x%x, Mode = 0x%x, IVec = 0x%x\n",
+		    HostP->Type, HostP->Mode, HostP->Ivec);
+
+
+		if ( (HostP->Flags & RUN_STATE) != RC_WAITING ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "%s %d already running\n","Host",host);
+			continue;
+		}
+
+		/*
+		** Grab a 32 bit pointer to the card.
+		*/
+		Cad = HostP->Caddr;
+
+		/*
+		** We are going to (try) and load in rbp->Count bytes.
+		** The last byte will reside at p->RIOConf.HostLoadBase-1;
+		** Therefore, we need to start copying at address
+		** (caddr+p->RIOConf.HostLoadBase-rbp->Count)
+		*/
+		StartP = (caddr_t)&Cad[p->RIOConf.HostLoadBase-rbp->Count];
+
+		rio_dprintk (RIO_DEBUG_BOOT, "kernel virtual address for host is 0x%x\n", (int)Cad );
+		rio_dprintk (RIO_DEBUG_BOOT, "kernel virtual address for download is 0x%x\n", (int)StartP);
+		rio_dprintk (RIO_DEBUG_BOOT, "host loadbase is 0x%x\n",p->RIOConf.HostLoadBase);
+		rio_dprintk (RIO_DEBUG_BOOT, "size of download is 0x%x\n", rbp->Count);
+
+		if ( p->RIOConf.HostLoadBase < rbp->Count ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "Bin too large\n");
+			p->RIOError.Error = HOST_FILE_TOO_LARGE;
+			func_exit ();
+			return -EFBIG;
+		}
+		/*
+		** Ensure that the host really is stopped.
+		** Disable it's external bus & twang its reset line.
+		*/
+		RIOHostReset( HostP->Type, (struct DpRam *)HostP->CardP, HostP->Slot );
+
+		/*
+		** Copy the data directly from user space to the SRAM.
+		** This ain't going to be none too clever if the download
+		** code is bigger than this segment.
+		*/
+		rio_dprintk (RIO_DEBUG_BOOT, "Copy in code\n");
+
+		/*
+		** PCI hostcard can't cope with 32 bit accesses and so need to copy 
+		** data to a local buffer, and then dripfeed the card.
+		*/
+		if ( HostP->Type == RIO_PCI ) {
+		  /* int offset; */
+
+			DownCode = sysbrk(rbp->Count);
+			if ( !DownCode ) {
+				rio_dprintk (RIO_DEBUG_BOOT, "No system memory available\n");
+				p->RIOError.Error = NOT_ENOUGH_CORE_FOR_PCI_COPY;
+				func_exit ();
+				return -ENOMEM;
+			}
+			bzero(DownCode, rbp->Count);
+
+			if ( copyin((int)rbp->DataP,DownCode,rbp->Count)==COPYFAIL ) {
+				rio_dprintk (RIO_DEBUG_BOOT, "Bad copyin of host data\n");
+				sysfree( DownCode, rbp->Count );
+				p->RIOError.Error = COPYIN_FAILED;
+				func_exit ();
+				return -EFAULT;
+			}
+
+			HostP->Copy( DownCode, StartP, rbp->Count );
+
+			sysfree( DownCode, rbp->Count );
+		}
+		else if ( copyin((int)rbp->DataP,StartP,rbp->Count)==COPYFAIL ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "Bad copyin of host data\n");
+			p->RIOError.Error = COPYIN_FAILED;
+			func_exit ();
+			return -EFAULT;
+		}
+
+		rio_dprintk (RIO_DEBUG_BOOT, "Copy completed\n");
+
+		/*
+		**			S T O P !
+		**
+		** Upto this point the code has been fairly rational, and possibly
+		** even straight forward. What follows is a pile of crud that will
+		** magically turn into six bytes of transputer assembler. Normally
+		** you would expect an array or something, but, being me, I have
+		** chosen [been told] to use a technique whereby the startup code
+		** will be correct if we change the loadbase for the code. Which
+		** brings us onto another issue - the loadbase is the *end* of the
+		** code, not the start.
+		**
+		** If I were you I wouldn't start from here.
+		*/
+
+		/*
+		** We now need to insert a short boot section into
+		** the memory at the end of Sram2. This is normally (de)composed
+		** of the last eight bytes of the download code. The
+		** download has been assembled/compiled to expect to be
+		** loaded from 0x7FFF downwards. We have loaded it
+		** at some other address. The startup code goes into the small
+		** ram window at Sram2, in the last 8 bytes, which are really
+		** at addresses 0x7FF8-0x7FFF.
+		**
+		** If the loadbase is, say, 0x7C00, then we need to branch to
+		** address 0x7BFE to run the host.bin startup code. We assemble
+		** this jump manually.
+		**
+		** The two byte sequence 60 08 is loaded into memory at address
+		** 0x7FFE,F. This is a local branch to location 0x7FF8 (60 is nfix 0,
+		** which adds '0' to the .O register, complements .O, and then shifts
+		** it left by 4 bit positions, 08 is a jump .O+8 instruction. This will
+		** add 8 to .O (which was 0xFFF0), and will branch RELATIVE to the new
+		** location. Now, the branch starts from the value of .PC (or .IP or
+		** whatever the bloody register is called on this chip), and the .PC
+		** will be pointing to the location AFTER the branch, in this case
+		** .PC == 0x8000, so the branch will be to 0x8000+0xFFF8 = 0x7FF8.
+		**
+		** A long branch is coded at 0x7FF8. This consists of loading a four
+		** byte offset into .O using nfix (as above) and pfix operators. The
+		** pfix operates in exactly the same way as the nfix operator, but
+		** without the complement operation. The offset, of course, must be
+		** relative to the address of the byte AFTER the branch instruction,
+		** which will be (urm) 0x7FFC, so, our final destination of the branch
+		** (loadbase-2), has to be reached from here. Imagine that the loadbase
+		** is 0x7C00 (which it is), then we will need to branch to 0x7BFE (which
+		** is the first byte of the initial two byte short local branch of the
+		** download code).
+		**
+		** To code a jump from 0x7FFC (which is where the branch will start
+		** from) to 0x7BFE, we will need to branch 0xFC02 bytes (0x7FFC+0xFC02)=
+		** 0x7BFE.
+		** This will be coded as four bytes:
+		** 60 2C 20 02
+		** being nfix .O+0
+		**	   pfix .O+C
+		**	   pfix .O+0
+		**	   jump .O+2
+		**
+		** The nfix operator is used, so that the startup code will be
+		** compatible with the whole Tp family. (lies, damn lies, it'll never
+		** work in a month of Sundays).
+		**
+		** The nfix nyble is the 1s complement of the nyble value you
+		** want to load - in this case we wanted 'F' so we nfix loaded '0'.
+		*/
+
+
+		/*
+		** Dest points to the top 8 bytes of Sram2. The Tp jumps
+		** to 0x7FFE at reset time, and starts executing. This is
+		** a short branch to 0x7FF8, where a long branch is coded.
+		*/
+
+		DestP = (BYTE *)&Cad[0x7FF8];	/* <<<---- READ THE ABOVE COMMENTS */
+
+#define	NFIX(N)	(0x60 | (N))	/* .O  = (~(.O + N))<<4 */
+#define	PFIX(N)	(0x20 | (N))	/* .O  =   (.O + N)<<4  */
+#define	JUMP(N)	(0x00 | (N))	/* .PC =   .PC + .O	 */
+
+		/*
+		** 0x7FFC is the address of the location following the last byte of
+		** the four byte jump instruction.
+		** READ THE ABOVE COMMENTS
+		**
+		** offset is (TO-FROM) % MEMSIZE, but with compound buggering about.
+		** Memsize is 64K for this range of Tp, so offset is a short (unsigned,
+		** cos I don't understand 2's complement).
+		*/
+		offset = (p->RIOConf.HostLoadBase-2)-0x7FFC;
+		WBYTE( DestP[0] , NFIX(((ushort)(~offset) >> (ushort)12) & 0xF) );
+		WBYTE( DestP[1] , PFIX(( offset >> 8) & 0xF) );
+		WBYTE( DestP[2] , PFIX(( offset >> 4) & 0xF) );
+		WBYTE( DestP[3] , JUMP( offset & 0xF) );
+
+		WBYTE( DestP[6] , NFIX(0) );
+		WBYTE( DestP[7] , JUMP(8) );
+
+		rio_dprintk (RIO_DEBUG_BOOT, "host loadbase is 0x%x\n",p->RIOConf.HostLoadBase);
+		rio_dprintk (RIO_DEBUG_BOOT, "startup offset is 0x%x\n",offset);
+
+		/*
+		** Flag what is going on
+		*/
+		HostP->Flags &= ~RUN_STATE;
+		HostP->Flags |= RC_STARTUP;
+
+		/*
+		** Grab a copy of the current ParmMap pointer, so we
+		** can tell when it has changed.
+		*/
+		OldParmMap = RWORD(HostP->__ParmMapR);
+
+		rio_dprintk (RIO_DEBUG_BOOT, "Original parmmap is 0x%x\n",OldParmMap);
+
+		/*
+		** And start it running (I hope).
+		** As there is nothing dodgy or obscure about the
+		** above code, this is guaranteed to work every time.
+		*/
+		rio_dprintk (RIO_DEBUG_BOOT,  "Host Type = 0x%x, Mode = 0x%x, IVec = 0x%x\n",
+		    HostP->Type, HostP->Mode, HostP->Ivec);
+
+		rio_start_card_running(HostP);
+
+		rio_dprintk (RIO_DEBUG_BOOT, "Set control port\n");
+
+		/*
+		** Now, wait for upto five seconds for the Tp to setup the parmmap
+		** pointer:
+		*/
+		for ( wait_count=0; (wait_count<p->RIOConf.StartupTime)&&
+			(RWORD(HostP->__ParmMapR)==OldParmMap); wait_count++ ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "Checkout %d, 0x%x\n",wait_count,RWORD(HostP->__ParmMapR));
+			delay(HostP, HUNDRED_MS);
+
+		}
+
+		/*
+		** If the parmmap pointer is unchanged, then the host code
+		** has crashed & burned in a really spectacular way
+		*/
+		if ( RWORD(HostP->__ParmMapR) == OldParmMap ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "parmmap 0x%x\n", RWORD(HostP->__ParmMapR));
+			rio_dprintk (RIO_DEBUG_BOOT, "RIO Mesg Run Fail\n");
+
+#define	HOST_DISABLE \
+		HostP->Flags &= ~RUN_STATE; \
+		HostP->Flags |= RC_STUFFED; \
+		RIOHostReset( HostP->Type, (struct DpRam *)HostP->CardP, HostP->Slot );\
+		continue
+
+			HOST_DISABLE;
+		}
+
+		rio_dprintk (RIO_DEBUG_BOOT, "Running 0x%x\n", RWORD(HostP->__ParmMapR));
+
+		/*
+		** Well, the board thought it was OK, and setup its parmmap
+		** pointer. For the time being, we will pretend that this
+		** board is running, and check out what the error flag says.
+		*/
+
+		/*
+		** Grab a 32 bit pointer to the parmmap structure
+		*/
+		ParmMapP = (PARM_MAP *)RIO_PTR(Cad,RWORD(HostP->__ParmMapR));
+		rio_dprintk (RIO_DEBUG_BOOT, "ParmMapP : %x\n", (int)ParmMapP);
+		ParmMapP = (PARM_MAP *)((unsigned long)Cad + 
+						(unsigned long)((RWORD((HostP->__ParmMapR))) & 0xFFFF)); 
+		rio_dprintk (RIO_DEBUG_BOOT, "ParmMapP : %x\n", (int)ParmMapP);
+
+		/*
+		** The links entry should be 0xFFFF; we set it up
+		** with a mask to say how many PHBs to use, and 
+		** which links to use.
+		*/
+		if ( (RWORD(ParmMapP->links) & 0xFFFF) != 0xFFFF ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "RIO Mesg Run Fail %s\n", HostP->Name);
+			rio_dprintk (RIO_DEBUG_BOOT, "Links = 0x%x\n",RWORD(ParmMapP->links));
+			HOST_DISABLE;
+		}
+
+		WWORD(ParmMapP->links , RIO_LINK_ENABLE);
+
+		/*
+		** now wait for the card to set all the parmmap->XXX stuff
+		** this is a wait of upto two seconds....
+		*/
+		rio_dprintk (RIO_DEBUG_BOOT, "Looking for init_done - %d ticks\n",p->RIOConf.StartupTime);
+		HostP->timeout_id = 0;
+		for ( wait_count=0; (wait_count<p->RIOConf.StartupTime) && 
+						!RWORD(ParmMapP->init_done); wait_count++ ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "Waiting for init_done\n");
+			delay(HostP, HUNDRED_MS);
+		}
+		rio_dprintk (RIO_DEBUG_BOOT, "OK! init_done!\n");
+
+		if (RWORD(ParmMapP->error) != E_NO_ERROR || 
+							!RWORD(ParmMapP->init_done) ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "RIO Mesg Run Fail %s\n", HostP->Name);
+			rio_dprintk (RIO_DEBUG_BOOT, "Timedout waiting for init_done\n");
+			HOST_DISABLE;
+		}
+
+		rio_dprintk (RIO_DEBUG_BOOT, "Got init_done\n");
+
+		/*
+		** It runs! It runs!
+		*/
+		rio_dprintk (RIO_DEBUG_BOOT, "Host ID %x Running\n",HostP->UniqueNum);
+
+		/*
+		** set the time period between interrupts.
+		*/
+		WWORD(ParmMapP->timer, (short)p->RIOConf.Timer );
+
+		/*
+		** Translate all the 16 bit pointers in the __ParmMapR into
+		** 32 bit pointers for the driver.
+		*/
+		HostP->ParmMapP	 =	ParmMapP;
+		HostP->PhbP		 =	(PHB*)RIO_PTR(Cad,RWORD(ParmMapP->phb_ptr));
+		HostP->RupP		 =	(RUP*)RIO_PTR(Cad,RWORD(ParmMapP->rups));
+		HostP->PhbNumP	  = (ushort*)RIO_PTR(Cad,RWORD(ParmMapP->phb_num_ptr));
+		HostP->LinkStrP	 =	(LPB*)RIO_PTR(Cad,RWORD(ParmMapP->link_str_ptr));
+
+		/*
+		** point the UnixRups at the real Rups
+		*/
+		for ( RupN = 0; RupN<MAX_RUP; RupN++ ) {
+			HostP->UnixRups[RupN].RupP		= &HostP->RupP[RupN];
+			HostP->UnixRups[RupN].Id		  = RupN+1;
+			HostP->UnixRups[RupN].BaseSysPort = NO_PORT;
+			spin_lock_init(&HostP->UnixRups[RupN].RupLock);
+		}
+
+		for ( RupN = 0; RupN<LINKS_PER_UNIT; RupN++ ) {
+			HostP->UnixRups[RupN+MAX_RUP].RupP	= &HostP->LinkStrP[RupN].rup;
+			HostP->UnixRups[RupN+MAX_RUP].Id  = 0;
+			HostP->UnixRups[RupN+MAX_RUP].BaseSysPort = NO_PORT;
+			spin_lock_init(&HostP->UnixRups[RupN+MAX_RUP].RupLock);
+		}
+
+		/*
+		** point the PortP->Phbs at the real Phbs
+		*/
+		for ( PortN=p->RIOFirstPortsMapped; 
+				PortN<p->RIOLastPortsMapped+PORTS_PER_RTA; PortN++ ) {
+			if ( p->RIOPortp[PortN]->HostP == HostP ) {
+				struct Port *PortP = p->RIOPortp[PortN];
+				struct PHB *PhbP;
+				/* int oldspl; */
+
+				if ( !PortP->Mapped )
+					continue;
+
+				PhbP = &HostP->PhbP[PortP->HostPort];
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+				PortP->PhbP = PhbP;
+
+				PortP->TxAdd	= (WORD *)RIO_PTR(Cad,RWORD(PhbP->tx_add));
+				PortP->TxStart  = (WORD *)RIO_PTR(Cad,RWORD(PhbP->tx_start));
+				PortP->TxEnd	= (WORD *)RIO_PTR(Cad,RWORD(PhbP->tx_end));
+				PortP->RxRemove = (WORD *)RIO_PTR(Cad,RWORD(PhbP->rx_remove));
+				PortP->RxStart  = (WORD *)RIO_PTR(Cad,RWORD(PhbP->rx_start));
+				PortP->RxEnd	= (WORD *)RIO_PTR(Cad,RWORD(PhbP->rx_end));
+
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				/*
+				** point the UnixRup at the base SysPort
+				*/
+				if ( !(PortN % PORTS_PER_RTA) )
+					HostP->UnixRups[PortP->RupNum].BaseSysPort = PortN;
+			}
+		}
+
+		rio_dprintk (RIO_DEBUG_BOOT, "Set the card running... \n");
+		/*
+		** last thing - show the world that everything is in place
+		*/
+		HostP->Flags &= ~RUN_STATE;
+		HostP->Flags |= RC_RUNNING;
+	}
+	/*
+	** MPX always uses a poller. This is actually patched into the system
+	** configuration and called directly from each clock tick.
+	**
+	*/
+	p->RIOPolling = 1;
+
+	p->RIOSystemUp++;
+	
+	rio_dprintk (RIO_DEBUG_BOOT, "Done everything %x\n", HostP->Ivec);
+	func_exit ();
+	return 0;
+}
+
+
+
+/*
+** Boot an RTA. If we have successfully processed this boot, then
+** return 1. If we havent, then return 0.
+*/
+int
+RIOBootRup( p, Rup, HostP, PacketP)
+struct rio_info *	p;
+uint Rup;
+struct Host *HostP;
+struct PKT *PacketP; 
+{
+	struct PktCmd *PktCmdP = (struct PktCmd *)PacketP->data;
+	struct PktCmd_M *PktReplyP;
+	struct CmdBlk *CmdBlkP;
+	uint sequence;
+
+#ifdef CHECK
+	CheckHost(Host);
+	CheckRup(Rup);
+	CheckHostP(HostP);
+	CheckPacketP(PacketP);
+#endif
+
+	/*
+	** If we haven't been told what to boot, we can't boot it.
+	*/
+	if ( p->RIONumBootPkts == 0 ) {
+		rio_dprintk (RIO_DEBUG_BOOT, "No RTA code to download yet\n");
+		return 0;
+	}
+
+	/* rio_dprint(RIO_DEBUG_BOOT, NULL,DBG_BOOT,"Incoming command packet\n"); */
+	/* ShowPacket( DBG_BOOT, PacketP ); */
+
+	/*
+	** Special case of boot completed - if we get one of these then we
+	** don't need a command block. For all other cases we do, so handle
+	** this first and then get a command block, then handle every other
+	** case, relinquishing the command block if disaster strikes!
+	*/
+	if ( (RBYTE(PacketP->len) & PKT_CMD_BIT) && 
+			(RBYTE(PktCmdP->Command)==BOOT_COMPLETED) )
+		return RIOBootComplete(p, HostP, Rup, PktCmdP );
+
+	/*
+	** try to unhook a command block from the command free list.
+	*/
+	if ( !(CmdBlkP = RIOGetCmdBlk()) ) {
+		rio_dprintk (RIO_DEBUG_BOOT, "No command blocks to boot RTA! come back later.\n");
+		return 0;
+	}
+
+	/*
+	** Fill in the default info on the command block
+	*/
+	CmdBlkP->Packet.dest_unit = Rup < (ushort)MAX_RUP ? Rup : 0;
+	CmdBlkP->Packet.dest_port = BOOT_RUP;
+	CmdBlkP->Packet.src_unit  = 0;
+	CmdBlkP->Packet.src_port  = BOOT_RUP;
+
+	CmdBlkP->PreFuncP = CmdBlkP->PostFuncP = NULL;
+	PktReplyP = (struct PktCmd_M *)CmdBlkP->Packet.data;
+
+	/*
+	** process COMMANDS on the boot rup!
+	*/
+	if ( RBYTE(PacketP->len) & PKT_CMD_BIT ) {
+		/*
+		** We only expect one type of command - a BOOT_REQUEST!
+		*/
+		if ( RBYTE(PktCmdP->Command) != BOOT_REQUEST ) {
+			rio_dprintk (RIO_DEBUG_BOOT, "Unexpected command %d on BOOT RUP %d of host %d\n", 
+						PktCmdP->Command,Rup,HostP-p->RIOHosts);
+			ShowPacket( DBG_BOOT, PacketP );
+			RIOFreeCmdBlk( CmdBlkP );
+			return 1;
+		}
+
+		/*
+		** Build a Boot Sequence command block
+		**
+		** 02.03.1999 ARG - ESIL 0820 fix
+		** We no longer need to use "Boot Mode", we'll always allow
+		** boot requests - the boot will not complete if the device
+		** appears in the bindings table.
+		** So, this conditional is not required ...
+		**
+		if (p->RIOBootMode == RC_BOOT_NONE)
+			**
+			** If the system is in slave mode, and a boot request is
+			** received, set command to BOOT_ABORT so that the boot
+			** will not complete.
+			**
+			PktReplyP->Command			 = BOOT_ABORT;
+		else
+		**
+		** We'll just (always) set the command field in packet reply
+		** to allow an attempted boot sequence :
+		*/
+		PktReplyP->Command = BOOT_SEQUENCE;
+
+		PktReplyP->BootSequence.NumPackets = p->RIONumBootPkts;
+		PktReplyP->BootSequence.LoadBase   = p->RIOConf.RtaLoadBase;
+		PktReplyP->BootSequence.CodeSize   = p->RIOBootCount;
+
+		CmdBlkP->Packet.len				= BOOT_SEQUENCE_LEN | PKT_CMD_BIT;
+
+		bcopy("BOOT",(void *)&CmdBlkP->Packet.data[BOOT_SEQUENCE_LEN],4);
+
+		rio_dprintk (RIO_DEBUG_BOOT, "Boot RTA on Host %d Rup %d - %d (0x%x) packets to 0x%x\n",
+			HostP-p->RIOHosts, Rup, p->RIONumBootPkts, p->RIONumBootPkts, 
+								p->RIOConf.RtaLoadBase);
+
+		/*
+		** If this host is in slave mode, send the RTA an invalid boot
+		** sequence command block to force it to kill the boot. We wait
+		** for half a second before sending this packet to prevent the RTA
+		** attempting to boot too often. The master host should then grab
+		** the RTA and make it its own.
+		*/
+		p->RIOBooting++;
+		RIOQueueCmdBlk( HostP, Rup, CmdBlkP );
+		return 1;
+	}
+
+	/*
+	** It is a request for boot data.
+	*/
+	sequence = RWORD(PktCmdP->Sequence);
+
+	rio_dprintk (RIO_DEBUG_BOOT, "Boot block %d on Host %d Rup%d\n",sequence,HostP-p->RIOHosts,Rup);
+
+	if ( sequence >= p->RIONumBootPkts ) {
+		rio_dprintk (RIO_DEBUG_BOOT, "Got a request for packet %d, max is %d\n", sequence, 
+					p->RIONumBootPkts);
+		ShowPacket( DBG_BOOT, PacketP );
+	}
+
+	PktReplyP->Sequence = sequence;
+
+	bcopy( p->RIOBootPackets[ p->RIONumBootPkts - sequence - 1 ], 
+				PktReplyP->BootData, RTA_BOOT_DATA_SIZE );
+
+	CmdBlkP->Packet.len = PKT_MAX_DATA_LEN;
+	ShowPacket( DBG_BOOT, &CmdBlkP->Packet );
+	RIOQueueCmdBlk( HostP, Rup, CmdBlkP );
+	return 1;
+}
+
+/*
+** This function is called when an RTA been booted.
+** If booted by a host, HostP->HostUniqueNum is the booting host.
+** If booted by an RTA, HostP->Mapping[Rup].RtaUniqueNum is the booting RTA.
+** RtaUniq is the booted RTA.
+*/
+static int RIOBootComplete( struct rio_info *p, struct Host *HostP, uint Rup, struct PktCmd *PktCmdP )
+{
+	struct Map	*MapP = NULL;
+	struct Map	*MapP2 = NULL;
+	int	Flag;
+	int	found;
+	int	host, rta;
+	int	EmptySlot = -1;
+	int	entry, entry2;
+	char	*MyType, *MyName;
+	uint	MyLink;
+	ushort	RtaType;
+	uint	RtaUniq = (RBYTE(PktCmdP->UniqNum[0])) +
+			  (RBYTE(PktCmdP->UniqNum[1]) << 8) +
+			  (RBYTE(PktCmdP->UniqNum[2]) << 16) +
+			  (RBYTE(PktCmdP->UniqNum[3]) << 24);
+
+	/* Was RIOBooting-- . That's bad. If an RTA sends two of them, the
+	   driver will never think that the RTA has booted... -- REW */
+	p->RIOBooting = 0;
+
+	rio_dprintk (RIO_DEBUG_BOOT, "RTA Boot completed - BootInProgress now %d\n", p->RIOBooting);
+
+	/*
+	** Determine type of unit (16/8 port RTA).
+	*/
+	RtaType = GetUnitType(RtaUniq);
+        if ( Rup >= (ushort)MAX_RUP ) {
+	    rio_dprintk (RIO_DEBUG_BOOT, "RIO: Host %s has booted an RTA(%d) on link %c\n",
+	     HostP->Name, 8 * RtaType, RBYTE(PktCmdP->LinkNum)+'A');
+	} else {
+	    rio_dprintk (RIO_DEBUG_BOOT, "RIO: RTA %s has booted an RTA(%d) on link %c\n",
+	     HostP->Mapping[Rup].Name, 8 * RtaType,
+	     RBYTE(PktCmdP->LinkNum)+'A');
+	}
+
+	rio_dprintk (RIO_DEBUG_BOOT, "UniqNum is 0x%x\n",RtaUniq);
+
+        if ( ( RtaUniq == 0x00000000 ) || ( RtaUniq == 0xffffffff ) )
+	{
+	    rio_dprintk (RIO_DEBUG_BOOT, "Illegal RTA Uniq Number\n");
+	    return TRUE;
+	}
+
+	/*
+	** If this RTA has just booted an RTA which doesn't belong to this
+	** system, or the system is in slave mode, do not attempt to create
+	** a new table entry for it.
+	*/
+	if (!RIOBootOk(p, HostP, RtaUniq))
+	{
+	    MyLink = RBYTE(PktCmdP->LinkNum);
+	    if (Rup < (ushort) MAX_RUP)
+	    {
+		/*
+		** RtaUniq was clone booted (by this RTA). Instruct this RTA
+		** to hold off further attempts to boot on this link for 30
+		** seconds.
+		*/
+		if (RIOSuspendBootRta(HostP, HostP->Mapping[Rup].ID, MyLink))
+		{
+		    rio_dprintk (RIO_DEBUG_BOOT, "RTA failed to suspend booting on link %c\n",
+		     'A' + MyLink);
+		}
+	    }
+	    else
+	    {
+		/*
+		** RtaUniq was booted by this host. Set the booting link
+		** to hold off for 30 seconds to give another unit a
+		** chance to boot it.
+		*/
+		WWORD(HostP->LinkStrP[MyLink].WaitNoBoot, 30);
+	    }
+	    rio_dprintk (RIO_DEBUG_BOOT, "RTA %x not owned - suspend booting down link %c on unit %x\n",
+	      RtaUniq, 'A' + MyLink, HostP->Mapping[Rup].RtaUniqueNum);
+	    return TRUE;
+	}
+
+	/*
+	** Check for a SLOT_IN_USE entry for this RTA attached to the
+	** current host card in the driver table.
+	**
+	** If it exists, make a note that we have booted it. Other parts of
+	** the driver are interested in this information at a later date,
+	** in particular when the booting RTA asks for an ID for this unit,
+	** we must have set the BOOTED flag, and the NEWBOOT flag is used
+	** to force an open on any ports that where previously open on this
+	** unit.
+	*/
+        for ( entry=0; entry<MAX_RUP; entry++ )
+	{
+	    uint sysport;
+
+	    if ((HostP->Mapping[entry].Flags & SLOT_IN_USE) && 
+	       (HostP->Mapping[entry].RtaUniqueNum==RtaUniq))
+	    {
+	        HostP->Mapping[entry].Flags |= RTA_BOOTED|RTA_NEWBOOT;
+#if NEED_TO_FIX
+		RIO_SV_BROADCAST(HostP->svFlags[entry]);
+#endif
+		if ( (sysport=HostP->Mapping[entry].SysPort) != NO_PORT )
+		{
+		   if ( sysport < p->RIOFirstPortsBooted )
+			p->RIOFirstPortsBooted = sysport;
+		   if ( sysport > p->RIOLastPortsBooted )
+			p->RIOLastPortsBooted = sysport;
+		   /*
+		   ** For a 16 port RTA, check the second bank of 8 ports
+		   */
+		   if (RtaType == TYPE_RTA16)
+		   {
+			entry2 = HostP->Mapping[entry].ID2 - 1;
+			HostP->Mapping[entry2].Flags |= RTA_BOOTED|RTA_NEWBOOT;
+#if NEED_TO_FIX
+			RIO_SV_BROADCAST(HostP->svFlags[entry2]);
+#endif
+			sysport = HostP->Mapping[entry2].SysPort;
+			if ( sysport < p->RIOFirstPortsBooted )
+			    p->RIOFirstPortsBooted = sysport;
+			if ( sysport > p->RIOLastPortsBooted )
+			    p->RIOLastPortsBooted = sysport;
+		   }
+		}
+		if (RtaType == TYPE_RTA16) {
+		   rio_dprintk (RIO_DEBUG_BOOT, "RTA will be given IDs %d+%d\n",
+		    entry+1, entry2+1);
+		} else {
+		   rio_dprintk (RIO_DEBUG_BOOT, "RTA will be given ID %d\n",entry+1);
+		}
+		return TRUE;
+	    }
+	}
+
+	rio_dprintk (RIO_DEBUG_BOOT, "RTA not configured for this host\n");
+
+	if ( Rup >= (ushort)MAX_RUP )
+	{
+	    /*
+	    ** It was a host that did the booting
+	    */
+	    MyType = "Host";
+	    MyName = HostP->Name;
+	}
+	else
+	{
+	    /*
+	    ** It was an RTA that did the booting
+	    */
+	    MyType = "RTA";
+	    MyName = HostP->Mapping[Rup].Name;
+	}
+#ifdef CHECK
+	CheckString(MyType);
+	CheckString(MyName);
+#endif
+
+	MyLink = RBYTE(PktCmdP->LinkNum);
+
+	/*
+	** There is no SLOT_IN_USE entry for this RTA attached to the current
+	** host card in the driver table.
+	**
+	** Check for a SLOT_TENTATIVE entry for this RTA attached to the
+	** current host card in the driver table.
+	**
+	** If we find one, then we re-use that slot.
+	*/
+	for ( entry=0; entry<MAX_RUP; entry++ )
+	{
+	    if ( (HostP->Mapping[entry].Flags & SLOT_TENTATIVE) &&
+		 (HostP->Mapping[entry].RtaUniqueNum == RtaUniq) )
+	    {
+		if (RtaType == TYPE_RTA16)
+		{
+		    entry2 = HostP->Mapping[entry].ID2 - 1;
+		    if ( (HostP->Mapping[entry2].Flags & SLOT_TENTATIVE) &&
+			 (HostP->Mapping[entry2].RtaUniqueNum == RtaUniq) )
+			rio_dprintk (RIO_DEBUG_BOOT, "Found previous tentative slots (%d+%d)\n",
+			 entry, entry2);
+		    else
+			continue;
+		}
+		else
+			rio_dprintk (RIO_DEBUG_BOOT, "Found previous tentative slot (%d)\n",entry);
+		if (! p->RIONoMessage)
+		    cprintf("RTA connected to %s '%s' (%c) not configured.\n",MyType,MyName,MyLink+'A');
+		return TRUE;
+	    }
+	}
+
+	/*
+	** There is no SLOT_IN_USE or SLOT_TENTATIVE entry for this RTA
+	** attached to the current host card in the driver table.
+	**
+	** Check if there is a SLOT_IN_USE or SLOT_TENTATIVE entry on another
+	** host for this RTA in the driver table.
+	**
+	** For a SLOT_IN_USE entry on another host, we need to delete the RTA
+	** entry from the other host and add it to this host (using some of
+	** the functions from table.c which do this).
+	** For a SLOT_TENTATIVE entry on another host, we must cope with the
+	** following scenario:
+	**
+	** + Plug 8 port RTA into host A. (This creates SLOT_TENTATIVE entry
+	**   in table)
+	** + Unplug RTA and plug into host B. (We now have 2 SLOT_TENTATIVE
+	**   entries)
+	** + Configure RTA on host B. (This slot now becomes SLOT_IN_USE)
+	** + Unplug RTA and plug back into host A.
+	** + Configure RTA on host A. We now have the same RTA configured
+	**   with different ports on two different hosts.
+	*/
+	rio_dprintk (RIO_DEBUG_BOOT, "Have we seen RTA %x before?\n", RtaUniq );
+	found = 0;
+	Flag = 0; /* Convince the compiler this variable is initialized */
+	for ( host = 0; !found && (host < p->RIONumHosts); host++ )
+	{
+	    for ( rta=0; rta<MAX_RUP; rta++ )
+	    {
+		if ((p->RIOHosts[host].Mapping[rta].Flags &
+		 (SLOT_IN_USE | SLOT_TENTATIVE)) &&
+		 (p->RIOHosts[host].Mapping[rta].RtaUniqueNum==RtaUniq))
+		{
+		    Flag = p->RIOHosts[host].Mapping[rta].Flags;
+		    MapP = &p->RIOHosts[host].Mapping[rta];
+		    if (RtaType == TYPE_RTA16)
+		    {
+			MapP2 = &p->RIOHosts[host].Mapping[MapP->ID2 - 1];
+			rio_dprintk (RIO_DEBUG_BOOT, "This RTA is units %d+%d from host %s\n",
+			 rta+1, MapP->ID2, p->RIOHosts[host].Name);
+		    }
+		    else
+			rio_dprintk (RIO_DEBUG_BOOT, "This RTA is unit %d from host %s\n",
+			 rta+1, p->RIOHosts[host].Name);
+		    found = 1;
+		    break;
+		}
+	    }
+	}
+
+	/*
+	** There is no SLOT_IN_USE or SLOT_TENTATIVE entry for this RTA
+	** attached to the current host card in the driver table.
+	**
+	** If we have not found a SLOT_IN_USE or SLOT_TENTATIVE entry on
+	** another host for this RTA in the driver table...
+	**
+	** Check for a SLOT_IN_USE entry for this RTA in the config table.
+	*/
+	if ( !MapP )
+	{
+	    rio_dprintk (RIO_DEBUG_BOOT, "Look for RTA %x in RIOSavedTable\n",RtaUniq);
+	    for ( rta=0; rta < TOTAL_MAP_ENTRIES; rta++ )
+	    {
+		rio_dprintk (RIO_DEBUG_BOOT, "Check table entry %d (%x)",
+		      rta,
+		      p->RIOSavedTable[rta].RtaUniqueNum);
+
+		if ( (p->RIOSavedTable[rta].Flags & SLOT_IN_USE) &&
+		 (p->RIOSavedTable[rta].RtaUniqueNum == RtaUniq) )
+		{
+		    MapP = &p->RIOSavedTable[rta];
+		    Flag = p->RIOSavedTable[rta].Flags;
+		    if (RtaType == TYPE_RTA16)
+		    {
+                        for (entry2 = rta + 1; entry2 < TOTAL_MAP_ENTRIES;
+                         entry2++)
+                        {
+                            if (p->RIOSavedTable[entry2].RtaUniqueNum == RtaUniq)
+                                break;
+                        }
+                        MapP2 = &p->RIOSavedTable[entry2];
+                        rio_dprintk (RIO_DEBUG_BOOT, "This RTA is from table entries %d+%d\n",
+                              rta, entry2);
+		    }
+		    else
+			rio_dprintk (RIO_DEBUG_BOOT, "This RTA is from table entry %d\n", rta);
+		    break;
+		}
+	    }
+	}
+
+	/*
+	** There is no SLOT_IN_USE or SLOT_TENTATIVE entry for this RTA
+	** attached to the current host card in the driver table.
+	**
+	** We may have found a SLOT_IN_USE entry on another host for this
+	** RTA in the config table, or a SLOT_IN_USE or SLOT_TENTATIVE entry
+	** on another host for this RTA in the driver table.
+	**
+	** Check the driver table for room to fit this newly discovered RTA.
+	** RIOFindFreeID() first looks for free slots and if it does not
+	** find any free slots it will then attempt to oust any
+	** tentative entry in the table.
+	*/
+	EmptySlot = 1;
+	if (RtaType == TYPE_RTA16)
+	{
+	    if (RIOFindFreeID(p, HostP, &entry, &entry2) == 0)
+	    {
+		RIODefaultName(p, HostP, entry);
+		FillSlot(entry, entry2, RtaUniq, HostP);
+		EmptySlot = 0;
+	    }
+	}
+	else
+	{
+	    if (RIOFindFreeID(p, HostP, &entry, NULL) == 0)
+	    {
+		RIODefaultName(p, HostP, entry);
+		FillSlot(entry, 0, RtaUniq, HostP);
+		EmptySlot = 0;
+	    }
+	}
+
+	/*
+	** There is no SLOT_IN_USE or SLOT_TENTATIVE entry for this RTA
+	** attached to the current host card in the driver table.
+	**
+	** If we found a SLOT_IN_USE entry on another host for this
+	** RTA in the config or driver table, and there are enough free
+	** slots in the driver table, then we need to move it over and
+	** delete it from the other host.
+	** If we found a SLOT_TENTATIVE entry on another host for this
+	** RTA in the driver table, just delete the other host entry.
+	*/
+	if (EmptySlot == 0)
+	{
+	    if ( MapP )
+	    {
+		if (Flag & SLOT_IN_USE)
+		{
+		    rio_dprintk (RIO_DEBUG_BOOT, 
+    "This RTA configured on another host - move entry to current host (1)\n");
+		    HostP->Mapping[entry].SysPort = MapP->SysPort;
+		    CCOPY( MapP->Name, HostP->Mapping[entry].Name, MAX_NAME_LEN );
+		    HostP->Mapping[entry].Flags =
+		     SLOT_IN_USE | RTA_BOOTED | RTA_NEWBOOT;
+#if NEED_TO_FIX
+		    RIO_SV_BROADCAST(HostP->svFlags[entry]);
+#endif
+		    RIOReMapPorts( p, HostP, &HostP->Mapping[entry] );
+		    if ( HostP->Mapping[entry].SysPort < p->RIOFirstPortsBooted )
+			p->RIOFirstPortsBooted = HostP->Mapping[entry].SysPort;
+		    if ( HostP->Mapping[entry].SysPort > p->RIOLastPortsBooted )
+			p->RIOLastPortsBooted = HostP->Mapping[entry].SysPort;
+		    rio_dprintk (RIO_DEBUG_BOOT, "SysPort %d, Name %s\n",(int)MapP->SysPort,MapP->Name);
+		}
+		else
+		{
+		    rio_dprintk (RIO_DEBUG_BOOT, 
+   "This RTA has a tentative entry on another host - delete that entry (1)\n");
+		    HostP->Mapping[entry].Flags =
+		     SLOT_TENTATIVE | RTA_BOOTED | RTA_NEWBOOT;
+#if NEED_TO_FIX
+		    RIO_SV_BROADCAST(HostP->svFlags[entry]);
+#endif
+		}
+		if (RtaType == TYPE_RTA16)
+		{
+		    if (Flag & SLOT_IN_USE)
+		    {
+			HostP->Mapping[entry2].Flags = SLOT_IN_USE |
+			 RTA_BOOTED | RTA_NEWBOOT | RTA16_SECOND_SLOT;
+#if NEED_TO_FIX
+			RIO_SV_BROADCAST(HostP->svFlags[entry2]);
+#endif
+			HostP->Mapping[entry2].SysPort = MapP2->SysPort;
+			/*
+			** Map second block of ttys for 16 port RTA
+			*/
+			RIOReMapPorts( p, HostP, &HostP->Mapping[entry2] );
+		       if (HostP->Mapping[entry2].SysPort < p->RIOFirstPortsBooted)
+			 p->RIOFirstPortsBooted = HostP->Mapping[entry2].SysPort;
+		       if (HostP->Mapping[entry2].SysPort > p->RIOLastPortsBooted)
+			 p->RIOLastPortsBooted = HostP->Mapping[entry2].SysPort;
+			rio_dprintk (RIO_DEBUG_BOOT, "SysPort %d, Name %s\n",
+			       (int)HostP->Mapping[entry2].SysPort,
+			       HostP->Mapping[entry].Name);
+		    }
+		    else
+			HostP->Mapping[entry2].Flags = SLOT_TENTATIVE |
+			 RTA_BOOTED | RTA_NEWBOOT | RTA16_SECOND_SLOT;
+#if NEED_TO_FIX
+			RIO_SV_BROADCAST(HostP->svFlags[entry2]);
+#endif
+		    bzero( (caddr_t)MapP2, sizeof(struct Map) );
+		}
+		bzero( (caddr_t)MapP, sizeof(struct Map) );
+		if (! p->RIONoMessage)
+		    cprintf("An orphaned RTA has been adopted by %s '%s' (%c).\n",MyType,MyName,MyLink+'A');
+	    }
+	    else if (! p->RIONoMessage)
+		cprintf("RTA connected to %s '%s' (%c) not configured.\n",MyType,MyName,MyLink+'A');
+	    RIOSetChange(p);
+	    return TRUE;
+	}
+
+	/*
+	** There is no room in the driver table to make an entry for the
+	** booted RTA. Keep a note of its Uniq Num in the overflow table,
+	** so we can ignore it's ID requests.
+	*/
+	if (! p->RIONoMessage)
+	    cprintf("The RTA connected to %s '%s' (%c) cannot be configured.  You cannot configure more than 128 ports to one host card.\n",MyType,MyName,MyLink+'A');
+	for ( entry=0; entry<HostP->NumExtraBooted; entry++ )
+	{
+	    if ( HostP->ExtraUnits[entry] == RtaUniq )
+	    {
+		/*
+		** already got it!
+		*/
+		return TRUE;
+	    }
+	}
+	/*
+	** If there is room, add the unit to the list of extras
+	*/
+	if ( HostP->NumExtraBooted < MAX_EXTRA_UNITS )
+	    HostP->ExtraUnits[HostP->NumExtraBooted++] = RtaUniq;
+	return TRUE;
+}
+
+
+/*
+** If the RTA or its host appears in the RIOBindTab[] structure then
+** we mustn't boot the RTA and should return FALSE.
+** This operation is slightly different from the other drivers for RIO
+** in that this is designed to work with the new utilities
+** not config.rio and is FAR SIMPLER.
+** We no longer support the RIOBootMode variable. It is all done from the
+** "boot/noboot" field in the rio.cf file.
+*/
+int
+RIOBootOk(p, HostP, RtaUniq)
+struct rio_info *	p;
+struct Host *		HostP;
+ulong RtaUniq;
+{
+    int		Entry;
+    uint HostUniq = HostP->UniqueNum;
+
+	/*
+	** Search bindings table for RTA or its parent.
+	** If it exists, return 0, else 1.
+	*/
+	for (Entry = 0;
+	    ( Entry < MAX_RTA_BINDINGS ) && ( p->RIOBindTab[Entry] != 0 );
+	    Entry++)
+	{
+		if ( (p->RIOBindTab[Entry] == HostUniq) ||
+		     (p->RIOBindTab[Entry] == RtaUniq) )
+			return 0;
+	}
+	return 1;
+}
+
+/*
+** Make an empty slot tentative. If this is a 16 port RTA, make both
+** slots tentative, and the second one RTA_SECOND_SLOT as well.
+*/
+
+void
+FillSlot(entry, entry2, RtaUniq, HostP)
+int entry;
+int entry2;
+uint RtaUniq;
+struct Host *HostP;
+{
+	int		link;
+
+	rio_dprintk (RIO_DEBUG_BOOT, "FillSlot(%d, %d, 0x%x...)\n", entry, entry2, RtaUniq);
+
+	HostP->Mapping[entry].Flags = (RTA_BOOTED | RTA_NEWBOOT | SLOT_TENTATIVE);
+	HostP->Mapping[entry].SysPort = NO_PORT;
+	HostP->Mapping[entry].RtaUniqueNum = RtaUniq;
+	HostP->Mapping[entry].HostUniqueNum = HostP->UniqueNum;
+	HostP->Mapping[entry].ID = entry + 1;
+	HostP->Mapping[entry].ID2 = 0;
+	if (entry2) {
+		HostP->Mapping[entry2].Flags = (RTA_BOOTED | RTA_NEWBOOT | 
+								SLOT_TENTATIVE | RTA16_SECOND_SLOT);
+		HostP->Mapping[entry2].SysPort = NO_PORT;
+		HostP->Mapping[entry2].RtaUniqueNum = RtaUniq;
+		HostP->Mapping[entry2].HostUniqueNum = HostP->UniqueNum;
+		HostP->Mapping[entry2].Name[0] = '\0';
+		HostP->Mapping[entry2].ID = entry2 + 1;
+		HostP->Mapping[entry2].ID2 = entry + 1;
+		HostP->Mapping[entry].ID2 = entry2 + 1;
+	}
+	/*
+	** Must set these up, so that utilities show
+	** topology of 16 port RTAs correctly
+	*/
+	for ( link=0; link<LINKS_PER_UNIT; link++ ) {
+		HostP->Mapping[entry].Topology[link].Unit = ROUTE_DISCONNECT;
+		HostP->Mapping[entry].Topology[link].Link = NO_LINK;
+		if (entry2) {
+			HostP->Mapping[entry2].Topology[link].Unit = ROUTE_DISCONNECT;
+			HostP->Mapping[entry2].Topology[link].Link = NO_LINK;
+		}
+	}
+}
+
+#if 0
+/*
+	Function:	This function is to disable the disk interrupt 
+    Returns :   Nothing
+*/
+void
+disable_interrupt(vector)
+int	vector;
+{
+	int	ps;
+	int	val;
+
+	disable(ps);
+	if (vector > 40)  {
+		val = 1 << (vector - 40);
+		__outb(S8259+1, __inb(S8259+1) | val);
+	}
+	else {
+		val = 1 << (vector - 32);
+		__outb(M8259+1, __inb(M8259+1) | val);
+	}
+	restore(ps);
+}
+
+/*
+	Function:	This function is to enable the disk interrupt 
+    Returns :   Nothing
+*/
+void
+enable_interrupt(vector)
+int	vector;
+{
+	int	ps;
+	int	val;
+
+	disable(ps);
+	if (vector > 40)  {
+		val = 1 << (vector - 40);
+		val = ~val;
+		__outb(S8259+1, __inb(S8259+1) & val);
+	}
+	else {
+		val = 1 << (vector - 32);
+		val = ~val;
+		__outb(M8259+1, __inb(M8259+1) & val);
+	}
+	restore(ps);
+}
+#endif
diff --git a/drivers/char/rio/riocmd.c b/drivers/char/rio/riocmd.c
new file mode 100644
index 0000000..533085e
--- /dev/null
+++ b/drivers/char/rio/riocmd.c
@@ -0,0 +1,1041 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  ported from the existing SCO driver source
+**
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: riocmd.c
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 10:33:41
+**	Retrieved	: 11/6/98 10:33:49
+**
+**  ident @(#)riocmd.c	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_riocmd_c_sccs_ = "@(#)riocmd.c	1.2";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+
+
+static struct IdentifyRta IdRta;
+static struct KillNeighbour KillUnit;
+
+int
+RIOFoadRta(struct Host *HostP, struct Map *MapP)
+{
+	struct CmdBlk *CmdBlkP;
+
+	rio_dprintk (RIO_DEBUG_CMD, "FOAD RTA\n");
+
+	CmdBlkP = RIOGetCmdBlk();
+
+	if ( !CmdBlkP ) {
+		rio_dprintk (RIO_DEBUG_CMD, "FOAD RTA: GetCmdBlk failed\n");
+		return -ENXIO;
+	}
+
+	CmdBlkP->Packet.dest_unit = MapP->ID;
+	CmdBlkP->Packet.dest_port = BOOT_RUP;
+	CmdBlkP->Packet.src_unit  = 0;
+	CmdBlkP->Packet.src_port  = BOOT_RUP;
+	CmdBlkP->Packet.len	   = 0x84;
+	CmdBlkP->Packet.data[0]   = IFOAD;
+	CmdBlkP->Packet.data[1]   = 0;
+	CmdBlkP->Packet.data[2]   = IFOAD_MAGIC & 0xFF;
+	CmdBlkP->Packet.data[3]   = (IFOAD_MAGIC >> 8) & 0xFF;
+
+	if ( RIOQueueCmdBlk( HostP, MapP->ID-1, CmdBlkP) == RIO_FAIL ) {
+		rio_dprintk (RIO_DEBUG_CMD, "FOAD RTA: Failed to queue foad command\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+int
+RIOZombieRta(struct Host *HostP, struct Map *MapP)
+{
+	struct CmdBlk *CmdBlkP;
+
+	rio_dprintk (RIO_DEBUG_CMD, "ZOMBIE RTA\n");
+
+	CmdBlkP = RIOGetCmdBlk();
+
+	if ( !CmdBlkP ) {
+		rio_dprintk (RIO_DEBUG_CMD, "ZOMBIE RTA: GetCmdBlk failed\n");
+		return -ENXIO;
+	}
+
+	CmdBlkP->Packet.dest_unit = MapP->ID;
+	CmdBlkP->Packet.dest_port = BOOT_RUP;
+	CmdBlkP->Packet.src_unit  = 0;
+	CmdBlkP->Packet.src_port  = BOOT_RUP;
+	CmdBlkP->Packet.len	   = 0x84;
+	CmdBlkP->Packet.data[0]   = ZOMBIE;
+	CmdBlkP->Packet.data[1]   = 0;
+	CmdBlkP->Packet.data[2]   = ZOMBIE_MAGIC & 0xFF;
+	CmdBlkP->Packet.data[3]   = (ZOMBIE_MAGIC >> 8) & 0xFF;
+
+	if ( RIOQueueCmdBlk( HostP, MapP->ID-1, CmdBlkP) == RIO_FAIL ) {
+		rio_dprintk (RIO_DEBUG_CMD, "ZOMBIE RTA: Failed to queue zombie command\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+int
+RIOCommandRta(struct rio_info *p, uint RtaUnique,
+	int (* func)(struct Host *HostP, struct Map *MapP))
+{
+	uint Host;
+
+	rio_dprintk (RIO_DEBUG_CMD, "Command RTA 0x%x func 0x%x\n", RtaUnique, (int)func);
+
+	if ( !RtaUnique )
+		return(0);
+
+	for ( Host = 0; Host < p->RIONumHosts; Host++ ) {
+		uint Rta;
+		struct Host *HostP = &p->RIOHosts[Host];
+
+		for ( Rta = 0; Rta < RTAS_PER_HOST; Rta++ ) {
+			struct Map *MapP = &HostP->Mapping[Rta];
+
+			if ( MapP->RtaUniqueNum == RtaUnique ) {
+				uint Link;
+
+				/*
+				** now, lets just check we have a route to it...
+				** IF the routing stuff is working, then one of the
+				** topology entries for this unit will have a legit
+				** route *somewhere*. We care not where - if its got
+				** any connections, we can get to it.
+				*/
+				for ( Link = 0; Link < LINKS_PER_UNIT; Link++ ) {
+					if ( MapP->Topology[Link].Unit <= (uchar)MAX_RUP ) {
+						/*
+						** Its worth trying the operation...
+						*/
+						return (*func)( HostP, MapP );
+					}
+				}
+			}
+		}
+	}
+	return -ENXIO;
+}
+
+
+int
+RIOIdentifyRta(struct rio_info *p, caddr_t arg)
+{
+	uint Host;
+
+	if ( copyin( (int)arg, (caddr_t)&IdRta, sizeof(IdRta) ) == COPYFAIL ) {
+		rio_dprintk (RIO_DEBUG_CMD, "RIO_IDENTIFY_RTA copy failed\n");
+		p->RIOError.Error = COPYIN_FAILED;
+		return -EFAULT;
+	}
+
+	for ( Host = 0 ; Host < p->RIONumHosts; Host++ ) {
+		uint Rta;
+		struct Host *HostP = &p->RIOHosts[Host];
+
+		for ( Rta = 0; Rta < RTAS_PER_HOST; Rta++ ) {
+			struct Map *MapP = &HostP->Mapping[Rta];
+
+			if ( MapP->RtaUniqueNum == IdRta.RtaUnique ) {
+				uint Link;
+				/*
+				** now, lets just check we have a route to it...
+				** IF the routing stuff is working, then one of the
+				** topology entries for this unit will have a legit
+				** route *somewhere*. We care not where - if its got
+				** any connections, we can get to it.
+				*/
+				for ( Link = 0; Link < LINKS_PER_UNIT; Link++ ) {
+					if ( MapP->Topology[Link].Unit <= (uchar)MAX_RUP ) {
+						/*
+						** Its worth trying the operation...
+						*/
+						struct CmdBlk *CmdBlkP;
+
+						rio_dprintk (RIO_DEBUG_CMD, "IDENTIFY RTA\n");
+
+						CmdBlkP = RIOGetCmdBlk();
+
+						if ( !CmdBlkP ) {
+							rio_dprintk (RIO_DEBUG_CMD, "IDENTIFY RTA: GetCmdBlk failed\n");
+							return -ENXIO;
+						}
+		
+						CmdBlkP->Packet.dest_unit = MapP->ID;
+						CmdBlkP->Packet.dest_port = BOOT_RUP;
+						CmdBlkP->Packet.src_unit  = 0;
+						CmdBlkP->Packet.src_port  = BOOT_RUP;
+						CmdBlkP->Packet.len	   = 0x84;
+						CmdBlkP->Packet.data[0]   = IDENTIFY;
+						CmdBlkP->Packet.data[1]   = 0;
+						CmdBlkP->Packet.data[2]   = IdRta.ID;
+		
+						if ( RIOQueueCmdBlk( HostP, MapP->ID-1, CmdBlkP) == RIO_FAIL ) {
+							rio_dprintk (RIO_DEBUG_CMD, "IDENTIFY RTA: Failed to queue command\n");
+							return -EIO;
+						}
+						return 0;
+					}
+				}
+			}
+		}
+	} 
+	return -ENOENT;
+}
+
+
+int
+RIOKillNeighbour(struct rio_info *p, caddr_t arg)
+{
+	uint Host;
+	uint ID;
+	struct Host *HostP;
+	struct CmdBlk *CmdBlkP;
+
+	rio_dprintk (RIO_DEBUG_CMD, "KILL HOST NEIGHBOUR\n");
+
+	if ( copyin( (int)arg, (caddr_t)&KillUnit, sizeof(KillUnit) ) == COPYFAIL ) {
+		rio_dprintk (RIO_DEBUG_CMD, "RIO_KILL_NEIGHBOUR copy failed\n");
+		p->RIOError.Error = COPYIN_FAILED;
+		return -EFAULT;
+	}
+
+	if ( KillUnit.Link > 3 )
+		return -ENXIO;
+ 
+	CmdBlkP = RIOGetCmdBlk();
+
+	if ( !CmdBlkP ) {
+		rio_dprintk (RIO_DEBUG_CMD, "UFOAD: GetCmdBlk failed\n");
+		return -ENXIO;
+	}
+
+	CmdBlkP->Packet.dest_unit = 0;
+	CmdBlkP->Packet.src_unit  = 0;
+	CmdBlkP->Packet.dest_port = BOOT_RUP;
+	CmdBlkP->Packet.src_port  = BOOT_RUP;
+	CmdBlkP->Packet.len	   = 0x84;
+	CmdBlkP->Packet.data[0]   = UFOAD;
+	CmdBlkP->Packet.data[1]   = KillUnit.Link;
+	CmdBlkP->Packet.data[2]   = UFOAD_MAGIC & 0xFF;
+	CmdBlkP->Packet.data[3]   = (UFOAD_MAGIC >> 8) & 0xFF;
+
+	for ( Host = 0; Host < p->RIONumHosts; Host++ ) {
+		ID = 0;
+		HostP = &p->RIOHosts[Host];
+
+		if ( HostP->UniqueNum == KillUnit.UniqueNum ) {
+			if ( RIOQueueCmdBlk( HostP, RTAS_PER_HOST+KillUnit.Link,
+							CmdBlkP) == RIO_FAIL ) {
+				rio_dprintk (RIO_DEBUG_CMD, "UFOAD: Failed queue command\n");
+				return -EIO;
+			}
+			return 0;
+		}
+
+		for ( ID=0; ID < RTAS_PER_HOST; ID++ ) {
+			if ( HostP->Mapping[ID].RtaUniqueNum == KillUnit.UniqueNum ) {
+				CmdBlkP->Packet.dest_unit = ID+1;
+				if ( RIOQueueCmdBlk( HostP, ID, CmdBlkP) == RIO_FAIL ) {
+					rio_dprintk (RIO_DEBUG_CMD, "UFOAD: Failed queue command\n");
+					return -EIO;
+				}
+				return 0;
+			}
+		}
+	}
+	RIOFreeCmdBlk( CmdBlkP );
+	return -ENXIO;
+}
+
+int
+RIOSuspendBootRta(struct Host *HostP, int ID, int Link)
+{
+	struct CmdBlk *CmdBlkP;
+
+	rio_dprintk (RIO_DEBUG_CMD, "SUSPEND BOOT ON RTA ID %d, link %c\n", ID, 'A' + Link);
+
+	CmdBlkP = RIOGetCmdBlk();
+
+	if ( !CmdBlkP ) {
+		rio_dprintk (RIO_DEBUG_CMD, "SUSPEND BOOT ON RTA: GetCmdBlk failed\n");
+		return -ENXIO;
+	}
+
+	CmdBlkP->Packet.dest_unit = ID;
+	CmdBlkP->Packet.dest_port = BOOT_RUP;
+	CmdBlkP->Packet.src_unit  = 0;
+	CmdBlkP->Packet.src_port  = BOOT_RUP;
+	CmdBlkP->Packet.len	   = 0x84;
+	CmdBlkP->Packet.data[0]   = IWAIT;
+	CmdBlkP->Packet.data[1]   = Link;
+	CmdBlkP->Packet.data[2]   = IWAIT_MAGIC & 0xFF;
+	CmdBlkP->Packet.data[3]   = (IWAIT_MAGIC >> 8) & 0xFF;
+
+	if ( RIOQueueCmdBlk( HostP, ID - 1, CmdBlkP) == RIO_FAIL ) {
+		rio_dprintk (RIO_DEBUG_CMD, "SUSPEND BOOT ON RTA: Failed to queue iwait command\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+int
+RIOFoadWakeup(struct rio_info *p)
+{
+	int port;
+	register struct Port *PortP;
+	unsigned long flags;
+
+	for ( port=0; port<RIO_PORTS; port++) {
+		PortP = p->RIOPortp[port];
+
+		rio_spin_lock_irqsave(&PortP->portSem, flags);
+		PortP->Config = 0;
+		PortP->State = 0;
+		PortP->InUse = NOT_INUSE;
+		PortP->PortState = 0;
+		PortP->FlushCmdBodge = 0;
+		PortP->ModemLines = 0;
+		PortP->ModemState = 0;
+		PortP->CookMode = 0;
+		PortP->ParamSem = 0;
+		PortP->Mapped = 0;
+		PortP->WflushFlag = 0;
+		PortP->MagicFlags = 0;
+		PortP->RxDataStart = 0;
+		PortP->TxBufferIn = 0;
+		PortP->TxBufferOut = 0;
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	}
+	return(0);
+}
+
+/*
+** Incoming command on the COMMAND_RUP to be processed.
+*/
+static int
+RIOCommandRup(struct rio_info *p, uint Rup, struct Host *HostP, PKT *PacketP)
+{
+	struct PktCmd *PktCmdP = (struct PktCmd *)PacketP->data;
+	struct Port *PortP;
+	struct UnixRup *UnixRupP;
+	ushort SysPort;
+	ushort ReportedModemStatus;
+	ushort rup;
+	ushort subCommand;
+	unsigned long flags;
+
+	func_enter ();
+
+#ifdef CHECK
+	CheckHost( Host );
+	CheckHostP( HostP );
+	CheckPacketP( PacketP );
+#endif
+
+	/*
+	** 16 port RTA note:
+	** Command rup packets coming from the RTA will have pkt->data[1] (which
+	** translates to PktCmdP->PhbNum) set to the host port number for the
+	** particular unit. To access the correct BaseSysPort for a 16 port RTA,
+	** we can use PhbNum to get the rup number for the appropriate 8 port
+	** block (for the first block, this should be equal to 'Rup').
+	*/
+	rup = RBYTE(PktCmdP->PhbNum) / (ushort)PORTS_PER_RTA;
+	UnixRupP = &HostP->UnixRups[rup];
+	SysPort = UnixRupP->BaseSysPort + 
+			(RBYTE(PktCmdP->PhbNum) % (ushort)PORTS_PER_RTA);
+	rio_dprintk (RIO_DEBUG_CMD, "Command on rup %d, port %d\n", rup, SysPort);
+
+#ifdef CHECK
+	CheckRup( rup );
+	CheckUnixRupP( UnixRupP );
+#endif
+	if ( UnixRupP->BaseSysPort == NO_PORT ) {
+		rio_dprintk (RIO_DEBUG_CMD, "OBSCURE ERROR!\n");
+		rio_dprintk (RIO_DEBUG_CMD, "Diagnostics follow. Please WRITE THESE DOWN and report them to Specialix Technical Support\n");
+		rio_dprintk (RIO_DEBUG_CMD, "CONTROL information: Host number %d, name ``%s''\n", 
+			     HostP-p->RIOHosts, HostP->Name );
+		rio_dprintk (RIO_DEBUG_CMD, "CONTROL information: Rup number  0x%x\n", rup);
+
+		if ( Rup >= (ushort)MAX_RUP ) {
+			rio_dprintk (RIO_DEBUG_CMD, "CONTROL information: This is the RUP for RTA ``%s''\n",
+				     HostP->Mapping[Rup].Name);
+		} else
+			rio_dprintk (RIO_DEBUG_CMD, "CONTROL information: This is the RUP for link ``%c'' of host ``%s''\n", 
+				     ('A' + Rup - MAX_RUP), HostP->Name);
+
+		rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Destination 0x%x:0x%x\n",
+			     PacketP->dest_unit, PacketP->dest_port );
+		rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Source	  0x%x:0x%x\n",
+			     PacketP->src_unit, PacketP->src_port );
+		rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Length	  0x%x (%d)\n", PacketP->len,PacketP->len );
+		rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Control	 0x%x (%d)\n", PacketP->control, PacketP->control);
+		rio_dprintk (RIO_DEBUG_CMD, "PACKET information: Check	   0x%x (%d)\n", PacketP->csum, PacketP->csum );
+		rio_dprintk (RIO_DEBUG_CMD, "COMMAND information: Host Port Number 0x%x, "
+					"Command Code 0x%x\n", PktCmdP->PhbNum, PktCmdP->Command );
+		return TRUE;
+	}
+
+#ifdef CHECK
+	CheckSysPort( SysPort );
+#endif
+	PortP = p->RIOPortp[ SysPort ];
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+	switch( RBYTE(PktCmdP->Command) ) {
+		case BREAK_RECEIVED:
+			rio_dprintk (RIO_DEBUG_CMD, "Received a break!\n");
+			/* If the current line disc. is not multi-threading and
+	   			the current processor is not the default, reset rup_intr
+	   			and return FALSE to ensure that the command packet is
+	   			not freed. */
+			/* Call tmgr HANGUP HERE */
+			/* Fix this later when every thing works !!!! RAMRAJ */
+			gs_got_break (&PortP->gs);
+			break;
+
+		case COMPLETE:
+			rio_dprintk (RIO_DEBUG_CMD, "Command complete on phb %d host %d\n",
+			     RBYTE(PktCmdP->PhbNum), HostP-p->RIOHosts);
+			subCommand = 1;
+			switch (RBYTE(PktCmdP->SubCommand)) {
+				case MEMDUMP :
+			rio_dprintk (RIO_DEBUG_CMD, "Memory dump cmd (0x%x) from addr 0x%x\n",
+				     RBYTE(PktCmdP->SubCommand), RWORD(PktCmdP->SubAddr));
+					break;
+				case READ_REGISTER :
+			rio_dprintk (RIO_DEBUG_CMD, "Read register (0x%x)\n", RWORD(PktCmdP->SubAddr));
+					p->CdRegister = (RBYTE(PktCmdP->ModemStatus) & MSVR1_HOST);
+					break;
+				default :
+					subCommand = 0;
+				break;
+			}
+			if (subCommand)
+				break;
+			rio_dprintk (RIO_DEBUG_CMD, "New status is 0x%x was 0x%x\n",
+				     RBYTE(PktCmdP->PortStatus),PortP->PortState);
+			if (PortP->PortState != RBYTE(PktCmdP->PortStatus)) {
+				rio_dprintk (RIO_DEBUG_CMD, "Mark status & wakeup\n");
+				PortP->PortState = RBYTE(PktCmdP->PortStatus);
+				/* What should we do here ...
+				wakeup( &PortP->PortState );
+				*/
+		} else 
+			rio_dprintk (RIO_DEBUG_CMD, "No change\n");
+
+			/* FALLTHROUGH */
+		case MODEM_STATUS:
+			/*
+			** Knock out the tbusy and tstop bits, as these are not relevant
+			** to the check for modem status change (they're just there because
+			** it's a convenient place to put them!).
+			*/
+			ReportedModemStatus = RBYTE(PktCmdP->ModemStatus);
+			if ((PortP->ModemState & MSVR1_HOST) ==
+					(ReportedModemStatus & MSVR1_HOST)) {
+				rio_dprintk (RIO_DEBUG_CMD, "Modem status unchanged 0x%x\n", PortP->ModemState);
+				/*
+				** Update ModemState just in case tbusy or tstop states have
+				** changed.
+				*/
+				PortP->ModemState = ReportedModemStatus;
+			}
+			else {
+				rio_dprintk (RIO_DEBUG_CMD, "Modem status change from 0x%x to 0x%x\n",
+				     PortP->ModemState, ReportedModemStatus);
+				PortP->ModemState = ReportedModemStatus;
+#ifdef MODEM_SUPPORT
+				if ( PortP->Mapped ) {
+				/***********************************************************\
+				*************************************************************
+				***													   ***
+				***		  M O D E M   S T A T E   C H A N G E		  ***
+				***													   ***
+				*************************************************************
+				\***********************************************************/
+				/*
+				** If the device is a modem, then check the modem
+				** carrier.
+				*/
+				if (PortP->gs.tty == NULL)
+					break;
+				if (PortP->gs.tty->termios == NULL)
+					break;
+			  
+				if (!(PortP->gs.tty->termios->c_cflag & CLOCAL) &&
+				((PortP->State & (RIO_MOPEN|RIO_WOPEN)))) {
+
+					rio_dprintk (RIO_DEBUG_CMD, "Is there a Carrier?\n");
+			/*
+			** Is there a carrier?
+			*/
+					if ( PortP->ModemState & MSVR1_CD ) {
+			/*
+			** Has carrier just appeared?
+			*/
+						if (!(PortP->State & RIO_CARR_ON)) {
+							rio_dprintk (RIO_DEBUG_CMD, "Carrier just came up.\n");
+							PortP->State |= RIO_CARR_ON;
+				/*
+				** wakeup anyone in WOPEN
+				*/
+							if (PortP->State & (PORT_ISOPEN | RIO_WOPEN) )
+								wake_up_interruptible (&PortP->gs.open_wait);
+#ifdef STATS
+				PortP->Stat.ModemOnCnt++;
+#endif
+			}
+					} else {
+			/*
+			** Has carrier just dropped?
+			*/
+						if (PortP->State & RIO_CARR_ON) {
+							if (PortP->State & (PORT_ISOPEN|RIO_WOPEN|RIO_MOPEN))
+								tty_hangup (PortP->gs.tty);
+							PortP->State &= ~RIO_CARR_ON;
+							rio_dprintk (RIO_DEBUG_CMD, "Carrirer just went down\n");
+#ifdef STATS
+				PortP->Stat.ModemOffCnt++;
+#endif
+			}
+			}
+		}
+		}
+#endif
+			}
+			break;
+
+		default:
+			rio_dprintk (RIO_DEBUG_CMD, "Unknown command %d on CMD_RUP of host %d\n",
+			     RBYTE(PktCmdP->Command),HostP-p->RIOHosts);
+			break;
+	}
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+
+	func_exit ();
+
+	return TRUE;
+}
+/*
+** The command mechanism:
+**	Each rup has a chain of commands associated with it.
+**	This chain is maintained by routines in this file.
+**	Periodically we are called and we run a quick check of all the
+**	active chains to determine if there is a command to be executed,
+**	and if the rup is ready to accept it.
+**
+*/
+
+/*
+** Allocate an empty command block.
+*/
+struct CmdBlk *
+RIOGetCmdBlk(void)
+{
+	struct CmdBlk *CmdBlkP;
+
+	CmdBlkP = (struct CmdBlk *)sysbrk(sizeof(struct CmdBlk));
+	if (CmdBlkP)
+		bzero(CmdBlkP, sizeof(struct CmdBlk));
+
+	return CmdBlkP;
+}
+
+/*
+** Return a block to the head of the free list.
+*/
+void
+RIOFreeCmdBlk(struct CmdBlk *CmdBlkP)
+{
+	sysfree((void *)CmdBlkP, sizeof(struct CmdBlk));
+}
+
+/*
+** attach a command block to the list of commands to be performed for
+** a given rup.
+*/
+int
+RIOQueueCmdBlk(struct Host *HostP, uint Rup, struct CmdBlk *CmdBlkP)
+{
+	struct CmdBlk **Base;
+	struct UnixRup *UnixRupP;
+	unsigned long flags;
+
+#ifdef CHECK
+	CheckHostP( HostP );
+	CheckRup( Rup );
+	CheckCmdBlkP( CmdBlkP );
+#endif
+	if ( Rup >= (ushort)(MAX_RUP+LINKS_PER_UNIT) ) {
+		rio_dprintk (RIO_DEBUG_CMD, "Illegal rup number %d in RIOQueueCmdBlk\n",Rup);
+		RIOFreeCmdBlk( CmdBlkP );
+		return RIO_FAIL;
+	}
+
+	UnixRupP = &HostP->UnixRups[Rup];
+
+	rio_spin_lock_irqsave(&UnixRupP->RupLock, flags);
+
+	/*
+	** If the RUP is currently inactive, then put the request
+	** straight on the RUP....
+	*/
+	if ( (UnixRupP->CmdsWaitingP == NULL) && (UnixRupP->CmdPendingP == NULL) && 
+	     (RWORD(UnixRupP->RupP->txcontrol) == TX_RUP_INACTIVE ) &&
+		(CmdBlkP->PreFuncP ? (*CmdBlkP->PreFuncP)(CmdBlkP->PreArg,CmdBlkP)
+							:TRUE)) {
+		rio_dprintk (RIO_DEBUG_CMD, "RUP inactive-placing command straight on. Cmd byte is 0x%x\n",
+					     CmdBlkP->Packet.data[0]);
+
+		/*
+		** Whammy! blat that pack!
+		*/
+		HostP->Copy( (caddr_t)&CmdBlkP->Packet, 
+			RIO_PTR(HostP->Caddr, UnixRupP->RupP->txpkt ), sizeof(PKT) );
+
+		/*
+		** place command packet on the pending position.
+		*/
+		UnixRupP->CmdPendingP = CmdBlkP;
+
+		/*
+		** set the command register
+		*/
+		WWORD(UnixRupP->RupP->txcontrol , TX_PACKET_READY);
+
+		rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+
+		return RIO_SUCCESS;
+	}
+	rio_dprintk (RIO_DEBUG_CMD, "RUP active - en-queing\n");
+
+	if ( UnixRupP->CmdsWaitingP != NULL)
+		rio_dprintk (RIO_DEBUG_CMD, "Rup active - command waiting\n");
+	if ( UnixRupP->CmdPendingP != NULL )
+		rio_dprintk (RIO_DEBUG_CMD, "Rup active - command pending\n");
+	if ( RWORD(UnixRupP->RupP->txcontrol) != TX_RUP_INACTIVE )
+		rio_dprintk (RIO_DEBUG_CMD, "Rup active - command rup not ready\n");
+
+	Base = &UnixRupP->CmdsWaitingP;
+
+	rio_dprintk (RIO_DEBUG_CMD, "First try to queue cmdblk 0x%x at 0x%x\n", (int)CmdBlkP,(int)Base);
+
+	while ( *Base ) {
+		rio_dprintk (RIO_DEBUG_CMD, "Command cmdblk 0x%x here\n", (int)(*Base));
+		Base = &((*Base)->NextP);
+		rio_dprintk (RIO_DEBUG_CMD, "Now try to queue cmd cmdblk 0x%x at 0x%x\n",
+					     (int)CmdBlkP,(int)Base);
+	}
+
+	rio_dprintk (RIO_DEBUG_CMD, "Will queue cmdblk 0x%x at 0x%x\n",(int)CmdBlkP,(int)Base);
+
+	*Base = CmdBlkP;
+
+	CmdBlkP->NextP = NULL;
+
+	rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+
+	return RIO_SUCCESS;
+}
+
+/*
+** Here we go - if there is an empty rup, fill it!
+** must be called at splrio() or higher.
+*/
+void
+RIOPollHostCommands(struct rio_info *p, struct Host *HostP)
+{
+	register struct CmdBlk *CmdBlkP;
+	register struct UnixRup *UnixRupP;
+	struct PKT *PacketP;
+	ushort Rup;
+	unsigned long flags;
+
+
+	Rup = MAX_RUP+LINKS_PER_UNIT;
+
+	do {	/* do this loop for each RUP */
+		/*
+		** locate the rup we are processing & lock it
+		*/
+		UnixRupP = &HostP->UnixRups[--Rup];
+
+		spin_lock_irqsave(&UnixRupP->RupLock, flags);
+
+		/*
+		** First check for incoming commands:
+		*/
+		if ( RWORD(UnixRupP->RupP->rxcontrol) != RX_RUP_INACTIVE ) {
+			int FreeMe;
+
+			PacketP =(PKT *)RIO_PTR(HostP->Caddr,RWORD(UnixRupP->RupP->rxpkt));
+
+			ShowPacket( DBG_CMD, PacketP );
+
+			switch ( RBYTE(PacketP->dest_port) ) {
+				case BOOT_RUP:
+					rio_dprintk (RIO_DEBUG_CMD, "Incoming Boot %s packet '%x'\n", 
+						RBYTE(PacketP->len) & 0x80 ? "Command":"Data", 
+							     RBYTE(PacketP->data[0])); 
+					rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+					FreeMe= RIOBootRup(p, Rup,HostP,PacketP);
+					rio_spin_lock_irqsave(&UnixRupP->RupLock, flags);
+					break;
+
+				case COMMAND_RUP:
+					/*
+					** Free the RUP lock as loss of carrier causes a
+					** ttyflush which will (eventually) call another
+					** routine that uses the RUP lock.
+					*/
+					rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+					FreeMe= RIOCommandRup(p, Rup,HostP,PacketP);
+					if (PacketP->data[5] == MEMDUMP) {
+						rio_dprintk (RIO_DEBUG_CMD, "Memdump from 0x%x complete\n",
+								     *(ushort *) &(PacketP->data[6]));
+						HostP->Copy( (caddr_t)&(PacketP->data[8]), 
+								(caddr_t)p->RIOMemDump, 32 );
+					}
+					rio_spin_lock_irqsave(&UnixRupP->RupLock, flags);
+					break;
+
+				case ROUTE_RUP:
+					rio_spin_unlock_irqrestore( &UnixRupP->RupLock, flags);
+					FreeMe = RIORouteRup(p, Rup, HostP, PacketP );
+					rio_spin_lock_irqsave( &UnixRupP->RupLock, flags );
+					break;
+
+				default:
+					rio_dprintk (RIO_DEBUG_CMD, "Unknown RUP %d\n", RBYTE(PacketP->dest_port));
+					FreeMe = 1;
+					break;
+			}
+
+			if ( FreeMe ) {
+				rio_dprintk (RIO_DEBUG_CMD, "Free processed incoming command packet\n");
+				put_free_end(HostP,PacketP);
+
+				WWORD(UnixRupP->RupP->rxcontrol , RX_RUP_INACTIVE);
+
+				if ( RWORD(UnixRupP->RupP->handshake)==PHB_HANDSHAKE_SET ) {
+					rio_dprintk (RIO_DEBUG_CMD, "Handshake rup %d\n",Rup);
+					WWORD(UnixRupP->RupP->handshake,
+						PHB_HANDSHAKE_SET|PHB_HANDSHAKE_RESET);
+				}
+			}
+		}
+
+		/*
+		** IF a command was running on the port, 
+		** and it has completed, then tidy it up.
+		*/
+		if ( (CmdBlkP = UnixRupP->CmdPendingP) && /* ASSIGN! */
+		     (RWORD(UnixRupP->RupP->txcontrol) == TX_RUP_INACTIVE)) {
+			/*
+			** we are idle.
+			** there is a command in pending.
+			** Therefore, this command has finished.
+			** So, wakeup whoever is waiting for it (and tell them
+			** what happened).
+			*/
+			if ( CmdBlkP->Packet.dest_port == BOOT_RUP )
+				rio_dprintk (RIO_DEBUG_CMD, "Free Boot %s Command Block '%x'\n", 
+						CmdBlkP->Packet.len & 0x80 ? "Command":"Data", 
+							     CmdBlkP->Packet.data[0]);
+
+			rio_dprintk (RIO_DEBUG_CMD, "Command 0x%x completed\n",(int)CmdBlkP);
+
+			/*
+			** Clear the Rup lock to prevent mutual exclusion.
+			*/
+			if ( CmdBlkP->PostFuncP ) {
+				rio_spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+				(*CmdBlkP->PostFuncP) (CmdBlkP->PostArg,CmdBlkP);
+				rio_spin_lock_irqsave(&UnixRupP->RupLock, flags);
+			}
+
+			/*
+			** ....clear the pending flag....
+			*/
+			UnixRupP->CmdPendingP = NULL;
+
+			/*
+			** ....and return the command block to the freelist.
+			*/
+			RIOFreeCmdBlk( CmdBlkP );
+		}
+
+		/*
+		** If there is a command for this rup, and the rup
+		** is idle, then process the command
+		*/
+		if ( (CmdBlkP = UnixRupP->CmdsWaitingP) && /* ASSIGN! */
+			(UnixRupP->CmdPendingP == NULL) &&
+		     (RWORD(UnixRupP->RupP->txcontrol) == TX_RUP_INACTIVE)) {
+			/*
+			** if the pre-function is non-zero, call it.
+			** If it returns RIO_FAIL then don't
+			** send this command yet!
+			*/
+#ifdef CHECK
+			CheckCmdBlkP (CmdBlkP);
+#endif
+			if ( !(CmdBlkP->PreFuncP ?
+				(*CmdBlkP->PreFuncP)(CmdBlkP->PreArg, CmdBlkP) : TRUE)) {
+				rio_dprintk (RIO_DEBUG_CMD, "Not ready to start command 0x%x\n",(int)CmdBlkP);
+			}
+			else {
+				rio_dprintk (RIO_DEBUG_CMD, "Start new command 0x%x Cmd byte is 0x%x\n",
+							     (int)CmdBlkP, CmdBlkP->Packet.data[0]);
+				/*
+				** Whammy! blat that pack!
+				*/
+#ifdef CHECK
+				CheckPacketP ((PKT *)RIO_PTR(HostP->Caddr, UnixRupP->RupP->txpkt));
+#endif
+				HostP->Copy( (caddr_t)&CmdBlkP->Packet, 
+					RIO_PTR(HostP->Caddr, UnixRupP->RupP->txpkt), sizeof(PKT));
+
+				/*
+				** remove the command from the rup command queue...
+				*/
+				UnixRupP->CmdsWaitingP = CmdBlkP->NextP;
+
+				/*
+				** ...and place it on the pending position.
+				*/
+				UnixRupP->CmdPendingP = CmdBlkP;
+
+				/*
+				** set the command register
+				*/
+				WWORD(UnixRupP->RupP->txcontrol,TX_PACKET_READY);
+
+				/*
+				** the command block will be freed
+				** when the command has been processed.
+				*/
+			}
+		}
+		spin_unlock_irqrestore(&UnixRupP->RupLock, flags);
+	} while ( Rup );
+}
+
+int
+RIOWFlushMark(int iPortP, struct CmdBlk *CmdBlkP)
+{
+	struct Port *	PortP = (struct Port *)iPortP;
+	unsigned long flags;
+
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+#ifdef CHECK
+	CheckPortP( PortP );
+#endif
+	PortP->WflushFlag++;
+	PortP->MagicFlags |= MAGIC_FLUSH;
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	return RIOUnUse( iPortP, CmdBlkP );
+}
+
+int
+RIORFlushEnable(int iPortP, struct CmdBlk *CmdBlkP)
+{
+	struct Port *	PortP = (struct Port *)iPortP;
+	PKT *PacketP;
+	unsigned long flags;
+
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+	while ( can_remove_receive(&PacketP, PortP) ) {
+		remove_receive(PortP);
+		ShowPacket(DBG_PROC, PacketP );
+		put_free_end( PortP->HostP, PacketP );
+	}
+
+	if ( RWORD(PortP->PhbP->handshake)==PHB_HANDSHAKE_SET ) {
+		/*
+		** MAGIC! (Basically, handshake the RX buffer, so that
+		** the RTAs upstream can be re-enabled.)
+		*/
+		rio_dprintk (RIO_DEBUG_CMD, "Util: Set RX handshake bit\n");
+		WWORD(PortP->PhbP->handshake, PHB_HANDSHAKE_SET|PHB_HANDSHAKE_RESET);
+	}
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	return RIOUnUse( iPortP, CmdBlkP );
+}
+
+int
+RIOUnUse(int iPortP, struct CmdBlk *CmdBlkP)
+{
+	struct Port *	PortP = (struct Port *)iPortP;
+	unsigned long flags;
+
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+#ifdef CHECK
+	CheckPortP( PortP );
+#endif
+	rio_dprintk (RIO_DEBUG_CMD, "Decrement in use count for port\n");
+
+	if (PortP->InUse) {
+		if ( --PortP->InUse != NOT_INUSE ) {
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+		}
+	}
+	/*
+	** While PortP->InUse is set (i.e. a preemptive command has been sent to
+	** the RTA and is awaiting completion), any transmit data is prevented from
+	** being transferred from the write queue into the transmit packets
+	** (add_transmit) and no furthur transmit interrupt will be sent for that
+	** data. The next interrupt will occur up to 500ms later (RIOIntr is called
+	** twice a second as a saftey measure). This was the case when kermit was
+	** used to send data into a RIO port. After each packet was sent, TCFLSH
+	** was called to flush the read queue preemptively. PortP->InUse was
+	** incremented, thereby blocking the 6 byte acknowledgement packet
+	** transmitted back. This acknowledgment hung around for 500ms before
+	** being sent, thus reducing input performance substantially!.
+	** When PortP->InUse becomes NOT_INUSE, we must ensure that any data
+	** hanging around in the transmit buffer is sent immediately.
+	*/
+	WWORD(PortP->HostP->ParmMapP->tx_intr, 1);
+	/* What to do here ..
+	wakeup( (caddr_t)&(PortP->InUse) );
+	*/
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	return 0;
+}
+
+void
+ShowPacket(uint Flags, struct PKT *PacketP)
+{
+}
+
+/*
+** 
+** How to use this file:
+** 
+** To send a command down a rup, you need to allocate a command block, fill
+** in the packet information, fill in the command number, fill in the pre-
+** and post- functions and arguments, and then add the command block to the
+** queue of command blocks for the port in question. When the port is idle,
+** then the pre-function will be called. If this returns RIO_FAIL then the
+** command will be re-queued and tried again at a later date (probably in one
+** clock tick). If the pre-function returns NOT RIO_FAIL, then the command
+** packet will be queued on the RUP, and the txcontrol field set to the
+** command number. When the txcontrol field has changed from being the
+** command number, then the post-function will be called, with the argument
+** specified earlier, a pointer to the command block, and the value of
+** txcontrol.
+** 
+** To allocate a command block, call RIOGetCmdBlk(). This returns a pointer
+** to the command block structure allocated, or NULL if there aren't any.
+** The block will have been zeroed for you.
+** 
+** The structure has the following fields:
+** 
+** struct CmdBlk
+** {
+**	 struct CmdBlk *NextP;		  ** Pointer to next command block   **
+**	 struct PKT	 Packet;		** A packet, to copy to the rup	**
+**			int	 (*PreFuncP)();  ** The func to call to check if OK **
+**			int	 PreArg;		** The arg for the func			**
+**			int	 (*PostFuncP)(); ** The func to call when completed **
+**			int	 PostArg;	   ** The arg for the func			**
+** };
+** 
+** You need to fill in ALL fields EXCEPT NextP, which is used to link the
+** blocks together either on the free list or on the Rup list.
+** 
+** Packet is an actual packet structure to be filled in with the packet
+** information associated with the command. You need to fill in everything,
+** as the command processore doesn't process the command packet in any way.
+** 
+** The PreFuncP is called before the packet is enqueued on the host rup.
+** PreFuncP is called as (*PreFuncP)(PreArg, CmdBlkP);. PreFuncP must
+** return !RIO_FAIL to have the packet queued on the rup, and RIO_FAIL
+** if the packet is NOT to be queued.
+** 
+** The PostFuncP is called when the command has completed. It is called
+** as (*PostFuncP)(PostArg, CmdBlkP, txcontrol);. PostFuncP is not expected
+** to return a value. PostFuncP does NOT need to free the command block,
+** as this happens automatically after PostFuncP returns.
+** 
+** Once the command block has been filled in, it is attached to the correct
+** queue by calling RIOQueueCmdBlk( HostP, Rup, CmdBlkP ) where HostP is
+** a pointer to the struct Host, Rup is the NUMBER of the rup (NOT a pointer
+** to it!), and CmdBlkP is the pointer to the command block allocated using
+** RIOGetCmdBlk().
+** 
+*/
diff --git a/drivers/char/rio/rioctrl.c b/drivers/char/rio/rioctrl.c
new file mode 100644
index 0000000..b4d1a23
--- /dev/null
+++ b/drivers/char/rio/rioctrl.c
@@ -0,0 +1,1869 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: rioctrl.c
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 10:33:42
+**	Retrieved	: 11/6/98 10:33:49
+**
+**  ident @(#)rioctrl.c	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_rioctrl_c_sccs_ = "@(#)rioctrl.c	1.3";
+#endif
+
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+
+
+static struct LpbReq	 LpbReq;
+static struct RupReq	 RupReq;
+static struct PortReq	PortReq;
+static struct HostReq	HostReq;
+static struct HostDpRam HostDpRam;
+static struct DebugCtrl DebugCtrl;
+static struct Map		 MapEnt;
+static struct PortSetup PortSetup;
+static struct DownLoad	DownLoad;
+static struct SendPack  SendPack;
+/* static struct StreamInfo	StreamInfo; */
+/* static char modemtable[RIO_PORTS]; */
+static struct SpecialRupCmd SpecialRupCmd;
+static struct PortParams PortParams;
+static struct portStats portStats;
+
+static struct SubCmdStruct {
+	ushort	Host;
+	ushort	Rup;
+	ushort	Port;
+	ushort	Addr;
+} SubCmd;
+
+struct PortTty {
+	uint		port;
+	struct ttystatics	Tty;
+};
+
+static struct PortTty	PortTty;
+typedef struct ttystatics TERMIO;
+
+/*
+** This table is used when the config.rio downloads bin code to the
+** driver. We index the table using the product code, 0-F, and call
+** the function pointed to by the entry, passing the information
+** about the boot.
+** The RIOBootCodeUNKNOWN entry is there to politely tell the calling
+** process to bog off.
+*/
+static int 
+(*RIOBootTable[MAX_PRODUCT])(struct rio_info *, struct DownLoad *) =
+{
+/* 0 */	RIOBootCodeHOST,	/* Host Card */
+/* 1 */	RIOBootCodeRTA,		/* RTA */
+};
+
+#define drv_makedev(maj, min) ((((uint) maj & 0xff) << 8) | ((uint) min & 0xff))
+
+int copyin (int arg, caddr_t dp, int siz)
+{
+  int rv;
+
+  rio_dprintk (RIO_DEBUG_CTRL, "Copying %d bytes from user %p to %p.\n", siz, (void *)arg, dp);
+  rv = copy_from_user (dp, (void *)arg, siz);
+  if (rv) return COPYFAIL;
+  else return rv;
+}
+
+static int copyout (caddr_t dp, int arg, int siz)
+{
+  int rv;
+
+  rio_dprintk (RIO_DEBUG_CTRL, "Copying %d bytes to user %p from %p.\n", siz, (void *)arg, dp);
+  rv = copy_to_user ((void *)arg, dp, siz);
+  if (rv) return COPYFAIL;
+  else return rv;
+}
+
+int
+riocontrol(p, dev, cmd, arg, su)
+struct rio_info	* p;
+dev_t		dev;
+int		cmd;
+caddr_t		arg;
+int		su;
+{
+	uint	Host;	/* leave me unsigned! */
+	uint	port;	/* and me! */
+	struct Host	*HostP;
+	ushort	loop;
+	int		Entry;
+	struct Port	*PortP;
+	PKT	*PacketP;
+	int		retval = 0;
+	unsigned long flags;
+	
+	func_enter ();
+	
+	/* Confuse the compiler to think that we've initialized these */
+	Host=0;
+	PortP = NULL;
+
+	rio_dprintk (RIO_DEBUG_CTRL, "control ioctl cmd: 0x%x arg: 0x%x\n", cmd, (int)arg);
+
+	switch (cmd) {
+		/*
+		** RIO_SET_TIMER
+		**
+		** Change the value of the host card interrupt timer.
+		** If the host card number is -1 then all host cards are changed
+		** otherwise just the specified host card will be changed.
+		*/
+		case RIO_SET_TIMER:
+			rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET_TIMER to %dms\n", (uint)arg);
+			{
+				int host, value;
+				host = (uint)arg >> 16;
+				value = (uint)arg & 0x0000ffff;
+				if (host == -1) {
+					for (host = 0; host < p->RIONumHosts; host++) {
+						if (p->RIOHosts[host].Flags == RC_RUNNING) {
+							WWORD(p->RIOHosts[host].ParmMapP->timer , value);
+						}
+					}
+				} else if (host >= p->RIONumHosts) {
+					return -EINVAL;
+				} else {
+					if ( p->RIOHosts[host].Flags == RC_RUNNING ) {
+						WWORD(p->RIOHosts[host].ParmMapP->timer , value);
+					}
+				}
+			}
+			return 0;
+
+		case RIO_IDENTIFY_DRIVER:
+			/*
+			** 15.10.1998 ARG - ESIL 0760 part fix
+			** Added driver ident string output.
+			**
+#ifndef __THIS_RELEASE__
+#warning Driver Version string not defined !
+#endif
+			cprintf("%s %s %s %s\n",
+				RIO_DRV_STR,
+				__THIS_RELEASE__,
+				__DATE__, __TIME__ );
+
+			return 0;
+
+		case RIO_DISPLAY_HOST_CFG:
+			**
+			** 15.10.1998 ARG - ESIL 0760 part fix
+			** Added driver host card ident string output.
+			**
+			** Note that the only types currently supported
+			** are ISA and PCI. Also this driver does not
+			** (yet) distinguish between the Old PCI card
+			** and the Jet PCI card. In fact I think this
+			** driver only supports JET PCI !
+			**
+
+			for (Host = 0; Host < p->RIONumHosts; Host++)
+			{
+				HostP = &(p->RIOHosts[Host]);
+
+				switch ( HostP->Type )
+				{
+				    case RIO_AT :
+					strcpy( host_type, RIO_AT_HOST_STR );
+					break;
+
+				    case RIO_PCI :
+					strcpy( host_type, RIO_PCI_HOST_STR );
+					break;
+
+				    default :
+					strcpy( host_type, "Unknown" );
+					break;
+				}
+
+				cprintf(
+				  "RIO Host %d - Type:%s Addr:%X IRQ:%d\n",
+					Host, host_type,
+					(uint)HostP->PaddrP,
+					(int)HostP->Ivec - 32  );
+			}
+			return 0;
+			**
+			*/
+
+		case RIO_FOAD_RTA:
+			rio_dprintk (RIO_DEBUG_CTRL, "RIO_FOAD_RTA\n");
+			return RIOCommandRta(p, (uint)arg, RIOFoadRta);
+
+		case RIO_ZOMBIE_RTA:
+			rio_dprintk (RIO_DEBUG_CTRL, "RIO_ZOMBIE_RTA\n");
+			return RIOCommandRta(p, (uint)arg, RIOZombieRta);
+
+		case RIO_IDENTIFY_RTA:
+			rio_dprintk (RIO_DEBUG_CTRL, "RIO_IDENTIFY_RTA\n");
+			return RIOIdentifyRta(p, arg);
+
+		case RIO_KILL_NEIGHBOUR:
+			rio_dprintk (RIO_DEBUG_CTRL, "RIO_KILL_NEIGHBOUR\n");
+			return RIOKillNeighbour(p, arg);
+
+		case SPECIAL_RUP_CMD:
+			{
+				struct CmdBlk *CmdBlkP;
+
+				rio_dprintk (RIO_DEBUG_CTRL, "SPECIAL_RUP_CMD\n");
+				if (copyin((int)arg, (caddr_t)&SpecialRupCmd, 
+							sizeof(SpecialRupCmd)) == COPYFAIL ) {
+					rio_dprintk (RIO_DEBUG_CTRL, "SPECIAL_RUP_CMD copy failed\n");
+					p->RIOError.Error = COPYIN_FAILED;
+		 			return -EFAULT;
+				}
+				CmdBlkP = RIOGetCmdBlk();
+				if ( !CmdBlkP ) {
+					rio_dprintk (RIO_DEBUG_CTRL, "SPECIAL_RUP_CMD GetCmdBlk failed\n");
+					return -ENXIO;
+				}
+				CmdBlkP->Packet = SpecialRupCmd.Packet;
+				if ( SpecialRupCmd.Host >= p->RIONumHosts )
+					SpecialRupCmd.Host = 0;
+					rio_dprintk (RIO_DEBUG_CTRL, "Queue special rup command for host %d rup %d\n",
+						SpecialRupCmd.Host, SpecialRupCmd.RupNum);
+					if (RIOQueueCmdBlk(&p->RIOHosts[SpecialRupCmd.Host], 
+							SpecialRupCmd.RupNum, CmdBlkP) == RIO_FAIL) {
+						cprintf("FAILED TO QUEUE SPECIAL RUP COMMAND\n");
+					}
+					return 0;
+				}
+
+			case RIO_DEBUG_MEM:
+#ifdef DEBUG_MEM_SUPPORT
+RIO_DEBUG_CTRL, 				if (su)
+					return rio_RIODebugMemory(RIO_DEBUG_CTRL, arg);
+				else
+#endif
+					return -EPERM;
+
+			case RIO_ALL_MODEM:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_ALL_MODEM\n");
+				p->RIOError.Error = IOCTL_COMMAND_UNKNOWN;
+				return -EINVAL;
+
+			case RIO_GET_TABLE:
+				/*
+				** Read the routing table from the device driver to user space
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_TABLE\n");
+
+				if ((retval = RIOApel(p)) != 0)
+		 			return retval;
+
+				if (copyout((caddr_t)p->RIOConnectTable, (int)arg,
+						TOTAL_MAP_ENTRIES*sizeof(struct Map)) == COPYFAIL) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_TABLE copy failed\n");
+		 			p->RIOError.Error = COPYOUT_FAILED;
+		 			return -EFAULT;
+				}
+
+				{
+					int entry;
+					rio_dprintk (RIO_DEBUG_CTRL,  "*****\nMAP ENTRIES\n");
+					for ( entry=0; entry<TOTAL_MAP_ENTRIES; entry++ )
+					{
+					  if ((p->RIOConnectTable[entry].ID == 0) &&
+					      (p->RIOConnectTable[entry].HostUniqueNum == 0) &&
+					      (p->RIOConnectTable[entry].RtaUniqueNum == 0)) continue;
+					      
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.HostUniqueNum = 0x%x\n", entry, p->RIOConnectTable[entry].HostUniqueNum );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.RtaUniqueNum = 0x%x\n", entry, p->RIOConnectTable[entry].RtaUniqueNum );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.ID = 0x%x\n", entry, p->RIOConnectTable[entry].ID );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.ID2 = 0x%x\n", entry, p->RIOConnectTable[entry].ID2 );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Flags = 0x%x\n", entry, (int)p->RIOConnectTable[entry].Flags );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.SysPort = 0x%x\n", entry, (int)p->RIOConnectTable[entry].SysPort );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[0].Unit = %x\n", entry, p->RIOConnectTable[entry].Topology[0].Unit );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[0].Link = %x\n", entry, p->RIOConnectTable[entry].Topology[0].Link );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[1].Unit = %x\n", entry, p->RIOConnectTable[entry].Topology[1].Unit );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[1].Link = %x\n", entry, p->RIOConnectTable[entry].Topology[1].Link );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[2].Unit = %x\n", entry, p->RIOConnectTable[entry].Topology[2].Unit );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[2].Link = %x\n", entry, p->RIOConnectTable[entry].Topology[2].Link );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[3].Unit = %x\n", entry, p->RIOConnectTable[entry].Topology[3].Unit );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Top[4].Link = %x\n", entry, p->RIOConnectTable[entry].Topology[3].Link );
+						rio_dprintk (RIO_DEBUG_CTRL, "Map entry %d.Name = %s\n", entry, p->RIOConnectTable[entry].Name );
+					}
+					rio_dprintk (RIO_DEBUG_CTRL,  "*****\nEND MAP ENTRIES\n");
+				}
+				p->RIOQuickCheck = NOT_CHANGED;	/* a table has been gotten */
+				return 0;
+
+			case RIO_PUT_TABLE:
+				/*
+				** Write the routing table to the device driver from user space
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_TABLE\n");
+
+				if ( !su ) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_TABLE !Root\n");
+		 			p->RIOError.Error = NOT_SUPER_USER;
+		 			return -EPERM;
+				}
+				if ( copyin((int)arg, (caddr_t)&p->RIOConnectTable[0], 
+					TOTAL_MAP_ENTRIES*sizeof(struct Map) ) == COPYFAIL ) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_TABLE copy failed\n");
+		 			p->RIOError.Error = COPYIN_FAILED;
+		 			return -EFAULT;
+				}
+/*
+***********************************
+				{
+					int entry;
+					rio_dprint(RIO_DEBUG_CTRL,  ("*****\nMAP ENTRIES\n") );
+					for ( entry=0; entry<TOTAL_MAP_ENTRIES; entry++ )
+					{
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.HostUniqueNum = 0x%x\n", entry, p->RIOConnectTable[entry].HostUniqueNum ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.RtaUniqueNum = 0x%x\n", entry, p->RIOConnectTable[entry].RtaUniqueNum ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.ID = 0x%x\n", entry, p->RIOConnectTable[entry].ID ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.ID2 = 0x%x\n", entry, p->RIOConnectTable[entry].ID2 ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Flags = 0x%x\n", entry, p->RIOConnectTable[entry].Flags ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.SysPort = 0x%x\n", entry, p->RIOConnectTable[entry].SysPort ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Top[0].Unit = %b\n", entry, p->RIOConnectTable[entry].Topology[0].Unit ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Top[0].Link = %b\n", entry, p->RIOConnectTable[entry].Topology[0].Link ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Top[1].Unit = %b\n", entry, p->RIOConnectTable[entry].Topology[1].Unit ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Top[1].Link = %b\n", entry, p->RIOConnectTable[entry].Topology[1].Link ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Top[2].Unit = %b\n", entry, p->RIOConnectTable[entry].Topology[2].Unit ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Top[2].Link = %b\n", entry, p->RIOConnectTable[entry].Topology[2].Link ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Top[3].Unit = %b\n", entry, p->RIOConnectTable[entry].Topology[3].Unit ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Top[4].Link = %b\n", entry, p->RIOConnectTable[entry].Topology[3].Link ) );
+						rio_dprint(RIO_DEBUG_CTRL,  ("Map entry %d.Name = %s\n", entry, p->RIOConnectTable[entry].Name ) );
+					}
+					rio_dprint(RIO_DEBUG_CTRL,  ("*****\nEND MAP ENTRIES\n") );
+				}
+***********************************
+*/
+				return RIONewTable(p);
+
+	 		case RIO_GET_BINDINGS :
+				/*
+				** Send bindings table, containing unique numbers of RTAs owned
+				** by this system to user space
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_BINDINGS\n");
+
+				if ( !su )
+				{
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_BINDINGS !Root\n");
+		 			p->RIOError.Error = NOT_SUPER_USER;
+		 			return -EPERM;
+				}
+				if (copyout((caddr_t) p->RIOBindTab, (int)arg, 
+						(sizeof(ulong) * MAX_RTA_BINDINGS)) == COPYFAIL ) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_BINDINGS copy failed\n");
+		 			p->RIOError.Error = COPYOUT_FAILED;
+		 			return -EFAULT;
+				}
+				return 0;
+
+	 		case RIO_PUT_BINDINGS :
+			/*
+			** Receive a bindings table, containing unique numbers of RTAs owned
+			** by this system
+			*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_BINDINGS\n");
+
+				if ( !su )
+				{
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_BINDINGS !Root\n");
+		 			p->RIOError.Error = NOT_SUPER_USER;
+		 			return -EPERM;
+				}
+				if (copyin((int)arg, (caddr_t)&p->RIOBindTab[0], 
+						(sizeof(ulong) * MAX_RTA_BINDINGS))==COPYFAIL ) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_PUT_BINDINGS copy failed\n");
+		 			p->RIOError.Error = COPYIN_FAILED;
+		 			return -EFAULT;
+				}
+				return 0;
+
+			case RIO_BIND_RTA :
+				{
+					int	EmptySlot = -1;
+					/*
+					** Bind this RTA to host, so that it will be booted by 
+					** host in 'boot owned RTAs' mode.
+					*/
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_BIND_RTA\n");
+
+					if ( !su ) {
+		 				rio_dprintk (RIO_DEBUG_CTRL, "RIO_BIND_RTA !Root\n");
+		 				p->RIOError.Error = NOT_SUPER_USER;
+		 				return -EPERM;
+					}
+					for (Entry = 0; Entry < MAX_RTA_BINDINGS; Entry++) {
+		 				if ((EmptySlot == -1) && (p->RIOBindTab[Entry] == 0L))
+							EmptySlot = Entry;
+		 				else if (p->RIOBindTab[Entry] == (int) arg) {
+							/*
+							** Already exists - delete
+							*/
+							p->RIOBindTab[Entry] = 0L;
+							rio_dprintk (RIO_DEBUG_CTRL, "Removing Rta %x from p->RIOBindTab\n",
+		 												(int) arg);
+							return 0;
+		 				}
+					}
+					/*
+					** Dosen't exist - add
+					*/
+					if (EmptySlot != -1) {
+		 				p->RIOBindTab[EmptySlot] = (int) arg;
+		 				rio_dprintk (RIO_DEBUG_CTRL, "Adding Rta %x to p->RIOBindTab\n",
+		  					(int) arg);
+					}
+					else {
+		 				rio_dprintk (RIO_DEBUG_CTRL, "p->RIOBindTab full! - Rta %x not added\n",
+		  					(int) arg);
+		 				return -ENOMEM;
+					}
+					return 0;
+				}
+
+			case RIO_RESUME :
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME\n");
+				port = (uint) arg;
+				if ((port < 0) || (port > 511)) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME: Bad port number %d\n", port);
+		 			p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+		 			return -EINVAL;
+				}
+				PortP = p->RIOPortp[port];
+				if (!PortP->Mapped) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME: Port %d not mapped\n", port);
+		 			p->RIOError.Error = PORT_NOT_MAPPED_INTO_SYSTEM;
+		 			return -EINVAL;
+				}
+				if (!(PortP->State & (RIO_LOPEN | RIO_MOPEN))) {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME: Port %d not open\n", port);
+		 			return -EINVAL;
+				}
+
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+				if (RIOPreemptiveCmd(p, (p->RIOPortp[port]), RESUME) == 
+										RIO_FAIL) {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME failed\n");
+					rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+					return -EBUSY;
+				}
+				else {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESUME: Port %d resumed\n", port);
+					PortP->State |= RIO_BUSY;
+				}
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				return retval;
+
+			case RIO_ASSIGN_RTA:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_ASSIGN_RTA\n");
+				if ( !su ) {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_ASSIGN_RTA !Root\n");
+					p->RIOError.Error = NOT_SUPER_USER;
+					return -EPERM;
+				}
+				if (copyin((int)arg, (caddr_t)&MapEnt, sizeof(MapEnt))
+									== COPYFAIL) {
+					rio_dprintk (RIO_DEBUG_CTRL, "Copy from user space failed\n");
+					p->RIOError.Error = COPYIN_FAILED;
+					return -EFAULT;
+				}
+				return RIOAssignRta(p, &MapEnt);
+
+			case RIO_CHANGE_NAME:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_CHANGE_NAME\n");
+				if ( !su ) {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_CHANGE_NAME !Root\n");
+					p->RIOError.Error = NOT_SUPER_USER;
+					return -EPERM;
+				}
+				if (copyin((int)arg, (caddr_t)&MapEnt, sizeof(MapEnt))
+						== COPYFAIL) {
+					rio_dprintk (RIO_DEBUG_CTRL, "Copy from user space failed\n");
+					p->RIOError.Error = COPYIN_FAILED;
+					return -EFAULT;
+				}
+				return RIOChangeName(p, &MapEnt);
+
+			case RIO_DELETE_RTA:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_DELETE_RTA\n");
+				if ( !su ) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_DELETE_RTA !Root\n");
+		 			p->RIOError.Error = NOT_SUPER_USER;
+		 			return -EPERM;
+				}
+				if (copyin((int)arg, (caddr_t)&MapEnt, sizeof(MapEnt))
+							== COPYFAIL ) {
+		 			rio_dprintk (RIO_DEBUG_CTRL, "Copy from data space failed\n");
+		 			p->RIOError.Error = COPYIN_FAILED;
+		 			return -EFAULT;
+				}
+				return RIODeleteRta(p, &MapEnt);
+
+			case RIO_QUICK_CHECK:
+				/*
+				** 09.12.1998 ARG - ESIL 0776 part fix
+				** A customer was using this to get the RTAs
+				** connect/disconnect status.
+				** RIOConCon() had been botched use RIOHalted
+				** to keep track of RTA connections and
+				** disconnections. That has been changed and
+				** RIORtaDisCons in the rio_info struct now
+				** does the job. So we need to return the value
+				** of RIORtaCons instead of RIOHalted.
+				**
+				if (copyout((caddr_t)&p->RIOHalted,(int)arg,
+							sizeof(uint))==COPYFAIL) {
+				**
+				*/
+
+				if (copyout((caddr_t)&p->RIORtaDisCons,(int)arg,
+							sizeof(uint))==COPYFAIL) {
+					p->RIOError.Error = COPYOUT_FAILED;
+					return -EFAULT;
+				}
+				return 0;
+
+			case RIO_LAST_ERROR:
+				if (copyout((caddr_t)&p->RIOError, (int)arg, 
+						sizeof(struct Error)) ==COPYFAIL )
+					return -EFAULT;
+				return 0;
+
+			case RIO_GET_LOG:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_LOG\n");
+#ifdef LOGGING
+				RIOGetLog(arg);
+				return 0;
+#else
+				return -EINVAL;
+#endif
+
+			case RIO_GET_MODTYPE:
+				if ( copyin( (int)arg, (caddr_t)&port, 
+									sizeof(uint)) == COPYFAIL )
+				{
+		 			p->RIOError.Error = COPYIN_FAILED;
+		 			return -EFAULT;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Get module type for port %d\n", port);
+				if ( port < 0 || port > 511 )
+				{
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_MODTYPE: Bad port number %d\n", port);
+		 			p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+		 			return -EINVAL;
+				}
+				PortP = (p->RIOPortp[port]);
+				if (!PortP->Mapped)
+				{
+		 			rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_MODTYPE: Port %d not mapped\n", port);
+		 			p->RIOError.Error = PORT_NOT_MAPPED_INTO_SYSTEM;
+		 			return -EINVAL;
+				}
+				/*
+				** Return module type of port
+				*/
+				port = PortP->HostP->UnixRups[PortP->RupNum].ModTypes;
+				if (copyout((caddr_t)&port, (int)arg, 
+							sizeof(uint)) == COPYFAIL) {
+		 			p->RIOError.Error = COPYOUT_FAILED;
+		 			return -EFAULT;
+				}
+				return(0);
+			/*
+			** 02.03.1999 ARG - ESIL 0820 fix
+			** We are no longer using "Boot Mode", so these ioctls
+			** are not required :
+			**
+	 		case RIO_GET_BOOT_MODE :
+				rio_dprint(RIO_DEBUG_CTRL, ("Get boot mode - %x\n", p->RIOBootMode));
+				**
+				** Return boot state of system - BOOT_ALL, BOOT_OWN or BOOT_NONE
+				**
+				if (copyout((caddr_t)&p->RIOBootMode, (int)arg, 
+						sizeof(p->RIOBootMode)) == COPYFAIL) {
+		 			p->RIOError.Error = COPYOUT_FAILED;
+		 			return -EFAULT;
+				}
+				return(0);
+			
+ 			case RIO_SET_BOOT_MODE :
+				p->RIOBootMode = (uint) arg;
+				rio_dprint(RIO_DEBUG_CTRL, ("Set boot mode to 0x%x\n", p->RIOBootMode));
+				return(0);
+			**
+			** End ESIL 0820 fix
+			*/
+
+	 		case RIO_BLOCK_OPENS:
+				rio_dprintk (RIO_DEBUG_CTRL, "Opens block until booted\n");
+				for ( Entry=0; Entry < RIO_PORTS; Entry++ ) {
+		 			rio_spin_lock_irqsave(&PortP->portSem, flags);
+		 			p->RIOPortp[Entry]->WaitUntilBooted = 1;
+		 			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				}
+				return 0;
+			
+	 		case RIO_SETUP_PORTS:
+				rio_dprintk (RIO_DEBUG_CTRL, "Setup ports\n");
+				if (copyin((int)arg, (caddr_t)&PortSetup, sizeof(PortSetup)) 
+						== COPYFAIL ) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 rio_dprintk (RIO_DEBUG_CTRL, "EFAULT");
+					 return -EFAULT;
+				}
+				if ( PortSetup.From > PortSetup.To || 
+								PortSetup.To >= RIO_PORTS ) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 rio_dprintk (RIO_DEBUG_CTRL, "ENXIO");
+					 return -ENXIO;
+				}
+				if ( PortSetup.XpCps > p->RIOConf.MaxXpCps ||
+					 PortSetup.XpCps < p->RIOConf.MinXpCps ) {
+					 p->RIOError.Error = XPRINT_CPS_OUT_OF_RANGE;
+					 rio_dprintk (RIO_DEBUG_CTRL, "EINVAL");
+					 return -EINVAL;
+				}
+				if ( !p->RIOPortp ) {
+					 cprintf("No p->RIOPortp array!\n");
+					 rio_dprintk (RIO_DEBUG_CTRL, "No p->RIOPortp array!\n");
+					 return -EIO;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "entering loop (%d %d)!\n", PortSetup.From, PortSetup.To);
+				for (loop=PortSetup.From; loop<=PortSetup.To; loop++) {
+				rio_dprintk (RIO_DEBUG_CTRL, "in loop (%d)!\n", loop);
+#if 0
+					PortP = p->RIOPortp[loop];
+					if ( !PortP->TtyP )
+						PortP->TtyP = &p->channel[loop];
+
+		 				rio_spin_lock_irqsave(&PortP->portSem, flags);
+						if ( PortSetup.IxAny )
+							PortP->Config |= RIO_IXANY;
+						else
+							PortP->Config &= ~RIO_IXANY;
+						if ( PortSetup.IxOn )
+							PortP->Config |= RIO_IXON;
+						else
+							PortP->Config &= ~RIO_IXON;
+					 
+					 /*
+					 ** If the port needs to wait for all a processes output
+					 ** to drain before closing then this flag will be set.
+					 */
+					 	if (PortSetup.Drain) {
+							PortP->Config |= RIO_WAITDRAIN;
+					 	} else {
+							PortP->Config &= ~RIO_WAITDRAIN;
+					 	}
+					 /*
+					 ** Store settings if locking or unlocking port or if the
+					 ** port is not locked, when setting the store option.
+					 */
+					 if (PortP->Mapped &&
+						 ((PortSetup.Lock && !PortP->Lock) ||
+							(!PortP->Lock &&
+							(PortSetup.Store && !PortP->Store)))) {
+						PortP->StoredTty.iflag = PortP->TtyP->tm.c_iflag;
+						PortP->StoredTty.oflag = PortP->TtyP->tm.c_oflag;
+						PortP->StoredTty.cflag = PortP->TtyP->tm.c_cflag;
+						PortP->StoredTty.lflag = PortP->TtyP->tm.c_lflag;
+						PortP->StoredTty.line = PortP->TtyP->tm.c_line;
+						bcopy(PortP->TtyP->tm.c_cc, PortP->StoredTty.cc,
+					 		NCC + 5);
+					 }
+					 PortP->Lock = PortSetup.Lock;
+					 PortP->Store = PortSetup.Store;
+					 PortP->Xprint.XpCps = PortSetup.XpCps;
+					 bcopy(PortSetup.XpOn,PortP->Xprint.XpOn,MAX_XP_CTRL_LEN);
+					 bcopy(PortSetup.XpOff,PortP->Xprint.XpOff,MAX_XP_CTRL_LEN);
+					 PortP->Xprint.XpOn[MAX_XP_CTRL_LEN-1] = '\0';
+					 PortP->Xprint.XpOff[MAX_XP_CTRL_LEN-1] = '\0';
+					 PortP->Xprint.XpLen = RIOStrlen(PortP->Xprint.XpOn)+
+								RIOStrlen(PortP->Xprint.XpOff);
+					 rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+#endif
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "after loop (%d)!\n", loop);
+				rio_dprintk (RIO_DEBUG_CTRL, "Retval:%x\n", retval);
+				return retval;
+
+			case RIO_GET_PORT_SETUP :
+				rio_dprintk (RIO_DEBUG_CTRL, "Get port setup\n");
+				if (copyin((int)arg, (caddr_t)&PortSetup, sizeof(PortSetup)) 
+							== COPYFAIL ) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if ( PortSetup.From >= RIO_PORTS ) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+
+				port = PortSetup.To = PortSetup.From;
+				PortSetup.IxAny = (p->RIOPortp[port]->Config & RIO_IXANY) ? 
+													1 : 0;
+				PortSetup.IxOn = (p->RIOPortp[port]->Config & RIO_IXON) ? 
+													1 : 0;
+				PortSetup.Drain = (p->RIOPortp[port]->Config & RIO_WAITDRAIN) ?
+												 	1 : 0;
+				PortSetup.Store = p->RIOPortp[port]->Store;
+				PortSetup.Lock = p->RIOPortp[port]->Lock;
+				PortSetup.XpCps = p->RIOPortp[port]->Xprint.XpCps;
+				bcopy(p->RIOPortp[port]->Xprint.XpOn, PortSetup.XpOn,
+													MAX_XP_CTRL_LEN);
+				bcopy(p->RIOPortp[port]->Xprint.XpOff, PortSetup.XpOff,
+													MAX_XP_CTRL_LEN);
+				PortSetup.XpOn[MAX_XP_CTRL_LEN-1] = '\0';
+				PortSetup.XpOff[MAX_XP_CTRL_LEN-1] = '\0';
+
+				if ( copyout((caddr_t)&PortSetup,(int)arg,sizeof(PortSetup))
+														==COPYFAIL ) {
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return retval;
+
+			case RIO_GET_PORT_PARAMS :
+				rio_dprintk (RIO_DEBUG_CTRL, "Get port params\n");
+				if (copyin( (int)arg, (caddr_t)&PortParams,
+					sizeof(struct PortParams)) == COPYFAIL) {
+					p->RIOError.Error = COPYIN_FAILED;
+					return -EFAULT;
+				}
+				if (PortParams.Port >= RIO_PORTS) {
+					p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					return -ENXIO;
+				}
+				PortP = (p->RIOPortp[PortParams.Port]);
+				PortParams.Config = PortP->Config;
+				PortParams.State = PortP->State;
+				rio_dprintk (RIO_DEBUG_CTRL, "Port %d\n", PortParams.Port);
+
+				if (copyout((caddr_t)&PortParams, (int)arg, 
+						sizeof(struct PortParams)) == COPYFAIL ) {
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return retval;
+
+			case RIO_GET_PORT_TTY :
+				rio_dprintk (RIO_DEBUG_CTRL, "Get port tty\n");
+				if (copyin((int)arg, (caddr_t)&PortTty, sizeof(struct PortTty)) 
+						== COPYFAIL) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if ( PortTty.port >= RIO_PORTS ) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+
+				rio_dprintk (RIO_DEBUG_CTRL, "Port %d\n", PortTty.port);
+				PortP = (p->RIOPortp[PortTty.port]);
+#if 0
+				PortTty.Tty.tm.c_iflag = PortP->TtyP->tm.c_iflag;
+				PortTty.Tty.tm.c_oflag = PortP->TtyP->tm.c_oflag;
+				PortTty.Tty.tm.c_cflag = PortP->TtyP->tm.c_cflag;
+				PortTty.Tty.tm.c_lflag = PortP->TtyP->tm.c_lflag;
+#endif
+				if (copyout((caddr_t)&PortTty, (int)arg, 
+							sizeof(struct PortTty)) == COPYFAIL) {
+					p->RIOError.Error = COPYOUT_FAILED;
+					return -EFAULT;
+				}
+				return retval;
+
+			case RIO_SET_PORT_TTY :
+				if (copyin((int)arg, (caddr_t)&PortTty, 
+						sizeof(struct PortTty)) == COPYFAIL) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Set port %d tty\n", PortTty.port);
+				if (PortTty.port >= (ushort) RIO_PORTS) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+				PortP = (p->RIOPortp[PortTty.port]);
+#if 0
+		 		rio_spin_lock_irqsave(&PortP->portSem, flags);
+				PortP->TtyP->tm.c_iflag = PortTty.Tty.tm.c_iflag;
+				PortP->TtyP->tm.c_oflag = PortTty.Tty.tm.c_oflag;
+				PortP->TtyP->tm.c_cflag = PortTty.Tty.tm.c_cflag;
+				PortP->TtyP->tm.c_lflag = PortTty.Tty.tm.c_lflag;
+				rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+#endif
+
+				RIOParam(PortP, CONFIG, PortP->State & RIO_MODEM, OK_TO_SLEEP);
+				return retval;
+
+			case RIO_SET_PORT_PARAMS :
+				rio_dprintk (RIO_DEBUG_CTRL, "Set port params\n");
+				if ( copyin((int)arg, (caddr_t)&PortParams, sizeof(PortParams))
+					== COPYFAIL ) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if (PortParams.Port >= (ushort) RIO_PORTS) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+				PortP = (p->RIOPortp[PortParams.Port]);
+		 		rio_spin_lock_irqsave(&PortP->portSem, flags);
+				PortP->Config = PortParams.Config;
+				rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+				return retval;
+
+			case RIO_GET_PORT_STATS :
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_GET_PORT_STATS\n");
+				if ( copyin((int)arg, (caddr_t)&portStats, 
+						sizeof(struct portStats)) == COPYFAIL ) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if ( portStats.port >= RIO_PORTS ) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+				PortP = (p->RIOPortp[portStats.port]);
+				portStats.gather = PortP->statsGather;
+				portStats.txchars = PortP->txchars;
+				portStats.rxchars = PortP->rxchars;
+				portStats.opens = PortP->opens;
+				portStats.closes = PortP->closes;
+				portStats.ioctls = PortP->ioctls;
+				if ( copyout((caddr_t)&portStats, (int)arg, 
+							sizeof(struct portStats)) == COPYFAIL ) {
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return retval;
+
+			case RIO_RESET_PORT_STATS :
+				port = (uint) arg;
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_RESET_PORT_STATS\n");
+				if ( port >= RIO_PORTS ) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+				PortP = (p->RIOPortp[port]);
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+				PortP->txchars	= 0;
+				PortP->rxchars	= 0;
+				PortP->opens	= 0;
+				PortP->closes	= 0;
+				PortP->ioctls	= 0;
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				return retval;
+
+			case RIO_GATHER_PORT_STATS :
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_GATHER_PORT_STATS\n");
+				if ( copyin( (int)arg, (caddr_t)&portStats, 
+						sizeof(struct portStats)) == COPYFAIL ) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if ( portStats.port >= RIO_PORTS ) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+				PortP = (p->RIOPortp[portStats.port]);
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+				PortP->statsGather = portStats.gather;
+				rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+				return retval;
+
+#ifdef DEBUG_SUPPORTED
+			case RIO_READ_LEVELS:
+				{
+					 int num;
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_LEVELS\n");
+					 for ( num=0; RIODbInf[num].Flag; num++ ) ;
+					 rio_dprintk (RIO_DEBUG_CTRL, "%d levels to copy\n",num);
+					 if (copyout((caddr_t)RIODbInf,(int)arg,
+						sizeof(struct DbInf)*(num+1))==COPYFAIL) {
+						rio_dprintk (RIO_DEBUG_CTRL, "ReadLevels Copy failed\n");
+						p->RIOError.Error = COPYOUT_FAILED;
+						return -EFAULT;
+					 }
+					 rio_dprintk (RIO_DEBUG_CTRL, "%d levels to copied\n",num);
+					 return retval;
+				}
+#endif
+
+			 case RIO_READ_CONFIG:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_CONFIG\n");
+				if (copyout((caddr_t)&p->RIOConf, (int)arg, 
+							sizeof(struct Conf)) ==COPYFAIL ) {
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return retval;
+
+			case RIO_SET_CONFIG:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET_CONFIG\n");
+				if ( !su ) {
+					 p->RIOError.Error = NOT_SUPER_USER;
+					 return -EPERM;
+				}
+				if ( copyin((int)arg, (caddr_t)&p->RIOConf, sizeof(struct Conf) )
+						==COPYFAIL ) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				/*
+				** move a few value around
+				*/
+				for (Host=0; Host < p->RIONumHosts; Host++)
+					 if ( (p->RIOHosts[Host].Flags & RUN_STATE) == RC_RUNNING )
+					 	WWORD(p->RIOHosts[Host].ParmMapP->timer , 
+								p->RIOConf.Timer);
+				return retval;
+
+			case RIO_START_POLLER:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_START_POLLER\n");
+				return -EINVAL;
+
+			case RIO_STOP_POLLER:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_STOP_POLLER\n");
+				if ( !su ) {
+					 p->RIOError.Error = NOT_SUPER_USER;
+					 return -EPERM;
+				}
+				p->RIOPolling = NOT_POLLING;
+				return retval;
+
+			case RIO_SETDEBUG:
+			case RIO_GETDEBUG:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_SETDEBUG/RIO_GETDEBUG\n");
+				if ( copyin( (int)arg, (caddr_t)&DebugCtrl, sizeof(DebugCtrl) )
+							==COPYFAIL ) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if ( DebugCtrl.SysPort == NO_PORT ) {
+					if ( cmd == RIO_SETDEBUG ) {
+						if ( !su ) {
+							p->RIOError.Error = NOT_SUPER_USER;
+							return -EPERM;
+						}
+						p->rio_debug = DebugCtrl.Debug;
+						p->RIODebugWait = DebugCtrl.Wait;
+						rio_dprintk (RIO_DEBUG_CTRL, "Set global debug to 0x%x set wait to 0x%x\n",
+							p->rio_debug,p->RIODebugWait);
+					}
+				 	else {
+						rio_dprintk (RIO_DEBUG_CTRL, "Get global debug 0x%x wait 0x%x\n",
+										p->rio_debug,p->RIODebugWait);
+						DebugCtrl.Debug = p->rio_debug;
+						DebugCtrl.Wait  = p->RIODebugWait;
+						if ( copyout((caddr_t)&DebugCtrl,(int)arg,
+								sizeof(DebugCtrl)) == COPYFAIL ) {
+							rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET/GET DEBUG: bad port number %d\n",
+									DebugCtrl.SysPort);
+						 	p->RIOError.Error = COPYOUT_FAILED;
+						 	return -EFAULT;
+						}
+					}
+				}
+				else if ( DebugCtrl.SysPort >= RIO_PORTS && 
+							DebugCtrl.SysPort != NO_PORT ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET/GET DEBUG: bad port number %d\n",
+									DebugCtrl.SysPort);
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+				else if ( cmd == RIO_SETDEBUG ) {
+					if ( !su ) {
+						p->RIOError.Error = NOT_SUPER_USER;
+						return -EPERM;
+					}
+					rio_spin_lock_irqsave(&PortP->portSem, flags);
+					p->RIOPortp[DebugCtrl.SysPort]->Debug = DebugCtrl.Debug;
+					rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_SETDEBUG 0x%x\n",
+								p->RIOPortp[DebugCtrl.SysPort]->Debug);
+				}
+				else {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_GETDEBUG 0x%x\n",
+									 p->RIOPortp[DebugCtrl.SysPort]->Debug);
+					DebugCtrl.Debug = p->RIOPortp[DebugCtrl.SysPort]->Debug;
+					if ( copyout((caddr_t)&DebugCtrl,(int)arg,
+								sizeof(DebugCtrl))==COPYFAIL ) {
+						rio_dprintk (RIO_DEBUG_CTRL, "RIO_GETDEBUG: Bad copy to user space\n");
+						p->RIOError.Error = COPYOUT_FAILED;
+						return -EFAULT;
+					}
+				}
+				return retval;
+
+			case RIO_VERSID:
+				/*
+				** Enquire about the release and version.
+				** We return MAX_VERSION_LEN bytes, being a
+				** textual null terminated string.
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_VERSID\n");
+				if ( copyout(	(caddr_t)RIOVersid(),
+						(int)arg,
+						sizeof(struct rioVersion) ) == COPYFAIL )
+				{
+					 rio_dprintk (RIO_DEBUG_CTRL,  "RIO_VERSID: Bad copy to user space (host=%d)\n", Host);
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return retval;
+
+			/*
+			** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+			** !! commented out previous 'RIO_VERSID' functionality !!
+			** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+			**
+			case RIO_VERSID:
+				**
+				** Enquire about the release and version.
+				** We return MAX_VERSION_LEN bytes, being a textual null
+				** terminated string.
+				**
+				rio_dprint(RIO_DEBUG_CTRL, ("RIO_VERSID\n"));
+				if (copyout((caddr_t)RIOVersid(), 
+						(int)arg, MAX_VERSION_LEN ) == COPYFAIL ) {
+					 rio_dprint(RIO_DEBUG_CTRL, ("RIO_VERSID: Bad copy to user space\n",Host));
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return retval;
+			**
+			** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+			*/
+
+			case RIO_NUM_HOSTS:
+				/*
+				** Enquire as to the number of hosts located
+				** at init time.
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_NUM_HOSTS\n");
+				if (copyout((caddr_t)&p->RIONumHosts, (int)arg, 
+							sizeof(p->RIONumHosts) )==COPYFAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_NUM_HOSTS: Bad copy to user space\n");
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return retval;
+
+			case RIO_HOST_FOAD:
+				/*
+				** Kill host. This may not be in the final version...
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_FOAD %d\n", (int)arg);
+				if ( !su ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_FOAD: Not super user\n");
+					 p->RIOError.Error = NOT_SUPER_USER;
+					 return -EPERM;
+				}
+				p->RIOHalted = 1;
+				p->RIOSystemUp = 0;
+
+				for ( Host=0; Host<p->RIONumHosts; Host++ ) {
+					 (void)RIOBoardTest( p->RIOHosts[Host].PaddrP, 
+						p->RIOHosts[Host].Caddr, p->RIOHosts[Host].Type, 
+								p->RIOHosts[Host].Slot );
+					 bzero( (caddr_t)&p->RIOHosts[Host].Flags, 
+							((int)&p->RIOHosts[Host].____end_marker____) -
+								 ((int)&p->RIOHosts[Host].Flags) );
+					 p->RIOHosts[Host].Flags  = RC_WAITING;
+#if 0
+					 RIOSetupDataStructs(p);
+#endif
+				}
+				RIOFoadWakeup(p);
+				p->RIONumBootPkts = 0;
+				p->RIOBooting = 0;
+
+#ifdef RINGBUFFER_SUPPORT
+				for( loop=0; loop<RIO_PORTS; loop++ )
+					if ( p->RIOPortp[loop]->TxRingBuffer )
+						sysfree((void *)p->RIOPortp[loop]->TxRingBuffer, 
+							RIOBufferSize );
+#endif
+#if 0
+				bzero((caddr_t)&p->RIOPortp[0],RIO_PORTS*sizeof(struct Port));
+#else
+				printk ("HEEEEELP!\n");
+#endif
+
+				for( loop=0; loop<RIO_PORTS; loop++ ) {
+#if 0
+					p->RIOPortp[loop]->TtyP = &p->channel[loop];
+#endif
+					
+					spin_lock_init(&p->RIOPortp[loop]->portSem);
+					p->RIOPortp[loop]->InUse = NOT_INUSE;
+				}
+
+				p->RIOSystemUp = 0;
+				return retval;
+
+			case RIO_DOWNLOAD:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_DOWNLOAD\n");
+				if ( !su ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_DOWNLOAD: Not super user\n");
+					 p->RIOError.Error = NOT_SUPER_USER;
+					 return -EPERM;
+				}
+				if ( copyin((int)arg, (caddr_t)&DownLoad, 
+							sizeof(DownLoad) )==COPYFAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_DOWNLOAD: Copy in from user space failed\n");
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Copied in download code for product code 0x%x\n",
+				    DownLoad.ProductCode);
+
+				/*
+				** It is important that the product code is an unsigned object!
+				*/
+				if ( DownLoad.ProductCode > MAX_PRODUCT ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_DOWNLOAD: Bad product code %d passed\n",
+							DownLoad.ProductCode);
+					 p->RIOError.Error = NO_SUCH_PRODUCT;
+					 return -ENXIO;
+				}
+				/*
+				** do something!
+				*/
+				retval = (*(RIOBootTable[DownLoad.ProductCode]))(p, &DownLoad);
+										/* <-- Panic */
+				p->RIOHalted = 0;
+				/*
+				** and go back, content with a job well completed.
+				*/
+				return retval;
+
+			case RIO_PARMS:
+				{
+					uint host;
+
+					if (copyin((int)arg, (caddr_t)&host, 
+							sizeof(host) ) == COPYFAIL ) {
+						rio_dprintk (RIO_DEBUG_CTRL, 
+							"RIO_HOST_REQ: Copy in from user space failed\n");
+						p->RIOError.Error = COPYIN_FAILED;
+						return -EFAULT;
+					}
+					/*
+					** Fetch the parmmap
+					*/
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_PARMS\n");
+					if ( copyout( (caddr_t)p->RIOHosts[host].ParmMapP, 
+								(int)arg, sizeof(PARM_MAP) )==COPYFAIL ) {
+						p->RIOError.Error = COPYOUT_FAILED;
+						rio_dprintk (RIO_DEBUG_CTRL, "RIO_PARMS: Copy out to user space failed\n");
+						return -EFAULT;
+					}
+				}
+				return retval;
+
+			case RIO_HOST_REQ:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_REQ\n");
+				if (copyin((int)arg, (caddr_t)&HostReq, 
+							sizeof(HostReq) )==COPYFAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_REQ: Copy in from user space failed\n");
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if ( HostReq.HostNum >= p->RIONumHosts ) {
+					 p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_REQ: Illegal host number %d\n",
+							HostReq.HostNum);
+					 return -ENXIO;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Request for host %d\n", HostReq.HostNum);
+
+				if (copyout((caddr_t)&p->RIOHosts[HostReq.HostNum], 
+					(int)HostReq.HostP,sizeof(struct Host) ) == COPYFAIL) {
+					p->RIOError.Error = COPYOUT_FAILED;
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_REQ: Bad copy to user space\n");
+					return -EFAULT;
+				}
+				return retval;
+
+			 case RIO_HOST_DPRAM:
+				rio_dprintk (RIO_DEBUG_CTRL, "Request for DPRAM\n");
+				if ( copyin( (int)arg, (caddr_t)&HostDpRam, 
+								sizeof(HostDpRam) )==COPYFAIL ) {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_DPRAM: Copy in from user space failed\n");
+					p->RIOError.Error = COPYIN_FAILED;
+					return -EFAULT;
+				}
+				if ( HostDpRam.HostNum >= p->RIONumHosts ) {
+					p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_DPRAM: Illegal host number %d\n",
+										HostDpRam.HostNum);
+					return -ENXIO;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Request for host %d\n", HostDpRam.HostNum);
+
+				if (p->RIOHosts[HostDpRam.HostNum].Type == RIO_PCI) {
+					 int off;
+					 /* It's hardware like this that really gets on my tits. */
+					 static unsigned char copy[sizeof(struct DpRam)];
+					for ( off=0; off<sizeof(struct DpRam); off++ )
+						copy[off] = p->RIOHosts[HostDpRam.HostNum].Caddr[off];
+					if ( copyout( (caddr_t)copy, (int)HostDpRam.DpRamP, 
+							sizeof(struct DpRam) ) == COPYFAIL ) {
+						p->RIOError.Error = COPYOUT_FAILED;
+						rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_DPRAM: Bad copy to user space\n");
+						return -EFAULT;
+					}
+				}
+				else if (copyout((caddr_t)p->RIOHosts[HostDpRam.HostNum].Caddr,
+					(int)HostDpRam.DpRamP, 
+						sizeof(struct DpRam) ) == COPYFAIL ) {
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_DPRAM: Bad copy to user space\n");
+					 return -EFAULT;
+				}
+				return retval;
+
+			 case RIO_SET_BUSY:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET_BUSY\n");
+				if ( (int)arg < 0 || (int)arg > 511 ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_SET_BUSY: Bad port number %d\n",(int)arg);
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+				p->RIOPortp[(int)arg]->State |= RIO_BUSY;
+				rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+				return retval;
+
+			 case RIO_HOST_PORT:
+				/*
+				** The daemon want port information
+				** (probably for debug reasons)
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_PORT\n");
+				if ( copyin((int)arg, (caddr_t)&PortReq, 
+					sizeof(PortReq) )==COPYFAIL ) {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_PORT: Copy in from user space failed\n");
+					p->RIOError.Error = COPYIN_FAILED;
+					return -EFAULT;
+				}
+
+				if (PortReq.SysPort >= RIO_PORTS) { /* SysPort is unsigned */
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_PORT: Illegal port number %d\n",
+											PortReq.SysPort);
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Request for port %d\n", PortReq.SysPort);
+				if (copyout((caddr_t)p->RIOPortp[PortReq.SysPort], 
+							 (int)PortReq.PortP,
+								sizeof(struct Port) ) == COPYFAIL) {
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_PORT: Bad copy to user space\n");
+					 return -EFAULT;
+				}
+				return retval;
+
+			case RIO_HOST_RUP:
+				/*
+				** The daemon want rup information
+				** (probably for debug reasons)
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP\n");
+				if (copyin((int)arg, (caddr_t)&RupReq, 
+						sizeof(RupReq) )==COPYFAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Copy in from user space failed\n");
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if (RupReq.HostNum >= p->RIONumHosts) { /* host is unsigned */
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Illegal host number %d\n",
+								RupReq.HostNum);
+					 p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+				if ( RupReq.RupNum >= MAX_RUP+LINKS_PER_UNIT ) { /* eek! */
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Illegal rup number %d\n",
+							RupReq.RupNum);
+					 p->RIOError.Error = RUP_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+				HostP = &p->RIOHosts[RupReq.HostNum];
+
+				if ((HostP->Flags & RUN_STATE) != RC_RUNNING) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Host %d not running\n",
+							RupReq.HostNum);
+					 p->RIOError.Error = HOST_NOT_RUNNING;
+					 return -EIO;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Request for rup %d from host %d\n",
+						RupReq.RupNum,RupReq.HostNum);
+
+				if (copyout((caddr_t)HostP->UnixRups[RupReq.RupNum].RupP,
+					(int)RupReq.RupP,sizeof(struct RUP) ) == COPYFAIL) {
+					p->RIOError.Error = COPYOUT_FAILED;
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_RUP: Bad copy to user space\n");
+					return -EFAULT;
+				}
+				return retval;
+
+			case RIO_HOST_LPB:
+				/*
+				** The daemon want lpb information
+				** (probably for debug reasons)
+				*/
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB\n");
+				if (copyin((int)arg, (caddr_t)&LpbReq, 
+					sizeof(LpbReq) )==COPYFAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Bad copy from user space\n");
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if (LpbReq.Host >= p->RIONumHosts) { /* host is unsigned */
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Illegal host number %d\n",
+							LpbReq.Host);
+					p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+					return -ENXIO;
+				}
+				if ( LpbReq.Link >= LINKS_PER_UNIT ) { /* eek! */
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Illegal link number %d\n",
+							LpbReq.Link);
+					 p->RIOError.Error = LINK_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+				HostP = &p->RIOHosts[LpbReq.Host];
+
+				if ( (HostP->Flags & RUN_STATE) != RC_RUNNING ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Host %d not running\n",
+						LpbReq.Host );
+					 p->RIOError.Error = HOST_NOT_RUNNING;
+					 return -EIO;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Request for lpb %d from host %d\n",
+					LpbReq.Link, LpbReq.Host);
+
+				if (copyout((caddr_t)&HostP->LinkStrP[LpbReq.Link],
+					(int)LpbReq.LpbP,sizeof(struct LPB) ) == COPYFAIL) {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_HOST_LPB: Bad copy to user space\n");
+					p->RIOError.Error = COPYOUT_FAILED;
+					return -EFAULT;
+				}
+				return retval;
+
+				/*
+				** Here 3 IOCTL's that allow us to change the way in which
+				** rio logs errors. send them just to syslog or send them
+				** to both syslog and console or send them to just the console.
+				**
+				** See RioStrBuf() in util.c for the other half.
+				*/
+			case RIO_SYSLOG_ONLY:
+				p->RIOPrintLogState = PRINT_TO_LOG;	/* Just syslog */
+				return 0;
+
+			case RIO_SYSLOG_CONS:
+				p->RIOPrintLogState = PRINT_TO_LOG_CONS;/* syslog and console */
+				return 0;
+
+			case RIO_CONS_ONLY:
+				p->RIOPrintLogState = PRINT_TO_CONS;	/* Just console */
+				return 0;
+
+			case RIO_SIGNALS_ON:
+				if ( p->RIOSignalProcess ) {
+					 p->RIOError.Error = SIGNALS_ALREADY_SET;
+					 return -EBUSY;
+				}
+				p->RIOSignalProcess = getpid();
+				p->RIOPrintDisabled = DONT_PRINT;
+				return retval;
+
+			case RIO_SIGNALS_OFF:
+				if ( p->RIOSignalProcess != getpid() ) {
+					 p->RIOError.Error = NOT_RECEIVING_PROCESS;
+					 return -EPERM;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "Clear signal process to zero\n");
+				p->RIOSignalProcess = 0;
+				return retval;
+
+			case RIO_SET_BYTE_MODE:
+				for ( Host=0; Host<p->RIONumHosts; Host++ )
+					 if ( p->RIOHosts[Host].Type == RIO_AT )
+						 p->RIOHosts[Host].Mode &= ~WORD_OPERATION;
+				return retval;
+
+			case RIO_SET_WORD_MODE:
+				for ( Host=0; Host<p->RIONumHosts; Host++ )
+					 if ( p->RIOHosts[Host].Type == RIO_AT )
+						 p->RIOHosts[Host].Mode |= WORD_OPERATION;
+				return retval;
+
+			case RIO_SET_FAST_BUS:
+				for ( Host=0; Host<p->RIONumHosts; Host++ )
+					 if ( p->RIOHosts[Host].Type == RIO_AT )
+						 p->RIOHosts[Host].Mode |= FAST_AT_BUS;
+				return retval;
+
+			case RIO_SET_SLOW_BUS:
+				for ( Host=0; Host<p->RIONumHosts; Host++ )
+					 if ( p->RIOHosts[Host].Type == RIO_AT )
+						 p->RIOHosts[Host].Mode &= ~FAST_AT_BUS;
+				return retval;
+
+			case RIO_MAP_B50_TO_50:
+			case RIO_MAP_B50_TO_57600:
+			case RIO_MAP_B110_TO_110:
+			case RIO_MAP_B110_TO_115200:
+				rio_dprintk (RIO_DEBUG_CTRL, "Baud rate mapping\n");
+				port = (uint) arg;
+				if ( port < 0 || port > 511 ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "Baud rate mapping: Bad port number %d\n", port);
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+				switch( cmd )
+				{
+					case RIO_MAP_B50_TO_50 :
+						p->RIOPortp[port]->Config |= RIO_MAP_50_TO_50;
+						break;
+					case RIO_MAP_B50_TO_57600 :
+						p->RIOPortp[port]->Config &= ~RIO_MAP_50_TO_50;
+						break;
+					case RIO_MAP_B110_TO_110 :
+						p->RIOPortp[port]->Config |= RIO_MAP_110_TO_110;
+						break;
+					case RIO_MAP_B110_TO_115200 :
+						p->RIOPortp[port]->Config &= ~RIO_MAP_110_TO_110;
+						break;
+				}
+				rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+				return retval;
+
+			case RIO_STREAM_INFO:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_STREAM_INFO\n");
+				return -EINVAL;
+
+			case RIO_SEND_PACKET:
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_SEND_PACKET\n");
+				if ( copyin( (int)arg, (caddr_t)&SendPack,
+									sizeof(SendPack) )==COPYFAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_SEND_PACKET: Bad copy from user space\n");
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				if ( SendPack.PortNum >= 128 ) {
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -ENXIO;
+				}
+
+				PortP = p->RIOPortp[SendPack.PortNum];
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+				if ( !can_add_transmit(&PacketP,PortP) ) {
+					 p->RIOError.Error = UNIT_IS_IN_USE;
+					 rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+					 return -ENOSPC;
+				}
+
+				for ( loop=0; loop<(ushort)(SendPack.Len & 127); loop++ )
+					 WBYTE(PacketP->data[loop], SendPack.Data[loop] );
+
+				WBYTE(PacketP->len, SendPack.Len);
+
+				add_transmit( PortP );
+				/*
+				** Count characters transmitted for port statistics reporting
+				*/
+				if (PortP->statsGather)
+					 PortP->txchars += (SendPack.Len & 127);
+				rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+				return retval;
+
+			case RIO_NO_MESG:
+				if ( su )
+					 p->RIONoMessage = 1;
+				return su ? 0 : -EPERM;
+
+			case RIO_MESG:
+				if ( su )
+					p->RIONoMessage = 0;
+				return su ? 0 : -EPERM;
+
+			case RIO_WHAT_MESG:
+				if ( copyout( (caddr_t)&p->RIONoMessage, (int)arg, 
+					sizeof(p->RIONoMessage) )==COPYFAIL ) {
+					rio_dprintk (RIO_DEBUG_CTRL, "RIO_WHAT_MESG: Bad copy to user space\n");
+					p->RIOError.Error = COPYOUT_FAILED;
+					return -EFAULT;
+				}
+				return 0;
+
+			case RIO_MEM_DUMP :
+				if (copyin((int)arg, (caddr_t)&SubCmd, 
+						sizeof(struct SubCmdStruct)) == COPYFAIL) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_MEM_DUMP host %d rup %d addr %x\n", 
+						SubCmd.Host, SubCmd.Rup, SubCmd.Addr);
+
+				if (SubCmd.Rup >= MAX_RUP+LINKS_PER_UNIT ) {
+					 p->RIOError.Error = RUP_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+
+				if (SubCmd.Host >= p->RIONumHosts ) {
+					 p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+
+				port = p->RIOHosts[SubCmd.Host].
+								UnixRups[SubCmd.Rup].BaseSysPort;
+
+				PortP = p->RIOPortp[port];
+
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+				if ( RIOPreemptiveCmd(p,  PortP, MEMDUMP ) == RIO_FAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_MEM_DUMP failed\n");
+					 rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+					 return -EBUSY;
+				}
+				else
+					 PortP->State |= RIO_BUSY;
+
+				rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+				if ( copyout( (caddr_t)p->RIOMemDump, (int)arg, 
+							MEMDUMP_SIZE) == COPYFAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_MEM_DUMP copy failed\n");
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return 0;
+
+			case RIO_TICK:
+				if ((int)arg < 0 || (int)arg >= p->RIONumHosts)
+					 return -EINVAL;
+				rio_dprintk (RIO_DEBUG_CTRL, "Set interrupt for host %d\n", (int)arg);
+				WBYTE(p->RIOHosts[(int)arg].SetInt , 0xff);
+				return 0;
+
+			case RIO_TOCK:
+				if ((int)arg < 0 || (int)arg >= p->RIONumHosts)
+					 return -EINVAL;
+				rio_dprintk (RIO_DEBUG_CTRL, "Clear interrupt for host %d\n", (int)arg);
+				WBYTE((p->RIOHosts[(int)arg].ResetInt) , 0xff);
+				return 0;
+
+			case RIO_READ_CHECK:
+				/* Check reads for pkts with data[0] the same */
+				p->RIOReadCheck = !p->RIOReadCheck;
+				if (copyout((caddr_t)&p->RIOReadCheck,(int)arg,
+							sizeof(uint))== COPYFAIL) {
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return 0;
+
+			case RIO_READ_REGISTER :
+				if (copyin((int)arg, (caddr_t)&SubCmd, 
+							sizeof(struct SubCmdStruct)) == COPYFAIL) {
+					 p->RIOError.Error = COPYIN_FAILED;
+					 return -EFAULT;
+				}
+				rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_REGISTER host %d rup %d port %d reg %x\n", 
+						SubCmd.Host, SubCmd.Rup, SubCmd.Port, SubCmd.Addr);
+
+				if (SubCmd.Port > 511) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "Baud rate mapping: Bad port number %d\n", 
+								SubCmd.Port);
+					 p->RIOError.Error = PORT_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+
+				if (SubCmd.Rup >= MAX_RUP+LINKS_PER_UNIT ) {
+					 p->RIOError.Error = RUP_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+
+				if (SubCmd.Host >= p->RIONumHosts ) {
+					 p->RIOError.Error = HOST_NUMBER_OUT_OF_RANGE;
+					 return -EINVAL;
+				}
+
+				port = p->RIOHosts[SubCmd.Host].
+						UnixRups[SubCmd.Rup].BaseSysPort + SubCmd.Port;
+				PortP = p->RIOPortp[port];
+
+				rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+				if (RIOPreemptiveCmd(p, PortP, READ_REGISTER) == RIO_FAIL) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_REGISTER failed\n");
+					 rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+					 return -EBUSY;
+				}
+				else
+					 PortP->State |= RIO_BUSY;
+
+				rio_spin_unlock_irqrestore( &PortP->portSem , flags);
+				if (copyout((caddr_t)&p->CdRegister, (int)arg, 
+							sizeof(uint)) == COPYFAIL ) {
+					 rio_dprintk (RIO_DEBUG_CTRL, "RIO_READ_REGISTER copy failed\n");
+					 p->RIOError.Error = COPYOUT_FAILED;
+					 return -EFAULT;
+				}
+				return 0;
+				/*
+				** rio_make_dev: given port number (0-511) ORed with port type
+				** (RIO_DEV_DIRECT, RIO_DEV_MODEM, RIO_DEV_XPRINT) return dev_t
+				** value to pass to mknod to create the correct device node.
+				*/
+			case RIO_MAKE_DEV:
+				{
+					uint port = (uint)arg & RIO_MODEM_MASK;
+
+					switch ( (uint)arg & RIO_DEV_MASK ) {
+						case RIO_DEV_DIRECT:
+							arg = (caddr_t)drv_makedev(MAJOR(dev), port);
+							rio_dprintk (RIO_DEBUG_CTRL, "Makedev direct 0x%x is 0x%x\n",port, (int)arg);
+							return (int)arg;
+					 	case RIO_DEV_MODEM:
+							arg =  (caddr_t)drv_makedev(MAJOR(dev), (port|RIO_MODEM_BIT) );
+							rio_dprintk (RIO_DEBUG_CTRL, "Makedev modem 0x%x is 0x%x\n",port, (int)arg);
+							return (int)arg;
+						case RIO_DEV_XPRINT:
+							arg = (caddr_t)drv_makedev(MAJOR(dev), port);
+							rio_dprintk (RIO_DEBUG_CTRL, "Makedev printer 0x%x is 0x%x\n",port, (int)arg);
+							return (int)arg;
+					}
+					rio_dprintk (RIO_DEBUG_CTRL, "MAKE Device is called\n");
+					return -EINVAL;
+				}
+				/*
+				** rio_minor: given a dev_t from a stat() call, return
+				** the port number (0-511) ORed with the port type
+				** ( RIO_DEV_DIRECT, RIO_DEV_MODEM, RIO_DEV_XPRINT )
+				*/
+			case RIO_MINOR:
+				{
+					dev_t dv;
+					int mino;
+
+					dv = (dev_t)((int)arg);
+					mino = RIO_UNMODEM(dv);
+
+					if ( RIO_ISMODEM(dv) ) {
+						rio_dprintk (RIO_DEBUG_CTRL, "Minor for device 0x%x: modem %d\n", dv, mino);
+						arg = (caddr_t)(mino | RIO_DEV_MODEM);
+					}
+					else {
+						rio_dprintk (RIO_DEBUG_CTRL, "Minor for device 0x%x: direct %d\n", dv, mino);
+						arg = (caddr_t)(mino | RIO_DEV_DIRECT);
+					}
+					return (int)arg;
+				}
+	}
+	rio_dprintk (RIO_DEBUG_CTRL, "INVALID DAEMON IOCTL 0x%x\n",cmd);
+	p->RIOError.Error = IOCTL_COMMAND_UNKNOWN;
+
+	func_exit ();
+	return -EINVAL;
+}
+
+/*
+** Pre-emptive commands go on RUPs and are only one byte long.
+*/
+int
+RIOPreemptiveCmd(p, PortP, Cmd)
+struct rio_info *	p;
+struct Port *PortP;
+uchar Cmd;
+{
+	struct CmdBlk *CmdBlkP;
+	struct PktCmd_M *PktCmdP;
+	int Ret;
+	ushort rup;
+	int port;
+
+#ifdef CHECK
+	CheckPortP( PortP );
+#endif
+
+	if ( PortP->State & RIO_DELETED ) {
+		rio_dprintk (RIO_DEBUG_CTRL, "Preemptive command to deleted RTA ignored\n");
+		return RIO_FAIL;
+	}
+
+	if (((int)((char)PortP->InUse) == -1) || ! (CmdBlkP = RIOGetCmdBlk()) ) {
+		rio_dprintk (RIO_DEBUG_CTRL, "Cannot allocate command block for command %d on port %d\n",
+		       Cmd, PortP->PortNum);
+		return RIO_FAIL;
+	}
+
+	rio_dprintk (RIO_DEBUG_CTRL, "Command blk 0x%x - InUse now %d\n", 
+	       (int)CmdBlkP,PortP->InUse);
+
+	PktCmdP = (struct PktCmd_M *)&CmdBlkP->Packet.data[0];
+
+	CmdBlkP->Packet.src_unit  = 0;
+	if (PortP->SecondBlock)
+		rup = PortP->ID2;
+	else
+		rup = PortP->RupNum;
+	CmdBlkP->Packet.dest_unit = rup;
+	CmdBlkP->Packet.src_port  = COMMAND_RUP;
+	CmdBlkP->Packet.dest_port = COMMAND_RUP;
+	CmdBlkP->Packet.len	  = PKT_CMD_BIT | 2;
+	CmdBlkP->PostFuncP	= RIOUnUse;
+	CmdBlkP->PostArg	= (int)PortP;
+	PktCmdP->Command	= Cmd;
+	port				= PortP->HostPort % (ushort)PORTS_PER_RTA;
+	/*
+	** Index ports 8-15 for 2nd block of 16 port RTA.
+	*/
+	if (PortP->SecondBlock)
+		port += (ushort) PORTS_PER_RTA;
+	PktCmdP->PhbNum	   = port;
+
+	switch ( Cmd ) {
+		case MEMDUMP:
+			rio_dprintk (RIO_DEBUG_CTRL, "Queue MEMDUMP command blk 0x%x (addr 0x%x)\n",
+			       (int)CmdBlkP, (int)SubCmd.Addr);
+			PktCmdP->SubCommand		= MEMDUMP;
+			PktCmdP->SubAddr		= SubCmd.Addr;
+			break;
+		case FCLOSE:
+			rio_dprintk (RIO_DEBUG_CTRL, "Queue FCLOSE command blk 0x%x\n",(int)CmdBlkP);
+			break;
+		case READ_REGISTER:
+			rio_dprintk (RIO_DEBUG_CTRL, "Queue READ_REGISTER (0x%x) command blk 0x%x\n",
+		 		(int)SubCmd.Addr, (int)CmdBlkP);
+			PktCmdP->SubCommand		= READ_REGISTER;
+			PktCmdP->SubAddr		= SubCmd.Addr;
+			break;
+		case RESUME:
+			rio_dprintk (RIO_DEBUG_CTRL, "Queue RESUME command blk 0x%x\n",(int)CmdBlkP);
+			break;
+		case RFLUSH:
+			rio_dprintk (RIO_DEBUG_CTRL, "Queue RFLUSH command blk 0x%x\n",(int)CmdBlkP);
+			CmdBlkP->PostFuncP = RIORFlushEnable;
+			break;
+		case SUSPEND:
+			rio_dprintk (RIO_DEBUG_CTRL, "Queue SUSPEND command blk 0x%x\n",(int)CmdBlkP);
+			break;
+
+		case MGET :
+			rio_dprintk (RIO_DEBUG_CTRL, "Queue MGET command blk 0x%x\n", (int)CmdBlkP);
+			break;
+
+		case MSET :
+		case MBIC :
+		case MBIS :
+			CmdBlkP->Packet.data[4] = (char) PortP->ModemLines;
+			rio_dprintk (RIO_DEBUG_CTRL, "Queue MSET/MBIC/MBIS command blk 0x%x\n", (int)CmdBlkP);
+			break;
+
+		case WFLUSH:
+			/*
+			** If we have queued up the maximum number of Write flushes
+			** allowed then we should not bother sending any more to the
+			** RTA.
+			*/
+			if ((int)((char)PortP->WflushFlag) == (int)-1) {
+				rio_dprintk (RIO_DEBUG_CTRL, "Trashed WFLUSH, WflushFlag about to wrap!");
+				RIOFreeCmdBlk(CmdBlkP);
+				return(RIO_FAIL);
+			} else {
+				rio_dprintk (RIO_DEBUG_CTRL, "Queue WFLUSH command blk 0x%x\n",
+				       (int)CmdBlkP);
+				CmdBlkP->PostFuncP = RIOWFlushMark;
+			}
+			break;
+	}
+
+	PortP->InUse++;
+
+	Ret = RIOQueueCmdBlk( PortP->HostP, rup, CmdBlkP );
+
+	return Ret;
+}
diff --git a/drivers/char/rio/riodrvr.h b/drivers/char/rio/riodrvr.h
new file mode 100644
index 0000000..bc38ac5
--- /dev/null
+++ b/drivers/char/rio/riodrvr.h
@@ -0,0 +1,144 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: riodrvr.h
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 09:22:46
+**	Retrieved	: 11/6/98 09:22:46
+**
+**  ident @(#)riodrvr.h	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __riodrvr_h
+#define __riodrvr_h
+
+#include <asm/param.h>	/* for HZ */
+
+#ifdef SCCS_LABELS
+static char *_riodrvr_h_sccs_ = "@(#)riodrvr.h	1.3";
+#endif
+
+#define MEMDUMP_SIZE	32
+#define	MOD_DISABLE	(RIO_NOREAD|RIO_NOWRITE|RIO_NOXPRINT)
+
+
+struct rio_info {
+	int			mode;			/* Intr or polled, word/byte */
+	spinlock_t		RIOIntrSem;		/* Interrupt thread sem */
+	int			current_chan;		/* current channel */
+	int			RIOFailed;		/* Not initialised ? */
+	int			RIOInstallAttempts;	/* no. of rio-install() calls */
+	int			RIOLastPCISearch;	/* status of last search */
+	int			RIONumHosts;		/* Number of RIO Hosts */
+	struct Host		* RIOHosts;		/* RIO Host values */
+	struct Port		**RIOPortp;		/* RIO port values */
+/*
+** 02.03.1999 ARG - ESIL 0820 fix
+** We no longer use RIOBootMode
+**
+	int			RIOBootMode;		* RIO boot mode *
+**
+*/
+	int			RIOPrintDisabled;	/* RIO printing disabled ? */
+	int			RIOPrintLogState;	/* RIO printing state ? */
+	int			RIOPolling;		/* Polling ? */
+/*
+** 09.12.1998 ARG - ESIL 0776 part fix
+** The 'RIO_QUICK_CHECK' ioctl was using RIOHalted.
+** The fix for this ESIL introduces another member (RIORtaDisCons) here to be
+** updated in RIOConCon() - to keep track of RTA connections/disconnections.
+** 'RIO_QUICK_CHECK' now returns the value of RIORtaDisCons.
+*/
+	int			RIOHalted;		/* halted ? */
+	int			RIORtaDisCons;		/* RTA connections/disconnections */
+	uint			RIOReadCheck;		/* Rio read check */
+	uint			RIONoMessage;		/* To display message or not */
+	uint			RIONumBootPkts;		/* how many packets for an RTA */
+	uint			RIOBootCount; 		/* size of RTA code */
+	uint			RIOBooting;		/* count of outstanding boots */
+	uint			RIOSystemUp;		/* Booted ?? */
+	uint			RIOCounting;		/* for counting interrupts */
+	uint			RIOIntCount;		/* # of intr since last check */
+	uint			RIOTxCount;		/* number of xmit intrs  */
+	uint			RIORxCount;		/* number of rx intrs */
+	uint			RIORupCount;		/* number of rup intrs */
+	int			RIXTimer; 
+	int			RIOBufferSize;		/* Buffersize */
+	int			RIOBufferMask;		/* Buffersize */
+
+	int			RIOFirstMajor;		/* First host card's major no */
+
+	uint			RIOLastPortsMapped;	/* highest port number known */
+	uint			RIOFirstPortsMapped;	/* lowest port number known */
+
+	uint			RIOLastPortsBooted;	/* highest port number running */
+	uint			RIOFirstPortsBooted;	/* lowest port number running */
+
+	uint			RIOLastPortsOpened;	/* highest port number running */
+	uint			RIOFirstPortsOpened;	/* lowest port number running */
+
+	/* Flag to say that the topology information has been changed. */
+	uint			RIOQuickCheck; 
+	uint			CdRegister;		/* ??? */
+	int			RIOSignalProcess;	/* Signalling process */
+	int			rio_debug;		/* To debug ... */
+	int			RIODebugWait;		/* For what ??? */
+	int			tpri;			/* Thread prio */
+	int			tid;			/* Thread id */
+	uint			_RIO_Polled;		/* Counter for polling */
+	uint			_RIO_Interrupted;	/* Counter for interrupt */
+	int			intr_tid;		/* iointset return value */
+	int			TxEnSem;		/* TxEnable Semaphore */
+
+
+	struct Error		RIOError;		/* to Identify what went wrong */ 
+	struct Conf		RIOConf;		/* Configuration ??? */
+	struct ttystatics	channel[RIO_PORTS];	/* channel information */
+	char			RIOBootPackets[1+(SIXTY_FOUR_K/RTA_BOOT_DATA_SIZE)]
+								[RTA_BOOT_DATA_SIZE];
+	struct Map		RIOConnectTable[TOTAL_MAP_ENTRIES];
+	struct Map		RIOSavedTable[TOTAL_MAP_ENTRIES];
+
+	/* RTA to host binding table for master/slave operation */
+	ulong			RIOBindTab[MAX_RTA_BINDINGS];
+	/* RTA memory dump variable */
+	uchar			RIOMemDump[MEMDUMP_SIZE]; 
+	struct ModuleInfo 	RIOModuleTypes[MAX_MODULE_TYPES];
+
+};
+
+
+#ifdef linux
+#define debug(x)        printk x
+#else
+#define debug(x)	kkprintf x
+#endif
+
+
+
+#define RIO_RESET_INT	0x7d80
+#define WRBYTE(x,y)		*(volatile unsigned char *)((x)) = \
+					(unsigned char)(y)
+
+#endif	/* __riodrvr.h */
diff --git a/drivers/char/rio/rioinfo.h b/drivers/char/rio/rioinfo.h
new file mode 100644
index 0000000..e08421c
--- /dev/null
+++ b/drivers/char/rio/rioinfo.h
@@ -0,0 +1,96 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: rioinfo.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 14:07:49
+**	Retrieved	: 11/6/98 14:07:50
+**
+**  ident @(#)rioinfo.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rioinfo_h
+#define __rioinfo_h
+
+#ifdef SCCS_LABELS
+static char *_rioinfo_h_sccs_ = "@(#)rioinfo.h	1.2";
+#endif
+
+/*
+** Host card data structure
+*/
+struct RioHostInfo {
+	long	location;	/* RIO Card Base I/O address */
+	long	vector;		/* RIO Card IRQ vector */
+	int	bus;		/* ISA/EISA/MCA/PCI */
+	int	mode;		/* pointer to host mode - INTERRUPT / POLLED */
+	struct old_sgttyb
+		* Sg;		/* pointer to default term characteristics */
+};
+
+
+/* Mode in rio device info */
+#define INTERRUPTED_MODE	0x01		/* Interrupt is generated */
+#define POLLED_MODE		0x02		/* No interrupt */
+#define AUTO_MODE		0x03		/* Auto mode */
+
+#define WORD_ACCESS_MODE	0x10		/* Word Access Mode */
+#define BYTE_ACCESS_MODE	0x20		/* Byte Access Mode */
+
+
+/* Bus type that RIO supports */
+#define ISA_BUS			0x01		/* The card is ISA */
+#define EISA_BUS		0x02		/* The card is EISA */
+#define MCA_BUS			0x04		/* The card is MCA */
+#define PCI_BUS			0x08		/* The card is PCI */
+
+/*
+** 11.11.1998 ARG - ESIL ???? part fix
+** Moved definition for 'CHAN' here from rioinfo.c (it is now
+** called 'DEF_TERM_CHARACTERISTICS').
+*/
+
+#define DEF_TERM_CHARACTERISTICS \
+{ \
+	B19200, B19200,				/* input and output speed */ \
+	'H' - '@',				/* erase char */ \
+	-1,					/* 2nd erase char */ \
+	'U' - '@',				/* kill char */ \
+	ECHO | CRMOD,				/* mode */ \
+	'C' - '@',				/* interrupt character */ \
+	'\\' - '@',				/* quit char */ \
+	'Q' - '@',				/* start char */ \
+	'S' - '@',				/* stop char */ \
+	'D' - '@',				/* EOF */ \
+	-1,					/* brk */ \
+	(LCRTBS | LCRTERA | LCRTKIL | LCTLECH),	/* local mode word */ \
+	'Z' - '@',				/* process stop */ \
+	'Y' - '@',				/* delayed stop */ \
+	'R' - '@',				/* reprint line */ \
+	'O' - '@',				/* flush output */ \
+	'W' - '@',				/* word erase */ \
+	'V' - '@'				/* literal next char */ \
+}
+
+#endif /* __rioinfo_h */
diff --git a/drivers/char/rio/rioinit.c b/drivers/char/rio/rioinit.c
new file mode 100644
index 0000000..dca941e
--- /dev/null
+++ b/drivers/char/rio/rioinit.c
@@ -0,0 +1,1617 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: rioinit.c
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 10:33:43
+**	Retrieved	: 11/6/98 10:33:49
+**
+**  ident @(#)rioinit.c	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_rioinit_c_sccs_ = "@(#)rioinit.c	1.3";
+#endif
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "rio_linux.h"
+
+#undef bcopy
+#define bcopy rio_pcicopy
+
+int RIOPCIinit(struct rio_info *p, int Mode);
+
+#if 0
+static void RIOAllocateInterrupts(struct rio_info *);
+static int RIOReport(struct rio_info *);
+static void RIOStopInterrupts(struct rio_info *, int, int);
+#endif
+
+static int RIOScrub(int, BYTE *, int);
+
+#if 0
+extern int	rio_intr();
+
+/*
+**	Init time code.
+*/
+void
+rioinit( p, info )
+struct rio_info		* p;
+struct RioHostInfo	* info;
+{
+	/*
+	** Multi-Host card support - taking the easy way out - sorry !
+	** We allocate and set up the Host and Port structs when the
+	** driver is called to 'install' the first host.
+	** We check for this first 'call' by testing the RIOPortp pointer.
+	*/
+	if ( !p->RIOPortp )
+	{
+		rio_dprintk (RIO_DEBUG_INIT,  "Allocating and setting up driver data structures\n");
+
+		RIOAllocDataStructs(p);		/* allocate host/port structs */
+		RIOSetupDataStructs(p);		/* setup topology structs */
+	}
+
+	RIOInitHosts( p, info );	/* hunt down the hardware */
+
+	RIOAllocateInterrupts(p);	/* allocate interrupts */
+	RIOReport(p);			/* show what we found */
+}
+
+/*
+** Initialise the Cards 
+*/ 
+void
+RIOInitHosts(p, info)
+struct rio_info		* p;
+struct RioHostInfo	* info;
+{
+/*
+** 15.10.1998 ARG - ESIL 0762 part fix
+** If there is no ISA card definition - we always look for PCI cards.
+** As we currently only support one host card this lets an ISA card
+** definition take precedence over PLUG and PLAY.
+** No ISA card - we are PLUG and PLAY with PCI.
+*/
+
+	/*
+	** Note - for PCI both these will be zero, that's okay because
+	** RIOPCIInit() fills them in if a card is found.
+	*/
+	p->RIOHosts[p->RIONumHosts].Ivec	= info->vector;
+	p->RIOHosts[p->RIONumHosts].PaddrP	= info->location;
+
+	/*
+	** Check that we are able to accommodate another host
+	*/
+	if ( p->RIONumHosts >= RIO_HOSTS )
+	{
+		p->RIOFailed++;
+		return;
+	}
+
+	if ( info->bus & ISA_BUS )
+	{
+		rio_dprintk (RIO_DEBUG_INIT,  "initialising card %d (ISA)\n", p->RIONumHosts);
+		RIOISAinit(p, p->mode);
+	}
+	else
+	{
+		rio_dprintk (RIO_DEBUG_INIT,  "initialising card %d (PCI)\n", p->RIONumHosts);
+		RIOPCIinit(p, RIO_PCI_DEFAULT_MODE);
+	}
+
+	rio_dprintk (RIO_DEBUG_INIT,  "Total hosts initialised so far : %d\n", p->RIONumHosts);
+
+
+#ifdef FUTURE_RELEASE
+	if (p->bus & EISA_BUS)
+		/* EISA card */
+		RIOEISAinit(p, RIO_EISA_DEFAULT_MODE);
+
+	if (p->bus & MCA_BUS)
+		/* MCA card */
+		RIOMCAinit(p, RIO_MCA_DEFAULT_MODE);
+#endif
+}
+
+/*
+** go through memory for an AT host that we pass in the device info
+** structure and initialise
+*/
+void
+RIOISAinit(p, mode)
+struct rio_info *	p;
+int					mode;
+{
+
+  /* XXX Need to implement this. */
+#if 0
+	p->intr_tid = iointset(p->RIOHosts[p->RIONumHosts].Ivec,
+					(int (*)())rio_intr, (char*)p->RIONumHosts);
+
+	rio_dprintk (RIO_DEBUG_INIT,  "Set interrupt handler, intr_tid = 0x%x\n", p->intr_tid );
+
+	if (RIODoAT(p, p->RIOHosts[p->RIONumHosts].PaddrP, mode)) {
+		return;
+	}
+	else {
+		rio_dprintk (RIO_DEBUG_INIT, "RIODoAT failed\n");
+		p->RIOFailed++;
+	}
+#endif
+
+}
+
+/*
+** RIODoAT :
+**
+** Map in a boards physical address, check that the board is there,
+** test the board and if everything is okay assign the board an entry
+** in the Rio Hosts structure.
+*/
+int
+RIODoAT(p, Base, mode)
+struct rio_info *	p;
+int		Base;
+int		mode;
+{
+#define	FOUND		1
+#define NOT_FOUND	0
+
+	caddr_t		cardAddr;
+
+	/*
+	** Check to see if we actually have a board at this physical address.
+	*/
+	if ((cardAddr = RIOCheckForATCard(Base)) != 0) {
+		/*
+		** Now test the board to see if it is working.
+		*/
+		if (RIOBoardTest(Base, cardAddr, RIO_AT, 0) == RIO_SUCCESS) {
+			/*
+			** Fill out a slot in the Rio host structure.
+			*/
+			if (RIOAssignAT(p, Base, cardAddr, mode)) {
+				return(FOUND);
+			}
+		}
+		RIOMapout(Base, RIO_AT_MEM_SIZE, cardAddr);
+	}
+	return(NOT_FOUND);
+}
+
+caddr_t
+RIOCheckForATCard(Base)
+int		Base;
+{
+	int				off;
+	struct DpRam	*cardp;		/* (Points at the host) */
+	caddr_t			virtAddr;
+	unsigned char			RIOSigTab[24];
+/*
+** Table of values to search for as prom signature of a host card
+*/
+	strcpy(RIOSigTab, "JBJGPGGHINSMJPJR");
+
+	/*
+	** Hey! Yes, You reading this code! Yo, grab a load a this:
+	**
+	** IF the card is using WORD MODE rather than BYTE MODE
+	** then it will occupy 128K of PHYSICAL memory area. So,
+	** you might think that the following Mapin is wrong. Well,
+	** it isn't, because the SECOND 64K of occupied space is an
+	** EXACT COPY of the FIRST 64K. (good?), so, we need only
+	** map it in in one 64K block.
+	*/
+	if (RIOMapin(Base, RIO_AT_MEM_SIZE, &virtAddr) == -1) {
+		rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Couldn't map the board in!\n");
+		return((caddr_t)0);
+	}
+
+	/*
+	** virtAddr points to the DP ram of the system.
+	** We now cast this to a pointer to a RIO Host,
+	** and have a rummage about in the PROM.
+	*/
+	cardp = (struct DpRam *)virtAddr;
+
+	for (off=0; RIOSigTab[off]; off++) {
+		if ((RBYTE(cardp->DpSignature[off]) & 0xFF) != RIOSigTab[off]) {
+			/*
+			** Signature mismatch - card not at this address
+			*/
+			RIOMapout(Base, RIO_AT_MEM_SIZE, virtAddr);
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Couldn't match the signature 0x%x 0x%x!\n",
+						(int)cardp, off);
+			return((caddr_t)0);
+		}
+	}
+
+	/*
+	** If we get here then we must have found a valid board so return
+	** its virtual address.
+	*/
+	return(virtAddr);
+}
+#endif
+
+/**
+** RIOAssignAT :
+**
+** Fill out the fields in the p->RIOHosts structure now we know we know
+** we have a board present.
+**
+** bits < 0 indicates 8 bit operation requested,
+** bits > 0 indicates 16 bit operation.
+*/
+int
+RIOAssignAT(p, Base, virtAddr, mode)
+struct rio_info *	p;
+int		Base;
+caddr_t	virtAddr;
+int		mode;
+{
+	int		bits;
+	struct DpRam *cardp = (struct DpRam *)virtAddr;
+
+	if ((Base < ONE_MEG) || (mode & BYTE_ACCESS_MODE))
+		bits = BYTE_OPERATION;
+	else
+		bits = WORD_OPERATION;
+
+	/*
+	** Board has passed its scrub test. Fill in all the
+	** transient stuff.
+	*/
+	p->RIOHosts[p->RIONumHosts].Caddr	= virtAddr;
+	p->RIOHosts[p->RIONumHosts].CardP	= (struct DpRam *)virtAddr;
+
+	/*
+	** Revision 01 AT host cards don't support WORD operations,
+	*/
+	if ( RBYTE(cardp->DpRevision) == 01 )
+		bits = BYTE_OPERATION;
+
+	p->RIOHosts[p->RIONumHosts].Type = RIO_AT;
+	p->RIOHosts[p->RIONumHosts].Copy = bcopy;
+											/* set this later */
+	p->RIOHosts[p->RIONumHosts].Slot = -1;
+	p->RIOHosts[p->RIONumHosts].Mode = SLOW_LINKS | SLOW_AT_BUS | bits;
+	WBYTE(p->RIOHosts[p->RIONumHosts].Control, 
+			BOOT_FROM_RAM | EXTERNAL_BUS_OFF | 
+			p->RIOHosts[p->RIONumHosts].Mode | 
+			INTERRUPT_DISABLE );
+	WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt,0xff);
+	WBYTE(p->RIOHosts[p->RIONumHosts].Control,
+			BOOT_FROM_RAM | EXTERNAL_BUS_OFF | 
+			p->RIOHosts[p->RIONumHosts].Mode |
+			INTERRUPT_DISABLE );
+	WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt,0xff);
+	p->RIOHosts[p->RIONumHosts].UniqueNum =
+		((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[0])&0xFF)<<0)|
+		((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[1])&0xFF)<<8)|
+		((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[2])&0xFF)<<16)|
+		((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[3])&0xFF)<<24);
+	rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Uniquenum 0x%x\n",p->RIOHosts[p->RIONumHosts].UniqueNum);
+
+	p->RIONumHosts++;
+	rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Tests Passed at 0x%x\n", Base);
+	return(1);
+}
+#if 0
+#ifdef FUTURE_RELEASE
+int RIOMCAinit(int Mode)
+{
+	uchar SlotNumber;
+	caddr_t Caddr;
+	uint	Paddr;
+	uint	Ivec;
+	int	 Handle;
+	int	 ret = 0;
+
+	/*
+	** Valid mode information for MCA cards
+	** is only FAST LINKS
+	*/
+	Mode = (Mode & FAST_LINKS) ? McaTpFastLinks : McaTpSlowLinks;
+	rio_dprintk (RIO_DEBUG_INIT, "RIOMCAinit(%d)\n",Mode);
+
+
+	/*
+	** Check out each of the slots
+	*/
+	for (SlotNumber = 0; SlotNumber < McaMaxSlots; SlotNumber++) {
+	/*
+	** Enable the slot we want to talk to
+	*/
+	outb( McaSlotSelect, SlotNumber | McaSlotEnable );
+
+	/*
+	** Read the ID word from the slot
+	*/
+	if (((inb(McaIdHigh)<< 8)|inb(McaIdLow)) == McaRIOId)
+	{
+		rio_dprintk (RIO_DEBUG_INIT, "Potential MCA card in slot %d\n", SlotNumber);
+
+		/*
+		** Card appears to be a RIO MCA card!
+		*/
+		RIOMachineType |= (1<<RIO_MCA);
+
+		/*
+		** Just check we haven't found too many wonderful objects
+		*/
+		if ( RIONumHosts >= RIO_HOSTS )
+		{
+		Rprintf(RIOMesgTooManyCards);
+		return(ret);
+		}
+
+		/*
+		** McaIrqEnable contains the interrupt vector, and a card
+		** enable bit.
+		*/
+		Ivec = inb(McaIrqEnable);
+
+		rio_dprintk (RIO_DEBUG_INIT, "Ivec is %x\n", Ivec);
+
+		switch ( Ivec & McaIrqMask )
+		{
+		case McaIrq9:
+		rio_dprintk (RIO_DEBUG_INIT, "IRQ9\n");
+		break;
+		case McaIrq3:
+		rio_dprintk (RIO_DEBUG_INIT, "IRQ3\n");
+		break;
+		case McaIrq4:
+		rio_dprintk (RIO_DEBUG_INIT, "IRQ4\n");
+		break;
+		case McaIrq7:
+		rio_dprintk (RIO_DEBUG_INIT, "IRQ7\n");
+		break;
+		case McaIrq10:
+		rio_dprintk (RIO_DEBUG_INIT, "IRQ10\n");
+		break;
+		case McaIrq11:
+		rio_dprintk (RIO_DEBUG_INIT, "IRQ11\n");
+		break;
+		case McaIrq12:
+		rio_dprintk (RIO_DEBUG_INIT, "IRQ12\n");
+		break;
+		case McaIrq15:
+		rio_dprintk (RIO_DEBUG_INIT, "IRQ15\n");
+		break;
+		}
+
+		/*
+		** If the card enable bit isn't set, then set it!
+		*/
+		if ((Ivec & McaCardEnable) != McaCardEnable) {
+			rio_dprintk (RIO_DEBUG_INIT, "McaCardEnable not set - setting!\n");
+			outb(McaIrqEnable,Ivec|McaCardEnable);
+		} else
+			rio_dprintk (RIO_DEBUG_INIT, "McaCardEnable already set\n");
+
+		/*
+		** Convert the IRQ enable mask into something useful
+		*/
+		Ivec = RIOMcaToIvec[Ivec & McaIrqMask];
+
+		/*
+		** Find the physical address
+		*/
+		rio_dprintk (RIO_DEBUG_INIT, "inb(McaMemory) is %x\n", inb(McaMemory));
+		Paddr = McaAddress(inb(McaMemory));
+
+		rio_dprintk (RIO_DEBUG_INIT, "MCA card has Ivec %d Addr %x\n", Ivec, Paddr);
+
+		if ( Paddr != 0 )
+		{
+
+		/*
+		** Tell the memory mapper that we want to talk to it
+		*/
+		Handle = RIOMapin( Paddr, RIO_MCA_MEM_SIZE, &Caddr );
+
+		if ( Handle == -1 ) {
+			rio_dprintk (RIO_DEBUG_INIT, "Couldn't map %d bytes at %x\n", RIO_MCA_MEM_SIZE, Paddr;
+			continue;
+		}
+
+		rio_dprintk (RIO_DEBUG_INIT, "Board mapped to vaddr 0x%x\n", Caddr);
+
+		/*
+		** And check that it is actually there!
+		*/
+		if ( RIOBoardTest( Paddr,Caddr,RIO_MCA,SlotNumber ) == RIO_SUCCESS )
+		{
+			rio_dprintk (RIO_DEBUG_INIT, "Board has passed test\n");
+			rio_dprintk (RIO_DEBUG_INIT, "Slot %d. Type %d. Paddr 0x%x. Caddr 0x%x. Mode 0x%x.\n",
+			                            SlotNumber, RIO_MCA, Paddr, Caddr, Mode);
+
+			/*
+			** Board has passed its scrub test. Fill in all the
+			** transient stuff.
+			*/
+			p->RIOHosts[RIONumHosts].Slot	 = SlotNumber;
+			p->RIOHosts[RIONumHosts].Ivec	 = Ivec;
+			p->RIOHosts[RIONumHosts].Type	 = RIO_MCA;
+			p->RIOHosts[RIONumHosts].Copy	 = bcopy;
+			p->RIOHosts[RIONumHosts].PaddrP   = Paddr;
+			p->RIOHosts[RIONumHosts].Caddr	= Caddr;
+			p->RIOHosts[RIONumHosts].CardP	= (struct DpRam *)Caddr;
+			p->RIOHosts[RIONumHosts].Mode	 = Mode;
+			WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt , 0xff);
+			p->RIOHosts[RIONumHosts].UniqueNum =
+			((RBYTE(p->RIOHosts[RIONumHosts].Unique[0])&0xFF)<<0)|
+						((RBYTE(p->RIOHosts[RIONumHosts].Unique[1])&0xFF)<<8)|
+			((RBYTE(p->RIOHosts[RIONumHosts].Unique[2])&0xFF)<<16)|
+			((RBYTE(p->RIOHosts[RIONumHosts].Unique[3])&0xFF)<<24);
+			RIONumHosts++;
+			ret++;
+		}
+		else
+		{
+			/*
+			** It failed the test, so ignore it.
+			*/
+			rio_dprintk (RIO_DEBUG_INIT, "TEST FAILED\n");
+			RIOMapout(Paddr, RIO_MCA_MEM_SIZE, Caddr );
+		}
+		}
+		else
+		{
+		rio_dprintk (RIO_DEBUG_INIT, "Slot %d - Paddr zero!\n", SlotNumber);
+		}
+	}
+	else
+	{
+		rio_dprintk (RIO_DEBUG_INIT, "Slot %d NOT RIO\n", SlotNumber);
+	}
+	}
+	/*
+	** Now we have checked all the slots, turn off the MCA slot selector
+	*/
+	outb(McaSlotSelect,0);
+	rio_dprintk (RIO_DEBUG_INIT, "Slot %d NOT RIO\n", SlotNumber);
+	return ret;
+}
+
+int RIOEISAinit( int Mode )
+{
+	static int EISADone = 0;
+	uint Paddr;
+	int PollIntMixMsgDone = 0;
+	caddr_t Caddr;
+	ushort Ident;
+	uchar EisaSlot;
+	uchar Ivec;
+	int ret = 0;
+
+	/*
+	** The only valid mode information for EISA hosts is fast or slow
+	** links.
+	*/
+	Mode = (Mode & FAST_LINKS) ? EISA_TP_FAST_LINKS : EISA_TP_SLOW_LINKS;
+
+	if ( EISADone )
+	{
+		rio_dprintk (RIO_DEBUG_INIT, "RIOEISAinit() - already done, return.\n");
+		return(0);
+	}
+
+	EISADone++;
+
+	rio_dprintk (RIO_DEBUG_INIT, "RIOEISAinit()\n");
+
+
+	/*
+	** First check all cards to see if ANY are set for polled mode operation.
+	** If so, set ALL to polled.
+	*/
+
+	for ( EisaSlot=1; EisaSlot<=RIO_MAX_EISA_SLOTS; EisaSlot++ )
+	{
+	Ident = (INBZ(EisaSlot,EISA_PRODUCT_IDENT_HI)<<8) |
+		 INBZ(EisaSlot,EISA_PRODUCT_IDENT_LO);
+
+	if ( Ident == RIO_EISA_IDENT )
+	{
+		rio_dprintk (RIO_DEBUG_INIT, "Found Specialix product\n");
+
+		if ( INBZ(EisaSlot,EISA_PRODUCT_NUMBER) != RIO_EISA_PRODUCT_CODE )
+		{
+		rio_dprintk (RIO_DEBUG_INIT, "Not Specialix RIO - Product number %x\n",
+						INBZ(EisaSlot, EISA_PRODUCT_NUMBER));
+		continue;  /* next slot */
+		}
+		/*
+		** Its a Specialix RIO!
+		*/
+		rio_dprintk (RIO_DEBUG_INIT, "RIO Revision %d\n",
+					INBZ(EisaSlot, EISA_REVISION_NUMBER));
+		
+		RIOMachineType |= (1<<RIO_EISA);
+
+		/*
+		** Just check we haven't found too many wonderful objects
+		*/
+		if ( RIONumHosts >= RIO_HOSTS )
+		{
+		Rprintf(RIOMesgTooManyCards);
+		return 0;
+		}
+
+		/*
+		** Ensure that the enable bit is set!
+		*/
+		OUTBZ( EisaSlot, EISA_ENABLE, RIO_EISA_ENABLE_BIT );
+
+		/*
+		** EISA_INTERRUPT_VEC contains the interrupt vector.
+		*/
+		Ivec = INBZ(EisaSlot,EISA_INTERRUPT_VEC);
+
+#ifdef RIODEBUG
+		switch ( Ivec & EISA_INTERRUPT_MASK )
+		{
+		case EISA_IRQ_3:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 3\n");
+		break;
+		case EISA_IRQ_4:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 4\n");
+		break;
+		case EISA_IRQ_5:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 5\n");
+		break;
+		case EISA_IRQ_6:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 6\n");
+		break;
+		case EISA_IRQ_7:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 7\n");
+		break;
+		case EISA_IRQ_9:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 9\n");
+		break;
+		case EISA_IRQ_10:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 10\n");
+		break;
+		case EISA_IRQ_11:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 11\n");
+		break;
+		case EISA_IRQ_12:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 12\n");
+		break;
+		case EISA_IRQ_14:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 14\n");
+		break;
+		case EISA_IRQ_15:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA IRQ 15\n");
+		break;
+		case EISA_POLLED:
+			rio_dprintk (RIO_DEBUG_INIT, "EISA POLLED\n");
+		break;
+		default:
+			rio_dprintk (RIO_DEBUG_INIT, NULL,DBG_INIT|DBG_FAIL,"Shagged interrupt number!\n");
+		Ivec &= EISA_CONTROL_MASK;
+		}
+#endif
+
+		if ( (Ivec & EISA_INTERRUPT_MASK) ==
+		 EISA_POLLED )
+		{
+		RIOWillPoll = 1;
+		break;		/* From EisaSlot loop */
+		}
+	}
+	}
+
+	/*
+	** Do it all again now we know whether to change all cards to polled
+	** mode or not
+	*/
+
+	for ( EisaSlot=1; EisaSlot<=RIO_MAX_EISA_SLOTS; EisaSlot++ )
+	{
+	Ident = (INBZ(EisaSlot,EISA_PRODUCT_IDENT_HI)<<8) |
+		 INBZ(EisaSlot,EISA_PRODUCT_IDENT_LO);
+
+	if ( Ident == RIO_EISA_IDENT )
+	{
+		if ( INBZ(EisaSlot,EISA_PRODUCT_NUMBER) != RIO_EISA_PRODUCT_CODE )
+		continue;  /* next slot */
+
+		/*
+		** Its a Specialix RIO!
+		*/
+		
+		/*
+		** Ensure that the enable bit is set!
+		*/
+		OUTBZ( EisaSlot, EISA_ENABLE, RIO_EISA_ENABLE_BIT );
+
+		/*
+		** EISA_INTERRUPT_VEC contains the interrupt vector.
+		*/
+		Ivec = INBZ(EisaSlot,EISA_INTERRUPT_VEC);
+
+		if ( RIOWillPoll )
+		{
+			/*
+			** If we are going to operate in polled mode, but this
+			** board is configured to be interrupt driven, display
+			** the message explaining the situation to the punter,
+			** assuming we haven't already done so.
+			*/
+
+			if ( !PollIntMixMsgDone &&
+			 (Ivec & EISA_INTERRUPT_MASK) != EISA_POLLED )
+			{
+			Rprintf(RIOMesgAllPolled);
+			PollIntMixMsgDone = 1;
+			}
+
+			/*
+			** Ungraciously ignore whatever the board reports as its
+			** interrupt vector...
+			*/
+
+			Ivec &= ~EISA_INTERRUPT_MASK;
+
+			/*
+			** ...and force it to dance to the poll tune.
+			*/
+
+			Ivec |= EISA_POLLED;
+		}
+
+		/*
+		** Convert the IRQ enable mask into something useful (0-15)
+		*/
+		Ivec = RIOEisaToIvec(Ivec);
+
+		rio_dprintk (RIO_DEBUG_INIT, "EISA host in slot %d has Ivec 0x%x\n",
+		 EisaSlot, Ivec);
+
+		/*
+		** Find the physical address
+		*/
+		Paddr = (INBZ(EisaSlot,EISA_MEMORY_BASE_HI)<<24) |
+				(INBZ(EisaSlot,EISA_MEMORY_BASE_LO)<<16);
+
+		rio_dprintk (RIO_DEBUG_INIT, "EISA card has Ivec %d Addr %x\n", Ivec, Paddr);
+
+		if ( Paddr == 0 )
+		{
+		rio_dprintk (RIO_DEBUG_INIT,
+		 "Board in slot %d configured for address zero!\n", EisaSlot);
+		continue;
+		}
+
+		/*
+		** Tell the memory mapper that we want to talk to it
+		*/
+		rio_dprintk (RIO_DEBUG_INIT, "About to map EISA card \n");
+
+		if (RIOMapin( Paddr, RIO_EISA_MEM_SIZE, &Caddr) == -1) {
+		rio_dprintk (RIO_DEBUG_INIT, "Couldn't map %d bytes at %x\n",
+							RIO_EISA_MEM_SIZE,Paddr);
+		continue;
+		}
+
+		rio_dprintk (RIO_DEBUG_INIT, "Board mapped to vaddr 0x%x\n", Caddr);
+
+		/*
+		** And check that it is actually there!
+		*/
+		if ( RIOBoardTest( Paddr,Caddr,RIO_EISA,EisaSlot) == RIO_SUCCESS )
+			{
+		rio_dprintk (RIO_DEBUG_INIT, "Board has passed test\n");
+		rio_dprintk (RIO_DEBUG_INIT, 
+		"Slot %d. Ivec %d. Type %d. Paddr 0x%x. Caddr 0x%x. Mode 0x%x.\n",
+			EisaSlot,Ivec,RIO_EISA,Paddr,Caddr,Mode);
+
+		/*
+		** Board has passed its scrub test. Fill in all the
+		** transient stuff.
+		*/
+		p->RIOHosts[RIONumHosts].Slot	 = EisaSlot;
+		p->RIOHosts[RIONumHosts].Ivec	 = Ivec;
+		p->RIOHosts[RIONumHosts].Type	 = RIO_EISA;
+		p->RIOHosts[RIONumHosts].Copy	 = bcopy;
+				p->RIOHosts[RIONumHosts].PaddrP   = Paddr;
+				p->RIOHosts[RIONumHosts].Caddr	= Caddr;
+		p->RIOHosts[RIONumHosts].CardP	= (struct DpRam *)Caddr;
+				p->RIOHosts[RIONumHosts].Mode	 = Mode;
+		/*
+		** because the EISA prom is mapped into IO space, we
+		** need to copy the unqiue number into the memory area
+		** that it would have occupied, so that the download
+		** code can determine its ID and card type.
+		*/
+	 WBYTE(p->RIOHosts[RIONumHosts].Unique[0],INBZ(EisaSlot,EISA_UNIQUE_NUM_0));
+	 WBYTE(p->RIOHosts[RIONumHosts].Unique[1],INBZ(EisaSlot,EISA_UNIQUE_NUM_1));
+	 WBYTE(p->RIOHosts[RIONumHosts].Unique[2],INBZ(EisaSlot,EISA_UNIQUE_NUM_2));
+	 WBYTE(p->RIOHosts[RIONumHosts].Unique[3],INBZ(EisaSlot,EISA_UNIQUE_NUM_3));
+		p->RIOHosts[RIONumHosts].UniqueNum =
+			((RBYTE(p->RIOHosts[RIONumHosts].Unique[0])&0xFF)<<0)|
+						((RBYTE(p->RIOHosts[RIONumHosts].Unique[1])&0xFF)<<8)|
+			((RBYTE(p->RIOHosts[RIONumHosts].Unique[2])&0xFF)<<16)|
+			((RBYTE(p->RIOHosts[RIONumHosts].Unique[3])&0xFF)<<24);
+		INBZ(EisaSlot,EISA_INTERRUPT_RESET);
+				RIONumHosts++;
+		ret++;
+			}
+		else
+		{
+		/*
+		** It failed the test, so ignore it.
+		*/
+		rio_dprintk (RIO_DEBUG_INIT, "TEST FAILED\n");
+
+		RIOMapout(Paddr, RIO_EISA_MEM_SIZE, Caddr );
+		}
+	}
+	}
+	if (RIOMachineType & RIO_EISA)
+	return ret+1;
+	return ret;
+}
+#endif
+
+
+#ifndef linux
+
+#define CONFIG_ADDRESS	0xcf8
+#define CONFIG_DATA		0xcfc
+#define FORWARD_REG		0xcfa
+
+
+static int
+read_config(int bus_number, int device_num, int r_number) 
+{
+	unsigned int cav;
+	unsigned int val;
+
+/*
+   Build config_address_value:
+
+      31        24 23        16 15      11 10  8 7        0 
+      ------------------------------------------------------
+      |1| 0000000 | bus_number | device # | 000 | register |
+      ------------------------------------------------------
+*/
+
+	cav = r_number & 0xff;
+	cav |= ((device_num & 0x1f) << 11);
+	cav |= ((bus_number & 0xff) << 16);
+	cav |= 0x80000000; /* Enable bit */
+	outpd(CONFIG_ADDRESS,cav);
+	val = inpd(CONFIG_DATA);
+	outpd(CONFIG_ADDRESS,0);
+	return val;
+}
+
+static
+write_config(bus_number,device_num,r_number,val) 
+{
+	unsigned int cav;
+
+/*
+   Build config_address_value:
+
+      31        24 23        16 15      11 10  8 7        0 
+      ------------------------------------------------------
+      |1| 0000000 | bus_number | device # | 000 | register |
+      ------------------------------------------------------
+*/
+
+	cav = r_number & 0xff;
+	cav |= ((device_num & 0x1f) << 11);
+	cav |= ((bus_number & 0xff) << 16);
+	cav |= 0x80000000; /* Enable bit */
+	outpd(CONFIG_ADDRESS, cav);
+	outpd(CONFIG_DATA, val);
+	outpd(CONFIG_ADDRESS, 0);
+	return val;
+}
+#else
+/* XXX Implement these... */
+static int
+read_config(int bus_number, int device_num, int r_number) 
+{
+  return 0;
+}
+
+static int
+write_config(int bus_number, int device_num, int r_number) 
+{
+  return 0;
+}
+
+#endif
+
+int
+RIOPCIinit(p, Mode)
+struct rio_info	*p;
+int 		Mode;
+{
+	#define MAX_PCI_SLOT		32
+	#define RIO_PCI_JET_CARD	0x200011CB
+
+	static int	slot;	/* count of machine's PCI slots searched so far */
+	caddr_t		Caddr;	/* Virtual address of the current PCI host card. */
+	unsigned char	Ivec;	/* interrupt vector for the current PCI host */
+	unsigned long	Paddr;	/* Physical address for the current PCI host */
+	int		Handle;	/* Handle to Virtual memory allocated for current PCI host */
+
+
+	rio_dprintk (RIO_DEBUG_INIT,  "Search for a RIO PCI card - start at slot %d\n", slot);
+
+	/*
+	** Initialise the search status
+	*/
+	p->RIOLastPCISearch	= RIO_FAIL;
+
+	while ( (slot < MAX_PCI_SLOT) & (p->RIOLastPCISearch != RIO_SUCCESS) )
+	{
+		rio_dprintk (RIO_DEBUG_INIT,  "Currently testing slot %d\n", slot);
+
+		if (read_config(0,slot,0) == RIO_PCI_JET_CARD) {
+			p->RIOHosts[p->RIONumHosts].Ivec = 0;
+			Paddr = read_config(0,slot,0x18);
+			Paddr = Paddr - (Paddr & 0x1); /* Mask off the io bit */
+
+			if ( (Paddr == 0) || ((Paddr & 0xffff0000) == 0xffff0000) ) {
+				rio_dprintk (RIO_DEBUG_INIT,  "Goofed up slot\n");	/* what! */
+				slot++;
+				continue;
+			}
+
+			p->RIOHosts[p->RIONumHosts].PaddrP = Paddr;
+			Ivec = (read_config(0,slot,0x3c) & 0xff);
+
+			rio_dprintk (RIO_DEBUG_INIT,  "PCI Host at 0x%x, Intr %d\n", (int)Paddr, Ivec);
+
+			Handle = RIOMapin( Paddr, RIO_PCI_MEM_SIZE, &Caddr );
+			if (Handle == -1) {
+				rio_dprintk (RIO_DEBUG_INIT,  "Couldn't map %d bytes at 0x%x\n", RIO_PCI_MEM_SIZE, (int)Paddr);
+				slot++;
+				continue;
+			}
+			p->RIOHosts[p->RIONumHosts].Ivec = Ivec + 32;
+			p->intr_tid = iointset(p->RIOHosts[p->RIONumHosts].Ivec,
+						(int (*)())rio_intr, (char *)p->RIONumHosts);
+			if (RIOBoardTest( Paddr, Caddr, RIO_PCI, 0 ) == RIO_SUCCESS) {
+				rio_dprintk (RIO_DEBUG_INIT, ("Board has passed test\n");
+				rio_dprintk (RIO_DEBUG_INIT, ("Paddr 0x%x. Caddr 0x%x. Mode 0x%x.\n", Paddr, Caddr, Mode);
+
+				/*
+				** Board has passed its scrub test. Fill in all the
+				** transient stuff.
+				*/
+				p->RIOHosts[p->RIONumHosts].Slot	   = 0;
+				p->RIOHosts[p->RIONumHosts].Ivec	   = Ivec + 32;
+				p->RIOHosts[p->RIONumHosts].Type	   = RIO_PCI;
+				p->RIOHosts[p->RIONumHosts].Copy	   = rio_pcicopy; 
+				p->RIOHosts[p->RIONumHosts].PaddrP	   = Paddr;
+				p->RIOHosts[p->RIONumHosts].Caddr	   = Caddr;
+				p->RIOHosts[p->RIONumHosts].CardP	   = (struct DpRam *)Caddr;
+				p->RIOHosts[p->RIONumHosts].Mode	   = Mode;
+
+#if 0
+				WBYTE(p->RIOHosts[p->RIONumHosts].Control, 
+						BOOT_FROM_RAM | EXTERNAL_BUS_OFF | 
+						p->RIOHosts[p->RIONumHosts].Mode | 
+						INTERRUPT_DISABLE );
+				WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt,0xff);
+				WBYTE(p->RIOHosts[p->RIONumHosts].Control,
+						BOOT_FROM_RAM | EXTERNAL_BUS_OFF | 
+						p->RIOHosts[p->RIONumHosts].Mode |
+						INTERRUPT_DISABLE );
+				WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt,0xff);
+#else
+				WBYTE(p->RIOHosts[p->RIONumHosts].ResetInt, 0xff);
+#endif
+				p->RIOHosts[p->RIONumHosts].UniqueNum  =
+					((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[0])&0xFF)<<0)|
+					((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[1])&0xFF)<<8)|
+					((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[2])&0xFF)<<16)|
+					((RBYTE(p->RIOHosts[p->RIONumHosts].Unique[3])&0xFF)<<24);
+
+				rio_dprintk (RIO_DEBUG_INIT, "Unique no 0x%x.\n", 
+				    p->RIOHosts[p->RIONumHosts].UniqueNum);
+
+				p->RIOLastPCISearch = RIO_SUCCESS;
+				p->RIONumHosts++;
+			}
+		}
+		slot++;
+	}
+
+	if ( slot >= MAX_PCI_SLOT ) {
+		rio_dprintk (RIO_DEBUG_INIT,  "All %d PCI slots have tested for RIO cards !!!\n",
+			     MAX_PCI_SLOT);
+	}
+
+
+	/*
+	** I don't think we want to do this anymore
+	**
+
+	if (!p->RIOLastPCISearch == RIO_FAIL ) {
+		p->RIOFailed++;
+	}
+
+	**
+	*/
+}
+
+#ifdef FUTURE_RELEASE
+void riohalt( void )
+{
+	int host;
+	for ( host=0; host<p->RIONumHosts; host++ )
+	{
+		rio_dprintk (RIO_DEBUG_INIT, "Stop host %d\n", host);
+		(void)RIOBoardTest( p->RIOHosts[host].PaddrP, p->RIOHosts[host].Caddr, p->RIOHosts[host].Type,p->RIOHosts[host].Slot );
+	}
+}
+#endif
+#endif
+
+static	uchar	val[] = {
+#ifdef VERY_LONG_TEST
+	  0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
+	  0xa5, 0xff, 0x5a, 0x00, 0xff, 0xc9, 0x36, 
+#endif
+	  0xff, 0x00, 0x00 };
+
+#define	TEST_END sizeof(val)
+
+/*
+** RAM test a board. 
+** Nothing too complicated, just enough to check it out.
+*/
+int
+RIOBoardTest(paddr, caddr, type, slot)
+paddr_t	paddr;
+caddr_t	caddr;
+uchar	type;
+int		slot;
+{
+	struct DpRam *DpRam = (struct DpRam *)caddr;
+	char *ram[4];
+	int  size[4];
+	int  op, bank;
+	int  nbanks;
+
+	rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Reset host type=%d, DpRam=0x%x, slot=%d\n",
+			type,(int)DpRam, slot);
+
+	RIOHostReset(type, DpRam, slot);
+
+	/*
+	** Scrub the memory. This comes in several banks:
+	** DPsram1	- 7000h bytes
+	** DPsram2	- 200h  bytes
+	** DPsram3	- 7000h bytes
+	** scratch	- 1000h bytes
+	*/
+
+	rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Setup ram/size arrays\n");
+
+	size[0] = DP_SRAM1_SIZE;
+	size[1] = DP_SRAM2_SIZE;
+	size[2] = DP_SRAM3_SIZE;
+	size[3] = DP_SCRATCH_SIZE;
+
+	ram[0] = (char *)&DpRam->DpSram1[0];
+	ram[1] = (char *)&DpRam->DpSram2[0];
+	ram[2] = (char *)&DpRam->DpSram3[0];
+	nbanks = (type == RIO_PCI) ? 3 : 4;
+	if (nbanks == 4)
+		ram[3] = (char *)&DpRam->DpScratch[0];
+
+
+	if (nbanks == 3) {
+		rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Memory: 0x%x(0x%x), 0x%x(0x%x), 0x%x(0x%x)\n",
+				(int)ram[0], size[0], (int)ram[1], size[1], (int)ram[2], size[2]);
+	} else {
+		rio_dprintk (RIO_DEBUG_INIT, "RIO-init: 0x%x(0x%x), 0x%x(0x%x), 0x%x(0x%x), 0x%x(0x%x)\n",
+			(int)ram[0], size[0], (int)ram[1], size[1], (int)ram[2], size[2], (int)ram[3], 
+					size[3]);
+	}
+
+	/*
+	** This scrub operation will test for crosstalk between
+	** banks. TEST_END is a magic number, and relates to the offset
+	** within the 'val' array used by Scrub.
+	*/
+	for (op=0; op<TEST_END; op++) {
+		for (bank=0; bank<nbanks; bank++) {
+			if (RIOScrub(op, (BYTE *)ram[bank], size[bank]) == RIO_FAIL) {
+				rio_dprintk (RIO_DEBUG_INIT, "RIO-init: RIOScrub band %d, op %d failed\n", 
+							bank, op);
+				return RIO_FAIL;
+			}
+		}
+	}
+
+	rio_dprintk (RIO_DEBUG_INIT, "Test completed\n");
+	return RIO_SUCCESS;
+}
+
+
+/*
+** Scrub an area of RAM.
+** Define PRETEST and POSTTEST for a more thorough checking of the
+** state of the memory.
+** Call with op set to an index into the above 'val' array to determine
+** which value will be written into memory.
+** Call with op set to zero means that the RAM will not be read and checked
+** before it is written.
+** Call with op not zero, and the RAM will be read and compated with val[op-1]
+** to check that the data from the previous phase was retained.
+*/
+static int
+RIOScrub(op, ram, size)
+int		op;
+BYTE *	ram;
+int		size; 
+{
+	int				off;
+	unsigned char	oldbyte;
+	unsigned char	newbyte;
+	unsigned char	invbyte;
+	unsigned short	oldword;
+	unsigned short	newword;
+	unsigned short	invword;
+	unsigned short	swapword;
+
+	if (op) {
+		oldbyte = val[op-1];
+		oldword = oldbyte | (oldbyte<<8);
+	} else
+	  oldbyte = oldword = 0; /* Tell the compiler we've initilalized them. */
+	newbyte = val[op];
+	newword = newbyte | (newbyte<<8);
+	invbyte = ~newbyte;
+	invword = invbyte | (invbyte<<8);
+
+	/*
+	** Check that the RAM contains the value that should have been left there
+	** by the previous test (not applicable for pass zero)
+	*/
+	if (op) {
+		for (off=0; off<size; off++) {
+			if (RBYTE(ram[off]) != oldbyte) {
+				rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Byte Pre Check 1: BYTE at offset 0x%x should have been=%x, was=%x\n", off, oldbyte, RBYTE(ram[off]));
+				return RIO_FAIL;
+			}
+		}
+		for (off=0; off<size; off+=2) {
+			if (*(ushort *)&ram[off] != oldword) {
+				rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Word Pre Check: WORD at offset 0x%x should have been=%x, was=%x\n",off,oldword,*(ushort *)&ram[off]);
+				rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Word Pre Check: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+				return RIO_FAIL;
+			}
+		}
+	}
+
+	/*
+	** Now write the INVERSE of the test data into every location, using
+	** BYTE write operations, first checking before each byte is written
+	** that the location contains the old value still, and checking after
+	** the write that the location contains the data specified - this is
+	** the BYTE read/write test.
+	*/
+	for (off=0; off<size; off++) {
+		if (op && (RBYTE(ram[off]) != oldbyte)) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Byte Pre Check 2: BYTE at offset 0x%x should have been=%x, was=%x\n", off, oldbyte, RBYTE(ram[off]));
+			return RIO_FAIL;
+		}
+		WBYTE(ram[off],invbyte);
+		if (RBYTE(ram[off]) != invbyte) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Byte Inv Check: BYTE at offset 0x%x should have been=%x, was=%x\n", off, invbyte, RBYTE(ram[off]));
+			return RIO_FAIL;
+		}
+	}
+
+	/*
+	** now, use WORD operations to write the test value into every location,
+	** check as before that the location contains the previous test value
+	** before overwriting, and that it contains the data value written
+	** afterwards.
+	** This is the WORD operation test.
+	*/
+	for (off=0; off<size; off+=2) {
+		if (*(ushort *)&ram[off] != invword) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Word Inv Check: WORD at offset 0x%x should have been=%x, was=%x\n", off, invword, *(ushort *)&ram[off]);
+		rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Word Inv Check: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+			return RIO_FAIL;
+		}
+
+		*(ushort *)&ram[off] = newword;
+		if ( *(ushort *)&ram[off] != newword ) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Word Check 1: WORD at offset 0x%x should have been=%x, was=%x\n", off, newword, *(ushort *)&ram[off]);
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Word Check 1: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+			return RIO_FAIL;
+		}
+	}
+
+	/*
+	** now run through the block of memory again, first in byte mode
+	** then in word mode, and check that all the locations contain the
+	** required test data.
+	*/
+	for (off=0; off<size; off++) {
+		if (RBYTE(ram[off]) != newbyte) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Byte Check: BYTE at offset 0x%x should have been=%x, was=%x\n", off, newbyte, RBYTE(ram[off]));
+			return RIO_FAIL;
+		}
+	}
+
+	for (off=0; off<size; off+=2) {
+		if ( *(ushort *)&ram[off] != newword ) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Word Check 2: WORD at offset 0x%x should have been=%x, was=%x\n", off, newword, *(ushort *)&ram[off]);
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: Post Word Check 2: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+			return RIO_FAIL;
+		}
+	}
+
+	/*
+	** time to check out byte swapping errors
+	*/
+	swapword = invbyte | (newbyte << 8);
+
+	for (off=0; off<size; off+=2) {
+		WBYTE(ram[off],invbyte);
+		WBYTE(ram[off+1],newbyte);
+	}
+
+	for ( off=0; off<size; off+=2 ) {
+		if (*(ushort *)&ram[off] != swapword) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: SwapWord Check 1: WORD at offset 0x%x should have been=%x, was=%x\n", off, swapword, *((ushort *)&ram[off]));
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: SwapWord Check 1: BYTE at offset 0x%x is %x BYTE at offset 0x%x is %x\n", off, RBYTE(ram[off]), off+1, RBYTE(ram[off+1]));
+			return RIO_FAIL;
+		}
+		*((ushort *)&ram[off]) = ~swapword;
+	}
+
+	for (off=0; off<size; off+=2) {
+		if (RBYTE(ram[off]) != newbyte) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: SwapWord Check 2: BYTE at offset 0x%x should have been=%x, was=%x\n", off, newbyte, RBYTE(ram[off]));
+			return RIO_FAIL;
+		}
+		if (RBYTE(ram[off+1]) != invbyte) {
+			rio_dprintk (RIO_DEBUG_INIT, "RIO-init: SwapWord Check 2: BYTE at offset 0x%x should have been=%x, was=%x\n", off+1, invbyte, RBYTE(ram[off+1]));
+			return RIO_FAIL;
+		}
+		*((ushort *)&ram[off]) = newword;
+	}
+	return RIO_SUCCESS;
+}
+
+/*
+** try to ensure that every host is either in polled mode
+** or is in interrupt mode. Only allow interrupt mode if
+** all hosts can interrupt (why?)
+** and force into polled mode if told to. Patch up the
+** interrupt vector & salute The Queen when you've done.
+*/
+#if 0
+static void
+RIOAllocateInterrupts(p)
+struct rio_info *	p;
+{
+	int Host;
+
+	/*
+	** Easy case - if we have been told to poll, then we poll.
+	*/
+	if (p->mode & POLLED_MODE) {
+		RIOStopInterrupts(p, 0, 0);
+		return;
+	}
+
+	/*
+	** check - if any host has been set to polled mode, then all must be.
+	*/
+	for (Host=0; Host<p->RIONumHosts; Host++) {
+		if ( (p->RIOHosts[Host].Type != RIO_AT) &&
+				(p->RIOHosts[Host].Ivec == POLLED) ) {
+			RIOStopInterrupts(p, 1, Host );
+			return;
+		}
+	}
+	for (Host=0; Host<p->RIONumHosts; Host++) {
+		if (p->RIOHosts[Host].Type == RIO_AT) {
+			if ( (p->RIOHosts[Host].Ivec - 32) == 0) {
+				RIOStopInterrupts(p, 2, Host );
+				return;
+			}
+		}
+	}
+}
+
+/*
+** something has decided that we can't be doing with these
+** new-fangled interrupt thingies. Set everything up to just
+** poll.
+*/
+static void
+RIOStopInterrupts(p, Reason, Host)
+struct rio_info *	p;
+int	Reason;
+int	Host; 
+{
+#ifdef FUTURE_RELEASE
+	switch (Reason) {
+		case 0:	/* forced into polling by rio_polled */
+			break;
+		case 1:	/* SCU has set 'Host' into polled mode */
+			break;
+		case 2:	/* there aren't enough interrupt vectors for 'Host' */
+			break;
+	}
+#endif
+
+	for (Host=0; Host<p->RIONumHosts; Host++ ) {
+		struct Host *HostP = &p->RIOHosts[Host];
+
+		switch (HostP->Type) {
+			case RIO_AT:
+				/*
+				** The AT host has it's interrupts disabled by clearing the
+				** int_enable bit.
+				*/
+				HostP->Mode &= ~INTERRUPT_ENABLE;
+				HostP->Ivec = POLLED;
+				break;
+#ifdef FUTURE_RELEASE
+			case RIO_EISA:
+				/*
+				** The EISA host has it's interrupts disabled by setting the
+				** Ivec to zero
+				*/
+				HostP->Ivec = POLLED;
+				break;
+#endif
+			case RIO_PCI:
+				/*
+				** The PCI host has it's interrupts disabled by clearing the
+				** int_enable bit, like a regular host card.
+				*/
+				HostP->Mode &= ~RIO_PCI_INT_ENABLE;
+				HostP->Ivec = POLLED;
+				break;
+#ifdef FUTURE_RELEASE
+			case RIO_MCA:
+				/*
+				** There's always one, isn't there?
+				** The MCA host card cannot have it's interrupts disabled.
+				*/
+				RIOPatchVec(HostP);
+				break;
+#endif
+		}
+	}
+}
+
+/*
+** This function is called at init time to setup the data structures.
+*/
+void
+RIOAllocDataStructs(p)
+struct rio_info *	p;
+{
+	int	port,
+		host,
+		tm;
+
+	p->RIOPortp = (struct Port *)sysbrk(RIO_PORTS * sizeof(struct Port));
+	if (!p->RIOPortp) {
+		rio_dprintk (RIO_DEBUG_INIT, "RIO-init: No memory for port structures\n");
+		p->RIOFailed++;
+		return;
+	} 
+	bzero( p->RIOPortp, sizeof(struct Port) * RIO_PORTS );
+	rio_dprintk (RIO_DEBUG_INIT,  "RIO-init: allocated and cleared memory for port structs\n");
+	rio_dprintk (RIO_DEBUG_INIT,  "First RIO port struct @0x%x, size=0x%x bytes\n",
+	    (int)p->RIOPortp, sizeof(struct Port));
+
+	for( port=0; port<RIO_PORTS; port++ ) {
+		p->RIOPortp[port].PortNum = port;
+		p->RIOPortp[port].TtyP = &p->channel[port];
+		sreset (p->RIOPortp[port].InUse);	/* Let the first guy uses it */
+		p->RIOPortp[port].portSem = -1;	/* Let the first guy takes it */
+		p->RIOPortp[port].ParamSem = -1;	/* Let the first guy takes it */
+		p->RIOPortp[port].timeout_id = 0;	/* Let the first guy takes it */
+	}
+
+	p->RIOHosts = (struct Host *)sysbrk(RIO_HOSTS * sizeof(struct Host));
+	if (!p->RIOHosts) {
+		rio_dprintk (RIO_DEBUG_INIT, "RIO-init: No memory for host structures\n");
+		p->RIOFailed++;
+		return;
+	}
+	bzero(p->RIOHosts, sizeof(struct Host)*RIO_HOSTS);
+	rio_dprintk (RIO_DEBUG_INIT,  "RIO-init: allocated and cleared memory for host structs\n");
+	rio_dprintk (RIO_DEBUG_INIT,  "First RIO host struct @0x%x, size=0x%x bytes\n",
+	    (int)p->RIOHosts, sizeof(struct Host));
+
+	for( host=0; host<RIO_HOSTS; host++ ) {
+		spin_lock_init (&p->RIOHosts[host].HostLock);
+		p->RIOHosts[host].timeout_id = 0; /* Let the first guy takes it */
+	}
+	/*
+	** check that the buffer size is valid, round down to the next power of
+	** two if necessary; if the result is zero, then, hey, no double buffers.
+	*/
+	for ( tm = 1; tm && tm <= p->RIOConf.BufferSize; tm <<= 1 )
+		;
+	tm >>= 1;
+	p->RIOBufferSize = tm;
+	p->RIOBufferMask = tm ? tm - 1 : 0;
+}
+
+/*
+** this function gets called whenever the data structures need to be
+** re-setup, for example, after a riohalt (why did I ever invent it?)
+*/
+void
+RIOSetupDataStructs(p)
+struct rio_info	* p;
+{
+	int host, entry, rup;
+
+	for ( host=0; host<RIO_HOSTS; host++ ) {
+		struct Host *HostP = &p->RIOHosts[host];
+		for ( entry=0; entry<LINKS_PER_UNIT; entry++ ) {
+			HostP->Topology[entry].Unit = ROUTE_DISCONNECT;
+			HostP->Topology[entry].Link = NO_LINK;
+		}
+		bcopy("HOST X", HostP->Name, 7);
+		HostP->Name[5] = '1'+host;
+		for (rup=0; rup<(MAX_RUP + LINKS_PER_UNIT); rup++) {
+			if (rup < MAX_RUP) {
+				for (entry=0; entry<LINKS_PER_UNIT; entry++ ) {
+					HostP->Mapping[rup].Topology[entry].Unit = ROUTE_DISCONNECT;
+					HostP->Mapping[rup].Topology[entry].Link = NO_LINK;
+				}
+				RIODefaultName(p, HostP, rup);
+			}
+			spin_lock_init(&HostP->UnixRups[rup].RupLock);
+		}
+	}
+}
+#endif
+
+int
+RIODefaultName(p, HostP, UnitId)
+struct rio_info *	p;
+struct Host *	HostP;
+uint			UnitId;
+{
+#ifdef CHECK
+	CheckHost( Host );
+	CheckUnitId( UnitId );
+#endif
+	bcopy("UNKNOWN RTA X-XX",HostP->Mapping[UnitId].Name,17);
+	HostP->Mapping[UnitId].Name[12]='1'+(HostP-p->RIOHosts);
+	if ((UnitId+1) > 9) {
+		HostP->Mapping[UnitId].Name[14]='0'+((UnitId+1)/10);
+		HostP->Mapping[UnitId].Name[15]='0'+((UnitId+1)%10);
+	}
+	else {
+		HostP->Mapping[UnitId].Name[14]='1'+UnitId;
+		HostP->Mapping[UnitId].Name[15]=0;
+	}
+	return 0;
+}
+
+#define RIO_RELEASE	"Linux"
+#define RELEASE_ID	"1.0"
+
+#if 0
+static int
+RIOReport(p)
+struct rio_info *	p;
+{
+	char *	RIORelease = RIO_RELEASE;
+	char *	RIORelID = RELEASE_ID;
+	int		host;
+
+	rio_dprintk (RIO_DEBUG_INIT, "RIO : Release: %s ID: %s\n", RIORelease, RIORelID);
+
+	if ( p->RIONumHosts==0 ) {
+		rio_dprintk (RIO_DEBUG_INIT, "\nNo Hosts configured\n");
+		return(0);
+	}
+
+	for ( host=0; host < p->RIONumHosts; host++ ) {
+		struct Host *HostP = &p->RIOHosts[host];
+		switch ( HostP->Type ) {
+			case RIO_AT:
+				rio_dprintk (RIO_DEBUG_INIT, "AT BUS : found the card at 0x%x\n", HostP->PaddrP);
+		}
+	}
+	return 0;
+}
+#endif
+
+static struct rioVersion	stVersion;
+
+struct rioVersion *
+RIOVersid(void)
+{
+    strlcpy(stVersion.version, "RIO driver for linux V1.0",
+	    sizeof(stVersion.version));
+    strlcpy(stVersion.buildDate, __DATE__,
+	    sizeof(stVersion.buildDate));
+
+    return &stVersion;
+}
+
+#if 0
+int
+RIOMapin(paddr, size, vaddr)
+paddr_t		paddr;
+int			size;
+caddr_t *	vaddr;
+{
+	*vaddr = (caddr_t)permap( (long)paddr, size);
+	return ((int)*vaddr);
+}
+
+void
+RIOMapout(paddr, size, vaddr)
+paddr_t		paddr;
+long		size;
+caddr_t 	vaddr;
+{
+}
+#endif
+
+
+void
+RIOHostReset(Type, DpRamP, Slot)
+uint Type;
+volatile struct DpRam *DpRamP;
+uint Slot; 
+{
+	/*
+	** Reset the Tpu
+	*/
+	rio_dprintk (RIO_DEBUG_INIT,  "RIOHostReset: type 0x%x", Type);
+	switch ( Type ) {
+		case RIO_AT:
+			rio_dprintk (RIO_DEBUG_INIT, " (RIO_AT)\n");
+			WBYTE(DpRamP->DpControl,  BOOT_FROM_RAM | EXTERNAL_BUS_OFF | 
+					  INTERRUPT_DISABLE | BYTE_OPERATION |
+					  SLOW_LINKS | SLOW_AT_BUS);
+			WBYTE(DpRamP->DpResetTpu, 0xFF);
+			rio_udelay (3);
+
+			rio_dprintk (RIO_DEBUG_INIT,  "RIOHostReset: Don't know if it worked. Try reset again\n");
+			WBYTE(DpRamP->DpControl,  BOOT_FROM_RAM | EXTERNAL_BUS_OFF |
+					  INTERRUPT_DISABLE | BYTE_OPERATION |
+					  SLOW_LINKS | SLOW_AT_BUS);
+			WBYTE(DpRamP->DpResetTpu, 0xFF);
+			rio_udelay (3);
+			break;
+#ifdef FUTURE_RELEASE
+	case RIO_EISA:
+	/*
+	** Bet this doesn't work!
+	*/
+	OUTBZ( Slot, EISA_CONTROL_PORT,
+		EISA_TP_RUN		| EISA_TP_BUS_DISABLE   |
+		EISA_TP_SLOW_LINKS | EISA_TP_BOOT_FROM_RAM );
+	OUTBZ( Slot, EISA_CONTROL_PORT,
+		EISA_TP_RESET	  | EISA_TP_BUS_DISABLE   | 
+		EISA_TP_SLOW_LINKS | EISA_TP_BOOT_FROM_RAM );
+	suspend( 3 );
+	OUTBZ( Slot, EISA_CONTROL_PORT,
+		EISA_TP_RUN		| EISA_TP_BUS_DISABLE   | 
+		EISA_TP_SLOW_LINKS | EISA_TP_BOOT_FROM_RAM );
+	break;
+	case RIO_MCA:
+	WBYTE(DpRamP->DpControl  , McaTpBootFromRam | McaTpBusDisable );
+	WBYTE(DpRamP->DpResetTpu , 0xFF );
+	suspend( 3 );
+	WBYTE(DpRamP->DpControl  , McaTpBootFromRam | McaTpBusDisable );
+	WBYTE(DpRamP->DpResetTpu , 0xFF );
+	suspend( 3 );
+		break;
+#endif
+	case RIO_PCI:
+		rio_dprintk (RIO_DEBUG_INIT, " (RIO_PCI)\n");
+		DpRamP->DpControl  = RIO_PCI_BOOT_FROM_RAM;
+		DpRamP->DpResetInt = 0xFF;
+		DpRamP->DpResetTpu = 0xFF;
+		rio_udelay (100);
+		/* for (i=0; i<6000; i++);  */
+		/* suspend( 3 ); */
+		break;
+#ifdef FUTURE_RELEASE
+	default:
+	Rprintf(RIOMesgNoSupport,Type,DpRamP,Slot);
+	return;
+#endif
+
+	default:
+		rio_dprintk (RIO_DEBUG_INIT, " (UNKNOWN)\n");
+		break;
+	}
+	return;
+}
diff --git a/drivers/char/rio/riointr.c b/drivers/char/rio/riointr.c
new file mode 100644
index 0000000..e42e7b5
--- /dev/null
+++ b/drivers/char/rio/riointr.c
@@ -0,0 +1,951 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: riointr.c
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 10:33:44
+**	Retrieved	: 11/6/98 10:33:49
+**
+**  ident @(#)riointr.c	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_riointr_c_sccs_ = "@(#)riointr.c	1.2";
+#endif
+
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+#include <linux/delay.h>
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+
+
+static void RIOReceive(struct rio_info *, struct Port *);
+
+
+static char *firstchars (char *p, int nch)
+{
+  static char buf[2][128];
+  static int t=0;
+  t = ! t;
+  memcpy (buf[t], p, nch);
+  buf[t][nch] = 0;
+  return buf[t];
+}
+
+
+#define	INCR( P, I )	((P) = (((P)+(I)) & p->RIOBufferMask))
+/* Enable and start the transmission of packets */
+void
+RIOTxEnable(en)
+char *		en;
+{
+  struct Port *	PortP;
+  struct rio_info *p;
+  struct tty_struct* tty;
+  int c;
+  struct PKT *	PacketP;
+  unsigned long flags;
+
+  PortP = (struct Port *)en; 
+  p = (struct rio_info *)PortP->p;
+  tty = PortP->gs.tty;
+
+
+  rio_dprintk (RIO_DEBUG_INTR, "tx port %d: %d chars queued.\n", 
+	      PortP->PortNum, PortP->gs.xmit_cnt);
+
+  if (!PortP->gs.xmit_cnt) return;
+  
+
+  /* This routine is an order of magnitude simpler than the specialix
+     version. One of the disadvantages is that this version will send
+     an incomplete packet (usually 64 bytes instead of 72) once for
+     every 4k worth of data. Let's just say that this won't influence
+     performance significantly..... */
+
+  rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+  while (can_add_transmit( &PacketP, PortP )) {
+    c = PortP->gs.xmit_cnt;
+    if (c > PKT_MAX_DATA_LEN) c = PKT_MAX_DATA_LEN;
+
+    /* Don't copy past the end of the source buffer */
+    if (c > SERIAL_XMIT_SIZE - PortP->gs.xmit_tail) 
+      c = SERIAL_XMIT_SIZE - PortP->gs.xmit_tail;
+
+    { int t;
+    t = (c > 10)?10:c;
+    
+    rio_dprintk (RIO_DEBUG_INTR, "rio: tx port %d: copying %d chars: %s - %s\n", 
+		 PortP->PortNum, c, 
+		 firstchars (PortP->gs.xmit_buf + PortP->gs.xmit_tail      , t),
+		 firstchars (PortP->gs.xmit_buf + PortP->gs.xmit_tail + c-t, t));
+    }
+    /* If for one reason or another, we can't copy more data, 
+       we're done! */
+    if (c == 0) break;
+
+    rio_memcpy_toio (PortP->HostP->Caddr, (caddr_t)PacketP->data, 
+		 PortP->gs.xmit_buf + PortP->gs.xmit_tail, c);
+    /*    udelay (1); */
+
+    writeb (c, &(PacketP->len));
+    if (!( PortP->State & RIO_DELETED ) ) {
+      add_transmit ( PortP );
+      /*
+      ** Count chars tx'd for port statistics reporting
+      */
+      if ( PortP->statsGather )
+	PortP->txchars += c;
+    }
+    PortP->gs.xmit_tail = (PortP->gs.xmit_tail + c) & (SERIAL_XMIT_SIZE-1);
+    PortP->gs.xmit_cnt -= c;
+  }
+
+  rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+
+  if (PortP->gs.xmit_cnt <= (PortP->gs.wakeup_chars + 2*PKT_MAX_DATA_LEN)) {
+    rio_dprintk (RIO_DEBUG_INTR, "Waking up.... ldisc:%d (%d/%d)....",
+		 (int)(PortP->gs.tty->flags & (1 << TTY_DO_WRITE_WAKEUP)),
+		 PortP->gs.wakeup_chars, PortP->gs.xmit_cnt); 
+    if ((PortP->gs.tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+	PortP->gs.tty->ldisc.write_wakeup)
+      (PortP->gs.tty->ldisc.write_wakeup)(PortP->gs.tty);
+    rio_dprintk (RIO_DEBUG_INTR, "(%d/%d)\n",
+		PortP->gs.wakeup_chars, PortP->gs.xmit_cnt); 
+    wake_up_interruptible(&PortP->gs.tty->write_wait);
+  }
+
+}
+
+
+/*
+** RIO Host Service routine. Does all the work traditionally associated with an
+** interrupt.
+*/
+static int	RupIntr;
+static int	RxIntr;
+static int	TxIntr;
+void
+RIOServiceHost(p, HostP, From)
+struct rio_info *	p;
+struct Host *HostP;
+int From; 
+{
+  rio_spin_lock (&HostP->HostLock);
+  if ( (HostP->Flags & RUN_STATE) != RC_RUNNING ) { 
+    static int t =0;
+    rio_spin_unlock (&HostP->HostLock); 
+    if ((t++ % 200) == 0)
+      rio_dprintk (RIO_DEBUG_INTR, "Interrupt but host not running. flags=%x.\n", (int)HostP->Flags);
+    return;
+  }
+  rio_spin_unlock (&HostP->HostLock); 
+
+  if ( RWORD( HostP->ParmMapP->rup_intr ) ) {
+    WWORD( HostP->ParmMapP->rup_intr , 0 );
+    p->RIORupCount++;
+    RupIntr++;
+    rio_dprintk (RIO_DEBUG_INTR, "rio: RUP interrupt on host %d\n", HostP-p->RIOHosts);
+    RIOPollHostCommands(p, HostP );
+  }
+
+  if ( RWORD( HostP->ParmMapP->rx_intr ) ) {
+    int port;
+
+    WWORD( HostP->ParmMapP->rx_intr , 0 );
+    p->RIORxCount++;
+    RxIntr++;
+
+    rio_dprintk (RIO_DEBUG_INTR, "rio: RX interrupt on host %d\n", HostP-p->RIOHosts);
+    /*
+    ** Loop through every port. If the port is mapped into
+    ** the system ( i.e. has /dev/ttyXXXX associated ) then it is
+    ** worth checking. If the port isn't open, grab any packets
+    ** hanging on its receive queue and stuff them on the free
+    ** list; check for commands on the way.
+    */
+    for ( port=p->RIOFirstPortsBooted; 
+	  port<p->RIOLastPortsBooted+PORTS_PER_RTA; port++ ) {
+      struct Port *PortP = p->RIOPortp[port];
+      struct tty_struct *ttyP;
+      struct PKT *PacketP;
+		
+      /*
+      ** not mapped in - most of the RIOPortp[] information
+      ** has not been set up!
+      ** Optimise: ports come in bundles of eight.
+      */
+      if ( !PortP->Mapped ) {
+	port += 7;
+	continue; /* with the next port */
+      }
+
+      /*
+      ** If the host board isn't THIS host board, check the next one.
+      ** optimise: ports come in bundles of eight.
+      */
+      if ( PortP->HostP != HostP ) {
+	port += 7;
+	continue;
+      }
+
+      /*
+      ** Let us see - is the port open? If not, then don't service it.
+      */
+      if ( !( PortP->PortState & PORT_ISOPEN ) ) {
+	continue;
+      }
+
+      /*
+      ** find corresponding tty structure. The process of mapping
+      ** the ports puts these here.
+      */
+      ttyP = PortP->gs.tty;
+
+      /*
+      ** Lock the port before we begin working on it.
+      */
+      rio_spin_lock(&PortP->portSem);
+
+      /*
+      ** Process received data if there is any.
+      */
+      if ( can_remove_receive( &PacketP, PortP ) )
+	RIOReceive(p, PortP);
+
+      /*
+      ** If there is no data left to be read from the port, and
+      ** it's handshake bit is set, then we must clear the handshake,
+      ** so that that downstream RTA is re-enabled.
+      */
+      if ( !can_remove_receive( &PacketP, PortP ) && 
+	   ( RWORD( PortP->PhbP->handshake )==PHB_HANDSHAKE_SET ) ) {
+				/*
+				** MAGIC! ( Basically, handshake the RX buffer, so that
+				** the RTAs upstream can be re-enabled. )
+				*/
+	rio_dprintk (RIO_DEBUG_INTR, "Set RX handshake bit\n");
+	WWORD( PortP->PhbP->handshake, 
+	       PHB_HANDSHAKE_SET|PHB_HANDSHAKE_RESET );
+      }
+      rio_spin_unlock(&PortP->portSem);
+    }
+  }
+
+  if ( RWORD( HostP->ParmMapP->tx_intr ) ) {
+    int port;
+
+    WWORD( HostP->ParmMapP->tx_intr , 0);
+
+    p->RIOTxCount++;
+    TxIntr++;
+    rio_dprintk (RIO_DEBUG_INTR, "rio: TX interrupt on host %d\n", HostP-p->RIOHosts);
+
+    /*
+    ** Loop through every port.
+    ** If the port is mapped into the system ( i.e. has /dev/ttyXXXX
+    ** associated ) then it is worth checking.
+    */
+    for ( port=p->RIOFirstPortsBooted; 
+	  port<p->RIOLastPortsBooted+PORTS_PER_RTA; port++ ) {
+      struct Port *PortP = p->RIOPortp[port];
+      struct tty_struct *ttyP;
+      struct PKT *PacketP;
+
+      /*
+      ** not mapped in - most of the RIOPortp[] information
+      ** has not been set up!
+      */
+      if ( !PortP->Mapped ) {
+	port += 7;
+	continue; /* with the next port */
+      }
+
+      /*
+      ** If the host board isn't running, then its data structures
+      ** are no use to us - continue quietly.
+      */
+      if ( PortP->HostP != HostP ) {
+	port += 7;
+	continue; /* with the next port */
+      }
+
+      /*
+      ** Let us see - is the port open? If not, then don't service it.
+      */
+      if ( !( PortP->PortState & PORT_ISOPEN ) ) {
+	continue;
+      }
+
+      rio_dprintk (RIO_DEBUG_INTR, "rio: Looking into port %d.\n", port);
+      /*
+      ** Lock the port before we begin working on it.
+      */
+      rio_spin_lock(&PortP->portSem);
+
+      /*
+      ** If we can't add anything to the transmit queue, then
+      ** we need do none of this processing.
+      */
+      if ( !can_add_transmit( &PacketP, PortP ) ) {
+	rio_dprintk (RIO_DEBUG_INTR, "Can't add to port, so skipping.\n");
+	rio_spin_unlock(&PortP->portSem);
+	continue;
+      }
+
+      /*
+      ** find corresponding tty structure. The process of mapping
+      ** the ports puts these here.
+      */
+      ttyP = PortP->gs.tty;
+      /* If ttyP is NULL, the port is getting closed. Forget about it. */
+      if (!ttyP) {
+	rio_dprintk (RIO_DEBUG_INTR, "no tty, so skipping.\n");
+	rio_spin_unlock(&PortP->portSem);
+	continue;
+      }
+      /*
+      ** If there is more room available we start up the transmit
+      ** data process again. This can be direct I/O, if the cookmode
+      ** is set to COOK_RAW or COOK_MEDIUM, or will be a call to the
+      ** riotproc( T_OUTPUT ) if we are in COOK_WELL mode, to fetch
+      ** characters via the line discipline. We must always call
+      ** the line discipline,
+      ** so that user input characters can be echoed correctly.
+      **
+      ** ++++ Update +++++
+      ** With the advent of double buffering, we now see if
+      ** TxBufferOut-In is non-zero. If so, then we copy a packet
+      ** to the output place, and set it going. If this empties
+      ** the buffer, then we must issue a wakeup( ) on OUT.
+      ** If it frees space in the buffer then we must issue
+      ** a wakeup( ) on IN.
+      **
+      ** ++++ Extra! Extra! If PortP->WflushFlag is set, then we
+      ** have to send a WFLUSH command down the PHB, to mark the
+      ** end point of a WFLUSH. We also need to clear out any
+      ** data from the double buffer! ( note that WflushFlag is a
+      ** *count* of the number of WFLUSH commands outstanding! )
+      **
+      ** ++++ And there's more!
+      ** If an RTA is powered off, then on again, and rebooted,
+      ** whilst it has ports open, then we need to re-open the ports.
+      ** ( reasonable enough ). We can't do this when we spot the
+      ** re-boot, in interrupt time, because the queue is probably
+      ** full. So, when we come in here, we need to test if any
+      ** ports are in this condition, and re-open the port before
+      ** we try to send any more data to it. Now, the re-booted
+      ** RTA will be discarding packets from the PHB until it
+      ** receives this open packet, but don't worry tooo much
+      ** about that. The one thing that is interesting is the
+      ** combination of this effect and the WFLUSH effect!
+      */
+      /* For now don't handle RTA reboots. -- REW. 
+	 Reenabled. Otherwise RTA reboots didn't work. Duh. -- REW */
+      if ( PortP->MagicFlags ) {
+#if 1
+	if ( PortP->MagicFlags & MAGIC_REBOOT ) {
+	  /*
+	  ** well, the RTA has been rebooted, and there is room
+	  ** on its queue to add the open packet that is required.
+	  **
+	  ** The messy part of this line is trying to decide if
+	  ** we need to call the Param function as a tty or as
+	  ** a modem.
+	  ** DONT USE CLOCAL AS A TEST FOR THIS!
+	  **
+	  ** If we can't param the port, then move on to the
+	  ** next port.
+	  */
+	  PortP->InUse = NOT_INUSE;
+
+	  rio_spin_unlock(&PortP->portSem);
+	  if ( RIOParam(PortP, OPEN, ((PortP->Cor2Copy & 
+				       (COR2_RTSFLOW|COR2_CTSFLOW ) )== 
+				      (COR2_RTSFLOW|COR2_CTSFLOW ) ) ? 
+			TRUE : FALSE, DONT_SLEEP ) == RIO_FAIL ) {
+	    continue; /* with next port */
+	  }
+	  rio_spin_lock(&PortP->portSem);
+	  PortP->MagicFlags &= ~MAGIC_REBOOT;
+	}
+#endif
+
+	/*
+	** As mentioned above, this is a tacky hack to cope
+	** with WFLUSH
+	*/
+	if ( PortP->WflushFlag ) {
+	  rio_dprintk (RIO_DEBUG_INTR, "Want to WFLUSH mark this port\n");
+
+	  if ( PortP->InUse )
+	    rio_dprintk (RIO_DEBUG_INTR, "FAILS - PORT IS IN USE\n");
+	}
+				
+	while ( PortP->WflushFlag &&
+		can_add_transmit( &PacketP, PortP ) && 
+		( PortP->InUse == NOT_INUSE ) ) {
+	  int p;
+	  struct PktCmd *PktCmdP;
+
+	  rio_dprintk (RIO_DEBUG_INTR, "Add WFLUSH marker to data queue\n");
+	  /*
+	  ** make it look just like a WFLUSH command
+	  */
+	  PktCmdP = ( struct PktCmd * )&PacketP->data[0];
+
+	  WBYTE( PktCmdP->Command , WFLUSH );
+
+	  p =  PortP->HostPort % ( ushort )PORTS_PER_RTA;
+
+	  /*
+	  ** If second block of ports for 16 port RTA, add 8
+	  ** to index 8-15.
+	  */
+	  if ( PortP->SecondBlock )
+	    p += PORTS_PER_RTA;
+
+	  WBYTE( PktCmdP->PhbNum, p );
+
+	  /*
+	  ** to make debuggery easier
+	  */
+	  WBYTE( PacketP->data[ 2], 'W'  );
+	  WBYTE( PacketP->data[ 3], 'F'  );
+	  WBYTE( PacketP->data[ 4], 'L'  );
+	  WBYTE( PacketP->data[ 5], 'U'  );
+	  WBYTE( PacketP->data[ 6], 'S'  );
+	  WBYTE( PacketP->data[ 7], 'H'  );
+	  WBYTE( PacketP->data[ 8], ' '  );
+	  WBYTE( PacketP->data[ 9], '0'+PortP->WflushFlag );
+	  WBYTE( PacketP->data[10], ' '  );
+	  WBYTE( PacketP->data[11], ' '  );
+	  WBYTE( PacketP->data[12], '\0' );
+
+	  /*
+	  ** its two bytes long!
+	  */
+	  WBYTE( PacketP->len , PKT_CMD_BIT | 2 );
+
+	  /*
+	  ** queue it!
+	  */
+	  if ( !( PortP->State & RIO_DELETED ) ) {
+	    add_transmit( PortP );
+	    /*
+	    ** Count chars tx'd for port statistics reporting
+	    */
+	    if ( PortP->statsGather )
+	      PortP->txchars += 2;
+	  }
+
+	  if ( --( PortP->WflushFlag ) == 0 ) {
+	    PortP->MagicFlags &= ~MAGIC_FLUSH;
+	  }
+
+	  rio_dprintk (RIO_DEBUG_INTR, "Wflush count now stands at %d\n", 
+		 PortP->WflushFlag);
+	}
+	if ( PortP->MagicFlags & MORE_OUTPUT_EYGOR ) {
+	  if ( PortP->MagicFlags & MAGIC_FLUSH ) {
+	    PortP->MagicFlags |= MORE_OUTPUT_EYGOR;
+	  }
+	  else {
+	    if ( !can_add_transmit( &PacketP, PortP ) ) {
+	      rio_spin_unlock(&PortP->portSem);
+	      continue;
+	    }
+	    rio_spin_unlock(&PortP->portSem);
+	    RIOTxEnable((char *)PortP);
+	    rio_spin_lock(&PortP->portSem);
+	    PortP->MagicFlags &= ~MORE_OUTPUT_EYGOR;
+	  }
+	}
+      }
+
+
+      /*
+      ** If we can't add anything to the transmit queue, then
+      ** we need do none of the remaining processing.
+      */
+      if (!can_add_transmit( &PacketP, PortP ) ) {
+	rio_spin_unlock(&PortP->portSem);
+	continue;
+      }
+
+      rio_spin_unlock(&PortP->portSem);
+      RIOTxEnable((char *)PortP);
+    }
+  }
+}
+
+/*
+** Routine for handling received data for clist drivers.
+** NB: Called with the tty locked. The spl from the lockb( ) is passed.
+** we return the ttySpl level that we re-locked at.
+*/
+static void
+RIOReceive(p, PortP)
+struct rio_info *	p;
+struct Port *		PortP;
+{
+  struct tty_struct *TtyP;
+  register ushort transCount;
+  struct PKT *PacketP;
+  register uint	DataCnt;
+  uchar *	ptr;
+  int copied =0;
+
+  static int intCount, RxIntCnt;
+
+  /*
+  ** The receive data process is to remove packets from the
+  ** PHB until there aren't any more or the current cblock
+  ** is full. When this occurs, there will be some left over
+  ** data in the packet, that we must do something with.
+  ** As we haven't unhooked the packet from the read list
+  ** yet, we can just leave the packet there, having first
+  ** made a note of how far we got. This means that we need
+  ** a pointer per port saying where we start taking the
+  ** data from - this will normally be zero, but when we
+  ** run out of space it will be set to the offset of the
+  ** next byte to copy from the packet data area. The packet
+  ** length field is decremented by the number of bytes that
+  ** we succesfully removed from the packet. When this reaches
+  ** zero, we reset the offset pointer to be zero, and free
+  ** the packet from the front of the queue.
+  */
+
+  intCount++;
+
+  TtyP = PortP->gs.tty;
+  if (!TtyP) {
+    rio_dprintk (RIO_DEBUG_INTR, "RIOReceive: tty is null. \n");
+    return;
+  }
+
+  if (PortP->State & RIO_THROTTLE_RX) {
+    rio_dprintk (RIO_DEBUG_INTR, "RIOReceive: Throttled. Can't handle more input.\n");
+    return;
+  }
+
+  if ( PortP->State & RIO_DELETED )
+    {
+      while ( can_remove_receive( &PacketP, PortP ) )
+	{
+	  remove_receive( PortP );
+	  put_free_end( PortP->HostP, PacketP );
+	}
+    }
+  else
+    {
+      /*
+      ** loop, just so long as:
+      **   i ) there's some data ( i.e. can_remove_receive )
+      **  ii ) we haven't been blocked
+      ** iii ) there's somewhere to put the data
+      **  iv ) we haven't outstayed our welcome
+      */
+      transCount = 1;
+      while ( can_remove_receive(&PacketP, PortP)
+	      && transCount)
+	{
+#ifdef STATS
+	  PortP->Stat.RxIntCnt++;
+#endif /* STATS */
+	  RxIntCnt++;
+
+	  /*
+	  ** check that it is not a command!
+	  */
+	  if ( PacketP->len & PKT_CMD_BIT ) {
+	    rio_dprintk (RIO_DEBUG_INTR, "RIO: unexpected command packet received on PHB\n");
+	    /*	    rio_dprint(RIO_DEBUG_INTR, (" sysport   = %d\n", p->RIOPortp->PortNum)); */
+	    rio_dprintk (RIO_DEBUG_INTR, " dest_unit = %d\n", PacketP->dest_unit);
+	    rio_dprintk (RIO_DEBUG_INTR, " dest_port = %d\n", PacketP->dest_port);
+	    rio_dprintk (RIO_DEBUG_INTR, " src_unit  = %d\n", PacketP->src_unit);
+	    rio_dprintk (RIO_DEBUG_INTR, " src_port  = %d\n", PacketP->src_port);
+	    rio_dprintk (RIO_DEBUG_INTR, " len	   = %d\n", PacketP->len);
+	    rio_dprintk (RIO_DEBUG_INTR, " control   = %d\n", PacketP->control);
+	    rio_dprintk (RIO_DEBUG_INTR, " csum	   = %d\n", PacketP->csum);
+	    rio_dprintk (RIO_DEBUG_INTR, "	 data bytes: ");
+	    for ( DataCnt=0; DataCnt<PKT_MAX_DATA_LEN; DataCnt++ )
+	      rio_dprintk (RIO_DEBUG_INTR, "%d\n", PacketP->data[DataCnt]);
+	    remove_receive( PortP );
+	    put_free_end( PortP->HostP, PacketP );
+	    continue; /* with next packet */
+	  }
+
+	  /*
+	  ** How many characters can we move 'upstream' ?
+	  **
+	  ** Determine the minimum of the amount of data
+	  ** available and the amount of space in which to
+	  ** put it.
+	  **
+	  ** 1.	Get the packet length by masking 'len'
+	  **	for only the length bits.
+	  ** 2.	Available space is [buffer size] - [space used]
+	  **
+	  ** Transfer count is the minimum of packet length
+	  ** and available space.
+	  */
+			
+	  transCount = min_t(unsigned int, PacketP->len & PKT_LEN_MASK,
+			   TTY_FLIPBUF_SIZE - TtyP->flip.count);
+	  rio_dprintk (RIO_DEBUG_REC,  "port %d: Copy %d bytes\n", 
+				      PortP->PortNum, transCount);
+	  /*
+	  ** To use the following 'kkprintfs' for debugging - change the '#undef'
+	  ** to '#define', (this is the only place ___DEBUG_IT___ occurs in the
+	  ** driver).
+	  */
+#undef ___DEBUG_IT___
+#ifdef ___DEBUG_IT___
+	  kkprintf("I:%d R:%d P:%d Q:%d C:%d F:%x ",
+		   intCount,
+		   RxIntCnt,
+		   PortP->PortNum,
+		   TtyP->rxqueue.count,
+		   transCount,
+		   TtyP->flags );
+#endif
+	  ptr = (uchar *) PacketP->data + PortP->RxDataStart;
+
+	  rio_memcpy_fromio (TtyP->flip.char_buf_ptr, ptr, transCount);
+	  memset(TtyP->flip.flag_buf_ptr, TTY_NORMAL, transCount);
+
+#ifdef STATS
+	  /*
+	  ** keep a count for statistical purposes
+	  */
+	  PortP->Stat.RxCharCnt	+= transCount;
+#endif
+	  PortP->RxDataStart	+= transCount;
+	  PacketP->len		-= transCount;
+	  copied += transCount;
+	  TtyP->flip.count += transCount;
+	  TtyP->flip.char_buf_ptr += transCount;
+	  TtyP->flip.flag_buf_ptr += transCount;
+
+
+#ifdef ___DEBUG_IT___
+	  kkprintf("T:%d L:%d\n", DataCnt, PacketP->len );
+#endif
+
+	  if ( PacketP->len == 0 )
+	    {
+				/*
+				** If we have emptied the packet, then we can
+				** free it, and reset the start pointer for
+				** the next packet.
+				*/
+	      remove_receive( PortP );
+	      put_free_end( PortP->HostP, PacketP );
+	      PortP->RxDataStart = 0;
+#ifdef STATS
+				/*
+				** more lies ( oops, I mean statistics )
+				*/
+	      PortP->Stat.RxPktCnt++;
+#endif /* STATS */
+	    }
+	}
+    }
+  if (copied) {
+    rio_dprintk (RIO_DEBUG_REC, "port %d: pushing tty flip buffer: %d total bytes copied.\n", PortP->PortNum, copied);
+    tty_flip_buffer_push (TtyP);
+  }
+
+  return;
+}
+
+#ifdef FUTURE_RELEASE
+/*
+** The proc routine called by the line discipline to do the work for it.
+** The proc routine works hand in hand with the interrupt routine.
+*/
+int
+riotproc(p, tp, cmd, port)
+struct rio_info *	p;
+register struct ttystatics *tp;
+int cmd;
+int	port;
+{
+	register struct Port *PortP;
+	int SysPort;
+	struct PKT *PacketP;
+
+	SysPort = port;	/* Believe me, it works. */
+
+	if ( SysPort < 0 || SysPort >= RIO_PORTS ) {
+		rio_dprintk (RIO_DEBUG_INTR, "Illegal port %d derived from TTY in riotproc()\n",SysPort);
+		return 0;
+	}
+	PortP = p->RIOPortp[SysPort];
+
+	if ((uint)PortP->PhbP < (uint)PortP->Caddr || 
+			(uint)PortP->PhbP >= (uint)PortP->Caddr+SIXTY_FOUR_K ) {
+		rio_dprintk (RIO_DEBUG_INTR, "RIO: NULL or BAD PhbP on sys port %d in proc routine\n",
+							SysPort);
+		rio_dprintk (RIO_DEBUG_INTR, "	 PortP = 0x%x\n",PortP);
+		rio_dprintk (RIO_DEBUG_INTR, "	 PortP->PhbP = 0x%x\n",PortP->PhbP);
+		rio_dprintk (RIO_DEBUG_INTR, "	 PortP->Caddr = 0x%x\n",PortP->PhbP);
+		rio_dprintk (RIO_DEBUG_INTR, "	 PortP->HostPort = 0x%x\n",PortP->HostPort);
+		return 0;
+	}
+
+	switch(cmd) {
+		case T_WFLUSH:
+			rio_dprintk (RIO_DEBUG_INTR, "T_WFLUSH\n");
+			/*
+			** Because of the spooky way the RIO works, we don't need
+			** to issue a flush command on any of the SET*F commands,
+			** as that causes trouble with getty and login, which issue
+			** these commands to incur a READ flush, and rely on the fact
+			** that the line discipline does a wait for drain for them.
+			** As the rio doesn't wait for drain, the write flush would
+			** destroy the Password: prompt. This isn't very friendly, so
+			** here we only issue a WFLUSH command if we are in the interrupt
+			** routine, or we aren't executing a SET*F command.
+			*/
+			if ( PortP->HostP->InIntr || !PortP->FlushCmdBodge ) {
+				/*
+				** form a wflush packet - 1 byte long, no data
+				*/
+				if ( PortP->State & RIO_DELETED ) {
+					rio_dprintk (RIO_DEBUG_INTR, "WFLUSH on deleted RTA\n");
+				}
+				else {
+					if ( RIOPreemptiveCmd(p, PortP, WFLUSH ) == RIO_FAIL ) {
+						rio_dprintk (RIO_DEBUG_INTR, "T_WFLUSH Command failed\n");
+					}
+					else
+						rio_dprintk (RIO_DEBUG_INTR, "T_WFLUSH Command\n");
+				}
+				/*
+				** WFLUSH operation - flush the data!
+				*/
+				PortP->TxBufferIn = PortP->TxBufferOut = 0;
+			}
+			else {
+				rio_dprintk (RIO_DEBUG_INTR, "T_WFLUSH Command ignored\n");
+			}
+			/*
+			** sort out the line discipline
+			*/
+			if (PortP->CookMode == COOK_WELL)
+				goto start;
+			break;
+	
+		case T_RESUME:
+			rio_dprintk (RIO_DEBUG_INTR, "T_RESUME\n");
+			/*
+			** send pre-emptive resume packet
+			*/
+			if ( PortP->State & RIO_DELETED ) {
+				rio_dprintk (RIO_DEBUG_INTR, "RESUME on deleted RTA\n");
+			}
+			else {
+				if ( RIOPreemptiveCmd(p, PortP, RESUME ) == RIO_FAIL ) {
+					rio_dprintk (RIO_DEBUG_INTR, "T_RESUME Command failed\n");
+				}
+			}
+			/*
+			** and re-start the sender software!
+			*/
+			if (PortP->CookMode == COOK_WELL)
+				goto start;
+			break;
+	
+		case T_TIME:
+			rio_dprintk (RIO_DEBUG_INTR, "T_TIME\n");
+			/*
+			** T_TIME is called when xDLY is set in oflags and
+			** the line discipline timeout has expired. It's
+			** function in life is to clear the TIMEOUT flag
+			** and to re-start output to the port.
+			*/
+			/*
+			** Fall through and re-start output
+			*/
+		case T_OUTPUT:
+start:
+			if ( PortP->MagicFlags & MAGIC_FLUSH ) {
+				PortP->MagicFlags |= MORE_OUTPUT_EYGOR;
+				return 0;
+			}
+			RIOTxEnable((char *)PortP);
+			PortP->MagicFlags &= ~MORE_OUTPUT_EYGOR;
+			/*rio_dprint(RIO_DEBUG_INTR, PortP,DBG_PROC,"T_OUTPUT finished\n");*/
+			break;
+	
+		case T_SUSPEND:
+			rio_dprintk (RIO_DEBUG_INTR, "T_SUSPEND\n");
+			/*
+			** send a suspend pre-emptive packet.
+			*/
+			if ( PortP->State & RIO_DELETED ) {
+				rio_dprintk (RIO_DEBUG_INTR, "SUSPEND deleted RTA\n");
+			}
+			else {
+				if ( RIOPreemptiveCmd(p, PortP, SUSPEND ) == RIO_FAIL ) {
+					rio_dprintk (RIO_DEBUG_INTR, "T_SUSPEND Command failed\n");
+				}
+			}
+			/*
+			** done!
+			*/
+			break;
+	
+		case T_BLOCK:
+			rio_dprintk (RIO_DEBUG_INTR, "T_BLOCK\n");
+			break;
+	
+		case T_RFLUSH:
+			rio_dprintk (RIO_DEBUG_INTR, "T_RFLUSH\n");
+			if ( PortP->State & RIO_DELETED ) {
+				rio_dprintk (RIO_DEBUG_INTR, "RFLUSH on deleted RTA\n");
+				PortP->RxDataStart = 0;
+			}
+			else {
+				if ( RIOPreemptiveCmd( p, PortP, RFLUSH ) == RIO_FAIL ) {
+					rio_dprintk (RIO_DEBUG_INTR, "T_RFLUSH Command failed\n");
+					return 0;
+				}
+				PortP->RxDataStart = 0;
+				while ( can_remove_receive(&PacketP, PortP) ) {
+					remove_receive(PortP);
+					ShowPacket(DBG_PROC, PacketP );
+					put_free_end(PortP->HostP, PacketP );
+				}
+				if ( PortP->PhbP->handshake == PHB_HANDSHAKE_SET ) {
+					/*
+					** MAGIC!
+					*/
+					rio_dprintk (RIO_DEBUG_INTR, "Set receive handshake bit\n");
+					PortP->PhbP->handshake |= PHB_HANDSHAKE_RESET;
+				}
+			}
+			break;
+			/* FALLTHROUGH */
+		case T_UNBLOCK:
+			rio_dprintk (RIO_DEBUG_INTR, "T_UNBLOCK\n");
+			/*
+			** If there is any data to receive set a timeout to service it.
+			*/
+			RIOReceive(p, PortP);
+			break;
+	
+		case T_BREAK:
+			rio_dprintk (RIO_DEBUG_INTR, "T_BREAK\n");
+			/*
+			** Send a break command. For Sys V
+			** this is a timed break, so we
+			** send a SBREAK[time] packet
+			*/
+			/*
+			** Build a BREAK command
+			*/
+			if ( PortP->State & RIO_DELETED ) {
+				rio_dprintk (RIO_DEBUG_INTR, "BREAK on deleted RTA\n");
+			}
+			else {
+				if (RIOShortCommand(PortP,SBREAK,2,
+								p->RIOConf.BreakInterval)==RIO_FAIL) {
+			   		rio_dprintk (RIO_DEBUG_INTR, "SBREAK RIOShortCommand failed\n");
+				}
+			}
+	
+			/*
+			** done!
+			*/
+			break;
+	
+		case T_INPUT:
+			rio_dprintk (RIO_DEBUG_INTR, "Proc T_INPUT called - I don't know what to do!\n");
+			break;
+		case T_PARM:
+			rio_dprintk (RIO_DEBUG_INTR, "Proc T_PARM called - I don't know what to do!\n");
+			break;
+	
+		case T_SWTCH:
+			rio_dprintk (RIO_DEBUG_INTR, "Proc T_SWTCH called - I don't know what to do!\n");
+			break;
+	
+		default:
+			rio_dprintk (RIO_DEBUG_INTR, "Proc UNKNOWN command %d\n",cmd);
+	}
+	/*
+	** T_OUTPUT returns without passing through this point!
+	*/
+	/*rio_dprint(RIO_DEBUG_INTR, PortP,DBG_PROC,"riotproc done\n");*/
+	return(0);
+}
+#endif
diff --git a/drivers/char/rio/rioioctl.h b/drivers/char/rio/rioioctl.h
new file mode 100644
index 0000000..c3d6797
--- /dev/null
+++ b/drivers/char/rio/rioioctl.h
@@ -0,0 +1,103 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: rioioctl.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:13
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)rioioctl.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef	__rioioctl_h__
+#define	__rioioctl_h__
+
+#ifdef SCCS_LABELS
+static char *_rioioctl_h_sccs_ = "@(#)rioioctl.h	1.2";
+#endif
+
+/*
+** RIO device driver - user ioctls and associated structures.
+*/
+
+struct portStats {
+	int	port;
+	int	gather;
+	ulong	txchars;
+	ulong	rxchars;
+	ulong	opens;
+	ulong	closes;
+	ulong	ioctls;
+}; 
+
+
+#define rIOC	('r'<<8)
+#define	TCRIOSTATE	(rIOC | 1)
+#define	TCRIOXPON	(rIOC | 2)
+#define	TCRIOXPOFF	(rIOC | 3)
+#define	TCRIOXPCPS	(rIOC | 4)
+#define	TCRIOXPRINT	(rIOC | 5)
+#define TCRIOIXANYON	(rIOC | 6)
+#define	TCRIOIXANYOFF	(rIOC | 7)
+#define TCRIOIXONON	(rIOC | 8)
+#define	TCRIOIXONOFF	(rIOC | 9)
+#define	TCRIOMBIS	(rIOC | 10)
+#define	TCRIOMBIC	(rIOC | 11)
+#define	TCRIOTRIAD	(rIOC | 12)
+#define TCRIOTSTATE	(rIOC | 13)
+
+/*
+** 15.10.1998 ARG - ESIL 0761 part fix
+** Add RIO ioctls for manipulating RTS and CTS flow control, (as LynxOS
+** appears to not support hardware flow control).
+*/
+#define TCRIOCTSFLOWEN	(rIOC | 14)	/* enable CTS flow control */
+#define TCRIOCTSFLOWDIS	(rIOC | 15)	/* disable CTS flow control */
+#define TCRIORTSFLOWEN	(rIOC | 16)	/* enable RTS flow control */
+#define TCRIORTSFLOWDIS	(rIOC | 17)	/* disable RTS flow control */
+
+/*
+** 09.12.1998 ARG - ESIL 0776 part fix
+** Definition for 'RIOC' also appears in daemon.h, so we'd better do a
+** #ifndef here first.
+** 'RIO_QUICK_CHECK' also #define'd here as this ioctl is now
+** allowed to be used by customers.
+**
+** 05.02.1999 ARG -
+** This is what I've decied to do with ioctls etc., which are intended to be
+** invoked from users applications :
+** Anything that needs to be defined here will be removed from daemon.h, that
+** way it won't end up having to be defined/maintained in two places. The only
+** consequence of this is that this file should now be #include'd by daemon.h
+**
+** 'stats' ioctls now #define'd here as they are to be used by customers.
+*/
+#define	RIOC	('R'<<8)|('i'<<16)|('o'<<24)
+
+#define	RIO_QUICK_CHECK	  	(RIOC | 105)
+#define RIO_GATHER_PORT_STATS	(RIOC | 193)
+#define RIO_RESET_PORT_STATS	(RIOC | 194)
+#define RIO_GET_PORT_STATS	(RIOC | 195)
+
+#endif	/* __rioioctl_h__ */
diff --git a/drivers/char/rio/riolocks.h b/drivers/char/rio/riolocks.h
new file mode 100644
index 0000000..0e0cdac
--- /dev/null
+++ b/drivers/char/rio/riolocks.h
@@ -0,0 +1,43 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: riolocks.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:13
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)riolocks.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef	__rio_riolocks_h__
+#define	__rio_riolocks_h__
+
+#ifdef SCCS_LABELS
+static char *_riolocks_h_sccs_ = "@(#)riolocks.h	1.2";
+#endif
+
+#define LOCKB(lk)		lockb(lk);
+#define UNLOCKB(lk, oldspl)	unlockb(lk, oldspl);
+
+#endif
diff --git a/drivers/char/rio/rioparam.c b/drivers/char/rio/rioparam.c
new file mode 100644
index 0000000..f109163
--- /dev/null
+++ b/drivers/char/rio/rioparam.c
@@ -0,0 +1,744 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: rioparam.c
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 10:33:45
+**	Retrieved	: 11/6/98 10:33:50
+**
+**  ident @(#)rioparam.c	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifdef SCCS_LABELS
+static char *_rioparam_c_sccs_ = "@(#)rioparam.c	1.3";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+
+
+
+/*
+** The Scam, based on email from jeremyr@bugs.specialix.co.uk....
+**
+** To send a command on a particular port, you put a packet with the
+** command bit set onto the port. The command bit is in the len field,
+** and gets ORed in with the actual byte count.
+**
+** When you send a packet with the command bit set, then the first
+** data byte ( data[0] ) is interpretted as the command to execute.
+** It also governs what data structure overlay should accompany the packet.
+** Commands are defined in cirrus/cirrus.h
+**
+** If you want the command to pre-emt data already on the queue for the
+** port, set the pre-emptive bit in conjunction with the command bit.
+** It is not defined what will happen if you set the preemptive bit
+** on a packet that is NOT a command.
+**
+** Pre-emptive commands should be queued at the head of the queue using
+** add_start(), whereas normal commands and data are enqueued using
+** add_end().
+**
+** Most commands do not use the remaining bytes in the data array. The
+** exceptions are OPEN MOPEN and CONFIG. (NB. As with the SI CONFIG and
+** OPEN are currently analagous). With these three commands the following
+** 11 data bytes are all used to pass config information such as baud rate etc.
+** The fields are also defined in cirrus.h. Some contain straightforward
+** information such as the transmit XON character. Two contain the transmit and
+** receive baud rates respectively. For most baud rates there is a direct
+** mapping between the rates defined in <sys/termio.h> and the byte in the
+** packet. There are additional (non UNIX-standard) rates defined in
+** /u/dos/rio/cirrus/h/brates.h.
+**
+** The rest of the data fields contain approximations to the Cirrus registers
+** that are used to program number of bits etc. Each registers bit fields is
+** defined in cirrus.h.
+** 
+** NB. Only use those bits that are defined as being driver specific
+** or common to the RTA and the driver.
+** 
+** All commands going from RTA->Host will be dealt with by the Host code - you
+** will never see them. As with the SI there will be three fields to look out
+** for in each phb (not yet defined - needs defining a.s.a.p).
+** 
+** modem_status	- current state of handshake pins.
+**
+** port_status	 - current port status - equivalent to hi_stat for SI, indicates
+** if port is IDLE_OPEN, IDLE_CLOSED etc.
+**
+** break_status	- bit X set if break has been received.
+** 
+** Happy hacking.
+** 
+*/
+
+/* 
+** RIOParam is used to open or configure a port. You pass it a PortP,
+** which will have a tty struct attached to it. You also pass a command,
+** either OPEN or CONFIG. The port's setup is taken from the t_ fields
+** of the tty struct inside the PortP, and the port is either opened
+** or re-configured. You must also tell RIOParam if the device is a modem
+** device or not (i.e. top bit of minor number set or clear - take special
+** care when deciding on this!).
+** RIOParam neither flushes nor waits for drain, and is NOT preemptive.
+**
+** RIOParam assumes it will be called at splrio(), and also assumes
+** that CookMode is set correctly in the port structure.
+**
+** NB. for MPX
+**	tty lock must NOT have been previously acquired.
+*/
+int
+RIOParam(PortP, cmd, Modem, SleepFlag)
+struct Port *PortP;
+int cmd;
+int Modem;
+int SleepFlag; 
+{
+	register struct tty_struct *TtyP;
+	int	retval;
+	register struct phb_param *phb_param_ptr;
+	PKT *PacketP;
+	int res;
+	uchar Cor1=0, Cor2=0, Cor4=0, Cor5=0;
+	uchar TxXon=0, TxXoff=0, RxXon=0, RxXoff=0;
+	uchar LNext=0, TxBaud=0, RxBaud=0;
+	int		retries = 0xff;
+	unsigned long flags;
+
+	func_enter ();
+
+	TtyP = PortP->gs.tty;
+
+	rio_dprintk (RIO_DEBUG_PARAM, "RIOParam: Port:%d cmd:%d Modem:%d SleepFlag:%d Mapped: %d, tty=%p\n",
+	    PortP->PortNum, cmd, Modem, SleepFlag, PortP->Mapped, TtyP);
+
+	if (!TtyP) {
+	  rio_dprintk (RIO_DEBUG_PARAM, "Can't call rioparam with null tty.\n");
+
+	  func_exit ();
+
+	  return RIO_FAIL;
+	}
+	rio_spin_lock_irqsave(&PortP->portSem, flags );
+
+	if (cmd == OPEN) {
+		/*
+		** If the port is set to store or lock the parameters, and it is
+		** paramed with OPEN, we want to restore the saved port termio, but
+		** only if StoredTermio has been saved, i.e. NOT 1st open after reboot.
+		*/
+#if 0
+		if (PortP->FirstOpen) {
+			PortP->StoredTty.iflag = TtyP->tm.c_iflag;
+			PortP->StoredTty.oflag = TtyP->tm.c_oflag;
+			PortP->StoredTty.cflag = TtyP->tm.c_cflag;
+			PortP->StoredTty.lflag = TtyP->tm.c_lflag;
+			PortP->StoredTty.line = TtyP->tm.c_line;
+			for (i = 0; i < NCC + 5; i++)
+				PortP->StoredTty.cc[i] = TtyP->tm.c_cc[i];
+			PortP->FirstOpen = 0;
+		}
+		else if (PortP->Store || PortP->Lock) {
+			rio_dprintk (RIO_DEBUG_PARAM, "OPEN: Restoring stored/locked params\n");
+			TtyP->tm.c_iflag = PortP->StoredTty.iflag;
+			TtyP->tm.c_oflag = PortP->StoredTty.oflag;
+			TtyP->tm.c_cflag = PortP->StoredTty.cflag;
+			TtyP->tm.c_lflag = PortP->StoredTty.lflag;
+			TtyP->tm.c_line = PortP->StoredTty.line;
+			for (i = 0; i < NCC + 5; i++)
+				TtyP->tm.c_cc[i] = PortP->StoredTty.cc[i];
+		}
+#endif
+	}
+
+	/*
+	** wait for space
+	*/
+	while ( !(res=can_add_transmit(&PacketP,PortP)) || 
+			(PortP->InUse != NOT_INUSE) ) {
+		if (retries -- <= 0) {
+			break;
+		}
+		if ( PortP->InUse != NOT_INUSE ) {
+			rio_dprintk (RIO_DEBUG_PARAM, "Port IN_USE for pre-emptive command\n");
+		}
+
+		if ( !res ) {
+			rio_dprintk (RIO_DEBUG_PARAM, "Port has no space on transmit queue\n");
+		}
+
+		if ( SleepFlag != OK_TO_SLEEP ) {
+			rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+			func_exit();
+			
+			return RIO_FAIL;
+		}
+
+		rio_dprintk (RIO_DEBUG_PARAM, "wait for can_add_transmit\n");
+		rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+		retval = RIODelay(PortP, HUNDRED_MS);
+		rio_spin_lock_irqsave( &PortP->portSem, flags);
+		if (retval == RIO_FAIL) {
+			rio_dprintk (RIO_DEBUG_PARAM, "wait for can_add_transmit broken by signal\n");
+			rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+			pseterr(EINTR);
+			func_exit();
+
+			return RIO_FAIL;
+		}
+		if ( PortP->State & RIO_DELETED ) {
+			rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+			func_exit ();
+
+			return RIO_SUCCESS;
+		}
+	}
+
+	if (!res) {
+		rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+		func_exit ();
+
+		return RIO_FAIL;
+	}
+
+	rio_dprintk (RIO_DEBUG_PARAM, "can_add_transmit() returns %x\n",res);
+	rio_dprintk (RIO_DEBUG_PARAM, "Packet is 0x%x\n",(int) PacketP);
+
+	phb_param_ptr = (struct phb_param *)PacketP->data;
+
+
+#if 0
+	/*
+	** COR 1
+	*/
+	if ( TtyP->tm.c_iflag & INPCK ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Parity checking on input enabled\n");
+		Cor1 |= COR1_INPCK;
+	}
+#endif
+
+	switch ( TtyP->termios->c_cflag & CSIZE ) {
+		case CS5:
+		{
+			rio_dprintk (RIO_DEBUG_PARAM, "5 bit data\n");
+			Cor1 |= COR1_5BITS;
+			break;
+		}
+		case CS6:
+		{
+			rio_dprintk (RIO_DEBUG_PARAM, "6 bit data\n");
+			Cor1 |= COR1_6BITS;
+			break;
+		}
+		case CS7:
+		{
+			rio_dprintk (RIO_DEBUG_PARAM, "7 bit data\n");
+			Cor1 |= COR1_7BITS;
+			break;
+		}
+		case CS8:
+		{
+			rio_dprintk (RIO_DEBUG_PARAM, "8 bit data\n");
+			Cor1 |= COR1_8BITS;
+			break;
+		}
+	}
+
+	if ( TtyP->termios->c_cflag & CSTOPB ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "2 stop bits\n");
+		Cor1 |= COR1_2STOP;
+	}
+	else {
+		rio_dprintk (RIO_DEBUG_PARAM, "1 stop bit\n");
+		Cor1 |= COR1_1STOP;
+	}
+
+	if ( TtyP->termios->c_cflag & PARENB ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable parity\n");
+		Cor1 |= COR1_NORMAL;
+	}
+	else {
+		rio_dprintk (RIO_DEBUG_PARAM, "Disable parity\n");
+		Cor1 |= COR1_NOP;
+	}
+	if ( TtyP->termios->c_cflag & PARODD ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Odd parity\n");
+		Cor1 |= COR1_ODD;
+	}
+	else {
+		rio_dprintk (RIO_DEBUG_PARAM, "Even parity\n");
+		Cor1 |= COR1_EVEN; 
+	}
+
+	/*
+	** COR 2
+	*/
+	if ( TtyP->termios->c_iflag & IXON ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable start/stop output control\n");
+		Cor2 |= COR2_IXON;
+	}
+	else {
+		if ( PortP->Config & RIO_IXON ) {
+			rio_dprintk (RIO_DEBUG_PARAM, "Force enable start/stop output control\n");
+			Cor2 |= COR2_IXON;
+		}
+		else
+			rio_dprintk (RIO_DEBUG_PARAM, "IXON has been disabled.\n");
+	}
+
+	if (TtyP->termios->c_iflag & IXANY) {
+		if ( PortP->Config & RIO_IXANY ) {
+			rio_dprintk (RIO_DEBUG_PARAM, "Enable any key to restart output\n");
+			Cor2 |= COR2_IXANY;
+		}
+		else
+			rio_dprintk (RIO_DEBUG_PARAM, "IXANY has been disabled due to sanity reasons.\n");
+	}
+
+	if ( TtyP->termios->c_iflag & IXOFF ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable start/stop input control 2\n");
+		Cor2 |= COR2_IXOFF;
+	}
+
+	if ( TtyP->termios->c_cflag & HUPCL ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Hangup on last close\n");
+		Cor2 |= COR2_HUPCL;
+	}
+
+	if ( C_CRTSCTS (TtyP)) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Rx hardware flow control enabled\n");
+		Cor2 |= COR2_CTSFLOW;
+		Cor2 |= COR2_RTSFLOW;
+	} else {
+		rio_dprintk (RIO_DEBUG_PARAM, "Rx hardware flow control disabled\n");
+		Cor2 &= ~COR2_CTSFLOW;
+		Cor2 &= ~COR2_RTSFLOW;
+	}
+
+
+	if ( TtyP->termios->c_cflag & CLOCAL ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Local line\n");
+	}
+	else {
+		rio_dprintk (RIO_DEBUG_PARAM, "Possible Modem line\n");
+	}
+
+	/*
+	** COR 4 (there is no COR 3)
+	*/
+	if ( TtyP->termios->c_iflag & IGNBRK ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Ignore break condition\n");
+		Cor4 |= COR4_IGNBRK;
+	}
+	if ( !(TtyP->termios->c_iflag & BRKINT) ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Break generates NULL condition\n");
+		Cor4 |= COR4_NBRKINT;
+	} else {
+		rio_dprintk (RIO_DEBUG_PARAM, "Interrupt on	break condition\n");
+	}
+
+	if ( TtyP->termios->c_iflag & INLCR ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Map newline to carriage return on input\n");
+		Cor4 |= COR4_INLCR;
+	}
+
+	if ( TtyP->termios->c_iflag & IGNCR ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Ignore carriage return on input\n");
+		Cor4 |= COR4_IGNCR;
+	}
+
+	if ( TtyP->termios->c_iflag & ICRNL ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Map carriage return to newline on input\n");
+		Cor4 |= COR4_ICRNL;
+	}
+	if ( TtyP->termios->c_iflag & IGNPAR ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Ignore characters with parity errors\n");
+		Cor4 |= COR4_IGNPAR;
+	}
+	if ( TtyP->termios->c_iflag & PARMRK ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Mark parity errors\n");
+		Cor4 |= COR4_PARMRK;
+	}
+
+	/*
+	** Set the RAISEMOD flag to ensure that the modem lines are raised
+	** on reception of a config packet.
+	** The download code handles the zero baud condition.
+	*/
+	Cor4 |= COR4_RAISEMOD;
+
+	/*
+	** COR 5
+	*/
+
+	Cor5 = COR5_CMOE;
+
+	/*
+	** Set to monitor tbusy/tstop (or not).
+	*/
+
+	if (PortP->MonitorTstate)
+		Cor5 |= COR5_TSTATE_ON;
+	else
+		Cor5 |= COR5_TSTATE_OFF;
+
+	/*
+	** Could set LNE here if you wanted LNext processing. SVR4 will use it.
+	*/
+	if ( TtyP->termios->c_iflag & ISTRIP ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Strip input characters\n");
+		if (! (PortP->State & RIO_TRIAD_MODE)) {
+			Cor5 |= COR5_ISTRIP;
+		}
+	}
+
+	if ( TtyP->termios->c_oflag & ONLCR ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Map newline to carriage-return, newline on output\n");
+		if ( PortP->CookMode == COOK_MEDIUM )
+			Cor5 |= COR5_ONLCR;
+	}
+	if ( TtyP->termios->c_oflag & OCRNL ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Map carriage return to newline on output\n");
+		if ( PortP->CookMode == COOK_MEDIUM )
+			Cor5 |= COR5_OCRNL;
+	}
+	if ( ( TtyP->termios->c_oflag & TABDLY) == TAB3 ) {
+		rio_dprintk (RIO_DEBUG_PARAM, "Tab delay 3 set\n");
+		if ( PortP->CookMode == COOK_MEDIUM )
+			Cor5 |= COR5_TAB3;
+	}
+
+	/*
+	** Flow control bytes.
+	*/
+	TxXon = TtyP->termios->c_cc[VSTART];
+	TxXoff = TtyP->termios->c_cc[VSTOP];
+	RxXon = TtyP->termios->c_cc[VSTART];
+	RxXoff = TtyP->termios->c_cc[VSTOP];
+	/*
+	** LNEXT byte
+	*/
+	LNext = 0;
+
+	/*
+	** Baud rate bytes
+	*/
+	rio_dprintk (RIO_DEBUG_PARAM, "Mapping of rx/tx baud %x (%x)\n", 
+				     TtyP->termios->c_cflag, CBAUD);
+
+	switch (TtyP->termios->c_cflag & CBAUD) {
+#define e(b) case B ## b : RxBaud = TxBaud = RIO_B ## b ;break
+	  e(50);e(75);e(110);e(134);e(150);e(200);e(300);e(600);e(1200);
+	  e(1800);e(2400);e(4800);e(9600);e(19200);e(38400);e(57600);
+	  e(115200); /* e(230400);e(460800); e(921600);  */
+	}
+
+	/* XXX MIssing conversion table. XXX */
+	/* 	 (TtyP->termios->c_cflag & V_CBAUD); */
+
+	rio_dprintk (RIO_DEBUG_PARAM, "tx baud 0x%x, rx baud 0x%x\n", TxBaud, RxBaud);
+
+
+	/*
+	** Leftovers
+	*/
+	if ( TtyP->termios->c_cflag & CREAD )
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable receiver\n");
+#ifdef RCV1EN
+	if ( TtyP->termios->c_cflag & RCV1EN )
+		rio_dprintk (RIO_DEBUG_PARAM, "RCV1EN (?)\n");
+#endif
+#ifdef XMT1EN
+	if ( TtyP->termios->c_cflag & XMT1EN )
+		rio_dprintk (RIO_DEBUG_PARAM, "XMT1EN (?)\n");
+#endif
+#if 0
+	if ( TtyP->termios->c_cflag & LOBLK )
+		rio_dprintk (RIO_DEBUG_PARAM, "LOBLK - JCL output blocks when not current\n");
+#endif
+	if ( TtyP->termios->c_lflag & ISIG )
+		rio_dprintk (RIO_DEBUG_PARAM, "Input character signal generating enabled\n");
+	if ( TtyP->termios->c_lflag & ICANON )
+		rio_dprintk (RIO_DEBUG_PARAM, "Canonical input: erase and kill enabled\n");
+	if ( TtyP->termios->c_lflag & XCASE )
+		rio_dprintk (RIO_DEBUG_PARAM, "Canonical upper/lower presentation\n");
+	if ( TtyP->termios->c_lflag & ECHO )
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable input echo\n");
+	if ( TtyP->termios->c_lflag & ECHOE )
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable echo erase\n");
+	if ( TtyP->termios->c_lflag & ECHOK )
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable echo kill\n");
+	if ( TtyP->termios->c_lflag & ECHONL )
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable echo newline\n");
+	if ( TtyP->termios->c_lflag & NOFLSH )
+		rio_dprintk (RIO_DEBUG_PARAM, "Disable flush after interrupt or quit\n");
+#ifdef TOSTOP
+	if ( TtyP->termios->c_lflag & TOSTOP )
+		rio_dprintk (RIO_DEBUG_PARAM, "Send SIGTTOU for background output\n");
+#endif
+#ifdef XCLUDE
+	if ( TtyP->termios->c_lflag & XCLUDE )
+		rio_dprintk (RIO_DEBUG_PARAM, "Exclusive use of this line\n");
+#endif
+	if ( TtyP->termios->c_iflag & IUCLC )
+		rio_dprintk (RIO_DEBUG_PARAM, "Map uppercase to lowercase on input\n");
+	if ( TtyP->termios->c_oflag & OPOST )
+		rio_dprintk (RIO_DEBUG_PARAM, "Enable output post-processing\n");
+	if ( TtyP->termios->c_oflag & OLCUC )
+		rio_dprintk (RIO_DEBUG_PARAM, "Map lowercase to uppercase on output\n");
+	if ( TtyP->termios->c_oflag & ONOCR )
+		rio_dprintk (RIO_DEBUG_PARAM, "No carriage return output at column 0\n");
+	if ( TtyP->termios->c_oflag & ONLRET )
+		rio_dprintk (RIO_DEBUG_PARAM, "Newline performs carriage return function\n");
+	if ( TtyP->termios->c_oflag & OFILL )
+		rio_dprintk (RIO_DEBUG_PARAM, "Use fill characters for delay\n");
+	if ( TtyP->termios->c_oflag & OFDEL )
+		rio_dprintk (RIO_DEBUG_PARAM, "Fill character is DEL\n");
+	if ( TtyP->termios->c_oflag & NLDLY )
+		rio_dprintk (RIO_DEBUG_PARAM, "Newline delay set\n");
+	if ( TtyP->termios->c_oflag & CRDLY )
+		rio_dprintk (RIO_DEBUG_PARAM, "Carriage return delay set\n");
+	if ( TtyP->termios->c_oflag & TABDLY )
+		rio_dprintk (RIO_DEBUG_PARAM, "Tab delay set\n");
+#if 0
+	if ( TtyP->termios->c_oflag & BSDLY )
+		rio_dprintk (RIO_DEBUG_PARAM, "Back-space delay set\n");
+	if ( TtyP->termios->c_oflag & VTDLY )
+		rio_dprintk (RIO_DEBUG_PARAM, "Vertical tab delay set\n");
+	if ( TtyP->termios->c_oflag & FFDLY )
+		rio_dprintk (RIO_DEBUG_PARAM, "Form-feed delay set\n");
+#endif
+	/*
+	** These things are kind of useful in a later life!
+	*/
+	PortP->Cor2Copy = Cor2;
+
+	if ( PortP->State & RIO_DELETED ) {
+		rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+		func_exit ();
+
+		return RIO_FAIL;
+	}
+
+	/*
+	** Actually write the info into the packet to be sent
+	*/
+	WBYTE(phb_param_ptr->Cmd,	cmd);
+	WBYTE(phb_param_ptr->Cor1,	 Cor1);
+	WBYTE(phb_param_ptr->Cor2,	 Cor2);
+	WBYTE(phb_param_ptr->Cor4,	 Cor4);
+	WBYTE(phb_param_ptr->Cor5,	 Cor5);
+	WBYTE(phb_param_ptr->TxXon,	TxXon);
+	WBYTE(phb_param_ptr->RxXon,	RxXon);
+	WBYTE(phb_param_ptr->TxXoff, TxXoff);
+	WBYTE(phb_param_ptr->RxXoff, RxXoff);
+	WBYTE(phb_param_ptr->LNext,	LNext);
+	WBYTE(phb_param_ptr->TxBaud, TxBaud);
+	WBYTE(phb_param_ptr->RxBaud, RxBaud);
+
+	/*
+	** Set the length/command field
+	*/
+	WBYTE(PacketP->len , 12 | PKT_CMD_BIT);
+
+	/*
+	** The packet is formed - now, whack it off
+	** to its final destination:
+	*/
+	add_transmit(PortP);
+	/*
+	** Count characters transmitted for port statistics reporting
+	*/
+	if (PortP->statsGather)
+		PortP->txchars += 12;
+
+	rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+
+	rio_dprintk (RIO_DEBUG_PARAM, "add_transmit returned.\n");
+	/*
+	** job done.
+	*/
+	func_exit ();
+
+	return RIO_SUCCESS;
+}
+
+
+/*
+** We can add another packet to a transmit queue if the packet pointer pointed
+** to by the TxAdd pointer has PKT_IN_USE clear in its address.
+*/
+int
+can_add_transmit(PktP, PortP)
+PKT **PktP;
+struct Port *PortP; 
+{
+	register PKT *tp;
+
+	*PktP = tp = (PKT *)RIO_PTR(PortP->Caddr,RWORD(*PortP->TxAdd));
+
+	return !((uint)tp & PKT_IN_USE);
+}
+
+/*
+** To add a packet to the queue, you set the PKT_IN_USE bit in the address,
+** and then move the TxAdd pointer along one position to point to the next
+** packet pointer. You must wrap the pointer from the end back to the start.
+*/
+void
+add_transmit(PortP)
+struct Port *PortP; 
+{
+  if (RWORD(*PortP->TxAdd) & PKT_IN_USE) {
+    rio_dprintk (RIO_DEBUG_PARAM, "add_transmit: Packet has been stolen!");
+  }
+	WWORD( *(ushort *)PortP->TxAdd, RWORD(*PortP->TxAdd) | PKT_IN_USE);
+	PortP->TxAdd = (PortP->TxAdd == PortP->TxEnd) ? PortP->TxStart : 
+					PortP->TxAdd + 1;
+	WWORD( PortP->PhbP->tx_add , RIO_OFF(PortP->Caddr,PortP->TxAdd) );
+}
+
+/****************************************
+ * Put a packet onto the end of the
+ * free list
+ ****************************************/
+void
+put_free_end(HostP, PktP)
+struct Host *HostP;
+PKT *PktP;
+{
+	FREE_LIST *tmp_pointer;
+	ushort old_end, new_end;
+	unsigned long flags;
+
+	rio_spin_lock_irqsave(&HostP->HostLock, flags);
+
+	 /*************************************************
+	* Put a packet back onto the back of the free list
+	*
+	************************************************/
+
+	rio_dprintk (RIO_DEBUG_PFE,  "put_free_end(PktP=%x)\n",(int)PktP);
+
+	if ((old_end=RWORD(HostP->ParmMapP->free_list_end)) != TPNULL) {
+		new_end = RIO_OFF(HostP->Caddr,PktP);
+		tmp_pointer = (FREE_LIST *)RIO_PTR(HostP->Caddr,old_end);
+		WWORD(tmp_pointer->next , new_end );
+		WWORD(((FREE_LIST *)PktP)->prev , old_end);
+		WWORD(((FREE_LIST *)PktP)->next , TPNULL);
+		WWORD(HostP->ParmMapP->free_list_end, new_end);
+	}
+	else {	/* First packet on the free list this should never happen! */
+		rio_dprintk (RIO_DEBUG_PFE, "put_free_end(): This should never happen\n");
+		WWORD(HostP->ParmMapP->free_list_end , RIO_OFF(HostP->Caddr,PktP));
+		tmp_pointer = (FREE_LIST *)PktP;
+		WWORD(tmp_pointer->prev , TPNULL);
+		WWORD(tmp_pointer->next , TPNULL);
+	}
+	rio_dprintk (RIO_DEBUG_CMD, "Before unlock: %p\n", &HostP->HostLock);
+	rio_spin_unlock_irqrestore(&HostP->HostLock, flags);
+}
+
+/*
+** can_remove_receive(PktP,P) returns non-zero if PKT_IN_USE is set
+** for the next packet on the queue. It will also set PktP to point to the
+** relevant packet, [having cleared the PKT_IN_USE bit]. If PKT_IN_USE is clear,
+** then can_remove_receive() returns 0.
+*/
+int
+can_remove_receive(PktP, PortP)
+PKT **PktP;
+struct Port *PortP;
+{
+	if ( RWORD(*PortP->RxRemove) & PKT_IN_USE) {
+		*PktP = (PKT *)RIO_PTR(PortP->Caddr,
+					RWORD(*PortP->RxRemove) & ~PKT_IN_USE);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+** To remove a packet from the receive queue you clear its PKT_IN_USE bit,
+** and then bump the pointers. Once the pointers get to the end, they must
+** be wrapped back to the start.
+*/
+void
+remove_receive(PortP)
+struct Port *PortP; 
+{
+	WWORD( *PortP->RxRemove, RWORD(*PortP->RxRemove) & ~PKT_IN_USE );
+	PortP->RxRemove = (PortP->RxRemove == PortP->RxEnd) ? PortP->RxStart : 
+								PortP->RxRemove + 1;
+	WWORD( PortP->PhbP->rx_remove , RIO_OFF(PortP->Caddr, PortP->RxRemove) );
+}
diff --git a/drivers/char/rio/riopcicopy.c b/drivers/char/rio/riopcicopy.c
new file mode 100644
index 0000000..2ea99a6
--- /dev/null
+++ b/drivers/char/rio/riopcicopy.c
@@ -0,0 +1,8 @@
+
+/* Yeah. We have copyright on this one. Sure. */
+
+void rio_pcicopy( char *from, char *to, int amount)
+{
+  while ( amount-- )
+    *to++ = *from++;
+}
diff --git a/drivers/char/rio/rioroute.c b/drivers/char/rio/rioroute.c
new file mode 100644
index 0000000..106b31f
--- /dev/null
+++ b/drivers/char/rio/rioroute.c
@@ -0,0 +1,1238 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: rioroute.c
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 10:33:46
+**	Retrieved	: 11/6/98 10:33:50
+**
+**  ident @(#)rioroute.c	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_rioroute_c_sccs_ = "@(#)rioroute.c	1.3";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+
+static int RIOCheckIsolated(struct rio_info *, struct Host *, uint);
+static int RIOIsolate(struct rio_info *, struct Host *, uint);
+static int RIOCheck(struct Host *, uint);
+static void RIOConCon(struct rio_info *, struct Host *, uint, uint, uint, uint, int);
+
+
+/*
+** Incoming on the ROUTE_RUP
+** I wrote this while I was tired. Forgive me.
+*/
+int RIORouteRup( struct rio_info *p, uint Rup, struct Host *HostP, PKT *PacketP )
+{
+  struct PktCmd *PktCmdP = (struct PktCmd *)PacketP->data;
+  struct PktCmd_M *PktReplyP;
+  struct CmdBlk *CmdBlkP;
+  struct Port *PortP;
+  struct Map *MapP;
+  struct Top *TopP;
+  int ThisLink, ThisLinkMin, ThisLinkMax;
+  int port;
+  int Mod, Mod1, Mod2;
+  ushort RtaType;
+  uint RtaUniq;
+  uint ThisUnit, ThisUnit2;	/* 2 ids to accommodate 16 port RTA */
+  uint OldUnit, NewUnit, OldLink, NewLink;
+  char *MyType, *MyName;
+  int Lies;
+  unsigned long flags;
+
+#ifdef STACK
+    RIOStackCheck("RIORouteRup");
+#endif
+#ifdef CHECK
+    CheckPacketP(PacketP);
+    CheckHostP(HostP);
+    CheckRup(Rup);
+    CheckHost(Host);
+#endif
+  /*
+  ** Is this unit telling us it's current link topology?
+  */
+  if ( RBYTE(PktCmdP->Command) == ROUTE_TOPOLOGY )
+  {
+    MapP = HostP->Mapping;
+
+    /*
+    ** The packet can be sent either by the host or by an RTA.
+    ** If it comes from the host, then we need to fill in the
+    ** Topology array in the host structure. If it came in
+    ** from an RTA then we need to fill in the Mapping structure's
+    ** Topology array for the unit.
+    */
+    if ( Rup >= (ushort)MAX_RUP )
+    {
+      ThisUnit = HOST_ID;
+      TopP = HostP->Topology;
+      MyType = "Host";
+      MyName = HostP->Name;
+      ThisLinkMin = ThisLinkMax = Rup - MAX_RUP;
+    }
+    else
+    {
+      ThisUnit = Rup+1;
+      TopP = HostP->Mapping[Rup].Topology;
+      MyType = "RTA";
+      MyName = HostP->Mapping[Rup].Name;
+      ThisLinkMin = 0;
+      ThisLinkMax = LINKS_PER_UNIT - 1;
+    }
+
+    /*
+    ** Lies will not be tolerated.
+    ** If any pair of links claim to be connected to the same
+    ** place, then ignore this packet completely.
+    */
+    Lies = 0;
+    for ( ThisLink=ThisLinkMin + 1; ThisLink <= ThisLinkMax; ThisLink++)
+    {
+      /*
+      ** it won't lie about network interconnect, total disconnects
+      ** and no-IDs. (or at least, it doesn't *matter* if it does)
+      */
+      if ( RBYTE(PktCmdP->RouteTopology[ThisLink].Unit) > (ushort)MAX_RUP )
+	  continue;
+
+      for ( NewLink=ThisLinkMin; NewLink < ThisLink; NewLink++ )
+      {
+        if ( (RBYTE(PktCmdP->RouteTopology[ThisLink].Unit) ==
+              RBYTE(PktCmdP->RouteTopology[NewLink].Unit)) &&
+	     (RBYTE(PktCmdP->RouteTopology[ThisLink].Link) ==
+              RBYTE(PktCmdP->RouteTopology[NewLink].Link)) )
+	{
+          Lies++;
+	}
+      }
+    }
+
+    if ( Lies )
+    {
+      rio_dprintk (RIO_DEBUG_ROUTE, "LIES! DAMN LIES! %d LIES!\n",Lies);
+      rio_dprintk (RIO_DEBUG_ROUTE, "%d:%c %d:%c %d:%c %d:%c\n",
+          RBYTE(PktCmdP->RouteTopology[0].Unit), 
+	  'A'+RBYTE(PktCmdP->RouteTopology[0].Link),
+          RBYTE(PktCmdP->RouteTopology[1].Unit),
+	  'A'+RBYTE(PktCmdP->RouteTopology[1].Link),
+          RBYTE(PktCmdP->RouteTopology[2].Unit),
+	  'A'+RBYTE(PktCmdP->RouteTopology[2].Link),
+          RBYTE(PktCmdP->RouteTopology[3].Unit),
+	  'A'+RBYTE(PktCmdP->RouteTopology[3].Link));
+      return TRUE;
+    }
+
+    /*
+    ** now, process each link.
+    */
+    for ( ThisLink=ThisLinkMin; ThisLink <= ThisLinkMax; ThisLink++)
+    {
+      /*
+      ** this is what it was connected to
+      */
+      OldUnit = TopP[ThisLink].Unit;
+      OldLink = TopP[ThisLink].Link;
+
+      /*
+      ** this is what it is now connected to
+      */
+      NewUnit = RBYTE(PktCmdP->RouteTopology[ThisLink].Unit);
+      NewLink = RBYTE(PktCmdP->RouteTopology[ThisLink].Link);
+
+      if ( OldUnit != NewUnit || OldLink != NewLink )
+      {
+	/*
+	** something has changed!
+	*/
+
+        if ( NewUnit > MAX_RUP &&
+	     NewUnit != ROUTE_DISCONNECT &&
+	     NewUnit != ROUTE_NO_ID &&
+	     NewUnit != ROUTE_INTERCONNECT )
+	{
+	    rio_dprintk (RIO_DEBUG_ROUTE, "I have a link from %s %s to unit %d:%d - I don't like it.\n",
+		  MyType,
+		  MyName,
+		  NewUnit,
+		  NewLink);
+	}
+	else
+	{
+	  /*
+	  ** put the new values in
+	  */
+	  TopP[ThisLink].Unit = NewUnit;
+	  TopP[ThisLink].Link = NewLink;
+
+	  RIOSetChange(p);
+
+	  if ( OldUnit <= MAX_RUP )
+	  {
+	    /*
+	    ** If something has become bust, then re-enable them messages
+	    */
+	    if (! p->RIONoMessage)
+		RIOConCon(p,HostP,ThisUnit,ThisLink,OldUnit,OldLink,DISCONNECT);
+	  }
+
+	  if ( ( NewUnit <= MAX_RUP ) && !p->RIONoMessage )
+	    RIOConCon(p,HostP,ThisUnit,ThisLink,NewUnit,NewLink,CONNECT);
+
+	  if ( NewUnit == ROUTE_NO_ID )
+	    rio_dprintk (RIO_DEBUG_ROUTE, "%s %s (%c) is connected to an unconfigured unit.\n",
+		    MyType,MyName,'A'+ThisLink);
+
+	  if ( NewUnit == ROUTE_INTERCONNECT )
+	  {
+	    if (! p->RIONoMessage)
+		cprintf("%s '%s' (%c) is connected to another network.\n", MyType,MyName,'A'+ThisLink);
+	  }
+
+	  /*
+	  ** perform an update for 'the other end', so that these messages
+	  ** only appears once. Only disconnect the other end if it is pointing
+	  ** at us!
+	  */
+	  if ( OldUnit == HOST_ID )
+	  {
+	    if ( HostP->Topology[OldLink].Unit == ThisUnit &&
+		 HostP->Topology[OldLink].Link == ThisLink )
+	    {
+	      rio_dprintk (RIO_DEBUG_ROUTE, "SETTING HOST (%c) TO DISCONNECTED!\n", OldLink+'A');
+	      HostP->Topology[OldLink].Unit = ROUTE_DISCONNECT;
+	      HostP->Topology[OldLink].Link = NO_LINK;
+	    }
+	    else
+	    {
+	      rio_dprintk (RIO_DEBUG_ROUTE, "HOST(%c) WAS NOT CONNECTED TO %s (%c)!\n",
+		    OldLink+'A',HostP->Mapping[ThisUnit-1].Name,ThisLink+'A');
+	    }
+	  }
+	  else if ( OldUnit <= MAX_RUP )
+	  {
+	    if ( HostP->Mapping[OldUnit-1].Topology[OldLink].Unit == ThisUnit &&
+	         HostP->Mapping[OldUnit-1].Topology[OldLink].Link == ThisLink )
+	    {
+	      rio_dprintk (RIO_DEBUG_ROUTE, "SETTING RTA %s (%c) TO DISCONNECTED!\n",
+				   HostP->Mapping[OldUnit-1].Name,OldLink+'A');
+	      HostP->Mapping[OldUnit-1].Topology[OldLink].Unit=ROUTE_DISCONNECT;
+	      HostP->Mapping[OldUnit-1].Topology[OldLink].Link=NO_LINK;
+	    }
+	    else
+	    {
+	      rio_dprintk (RIO_DEBUG_ROUTE, "RTA %s (%c) WAS NOT CONNECTED TO %s (%c)\n",
+			    HostP->Mapping[OldUnit-1].Name,OldLink+'A',
+			    HostP->Mapping[ThisUnit-1].Name,ThisLink+'A');
+	    }
+	  }
+	  if ( NewUnit == HOST_ID )
+	  {
+	    rio_dprintk (RIO_DEBUG_ROUTE, "MARKING HOST (%c) CONNECTED TO %s (%c)\n",
+				NewLink+'A',MyName,ThisLink+'A');
+	    HostP->Topology[NewLink].Unit = ThisUnit;
+	    HostP->Topology[NewLink].Link = ThisLink;
+	  }
+	  else if ( NewUnit <= MAX_RUP )
+	  {
+	    rio_dprintk (RIO_DEBUG_ROUTE, "MARKING RTA %s (%c) CONNECTED TO %s (%c)\n",
+	      HostP->Mapping[NewUnit-1].Name,NewLink+'A',MyName,ThisLink+'A');
+	    HostP->Mapping[NewUnit-1].Topology[NewLink].Unit=ThisUnit;
+	    HostP->Mapping[NewUnit-1].Topology[NewLink].Link=ThisLink;
+	  }
+	}
+	RIOSetChange(p);
+	RIOCheckIsolated(p, HostP, OldUnit );
+      }
+    }
+    return TRUE;
+  }
+
+  /*
+  ** The only other command we recognise is a route_request command
+  */
+  if ( RBYTE(PktCmdP->Command) != ROUTE_REQUEST )
+  {
+    rio_dprintk (RIO_DEBUG_ROUTE, "Unknown command %d received on rup %d host %d ROUTE_RUP\n", 
+	   RBYTE(PktCmdP->Command),Rup,(int)HostP);
+    return TRUE;
+  }
+      
+  RtaUniq = (RBYTE(PktCmdP->UniqNum[0])) +
+	    (RBYTE(PktCmdP->UniqNum[1]) << 8) +
+	    (RBYTE(PktCmdP->UniqNum[2]) << 16) +
+	    (RBYTE(PktCmdP->UniqNum[3]) << 24);
+
+  /*
+  ** Determine if 8 or 16 port RTA
+  */
+  RtaType = GetUnitType(RtaUniq);
+
+  rio_dprintk (RIO_DEBUG_ROUTE, "Received a request for an ID for serial number %x\n", RtaUniq);
+
+  Mod = RBYTE(PktCmdP->ModuleTypes);
+  Mod1 = LONYBLE(Mod);
+  if (RtaType == TYPE_RTA16)
+  {
+    /*
+    ** Only one ident is set for a 16 port RTA. To make compatible
+    ** with 8 port, set 2nd ident in Mod2 to the same as Mod1.
+    */
+    Mod2 = Mod1;
+    rio_dprintk (RIO_DEBUG_ROUTE, "Backplane type is %s (all ports)\n",
+     p->RIOModuleTypes[Mod1].Name);
+  }
+  else
+  {
+    Mod2 = HINYBLE(Mod);
+    rio_dprintk (RIO_DEBUG_ROUTE, "Module types are %s (ports 0-3) and %s (ports 4-7)\n",
+     p->RIOModuleTypes[Mod1].Name, p->RIOModuleTypes[Mod2].Name);
+  }
+
+  if ( RtaUniq == 0xffffffff )
+  {
+      ShowPacket( DBG_SPECIAL, PacketP );
+  }
+
+  /*
+  ** try to unhook a command block from the command free list.
+  */
+  if ( !(CmdBlkP = RIOGetCmdBlk()) )
+  {
+    rio_dprintk (RIO_DEBUG_ROUTE, "No command blocks to route RTA! come back later.\n");
+    return 0;
+  }
+
+  /*
+  ** Fill in the default info on the command block
+  */
+  CmdBlkP->Packet.dest_unit = Rup;
+  CmdBlkP->Packet.dest_port = ROUTE_RUP;
+  CmdBlkP->Packet.src_unit = HOST_ID;
+  CmdBlkP->Packet.src_port = ROUTE_RUP;
+  CmdBlkP->Packet.len = PKT_CMD_BIT | 1;
+  CmdBlkP->PreFuncP = CmdBlkP->PostFuncP = NULL;
+  PktReplyP = (struct PktCmd_M *)CmdBlkP->Packet.data;
+
+  if (! RIOBootOk(p, HostP, RtaUniq))
+  {
+    rio_dprintk (RIO_DEBUG_ROUTE, "RTA %x tried to get an ID, but does not belong - FOAD it!\n",
+	  RtaUniq);
+    PktReplyP->Command = ROUTE_FOAD;
+    HostP->Copy("RT_FOAD", PktReplyP->CommandText, 7);
+    RIOQueueCmdBlk(HostP, Rup, CmdBlkP);
+    return TRUE;
+  }
+
+  /*
+  ** Check to see if the RTA is configured for this host
+  */
+  for ( ThisUnit=0; ThisUnit<MAX_RUP; ThisUnit++ )
+  {
+    rio_dprintk (RIO_DEBUG_ROUTE, "Entry %d Flags=%s %s UniqueNum=0x%x\n",
+			ThisUnit,
+			HostP->Mapping[ThisUnit].Flags & SLOT_IN_USE ?
+					    "Slot-In-Use":"Not In Use",
+			HostP->Mapping[ThisUnit].Flags & SLOT_TENTATIVE ? 
+					    "Slot-Tentative":"Not Tentative",
+			HostP->Mapping[ThisUnit].RtaUniqueNum);
+
+    /*
+    ** We have an entry for it.
+    */
+    if ( (HostP->Mapping[ThisUnit].Flags & (SLOT_IN_USE | SLOT_TENTATIVE)) &&
+         (HostP->Mapping[ThisUnit].RtaUniqueNum == RtaUniq) )
+    {
+      if (RtaType == TYPE_RTA16)
+      {
+	  ThisUnit2 = HostP->Mapping[ThisUnit].ID2 - 1;
+          rio_dprintk (RIO_DEBUG_ROUTE, "Found unit 0x%x at slots %d+%d\n",
+					    RtaUniq,ThisUnit,ThisUnit2);
+      }
+      else
+          rio_dprintk (RIO_DEBUG_ROUTE, "Found unit 0x%x at slot %d\n",
+					    RtaUniq,ThisUnit);
+      /*
+      ** If we have no knowledge of booting it, then the host has
+      ** been re-booted, and so we must kill the RTA, so that it
+      ** will be booted again (potentially with new bins)
+      ** and it will then re-ask for an ID, which we will service.
+      */
+      if ( (HostP->Mapping[ThisUnit].Flags & SLOT_IN_USE) && 
+	  !(HostP->Mapping[ThisUnit].Flags & RTA_BOOTED) )
+      {
+	if ( !(HostP->Mapping[ThisUnit].Flags & MSG_DONE) )
+	{
+	    if ( !p->RIONoMessage )
+	        cprintf("RTA '%s' is being updated.\n",HostP->Mapping[ThisUnit].Name);
+	    HostP->Mapping[ThisUnit].Flags |= MSG_DONE;
+	}
+	PktReplyP->Command = ROUTE_FOAD;
+	HostP->Copy("RT_FOAD",PktReplyP->CommandText,7);
+	RIOQueueCmdBlk(HostP, Rup, CmdBlkP);
+	return TRUE;
+      }
+
+      /*
+      ** Send the ID (entry) to this RTA. The ID number is implicit as
+      ** the offset into the table. It is worth noting at this stage
+      ** that offset zero in the table contains the entries for the
+      ** RTA with ID 1!!!!
+      */
+      PktReplyP->Command = ROUTE_ALLOCATE;
+      PktReplyP->IDNum   = ThisUnit+1;
+      if (RtaType == TYPE_RTA16)
+      {
+        if (HostP->Mapping[ThisUnit].Flags & SLOT_IN_USE)
+	    /*
+	    ** Adjust the phb and tx pkt dest_units for 2nd block of 8
+	    ** only if the RTA has ports associated (SLOT_IN_USE)
+	    */
+	    RIOFixPhbs(p, HostP, ThisUnit2);
+	    PktReplyP->IDNum2  = ThisUnit2+1;
+	    rio_dprintk (RIO_DEBUG_ROUTE, "RTA '%s' has been allocated IDs %d+%d\n",
+	          HostP->Mapping[ThisUnit].Name, PktReplyP->IDNum, PktReplyP->IDNum2);
+      }
+      else
+      {
+	    PktReplyP->IDNum2 = ROUTE_NO_ID;
+	    rio_dprintk (RIO_DEBUG_ROUTE, "RTA '%s' has been allocated ID %d\n",
+	          HostP->Mapping[ThisUnit].Name,PktReplyP->IDNum);
+      }
+      HostP->Copy("RT_ALLOCAT",PktReplyP->CommandText,10);
+
+      RIOQueueCmdBlk( HostP, Rup, CmdBlkP);
+
+      /*
+      ** If this is a freshly booted RTA, then we need to re-open
+      ** the ports, if any where open, so that data may once more
+      ** flow around the system!
+      */
+      if ( (HostP->Mapping[ThisUnit].Flags & RTA_NEWBOOT) &&
+	   (HostP->Mapping[ThisUnit].SysPort != NO_PORT) )
+      {
+	/*
+	** look at the ports associated with this beast and
+	** see if any where open. If they was, then re-open
+	** them, using the info from the tty flags.
+	*/
+	for ( port=0; port<PORTS_PER_RTA; port++ )
+	{
+	  PortP = p->RIOPortp[port+HostP->Mapping[ThisUnit].SysPort];
+	  if ( PortP->State & (RIO_MOPEN|RIO_LOPEN) )
+	  {
+	    rio_dprintk (RIO_DEBUG_ROUTE, "Re-opened this port\n");
+	    rio_spin_lock_irqsave(&PortP->portSem, flags);
+	    PortP->MagicFlags |= MAGIC_REBOOT;
+	    rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	  }
+	}
+	if (RtaType == TYPE_RTA16)
+	{
+	  for ( port=0; port<PORTS_PER_RTA; port++ )
+	  {
+	    PortP = p->RIOPortp[port+HostP->Mapping[ThisUnit2].SysPort];
+	    if ( PortP->State & (RIO_MOPEN|RIO_LOPEN) )
+	    {
+	      rio_dprintk (RIO_DEBUG_ROUTE, "Re-opened this port\n");
+	      rio_spin_lock_irqsave(&PortP->portSem, flags);
+	      PortP->MagicFlags |= MAGIC_REBOOT;
+	      rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	    }
+	  }
+	}
+      }
+
+      /*
+      ** keep a copy of the module types!
+      */
+      HostP->UnixRups[ThisUnit].ModTypes = Mod;
+      if (RtaType == TYPE_RTA16)
+	      HostP->UnixRups[ThisUnit2].ModTypes = Mod;
+
+      /*
+      ** If either of the modules on this unit is read-only or write-only
+      ** or none-xprint, then we need to transfer that info over to the
+      ** relevant ports.
+      */
+      if ( HostP->Mapping[ThisUnit].SysPort != NO_PORT )
+      {
+        for ( port=0; port<PORTS_PER_MODULE; port++ )
+	{
+	  p->RIOPortp[port+HostP->Mapping[ThisUnit].SysPort]->Config &= ~RIO_NOMASK;
+	  p->RIOPortp[port+HostP->Mapping[ThisUnit].SysPort]->Config |=
+	   p->RIOModuleTypes[Mod1].Flags[port];
+	  p->RIOPortp[port+PORTS_PER_MODULE+HostP->Mapping[ThisUnit].SysPort]->Config &= ~RIO_NOMASK;
+	  p->RIOPortp[port+PORTS_PER_MODULE+HostP->Mapping[ThisUnit].SysPort]->Config |= p->RIOModuleTypes[Mod2].Flags[port];
+	}
+	if (RtaType == TYPE_RTA16)
+	{
+          for ( port=0; port<PORTS_PER_MODULE; port++ )
+	  {
+	    p->RIOPortp[port+HostP->Mapping[ThisUnit2].SysPort]->Config &= ~RIO_NOMASK;
+	    p->RIOPortp[port+HostP->Mapping[ThisUnit2].SysPort]->Config |= p->RIOModuleTypes[Mod1].Flags[port];
+	    p->RIOPortp[port+PORTS_PER_MODULE+HostP->Mapping[ThisUnit2].SysPort]->Config &= ~RIO_NOMASK;
+	    p->RIOPortp[port+PORTS_PER_MODULE+HostP->Mapping[ThisUnit2].SysPort]->Config |= p->RIOModuleTypes[Mod2].Flags[port];
+          }
+	}
+      }
+
+      /*
+      ** Job done, get on with the interrupts!
+      */
+      return TRUE;
+    }
+  }
+  /*
+  ** There is no table entry for this RTA at all.
+  **
+  ** Lets check to see if we actually booted this unit - if not,
+  ** then we reset it and it will go round the loop of being booted
+  ** we can then worry about trying to fit it into the table.
+  */
+  for ( ThisUnit=0; ThisUnit<HostP->NumExtraBooted; ThisUnit++ )
+    if ( HostP->ExtraUnits[ThisUnit] == RtaUniq )
+      break;
+  if ( ThisUnit == HostP->NumExtraBooted && ThisUnit != MAX_EXTRA_UNITS )
+  {
+    /*
+    ** if the unit wasn't in the table, and the table wasn't full, then
+    ** we reset the unit, because we didn't boot it.
+    ** However, if the table is full, it could be that we did boot
+    ** this unit, and so we won't reboot it, because it isn't really
+    ** all that disasterous to keep the old bins in most cases. This
+    ** is a rather tacky feature, but we are on the edge of reallity
+    ** here, because the implication is that someone has connected
+    ** 16+MAX_EXTRA_UNITS onto one host.
+    */
+    static int UnknownMesgDone = 0;
+
+    if ( !UnknownMesgDone )
+    {
+	if (! p->RIONoMessage)
+	    cprintf("One or more unknown RTAs are being updated.\n");
+	UnknownMesgDone = 1;
+    }
+
+    PktReplyP->Command = ROUTE_FOAD;
+    HostP->Copy("RT_FOAD",PktReplyP->CommandText,7);
+  }
+  else
+  {
+    /*
+    ** we did boot it (as an extra), and there may now be a table
+    ** slot free (because of a delete), so we will try to make
+    ** a tentative entry for it, so that the configurator can see it
+    ** and fill in the details for us.
+    */
+    if (RtaType == TYPE_RTA16)
+    {
+	if (RIOFindFreeID(p, HostP, &ThisUnit, &ThisUnit2) == 0)
+	{
+	    RIODefaultName(p, HostP, ThisUnit);
+	    FillSlot(ThisUnit, ThisUnit2, RtaUniq, HostP);
+	}
+    }
+    else
+    {
+	if (RIOFindFreeID(p, HostP, &ThisUnit, NULL) == 0)
+	{
+	    RIODefaultName(p, HostP, ThisUnit);
+	    FillSlot(ThisUnit, 0, RtaUniq, HostP);
+	}
+    }
+    PktReplyP->Command = ROUTE_USED;
+    HostP->Copy("RT_USED",PktReplyP->CommandText,7);
+  }
+  RIOQueueCmdBlk( HostP, Rup, CmdBlkP);
+  return TRUE;
+}
+
+
+void
+RIOFixPhbs(p, HostP, unit)
+struct rio_info *p;
+struct Host *HostP;
+uint unit;
+{
+	ushort			link, port;
+	struct Port		*PortP;
+	unsigned long flags;
+	int PortN = HostP->Mapping[unit].SysPort;
+
+	rio_dprintk (RIO_DEBUG_ROUTE, "RIOFixPhbs unit %d sysport %d\n", unit, PortN);
+
+	if (PortN != -1) {
+		ushort		dest_unit = HostP->Mapping[unit].ID2;
+
+		/*
+		** Get the link number used for the 1st 8 phbs on this unit.
+		*/
+		PortP = p->RIOPortp[HostP->Mapping[dest_unit - 1].SysPort];
+
+		link = RWORD(PortP->PhbP->link);
+
+		for (port = 0; port < PORTS_PER_RTA; port++, PortN++) {
+			ushort		dest_port = port + 8;
+#if 0
+			uint		PktInt;
+#endif
+			WORD		*TxPktP;
+			PKT		*Pkt;
+
+			PortP = p->RIOPortp[PortN];
+
+			rio_spin_lock_irqsave(&PortP->portSem, flags);
+			/*
+			** If RTA is not powered on, the tx packets will be
+			** unset, so go no further.
+			*/
+			if (PortP->TxStart == 0) {
+					rio_dprintk (RIO_DEBUG_ROUTE, "Tx pkts not set up yet\n");
+					rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+					break;
+			}
+
+			/*
+			** For the second slot of a 16 port RTA, the driver needs to
+			** sort out the phb to port mappings. The dest_unit for this
+			** group of 8 phbs is set to the dest_unit of the accompanying
+			** 8 port block. The dest_port of the second unit is set to
+			** be in the range 8-15 (i.e. 8 is added). Thus, for a 16 port
+			** RTA with IDs 5 and 6, traffic bound for port 6 of unit 6
+			** (being the second map ID) will be sent to dest_unit 5, port
+			** 14. When this RTA is deleted, dest_unit for ID 6 will be
+			** restored, and the dest_port will be reduced by 8.
+			** Transmit packets also have a destination field which needs
+			** adjusting in the same manner.
+			** Note that the unit/port bytes in 'dest' are swapped.
+			** We also need to adjust the phb and rup link numbers for the
+			** second block of 8 ttys.
+			*/
+			for (TxPktP = PortP->TxStart; TxPktP <= PortP->TxEnd; TxPktP++) {
+				/*
+				** *TxPktP is the pointer to the transmit packet on the host
+				** card. This needs to be translated into a 32 bit pointer
+				** so it can be accessed from the driver.
+				*/
+				Pkt = (PKT *) RIO_PTR(HostP->Caddr,RINDW(TxPktP));
+
+				/*
+				** If the packet is used, reset it.
+				*/
+				Pkt = (PKT *)((uint)Pkt & ~PKT_IN_USE);
+				WBYTE(Pkt->dest_unit, dest_unit);
+				WBYTE(Pkt->dest_port, dest_port);
+			}
+			rio_dprintk (RIO_DEBUG_ROUTE, "phb dest: Old %x:%x New %x:%x\n",
+					RWORD(PortP->PhbP->destination) & 0xff,
+					(RWORD(PortP->PhbP->destination) >> 8) & 0xff,
+					dest_unit, dest_port);
+			WWORD(PortP->PhbP->destination, dest_unit + (dest_port << 8));
+			WWORD(PortP->PhbP->link, link);
+
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		}
+		/*
+		** Now make sure the range of ports to be serviced includes
+		** the 2nd 8 on this 16 port RTA.
+		*/
+		if (link > 3) return;
+		if (((unit * 8) + 7) > RWORD(HostP->LinkStrP[link].last_port)) {
+			rio_dprintk (RIO_DEBUG_ROUTE, "last port on host link %d: %d\n", link, (unit * 8) + 7);
+			WWORD(HostP->LinkStrP[link].last_port, (unit * 8) + 7);
+		}
+	}
+}
+
+/*
+** Check to see if the new disconnection has isolated this unit.
+** If it has, then invalidate all its link information, and tell
+** the world about it. This is done to ensure that the configurator
+** only gets up-to-date information about what is going on.
+*/
+static int
+RIOCheckIsolated(p, HostP, UnitId)
+struct rio_info *	p;
+struct Host *HostP;
+uint UnitId;
+{
+	unsigned long flags;
+	rio_spin_lock_irqsave(&HostP->HostLock, flags);
+
+#ifdef CHECK
+	CheckHostP( HostP );
+	CheckUnitId( UnitId );
+#endif
+	if ( RIOCheck( HostP, UnitId ) ) {
+		rio_dprintk (RIO_DEBUG_ROUTE, "Unit %d is NOT isolated\n", UnitId);
+		rio_spin_unlock_irqrestore(&HostP->HostLock, flags);
+		return(0);
+	}
+
+	RIOIsolate(p, HostP, UnitId );
+	RIOSetChange(p);
+	rio_spin_unlock_irqrestore(&HostP->HostLock, flags);
+	return 1;
+}
+
+/*
+** Invalidate all the link interconnectivity of this unit, and of
+** all the units attached to it. This will mean that the entire
+** subnet will re-introduce itself.
+*/
+static int
+RIOIsolate(p, HostP, UnitId)
+struct rio_info *	p;
+struct Host *		HostP;
+uint UnitId; 
+{
+	uint link, unit;
+
+#ifdef CHECK
+	CheckHostP( HostP );
+	CheckUnitId( UnitId );
+#endif
+	UnitId--;		/* this trick relies on the Unit Id being UNSIGNED! */
+
+	if ( UnitId >= MAX_RUP )	/* dontcha just lurv unsigned maths! */
+		return(0);
+
+	if ( HostP->Mapping[UnitId].Flags & BEEN_HERE )
+		return(0);
+
+	HostP->Mapping[UnitId].Flags |= BEEN_HERE;
+
+	if ( p->RIOPrintDisabled == DO_PRINT )
+		rio_dprintk (RIO_DEBUG_ROUTE, "RIOMesgIsolated %s", HostP->Mapping[UnitId].Name);
+
+	for ( link=0; link<LINKS_PER_UNIT; link++) {
+		unit = HostP->Mapping[UnitId].Topology[link].Unit;
+		HostP->Mapping[UnitId].Topology[link].Unit = ROUTE_DISCONNECT;
+		HostP->Mapping[UnitId].Topology[link].Link = NO_LINK;
+		RIOIsolate(p, HostP, unit );
+	}
+	HostP->Mapping[UnitId].Flags &= ~BEEN_HERE;
+	return 1;
+}
+
+static int
+RIOCheck(HostP, UnitId)
+struct Host *HostP;
+uint UnitId;
+{
+  unsigned char link;
+
+#ifdef CHECK
+	CheckHostP( HostP );
+	CheckUnitId( UnitId );
+#endif
+/* 	rio_dprint(RIO_DEBUG_ROUTE, ("Check to see if unit %d has a route to the host\n",UnitId)); */
+	rio_dprintk (RIO_DEBUG_ROUTE, "RIOCheck : UnitID = %d\n", UnitId);
+
+	if ( UnitId == HOST_ID ) {
+		/* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d is NOT isolated - it IS the host!\n", UnitId)); */
+		return 1;
+	}
+
+	UnitId--;
+
+	if ( UnitId >= MAX_RUP ) {
+		/* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d - ignored.\n", UnitId)); */
+		return 0;
+	}
+
+	for ( link=0; link<LINKS_PER_UNIT; link++ ) {
+		if ( HostP->Mapping[UnitId].Topology[link].Unit==HOST_ID ) {
+			/* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d is connected directly to host via link (%c).\n", 
+						UnitId, 'A'+link)); */
+			return 1;
+		}
+	}
+
+	if ( HostP->Mapping[UnitId].Flags & BEEN_HERE ) {
+		/* rio_dprint(RIO_DEBUG_ROUTE, ("Been to Unit %d before - ignoring\n", UnitId)); */
+		return 0;
+	}
+
+	HostP->Mapping[UnitId].Flags |= BEEN_HERE;
+
+	for ( link=0; link < LINKS_PER_UNIT; link++ ) {
+		/* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d check link (%c)\n", UnitId,'A'+link)); */
+		if ( RIOCheck( HostP, HostP->Mapping[UnitId].Topology[link].Unit ) ) {
+			/* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d is connected to something that knows the host via link (%c)\n", UnitId,link+'A')); */
+			HostP->Mapping[UnitId].Flags &= ~BEEN_HERE;
+			return 1;
+		}
+	}
+
+	HostP->Mapping[UnitId].Flags &= ~BEEN_HERE;
+
+	/* rio_dprint(RIO_DEBUG_ROUTE, ("Unit %d DOESNT KNOW THE HOST!\n", UnitId)); */
+	
+	return 0;
+}
+
+/*
+** Returns the type of unit (host, 16/8 port RTA)
+*/
+
+uint
+GetUnitType(Uniq)
+uint Uniq;
+{
+	switch ( (Uniq >> 28) & 0xf)
+	{
+		case RIO_AT:
+		case RIO_MCA:
+		case RIO_EISA:
+		case RIO_PCI:
+			rio_dprintk (RIO_DEBUG_ROUTE, "Unit type: Host\n");
+			return(TYPE_HOST);
+		case RIO_RTA_16:
+			rio_dprintk (RIO_DEBUG_ROUTE, "Unit type: 16 port RTA\n");
+			return(TYPE_RTA16);
+		case RIO_RTA:
+			rio_dprintk (RIO_DEBUG_ROUTE, "Unit type: 8 port RTA\n");
+			return(TYPE_RTA8);
+		default :
+			rio_dprintk (RIO_DEBUG_ROUTE, "Unit type: Unrecognised\n");
+			return(99);
+	}
+}
+
+int
+RIOSetChange(p)
+struct rio_info *	p;
+{
+	if ( p->RIOQuickCheck != NOT_CHANGED )
+		return(0);
+	p->RIOQuickCheck = CHANGED;
+	if ( p->RIOSignalProcess ) {
+		rio_dprintk (RIO_DEBUG_ROUTE, "Send SIG-HUP");
+		/*
+		psignal( RIOSignalProcess, SIGHUP );
+		*/
+	}
+	return(0);
+}
+
+static void
+RIOConCon(p, HostP, FromId, FromLink, ToId, ToLink, Change)
+struct rio_info *	p;
+struct Host *HostP;
+uint FromId;
+uint FromLink;
+uint ToId;
+uint ToLink;
+int Change; 
+{
+    char *FromName;
+    char *FromType;
+    char *ToName;
+    char *ToType;
+    unsigned int tp;
+
+/*
+** 15.10.1998 ARG - ESIL 0759
+** (Part) fix for port being trashed when opened whilst RTA "disconnected"
+**
+** What's this doing in here anyway ?
+** It was causing the port to be 'unmapped' if opened whilst RTA "disconnected"
+**
+** 09.12.1998 ARG - ESIL 0776 - part fix
+** Okay, We've found out what this was all about now !
+** Someone had botched this to use RIOHalted to indicated the number of RTAs
+** 'disconnected'. The value in RIOHalted was then being used in the
+** 'RIO_QUICK_CHECK' ioctl. A none zero value indicating that a least one RTA
+** is 'disconnected'. The change was put in to satisfy a customer's needs.
+** Having taken this bit of code out 'RIO_QUICK_CHECK' now no longer works for
+** the customer.
+**
+    if (Change == CONNECT) {
+		if (p->RIOHalted) p->RIOHalted --;
+	 }
+	 else {
+		p->RIOHalted ++;
+	 }
+**
+** So - we need to implement it slightly differently - a new member of the
+** rio_info struct - RIORtaDisCons (RIO RTA connections) keeps track of RTA
+** connections and disconnections. 
+*/
+    if (Change == CONNECT) {
+		if (p->RIORtaDisCons) p->RIORtaDisCons--;
+	 }
+	 else {
+		p->RIORtaDisCons++;
+	 }
+
+    if ( p->RIOPrintDisabled == DONT_PRINT )
+		return;
+
+    if ( FromId > ToId ) {
+		tp = FromId;
+		FromId = ToId;
+		ToId = tp;
+		tp = FromLink;
+		FromLink = ToLink;
+		ToLink = tp;
+    }
+
+    FromName = FromId ? HostP->Mapping[FromId-1].Name : HostP->Name;
+    FromType = FromId ? "RTA" : "HOST";
+    ToName = ToId ? HostP->Mapping[ToId-1].Name : HostP->Name;
+    ToType = ToId ? "RTA" : "HOST";
+
+    rio_dprintk (RIO_DEBUG_ROUTE, "Link between %s '%s' (%c) and %s '%s' (%c) %s.\n",
+			    FromType, FromName, 'A'+FromLink,
+			    ToType,   ToName,   'A'+ToLink,
+			    (Change==CONNECT) ? "established" : "disconnected");
+    cprintf("Link between %s '%s' (%c) and %s '%s' (%c) %s.\n",
+			    FromType, FromName, 'A'+FromLink,
+			    ToType,   ToName,   'A'+ToLink,
+			    (Change==CONNECT) ? "established" : "disconnected");
+}
+
+/*
+** RIORemoveFromSavedTable :
+**
+** Delete and RTA entry from the saved table given to us
+** by the configuration program.
+*/
+static int
+RIORemoveFromSavedTable(struct rio_info *p, struct Map *pMap)
+{
+    int		entry;
+
+    /*
+    ** We loop for all entries even after finding an entry and
+    ** zeroing it because we may have two entries to delete if
+    ** it's a 16 port RTA.
+    */
+    for (entry = 0; entry < TOTAL_MAP_ENTRIES; entry++)
+    {
+	if (p->RIOSavedTable[entry].RtaUniqueNum == pMap->RtaUniqueNum)
+	{
+	    bzero((caddr_t)&p->RIOSavedTable[entry], sizeof(struct Map));
+	}
+    }
+    return 0;
+}
+
+
+/*
+** RIOCheckDisconnected :
+**
+** Scan the unit links to and return zero if the unit is completely
+** disconnected.
+*/
+static int
+RIOFreeDisconnected(struct rio_info *p, struct Host *HostP, int unit)
+{
+    int		link;
+
+
+    rio_dprintk (RIO_DEBUG_ROUTE, "RIOFreeDisconnect unit %d\n", unit);
+    /*
+    ** If the slot is tentative and does not belong to the
+    ** second half of a 16 port RTA then scan to see if
+    ** is disconnected.
+    */
+    for (link = 0; link < LINKS_PER_UNIT; link++)
+    {
+	if (HostP->Mapping[unit].Topology[link].Unit != ROUTE_DISCONNECT)
+	    break;
+    }
+
+    /*
+    ** If not all links are disconnected then we can forget about it.
+    */
+    if (link < LINKS_PER_UNIT)
+	    return 1;
+
+#if NEED_TO_FIX_THIS
+    /* Ok so all the links are disconnected. But we may have only just
+    ** made this slot tentative and not yet received a topology update.
+    ** Lets check how long ago we made it tentative.
+    */
+    rio_dprintk (RIO_DEBUG_ROUTE, "Just about to check LBOLT on entry %d\n", unit);
+    if (drv_getparm(LBOLT, (ulong_t *) &current_time))
+        rio_dprintk (RIO_DEBUG_ROUTE, "drv_getparm(LBOLT,....) Failed.\n");
+
+    elapse_time = current_time - TentTime[unit];
+    rio_dprintk (RIO_DEBUG_ROUTE, "elapse %d = current %d - tent %d (%d usec)\n",
+        elapse_time, current_time, TentTime[unit], drv_hztousec(elapse_time));
+    if (drv_hztousec(elapse_time) < WAIT_TO_FINISH)
+    {
+      rio_dprintk (RIO_DEBUG_ROUTE, "Skipping slot %d, not timed out yet %d\n",
+            unit, drv_hztousec(elapse_time));
+        return 1;
+    }
+#endif
+
+    /*
+    ** We have found an usable slot.
+    ** If it is half of a 16 port RTA then delete the other half.
+    */
+    if (HostP->Mapping[unit].ID2 != 0)
+    {
+	int nOther = (HostP->Mapping[unit].ID2) -1;
+
+	rio_dprintk (RIO_DEBUG_ROUTE, "RioFreedis second slot %d.\n", nOther);
+	bzero((caddr_t)&HostP->Mapping[nOther], sizeof(struct Map));
+    }
+    RIORemoveFromSavedTable(p, &HostP->Mapping[unit]);
+
+    return 0;
+}
+
+
+/*
+** RIOFindFreeID :
+**
+** This function scans the given host table for either one
+** or two free unit ID's.
+*/
+int
+RIOFindFreeID(struct rio_info *p, struct Host *HostP, uint *pID1, uint *pID2)
+{
+    int unit,tempID;
+
+    /*
+    ** Initialise the ID's to MAX_RUP.
+    ** We do this to make the loop for setting the ID's as simple as
+    ** possible.
+    */
+    *pID1 = MAX_RUP;
+    if (pID2 != NULL)
+	*pID2 = MAX_RUP;
+
+    /*
+    ** Scan all entries of the host mapping table for free slots.
+    ** We scan for free slots first and then if that is not successful
+    ** we start all over again looking for tentative slots we can re-use.
+    */
+    for (unit = 0; unit < MAX_RUP; unit++)
+    {
+	rio_dprintk (RIO_DEBUG_ROUTE, "Scanning unit %d\n",unit);
+	/*
+	** If the flags are zero then the slot is empty.
+	*/
+	if (HostP->Mapping[unit].Flags == 0)
+	{
+	    rio_dprintk (RIO_DEBUG_ROUTE, "      This slot is empty.\n");
+	    /*
+	    ** If we haven't allocated the first ID then do it now.
+	    */
+	    if (*pID1 == MAX_RUP)
+	    {
+		rio_dprintk (RIO_DEBUG_ROUTE, "Make tentative entry for first unit %d\n", unit);
+		*pID1 = unit;
+
+		/*
+		** If the second ID is not needed then we can return
+		** now.
+		*/
+		if (pID2 == NULL)
+		    return 0;
+	    }
+	    else
+	    {
+		/*
+		** Allocate the second slot and return.
+		*/
+		rio_dprintk (RIO_DEBUG_ROUTE, "Make tentative entry for second unit %d\n", unit);
+		*pID2 = unit;
+		return 0;
+	    }
+	}
+    }
+
+    /*
+    ** If we manage to come out of the free slot loop then we
+    ** need to start all over again looking for tentative slots
+    ** that we can re-use.
+    */
+    rio_dprintk (RIO_DEBUG_ROUTE, "Starting to scan for tentative slots\n");
+    for (unit = 0; unit < MAX_RUP; unit++)
+    {
+	if (((HostP->Mapping[unit].Flags & SLOT_TENTATIVE) ||
+	                       (HostP->Mapping[unit].Flags == 0))  && ! 
+	   (HostP->Mapping[unit].Flags & RTA16_SECOND_SLOT ))
+	{
+	    rio_dprintk (RIO_DEBUG_ROUTE, "    Slot %d looks promising.\n",unit);
+
+	    if(unit == *pID1)
+	    {
+	    	rio_dprintk (RIO_DEBUG_ROUTE, "    No it isn't, its the 1st half\n");
+		continue;
+	    }
+
+	    /*
+	    ** Slot is Tentative or Empty, but not a tentative second 
+	    ** slot of a 16 porter.
+	    ** Attempt to free up this slot (and its parnter if
+	    ** it is a 16 port slot. The second slot will become
+	    ** empty after a call to RIOFreeDisconnected so thats why
+	    ** we look for empty slots above  as well).
+	    */
+	    if (HostP->Mapping[unit].Flags != 0) 
+	    	if (RIOFreeDisconnected(p, HostP, unit) != 0)
+			    continue;
+	    /*
+	    ** If we haven't allocated the first ID then do it now.
+	    */
+	    if (*pID1 == MAX_RUP)
+	    {
+		rio_dprintk (RIO_DEBUG_ROUTE, "Grab tentative entry for first unit %d\n", unit);
+		*pID1 = unit;
+
+		/*
+		** Clear out this slot now that we intend to use it.
+		*/
+		bzero(&HostP->Mapping[unit], sizeof(struct Map));
+
+		/*
+		** If the second ID is not needed then we can return
+		** now.
+		*/
+		if (pID2 == NULL)
+		    return 0;
+	    }
+	    else
+	    {
+		/*
+		** Allocate the second slot and return.
+		*/
+		rio_dprintk (RIO_DEBUG_ROUTE, "Grab tentative/empty  entry for second unit %d\n",
+		      unit);
+		*pID2 = unit;
+
+		/*
+		** Clear out this slot now that we intend to use it.
+		*/
+		bzero(&HostP->Mapping[unit], sizeof(struct Map));
+
+		/* At this point under the right(wrong?) conditions
+		** we may have a first unit ID being higher than the
+		** second unit ID. This is a bad idea if we are about
+		** to fill the slots with a 16 port RTA.
+		** Better check and swap them over.
+		*/
+
+		if (*pID1 > *pID2)
+		{
+			rio_dprintk (RIO_DEBUG_ROUTE, "Swapping IDS %d %d\n", *pID1, *pID2);
+			tempID = *pID1;
+			*pID1 = *pID2;
+			*pID2 = tempID;
+		}
+		return 0;
+	    }
+	}
+    }
+
+    /*
+    ** If we manage to get to the end of the second loop then we
+    ** can give up and return a failure.
+    */
+    return 1;
+}
+
+
+/*
+** The link switch scenario.
+**
+** Rta Wun (A) is connected to Tuw (A).
+** The tables are all up to date, and the system is OK.
+**
+** If Wun (A) is now moved to Wun (B) before Wun (A) can
+** become disconnected, then the follow happens:
+**
+** Tuw (A) spots the change of unit:link at the other end
+** of its link and Tuw sends a topology packet reflecting
+** the change: Tuw (A) now disconnected from Wun (A), and
+** this is closely followed by a packet indicating that 
+** Tuw (A) is now connected to Wun (B).
+**
+** Wun (B) will spot that it has now become connected, and
+** Wun will send a topology packet, which indicates that
+** both Wun (A) and Wun (B) is connected to Tuw (A).
+**
+** Eventually Wun (A) realises that it is now disconnected
+** and Wun will send out a topology packet indicating that
+** Wun (A) is now disconnected.
+*/
diff --git a/drivers/char/rio/riospace.h b/drivers/char/rio/riospace.h
new file mode 100644
index 0000000..32b09b0
--- /dev/null
+++ b/drivers/char/rio/riospace.h
@@ -0,0 +1,161 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: riospace.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:13
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)riospace.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_riospace_h__
+#define __rio_riospace_h__
+
+#ifdef SCCS_LABELS
+static char *_riospace_h_sccs_ = "@(#)riospace.h	1.2";
+#endif
+
+#define	RIO_LOCATOR_LEN	16
+#define	MAX_RIO_BOARDS	4
+
+/*
+** DONT change this file. At all. Unless you can rebuild the entire
+** device driver, which you probably can't, then the rest of the
+** driver won't see any changes you make here. So don't make any.
+** In particular, it won't be able to see changes to RIO_SLOTS
+*/
+
+struct Conf
+{
+	char         Locator[24];
+	unsigned int StartupTime;
+	unsigned int SlowCook;
+	unsigned int IntrPollTime;
+	unsigned int BreakInterval;
+	unsigned int Timer;
+	unsigned int RtaLoadBase;
+	unsigned int HostLoadBase;
+	unsigned int XpHz;
+	unsigned int XpCps;
+	char         *XpOn;
+	char         *XpOff;
+	unsigned int MaxXpCps;
+	unsigned int MinXpCps;
+	unsigned int SpinCmds;
+	unsigned int FirstAddr;
+	unsigned int LastAddr;
+	unsigned int BufferSize;
+	unsigned int LowWater;
+	unsigned int LineLength;
+	unsigned int CmdTime;
+};
+
+/*
+**	Board types - these MUST correspond to product codes!
+*/ 
+#define	RIO_EMPTY	0x0
+#define	RIO_EISA	0x3
+#define	RIO_RTA_16	0x9
+#define	RIO_AT		0xA
+#define	RIO_MCA		0xB
+#define	RIO_PCI		0xD
+#define	RIO_RTA		0xE
+
+/*
+**	Board data structure. This is used for configuration info
+*/
+struct	Brd
+{
+    unsigned char Type;	/* RIO_EISA, RIO_MCA, RIO_AT, RIO_EMPTY... */
+    unsigned char Ivec;	/* POLLED or ivec number */
+    unsigned char Mode;	/* Control stuff, see below */
+};
+
+struct	Board
+{
+    char       Locator[RIO_LOCATOR_LEN];
+    int        NumSlots;
+    struct Brd Boards[MAX_RIO_BOARDS];
+};
+
+#define	BOOT_FROM_LINK		0x00
+#define	BOOT_FROM_RAM		0x01
+#define	EXTERNAL_BUS_OFF	0x00
+#define	EXTERNAL_BUS_ON		0x02
+#define	INTERRUPT_DISABLE	0x00
+#define	INTERRUPT_ENABLE	0x04
+#define	BYTE_OPERATION		0x00
+#define	WORD_OPERATION		0x08
+#define	POLLED			INTERRUPT_DISABLE
+#define	IRQ_15			(0x00 | INTERRUPT_ENABLE)
+#define	IRQ_12			(0x10 | INTERRUPT_ENABLE)
+#define	IRQ_11			(0x20 | INTERRUPT_ENABLE)
+#define	IRQ_9			(0x30 | INTERRUPT_ENABLE)
+#define	SLOW_LINKS		0x00
+#define	FAST_LINKS		0x40
+#define	SLOW_AT_BUS		0x00
+#define	FAST_AT_BUS		0x80
+#define	SLOW_PCI_TP		0x00
+#define	FAST_PCI_TP		0x80
+/*
+**	Debug levels
+*/
+#define	DBG_NONE	0x00000000
+
+#define	DBG_INIT	0x00000001
+#define	DBG_OPEN	0x00000002
+#define	DBG_CLOSE	0x00000004
+#define	DBG_IOCTL	0x00000008
+
+#define	DBG_READ	0x00000010
+#define	DBG_WRITE	0x00000020
+#define	DBG_INTR	0x00000040
+#define	DBG_PROC	0x00000080
+
+#define	DBG_PARAM	0x00000100
+#define	DBG_CMD		0x00000200
+#define	DBG_XPRINT	0x00000400
+#define	DBG_POLL	0x00000800
+
+#define	DBG_DAEMON	0x00001000
+#define	DBG_FAIL	0x00002000
+#define DBG_MODEM	0x00004000
+#define	DBG_LIST	0x00008000
+
+#define	DBG_ROUTE	0x00010000
+#define DBG_UTIL        0x00020000
+#define DBG_BOOT	0x00040000
+#define DBG_BUFFER	0x00080000
+
+#define	DBG_MON		0x00100000
+#define DBG_SPECIAL     0x00200000
+#define	DBG_VPIX	0x00400000
+#define	DBG_FLUSH	0x00800000
+
+#define	DBG_QENABLE	0x01000000
+
+#define	DBG_ALWAYS	0x80000000
+
+#endif /* __rio_riospace_h__ */
diff --git a/drivers/char/rio/riotable.c b/drivers/char/rio/riotable.c
new file mode 100644
index 0000000..8fb26ad
--- /dev/null
+++ b/drivers/char/rio/riotable.c
@@ -0,0 +1,1044 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: riotable.c
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 10:33:47
+**	Retrieved	: 11/6/98 10:33:50
+**
+**  ident @(#)riotable.c	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_riotable_c_sccs_ = "@(#)riotable.c	1.2";
+#endif
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/string.h>
+
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+#include "protsts.h"
+
+/*
+** A configuration table has been loaded. It is now up to us
+** to sort it out and use the information contained therein.
+*/
+int
+RIONewTable(p)
+struct rio_info *	p;
+{
+	int Host, Host1, Host2, NameIsUnique, Entry, SubEnt;
+	struct Map *MapP;
+	struct Map *HostMapP;
+	struct Host *HostP;
+
+	char *cptr;
+
+	/*
+	** We have been sent a new table to install. We need to break
+	** it down into little bits and spread it around a bit to see
+	** what we have got.
+	*/
+	/*
+	** Things to check:
+	** (things marked 'xx' aren't checked any more!)
+	** (1)	That there are no booted Hosts/RTAs out there.
+	** (2)	That the names are properly formed
+	** (3)	That blank entries really are.
+	** xx (4)	That hosts mentioned in the table actually exist. xx
+	** (5)	That the IDs are unique (per host).
+	** (6)	That host IDs are zero
+	** (7)	That port numbers are valid
+	** (8)	That port numbers aren't duplicated
+	** (9)	That names aren't duplicated
+	** xx (10) That hosts that actually exist are mentioned in the table. xx
+	*/
+	rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(1)\n"); 
+	if ( p->RIOSystemUp ) {		/* (1) */
+		p->RIOError.Error = HOST_HAS_ALREADY_BEEN_BOOTED;
+		return -EBUSY;
+	}
+
+	p->RIOError.Error = NOTHING_WRONG_AT_ALL;
+	p->RIOError.Entry = -1;
+	p->RIOError.Other = -1;
+
+	for ( Entry=0; Entry<TOTAL_MAP_ENTRIES; Entry++ ) {
+		MapP = &p->RIOConnectTable[Entry];
+		if ((MapP->Flags & RTA16_SECOND_SLOT) == 0) {
+			rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(2)\n");
+			cptr = MapP->Name;		/* (2) */
+			cptr[MAX_NAME_LEN-1]='\0';
+			if ( cptr[0]=='\0' ) {
+				bcopy(MapP->RtaUniqueNum?"RTA	NN":"HOST NN",MapP->Name,8);
+				MapP->Name[5] = '0'+Entry/10;
+				MapP->Name[6] = '0'+Entry%10;
+			}
+			while ( *cptr ) {
+				if ( *cptr<' ' || *cptr>'~' ) {
+					p->RIOError.Error = BAD_CHARACTER_IN_NAME;
+					p->RIOError.Entry = Entry;
+					return -ENXIO;
+				}
+				cptr++;
+			}
+		}
+
+		/*
+		** If the entry saved was a tentative entry then just forget
+		** about it.
+		*/
+		if ( MapP->Flags & SLOT_TENTATIVE ) {
+			MapP->HostUniqueNum = 0;
+			MapP->RtaUniqueNum = 0;
+			continue;
+		}
+
+		rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(3)\n");
+		if ( !MapP->RtaUniqueNum && !MapP->HostUniqueNum ) { /* (3) */
+			if ( MapP->ID || MapP->SysPort || MapP->Flags ) {
+				rio_dprintk (RIO_DEBUG_TABLE, "%s pretending to be empty but isn't\n",MapP->Name);
+				p->RIOError.Error = TABLE_ENTRY_ISNT_PROPERLY_NULL;
+				p->RIOError.Entry = Entry;
+				return -ENXIO;
+			}
+			rio_dprintk (RIO_DEBUG_TABLE, "!RIO: Daemon: test (3) passes\n");
+			continue;
+		}
+
+		rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(4)\n");
+		for ( Host=0; Host<p->RIONumHosts; Host++ ) { /* (4) */
+			if ( p->RIOHosts[Host].UniqueNum==MapP->HostUniqueNum ) {
+				HostP = &p->RIOHosts[Host];
+				/*
+				** having done the lookup, we don't really want to do
+				** it again, so hang the host number in a safe place
+				*/
+				MapP->Topology[0].Unit = Host;
+				break;
+			}
+		}
+
+		if ( Host >= p->RIONumHosts ) {
+			rio_dprintk (RIO_DEBUG_TABLE, "RTA %s has unknown host unique number 0x%x\n",
+									MapP->Name, MapP->HostUniqueNum);
+			MapP->HostUniqueNum = 0;
+			/* MapP->RtaUniqueNum	= 0; */
+			/* MapP->ID			= 0; */
+			/* MapP->Flags		 = 0; */
+			/* MapP->SysPort		 = 0; */
+			/* MapP->Name[0]		 = 0; */
+			continue;
+		}
+
+		rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(5)\n"); 
+		if ( MapP->RtaUniqueNum ) { /* (5) */
+			if ( !MapP->ID ) {
+				rio_dprintk (RIO_DEBUG_TABLE, "RIO: RTA %s has been allocated an ID of zero!\n",
+							MapP->Name);
+				p->RIOError.Error		 = ZERO_RTA_ID;
+				p->RIOError.Entry = Entry;
+				return -ENXIO;
+			}
+			if ( MapP->ID > MAX_RUP ) {
+				rio_dprintk (RIO_DEBUG_TABLE, "RIO: RTA %s has been allocated an invalid ID %d\n",
+							MapP->Name, MapP->ID);
+				p->RIOError.Error = ID_NUMBER_OUT_OF_RANGE;
+				p->RIOError.Entry = Entry;
+				return -ENXIO;
+			}
+			for ( SubEnt=0; SubEnt<Entry; SubEnt++ ) {
+				if ( MapP->HostUniqueNum == 
+						p->RIOConnectTable[SubEnt].HostUniqueNum && 
+						MapP->ID == p->RIOConnectTable[SubEnt].ID ) {
+					rio_dprintk (RIO_DEBUG_TABLE, "Dupl. ID number allocated to RTA %s and RTA %s\n",
+							MapP->Name, p->RIOConnectTable[SubEnt].Name);
+					p->RIOError.Error = DUPLICATED_RTA_ID;
+					p->RIOError.Entry = Entry;
+					p->RIOError.Other = SubEnt;
+					return -ENXIO;
+				}
+				/*
+				** If the RtaUniqueNum is the same, it may be looking at both
+				** entries for a 16 port RTA, so check the ids
+				*/
+				if ((MapP->RtaUniqueNum == 
+						p->RIOConnectTable[SubEnt].RtaUniqueNum)
+				 		&& (MapP->ID2 != p->RIOConnectTable[SubEnt].ID)) {
+					rio_dprintk (RIO_DEBUG_TABLE, "RTA %s has duplicate unique number\n",MapP->Name);
+					rio_dprintk (RIO_DEBUG_TABLE, "RTA %s has duplicate unique number\n",
+										p->RIOConnectTable[SubEnt].Name);
+					p->RIOError.Error = DUPLICATE_UNIQUE_NUMBER;
+					p->RIOError.Entry = Entry;
+					p->RIOError.Other = SubEnt;
+					return -ENXIO;
+				}
+			}
+			rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(7a)\n"); 
+			/* (7a) */
+			if ((MapP->SysPort != NO_PORT)&&(MapP->SysPort % PORTS_PER_RTA)) {
+				rio_dprintk (RIO_DEBUG_TABLE, "TTY Port number %d-RTA %s is not a multiple of %d!\n",
+					(int)MapP->SysPort,MapP->Name, PORTS_PER_RTA);
+				p->RIOError.Error = TTY_NUMBER_OUT_OF_RANGE;
+				p->RIOError.Entry = Entry;
+				return -ENXIO;
+			}
+			rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(7b)\n"); 
+			/* (7b) */
+			if ((MapP->SysPort != NO_PORT)&&(MapP->SysPort >= RIO_PORTS)) {
+				rio_dprintk (RIO_DEBUG_TABLE, "TTY Port number %d for RTA %s is too big\n",
+							(int)MapP->SysPort, MapP->Name);
+				p->RIOError.Error = TTY_NUMBER_OUT_OF_RANGE;
+				p->RIOError.Entry = Entry;
+				return -ENXIO;
+			}
+			for ( SubEnt=0; SubEnt<Entry; SubEnt++ ) {
+				if ( p->RIOConnectTable[SubEnt].Flags & RTA16_SECOND_SLOT )
+						continue;
+				if ( p->RIOConnectTable[SubEnt].RtaUniqueNum ) {
+					rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(8)\n"); 
+					/* (8) */
+					if ( (MapP->SysPort != NO_PORT) && (MapP->SysPort == 
+									p->RIOConnectTable[SubEnt].SysPort) ) {
+						rio_dprintk (RIO_DEBUG_TABLE, "RTA %s:same TTY port # as RTA %s (%d)\n",
+							MapP->Name, p->RIOConnectTable[SubEnt].Name,
+							(int)MapP->SysPort);
+						p->RIOError.Error = TTY_NUMBER_IN_USE;
+						p->RIOError.Entry = Entry;
+						p->RIOError.Other = SubEnt;
+						return -ENXIO;
+					}
+					rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(9)\n"); 
+					if (strcmp(MapP->Name,
+							p->RIOConnectTable[SubEnt].Name)==0 && !(MapP->Flags & RTA16_SECOND_SLOT)) { /* (9) */
+						rio_dprintk (RIO_DEBUG_TABLE, "RTA name %s used twice\n", MapP->Name);
+						p->RIOError.Error = NAME_USED_TWICE;
+						p->RIOError.Entry = Entry;
+						p->RIOError.Other = SubEnt;
+						return -ENXIO;
+					}
+				}
+			}
+		}
+		else { /* (6) */
+			rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: entering(6)\n"); 
+			if ( MapP->ID ) {
+				rio_dprintk (RIO_DEBUG_TABLE, "RIO:HOST %s has been allocated ID that isn't zero!\n",
+					MapP->Name);
+				p->RIOError.Error = HOST_ID_NOT_ZERO;
+				p->RIOError.Entry = Entry;
+				return -ENXIO;
+			}
+			if ( MapP->SysPort != NO_PORT ) {
+				rio_dprintk (RIO_DEBUG_TABLE, "RIO: HOST %s has been allocated port numbers!\n",
+					MapP->Name);
+				p->RIOError.Error = HOST_SYSPORT_BAD;
+				p->RIOError.Entry = Entry;
+				return -ENXIO;
+			}
+		}
+	}
+
+	/*
+	** wow! if we get here then it's a goody!
+	*/
+
+	/*
+	** Zero the (old) entries for each host...
+	*/
+	for ( Host=0; Host<RIO_HOSTS; Host++ ) {
+		for ( Entry=0; Entry<MAX_RUP; Entry++ ) {
+			bzero((caddr_t)&p->RIOHosts[Host].Mapping[Entry], 
+											sizeof(struct Map));
+		}
+		bzero((caddr_t)&p->RIOHosts[Host].Name[0],
+								sizeof(p->RIOHosts[Host].Name) );
+	}
+
+	/*
+	** Copy in the new table entries
+	*/
+	for ( Entry=0; Entry< TOTAL_MAP_ENTRIES; Entry++ ) {
+		rio_dprintk (RIO_DEBUG_TABLE, "RIONewTable: Copy table for Host entry %d\n", Entry);
+		MapP = &p->RIOConnectTable[Entry];
+
+		/*
+		** Now, if it is an empty slot ignore it!
+		*/
+		if ( MapP->HostUniqueNum==0 )
+			continue;
+
+		/*
+		** we saved the host number earlier, so grab it back
+		*/
+		HostP = &p->RIOHosts[MapP->Topology[0].Unit];
+
+		/*
+		** If it is a host, then we only need to fill in the name field.
+		*/
+		if ( MapP->ID==0 ) {
+			rio_dprintk (RIO_DEBUG_TABLE, "Host entry found. Name %s\n", MapP->Name);
+			bcopy(MapP->Name,HostP->Name,MAX_NAME_LEN);
+			continue;
+		}
+
+		/*
+		** Its an RTA entry, so fill in the host mapping entries for it
+		** and the port mapping entries. Notice that entry zero is for
+		** ID one.
+		*/
+		HostMapP = &HostP->Mapping[MapP->ID-1];
+
+		if (MapP->Flags & SLOT_IN_USE) {
+			rio_dprintk (RIO_DEBUG_TABLE, "Rta entry found. Name %s\n", MapP->Name);
+			/*
+			** structure assign, then sort out the bits we shouldn't have done
+			*/
+			*HostMapP = *MapP;
+
+			HostMapP->Flags = SLOT_IN_USE;
+			if (MapP->Flags & RTA16_SECOND_SLOT)
+				HostMapP->Flags |= RTA16_SECOND_SLOT;
+
+			RIOReMapPorts(p, HostP, HostMapP );
+		}
+		else {
+			rio_dprintk (RIO_DEBUG_TABLE, "TENTATIVE Rta entry found. Name %s\n", MapP->Name);
+		}
+	}
+
+	for ( Entry=0; Entry< TOTAL_MAP_ENTRIES; Entry++ ) {
+		p->RIOSavedTable[Entry] = p->RIOConnectTable[Entry];
+	}
+
+	for ( Host=0; Host<p->RIONumHosts; Host++ ) {
+		for ( SubEnt=0; SubEnt<LINKS_PER_UNIT; SubEnt++ ) {
+			p->RIOHosts[Host].Topology[SubEnt].Unit = ROUTE_DISCONNECT;
+			p->RIOHosts[Host].Topology[SubEnt].Link = NO_LINK;
+		}
+		for ( Entry=0; Entry<MAX_RUP; Entry++ ) {
+			for ( SubEnt=0; SubEnt<LINKS_PER_UNIT; SubEnt++ ) {
+				p->RIOHosts[Host].Mapping[Entry].Topology[SubEnt].Unit = 
+								ROUTE_DISCONNECT;
+				p->RIOHosts[Host].Mapping[Entry].Topology[SubEnt].Link = 
+								NO_LINK;
+			}
+		}
+		if ( !p->RIOHosts[Host].Name[0] ) {
+			bcopy("HOST 1",p->RIOHosts[Host].Name,7);
+			p->RIOHosts[Host].Name[5] += Host;
+		}
+		/*
+		** Check that default name assigned is unique.
+		*/
+		Host1 = Host;
+		NameIsUnique = 0;
+		while (!NameIsUnique) {
+			NameIsUnique = 1;
+			for ( Host2=0; Host2<p->RIONumHosts; Host2++ ) {
+				if (Host2 == Host)
+					continue;
+				if (strcmp(p->RIOHosts[Host].Name, p->RIOHosts[Host2].Name)
+									 == 0) {
+					NameIsUnique = 0;
+					Host1++;
+					if (Host1 >= p->RIONumHosts)
+						Host1 = 0;
+					p->RIOHosts[Host].Name[5] = '1' + Host1;
+				}
+			}
+		}
+		/*
+		** Rename host if name already used.
+		*/
+		if (Host1 != Host)
+		{
+			rio_dprintk (RIO_DEBUG_TABLE, "Default name %s already used\n", p->RIOHosts[Host].Name);
+			bcopy("HOST 1",p->RIOHosts[Host].Name,7);
+			p->RIOHosts[Host].Name[5] += Host1;
+		}
+		rio_dprintk (RIO_DEBUG_TABLE, "Assigning default name %s\n", p->RIOHosts[Host].Name);
+	}
+	return 0;
+}
+
+/*
+** User process needs the config table - build it from first
+** principles.
+*/
+int
+RIOApel(p)
+struct rio_info *	p;
+{
+	int Host;
+	int link;
+	int Rup;
+	int Next = 0;
+	struct Map *MapP;
+	struct Host *HostP;
+	long oldspl;
+
+	disable(oldspl);		/* strange but true! */
+ 
+	rio_dprintk (RIO_DEBUG_TABLE, "Generating a table to return to config.rio\n");
+
+	bzero((caddr_t)&p->RIOConnectTable[0], 
+					sizeof(struct Map) * TOTAL_MAP_ENTRIES );
+
+	for ( Host=0; Host<RIO_HOSTS; Host++ ) {
+		rio_dprintk (RIO_DEBUG_TABLE, "Processing host %d\n", Host);
+		HostP = &p->RIOHosts[Host];
+		MapP = &p->RIOConnectTable[Next++];
+		MapP->HostUniqueNum = HostP->UniqueNum;
+		if ( (HostP->Flags & RUN_STATE) != RC_RUNNING )
+			continue;
+		MapP->RtaUniqueNum = 0;
+		MapP->ID = 0;
+		MapP->Flags = SLOT_IN_USE;
+		MapP->SysPort = NO_PORT;
+		for ( link=0; link<LINKS_PER_UNIT; link++ )
+			MapP->Topology[link] = HostP->Topology[link];
+		bcopy(HostP->Name,MapP->Name,MAX_NAME_LEN);
+		for ( Rup=0; Rup<MAX_RUP; Rup++ ) {
+			if ( HostP->Mapping[Rup].Flags & (SLOT_IN_USE|SLOT_TENTATIVE) ) {
+				p->RIOConnectTable[Next] = HostP->Mapping[Rup];
+				if ( HostP->Mapping[Rup].Flags & SLOT_IN_USE)
+					p->RIOConnectTable[Next].Flags |= SLOT_IN_USE;
+				if ( HostP->Mapping[Rup].Flags & SLOT_TENTATIVE)
+					p->RIOConnectTable[Next].Flags |= SLOT_TENTATIVE;
+				if ( HostP->Mapping[Rup].Flags & RTA16_SECOND_SLOT )
+					p->RIOConnectTable[Next].Flags |= RTA16_SECOND_SLOT;
+				Next++;
+			}
+		}
+	}
+	restore(oldspl);
+	return 0;
+}
+
+/*
+** config.rio has taken a dislike to one of the gross maps entries.
+** if the entry is suitably inactive, then we can gob on it and remove
+** it from the table.
+*/
+int
+RIODeleteRta(p, MapP)
+struct rio_info *p;
+struct Map *MapP;
+{
+	int host, entry, port, link;
+	int SysPort;
+	struct Host *HostP;
+	struct Map *HostMapP;
+	struct Port *PortP;
+	int work_done = 0;
+	unsigned long lock_flags, sem_flags;
+
+	rio_dprintk (RIO_DEBUG_TABLE, "Delete entry on host %x, rta %x\n",
+								MapP->HostUniqueNum, MapP->RtaUniqueNum);
+
+	for ( host=0; host < p->RIONumHosts; host++ ) {
+		HostP = &p->RIOHosts[host];
+
+		rio_spin_lock_irqsave( &HostP->HostLock, lock_flags );
+
+		if ( (HostP->Flags & RUN_STATE) != RC_RUNNING ) {
+			rio_spin_unlock_irqrestore(&HostP->HostLock, lock_flags);
+			continue;
+		}
+
+		for ( entry=0; entry<MAX_RUP; entry++ ) {
+			if ( MapP->RtaUniqueNum == HostP->Mapping[entry].RtaUniqueNum ) {
+				HostMapP = &HostP->Mapping[entry];
+				rio_dprintk (RIO_DEBUG_TABLE, "Found entry offset %d on host %s\n", 
+						entry, HostP->Name);
+
+				/*
+				** Check all four links of the unit are disconnected
+				*/
+				for ( link=0; link< LINKS_PER_UNIT; link++ ) {
+					if ( HostMapP->Topology[link].Unit != ROUTE_DISCONNECT ) {
+						rio_dprintk (RIO_DEBUG_TABLE, "Entry is in use and cannot be deleted!\n");
+						p->RIOError.Error = UNIT_IS_IN_USE;
+						rio_spin_unlock_irqrestore( &HostP->HostLock, lock_flags);
+						return -EBUSY;
+					}
+				}
+				/*
+				** Slot has been allocated, BUT not booted/routed/
+				** connected/selected or anything else-ed
+				*/
+				SysPort = HostMapP->SysPort;
+
+				if ( SysPort != NO_PORT ) {
+					for (port=SysPort; port < SysPort+PORTS_PER_RTA; port++) {
+						PortP = p->RIOPortp[port];
+						rio_dprintk (RIO_DEBUG_TABLE, "Unmap port\n");
+
+						rio_spin_lock_irqsave( &PortP->portSem, sem_flags );
+
+						PortP->Mapped = 0;
+
+						if ( PortP->State & (RIO_MOPEN|RIO_LOPEN) ) {
+
+							rio_dprintk (RIO_DEBUG_TABLE, "Gob on port\n");
+							PortP->TxBufferIn = PortP->TxBufferOut = 0;
+							/* What should I do 
+							wakeup( &PortP->TxBufferIn );
+							wakeup( &PortP->TxBufferOut);
+							*/
+							PortP->InUse = NOT_INUSE;
+							/* What should I do 
+							wakeup( &PortP->InUse );
+							signal(PortP->TtyP->t_pgrp,SIGKILL);
+							ttyflush(PortP->TtyP,(FREAD|FWRITE));
+							*/
+							PortP->State |= RIO_CLOSING | RIO_DELETED;
+						}
+
+						/*
+						** For the second slot of a 16 port RTA, the
+						** driver needs to reset the changes made to
+						** the phb to port mappings in RIORouteRup.
+						*/
+						if (PortP->SecondBlock) {
+							ushort dest_unit = HostMapP->ID;
+							ushort dest_port = port - SysPort;
+							WORD	 *TxPktP;
+							PKT	*Pkt;
+
+							for (TxPktP = PortP->TxStart;
+								TxPktP <= PortP->TxEnd; TxPktP++) {
+								/*
+								** *TxPktP is the pointer to the
+								** transmit packet on the host card.
+								** This needs to be translated into
+								** a 32 bit pointer so it can be
+								** accessed from the driver.
+								*/
+								Pkt = (PKT *) RIO_PTR(HostP->Caddr,
+								 	RWORD(*TxPktP));
+								rio_dprintk (RIO_DEBUG_TABLE, 
+						"Tx packet (%x) destination: Old %x:%x New %x:%x\n",
+								 *TxPktP, Pkt->dest_unit,
+								 Pkt->dest_port, dest_unit, dest_port);
+								WWORD(Pkt->dest_unit, dest_unit);
+								WWORD(Pkt->dest_port, dest_port);
+							}
+							rio_dprintk (RIO_DEBUG_TABLE, 
+						"Port %d phb destination: Old %x:%x New %x:%x\n",
+							 port, PortP->PhbP->destination & 0xff,
+							 (PortP->PhbP->destination >> 8) & 0xff,
+							 dest_unit, dest_port);
+							WWORD(PortP->PhbP->destination,
+							 dest_unit + (dest_port << 8));
+						}
+						rio_spin_unlock_irqrestore(&PortP->portSem, sem_flags);
+					}
+				}
+				rio_dprintk (RIO_DEBUG_TABLE, "Entry nulled.\n");
+				bzero((char *)HostMapP,sizeof(struct Map));
+				work_done++;
+			}
+		}
+		rio_spin_unlock_irqrestore(&HostP->HostLock, lock_flags);
+	}
+
+	/* XXXXX lock me up */
+	for ( entry=0; entry< TOTAL_MAP_ENTRIES; entry++ ) {
+		if ( p->RIOSavedTable[entry].RtaUniqueNum == MapP->RtaUniqueNum ) {
+			bzero((char *)&p->RIOSavedTable[entry],sizeof(struct Map));
+			work_done++;
+		}
+		if ( p->RIOConnectTable[entry].RtaUniqueNum == MapP->RtaUniqueNum ) {
+			bzero((char *)&p->RIOConnectTable[entry],sizeof(struct Map));
+			work_done++;
+		}
+	}
+	if ( work_done )
+		return 0;
+
+	rio_dprintk (RIO_DEBUG_TABLE, "Couldn't find entry to be deleted\n");
+	p->RIOError.Error = COULDNT_FIND_ENTRY;
+	return -ENXIO;
+}
+
+int RIOAssignRta( struct rio_info *p, struct Map *MapP )
+{
+    int host;
+    struct Map *HostMapP;
+    char *sptr;
+    int	link;
+
+
+    rio_dprintk (RIO_DEBUG_TABLE, "Assign entry on host %x, rta %x, ID %d, Sysport %d\n",
+				MapP->HostUniqueNum,MapP->RtaUniqueNum,
+				MapP->ID, (int)MapP->SysPort);
+
+    if ((MapP->ID != (ushort)-1) &&
+	((int)MapP->ID < (int)1 || (int)MapP->ID > MAX_RUP ))
+    {
+	rio_dprintk (RIO_DEBUG_TABLE, "Bad ID in map entry!\n");
+	p->RIOError.Error = ID_NUMBER_OUT_OF_RANGE;
+	return -EINVAL;
+    }
+    if (MapP->RtaUniqueNum == 0)
+    {
+	rio_dprintk (RIO_DEBUG_TABLE, "Rta Unique number zero!\n");
+	p->RIOError.Error = RTA_UNIQUE_NUMBER_ZERO;
+	return -EINVAL;
+    }
+    if ( (MapP->SysPort != NO_PORT) && (MapP->SysPort % PORTS_PER_RTA) )
+    {
+	rio_dprintk (RIO_DEBUG_TABLE, "Port %d not multiple of %d!\n",(int)MapP->SysPort,PORTS_PER_RTA);
+	p->RIOError.Error = TTY_NUMBER_OUT_OF_RANGE;
+	return -EINVAL;
+    }
+    if ( (MapP->SysPort != NO_PORT) && (MapP->SysPort >= RIO_PORTS) )
+    {
+	rio_dprintk (RIO_DEBUG_TABLE, "Port %d not valid!\n",(int)MapP->SysPort);
+	p->RIOError.Error = TTY_NUMBER_OUT_OF_RANGE;
+	return -EINVAL;
+    }
+
+    /*
+    ** Copy the name across to the map entry.
+    */
+    MapP->Name[MAX_NAME_LEN-1] = '\0';
+    sptr = MapP->Name;
+    while ( *sptr )
+    {
+    if ( *sptr<' ' || *sptr>'~' )
+    {
+	rio_dprintk (RIO_DEBUG_TABLE, "Name entry contains non-printing characters!\n");
+	p->RIOError.Error = BAD_CHARACTER_IN_NAME;
+	return -EINVAL;
+    }
+    sptr++;
+    }
+
+    for ( host=0; host < p->RIONumHosts; host++ )
+    {
+	if ( MapP->HostUniqueNum == p->RIOHosts[host].UniqueNum )
+	{
+	    if ( (p->RIOHosts[host].Flags & RUN_STATE) != RC_RUNNING )
+	    {
+		p->RIOError.Error = HOST_NOT_RUNNING;
+		return -ENXIO;
+	    }
+
+	    /*
+	    ** Now we have a host we need to allocate an ID
+	    ** if the entry does not already have one.
+	    */
+	    if (MapP->ID == (ushort)-1)
+	    {
+		int nNewID;
+
+		rio_dprintk (RIO_DEBUG_TABLE, "Attempting to get a new ID for rta \"%s\"\n",
+		      MapP->Name);
+		/*
+		** The idea here is to allow RTA's to be assigned
+		** before they actually appear on the network.
+		** This allows the addition of RTA's without having
+		** to plug them in.
+		** What we do is:
+		**  - Find a free ID and allocate it to the RTA.
+		**  - If this map entry is the second half of a
+		**    16 port entry then find the other half and
+		**    make sure the 2 cross reference each other.
+		*/
+		if (RIOFindFreeID(p, &p->RIOHosts[host], &nNewID, NULL) != 0)
+		{
+		    p->RIOError.Error = COULDNT_FIND_ENTRY;
+		    return -EBUSY;
+		}
+		MapP->ID = (ushort)nNewID + 1;
+		rio_dprintk (RIO_DEBUG_TABLE, "Allocated ID %d for this new RTA.\n", MapP->ID);
+		HostMapP = &p->RIOHosts[host].Mapping[nNewID];
+		HostMapP->RtaUniqueNum = MapP->RtaUniqueNum;
+		HostMapP->HostUniqueNum = MapP->HostUniqueNum;
+		HostMapP->ID = MapP->ID;
+		for (link = 0; link < LINKS_PER_UNIT; link++)
+		{
+		    HostMapP->Topology[link].Unit = ROUTE_DISCONNECT;
+		    HostMapP->Topology[link].Link = NO_LINK;
+		}
+		if (MapP->Flags & RTA16_SECOND_SLOT)
+		{
+		    int unit;
+
+		    for (unit = 0; unit < MAX_RUP; unit++)
+			if (p->RIOHosts[host].Mapping[unit].RtaUniqueNum ==
+			    MapP->RtaUniqueNum)
+			    break;
+		    if (unit == MAX_RUP)
+		    {
+			p->RIOError.Error = COULDNT_FIND_ENTRY;
+			return -EBUSY;
+		    }
+		    HostMapP->Flags |= RTA16_SECOND_SLOT;
+		    HostMapP->ID2 = MapP->ID2 = p->RIOHosts[host].Mapping[unit].ID;
+		    p->RIOHosts[host].Mapping[unit].ID2 = MapP->ID;
+		    rio_dprintk (RIO_DEBUG_TABLE, "Cross referenced id %d to ID %d.\n",
+			  MapP->ID,
+			  p->RIOHosts[host].Mapping[unit].ID);
+		}
+	    }
+
+	    HostMapP = &p->RIOHosts[host].Mapping[MapP->ID-1];
+
+	    if ( HostMapP->Flags & SLOT_IN_USE )
+	    {
+		rio_dprintk (RIO_DEBUG_TABLE, "Map table slot for ID %d is already in use.\n", MapP->ID);
+		p->RIOError.Error = ID_ALREADY_IN_USE;
+		return -EBUSY;
+	    }
+
+	    /*
+	    ** Assign the sys ports and the name, and mark the slot as
+	    ** being in use.
+	    */
+	    HostMapP->SysPort = MapP->SysPort;
+	    if ((MapP->Flags & RTA16_SECOND_SLOT) == 0)
+	      CCOPY( MapP->Name, HostMapP->Name, MAX_NAME_LEN );
+	    HostMapP->Flags = SLOT_IN_USE | RTA_BOOTED;
+#if NEED_TO_FIX
+	    RIO_SV_BROADCAST(p->RIOHosts[host].svFlags[MapP->ID-1]);
+#endif
+	    if (MapP->Flags & RTA16_SECOND_SLOT)
+		HostMapP->Flags |= RTA16_SECOND_SLOT;
+
+	    RIOReMapPorts( p, &p->RIOHosts[host], HostMapP );
+	    /*
+	    ** Adjust 2nd block of 8 phbs
+	    */
+	    if (MapP->Flags & RTA16_SECOND_SLOT)
+		RIOFixPhbs(p, &p->RIOHosts[host], HostMapP->ID - 1);
+
+	    if ( HostMapP->SysPort != NO_PORT )
+	    {
+		if ( HostMapP->SysPort < p->RIOFirstPortsBooted )
+		    p->RIOFirstPortsBooted = HostMapP->SysPort;
+		if ( HostMapP->SysPort > p->RIOLastPortsBooted )
+		    p->RIOLastPortsBooted = HostMapP->SysPort;
+	    }
+	    if (MapP->Flags & RTA16_SECOND_SLOT)
+	        rio_dprintk (RIO_DEBUG_TABLE, "Second map of RTA %s added to configuration\n",
+		 p->RIOHosts[host].Mapping[MapP->ID2 - 1].Name);
+	    else
+	        rio_dprintk (RIO_DEBUG_TABLE, "RTA %s added to configuration\n", MapP->Name);
+	    return 0;
+	}
+    }
+    p->RIOError.Error = UNKNOWN_HOST_NUMBER;
+    rio_dprintk (RIO_DEBUG_TABLE, "Unknown host %x\n", MapP->HostUniqueNum);
+    return -ENXIO;
+}
+
+
+int
+RIOReMapPorts(p, HostP, HostMapP)
+struct rio_info *	p;
+struct Host *HostP;
+struct Map *HostMapP; 
+{
+	register struct Port *PortP;
+	uint SubEnt;
+	uint HostPort;
+	uint SysPort;
+	ushort RtaType;
+	unsigned long flags;
+
+#ifdef CHECK
+	CheckHostP( HostP );
+	CheckHostMapP( HostMapP );
+#endif
+
+	rio_dprintk (RIO_DEBUG_TABLE, "Mapping sysport %d to id %d\n", (int)HostMapP->SysPort, HostMapP->ID);
+
+	/*
+	** We need to tell the UnixRups which sysport the rup corresponds to
+	*/
+	HostP->UnixRups[HostMapP->ID-1].BaseSysPort = HostMapP->SysPort;
+
+	if ( HostMapP->SysPort == NO_PORT )
+		return(0);
+
+	RtaType = GetUnitType(HostMapP->RtaUniqueNum);
+	rio_dprintk (RIO_DEBUG_TABLE, "Mapping sysport %d-%d\n",
+				(int)HostMapP->SysPort, (int)HostMapP->SysPort+PORTS_PER_RTA-1);
+
+	/*
+	** now map each of its eight ports
+	*/
+	for ( SubEnt=0; SubEnt<PORTS_PER_RTA; SubEnt++) {
+	  rio_dprintk (RIO_DEBUG_TABLE, "subent = %d, HostMapP->SysPort = %d\n", 
+		  SubEnt, (int)HostMapP->SysPort);
+		SysPort = HostMapP->SysPort+SubEnt;		/* portnumber within system */
+					/* portnumber on host */
+		
+		HostPort = (HostMapP->ID-1)*PORTS_PER_RTA+SubEnt; 
+
+		rio_dprintk (RIO_DEBUG_TABLE, "c1 p = %p, p->rioPortp = %p\n", p, p->RIOPortp);
+		PortP = p->RIOPortp[SysPort];
+#if 0
+		PortP->TtyP	= &p->channel[SysPort];
+#endif
+		rio_dprintk (RIO_DEBUG_TABLE, "Map port\n");
+
+		/*
+		** Point at all the real neat data structures
+		*/
+		rio_spin_lock_irqsave(&PortP->portSem, flags);
+		PortP->HostP = HostP;
+		PortP->Caddr = HostP->Caddr;
+
+		/*
+		** The PhbP cannot be filled in yet
+		** unless the host has been booted
+		*/
+		if ((HostP->Flags & RUN_STATE) == RC_RUNNING) {
+			struct PHB *PhbP = PortP->PhbP = &HostP->PhbP[HostPort];
+			PortP->TxAdd =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->tx_add));
+			PortP->TxStart =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->tx_start));
+			PortP->TxEnd =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->tx_end));
+			PortP->RxRemove=(WORD *)RIO_PTR(HostP->Caddr,
+									RWORD(PhbP->rx_remove));
+			PortP->RxStart =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->rx_start));
+			PortP->RxEnd =(WORD *)RIO_PTR(HostP->Caddr,RWORD(PhbP->rx_end));
+		}
+		else
+			PortP->PhbP = NULL;
+
+		/*
+		** port related flags
+		*/
+		PortP->HostPort	= HostPort;
+		/*
+		** For each part of a 16 port RTA, RupNum is ID - 1.
+		*/
+		PortP->RupNum = HostMapP->ID - 1;
+		if (HostMapP->Flags & RTA16_SECOND_SLOT) {
+			PortP->ID2			 = HostMapP->ID2 - 1;
+			PortP->SecondBlock	 = TRUE;
+		}
+		else {
+			PortP->ID2			 = 0;
+			PortP->SecondBlock	 = FALSE;
+		}
+		PortP->RtaUniqueNum	= HostMapP->RtaUniqueNum;
+
+		/*
+		** If the port was already mapped then thats all we need to do.
+		*/
+		if (PortP->Mapped) {
+			rio_spin_unlock_irqrestore( &PortP->portSem, flags);
+			continue;
+		}
+		else HostMapP->Flags &= ~RTA_NEWBOOT;
+
+		PortP->State		 = 0;
+		PortP->Config		= 0;
+		/*
+		** Check out the module type - if it is special (read only etc.)
+		** then we need to set flags in the PortP->Config.
+		** Note: For 16 port RTA, all ports are of the same type.
+		*/
+		if (RtaType == TYPE_RTA16) {
+			PortP->Config |= p->RIOModuleTypes[HostP->UnixRups
+				[HostMapP->ID-1].ModTypes].Flags[SubEnt % PORTS_PER_MODULE];
+		} else {
+			if ( SubEnt < PORTS_PER_MODULE )
+				PortP->Config |= p->RIOModuleTypes[LONYBLE(HostP->UnixRups
+				[HostMapP->ID-1].ModTypes)].Flags[SubEnt % PORTS_PER_MODULE];
+			else
+				PortP->Config |= p->RIOModuleTypes[HINYBLE(HostP->UnixRups
+				[HostMapP->ID-1].ModTypes)].Flags[SubEnt % PORTS_PER_MODULE];
+		}
+
+		/*
+		** more port related flags
+		*/
+		PortP->PortState	= 0;
+		PortP->ModemLines	= 0;
+		PortP->ModemState	= 0;
+		PortP->CookMode		= COOK_WELL;
+		PortP->ParamSem		= 0;
+		PortP->FlushCmdBodge= 0;
+		PortP->WflushFlag	= 0;
+		PortP->MagicFlags	= 0;
+		PortP->Lock			= 0;
+		PortP->Store		= 0;
+		PortP->FirstOpen	= 1;
+
+		/*
+		** Buffers 'n things
+		*/
+		PortP->RxDataStart	= 0;
+		PortP->Cor2Copy	 = 0;
+		PortP->Name		 = &HostMapP->Name[0];
+#ifdef STATS
+		bzero( (caddr_t)&PortP->Stat, sizeof(struct RIOStats) );
+#endif
+		PortP->statsGather = 0;
+		PortP->txchars = 0;
+		PortP->rxchars = 0;
+		PortP->opens = 0;
+		PortP->closes = 0;
+		PortP->ioctls = 0;
+		if ( PortP->TxRingBuffer )
+			bzero( PortP->TxRingBuffer, p->RIOBufferSize );
+		else if ( p->RIOBufferSize ) {
+			PortP->TxRingBuffer = sysbrk(p->RIOBufferSize);
+			bzero( PortP->TxRingBuffer, p->RIOBufferSize );
+		}
+		PortP->TxBufferOut	= 0;
+		PortP->TxBufferIn	 = 0;
+		PortP->Debug		= 0;
+		/*
+		** LastRxTgl stores the state of the rx toggle bit for this
+		** port, to be compared with the state of the next pkt received.
+		** If the same, we have received the same rx pkt from the RTA
+		** twice. Initialise to a value not equal to PHB_RX_TGL or 0.
+		*/
+		PortP->LastRxTgl	= ~(uchar)PHB_RX_TGL;
+
+		/*
+		** and mark the port as usable
+		*/
+		PortP->Mapped = 1;
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	}
+	if ( HostMapP->SysPort < p->RIOFirstPortsMapped )
+		p->RIOFirstPortsMapped = HostMapP->SysPort;
+	if ( HostMapP->SysPort > p->RIOLastPortsMapped )
+		p->RIOLastPortsMapped = HostMapP->SysPort;
+
+	return 0;
+}
+
+int
+RIOChangeName(p, MapP)
+struct rio_info *p;
+struct Map* MapP; 
+{
+	int host;
+	struct Map *HostMapP;
+	char *sptr;
+
+	rio_dprintk (RIO_DEBUG_TABLE, "Change name entry on host %x, rta %x, ID %d, Sysport %d\n",
+								MapP->HostUniqueNum,MapP->RtaUniqueNum,
+								MapP->ID, (int)MapP->SysPort);
+
+	if ( MapP->ID > MAX_RUP ) {
+		rio_dprintk (RIO_DEBUG_TABLE, "Bad ID in map entry!\n");
+		p->RIOError.Error = ID_NUMBER_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+
+	MapP->Name[MAX_NAME_LEN-1] = '\0';
+	sptr = MapP->Name;
+
+	while ( *sptr ) {
+		if ( *sptr<' ' || *sptr>'~' ) {
+			rio_dprintk (RIO_DEBUG_TABLE, "Name entry contains non-printing characters!\n");
+			p->RIOError.Error = BAD_CHARACTER_IN_NAME;
+			return -EINVAL;
+		}
+		sptr++;
+	}
+
+	for ( host=0; host < p->RIONumHosts; host++ ) {
+		if ( MapP->HostUniqueNum == p->RIOHosts[host].UniqueNum ) {
+			if ( (p->RIOHosts[host].Flags & RUN_STATE) != RC_RUNNING ) {
+				p->RIOError.Error = HOST_NOT_RUNNING;
+				return -ENXIO;
+			}
+			if ( MapP->ID==0 ) {
+				CCOPY( MapP->Name, p->RIOHosts[host].Name, MAX_NAME_LEN );
+				return 0;
+			}
+
+			HostMapP = &p->RIOHosts[host].Mapping[MapP->ID-1];
+
+			if ( HostMapP->RtaUniqueNum != MapP->RtaUniqueNum ) {
+				p->RIOError.Error = RTA_NUMBER_WRONG;
+				return -ENXIO;
+			}
+			CCOPY( MapP->Name, HostMapP->Name, MAX_NAME_LEN );
+			return 0;
+		}
+	}
+	p->RIOError.Error = UNKNOWN_HOST_NUMBER;
+	rio_dprintk (RIO_DEBUG_TABLE, "Unknown host %x\n", MapP->HostUniqueNum);
+	return -ENXIO;
+}
diff --git a/drivers/char/rio/riotime.h b/drivers/char/rio/riotime.h
new file mode 100644
index 0000000..66d52bc
--- /dev/null
+++ b/drivers/char/rio/riotime.h
@@ -0,0 +1,63 @@
+/****************************************************************************
+ *******                                                              *******
+ *******            T I M E
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _riotime_h
+#define _riotime_h 1
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_riotime_h_sccs = "@(#)riotime.h	1.1" ;
+#endif
+#endif
+
+#define TWO_POWER_FIFTEEN (ushort)32768
+#define RioTime()    riotime
+#define RioTimeAfter(time1,time2) ((ushort)time1 - (ushort)time2) < TWO_POWER_FIFTEEN
+#define RioTimePlus(time1,time2) ((ushort)time1 + (ushort)time2)
+
+/**************************************
+ * Convert a RIO tick (1/10th second)
+ * into transputer low priority ticks
+ *************************************/ 
+#define RioTimeToLow(time) (time*(100000 / 64))
+#define RioLowToTime(time) ((time*64)/100000)
+
+#define RIOTENTHSECOND (ushort)1
+#define RIOSECOND (ushort)(RIOTENTHSECOND * 10)
+#endif
+
+/*********** end of file ***********/
diff --git a/drivers/char/rio/riotty.c b/drivers/char/rio/riotty.c
new file mode 100644
index 0000000..db65500
--- /dev/null
+++ b/drivers/char/rio/riotty.c
@@ -0,0 +1,1376 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: riotty.c
+**	SID		: 1.3
+**	Last Modified	: 11/6/98 10:33:47
+**	Retrieved	: 11/6/98 10:33:50
+**
+**  ident @(#)riotty.c	1.3
+**
+** -----------------------------------------------------------------------------
+*/
+#ifdef SCCS_LABELS
+static char *_riotty_c_sccs_ = "@(#)riotty.c	1.3";
+#endif
+
+
+#define __EXPLICIT_DEF_H__
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/string.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/string.h>
+#include <asm/semaphore.h>
+#include <asm/uaccess.h>
+
+#include <linux/termios.h>
+
+#include <linux/serial.h>
+
+#include <linux/generic_serial.h>
+
+
+#include "linux_compat.h"
+#include "rio_linux.h"
+#include "typdef.h"
+#include "pkt.h"
+#include "daemon.h"
+#include "rio.h"
+#include "riospace.h"
+#include "top.h"
+#include "cmdpkt.h"
+#include "map.h"
+#include "riotypes.h"
+#include "rup.h"
+#include "port.h"
+#include "riodrvr.h"
+#include "rioinfo.h"
+#include "func.h"
+#include "errors.h"
+#include "pci.h"
+
+#include "parmmap.h"
+#include "unixrup.h"
+#include "board.h"
+#include "host.h"
+#include "error.h"
+#include "phb.h"
+#include "link.h"
+#include "cmdblk.h"
+#include "route.h"
+#include "control.h"
+#include "cirrus.h"
+#include "rioioctl.h"
+#include "param.h"
+#include "list.h"
+#include "sam.h"
+
+#if 0
+static void ttyseth_pv(struct Port *, struct ttystatics *, 
+				struct termios *sg, int);
+#endif
+
+static void RIOClearUp(struct Port *PortP);
+int RIOShortCommand(struct rio_info *p, struct Port *PortP, 
+			   int command, int len, int arg);
+
+#if 0
+static int RIOCookMode(struct ttystatics *);
+#endif
+
+extern int	conv_vb[];	/* now defined in ttymgr.c */
+extern int	conv_bv[];	/* now defined in ttymgr.c */
+ 
+/*
+** 16.09.1998 ARG - Fix to build riotty.k.o for Modular Kernel Support
+**
+** ep.def.h is necessary for Modular Kernel Support
+** DO NOT place any kernel 'extern's after this line
+** or this source file will not build riotty.k.o
+*/
+#ifdef uLYNX
+#include <ep.def.h>
+#endif
+
+#ifdef NEED_THIS2
+static struct old_sgttyb 
+default_sg = 
+{ 
+	B19200, B19200,				/* input and output speed */ 
+	'H' - '@',					/* erase char */ 
+	-1,							/* 2nd erase char */ 
+	'U' - '@',					/* kill char */ 
+	ECHO | CRMOD,				/* mode */ 
+	'C' - '@',					/* interrupt character */ 
+	'\\' - '@',					/* quit char */ 
+	'Q' - '@',					/* start char */
+	'S' - '@',					/* stop char */ 
+	'D' - '@',					/* EOF */
+	-1,							/* brk */
+	(LCRTBS | LCRTERA | LCRTKIL | LCTLECH),	/* local mode word */ 
+	'Z' - '@',					/* process stop */
+	'Y' - '@',					/* delayed stop */
+	'R' - '@',					/* reprint line */ 
+	'O' - '@',					/* flush output */
+	'W' - '@',					/* word erase */
+	'V' - '@'					/* literal next char */
+};
+#endif
+
+
+extern struct rio_info *p;
+
+
+int
+riotopen(struct tty_struct * tty, struct file * filp)
+{
+	register uint SysPort;
+	int Modem;
+	int repeat_this = 250;
+	struct Port *PortP;		 /* pointer to the port structure */
+	unsigned long flags;
+	int retval = 0;
+
+	func_enter ();
+
+	/* Make sure driver_data is NULL in case the rio isn't booted jet. Else gs_close
+	   is going to oops.
+	*/
+	tty->driver_data = NULL;
+        
+	SysPort = rio_minor(tty);
+	Modem   = rio_ismodem(tty);
+
+	if ( p->RIOFailed ) {
+		rio_dprintk (RIO_DEBUG_TTY, "System initialisation failed\n");
+		pseterr(ENXIO);
+		func_exit ();
+		return -ENXIO;
+	}
+
+	rio_dprintk (RIO_DEBUG_TTY, "port open SysPort %d (%s) (mapped:%d)\n",
+	       SysPort,  Modem ? "Modem" : "tty",
+				   p->RIOPortp[SysPort]->Mapped);
+
+	/*
+	** Validate that we have received a legitimate request.
+	** Currently, just check that we are opening a port on
+	** a host card that actually exists, and that the port
+	** has been mapped onto a host.
+	*/
+	if (SysPort >= RIO_PORTS) {	/* out of range ? */
+		rio_dprintk (RIO_DEBUG_TTY, "Illegal port number %d\n",SysPort);
+		pseterr(ENXIO);
+		func_exit();
+		return -ENXIO;
+	}
+
+	/*
+	** Grab pointer to the port stucture
+	*/
+	PortP = p->RIOPortp[SysPort];	/* Get control struc */
+	rio_dprintk (RIO_DEBUG_TTY, "PortP: %p\n", PortP);
+	if ( !PortP->Mapped ) {	/* we aren't mapped yet! */
+		/*
+		** The system doesn't know which RTA this port
+		** corresponds to.
+		*/
+		rio_dprintk (RIO_DEBUG_TTY, "port not mapped into system\n");
+		func_exit ();
+		pseterr(ENXIO);
+		return -ENXIO;
+	}
+
+	tty->driver_data = PortP;
+
+	PortP->gs.tty = tty;
+	PortP->gs.count++;
+
+	rio_dprintk (RIO_DEBUG_TTY, "%d bytes in tx buffer\n",
+				   PortP->gs.xmit_cnt);
+
+	retval = gs_init_port (&PortP->gs);
+	if (retval) {
+		PortP->gs.count--;
+		return -ENXIO;
+	}
+	/*
+	** If the host hasn't been booted yet, then 
+	** fail
+	*/
+	if ( (PortP->HostP->Flags & RUN_STATE) != RC_RUNNING ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Host not running\n");
+		pseterr(ENXIO);
+		func_exit ();
+		return -ENXIO;
+	}
+
+	/*
+	** If the RTA has not booted yet and the user has choosen to block
+	** until the RTA is present then we must spin here waiting for
+	** the RTA to boot.
+	*/
+#if 0
+	if (!(PortP->HostP->Mapping[PortP->RupNum].Flags & RTA_BOOTED)) {
+		if (PortP->WaitUntilBooted) {
+			rio_dprintk (RIO_DEBUG_TTY, "Waiting for RTA to boot\n");
+			do {
+				if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+					rio_dprintk (RIO_DEBUG_TTY, "RTA EINTR in delay \n");
+					func_exit ();
+					return -EINTR;
+				}
+				if (repeat_this -- <= 0) {
+					rio_dprintk (RIO_DEBUG_TTY, "Waiting for RTA to boot timeout\n");
+					RIOPreemptiveCmd(p, PortP, FCLOSE ); 
+					pseterr(EINTR);
+					func_exit ();
+					return -EIO;
+				}
+			} while(!(PortP->HostP->Mapping[PortP->RupNum].Flags & RTA_BOOTED));
+			rio_dprintk (RIO_DEBUG_TTY, "RTA has been booted\n");
+		} else {
+			rio_dprintk (RIO_DEBUG_TTY, "RTA never booted\n");
+			pseterr(ENXIO);
+			func_exit ();
+			return 0;
+		}
+	}
+#else
+	/* I find the above code a bit hairy. I find the below code
+           easier to read and shorter. Now, if it works too that would
+	   be great... -- REW 
+	*/
+	rio_dprintk (RIO_DEBUG_TTY, "Checking if RTA has booted... \n");
+	while (!(PortP->HostP->Mapping[PortP->RupNum].Flags & RTA_BOOTED)) {
+	  if (!PortP->WaitUntilBooted) {
+	    rio_dprintk (RIO_DEBUG_TTY, "RTA never booted\n");
+	    func_exit ();
+	    return -ENXIO;
+	  }
+
+	  /* Under Linux you'd normally use a wait instead of this
+	     busy-waiting. I'll stick with the old implementation for
+	     now. --REW 
+	  */
+	  if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+	    rio_dprintk (RIO_DEBUG_TTY, "RTA_wait_for_boot: EINTR in delay \n");
+	    func_exit ();
+	    return -EINTR;
+	  }
+	  if (repeat_this -- <= 0) {
+	    rio_dprintk (RIO_DEBUG_TTY, "Waiting for RTA to boot timeout\n");
+	    func_exit ();
+	    return -EIO;
+	  }
+	}
+	rio_dprintk (RIO_DEBUG_TTY, "RTA has been booted\n");
+#endif
+#if 0
+	tp =  PortP->TtyP;		/* get tty struct */
+#endif
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+	if ( p->RIOHalted ) {
+		goto bombout;
+	}
+#if 0
+	retval = gs_init_port(&PortP->gs);
+	if (retval){
+		func_exit ();
+		return retval;
+	}
+#endif
+
+	/*
+	** If the port is in the final throws of being closed,
+	** we should wait here (politely), waiting
+	** for it to finish, so that it doesn't close us!
+	*/
+	while ( (PortP->State & RIO_CLOSING) && !p->RIOHalted ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Waiting for RIO_CLOSING to go away\n");
+		if (repeat_this -- <= 0) {
+			rio_dprintk (RIO_DEBUG_TTY, "Waiting for not idle closed broken by signal\n");
+			RIOPreemptiveCmd(p, PortP, FCLOSE ); 
+			retval = -EINTR;
+			goto bombout;
+		}
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+			rio_spin_lock_irqsave(&PortP->portSem, flags); 
+			retval = -EINTR;
+			goto bombout;
+		}
+		rio_spin_lock_irqsave(&PortP->portSem, flags); 
+	}
+
+	if ( !PortP->Mapped ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Port unmapped while closing!\n");
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		retval = -ENXIO;
+		func_exit ();
+		return retval;
+	}
+
+	if ( p->RIOHalted ) {
+		goto bombout;
+	}
+
+/*
+** 15.10.1998 ARG - ESIL 0761 part fix
+** RIO has it's own CTSFLOW and RTSFLOW flags in 'Config' in the port structure,
+** we need to make sure that the flags are clear when the port is opened.
+*/
+	/* Uh? Suppose I turn these on and then another process opens
+	   the port again? The flags get cleared! Not good. -- REW */
+	if ( !(PortP->State & (RIO_LOPEN | RIO_MOPEN)) ) {
+		PortP->Config &= ~(RIO_CTSFLOW|RIO_RTSFLOW);
+	}
+
+	if (!(PortP->firstOpen)) {	/* First time ? */
+		rio_dprintk (RIO_DEBUG_TTY, "First open for this port\n");
+	
+
+		PortP->firstOpen++;
+		PortP->CookMode = 0; /* XXX RIOCookMode(tp); */
+		PortP->InUse = NOT_INUSE;
+
+		/* Tentative fix for bug PR27. Didn't work. */
+		/* PortP->gs.xmit_cnt = 0; */
+
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+#ifdef NEED_THIS
+		ttyseth(PortP, tp, (struct old_sgttyb *)&default_sg);
+#endif
+
+		/* Someone explain to me why this delay/config is
+                   here. If I read the docs correctly the "open"
+                   command piggybacks the parameters immediately. 
+		   -- REW */
+		RIOParam(PortP,OPEN,Modem,OK_TO_SLEEP);		/* Open the port */
+#if 0
+		/* This delay of 1 second was annoying. I removed it. -- REW */
+		RIODelay(PortP, HUNDRED_MS*10);
+		RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);	/* Config the port */
+#endif
+		rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+		/*
+		** wait for the port to be not closed.
+		*/
+		while ( !(PortP->PortState & PORT_ISOPEN) && !p->RIOHalted ) {
+			rio_dprintk (RIO_DEBUG_TTY, "Waiting for PORT_ISOPEN-currently %x\n",PortP->PortState);
+/*
+** 15.10.1998 ARG - ESIL 0759
+** (Part) fix for port being trashed when opened whilst RTA "disconnected"
+** Take out the limited wait - now wait for ever or until user
+** bangs us out.
+**
+			if (repeat_this -- <= 0) {
+				rio_dprint(RIO_DEBUG_TTY, ("Waiting for open to finish timed out.\n"));
+				RIOPreemptiveCmd(p, PortP, FCLOSE ); 
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				return -EINTR;
+			}
+**
+*/
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+				rio_dprintk (RIO_DEBUG_TTY, "Waiting for open to finish broken by signal\n");
+				RIOPreemptiveCmd(p, PortP, FCLOSE );
+				func_exit ();
+				return -EINTR;
+			}
+			rio_spin_lock_irqsave(&PortP->portSem, flags);
+		}
+
+		if ( p->RIOHalted ) {
+		  retval = -EIO;
+bombout:
+		  /* 			RIOClearUp( PortP ); */
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return retval;
+		}
+		rio_dprintk (RIO_DEBUG_TTY, "PORT_ISOPEN found\n");
+	}
+
+#ifdef MODEM_SUPPORT 
+	if (Modem) {
+		rio_dprintk (RIO_DEBUG_TTY, "Modem - test for carrier\n");
+		/*
+		** ACTION
+		** insert test for carrier here. -- ???
+		** I already see that test here. What's the deal? -- REW
+		*/
+		if ((PortP->gs.tty->termios->c_cflag & CLOCAL) || (PortP->ModemState & MSVR1_CD))
+		{
+			rio_dprintk (RIO_DEBUG_TTY, "open(%d) Modem carr on\n", SysPort);
+			/*
+			tp->tm.c_state |= CARR_ON;
+			wakeup((caddr_t) &tp->tm.c_canq);
+			*/
+			PortP->State |= RIO_CARR_ON;
+			wake_up_interruptible (&PortP->gs.open_wait);
+		}
+		else /* no carrier - wait for DCD */
+		{
+		  /*
+			while (!(PortP->gs.tty->termios->c_state & CARR_ON) && 
+			       !(filp->f_flags & O_NONBLOCK) && !p->RIOHalted )
+		  */
+			while (!(PortP->State & RIO_CARR_ON) && 
+			       !(filp->f_flags & O_NONBLOCK) && !p->RIOHalted ) {
+
+				rio_dprintk (RIO_DEBUG_TTY, "open(%d) sleeping for carr on\n",SysPort);
+				/*
+				PortP->gs.tty->termios->c_state |= WOPEN;
+				*/
+				PortP->State |= RIO_WOPEN;
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				if (RIODelay (PortP, HUNDRED_MS) == RIO_FAIL)
+#if 0
+				if ( sleep((caddr_t)&tp->tm.c_canqo, TTIPRI|PCATCH))
+#endif
+				{
+					/*
+					** ACTION: verify that this is a good thing
+					** to do here. -- ???
+					** I think it's OK. -- REW
+					*/
+					rio_dprintk (RIO_DEBUG_TTY, "open(%d) sleeping for carr broken by signal\n",
+					       SysPort);
+					RIOPreemptiveCmd( p, PortP, FCLOSE );
+					/*
+					tp->tm.c_state &= ~WOPEN;
+					*/
+					PortP->State &= ~RIO_WOPEN;
+					rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+					func_exit ();
+					return -EINTR;
+				}
+			}
+			PortP->State &= ~RIO_WOPEN;
+		}
+		if ( p->RIOHalted )
+			goto bombout;
+		rio_dprintk (RIO_DEBUG_TTY, "Setting RIO_MOPEN\n");
+		PortP->State |= RIO_MOPEN;
+	}
+	else
+#endif
+	{
+		/*
+		** ACTION
+		** Direct line open - force carrier (will probably mean
+		** that sleeping Modem line fubar)
+		*/
+		PortP->State |= RIO_LOPEN;
+	}
+
+	if ( p->RIOHalted ) {
+		goto bombout;
+	}
+
+	rio_dprintk (RIO_DEBUG_TTY, "high level open done\n");
+
+#ifdef STATS
+	PortP->Stat.OpenCnt++;
+#endif
+	/*
+	** Count opens for port statistics reporting
+	*/
+	if (PortP->statsGather)
+		PortP->opens++;
+
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	rio_dprintk (RIO_DEBUG_TTY, "Returning from open\n");
+	func_exit ();
+	return 0;
+}
+
+/*
+** RIOClose the port.
+** The operating system thinks that this is last close for the device.
+** As there are two interfaces to the port (Modem and tty), we need to
+** check that both are closed before we close the device.
+*/ 
+int
+riotclose(void  *ptr)
+{
+#if 0
+	register uint SysPort = dev;
+	struct ttystatics *tp;		/* pointer to our ttystruct */
+#endif
+	struct Port *PortP =ptr;	/* pointer to the port structure */
+	int deleted = 0;
+	int	try = -1; /* Disable the timeouts by setting them to -1 */
+	int	repeat_this = -1; /* Congrats to those having 15 years of 
+				     uptime! (You get to break the driver.) */
+	long end_time;
+	struct tty_struct * tty;
+	unsigned long flags;
+	int Modem;
+	int rv =0;
+	
+	rio_dprintk (RIO_DEBUG_TTY, "port close SysPort %d\n",PortP->PortNum);
+
+	/* PortP = p->RIOPortp[SysPort]; */
+	rio_dprintk (RIO_DEBUG_TTY, "Port is at address 0x%x\n",(int)PortP);
+	/* tp = PortP->TtyP;*/			/* Get tty */
+	tty = PortP->gs.tty;
+	rio_dprintk (RIO_DEBUG_TTY, "TTY is at address 0x%x\n",(int)tty);
+
+	if (PortP->gs.closing_wait) 
+		end_time = jiffies + PortP->gs.closing_wait;
+	else 
+		end_time = jiffies + MAX_SCHEDULE_TIMEOUT;
+
+	Modem = rio_ismodem(tty);
+#if 0
+	/* What F.CKING cache? Even then, a higly idle multiprocessor,
+	   system with large caches this won't work . Better find out when 
+	   this doesn't work asap, and fix the cause.  -- REW */
+	
+	RIODelay(PortP, HUNDRED_MS*10);	/* To flush the cache */
+#endif
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+	/*
+	** Setting this flag will make any process trying to open
+	** this port block until we are complete closing it.
+	*/
+	PortP->State |= RIO_CLOSING;
+
+	if ( (PortP->State & RIO_DELETED) ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Close on deleted RTA\n");
+		deleted = 1;
+	}
+	
+	if ( p->RIOHalted ) {
+		RIOClearUp( PortP );
+		rv = -EIO;
+		goto close_end;
+	}
+
+	rio_dprintk (RIO_DEBUG_TTY, "Clear bits\n");
+	/*
+	** clear the open bits for this device
+	*/
+	PortP->State &= (Modem ? ~RIO_MOPEN : ~RIO_LOPEN);
+	PortP->State &= ~RIO_CARR_ON;
+	PortP->ModemState &= ~MSVR1_CD;
+	/*
+	** If the device was open as both a Modem and a tty line
+	** then we need to wimp out here, as the port has not really
+	** been finally closed (gee, whizz!) The test here uses the
+	** bit for the OTHER mode of operation, to see if THAT is
+	** still active!
+	*/
+	if ( (PortP->State & (RIO_LOPEN|RIO_MOPEN)) ) {
+		/*
+		** The port is still open for the other task -
+		** return, pretending that we are still active.
+		*/
+		rio_dprintk (RIO_DEBUG_TTY, "Channel %d still open !\n",PortP->PortNum);
+		PortP->State &= ~RIO_CLOSING;
+		if (PortP->firstOpen)
+			PortP->firstOpen--;
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		return -EIO;
+	}
+
+	rio_dprintk (RIO_DEBUG_TTY, "Closing down - everything must go!\n");
+
+	PortP->State &= ~RIO_DYNOROD;
+
+	/*
+	** This is where we wait for the port
+	** to drain down before closing. Bye-bye....
+	** (We never meant to do this)
+	*/
+	rio_dprintk (RIO_DEBUG_TTY, "Timeout 1 starts\n");
+
+	if (!deleted)
+	while ( (PortP->InUse != NOT_INUSE) && !p->RIOHalted && 
+		(PortP->TxBufferIn != PortP->TxBufferOut) ) {
+		cprintf("Need to flush the ttyport\n");
+		if (repeat_this -- <= 0) {
+			rv = -EINTR;
+			rio_dprintk (RIO_DEBUG_TTY, "Waiting for not idle closed broken by signal\n");
+			RIOPreemptiveCmd(p, PortP, FCLOSE ); 
+			goto close_end;
+		}
+		rio_dprintk (RIO_DEBUG_TTY, "Calling timeout to flush in closing\n");
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		if (RIODelay_ni(PortP, HUNDRED_MS*10) == RIO_FAIL) {
+			rio_dprintk (RIO_DEBUG_TTY, "RTA EINTR in delay \n");
+			rv = -EINTR;
+			rio_spin_lock_irqsave(&PortP->portSem, flags);
+			goto close_end;
+		}
+		rio_spin_lock_irqsave(&PortP->portSem, flags);
+	}
+
+	PortP->TxBufferIn = PortP->TxBufferOut = 0;
+	repeat_this = 0xff;
+
+	PortP->InUse = 0;
+	if ( (PortP->State & (RIO_LOPEN|RIO_MOPEN)) ) {
+		/*
+		** The port has been re-opened for the other task -
+		** return, pretending that we are still active.
+		*/
+		rio_dprintk (RIO_DEBUG_TTY, "Channel %d re-open!\n", PortP->PortNum);
+		PortP->State &= ~RIO_CLOSING;
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		if (PortP->firstOpen)
+			PortP->firstOpen--;
+		return -EIO;
+	}
+
+	if ( p->RIOHalted ) {
+		RIOClearUp( PortP );
+		goto close_end;
+	}
+
+	
+
+	/* Can't call RIOShortCommand with the port locked. */
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+
+	if (RIOShortCommand(p, PortP, CLOSE, 1, 0) == RIO_FAIL) {
+	  RIOPreemptiveCmd(p, PortP,FCLOSE);
+	  goto close_end;
+	}
+
+	if (!deleted)
+	  while (try && (PortP->PortState & PORT_ISOPEN)) {
+	        try--;
+		if (time_after (jiffies, end_time)) {
+		  rio_dprintk (RIO_DEBUG_TTY, "Run out of tries - force the bugger shut!\n" );
+		  RIOPreemptiveCmd(p, PortP,FCLOSE);
+		  break;
+		}
+		rio_dprintk (RIO_DEBUG_TTY, "Close: PortState:ISOPEN is %d\n", 
+					   PortP->PortState & PORT_ISOPEN);
+
+		if ( p->RIOHalted ) {
+			RIOClearUp( PortP );
+			goto close_end;
+		}
+		if (RIODelay(PortP, HUNDRED_MS) == RIO_FAIL) {
+			rio_dprintk (RIO_DEBUG_TTY, "RTA EINTR in delay \n");
+			RIOPreemptiveCmd(p, PortP,FCLOSE);
+			break;
+		}
+	}
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+	rio_dprintk (RIO_DEBUG_TTY, "Close: try was %d on completion\n", try );
+ 
+	/* RIOPreemptiveCmd(p, PortP, FCLOSE); */
+
+/*
+** 15.10.1998 ARG - ESIL 0761 part fix
+** RIO has it's own CTSFLOW and RTSFLOW flags in 'Config' in the port structure,** we need to make sure that the flags are clear when the port is opened.
+*/
+	PortP->Config &= ~(RIO_CTSFLOW|RIO_RTSFLOW);
+
+
+#ifdef STATS
+	PortP->Stat.CloseCnt++;
+#endif
+	/*
+	** Count opens for port statistics reporting
+	*/
+	if (PortP->statsGather)
+		PortP->closes++;
+
+close_end:
+	/* XXX: Why would a "DELETED" flag be reset here? I'd have
+	   thought that a "deleted" flag means that the port was
+	   permanently gone, but here we can make it reappear by it
+	   being in close during the "deletion".
+	*/
+	PortP->State &= ~(RIO_CLOSING|RIO_DELETED);
+	if (PortP->firstOpen)
+		PortP->firstOpen--;
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	rio_dprintk (RIO_DEBUG_TTY, "Return from close\n");
+	return rv;
+}
+
+
+/*
+** decide if we need to use the line discipline.
+** This routine can return one of three values:
+** COOK_RAW if no processing has to be done by the line discipline or the card
+** COOK_WELL if the line discipline must be used to do the processing
+** COOK_MEDIUM if the card can do all the processing necessary.
+*/
+#if 0
+static int
+RIOCookMode(struct ttystatics *tp)
+{
+	/*
+	** We can't handle tm.c_mstate != 0 on SCO
+	** We can't handle mapping
+	** We can't handle non-ttwrite line disc.
+	** We can't handle lflag XCASE
+	** We can handle oflag OPOST & (OCRNL, ONLCR, TAB3)
+	*/
+
+#ifdef CHECK
+	CheckTtyP( tp );
+#endif
+	if (!(tp->tm.c_oflag & OPOST))	/* No post processing */
+		return COOK_RAW;	/* Raw mode o/p */
+
+	if ( tp->tm.c_lflag & XCASE )
+		return COOK_WELL;	/* Use line disc */
+
+	if (tp->tm.c_oflag & ~(OPOST | ONLCR | OCRNL | TAB3 ) )
+		return COOK_WELL;	/* Use line disc for strange modes */
+
+	if ( tp->tm.c_oflag == OPOST )	/* If only OPOST is set, do RAW */
+		return COOK_RAW;
+
+	/*
+	** So, we need to output process!
+	*/
+	return COOK_MEDIUM;
+}
+#endif
+
+static void
+RIOClearUp(PortP)
+struct Port *PortP;
+{
+	rio_dprintk (RIO_DEBUG_TTY, "RIOHalted set\n");
+	PortP->Config = 0;	  /* Direct semaphore */
+	PortP->PortState = 0;
+	PortP->firstOpen = 0;
+	PortP->FlushCmdBodge = 0;
+	PortP->ModemState = PortP->CookMode = 0;
+	PortP->Mapped = 0;
+	PortP->WflushFlag = 0;
+	PortP->MagicFlags	= 0;
+	PortP->RxDataStart = 0;
+	PortP->TxBufferIn = 0;
+	PortP->TxBufferOut = 0;
+}
+
+/*
+** Put a command onto a port.
+** The PortPointer, command, length and arg are passed.
+** The len is the length *inclusive* of the command byte,
+** and so for a command that takes no data, len==1.
+** The arg is a single byte, and is only used if len==2.
+** Other values of len aren't allowed, and will cause
+** a panic.
+*/
+int RIOShortCommand(struct rio_info *p, struct Port *PortP,
+		int command, int len, int arg)
+{
+	PKT *PacketP;
+	int		retries = 20; /* at 10 per second -> 2 seconds */
+	unsigned long flags;
+
+	rio_dprintk (RIO_DEBUG_TTY, "entering shortcommand.\n");
+#ifdef CHECK
+	CheckPortP( PortP );
+	if ( len < 1 || len > 2 )
+		cprintf(("STUPID LENGTH %d\n",len));
+#endif
+
+	if ( PortP->State & RIO_DELETED ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Short command to deleted RTA ignored\n");
+		return RIO_FAIL;
+	}
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+	/*
+	** If the port is in use for pre-emptive command, then wait for it to 
+	** be free again.
+	*/
+	while ( (PortP->InUse != NOT_INUSE) && !p->RIOHalted ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Waiting for not in use (%d)\n", 
+					   retries);
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		if (retries-- <= 0) {
+			return RIO_FAIL;
+		}
+		if (RIODelay_ni(PortP, HUNDRED_MS) == RIO_FAIL) {
+			return RIO_FAIL;
+		}
+		rio_spin_lock_irqsave(&PortP->portSem, flags);
+	}
+	if ( PortP->State & RIO_DELETED ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Short command to deleted RTA ignored\n");
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		return RIO_FAIL;
+	}
+
+	while ( !can_add_transmit(&PacketP,PortP) && !p->RIOHalted ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Waiting to add short command to queue (%d)\n", retries);
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		if (retries-- <= 0) {
+		  rio_dprintk (RIO_DEBUG_TTY, "out of tries. Failing\n");
+			return RIO_FAIL;
+		}
+		if ( RIODelay_ni(PortP, HUNDRED_MS)==RIO_FAIL ) {
+			return RIO_FAIL;
+		}
+		rio_spin_lock_irqsave(&PortP->portSem, flags);
+	}
+
+	if ( p->RIOHalted ) {
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		return RIO_FAIL;
+	}
+
+	/*
+	** set the command byte and the argument byte
+	*/
+	WBYTE(PacketP->data[0] , command);
+
+	if ( len==2 )
+		WBYTE(PacketP->data[1] , arg);
+
+	/*
+	** set the length of the packet and set the command bit.
+	*/
+	WBYTE(PacketP->len , PKT_CMD_BIT | len);
+
+	add_transmit(PortP);
+	/*
+	** Count characters transmitted for port statistics reporting
+	*/
+	if (PortP->statsGather)
+		PortP->txchars += len;
+
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	return p->RIOHalted ? RIO_FAIL : ~RIO_FAIL;
+}
+
+
+#if 0
+/*
+** This is an ioctl interface. This is the twentieth century. You know what
+** its all about.
+*/
+int
+riotioctl(struct rio_info *p, struct tty_struct *tty, int cmd, caddr_t arg)
+{
+	register struct		Port *PortP;
+	register struct		ttystatics *tp;
+	int					current;
+	int					ParamSemIncremented = 0;
+	int					old_oflag, old_cflag, old_iflag, changed, oldcook;
+	int					i;
+	unsigned char		sio_regs[5];		/* Here be magic */
+	short				vpix_cflag;
+	short				divisor;
+	int					baud;
+	uint				SysPort = rio_minor(tty);
+	int				Modem = rio_ismodem(tty);
+	int					ioctl_processed;
+
+	rio_dprintk (RIO_DEBUG_TTY, "port ioctl SysPort %d command 0x%x argument 0x%x %s\n",
+			SysPort, cmd, arg, Modem?"Modem":"tty") ;
+
+	if ( SysPort >= RIO_PORTS ) {
+		rio_dprintk (RIO_DEBUG_TTY, "Bad port number %d\n", SysPort);
+		return -ENXIO;
+	}
+
+	PortP = p->RIOPortp[SysPort];
+	tp = PortP->TtyP;
+
+	rio_spin_lock_irqsave(&PortP->portSem, flags);
+
+#ifdef STATS
+	PortP->Stat.IoctlCnt++;
+#endif
+
+	if ( PortP->State & RIO_DELETED ) {
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		return -EIO;
+	}
+
+
+	if ( p->RIOHalted ) {
+		RIOClearUp( PortP );
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		return -EIO;
+	}
+
+	/*
+	** Count ioctls for port statistics reporting
+	*/
+	if (PortP->statsGather)
+		PortP->ioctls++;
+
+	/*
+	** Specialix RIO Ioctl calls
+	*/
+	switch (cmd) {
+
+		case TCRIOTRIAD:
+			if ( arg )
+				PortP->State |= RIO_TRIAD_MODE;
+			else
+				PortP->State &= ~RIO_TRIAD_MODE;
+			/*
+			** Normally, when istrip is set on a port, a config is
+			** sent to the RTA instructing the CD1400 to do the
+			** stripping. In TRIAD mode, the interrupt receive routine
+			** must do the stripping instead, since it has to detect
+			** an 8 bit function key sequence. If istrip is set with
+			** TRIAD mode on(off), and 8 bit data is being read by
+			** the port, the user then turns TRIAD mode off(on), the RTA
+			** must be reconfigured (not) to do the stripping.
+			** Hence we call RIOParam here.
+			*/
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);	
+			return 0;
+
+		case TCRIOTSTATE:
+			rio_dprintk (RIO_DEBUG_TTY, "tbusy/tstop monitoring %sabled\n",
+		 		arg ? "en" : "dis");
+			/* MonitorTstate = 0 ;*/
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			RIOParam(PortP, CONFIG, Modem, OK_TO_SLEEP);
+			return 0;
+
+		case TCRIOSTATE: /* current state of Modem input pins */
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOSTATE\n");
+			if (RIOPreemptiveCmd(p, PortP, MGET) == RIO_FAIL)
+				rio_dprintk (RIO_DEBUG_TTY, "TCRIOSTATE command failed\n");
+			PortP->State |= RIO_BUSY;
+			current = PortP->ModemState;
+			if ( copyout((caddr_t)&current, (int)arg,
+							sizeof(current))==COPYFAIL ) {
+				rio_dprintk (RIO_DEBUG_TTY, "Copyout failed\n");
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				pseterr(EFAULT);
+			}
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOMBIS:		/* Set modem lines */
+		case TCRIOMBIC:		/* Clear modem lines */
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOMBIS/TCRIOMBIC\n");
+			if (cmd == TCRIOMBIS) {
+				uint		state;
+				state = (uint)arg;
+				PortP->ModemState |= (ushort)state;
+				PortP->ModemLines = (ulong) arg;
+				if (RIOPreemptiveCmd(p, PortP, MBIS) == RIO_FAIL)
+					rio_dprintk (RIO_DEBUG_TTY, 
+					 "TCRIOMBIS command failed\n");
+			}
+			else {
+				uint		state;
+
+				state = (uint)arg;
+				PortP->ModemState &= ~(ushort)state;
+				PortP->ModemLines = (ulong) arg;
+				if (RIOPreemptiveCmd(p, PortP, MBIC) == RIO_FAIL)
+					rio_dprintk (RIO_DEBUG_TTY, "TCRIOMBIC command failed\n");
+			}
+			PortP->State |= RIO_BUSY;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOXPON: /* set Xprint ON string */
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOXPON\n");
+			if ( copyin((int)arg, (caddr_t)PortP->Xprint.XpOn,
+						MAX_XP_CTRL_LEN)==COPYFAIL ) {
+				rio_dprintk (RIO_DEBUG_TTY, "Copyin failed\n");
+				PortP->Xprint.XpOn[0] = '\0';
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				pseterr(EFAULT);
+			}
+			PortP->Xprint.XpOn[MAX_XP_CTRL_LEN-1] = '\0';
+			PortP->Xprint.XpLen = strlen(PortP->Xprint.XpOn)+
+												strlen(PortP->Xprint.XpOff);
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOXPOFF: /* set Xprint OFF string */
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOXPOFF\n");
+			if ( copyin( (int)arg, (caddr_t)PortP->Xprint.XpOff,
+						MAX_XP_CTRL_LEN)==COPYFAIL ) {
+				rio_dprintk (RIO_DEBUG_TTY, "Copyin failed\n");
+				PortP->Xprint.XpOff[0] = '\0';
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				pseterr(EFAULT);
+			}
+			PortP->Xprint.XpOff[MAX_XP_CTRL_LEN-1] = '\0';
+			PortP->Xprint.XpLen = strlen(PortP->Xprint.XpOn)+
+										strlen(PortP->Xprint.XpOff);
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOXPCPS: /* set Xprint CPS string */
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOXPCPS\n");
+			if ( (uint)arg > p->RIOConf.MaxXpCps || 
+					(uint)arg < p->RIOConf.MinXpCps ) {
+				rio_dprintk (RIO_DEBUG_TTY, "%d CPS out of range\n",arg);
+				rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				pseterr(EINVAL);
+				return 0;
+			}
+			PortP->Xprint.XpCps = (uint)arg;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOXPRINT:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOXPRINT\n");
+			if ( copyout((caddr_t)&PortP->Xprint, (int)arg,
+					sizeof(struct Xprint))==COPYFAIL ) {
+			        rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+				pseterr(EFAULT);
+			}
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOIXANYON:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOIXANYON\n");
+			PortP->Config |= RIO_IXANY;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOIXANYOFF:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOIXANYOFF\n");
+			PortP->Config &= ~RIO_IXANY;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOIXONON:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOIXONON\n");
+			PortP->Config |= RIO_IXON;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+		case TCRIOIXONOFF:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOIXONOFF\n");
+			PortP->Config &= ~RIO_IXON;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			return 0;
+
+/*
+** 15.10.1998 ARG - ESIL 0761 part fix
+** Added support for CTS and RTS flow control ioctls :
+*/
+		case TCRIOCTSFLOWEN:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOCTSFLOWEN\n");
+			PortP->Config |= RIO_CTSFLOW;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);	
+			return 0;
+
+		case TCRIOCTSFLOWDIS:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIOCTSFLOWDIS\n");
+			PortP->Config &= ~RIO_CTSFLOW;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);	
+			return 0;
+
+		case TCRIORTSFLOWEN:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIORTSFLOWEN\n");
+			PortP->Config |= RIO_RTSFLOW;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);	
+			return 0;
+
+		case TCRIORTSFLOWDIS:
+			rio_dprintk (RIO_DEBUG_TTY, "TCRIORTSFLOWDIS\n");
+			PortP->Config &= ~RIO_RTSFLOW;
+			rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+			RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);	
+			return 0;
+
+/* end ESIL 0761 part fix */
+
+	}
+
+
+	/* Lynx IOCTLS */
+	switch (cmd) {
+		case TIOCSETP:
+		case TIOCSETN:
+		case OTIOCSETP:
+		case OTIOCSETN:
+			ioctl_processed++;
+			ttyseth(PortP, tp, (struct old_sgttyb *)arg);
+			break;
+		case TCSETA:
+		case TCSETAW:
+		case TCSETAF:
+			ioctl_processed++;
+			rio_dprintk (RIO_DEBUG_TTY, "NON POSIX ioctl\n");
+			ttyseth_pv(PortP, tp, (struct termios *)arg, 0);
+			break;
+		case TCSETAP:	/* posix tcsetattr() */
+		case TCSETAWP:	/* posix tcsetattr() */
+		case TCSETAFP:	/* posix tcsetattr() */
+			rio_dprintk (RIO_DEBUG_TTY, "NON POSIX SYSV ioctl\n");
+			ttyseth_pv(PortP, tp, (struct termios *)arg, 1);
+			ioctl_processed++;
+			break;
+	}
+
+	/*
+	** If its any of the commands that require the port to be in the
+	** non-busy state wait until all output has drained 
+	*/
+	if (!ioctl_processed)
+	switch(cmd) {
+		case TCSETAW:
+		case TCSETAF:
+		case TCSETA:
+		case TCSBRK:
+#define OLD_POSIX ('x' << 8)
+#define OLD_POSIX_SETA (OLD_POSIX | 2)
+#define OLD_POSIX_SETAW (OLD_POSIX | 3)
+#define OLD_POSIX_SETAF (OLD_POSIX | 4)
+#define NEW_POSIX (('i' << 24) | ('X' << 16))
+#define NEW_POSIX_SETA (NEW_POSIX | 2)
+#define NEW_POSIX_SETAW (NEW_POSIX | 3)
+#define NEW_POSIX_SETAF (NEW_POSIX | 4)
+		case OLD_POSIX_SETA:
+		case OLD_POSIX_SETAW:
+		case OLD_POSIX_SETAF:
+		case NEW_POSIX_SETA:
+		case NEW_POSIX_SETAW:
+		case NEW_POSIX_SETAF:
+#ifdef TIOCSETP
+		case TIOCSETP:
+#endif
+		case TIOCSETD:
+		case TIOCSETN:
+			rio_dprintk (RIO_DEBUG_TTY, "wait for non-BUSY, semaphore set\n");
+			/*
+			** Wait for drain here, at least as far as the double buffer
+			** being empty.
+			*/
+			/* XXX Does the above comment mean that this has
+			   still to be implemented? -- REW */
+			/* XXX Is the locking OK together with locking
+                           in txenable? (Deadlock?) -- REW */
+			
+			RIOTxEnable((char *)PortP);
+			break;
+		default:
+			break;
+	}
+
+	old_cflag = tp->tm.c_cflag;
+	old_iflag = tp->tm.c_iflag;
+	old_oflag = tp->tm.c_oflag;
+	oldcook = PortP->CookMode;
+
+	if ( p->RIOHalted ) {
+		RIOClearUp( PortP );
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		pseterr(EIO);
+		return 0;
+	}
+
+	PortP->FlushCmdBodge = 0;
+
+	/*
+	** If the port is locked, and it is reconfigured, we want
+	** to restore the state of the tty structure so the change is NOT
+	** made.
+	*/
+	if (PortP->Lock) {
+		tp->tm.c_iflag = PortP->StoredTty.iflag;
+		tp->tm.c_oflag = PortP->StoredTty.oflag;
+		tp->tm.c_cflag = PortP->StoredTty.cflag;
+		tp->tm.c_lflag = PortP->StoredTty.lflag;
+		tp->tm.c_line = PortP->StoredTty.line;
+		for (i = 0; i < NCC + 1; i++)
+			tp->tm.c_cc[i] = PortP->StoredTty.cc[i];
+	}
+	else {
+		/*
+		** If the port is set to store the parameters, and it is
+		** reconfigured, we want to save the current tty struct so it
+		** may be restored on the next open.
+		*/
+		if (PortP->Store) {
+			PortP->StoredTty.iflag = tp->tm.c_iflag;
+			PortP->StoredTty.oflag = tp->tm.c_oflag;
+			PortP->StoredTty.cflag = tp->tm.c_cflag;
+			PortP->StoredTty.lflag = tp->tm.c_lflag;
+			PortP->StoredTty.line = tp->tm.c_line;
+			for (i = 0; i < NCC + 1; i++)
+				PortP->StoredTty.cc[i] = tp->tm.c_cc[i];
+		}
+	}
+
+	changed = (tp->tm.c_cflag != old_cflag) ||
+				(tp->tm.c_iflag != old_iflag) ||
+				(tp->tm.c_oflag != old_oflag);
+
+	PortP->CookMode = RIOCookMode(tp);	/* Set new cooking mode */
+
+	rio_dprintk (RIO_DEBUG_TTY, "RIOIoctl changed %d newcook %d oldcook %d\n",
+			changed,PortP->CookMode,oldcook);
+
+#ifdef MODEM_SUPPORT
+	/*
+	** kludge to force CARR_ON if CLOCAL set
+	*/
+	if ((tp->tm.c_cflag & CLOCAL) || (PortP->ModemState & MSVR1_CD))	{
+		tp->tm.c_state |= CARR_ON;
+		wakeup ((caddr_t)&tp->tm.c_canq);
+	}
+#endif
+
+	if ( p->RIOHalted ) {
+		RIOClearUp( PortP );
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		pseterr(EIO);
+		return 0;
+	}
+	/*
+	** Re-configure if modes or cooking have changed
+	*/
+	if (changed || oldcook != PortP->CookMode || (ioctl_processed)) {
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		rio_dprintk (RIO_DEBUG_TTY, "Ioctl changing the PORT settings\n");
+		RIOParam(PortP,CONFIG,Modem,OK_TO_SLEEP);	
+		rio_spin_lock_irqsave(&PortP->portSem, flags);
+	}
+
+	if (p->RIOHalted) {
+		rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+		RIOClearUp( PortP );
+		pseterr(EIO);
+		return 0;
+	}
+	rio_spin_unlock_irqrestore(&PortP->portSem, flags);
+	return 0;
+}
+
+/*
+	ttyseth -- set hardware dependent tty settings
+*/
+void
+ttyseth(PortP, s, sg)
+struct Port *		PortP;
+struct ttystatics *		s;
+struct old_sgttyb *sg;
+{
+	struct old_sgttyb *	tsg;
+	struct termios *tp = &s->tm;
+
+	tsg = &s->sg;
+
+	if (sg->sg_flags & (EVENP|ODDP))  {
+		tp->c_cflag &= PARENB;
+		if (sg->sg_flags & EVENP) {
+			if (sg->sg_flags & ODDP) {
+				tp->c_cflag &= V_CS7;
+				tp->c_cflag &= ~PARENB;
+			}
+			else {
+				tp->c_cflag &= V_CS7;
+				tp->c_cflag &= PARENB;
+				tp->c_cflag &= PARODD;
+			}
+		}
+		else if (sg->sg_flags & ODDP) {
+			tp->c_cflag &= V_CS7;
+			tp->c_cflag &= PARENB;
+			tp->c_cflag &= PARODD;
+		}
+		else {
+			tp->c_cflag &= V_CS7;
+			tp->c_cflag &= PARENB;
+		}
+	}
+/*
+ * Use ispeed as the desired speed.  Most implementations don't handle 
+ * separate input and output speeds very well. If the RIO handles this, 
+ * I will have to use separate sets of flags to store them in the 
+ * Port structure.
+ */
+	if ( !sg->sg_ospeed )
+		sg->sg_ospeed = sg->sg_ispeed;
+	else
+		sg->sg_ispeed = sg->sg_ospeed;
+	if (sg->sg_ispeed > V_EXTB ) 
+		sg->sg_ispeed = V_EXTB;
+	if (sg->sg_ispeed < V_B0)
+		sg->sg_ispeed = V_B0;
+	*tsg = *sg;
+   tp->c_cflag = (tp->c_cflag & ~V_CBAUD) | conv_bv[(int)sg->sg_ispeed];
+}
+
+/*
+	ttyseth_pv -- set hardware dependent tty settings using either the
+			POSIX termios structure or the System V termio structure.
+				sysv = 0 => (POSIX):	 struct termios *sg
+				sysv != 0 => (System V): struct termio *sg
+*/
+static void
+ttyseth_pv(PortP, s, sg, sysv)
+struct Port *PortP;
+struct ttystatics *s;
+struct termios *sg;
+int sysv;
+{
+    int speed;
+    unsigned char csize;
+    unsigned char cread;
+    unsigned int lcr_flags;
+    int ps;
+ 
+    if (sysv) {
+        /* sg points to a System V termio structure */
+        csize = ((struct termio *)sg)->c_cflag & CSIZE;
+        cread = ((struct termio *)sg)->c_cflag & CREAD;
+        speed = conv_vb[((struct termio *)sg)->c_cflag & V_CBAUD];
+    }
+    else {
+        /* sg points to a POSIX termios structure */
+        csize = sg->c_cflag & CSIZE;
+        cread = sg->c_cflag & CREAD;
+        speed = conv_vb[sg->c_cflag & V_CBAUD];
+    }
+    if (s->sg.sg_ispeed != speed || s->sg.sg_ospeed != speed) {
+        s->sg.sg_ispeed = speed;
+        s->sg.sg_ospeed = speed;
+        s->tm.c_cflag = (s->tm.c_cflag & ~V_CBAUD) |
+                         conv_bv[(int)s->sg.sg_ispeed];
+    }
+}
+#endif
diff --git a/drivers/char/rio/riotypes.h b/drivers/char/rio/riotypes.h
new file mode 100644
index 0000000..1c7c42c
--- /dev/null
+++ b/drivers/char/rio/riotypes.h
@@ -0,0 +1,135 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                      R I O T Y P E S
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Jon Brawn
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _riotypes_h
+#define _riotypes_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_riotypes_h_sccs = "@(#)riotypes.h	1.10"; */
+#endif
+#endif
+
+#ifdef INKERNEL
+
+#if !defined(MIPSAT)
+typedef unsigned short NUMBER_ptr;
+typedef unsigned short WORD_ptr;
+typedef unsigned short BYTE_ptr;
+typedef unsigned short char_ptr;
+typedef unsigned short Channel_ptr;
+typedef unsigned short FREE_LIST_ptr_ptr;
+typedef unsigned short FREE_LIST_ptr;
+typedef unsigned short LPB_ptr;
+typedef unsigned short Process_ptr;
+typedef unsigned short PHB_ptr;
+typedef unsigned short PKT_ptr;
+typedef unsigned short PKT_ptr_ptr;
+typedef unsigned short Q_BUF_ptr;
+typedef unsigned short Q_BUF_ptr_ptr;
+typedef unsigned short ROUTE_STR_ptr;
+typedef unsigned short RUP_ptr;
+typedef unsigned short short_ptr;
+typedef unsigned short u_short_ptr;
+typedef unsigned short ushort_ptr;
+#else
+/* MIPSAT types */
+typedef char RIO_POINTER[8];
+typedef RIO_POINTER NUMBER_ptr;
+typedef RIO_POINTER WORD_ptr;
+typedef RIO_POINTER BYTE_ptr;
+typedef RIO_POINTER char_ptr;
+typedef RIO_POINTER Channel_ptr;
+typedef RIO_POINTER FREE_LIST_ptr_ptr;
+typedef RIO_POINTER FREE_LIST_ptr;
+typedef RIO_POINTER LPB_ptr;
+typedef RIO_POINTER Process_ptr;
+typedef RIO_POINTER PHB_ptr;
+typedef RIO_POINTER PKT_ptr;
+typedef RIO_POINTER PKT_ptr_ptr;
+typedef RIO_POINTER Q_BUF_ptr;
+typedef RIO_POINTER Q_BUF_ptr_ptr;
+typedef RIO_POINTER ROUTE_STR_ptr;
+typedef RIO_POINTER RUP_ptr;
+typedef RIO_POINTER short_ptr;
+typedef RIO_POINTER u_short_ptr;
+typedef RIO_POINTER ushort_ptr;
+#endif
+
+#else /* not INKERNEL */
+typedef unsigned char   BYTE;
+typedef unsigned short  WORD;
+typedef unsigned long   DWORD;
+typedef short           NUMBER;
+typedef short           *NUMBER_ptr;
+typedef unsigned short  *WORD_ptr;
+typedef unsigned char   *BYTE_ptr;
+typedef unsigned char   uchar ;
+typedef unsigned short  ushort ;
+typedef unsigned int    uint ;
+typedef unsigned long   ulong ;
+typedef unsigned char   u_char ;
+typedef unsigned short  u_short ;
+typedef unsigned int    u_int ;
+typedef unsigned long   u_long ;
+typedef unsigned short  ERROR ;
+typedef unsigned long ID ;
+typedef char             *char_ptr;
+typedef Channel          *Channel_ptr;
+typedef struct FREE_LIST *FREE_LIST_ptr;
+typedef struct FREE_LIST **FREE_LIST_ptr_ptr;
+typedef struct LPB       *LPB_ptr;
+typedef struct Process   *Process_ptr;
+typedef struct PHB       *PHB_ptr;
+typedef struct PKT       *PKT_ptr;
+typedef struct PKT       **PKT_ptr_ptr;
+typedef struct Q_BUF     *Q_BUF_ptr;
+typedef struct Q_BUF     **Q_BUF_ptr_ptr;
+typedef struct ROUTE_STR *ROUTE_STR_ptr;
+typedef struct RUP       *RUP_ptr;
+typedef short            *short_ptr;
+typedef u_short          *u_short_ptr;
+typedef ushort           *ushort_ptr;
+typedef struct PKT	 PKT;
+typedef struct LPB	 LPB;
+typedef struct RUP	 RUP;
+#endif
+
+
+#endif /* __riotypes__ */
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/riowinif.h b/drivers/char/rio/riowinif.h
new file mode 100644
index 0000000..18a4f14
--- /dev/null
+++ b/drivers/char/rio/riowinif.h
@@ -0,0 +1,1335 @@
+/************************************************************************/
+/*									*/
+/*	Title		:	RIO Shared Memory Window Inteface	*/
+/*									*/
+/*	Author		:	N.P.Vassallo				*/
+/*									*/
+/*	Creation	:	7th June 1999				*/
+/*									*/
+/*	Version		:	1.0.0					*/
+/*									*/
+/*	Copyright	:	(c) Specialix International Ltd. 1999	*
+ *      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.
+ *									*/
+/*	Description	:	Prototypes, structures and definitions	*/
+/*				describing RIO host card shared	memory	*/
+/*				window interface structures:		*/
+/*					PARMMAP				*/
+/*					RUP				*/
+/*					PHB				*/
+/*					LPB				*/
+/*					PKT				*/
+/*									*/
+/************************************************************************/
+
+/* History...
+
+1.0.0	07/06/99 NPV	Creation. (based on PARMMAP.H)
+
+*/
+
+#ifndef	_riowinif_h				/* If RIOWINDIF.H not already defined */
+#define	_riowinif_h    1
+
+/*****************************************************************************
+********************************             *********************************
+********************************   General   *********************************
+********************************             *********************************
+*****************************************************************************/
+
+#define	TPNULL		((_u16)(0x8000))
+
+/*****************************************************************************
+********************************              ********************************
+********************************   PARM_MAP   ********************************
+********************************              ********************************
+*****************************************************************************/
+
+/* The PARM_MAP structure defines global values relating to the Host Card / RTA
+   and is the main structure from which all other structures are referenced. */
+
+typedef	struct	_PARM_MAP
+{
+	_u16	phb_ptr;		/* 0x00 Pointer to the PHB array */
+	_u16	phb_num_ptr;		/* 0x02 Ptr to Number of PHB's */
+	_u16	free_list;		/* 0x04 Free List pointer */
+	_u16	free_list_end;		/* 0x06 Free List End pointer */
+	_u16	q_free_list_ptr;	/* 0x08 Ptr to Q_BUF variable */
+	_u16	unit_id_ptr;		/* 0x0A Unit Id */
+	_u16	link_str_ptr;		/* 0x0C Link Structure Array */
+	_u16	bootloader_1;		/* 0x0E 1st Stage Boot Loader */
+	_u16	bootloader_2;		/* 0x10 2nd Stage Boot Loader */
+	_u16	port_route_map_ptr;	/* 0x12 Port Route Map */
+	_u16	route_ptr;		/* 0x14 Route Map */
+	_u16	map_present;		/* 0x16 Route Map present */
+	_u16	pkt_num;		/* 0x18 Total number of packets */
+	_u16	q_num;			/* 0x1A Total number of Q packets */
+	_u16	buffers_per_port;	/* 0x1C Number of buffers per port */
+	_u16	heap_size;		/* 0x1E Initial size of heap */
+	_u16	heap_left;		/* 0x20 Current Heap left */
+	_u16	error;			/* 0x22 Error code */
+	_u16	tx_max;			/* 0x24 Max number of tx pkts per phb */
+	_u16	rx_max;			/* 0x26 Max number of rx pkts per phb */
+	_u16	rx_limit;		/* 0x28 For high / low watermarks */
+	_u16	links;			/* 0x2A Links to use */
+	_u16	timer;			/* 0x2C Interrupts per second */
+	_u16	rups;			/* 0x2E Pointer to the RUPs */
+	_u16	max_phb;		/* 0x30 Mostly for debugging */
+	_u16	living;			/* 0x32 Just increments!! */
+	_u16	init_done;		/* 0x34 Initialisation over */
+	_u16	booting_link;		/* 0x36 */
+	_u16	idle_count;		/* 0x38 Idle time counter */
+	_u16	busy_count;		/* 0x3A Busy counter */
+	_u16	idle_control;		/* 0x3C Control Idle Process */
+	_u16	tx_intr;		/* 0x3E TX interrupt pending */
+	_u16	rx_intr;		/* 0x40 RX interrupt pending */
+	_u16	rup_intr;		/* 0x42 RUP interrupt pending */
+
+} PARM_MAP;
+
+/* Same thing again, but defined as offsets... */
+
+#define	PM_phb_ptr		0x00	/* 0x00 Pointer to the PHB array */
+#define	PM_phb_num_ptr		0x02	/* 0x02 Ptr to Number of PHB's */
+#define	PM_free_list		0x04	/* 0x04 Free List pointer */
+#define	PM_free_list_end	0x06	/* 0x06 Free List End pointer */
+#define	PM_q_free_list_ptr	0x08	/* 0x08 Ptr to Q_BUF variable */
+#define	PM_unit_id_ptr		0x0A	/* 0x0A Unit Id */
+#define	PM_link_str_ptr		0x0C	/* 0x0C Link Structure Array */
+#define	PM_bootloader_1		0x0E	/* 0x0E 1st Stage Boot Loader */
+#define	PM_bootloader_2		0x10	/* 0x10 2nd Stage Boot Loader */
+#define	PM_port_route_map_ptr	0x12	/* 0x12 Port Route Map */
+#define	PM_route_ptr		0x14	/* 0x14 Route Map */
+#define	PM_map_present		0x16	/* 0x16 Route Map present */
+#define	PM_pkt_num		0x18	/* 0x18 Total number of packets */
+#define	PM_q_num		0x1A	/* 0x1A Total number of Q packets */
+#define	PM_buffers_per_port	0x1C	/* 0x1C Number of buffers per port */
+#define	PM_heap_size		0x1E	/* 0x1E Initial size of heap */
+#define	PM_heap_left		0x20	/* 0x20 Current Heap left */
+#define	PM_error		0x22	/* 0x22 Error code */
+#define	PM_tx_max		0x24	/* 0x24 Max number of tx pkts per phb */
+#define	PM_rx_max		0x26	/* 0x26 Max number of rx pkts per phb */
+#define	PM_rx_limit		0x28	/* 0x28 For high / low watermarks */
+#define	PM_links		0x2A	/* 0x2A Links to use */
+#define	PM_timer		0x2C	/* 0x2C Interrupts per second */
+#define	PM_rups			0x2E	/* 0x2E Pointer to the RUPs */
+#define	PM_max_phb		0x30	/* 0x30 Mostly for debugging */
+#define	PM_living		0x32	/* 0x32 Just increments!! */
+#define	PM_init_done		0x34	/* 0x34 Initialisation over */
+#define	PM_booting_link		0x36	/* 0x36 */
+#define	PM_idle_count		0x38	/* 0x38 Idle time counter */
+#define	PM_busy_count		0x3A	/* 0x3A Busy counter */
+#define	PM_idle_control		0x3C	/* 0x3C Control Idle Process */
+#define	PM_tx_intr		0x3E	/* 0x4E TX interrupt pending */
+#define	PM_rx_intr		0x40	/* 0x40 RX interrupt pending */
+#define	PM_rup_intr		0x42	/* 0x42 RUP interrupt pending */
+#define	sizeof_PARM_MAP		0x44	/* structure size = 0x44 */
+
+/* PARM_MAP.error definitions... */
+#define	E_NO_ERROR		0x00
+#define	E_PROCESS_NOT_INIT	0x01
+#define	E_LINK_TIMEOUT		0x02
+#define	E_NO_ROUTE		0x03
+#define	E_CONFUSED		0x04
+#define	E_HOME			0x05
+#define	E_CSUM_FAIL		0x06
+#define	E_DISCONNECTED		0x07
+#define	E_BAD_RUP		0x08
+#define	E_NO_VIRGIN		0x09
+#define	E_BOOT_RUP_BUSY		0x10
+#define	E_CHANALLOC		0x80
+#define	E_POLL_ALLOC		0x81
+#define	E_LTTWAKE		0x82
+#define	E_LTT_ALLOC		0x83
+#define	E_LRT_ALLOC		0x84
+#define	E_CIRRUS		0x85
+#define	E_MONITOR		0x86
+#define	E_PHB_ALLOC		0x87
+#define	E_ARRAY_ALLOC		0x88
+#define	E_QBUF_ALLOC		0x89
+#define	E_PKT_ALLOC		0x8a
+#define	E_GET_TX_Q_BUF		0x8b
+#define	E_GET_RX_Q_BUF		0x8c
+#define	E_MEM_OUT		0x8d
+#define	E_MMU_INIT		0x8e
+#define	E_LTT_INIT		0x8f
+#define	E_LRT_INIT		0x90
+#define	E_LINK_RUN		0x91
+#define	E_MONITOR_ALLOC		0x92
+#define	E_MONITOR_INIT		0x93
+#define	E_POLL_INIT		0x94
+
+/* PARM_MAP.links definitions... */
+#define	RIO_LINK_ENABLE	0x80FF
+
+/*****************************************************************************
+**********************************         ***********************************
+**********************************   RUP   ***********************************
+**********************************         ***********************************
+*****************************************************************************/
+
+/* The RUP (Remote Unit Port) structure relates to the Remote Terminal Adapters
+   attached to the system and there is normally an array of MAX_RUPS (=16) structures
+   in a host card, defined by PARM_MAP->rup. */
+
+typedef	struct	_RUP
+{
+	_u16		txpkt;			/* 0x00 Outgoing packet */
+	_u16		rxpkt;			/* 0x02 ncoming packet */
+	_u16		link;			/* 0x04 Which link to send packet down ? */
+	_u8		rup_dest_unit[2];	/* 0x06 Destination Unit */
+	_u16		handshake;		/* 0x08 Handshaking */
+	_u16		timeout;		/* 0x0A Timeout */
+	_u16		status;			/* 0x0C Status */
+	_u16		txcontrol;		/* 0x0E Transmit control */
+	_u16		rxcontrol;		/* 0x10 Receive control */
+
+} RUP;
+
+/* Same thing again, but defined as offsets... */
+
+#define	RUP_txpkt		0x00		/* 0x00 Outgoing packet */
+#define	RUP_rxpkt		0x02		/* 0x02 Incoming packet */
+#define	RUP_link		0x04		/* 0x04 Which link to send packet down ? */
+#define	RUP_rup_dest_unit	0x06		/* 0x06 Destination Unit */
+#define	RUP_handshake		0x08		/* 0x08 Handshaking */
+#define	RUP_timeout		0x0A		/* 0x0A Timeout */
+#define	RUP_status		0x0C		/* 0x0C Status */
+#define	RUP_txcontrol		0x0E		/* 0x0E Transmit control */
+#define	RUP_rxcontrol		0x10		/* 0x10 Receive control */
+#define	sizeof_RUP		0x12		/* structure size = 0x12 */
+
+#define MAX_RUP			16
+
+/* RUP.txcontrol definitions... */
+#define	TX_RUP_INACTIVE		0		/* Nothing to transmit */
+#define	TX_PACKET_READY		1		/* Transmit packet ready */
+#define	TX_LOCK_RUP		2		/* Transmit side locked */
+
+/* RUP.txcontrol definitions... */
+#define	RX_RUP_INACTIVE		0		/* Nothing received */
+#define	RX_PACKET_READY		1		/* Packet received */
+
+#define	RUP_NO_OWNER		0xFF		/* RUP not owned by any process */
+
+/*****************************************************************************
+**********************************         ***********************************
+**********************************   PHB   ***********************************
+**********************************         ***********************************
+*****************************************************************************/
+
+/* The PHB (Port Header Block) structure relates to the serial ports attached
+   to the system and there is normally an array of MAX_PHBS (=128) structures
+   in a host card, defined by PARM_MAP->phb_ptr and PARM_MAP->phb_num_ptr. */
+
+typedef	struct	_PHB
+{
+	_u16		source;			/* 0x00 Location of the PHB in the host card */
+	_u16		handshake;		/* 0x02 Used to manage receive packet flow control */
+	_u16		status;			/* 0x04 Internal port transmit/receive status */
+	_u16		timeout;		/* 0x06 Time period to wait for an ACK */
+	_u16		link;			/* 0x08 The host link associated with the PHB */
+	_u16		destination;		/* 0x0A Location of the remote port on the network */
+
+	_u16		tx_start;		/* 0x0C first entry in the packet array for transmit packets */
+	_u16		tx_end;			/* 0x0E last entry in the packet array for transmit packets */
+	_u16		tx_add;			/* 0x10 position in the packet array for new transmit packets */
+	_u16		tx_remove;		/* 0x12 current position in the packet pointer array */
+
+	_u16		rx_start;		/* 0x14 first entry in the packet array for receive packets */
+	_u16		rx_end;			/* 0x16 last entry in the packet array for receive packets */
+	_u16		rx_add;			/* 0x18 position in the packet array for new receive packets */
+	_u16		rx_remove;		/* 0x1A current position in the packet pointer array */
+
+} PHB;
+
+/* Same thing again, but defined as offsets... */
+
+#define	PHB_source		0x00		/* 0x00 Location of the PHB in the host card */
+#define	PHB_handshake		0x02		/* 0x02 Used to manage receive packet flow control */
+#define	PHB_status		0x04		/* 0x04 Internal port transmit/receive status */
+#define	PHB_timeout		0x06		/* 0x06 Time period to wait for an ACK */
+#define	PHB_link		0x08		/* 0x08 The host link associated with the PHB */
+#define	PHB_destination		0x0A		/* 0x0A Location of the remote port on the network */
+#define	PHB_tx_start		0x0C		/* 0x0C first entry in the packet array for transmit packets */
+#define	PHB_tx_end		0x0E		/* 0x0E last entry in the packet array for transmit packets */
+#define	PHB_tx_add		0x10		/* 0x10 position in the packet array for new transmit packets */
+#define	PHB_tx_remove		0x12		/* 0x12 current position in the packet pointer array */
+#define	PHB_rx_start		0x14		/* 0x14 first entry in the packet array for receive packets */
+#define	PHB_rx_end		0x16		/* 0x16 last entry in the packet array for receive packets */
+#define	PHB_rx_add		0x18		/* 0x18 position in the packet array for new receive packets */
+#define	PHB_rx_remove		0x1A		/* 0x1A current position in the packet pointer array */
+#define	sizeof_PHB		0x1C		/* structure size = 0x1C */
+
+/* PHB.handshake definitions... */
+#define	PHB_HANDSHAKE_SET	0x0001		/* Set by LRT */
+#define	PHB_HANDSHAKE_RESET	0x0002		/* Set by ISR / driver */
+#define	PHB_HANDSHAKE_FLAGS	(PHB_HANDSHAKE_RESET|PHB_HANDSHAKE_SET)
+						/* Reset by ltt */
+
+#define	MAX_PHB			128		/* range 0-127 */
+
+/*****************************************************************************
+**********************************         ***********************************
+**********************************   LPB   ***********************************
+**********************************         ***********************************
+*****************************************************************************/
+
+/* The LPB (Link Parameter Block) structure relates to a RIO Network Link
+   and there is normally an array of MAX_LINKS (=4) structures in a host card,
+   defined by PARM_MAP->link_str_ptr. */
+
+typedef	struct	_LPB
+{
+	_u16		link_number;		/* 0x00 Link Number */
+	_u16		in_ch;			/* 0x02 Link In Channel */
+	_u16		out_ch;			/* 0x04 Link Out Channel */
+	_u8		attached_serial[4];	/* 0x06 Attached serial number */
+	_u8		attached_host_serial[4];/* 0x0A Serial number of Host who booted other end */
+	_u16		descheduled;		/* 0x0E Currently Descheduled */
+	_u16		state;			/* 0x10 Current state */
+	_u16		send_poll;		/* 0x12 Send a Poll Packet */
+	_u16		ltt_p;			/* 0x14 Process Descriptor */
+	_u16		lrt_p;			/* 0x16 Process Descriptor */
+	_u16		lrt_status;		/* 0x18 Current lrt status */
+	_u16		ltt_status;		/* 0x1A Current ltt status */
+	_u16		timeout;		/* 0x1C Timeout value */
+	_u16		topology;		/* 0x1E Topology bits */
+	_u16		mon_ltt;		/* 0x20 */
+	_u16		mon_lrt;		/* 0x22 */
+	_u16		num_pkts;		/* 0x24 */
+	_u16		add_packet_list;	/* 0x26 Add packets to here */
+	_u16		remove_packet_list;	/* 0x28 Send packets from here */
+
+	_u16		lrt_fail_chan;		/* 0x2A Lrt's failure channel */
+	_u16		ltt_fail_chan;		/* 0x2C Ltt's failure channel */
+
+	RUP		rup;			/* 0x2E RUP structure for HOST to driver comms */
+	RUP		link_rup;		/* 0x40 RUP for the link (POLL, topology etc.) */
+	_u16		attached_link;		/* 0x52 Number of attached link */
+	_u16		csum_errors;		/* 0x54 csum errors */
+	_u16		num_disconnects;	/* 0x56 number of disconnects */
+	_u16		num_sync_rcvd;		/* 0x58 # sync's received */
+	_u16		num_sync_rqst;		/* 0x5A # sync requests */
+	_u16		num_tx;			/* 0x5C Num pkts sent */
+	_u16		num_rx;			/* 0x5E Num pkts received */
+	_u16		module_attached;	/* 0x60 Module tpyes of attached */
+	_u16		led_timeout;		/* 0x62 LED timeout */
+	_u16		first_port;		/* 0x64 First port to service */
+	_u16		last_port;		/* 0x66 Last port to service */
+
+} LPB;
+
+/* Same thing again, but defined as offsets... */
+
+#define	LPB_link_number		0x00		/* 0x00 Link Number */
+#define	LPB_in_ch		0x02		/* 0x02 Link In Channel */
+#define	LPB_out_ch		0x04		/* 0x04 Link Out Channel */
+#define	LPB_attached_serial	0x06		/* 0x06 Attached serial number */
+#define	LPB_attached_host_serial 0x0A		/* 0x0A Serial number of Host who booted other end */
+#define	LPB_descheduled		0x0E		/* 0x0E Currently Descheduled */
+#define	LPB_state		0x10		/* 0x10 Current state */
+#define	LPB_send_poll		0x12		/* 0x12 Send a Poll Packet */
+#define	LPB_ltt_p		0x14		/* 0x14 Process Descriptor */
+#define	LPB_lrt_p		0x16		/* 0x16 Process Descriptor */
+#define	LPB_lrt_status		0x18		/* 0x18 Current lrt status */
+#define	LPB_ltt_status		0x1A		/* 0x1A Current ltt status */
+#define	LPB_timeout		0x1C		/* 0x1C Timeout value */
+#define	LPB_topology		0x1E		/* 0x1E Topology bits */
+#define	LPB_mon_ltt		0x20		/* 0x20 */
+#define	LPB_mon_lrt		0x22		/* 0x22 */
+#define	LPB_num_pkts		0x24		/* 0x24 */
+#define	LPB_add_packet_list	0x26		/* 0x26 Add packets to here */
+#define	LPB_remove_packet_list	0x28		/* 0x28 Send packets from here */
+#define	LPB_lrt_fail_chan	0x2A		/* 0x2A Lrt's failure channel */
+#define	LPB_ltt_fail_chan	0x2C		/* 0x2C Ltt's failure channel */
+#define	LPB_rup			0x2E		/* 0x2E RUP structure for HOST to driver comms */
+#define	LPB_link_rup		0x40		/* 0x40 RUP for the link (POLL, topology etc.) */
+#define	LPB_attached_link	0x52		/* 0x52 Number of attached link */
+#define	LPB_csum_errors		0x54		/* 0x54 csum errors */
+#define	LPB_num_disconnects	0x56		/* 0x56 number of disconnects */
+#define	LPB_num_sync_rcvd	0x58		/* 0x58 # sync's received */
+#define	LPB_num_sync_rqst	0x5A		/* 0x5A # sync requests */
+#define	LPB_num_tx		0x5C		/* 0x5C Num pkts sent */
+#define	LPB_num_rx		0x5E		/* 0x5E Num pkts received */
+#define	LPB_module_attached	0x60		/* 0x60 Module tpyes of attached */
+#define	LPB_led_timeout		0x62		/* 0x62 LED timeout */
+#define	LPB_first_port		0x64		/* 0x64 First port to service */
+#define	LPB_last_port		0x66		/* 0x66 Last port to service */
+#define	sizeof_LPB		0x68		/* structure size = 0x68 */
+
+#define	LINKS_PER_UNIT		4		/* number of links from a host */
+
+/*****************************************************************************
+********************************               *******************************
+********************************   FREE_LIST   *******************************
+********************************               *******************************
+*****************************************************************************/
+
+/* Used to overlay packet headers when allocating/freeing packets from the free list */
+
+typedef	struct	_FREE_LIST
+{
+	_u16		next;			/* 0x00 offset of next list item */
+	_u16		prev;			/* 0x02 offset of previous list item */
+
+} FREE_LIST;
+
+/* Same thing again, but defined as offsets... */
+
+#define	FL_next			0x00		/* 0x00 offset of next list item */
+#define	FL_prev			0x02		/* 0x02 offset of previous list item */
+
+/*****************************************************************************
+**********************************         ***********************************
+**********************************   PKT   ***********************************
+**********************************         ***********************************
+*****************************************************************************/
+
+/* The PKT is the main unit of communication between Host Cards and RTAs across
+   the RIO network.  */
+
+#define PKT_MAX_DATA_LEN   72			/* Size of packet data */
+
+typedef	struct	_PKT
+{
+	_u8		dest_unit;		/* 0x00 Destination Unit Id */
+	_u8		dest_port;		/* 0x01 Destination Port */
+	_u8		src_unit;		/* 0x02 Source Unit Id */
+	_u8		src_port;		/* 0x03 Source Port */
+	_u8		len;			/* 0x04 Length (in bytes) of data field */
+	_u8		control;		/* 0x05 */
+	_u8		data[PKT_MAX_DATA_LEN];	/* 0x06 Actual data */
+	_u16		csum;			/* 0x4E C-SUM */
+
+} PKT;
+
+/* Same thing again, but defined as offsets... */
+
+#define	PKT_dest_unit		0x00		/* 0x00 Destination Unit Id */
+#define	PKT_dest_port		0x01		/* 0x01 Destination Port */
+#define	PKT_src_unit		0x02		/* 0x02 Source Unit Id */
+#define	PKT_src_port		0x03		/* 0x03 Source Port */
+#define	PKT_len			0x04		/* 0x04 Length (in bytes) of data field */
+#define	PKT_control		0x05		/* 0x05 */
+#define	PKT_data		0x06		/* 0x06 Actual data */
+#define	PKT_csum		0x4E		/* 0x4E C-SUM */
+#define	sizeof_PKT		0x50		/* structure size = 0x50 */
+
+/* PKT.len definitions... */
+#define	PKT_CMD_BIT		0x80
+#define	PKT_CMD_DATA		0x80
+#define	PKT_LEN_MASK		0x7F
+
+/* PKT.control definitions... */
+#define	PKT_ACK			0x40
+#define	PKT_TGL			0x20
+#define	DATA_WNDW		0x10
+#define	PKT_TTL_MASK		0x0F
+#define	MAX_TTL			0x0F
+
+/*****************************************************************************
+*****************************                     ****************************
+*****************************   Control Packets   ****************************
+*****************************                     ****************************
+*****************************************************************************/
+
+/* The following definitions and structures define the control packets sent
+   between the driver and RIO Ports, RTAs and Host Cards. */
+
+#define	PRE_EMPTIVE		0x80			/* Pre-emptive command (sent via port's RUP) */
+
+/* "in-band" and "pre-emptive" port commands... */
+#define	OPEN			0x00			/* Driver->RIO Open a port */
+#define	CONFIG			0x01			/* Driver->RIO Configure a port */
+#define	MOPEN			0x02			/* Driver->RIO Modem open (wait for DCD) */
+#define	CLOSE			0x03			/* Driver->RIO Close a port */
+#define	WFLUSH			(0x04|PRE_EMPTIVE)	/* Driver->RIO Write flush */
+#define	RFLUSH			(0x05|PRE_EMPTIVE)	/* Driver->RIO Read flush */
+#define	RESUME			(0x06|PRE_EMPTIVE)	/* Driver->RIO Behave as if XON received */
+#define	SBREAK			0x07			/* Driver->RIO Start break */
+#define	EBREAK			0x08			/* Driver->RIO End break */
+#define	SUSPEND			(0x09|PRE_EMPTIVE)	/* Driver->RIO Behave as if XOFF received */
+#define	FCLOSE			(0x0A|PRE_EMPTIVE)	/* Driver->RIO Force close */
+#define	XPRINT			0x0B			/* Driver->RIO Xprint packet */
+#define	MBIS			(0x0C|PRE_EMPTIVE)	/* Driver->RIO Set modem lines */
+#define	MBIC			(0x0D|PRE_EMPTIVE)	/* Driver->RIO Clear modem lines */
+#define	MSET			(0x0E|PRE_EMPTIVE)	/* Driver->RIO Set modem lines */
+#define	PCLOSE			0x0F			/* Driver->RIO Pseudo close */
+#define	MGET			(0x10|PRE_EMPTIVE)	/* Driver->RIO Force update of modem status */
+#define	MEMDUMP			(0x11|PRE_EMPTIVE)	/* Driver->RIO DEBUG request for RTA memory */
+#define	READ_REGISTER		(0x12|PRE_EMPTIVE)	/* Driver->RIO DEBUG read CD1400 register */
+
+/* Remote Unit Port (RUP) packet definitions... (specified in PKT.dest_unit and PKT.src_unit) */
+#define	SYNC_RUP		0xFF			/* Download internal */
+#define	COMMAND_RUP		0xFE			/* Command ack/status */
+#define	ERROR_RUP		0xFD			/* Download internal */
+#define	POLL_RUP		0xFC			/* Download internal */
+#define	BOOT_RUP		0xFB			/* Used to boot RTAs */
+#define	ROUTE_RUP		0xFA			/* Used to specify routing/topology */
+#define	STATUS_RUP		0xF9			/* Not used */
+#define	POWER_RUP		0xF8			/* Download internal */
+
+/* COMMAND_RUP definitions... */
+#define	COMPLETE		(0x20|PRE_EMPTIVE)	/* RIO->Driver Command complete */
+#define	BREAK_RECEIVED		(0x21|PRE_EMPTIVE)	/* RIO->Driver Break received */
+#define	MODEM_STATUS		(0x22|PRE_EMPTIVE)	/* RIO->Driver Modem status change */
+
+/* BOOT_RUP definitions... */
+#define	BOOT_REQUEST		0x00			/* RIO->Driver Request for boot */
+#define	BOOT_ABORT		0x01			/* Driver->RIO Abort a boot */
+#define	BOOT_SEQUENCE		0x02			/* Driver->RIO Packet with firmware details */
+#define	BOOT_COMPLETED		0x03			/* RIO->Driver Boot completed */
+#define IFOAD			0x2F			/* Driver->RIO Shutdown/Reboot RTA (Fall Over And Die) */
+#define	IDENTIFY		0x30			/* Driver->RIO Identify RTA */
+#define	ZOMBIE			0x31			/* Driver->RIO Shutdown/Flash LEDs */
+#define	UFOAD			0x32			/* Driver->RIO Shutdown/Reboot neighbouring RTA */
+#define IWAIT			0x33			/* Driver->RIO Pause booting process */
+
+/* ROUTE_RUP definitions... */
+#define	ROUTE_REQUEST		0x00			/* RIO->Driver Request an ID */
+#define	ROUTE_FOAD		0x01			/* Driver->RIO Shutdown/reboot RTA */
+#define	ROUTE_ALREADY		0x02			/* Driver->RIO Not used */
+#define	ROUTE_USED		0x03			/* Driver->RIO Not used */
+#define	ROUTE_ALLOCATE		0x04			/* Driver->RIO Allocate RTA RUP numbers */
+#define	ROUTE_REQ_TOP		0x05			/* Driver->RIO Not used */
+#define ROUTE_TOPOLOGY		0x06			/* RIO->Driver Route/Topology status */
+
+/*****************************************************************************
+**********************************          **********************************
+**********************************   OPEN   **********************************
+**********************************          **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+   Sent to open a port. 
+   Structure of configuration info used with OPEN, CONFIG and MOPEN packets... */
+
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_Cor1		(PKT_Data+1)		/* Channel Option Register 1 */
+#define	PKT_Cor2		(PKT_Data+2)		/* Channel Option Register 2 */
+#define	PKT_Cor4		(PKT_Data+3)		/* Channel Option Register 4 */
+#define	PKT_Cor5		(PKT_Data+4)		/* Channel Option Register 5 */
+#define	PKT_TxXon		(PKT_Data+5)		/* Transmit XON character */
+#define	PKT_TxXoff		(PKT_Data+6)		/* Transmit XOFF character */
+#define	PKT_RxXon		(PKT_Data+7)		/* Receive XON character */
+#define	PKT_RxXoff		(PKT_Data+8)		/* Receive XOFF character */
+#define	PKT_Lnext		(PKT_Data+9)		/* Lnext character */
+#define	PKT_TxBaud		(PKT_Data+10)		/* Transmit baud rate */
+#define	PKT_RxBaud		(PKT_Data+11)		/* Receive baud rate */
+
+/* COR1 definitions... */
+#define	COR1_PARITY		0xE0			/* Parity mask */
+#define	COR1_NONE		0x00			/* No parity */
+#define	COR1_SPACE		0x20			/* Space parity */
+#define	COR1_EVEN		0x40			/* Even parity */
+#define	COR1_MARK		0xA0			/* Mark parity */
+#define	COR1_ODD		0xC0			/* Odd parity */
+
+#define	COR1_STOPBITS		0x0C			/* Stop bits mask */
+#define	COR1_STOP1		0x00			/* 1 stop bit */
+#define	COR1_STOP1_5		0x04			/* 1.5 stop bits */
+#define	COR1_STOP2		0x08			/* 2 stop bits */
+
+#define	COR1_DATABITS		0x03			/* Data bits mask */
+#define	COR1_DATA5		0x00			/* 5 data bits */
+#define	COR1_DATA6		0x01			/* 6 data bits */
+#define	COR1_DATA7		0x02			/* 7 data bits */
+#define	COR1_DATA8		0x03			/* 8 data bits */
+
+/* COR2 definitions... */
+#define	COR2_XON_TXFLOW		0x40			/* XON/XOFF Transmit Flow */
+#define	COR2_XANY_TXFLOW	0xC0			/* XON/XANY Transmit Flow */
+#define	COR2_HUPCL		0x20			/* Hang Up On Close */
+#define	COR2_DSR_TXFLOW		0x08			/* DSR Transmit Flow Control */
+#define	COR2_RTS_RXFLOW		0x04			/* RTS Receive Flow Control */
+#define	COR2_CTS_TXFLOW		0x02			/* CTS Transmit Flow Control */
+#define	COR2_XON_RXFLOW		0x01			/* XON/XOFF Receive Flow */
+
+/* COR4 definition... */
+#define	COR4_IGNCR		0x80			/* Discard received CR */
+#define	COR4_ICRNL		0x40			/* Map received CR -> NL */
+#define	COR4_INLCR		0x20			/* Map received NL -> CR */
+#define	COR4_IGNBRK		0x10			/* Ignore Received Break */
+#define	COR4_NBRKINT		0x08			/* No interrupt on rx Break */
+#define	COR4_IGNPAR		0x04			/* ignore rx parity error chars */
+#define	COR4_PARMRK		0x02			/* Mark rx parity error chars */
+#define	COR4_RAISEMOD		0x01			/* Raise modem lines on !0 baud */
+
+/* COR5 definitions... */
+#define	COR5_ISTRIP		0x80			/* Strip input chars to 7 bits */
+#define	COR5_LNE		0x40			/* Enable LNEXT processing */
+#define	COR5_CMOE		0x20			/* Match good & error characters */
+#define	COR5_TAB3		0x10			/* TAB3 mode */
+#define	COR5_TSTATE_ON		0x08			/* Enable tbusy/tstop monitoring */
+#define	COR5_TSTATE_OFF		0x04			/* Disable tbusy/tstop monitoring */
+#define	COR5_ONLCR		0x02			/* NL -> CR NL on output */
+#define	COR5_OCRNL		0x01			/* CR -> NL on output */
+
+/* RxBaud and TxBaud definitions... */
+#define	RIO_B0			0x00			/* RTS / DTR signals dropped */
+#define	RIO_B50			0x01			/* 50 baud */
+#define	RIO_B75			0x02			/* 75 baud */
+#define	RIO_B110		0x03			/* 110 baud */
+#define	RIO_B134		0x04			/* 134.5 baud */
+#define	RIO_B150		0x05			/* 150 baud */
+#define	RIO_B200		0x06			/* 200 baud */
+#define	RIO_B300		0x07			/* 300 baud */
+#define	RIO_B600		0x08			/* 600 baud */
+#define	RIO_B1200		0x09			/* 1200 baud */
+#define	RIO_B1800		0x0A			/* 1800 baud */
+#define	RIO_B2400		0x0B			/* 2400 baud */
+#define	RIO_B4800		0x0C			/* 4800 baud */
+#define	RIO_B9600		0x0D			/* 9600 baud */
+#define	RIO_B19200		0x0E			/* 19200 baud */
+#define	RIO_B38400		0x0F			/* 38400 baud */
+#define	RIO_B56000		0x10			/* 56000 baud */
+#define	RIO_B57600		0x11			/* 57600 baud */
+#define	RIO_B64000		0x12			/* 64000 baud */
+#define	RIO_B115200		0x13			/* 115200 baud */
+#define	RIO_B2000		0x14			/* 2000 baud */
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   CONFIG   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+   CONFIG is sent from the driver to configure an already opened port.
+   Packet structure is same as OPEN.  */
+
+/*****************************************************************************
+*********************************           **********************************
+*********************************   MOPEN   **********************************
+*********************************           **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+   MOPEN is sent from the driver to open a port attached to a modem. (in-band)
+   Packet structure is same as OPEN.  */
+
+/*****************************************************************************
+*********************************           **********************************
+*********************************   CLOSE   **********************************
+*********************************           **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+   CLOSE is sent from the driver to close a previously opened port.
+   No parameters.
+ */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+/*****************************************************************************
+*********************************            *********************************
+*********************************   WFLUSH   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   WFLUSH is sent pre-emptively from the driver to flush the write buffers and
+   packets of a port.  (pre-emptive)
+   
+   WFLUSH is also sent in-band from the driver to a port as a marker to end
+   write flushing previously started by a pre-emptive WFLUSH packet. (in-band)
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   RFLUSH   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   RFLUSH is sent pre-emptively from the driver to flush the read buffers and
+   packets of a port.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   RESUME   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   RESUME is sent pre-emptively from the driver to cause a port to resume 
+   transmission of data if blocked by XOFF.  (as if XON had been received)
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   SBREAK   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+   SBREAK is sent in-band from the driver to a port to suspend data and start
+   break signal transmission.
+
+   If the break delay is 0, the break signal will be acknowledged with a
+   RUP_COMMAND, COMPLETE packet and continue until an EBREAK packet is received.
+
+   Otherwise, there is no acknowledgement and the break signal will last for the
+   specified number of mS.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_BreakDelay		(PKT_Data+1)		/* Break delay in mS */
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   EBREAK   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+   EBREAK is sent in-band from the driver to a port to stop transmission of a
+   break signal.
+
+   No parameters.  */
+
+/*****************************************************************************
+*********************************             ********************************
+*********************************   SUSPEND   ********************************
+*********************************             ********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   SUSPEND is sent pre-emptively from the driver to cause a port to suspend
+   transmission of data.  (as if XOFF had been received)
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   FCLOSE   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   FCLOSE is sent pre-emptively from the driver to force close a port.
+   A force close flushes receive and transmit queues, and also lowers all output
+   modem signals if the COR5_HUPCL (Hang Up On Close) flag is set.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   XPRINT   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+   XPRINT is sent as a normal I/O data packet except that the PKT_CMD_BIT of
+   the "len" field is set, and the first "data" byte is XPRINT.
+
+   The I/O data in the XPRINT packet will contain the following:
+   -	Transparent Print Start Sequence
+   -	Transparent Print Data
+   -	Transparent Print Stop Sequence.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#endif
+
+/*****************************************************************************
+**********************************          **********************************
+**********************************   MBIS   **********************************
+**********************************          **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   MBIS is sent pre-emptively from the driver to set a port's modem signals.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#endif
+#define	PKT_ModemSet		(PKT_Data+4)		/* Modem set signals mask */
+
+/* ModemSet definitions... */
+#define	MBIS_RTS		0x01			/* RTS modem signal */
+#define	MBIS_DTR		0x02			/* DTR modem signal */
+
+/*****************************************************************************
+**********************************          **********************************
+**********************************   MBIC   **********************************
+**********************************          **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   MBIC is sent pre-emptively from the driver to clear a port's modem signals.
+   */
+#if 0   
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#endif
+
+#define	PKT_ModemClear		(PKT_Data+4)		/* Modem clear signals mask */
+
+/* ModemClear definitions... */
+#define	MBIC_RTS		0x01			/* RTS modem signal */
+#define	MBIC_DTR		0x02			/* DTR modem signal */
+
+/*****************************************************************************
+**********************************          **********************************
+**********************************   MSET   **********************************
+**********************************          **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   MSET is sent pre-emptively from the driver to set/clear a port's modem signals. */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#endif
+
+#define	PKT_ModemSet		(PKT_Data+4)		/* Modem set signals mask */
+
+/* ModemSet definitions... */
+#define	MSET_RTS		0x01			/* RTS modem signal */
+#define	MSET_DTR		0x02			/* DTR modem signal */
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   PCLOSE   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+/* (Driver->RIO,in-band)
+
+   PCLOSE is sent from the driver to pseudo close a previously opened port.
+   
+   The port will close when all data has been sent/received, however, the
+   port's transmit / receive and modem signals will be left enabled and the
+   port marked internally as Pseudo Closed. */
+
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+
+/*****************************************************************************
+**********************************          **********************************
+**********************************   MGET   **********************************
+**********************************          **********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   MGET is sent pre-emptively from the driver to request the port's current modem signals. */
+
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+
+/*****************************************************************************
+*********************************             ********************************
+*********************************   MEMDUMP   ********************************
+*********************************             ********************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   MEMDUMP is sent pre-emptively from the driver to request a dump of 32 bytes
+   of the specified port's RTA address space.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#define	PKT_SubCmd		(PKT_Data+5)		/* Sub Command */
+#define	PKT_Address		(PKT_Data+6)		/* Requested address */
+
+/*****************************************************************************
+******************************                   *****************************
+******************************   READ_REGISTER   *****************************
+******************************                   *****************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   READ_REGISTER is sent pre-emptively from the driver to request the contents
+   of the CD1400 register specified in address.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#define	PKT_SubCmd		(PKT_Data+5)		/* Sub Command */
+#define	PKT_Address		(PKT_Data+6)		/* Requested address */
+
+/*****************************************************************************
+************************                            **************************
+************************   COMMAND_RUP - COMPLETE   **************************
+************************                            **************************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   COMMAND_RUP - COMPLETE is sent in response to all port I/O control command
+   packets, except MEMDUMP and READ_REGISTER.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#define	PKT_Cmd2		(PKT_Data+2)		/* Command code copy */
+#define	PKT_ModemStatus		(PKT_Data+3)		/* Modem signal status */
+#define	PKT_PortStatus		(PKT_Data+4)		/* Port signal status */
+#define	PKT_SubCmd		(PKT_Data+5)		/* Sub Command */
+
+/* ModemStatus definitions... */
+#define	MODEM_DSR		0x80			/* Data Set Ready modem state */
+#define	MODEM_CTS		0x40			/* Clear To Send modem state */
+#define	MODEM_RI		0x20			/* Ring Indicate modem state */
+#define	MODEM_CD		0x10			/* Carrier Detect modem state */
+#define	MODEM_TSTOP		0x08			/* Transmit Stopped state */
+#define	MODEM_TEMPTY		0x04			/* Transmit Empty state */
+#define	MODEM_DTR		0x02			/* DTR modem output state */
+#define	MODEM_RTS		0x01			/* RTS modem output state */
+
+/* PortStatus definitions... */
+#define	PORT_ISOPEN		0x01			/* Port open ? */
+#define	PORT_HUPCL		0x02			/* Hangup on close? */
+#define	PORT_MOPENPEND		0x04			/* Modem open pending */
+#define	PORT_ISPARALLEL		0x08			/* Parallel port */
+#define	PORT_BREAK		0x10			/* Port on break */
+#define	PORT_STATUSPEND		0020			/* Status packet pending */
+#define	PORT_BREAKPEND		0x40			/* Break packet pending */
+#define	PORT_MODEMPEND		0x80			/* Modem status packet pending */
+
+/*****************************************************************************
+************************                            **************************
+************************   COMMAND_RUP - COMPLETE   **************************
+************************                            **************************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   COMMAND_RUP - COMPLETE is sent in response to all port I/O control command
+   packets, except MEMDUMP and READ_REGISTER.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#define	PKT_Cmd2		(PKT_Data+2)		/* Command code copy */
+#endif
+#define	PKT_ModemStatus		(PKT_Data+3)		/* Modem signal status */
+#define	PKT_PortStatus		(PKT_Data+4)		/* Port signal status */
+#if 0
+#define	PKT_SubCmd		(PKT_Data+5)		/* Sub Command */
+#endif
+
+/* ModemStatus definitions... */
+#define	MODEM_DSR		0x80			/* Data Set Ready modem state */
+#define	MODEM_CTS		0x40			/* Clear To Send modem state */
+#define	MODEM_RI		0x20			/* Ring Indicate modem state */
+#define	MODEM_CD		0x10			/* Carrier Detect modem state */
+#define	MODEM_TSTOP		0x08			/* Transmit Stopped state */
+#define	MODEM_TEMPTY		0x04			/* Transmit Empty state */
+#define	MODEM_DTR		0x02			/* DTR modem output state */
+#define	MODEM_RTS		0x01			/* RTS modem output state */
+
+/* PortStatus definitions... */
+#define	PORT_ISOPEN		0x01			/* Port open ? */
+#define	PORT_HUPCL		0x02			/* Hangup on close? */
+#define	PORT_MOPENPEND		0x04			/* Modem open pending */
+#define	PORT_ISPARALLEL		0x08			/* Parallel port */
+#define	PORT_BREAK		0x10			/* Port on break */
+#define	PORT_STATUSPEND		0020			/* Status packet pending */
+#define	PORT_BREAKPEND		0x40			/* Break packet pending */
+#define	PORT_MODEMPEND		0x80			/* Modem status packet pending */
+
+/*****************************************************************************
+********************                                      ********************
+********************   COMMAND_RUP - COMPLETE - MEMDUMP   ********************
+********************                                      ********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   COMMAND_RUP - COMPLETE - MEMDUMP is sent as an acknowledgement for a MEMDUMP
+   port I/O control command packet.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#define	PKT_Cmd2		(PKT_Data+2)		/* Command code copy */
+#define	PKT_ModemStatus		(PKT_Data+3)		/* Modem signal status */
+#define	PKT_PortStatus		(PKT_Data+4)		/* Port signal status */
+#define	PKT_SubCmd		(PKT_Data+5)		/* Sub Command */
+#define	PKT_Address		(PKT_Data+6)		/* Requested address */
+#endif
+#define	PKT_Dump		(PKT_Data+8)		/* 32bytes of requested dump data */
+
+/*****************************************************************************
+*****************                                            *****************
+*****************   COMMAND_RUP - COMPLETE - READ_REGISTER   *****************
+*****************                                            *****************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   COMMAND_RUP - COMPLETE - READ_REGISTER is sent as an acknowledgement for a
+   READ_REGISTER port I/O control command packet.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/*Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/*Port number wrt RTA */
+#define	PKT_Cmd2		(PKT_Data+2)		/* Command code copy */
+#endif
+#define	PKT_RegisterValue	(PKT_Data+3)		/* Modem signal status */
+#if 0
+#define	PKT_PortStatus		(PKT_Data+4)		/* Port signal status */
+#define	PKT_SubCmd		(PKT_Data+5)		/* Sub Command */
+#endif
+
+/*****************************************************************************
+*********************                                  ***********************
+*********************   COMMAND_RUP - BREAK_RECEIVED   ***********************
+*********************                                  ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   COMMAND_RUP - BREAK_RECEIVED packets are sent when the port detects a receive BREAK signal.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#define	PKT_Cmd2		(PKT_Data+2)		/* Command code copy */
+#endif
+
+/*****************************************************************************
+*********************                                *************************
+*********************   COMMAND_RUP - MODEM_STATUS   *************************
+*********************                                *************************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   COMMAND_RUP - MODEM_STATUS packets are sent whenever the port detects a
+   change in the input modem signal states.
+
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_PhbNum		(PKT_Data+1)		/* Port number wrt RTA */
+#define	PKT_Cmd2		(PKT_Data+2)		/* Command code copy */
+#define	PKT_ModemStatus		(PKT_Data+3)		/* Modem signal status */
+#endif
+
+/*****************************************************************************
+************************                             *************************
+************************   BOOT_RUP - BOOT_REQUEST   *************************
+************************                             *************************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   BOOT_RUP - BOOT_REQUEST packets are sent to the Driver from RIO to request
+   firmware code to load onto attached RTAs.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+
+/*****************************************************************************
+************************                              ************************
+************************   BOOT_RUP - BOOT_SEQUENCE   ************************
+************************                              ************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   BOOT_RUP - BOOT_SEQUENCE packets are sent from the Driver to RIO in response
+   to a BOOT_RUP - BOOT_REQUEST packet.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_NumPackets		(PKT_Data+2)		/* Packets required to load firmware */
+#define	PKT_LoadBase		(PKT_Data+4)		/* RTA firmware load address */
+#define	PKT_CodeSize		(PKT_Data+6)		/* Size of firmware in bytes */
+#define	PKT_CmdString		(PKT_Data+8)		/* Command string */
+
+/*****************************************************************************
+************************                               ***********************
+************************   BOOT_RUP - BOOT_COMPLETED   ***********************
+************************                               ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   BOOT_RUP - BOOT_COMPLETE is sent to the Driver from RIO when downloading of
+   RTA firmware has completed.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_LinkNumber		(PKT_Data+1)		/* Link number RTA booted on */
+#define	PKT_SerialNumber	(PKT_Data+2)		/* 4 byte serial number */
+
+/*****************************************************************************
+************************                               ***********************
+************************   BOOT_RUP - Packet Request   ***********************
+************************                               ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   BOOT_RUP packet without the PKT_CMD_BIT set in the PKT->len field is sent
+   from RIO to the Driver as a request for a firmware boot packet. */
+
+#define	PKT_SequenceNumber	(PKT_Data+0)		/* Packet sequence number */
+
+/*****************************************************************************
+***********************                                ***********************
+***********************   BOOT_RUP - Packet Response   ***********************
+***********************                                ***********************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   In response to a BOOT_RUP boot packet request, the driver fills out the response
+   packet with the 70 bytes of the requested sequence.
+   */
+#if 0
+#define	PKT_SequenceNumber	(PKT_Data+0)		/* Packet sequence number */
+#endif
+#define	PKT_FirmwarePacket	(PKT_Data+2)		/* Firmware packet */
+
+/*****************************************************************************
+****************************                      ****************************
+****************************   BOOT_RUP - IFOAD   ****************************
+****************************                      ****************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   BOOT_RUP - IFOAD packets are sent from the Driver to an RTA to cause the
+   RTA to shut down and reboot.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_IfoadId1		(PKT_Data+2)		/* IFOAD Id 1 */
+#define	PKT_IfoadId2		(PKT_Data+3)		/* IFOAD Id 2 */
+
+#define	IFOADID1		0xAD
+#define	IFOADID2		0xF0
+
+/*****************************************************************************
+**************************                         ***************************
+**************************   BOOT_RUP - IDENTIFY   ***************************
+**************************                         ***************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   BOOT_RUP - IDENTIFY packets are sent from the Driver to an RTA to cause the
+   RTA to flash its LEDs for a period of time.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_IdentifyId		(PKT_Data+2)		/* defines pattern to flash */
+
+/*****************************************************************************
+****************************                       ***************************
+****************************   BOOT_RUP - ZOMBIE   ***************************
+****************************                       ***************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   BOOT_RUP - ZOMBIE packets are sent from the Driver to an RTA to cause the
+   RTA to shut down and flash it's LEDs.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_ZombieId1		(PKT_Data+2)		/* ZOMBIE Id 1 */
+#define	PKT_ZombieId2		(PKT_Data+3)		/* ZOMBIE Id 2 */
+
+#define	ZOMBIEID1		0x52
+#define	ZOMBIEID2		0x21
+
+/*****************************************************************************
+****************************                      ****************************
+****************************   BOOT_RUP - UFOAD   ****************************
+****************************                      ****************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   BOOT_RUP - UFOAD packets are sent from the Driver to an RTA to cause the RTA
+   to ask it's neighbouring RTA to shut down and reboot.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_LinkNumber		(PKT_Data+1)		/* Link number of RTA to UFOAD */
+#endif
+#define	PKT_UfoadId1		(PKT_Data+2)		/* UFOAD Id 1 */
+#define	PKT_UfoadId2		(PKT_Data+3)		/* UFOAD Id 2 */
+
+#define	UFOADID1		0x1E
+#define	UFOADID2		0x0D
+
+/*****************************************************************************
+****************************                      ****************************
+****************************   BOOT_RUP - IWAIT   ****************************
+****************************                      ****************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   BOOT_RUP - IWAIT packets are sent from the Driver to an RTA to cause the RTA
+   to pause booting on the specified link for 30 seconds.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#define	PKT_LinkNumber		(PKT_Data+1)		/* Link number of RTA to UFOAD */
+#endif
+#define	PKT_IwaitId1		(PKT_Data+2)		/* IWAIT Id 1 */
+#define	PKT_IwaitId2		(PKT_Data+3)		/* IWAIT Id 2 */
+
+#define	IWAITID1		0xDE
+#define	IWAITID2		0xB1
+
+/*****************************************************************************
+************************                               ***********************
+************************   ROUTE_RUP - ROUTE_REQUEST   ***********************
+************************                               ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   ROUTE_RUP - ROUTE_REQUEST packets are sent from a newly booted or connected
+   RTA to a Driver to request an ID (RUP or unit number).
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_SerialNumber	(PKT_Data+2)		/* 4 byte serial number */
+#define	PKT_ModuleTypes		(PKT_Data+6)		/* RTA Module types */
+
+/* ModuleTypes definitions... */
+#define	MOD_BLANK		0x0F			/* Blank plate attached */
+#define	MOD_RS232DB25		0x00			/* RS232 DB25 connector */
+#define	MOD_RS232RJ45		0x01			/* RS232 RJ45 connector */
+#define	MOD_RS422DB25		0x02			/* RS422 DB25 connector */
+#define	MOD_RS485DB25		0x03			/* RS485 DB25 connector */
+#define	MOD_PARALLEL		0x04			/* Centronics parallel */
+
+#define	MOD2			0x08			/* Set to indicate Rev2 module */
+
+/*****************************************************************************
+*************************                            *************************
+*************************   ROUTE_RUP - ROUTE_FOAD   *************************
+*************************                            *************************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   ROUTE_RUP - ROUTE_FOAD packet is sent as a response to a ROUTE_RUP - ROUTE_REQUEST
+   packet to cause the RTA to "Fall Over And Die"., i.e. shutdown and reboot.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_RouteCmdString	(PKT_Data+2)		/* Command string */
+
+/*****************************************************************************
+***********************                                ***********************
+***********************   ROUTE_RUP - ROUTE_ALLOCATE   ***********************
+***********************                                ***********************
+*****************************************************************************/
+
+/* (Driver->RIO,pre-emptive)
+
+   ROUTE_RUP - ROUTE_ALLOCATE packet is sent as a response to a ROUTE_RUP - ROUTE_REQUEST
+   packet to allocate the RTA's Id number (RUP number 1..16)
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_IdNum		(PKT_Data+1)		/* RUP number for ports 1..8 */
+#if 0
+#define	PKT_RouteCmdString	(PKT_Data+2)		/* Command string */
+#endif
+#define	PKT_IdNum2		(PKT_Data+0x17)		/* RUP number for ports 9..16 */
+
+/*****************************************************************************
+***********************                                ***********************
+***********************   ROUTE_RUP - ROUTE_TOPOLOGY   ***********************
+***********************                                ***********************
+*****************************************************************************/
+
+/* (RIO->Driver,pre-emptive)
+
+   ROUTE_RUP - ROUTE_TOPOLOGY packet is sent to inform the driver of an RTA's
+   current link status.
+   */
+#if 0
+#define	PKT_Cmd			(PKT_Data+0)		/* Command code */
+#endif
+#define	PKT_Link1Rup		(PKT_Data+2)		/* Link 1 RUP number */
+#define	PKT_Link1Link		(PKT_Data+3)		/* Link 1 link number */
+#define	PKT_Link2Rup		(PKT_Data+4)		/* Link 2 RUP number */
+#define	PKT_Link2Link		(PKT_Data+5)		/* Link 2 link number */
+#define	PKT_Link3Rup		(PKT_Data+6)		/* Link 3 RUP number */
+#define	PKT_Link3Link		(PKT_Data+7)		/* Link 3 link number */
+#define	PKT_Link4Rup		(PKT_Data+8)		/* Link 4 RUP number */
+#define	PKT_Link4Link		(PKT_Data+9)		/* Link 4 link number */
+#define	PKT_RtaVpdProm		(PKT_Data+10)		/* 32 bytes of RTA VPD PROM Contents */
+
+#endif						/* _sxwinif_h */
+
+/* End of RIOWINIF.H */
diff --git a/drivers/char/rio/riscos.h b/drivers/char/rio/riscos.h
new file mode 100644
index 0000000..7685cc1
--- /dev/null
+++ b/drivers/char/rio/riscos.h
@@ -0,0 +1,63 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: riscos.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:19
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)riscos.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_riscos_h__
+#define __rio_riscos_h__
+
+#ifdef SCCS_LABELS
+static char *_riscos_h_sccs_ = "@(#)riscos.h	1.2";
+#endif
+
+/*
+** This module used to define all those little itsy bits required for RISC/OS
+** now it's full of null macros.
+*/
+
+/*
+**	RBYTE reads a byte from a location.
+**	RWORD reads a word from a location.
+**	WBYTE writes a byte to a location.
+**	WWORD writes a word to a location.
+**	RINDW reads a word through a pointer.
+**	WINDW writes a word through a pointer.
+**	RIOSWAB swaps the two bytes of a word, if needed.
+*/
+
+#define	RIOSWAB(N)      (N)
+#define	WBYTE(A,V)	(A)=(uchar)(V)
+#define WWORD(A,V)	(A)=(ushort)(V)
+#define RBYTE(A)	(uchar)(A)
+#define RWORD(A)	(ushort)(A)
+#define RINDW(A)	(*(ushort *)(A))
+#define WINDW(A,V)	(*(ushort *)(A)=(ushort)(V))
+
+#endif /* __rio_riscos_h__ */
diff --git a/drivers/char/rio/rom.h b/drivers/char/rio/rom.h
new file mode 100644
index 0000000..ee79b8e
--- /dev/null
+++ b/drivers/char/rio/rom.h
@@ -0,0 +1,64 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                      R O M
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _rom_h
+#define _rom_h 1
+
+#ifndef lint
+#ifdef SCCS
+static char *_rio_rom_h_sccs = "@(#)rom.h	1.1" ;
+#endif
+#endif
+
+typedef struct  ROM  ROM ;
+struct  ROM  {
+                 u_short    slx ;
+                 char       pcb_letter_rev ;
+                 char       pcb_number_rev ;
+                 char       serial[4] ;
+                 char       year ;
+                 char       week ;
+             } ;
+
+#endif
+
+#define HOST_ROM    (ROM *) 0x7c00
+#define RTA_ROM	    (ROM *) 0x7801
+#define ROM_LENGTH  0x20
+
+/*********** end of file ***********/
+
+
diff --git a/drivers/char/rio/route.h b/drivers/char/rio/route.h
new file mode 100644
index 0000000..c42dbb9
--- /dev/null
+++ b/drivers/char/rio/route.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                 R O U T E     H E A D E R
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra / Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _route_h
+#define _route_h
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_route_h_sccs = "@(#)route.h	1.3"; */
+#endif
+#endif
+
+#define MAX_LINKS 4
+#define MAX_NODES 17                          /* Maximum nodes in a subnet */
+#define NODE_BYTES ((MAX_NODES / 8) + 1)      /* Number of bytes needed for
+                                                 1 bit per node */
+#define ROUTE_DATA_SIZE  (NODE_BYTES + 2)     /* Number of bytes for complete 
+                                                 info about cost etc. */
+#define ROUTES_PER_PACKET ((PKT_MAX_DATA_LEN -2)/ ROUTE_DATA_SIZE)
+                                              /* Number of nodes we can squeeze
+                                                 into one packet */
+#define MAX_TOPOLOGY_PACKETS (MAX_NODES / ROUTES_PER_PACKET + 1)
+/************************************************
+ * Define the types of command for the ROUTE RUP.
+ ************************************************/
+#define ROUTE_REQUEST    0                    /* Request an ID */
+#define ROUTE_FOAD       1                    /* Kill the RTA */
+#define ROUTE_ALREADY    2                    /* ID given already */
+#define ROUTE_USED       3                    /* All ID's used */
+#define ROUTE_ALLOCATE   4                    /* Here it is */
+#define ROUTE_REQ_TOP    5                    /* I bet you didn't expect....
+                                                 the Topological Inquisition */
+#define ROUTE_TOPOLOGY   6                    /* Topology request answered FD */
+/*******************************************************************
+ * Define the Route Map Structure
+ *
+ * The route map gives a pointer to a Link Structure to use.
+ * This allows Disconnected Links to be checked quickly
+ ******************************************************************/
+typedef struct COST_ROUTE COST_ROUTE;
+struct COST_ROUTE {
+                      unsigned char cost;        /* Cost down this link */
+                      unsigned char route[NODE_BYTES]; /* Nodes thorough this route */
+                  } ;
+
+typedef struct ROUTE_STR ROUTE_STR ;
+struct  ROUTE_STR {
+                      COST_ROUTE cost_route[MAX_LINKS];
+                                                /* cost / route for this link */
+                      ushort favoured;          /* favoured link */
+                  } ;
+
+
+#define NO_LINK            (short) 5      /* Link unattached */
+#define ROUTE_NO_ID        (short) 100    /* No Id */
+#define ROUTE_DISCONNECT   (ushort) 0xff  /* Not connected */
+#define ROUTE_INTERCONNECT (ushort) 0x40  /* Sub-net interconnect */
+
+
+#define SYNC_RUP         (ushort) 255
+#define COMMAND_RUP      (ushort) 254
+#define ERROR_RUP        (ushort) 253
+#define POLL_RUP         (ushort) 252
+#define BOOT_RUP         (ushort) 251
+#define ROUTE_RUP        (ushort) 250
+#define STATUS_RUP       (ushort) 249
+#define POWER_RUP        (ushort) 248
+
+#define HIGHEST_RUP      (ushort) 255   /* Set to Top one */
+#define LOWEST_RUP       (ushort) 248   /* Set to bottom one */
+
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/rtahw.h b/drivers/char/rio/rtahw.h
new file mode 100644
index 0000000..0686011
--- /dev/null
+++ b/drivers/char/rio/rtahw.h
@@ -0,0 +1,75 @@
+
+/****************************************************************************
+ *******                                                              *******
+ *******                R T A    H A R D W A R E
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_rtahw_h_sccs = "@(#)rtahw.h	1.5" ;
+#endif
+#endif
+
+#define	WATCHDOG_ADDR	((unsigned short *)0x7a00)
+#define RTA_LED_ADDR	((unsigned short *)0x7c00)
+#define SERIALNUM_ADDR	((unsigned char *)0x7809)
+#define LATCH_ADDR      ((unsigned char *)0x7800)
+
+/*
+** Here we define where the cd1400 chips are in memory.
+*/
+#define CD1400_ONE_ADDR		(0x7300)
+#define CD1400_TWO_ADDR		(0x7200)
+#define CD1400_THREE_ADDR	(0x7100)
+#define CD1400_FOUR_ADDR	(0x7000)
+
+/*
+** Define the different types of modules we can have
+*/
+enum module {
+    MOD_BLANK		= 0x0f,		/* Blank plate attached */
+    MOD_RS232DB25	= 0x00,		/* RS232 DB25 connector */
+    MOD_RS232RJ45	= 0x01,		/* RS232 RJ45 connector */
+    MOD_RS422DB25	= 0x02,		/* RS422 DB25 connector */
+    MOD_RS485DB25	= 0x03,		/* RS485 DB25 connector */
+    MOD_PARALLEL	= 0x04		/* Centronics parallel */
+};
+
+#define TYPE_HOST	0
+#define TYPE_RTA8	1
+#define TYPE_RTA16	2
+
+#define	WATCH_DOG	WATCHDOG_ADDR
+
+/*********** end of file ***********/
diff --git a/drivers/char/rio/rup.h b/drivers/char/rio/rup.h
new file mode 100644
index 0000000..b9d2bc0
--- /dev/null
+++ b/drivers/char/rio/rup.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+ *******                                                              *******
+ *******               R U P   S T R U C T U R E
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _rup_h
+#define _rup_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_rup_h_sccs = "@(#)rup.h	1.5"; */
+#endif
+#endif
+
+#if defined( HOST ) || defined( INKERNEL )
+#define MAX_RUP          ((short) 16) 
+#endif
+#ifdef RTA
+#define MAX_RUP          ((short) 1)
+#endif
+
+#define PKTS_PER_RUP     ((short) 2)     /* They are always used in pairs */
+
+/*************************************************
+ * Define all the  packet request stuff
+ ************************************************/
+#define TX_RUP_INACTIVE          0        /* Nothing to transmit */
+#define TX_PACKET_READY          1        /* Transmit packet ready */
+#define TX_LOCK_RUP              2        /* Transmit side locked */
+
+#define RX_RUP_INACTIVE          0        /* Nothing received */
+#define RX_PACKET_READY          1        /* Packet received */
+
+#define RUP_NO_OWNER             0xff     /* RUP not owned by any process */
+
+struct RUP {
+             PKT_ptr    txpkt;            /* Outgoing packet */
+             PKT_ptr    rxpkt;            /* Incoming packet */
+             WORD       link;             /* Which link to send down? */
+             BYTE       rup_dest_unit[2]; /* Destination unit */
+             WORD       handshake;        /* For handshaking */
+             WORD       timeout;          /* Timeout */
+             WORD       status;           /* Status */
+             WORD       txcontrol;        /* Transmit control */
+             WORD       rxcontrol;        /* Receive control */
+           };
+ 
+#endif
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/rupstat.h b/drivers/char/rio/rupstat.h
new file mode 100644
index 0000000..b4aafaf
--- /dev/null
+++ b/drivers/char/rio/rupstat.h
@@ -0,0 +1,51 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                      RUPSTAT
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Jeremy Rolls
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef _rupstat_h
+#define _rupstat_h
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_rupstat_h_sccs = "@(#)rupstat.h	1.1" ;
+#endif
+#endif
+
+#define    STATUS_SYNC    0
+#define    STATUS_REQ_TOP 1
+#define    STATUS_TOPOLOGY    2
+
+#endif
+
diff --git a/drivers/char/rio/sam.h b/drivers/char/rio/sam.h
new file mode 100644
index 0000000..c1accb8
--- /dev/null
+++ b/drivers/char/rio/sam.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+ *******                                                              *******
+ *******                    S A M . H
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+#ifndef _sam_h
+#define _sam_h 1
+
+#ifdef SCCS_LABELS
+#ifndef lint
+/* static char *_rio_sam_h_sccs = "@(#)sam.h	1.3"; */
+#endif
+#endif
+
+
+#if !defined( HOST ) && !defined( INKERNEL )
+#define RTA 1
+#endif
+
+#define NUM_FREE_LIST_UNITS     500
+
+#ifndef FALSE
+#define FALSE (short)  0x00
+#endif
+#ifndef TRUE
+#define TRUE  (short)  !FALSE
+#endif
+
+#define TX    TRUE
+#define RX    FALSE
+
+
+typedef struct FREE_LIST FREE_LIST ;
+struct FREE_LIST   {
+                       FREE_LIST_ptr next ;
+                       FREE_LIST_ptr prev ;
+                   } ;
+
+
+#endif
+/*********** end of file ***********/
+
+
+
diff --git a/drivers/char/rio/selftest.h b/drivers/char/rio/selftest.h
new file mode 100644
index 0000000..deae487
--- /dev/null
+++ b/drivers/char/rio/selftest.h
@@ -0,0 +1,73 @@
+/*
+** File:		selftest.h
+**
+** Author:		David Dix
+**
+** Created:		15th March 1993
+**
+** Last modified:	94/06/14
+**
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+*/
+
+#ifndef	_selftests_h_
+#define _selftests_h_
+
+/*
+** Selftest identifier...
+*/
+#define SELFTEST_MAGIC	0x5a5a
+
+/*
+** This is the structure of the packet that is sent back after each
+** selftest on a booting RTA.
+*/
+typedef struct {
+    short		magic;			/* Identifies packet type */
+    int			test;			/* Test number, see below */
+    unsigned int	result;			/* Result value */
+    unsigned int	dataIn;
+    unsigned int	dataOut;
+}selftestStruct;
+
+/*
+** The different tests are identified by the following data values.
+*/
+enum test {
+    TESTS_COMPLETE	= 0x00,
+    MEMTEST_ADDR	= 0x01,
+    MEMTEST_BIT		= 0x02,
+    MEMTEST_FILL	= 0x03,
+    MEMTEST_DATABUS	= 0x04,
+    MEMTEST_ADDRBUS	= 0x05,
+    CD1400_INIT		= 0x10,
+    CD1400_LOOP		= 0x11,
+    CD1400_INTERRUPT    = 0x12
+};
+
+enum result {
+    E_PORT		= 0x10,
+    E_TX		= 0x11,
+    E_RX		= 0x12,
+    E_EXCEPT		= 0x13,
+    E_COMPARE		= 0x14,
+    E_MODEM		= 0x15,
+    E_TIMEOUT		= 0x16,
+    E_INTERRUPT         = 0x17
+};
+#endif	/* _selftests_h_ */
diff --git a/drivers/char/rio/space.h b/drivers/char/rio/space.h
new file mode 100644
index 0000000..72398d3
--- /dev/null
+++ b/drivers/char/rio/space.h
@@ -0,0 +1,45 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: space.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:19
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)space.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_space_h__
+#define __rio_space_h__
+
+#ifdef SCCS_LABELS
+static char *_space_h_sccs_ = "@(#)space.h	1.2";
+#endif
+
+extern int rio_cntls;
+extern int rio_bases[];
+extern int rio_limits[];
+extern int rio_vects[];
+
+#endif /* __rio_space_h__ */
diff --git a/drivers/char/rio/sysmap.h b/drivers/char/rio/sysmap.h
new file mode 100644
index 0000000..fdc7313
--- /dev/null
+++ b/drivers/char/rio/sysmap.h
@@ -0,0 +1,63 @@
+
+/****************************************************************************
+ *******                                                              *******
+ *******          S Y S T E M   M A P   H E A D E R
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_sysmap_h_sccs = "@(#)sysmap.h	1.1" ;
+#endif
+#endif
+
+#define SYSTEM_MAP_LEN     64           /* Len of System Map array */
+
+
+typedef struct SYS_MAP        SYS_MAP ;
+typedef struct SYS_MAP_LINK   SYS_MAP_LINK ;
+
+struct SYS_MAP_LINK {
+                        short id ;          /* Unit Id */
+                        short link ;        /* Id's Link */
+                        short been_here ;   /* Used by map_gen */
+                    } ;
+
+struct SYS_MAP {
+                   char         serial_num[4] ;
+                   SYS_MAP_LINK link[4] ;
+               } ;
+
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/timeouts.h b/drivers/char/rio/timeouts.h
new file mode 100644
index 0000000..11b3133
--- /dev/null
+++ b/drivers/char/rio/timeouts.h
@@ -0,0 +1,51 @@
+
+/****************************************************************************
+ *******                                                              *******
+ *******                     T I M E O U T S
+ *******                                                              *******
+ ****************************************************************************
+
+ Author  : Ian Nandhra
+ Date    :
+
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+
+ Version : 0.01
+
+
+                            Mods
+ ----------------------------------------------------------------------------
+  Date     By                Description
+ ----------------------------------------------------------------------------
+
+ ***************************************************************************/
+
+#ifndef lint
+#ifdef SCCS_LABELS
+static char *_rio_defaults_h_sccs = "@(#)timeouts.h	1.3" ;
+#endif
+#endif
+
+#define MILLISECOND           (int) (1000/64)   /* 15.625 low ticks */
+#define SECOND                (int) 15625       /* Low priority ticks */
+
+#define TX_TIMEOUT          (int) (200 * MILLISECOND)
+
+
+/*********** end of file ***********/
+
diff --git a/drivers/char/rio/top.h b/drivers/char/rio/top.h
new file mode 100644
index 0000000..255c40d
--- /dev/null
+++ b/drivers/char/rio/top.h
@@ -0,0 +1,49 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: top.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:19
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)top.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_top_h__
+#define __rio_top_h__
+
+#ifdef SCCS_LABELS
+static char *_top_h_sccs_ = "@(#)top.h	1.2";
+#endif
+
+/*
+** Topology information
+*/
+struct Top
+{
+    uchar Unit;
+    uchar Link;
+};
+
+#endif /* __rio_top_h__ */
diff --git a/drivers/char/rio/typdef.h b/drivers/char/rio/typdef.h
new file mode 100644
index 0000000..2cb9dd6
--- /dev/null
+++ b/drivers/char/rio/typdef.h
@@ -0,0 +1,82 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: typdef.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:20
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)typdef.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_typdef_h__
+#define __rio_typdef_h__
+
+#ifdef SCCS_LABELS
+static char *_typdef_h_sccs_ = "@(#)typdef.h	1.2";
+#endif
+
+#undef VPIX
+
+/*
+** IT IS REALLY, REALLY, IMPORTANT THAT BYTES ARE UNSIGNED!
+**
+** These types are ONLY to be used for refering to data structures
+** on the RIO Host card!
+*/
+typedef	volatile unsigned char	BYTE;
+typedef volatile unsigned short	WORD;
+typedef volatile unsigned int	DWORD;
+typedef	volatile unsigned short RIOP;
+typedef	volatile short          NUMBER;
+
+
+/*
+** 27.01.199 ARG - mods to compile 'newutils' on LyxnOS -
+** These #defines are for the benefit of the 'libfuncs' library
+** only. They are not necessarily correct type mappings and
+** are here only to make the source compile.
+*/
+/* typedef unsigned int	uint; */
+typedef unsigned long	ulong_t;
+typedef unsigned short	ushort_t;
+typedef unsigned char	uchar_t;
+typedef unsigned char	queue_t;
+typedef unsigned char	mblk_t;
+typedef	unsigned int 	paddr_t;
+typedef unsigned char   uchar;
+
+#define	TPNULL	((ushort)(0x8000))
+
+
+/*
+** RIO structures defined in other include files.
+*/
+typedef struct PKT	 	PKT;
+typedef struct LPB	 	LPB;
+typedef struct RUP	 	RUP;
+typedef struct Port		Port;
+typedef struct DpRam		DpRam;
+
+#endif /* __rio_typdef_h__ */
diff --git a/drivers/char/rio/unixrup.h b/drivers/char/rio/unixrup.h
new file mode 100644
index 0000000..eddf862
--- /dev/null
+++ b/drivers/char/rio/unixrup.h
@@ -0,0 +1,56 @@
+/*
+** -----------------------------------------------------------------------------
+**
+**  Perle Specialix driver for Linux
+**  Ported from existing RIO Driver for SCO sources.
+ *
+ *  (C) 1990 - 2000 Specialix International Ltd., Byfleet, Surrey, UK.
+ *
+ *      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.
+**
+**	Module		: unixrup.h
+**	SID		: 1.2
+**	Last Modified	: 11/6/98 11:34:20
+**	Retrieved	: 11/6/98 11:34:22
+**
+**  ident @(#)unixrup.h	1.2
+**
+** -----------------------------------------------------------------------------
+*/
+
+#ifndef __rio_unixrup_h__
+#define __rio_unixrup_h__
+
+#ifdef SCCS_LABELS
+static char *_unixrup_h_sccs_ = "@(#)unixrup.h	1.2";
+#endif
+
+/*
+**    UnixRup data structure. This contains pointers to actual RUPs on the
+**    host card, and all the command/boot control stuff.
+*/
+struct    UnixRup
+{
+    struct CmdBlk    *CmdsWaitingP;	/* Commands waiting to be done */
+    struct CmdBlk    *CmdPendingP;	/* The command currently being sent */
+    struct RUP       *RupP;		/* the Rup to send it to */
+    uint             Id;		/* Id number */
+    uint             BaseSysPort;	/* SysPort of first tty on this RTA */
+    uint             ModTypes;		/* Modules on this RTA */
+    spinlock_t	     RupLock;		/* Lock structure for MPX */
+/*    struct lockb     RupLock;	*/	/* Lock structure for MPX */
+};
+
+#endif /* __rio_unixrup_h__ */
diff --git a/drivers/char/riscom8.c b/drivers/char/riscom8.c
new file mode 100644
index 0000000..55a3a01
--- /dev/null
+++ b/drivers/char/riscom8.c
@@ -0,0 +1,1809 @@
+/*
+ *      linux/drivers/char/riscom.c  -- RISCom/8 multiport serial driver.
+ *
+ *      Copyright (C) 1994-1996  Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ *      This code is loosely based on the Linux serial driver, written by
+ *      Linus Torvalds, Theodore T'so and others. The RISCom/8 card 
+ *      programming info was obtained from various drivers for other OSes 
+ *	(FreeBSD, ISC, etc), but no source code from those drivers were 
+ *	directly included in this driver.
+ *
+ *
+ *      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.
+ *
+ *	Revision 1.1
+ *
+ *	ChangeLog:
+ *	Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 27-Jun-2001
+ *	- get rid of check_region and several cleanups
+ */
+
+#include <linux/module.h>
+
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <asm/uaccess.h>
+
+#include "riscom8.h"
+#include "riscom8_reg.h"
+
+/* Am I paranoid or not ? ;-) */
+#define RISCOM_PARANOIA_CHECK
+
+/* 
+ * Crazy InteliCom/8 boards sometimes has swapped CTS & DSR signals.
+ * You can slightly speed up things by #undefing the following option,
+ * if you are REALLY sure that your board is correct one. 
+ */
+
+#define RISCOM_BRAIN_DAMAGED_CTS
+
+/* 
+ * The following defines are mostly for testing purposes. But if you need
+ * some nice reporting in your syslog, you can define them also.
+ */
+#undef RC_REPORT_FIFO
+#undef RC_REPORT_OVERRUN
+
+
+#define RISCOM_LEGAL_FLAGS \
+	(ASYNC_HUP_NOTIFY   | ASYNC_SAK          | ASYNC_SPLIT_TERMIOS   | \
+	 ASYNC_SPD_HI       | ASYNC_SPEED_VHI    | ASYNC_SESSION_LOCKOUT | \
+	 ASYNC_PGRP_LOCKOUT | ASYNC_CALLOUT_NOHUP)
+
+#define RS_EVENT_WRITE_WAKEUP	0
+
+static struct riscom_board * IRQ_to_board[16];
+static struct tty_driver *riscom_driver;
+static unsigned char * tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static unsigned long baud_table[] =  {
+	0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+	9600, 19200, 38400, 57600, 76800, 0, 
+};
+
+static struct riscom_board rc_board[RC_NBOARD] =  {
+	{
+		.base	= RC_IOBASE1,
+	},
+	{
+		.base	= RC_IOBASE2,
+	},
+	{
+		.base	= RC_IOBASE3,
+	},
+	{
+		.base	= RC_IOBASE4,
+	},
+};
+
+static struct riscom_port rc_port[RC_NBOARD * RC_NPORT];
+
+/* RISCom/8 I/O ports addresses (without address translation) */
+static unsigned short rc_ioport[] =  {
+#if 1	
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0a, 0x0b, 0x0c,
+#else	
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x09, 0x0a, 0x0b, 0x0c, 0x10,
+	0x11, 0x12, 0x18, 0x28, 0x31, 0x32, 0x39, 0x3a, 0x40, 0x41, 0x61, 0x62,
+	0x63, 0x64, 0x6b, 0x70, 0x71, 0x78, 0x7a, 0x7b, 0x7f, 0x100, 0x101
+#endif	
+};
+#define RC_NIOPORT	(sizeof(rc_ioport) / sizeof(rc_ioport[0]))
+
+
+static inline int rc_paranoia_check(struct riscom_port const * port,
+				    char *name, const char *routine)
+{
+#ifdef RISCOM_PARANOIA_CHECK
+	static const char badmagic[] = KERN_INFO
+		"rc: Warning: bad riscom port magic number for device %s in %s\n";
+	static const char badinfo[] = KERN_INFO
+		"rc: Warning: null riscom port for device %s in %s\n";
+
+	if (!port) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (port->magic != RISCOM8_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+#endif
+	return 0;
+}
+
+/*
+ * 
+ *  Service functions for RISCom/8 driver.
+ * 
+ */
+
+/* Get board number from pointer */
+static inline int board_No (struct riscom_board const * bp)
+{
+	return bp - rc_board;
+}
+
+/* Get port number from pointer */
+static inline int port_No (struct riscom_port const * port)
+{
+	return RC_PORT(port - rc_port); 
+}
+
+/* Get pointer to board from pointer to port */
+static inline struct riscom_board * port_Board(struct riscom_port const * port)
+{
+	return &rc_board[RC_BOARD(port - rc_port)];
+}
+
+/* Input Byte from CL CD180 register */
+static inline unsigned char rc_in(struct riscom_board const * bp, unsigned short reg)
+{
+	return inb(bp->base + RC_TO_ISA(reg));
+}
+
+/* Output Byte to CL CD180 register */
+static inline void rc_out(struct riscom_board const * bp, unsigned short reg,
+			  unsigned char val)
+{
+	outb(val, bp->base + RC_TO_ISA(reg));
+}
+
+/* Wait for Channel Command Register ready */
+static inline void rc_wait_CCR(struct riscom_board const * bp)
+{
+	unsigned long delay;
+
+	/* FIXME: need something more descriptive then 100000 :) */
+	for (delay = 100000; delay; delay--) 
+		if (!rc_in(bp, CD180_CCR))
+			return;
+	
+	printk(KERN_INFO "rc%d: Timeout waiting for CCR.\n", board_No(bp));
+}
+
+/*
+ *  RISCom/8 probe functions.
+ */
+
+static inline int rc_request_io_range(struct riscom_board * const bp)
+{
+	int i;
+	
+	for (i = 0; i < RC_NIOPORT; i++)  
+		if (!request_region(RC_TO_ISA(rc_ioport[i]) + bp->base, 1,
+				   "RISCom/8"))  {
+			goto out_release;
+		}
+	return 0;
+out_release:
+	printk(KERN_INFO "rc%d: Skipping probe at 0x%03x. IO address in use.\n",
+			 board_No(bp), bp->base);
+	while(--i >= 0)
+		release_region(RC_TO_ISA(rc_ioport[i]) + bp->base, 1);
+	return 1;
+}
+
+static inline void rc_release_io_range(struct riscom_board * const bp)
+{
+	int i;
+	
+	for (i = 0; i < RC_NIOPORT; i++)  
+		release_region(RC_TO_ISA(rc_ioport[i]) + bp->base, 1);
+}
+	
+/* Must be called with enabled interrupts */
+static inline void rc_long_delay(unsigned long delay)
+{
+	unsigned long i;
+	
+	for (i = jiffies + delay; time_after(i,jiffies); ) ;
+}
+
+/* Reset and setup CD180 chip */
+static void __init rc_init_CD180(struct riscom_board const * bp)
+{
+	unsigned long flags;
+	
+	save_flags(flags); cli();
+	rc_out(bp, RC_CTOUT, 0);     	           /* Clear timeout             */
+	rc_wait_CCR(bp);			   /* Wait for CCR ready        */
+	rc_out(bp, CD180_CCR, CCR_HARDRESET);      /* Reset CD180 chip          */
+	sti();
+	rc_long_delay(HZ/20);                      /* Delay 0.05 sec            */
+	cli();
+	rc_out(bp, CD180_GIVR, RC_ID);             /* Set ID for this chip      */
+	rc_out(bp, CD180_GICR, 0);                 /* Clear all bits            */
+	rc_out(bp, CD180_PILR1, RC_ACK_MINT);      /* Prio for modem intr       */
+	rc_out(bp, CD180_PILR2, RC_ACK_TINT);      /* Prio for transmitter intr */
+	rc_out(bp, CD180_PILR3, RC_ACK_RINT);      /* Prio for receiver intr    */
+	
+	/* Setting up prescaler. We need 4 ticks per 1 ms */
+	rc_out(bp, CD180_PPRH, (RC_OSCFREQ/(1000000/RISCOM_TPS)) >> 8);
+	rc_out(bp, CD180_PPRL, (RC_OSCFREQ/(1000000/RISCOM_TPS)) & 0xff);
+	
+	restore_flags(flags);
+}
+
+/* Main probing routine, also sets irq. */
+static int __init rc_probe(struct riscom_board *bp)
+{
+	unsigned char val1, val2;
+	int irqs = 0;
+	int retries;
+	
+	bp->irq = 0;
+
+	if (rc_request_io_range(bp))
+		return 1;
+	
+	/* Are the I/O ports here ? */
+	rc_out(bp, CD180_PPRL, 0x5a);
+	outb(0xff, 0x80);
+	val1 = rc_in(bp, CD180_PPRL);
+	rc_out(bp, CD180_PPRL, 0xa5);
+	outb(0x00, 0x80);
+	val2 = rc_in(bp, CD180_PPRL);
+	
+	if ((val1 != 0x5a) || (val2 != 0xa5))  {
+		printk(KERN_ERR "rc%d: RISCom/8 Board at 0x%03x not found.\n",
+		       board_No(bp), bp->base);
+		goto out_release;
+	}
+	
+	/* It's time to find IRQ for this board */
+	for (retries = 0; retries < 5 && irqs <= 0; retries++)  {
+		irqs = probe_irq_on();
+		rc_init_CD180(bp);	       		/* Reset CD180 chip       */
+		rc_out(bp, CD180_CAR, 2);               /* Select port 2          */
+		rc_wait_CCR(bp);
+		rc_out(bp, CD180_CCR, CCR_TXEN);        /* Enable transmitter     */
+		rc_out(bp, CD180_IER, IER_TXRDY);       /* Enable tx empty intr   */
+		rc_long_delay(HZ/20);	       		
+		irqs = probe_irq_off(irqs);
+		val1 = rc_in(bp, RC_BSR);		/* Get Board Status reg   */
+		val2 = rc_in(bp, RC_ACK_TINT);          /* ACK interrupt          */
+		rc_init_CD180(bp);	       		/* Reset CD180 again      */
+	
+		if ((val1 & RC_BSR_TINT) || (val2 != (RC_ID | GIVR_IT_TX)))  {
+			printk(KERN_ERR "rc%d: RISCom/8 Board at 0x%03x not "
+					"found.\n", board_No(bp), bp->base);
+			goto out_release;
+		}
+	}
+	
+	if (irqs <= 0)  {
+		printk(KERN_ERR "rc%d: Can't find IRQ for RISCom/8 board "
+				"at 0x%03x.\n", board_No(bp), bp->base);
+		goto out_release;
+	}
+	bp->irq = irqs;
+	bp->flags |= RC_BOARD_PRESENT;
+	
+	printk(KERN_INFO "rc%d: RISCom/8 Rev. %c board detected at "
+			 "0x%03x, IRQ %d.\n",
+	       board_No(bp),
+	       (rc_in(bp, CD180_GFRCR) & 0x0f) + 'A',   /* Board revision */
+	       bp->base, bp->irq);
+	
+	return 0;
+out_release:
+	rc_release_io_range(bp);
+	return 1;
+}
+
+/* 
+ * 
+ *  Interrupt processing routines.
+ * 
+ */
+
+static inline void rc_mark_event(struct riscom_port * port, int event)
+{
+	set_bit(event, &port->event);
+	schedule_work(&port->tqueue);
+}
+
+static inline struct riscom_port * rc_get_port(struct riscom_board const * bp,
+					       unsigned char const * what)
+{
+	unsigned char channel;
+	struct riscom_port * port;
+	
+	channel = rc_in(bp, CD180_GICR) >> GICR_CHAN_OFF;
+	if (channel < CD180_NCH)  {
+		port = &rc_port[board_No(bp) * RC_NPORT + channel];
+		if (port->flags & ASYNC_INITIALIZED)  {
+			return port;
+		}
+	}
+	printk(KERN_ERR "rc%d: %s interrupt from invalid port %d\n", 
+	       board_No(bp), what, channel);
+	return NULL;
+}
+
+static inline void rc_receive_exc(struct riscom_board const * bp)
+{
+	struct riscom_port *port;
+	struct tty_struct *tty;
+	unsigned char status;
+	unsigned char ch;
+	
+	if (!(port = rc_get_port(bp, "Receive")))
+		return;
+
+	tty = port->tty;
+	if (tty->flip.count >= TTY_FLIPBUF_SIZE)  {
+		printk(KERN_WARNING "rc%d: port %d: Working around flip "
+				    "buffer overflow.\n",
+		       board_No(bp), port_No(port));
+		return;
+	}
+	
+#ifdef RC_REPORT_OVERRUN	
+	status = rc_in(bp, CD180_RCSR);
+	if (status & RCSR_OE)  {
+		port->overrun++;
+#if 0		
+		printk(KERN_ERR "rc%d: port %d: Overrun. Total %ld overruns\n", 
+		       board_No(bp), port_No(port), port->overrun);
+#endif		
+	}
+	status &= port->mark_mask;
+#else	
+	status = rc_in(bp, CD180_RCSR) & port->mark_mask;
+#endif	
+	ch = rc_in(bp, CD180_RDR);
+	if (!status)  {
+		return;
+	}
+	if (status & RCSR_TOUT)  {
+		printk(KERN_WARNING "rc%d: port %d: Receiver timeout. "
+				    "Hardware problems ?\n", 
+		       board_No(bp), port_No(port));
+		return;
+		
+	} else if (status & RCSR_BREAK)  {
+		printk(KERN_INFO "rc%d: port %d: Handling break...\n",
+		       board_No(bp), port_No(port));
+		*tty->flip.flag_buf_ptr++ = TTY_BREAK;
+		if (port->flags & ASYNC_SAK)
+			do_SAK(tty);
+		
+	} else if (status & RCSR_PE) 
+		*tty->flip.flag_buf_ptr++ = TTY_PARITY;
+	
+	else if (status & RCSR_FE) 
+		*tty->flip.flag_buf_ptr++ = TTY_FRAME;
+	
+        else if (status & RCSR_OE)
+		*tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
+	
+	else
+		*tty->flip.flag_buf_ptr++ = 0;
+	
+	*tty->flip.char_buf_ptr++ = ch;
+	tty->flip.count++;
+	schedule_delayed_work(&tty->flip.work, 1);
+}
+
+static inline void rc_receive(struct riscom_board const * bp)
+{
+	struct riscom_port *port;
+	struct tty_struct *tty;
+	unsigned char count;
+	
+	if (!(port = rc_get_port(bp, "Receive")))
+		return;
+	
+	tty = port->tty;
+	
+	count = rc_in(bp, CD180_RDCR);
+	
+#ifdef RC_REPORT_FIFO
+	port->hits[count > 8 ? 9 : count]++;
+#endif	
+	
+	while (count--)  {
+		if (tty->flip.count >= TTY_FLIPBUF_SIZE)  {
+			printk(KERN_WARNING "rc%d: port %d: Working around "
+					    "flip buffer overflow.\n",
+			       board_No(bp), port_No(port));
+			break;
+		}
+		*tty->flip.char_buf_ptr++ = rc_in(bp, CD180_RDR);
+		*tty->flip.flag_buf_ptr++ = 0;
+		tty->flip.count++;
+	}
+	schedule_delayed_work(&tty->flip.work, 1);
+}
+
+static inline void rc_transmit(struct riscom_board const * bp)
+{
+	struct riscom_port *port;
+	struct tty_struct *tty;
+	unsigned char count;
+	
+	
+	if (!(port = rc_get_port(bp, "Transmit")))
+		return;
+	
+	tty = port->tty;
+	
+	if (port->IER & IER_TXEMPTY)  {
+		/* FIFO drained */
+		rc_out(bp, CD180_CAR, port_No(port));
+		port->IER &= ~IER_TXEMPTY;
+		rc_out(bp, CD180_IER, port->IER);
+		return;
+	}
+	
+	if ((port->xmit_cnt <= 0 && !port->break_length)
+	    || tty->stopped || tty->hw_stopped)  {
+		rc_out(bp, CD180_CAR, port_No(port));
+		port->IER &= ~IER_TXRDY;
+		rc_out(bp, CD180_IER, port->IER);
+		return;
+	}
+	
+	if (port->break_length)  {
+		if (port->break_length > 0)  {
+			if (port->COR2 & COR2_ETC)  {
+				rc_out(bp, CD180_TDR, CD180_C_ESC);
+				rc_out(bp, CD180_TDR, CD180_C_SBRK);
+				port->COR2 &= ~COR2_ETC;
+			}
+			count = min_t(int, port->break_length, 0xff);
+			rc_out(bp, CD180_TDR, CD180_C_ESC);
+			rc_out(bp, CD180_TDR, CD180_C_DELAY);
+			rc_out(bp, CD180_TDR, count);
+			if (!(port->break_length -= count))
+				port->break_length--;
+		} else  {
+			rc_out(bp, CD180_TDR, CD180_C_ESC);
+			rc_out(bp, CD180_TDR, CD180_C_EBRK);
+			rc_out(bp, CD180_COR2, port->COR2);
+			rc_wait_CCR(bp);
+			rc_out(bp, CD180_CCR, CCR_CORCHG2);
+			port->break_length = 0;
+		}
+		return;
+	}
+	
+	count = CD180_NFIFO;
+	do {
+		rc_out(bp, CD180_TDR, port->xmit_buf[port->xmit_tail++]);
+		port->xmit_tail = port->xmit_tail & (SERIAL_XMIT_SIZE-1);
+		if (--port->xmit_cnt <= 0)
+			break;
+	} while (--count > 0);
+	
+	if (port->xmit_cnt <= 0)  {
+		rc_out(bp, CD180_CAR, port_No(port));
+		port->IER &= ~IER_TXRDY;
+		rc_out(bp, CD180_IER, port->IER);
+	}
+	if (port->xmit_cnt <= port->wakeup_chars)
+		rc_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+}
+
+static inline void rc_check_modem(struct riscom_board const * bp)
+{
+	struct riscom_port *port;
+	struct tty_struct *tty;
+	unsigned char mcr;
+	
+	if (!(port = rc_get_port(bp, "Modem")))
+		return;
+	
+	tty = port->tty;
+	
+	mcr = rc_in(bp, CD180_MCR);
+	if (mcr & MCR_CDCHG)  {
+		if (rc_in(bp, CD180_MSVR) & MSVR_CD) 
+			wake_up_interruptible(&port->open_wait);
+		else
+			schedule_work(&port->tqueue_hangup);
+	}
+	
+#ifdef RISCOM_BRAIN_DAMAGED_CTS
+	if (mcr & MCR_CTSCHG)  {
+		if (rc_in(bp, CD180_MSVR) & MSVR_CTS)  {
+			tty->hw_stopped = 0;
+			port->IER |= IER_TXRDY;
+			if (port->xmit_cnt <= port->wakeup_chars)
+				rc_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+		} else  {
+			tty->hw_stopped = 1;
+			port->IER &= ~IER_TXRDY;
+		}
+		rc_out(bp, CD180_IER, port->IER);
+	}
+	if (mcr & MCR_DSRCHG)  {
+		if (rc_in(bp, CD180_MSVR) & MSVR_DSR)  {
+			tty->hw_stopped = 0;
+			port->IER |= IER_TXRDY;
+			if (port->xmit_cnt <= port->wakeup_chars)
+				rc_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+		} else  {
+			tty->hw_stopped = 1;
+			port->IER &= ~IER_TXRDY;
+		}
+		rc_out(bp, CD180_IER, port->IER);
+	}
+#endif /* RISCOM_BRAIN_DAMAGED_CTS */
+	
+	/* Clear change bits */
+	rc_out(bp, CD180_MCR, 0);
+}
+
+/* The main interrupt processing routine */
+static irqreturn_t rc_interrupt(int irq, void * dev_id, struct pt_regs * regs)
+{
+	unsigned char status;
+	unsigned char ack;
+	struct riscom_board *bp;
+	unsigned long loop = 0;
+	int handled = 0;
+
+	bp = IRQ_to_board[irq];
+	
+	if (!bp || !(bp->flags & RC_BOARD_ACTIVE))  {
+		return IRQ_NONE;
+	}
+	
+	while ((++loop < 16) && ((status = ~(rc_in(bp, RC_BSR))) &
+				 (RC_BSR_TOUT | RC_BSR_TINT |
+				  RC_BSR_MINT | RC_BSR_RINT))) {
+		handled = 1;
+		if (status & RC_BSR_TOUT) 
+			printk(KERN_WARNING "rc%d: Got timeout. Hardware "
+					    "error?\n", board_No(bp));
+		
+		else if (status & RC_BSR_RINT) {
+			ack = rc_in(bp, RC_ACK_RINT);
+		
+			if (ack == (RC_ID | GIVR_IT_RCV))
+				rc_receive(bp);
+			else if (ack == (RC_ID | GIVR_IT_REXC))
+				rc_receive_exc(bp);
+			else
+				printk(KERN_WARNING "rc%d: Bad receive ack "
+						    "0x%02x.\n",
+				       board_No(bp), ack);
+		
+		} else if (status & RC_BSR_TINT) {
+			ack = rc_in(bp, RC_ACK_TINT);
+		
+			if (ack == (RC_ID | GIVR_IT_TX))
+				rc_transmit(bp);
+			else
+				printk(KERN_WARNING "rc%d: Bad transmit ack "
+						    "0x%02x.\n",
+				       board_No(bp), ack);
+		
+		} else /* if (status & RC_BSR_MINT) */ {
+			ack = rc_in(bp, RC_ACK_MINT);
+		
+			if (ack == (RC_ID | GIVR_IT_MODEM)) 
+				rc_check_modem(bp);
+			else
+				printk(KERN_WARNING "rc%d: Bad modem ack "
+						    "0x%02x.\n",
+				       board_No(bp), ack);
+		
+		} 
+
+		rc_out(bp, CD180_EOIR, 0);   /* Mark end of interrupt */
+		rc_out(bp, RC_CTOUT, 0);     /* Clear timeout flag    */
+	}
+	return IRQ_RETVAL(handled);
+}
+
+/*
+ *  Routines for open & close processing.
+ */
+
+/* Called with disabled interrupts */
+static inline int rc_setup_board(struct riscom_board * bp)
+{
+	int error;
+
+	if (bp->flags & RC_BOARD_ACTIVE) 
+		return 0;
+	
+	error = request_irq(bp->irq, rc_interrupt, SA_INTERRUPT,
+			    "RISCom/8", NULL);
+	if (error) 
+		return error;
+	
+	rc_out(bp, RC_CTOUT, 0);       		/* Just in case         */
+	bp->DTR = ~0;
+	rc_out(bp, RC_DTR, bp->DTR);	        /* Drop DTR on all ports */
+	
+	IRQ_to_board[bp->irq] = bp;
+	bp->flags |= RC_BOARD_ACTIVE;
+	
+	return 0;
+}
+
+/* Called with disabled interrupts */
+static inline void rc_shutdown_board(struct riscom_board *bp)
+{
+	if (!(bp->flags & RC_BOARD_ACTIVE))
+		return;
+	
+	bp->flags &= ~RC_BOARD_ACTIVE;
+	
+	free_irq(bp->irq, NULL);
+	IRQ_to_board[bp->irq] = NULL;
+	
+	bp->DTR = ~0;
+	rc_out(bp, RC_DTR, bp->DTR);	       /* Drop DTR on all ports */
+	
+}
+
+/*
+ * Setting up port characteristics. 
+ * Must be called with disabled interrupts
+ */
+static void rc_change_speed(struct riscom_board *bp, struct riscom_port *port)
+{
+	struct tty_struct *tty;
+	unsigned long baud;
+	long tmp;
+	unsigned char cor1 = 0, cor3 = 0;
+	unsigned char mcor1 = 0, mcor2 = 0;
+	
+	if (!(tty = port->tty) || !tty->termios)
+		return;
+
+	port->IER  = 0;
+	port->COR2 = 0;
+	port->MSVR = MSVR_RTS;
+	
+	baud = C_BAUD(tty);
+	
+	if (baud & CBAUDEX) {
+		baud &= ~CBAUDEX;
+		if (baud < 1 || baud > 2) 
+			port->tty->termios->c_cflag &= ~CBAUDEX;
+		else
+			baud += 15;
+	}
+	if (baud == 15)  {
+		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			baud ++;
+		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			baud += 2;
+	}
+	
+	/* Select port on the board */
+	rc_out(bp, CD180_CAR, port_No(port));
+	
+	if (!baud_table[baud])  {
+		/* Drop DTR & exit */
+		bp->DTR |= (1u << port_No(port));
+		rc_out(bp, RC_DTR, bp->DTR);
+		return;
+	} else  {
+		/* Set DTR on */
+		bp->DTR &= ~(1u << port_No(port));
+		rc_out(bp, RC_DTR, bp->DTR);
+	}
+	
+	/*
+	 * Now we must calculate some speed depended things 
+	 */
+	
+	/* Set baud rate for port */
+	tmp = (((RC_OSCFREQ + baud_table[baud]/2) / baud_table[baud] +
+		CD180_TPC/2) / CD180_TPC);
+
+	rc_out(bp, CD180_RBPRH, (tmp >> 8) & 0xff); 
+	rc_out(bp, CD180_TBPRH, (tmp >> 8) & 0xff); 
+	rc_out(bp, CD180_RBPRL, tmp & 0xff); 
+	rc_out(bp, CD180_TBPRL, tmp & 0xff);
+	
+	baud = (baud_table[baud] + 5) / 10;   /* Estimated CPS */
+	
+	/* Two timer ticks seems enough to wakeup something like SLIP driver */
+	tmp = ((baud + HZ/2) / HZ) * 2 - CD180_NFIFO;		
+	port->wakeup_chars = (tmp < 0) ? 0 : ((tmp >= SERIAL_XMIT_SIZE) ?
+					      SERIAL_XMIT_SIZE - 1 : tmp);
+	
+	/* Receiver timeout will be transmission time for 1.5 chars */
+	tmp = (RISCOM_TPS + RISCOM_TPS/2 + baud/2) / baud;
+	tmp = (tmp > 0xff) ? 0xff : tmp;
+	rc_out(bp, CD180_RTPR, tmp);
+	
+	switch (C_CSIZE(tty))  {
+	 case CS5:
+		cor1 |= COR1_5BITS;
+		break;
+	 case CS6:
+		cor1 |= COR1_6BITS;
+		break;
+	 case CS7:
+		cor1 |= COR1_7BITS;
+		break;
+	 case CS8:
+		cor1 |= COR1_8BITS;
+		break;
+	}
+	
+	if (C_CSTOPB(tty)) 
+		cor1 |= COR1_2SB;
+	
+	cor1 |= COR1_IGNORE;
+	if (C_PARENB(tty))  {
+		cor1 |= COR1_NORMPAR;
+		if (C_PARODD(tty)) 
+			cor1 |= COR1_ODDP;
+		if (I_INPCK(tty)) 
+			cor1 &= ~COR1_IGNORE;
+	}
+	/* Set marking of some errors */
+	port->mark_mask = RCSR_OE | RCSR_TOUT;
+	if (I_INPCK(tty)) 
+		port->mark_mask |= RCSR_FE | RCSR_PE;
+	if (I_BRKINT(tty) || I_PARMRK(tty)) 
+		port->mark_mask |= RCSR_BREAK;
+	if (I_IGNPAR(tty)) 
+		port->mark_mask &= ~(RCSR_FE | RCSR_PE);
+	if (I_IGNBRK(tty))  {
+		port->mark_mask &= ~RCSR_BREAK;
+		if (I_IGNPAR(tty)) 
+			/* Real raw mode. Ignore all */
+			port->mark_mask &= ~RCSR_OE;
+	}
+	/* Enable Hardware Flow Control */
+	if (C_CRTSCTS(tty))  {
+#ifdef RISCOM_BRAIN_DAMAGED_CTS
+		port->IER |= IER_DSR | IER_CTS;
+		mcor1 |= MCOR1_DSRZD | MCOR1_CTSZD;
+		mcor2 |= MCOR2_DSROD | MCOR2_CTSOD;
+		tty->hw_stopped = !(rc_in(bp, CD180_MSVR) & (MSVR_CTS|MSVR_DSR));
+#else
+		port->COR2 |= COR2_CTSAE;
+#endif
+	}
+	/* Enable Software Flow Control. FIXME: I'm not sure about this */
+	/* Some people reported that it works, but I still doubt */
+	if (I_IXON(tty))  {
+		port->COR2 |= COR2_TXIBE;
+		cor3 |= (COR3_FCT | COR3_SCDE);
+		if (I_IXANY(tty))
+			port->COR2 |= COR2_IXM;
+		rc_out(bp, CD180_SCHR1, START_CHAR(tty));
+		rc_out(bp, CD180_SCHR2, STOP_CHAR(tty));
+		rc_out(bp, CD180_SCHR3, START_CHAR(tty));
+		rc_out(bp, CD180_SCHR4, STOP_CHAR(tty));
+	}
+	if (!C_CLOCAL(tty))  {
+		/* Enable CD check */
+		port->IER |= IER_CD;
+		mcor1 |= MCOR1_CDZD;
+		mcor2 |= MCOR2_CDOD;
+	}
+	
+	if (C_CREAD(tty)) 
+		/* Enable receiver */
+		port->IER |= IER_RXD;
+	
+	/* Set input FIFO size (1-8 bytes) */
+	cor3 |= RISCOM_RXFIFO; 
+	/* Setting up CD180 channel registers */
+	rc_out(bp, CD180_COR1, cor1);
+	rc_out(bp, CD180_COR2, port->COR2);
+	rc_out(bp, CD180_COR3, cor3);
+	/* Make CD180 know about registers change */
+	rc_wait_CCR(bp);
+	rc_out(bp, CD180_CCR, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3);
+	/* Setting up modem option registers */
+	rc_out(bp, CD180_MCOR1, mcor1);
+	rc_out(bp, CD180_MCOR2, mcor2);
+	/* Enable CD180 transmitter & receiver */
+	rc_wait_CCR(bp);
+	rc_out(bp, CD180_CCR, CCR_TXEN | CCR_RXEN);
+	/* Enable interrupts */
+	rc_out(bp, CD180_IER, port->IER);
+	/* And finally set RTS on */
+	rc_out(bp, CD180_MSVR, port->MSVR);
+}
+
+/* Must be called with interrupts enabled */
+static int rc_setup_port(struct riscom_board *bp, struct riscom_port *port)
+{
+	unsigned long flags;
+	
+	if (port->flags & ASYNC_INITIALIZED)
+		return 0;
+	
+	if (!port->xmit_buf) {
+		/* We may sleep in get_zeroed_page() */
+		unsigned long tmp;
+		
+		if (!(tmp = get_zeroed_page(GFP_KERNEL)))
+			return -ENOMEM;
+		    
+		if (port->xmit_buf) {
+			free_page(tmp);
+			return -ERESTARTSYS;
+		}
+		port->xmit_buf = (unsigned char *) tmp;
+	}
+		
+	save_flags(flags); cli();
+		
+	if (port->tty) 
+		clear_bit(TTY_IO_ERROR, &port->tty->flags);
+		
+	if (port->count == 1) 
+		bp->count++;
+		
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	rc_change_speed(bp, port);
+	port->flags |= ASYNC_INITIALIZED;
+		
+	restore_flags(flags);
+	return 0;
+}
+
+/* Must be called with interrupts disabled */
+static void rc_shutdown_port(struct riscom_board *bp, struct riscom_port *port)
+{
+	struct tty_struct *tty;
+	
+	if (!(port->flags & ASYNC_INITIALIZED)) 
+		return;
+	
+#ifdef RC_REPORT_OVERRUN
+	printk(KERN_INFO "rc%d: port %d: Total %ld overruns were detected.\n",
+	       board_No(bp), port_No(port), port->overrun);
+#endif	
+#ifdef RC_REPORT_FIFO
+	{
+		int i;
+		
+		printk(KERN_INFO "rc%d: port %d: FIFO hits [ ",
+		       board_No(bp), port_No(port));
+		for (i = 0; i < 10; i++)  {
+			printk("%ld ", port->hits[i]);
+		}
+		printk("].\n");
+	}
+#endif	
+	if (port->xmit_buf)  {
+		free_page((unsigned long) port->xmit_buf);
+		port->xmit_buf = NULL;
+	}
+
+	if (!(tty = port->tty) || C_HUPCL(tty))  {
+		/* Drop DTR */
+		bp->DTR |= (1u << port_No(port));
+		rc_out(bp, RC_DTR, bp->DTR);
+	}
+	
+        /* Select port */
+	rc_out(bp, CD180_CAR, port_No(port));
+	/* Reset port */
+	rc_wait_CCR(bp);
+	rc_out(bp, CD180_CCR, CCR_SOFTRESET);
+	/* Disable all interrupts from this port */
+	port->IER = 0;
+	rc_out(bp, CD180_IER, port->IER);
+	
+	if (tty)  
+		set_bit(TTY_IO_ERROR, &tty->flags);
+	port->flags &= ~ASYNC_INITIALIZED;
+	
+	if (--bp->count < 0)  {
+		printk(KERN_INFO "rc%d: rc_shutdown_port: "
+				 "bad board count: %d\n",
+		       board_No(bp), bp->count);
+		bp->count = 0;
+	}
+	
+	/*
+	 * If this is the last opened port on the board
+	 * shutdown whole board
+	 */
+	if (!bp->count) 
+		rc_shutdown_board(bp);
+}
+
+	
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+			   struct riscom_port *port)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	struct riscom_board *bp = port_Board(port);
+	int    retval;
+	int    do_clocal = 0;
+	int    CD;
+
+	/*
+	 * 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 & ASYNC_CLOSING) {
+		interruptible_sleep_on(&port->close_wait);
+		if (port->flags & ASYNC_HUP_NOTIFY)
+			return -EAGAIN;
+		else
+			return -ERESTARTSYS;
+	}
+
+	/*
+	 * 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))) {
+		port->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	if (C_CLOCAL(tty))  
+		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, info->count is dropped by one, so that
+	 * rs_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	retval = 0;
+	add_wait_queue(&port->open_wait, &wait);
+	cli();
+	if (!tty_hung_up_p(filp))
+		port->count--;
+	sti();
+	port->blocked_open++;
+	while (1) {
+		cli();
+		rc_out(bp, CD180_CAR, port_No(port));
+		CD = rc_in(bp, CD180_MSVR) & MSVR_CD;
+		rc_out(bp, CD180_MSVR, MSVR_RTS);
+		bp->DTR &= ~(1u << port_No(port));
+		rc_out(bp, RC_DTR, bp->DTR);
+		sti();
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) ||
+		    !(port->flags & ASYNC_INITIALIZED)) {
+			if (port->flags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;	
+			break;
+		}
+		if (!(port->flags & ASYNC_CLOSING) &&
+		    (do_clocal || CD))
+			break;
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&port->open_wait, &wait);
+	if (!tty_hung_up_p(filp))
+		port->count++;
+	port->blocked_open--;
+	if (retval)
+		return retval;
+	
+	port->flags |= ASYNC_NORMAL_ACTIVE;
+	return 0;
+}	
+
+static int rc_open(struct tty_struct * tty, struct file * filp)
+{
+	int board;
+	int error;
+	struct riscom_port * port;
+	struct riscom_board * bp;
+	
+	board = RC_BOARD(tty->index);
+	if (board >= RC_NBOARD || !(rc_board[board].flags & RC_BOARD_PRESENT))
+		return -ENODEV;
+	
+	bp = &rc_board[board];
+	port = rc_port + board * RC_NPORT + RC_PORT(tty->index);
+	if (rc_paranoia_check(port, tty->name, "rc_open"))
+		return -ENODEV;
+	
+	if ((error = rc_setup_board(bp))) 
+		return error;
+		
+	port->count++;
+	tty->driver_data = port;
+	port->tty = tty;
+	
+	if ((error = rc_setup_port(bp, port))) 
+		return error;
+	
+	if ((error = block_til_ready(tty, filp, port)))
+		return error;
+	
+	return 0;
+}
+
+static void rc_close(struct tty_struct * tty, struct file * filp)
+{
+	struct riscom_port *port = (struct riscom_port *) tty->driver_data;
+	struct riscom_board *bp;
+	unsigned long flags;
+	unsigned long timeout;
+	
+	if (!port || rc_paranoia_check(port, tty->name, "close"))
+		return;
+	
+	save_flags(flags); cli();
+	if (tty_hung_up_p(filp))
+		goto out;
+	
+	bp = port_Board(port);
+	if ((tty->count == 1) && (port->count != 1))  {
+		printk(KERN_INFO "rc%d: rc_close: bad port count;"
+		       " tty->count is 1, port count is %d\n",
+		       board_No(bp), port->count);
+		port->count = 1;
+	}
+	if (--port->count < 0)  {
+		printk(KERN_INFO "rc%d: rc_close: bad port count "
+				 "for tty%d: %d\n",
+		       board_No(bp), port_No(port), port->count);
+		port->count = 0;
+	}
+	if (port->count)
+		goto out;
+	port->flags |= ASYNC_CLOSING;
+	/*
+	 * Now we wait for the transmit buffer to clear; and we notify 
+	 * the line discipline to only process XON/XOFF characters.
+	 */
+	tty->closing = 1;
+	if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, port->closing_wait);
+	/*
+	 * At this point we stop accepting input.  To do this, we
+	 * disable the receive line status interrupts, and tell the
+	 * interrupt driver to stop checking the data ready bit in the
+	 * line status register.
+	 */
+	port->IER &= ~IER_RXD;
+	if (port->flags & ASYNC_INITIALIZED) {
+		port->IER &= ~IER_TXRDY;
+		port->IER |= IER_TXEMPTY;
+		rc_out(bp, CD180_CAR, port_No(port));
+		rc_out(bp, CD180_IER, port->IER);
+		/*
+		 * Before we drop DTR, make sure the UART transmitter
+		 * has completely drained; this is especially
+		 * important if there is a transmit FIFO!
+		 */
+		timeout = jiffies+HZ;
+		while(port->IER & IER_TXEMPTY)  {
+			msleep_interruptible(jiffies_to_msecs(port->timeout));
+			if (time_after(jiffies, timeout))
+				break;
+		}
+	}
+	rc_shutdown_port(bp, port);
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+	tty_ldisc_flush(tty);
+
+	tty->closing = 0;
+	port->event = 0;
+	port->tty = NULL;
+	if (port->blocked_open) {
+		if (port->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(port->close_delay));
+		}
+		wake_up_interruptible(&port->open_wait);
+	}
+	port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+	wake_up_interruptible(&port->close_wait);
+out:	restore_flags(flags);
+}
+
+static int rc_write(struct tty_struct * tty, 
+		    const unsigned char *buf, int count)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	struct riscom_board *bp;
+	int c, total = 0;
+	unsigned long flags;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_write"))
+		return 0;
+	
+	bp = port_Board(port);
+
+	if (!tty || !port->xmit_buf || !tmp_buf)
+		return 0;
+
+	save_flags(flags);
+	while (1) {
+		cli();		
+		c = min_t(int, count, min(SERIAL_XMIT_SIZE - port->xmit_cnt - 1,
+					  SERIAL_XMIT_SIZE - port->xmit_head));
+		if (c <= 0) {
+			restore_flags(flags);
+			break;
+		}
+
+		memcpy(port->xmit_buf + port->xmit_head, buf, c);
+		port->xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+		port->xmit_cnt += c;
+		restore_flags(flags);
+
+		buf += c;
+		count -= c;
+		total += c;
+	}
+
+	cli();
+	if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped &&
+	    !(port->IER & IER_TXRDY)) {
+		port->IER |= IER_TXRDY;
+		rc_out(bp, CD180_CAR, port_No(port));
+		rc_out(bp, CD180_IER, port->IER);
+	}
+	restore_flags(flags);
+
+	return total;
+}
+
+static void rc_put_char(struct tty_struct * tty, unsigned char ch)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	unsigned long flags;
+
+	if (rc_paranoia_check(port, tty->name, "rc_put_char"))
+		return;
+
+	if (!tty || !port->xmit_buf)
+		return;
+
+	save_flags(flags); cli();
+	
+	if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1)
+		goto out;
+
+	port->xmit_buf[port->xmit_head++] = ch;
+	port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+	port->xmit_cnt++;
+out:	restore_flags(flags);
+}
+
+static void rc_flush_chars(struct tty_struct * tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	unsigned long flags;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_flush_chars"))
+		return;
+	
+	if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+	    !port->xmit_buf)
+		return;
+
+	save_flags(flags); cli();
+	port->IER |= IER_TXRDY;
+	rc_out(port_Board(port), CD180_CAR, port_No(port));
+	rc_out(port_Board(port), CD180_IER, port->IER);
+	restore_flags(flags);
+}
+
+static int rc_write_room(struct tty_struct * tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	int	ret;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_write_room"))
+		return 0;
+
+	ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+	return ret;
+}
+
+static int rc_chars_in_buffer(struct tty_struct *tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_chars_in_buffer"))
+		return 0;
+	
+	return port->xmit_cnt;
+}
+
+static void rc_flush_buffer(struct tty_struct *tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	unsigned long flags;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_flush_buffer"))
+		return;
+
+	save_flags(flags); cli();
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	restore_flags(flags);
+	
+	wake_up_interruptible(&tty->write_wait);
+	tty_wakeup(tty);
+}
+
+static int rc_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	struct riscom_board * bp;
+	unsigned char status;
+	unsigned int result;
+	unsigned long flags;
+
+	if (rc_paranoia_check(port, tty->name, __FUNCTION__))
+		return -ENODEV;
+
+	bp = port_Board(port);
+	save_flags(flags); cli();
+	rc_out(bp, CD180_CAR, port_No(port));
+	status = rc_in(bp, CD180_MSVR);
+	result = rc_in(bp, RC_RI) & (1u << port_No(port)) ? 0 : TIOCM_RNG;
+	restore_flags(flags);
+	result |= ((status & MSVR_RTS) ? TIOCM_RTS : 0)
+		| ((status & MSVR_DTR) ? TIOCM_DTR : 0)
+		| ((status & MSVR_CD)  ? TIOCM_CAR : 0)
+		| ((status & MSVR_DSR) ? TIOCM_DSR : 0)
+		| ((status & MSVR_CTS) ? TIOCM_CTS : 0);
+	return result;
+}
+
+static int rc_tiocmset(struct tty_struct *tty, struct file *file,
+		       unsigned int set, unsigned int clear)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	unsigned long flags;
+	struct riscom_board *bp;
+
+	if (rc_paranoia_check(port, tty->name, __FUNCTION__))
+		return -ENODEV;
+
+	bp = port_Board(port);
+
+	save_flags(flags); cli();
+	if (set & TIOCM_RTS)
+		port->MSVR |= MSVR_RTS;
+	if (set & TIOCM_DTR)
+		bp->DTR &= ~(1u << port_No(port));
+
+	if (clear & TIOCM_RTS)
+		port->MSVR &= ~MSVR_RTS;
+	if (clear & TIOCM_DTR)
+		bp->DTR |= (1u << port_No(port));
+
+	rc_out(bp, CD180_CAR, port_No(port));
+	rc_out(bp, CD180_MSVR, port->MSVR);
+	rc_out(bp, RC_DTR, bp->DTR);
+	restore_flags(flags);
+	return 0;
+}
+
+static inline void rc_send_break(struct riscom_port * port, unsigned long length)
+{
+	struct riscom_board *bp = port_Board(port);
+	unsigned long flags;
+	
+	save_flags(flags); cli();
+	port->break_length = RISCOM_TPS / HZ * length;
+	port->COR2 |= COR2_ETC;
+	port->IER  |= IER_TXRDY;
+	rc_out(bp, CD180_CAR, port_No(port));
+	rc_out(bp, CD180_COR2, port->COR2);
+	rc_out(bp, CD180_IER, port->IER);
+	rc_wait_CCR(bp);
+	rc_out(bp, CD180_CCR, CCR_CORCHG2);
+	rc_wait_CCR(bp);
+	restore_flags(flags);
+}
+
+static inline int rc_set_serial_info(struct riscom_port * port,
+				     struct serial_struct __user * newinfo)
+{
+	struct serial_struct tmp;
+	struct riscom_board *bp = port_Board(port);
+	int change_speed;
+	unsigned long flags;
+	
+	if (copy_from_user(&tmp, newinfo, sizeof(tmp)))
+		return -EFAULT;
+	
+#if 0	
+	if ((tmp.irq != bp->irq) ||
+	    (tmp.port != bp->base) ||
+	    (tmp.type != PORT_CIRRUS) ||
+	    (tmp.baud_base != (RC_OSCFREQ + CD180_TPC/2) / CD180_TPC) ||
+	    (tmp.custom_divisor != 0) ||
+	    (tmp.xmit_fifo_size != CD180_NFIFO) ||
+	    (tmp.flags & ~RISCOM_LEGAL_FLAGS))
+		return -EINVAL;
+#endif	
+	
+	change_speed = ((port->flags & ASYNC_SPD_MASK) !=
+			(tmp.flags & ASYNC_SPD_MASK));
+	
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((tmp.close_delay != port->close_delay) ||
+		    (tmp.closing_wait != port->closing_wait) ||
+		    ((tmp.flags & ~ASYNC_USR_MASK) !=
+		     (port->flags & ~ASYNC_USR_MASK)))  
+			return -EPERM;
+		port->flags = ((port->flags & ~ASYNC_USR_MASK) |
+			       (tmp.flags & ASYNC_USR_MASK));
+	} else  {
+		port->flags = ((port->flags & ~ASYNC_FLAGS) |
+			       (tmp.flags & ASYNC_FLAGS));
+		port->close_delay = tmp.close_delay;
+		port->closing_wait = tmp.closing_wait;
+	}
+	if (change_speed)  {
+		save_flags(flags); cli();
+		rc_change_speed(bp, port);
+		restore_flags(flags);
+	}
+	return 0;
+}
+
+static inline int rc_get_serial_info(struct riscom_port * port,
+				     struct serial_struct __user *retinfo)
+{
+	struct serial_struct tmp;
+	struct riscom_board *bp = port_Board(port);
+	
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.type = PORT_CIRRUS;
+	tmp.line = port - rc_port;
+	tmp.port = bp->base;
+	tmp.irq  = bp->irq;
+	tmp.flags = port->flags;
+	tmp.baud_base = (RC_OSCFREQ + CD180_TPC/2) / CD180_TPC;
+	tmp.close_delay = port->close_delay * HZ/100;
+	tmp.closing_wait = port->closing_wait * HZ/100;
+	tmp.xmit_fifo_size = CD180_NFIFO;
+	return copy_to_user(retinfo, &tmp, sizeof(tmp)) ? -EFAULT : 0;
+}
+
+static int rc_ioctl(struct tty_struct * tty, struct file * filp, 
+		    unsigned int cmd, unsigned long arg)
+		    
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	void __user *argp = (void __user *)arg;
+	int retval;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_ioctl"))
+		return -ENODEV;
+	
+	switch (cmd) {
+	 case TCSBRK:	/* SVID version: non-zero arg --> no break */
+		retval = tty_check_change(tty);
+		if (retval)
+			return retval;
+		tty_wait_until_sent(tty, 0);
+		if (!arg)
+			rc_send_break(port, HZ/4);	/* 1/4 second */
+		break;
+	 case TCSBRKP:	/* support for POSIX tcsendbreak() */
+		retval = tty_check_change(tty);
+		if (retval)
+			return retval;
+		tty_wait_until_sent(tty, 0);
+		rc_send_break(port, arg ? arg*(HZ/10) : HZ/4);
+		break;
+	 case TIOCGSOFTCAR:
+		return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned __user *)argp);
+	 case TIOCSSOFTCAR:
+		if (get_user(arg,(unsigned __user *) argp))
+			return -EFAULT;
+		tty->termios->c_cflag =
+			((tty->termios->c_cflag & ~CLOCAL) |
+			(arg ? CLOCAL : 0));
+		break;
+	 case TIOCGSERIAL:	
+		return rc_get_serial_info(port, argp);
+	 case TIOCSSERIAL:	
+		return rc_set_serial_info(port, argp);
+	 default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static void rc_throttle(struct tty_struct * tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	struct riscom_board *bp;
+	unsigned long flags;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_throttle"))
+		return;
+	
+	bp = port_Board(port);
+	
+	save_flags(flags); cli();
+	port->MSVR &= ~MSVR_RTS;
+	rc_out(bp, CD180_CAR, port_No(port));
+	if (I_IXOFF(tty))  {
+		rc_wait_CCR(bp);
+		rc_out(bp, CD180_CCR, CCR_SSCH2);
+		rc_wait_CCR(bp);
+	}
+	rc_out(bp, CD180_MSVR, port->MSVR);
+	restore_flags(flags);
+}
+
+static void rc_unthrottle(struct tty_struct * tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	struct riscom_board *bp;
+	unsigned long flags;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_unthrottle"))
+		return;
+	
+	bp = port_Board(port);
+	
+	save_flags(flags); cli();
+	port->MSVR |= MSVR_RTS;
+	rc_out(bp, CD180_CAR, port_No(port));
+	if (I_IXOFF(tty))  {
+		rc_wait_CCR(bp);
+		rc_out(bp, CD180_CCR, CCR_SSCH1);
+		rc_wait_CCR(bp);
+	}
+	rc_out(bp, CD180_MSVR, port->MSVR);
+	restore_flags(flags);
+}
+
+static void rc_stop(struct tty_struct * tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	struct riscom_board *bp;
+	unsigned long flags;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_stop"))
+		return;
+	
+	bp = port_Board(port);
+	
+	save_flags(flags); cli();
+	port->IER &= ~IER_TXRDY;
+	rc_out(bp, CD180_CAR, port_No(port));
+	rc_out(bp, CD180_IER, port->IER);
+	restore_flags(flags);
+}
+
+static void rc_start(struct tty_struct * tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	struct riscom_board *bp;
+	unsigned long flags;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_start"))
+		return;
+	
+	bp = port_Board(port);
+	
+	save_flags(flags); cli();
+	if (port->xmit_cnt && port->xmit_buf && !(port->IER & IER_TXRDY))  {
+		port->IER |= IER_TXRDY;
+		rc_out(bp, CD180_CAR, port_No(port));
+		rc_out(bp, CD180_IER, port->IER);
+	}
+	restore_flags(flags);
+}
+
+/*
+ * This routine is called from the work queue when the interrupt
+ * routine has signalled that a hangup has occurred.  The path of
+ * hangup processing is:
+ *
+ * 	serial interrupt routine -> (workqueue) ->
+ * 	do_rc_hangup() -> tty->hangup() -> rc_hangup()
+ * 
+ */
+static void do_rc_hangup(void *private_)
+{
+	struct riscom_port	*port = (struct riscom_port *) private_;
+	struct tty_struct	*tty;
+	
+	tty = port->tty;
+	if (tty)
+		tty_hangup(tty);	/* FIXME: module removal race still here */
+}
+
+static void rc_hangup(struct tty_struct * tty)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	struct riscom_board *bp;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_hangup"))
+		return;
+	
+	bp = port_Board(port);
+	
+	rc_shutdown_port(bp, port);
+	port->event = 0;
+	port->count = 0;
+	port->flags &= ~ASYNC_NORMAL_ACTIVE;
+	port->tty = NULL;
+	wake_up_interruptible(&port->open_wait);
+}
+
+static void rc_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+	struct riscom_port *port = (struct riscom_port *)tty->driver_data;
+	unsigned long flags;
+				
+	if (rc_paranoia_check(port, tty->name, "rc_set_termios"))
+		return;
+	
+	if (tty->termios->c_cflag == old_termios->c_cflag &&
+	    tty->termios->c_iflag == old_termios->c_iflag)
+		return;
+
+	save_flags(flags); cli();
+	rc_change_speed(port_Board(port), port);
+	restore_flags(flags);
+
+	if ((old_termios->c_cflag & CRTSCTS) &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {
+		tty->hw_stopped = 0;
+		rc_start(tty);
+	}
+}
+
+static void do_softint(void *private_)
+{
+	struct riscom_port	*port = (struct riscom_port *) private_;
+	struct tty_struct	*tty;
+	
+	if(!(tty = port->tty)) 
+		return;
+
+	if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &port->event)) {
+		tty_wakeup(tty);
+		wake_up_interruptible(&tty->write_wait);
+	}
+}
+
+static struct tty_operations riscom_ops = {
+	.open  = rc_open,
+	.close = rc_close,
+	.write = rc_write,
+	.put_char = rc_put_char,
+	.flush_chars = rc_flush_chars,
+	.write_room = rc_write_room,
+	.chars_in_buffer = rc_chars_in_buffer,
+	.flush_buffer = rc_flush_buffer,
+	.ioctl = rc_ioctl,
+	.throttle = rc_throttle,
+	.unthrottle = rc_unthrottle,
+	.set_termios = rc_set_termios,
+	.stop = rc_stop,
+	.start = rc_start,
+	.hangup = rc_hangup,
+	.tiocmget = rc_tiocmget,
+	.tiocmset = rc_tiocmset,
+};
+
+static inline int rc_init_drivers(void)
+{
+	int error;
+	int i;
+
+	riscom_driver = alloc_tty_driver(RC_NBOARD * RC_NPORT);
+	if (!riscom_driver)	
+		return -ENOMEM;
+	
+	if (!(tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL))) {
+		printk(KERN_ERR "rc: Couldn't get free page.\n");
+		put_tty_driver(riscom_driver);
+		return 1;
+	}
+	memset(IRQ_to_board, 0, sizeof(IRQ_to_board));
+	riscom_driver->owner = THIS_MODULE;
+	riscom_driver->name = "ttyL";
+	riscom_driver->devfs_name = "tts/L";
+	riscom_driver->major = RISCOM8_NORMAL_MAJOR;
+	riscom_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	riscom_driver->subtype = SERIAL_TYPE_NORMAL;
+	riscom_driver->init_termios = tty_std_termios;
+	riscom_driver->init_termios.c_cflag =
+		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	riscom_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(riscom_driver, &riscom_ops);
+	if ((error = tty_register_driver(riscom_driver)))  {
+		free_page((unsigned long)tmp_buf);
+		put_tty_driver(riscom_driver);
+		printk(KERN_ERR "rc: Couldn't register RISCom/8 driver, "
+				"error = %d\n",
+		       error);
+		return 1;
+	}
+
+	memset(rc_port, 0, sizeof(rc_port));
+	for (i = 0; i < RC_NPORT * RC_NBOARD; i++)  {
+		rc_port[i].magic = RISCOM8_MAGIC;
+		INIT_WORK(&rc_port[i].tqueue, do_softint, &rc_port[i]);
+		INIT_WORK(&rc_port[i].tqueue_hangup, do_rc_hangup, &rc_port[i]);
+		rc_port[i].close_delay = 50 * HZ/100;
+		rc_port[i].closing_wait = 3000 * HZ/100;
+		init_waitqueue_head(&rc_port[i].open_wait);
+		init_waitqueue_head(&rc_port[i].close_wait);
+	}
+	
+	return 0;
+}
+
+static void rc_release_drivers(void)
+{
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	free_page((unsigned long)tmp_buf);
+	tty_unregister_driver(riscom_driver);
+	put_tty_driver(riscom_driver);
+	restore_flags(flags);
+}
+
+#ifndef MODULE
+/*
+ * Called at boot time.
+ * 
+ * You can specify IO base for up to RC_NBOARD cards,
+ * using line "riscom8=0xiobase1,0xiobase2,.." at LILO prompt.
+ * Note that there will be no probing at default
+ * addresses in this case.
+ *
+ */ 
+static int __init riscom8_setup(char *str)
+{
+	int ints[RC_NBOARD];
+	int i;
+
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	for (i = 0; i < RC_NBOARD; i++) {
+		if (i < ints[0])
+			rc_board[i].base = ints[i+1];
+		else 
+			rc_board[i].base = 0;
+	}
+	return 1;
+}
+
+__setup("riscom8=", riscom8_setup);
+#endif
+
+static char banner[] __initdata =
+	KERN_INFO "rc: SDL RISCom/8 card driver v1.1, (c) D.Gorodchanin "
+		  "1994-1996.\n";
+static char no_boards_msg[] __initdata =
+	KERN_INFO "rc: No RISCom/8 boards detected.\n";
+
+/* 
+ * This routine must be called by kernel at boot time 
+ */
+static int __init riscom8_init(void)
+{
+	int i;
+	int found = 0;
+
+	printk(banner);
+
+	if (rc_init_drivers()) 
+		return -EIO;
+
+	for (i = 0; i < RC_NBOARD; i++) 
+		if (rc_board[i].base && !rc_probe(&rc_board[i]))  
+			found++;
+	
+	if (!found)  {
+		rc_release_drivers();
+		printk(no_boards_msg);
+		return -EIO;
+	}
+	return 0;
+}
+
+#ifdef MODULE
+static int iobase;
+static int iobase1;
+static int iobase2;
+static int iobase3;
+MODULE_PARM(iobase, "i");
+MODULE_PARM(iobase1, "i");
+MODULE_PARM(iobase2, "i");
+MODULE_PARM(iobase3, "i");
+
+MODULE_LICENSE("GPL");
+#endif /* MODULE */
+
+/*
+ * You can setup up to 4 boards (current value of RC_NBOARD)
+ * by specifying "iobase=0xXXX iobase1=0xXXX ..." as insmod parameter.
+ *
+ */
+static int __init riscom8_init_module (void)
+{
+#ifdef MODULE
+	int i;
+
+	if (iobase || iobase1 || iobase2 || iobase3) {
+		for(i = 0; i < RC_NBOARD; i++)
+			rc_board[0].base = 0;
+	}
+
+	if (iobase)
+		rc_board[0].base = iobase;
+	if (iobase1)
+		rc_board[1].base = iobase1;
+	if (iobase2)
+		rc_board[2].base = iobase2;
+	if (iobase3)
+		rc_board[3].base = iobase3;
+#endif /* MODULE */
+
+	return riscom8_init();
+}
+	
+static void __exit riscom8_exit_module (void)
+{
+	int i;
+	
+	rc_release_drivers();
+	for (i = 0; i < RC_NBOARD; i++)  
+		if (rc_board[i].flags & RC_BOARD_PRESENT) 
+			rc_release_io_range(&rc_board[i]);
+	
+}
+
+module_init(riscom8_init_module);
+module_exit(riscom8_exit_module);
+
diff --git a/drivers/char/riscom8.h b/drivers/char/riscom8.h
new file mode 100644
index 0000000..6317aad
--- /dev/null
+++ b/drivers/char/riscom8.h
@@ -0,0 +1,102 @@
+/*
+ *      linux/drivers/char/riscom8.h  -- RISCom/8 multiport serial driver.
+ *
+ *      Copyright (C) 1994-1996  Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ *      This code is loosely based on the Linux serial driver, written by
+ *      Linus Torvalds, Theodore T'so and others. The RISCom/8 card 
+ *      programming info was obtained from various drivers for other OSes 
+ *	(FreeBSD, ISC, etc), but no source code from those drivers were 
+ *	directly included in this driver.
+ *
+ *
+ *      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.
+ */
+
+#ifndef __LINUX_RISCOM8_H
+#define __LINUX_RISCOM8_H
+
+#include <linux/serial.h>
+
+#ifdef __KERNEL__
+
+#define RC_NBOARD		4
+/* NOTE: RISCom decoder recognizes 16 addresses... */
+#define RC_NPORT        	8  
+#define RC_BOARD(line)		(((line) >> 3) & 0x07)
+#define RC_PORT(line)		((line) & (RC_NPORT - 1))
+
+/* Ticks per sec. Used for setting receiver timeout and break length */
+#define RISCOM_TPS		4000
+
+/* Yeah, after heavy testing I decided it must be 6.
+ * Sure, You can change it if needed.
+ */
+#define RISCOM_RXFIFO		6	/* Max. receiver FIFO size (1-8) */
+
+#define RISCOM8_MAGIC		0x0907
+
+#define RC_IOBASE1	0x220
+#define RC_IOBASE2	0x240
+#define RC_IOBASE3	0x250
+#define RC_IOBASE4	0x260
+
+struct riscom_board {
+	unsigned long   flags;
+	unsigned short	base;
+	unsigned char 	irq;
+	signed   char	count;
+	unsigned char	DTR;
+};
+
+#define RC_BOARD_PRESENT	0x00000001
+#define RC_BOARD_ACTIVE		0x00000002
+	
+struct riscom_port {
+	int			magic;
+	int			baud_base;
+	int			flags;
+	struct tty_struct 	* tty;
+	int			count;
+	int			blocked_open;
+	long			event; /* long req'd for set_bit --RR */
+	int			timeout;
+	int			close_delay;
+	unsigned char 		* xmit_buf;
+	int			custom_divisor;
+	int			xmit_head;
+	int			xmit_tail;
+	int			xmit_cnt;
+	wait_queue_head_t	open_wait;
+	wait_queue_head_t	close_wait;
+	struct work_struct	tqueue;
+	struct work_struct	tqueue_hangup;
+	short			wakeup_chars;
+	short			break_length;
+	unsigned short		closing_wait;
+	unsigned char		mark_mask;
+	unsigned char		IER;
+	unsigned char		MSVR;
+	unsigned char		COR2;
+#ifdef RC_REPORT_OVERRUN
+	unsigned long		overrun;
+#endif	
+#ifdef RC_REPORT_FIFO
+	unsigned long		hits[10];
+#endif
+};
+
+#endif /* __KERNEL__ */
+#endif /* __LINUX_RISCOM8_H */
diff --git a/drivers/char/riscom8_reg.h b/drivers/char/riscom8_reg.h
new file mode 100644
index 0000000..a32475e
--- /dev/null
+++ b/drivers/char/riscom8_reg.h
@@ -0,0 +1,254 @@
+/*
+ *      linux/drivers/char/riscom8_reg.h  -- RISCom/8 multiport serial driver.
+ */
+
+/*
+ * Definitions for RISCom/8 Async Mux card by SDL Communications, Inc.
+ */
+
+/*
+ * Address mapping between Cirrus Logic CD180 chip internal registers
+ * and ISA port addresses:
+ *
+ *      CL-CD180                A6  A5   A4  A3                      A2 A1 A0
+ *      ISA             A15 A14 A13 A12  A11 A10 A9 A8  A7 A6 A5 A4  A3 A2 A1 A0
+ */
+#define RC_TO_ISA(r)    ((((r)&0x07)<<1) | (((r)&~0x07)<<7))
+
+
+/* RISCom/8 On-Board Registers (assuming address translation) */
+
+#define RC_RI           0x100   /* Ring Indicator Register (R/O)           */
+#define RC_DTR          0x100   /* DTR Register (W/O)                      */
+#define RC_BSR          0x101   /* Board Status Register (R/O)             */
+#define RC_CTOUT        0x101   /* Clear Timeout (W/O)                     */
+
+
+/* Board Status Register */
+
+#define RC_BSR_TOUT     0x08     /* Hardware Timeout                       */
+#define RC_BSR_RINT     0x04     /* Receiver Interrupt                     */
+#define RC_BSR_TINT     0x02     /* Transmitter Interrupt                  */
+#define RC_BSR_MINT     0x01     /* Modem Ctl Interrupt                    */
+
+
+/* On-board oscillator frequency (in Hz) */
+#define RC_OSCFREQ      9830400
+
+/* Values of choice for Interrupt ACKs */
+#define RC_ACK_MINT     0x81    /* goes to PILR1                           */
+#define RC_ACK_RINT     0x82    /* goes to PILR3                           */
+#define RC_ACK_TINT     0x84    /* goes to PILR2                           */
+
+/* Chip ID (sorry, only one chip now) */
+#define RC_ID           0x10
+
+/* Definitions for Cirrus Logic CL-CD180 8-port async mux chip */
+ 
+#define CD180_NCH       8       /* Total number of channels                */
+#define CD180_TPC       16      /* Ticks per character                     */
+#define CD180_NFIFO	8	/* TX FIFO size                            */
+
+
+/* Global registers */
+
+#define CD180_GIVR      0x40    /* Global Interrupt Vector Register        */
+#define CD180_GICR      0x41    /* Global Interrupting Channel Register    */
+#define CD180_PILR1     0x61    /* Priority Interrupt Level Register 1     */
+#define CD180_PILR2     0x62    /* Priority Interrupt Level Register 2     */
+#define CD180_PILR3     0x63    /* Priority Interrupt Level Register 3     */
+#define CD180_CAR       0x64    /* Channel Access Register                 */
+#define CD180_GFRCR     0x6b    /* Global Firmware Revision Code Register  */
+#define CD180_PPRH      0x70    /* Prescaler Period Register High          */
+#define CD180_PPRL      0x71    /* Prescaler Period Register Low           */
+#define CD180_RDR       0x78    /* Receiver Data Register                  */
+#define CD180_RCSR      0x7a    /* Receiver Character Status Register      */
+#define CD180_TDR       0x7b    /* Transmit Data Register                  */
+#define CD180_EOIR      0x7f    /* End of Interrupt Register               */
+
+
+/* Channel Registers */
+
+#define CD180_CCR       0x01    /* Channel Command Register                */
+#define CD180_IER       0x02    /* Interrupt Enable Register               */
+#define CD180_COR1      0x03    /* Channel Option Register 1               */
+#define CD180_COR2      0x04    /* Channel Option Register 2               */
+#define CD180_COR3      0x05    /* Channel Option Register 3               */
+#define CD180_CCSR      0x06    /* Channel Control Status Register         */
+#define CD180_RDCR      0x07    /* Receive Data Count Register             */
+#define CD180_SCHR1     0x09    /* Special Character Register 1            */
+#define CD180_SCHR2     0x0a    /* Special Character Register 2            */
+#define CD180_SCHR3     0x0b    /* Special Character Register 3            */
+#define CD180_SCHR4     0x0c    /* Special Character Register 4            */
+#define CD180_MCOR1     0x10    /* Modem Change Option 1 Register          */
+#define CD180_MCOR2     0x11    /* Modem Change Option 2 Register          */
+#define CD180_MCR       0x12    /* Modem Change Register                   */
+#define CD180_RTPR      0x18    /* Receive Timeout Period Register         */
+#define CD180_MSVR      0x28    /* Modem Signal Value Register             */
+#define CD180_RBPRH     0x31    /* Receive Baud Rate Period Register High  */
+#define CD180_RBPRL     0x32    /* Receive Baud Rate Period Register Low   */
+#define CD180_TBPRH     0x39    /* Transmit Baud Rate Period Register High */
+#define CD180_TBPRL     0x3a    /* Transmit Baud Rate Period Register Low  */
+
+
+/* Global Interrupt Vector Register (R/W) */
+
+#define GIVR_ITMASK     0x07     /* Interrupt type mask                     */
+#define  GIVR_IT_MODEM   0x01    /* Modem Signal Change Interrupt           */
+#define  GIVR_IT_TX      0x02    /* Transmit Data Interrupt                 */
+#define  GIVR_IT_RCV     0x03    /* Receive Good Data Interrupt             */
+#define  GIVR_IT_REXC    0x07    /* Receive Exception Interrupt             */
+
+
+/* Global Interrupt Channel Register (R/W) */
+ 
+#define GICR_CHAN       0x1c    /* Channel Number Mask                     */
+#define GICR_CHAN_OFF   2       /* Channel Number Offset                   */
+
+
+/* Channel Address Register (R/W) */
+
+#define CAR_CHAN        0x07    /* Channel Number Mask                     */
+#define CAR_A7          0x08    /* A7 Address Extension (unused)           */
+
+
+/* Receive Character Status Register (R/O) */
+
+#define RCSR_TOUT       0x80    /* Rx Timeout                              */
+#define RCSR_SCDET      0x70    /* Special Character Detected Mask         */
+#define  RCSR_NO_SC      0x00   /* No Special Characters Detected          */
+#define  RCSR_SC_1       0x10   /* Special Char 1 (or 1 & 3) Detected      */
+#define  RCSR_SC_2       0x20   /* Special Char 2 (or 2 & 4) Detected      */
+#define  RCSR_SC_3       0x30   /* Special Char 3 Detected                 */
+#define  RCSR_SC_4       0x40   /* Special Char 4 Detected                 */
+#define RCSR_BREAK      0x08    /* Break has been detected                 */
+#define RCSR_PE         0x04    /* Parity Error                            */
+#define RCSR_FE         0x02    /* Frame Error                             */
+#define RCSR_OE         0x01    /* Overrun Error                           */
+
+
+/* Channel Command Register (R/W) (commands in groups can be OR-ed) */
+
+#define CCR_HARDRESET   0x81    /* Reset the chip                          */
+
+#define CCR_SOFTRESET   0x80    /* Soft Channel Reset                      */
+
+#define CCR_CORCHG1     0x42    /* Channel Option Register 1 Changed       */
+#define CCR_CORCHG2     0x44    /* Channel Option Register 2 Changed       */
+#define CCR_CORCHG3     0x48    /* Channel Option Register 3 Changed       */
+
+#define CCR_SSCH1       0x21    /* Send Special Character 1                */
+
+#define CCR_SSCH2       0x22    /* Send Special Character 2                */
+
+#define CCR_SSCH3       0x23    /* Send Special Character 3                */
+
+#define CCR_SSCH4       0x24    /* Send Special Character 4                */
+
+#define CCR_TXEN        0x18    /* Enable Transmitter                      */
+#define CCR_RXEN        0x12    /* Enable Receiver                         */
+
+#define CCR_TXDIS       0x14    /* Disable Transmitter                     */
+#define CCR_RXDIS       0x11    /* Disable Receiver                        */
+
+
+/* Interrupt Enable Register (R/W) */
+
+#define IER_DSR         0x80    /* Enable interrupt on DSR change          */
+#define IER_CD          0x40    /* Enable interrupt on CD change           */
+#define IER_CTS         0x20    /* Enable interrupt on CTS change          */
+#define IER_RXD         0x10    /* Enable interrupt on Receive Data        */
+#define IER_RXSC        0x08    /* Enable interrupt on Receive Spec. Char  */
+#define IER_TXRDY       0x04    /* Enable interrupt on TX FIFO empty       */
+#define IER_TXEMPTY     0x02    /* Enable interrupt on TX completely empty */
+#define IER_RET         0x01    /* Enable interrupt on RX Exc. Timeout     */
+
+
+/* Channel Option Register 1 (R/W) */
+
+#define COR1_ODDP       0x80    /* Odd Parity                              */
+#define COR1_PARMODE    0x60    /* Parity Mode mask                        */
+#define  COR1_NOPAR      0x00   /* No Parity                               */
+#define  COR1_FORCEPAR   0x20   /* Force Parity                            */
+#define  COR1_NORMPAR    0x40   /* Normal Parity                           */
+#define COR1_IGNORE     0x10    /* Ignore Parity on RX                     */
+#define COR1_STOPBITS   0x0c    /* Number of Stop Bits                     */
+#define  COR1_1SB        0x00   /* 1 Stop Bit                              */
+#define  COR1_15SB       0x04   /* 1.5 Stop Bits                           */
+#define  COR1_2SB        0x08   /* 2 Stop Bits                             */
+#define COR1_CHARLEN    0x03    /* Character Length                        */
+#define  COR1_5BITS      0x00   /* 5 bits                                  */
+#define  COR1_6BITS      0x01   /* 6 bits                                  */
+#define  COR1_7BITS      0x02   /* 7 bits                                  */
+#define  COR1_8BITS      0x03   /* 8 bits                                  */
+
+
+/* Channel Option Register 2 (R/W) */
+
+#define COR2_IXM        0x80    /* Implied XON mode                        */
+#define COR2_TXIBE      0x40    /* Enable In-Band (XON/XOFF) Flow Control  */
+#define COR2_ETC        0x20    /* Embedded Tx Commands Enable             */
+#define COR2_LLM        0x10    /* Local Loopback Mode                     */
+#define COR2_RLM        0x08    /* Remote Loopback Mode                    */
+#define COR2_RTSAO      0x04    /* RTS Automatic Output Enable             */
+#define COR2_CTSAE      0x02    /* CTS Automatic Enable                    */
+#define COR2_DSRAE      0x01    /* DSR Automatic Enable                    */
+
+
+/* Channel Option Register 3 (R/W) */
+
+#define COR3_XONCH      0x80    /* XON is a pair of characters (1 & 3)     */
+#define COR3_XOFFCH     0x40    /* XOFF is a pair of characters (2 & 4)    */
+#define COR3_FCT        0x20    /* Flow-Control Transparency Mode          */
+#define COR3_SCDE       0x10    /* Special Character Detection Enable      */
+#define COR3_RXTH       0x0f    /* RX FIFO Threshold value (1-8)           */
+
+
+/* Channel Control Status Register (R/O) */
+
+#define CCSR_RXEN       0x80    /* Receiver Enabled                        */
+#define CCSR_RXFLOFF    0x40    /* Receive Flow Off (XOFF was sent)        */
+#define CCSR_RXFLON     0x20    /* Receive Flow On (XON was sent)          */
+#define CCSR_TXEN       0x08    /* Transmitter Enabled                     */
+#define CCSR_TXFLOFF    0x04    /* Transmit Flow Off (got XOFF)            */
+#define CCSR_TXFLON     0x02    /* Transmit Flow On (got XON)              */
+
+
+/* Modem Change Option Register 1 (R/W) */
+
+#define MCOR1_DSRZD     0x80    /* Detect 0->1 transition of DSR           */
+#define MCOR1_CDZD      0x40    /* Detect 0->1 transition of CD            */
+#define MCOR1_CTSZD     0x20    /* Detect 0->1 transition of CTS           */
+#define MCOR1_DTRTH     0x0f    /* Auto DTR flow control Threshold (1-8)   */
+#define  MCOR1_NODTRFC   0x0     /* Automatic DTR flow control disabled     */
+
+
+/* Modem Change Option Register 2 (R/W) */
+
+#define MCOR2_DSROD     0x80    /* Detect 1->0 transition of DSR           */
+#define MCOR2_CDOD      0x40    /* Detect 1->0 transition of CD            */
+#define MCOR2_CTSOD     0x20    /* Detect 1->0 transition of CTS           */
+
+
+/* Modem Change Register (R/W) */
+
+#define MCR_DSRCHG      0x80    /* DSR Changed                             */
+#define MCR_CDCHG       0x40    /* CD Changed                              */
+#define MCR_CTSCHG      0x20    /* CTS Changed                             */
+
+
+/* Modem Signal Value Register (R/W) */
+
+#define MSVR_DSR        0x80    /* Current state of DSR input              */
+#define MSVR_CD         0x40    /* Current state of CD input               */
+#define MSVR_CTS        0x20    /* Current state of CTS input              */
+#define MSVR_DTR        0x02    /* Current state of DTR output             */
+#define MSVR_RTS        0x01    /* Current state of RTS output             */
+
+
+/* Escape characters */
+
+#define CD180_C_ESC     0x00    /* Escape character                        */
+#define CD180_C_SBRK    0x81    /* Start sending BREAK                     */
+#define CD180_C_DELAY   0x82    /* Delay output                            */
+#define CD180_C_EBRK    0x83    /* Stop sending BREAK                      */
diff --git a/drivers/char/rocket.c b/drivers/char/rocket.c
new file mode 100644
index 0000000..5bcbeb0
--- /dev/null
+++ b/drivers/char/rocket.c
@@ -0,0 +1,3299 @@
+/*
+ * RocketPort device driver for Linux
+ *
+ * Written by Theodore Ts'o, 1995, 1996, 1997, 1998, 1999, 2000.
+ * 
+ * Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2003 by Comtrol, 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.
+ * 
+ * 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.
+ */
+
+/*
+ * Kernel Synchronization:
+ *
+ * This driver has 2 kernel control paths - exception handlers (calls into the driver
+ * from user mode) and the timer bottom half (tasklet).  This is a polled driver, interrupts
+ * are not used.
+ *
+ * Critical data: 
+ * -  rp_table[], accessed through passed "info" pointers, is a global (static) array of 
+ *    serial port state information and the xmit_buf circular buffer.  Protected by 
+ *    a per port spinlock.
+ * -  xmit_flags[], an array of ints indexed by line (port) number, indicating that there
+ *    is data to be transmitted.  Protected by atomic bit operations.
+ * -  rp_num_ports, int indicating number of open ports, protected by atomic operations.
+ * 
+ * rp_write() and rp_write_char() functions use a per port semaphore to protect against
+ * simultaneous access to the same port by more than one process.
+ */
+
+/****** Defines ******/
+#ifdef PCI_NUM_RESOURCES
+#define PCI_BASE_ADDRESS(dev, r) ((dev)->resource[r].start)
+#else
+#define PCI_BASE_ADDRESS(dev, r) ((dev)->base_address[r])
+#endif
+
+#define ROCKET_PARANOIA_CHECK
+#define ROCKET_DISABLE_SIMUSAGE
+
+#undef ROCKET_SOFT_FLOW
+#undef ROCKET_DEBUG_OPEN
+#undef ROCKET_DEBUG_INTR
+#undef ROCKET_DEBUG_WRITE
+#undef ROCKET_DEBUG_FLOW
+#undef ROCKET_DEBUG_THROTTLE
+#undef ROCKET_DEBUG_WAIT_UNTIL_SENT
+#undef ROCKET_DEBUG_RECEIVE
+#undef ROCKET_DEBUG_HANGUP
+#undef REV_PCI_ORDER
+#undef ROCKET_DEBUG_IO
+
+#define POLL_PERIOD HZ/100	/*  Polling period .01 seconds (10ms) */
+
+/****** Kernel includes ******/
+
+#ifdef MODVERSIONS
+#include <config/modversions.h>
+#endif				
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/pci.h>
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <asm/semaphore.h>
+#include <linux/init.h>
+
+/****** RocketPort includes ******/
+
+#include "rocket_int.h"
+#include "rocket.h"
+
+#define ROCKET_VERSION "2.09"
+#define ROCKET_DATE "12-June-2003"
+
+/****** RocketPort Local Variables ******/
+
+static struct tty_driver *rocket_driver;
+
+static struct rocket_version driver_version = {	
+	ROCKET_VERSION, ROCKET_DATE
+};
+
+static struct r_port *rp_table[MAX_RP_PORTS];	       /*  The main repository of serial port state information. */
+static unsigned int xmit_flags[NUM_BOARDS];	       /*  Bit significant, indicates port had data to transmit. */
+						       /*  eg.  Bit 0 indicates port 0 has xmit data, ...        */
+static atomic_t rp_num_ports_open;	               /*  Number of serial ports open                           */
+static struct timer_list rocket_timer;
+
+static unsigned long board1;	                       /* ISA addresses, retrieved from rocketport.conf          */
+static unsigned long board2;
+static unsigned long board3;
+static unsigned long board4;
+static unsigned long controller;
+static int support_low_speed;
+static unsigned long modem1;
+static unsigned long modem2;
+static unsigned long modem3;
+static unsigned long modem4;
+static unsigned long pc104_1[8];
+static unsigned long pc104_2[8];
+static unsigned long pc104_3[8];
+static unsigned long pc104_4[8];
+static unsigned long *pc104[4] = { pc104_1, pc104_2, pc104_3, pc104_4 };
+
+static int rp_baud_base[NUM_BOARDS];	               /*  Board config info (Someday make a per-board structure)  */
+static unsigned long rcktpt_io_addr[NUM_BOARDS];
+static int rcktpt_type[NUM_BOARDS];
+static int is_PCI[NUM_BOARDS];
+static rocketModel_t rocketModel[NUM_BOARDS];
+static int max_board;
+
+/*
+ * The following arrays define the interrupt bits corresponding to each AIOP.
+ * These bits are different between the ISA and regular PCI boards and the
+ * Universal PCI boards.
+ */
+
+static Word_t aiop_intr_bits[AIOP_CTL_SIZE] = {
+	AIOP_INTR_BIT_0,
+	AIOP_INTR_BIT_1,
+	AIOP_INTR_BIT_2,
+	AIOP_INTR_BIT_3
+};
+
+static Word_t upci_aiop_intr_bits[AIOP_CTL_SIZE] = {
+	UPCI_AIOP_INTR_BIT_0,
+	UPCI_AIOP_INTR_BIT_1,
+	UPCI_AIOP_INTR_BIT_2,
+	UPCI_AIOP_INTR_BIT_3
+};
+
+/*
+ *  Line number is the ttySIx number (x), the Minor number.  We 
+ *  assign them sequentially, starting at zero.  The following 
+ *  array keeps track of the line number assigned to a given board/aiop/channel.
+ */
+static unsigned char lineNumbers[MAX_RP_PORTS];
+static unsigned long nextLineNumber;
+
+/*****  RocketPort Static Prototypes   *********/
+static int __init init_ISA(int i);
+static void rp_wait_until_sent(struct tty_struct *tty, int timeout);
+static void rp_flush_buffer(struct tty_struct *tty);
+static void rmSpeakerReset(CONTROLLER_T * CtlP, unsigned long model);
+static unsigned char GetLineNumber(int ctrl, int aiop, int ch);
+static unsigned char SetLineNumber(int ctrl, int aiop, int ch);
+static void rp_start(struct tty_struct *tty);
+
+#ifdef MODULE
+MODULE_AUTHOR("Theodore Ts'o");
+MODULE_DESCRIPTION("Comtrol RocketPort driver");
+module_param(board1, ulong, 0);
+MODULE_PARM_DESC(board1, "I/O port for (ISA) board #1");
+module_param(board2, ulong, 0);
+MODULE_PARM_DESC(board2, "I/O port for (ISA) board #2");
+module_param(board3, ulong, 0);
+MODULE_PARM_DESC(board3, "I/O port for (ISA) board #3");
+module_param(board4, ulong, 0);
+MODULE_PARM_DESC(board4, "I/O port for (ISA) board #4");
+module_param(controller, ulong, 0);
+MODULE_PARM_DESC(controller, "I/O port for (ISA) rocketport controller");
+module_param(support_low_speed, bool, 0);
+MODULE_PARM_DESC(support_low_speed, "1 means support 50 baud, 0 means support 460400 baud");
+module_param(modem1, ulong, 0);
+MODULE_PARM_DESC(modem1, "1 means (ISA) board #1 is a RocketModem");
+module_param(modem2, ulong, 0);
+MODULE_PARM_DESC(modem2, "1 means (ISA) board #2 is a RocketModem");
+module_param(modem3, ulong, 0);
+MODULE_PARM_DESC(modem3, "1 means (ISA) board #3 is a RocketModem");
+module_param(modem4, ulong, 0);
+MODULE_PARM_DESC(modem4, "1 means (ISA) board #4 is a RocketModem");
+module_param_array(pc104_1, ulong, NULL, 0);
+MODULE_PARM_DESC(pc104_1, "set interface types for ISA(PC104) board #1 (e.g. pc104_1=232,232,485,485,...");
+module_param_array(pc104_2, ulong, NULL, 0);
+MODULE_PARM_DESC(pc104_2, "set interface types for ISA(PC104) board #2 (e.g. pc104_2=232,232,485,485,...");
+module_param_array(pc104_3, ulong, NULL, 0);
+MODULE_PARM_DESC(pc104_3, "set interface types for ISA(PC104) board #3 (e.g. pc104_3=232,232,485,485,...");
+module_param_array(pc104_4, ulong, NULL, 0);
+MODULE_PARM_DESC(pc104_4, "set interface types for ISA(PC104) board #4 (e.g. pc104_4=232,232,485,485,...");
+
+int rp_init(void);
+static void rp_cleanup_module(void);
+
+module_init(rp_init);
+module_exit(rp_cleanup_module);
+
+#endif
+
+#ifdef MODULE_LICENSE
+MODULE_LICENSE("Dual BSD/GPL");
+#endif
+
+/*************************************************************************/
+/*                     Module code starts here                           */
+
+static inline int rocket_paranoia_check(struct r_port *info,
+					const char *routine)
+{
+#ifdef ROCKET_PARANOIA_CHECK
+	if (!info)
+		return 1;
+	if (info->magic != RPORT_MAGIC) {
+		printk(KERN_INFO "Warning: bad magic number for rocketport struct in %s\n",
+		     routine);
+		return 1;
+	}
+#endif
+	return 0;
+}
+
+
+/*  Serial port receive data function.  Called (from timer poll) when an AIOPIC signals 
+ *  that receive data is present on a serial port.  Pulls data from FIFO, moves it into the 
+ *  tty layer.  
+ */
+static void rp_do_receive(struct r_port *info,
+			  struct tty_struct *tty,
+			  CHANNEL_t * cp, unsigned int ChanStatus)
+{
+	unsigned int CharNStat;
+	int ToRecv, wRecv, space = 0, count;
+	unsigned char *cbuf;
+	char *fbuf;
+	struct tty_ldisc *ld;
+
+	ld = tty_ldisc_ref(tty);
+
+	ToRecv = sGetRxCnt(cp);
+	if (ld)
+		space = ld->receive_room(tty);
+	if (space > 2 * TTY_FLIPBUF_SIZE)
+		space = 2 * TTY_FLIPBUF_SIZE;
+	cbuf = tty->flip.char_buf;
+	fbuf = tty->flip.flag_buf;
+	count = 0;
+#ifdef ROCKET_DEBUG_INTR
+	printk(KERN_INFO "rp_do_receive(%d, %d)...", ToRecv, space);
+#endif
+
+	/*
+	 * determine how many we can actually read in.  If we can't
+	 * read any in then we have a software overrun condition.
+	 */
+	if (ToRecv > space)
+		ToRecv = space;
+
+	if (ToRecv <= 0)
+		return;
+
+	/*
+	 * if status indicates there are errored characters in the
+	 * FIFO, then enter status mode (a word in FIFO holds
+	 * character and status).
+	 */
+	if (ChanStatus & (RXFOVERFL | RXBREAK | RXFRAME | RXPARITY)) {
+		if (!(ChanStatus & STATMODE)) {
+#ifdef ROCKET_DEBUG_RECEIVE
+			printk(KERN_INFO "Entering STATMODE...");
+#endif
+			ChanStatus |= STATMODE;
+			sEnRxStatusMode(cp);
+		}
+	}
+
+	/* 
+	 * if we previously entered status mode, then read down the
+	 * FIFO one word at a time, pulling apart the character and
+	 * the status.  Update error counters depending on status
+	 */
+	if (ChanStatus & STATMODE) {
+#ifdef ROCKET_DEBUG_RECEIVE
+		printk(KERN_INFO "Ignore %x, read %x...", info->ignore_status_mask,
+		       info->read_status_mask);
+#endif
+		while (ToRecv) {
+			CharNStat = sInW(sGetTxRxDataIO(cp));
+#ifdef ROCKET_DEBUG_RECEIVE
+			printk(KERN_INFO "%x...", CharNStat);
+#endif
+			if (CharNStat & STMBREAKH)
+				CharNStat &= ~(STMFRAMEH | STMPARITYH);
+			if (CharNStat & info->ignore_status_mask) {
+				ToRecv--;
+				continue;
+			}
+			CharNStat &= info->read_status_mask;
+			if (CharNStat & STMBREAKH)
+				*fbuf++ = TTY_BREAK;
+			else if (CharNStat & STMPARITYH)
+				*fbuf++ = TTY_PARITY;
+			else if (CharNStat & STMFRAMEH)
+				*fbuf++ = TTY_FRAME;
+			else if (CharNStat & STMRCVROVRH)
+				*fbuf++ = TTY_OVERRUN;
+			else
+				*fbuf++ = 0;
+			*cbuf++ = CharNStat & 0xff;
+			count++;
+			ToRecv--;
+		}
+
+		/*
+		 * after we've emptied the FIFO in status mode, turn
+		 * status mode back off
+		 */
+		if (sGetRxCnt(cp) == 0) {
+#ifdef ROCKET_DEBUG_RECEIVE
+			printk(KERN_INFO "Status mode off.\n");
+#endif
+			sDisRxStatusMode(cp);
+		}
+	} else {
+		/*
+		 * we aren't in status mode, so read down the FIFO two
+		 * characters at time by doing repeated word IO
+		 * transfer.
+		 */
+		wRecv = ToRecv >> 1;
+		if (wRecv)
+			sInStrW(sGetTxRxDataIO(cp), (unsigned short *) cbuf, wRecv);
+		if (ToRecv & 1)
+			cbuf[ToRecv - 1] = sInB(sGetTxRxDataIO(cp));
+		memset(fbuf, 0, ToRecv);
+		cbuf += ToRecv;
+		fbuf += ToRecv;
+		count += ToRecv;
+	}
+	/*  Push the data up to the tty layer */
+	ld->receive_buf(tty, tty->flip.char_buf, tty->flip.flag_buf, count);
+	tty_ldisc_deref(ld);
+}
+
+/*
+ *  Serial port transmit data function.  Called from the timer polling loop as a 
+ *  result of a bit set in xmit_flags[], indicating data (from the tty layer) is ready
+ *  to be sent out the serial port.  Data is buffered in rp_table[line].xmit_buf, it is 
+ *  moved to the port's xmit FIFO.  *info is critical data, protected by spinlocks.
+ */
+static void rp_do_transmit(struct r_port *info)
+{
+	int c;
+	CHANNEL_t *cp = &info->channel;
+	struct tty_struct *tty;
+	unsigned long flags;
+
+#ifdef ROCKET_DEBUG_INTR
+	printk(KERN_INFO "rp_do_transmit ");
+#endif
+	if (!info)
+		return;
+	if (!info->tty) {
+		printk(KERN_INFO  "rp: WARNING rp_do_transmit called with info->tty==NULL\n");
+		clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+		return;
+	}
+
+	spin_lock_irqsave(&info->slock, flags);
+	tty = info->tty;
+	info->xmit_fifo_room = TXFIFO_SIZE - sGetTxCnt(cp);
+
+	/*  Loop sending data to FIFO until done or FIFO full */
+	while (1) {
+		if (tty->stopped || tty->hw_stopped)
+			break;
+		c = min(info->xmit_fifo_room, min(info->xmit_cnt, XMIT_BUF_SIZE - info->xmit_tail));
+		if (c <= 0 || info->xmit_fifo_room <= 0)
+			break;
+		sOutStrW(sGetTxRxDataIO(cp), (unsigned short *) (info->xmit_buf + info->xmit_tail), c / 2);
+		if (c & 1)
+			sOutB(sGetTxRxDataIO(cp), info->xmit_buf[info->xmit_tail + c - 1]);
+		info->xmit_tail += c;
+		info->xmit_tail &= XMIT_BUF_SIZE - 1;
+		info->xmit_cnt -= c;
+		info->xmit_fifo_room -= c;
+#ifdef ROCKET_DEBUG_INTR
+		printk(KERN_INFO "tx %d chars...", c);
+#endif
+	}
+
+	if (info->xmit_cnt == 0)
+		clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+	if (info->xmit_cnt < WAKEUP_CHARS) {
+		tty_wakeup(tty);
+		wake_up_interruptible(&tty->write_wait);
+#ifdef ROCKETPORT_HAVE_POLL_WAIT
+		wake_up_interruptible(&tty->poll_wait);
+#endif
+	}
+
+	spin_unlock_irqrestore(&info->slock, flags);
+
+#ifdef ROCKET_DEBUG_INTR
+	printk(KERN_INFO "(%d,%d,%d,%d)...", info->xmit_cnt, info->xmit_head,
+	       info->xmit_tail, info->xmit_fifo_room);
+#endif
+}
+
+/*
+ *  Called when a serial port signals it has read data in it's RX FIFO.
+ *  It checks what interrupts are pending and services them, including
+ *  receiving serial data.  
+ */
+static void rp_handle_port(struct r_port *info)
+{
+	CHANNEL_t *cp;
+	struct tty_struct *tty;
+	unsigned int IntMask, ChanStatus;
+
+	if (!info)
+		return;
+
+	if ((info->flags & ROCKET_INITIALIZED) == 0) {
+		printk(KERN_INFO "rp: WARNING: rp_handle_port called with info->flags & NOT_INIT\n");
+		return;
+	}
+	if (!info->tty) {
+		printk(KERN_INFO "rp: WARNING: rp_handle_port called with info->tty==NULL\n");
+		return;
+	}
+	cp = &info->channel;
+	tty = info->tty;
+
+	IntMask = sGetChanIntID(cp) & info->intmask;
+#ifdef ROCKET_DEBUG_INTR
+	printk(KERN_INFO "rp_interrupt %02x...", IntMask);
+#endif
+	ChanStatus = sGetChanStatus(cp);
+	if (IntMask & RXF_TRIG) {	/* Rx FIFO trigger level */
+		rp_do_receive(info, tty, cp, ChanStatus);
+	}
+	if (IntMask & DELTA_CD) {	/* CD change  */
+#if (defined(ROCKET_DEBUG_OPEN) || defined(ROCKET_DEBUG_INTR) || defined(ROCKET_DEBUG_HANGUP))
+		printk(KERN_INFO "ttyR%d CD now %s...", info->line,
+		       (ChanStatus & CD_ACT) ? "on" : "off");
+#endif
+		if (!(ChanStatus & CD_ACT) && info->cd_status) {
+#ifdef ROCKET_DEBUG_HANGUP
+			printk(KERN_INFO "CD drop, calling hangup.\n");
+#endif
+			tty_hangup(tty);
+		}
+		info->cd_status = (ChanStatus & CD_ACT) ? 1 : 0;
+		wake_up_interruptible(&info->open_wait);
+	}
+#ifdef ROCKET_DEBUG_INTR
+	if (IntMask & DELTA_CTS) {	/* CTS change */
+		printk(KERN_INFO "CTS change...\n");
+	}
+	if (IntMask & DELTA_DSR) {	/* DSR change */
+		printk(KERN_INFO "DSR change...\n");
+	}
+#endif
+}
+
+/*
+ *  The top level polling routine.  Repeats every 1/100 HZ (10ms).
+ */
+static void rp_do_poll(unsigned long dummy)
+{
+	CONTROLLER_t *ctlp;
+	int ctrl, aiop, ch, line, i;
+	unsigned int xmitmask;
+	unsigned int CtlMask;
+	unsigned char AiopMask;
+	Word_t bit;
+
+	/*  Walk through all the boards (ctrl's) */
+	for (ctrl = 0; ctrl < max_board; ctrl++) {
+		if (rcktpt_io_addr[ctrl] <= 0)
+			continue;
+
+		/*  Get a ptr to the board's control struct */
+		ctlp = sCtlNumToCtlPtr(ctrl);
+
+		/*  Get the interupt status from the board */
+#ifdef CONFIG_PCI
+		if (ctlp->BusType == isPCI)
+			CtlMask = sPCIGetControllerIntStatus(ctlp);
+		else
+#endif
+			CtlMask = sGetControllerIntStatus(ctlp);
+
+		/*  Check if any AIOP read bits are set */
+		for (aiop = 0; CtlMask; aiop++) {
+			bit = ctlp->AiopIntrBits[aiop];
+			if (CtlMask & bit) {
+				CtlMask &= ~bit;
+				AiopMask = sGetAiopIntStatus(ctlp, aiop);
+
+				/*  Check if any port read bits are set */
+				for (ch = 0; AiopMask;  AiopMask >>= 1, ch++) {
+					if (AiopMask & 1) {
+
+						/*  Get the line number (/dev/ttyRx number). */
+						/*  Read the data from the port. */
+						line = GetLineNumber(ctrl, aiop, ch);
+						rp_handle_port(rp_table[line]);
+					}
+				}
+			}
+		}
+
+		xmitmask = xmit_flags[ctrl];
+
+		/*
+		 *  xmit_flags contains bit-significant flags, indicating there is data
+		 *  to xmit on the port. Bit 0 is port 0 on this board, bit 1 is port 
+		 *  1, ... (32 total possible).  The variable i has the aiop and ch 
+		 *  numbers encoded in it (port 0-7 are aiop0, 8-15 are aiop1, etc).
+		 */
+		if (xmitmask) {
+			for (i = 0; i < rocketModel[ctrl].numPorts; i++) {
+				if (xmitmask & (1 << i)) {
+					aiop = (i & 0x18) >> 3;
+					ch = i & 0x07;
+					line = GetLineNumber(ctrl, aiop, ch);
+					rp_do_transmit(rp_table[line]);
+				}
+			}
+		}
+	}
+
+	/*
+	 * Reset the timer so we get called at the next clock tick (10ms).
+	 */
+	if (atomic_read(&rp_num_ports_open))
+		mod_timer(&rocket_timer, jiffies + POLL_PERIOD);
+}
+
+/*
+ *  Initializes the r_port structure for a port, as well as enabling the port on 
+ *  the board.  
+ *  Inputs:  board, aiop, chan numbers
+ */
+static void init_r_port(int board, int aiop, int chan, struct pci_dev *pci_dev)
+{
+	unsigned rocketMode;
+	struct r_port *info;
+	int line;
+	CONTROLLER_T *ctlp;
+
+	/*  Get the next available line number */
+	line = SetLineNumber(board, aiop, chan);
+
+	ctlp = sCtlNumToCtlPtr(board);
+
+	/*  Get a r_port struct for the port, fill it in and save it globally, indexed by line number */
+	info = kmalloc(sizeof (struct r_port), GFP_KERNEL);
+	if (!info) {
+		printk(KERN_INFO "Couldn't allocate info struct for line #%d\n", line);
+		return;
+	}
+	memset(info, 0, sizeof (struct r_port));
+
+	info->magic = RPORT_MAGIC;
+	info->line = line;
+	info->ctlp = ctlp;
+	info->board = board;
+	info->aiop = aiop;
+	info->chan = chan;
+	info->closing_wait = 3000;
+	info->close_delay = 50;
+	init_waitqueue_head(&info->open_wait);
+	init_waitqueue_head(&info->close_wait);
+	info->flags &= ~ROCKET_MODE_MASK;
+	switch (pc104[board][line]) {
+	case 422:
+		info->flags |= ROCKET_MODE_RS422;
+		break;
+	case 485:
+		info->flags |= ROCKET_MODE_RS485;
+		break;
+	case 232:
+	default:
+		info->flags |= ROCKET_MODE_RS232;
+		break;
+	}
+
+	info->intmask = RXF_TRIG | TXFIFO_MT | SRC_INT | DELTA_CD | DELTA_CTS | DELTA_DSR;
+	if (sInitChan(ctlp, &info->channel, aiop, chan) == 0) {
+		printk(KERN_INFO "RocketPort sInitChan(%d, %d, %d) failed!\n", board, aiop, chan);
+		kfree(info);
+		return;
+	}
+
+	rocketMode = info->flags & ROCKET_MODE_MASK;
+
+	if ((info->flags & ROCKET_RTS_TOGGLE) || (rocketMode == ROCKET_MODE_RS485))
+		sEnRTSToggle(&info->channel);
+	else
+		sDisRTSToggle(&info->channel);
+
+	if (ctlp->boardType == ROCKET_TYPE_PC104) {
+		switch (rocketMode) {
+		case ROCKET_MODE_RS485:
+			sSetInterfaceMode(&info->channel, InterfaceModeRS485);
+			break;
+		case ROCKET_MODE_RS422:
+			sSetInterfaceMode(&info->channel, InterfaceModeRS422);
+			break;
+		case ROCKET_MODE_RS232:
+		default:
+			if (info->flags & ROCKET_RTS_TOGGLE)
+				sSetInterfaceMode(&info->channel, InterfaceModeRS232T);
+			else
+				sSetInterfaceMode(&info->channel, InterfaceModeRS232);
+			break;
+		}
+	}
+	spin_lock_init(&info->slock);
+	sema_init(&info->write_sem, 1);
+	rp_table[line] = info;
+	if (pci_dev)
+		tty_register_device(rocket_driver, line, &pci_dev->dev);
+}
+
+/*
+ *  Configures a rocketport port according to its termio settings.  Called from 
+ *  user mode into the driver (exception handler).  *info CD manipulation is spinlock protected.
+ */
+static void configure_r_port(struct r_port *info,
+			     struct termios *old_termios)
+{
+	unsigned cflag;
+	unsigned long flags;
+	unsigned rocketMode;
+	int bits, baud, divisor;
+	CHANNEL_t *cp;
+
+	if (!info->tty || !info->tty->termios)
+		return;
+	cp = &info->channel;
+	cflag = info->tty->termios->c_cflag;
+
+	/* Byte size and parity */
+	if ((cflag & CSIZE) == CS8) {
+		sSetData8(cp);
+		bits = 10;
+	} else {
+		sSetData7(cp);
+		bits = 9;
+	}
+	if (cflag & CSTOPB) {
+		sSetStop2(cp);
+		bits++;
+	} else {
+		sSetStop1(cp);
+	}
+
+	if (cflag & PARENB) {
+		sEnParity(cp);
+		bits++;
+		if (cflag & PARODD) {
+			sSetOddParity(cp);
+		} else {
+			sSetEvenParity(cp);
+		}
+	} else {
+		sDisParity(cp);
+	}
+
+	/* baud rate */
+	baud = tty_get_baud_rate(info->tty);
+	if (!baud)
+		baud = 9600;
+	divisor = ((rp_baud_base[info->board] + (baud >> 1)) / baud) - 1;
+	if ((divisor >= 8192 || divisor < 0) && old_termios) {
+		info->tty->termios->c_cflag &= ~CBAUD;
+		info->tty->termios->c_cflag |=
+		    (old_termios->c_cflag & CBAUD);
+		baud = tty_get_baud_rate(info->tty);
+		if (!baud)
+			baud = 9600;
+		divisor = (rp_baud_base[info->board] / baud) - 1;
+	}
+	if (divisor >= 8192 || divisor < 0) {
+		baud = 9600;
+		divisor = (rp_baud_base[info->board] / baud) - 1;
+	}
+	info->cps = baud / bits;
+	sSetBaud(cp, divisor);
+
+	if (cflag & CRTSCTS) {
+		info->intmask |= DELTA_CTS;
+		sEnCTSFlowCtl(cp);
+	} else {
+		info->intmask &= ~DELTA_CTS;
+		sDisCTSFlowCtl(cp);
+	}
+	if (cflag & CLOCAL) {
+		info->intmask &= ~DELTA_CD;
+	} else {
+		spin_lock_irqsave(&info->slock, flags);
+		if (sGetChanStatus(cp) & CD_ACT)
+			info->cd_status = 1;
+		else
+			info->cd_status = 0;
+		info->intmask |= DELTA_CD;
+		spin_unlock_irqrestore(&info->slock, flags);
+	}
+
+	/*
+	 * Handle software flow control in the board
+	 */
+#ifdef ROCKET_SOFT_FLOW
+	if (I_IXON(info->tty)) {
+		sEnTxSoftFlowCtl(cp);
+		if (I_IXANY(info->tty)) {
+			sEnIXANY(cp);
+		} else {
+			sDisIXANY(cp);
+		}
+		sSetTxXONChar(cp, START_CHAR(info->tty));
+		sSetTxXOFFChar(cp, STOP_CHAR(info->tty));
+	} else {
+		sDisTxSoftFlowCtl(cp);
+		sDisIXANY(cp);
+		sClrTxXOFF(cp);
+	}
+#endif
+
+	/*
+	 * Set up ignore/read mask words
+	 */
+	info->read_status_mask = STMRCVROVRH | 0xFF;
+	if (I_INPCK(info->tty))
+		info->read_status_mask |= STMFRAMEH | STMPARITYH;
+	if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+		info->read_status_mask |= STMBREAKH;
+
+	/*
+	 * Characters to ignore
+	 */
+	info->ignore_status_mask = 0;
+	if (I_IGNPAR(info->tty))
+		info->ignore_status_mask |= STMFRAMEH | STMPARITYH;
+	if (I_IGNBRK(info->tty)) {
+		info->ignore_status_mask |= STMBREAKH;
+		/*
+		 * If we're ignoring parity and break indicators,
+		 * ignore overruns too.  (For real raw support).
+		 */
+		if (I_IGNPAR(info->tty))
+			info->ignore_status_mask |= STMRCVROVRH;
+	}
+
+	rocketMode = info->flags & ROCKET_MODE_MASK;
+
+	if ((info->flags & ROCKET_RTS_TOGGLE)
+	    || (rocketMode == ROCKET_MODE_RS485))
+		sEnRTSToggle(cp);
+	else
+		sDisRTSToggle(cp);
+
+	sSetRTS(&info->channel);
+
+	if (cp->CtlP->boardType == ROCKET_TYPE_PC104) {
+		switch (rocketMode) {
+		case ROCKET_MODE_RS485:
+			sSetInterfaceMode(cp, InterfaceModeRS485);
+			break;
+		case ROCKET_MODE_RS422:
+			sSetInterfaceMode(cp, InterfaceModeRS422);
+			break;
+		case ROCKET_MODE_RS232:
+		default:
+			if (info->flags & ROCKET_RTS_TOGGLE)
+				sSetInterfaceMode(cp, InterfaceModeRS232T);
+			else
+				sSetInterfaceMode(cp, InterfaceModeRS232);
+			break;
+		}
+	}
+}
+
+/*  info->count is considered critical, protected by spinlocks.  */
+static int block_til_ready(struct tty_struct *tty, struct file *filp,
+			   struct r_port *info)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	int retval;
+	int do_clocal = 0, extra_count = 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))
+		return ((info->flags & ROCKET_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+	if (info->flags & ROCKET_CLOSING) {
+		interruptible_sleep_on(&info->close_wait);
+		return ((info->flags & ROCKET_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+	}
+
+	/*
+	 * 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))) {
+		info->flags |= ROCKET_NORMAL_ACTIVE;
+		return 0;
+	}
+	if (tty->termios->c_cflag & CLOCAL)
+		do_clocal = 1;
+
+	/*
+	 * Block waiting for the carrier detect and the line to become free.  While we are in
+	 * this loop, info->count is dropped by one, so that rp_close() knows when to free things.  
+         * We restore it upon exit, either normal or abnormal.
+	 */
+	retval = 0;
+	add_wait_queue(&info->open_wait, &wait);
+#ifdef ROCKET_DEBUG_OPEN
+	printk(KERN_INFO "block_til_ready before block: ttyR%d, count = %d\n", info->line, info->count);
+#endif
+	spin_lock_irqsave(&info->slock, flags);
+
+#ifdef ROCKET_DISABLE_SIMUSAGE
+	info->flags |= ROCKET_NORMAL_ACTIVE;
+#else
+	if (!tty_hung_up_p(filp)) {
+		extra_count = 1;
+		info->count--;
+	}
+#endif
+	info->blocked_open++;
+
+	spin_unlock_irqrestore(&info->slock, flags);
+
+	while (1) {
+		if (tty->termios->c_cflag & CBAUD) {
+			sSetDTR(&info->channel);
+			sSetRTS(&info->channel);
+		}
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) || !(info->flags & ROCKET_INITIALIZED)) {
+			if (info->flags & ROCKET_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;
+			break;
+		}
+		if (!(info->flags & ROCKET_CLOSING) && (do_clocal || (sGetChanStatusLo(&info->channel) & CD_ACT)))
+			break;
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+#ifdef ROCKET_DEBUG_OPEN
+		printk(KERN_INFO "block_til_ready blocking: ttyR%d, count = %d, flags=0x%0x\n",
+		     info->line, info->count, info->flags);
+#endif
+		schedule();	/*  Don't hold spinlock here, will hang PC */
+	}
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&info->open_wait, &wait);
+
+	spin_lock_irqsave(&info->slock, flags);
+
+	if (extra_count)
+		info->count++;
+	info->blocked_open--;
+
+	spin_unlock_irqrestore(&info->slock, flags);
+
+#ifdef ROCKET_DEBUG_OPEN
+	printk(KERN_INFO "block_til_ready after blocking: ttyR%d, count = %d\n",
+	       info->line, info->count);
+#endif
+	if (retval)
+		return retval;
+	info->flags |= ROCKET_NORMAL_ACTIVE;
+	return 0;
+}
+
+/*
+ *  Exception handler that opens a serial port.  Creates xmit_buf storage, fills in 
+ *  port's r_port struct.  Initializes the port hardware.  
+ */
+static int rp_open(struct tty_struct *tty, struct file *filp)
+{
+	struct r_port *info;
+	int line = 0, retval;
+	CHANNEL_t *cp;
+	unsigned long page;
+
+	line = TTY_GET_LINE(tty);
+	if ((line < 0) || (line >= MAX_RP_PORTS) || ((info = rp_table[line]) == NULL))
+		return -ENXIO;
+
+	page = __get_free_page(GFP_KERNEL);
+	if (!page)
+		return -ENOMEM;
+
+	if (info->flags & ROCKET_CLOSING) {
+		interruptible_sleep_on(&info->close_wait);
+		free_page(page);
+		return ((info->flags & ROCKET_HUP_NOTIFY) ? -EAGAIN : -ERESTARTSYS);
+	}
+
+	/*
+	 * We must not sleep from here until the port is marked fully in use.
+	 */
+	if (info->xmit_buf)
+		free_page(page);
+	else
+		info->xmit_buf = (unsigned char *) page;
+
+	tty->driver_data = info;
+	info->tty = tty;
+
+	if (info->count++ == 0) {
+		atomic_inc(&rp_num_ports_open);
+
+#ifdef ROCKET_DEBUG_OPEN
+		printk(KERN_INFO "rocket mod++ = %d...", atomic_read(&rp_num_ports_open));
+#endif
+	}
+#ifdef ROCKET_DEBUG_OPEN
+	printk(KERN_INFO "rp_open ttyR%d, count=%d\n", info->line, info->count);
+#endif
+
+	/*
+	 * Info->count is now 1; so it's safe to sleep now.
+	 */
+	info->session = current->signal->session;
+	info->pgrp = process_group(current);
+
+	if ((info->flags & ROCKET_INITIALIZED) == 0) {
+		cp = &info->channel;
+		sSetRxTrigger(cp, TRIG_1);
+		if (sGetChanStatus(cp) & CD_ACT)
+			info->cd_status = 1;
+		else
+			info->cd_status = 0;
+		sDisRxStatusMode(cp);
+		sFlushRxFIFO(cp);
+		sFlushTxFIFO(cp);
+
+		sEnInterrupts(cp, (TXINT_EN | MCINT_EN | RXINT_EN | SRCINT_EN | CHANINT_EN));
+		sSetRxTrigger(cp, TRIG_1);
+
+		sGetChanStatus(cp);
+		sDisRxStatusMode(cp);
+		sClrTxXOFF(cp);
+
+		sDisCTSFlowCtl(cp);
+		sDisTxSoftFlowCtl(cp);
+
+		sEnRxFIFO(cp);
+		sEnTransmit(cp);
+
+		info->flags |= ROCKET_INITIALIZED;
+
+		/*
+		 * Set up the tty->alt_speed kludge
+		 */
+		if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_HI)
+			info->tty->alt_speed = 57600;
+		if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_VHI)
+			info->tty->alt_speed = 115200;
+		if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_SHI)
+			info->tty->alt_speed = 230400;
+		if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_WARP)
+			info->tty->alt_speed = 460800;
+
+		configure_r_port(info, NULL);
+		if (tty->termios->c_cflag & CBAUD) {
+			sSetDTR(cp);
+			sSetRTS(cp);
+		}
+	}
+	/*  Starts (or resets) the maint polling loop */
+	mod_timer(&rocket_timer, jiffies + POLL_PERIOD);
+
+	retval = block_til_ready(tty, filp, info);
+	if (retval) {
+#ifdef ROCKET_DEBUG_OPEN
+		printk(KERN_INFO "rp_open returning after block_til_ready with %d\n", retval);
+#endif
+		return retval;
+	}
+	return 0;
+}
+
+/*
+ *  Exception handler that closes a serial port. info->count is considered critical. 
+ */
+static void rp_close(struct tty_struct *tty, struct file *filp)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	unsigned long flags;
+	int timeout;
+	CHANNEL_t *cp;
+	
+	if (rocket_paranoia_check(info, "rp_close"))
+		return;
+
+#ifdef ROCKET_DEBUG_OPEN
+	printk(KERN_INFO "rp_close ttyR%d, count = %d\n", info->line, info->count);
+#endif
+
+	if (tty_hung_up_p(filp))
+		return;
+	spin_lock_irqsave(&info->slock, flags);
+
+	if ((tty->count == 1) && (info->count != 1)) {
+		/*
+		 * Uh, oh.  tty->count is 1, which means that the tty
+		 * structure will be freed.  Info->count should always
+		 * be one in these conditions.  If it's greater than
+		 * one, we've got real problems, since it means the
+		 * serial port won't be shutdown.
+		 */
+		printk(KERN_INFO "rp_close: bad serial port count; tty->count is 1, "
+		       "info->count is %d\n", info->count);
+		info->count = 1;
+	}
+	if (--info->count < 0) {
+		printk(KERN_INFO "rp_close: bad serial port count for ttyR%d: %d\n",
+		       info->line, info->count);
+		info->count = 0;
+	}
+	if (info->count) {
+		spin_unlock_irqrestore(&info->slock, flags);
+		return;
+	}
+	info->flags |= ROCKET_CLOSING;
+	spin_unlock_irqrestore(&info->slock, flags);
+
+	cp = &info->channel;
+
+	/*
+	 * Notify the line discpline to only process XON/XOFF characters
+	 */
+	tty->closing = 1;
+
+	/*
+	 * If transmission was throttled by the application request,
+	 * just flush the xmit buffer.
+	 */
+	if (tty->flow_stopped)
+		rp_flush_buffer(tty);
+
+	/*
+	 * Wait for the transmit buffer to clear
+	 */
+	if (info->closing_wait != ROCKET_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, info->closing_wait);
+	/*
+	 * Before we drop DTR, make sure the UART transmitter
+	 * has completely drained; this is especially
+	 * important if there is a transmit FIFO!
+	 */
+	timeout = (sGetTxCnt(cp) + 1) * HZ / info->cps;
+	if (timeout == 0)
+		timeout = 1;
+	rp_wait_until_sent(tty, timeout);
+	clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+	sDisTransmit(cp);
+	sDisInterrupts(cp, (TXINT_EN | MCINT_EN | RXINT_EN | SRCINT_EN | CHANINT_EN));
+	sDisCTSFlowCtl(cp);
+	sDisTxSoftFlowCtl(cp);
+	sClrTxXOFF(cp);
+	sFlushRxFIFO(cp);
+	sFlushTxFIFO(cp);
+	sClrRTS(cp);
+	if (C_HUPCL(tty))
+		sClrDTR(cp);
+
+	if (TTY_DRIVER_FLUSH_BUFFER_EXISTS(tty))
+		TTY_DRIVER_FLUSH_BUFFER(tty);
+		
+	tty_ldisc_flush(tty);
+
+	clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+	if (info->blocked_open) {
+		if (info->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(info->close_delay));
+		}
+		wake_up_interruptible(&info->open_wait);
+	} else {
+		if (info->xmit_buf) {
+			free_page((unsigned long) info->xmit_buf);
+			info->xmit_buf = NULL;
+		}
+	}
+	info->flags &= ~(ROCKET_INITIALIZED | ROCKET_CLOSING | ROCKET_NORMAL_ACTIVE);
+	tty->closing = 0;
+	wake_up_interruptible(&info->close_wait);
+	atomic_dec(&rp_num_ports_open);
+
+#ifdef ROCKET_DEBUG_OPEN
+	printk(KERN_INFO "rocket mod-- = %d...", atomic_read(&rp_num_ports_open));
+	printk(KERN_INFO "rp_close ttyR%d complete shutdown\n", info->line);
+#endif
+
+}
+
+static void rp_set_termios(struct tty_struct *tty,
+			   struct termios *old_termios)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+	unsigned cflag;
+
+	if (rocket_paranoia_check(info, "rp_set_termios"))
+		return;
+
+	cflag = tty->termios->c_cflag;
+
+	if (cflag == old_termios->c_cflag)
+		return;
+
+	/*
+	 * This driver doesn't support CS5 or CS6
+	 */
+	if (((cflag & CSIZE) == CS5) || ((cflag & CSIZE) == CS6))
+		tty->termios->c_cflag =
+		    ((cflag & ~CSIZE) | (old_termios->c_cflag & CSIZE));
+
+	configure_r_port(info, old_termios);
+
+	cp = &info->channel;
+
+	/* Handle transition to B0 status */
+	if ((old_termios->c_cflag & CBAUD) && !(tty->termios->c_cflag & CBAUD)) {
+		sClrDTR(cp);
+		sClrRTS(cp);
+	}
+
+	/* Handle transition away from B0 status */
+	if (!(old_termios->c_cflag & CBAUD) && (tty->termios->c_cflag & CBAUD)) {
+		if (!tty->hw_stopped || !(tty->termios->c_cflag & CRTSCTS))
+			sSetRTS(cp);
+		sSetDTR(cp);
+	}
+
+	if ((old_termios->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) {
+		tty->hw_stopped = 0;
+		rp_start(tty);
+	}
+}
+
+static void rp_break(struct tty_struct *tty, int break_state)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	unsigned long flags;
+
+	if (rocket_paranoia_check(info, "rp_break"))
+		return;
+
+	spin_lock_irqsave(&info->slock, flags);
+	if (break_state == -1)
+		sSendBreak(&info->channel);
+	else
+		sClrBreak(&info->channel);
+	spin_unlock_irqrestore(&info->slock, flags);
+}
+
+/*
+ * sGetChanRI used to be a macro in rocket_int.h. When the functionality for
+ * the UPCI boards was added, it was decided to make this a function because
+ * the macro was getting too complicated. All cases except the first one
+ * (UPCIRingInd) are taken directly from the original macro.
+ */
+static int sGetChanRI(CHANNEL_T * ChP)
+{
+	CONTROLLER_t *CtlP = ChP->CtlP;
+	int ChanNum = ChP->ChanNum;
+	int RingInd = 0;
+
+	if (CtlP->UPCIRingInd)
+		RingInd = !(sInB(CtlP->UPCIRingInd) & sBitMapSetTbl[ChanNum]);
+	else if (CtlP->AltChanRingIndicator)
+		RingInd = sInB((ByteIO_t) (ChP->ChanStat + 8)) & DSR_ACT;
+	else if (CtlP->boardType == ROCKET_TYPE_PC104)
+		RingInd = !(sInB(CtlP->AiopIO[3]) & sBitMapSetTbl[ChanNum]);
+
+	return RingInd;
+}
+
+/********************************************************************************************/
+/*  Here are the routines used by rp_ioctl.  These are all called from exception handlers.  */
+
+/*
+ *  Returns the state of the serial modem control lines.  These next 2 functions 
+ *  are the way kernel versions > 2.5 handle modem control lines rather than IOCTLs.
+ */
+static int rp_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct r_port *info = (struct r_port *)tty->driver_data;
+	unsigned int control, result, ChanStatus;
+
+	ChanStatus = sGetChanStatusLo(&info->channel);
+	control = info->channel.TxControl[3];
+	result = ((control & SET_RTS) ? TIOCM_RTS : 0) | 
+		((control & SET_DTR) ?  TIOCM_DTR : 0) |
+		((ChanStatus & CD_ACT) ? TIOCM_CAR : 0) |
+		(sGetChanRI(&info->channel) ? TIOCM_RNG : 0) |
+		((ChanStatus & DSR_ACT) ? TIOCM_DSR : 0) |
+		((ChanStatus & CTS_ACT) ? TIOCM_CTS : 0);
+
+	return result;
+}
+
+/* 
+ *  Sets the modem control lines
+ */
+static int rp_tiocmset(struct tty_struct *tty, struct file *file,
+		    unsigned int set, unsigned int clear)
+{
+	struct r_port *info = (struct r_port *)tty->driver_data;
+
+	if (set & TIOCM_RTS)
+		info->channel.TxControl[3] |= SET_RTS;
+	if (set & TIOCM_DTR)
+		info->channel.TxControl[3] |= SET_DTR;
+	if (clear & TIOCM_RTS)
+		info->channel.TxControl[3] &= ~SET_RTS;
+	if (clear & TIOCM_DTR)
+		info->channel.TxControl[3] &= ~SET_DTR;
+
+	sOutDW(info->channel.IndexAddr, *(DWord_t *) & (info->channel.TxControl[0]));
+	return 0;
+}
+
+static int get_config(struct r_port *info, struct rocket_config __user *retinfo)
+{
+	struct rocket_config tmp;
+
+	if (!retinfo)
+		return -EFAULT;
+	memset(&tmp, 0, sizeof (tmp));
+	tmp.line = info->line;
+	tmp.flags = info->flags;
+	tmp.close_delay = info->close_delay;
+	tmp.closing_wait = info->closing_wait;
+	tmp.port = rcktpt_io_addr[(info->line >> 5) & 3];
+
+	if (copy_to_user(retinfo, &tmp, sizeof (*retinfo)))
+		return -EFAULT;
+	return 0;
+}
+
+static int set_config(struct r_port *info, struct rocket_config __user *new_info)
+{
+	struct rocket_config new_serial;
+
+	if (copy_from_user(&new_serial, new_info, sizeof (new_serial)))
+		return -EFAULT;
+
+	if (!capable(CAP_SYS_ADMIN))
+	{
+		if ((new_serial.flags & ~ROCKET_USR_MASK) != (info->flags & ~ROCKET_USR_MASK))
+			return -EPERM;
+		info->flags = ((info->flags & ~ROCKET_USR_MASK) | (new_serial.flags & ROCKET_USR_MASK));
+		configure_r_port(info, NULL);
+		return 0;
+	}
+
+	info->flags = ((info->flags & ~ROCKET_FLAGS) | (new_serial.flags & ROCKET_FLAGS));
+	info->close_delay = new_serial.close_delay;
+	info->closing_wait = new_serial.closing_wait;
+
+	if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_HI)
+		info->tty->alt_speed = 57600;
+	if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_VHI)
+		info->tty->alt_speed = 115200;
+	if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_SHI)
+		info->tty->alt_speed = 230400;
+	if ((info->flags & ROCKET_SPD_MASK) == ROCKET_SPD_WARP)
+		info->tty->alt_speed = 460800;
+
+	configure_r_port(info, NULL);
+	return 0;
+}
+
+/*
+ *  This function fills in a rocket_ports struct with information
+ *  about what boards/ports are in the system.  This info is passed
+ *  to user space.  See setrocket.c where the info is used to create
+ *  the /dev/ttyRx ports.
+ */
+static int get_ports(struct r_port *info, struct rocket_ports __user *retports)
+{
+	struct rocket_ports tmp;
+	int board;
+
+	if (!retports)
+		return -EFAULT;
+	memset(&tmp, 0, sizeof (tmp));
+	tmp.tty_major = rocket_driver->major;
+
+	for (board = 0; board < 4; board++) {
+		tmp.rocketModel[board].model = rocketModel[board].model;
+		strcpy(tmp.rocketModel[board].modelString, rocketModel[board].modelString);
+		tmp.rocketModel[board].numPorts = rocketModel[board].numPorts;
+		tmp.rocketModel[board].loadrm2 = rocketModel[board].loadrm2;
+		tmp.rocketModel[board].startingPortNumber = rocketModel[board].startingPortNumber;
+	}
+	if (copy_to_user(retports, &tmp, sizeof (*retports)))
+		return -EFAULT;
+	return 0;
+}
+
+static int reset_rm2(struct r_port *info, void __user *arg)
+{
+	int reset;
+
+	if (copy_from_user(&reset, arg, sizeof (int)))
+		return -EFAULT;
+	if (reset)
+		reset = 1;
+
+	if (rcktpt_type[info->board] != ROCKET_TYPE_MODEMII &&
+            rcktpt_type[info->board] != ROCKET_TYPE_MODEMIII)
+		return -EINVAL;
+
+	if (info->ctlp->BusType == isISA)
+		sModemReset(info->ctlp, info->chan, reset);
+	else
+		sPCIModemReset(info->ctlp, info->chan, reset);
+
+	return 0;
+}
+
+static int get_version(struct r_port *info, struct rocket_version __user *retvers)
+{
+	if (copy_to_user(retvers, &driver_version, sizeof (*retvers)))
+		return -EFAULT;
+	return 0;
+}
+
+/*  IOCTL call handler into the driver */
+static int rp_ioctl(struct tty_struct *tty, struct file *file,
+		    unsigned int cmd, unsigned long arg)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	void __user *argp = (void __user *)arg;
+
+	if (cmd != RCKP_GET_PORTS && rocket_paranoia_check(info, "rp_ioctl"))
+		return -ENXIO;
+
+	switch (cmd) {
+	case RCKP_GET_STRUCT:
+		if (copy_to_user(argp, info, sizeof (struct r_port)))
+			return -EFAULT;
+		return 0;
+	case RCKP_GET_CONFIG:
+		return get_config(info, argp);
+	case RCKP_SET_CONFIG:
+		return set_config(info, argp);
+	case RCKP_GET_PORTS:
+		return get_ports(info, argp);
+	case RCKP_RESET_RM2:
+		return reset_rm2(info, argp);
+	case RCKP_GET_VERSION:
+		return get_version(info, argp);
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static void rp_send_xchar(struct tty_struct *tty, char ch)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+
+	if (rocket_paranoia_check(info, "rp_send_xchar"))
+		return;
+
+	cp = &info->channel;
+	if (sGetTxCnt(cp))
+		sWriteTxPrioByte(cp, ch);
+	else
+		sWriteTxByte(sGetTxRxDataIO(cp), ch);
+}
+
+static void rp_throttle(struct tty_struct *tty)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+
+#ifdef ROCKET_DEBUG_THROTTLE
+	printk(KERN_INFO "throttle %s: %d....\n", tty->name,
+	       tty->ldisc.chars_in_buffer(tty));
+#endif
+
+	if (rocket_paranoia_check(info, "rp_throttle"))
+		return;
+
+	cp = &info->channel;
+	if (I_IXOFF(tty))
+		rp_send_xchar(tty, STOP_CHAR(tty));
+
+	sClrRTS(&info->channel);
+}
+
+static void rp_unthrottle(struct tty_struct *tty)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+#ifdef ROCKET_DEBUG_THROTTLE
+	printk(KERN_INFO "unthrottle %s: %d....\n", tty->name,
+	       tty->ldisc.chars_in_buffer(tty));
+#endif
+
+	if (rocket_paranoia_check(info, "rp_throttle"))
+		return;
+
+	cp = &info->channel;
+	if (I_IXOFF(tty))
+		rp_send_xchar(tty, START_CHAR(tty));
+
+	sSetRTS(&info->channel);
+}
+
+/*
+ * ------------------------------------------------------------
+ * rp_stop() and rp_start()
+ *
+ * This routines are called before setting or resetting tty->stopped.
+ * They enable or disable transmitter interrupts, as necessary.
+ * ------------------------------------------------------------
+ */
+static void rp_stop(struct tty_struct *tty)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+
+#ifdef ROCKET_DEBUG_FLOW
+	printk(KERN_INFO "stop %s: %d %d....\n", tty->name,
+	       info->xmit_cnt, info->xmit_fifo_room);
+#endif
+
+	if (rocket_paranoia_check(info, "rp_stop"))
+		return;
+
+	if (sGetTxCnt(&info->channel))
+		sDisTransmit(&info->channel);
+}
+
+static void rp_start(struct tty_struct *tty)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+
+#ifdef ROCKET_DEBUG_FLOW
+	printk(KERN_INFO "start %s: %d %d....\n", tty->name,
+	       info->xmit_cnt, info->xmit_fifo_room);
+#endif
+
+	if (rocket_paranoia_check(info, "rp_stop"))
+		return;
+
+	sEnTransmit(&info->channel);
+	set_bit((info->aiop * 8) + info->chan,
+		(void *) &xmit_flags[info->board]);
+}
+
+/*
+ * rp_wait_until_sent() --- wait until the transmitter is empty
+ */
+static void rp_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+	unsigned long orig_jiffies;
+	int check_time, exit_time;
+	int txcnt;
+
+	if (rocket_paranoia_check(info, "rp_wait_until_sent"))
+		return;
+
+	cp = &info->channel;
+
+	orig_jiffies = jiffies;
+#ifdef ROCKET_DEBUG_WAIT_UNTIL_SENT
+	printk(KERN_INFO "In RP_wait_until_sent(%d) (jiff=%lu)...", timeout,
+	       jiffies);
+	printk(KERN_INFO "cps=%d...", info->cps);
+#endif
+	while (1) {
+		txcnt = sGetTxCnt(cp);
+		if (!txcnt) {
+			if (sGetChanStatusLo(cp) & TXSHRMT)
+				break;
+			check_time = (HZ / info->cps) / 5;
+		} else {
+			check_time = HZ * txcnt / info->cps;
+		}
+		if (timeout) {
+			exit_time = orig_jiffies + timeout - jiffies;
+			if (exit_time <= 0)
+				break;
+			if (exit_time < check_time)
+				check_time = exit_time;
+		}
+		if (check_time == 0)
+			check_time = 1;
+#ifdef ROCKET_DEBUG_WAIT_UNTIL_SENT
+		printk(KERN_INFO "txcnt = %d (jiff=%lu,check=%d)...", txcnt, jiffies, check_time);
+#endif
+		msleep_interruptible(jiffies_to_msecs(check_time));
+		if (signal_pending(current))
+			break;
+	}
+	current->state = TASK_RUNNING;
+#ifdef ROCKET_DEBUG_WAIT_UNTIL_SENT
+	printk(KERN_INFO "txcnt = %d (jiff=%lu)...done\n", txcnt, jiffies);
+#endif
+}
+
+/*
+ * rp_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+static void rp_hangup(struct tty_struct *tty)
+{
+	CHANNEL_t *cp;
+	struct r_port *info = (struct r_port *) tty->driver_data;
+
+	if (rocket_paranoia_check(info, "rp_hangup"))
+		return;
+
+#if (defined(ROCKET_DEBUG_OPEN) || defined(ROCKET_DEBUG_HANGUP))
+	printk(KERN_INFO "rp_hangup of ttyR%d...", info->line);
+#endif
+	rp_flush_buffer(tty);
+	if (info->flags & ROCKET_CLOSING)
+		return;
+	if (info->count) 
+		atomic_dec(&rp_num_ports_open);
+	clear_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+
+	info->count = 0;
+	info->flags &= ~ROCKET_NORMAL_ACTIVE;
+	info->tty = NULL;
+
+	cp = &info->channel;
+	sDisRxFIFO(cp);
+	sDisTransmit(cp);
+	sDisInterrupts(cp, (TXINT_EN | MCINT_EN | RXINT_EN | SRCINT_EN | CHANINT_EN));
+	sDisCTSFlowCtl(cp);
+	sDisTxSoftFlowCtl(cp);
+	sClrTxXOFF(cp);
+	info->flags &= ~ROCKET_INITIALIZED;
+
+	wake_up_interruptible(&info->open_wait);
+}
+
+/*
+ *  Exception handler - write char routine.  The RocketPort driver uses a
+ *  double-buffering strategy, with the twist that if the in-memory CPU
+ *  buffer is empty, and there's space in the transmit FIFO, the
+ *  writing routines will write directly to transmit FIFO.
+ *  Write buffer and counters protected by spinlocks
+ */
+static void rp_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+	unsigned long flags;
+
+	if (rocket_paranoia_check(info, "rp_put_char"))
+		return;
+
+	/*  Grab the port write semaphore, locking out other processes that try to write to this port */
+	down(&info->write_sem);
+
+#ifdef ROCKET_DEBUG_WRITE
+	printk(KERN_INFO "rp_put_char %c...", ch);
+#endif
+
+	spin_lock_irqsave(&info->slock, flags);
+	cp = &info->channel;
+
+	if (!tty->stopped && !tty->hw_stopped && info->xmit_fifo_room == 0)
+		info->xmit_fifo_room = TXFIFO_SIZE - sGetTxCnt(cp);
+
+	if (tty->stopped || tty->hw_stopped || info->xmit_fifo_room == 0 || info->xmit_cnt != 0) {
+		info->xmit_buf[info->xmit_head++] = ch;
+		info->xmit_head &= XMIT_BUF_SIZE - 1;
+		info->xmit_cnt++;
+		set_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+	} else {
+		sOutB(sGetTxRxDataIO(cp), ch);
+		info->xmit_fifo_room--;
+	}
+	spin_unlock_irqrestore(&info->slock, flags);
+	up(&info->write_sem);
+}
+
+/*
+ *  Exception handler - write routine, called when user app writes to the device.
+ *  A per port write semaphore is used to protect from another process writing to
+ *  this port at the same time.  This other process could be running on the other CPU
+ *  or get control of the CPU if the copy_from_user() blocks due to a page fault (swapped out). 
+ *  Spinlocks protect the info xmit members.
+ */
+static int rp_write(struct tty_struct *tty,
+		    const unsigned char *buf, int count)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+	const unsigned char *b;
+	int c, retval = 0;
+	unsigned long flags;
+
+	if (count <= 0 || rocket_paranoia_check(info, "rp_write"))
+		return 0;
+
+	down_interruptible(&info->write_sem);
+
+#ifdef ROCKET_DEBUG_WRITE
+	printk(KERN_INFO "rp_write %d chars...", count);
+#endif
+	cp = &info->channel;
+
+	if (!tty->stopped && !tty->hw_stopped && info->xmit_fifo_room < count)
+		info->xmit_fifo_room = TXFIFO_SIZE - sGetTxCnt(cp);
+
+        /*
+	 *  If the write queue for the port is empty, and there is FIFO space, stuff bytes 
+	 *  into FIFO.  Use the write queue for temp storage.
+         */
+	if (!tty->stopped && !tty->hw_stopped && info->xmit_cnt == 0 && info->xmit_fifo_room > 0) {
+		c = min(count, info->xmit_fifo_room);
+		b = buf;
+
+		/*  Push data into FIFO, 2 bytes at a time */
+		sOutStrW(sGetTxRxDataIO(cp), (unsigned short *) b, c / 2);
+
+		/*  If there is a byte remaining, write it */
+		if (c & 1)
+			sOutB(sGetTxRxDataIO(cp), b[c - 1]);
+
+		retval += c;
+		buf += c;
+		count -= c;
+
+		spin_lock_irqsave(&info->slock, flags);
+		info->xmit_fifo_room -= c;
+		spin_unlock_irqrestore(&info->slock, flags);
+	}
+
+	/* If count is zero, we wrote it all and are done */
+	if (!count)
+		goto end;
+
+	/*  Write remaining data into the port's xmit_buf */
+	while (1) {
+		if (info->tty == 0)	/*   Seemingly obligatory check... */
+			goto end;
+
+		c = min(count, min(XMIT_BUF_SIZE - info->xmit_cnt - 1, XMIT_BUF_SIZE - info->xmit_head));
+		if (c <= 0)
+			break;
+
+		b = buf;
+		memcpy(info->xmit_buf + info->xmit_head, b, c);
+
+		spin_lock_irqsave(&info->slock, flags);
+		info->xmit_head =
+		    (info->xmit_head + c) & (XMIT_BUF_SIZE - 1);
+		info->xmit_cnt += c;
+		spin_unlock_irqrestore(&info->slock, flags);
+
+		buf += c;
+		count -= c;
+		retval += c;
+	}
+
+	if ((retval > 0) && !tty->stopped && !tty->hw_stopped)
+		set_bit((info->aiop * 8) + info->chan, (void *) &xmit_flags[info->board]);
+	
+end:
+ 	if (info->xmit_cnt < WAKEUP_CHARS) {
+ 		tty_wakeup(tty);
+		wake_up_interruptible(&tty->write_wait);
+#ifdef ROCKETPORT_HAVE_POLL_WAIT
+		wake_up_interruptible(&tty->poll_wait);
+#endif
+	}
+	up(&info->write_sem);
+	return retval;
+}
+
+/*
+ * Return the number of characters that can be sent.  We estimate
+ * only using the in-memory transmit buffer only, and ignore the
+ * potential space in the transmit FIFO.
+ */
+static int rp_write_room(struct tty_struct *tty)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	int ret;
+
+	if (rocket_paranoia_check(info, "rp_write_room"))
+		return 0;
+
+	ret = XMIT_BUF_SIZE - info->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+#ifdef ROCKET_DEBUG_WRITE
+	printk(KERN_INFO "rp_write_room returns %d...", ret);
+#endif
+	return ret;
+}
+
+/*
+ * Return the number of characters in the buffer.  Again, this only
+ * counts those characters in the in-memory transmit buffer.
+ */
+static int rp_chars_in_buffer(struct tty_struct *tty)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+
+	if (rocket_paranoia_check(info, "rp_chars_in_buffer"))
+		return 0;
+
+	cp = &info->channel;
+
+#ifdef ROCKET_DEBUG_WRITE
+	printk(KERN_INFO "rp_chars_in_buffer returns %d...", info->xmit_cnt);
+#endif
+	return info->xmit_cnt;
+}
+
+/*
+ *  Flushes the TX fifo for a port, deletes data in the xmit_buf stored in the
+ *  r_port struct for the port.  Note that spinlock are used to protect info members,
+ *  do not call this function if the spinlock is already held.
+ */
+static void rp_flush_buffer(struct tty_struct *tty)
+{
+	struct r_port *info = (struct r_port *) tty->driver_data;
+	CHANNEL_t *cp;
+	unsigned long flags;
+
+	if (rocket_paranoia_check(info, "rp_flush_buffer"))
+		return;
+
+	spin_lock_irqsave(&info->slock, flags);
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+	spin_unlock_irqrestore(&info->slock, flags);
+
+	wake_up_interruptible(&tty->write_wait);
+#ifdef ROCKETPORT_HAVE_POLL_WAIT
+	wake_up_interruptible(&tty->poll_wait);
+#endif
+	tty_wakeup(tty);
+
+	cp = &info->channel;
+	sFlushTxFIFO(cp);
+}
+
+#ifdef CONFIG_PCI
+
+/*
+ *  Called when a PCI card is found.  Retrieves and stores model information,
+ *  init's aiopic and serial port hardware.
+ *  Inputs:  i is the board number (0-n)
+ */
+__init int register_PCI(int i, struct pci_dev *dev)
+{
+	int num_aiops, aiop, max_num_aiops, num_chan, chan;
+	unsigned int aiopio[MAX_AIOPS_PER_BOARD];
+	char *str, *board_type;
+	CONTROLLER_t *ctlp;
+
+	int fast_clock = 0;
+	int altChanRingIndicator = 0;
+	int ports_per_aiop = 8;
+	int ret;
+	unsigned int class_rev;
+	WordIO_t ConfigIO = 0;
+	ByteIO_t UPCIRingInd = 0;
+
+	if (!dev || pci_enable_device(dev))
+		return 0;
+
+	rcktpt_io_addr[i] = pci_resource_start(dev, 0);
+	ret = pci_read_config_dword(dev, PCI_CLASS_REVISION, &class_rev);
+
+	if (ret) {
+		printk(KERN_INFO "  Error during register_PCI(), unable to read config dword \n");
+		return 0;
+	}
+
+	rcktpt_type[i] = ROCKET_TYPE_NORMAL;
+	rocketModel[i].loadrm2 = 0;
+	rocketModel[i].startingPortNumber = nextLineNumber;
+
+	/*  Depending on the model, set up some config variables */
+	switch (dev->device) {
+	case PCI_DEVICE_ID_RP4QUAD:
+		str = "Quadcable";
+		max_num_aiops = 1;
+		ports_per_aiop = 4;
+		rocketModel[i].model = MODEL_RP4QUAD;
+		strcpy(rocketModel[i].modelString, "RocketPort 4 port w/quad cable");
+		rocketModel[i].numPorts = 4;
+		break;
+	case PCI_DEVICE_ID_RP8OCTA:
+		str = "Octacable";
+		max_num_aiops = 1;
+		rocketModel[i].model = MODEL_RP8OCTA;
+		strcpy(rocketModel[i].modelString, "RocketPort 8 port w/octa cable");
+		rocketModel[i].numPorts = 8;
+		break;
+	case PCI_DEVICE_ID_URP8OCTA:
+		str = "Octacable";
+		max_num_aiops = 1;
+		rocketModel[i].model = MODEL_UPCI_RP8OCTA;
+		strcpy(rocketModel[i].modelString, "RocketPort UPCI 8 port w/octa cable");
+		rocketModel[i].numPorts = 8;
+		break;
+	case PCI_DEVICE_ID_RP8INTF:
+		str = "8";
+		max_num_aiops = 1;
+		rocketModel[i].model = MODEL_RP8INTF;
+		strcpy(rocketModel[i].modelString, "RocketPort 8 port w/external I/F");
+		rocketModel[i].numPorts = 8;
+		break;
+	case PCI_DEVICE_ID_URP8INTF:
+		str = "8";
+		max_num_aiops = 1;
+		rocketModel[i].model = MODEL_UPCI_RP8INTF;
+		strcpy(rocketModel[i].modelString, "RocketPort UPCI 8 port w/external I/F");
+		rocketModel[i].numPorts = 8;
+		break;
+	case PCI_DEVICE_ID_RP8J:
+		str = "8J";
+		max_num_aiops = 1;
+		rocketModel[i].model = MODEL_RP8J;
+		strcpy(rocketModel[i].modelString, "RocketPort 8 port w/RJ11 connectors");
+		rocketModel[i].numPorts = 8;
+		break;
+	case PCI_DEVICE_ID_RP4J:
+		str = "4J";
+		max_num_aiops = 1;
+		ports_per_aiop = 4;
+		rocketModel[i].model = MODEL_RP4J;
+		strcpy(rocketModel[i].modelString, "RocketPort 4 port w/RJ45 connectors");
+		rocketModel[i].numPorts = 4;
+		break;
+	case PCI_DEVICE_ID_RP8SNI:
+		str = "8 (DB78 Custom)";
+		max_num_aiops = 1;
+		rocketModel[i].model = MODEL_RP8SNI;
+		strcpy(rocketModel[i].modelString, "RocketPort 8 port w/ custom DB78");
+		rocketModel[i].numPorts = 8;
+		break;
+	case PCI_DEVICE_ID_RP16SNI:
+		str = "16 (DB78 Custom)";
+		max_num_aiops = 2;
+		rocketModel[i].model = MODEL_RP16SNI;
+		strcpy(rocketModel[i].modelString, "RocketPort 16 port w/ custom DB78");
+		rocketModel[i].numPorts = 16;
+		break;
+	case PCI_DEVICE_ID_RP16INTF:
+		str = "16";
+		max_num_aiops = 2;
+		rocketModel[i].model = MODEL_RP16INTF;
+		strcpy(rocketModel[i].modelString, "RocketPort 16 port w/external I/F");
+		rocketModel[i].numPorts = 16;
+		break;
+	case PCI_DEVICE_ID_URP16INTF:
+		str = "16";
+		max_num_aiops = 2;
+		rocketModel[i].model = MODEL_UPCI_RP16INTF;
+		strcpy(rocketModel[i].modelString, "RocketPort UPCI 16 port w/external I/F");
+		rocketModel[i].numPorts = 16;
+		break;
+	case PCI_DEVICE_ID_CRP16INTF:
+		str = "16";
+		max_num_aiops = 2;
+		rocketModel[i].model = MODEL_CPCI_RP16INTF;
+		strcpy(rocketModel[i].modelString, "RocketPort Compact PCI 16 port w/external I/F");
+		rocketModel[i].numPorts = 16;
+		break;
+	case PCI_DEVICE_ID_RP32INTF:
+		str = "32";
+		max_num_aiops = 4;
+		rocketModel[i].model = MODEL_RP32INTF;
+		strcpy(rocketModel[i].modelString, "RocketPort 32 port w/external I/F");
+		rocketModel[i].numPorts = 32;
+		break;
+	case PCI_DEVICE_ID_URP32INTF:
+		str = "32";
+		max_num_aiops = 4;
+		rocketModel[i].model = MODEL_UPCI_RP32INTF;
+		strcpy(rocketModel[i].modelString, "RocketPort UPCI 32 port w/external I/F");
+		rocketModel[i].numPorts = 32;
+		break;
+	case PCI_DEVICE_ID_RPP4:
+		str = "Plus Quadcable";
+		max_num_aiops = 1;
+		ports_per_aiop = 4;
+		altChanRingIndicator++;
+		fast_clock++;
+		rocketModel[i].model = MODEL_RPP4;
+		strcpy(rocketModel[i].modelString, "RocketPort Plus 4 port");
+		rocketModel[i].numPorts = 4;
+		break;
+	case PCI_DEVICE_ID_RPP8:
+		str = "Plus Octacable";
+		max_num_aiops = 2;
+		ports_per_aiop = 4;
+		altChanRingIndicator++;
+		fast_clock++;
+		rocketModel[i].model = MODEL_RPP8;
+		strcpy(rocketModel[i].modelString, "RocketPort Plus 8 port");
+		rocketModel[i].numPorts = 8;
+		break;
+	case PCI_DEVICE_ID_RP2_232:
+		str = "Plus 2 (RS-232)";
+		max_num_aiops = 1;
+		ports_per_aiop = 2;
+		altChanRingIndicator++;
+		fast_clock++;
+		rocketModel[i].model = MODEL_RP2_232;
+		strcpy(rocketModel[i].modelString, "RocketPort Plus 2 port RS232");
+		rocketModel[i].numPorts = 2;
+		break;
+	case PCI_DEVICE_ID_RP2_422:
+		str = "Plus 2 (RS-422)";
+		max_num_aiops = 1;
+		ports_per_aiop = 2;
+		altChanRingIndicator++;
+		fast_clock++;
+		rocketModel[i].model = MODEL_RP2_422;
+		strcpy(rocketModel[i].modelString, "RocketPort Plus 2 port RS422");
+		rocketModel[i].numPorts = 2;
+		break;
+	case PCI_DEVICE_ID_RP6M:
+
+		max_num_aiops = 1;
+		ports_per_aiop = 6;
+		str = "6-port";
+
+		/*  If class_rev is 1, the rocketmodem flash must be loaded.  If it is 2 it is a "socketed" version. */
+		if ((class_rev & 0xFF) == 1) {
+			rcktpt_type[i] = ROCKET_TYPE_MODEMII;
+			rocketModel[i].loadrm2 = 1;
+		} else {
+			rcktpt_type[i] = ROCKET_TYPE_MODEM;
+		}
+
+		rocketModel[i].model = MODEL_RP6M;
+		strcpy(rocketModel[i].modelString, "RocketModem 6 port");
+		rocketModel[i].numPorts = 6;
+		break;
+	case PCI_DEVICE_ID_RP4M:
+		max_num_aiops = 1;
+		ports_per_aiop = 4;
+		str = "4-port";
+		if ((class_rev & 0xFF) == 1) {
+			rcktpt_type[i] = ROCKET_TYPE_MODEMII;
+			rocketModel[i].loadrm2 = 1;
+		} else {
+			rcktpt_type[i] = ROCKET_TYPE_MODEM;
+		}
+
+		rocketModel[i].model = MODEL_RP4M;
+		strcpy(rocketModel[i].modelString, "RocketModem 4 port");
+		rocketModel[i].numPorts = 4;
+		break;
+	default:
+		str = "(unknown/unsupported)";
+		max_num_aiops = 0;
+		break;
+	}
+
+	/*
+	 * Check for UPCI boards.
+	 */
+
+	switch (dev->device) {
+	case PCI_DEVICE_ID_URP32INTF:
+	case PCI_DEVICE_ID_URP8INTF:
+	case PCI_DEVICE_ID_URP16INTF:
+	case PCI_DEVICE_ID_CRP16INTF:
+	case PCI_DEVICE_ID_URP8OCTA:
+		rcktpt_io_addr[i] = pci_resource_start(dev, 2);
+		ConfigIO = pci_resource_start(dev, 1);
+		if (dev->device == PCI_DEVICE_ID_URP8OCTA) {
+			UPCIRingInd = rcktpt_io_addr[i] + _PCI_9030_RING_IND;
+
+			/*
+			 * Check for octa or quad cable.
+			 */
+			if (!
+			    (sInW(ConfigIO + _PCI_9030_GPIO_CTRL) &
+			     PCI_GPIO_CTRL_8PORT)) {
+				str = "Quadcable";
+				ports_per_aiop = 4;
+				rocketModel[i].numPorts = 4;
+			}
+		}
+		break;
+	case PCI_DEVICE_ID_UPCI_RM3_8PORT:
+		str = "8 ports";
+		max_num_aiops = 1;
+		rocketModel[i].model = MODEL_UPCI_RM3_8PORT;
+		strcpy(rocketModel[i].modelString, "RocketModem III 8 port");
+		rocketModel[i].numPorts = 8;
+		rcktpt_io_addr[i] = pci_resource_start(dev, 2);
+		UPCIRingInd = rcktpt_io_addr[i] + _PCI_9030_RING_IND;
+		ConfigIO = pci_resource_start(dev, 1);
+		rcktpt_type[i] = ROCKET_TYPE_MODEMIII;
+		break;
+	case PCI_DEVICE_ID_UPCI_RM3_4PORT:
+		str = "4 ports";
+		max_num_aiops = 1;
+		rocketModel[i].model = MODEL_UPCI_RM3_4PORT;
+		strcpy(rocketModel[i].modelString, "RocketModem III 4 port");
+		rocketModel[i].numPorts = 4;
+		rcktpt_io_addr[i] = pci_resource_start(dev, 2);
+		UPCIRingInd = rcktpt_io_addr[i] + _PCI_9030_RING_IND;
+		ConfigIO = pci_resource_start(dev, 1);
+		rcktpt_type[i] = ROCKET_TYPE_MODEMIII;
+		break;
+	default:
+		break;
+	}
+
+	switch (rcktpt_type[i]) {
+	case ROCKET_TYPE_MODEM:
+		board_type = "RocketModem";
+		break;
+	case ROCKET_TYPE_MODEMII:
+		board_type = "RocketModem II";
+		break;
+	case ROCKET_TYPE_MODEMIII:
+		board_type = "RocketModem III";
+		break;
+	default:
+		board_type = "RocketPort";
+		break;
+	}
+
+	if (fast_clock) {
+		sClockPrescale = 0x12;	/* mod 2 (divide by 3) */
+		rp_baud_base[i] = 921600;
+	} else {
+		/*
+		 * If support_low_speed is set, use the slow clock
+		 * prescale, which supports 50 bps
+		 */
+		if (support_low_speed) {
+			/* mod 9 (divide by 10) prescale */
+			sClockPrescale = 0x19;
+			rp_baud_base[i] = 230400;
+		} else {
+			/* mod 4 (devide by 5) prescale */
+			sClockPrescale = 0x14;
+			rp_baud_base[i] = 460800;
+		}
+	}
+
+	for (aiop = 0; aiop < max_num_aiops; aiop++)
+		aiopio[aiop] = rcktpt_io_addr[i] + (aiop * 0x40);
+	ctlp = sCtlNumToCtlPtr(i);
+	num_aiops = sPCIInitController(ctlp, i, aiopio, max_num_aiops, ConfigIO, 0, FREQ_DIS, 0, altChanRingIndicator, UPCIRingInd);
+	for (aiop = 0; aiop < max_num_aiops; aiop++)
+		ctlp->AiopNumChan[aiop] = ports_per_aiop;
+
+	printk("Comtrol PCI controller #%d ID 0x%x found in bus:slot:fn %s at address %04lx, "
+	     "%d AIOP(s) (%s)\n", i, dev->device, pci_name(dev),
+	     rcktpt_io_addr[i], num_aiops, rocketModel[i].modelString);
+	printk(KERN_INFO "Installing %s, creating /dev/ttyR%d - %ld\n",
+	       rocketModel[i].modelString,
+	       rocketModel[i].startingPortNumber,
+	       rocketModel[i].startingPortNumber +
+	       rocketModel[i].numPorts - 1);
+
+	if (num_aiops <= 0) {
+		rcktpt_io_addr[i] = 0;
+		return (0);
+	}
+	is_PCI[i] = 1;
+
+	/*  Reset the AIOPIC, init the serial ports */
+	for (aiop = 0; aiop < num_aiops; aiop++) {
+		sResetAiopByNum(ctlp, aiop);
+		num_chan = ports_per_aiop;
+		for (chan = 0; chan < num_chan; chan++)
+			init_r_port(i, aiop, chan, dev);
+	}
+
+	/*  Rocket modems must be reset */
+	if ((rcktpt_type[i] == ROCKET_TYPE_MODEM) ||
+	    (rcktpt_type[i] == ROCKET_TYPE_MODEMII) ||
+	    (rcktpt_type[i] == ROCKET_TYPE_MODEMIII)) {
+		num_chan = ports_per_aiop;
+		for (chan = 0; chan < num_chan; chan++)
+			sPCIModemReset(ctlp, chan, 1);
+		mdelay(500);
+		for (chan = 0; chan < num_chan; chan++)
+			sPCIModemReset(ctlp, chan, 0);
+		mdelay(500);
+		rmSpeakerReset(ctlp, rocketModel[i].model);
+	}
+	return (1);
+}
+
+/*
+ *  Probes for PCI cards, inits them if found
+ *  Input:   board_found = number of ISA boards already found, or the
+ *           starting board number
+ *  Returns: Number of PCI boards found
+ */
+static int __init init_PCI(int boards_found)
+{
+	struct pci_dev *dev = NULL;
+	int count = 0;
+
+	/*  Work through the PCI device list, pulling out ours */
+	while ((dev = pci_find_device(PCI_VENDOR_ID_RP, PCI_ANY_ID, dev))) {
+		if (register_PCI(count + boards_found, dev))
+			count++;
+	}
+	return (count);
+}
+
+#endif				/* CONFIG_PCI */
+
+/*
+ *  Probes for ISA cards
+ *  Input:   i = the board number to look for
+ *  Returns: 1 if board found, 0 else
+ */
+static int __init init_ISA(int i)
+{
+	int num_aiops, num_chan = 0, total_num_chan = 0;
+	int aiop, chan;
+	unsigned int aiopio[MAX_AIOPS_PER_BOARD];
+	CONTROLLER_t *ctlp;
+	char *type_string;
+
+	/*  If io_addr is zero, no board configured */
+	if (rcktpt_io_addr[i] == 0)
+		return (0);
+
+	/*  Reserve the IO region */
+	if (!request_region(rcktpt_io_addr[i], 64, "Comtrol RocketPort")) {
+		printk(KERN_INFO "Unable to reserve IO region for configured ISA RocketPort at address 0x%lx, board not installed...\n", rcktpt_io_addr[i]);
+		rcktpt_io_addr[i] = 0;
+		return (0);
+	}
+
+	ctlp = sCtlNumToCtlPtr(i);
+
+	ctlp->boardType = rcktpt_type[i];
+
+	switch (rcktpt_type[i]) {
+	case ROCKET_TYPE_PC104:
+		type_string = "(PC104)";
+		break;
+	case ROCKET_TYPE_MODEM:
+		type_string = "(RocketModem)";
+		break;
+	case ROCKET_TYPE_MODEMII:
+		type_string = "(RocketModem II)";
+		break;
+	default:
+		type_string = "";
+		break;
+	}
+
+	/*
+	 * If support_low_speed is set, use the slow clock prescale,
+	 * which supports 50 bps
+	 */
+	if (support_low_speed) {
+		sClockPrescale = 0x19;	/* mod 9 (divide by 10) prescale */
+		rp_baud_base[i] = 230400;
+	} else {
+		sClockPrescale = 0x14;	/* mod 4 (devide by 5) prescale */
+		rp_baud_base[i] = 460800;
+	}
+
+	for (aiop = 0; aiop < MAX_AIOPS_PER_BOARD; aiop++)
+		aiopio[aiop] = rcktpt_io_addr[i] + (aiop * 0x400);
+
+	num_aiops = sInitController(ctlp, i, controller + (i * 0x400), aiopio,  MAX_AIOPS_PER_BOARD, 0, FREQ_DIS, 0);
+
+	if (ctlp->boardType == ROCKET_TYPE_PC104) {
+		sEnAiop(ctlp, 2);	/* only one AIOPIC, but these */
+		sEnAiop(ctlp, 3);	/* CSels used for other stuff */
+	}
+
+	/*  If something went wrong initing the AIOP's release the ISA IO memory */
+	if (num_aiops <= 0) {
+		release_region(rcktpt_io_addr[i], 64);
+		rcktpt_io_addr[i] = 0;
+		return (0);
+	}
+  
+	rocketModel[i].startingPortNumber = nextLineNumber;
+
+	for (aiop = 0; aiop < num_aiops; aiop++) {
+		sResetAiopByNum(ctlp, aiop);
+		sEnAiop(ctlp, aiop);
+		num_chan = sGetAiopNumChan(ctlp, aiop);
+		total_num_chan += num_chan;
+		for (chan = 0; chan < num_chan; chan++)
+			init_r_port(i, aiop, chan, NULL);
+	}
+	is_PCI[i] = 0;
+	if ((rcktpt_type[i] == ROCKET_TYPE_MODEM) || (rcktpt_type[i] == ROCKET_TYPE_MODEMII)) {
+		num_chan = sGetAiopNumChan(ctlp, 0);
+		total_num_chan = num_chan;
+		for (chan = 0; chan < num_chan; chan++)
+			sModemReset(ctlp, chan, 1);
+		mdelay(500);
+		for (chan = 0; chan < num_chan; chan++)
+			sModemReset(ctlp, chan, 0);
+		mdelay(500);
+		strcpy(rocketModel[i].modelString, "RocketModem ISA");
+	} else {
+		strcpy(rocketModel[i].modelString, "RocketPort ISA");
+	}
+	rocketModel[i].numPorts = total_num_chan;
+	rocketModel[i].model = MODEL_ISA;
+
+	printk(KERN_INFO "RocketPort ISA card #%d found at 0x%lx - %d AIOPs %s\n", 
+	       i, rcktpt_io_addr[i], num_aiops, type_string);
+
+	printk(KERN_INFO "Installing %s, creating /dev/ttyR%d - %ld\n",
+	       rocketModel[i].modelString,
+	       rocketModel[i].startingPortNumber,
+	       rocketModel[i].startingPortNumber +
+	       rocketModel[i].numPorts - 1);
+
+	return (1);
+}
+
+static struct tty_operations rocket_ops = {
+	.open = rp_open,
+	.close = rp_close,
+	.write = rp_write,
+	.put_char = rp_put_char,
+	.write_room = rp_write_room,
+	.chars_in_buffer = rp_chars_in_buffer,
+	.flush_buffer = rp_flush_buffer,
+	.ioctl = rp_ioctl,
+	.throttle = rp_throttle,
+	.unthrottle = rp_unthrottle,
+	.set_termios = rp_set_termios,
+	.stop = rp_stop,
+	.start = rp_start,
+	.hangup = rp_hangup,
+	.break_ctl = rp_break,
+	.send_xchar = rp_send_xchar,
+	.wait_until_sent = rp_wait_until_sent,
+	.tiocmget = rp_tiocmget,
+	.tiocmset = rp_tiocmset,
+};
+
+/*
+ * The module "startup" routine; it's run when the module is loaded.
+ */
+int __init rp_init(void)
+{
+	int retval, pci_boards_found, isa_boards_found, i;
+
+	printk(KERN_INFO "RocketPort device driver module, version %s, %s\n",
+	       ROCKET_VERSION, ROCKET_DATE);
+
+	rocket_driver = alloc_tty_driver(MAX_RP_PORTS);
+	if (!rocket_driver)
+		return -ENOMEM;
+
+	/*
+	 * Set up the timer channel.
+	 */
+	init_timer(&rocket_timer);
+	rocket_timer.function = rp_do_poll;
+
+	/*
+	 * Initialize the array of pointers to our own internal state
+	 * structures.
+	 */
+	memset(rp_table, 0, sizeof (rp_table));
+	memset(xmit_flags, 0, sizeof (xmit_flags));
+
+	for (i = 0; i < MAX_RP_PORTS; i++)
+		lineNumbers[i] = 0;
+	nextLineNumber = 0;
+	memset(rocketModel, 0, sizeof (rocketModel));
+
+	/*
+	 *  If board 1 is non-zero, there is at least one ISA configured.  If controller is 
+	 *  zero, use the default controller IO address of board1 + 0x40.
+	 */
+	if (board1) {
+		if (controller == 0)
+			controller = board1 + 0x40;
+	} else {
+		controller = 0;  /*  Used as a flag, meaning no ISA boards */
+	}
+
+	/*  If an ISA card is configured, reserve the 4 byte IO space for the Mudbac controller */
+	if (controller && (!request_region(controller, 4, "Comtrol RocketPort"))) {
+		printk(KERN_INFO "Unable to reserve IO region for first configured ISA RocketPort controller 0x%lx.  Driver exiting \n", controller);
+		return -EBUSY;
+	}
+
+	/*  Store ISA variable retrieved from command line or .conf file. */
+	rcktpt_io_addr[0] = board1;
+	rcktpt_io_addr[1] = board2;
+	rcktpt_io_addr[2] = board3;
+	rcktpt_io_addr[3] = board4;
+
+	rcktpt_type[0] = modem1 ? ROCKET_TYPE_MODEM : ROCKET_TYPE_NORMAL;
+	rcktpt_type[0] = pc104_1[0] ? ROCKET_TYPE_PC104 : rcktpt_type[0];
+	rcktpt_type[1] = modem2 ? ROCKET_TYPE_MODEM : ROCKET_TYPE_NORMAL;
+	rcktpt_type[1] = pc104_2[0] ? ROCKET_TYPE_PC104 : rcktpt_type[1];
+	rcktpt_type[2] = modem3 ? ROCKET_TYPE_MODEM : ROCKET_TYPE_NORMAL;
+	rcktpt_type[2] = pc104_3[0] ? ROCKET_TYPE_PC104 : rcktpt_type[2];
+	rcktpt_type[3] = modem4 ? ROCKET_TYPE_MODEM : ROCKET_TYPE_NORMAL;
+	rcktpt_type[3] = pc104_4[0] ? ROCKET_TYPE_PC104 : rcktpt_type[3];
+
+	/*
+	 * Set up the tty driver structure and then register this
+	 * driver with the tty layer.
+	 */
+
+	rocket_driver->owner = THIS_MODULE;
+	rocket_driver->flags = TTY_DRIVER_NO_DEVFS;
+	rocket_driver->devfs_name = "tts/R";
+	rocket_driver->name = "ttyR";
+	rocket_driver->driver_name = "Comtrol RocketPort";
+	rocket_driver->major = TTY_ROCKET_MAJOR;
+	rocket_driver->minor_start = 0;
+	rocket_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	rocket_driver->subtype = SERIAL_TYPE_NORMAL;
+	rocket_driver->init_termios = tty_std_termios;
+	rocket_driver->init_termios.c_cflag =
+	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+#ifdef ROCKET_SOFT_FLOW
+	rocket_driver->flags |= TTY_DRIVER_REAL_RAW | TTY_DRIVER_NO_DEVFS;
+#endif
+	tty_set_operations(rocket_driver, &rocket_ops);
+
+	retval = tty_register_driver(rocket_driver);
+	if (retval < 0) {
+		printk(KERN_INFO "Couldn't install tty RocketPort driver (error %d)\n", -retval);
+		put_tty_driver(rocket_driver);
+		return -1;
+	}
+
+#ifdef ROCKET_DEBUG_OPEN
+	printk(KERN_INFO "RocketPort driver is major %d\n", rocket_driver.major);
+#endif
+
+	/*
+	 *  OK, let's probe each of the controllers looking for boards.  Any boards found
+         *  will be initialized here.
+	 */
+	isa_boards_found = 0;
+	pci_boards_found = 0;
+
+	for (i = 0; i < NUM_BOARDS; i++) {
+		if (init_ISA(i))
+			isa_boards_found++;
+	}
+
+#ifdef CONFIG_PCI
+	if (isa_boards_found < NUM_BOARDS)
+		pci_boards_found = init_PCI(isa_boards_found);
+#endif
+
+	max_board = pci_boards_found + isa_boards_found;
+
+	if (max_board == 0) {
+		printk(KERN_INFO "No rocketport ports found; unloading driver.\n");
+		del_timer_sync(&rocket_timer);
+		tty_unregister_driver(rocket_driver);
+		put_tty_driver(rocket_driver);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+#ifdef MODULE
+
+static void rp_cleanup_module(void)
+{
+	int retval;
+	int i;
+
+	del_timer_sync(&rocket_timer);
+
+	retval = tty_unregister_driver(rocket_driver);
+	if (retval)
+		printk(KERN_INFO "Error %d while trying to unregister "
+		       "rocketport driver\n", -retval);
+	put_tty_driver(rocket_driver);
+
+	for (i = 0; i < MAX_RP_PORTS; i++) {
+		if (rp_table[i])
+			kfree(rp_table[i]);
+	}
+
+	for (i = 0; i < NUM_BOARDS; i++) {
+		if (rcktpt_io_addr[i] <= 0 || is_PCI[i])
+			continue;
+		release_region(rcktpt_io_addr[i], 64);
+	}
+	if (controller)
+		release_region(controller, 4);
+}
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+static Byte_t RData[RDATASIZE] = {
+	0x00, 0x09, 0xf6, 0x82,
+	0x02, 0x09, 0x86, 0xfb,
+	0x04, 0x09, 0x00, 0x0a,
+	0x06, 0x09, 0x01, 0x0a,
+	0x08, 0x09, 0x8a, 0x13,
+	0x0a, 0x09, 0xc5, 0x11,
+	0x0c, 0x09, 0x86, 0x85,
+	0x0e, 0x09, 0x20, 0x0a,
+	0x10, 0x09, 0x21, 0x0a,
+	0x12, 0x09, 0x41, 0xff,
+	0x14, 0x09, 0x82, 0x00,
+	0x16, 0x09, 0x82, 0x7b,
+	0x18, 0x09, 0x8a, 0x7d,
+	0x1a, 0x09, 0x88, 0x81,
+	0x1c, 0x09, 0x86, 0x7a,
+	0x1e, 0x09, 0x84, 0x81,
+	0x20, 0x09, 0x82, 0x7c,
+	0x22, 0x09, 0x0a, 0x0a
+};
+
+static Byte_t RRegData[RREGDATASIZE] = {
+	0x00, 0x09, 0xf6, 0x82,	/* 00: Stop Rx processor */
+	0x08, 0x09, 0x8a, 0x13,	/* 04: Tx software flow control */
+	0x0a, 0x09, 0xc5, 0x11,	/* 08: XON char */
+	0x0c, 0x09, 0x86, 0x85,	/* 0c: XANY */
+	0x12, 0x09, 0x41, 0xff,	/* 10: Rx mask char */
+	0x14, 0x09, 0x82, 0x00,	/* 14: Compare/Ignore #0 */
+	0x16, 0x09, 0x82, 0x7b,	/* 18: Compare #1 */
+	0x18, 0x09, 0x8a, 0x7d,	/* 1c: Compare #2 */
+	0x1a, 0x09, 0x88, 0x81,	/* 20: Interrupt #1 */
+	0x1c, 0x09, 0x86, 0x7a,	/* 24: Ignore/Replace #1 */
+	0x1e, 0x09, 0x84, 0x81,	/* 28: Interrupt #2 */
+	0x20, 0x09, 0x82, 0x7c,	/* 2c: Ignore/Replace #2 */
+	0x22, 0x09, 0x0a, 0x0a	/* 30: Rx FIFO Enable */
+};
+
+CONTROLLER_T sController[CTL_SIZE] = {
+	{-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0},
+	 {0, 0, 0, 0}, {-1, -1, -1, -1}, {0, 0, 0, 0}},
+	{-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0},
+	 {0, 0, 0, 0}, {-1, -1, -1, -1}, {0, 0, 0, 0}},
+	{-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0},
+	 {0, 0, 0, 0}, {-1, -1, -1, -1}, {0, 0, 0, 0}},
+	{-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0, 0},
+	 {0, 0, 0, 0}, {-1, -1, -1, -1}, {0, 0, 0, 0}}
+};
+
+Byte_t sBitMapClrTbl[8] = {
+	0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f
+};
+
+Byte_t sBitMapSetTbl[8] = {
+	0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
+};
+
+int sClockPrescale = 0x14;
+
+/***************************************************************************
+Function: sInitController
+Purpose:  Initialization of controller global registers and controller
+          structure.
+Call:     sInitController(CtlP,CtlNum,MudbacIO,AiopIOList,AiopIOListSize,
+                          IRQNum,Frequency,PeriodicOnly)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+          int CtlNum; Controller number
+          ByteIO_t MudbacIO; Mudbac base I/O address.
+          ByteIO_t *AiopIOList; List of I/O addresses for each AIOP.
+             This list must be in the order the AIOPs will be found on the
+             controller.  Once an AIOP in the list is not found, it is
+             assumed that there are no more AIOPs on the controller.
+          int AiopIOListSize; Number of addresses in AiopIOList
+          int IRQNum; Interrupt Request number.  Can be any of the following:
+                         0: Disable global interrupts
+                         3: IRQ 3
+                         4: IRQ 4
+                         5: IRQ 5
+                         9: IRQ 9
+                         10: IRQ 10
+                         11: IRQ 11
+                         12: IRQ 12
+                         15: IRQ 15
+          Byte_t Frequency: A flag identifying the frequency
+                   of the periodic interrupt, can be any one of the following:
+                      FREQ_DIS - periodic interrupt disabled
+                      FREQ_137HZ - 137 Hertz
+                      FREQ_69HZ - 69 Hertz
+                      FREQ_34HZ - 34 Hertz
+                      FREQ_17HZ - 17 Hertz
+                      FREQ_9HZ - 9 Hertz
+                      FREQ_4HZ - 4 Hertz
+                   If IRQNum is set to 0 the Frequency parameter is
+                   overidden, it is forced to a value of FREQ_DIS.
+          int PeriodicOnly: TRUE if all interrupts except the periodic
+                               interrupt are to be blocked.
+                            FALSE is both the periodic interrupt and
+                               other channel interrupts are allowed.
+                            If IRQNum is set to 0 the PeriodicOnly parameter is
+                               overidden, it is forced to a value of FALSE.
+Return:   int: Number of AIOPs on the controller, or CTLID_NULL if controller
+               initialization failed.
+
+Comments:
+          If periodic interrupts are to be disabled but AIOP interrupts
+          are allowed, set Frequency to FREQ_DIS and PeriodicOnly to FALSE.
+
+          If interrupts are to be completely disabled set IRQNum to 0.
+
+          Setting Frequency to FREQ_DIS and PeriodicOnly to TRUE is an
+          invalid combination.
+
+          This function performs initialization of global interrupt modes,
+          but it does not actually enable global interrupts.  To enable
+          and disable global interrupts use functions sEnGlobalInt() and
+          sDisGlobalInt().  Enabling of global interrupts is normally not
+          done until all other initializations are complete.
+
+          Even if interrupts are globally enabled, they must also be
+          individually enabled for each channel that is to generate
+          interrupts.
+
+Warnings: No range checking on any of the parameters is done.
+
+          No context switches are allowed while executing this function.
+
+          After this function all AIOPs on the controller are disabled,
+          they can be enabled with sEnAiop().
+*/
+int sInitController(CONTROLLER_T * CtlP, int CtlNum, ByteIO_t MudbacIO,
+		    ByteIO_t * AiopIOList, int AiopIOListSize, int IRQNum,
+		    Byte_t Frequency, int PeriodicOnly)
+{
+	int i;
+	ByteIO_t io;
+	int done;
+
+	CtlP->AiopIntrBits = aiop_intr_bits;
+	CtlP->AltChanRingIndicator = 0;
+	CtlP->CtlNum = CtlNum;
+	CtlP->CtlID = CTLID_0001;	/* controller release 1 */
+	CtlP->BusType = isISA;
+	CtlP->MBaseIO = MudbacIO;
+	CtlP->MReg1IO = MudbacIO + 1;
+	CtlP->MReg2IO = MudbacIO + 2;
+	CtlP->MReg3IO = MudbacIO + 3;
+#if 1
+	CtlP->MReg2 = 0;	/* interrupt disable */
+	CtlP->MReg3 = 0;	/* no periodic interrupts */
+#else
+	if (sIRQMap[IRQNum] == 0) {	/* interrupts globally disabled */
+		CtlP->MReg2 = 0;	/* interrupt disable */
+		CtlP->MReg3 = 0;	/* no periodic interrupts */
+	} else {
+		CtlP->MReg2 = sIRQMap[IRQNum];	/* set IRQ number */
+		CtlP->MReg3 = Frequency;	/* set frequency */
+		if (PeriodicOnly) {	/* periodic interrupt only */
+			CtlP->MReg3 |= PERIODIC_ONLY;
+		}
+	}
+#endif
+	sOutB(CtlP->MReg2IO, CtlP->MReg2);
+	sOutB(CtlP->MReg3IO, CtlP->MReg3);
+	sControllerEOI(CtlP);	/* clear EOI if warm init */
+	/* Init AIOPs */
+	CtlP->NumAiop = 0;
+	for (i = done = 0; i < AiopIOListSize; i++) {
+		io = AiopIOList[i];
+		CtlP->AiopIO[i] = (WordIO_t) io;
+		CtlP->AiopIntChanIO[i] = io + _INT_CHAN;
+		sOutB(CtlP->MReg2IO, CtlP->MReg2 | (i & 0x03));	/* AIOP index */
+		sOutB(MudbacIO, (Byte_t) (io >> 6));	/* set up AIOP I/O in MUDBAC */
+		if (done)
+			continue;
+		sEnAiop(CtlP, i);	/* enable the AIOP */
+		CtlP->AiopID[i] = sReadAiopID(io);	/* read AIOP ID */
+		if (CtlP->AiopID[i] == AIOPID_NULL)	/* if AIOP does not exist */
+			done = 1;	/* done looking for AIOPs */
+		else {
+			CtlP->AiopNumChan[i] = sReadAiopNumChan((WordIO_t) io);	/* num channels in AIOP */
+			sOutW((WordIO_t) io + _INDX_ADDR, _CLK_PRE);	/* clock prescaler */
+			sOutB(io + _INDX_DATA, sClockPrescale);
+			CtlP->NumAiop++;	/* bump count of AIOPs */
+		}
+		sDisAiop(CtlP, i);	/* disable AIOP */
+	}
+
+	if (CtlP->NumAiop == 0)
+		return (-1);
+	else
+		return (CtlP->NumAiop);
+}
+
+/***************************************************************************
+Function: sPCIInitController
+Purpose:  Initialization of controller global registers and controller
+          structure.
+Call:     sPCIInitController(CtlP,CtlNum,AiopIOList,AiopIOListSize,
+                          IRQNum,Frequency,PeriodicOnly)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+          int CtlNum; Controller number
+          ByteIO_t *AiopIOList; List of I/O addresses for each AIOP.
+             This list must be in the order the AIOPs will be found on the
+             controller.  Once an AIOP in the list is not found, it is
+             assumed that there are no more AIOPs on the controller.
+          int AiopIOListSize; Number of addresses in AiopIOList
+          int IRQNum; Interrupt Request number.  Can be any of the following:
+                         0: Disable global interrupts
+                         3: IRQ 3
+                         4: IRQ 4
+                         5: IRQ 5
+                         9: IRQ 9
+                         10: IRQ 10
+                         11: IRQ 11
+                         12: IRQ 12
+                         15: IRQ 15
+          Byte_t Frequency: A flag identifying the frequency
+                   of the periodic interrupt, can be any one of the following:
+                      FREQ_DIS - periodic interrupt disabled
+                      FREQ_137HZ - 137 Hertz
+                      FREQ_69HZ - 69 Hertz
+                      FREQ_34HZ - 34 Hertz
+                      FREQ_17HZ - 17 Hertz
+                      FREQ_9HZ - 9 Hertz
+                      FREQ_4HZ - 4 Hertz
+                   If IRQNum is set to 0 the Frequency parameter is
+                   overidden, it is forced to a value of FREQ_DIS.
+          int PeriodicOnly: TRUE if all interrupts except the periodic
+                               interrupt are to be blocked.
+                            FALSE is both the periodic interrupt and
+                               other channel interrupts are allowed.
+                            If IRQNum is set to 0 the PeriodicOnly parameter is
+                               overidden, it is forced to a value of FALSE.
+Return:   int: Number of AIOPs on the controller, or CTLID_NULL if controller
+               initialization failed.
+
+Comments:
+          If periodic interrupts are to be disabled but AIOP interrupts
+          are allowed, set Frequency to FREQ_DIS and PeriodicOnly to FALSE.
+
+          If interrupts are to be completely disabled set IRQNum to 0.
+
+          Setting Frequency to FREQ_DIS and PeriodicOnly to TRUE is an
+          invalid combination.
+
+          This function performs initialization of global interrupt modes,
+          but it does not actually enable global interrupts.  To enable
+          and disable global interrupts use functions sEnGlobalInt() and
+          sDisGlobalInt().  Enabling of global interrupts is normally not
+          done until all other initializations are complete.
+
+          Even if interrupts are globally enabled, they must also be
+          individually enabled for each channel that is to generate
+          interrupts.
+
+Warnings: No range checking on any of the parameters is done.
+
+          No context switches are allowed while executing this function.
+
+          After this function all AIOPs on the controller are disabled,
+          they can be enabled with sEnAiop().
+*/
+int sPCIInitController(CONTROLLER_T * CtlP, int CtlNum,
+		       ByteIO_t * AiopIOList, int AiopIOListSize,
+		       WordIO_t ConfigIO, int IRQNum, Byte_t Frequency,
+		       int PeriodicOnly, int altChanRingIndicator,
+		       int UPCIRingInd)
+{
+	int i;
+	ByteIO_t io;
+
+	CtlP->AltChanRingIndicator = altChanRingIndicator;
+	CtlP->UPCIRingInd = UPCIRingInd;
+	CtlP->CtlNum = CtlNum;
+	CtlP->CtlID = CTLID_0001;	/* controller release 1 */
+	CtlP->BusType = isPCI;	/* controller release 1 */
+
+	if (ConfigIO) {
+		CtlP->isUPCI = 1;
+		CtlP->PCIIO = ConfigIO + _PCI_9030_INT_CTRL;
+		CtlP->PCIIO2 = ConfigIO + _PCI_9030_GPIO_CTRL;
+		CtlP->AiopIntrBits = upci_aiop_intr_bits;
+	} else {
+		CtlP->isUPCI = 0;
+		CtlP->PCIIO =
+		    (WordIO_t) ((ByteIO_t) AiopIOList[0] + _PCI_INT_FUNC);
+		CtlP->AiopIntrBits = aiop_intr_bits;
+	}
+
+	sPCIControllerEOI(CtlP);	/* clear EOI if warm init */
+	/* Init AIOPs */
+	CtlP->NumAiop = 0;
+	for (i = 0; i < AiopIOListSize; i++) {
+		io = AiopIOList[i];
+		CtlP->AiopIO[i] = (WordIO_t) io;
+		CtlP->AiopIntChanIO[i] = io + _INT_CHAN;
+
+		CtlP->AiopID[i] = sReadAiopID(io);	/* read AIOP ID */
+		if (CtlP->AiopID[i] == AIOPID_NULL)	/* if AIOP does not exist */
+			break;	/* done looking for AIOPs */
+
+		CtlP->AiopNumChan[i] = sReadAiopNumChan((WordIO_t) io);	/* num channels in AIOP */
+		sOutW((WordIO_t) io + _INDX_ADDR, _CLK_PRE);	/* clock prescaler */
+		sOutB(io + _INDX_DATA, sClockPrescale);
+		CtlP->NumAiop++;	/* bump count of AIOPs */
+	}
+
+	if (CtlP->NumAiop == 0)
+		return (-1);
+	else
+		return (CtlP->NumAiop);
+}
+
+/***************************************************************************
+Function: sReadAiopID
+Purpose:  Read the AIOP idenfication number directly from an AIOP.
+Call:     sReadAiopID(io)
+          ByteIO_t io: AIOP base I/O address
+Return:   int: Flag AIOPID_XXXX if a valid AIOP is found, where X
+                 is replace by an identifying number.
+          Flag AIOPID_NULL if no valid AIOP is found
+Warnings: No context switches are allowed while executing this function.
+
+*/
+int sReadAiopID(ByteIO_t io)
+{
+	Byte_t AiopID;		/* ID byte from AIOP */
+
+	sOutB(io + _CMD_REG, RESET_ALL);	/* reset AIOP */
+	sOutB(io + _CMD_REG, 0x0);
+	AiopID = sInW(io + _CHN_STAT0) & 0x07;
+	if (AiopID == 0x06)
+		return (1);
+	else			/* AIOP does not exist */
+		return (-1);
+}
+
+/***************************************************************************
+Function: sReadAiopNumChan
+Purpose:  Read the number of channels available in an AIOP directly from
+          an AIOP.
+Call:     sReadAiopNumChan(io)
+          WordIO_t io: AIOP base I/O address
+Return:   int: The number of channels available
+Comments: The number of channels is determined by write/reads from identical
+          offsets within the SRAM address spaces for channels 0 and 4.
+          If the channel 4 space is mirrored to channel 0 it is a 4 channel
+          AIOP, otherwise it is an 8 channel.
+Warnings: No context switches are allowed while executing this function.
+*/
+int sReadAiopNumChan(WordIO_t io)
+{
+	Word_t x;
+	static Byte_t R[4] = { 0x00, 0x00, 0x34, 0x12 };
+
+	/* write to chan 0 SRAM */
+	sOutDW((DWordIO_t) io + _INDX_ADDR, *((DWord_t *) & R[0]));
+	sOutW(io + _INDX_ADDR, 0);	/* read from SRAM, chan 0 */
+	x = sInW(io + _INDX_DATA);
+	sOutW(io + _INDX_ADDR, 0x4000);	/* read from SRAM, chan 4 */
+	if (x != sInW(io + _INDX_DATA))	/* if different must be 8 chan */
+		return (8);
+	else
+		return (4);
+}
+
+/***************************************************************************
+Function: sInitChan
+Purpose:  Initialization of a channel and channel structure
+Call:     sInitChan(CtlP,ChP,AiopNum,ChanNum)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+          CHANNEL_T *ChP; Ptr to channel structure
+          int AiopNum; AIOP number within controller
+          int ChanNum; Channel number within AIOP
+Return:   int: TRUE if initialization succeeded, FALSE if it fails because channel
+               number exceeds number of channels available in AIOP.
+Comments: This function must be called before a channel can be used.
+Warnings: No range checking on any of the parameters is done.
+
+          No context switches are allowed while executing this function.
+*/
+int sInitChan(CONTROLLER_T * CtlP, CHANNEL_T * ChP, int AiopNum,
+	      int ChanNum)
+{
+	int i;
+	WordIO_t AiopIO;
+	WordIO_t ChIOOff;
+	Byte_t *ChR;
+	Word_t ChOff;
+	static Byte_t R[4];
+	int brd9600;
+
+	if (ChanNum >= CtlP->AiopNumChan[AiopNum])
+		return (FALSE);	/* exceeds num chans in AIOP */
+
+	/* Channel, AIOP, and controller identifiers */
+	ChP->CtlP = CtlP;
+	ChP->ChanID = CtlP->AiopID[AiopNum];
+	ChP->AiopNum = AiopNum;
+	ChP->ChanNum = ChanNum;
+
+	/* Global direct addresses */
+	AiopIO = CtlP->AiopIO[AiopNum];
+	ChP->Cmd = (ByteIO_t) AiopIO + _CMD_REG;
+	ChP->IntChan = (ByteIO_t) AiopIO + _INT_CHAN;
+	ChP->IntMask = (ByteIO_t) AiopIO + _INT_MASK;
+	ChP->IndexAddr = (DWordIO_t) AiopIO + _INDX_ADDR;
+	ChP->IndexData = AiopIO + _INDX_DATA;
+
+	/* Channel direct addresses */
+	ChIOOff = AiopIO + ChP->ChanNum * 2;
+	ChP->TxRxData = ChIOOff + _TD0;
+	ChP->ChanStat = ChIOOff + _CHN_STAT0;
+	ChP->TxRxCount = ChIOOff + _FIFO_CNT0;
+	ChP->IntID = (ByteIO_t) AiopIO + ChP->ChanNum + _INT_ID0;
+
+	/* Initialize the channel from the RData array */
+	for (i = 0; i < RDATASIZE; i += 4) {
+		R[0] = RData[i];
+		R[1] = RData[i + 1] + 0x10 * ChanNum;
+		R[2] = RData[i + 2];
+		R[3] = RData[i + 3];
+		sOutDW(ChP->IndexAddr, *((DWord_t *) & R[0]));
+	}
+
+	ChR = ChP->R;
+	for (i = 0; i < RREGDATASIZE; i += 4) {
+		ChR[i] = RRegData[i];
+		ChR[i + 1] = RRegData[i + 1] + 0x10 * ChanNum;
+		ChR[i + 2] = RRegData[i + 2];
+		ChR[i + 3] = RRegData[i + 3];
+	}
+
+	/* Indexed registers */
+	ChOff = (Word_t) ChanNum *0x1000;
+
+	if (sClockPrescale == 0x14)
+		brd9600 = 47;
+	else
+		brd9600 = 23;
+
+	ChP->BaudDiv[0] = (Byte_t) (ChOff + _BAUD);
+	ChP->BaudDiv[1] = (Byte_t) ((ChOff + _BAUD) >> 8);
+	ChP->BaudDiv[2] = (Byte_t) brd9600;
+	ChP->BaudDiv[3] = (Byte_t) (brd9600 >> 8);
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->BaudDiv[0]);
+
+	ChP->TxControl[0] = (Byte_t) (ChOff + _TX_CTRL);
+	ChP->TxControl[1] = (Byte_t) ((ChOff + _TX_CTRL) >> 8);
+	ChP->TxControl[2] = 0;
+	ChP->TxControl[3] = 0;
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxControl[0]);
+
+	ChP->RxControl[0] = (Byte_t) (ChOff + _RX_CTRL);
+	ChP->RxControl[1] = (Byte_t) ((ChOff + _RX_CTRL) >> 8);
+	ChP->RxControl[2] = 0;
+	ChP->RxControl[3] = 0;
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->RxControl[0]);
+
+	ChP->TxEnables[0] = (Byte_t) (ChOff + _TX_ENBLS);
+	ChP->TxEnables[1] = (Byte_t) ((ChOff + _TX_ENBLS) >> 8);
+	ChP->TxEnables[2] = 0;
+	ChP->TxEnables[3] = 0;
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxEnables[0]);
+
+	ChP->TxCompare[0] = (Byte_t) (ChOff + _TXCMP1);
+	ChP->TxCompare[1] = (Byte_t) ((ChOff + _TXCMP1) >> 8);
+	ChP->TxCompare[2] = 0;
+	ChP->TxCompare[3] = 0;
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxCompare[0]);
+
+	ChP->TxReplace1[0] = (Byte_t) (ChOff + _TXREP1B1);
+	ChP->TxReplace1[1] = (Byte_t) ((ChOff + _TXREP1B1) >> 8);
+	ChP->TxReplace1[2] = 0;
+	ChP->TxReplace1[3] = 0;
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxReplace1[0]);
+
+	ChP->TxReplace2[0] = (Byte_t) (ChOff + _TXREP2);
+	ChP->TxReplace2[1] = (Byte_t) ((ChOff + _TXREP2) >> 8);
+	ChP->TxReplace2[2] = 0;
+	ChP->TxReplace2[3] = 0;
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxReplace2[0]);
+
+	ChP->TxFIFOPtrs = ChOff + _TXF_OUTP;
+	ChP->TxFIFO = ChOff + _TX_FIFO;
+
+	sOutB(ChP->Cmd, (Byte_t) ChanNum | RESTXFCNT);	/* apply reset Tx FIFO count */
+	sOutB(ChP->Cmd, (Byte_t) ChanNum);	/* remove reset Tx FIFO count */
+	sOutW((WordIO_t) ChP->IndexAddr, ChP->TxFIFOPtrs);	/* clear Tx in/out ptrs */
+	sOutW(ChP->IndexData, 0);
+	ChP->RxFIFOPtrs = ChOff + _RXF_OUTP;
+	ChP->RxFIFO = ChOff + _RX_FIFO;
+
+	sOutB(ChP->Cmd, (Byte_t) ChanNum | RESRXFCNT);	/* apply reset Rx FIFO count */
+	sOutB(ChP->Cmd, (Byte_t) ChanNum);	/* remove reset Rx FIFO count */
+	sOutW((WordIO_t) ChP->IndexAddr, ChP->RxFIFOPtrs);	/* clear Rx out ptr */
+	sOutW(ChP->IndexData, 0);
+	sOutW((WordIO_t) ChP->IndexAddr, ChP->RxFIFOPtrs + 2);	/* clear Rx in ptr */
+	sOutW(ChP->IndexData, 0);
+	ChP->TxPrioCnt = ChOff + _TXP_CNT;
+	sOutW((WordIO_t) ChP->IndexAddr, ChP->TxPrioCnt);
+	sOutB(ChP->IndexData, 0);
+	ChP->TxPrioPtr = ChOff + _TXP_PNTR;
+	sOutW((WordIO_t) ChP->IndexAddr, ChP->TxPrioPtr);
+	sOutB(ChP->IndexData, 0);
+	ChP->TxPrioBuf = ChOff + _TXP_BUF;
+	sEnRxProcessor(ChP);	/* start the Rx processor */
+
+	return (TRUE);
+}
+
+/***************************************************************************
+Function: sStopRxProcessor
+Purpose:  Stop the receive processor from processing a channel.
+Call:     sStopRxProcessor(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+
+Comments: The receive processor can be started again with sStartRxProcessor().
+          This function causes the receive processor to skip over the
+          stopped channel.  It does not stop it from processing other channels.
+
+Warnings: No context switches are allowed while executing this function.
+
+          Do not leave the receive processor stopped for more than one
+          character time.
+
+          After calling this function a delay of 4 uS is required to ensure
+          that the receive processor is no longer processing this channel.
+*/
+void sStopRxProcessor(CHANNEL_T * ChP)
+{
+	Byte_t R[4];
+
+	R[0] = ChP->R[0];
+	R[1] = ChP->R[1];
+	R[2] = 0x0a;
+	R[3] = ChP->R[3];
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & R[0]);
+}
+
+/***************************************************************************
+Function: sFlushRxFIFO
+Purpose:  Flush the Rx FIFO
+Call:     sFlushRxFIFO(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   void
+Comments: To prevent data from being enqueued or dequeued in the Tx FIFO
+          while it is being flushed the receive processor is stopped
+          and the transmitter is disabled.  After these operations a
+          4 uS delay is done before clearing the pointers to allow
+          the receive processor to stop.  These items are handled inside
+          this function.
+Warnings: No context switches are allowed while executing this function.
+*/
+void sFlushRxFIFO(CHANNEL_T * ChP)
+{
+	int i;
+	Byte_t Ch;		/* channel number within AIOP */
+	int RxFIFOEnabled;	/* TRUE if Rx FIFO enabled */
+
+	if (sGetRxCnt(ChP) == 0)	/* Rx FIFO empty */
+		return;		/* don't need to flush */
+
+	RxFIFOEnabled = FALSE;
+	if (ChP->R[0x32] == 0x08) {	/* Rx FIFO is enabled */
+		RxFIFOEnabled = TRUE;
+		sDisRxFIFO(ChP);	/* disable it */
+		for (i = 0; i < 2000 / 200; i++)	/* delay 2 uS to allow proc to disable FIFO */
+			sInB(ChP->IntChan);	/* depends on bus i/o timing */
+	}
+	sGetChanStatus(ChP);	/* clear any pending Rx errors in chan stat */
+	Ch = (Byte_t) sGetChanNum(ChP);
+	sOutB(ChP->Cmd, Ch | RESRXFCNT);	/* apply reset Rx FIFO count */
+	sOutB(ChP->Cmd, Ch);	/* remove reset Rx FIFO count */
+	sOutW((WordIO_t) ChP->IndexAddr, ChP->RxFIFOPtrs);	/* clear Rx out ptr */
+	sOutW(ChP->IndexData, 0);
+	sOutW((WordIO_t) ChP->IndexAddr, ChP->RxFIFOPtrs + 2);	/* clear Rx in ptr */
+	sOutW(ChP->IndexData, 0);
+	if (RxFIFOEnabled)
+		sEnRxFIFO(ChP);	/* enable Rx FIFO */
+}
+
+/***************************************************************************
+Function: sFlushTxFIFO
+Purpose:  Flush the Tx FIFO
+Call:     sFlushTxFIFO(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   void
+Comments: To prevent data from being enqueued or dequeued in the Tx FIFO
+          while it is being flushed the receive processor is stopped
+          and the transmitter is disabled.  After these operations a
+          4 uS delay is done before clearing the pointers to allow
+          the receive processor to stop.  These items are handled inside
+          this function.
+Warnings: No context switches are allowed while executing this function.
+*/
+void sFlushTxFIFO(CHANNEL_T * ChP)
+{
+	int i;
+	Byte_t Ch;		/* channel number within AIOP */
+	int TxEnabled;		/* TRUE if transmitter enabled */
+
+	if (sGetTxCnt(ChP) == 0)	/* Tx FIFO empty */
+		return;		/* don't need to flush */
+
+	TxEnabled = FALSE;
+	if (ChP->TxControl[3] & TX_ENABLE) {
+		TxEnabled = TRUE;
+		sDisTransmit(ChP);	/* disable transmitter */
+	}
+	sStopRxProcessor(ChP);	/* stop Rx processor */
+	for (i = 0; i < 4000 / 200; i++)	/* delay 4 uS to allow proc to stop */
+		sInB(ChP->IntChan);	/* depends on bus i/o timing */
+	Ch = (Byte_t) sGetChanNum(ChP);
+	sOutB(ChP->Cmd, Ch | RESTXFCNT);	/* apply reset Tx FIFO count */
+	sOutB(ChP->Cmd, Ch);	/* remove reset Tx FIFO count */
+	sOutW((WordIO_t) ChP->IndexAddr, ChP->TxFIFOPtrs);	/* clear Tx in/out ptrs */
+	sOutW(ChP->IndexData, 0);
+	if (TxEnabled)
+		sEnTransmit(ChP);	/* enable transmitter */
+	sStartRxProcessor(ChP);	/* restart Rx processor */
+}
+
+/***************************************************************************
+Function: sWriteTxPrioByte
+Purpose:  Write a byte of priority transmit data to a channel
+Call:     sWriteTxPrioByte(ChP,Data)
+          CHANNEL_T *ChP; Ptr to channel structure
+          Byte_t Data; The transmit data byte
+
+Return:   int: 1 if the bytes is successfully written, otherwise 0.
+
+Comments: The priority byte is transmitted before any data in the Tx FIFO.
+
+Warnings: No context switches are allowed while executing this function.
+*/
+int sWriteTxPrioByte(CHANNEL_T * ChP, Byte_t Data)
+{
+	Byte_t DWBuf[4];	/* buffer for double word writes */
+	Word_t *WordPtr;	/* must be far because Win SS != DS */
+	register DWordIO_t IndexAddr;
+
+	if (sGetTxCnt(ChP) > 1) {	/* write it to Tx priority buffer */
+		IndexAddr = ChP->IndexAddr;
+		sOutW((WordIO_t) IndexAddr, ChP->TxPrioCnt);	/* get priority buffer status */
+		if (sInB((ByteIO_t) ChP->IndexData) & PRI_PEND)	/* priority buffer busy */
+			return (0);	/* nothing sent */
+
+		WordPtr = (Word_t *) (&DWBuf[0]);
+		*WordPtr = ChP->TxPrioBuf;	/* data byte address */
+
+		DWBuf[2] = Data;	/* data byte value */
+		sOutDW(IndexAddr, *((DWord_t *) (&DWBuf[0])));	/* write it out */
+
+		*WordPtr = ChP->TxPrioCnt;	/* Tx priority count address */
+
+		DWBuf[2] = PRI_PEND + 1;	/* indicate 1 byte pending */
+		DWBuf[3] = 0;	/* priority buffer pointer */
+		sOutDW(IndexAddr, *((DWord_t *) (&DWBuf[0])));	/* write it out */
+	} else {		/* write it to Tx FIFO */
+
+		sWriteTxByte(sGetTxRxDataIO(ChP), Data);
+	}
+	return (1);		/* 1 byte sent */
+}
+
+/***************************************************************************
+Function: sEnInterrupts
+Purpose:  Enable one or more interrupts for a channel
+Call:     sEnInterrupts(ChP,Flags)
+          CHANNEL_T *ChP; Ptr to channel structure
+          Word_t Flags: Interrupt enable flags, can be any combination
+             of the following flags:
+                TXINT_EN:   Interrupt on Tx FIFO empty
+                RXINT_EN:   Interrupt on Rx FIFO at trigger level (see
+                            sSetRxTrigger())
+                SRCINT_EN:  Interrupt on SRC (Special Rx Condition)
+                MCINT_EN:   Interrupt on modem input change
+                CHANINT_EN: Allow channel interrupt signal to the AIOP's
+                            Interrupt Channel Register.
+Return:   void
+Comments: If an interrupt enable flag is set in Flags, that interrupt will be
+          enabled.  If an interrupt enable flag is not set in Flags, that
+          interrupt will not be changed.  Interrupts can be disabled with
+          function sDisInterrupts().
+
+          This function sets the appropriate bit for the channel in the AIOP's
+          Interrupt Mask Register if the CHANINT_EN flag is set.  This allows
+          this channel's bit to be set in the AIOP's Interrupt Channel Register.
+
+          Interrupts must also be globally enabled before channel interrupts
+          will be passed on to the host.  This is done with function
+          sEnGlobalInt().
+
+          In some cases it may be desirable to disable interrupts globally but
+          enable channel interrupts.  This would allow the global interrupt
+          status register to be used to determine which AIOPs need service.
+*/
+void sEnInterrupts(CHANNEL_T * ChP, Word_t Flags)
+{
+	Byte_t Mask;		/* Interrupt Mask Register */
+
+	ChP->RxControl[2] |=
+	    ((Byte_t) Flags & (RXINT_EN | SRCINT_EN | MCINT_EN));
+
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->RxControl[0]);
+
+	ChP->TxControl[2] |= ((Byte_t) Flags & TXINT_EN);
+
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxControl[0]);
+
+	if (Flags & CHANINT_EN) {
+		Mask = sInB(ChP->IntMask) | sBitMapSetTbl[ChP->ChanNum];
+		sOutB(ChP->IntMask, Mask);
+	}
+}
+
+/***************************************************************************
+Function: sDisInterrupts
+Purpose:  Disable one or more interrupts for a channel
+Call:     sDisInterrupts(ChP,Flags)
+          CHANNEL_T *ChP; Ptr to channel structure
+          Word_t Flags: Interrupt flags, can be any combination
+             of the following flags:
+                TXINT_EN:   Interrupt on Tx FIFO empty
+                RXINT_EN:   Interrupt on Rx FIFO at trigger level (see
+                            sSetRxTrigger())
+                SRCINT_EN:  Interrupt on SRC (Special Rx Condition)
+                MCINT_EN:   Interrupt on modem input change
+                CHANINT_EN: Disable channel interrupt signal to the
+                            AIOP's Interrupt Channel Register.
+Return:   void
+Comments: If an interrupt flag is set in Flags, that interrupt will be
+          disabled.  If an interrupt flag is not set in Flags, that
+          interrupt will not be changed.  Interrupts can be enabled with
+          function sEnInterrupts().
+
+          This function clears the appropriate bit for the channel in the AIOP's
+          Interrupt Mask Register if the CHANINT_EN flag is set.  This blocks
+          this channel's bit from being set in the AIOP's Interrupt Channel
+          Register.
+*/
+void sDisInterrupts(CHANNEL_T * ChP, Word_t Flags)
+{
+	Byte_t Mask;		/* Interrupt Mask Register */
+
+	ChP->RxControl[2] &=
+	    ~((Byte_t) Flags & (RXINT_EN | SRCINT_EN | MCINT_EN));
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->RxControl[0]);
+	ChP->TxControl[2] &= ~((Byte_t) Flags & TXINT_EN);
+	sOutDW(ChP->IndexAddr, *(DWord_t *) & ChP->TxControl[0]);
+
+	if (Flags & CHANINT_EN) {
+		Mask = sInB(ChP->IntMask) & sBitMapClrTbl[ChP->ChanNum];
+		sOutB(ChP->IntMask, Mask);
+	}
+}
+
+void sSetInterfaceMode(CHANNEL_T * ChP, Byte_t mode)
+{
+	sOutB(ChP->CtlP->AiopIO[2], (mode & 0x18) | ChP->ChanNum);
+}
+
+/*
+ *  Not an official SSCI function, but how to reset RocketModems.
+ *  ISA bus version
+ */
+void sModemReset(CONTROLLER_T * CtlP, int chan, int on)
+{
+	ByteIO_t addr;
+	Byte_t val;
+
+	addr = CtlP->AiopIO[0] + 0x400;
+	val = sInB(CtlP->MReg3IO);
+	/* if AIOP[1] is not enabled, enable it */
+	if ((val & 2) == 0) {
+		val = sInB(CtlP->MReg2IO);
+		sOutB(CtlP->MReg2IO, (val & 0xfc) | (1 & 0x03));
+		sOutB(CtlP->MBaseIO, (unsigned char) (addr >> 6));
+	}
+
+	sEnAiop(CtlP, 1);
+	if (!on)
+		addr += 8;
+	sOutB(addr + chan, 0);	/* apply or remove reset */
+	sDisAiop(CtlP, 1);
+}
+
+/*
+ *  Not an official SSCI function, but how to reset RocketModems.
+ *  PCI bus version
+ */
+void sPCIModemReset(CONTROLLER_T * CtlP, int chan, int on)
+{
+	ByteIO_t addr;
+
+	addr = CtlP->AiopIO[0] + 0x40;	/* 2nd AIOP */
+	if (!on)
+		addr += 8;
+	sOutB(addr + chan, 0);	/* apply or remove reset */
+}
+
+/*  Resets the speaker controller on RocketModem II and III devices */
+static void rmSpeakerReset(CONTROLLER_T * CtlP, unsigned long model)
+{
+	ByteIO_t addr;
+
+	/* RocketModem II speaker control is at the 8th port location of offset 0x40 */
+	if ((model == MODEL_RP4M) || (model == MODEL_RP6M)) {
+		addr = CtlP->AiopIO[0] + 0x4F;
+		sOutB(addr, 0);
+	}
+
+	/* RocketModem III speaker control is at the 1st port location of offset 0x80 */
+	if ((model == MODEL_UPCI_RM3_8PORT)
+	    || (model == MODEL_UPCI_RM3_4PORT)) {
+		addr = CtlP->AiopIO[0] + 0x88;
+		sOutB(addr, 0);
+	}
+}
+
+/*  Returns the line number given the controller (board), aiop and channel number */
+static unsigned char GetLineNumber(int ctrl, int aiop, int ch)
+{
+	return lineNumbers[(ctrl << 5) | (aiop << 3) | ch];
+}
+
+/*
+ *  Stores the line number associated with a given controller (board), aiop
+ *  and channel number.  
+ *  Returns:  The line number assigned 
+ */
+static unsigned char SetLineNumber(int ctrl, int aiop, int ch)
+{
+	lineNumbers[(ctrl << 5) | (aiop << 3) | ch] = nextLineNumber++;
+	return (nextLineNumber - 1);
+}
diff --git a/drivers/char/rocket.h b/drivers/char/rocket.h
new file mode 100644
index 0000000..ae6b04f
--- /dev/null
+++ b/drivers/char/rocket.h
@@ -0,0 +1,111 @@
+/*
+ * rocket.h --- the exported interface of the rocket driver to its configuration program.
+ *
+ * Written by Theodore Ts'o, Copyright 1997.
+ * Copyright 1997 Comtrol Corporation. 
+ *
+ */
+
+/*  Model Information Struct */
+typedef struct {
+	unsigned long model;
+	char modelString[80];
+	unsigned long numPorts;
+	int loadrm2;
+	int startingPortNumber;
+} rocketModel_t;
+
+struct rocket_config {
+	int line;
+	int flags;
+	int closing_wait;
+	int close_delay;
+	int port;
+	int reserved[32];
+};
+
+struct rocket_ports {
+	int tty_major;
+	int callout_major;
+	rocketModel_t rocketModel[8];
+};
+
+struct rocket_version {
+	char rocket_version[32];
+	char rocket_date[32];
+	char reserved[64];
+};
+
+/*
+ * Rocketport flags
+ */
+#define ROCKET_CALLOUT_NOHUP    0x00000001
+#define ROCKET_FORCE_CD		0x00000002
+#define ROCKET_HUP_NOTIFY	0x00000004
+#define ROCKET_SPLIT_TERMIOS	0x00000008
+#define ROCKET_SPD_MASK		0x00000070
+#define ROCKET_SPD_HI		0x00000010	/* Use 56000 instead of 38400 bps */
+#define ROCKET_SPD_VHI		0x00000020	/* Use 115200 instead of 38400 bps */
+#define ROCKET_SPD_SHI		0x00000030	/* Use 230400 instead of 38400 bps */
+#define ROCKET_SPD_WARP	        0x00000040	/* Use 460800 instead of 38400 bps */
+#define ROCKET_SAK		0x00000080
+#define ROCKET_SESSION_LOCKOUT	0x00000100
+#define ROCKET_PGRP_LOCKOUT	0x00000200
+#define ROCKET_RTS_TOGGLE	0x00000400
+#define ROCKET_MODE_MASK        0x00003000
+#define ROCKET_MODE_RS232       0x00000000
+#define ROCKET_MODE_RS485       0x00001000
+#define ROCKET_MODE_RS422       0x00002000
+#define ROCKET_FLAGS		0x00003FFF
+
+#define ROCKET_USR_MASK 0x0071	/* Legal flags that non-privileged
+				 * users can set or reset */
+
+/*
+ * For closing_wait and closing_wait2
+ */
+#define ROCKET_CLOSING_WAIT_NONE	65535
+#define ROCKET_CLOSING_WAIT_INF		0
+
+/*
+ * Rocketport ioctls -- "RP"
+ */
+#define RCKP_GET_STRUCT		0x00525001
+#define RCKP_GET_CONFIG		0x00525002
+#define RCKP_SET_CONFIG		0x00525003
+#define RCKP_GET_PORTS		0x00525004
+#define RCKP_RESET_RM2		0x00525005
+#define RCKP_GET_VERSION	0x00525006
+
+/*  Rocketport Models */
+#define MODEL_RP32INTF        0x0001	/* RP 32 port w/external I/F   */
+#define MODEL_RP8INTF         0x0002	/* RP 8 port w/external I/F    */
+#define MODEL_RP16INTF        0x0003	/* RP 16 port w/external I/F   */
+#define MODEL_RP8OCTA         0x0005	/* RP 8 port w/octa cable      */
+#define MODEL_RP4QUAD         0x0004	/* RP 4 port w/quad cable      */
+#define MODEL_RP8J            0x0006	/* RP 8 port w/RJ11 connectors */
+#define MODEL_RP4J            0x0007	/* RP 4 port w/RJ45 connectors */
+#define MODEL_RP8SNI          0x0008	/* RP 8 port w/ DB78 SNI connector */
+#define MODEL_RP16SNI         0x0009	/* RP 16 port w/ DB78 SNI connector */
+#define MODEL_RPP4            0x000A	/* RP Plus 4 port              */
+#define MODEL_RPP8            0x000B	/* RP Plus 8 port              */
+#define MODEL_RP2_232         0x000E	/* RP Plus 2 port RS232        */
+#define MODEL_RP2_422         0x000F	/* RP Plus 2 port RS232        */
+
+/*  Rocketmodem II Models */
+#define MODEL_RP6M            0x000C	/* RM 6 port                   */
+#define MODEL_RP4M            0x000D	/* RM 4 port                   */
+
+/* Universal PCI boards */
+#define MODEL_UPCI_RP32INTF   0x0801	/* RP UPCI 32 port w/external I/F     */
+#define MODEL_UPCI_RP8INTF    0x0802	/* RP UPCI 8 port w/external I/F      */
+#define MODEL_UPCI_RP16INTF   0x0803	/* RP UPCI 16 port w/external I/F     */
+#define MODEL_UPCI_RP8OCTA    0x0805	/* RP UPCI 8 port w/octa cable        */ 
+#define MODEL_UPCI_RM3_8PORT  0x080C	/* RP UPCI Rocketmodem III 8 port     */
+#define MODEL_UPCI_RM3_4PORT  0x080C	/* RP UPCI Rocketmodem III 4 port     */
+
+/*  Compact PCI 16 port  */
+#define MODEL_CPCI_RP16INTF   0x0903	/* RP Compact PCI 16 port w/external I/F */
+
+/* All ISA boards */
+#define MODEL_ISA             0x1000
diff --git a/drivers/char/rocket_int.h b/drivers/char/rocket_int.h
new file mode 100644
index 0000000..8026872
--- /dev/null
+++ b/drivers/char/rocket_int.h
@@ -0,0 +1,1296 @@
+/*
+ * rocket_int.h --- internal header file for rocket.c
+ *
+ * Written by Theodore Ts'o, Copyright 1997.
+ * Copyright 1997 Comtrol Corporation.  
+ * 
+ */
+
+/*
+ * Definition of the types in rcktpt_type
+ */
+#define ROCKET_TYPE_NORMAL	0
+#define ROCKET_TYPE_MODEM	1
+#define ROCKET_TYPE_MODEMII	2
+#define ROCKET_TYPE_MODEMIII	3
+#define ROCKET_TYPE_PC104       4
+
+#include <asm/io.h>
+#include <asm/byteorder.h>
+
+typedef unsigned char Byte_t;
+typedef unsigned int ByteIO_t;
+
+typedef unsigned int Word_t;
+typedef unsigned int WordIO_t;
+
+typedef unsigned long DWord_t;
+typedef unsigned int DWordIO_t;
+
+/*
+ * Note!  Normally the Linux I/O macros already take care of
+ * byte-swapping the I/O instructions.  However, all accesses using
+ * sOutDW aren't really 32-bit accesses, but should be handled in byte
+ * order.  Hence the use of the cpu_to_le32() macro to byte-swap
+ * things to no-op the byte swapping done by the big-endian outl()
+ * instruction.
+ */
+
+#ifdef ROCKET_DEBUG_IO
+static inline void sOutB(unsigned short port, unsigned char value)
+{
+#ifdef ROCKET_DEBUG_IO
+	printk("sOutB(%x, %x)...", port, value);
+#endif
+	outb_p(value, port);
+}
+
+static inline void sOutW(unsigned short port, unsigned short value)
+{
+#ifdef ROCKET_DEBUG_IO
+	printk("sOutW(%x, %x)...", port, value);
+#endif
+	outw_p(value, port);
+}
+
+static inline void sOutDW(unsigned short port, unsigned long value)
+{
+#ifdef ROCKET_DEBUG_IO
+	printk("sOutDW(%x, %lx)...", port, value);
+#endif
+	outl_p(cpu_to_le32(value), port);
+}
+
+static inline unsigned char sInB(unsigned short port)
+{
+	return inb_p(port);
+}
+
+static inline unsigned short sInW(unsigned short port)
+{
+	return inw_p(port);
+}
+
+#else				/* !ROCKET_DEBUG_IO */
+#define sOutB(a, b) outb_p(b, a)
+#define sOutW(a, b) outw_p(b, a)
+#define sOutDW(port, value) outl_p(cpu_to_le32(value), port)
+#define sInB(a) (inb_p(a))
+#define sInW(a) (inw_p(a))
+#endif				/* ROCKET_DEBUG_IO */
+
+/* This is used to move arrays of bytes so byte swapping isn't appropriate. */
+#define sOutStrW(port, addr, count) if (count) outsw(port, addr, count)
+#define sInStrW(port, addr, count) if (count) insw(port, addr, count)
+
+#define CTL_SIZE 8
+#define AIOP_CTL_SIZE 4
+#define CHAN_AIOP_SIZE 8
+#define MAX_PORTS_PER_AIOP 8
+#define MAX_AIOPS_PER_BOARD 4
+#define MAX_PORTS_PER_BOARD 32
+
+/* Bus type ID */
+#define	isISA	0
+#define	isPCI	1
+#define	isMC	2
+
+/* Controller ID numbers */
+#define CTLID_NULL  -1		/* no controller exists */
+#define CTLID_0001  0x0001	/* controller release 1 */
+
+/* AIOP ID numbers, identifies AIOP type implementing channel */
+#define AIOPID_NULL -1		/* no AIOP or channel exists */
+#define AIOPID_0001 0x0001	/* AIOP release 1 */
+
+#define NULLDEV -1		/* identifies non-existant device */
+#define NULLCTL -1		/* identifies non-existant controller */
+#define NULLCTLPTR (CONTROLLER_T *)0	/* identifies non-existant controller */
+#define NULLAIOP -1		/* identifies non-existant AIOP */
+#define NULLCHAN -1		/* identifies non-existant channel */
+
+/************************************************************************
+ Global Register Offsets - Direct Access - Fixed values
+************************************************************************/
+
+#define _CMD_REG   0x38		/* Command Register            8    Write */
+#define _INT_CHAN  0x39		/* Interrupt Channel Register  8    Read */
+#define _INT_MASK  0x3A		/* Interrupt Mask Register     8    Read / Write */
+#define _UNUSED    0x3B		/* Unused                      8 */
+#define _INDX_ADDR 0x3C		/* Index Register Address      16   Write */
+#define _INDX_DATA 0x3E		/* Index Register Data         8/16 Read / Write */
+
+/************************************************************************
+ Channel Register Offsets for 1st channel in AIOP - Direct Access
+************************************************************************/
+#define _TD0       0x00		/* Transmit Data               16   Write */
+#define _RD0       0x00		/* Receive Data                16   Read */
+#define _CHN_STAT0 0x20		/* Channel Status              8/16 Read / Write */
+#define _FIFO_CNT0 0x10		/* Transmit/Receive FIFO Count 16   Read */
+#define _INT_ID0   0x30		/* Interrupt Identification    8    Read */
+
+/************************************************************************
+ Tx Control Register Offsets - Indexed - External - Fixed
+************************************************************************/
+#define _TX_ENBLS  0x980	/* Tx Processor Enables Register 8 Read / Write */
+#define _TXCMP1    0x988	/* Transmit Compare Value #1     8 Read / Write */
+#define _TXCMP2    0x989	/* Transmit Compare Value #2     8 Read / Write */
+#define _TXREP1B1  0x98A	/* Tx Replace Value #1 - Byte 1  8 Read / Write */
+#define _TXREP1B2  0x98B	/* Tx Replace Value #1 - Byte 2  8 Read / Write */
+#define _TXREP2    0x98C	/* Transmit Replace Value #2     8 Read / Write */
+
+/************************************************************************
+Memory Controller Register Offsets - Indexed - External - Fixed
+************************************************************************/
+#define _RX_FIFO    0x000	/* Rx FIFO */
+#define _TX_FIFO    0x800	/* Tx FIFO */
+#define _RXF_OUTP   0x990	/* Rx FIFO OUT pointer        16 Read / Write */
+#define _RXF_INP    0x992	/* Rx FIFO IN pointer         16 Read / Write */
+#define _TXF_OUTP   0x994	/* Tx FIFO OUT pointer        8  Read / Write */
+#define _TXF_INP    0x995	/* Tx FIFO IN pointer         8  Read / Write */
+#define _TXP_CNT    0x996	/* Tx Priority Count          8  Read / Write */
+#define _TXP_PNTR   0x997	/* Tx Priority Pointer        8  Read / Write */
+
+#define PRI_PEND    0x80	/* Priority data pending (bit7, Tx pri cnt) */
+#define TXFIFO_SIZE 255		/* size of Tx FIFO */
+#define RXFIFO_SIZE 1023	/* size of Rx FIFO */
+
+/************************************************************************
+Tx Priority Buffer - Indexed - External - Fixed
+************************************************************************/
+#define _TXP_BUF    0x9C0	/* Tx Priority Buffer  32  Bytes   Read / Write */
+#define TXP_SIZE    0x20	/* 32 bytes */
+
+/************************************************************************
+Channel Register Offsets - Indexed - Internal - Fixed
+************************************************************************/
+
+#define _TX_CTRL    0xFF0	/* Transmit Control               16  Write */
+#define _RX_CTRL    0xFF2	/* Receive Control                 8  Write */
+#define _BAUD       0xFF4	/* Baud Rate                      16  Write */
+#define _CLK_PRE    0xFF6	/* Clock Prescaler                 8  Write */
+
+#define STMBREAK   0x08		/* BREAK */
+#define STMFRAME   0x04		/* framing error */
+#define STMRCVROVR 0x02		/* receiver over run error */
+#define STMPARITY  0x01		/* parity error */
+#define STMERROR   (STMBREAK | STMFRAME | STMPARITY)
+#define STMBREAKH   0x800	/* BREAK */
+#define STMFRAMEH   0x400	/* framing error */
+#define STMRCVROVRH 0x200	/* receiver over run error */
+#define STMPARITYH  0x100	/* parity error */
+#define STMERRORH   (STMBREAKH | STMFRAMEH | STMPARITYH)
+
+#define CTS_ACT   0x20		/* CTS input asserted */
+#define DSR_ACT   0x10		/* DSR input asserted */
+#define CD_ACT    0x08		/* CD input asserted */
+#define TXFIFOMT  0x04		/* Tx FIFO is empty */
+#define TXSHRMT   0x02		/* Tx shift register is empty */
+#define RDA       0x01		/* Rx data available */
+#define DRAINED (TXFIFOMT | TXSHRMT)	/* indicates Tx is drained */
+
+#define STATMODE  0x8000	/* status mode enable bit */
+#define RXFOVERFL 0x2000	/* receive FIFO overflow */
+#define RX2MATCH  0x1000	/* receive compare byte 2 match */
+#define RX1MATCH  0x0800	/* receive compare byte 1 match */
+#define RXBREAK   0x0400	/* received BREAK */
+#define RXFRAME   0x0200	/* received framing error */
+#define RXPARITY  0x0100	/* received parity error */
+#define STATERROR (RXBREAK | RXFRAME | RXPARITY)
+
+#define CTSFC_EN  0x80		/* CTS flow control enable bit */
+#define RTSTOG_EN 0x40		/* RTS toggle enable bit */
+#define TXINT_EN  0x10		/* transmit interrupt enable */
+#define STOP2     0x08		/* enable 2 stop bits (0 = 1 stop) */
+#define PARITY_EN 0x04		/* enable parity (0 = no parity) */
+#define EVEN_PAR  0x02		/* even parity (0 = odd parity) */
+#define DATA8BIT  0x01		/* 8 bit data (0 = 7 bit data) */
+
+#define SETBREAK  0x10		/* send break condition (must clear) */
+#define LOCALLOOP 0x08		/* local loopback set for test */
+#define SET_DTR   0x04		/* assert DTR */
+#define SET_RTS   0x02		/* assert RTS */
+#define TX_ENABLE 0x01		/* enable transmitter */
+
+#define RTSFC_EN  0x40		/* RTS flow control enable */
+#define RXPROC_EN 0x20		/* receive processor enable */
+#define TRIG_NO   0x00		/* Rx FIFO trigger level 0 (no trigger) */
+#define TRIG_1    0x08		/* trigger level 1 char */
+#define TRIG_1_2  0x10		/* trigger level 1/2 */
+#define TRIG_7_8  0x18		/* trigger level 7/8 */
+#define TRIG_MASK 0x18		/* trigger level mask */
+#define SRCINT_EN 0x04		/* special Rx condition interrupt enable */
+#define RXINT_EN  0x02		/* Rx interrupt enable */
+#define MCINT_EN  0x01		/* modem change interrupt enable */
+
+#define RXF_TRIG  0x20		/* Rx FIFO trigger level interrupt */
+#define TXFIFO_MT 0x10		/* Tx FIFO empty interrupt */
+#define SRC_INT   0x08		/* special receive condition interrupt */
+#define DELTA_CD  0x04		/* CD change interrupt */
+#define DELTA_CTS 0x02		/* CTS change interrupt */
+#define DELTA_DSR 0x01		/* DSR change interrupt */
+
+#define REP1W2_EN 0x10		/* replace byte 1 with 2 bytes enable */
+#define IGN2_EN   0x08		/* ignore byte 2 enable */
+#define IGN1_EN   0x04		/* ignore byte 1 enable */
+#define COMP2_EN  0x02		/* compare byte 2 enable */
+#define COMP1_EN  0x01		/* compare byte 1 enable */
+
+#define RESET_ALL 0x80		/* reset AIOP (all channels) */
+#define TXOVERIDE 0x40		/* Transmit software off override */
+#define RESETUART 0x20		/* reset channel's UART */
+#define RESTXFCNT 0x10		/* reset channel's Tx FIFO count register */
+#define RESRXFCNT 0x08		/* reset channel's Rx FIFO count register */
+
+#define INTSTAT0  0x01		/* AIOP 0 interrupt status */
+#define INTSTAT1  0x02		/* AIOP 1 interrupt status */
+#define INTSTAT2  0x04		/* AIOP 2 interrupt status */
+#define INTSTAT3  0x08		/* AIOP 3 interrupt status */
+
+#define INTR_EN   0x08		/* allow interrupts to host */
+#define INT_STROB 0x04		/* strobe and clear interrupt line (EOI) */
+
+/**************************************************************************
+ MUDBAC remapped for PCI
+**************************************************************************/
+
+#define _CFG_INT_PCI  0x40
+#define _PCI_INT_FUNC 0x3A
+
+#define PCI_STROB 0x2000	/* bit 13 of int aiop register */
+#define INTR_EN_PCI   0x0010	/* allow interrupts to host */
+
+/*
+ * Definitions for Universal PCI board registers
+ */
+#define _PCI_9030_INT_CTRL	0x4c          /* Offsets from BAR1 */
+#define _PCI_9030_GPIO_CTRL	0x54
+#define PCI_INT_CTRL_AIOP	0x0001
+#define PCI_GPIO_CTRL_8PORT	0x4000
+#define _PCI_9030_RING_IND	0xc0          /* Offsets from BAR1 */
+
+#define CHAN3_EN  0x08		/* enable AIOP 3 */
+#define CHAN2_EN  0x04		/* enable AIOP 2 */
+#define CHAN1_EN  0x02		/* enable AIOP 1 */
+#define CHAN0_EN  0x01		/* enable AIOP 0 */
+#define FREQ_DIS  0x00
+#define FREQ_274HZ 0x60
+#define FREQ_137HZ 0x50
+#define FREQ_69HZ  0x40
+#define FREQ_34HZ  0x30
+#define FREQ_17HZ  0x20
+#define FREQ_9HZ   0x10
+#define PERIODIC_ONLY 0x80	/* only PERIODIC interrupt */
+
+#define CHANINT_EN 0x0100	/* flags to enable/disable channel ints */
+
+#define RDATASIZE 72
+#define RREGDATASIZE 52
+
+/*
+ * AIOP interrupt bits for ISA/PCI boards and UPCI boards.
+ */
+#define AIOP_INTR_BIT_0		0x0001
+#define AIOP_INTR_BIT_1		0x0002
+#define AIOP_INTR_BIT_2		0x0004
+#define AIOP_INTR_BIT_3		0x0008
+
+#define AIOP_INTR_BITS ( \
+	AIOP_INTR_BIT_0 \
+	| AIOP_INTR_BIT_1 \
+	| AIOP_INTR_BIT_2 \
+	| AIOP_INTR_BIT_3)
+
+#define UPCI_AIOP_INTR_BIT_0	0x0004
+#define UPCI_AIOP_INTR_BIT_1	0x0020
+#define UPCI_AIOP_INTR_BIT_2	0x0100
+#define UPCI_AIOP_INTR_BIT_3	0x0800
+
+#define UPCI_AIOP_INTR_BITS ( \
+	UPCI_AIOP_INTR_BIT_0 \
+	| UPCI_AIOP_INTR_BIT_1 \
+	| UPCI_AIOP_INTR_BIT_2 \
+	| UPCI_AIOP_INTR_BIT_3)
+
+/* Controller level information structure */
+typedef struct {
+	int CtlID;
+	int CtlNum;
+	int BusType;
+	int boardType;
+	int isUPCI;
+	WordIO_t PCIIO;
+	WordIO_t PCIIO2;
+	ByteIO_t MBaseIO;
+	ByteIO_t MReg1IO;
+	ByteIO_t MReg2IO;
+	ByteIO_t MReg3IO;
+	Byte_t MReg2;
+	Byte_t MReg3;
+	int NumAiop;
+	int AltChanRingIndicator;
+	ByteIO_t UPCIRingInd;
+	WordIO_t AiopIO[AIOP_CTL_SIZE];
+	ByteIO_t AiopIntChanIO[AIOP_CTL_SIZE];
+	int AiopID[AIOP_CTL_SIZE];
+	int AiopNumChan[AIOP_CTL_SIZE];
+	Word_t *AiopIntrBits;
+} CONTROLLER_T;
+
+typedef CONTROLLER_T CONTROLLER_t;
+
+/* Channel level information structure */
+typedef struct {
+	CONTROLLER_T *CtlP;
+	int AiopNum;
+	int ChanID;
+	int ChanNum;
+	int rtsToggle;
+
+	ByteIO_t Cmd;
+	ByteIO_t IntChan;
+	ByteIO_t IntMask;
+	DWordIO_t IndexAddr;
+	WordIO_t IndexData;
+
+	WordIO_t TxRxData;
+	WordIO_t ChanStat;
+	WordIO_t TxRxCount;
+	ByteIO_t IntID;
+
+	Word_t TxFIFO;
+	Word_t TxFIFOPtrs;
+	Word_t RxFIFO;
+	Word_t RxFIFOPtrs;
+	Word_t TxPrioCnt;
+	Word_t TxPrioPtr;
+	Word_t TxPrioBuf;
+
+	Byte_t R[RREGDATASIZE];
+
+	Byte_t BaudDiv[4];
+	Byte_t TxControl[4];
+	Byte_t RxControl[4];
+	Byte_t TxEnables[4];
+	Byte_t TxCompare[4];
+	Byte_t TxReplace1[4];
+	Byte_t TxReplace2[4];
+} CHANNEL_T;
+
+typedef CHANNEL_T CHANNEL_t;
+typedef CHANNEL_T *CHANPTR_T;
+
+#define InterfaceModeRS232  0x00
+#define InterfaceModeRS422  0x08
+#define InterfaceModeRS485  0x10
+#define InterfaceModeRS232T 0x18
+
+/***************************************************************************
+Function: sClrBreak
+Purpose:  Stop sending a transmit BREAK signal
+Call:     sClrBreak(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sClrBreak(ChP) \
+do { \
+   (ChP)->TxControl[3] &= ~SETBREAK; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sClrDTR
+Purpose:  Clr the DTR output
+Call:     sClrDTR(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sClrDTR(ChP) \
+do { \
+   (ChP)->TxControl[3] &= ~SET_DTR; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sClrRTS
+Purpose:  Clr the RTS output
+Call:     sClrRTS(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sClrRTS(ChP) \
+do { \
+   if ((ChP)->rtsToggle) break; \
+   (ChP)->TxControl[3] &= ~SET_RTS; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sClrTxXOFF
+Purpose:  Clear any existing transmit software flow control off condition
+Call:     sClrTxXOFF(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sClrTxXOFF(ChP) \
+do { \
+   sOutB((ChP)->Cmd,TXOVERIDE | (Byte_t)(ChP)->ChanNum); \
+   sOutB((ChP)->Cmd,(Byte_t)(ChP)->ChanNum); \
+} while (0)
+
+/***************************************************************************
+Function: sCtlNumToCtlPtr
+Purpose:  Convert a controller number to controller structure pointer
+Call:     sCtlNumToCtlPtr(CtlNum)
+          int CtlNum; Controller number
+Return:   CONTROLLER_T *: Ptr to controller structure
+*/
+#define sCtlNumToCtlPtr(CTLNUM) &sController[CTLNUM]
+
+/***************************************************************************
+Function: sControllerEOI
+Purpose:  Strobe the MUDBAC's End Of Interrupt bit.
+Call:     sControllerEOI(CtlP)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+*/
+#define sControllerEOI(CTLP) sOutB((CTLP)->MReg2IO,(CTLP)->MReg2 | INT_STROB)
+
+/***************************************************************************
+Function: sPCIControllerEOI
+Purpose:  Strobe the PCI End Of Interrupt bit.
+          For the UPCI boards, toggle the AIOP interrupt enable bit
+	  (this was taken from the Windows driver).
+Call:     sPCIControllerEOI(CtlP)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+*/
+#define sPCIControllerEOI(CTLP) \
+do { \
+    if ((CTLP)->isUPCI) { \
+	Word_t w = sInW((CTLP)->PCIIO); \
+	sOutW((CTLP)->PCIIO, (w ^ PCI_INT_CTRL_AIOP)); \
+	sOutW((CTLP)->PCIIO, w); \
+    } \
+    else { \
+	sOutW((CTLP)->PCIIO, PCI_STROB); \
+    } \
+} while (0)
+
+/***************************************************************************
+Function: sDisAiop
+Purpose:  Disable I/O access to an AIOP
+Call:     sDisAiop(CltP)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+          int AiopNum; Number of AIOP on controller
+*/
+#define sDisAiop(CTLP,AIOPNUM) \
+do { \
+   (CTLP)->MReg3 &= sBitMapClrTbl[AIOPNUM]; \
+   sOutB((CTLP)->MReg3IO,(CTLP)->MReg3); \
+} while (0)
+
+/***************************************************************************
+Function: sDisCTSFlowCtl
+Purpose:  Disable output flow control using CTS
+Call:     sDisCTSFlowCtl(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisCTSFlowCtl(ChP) \
+do { \
+   (ChP)->TxControl[2] &= ~CTSFC_EN; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sDisIXANY
+Purpose:  Disable IXANY Software Flow Control
+Call:     sDisIXANY(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisIXANY(ChP) \
+do { \
+   (ChP)->R[0x0e] = 0x86; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x0c]); \
+} while (0)
+
+/***************************************************************************
+Function: DisParity
+Purpose:  Disable parity
+Call:     sDisParity(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: Function sSetParity() can be used in place of functions sEnParity(),
+          sDisParity(), sSetOddParity(), and sSetEvenParity().
+*/
+#define sDisParity(ChP) \
+do { \
+   (ChP)->TxControl[2] &= ~PARITY_EN; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sDisRTSToggle
+Purpose:  Disable RTS toggle
+Call:     sDisRTSToggle(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisRTSToggle(ChP) \
+do { \
+   (ChP)->TxControl[2] &= ~RTSTOG_EN; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+   (ChP)->rtsToggle = 0; \
+} while (0)
+
+/***************************************************************************
+Function: sDisRxFIFO
+Purpose:  Disable Rx FIFO
+Call:     sDisRxFIFO(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisRxFIFO(ChP) \
+do { \
+   (ChP)->R[0x32] = 0x0a; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x30]); \
+} while (0)
+
+/***************************************************************************
+Function: sDisRxStatusMode
+Purpose:  Disable the Rx status mode
+Call:     sDisRxStatusMode(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: This takes the channel out of the receive status mode.  All
+          subsequent reads of receive data using sReadRxWord() will return
+          two data bytes.
+*/
+#define sDisRxStatusMode(ChP) sOutW((ChP)->ChanStat,0)
+
+/***************************************************************************
+Function: sDisTransmit
+Purpose:  Disable transmit
+Call:     sDisTransmit(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+          This disables movement of Tx data from the Tx FIFO into the 1 byte
+          Tx buffer.  Therefore there could be up to a 2 byte latency
+          between the time sDisTransmit() is called and the transmit buffer
+          and transmit shift register going completely empty.
+*/
+#define sDisTransmit(ChP) \
+do { \
+   (ChP)->TxControl[3] &= ~TX_ENABLE; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sDisTxSoftFlowCtl
+Purpose:  Disable Tx Software Flow Control
+Call:     sDisTxSoftFlowCtl(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sDisTxSoftFlowCtl(ChP) \
+do { \
+   (ChP)->R[0x06] = 0x8a; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x04]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnAiop
+Purpose:  Enable I/O access to an AIOP
+Call:     sEnAiop(CltP)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+          int AiopNum; Number of AIOP on controller
+*/
+#define sEnAiop(CTLP,AIOPNUM) \
+do { \
+   (CTLP)->MReg3 |= sBitMapSetTbl[AIOPNUM]; \
+   sOutB((CTLP)->MReg3IO,(CTLP)->MReg3); \
+} while (0)
+
+/***************************************************************************
+Function: sEnCTSFlowCtl
+Purpose:  Enable output flow control using CTS
+Call:     sEnCTSFlowCtl(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnCTSFlowCtl(ChP) \
+do { \
+   (ChP)->TxControl[2] |= CTSFC_EN; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnIXANY
+Purpose:  Enable IXANY Software Flow Control
+Call:     sEnIXANY(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnIXANY(ChP) \
+do { \
+   (ChP)->R[0x0e] = 0x21; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x0c]); \
+} while (0)
+
+/***************************************************************************
+Function: EnParity
+Purpose:  Enable parity
+Call:     sEnParity(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: Function sSetParity() can be used in place of functions sEnParity(),
+          sDisParity(), sSetOddParity(), and sSetEvenParity().
+
+Warnings: Before enabling parity odd or even parity should be chosen using
+          functions sSetOddParity() or sSetEvenParity().
+*/
+#define sEnParity(ChP) \
+do { \
+   (ChP)->TxControl[2] |= PARITY_EN; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnRTSToggle
+Purpose:  Enable RTS toggle
+Call:     sEnRTSToggle(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: This function will disable RTS flow control and clear the RTS
+          line to allow operation of RTS toggle.
+*/
+#define sEnRTSToggle(ChP) \
+do { \
+   (ChP)->RxControl[2] &= ~RTSFC_EN; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->RxControl[0]); \
+   (ChP)->TxControl[2] |= RTSTOG_EN; \
+   (ChP)->TxControl[3] &= ~SET_RTS; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+   (ChP)->rtsToggle = 1; \
+} while (0)
+
+/***************************************************************************
+Function: sEnRxFIFO
+Purpose:  Enable Rx FIFO
+Call:     sEnRxFIFO(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnRxFIFO(ChP) \
+do { \
+   (ChP)->R[0x32] = 0x08; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x30]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnRxProcessor
+Purpose:  Enable the receive processor
+Call:     sEnRxProcessor(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: This function is used to start the receive processor.  When
+          the channel is in the reset state the receive processor is not
+          running.  This is done to prevent the receive processor from
+          executing invalid microcode instructions prior to the
+          downloading of the microcode.
+
+Warnings: This function must be called after valid microcode has been
+          downloaded to the AIOP, and it must not be called before the
+          microcode has been downloaded.
+*/
+#define sEnRxProcessor(ChP) \
+do { \
+   (ChP)->RxControl[2] |= RXPROC_EN; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->RxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnRxStatusMode
+Purpose:  Enable the Rx status mode
+Call:     sEnRxStatusMode(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: This places the channel in the receive status mode.  All subsequent
+          reads of receive data using sReadRxWord() will return a data byte
+          in the low word and a status byte in the high word.
+
+*/
+#define sEnRxStatusMode(ChP) sOutW((ChP)->ChanStat,STATMODE)
+
+/***************************************************************************
+Function: sEnTransmit
+Purpose:  Enable transmit
+Call:     sEnTransmit(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnTransmit(ChP) \
+do { \
+   (ChP)->TxControl[3] |= TX_ENABLE; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sEnTxSoftFlowCtl
+Purpose:  Enable Tx Software Flow Control
+Call:     sEnTxSoftFlowCtl(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sEnTxSoftFlowCtl(ChP) \
+do { \
+   (ChP)->R[0x06] = 0xc5; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x04]); \
+} while (0)
+
+/***************************************************************************
+Function: sGetAiopIntStatus
+Purpose:  Get the AIOP interrupt status
+Call:     sGetAiopIntStatus(CtlP,AiopNum)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+          int AiopNum; AIOP number
+Return:   Byte_t: The AIOP interrupt status.  Bits 0 through 7
+                         represent channels 0 through 7 respectively.  If a
+                         bit is set that channel is interrupting.
+*/
+#define sGetAiopIntStatus(CTLP,AIOPNUM) sInB((CTLP)->AiopIntChanIO[AIOPNUM])
+
+/***************************************************************************
+Function: sGetAiopNumChan
+Purpose:  Get the number of channels supported by an AIOP
+Call:     sGetAiopNumChan(CtlP,AiopNum)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+          int AiopNum; AIOP number
+Return:   int: The number of channels supported by the AIOP
+*/
+#define sGetAiopNumChan(CTLP,AIOPNUM) (CTLP)->AiopNumChan[AIOPNUM]
+
+/***************************************************************************
+Function: sGetChanIntID
+Purpose:  Get a channel's interrupt identification byte
+Call:     sGetChanIntID(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   Byte_t: The channel interrupt ID.  Can be any
+             combination of the following flags:
+                RXF_TRIG:     Rx FIFO trigger level interrupt
+                TXFIFO_MT:    Tx FIFO empty interrupt
+                SRC_INT:      Special receive condition interrupt
+                DELTA_CD:     CD change interrupt
+                DELTA_CTS:    CTS change interrupt
+                DELTA_DSR:    DSR change interrupt
+*/
+#define sGetChanIntID(ChP) (sInB((ChP)->IntID) & (RXF_TRIG | TXFIFO_MT | SRC_INT | DELTA_CD | DELTA_CTS | DELTA_DSR))
+
+/***************************************************************************
+Function: sGetChanNum
+Purpose:  Get the number of a channel within an AIOP
+Call:     sGetChanNum(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   int: Channel number within AIOP, or NULLCHAN if channel does
+               not exist.
+*/
+#define sGetChanNum(ChP) (ChP)->ChanNum
+
+/***************************************************************************
+Function: sGetChanStatus
+Purpose:  Get the channel status
+Call:     sGetChanStatus(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   Word_t: The channel status.  Can be any combination of
+             the following flags:
+                LOW BYTE FLAGS
+                CTS_ACT:      CTS input asserted
+                DSR_ACT:      DSR input asserted
+                CD_ACT:       CD input asserted
+                TXFIFOMT:     Tx FIFO is empty
+                TXSHRMT:      Tx shift register is empty
+                RDA:          Rx data available
+
+                HIGH BYTE FLAGS
+                STATMODE:     status mode enable bit
+                RXFOVERFL:    receive FIFO overflow
+                RX2MATCH:     receive compare byte 2 match
+                RX1MATCH:     receive compare byte 1 match
+                RXBREAK:      received BREAK
+                RXFRAME:      received framing error
+                RXPARITY:     received parity error
+Warnings: This function will clear the high byte flags in the Channel
+          Status Register.
+*/
+#define sGetChanStatus(ChP) sInW((ChP)->ChanStat)
+
+/***************************************************************************
+Function: sGetChanStatusLo
+Purpose:  Get the low byte only of the channel status
+Call:     sGetChanStatusLo(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   Byte_t: The channel status low byte.  Can be any combination
+             of the following flags:
+                CTS_ACT:      CTS input asserted
+                DSR_ACT:      DSR input asserted
+                CD_ACT:       CD input asserted
+                TXFIFOMT:     Tx FIFO is empty
+                TXSHRMT:      Tx shift register is empty
+                RDA:          Rx data available
+*/
+#define sGetChanStatusLo(ChP) sInB((ByteIO_t)(ChP)->ChanStat)
+
+/**********************************************************************
+ * Get RI status of channel
+ * Defined as a function in rocket.c   -aes
+ */
+#if 0
+#define sGetChanRI(ChP) ((ChP)->CtlP->AltChanRingIndicator ? \
+                          (sInB((ByteIO_t)((ChP)->ChanStat+8)) & DSR_ACT) : \
+                            (((ChP)->CtlP->boardType == ROCKET_TYPE_PC104) ? \
+                               (!(sInB((ChP)->CtlP->AiopIO[3]) & sBitMapSetTbl[(ChP)->ChanNum])) : \
+                             0))
+#endif
+
+/***************************************************************************
+Function: sGetControllerIntStatus
+Purpose:  Get the controller interrupt status
+Call:     sGetControllerIntStatus(CtlP)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+Return:   Byte_t: The controller interrupt status in the lower 4
+                         bits.  Bits 0 through 3 represent AIOP's 0
+                         through 3 respectively.  If a bit is set that
+                         AIOP is interrupting.  Bits 4 through 7 will
+                         always be cleared.
+*/
+#define sGetControllerIntStatus(CTLP) (sInB((CTLP)->MReg1IO) & 0x0f)
+
+/***************************************************************************
+Function: sPCIGetControllerIntStatus
+Purpose:  Get the controller interrupt status
+Call:     sPCIGetControllerIntStatus(CtlP)
+          CONTROLLER_T *CtlP; Ptr to controller structure
+Return:   unsigned char: The controller interrupt status in the lower 4
+                         bits and bit 4.  Bits 0 through 3 represent AIOP's 0
+                         through 3 respectively. Bit 4 is set if the int 
+			 was generated from periodic. If a bit is set the
+			 AIOP is interrupting.
+*/
+#define sPCIGetControllerIntStatus(CTLP) \
+	((CTLP)->isUPCI ? \
+	  (sInW((CTLP)->PCIIO2) & UPCI_AIOP_INTR_BITS) : \
+	  ((sInW((CTLP)->PCIIO) >> 8) & AIOP_INTR_BITS))
+
+/***************************************************************************
+
+Function: sGetRxCnt
+Purpose:  Get the number of data bytes in the Rx FIFO
+Call:     sGetRxCnt(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   int: The number of data bytes in the Rx FIFO.
+Comments: Byte read of count register is required to obtain Rx count.
+
+*/
+#define sGetRxCnt(ChP) sInW((ChP)->TxRxCount)
+
+/***************************************************************************
+Function: sGetTxCnt
+Purpose:  Get the number of data bytes in the Tx FIFO
+Call:     sGetTxCnt(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   Byte_t: The number of data bytes in the Tx FIFO.
+Comments: Byte read of count register is required to obtain Tx count.
+
+*/
+#define sGetTxCnt(ChP) sInB((ByteIO_t)(ChP)->TxRxCount)
+
+/*****************************************************************************
+Function: sGetTxRxDataIO
+Purpose:  Get the I/O address of a channel's TxRx Data register
+Call:     sGetTxRxDataIO(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Return:   WordIO_t: I/O address of a channel's TxRx Data register
+*/
+#define sGetTxRxDataIO(ChP) (ChP)->TxRxData
+
+/***************************************************************************
+Function: sInitChanDefaults
+Purpose:  Initialize a channel structure to it's default state.
+Call:     sInitChanDefaults(ChP)
+          CHANNEL_T *ChP; Ptr to the channel structure
+Comments: This function must be called once for every channel structure
+          that exists before any other SSCI calls can be made.
+
+*/
+#define sInitChanDefaults(ChP) \
+do { \
+   (ChP)->CtlP = NULLCTLPTR; \
+   (ChP)->AiopNum = NULLAIOP; \
+   (ChP)->ChanID = AIOPID_NULL; \
+   (ChP)->ChanNum = NULLCHAN; \
+} while (0)
+
+/***************************************************************************
+Function: sResetAiopByNum
+Purpose:  Reset the AIOP by number
+Call:     sResetAiopByNum(CTLP,AIOPNUM)
+	CONTROLLER_T CTLP; Ptr to controller structure
+	AIOPNUM; AIOP index 
+*/
+#define sResetAiopByNum(CTLP,AIOPNUM) \
+do { \
+   sOutB((CTLP)->AiopIO[(AIOPNUM)]+_CMD_REG,RESET_ALL); \
+   sOutB((CTLP)->AiopIO[(AIOPNUM)]+_CMD_REG,0x0); \
+} while (0)
+
+/***************************************************************************
+Function: sSendBreak
+Purpose:  Send a transmit BREAK signal
+Call:     sSendBreak(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSendBreak(ChP) \
+do { \
+   (ChP)->TxControl[3] |= SETBREAK; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetBaud
+Purpose:  Set baud rate
+Call:     sSetBaud(ChP,Divisor)
+          CHANNEL_T *ChP; Ptr to channel structure
+          Word_t Divisor; 16 bit baud rate divisor for channel
+*/
+#define sSetBaud(ChP,DIVISOR) \
+do { \
+   (ChP)->BaudDiv[2] = (Byte_t)(DIVISOR); \
+   (ChP)->BaudDiv[3] = (Byte_t)((DIVISOR) >> 8); \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->BaudDiv[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetData7
+Purpose:  Set data bits to 7
+Call:     sSetData7(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetData7(ChP) \
+do { \
+   (ChP)->TxControl[2] &= ~DATA8BIT; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetData8
+Purpose:  Set data bits to 8
+Call:     sSetData8(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetData8(ChP) \
+do { \
+   (ChP)->TxControl[2] |= DATA8BIT; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetDTR
+Purpose:  Set the DTR output
+Call:     sSetDTR(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetDTR(ChP) \
+do { \
+   (ChP)->TxControl[3] |= SET_DTR; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetEvenParity
+Purpose:  Set even parity
+Call:     sSetEvenParity(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: Function sSetParity() can be used in place of functions sEnParity(),
+          sDisParity(), sSetOddParity(), and sSetEvenParity().
+
+Warnings: This function has no effect unless parity is enabled with function
+          sEnParity().
+*/
+#define sSetEvenParity(ChP) \
+do { \
+   (ChP)->TxControl[2] |= EVEN_PAR; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetOddParity
+Purpose:  Set odd parity
+Call:     sSetOddParity(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: Function sSetParity() can be used in place of functions sEnParity(),
+          sDisParity(), sSetOddParity(), and sSetEvenParity().
+
+Warnings: This function has no effect unless parity is enabled with function
+          sEnParity().
+*/
+#define sSetOddParity(ChP) \
+do { \
+   (ChP)->TxControl[2] &= ~EVEN_PAR; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetRTS
+Purpose:  Set the RTS output
+Call:     sSetRTS(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetRTS(ChP) \
+do { \
+   if ((ChP)->rtsToggle) break; \
+   (ChP)->TxControl[3] |= SET_RTS; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetRxTrigger
+Purpose:  Set the Rx FIFO trigger level
+Call:     sSetRxProcessor(ChP,Level)
+          CHANNEL_T *ChP; Ptr to channel structure
+          Byte_t Level; Number of characters in Rx FIFO at which the
+             interrupt will be generated.  Can be any of the following flags:
+
+             TRIG_NO:   no trigger
+             TRIG_1:    1 character in FIFO
+             TRIG_1_2:  FIFO 1/2 full
+             TRIG_7_8:  FIFO 7/8 full
+Comments: An interrupt will be generated when the trigger level is reached
+          only if function sEnInterrupt() has been called with flag
+          RXINT_EN set.  The RXF_TRIG flag in the Interrupt Idenfification
+          register will be set whenever the trigger level is reached
+          regardless of the setting of RXINT_EN.
+
+*/
+#define sSetRxTrigger(ChP,LEVEL) \
+do { \
+   (ChP)->RxControl[2] &= ~TRIG_MASK; \
+   (ChP)->RxControl[2] |= LEVEL; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->RxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetStop1
+Purpose:  Set stop bits to 1
+Call:     sSetStop1(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetStop1(ChP) \
+do { \
+   (ChP)->TxControl[2] &= ~STOP2; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetStop2
+Purpose:  Set stop bits to 2
+Call:     sSetStop2(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+*/
+#define sSetStop2(ChP) \
+do { \
+   (ChP)->TxControl[2] |= STOP2; \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->TxControl[0]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetTxXOFFChar
+Purpose:  Set the Tx XOFF flow control character
+Call:     sSetTxXOFFChar(ChP,Ch)
+          CHANNEL_T *ChP; Ptr to channel structure
+          Byte_t Ch; The value to set the Tx XOFF character to
+*/
+#define sSetTxXOFFChar(ChP,CH) \
+do { \
+   (ChP)->R[0x07] = (CH); \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x04]); \
+} while (0)
+
+/***************************************************************************
+Function: sSetTxXONChar
+Purpose:  Set the Tx XON flow control character
+Call:     sSetTxXONChar(ChP,Ch)
+          CHANNEL_T *ChP; Ptr to channel structure
+          Byte_t Ch; The value to set the Tx XON character to
+*/
+#define sSetTxXONChar(ChP,CH) \
+do { \
+   (ChP)->R[0x0b] = (CH); \
+   sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0x08]); \
+} while (0)
+
+/***************************************************************************
+Function: sStartRxProcessor
+Purpose:  Start a channel's receive processor
+Call:     sStartRxProcessor(ChP)
+          CHANNEL_T *ChP; Ptr to channel structure
+Comments: This function is used to start a Rx processor after it was
+          stopped with sStopRxProcessor() or sStopSWInFlowCtl().  It
+          will restart both the Rx processor and software input flow control.
+
+*/
+#define sStartRxProcessor(ChP) sOutDW((ChP)->IndexAddr,*(DWord_t *)&(ChP)->R[0])
+
+/***************************************************************************
+Function: sWriteTxByte
+Purpose:  Write a transmit data byte to a channel.
+          ByteIO_t io: Channel transmit register I/O address.  This can
+                           be obtained with sGetTxRxDataIO().
+          Byte_t Data; The transmit data byte.
+Warnings: This function writes the data byte without checking to see if
+          sMaxTxSize is exceeded in the Tx FIFO.
+*/
+#define sWriteTxByte(IO,DATA) sOutB(IO,DATA)
+
+int sInitController(CONTROLLER_T * CtlP,
+		    int CtlNum,
+		    ByteIO_t MudbacIO,
+		    ByteIO_t * AiopIOList,
+		    int AiopIOListSize,
+		    int IRQNum, Byte_t Frequency, int PeriodicOnly);
+
+int sPCIInitController(CONTROLLER_T * CtlP,
+		       int CtlNum,
+		       ByteIO_t * AiopIOList,
+		       int AiopIOListSize,
+		       WordIO_t ConfigIO,
+		       int IRQNum,
+		       Byte_t Frequency,
+		       int PeriodicOnly,
+		       int altChanRingIndicator, int UPCIRingInd);
+
+int sReadAiopID(ByteIO_t io);
+int sReadAiopNumChan(WordIO_t io);
+int sInitChan(CONTROLLER_T * CtlP,
+	      CHANNEL_T * ChP, int AiopNum, int ChanNum);
+Byte_t sGetRxErrStatus(CHANNEL_T * ChP);
+void sStopRxProcessor(CHANNEL_T * ChP);
+void sStopSWInFlowCtl(CHANNEL_T * ChP);
+void sFlushRxFIFO(CHANNEL_T * ChP);
+void sFlushTxFIFO(CHANNEL_T * ChP);
+int sWriteTxPrioByte(CHANNEL_T * ChP, Byte_t Data);
+void sEnInterrupts(CHANNEL_T * ChP, Word_t Flags);
+void sDisInterrupts(CHANNEL_T * ChP, Word_t Flags);
+void sModemReset(CONTROLLER_T * CtlP, int chan, int on);
+void sPCIModemReset(CONTROLLER_T * CtlP, int chan, int on);
+void sSetInterfaceMode(CHANNEL_T * ChP, Byte_t mode);
+
+extern Byte_t R[RDATASIZE];
+extern CONTROLLER_T sController[CTL_SIZE];
+extern Byte_t sIRQMap[16];
+extern Byte_t sBitMapClrTbl[8];
+extern Byte_t sBitMapSetTbl[8];
+extern int sClockPrescale;
+
+/*
+ * Begin Linux specific definitions for the Rocketport driver
+ *
+ * This code is Copyright Theodore Ts'o, 1995-1997
+ */
+
+struct r_port {
+	int magic;
+	int line;
+	int flags;
+	int count;
+	int blocked_open;
+	struct tty_struct *tty;
+	unsigned int board:3;
+	unsigned int aiop:2;
+	unsigned int chan:3;
+	CONTROLLER_t *ctlp;
+	CHANNEL_t channel;
+	int closing_wait;
+	int close_delay;
+	int intmask;
+	int xmit_fifo_room;	/* room in xmit fifo */
+	unsigned char *xmit_buf;
+	int xmit_head;
+	int xmit_tail;
+	int xmit_cnt;
+	int session;
+	int pgrp;
+	int cd_status;
+	int ignore_status_mask;
+	int read_status_mask;
+	int cps;
+
+#ifdef DECLARE_WAITQUEUE
+	wait_queue_head_t open_wait;
+	wait_queue_head_t close_wait;
+#else
+	struct wait_queue *open_wait;
+	struct wait_queue *close_wait;
+#endif
+	spinlock_t slock;
+	struct semaphore write_sem;
+};
+
+#define RPORT_MAGIC 0x525001
+
+#define NUM_BOARDS 8
+#define MAX_RP_PORTS (32*NUM_BOARDS)
+
+/*
+ * The size of the xmit buffer is 1 page, or 4096 bytes
+ */
+#define XMIT_BUF_SIZE 4096
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+/* Internal flags used only by the rocketport driver */
+#define ROCKET_INITIALIZED	0x80000000	/* Port is active */
+#define ROCKET_CLOSING		0x40000000	/* Serial port is closing */
+#define ROCKET_NORMAL_ACTIVE	0x20000000	/* Normal port is active */
+
+/* tty subtypes */
+#define SERIAL_TYPE_NORMAL 1
+
+/*
+ * Assigned major numbers for the Comtrol Rocketport
+ */
+#define TTY_ROCKET_MAJOR	46
+#define CUA_ROCKET_MAJOR	47
+
+#ifdef PCI_VENDOR_ID_RP
+#undef PCI_VENDOR_ID_RP
+#undef PCI_DEVICE_ID_RP8OCTA
+#undef PCI_DEVICE_ID_RP8INTF
+#undef PCI_DEVICE_ID_RP16INTF
+#undef PCI_DEVICE_ID_RP32INTF
+#undef PCI_DEVICE_ID_URP8OCTA
+#undef PCI_DEVICE_ID_URP8INTF
+#undef PCI_DEVICE_ID_URP16INTF
+#undef PCI_DEVICE_ID_CRP16INTF
+#undef PCI_DEVICE_ID_URP32INTF
+#endif
+
+/*  Comtrol PCI Vendor ID */
+#define PCI_VENDOR_ID_RP		0x11fe
+
+/*  Comtrol Device ID's */
+#define PCI_DEVICE_ID_RP32INTF		0x0001	/* Rocketport 32 port w/external I/F     */
+#define PCI_DEVICE_ID_RP8INTF		0x0002	/* Rocketport 8 port w/external I/F      */
+#define PCI_DEVICE_ID_RP16INTF		0x0003	/* Rocketport 16 port w/external I/F     */
+#define PCI_DEVICE_ID_RP4QUAD		0x0004	/* Rocketport 4 port w/quad cable        */
+#define PCI_DEVICE_ID_RP8OCTA		0x0005	/* Rocketport 8 port w/octa cable        */
+#define PCI_DEVICE_ID_RP8J		0x0006	/* Rocketport 8 port w/RJ11 connectors   */
+#define PCI_DEVICE_ID_RP4J		0x0007	/* Rocketport 4 port w/RJ11 connectors   */
+#define PCI_DEVICE_ID_RP8SNI		0x0008	/* Rocketport 8 port w/ DB78 SNI (Siemens) connector */
+#define PCI_DEVICE_ID_RP16SNI		0x0009	/* Rocketport 16 port w/ DB78 SNI (Siemens) connector   */
+#define PCI_DEVICE_ID_RPP4		0x000A	/* Rocketport Plus 4 port                */
+#define PCI_DEVICE_ID_RPP8		0x000B	/* Rocketport Plus 8 port                */
+#define PCI_DEVICE_ID_RP6M		0x000C	/* RocketModem 6 port                    */
+#define PCI_DEVICE_ID_RP4M		0x000D	/* RocketModem 4 port                    */
+#define PCI_DEVICE_ID_RP2_232           0x000E	/* Rocketport Plus 2 port RS232          */
+#define PCI_DEVICE_ID_RP2_422           0x000F	/* Rocketport Plus 2 port RS422          */ 
+
+/* Universal PCI boards  */
+#define PCI_DEVICE_ID_URP32INTF		0x0801	/* Rocketport UPCI 32 port w/external I/F */ 
+#define PCI_DEVICE_ID_URP8INTF		0x0802	/* Rocketport UPCI 8 port w/external I/F  */
+#define PCI_DEVICE_ID_URP16INTF		0x0803	/* Rocketport UPCI 16 port w/external I/F */
+#define PCI_DEVICE_ID_URP8OCTA		0x0805	/* Rocketport UPCI 8 port w/octa cable    */
+#define PCI_DEVICE_ID_UPCI_RM3_8PORT    0x080C	/* Rocketmodem III 8 port                 */
+#define PCI_DEVICE_ID_UPCI_RM3_4PORT    0x080D	/* Rocketmodem III 4 port                 */
+
+/* Compact PCI device */ 
+#define PCI_DEVICE_ID_CRP16INTF		0x0903	/* Rocketport Compact PCI 16 port w/external I/F */
+
+#define TTY_GET_LINE(t) t->index
+#define TTY_DRIVER_MINOR_START(t) t->driver->minor_start
+#define TTY_DRIVER_SUBTYPE(t) t->driver->subtype
+#define TTY_DRIVER_NAME(t) t->driver->name
+#define TTY_DRIVER_NAME_BASE(t) t->driver->name_base
+#define TTY_DRIVER_FLUSH_BUFFER_EXISTS(t) t->driver->flush_buffer
+#define TTY_DRIVER_FLUSH_BUFFER(t) t->driver->flush_buffer(t)
+
+
diff --git a/drivers/char/rtc.c b/drivers/char/rtc.c
new file mode 100644
index 0000000..ff4f098
--- /dev/null
+++ b/drivers/char/rtc.c
@@ -0,0 +1,1354 @@
+/*
+ *	Real Time Clock interface for Linux	
+ *
+ *	Copyright (C) 1996 Paul Gortmaker
+ *
+ *	This driver allows use of the real time clock (built into
+ *	nearly all computers) from user space. It exports the /dev/rtc
+ *	interface supporting various ioctl() and also the
+ *	/proc/driver/rtc pseudo-file for status information.
+ *
+ *	The ioctls can be used to set the interrupt behaviour and
+ *	generation rate from the RTC via IRQ 8. Then the /dev/rtc
+ *	interface can be used to make use of these timer interrupts,
+ *	be they interval or alarm based.
+ *
+ *	The /dev/rtc interface will block on reads until an interrupt
+ *	has been received. If a RTC interrupt has already happened,
+ *	it will output an unsigned long and then block. The output value
+ *	contains the interrupt status in the low byte and the number of
+ *	interrupts since the last read in the remaining high bytes. The 
+ *	/dev/rtc interface can also be used with the select(2) call.
+ *
+ *	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.
+ *
+ *	Based on other minimal char device drivers, like Alan's
+ *	watchdog, Ted's random, etc. etc.
+ *
+ *	1.07	Paul Gortmaker.
+ *	1.08	Miquel van Smoorenburg: disallow certain things on the
+ *		DEC Alpha as the CMOS clock is also used for other things.
+ *	1.09	Nikita Schmidt: epoch support and some Alpha cleanup.
+ *	1.09a	Pete Zaitcev: Sun SPARC
+ *	1.09b	Jeff Garzik: Modularize, init cleanup
+ *	1.09c	Jeff Garzik: SMP cleanup
+ *	1.10    Paul Barton-Davis: add support for async I/O
+ *	1.10a	Andrea Arcangeli: Alpha updates
+ *	1.10b	Andrew Morton: SMP lock fix
+ *	1.10c	Cesar Barros: SMP locking fixes and cleanup
+ *	1.10d	Paul Gortmaker: delete paranoia check in rtc_exit
+ *	1.10e	Maciej W. Rozycki: Handle DECstation's year weirdness.
+ *      1.11    Takashi Iwai: Kernel access functions
+ *			      rtc_register/rtc_unregister/rtc_control
+ *      1.11a   Daniele Bellucci: Audit create_proc_read_entry in rtc_init
+ *	1.12	Venkatesh Pallipadi: Hooks for emulating rtc on HPET base-timer
+ *		CONFIG_HPET_EMULATE_RTC
+ *
+ */
+
+#define RTC_VERSION		"1.12"
+
+#define RTC_IO_EXTENT	0x8
+
+/*
+ *	Note that *all* calls to CMOS_READ and CMOS_WRITE are done with
+ *	interrupts disabled. Due to the index-port/data-port (0x70/0x71)
+ *	design of the RTC, we don't want two different things trying to
+ *	get to it at once. (e.g. the periodic 11 min sync from time.c vs.
+ *	this driver.)
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/fcntl.h>
+#include <linux/mc146818rtc.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/sysctl.h>
+#include <linux/wait.h>
+#include <linux/bcd.h>
+
+#include <asm/current.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#if defined(__i386__)
+#include <asm/hpet.h>
+#endif
+
+#ifdef __sparc__
+#include <linux/pci.h>
+#include <asm/ebus.h>
+#ifdef __sparc_v9__
+#include <asm/isa.h>
+#endif
+
+static unsigned long rtc_port;
+static int rtc_irq = PCI_IRQ_NONE;
+#endif
+
+#ifdef	CONFIG_HPET_RTC_IRQ
+#undef	RTC_IRQ
+#endif
+
+#ifdef RTC_IRQ
+static int rtc_has_irq = 1;
+#endif
+
+#ifndef CONFIG_HPET_EMULATE_RTC
+#define is_hpet_enabled()			0
+#define hpet_set_alarm_time(hrs, min, sec) 	0
+#define hpet_set_periodic_freq(arg) 		0
+#define hpet_mask_rtc_irq_bit(arg) 		0
+#define hpet_set_rtc_irq_bit(arg) 		0
+#define hpet_rtc_timer_init() 			do { } while (0)
+#define hpet_rtc_dropped_irq() 			0
+static inline irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) {return 0;}
+#else
+extern irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+#endif
+
+/*
+ *	We sponge a minor off of the misc major. No need slurping
+ *	up another valuable major dev number for this. If you add
+ *	an ioctl, make sure you don't conflict with SPARC's RTC
+ *	ioctls.
+ */
+
+static struct fasync_struct *rtc_async_queue;
+
+static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);
+
+#ifdef RTC_IRQ
+static struct timer_list rtc_irq_timer;
+#endif
+
+static ssize_t rtc_read(struct file *file, char __user *buf,
+			size_t count, loff_t *ppos);
+
+static int rtc_ioctl(struct inode *inode, struct file *file,
+		     unsigned int cmd, unsigned long arg);
+
+#ifdef RTC_IRQ
+static unsigned int rtc_poll(struct file *file, poll_table *wait);
+#endif
+
+static void get_rtc_alm_time (struct rtc_time *alm_tm);
+#ifdef RTC_IRQ
+static void rtc_dropped_irq(unsigned long data);
+
+static void set_rtc_irq_bit(unsigned char bit);
+static void mask_rtc_irq_bit(unsigned char bit);
+#endif
+
+static int rtc_proc_open(struct inode *inode, struct file *file);
+
+/*
+ *	Bits in rtc_status. (6 bits of room for future expansion)
+ */
+
+#define RTC_IS_OPEN		0x01	/* means /dev/rtc is in use	*/
+#define RTC_TIMER_ON		0x02	/* missed irq timer active	*/
+
+/*
+ * rtc_status is never changed by rtc_interrupt, and ioctl/open/close is
+ * protected by the big kernel lock. However, ioctl can still disable the timer
+ * in rtc_status and then with del_timer after the interrupt has read
+ * rtc_status but before mod_timer is called, which would then reenable the
+ * timer (but you would need to have an awful timing before you'd trip on it)
+ */
+static unsigned long rtc_status = 0;	/* bitmapped status byte.	*/
+static unsigned long rtc_freq = 0;	/* Current periodic IRQ rate	*/
+static unsigned long rtc_irq_data = 0;	/* our output to the world	*/
+static unsigned long rtc_max_user_freq = 64; /* > this, need CAP_SYS_RESOURCE */
+
+#ifdef RTC_IRQ
+/*
+ * rtc_task_lock nests inside rtc_lock.
+ */
+static DEFINE_SPINLOCK(rtc_task_lock);
+static rtc_task_t *rtc_callback = NULL;
+#endif
+
+/*
+ *	If this driver ever becomes modularised, it will be really nice
+ *	to make the epoch retain its value across module reload...
+ */
+
+static unsigned long epoch = 1900;	/* year corresponding to 0x00	*/
+
+static const unsigned char days_in_mo[] = 
+{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+
+/*
+ * Returns true if a clock update is in progress
+ */
+static inline unsigned char rtc_is_updating(void)
+{
+	unsigned char uip;
+
+	spin_lock_irq(&rtc_lock);
+	uip = (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP);
+	spin_unlock_irq(&rtc_lock);
+	return uip;
+}
+
+#ifdef RTC_IRQ
+/*
+ *	A very tiny interrupt handler. It runs with SA_INTERRUPT set,
+ *	but there is possibility of conflicting with the set_rtc_mmss()
+ *	call (the rtc irq and the timer irq can easily run at the same
+ *	time in two different CPUs). So we need to serialize
+ *	accesses to the chip with the rtc_lock spinlock that each
+ *	architecture should implement in the timer code.
+ *	(See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.)
+ */
+
+irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	/*
+	 *	Can be an alarm interrupt, update complete interrupt,
+	 *	or a periodic interrupt. We store the status in the
+	 *	low byte and the number of interrupts received since
+	 *	the last read in the remainder of rtc_irq_data.
+	 */
+
+	spin_lock (&rtc_lock);
+	rtc_irq_data += 0x100;
+	rtc_irq_data &= ~0xff;
+	if (is_hpet_enabled()) {
+		/*
+		 * In this case it is HPET RTC interrupt handler
+		 * calling us, with the interrupt information
+		 * passed as arg1, instead of irq.
+		 */
+		rtc_irq_data |= (unsigned long)irq & 0xF0;
+	} else {
+		rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
+	}
+
+	if (rtc_status & RTC_TIMER_ON)
+		mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
+
+	spin_unlock (&rtc_lock);
+
+	/* Now do the rest of the actions */
+	spin_lock(&rtc_task_lock);
+	if (rtc_callback)
+		rtc_callback->func(rtc_callback->private_data);
+	spin_unlock(&rtc_task_lock);
+	wake_up_interruptible(&rtc_wait);	
+
+	kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
+
+	return IRQ_HANDLED;
+}
+#endif
+
+/*
+ * sysctl-tuning infrastructure.
+ */
+static ctl_table rtc_table[] = {
+	{
+		.ctl_name	= 1,
+		.procname	= "max-user-freq",
+		.data		= &rtc_max_user_freq,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec,
+	},
+	{ .ctl_name = 0 }
+};
+
+static ctl_table rtc_root[] = {
+	{
+		.ctl_name	= 1,
+		.procname	= "rtc",
+		.maxlen		= 0,
+		.mode		= 0555,
+		.child		= rtc_table,
+	},
+	{ .ctl_name = 0 }
+};
+
+static ctl_table dev_root[] = {
+	{
+		.ctl_name	= CTL_DEV,
+		.procname	= "dev",
+		.maxlen		= 0,
+		.mode		= 0555,
+		.child		= rtc_root,
+	},
+	{ .ctl_name = 0 }
+};
+
+static struct ctl_table_header *sysctl_header;
+
+static int __init init_sysctl(void)
+{
+    sysctl_header = register_sysctl_table(dev_root, 0);
+    return 0;
+}
+
+static void __exit cleanup_sysctl(void)
+{
+    unregister_sysctl_table(sysctl_header);
+}
+
+/*
+ *	Now all the various file operations that we export.
+ */
+
+static ssize_t rtc_read(struct file *file, char __user *buf,
+			size_t count, loff_t *ppos)
+{
+#ifndef RTC_IRQ
+	return -EIO;
+#else
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long data;
+	ssize_t retval;
+	
+	if (rtc_has_irq == 0)
+		return -EIO;
+
+	if (count < sizeof(unsigned))
+		return -EINVAL;
+
+	add_wait_queue(&rtc_wait, &wait);
+
+	do {
+		/* First make it right. Then make it fast. Putting this whole
+		 * block within the parentheses of a while would be too
+		 * confusing. And no, xchg() is not the answer. */
+
+		__set_current_state(TASK_INTERRUPTIBLE);
+		
+		spin_lock_irq (&rtc_lock);
+		data = rtc_irq_data;
+		rtc_irq_data = 0;
+		spin_unlock_irq (&rtc_lock);
+
+		if (data != 0)
+			break;
+
+		if (file->f_flags & O_NONBLOCK) {
+			retval = -EAGAIN;
+			goto out;
+		}
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			goto out;
+		}
+		schedule();
+	} while (1);
+
+	if (count < sizeof(unsigned long))
+		retval = put_user(data, (unsigned int __user *)buf) ?: sizeof(int); 
+	else
+		retval = put_user(data, (unsigned long __user *)buf) ?: sizeof(long);
+ out:
+	current->state = TASK_RUNNING;
+	remove_wait_queue(&rtc_wait, &wait);
+
+	return retval;
+#endif
+}
+
+static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
+{
+	struct rtc_time wtime; 
+
+#ifdef RTC_IRQ
+	if (rtc_has_irq == 0) {
+		switch (cmd) {
+		case RTC_AIE_OFF:
+		case RTC_AIE_ON:
+		case RTC_PIE_OFF:
+		case RTC_PIE_ON:
+		case RTC_UIE_OFF:
+		case RTC_UIE_ON:
+		case RTC_IRQP_READ:
+		case RTC_IRQP_SET:
+			return -EINVAL;
+		};
+	}
+#endif
+
+	switch (cmd) {
+#ifdef RTC_IRQ
+	case RTC_AIE_OFF:	/* Mask alarm int. enab. bit	*/
+	{
+		mask_rtc_irq_bit(RTC_AIE);
+		return 0;
+	}
+	case RTC_AIE_ON:	/* Allow alarm interrupts.	*/
+	{
+		set_rtc_irq_bit(RTC_AIE);
+		return 0;
+	}
+	case RTC_PIE_OFF:	/* Mask periodic int. enab. bit	*/
+	{
+		mask_rtc_irq_bit(RTC_PIE);
+		if (rtc_status & RTC_TIMER_ON) {
+			spin_lock_irq (&rtc_lock);
+			rtc_status &= ~RTC_TIMER_ON;
+			del_timer(&rtc_irq_timer);
+			spin_unlock_irq (&rtc_lock);
+		}
+		return 0;
+	}
+	case RTC_PIE_ON:	/* Allow periodic ints		*/
+	{
+
+		/*
+		 * We don't really want Joe User enabling more
+		 * than 64Hz of interrupts on a multi-user machine.
+		 */
+		if (!kernel && (rtc_freq > rtc_max_user_freq) &&
+			(!capable(CAP_SYS_RESOURCE)))
+			return -EACCES;
+
+		if (!(rtc_status & RTC_TIMER_ON)) {
+			spin_lock_irq (&rtc_lock);
+			rtc_irq_timer.expires = jiffies + HZ/rtc_freq + 2*HZ/100;
+			add_timer(&rtc_irq_timer);
+			rtc_status |= RTC_TIMER_ON;
+			spin_unlock_irq (&rtc_lock);
+		}
+		set_rtc_irq_bit(RTC_PIE);
+		return 0;
+	}
+	case RTC_UIE_OFF:	/* Mask ints from RTC updates.	*/
+	{
+		mask_rtc_irq_bit(RTC_UIE);
+		return 0;
+	}
+	case RTC_UIE_ON:	/* Allow ints for RTC updates.	*/
+	{
+		set_rtc_irq_bit(RTC_UIE);
+		return 0;
+	}
+#endif
+	case RTC_ALM_READ:	/* Read the present alarm time */
+	{
+		/*
+		 * This returns a struct rtc_time. Reading >= 0xc0
+		 * means "don't care" or "match all". Only the tm_hour,
+		 * tm_min, and tm_sec values are filled in.
+		 */
+		memset(&wtime, 0, sizeof(struct rtc_time));
+		get_rtc_alm_time(&wtime);
+		break; 
+	}
+	case RTC_ALM_SET:	/* Store a time into the alarm */
+	{
+		/*
+		 * This expects a struct rtc_time. Writing 0xff means
+		 * "don't care" or "match all". Only the tm_hour,
+		 * tm_min and tm_sec are used.
+		 */
+		unsigned char hrs, min, sec;
+		struct rtc_time alm_tm;
+
+		if (copy_from_user(&alm_tm, (struct rtc_time __user *)arg,
+				   sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		hrs = alm_tm.tm_hour;
+		min = alm_tm.tm_min;
+		sec = alm_tm.tm_sec;
+
+		spin_lock_irq(&rtc_lock);
+		if (hpet_set_alarm_time(hrs, min, sec)) {
+			/*
+			 * Fallthru and set alarm time in CMOS too,
+			 * so that we will get proper value in RTC_ALM_READ
+			 */
+		}
+		if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) ||
+		    RTC_ALWAYS_BCD)
+		{
+			if (sec < 60) BIN_TO_BCD(sec);
+			else sec = 0xff;
+
+			if (min < 60) BIN_TO_BCD(min);
+			else min = 0xff;
+
+			if (hrs < 24) BIN_TO_BCD(hrs);
+			else hrs = 0xff;
+		}
+		CMOS_WRITE(hrs, RTC_HOURS_ALARM);
+		CMOS_WRITE(min, RTC_MINUTES_ALARM);
+		CMOS_WRITE(sec, RTC_SECONDS_ALARM);
+		spin_unlock_irq(&rtc_lock);
+
+		return 0;
+	}
+	case RTC_RD_TIME:	/* Read the time/date from RTC	*/
+	{
+		memset(&wtime, 0, sizeof(struct rtc_time));
+		rtc_get_rtc_time(&wtime);
+		break;
+	}
+	case RTC_SET_TIME:	/* Set the RTC */
+	{
+		struct rtc_time rtc_tm;
+		unsigned char mon, day, hrs, min, sec, leap_yr;
+		unsigned char save_control, save_freq_select;
+		unsigned int yrs;
+#ifdef CONFIG_MACH_DECSTATION
+		unsigned int real_yrs;
+#endif
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		if (copy_from_user(&rtc_tm, (struct rtc_time __user *)arg,
+				   sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		yrs = rtc_tm.tm_year + 1900;
+		mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */
+		day = rtc_tm.tm_mday;
+		hrs = rtc_tm.tm_hour;
+		min = rtc_tm.tm_min;
+		sec = rtc_tm.tm_sec;
+
+		if (yrs < 1970)
+			return -EINVAL;
+
+		leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
+
+		if ((mon > 12) || (day == 0))
+			return -EINVAL;
+
+		if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
+			return -EINVAL;
+			
+		if ((hrs >= 24) || (min >= 60) || (sec >= 60))
+			return -EINVAL;
+
+		if ((yrs -= epoch) > 255)    /* They are unsigned */
+			return -EINVAL;
+
+		spin_lock_irq(&rtc_lock);
+#ifdef CONFIG_MACH_DECSTATION
+		real_yrs = yrs;
+		yrs = 72;
+
+		/*
+		 * We want to keep the year set to 73 until March
+		 * for non-leap years, so that Feb, 29th is handled
+		 * correctly.
+		 */
+		if (!leap_yr && mon < 3) {
+			real_yrs--;
+			yrs = 73;
+		}
+#endif
+		/* These limits and adjustments are independent of
+		 * whether the chip is in binary mode or not.
+		 */
+		if (yrs > 169) {
+			spin_unlock_irq(&rtc_lock);
+			return -EINVAL;
+		}
+		if (yrs >= 100)
+			yrs -= 100;
+
+		if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY)
+		    || RTC_ALWAYS_BCD) {
+			BIN_TO_BCD(sec);
+			BIN_TO_BCD(min);
+			BIN_TO_BCD(hrs);
+			BIN_TO_BCD(day);
+			BIN_TO_BCD(mon);
+			BIN_TO_BCD(yrs);
+		}
+
+		save_control = CMOS_READ(RTC_CONTROL);
+		CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL);
+		save_freq_select = CMOS_READ(RTC_FREQ_SELECT);
+		CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT);
+
+#ifdef CONFIG_MACH_DECSTATION
+		CMOS_WRITE(real_yrs, RTC_DEC_YEAR);
+#endif
+		CMOS_WRITE(yrs, RTC_YEAR);
+		CMOS_WRITE(mon, RTC_MONTH);
+		CMOS_WRITE(day, RTC_DAY_OF_MONTH);
+		CMOS_WRITE(hrs, RTC_HOURS);
+		CMOS_WRITE(min, RTC_MINUTES);
+		CMOS_WRITE(sec, RTC_SECONDS);
+
+		CMOS_WRITE(save_control, RTC_CONTROL);
+		CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
+
+		spin_unlock_irq(&rtc_lock);
+		return 0;
+	}
+#ifdef RTC_IRQ
+	case RTC_IRQP_READ:	/* Read the periodic IRQ rate.	*/
+	{
+		return put_user(rtc_freq, (unsigned long __user *)arg);
+	}
+	case RTC_IRQP_SET:	/* Set periodic IRQ rate.	*/
+	{
+		int tmp = 0;
+		unsigned char val;
+
+		/* 
+		 * The max we can do is 8192Hz.
+		 */
+		if ((arg < 2) || (arg > 8192))
+			return -EINVAL;
+		/*
+		 * We don't really want Joe User generating more
+		 * than 64Hz of interrupts on a multi-user machine.
+		 */
+		if (!kernel && (arg > rtc_max_user_freq) && (!capable(CAP_SYS_RESOURCE)))
+			return -EACCES;
+
+		while (arg > (1<<tmp))
+			tmp++;
+
+		/*
+		 * Check that the input was really a power of 2.
+		 */
+		if (arg != (1<<tmp))
+			return -EINVAL;
+
+		spin_lock_irq(&rtc_lock);
+		if (hpet_set_periodic_freq(arg)) {
+			spin_unlock_irq(&rtc_lock);
+			return 0;
+		}
+		rtc_freq = arg;
+
+		val = CMOS_READ(RTC_FREQ_SELECT) & 0xf0;
+		val |= (16 - tmp);
+		CMOS_WRITE(val, RTC_FREQ_SELECT);
+		spin_unlock_irq(&rtc_lock);
+		return 0;
+	}
+#endif
+	case RTC_EPOCH_READ:	/* Read the epoch.	*/
+	{
+		return put_user (epoch, (unsigned long __user *)arg);
+	}
+	case RTC_EPOCH_SET:	/* Set the epoch.	*/
+	{
+		/* 
+		 * There were no RTC clocks before 1900.
+		 */
+		if (arg < 1900)
+			return -EINVAL;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		epoch = arg;
+		return 0;
+	}
+	default:
+		return -ENOTTY;
+	}
+	return copy_to_user((void __user *)arg, &wtime, sizeof wtime) ? -EFAULT : 0;
+}
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+		     unsigned long arg)
+{
+	return rtc_do_ioctl(cmd, arg, 0);
+}
+
+/*
+ *	We enforce only one user at a time here with the open/close.
+ *	Also clear the previous interrupt data on an open, and clean
+ *	up things on a close.
+ */
+
+/* We use rtc_lock to protect against concurrent opens. So the BKL is not
+ * needed here. Or anywhere else in this driver. */
+static int rtc_open(struct inode *inode, struct file *file)
+{
+	spin_lock_irq (&rtc_lock);
+
+	if(rtc_status & RTC_IS_OPEN)
+		goto out_busy;
+
+	rtc_status |= RTC_IS_OPEN;
+
+	rtc_irq_data = 0;
+	spin_unlock_irq (&rtc_lock);
+	return 0;
+
+out_busy:
+	spin_unlock_irq (&rtc_lock);
+	return -EBUSY;
+}
+
+static int rtc_fasync (int fd, struct file *filp, int on)
+
+{
+	return fasync_helper (fd, filp, on, &rtc_async_queue);
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+#ifdef RTC_IRQ
+	unsigned char tmp;
+
+	if (rtc_has_irq == 0)
+		goto no_irq;
+
+	/*
+	 * Turn off all interrupts once the device is no longer
+	 * in use, and clear the data.
+	 */
+
+	spin_lock_irq(&rtc_lock);
+	if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) {
+		tmp = CMOS_READ(RTC_CONTROL);
+		tmp &=  ~RTC_PIE;
+		tmp &=  ~RTC_AIE;
+		tmp &=  ~RTC_UIE;
+		CMOS_WRITE(tmp, RTC_CONTROL);
+		CMOS_READ(RTC_INTR_FLAGS);
+	}
+	if (rtc_status & RTC_TIMER_ON) {
+		rtc_status &= ~RTC_TIMER_ON;
+		del_timer(&rtc_irq_timer);
+	}
+	spin_unlock_irq(&rtc_lock);
+
+	if (file->f_flags & FASYNC) {
+		rtc_fasync (-1, file, 0);
+	}
+no_irq:
+#endif
+
+	spin_lock_irq (&rtc_lock);
+	rtc_irq_data = 0;
+	rtc_status &= ~RTC_IS_OPEN;
+	spin_unlock_irq (&rtc_lock);
+	return 0;
+}
+
+#ifdef RTC_IRQ
+/* Called without the kernel lock - fine */
+static unsigned int rtc_poll(struct file *file, poll_table *wait)
+{
+	unsigned long l;
+
+	if (rtc_has_irq == 0)
+		return 0;
+
+	poll_wait(file, &rtc_wait, wait);
+
+	spin_lock_irq (&rtc_lock);
+	l = rtc_irq_data;
+	spin_unlock_irq (&rtc_lock);
+
+	if (l != 0)
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+#endif
+
+/*
+ * exported stuffs
+ */
+
+EXPORT_SYMBOL(rtc_register);
+EXPORT_SYMBOL(rtc_unregister);
+EXPORT_SYMBOL(rtc_control);
+
+int rtc_register(rtc_task_t *task)
+{
+#ifndef RTC_IRQ
+	return -EIO;
+#else
+	if (task == NULL || task->func == NULL)
+		return -EINVAL;
+	spin_lock_irq(&rtc_lock);
+	if (rtc_status & RTC_IS_OPEN) {
+		spin_unlock_irq(&rtc_lock);
+		return -EBUSY;
+	}
+	spin_lock(&rtc_task_lock);
+	if (rtc_callback) {
+		spin_unlock(&rtc_task_lock);
+		spin_unlock_irq(&rtc_lock);
+		return -EBUSY;
+	}
+	rtc_status |= RTC_IS_OPEN;
+	rtc_callback = task;
+	spin_unlock(&rtc_task_lock);
+	spin_unlock_irq(&rtc_lock);
+	return 0;
+#endif
+}
+
+int rtc_unregister(rtc_task_t *task)
+{
+#ifndef RTC_IRQ
+	return -EIO;
+#else
+	unsigned char tmp;
+
+	spin_lock_irq(&rtc_lock);
+	spin_lock(&rtc_task_lock);
+	if (rtc_callback != task) {
+		spin_unlock(&rtc_task_lock);
+		spin_unlock_irq(&rtc_lock);
+		return -ENXIO;
+	}
+	rtc_callback = NULL;
+	
+	/* disable controls */
+	if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) {
+		tmp = CMOS_READ(RTC_CONTROL);
+		tmp &= ~RTC_PIE;
+		tmp &= ~RTC_AIE;
+		tmp &= ~RTC_UIE;
+		CMOS_WRITE(tmp, RTC_CONTROL);
+		CMOS_READ(RTC_INTR_FLAGS);
+	}
+	if (rtc_status & RTC_TIMER_ON) {
+		rtc_status &= ~RTC_TIMER_ON;
+		del_timer(&rtc_irq_timer);
+	}
+	rtc_status &= ~RTC_IS_OPEN;
+	spin_unlock(&rtc_task_lock);
+	spin_unlock_irq(&rtc_lock);
+	return 0;
+#endif
+}
+
+int rtc_control(rtc_task_t *task, unsigned int cmd, unsigned long arg)
+{
+#ifndef RTC_IRQ
+	return -EIO;
+#else
+	spin_lock_irq(&rtc_task_lock);
+	if (rtc_callback != task) {
+		spin_unlock_irq(&rtc_task_lock);
+		return -ENXIO;
+	}
+	spin_unlock_irq(&rtc_task_lock);
+	return rtc_do_ioctl(cmd, arg, 1);
+#endif
+}
+
+
+/*
+ *	The various file operations we support.
+ */
+
+static struct file_operations rtc_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= rtc_read,
+#ifdef RTC_IRQ
+	.poll		= rtc_poll,
+#endif
+	.ioctl		= rtc_ioctl,
+	.open		= rtc_open,
+	.release	= rtc_release,
+	.fasync		= rtc_fasync,
+};
+
+static struct miscdevice rtc_dev = {
+	.minor		= RTC_MINOR,
+	.name		= "rtc",
+	.fops		= &rtc_fops,
+};
+
+static struct file_operations rtc_proc_fops = {
+	.owner = THIS_MODULE,
+	.open = rtc_proc_open,
+	.read  = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+#if defined(RTC_IRQ) && !defined(__sparc__)
+static irqreturn_t (*rtc_int_handler_ptr)(int irq, void *dev_id, struct pt_regs *regs);
+#endif
+
+static int __init rtc_init(void)
+{
+	struct proc_dir_entry *ent;
+#if defined(__alpha__) || defined(__mips__)
+	unsigned int year, ctrl;
+	unsigned long uip_watchdog;
+	char *guess = NULL;
+#endif
+#ifdef __sparc__
+	struct linux_ebus *ebus;
+	struct linux_ebus_device *edev;
+#ifdef __sparc_v9__
+	struct sparc_isa_bridge *isa_br;
+	struct sparc_isa_device *isa_dev;
+#endif
+#endif
+
+#ifdef __sparc__
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if(strcmp(edev->prom_name, "rtc") == 0) {
+				rtc_port = edev->resource[0].start;
+				rtc_irq = edev->irqs[0];
+				goto found;
+			}
+		}
+	}
+#ifdef __sparc_v9__
+	for_each_isa(isa_br) {
+		for_each_isadev(isa_dev, isa_br) {
+			if (strcmp(isa_dev->prom_name, "rtc") == 0) {
+				rtc_port = isa_dev->resource.start;
+				rtc_irq = isa_dev->irq;
+				goto found;
+			}
+		}
+	}
+#endif
+	printk(KERN_ERR "rtc_init: no PC rtc found\n");
+	return -EIO;
+
+found:
+	if (rtc_irq == PCI_IRQ_NONE) {
+		rtc_has_irq = 0;
+		goto no_irq;
+	}
+
+	/*
+	 * XXX Interrupt pin #7 in Espresso is shared between RTC and
+	 * PCI Slot 2 INTA# (and some INTx# in Slot 1). SA_INTERRUPT here
+	 * is asking for trouble with add-on boards. Change to SA_SHIRQ.
+	 */
+	if (request_irq(rtc_irq, rtc_interrupt, SA_INTERRUPT, "rtc", (void *)&rtc_port)) {
+		/*
+		 * Standard way for sparc to print irq's is to use
+		 * __irq_itoa(). I think for EBus it's ok to use %d.
+		 */
+		printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
+		return -EIO;
+	}
+no_irq:
+#else
+	if (!request_region(RTC_PORT(0), RTC_IO_EXTENT, "rtc")) {
+		printk(KERN_ERR "rtc: I/O port %d is not free.\n", RTC_PORT (0));
+		return -EIO;
+	}
+
+#ifdef RTC_IRQ
+	if (is_hpet_enabled()) {
+		rtc_int_handler_ptr = hpet_rtc_interrupt;
+	} else {
+		rtc_int_handler_ptr = rtc_interrupt;
+	}
+
+	if(request_irq(RTC_IRQ, rtc_int_handler_ptr, SA_INTERRUPT, "rtc", NULL)) {
+		/* Yeah right, seeing as irq 8 doesn't even hit the bus. */
+		printk(KERN_ERR "rtc: IRQ %d is not free.\n", RTC_IRQ);
+		release_region(RTC_PORT(0), RTC_IO_EXTENT);
+		return -EIO;
+	}
+	hpet_rtc_timer_init();
+
+#endif
+
+#endif /* __sparc__ vs. others */
+
+	if (misc_register(&rtc_dev)) {
+#ifdef RTC_IRQ
+		free_irq(RTC_IRQ, NULL);
+#endif
+		release_region(RTC_PORT(0), RTC_IO_EXTENT);
+		return -ENODEV;
+	}
+
+	ent = create_proc_entry("driver/rtc", 0, NULL);
+	if (!ent) {
+#ifdef RTC_IRQ
+		free_irq(RTC_IRQ, NULL);
+#endif
+		release_region(RTC_PORT(0), RTC_IO_EXTENT);
+		misc_deregister(&rtc_dev);
+		return -ENOMEM;
+	}
+	ent->proc_fops = &rtc_proc_fops;
+
+#if defined(__alpha__) || defined(__mips__)
+	rtc_freq = HZ;
+	
+	/* Each operating system on an Alpha uses its own epoch.
+	   Let's try to guess which one we are using now. */
+	
+	uip_watchdog = jiffies;
+	if (rtc_is_updating() != 0)
+		while (jiffies - uip_watchdog < 2*HZ/100) { 
+			barrier();
+			cpu_relax();
+		}
+	
+	spin_lock_irq(&rtc_lock);
+	year = CMOS_READ(RTC_YEAR);
+	ctrl = CMOS_READ(RTC_CONTROL);
+	spin_unlock_irq(&rtc_lock);
+	
+	if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
+		BCD_TO_BIN(year);       /* This should never happen... */
+	
+	if (year < 20) {
+		epoch = 2000;
+		guess = "SRM (post-2000)";
+	} else if (year >= 20 && year < 48) {
+		epoch = 1980;
+		guess = "ARC console";
+	} else if (year >= 48 && year < 72) {
+		epoch = 1952;
+		guess = "Digital UNIX";
+#if defined(__mips__)
+	} else if (year >= 72 && year < 74) {
+		epoch = 2000;
+		guess = "Digital DECstation";
+#else
+	} else if (year >= 70) {
+		epoch = 1900;
+		guess = "Standard PC (1900)";
+#endif
+	}
+	if (guess)
+		printk(KERN_INFO "rtc: %s epoch (%lu) detected\n", guess, epoch);
+#endif
+#ifdef RTC_IRQ
+	if (rtc_has_irq == 0)
+		goto no_irq2;
+
+	init_timer(&rtc_irq_timer);
+	rtc_irq_timer.function = rtc_dropped_irq;
+	spin_lock_irq(&rtc_lock);
+	rtc_freq = 1024;
+	if (!hpet_set_periodic_freq(rtc_freq)) {
+		/* Initialize periodic freq. to CMOS reset default, which is 1024Hz */
+		CMOS_WRITE(((CMOS_READ(RTC_FREQ_SELECT) & 0xF0) | 0x06), RTC_FREQ_SELECT);
+	}
+	spin_unlock_irq(&rtc_lock);
+no_irq2:
+#endif
+
+	(void) init_sysctl();
+
+	printk(KERN_INFO "Real Time Clock Driver v" RTC_VERSION "\n");
+
+	return 0;
+}
+
+static void __exit rtc_exit (void)
+{
+	cleanup_sysctl();
+	remove_proc_entry ("driver/rtc", NULL);
+	misc_deregister(&rtc_dev);
+
+#ifdef __sparc__
+	if (rtc_has_irq)
+		free_irq (rtc_irq, &rtc_port);
+#else
+	release_region (RTC_PORT (0), RTC_IO_EXTENT);
+#ifdef RTC_IRQ
+	if (rtc_has_irq)
+		free_irq (RTC_IRQ, NULL);
+#endif
+#endif /* __sparc__ */
+}
+
+module_init(rtc_init);
+module_exit(rtc_exit);
+
+#ifdef RTC_IRQ
+/*
+ * 	At IRQ rates >= 4096Hz, an interrupt may get lost altogether.
+ *	(usually during an IDE disk interrupt, with IRQ unmasking off)
+ *	Since the interrupt handler doesn't get called, the IRQ status
+ *	byte doesn't get read, and the RTC stops generating interrupts.
+ *	A timer is set, and will call this function if/when that happens.
+ *	To get it out of this stalled state, we just read the status.
+ *	At least a jiffy of interrupts (rtc_freq/HZ) will have been lost.
+ *	(You *really* shouldn't be trying to use a non-realtime system 
+ *	for something that requires a steady > 1KHz signal anyways.)
+ */
+
+static void rtc_dropped_irq(unsigned long data)
+{
+	unsigned long freq;
+
+	spin_lock_irq (&rtc_lock);
+
+	if (hpet_rtc_dropped_irq()) {
+		spin_unlock_irq(&rtc_lock);
+		return;
+	}
+
+	/* Just in case someone disabled the timer from behind our back... */
+	if (rtc_status & RTC_TIMER_ON)
+		mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
+
+	rtc_irq_data += ((rtc_freq/HZ)<<8);
+	rtc_irq_data &= ~0xff;
+	rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);	/* restart */
+
+	freq = rtc_freq;
+
+	spin_unlock_irq(&rtc_lock);
+
+	printk(KERN_WARNING "rtc: lost some interrupts at %ldHz.\n", freq);
+
+	/* Now we have new data */
+	wake_up_interruptible(&rtc_wait);
+
+	kill_fasync (&rtc_async_queue, SIGIO, POLL_IN);
+}
+#endif
+
+/*
+ *	Info exported via "/proc/driver/rtc".
+ */
+
+static int rtc_proc_show(struct seq_file *seq, void *v)
+{
+#define YN(bit) ((ctrl & bit) ? "yes" : "no")
+#define NY(bit) ((ctrl & bit) ? "no" : "yes")
+	struct rtc_time tm;
+	unsigned char batt, ctrl;
+	unsigned long freq;
+
+	spin_lock_irq(&rtc_lock);
+	batt = CMOS_READ(RTC_VALID) & RTC_VRT;
+	ctrl = CMOS_READ(RTC_CONTROL);
+	freq = rtc_freq;
+	spin_unlock_irq(&rtc_lock);
+
+
+	rtc_get_rtc_time(&tm);
+
+	/*
+	 * There is no way to tell if the luser has the RTC set for local
+	 * time or for Universal Standard Time (GMT). Probably local though.
+	 */
+	seq_printf(seq,
+		   "rtc_time\t: %02d:%02d:%02d\n"
+		   "rtc_date\t: %04d-%02d-%02d\n"
+		   "rtc_epoch\t: %04lu\n",
+		   tm.tm_hour, tm.tm_min, tm.tm_sec,
+		   tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, epoch);
+
+	get_rtc_alm_time(&tm);
+
+	/*
+	 * We implicitly assume 24hr mode here. Alarm values >= 0xc0 will
+	 * match any value for that particular field. Values that are
+	 * greater than a valid time, but less than 0xc0 shouldn't appear.
+	 */
+	seq_puts(seq, "alarm\t\t: ");
+	if (tm.tm_hour <= 24)
+		seq_printf(seq, "%02d:", tm.tm_hour);
+	else
+		seq_puts(seq, "**:");
+
+	if (tm.tm_min <= 59)
+		seq_printf(seq, "%02d:", tm.tm_min);
+	else
+		seq_puts(seq, "**:");
+
+	if (tm.tm_sec <= 59)
+		seq_printf(seq, "%02d\n", tm.tm_sec);
+	else
+		seq_puts(seq, "**\n");
+
+	seq_printf(seq,
+		   "DST_enable\t: %s\n"
+		   "BCD\t\t: %s\n"
+		   "24hr\t\t: %s\n"
+		   "square_wave\t: %s\n"
+		   "alarm_IRQ\t: %s\n"
+		   "update_IRQ\t: %s\n"
+		   "periodic_IRQ\t: %s\n"
+		   "periodic_freq\t: %ld\n"
+		   "batt_status\t: %s\n",
+		   YN(RTC_DST_EN),
+		   NY(RTC_DM_BINARY),
+		   YN(RTC_24H),
+		   YN(RTC_SQWE),
+		   YN(RTC_AIE),
+		   YN(RTC_UIE),
+		   YN(RTC_PIE),
+		   freq,
+		   batt ? "okay" : "dead");
+
+	return  0;
+#undef YN
+#undef NY
+}
+
+static int rtc_proc_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, rtc_proc_show, NULL);
+}
+
+void rtc_get_rtc_time(struct rtc_time *rtc_tm)
+{
+	unsigned long uip_watchdog = jiffies;
+	unsigned char ctrl;
+#ifdef CONFIG_MACH_DECSTATION
+	unsigned int real_year;
+#endif
+
+	/*
+	 * read RTC once any update in progress is done. The update
+	 * can take just over 2ms. We wait 10 to 20ms. There is no need to
+	 * to poll-wait (up to 1s - eeccch) for the falling edge of RTC_UIP.
+	 * If you need to know *exactly* when a second has started, enable
+	 * periodic update complete interrupts, (via ioctl) and then 
+	 * immediately read /dev/rtc which will block until you get the IRQ.
+	 * Once the read clears, read the RTC time (again via ioctl). Easy.
+	 */
+
+	if (rtc_is_updating() != 0)
+		while (jiffies - uip_watchdog < 2*HZ/100) {
+			barrier();
+			cpu_relax();
+		}
+
+	/*
+	 * Only the values that we read from the RTC are set. We leave
+	 * tm_wday, tm_yday and tm_isdst untouched. Even though the
+	 * RTC has RTC_DAY_OF_WEEK, we ignore it, as it is only updated
+	 * by the RTC when initially set to a non-zero value.
+	 */
+	spin_lock_irq(&rtc_lock);
+	rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS);
+	rtc_tm->tm_min = CMOS_READ(RTC_MINUTES);
+	rtc_tm->tm_hour = CMOS_READ(RTC_HOURS);
+	rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH);
+	rtc_tm->tm_mon = CMOS_READ(RTC_MONTH);
+	rtc_tm->tm_year = CMOS_READ(RTC_YEAR);
+#ifdef CONFIG_MACH_DECSTATION
+	real_year = CMOS_READ(RTC_DEC_YEAR);
+#endif
+	ctrl = CMOS_READ(RTC_CONTROL);
+	spin_unlock_irq(&rtc_lock);
+
+	if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
+	{
+		BCD_TO_BIN(rtc_tm->tm_sec);
+		BCD_TO_BIN(rtc_tm->tm_min);
+		BCD_TO_BIN(rtc_tm->tm_hour);
+		BCD_TO_BIN(rtc_tm->tm_mday);
+		BCD_TO_BIN(rtc_tm->tm_mon);
+		BCD_TO_BIN(rtc_tm->tm_year);
+	}
+
+#ifdef CONFIG_MACH_DECSTATION
+	rtc_tm->tm_year += real_year - 72;
+#endif
+
+	/*
+	 * Account for differences between how the RTC uses the values
+	 * and how they are defined in a struct rtc_time;
+	 */
+	if ((rtc_tm->tm_year += (epoch - 1900)) <= 69)
+		rtc_tm->tm_year += 100;
+
+	rtc_tm->tm_mon--;
+}
+
+static void get_rtc_alm_time(struct rtc_time *alm_tm)
+{
+	unsigned char ctrl;
+
+	/*
+	 * Only the values that we read from the RTC are set. That
+	 * means only tm_hour, tm_min, and tm_sec.
+	 */
+	spin_lock_irq(&rtc_lock);
+	alm_tm->tm_sec = CMOS_READ(RTC_SECONDS_ALARM);
+	alm_tm->tm_min = CMOS_READ(RTC_MINUTES_ALARM);
+	alm_tm->tm_hour = CMOS_READ(RTC_HOURS_ALARM);
+	ctrl = CMOS_READ(RTC_CONTROL);
+	spin_unlock_irq(&rtc_lock);
+
+	if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD)
+	{
+		BCD_TO_BIN(alm_tm->tm_sec);
+		BCD_TO_BIN(alm_tm->tm_min);
+		BCD_TO_BIN(alm_tm->tm_hour);
+	}
+}
+
+#ifdef RTC_IRQ
+/*
+ * Used to disable/enable interrupts for any one of UIE, AIE, PIE.
+ * Rumour has it that if you frob the interrupt enable/disable
+ * bits in RTC_CONTROL, you should read RTC_INTR_FLAGS, to
+ * ensure you actually start getting interrupts. Probably for
+ * compatibility with older/broken chipset RTC implementations.
+ * We also clear out any old irq data after an ioctl() that
+ * meddles with the interrupt enable/disable bits.
+ */
+
+static void mask_rtc_irq_bit(unsigned char bit)
+{
+	unsigned char val;
+
+	spin_lock_irq(&rtc_lock);
+	if (hpet_mask_rtc_irq_bit(bit)) {
+		spin_unlock_irq(&rtc_lock);
+		return;
+	}
+	val = CMOS_READ(RTC_CONTROL);
+	val &=  ~bit;
+	CMOS_WRITE(val, RTC_CONTROL);
+	CMOS_READ(RTC_INTR_FLAGS);
+
+	rtc_irq_data = 0;
+	spin_unlock_irq(&rtc_lock);
+}
+
+static void set_rtc_irq_bit(unsigned char bit)
+{
+	unsigned char val;
+
+	spin_lock_irq(&rtc_lock);
+	if (hpet_set_rtc_irq_bit(bit)) {
+		spin_unlock_irq(&rtc_lock);
+		return;
+	}
+	val = CMOS_READ(RTC_CONTROL);
+	val |= bit;
+	CMOS_WRITE(val, RTC_CONTROL);
+	CMOS_READ(RTC_INTR_FLAGS);
+
+	rtc_irq_data = 0;
+	spin_unlock_irq(&rtc_lock);
+}
+#endif
+
+MODULE_AUTHOR("Paul Gortmaker");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(RTC_MINOR);
diff --git a/drivers/char/s3c2410-rtc.c b/drivers/char/s3c2410-rtc.c
new file mode 100644
index 0000000..ec66639
--- /dev/null
+++ b/drivers/char/s3c2410-rtc.c
@@ -0,0 +1,588 @@
+/* drivers/char/s3c2410_rtc.c
+ *
+ * Copyright (c) 2004 Simtec Electronics <linux@simtec.co.uk>
+ *		      http://www.simtec.co.uk/products/SWLINUX/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * S3C2410 Internal RTC Driver
+ *
+ *  Changelog:
+ *	08-Nov-2004	BJD	Initial creation
+ *	12-Nov-2004	BJD	Added periodic IRQ and PM code
+ *	22-Nov-2004	BJD	Sign-test on alarm code to check for <0
+ *	10-Mar-2005	LCVR	Changed S3C2410_VA_RTC to S3C24XX_VA_RTC
+*/
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+
+#include <asm/hardware.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/rtc.h>
+
+#include <asm/mach/time.h>
+
+#include <asm/hardware/clock.h>
+#include <asm/arch/regs-rtc.h>
+
+/* need this for the RTC_AF definitions */
+#include <linux/mc146818rtc.h>
+
+#undef S3C24XX_VA_RTC
+#define S3C24XX_VA_RTC s3c2410_rtc_base
+
+static struct resource *s3c2410_rtc_mem;
+
+static void __iomem *s3c2410_rtc_base;
+static int s3c2410_rtc_alarmno = NO_IRQ;
+static int s3c2410_rtc_tickno  = NO_IRQ;
+static int s3c2410_rtc_freq    = 1;
+
+static DEFINE_SPINLOCK(s3c2410_rtc_pie_lock);
+
+/* IRQ Handlers */
+
+static irqreturn_t s3c2410_rtc_alarmirq(int irq, void *id, struct pt_regs *r)
+{
+	rtc_update(1, RTC_AF | RTC_IRQF);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t s3c2410_rtc_tickirq(int irq, void *id, struct pt_regs *r)
+{
+	rtc_update(1, RTC_PF | RTC_IRQF);
+	return IRQ_HANDLED;
+}
+
+/* Update control registers */
+static void s3c2410_rtc_setaie(int to)
+{
+	unsigned int tmp;
+
+	pr_debug("%s: aie=%d\n", __FUNCTION__, to);
+
+	tmp = readb(S3C2410_RTCALM);
+
+	if (to)
+		tmp |= S3C2410_RTCALM_ALMEN;
+	else
+		tmp &= ~S3C2410_RTCALM_ALMEN;
+
+
+	writeb(tmp, S3C2410_RTCALM);
+}
+
+static void s3c2410_rtc_setpie(int to)
+{
+	unsigned int tmp;
+
+	pr_debug("%s: pie=%d\n", __FUNCTION__, to);
+
+	spin_lock_irq(&s3c2410_rtc_pie_lock);
+	tmp = readb(S3C2410_TICNT) & ~S3C2410_TICNT_ENABLE;
+
+	if (to)
+		tmp |= S3C2410_TICNT_ENABLE;
+
+	writeb(tmp, S3C2410_TICNT);
+	spin_unlock_irq(&s3c2410_rtc_pie_lock);
+}
+
+static void s3c2410_rtc_setfreq(int freq)
+{
+	unsigned int tmp;
+
+	spin_lock_irq(&s3c2410_rtc_pie_lock);
+	tmp = readb(S3C2410_TICNT) & S3C2410_TICNT_ENABLE;
+
+	s3c2410_rtc_freq = freq;
+
+	tmp |= (128 / freq)-1;
+
+	writeb(tmp, S3C2410_TICNT);
+	spin_unlock_irq(&s3c2410_rtc_pie_lock);
+}
+
+/* Time read/write */
+
+static void s3c2410_rtc_gettime(struct rtc_time *rtc_tm)
+{
+	unsigned int have_retried = 0;
+
+ retry_get_time:
+	rtc_tm->tm_min  = readb(S3C2410_RTCMIN);
+	rtc_tm->tm_hour = readb(S3C2410_RTCHOUR);
+	rtc_tm->tm_mday = readb(S3C2410_RTCDATE);
+	rtc_tm->tm_mon  = readb(S3C2410_RTCMON);
+	rtc_tm->tm_year = readb(S3C2410_RTCYEAR);
+	rtc_tm->tm_sec  = readb(S3C2410_RTCSEC);
+
+	/* the only way to work out wether the system was mid-update
+	 * when we read it is to check the second counter, and if it
+	 * is zero, then we re-try the entire read
+	 */
+
+	if (rtc_tm->tm_sec == 0 && !have_retried) {
+		have_retried = 1;
+		goto retry_get_time;
+	}
+
+	pr_debug("read time %02x.%02x.%02x %02x/%02x/%02x\n",
+		 rtc_tm->tm_year, rtc_tm->tm_mon, rtc_tm->tm_mday,
+		 rtc_tm->tm_hour, rtc_tm->tm_min, rtc_tm->tm_sec);
+
+	BCD_TO_BIN(rtc_tm->tm_sec);
+	BCD_TO_BIN(rtc_tm->tm_min);
+	BCD_TO_BIN(rtc_tm->tm_hour);
+	BCD_TO_BIN(rtc_tm->tm_mday);
+	BCD_TO_BIN(rtc_tm->tm_mon);
+	BCD_TO_BIN(rtc_tm->tm_year);
+
+	rtc_tm->tm_year += 100;
+	rtc_tm->tm_mon -= 1;
+}
+
+
+static int s3c2410_rtc_settime(struct rtc_time *tm)
+{
+	/* the rtc gets round the y2k problem by just not supporting it */
+
+	if (tm->tm_year < 100)
+		return -EINVAL;
+
+	writeb(BIN2BCD(tm->tm_sec),  S3C2410_RTCSEC);
+	writeb(BIN2BCD(tm->tm_min),  S3C2410_RTCMIN);
+	writeb(BIN2BCD(tm->tm_hour), S3C2410_RTCHOUR);
+	writeb(BIN2BCD(tm->tm_mday), S3C2410_RTCDATE);
+	writeb(BIN2BCD(tm->tm_mon + 1), S3C2410_RTCMON);
+	writeb(BIN2BCD(tm->tm_year - 100), S3C2410_RTCYEAR);
+
+	return 0;
+}
+
+static void s3c2410_rtc_getalarm(struct rtc_wkalrm *alrm)
+{
+	struct rtc_time *alm_tm = &alrm->time;
+	unsigned int alm_en;
+
+	alm_tm->tm_sec  = readb(S3C2410_ALMSEC);
+	alm_tm->tm_min  = readb(S3C2410_ALMMIN);
+	alm_tm->tm_hour = readb(S3C2410_ALMHOUR);
+	alm_tm->tm_mon  = readb(S3C2410_ALMMON);
+	alm_tm->tm_mday = readb(S3C2410_ALMDATE);
+	alm_tm->tm_year = readb(S3C2410_ALMYEAR);
+
+	alm_en = readb(S3C2410_RTCALM);
+
+	pr_debug("read alarm %02x %02x.%02x.%02x %02x/%02x/%02x\n",
+		 alm_en,
+		 alm_tm->tm_year, alm_tm->tm_mon, alm_tm->tm_mday,
+		 alm_tm->tm_hour, alm_tm->tm_min, alm_tm->tm_sec);
+
+
+	/* decode the alarm enable field */
+
+	if (alm_en & S3C2410_RTCALM_SECEN) {
+		BCD_TO_BIN(alm_tm->tm_sec);
+	} else {
+		alm_tm->tm_sec = 0xff;
+	}
+
+	if (alm_en & S3C2410_RTCALM_MINEN) {
+		BCD_TO_BIN(alm_tm->tm_min);
+	} else {
+		alm_tm->tm_min = 0xff;
+	}
+
+	if (alm_en & S3C2410_RTCALM_HOUREN) {
+		BCD_TO_BIN(alm_tm->tm_hour);
+	} else {
+		alm_tm->tm_hour = 0xff;
+	}
+
+	if (alm_en & S3C2410_RTCALM_DAYEN) {
+		BCD_TO_BIN(alm_tm->tm_mday);
+	} else {
+		alm_tm->tm_mday = 0xff;
+	}
+
+	if (alm_en & S3C2410_RTCALM_MONEN) {
+		BCD_TO_BIN(alm_tm->tm_mon);
+		alm_tm->tm_mon -= 1;
+	} else {
+		alm_tm->tm_mon = 0xff;
+	}
+
+	if (alm_en & S3C2410_RTCALM_YEAREN) {
+		BCD_TO_BIN(alm_tm->tm_year);
+	} else {
+		alm_tm->tm_year = 0xffff;
+	}
+
+	/* todo - set alrm->enabled ? */
+}
+
+static int s3c2410_rtc_setalarm(struct rtc_wkalrm *alrm)
+{
+	struct rtc_time *tm = &alrm->time;
+	unsigned int alrm_en;
+
+	pr_debug("s3c2410_rtc_setalarm: %d, %02x/%02x/%02x %02x.%02x.%02x\n",
+		 alrm->enabled,
+		 tm->tm_mday & 0xff, tm->tm_mon & 0xff, tm->tm_year & 0xff,
+		 tm->tm_hour & 0xff, tm->tm_min & 0xff, tm->tm_sec);
+
+	if (alrm->enabled || 1) {
+		alrm_en = readb(S3C2410_RTCALM) & S3C2410_RTCALM_ALMEN;
+		writeb(0x00, S3C2410_RTCALM);
+
+		if (tm->tm_sec < 60 && tm->tm_sec >= 0) {
+			alrm_en |= S3C2410_RTCALM_SECEN;
+			writeb(BIN2BCD(tm->tm_sec), S3C2410_ALMSEC);
+		}
+
+		if (tm->tm_min < 60 && tm->tm_min >= 0) {
+			alrm_en |= S3C2410_RTCALM_MINEN;
+			writeb(BIN2BCD(tm->tm_min), S3C2410_ALMMIN);
+		}
+
+		if (tm->tm_hour < 24 && tm->tm_hour >= 0) {
+			alrm_en |= S3C2410_RTCALM_HOUREN;
+			writeb(BIN2BCD(tm->tm_hour), S3C2410_ALMHOUR);
+		}
+
+		pr_debug("setting S3C2410_RTCALM to %08x\n", alrm_en);
+
+		writeb(alrm_en, S3C2410_RTCALM);
+		enable_irq_wake(s3c2410_rtc_alarmno);
+	} else {
+		alrm_en = readb(S3C2410_RTCALM);
+		alrm_en &= ~S3C2410_RTCALM_ALMEN;
+		writeb(alrm_en, S3C2410_RTCALM);
+		disable_irq_wake(s3c2410_rtc_alarmno);
+	}
+
+	return 0;
+}
+
+static int s3c2410_rtc_ioctl(unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	case RTC_AIE_OFF:
+	case RTC_AIE_ON:
+		s3c2410_rtc_setaie((cmd == RTC_AIE_ON) ? 1 : 0);
+		return 0;
+
+	case RTC_PIE_OFF:
+	case RTC_PIE_ON:
+		s3c2410_rtc_setpie((cmd == RTC_PIE_ON) ? 1 : 0);
+		return 0;
+
+	case RTC_IRQP_READ:
+		return put_user(s3c2410_rtc_freq, (unsigned long __user *)arg);
+
+	case RTC_IRQP_SET:
+		if (arg < 1 || arg > 64)
+			return -EINVAL;
+
+		if (!capable(CAP_SYS_RESOURCE))
+			return -EACCES;
+
+		/* check for power of 2 */
+
+		if ((arg & (arg-1)) != 0)
+			return -EINVAL;
+
+		pr_debug("s3c2410_rtc: setting frequency %ld\n", arg);
+
+		s3c2410_rtc_setfreq(arg);
+		return 0;
+
+	case RTC_UIE_ON:
+	case RTC_UIE_OFF:
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+static int s3c2410_rtc_proc(char *buf)
+{
+	unsigned int rtcalm = readb(S3C2410_RTCALM);
+	unsigned int ticnt = readb (S3C2410_TICNT);
+	char *p = buf;
+
+	p += sprintf(p, "alarm_IRQ\t: %s\n",
+		     (rtcalm & S3C2410_RTCALM_ALMEN) ? "yes" : "no" );
+	p += sprintf(p, "periodic_IRQ\t: %s\n",
+		     (ticnt & S3C2410_TICNT_ENABLE) ? "yes" : "no" );
+	p += sprintf(p, "periodic_freq\t: %d\n", s3c2410_rtc_freq);
+
+	return p - buf;
+}
+
+static int s3c2410_rtc_open(void)
+{
+	int ret;
+
+	ret = request_irq(s3c2410_rtc_alarmno, s3c2410_rtc_alarmirq,
+			  SA_INTERRUPT,  "s3c2410-rtc alarm", NULL);
+
+	if (ret)
+		printk(KERN_ERR "IRQ%d already in use\n", s3c2410_rtc_alarmno);
+
+	ret = request_irq(s3c2410_rtc_tickno, s3c2410_rtc_tickirq,
+			  SA_INTERRUPT,  "s3c2410-rtc tick", NULL);
+
+	if (ret) {
+		printk(KERN_ERR "IRQ%d already in use\n", s3c2410_rtc_tickno);
+		goto tick_err;
+	}
+
+	return ret;
+
+ tick_err:
+	free_irq(s3c2410_rtc_alarmno, NULL);
+	return ret;
+}
+
+static void s3c2410_rtc_release(void)
+{
+	/* do not clear AIE here, it may be needed for wake */
+
+	s3c2410_rtc_setpie(0);
+	free_irq(s3c2410_rtc_alarmno, NULL);
+	free_irq(s3c2410_rtc_tickno, NULL);
+}
+
+static struct rtc_ops s3c2410_rtcops = {
+	.owner		= THIS_MODULE,
+	.open		= s3c2410_rtc_open,
+	.release	= s3c2410_rtc_release,
+	.ioctl		= s3c2410_rtc_ioctl,
+	.read_time	= s3c2410_rtc_gettime,
+	.set_time	= s3c2410_rtc_settime,
+	.read_alarm	= s3c2410_rtc_getalarm,
+	.set_alarm	= s3c2410_rtc_setalarm,
+	.proc	        = s3c2410_rtc_proc,
+};
+
+static void s3c2410_rtc_enable(struct device *dev, int en)
+{
+	unsigned int tmp;
+
+	if (s3c2410_rtc_base == NULL)
+		return;
+
+	if (!en) {
+		tmp = readb(S3C2410_RTCCON);
+		writeb(tmp & ~S3C2410_RTCCON_RTCEN, S3C2410_RTCCON);
+
+		tmp = readb(S3C2410_TICNT);
+		writeb(tmp & ~S3C2410_TICNT_ENABLE, S3C2410_TICNT);
+	} else {
+		/* re-enable the device, and check it is ok */
+
+		if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_RTCEN) == 0){
+			dev_info(dev, "rtc disabled, re-enabling\n");
+
+			tmp = readb(S3C2410_RTCCON);
+			writeb(tmp | S3C2410_RTCCON_RTCEN , S3C2410_RTCCON);
+		}
+
+		if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_CNTSEL)){
+			dev_info(dev, "removing S3C2410_RTCCON_CNTSEL\n");
+
+			tmp = readb(S3C2410_RTCCON);
+			writeb(tmp& ~S3C2410_RTCCON_CNTSEL , S3C2410_RTCCON);
+		}
+
+		if ((readb(S3C2410_RTCCON) & S3C2410_RTCCON_CLKRST)){
+			dev_info(dev, "removing S3C2410_RTCCON_CLKRST\n");
+
+			tmp = readb(S3C2410_RTCCON);
+			writeb(tmp & ~S3C2410_RTCCON_CLKRST, S3C2410_RTCCON);
+		}
+	}
+}
+
+static int s3c2410_rtc_remove(struct device *dev)
+{
+	unregister_rtc(&s3c2410_rtcops);
+
+	s3c2410_rtc_setpie(0);
+	s3c2410_rtc_setaie(0);
+
+	if (s3c2410_rtc_mem != NULL) {
+		pr_debug("s3c2410_rtc: releasing s3c2410_rtc_mem\n");
+		iounmap(s3c2410_rtc_base);
+		release_resource(s3c2410_rtc_mem);
+		kfree(s3c2410_rtc_mem);
+	}
+
+	return 0;
+}
+
+static int s3c2410_rtc_probe(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct resource *res;
+	int ret;
+
+	pr_debug("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev);
+
+	/* find the IRQs */
+
+	s3c2410_rtc_tickno = platform_get_irq(pdev, 1);
+	if (s3c2410_rtc_tickno <= 0) {
+		dev_err(dev, "no irq for rtc tick\n");
+		return -ENOENT;
+	}
+
+	s3c2410_rtc_alarmno = platform_get_irq(pdev, 0);
+	if (s3c2410_rtc_alarmno <= 0) {
+		dev_err(dev, "no irq for alarm\n");
+		return -ENOENT;
+	}
+
+	pr_debug("s3c2410_rtc: tick irq %d, alarm irq %d\n",
+		 s3c2410_rtc_tickno, s3c2410_rtc_alarmno);
+
+	/* get the memory region */
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		dev_err(dev, "failed to get memory region resource\n");
+		return -ENOENT;
+	}
+
+	s3c2410_rtc_mem = request_mem_region(res->start, res->end-res->start+1,
+				     pdev->name);
+
+	if (s3c2410_rtc_mem == NULL) {
+		dev_err(dev, "failed to reserve memory region\n");
+		ret = -ENOENT;
+		goto exit_err;
+	}
+
+	s3c2410_rtc_base = ioremap(res->start, res->end - res->start + 1);
+	if (s3c2410_rtc_base == NULL) {
+		dev_err(dev, "failed ioremap()\n");
+		ret = -EINVAL;
+		goto exit_err;
+	}
+
+	s3c2410_rtc_mem = res;
+	pr_debug("s3c2410_rtc_base=%p\n", s3c2410_rtc_base);
+
+ 	pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(S3C2410_RTCCON));
+
+	/* check to see if everything is setup correctly */
+
+	s3c2410_rtc_enable(dev, 1);
+
+ 	pr_debug("s3c2410_rtc: RTCCON=%02x\n", readb(S3C2410_RTCCON));
+
+	s3c2410_rtc_setfreq(s3c2410_rtc_freq);
+
+	/* register RTC and exit */
+
+	register_rtc(&s3c2410_rtcops);
+	return 0;
+
+ exit_err:
+	dev_err(dev, "error %d during initialisation\n", ret);
+
+	return ret;
+}
+
+#ifdef CONFIG_PM
+
+/* S3C2410 RTC Power management control */
+
+static struct timespec s3c2410_rtc_delta;
+
+static int ticnt_save;
+
+static int s3c2410_rtc_suspend(struct device *dev, u32 state, u32 level)
+{
+	struct rtc_time tm;
+	struct timespec time;
+
+	time.tv_nsec = 0;
+
+	if (level == SUSPEND_POWER_DOWN) {
+		/* save TICNT for anyone using periodic interrupts */
+
+		ticnt_save = readb(S3C2410_TICNT);
+
+		/* calculate time delta for suspend */
+
+		s3c2410_rtc_gettime(&tm);
+		rtc_tm_to_time(&tm, &time.tv_sec);
+		save_time_delta(&s3c2410_rtc_delta, &time);
+		s3c2410_rtc_enable(dev, 0);
+	}
+
+	return 0;
+}
+
+static int s3c2410_rtc_resume(struct device *dev, u32 level)
+{
+	struct rtc_time tm;
+	struct timespec time;
+
+	time.tv_nsec = 0;
+
+	s3c2410_rtc_enable(dev, 1);
+	s3c2410_rtc_gettime(&tm);
+	rtc_tm_to_time(&tm, &time.tv_sec);
+	restore_time_delta(&s3c2410_rtc_delta, &time);
+
+	writeb(ticnt_save, S3C2410_TICNT);
+	return 0;
+}
+#else
+#define s3c2410_rtc_suspend NULL
+#define s3c2410_rtc_resume  NULL
+#endif
+
+static struct device_driver s3c2410_rtcdrv = {
+	.name		= "s3c2410-rtc",
+	.bus		= &platform_bus_type,
+	.probe		= s3c2410_rtc_probe,
+	.remove		= s3c2410_rtc_remove,
+	.suspend	= s3c2410_rtc_suspend,
+	.resume		= s3c2410_rtc_resume,
+};
+
+static char __initdata banner[] = "S3C2410 RTC, (c) 2004 Simtec Electronics\n";
+
+static int __init s3c2410_rtc_init(void)
+{
+	printk(banner);
+	return driver_register(&s3c2410_rtcdrv);
+}
+
+static void __exit s3c2410_rtc_exit(void)
+{
+	driver_unregister(&s3c2410_rtcdrv);
+}
+
+module_init(s3c2410_rtc_init);
+module_exit(s3c2410_rtc_exit);
+
+MODULE_DESCRIPTION("S3C24XX RTC Driver");
+MODULE_AUTHOR("Ben Dooks, <ben@simtec.co.uk>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/scan_keyb.c b/drivers/char/scan_keyb.c
new file mode 100644
index 0000000..2b5bb4f
--- /dev/null
+++ b/drivers/char/scan_keyb.c
@@ -0,0 +1,149 @@
+/*
+ *	$Id: scan_keyb.c,v 1.2 2000/07/04 06:24:42 yaegashi Exp $ 
+ *	Copyright (C) 2000 YAEGASHI Takeshi
+ *	Generic scan keyboard driver
+ */
+
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/kbd_ll.h>
+#include <linux/delay.h>
+#include <linux/random.h>
+#include <linux/poll.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/kbd_kern.h>
+#include <linux/timer.h>
+
+#define SCANHZ	(HZ/20)
+
+struct scan_keyboard {
+	struct scan_keyboard *next;
+	int (*scan)(unsigned char *buffer);
+	const unsigned char *table;
+	unsigned char *s0, *s1;
+	int length;
+};
+
+static int scan_jiffies=0;
+static struct scan_keyboard *keyboards=NULL;
+struct timer_list scan_timer;
+
+static void check_kbd(const unsigned char *table,
+		      unsigned char *new, unsigned char *old, int length)
+{
+	int need_tasklet_schedule=0;
+	unsigned int xor, bit;
+	
+	while(length-->0) {
+		if((xor=*new^*old)==0) {
+			table+=8;
+		}
+		else {
+			for(bit=0x01; bit<0x100; bit<<=1) {
+				if(xor&bit) {
+					handle_scancode(*table, !(*new&bit));
+					need_tasklet_schedule=1;
+#if 0
+					printk("0x%x %s\n", *table, (*new&bit)?"released":"pressed");
+#endif
+				}
+				table++;
+			}
+		}
+		new++; old++;
+	}
+
+	if(need_tasklet_schedule)
+		tasklet_schedule(&keyboard_tasklet);
+}
+
+
+static void scan_kbd(unsigned long dummy)
+{
+	struct scan_keyboard *kbd;
+
+	scan_jiffies++;
+
+	for(kbd=keyboards; kbd!=NULL; kbd=kbd->next) {
+		if(scan_jiffies&1) {
+			if(!kbd->scan(kbd->s0))
+				check_kbd(kbd->table,
+					  kbd->s0, kbd->s1, kbd->length);
+			else
+				memcpy(kbd->s0, kbd->s1, kbd->length);
+		}
+		else {
+			if(!kbd->scan(kbd->s1))
+				check_kbd(kbd->table,
+					  kbd->s1, kbd->s0, kbd->length);
+			else
+				memcpy(kbd->s1, kbd->s0, kbd->length);
+		}
+		
+	}
+
+	init_timer(&scan_timer);
+	scan_timer.expires = jiffies + SCANHZ;
+	scan_timer.data = 0;
+	scan_timer.function = scan_kbd;
+	add_timer(&scan_timer);
+}
+
+
+int register_scan_keyboard(int (*scan)(unsigned char *buffer),
+			   const unsigned char *table,
+			   int length)
+{
+	struct scan_keyboard *kbd;
+
+	kbd = kmalloc(sizeof(struct scan_keyboard), GFP_KERNEL);
+	if (kbd == NULL)
+		goto error_out;
+
+	kbd->scan=scan;
+	kbd->table=table;
+	kbd->length=length;
+
+	kbd->s0 = kmalloc(length, GFP_KERNEL);
+	if (kbd->s0 == NULL)
+		goto error_free_kbd;
+
+	kbd->s1 = kmalloc(length, GFP_KERNEL);
+	if (kbd->s1 == NULL)
+		goto error_free_s0;
+
+	memset(kbd->s0, -1, kbd->length);
+	memset(kbd->s1, -1, kbd->length);
+	
+	kbd->next=keyboards;
+	keyboards=kbd;
+
+	return 0;
+
+ error_free_s0:
+	kfree(kbd->s0);
+
+ error_free_kbd:
+	kfree(kbd);
+
+ error_out:
+	return -ENOMEM;
+}
+			      
+			      
+void __init scan_kbd_init(void)
+{
+	init_timer(&scan_timer);
+	scan_timer.expires = jiffies + SCANHZ;
+	scan_timer.data = 0;
+	scan_timer.function = scan_kbd;
+	add_timer(&scan_timer);
+
+	printk(KERN_INFO "Generic scan keyboard driver initialized\n");
+}
diff --git a/drivers/char/scan_keyb.h b/drivers/char/scan_keyb.h
new file mode 100644
index 0000000..b4b6112
--- /dev/null
+++ b/drivers/char/scan_keyb.h
@@ -0,0 +1,15 @@
+#ifndef	__DRIVER_CHAR_SCAN_KEYB_H
+#define	__DRIVER_CHAR_SCAN_KEYB_H
+/*
+ *	$Id: scan_keyb.h,v 1.1 2000/06/10 21:45:30 yaegashi Exp $
+ *	Copyright (C) 2000 YAEGASHI Takeshi
+ *	Generic scan keyboard driver
+ */
+
+int register_scan_keyboard(int (*scan)(unsigned char *buffer),
+			   const unsigned char *table,
+			   int length);
+
+void __init scan_kbd_init(void);
+
+#endif
diff --git a/drivers/char/scc.h b/drivers/char/scc.h
new file mode 100644
index 0000000..51810f7
--- /dev/null
+++ b/drivers/char/scc.h
@@ -0,0 +1,613 @@
+/*
+ * atari_SCC.h: Definitions for the Am8530 Serial Communications Controller
+ *
+ * Copyright 1994 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+
+
+#ifndef _SCC_H
+#define _SCC_H
+
+#include <linux/delay.h>
+
+/* Special configuration ioctls for the Atari SCC5380 Serial
+ * Communications Controller
+ */
+
+/* ioctl command codes */
+
+#define TIOCGATSCC	0x54c0	/* get SCC configuration */
+#define TIOCSATSCC	0x54c1	/* set SCC configuration */
+#define TIOCDATSCC	0x54c2	/* reset configuration to defaults */
+
+/* Clock sources */
+
+#define CLK_RTxC	0
+#define CLK_TRxC	1
+#define CLK_PCLK	2
+
+/* baud_bases for the common clocks in the Atari. These are the real
+ * frequencies divided by 16.
+ */
+   
+#define SCC_BAUD_BASE_TIMC	19200	/* 0.3072 MHz from TT-MFP, Timer C */
+#define SCC_BAUD_BASE_BCLK	153600	/* 2.4576 MHz */
+#define SCC_BAUD_BASE_PCLK4	229500	/* 3.6720 MHz */
+#define SCC_BAUD_BASE_PCLK	503374	/* 8.0539763 MHz */
+#define SCC_BAUD_BASE_NONE	0	/* for not connected or unused
+					 * clock sources */
+
+/* The SCC clock configuration structure */
+
+struct scc_clock_config {
+	unsigned	RTxC_base;	/* base_baud of RTxC */
+	unsigned	TRxC_base;	/* base_baud of TRxC */
+	unsigned	PCLK_base;	/* base_baud of PCLK, both channels! */
+	struct {
+		unsigned clksrc;	/* CLK_RTxC, CLK_TRxC or CLK_PCLK */
+		unsigned divisor;	/* divisor for base baud, valid values:
+					 * see below */
+	} baud_table[17];		/* For 50, 75, 110, 135, 150, 200, 300,
+					 * 600, 1200, 1800, 2400, 4800, 9600,
+					 * 19200, 38400, 57600 and 115200 bps.
+					 * The last two could be replaced by
+					 * other rates > 38400 if they're not
+					 * possible.
+					 */
+};
+
+/* The following divisors are valid:
+ *
+ *   - CLK_RTxC: 1 or even (1, 2 and 4 are the direct modes, > 4 use
+ *               the BRG)
+ *
+ *   - CLK_TRxC: 1, 2 or 4 (no BRG, only direct modes possible)
+ *
+ *   - CLK_PCLK: >= 4 and even (no direct modes, only BRG)
+ *
+ */
+
+struct scc_port {
+	struct gs_port		gs;
+	volatile unsigned char	*ctrlp;
+	volatile unsigned char	*datap;
+	int			x_char;		/* xon/xoff character */
+	int			c_dcd;
+	int			channel;
+	struct scc_port		*port_a;	/* Reference to port A and B */
+	struct scc_port		*port_b;	/*   structs for reg access  */
+};
+
+#define SCC_MAGIC	0x52696368
+
+/***********************************************************************/
+/*                                                                     */
+/*                             Register Names                          */
+/*                                                                     */
+/***********************************************************************/
+
+/* The SCC documentation gives no explicit names to the registers,
+ * they're just called WR0..15 and RR0..15. To make the source code
+ * better readable and make the transparent write reg read access (see
+ * below) possible, I christen them here with self-invented names.
+ * Note that (real) read registers are assigned numbers 16..31. WR7'
+ * has number 33.
+ */
+
+#define	COMMAND_REG		0	/* wo */
+#define	INT_AND_DMA_REG		1	/* wo */
+#define	INT_VECTOR_REG		2	/* rw, common to both channels */
+#define	RX_CTRL_REG		3	/* rw */
+#define	AUX1_CTRL_REG		4	/* rw */
+#define	TX_CTRL_REG		5	/* rw */
+#define	SYNC_ADR_REG		6	/* wo */
+#define	SYNC_CHAR_REG		7	/* wo */
+#define	SDLC_OPTION_REG		33	/* wo */
+#define	TX_DATA_REG		8	/* wo */
+#define	MASTER_INT_CTRL		9	/* wo, common to both channels */
+#define	AUX2_CTRL_REG		10	/* rw */
+#define	CLK_CTRL_REG		11	/* wo */
+#define	TIMER_LOW_REG		12	/* rw */
+#define	TIMER_HIGH_REG		13	/* rw */
+#define	DPLL_CTRL_REG		14	/* wo */
+#define	INT_CTRL_REG		15	/* rw */
+
+#define	STATUS_REG		16	/* ro */
+#define	SPCOND_STATUS_REG	17	/* wo */
+/* RR2 is WR2 for Channel A, Channel B gives vector + current status: */
+#define	CURR_VECTOR_REG		18	/* Ch. B only, Ch. A for rw */
+#define	INT_PENDING_REG		19	/* Channel A only! */
+/* RR4 is WR4, if b6(MR7') == 1 */
+/* RR5 is WR5, if b6(MR7') == 1 */
+#define	FS_FIFO_LOW_REG		22	/* ro */
+#define	FS_FIFO_HIGH_REG	23	/* ro */
+#define	RX_DATA_REG		24	/* ro */
+/* RR9 is WR3, if b6(MR7') == 1 */
+#define	DPLL_STATUS_REG		26	/* ro */
+/* RR11 is WR10, if b6(MR7') == 1 */
+/* RR12 is WR12 */
+/* RR13 is WR13 */
+/* RR14 not present */
+/* RR15 is WR15 */
+
+
+/***********************************************************************/
+/*                                                                     */
+/*                             Register Values                         */
+/*                                                                     */
+/***********************************************************************/
+
+
+/* WR0: COMMAND_REG "CR" */
+
+#define	CR_RX_CRC_RESET		0x40
+#define	CR_TX_CRC_RESET		0x80
+#define	CR_TX_UNDERRUN_RESET	0xc0
+
+#define	CR_EXTSTAT_RESET	0x10
+#define	CR_SEND_ABORT		0x18
+#define	CR_ENAB_INT_NEXT_RX	0x20
+#define	CR_TX_PENDING_RESET	0x28
+#define	CR_ERROR_RESET		0x30
+#define	CR_HIGHEST_IUS_RESET	0x38
+
+
+/* WR1: INT_AND_DMA_REG "IDR" */
+
+#define	IDR_EXTSTAT_INT_ENAB	0x01
+#define	IDR_TX_INT_ENAB		0x02
+#define	IDR_PARERR_AS_SPCOND	0x04
+
+#define	IDR_RX_INT_DISAB	0x00
+#define	IDR_RX_INT_FIRST	0x08
+#define	IDR_RX_INT_ALL		0x10
+#define	IDR_RX_INT_SPCOND	0x18
+#define	IDR_RX_INT_MASK		0x18
+
+#define	IDR_WAITREQ_RX		0x20
+#define	IDR_WAITREQ_IS_REQ	0x40
+#define	IDR_WAITREQ_ENAB	0x80
+
+
+/* WR3: RX_CTRL_REG "RCR" */
+
+#define	RCR_RX_ENAB		0x01
+#define	RCR_DISCARD_SYNC_CHARS	0x02
+#define	RCR_ADDR_SEARCH		0x04
+#define	RCR_CRC_ENAB		0x08
+#define	RCR_SEARCH_MODE		0x10
+#define	RCR_AUTO_ENAB_MODE	0x20
+
+#define	RCR_CHSIZE_MASK		0xc0
+#define	RCR_CHSIZE_5		0x00
+#define	RCR_CHSIZE_6		0x40
+#define	RCR_CHSIZE_7		0x80
+#define	RCR_CHSIZE_8		0xc0
+
+
+/* WR4: AUX1_CTRL_REG "A1CR" */
+
+#define	A1CR_PARITY_MASK	0x03
+#define	A1CR_PARITY_NONE	0x00
+#define	A1CR_PARITY_ODD		0x01
+#define	A1CR_PARITY_EVEN	0x03
+
+#define	A1CR_MODE_MASK		0x0c
+#define	A1CR_MODE_SYNCR		0x00
+#define	A1CR_MODE_ASYNC_1	0x04
+#define	A1CR_MODE_ASYNC_15	0x08
+#define	A1CR_MODE_ASYNC_2	0x0c
+
+#define	A1CR_SYNCR_MODE_MASK	0x30
+#define	A1CR_SYNCR_MONOSYNC	0x00
+#define	A1CR_SYNCR_BISYNC	0x10
+#define	A1CR_SYNCR_SDLC		0x20
+#define	A1CR_SYNCR_EXTCSYNC	0x30
+
+#define	A1CR_CLKMODE_MASK	0xc0
+#define	A1CR_CLKMODE_x1		0x00
+#define	A1CR_CLKMODE_x16	0x40
+#define	A1CR_CLKMODE_x32	0x80
+#define	A1CR_CLKMODE_x64	0xc0
+
+
+/* WR5: TX_CTRL_REG "TCR" */
+
+#define	TCR_TX_CRC_ENAB		0x01
+#define	TCR_RTS			0x02
+#define	TCR_USE_CRC_CCITT	0x00
+#define	TCR_USE_CRC_16		0x04
+#define	TCR_TX_ENAB		0x08
+#define	TCR_SEND_BREAK		0x10
+
+#define	TCR_CHSIZE_MASK		0x60
+#define	TCR_CHSIZE_5		0x00
+#define	TCR_CHSIZE_6		0x20
+#define	TCR_CHSIZE_7		0x40
+#define	TCR_CHSIZE_8		0x60
+
+#define	TCR_DTR			0x80
+
+
+/* WR7': SLDC_OPTION_REG "SOR" */
+
+#define	SOR_AUTO_TX_ENAB	0x01
+#define	SOR_AUTO_EOM_RESET	0x02
+#define	SOR_AUTO_RTS_MODE	0x04
+#define	SOR_NRZI_DISAB_HIGH	0x08
+#define	SOR_ALT_DTRREQ_TIMING	0x10
+#define	SOR_READ_CRC_CHARS	0x20
+#define	SOR_EXTENDED_REG_ACCESS	0x40
+
+
+/* WR9: MASTER_INT_CTRL "MIC" */
+
+#define	MIC_VEC_INCL_STAT	0x01
+#define	MIC_NO_VECTOR		0x02
+#define	MIC_DISAB_LOWER_CHAIN	0x04
+#define	MIC_MASTER_INT_ENAB	0x08
+#define	MIC_STATUS_HIGH		0x10
+#define	MIC_IGN_INTACK		0x20
+
+#define	MIC_NO_RESET		0x00
+#define	MIC_CH_A_RESET		0x40
+#define	MIC_CH_B_RESET		0x80
+#define	MIC_HARD_RESET		0xc0
+
+
+/* WR10: AUX2_CTRL_REG "A2CR" */
+
+#define	A2CR_SYNC_6		0x01
+#define	A2CR_LOOP_MODE		0x02
+#define	A2CR_ABORT_ON_UNDERRUN	0x04
+#define	A2CR_MARK_IDLE		0x08
+#define	A2CR_GO_ACTIVE_ON_POLL	0x10
+
+#define	A2CR_CODING_MASK	0x60
+#define	A2CR_CODING_NRZ		0x00
+#define	A2CR_CODING_NRZI	0x20
+#define	A2CR_CODING_FM1		0x40
+#define	A2CR_CODING_FM0		0x60
+
+#define	A2CR_PRESET_CRC_1	0x80
+
+
+/* WR11: CLK_CTRL_REG "CCR" */
+
+#define	CCR_TRxCOUT_MASK	0x03
+#define	CCR_TRxCOUT_XTAL	0x00
+#define	CCR_TRxCOUT_TXCLK	0x01
+#define	CCR_TRxCOUT_BRG		0x02
+#define	CCR_TRxCOUT_DPLL	0x03
+
+#define	CCR_TRxC_OUTPUT		0x04
+
+#define	CCR_TXCLK_MASK		0x18
+#define	CCR_TXCLK_RTxC		0x00
+#define	CCR_TXCLK_TRxC		0x08
+#define	CCR_TXCLK_BRG		0x10
+#define	CCR_TXCLK_DPLL		0x18
+
+#define	CCR_RXCLK_MASK		0x60
+#define	CCR_RXCLK_RTxC		0x00
+#define	CCR_RXCLK_TRxC		0x20
+#define	CCR_RXCLK_BRG		0x40
+#define	CCR_RXCLK_DPLL		0x60
+
+#define	CCR_RTxC_XTAL		0x80
+
+
+/* WR14: DPLL_CTRL_REG "DCR" */
+
+#define	DCR_BRG_ENAB		0x01
+#define	DCR_BRG_USE_PCLK	0x02
+#define	DCR_DTRREQ_IS_REQ	0x04
+#define	DCR_AUTO_ECHO		0x08
+#define	DCR_LOCAL_LOOPBACK	0x10
+
+#define	DCR_DPLL_EDGE_SEARCH	0x20
+#define	DCR_DPLL_ERR_RESET	0x40
+#define	DCR_DPLL_DISAB		0x60
+#define	DCR_DPLL_CLK_BRG	0x80
+#define	DCR_DPLL_CLK_RTxC	0xa0
+#define	DCR_DPLL_FM		0xc0
+#define	DCR_DPLL_NRZI		0xe0
+
+
+/* WR15: INT_CTRL_REG "ICR" */
+
+#define	ICR_OPTIONREG_SELECT	0x01
+#define	ICR_ENAB_BRG_ZERO_INT	0x02
+#define	ICR_USE_FS_FIFO		0x04
+#define	ICR_ENAB_DCD_INT	0x08
+#define	ICR_ENAB_SYNC_INT	0x10
+#define	ICR_ENAB_CTS_INT	0x20
+#define	ICR_ENAB_UNDERRUN_INT	0x40
+#define	ICR_ENAB_BREAK_INT	0x80
+
+
+/* RR0: STATUS_REG "SR" */
+
+#define	SR_CHAR_AVAIL		0x01
+#define	SR_BRG_ZERO		0x02
+#define	SR_TX_BUF_EMPTY		0x04
+#define	SR_DCD			0x08
+#define	SR_SYNC_ABORT		0x10
+#define	SR_CTS			0x20
+#define	SR_TX_UNDERRUN		0x40
+#define	SR_BREAK		0x80
+
+
+/* RR1: SPCOND_STATUS_REG "SCSR" */
+
+#define	SCSR_ALL_SENT		0x01
+#define	SCSR_RESIDUAL_MASK	0x0e
+#define	SCSR_PARITY_ERR		0x10
+#define	SCSR_RX_OVERRUN		0x20
+#define	SCSR_CRC_FRAME_ERR	0x40
+#define	SCSR_END_OF_FRAME	0x80
+
+
+/* RR3: INT_PENDING_REG "IPR" */
+
+#define	IPR_B_EXTSTAT		0x01
+#define	IPR_B_TX		0x02
+#define	IPR_B_RX		0x04
+#define	IPR_A_EXTSTAT		0x08
+#define	IPR_A_TX		0x10
+#define	IPR_A_RX		0x20
+
+
+/* RR7: FS_FIFO_HIGH_REG "FFHR" */
+
+#define	FFHR_CNT_MASK		0x3f
+#define	FFHR_IS_FROM_FIFO	0x40
+#define	FFHR_FIFO_OVERRUN	0x80
+
+
+/* RR10: DPLL_STATUS_REG "DSR" */
+
+#define	DSR_ON_LOOP		0x02
+#define	DSR_ON_LOOP_SENDING	0x10
+#define	DSR_TWO_CLK_MISSING	0x40
+#define	DSR_ONE_CLK_MISSING	0x80
+
+/***********************************************************************/
+/*                                                                     */
+/*                             Register Access                         */
+/*                                                                     */
+/***********************************************************************/
+
+
+/* The SCC needs 3.5 PCLK cycles recovery time between to register
+ * accesses. PCLK runs with 8 MHz on an Atari, so this delay is 3.5 *
+ * 125 ns = 437.5 ns. This is too short for udelay().
+ * 10/16/95: A tstb mfp.par_dt_reg takes 600ns (sure?) and thus should be
+ * quite right
+ */
+
+#define scc_reg_delay() \
+    do {			\
+	if (MACH_IS_MVME16x || MACH_IS_BVME6000 || MACH_IS_MVME147)	\
+		__asm__ __volatile__ ( " nop; nop");			\
+	else if (MACH_IS_ATARI)						\
+		__asm__ __volatile__ ( "tstb %0" : : "g" (*_scc_del) : "cc" );\
+    } while (0)
+
+extern unsigned char scc_shadow[2][16];
+
+/* The following functions should relax the somehow complicated
+ * register access of the SCC. _SCCwrite() stores all written values
+ * (except for WR0 and WR8) in shadow registers for later recall. This
+ * removes the burden of remembering written values as needed. The
+ * extra work of storing the value doesn't count, since a delay is
+ * needed after a SCC access anyway. Additionally, _SCCwrite() manages
+ * writes to WR0 and WR8 differently, because these can be accessed
+ * directly with less overhead. Another special case are WR7 and WR7'.
+ * _SCCwrite automatically checks what of this registers is selected
+ * and changes b0 of WR15 if needed.
+ * 
+ * _SCCread() for standard read registers is straightforward, except
+ * for RR2 (split into two "virtual" registers: one for the value
+ * written to WR2 (from the shadow) and one for the vector including
+ * status from RR2, Ch. B) and RR3. The latter must be read from
+ * Channel A, because it reads as all zeros on Ch. B. RR0 and RR8 can
+ * be accessed directly as before.
+ * 
+ * The two inline function contain complicated switch statements. But
+ * I rely on regno and final_delay being constants, so gcc can reduce
+ * the whole stuff to just some assembler statements.
+ * 
+ * _SCCwrite and _SCCread aren't intended to be used directly under
+ * normal circumstances. The macros SCCread[_ND] and SCCwrite[_ND] are
+ * for that purpose. They assume that a local variable 'port' is
+ * declared and pointing to the port's scc_struct entry. The
+ * variants with "_NB" appended should be used if no other SCC
+ * accesses follow immediately (within 0.5 usecs). They just skip the
+ * final delay nops.
+ * 
+ * Please note that accesses to SCC registers should only take place
+ * when interrupts are turned off (at least if SCC interrupts are
+ * enabled). Otherwise, an interrupt could interfere with the
+ * two-stage accessing process.
+ *
+ */
+
+
+static __inline__ void _SCCwrite(
+	struct scc_port *port,
+	unsigned char *shadow,
+	volatile unsigned char *_scc_del,
+	int regno,
+	unsigned char val, int final_delay )
+{
+	switch( regno ) {
+
+	  case COMMAND_REG:
+		/* WR0 can be written directly without pointing */
+		*port->ctrlp = val;
+		break;
+
+	  case SYNC_CHAR_REG:
+		/* For WR7, first set b0 of WR15 to 0, if needed */
+		if (shadow[INT_CTRL_REG] & ICR_OPTIONREG_SELECT) {
+			*port->ctrlp = 15;
+			shadow[INT_CTRL_REG] &= ~ICR_OPTIONREG_SELECT;
+			scc_reg_delay();
+			*port->ctrlp = shadow[INT_CTRL_REG];
+			scc_reg_delay();
+		}
+		goto normal_case;
+		
+	  case SDLC_OPTION_REG:
+		/* For WR7', first set b0 of WR15 to 1, if needed */
+		if (!(shadow[INT_CTRL_REG] & ICR_OPTIONREG_SELECT)) {
+			*port->ctrlp = 15;
+			shadow[INT_CTRL_REG] |= ICR_OPTIONREG_SELECT;
+			scc_reg_delay();
+			*port->ctrlp = shadow[INT_CTRL_REG];
+			scc_reg_delay();
+		}
+		*port->ctrlp = 7;
+		shadow[8] = val;	/* WR7' shadowed at WR8 */
+		scc_reg_delay();
+		*port->ctrlp = val;
+		break;
+
+	  case TX_DATA_REG:		/* WR8 */
+		/* TX_DATA_REG can be accessed directly on some h/w */
+		if (MACH_IS_MVME16x || MACH_IS_BVME6000 || MACH_IS_MVME147)
+		{
+			*port->ctrlp = regno;
+			scc_reg_delay();
+			*port->ctrlp = val;
+		}
+		else
+			*port->datap = val;
+		break;
+
+	  case MASTER_INT_CTRL:
+		*port->ctrlp = regno;
+		val &= 0x3f;	/* bits 6..7 are the reset commands */
+		scc_shadow[0][regno] = val;
+		scc_reg_delay();
+		*port->ctrlp = val;
+		break;
+
+	  case DPLL_CTRL_REG:
+		*port->ctrlp = regno;
+		val &= 0x1f;			/* bits 5..7 are the DPLL commands */
+		shadow[regno] = val;
+		scc_reg_delay();
+		*port->ctrlp = val;
+		break;
+
+	  case 1 ... 6:	
+	  case 10 ... 13:
+	  case 15:
+	  normal_case:
+		*port->ctrlp = regno;
+		shadow[regno] = val;
+		scc_reg_delay();
+		*port->ctrlp = val;
+		break;
+		
+	  default:
+		printk( "Bad SCC write access to WR%d\n", regno );
+		break;
+		
+	}
+
+	if (final_delay)
+		scc_reg_delay();
+}
+
+
+static __inline__ unsigned char _SCCread(
+	struct scc_port *port,
+	unsigned char *shadow,
+	volatile unsigned char *_scc_del,
+	int regno, int final_delay )
+{
+	unsigned char rv;
+
+	switch( regno ) {
+
+		/* --- real read registers --- */
+	  case STATUS_REG:
+		rv = *port->ctrlp;
+		break;
+
+	  case INT_PENDING_REG:
+		/* RR3: read only from Channel A! */
+		port = port->port_a;
+		goto normal_case;
+
+	  case RX_DATA_REG:
+		/* RR8 can be accessed directly on some h/w */
+		if (MACH_IS_MVME16x || MACH_IS_BVME6000 || MACH_IS_MVME147)
+		{
+			*port->ctrlp = 8;
+			scc_reg_delay();
+			rv = *port->ctrlp;
+		}
+		else
+			rv = *port->datap;
+		break;
+
+	  case CURR_VECTOR_REG:
+		/* RR2 (vector including status) from Ch. B */
+		port = port->port_b;
+		goto normal_case;
+		
+		/* --- reading write registers: access the shadow --- */
+	  case 1 ... 7:
+	  case 10 ... 15:
+		return shadow[regno]; /* no final delay! */
+
+		/* WR7' is special, because it is shadowed at the place of WR8 */
+	  case SDLC_OPTION_REG:
+		return shadow[8]; /* no final delay! */
+
+		/* WR9 is special too, because it is common for both channels */
+	  case MASTER_INT_CTRL:
+		return scc_shadow[0][9]; /* no final delay! */
+
+	  default:
+		printk( "Bad SCC read access to %cR%d\n", (regno & 16) ? 'R' : 'W',
+				regno & ~16 );
+		break;
+		
+	  case SPCOND_STATUS_REG:
+	  case FS_FIFO_LOW_REG:
+	  case FS_FIFO_HIGH_REG:
+	  case DPLL_STATUS_REG:
+	  normal_case:
+		*port->ctrlp = regno & 0x0f;
+		scc_reg_delay();
+		rv = *port->ctrlp;
+		break;
+		
+	}
+
+	if (final_delay)
+		scc_reg_delay();
+	return rv;
+}
+
+#define SCC_ACCESS_INIT(port)						\
+	unsigned char *_scc_shadow = &scc_shadow[port->channel][0]
+
+#define	SCCwrite(reg,val)	_SCCwrite(port,_scc_shadow,scc_del,(reg),(val),1)
+#define	SCCwrite_NB(reg,val)	_SCCwrite(port,_scc_shadow,scc_del,(reg),(val),0)
+#define	SCCread(reg)		_SCCread(port,_scc_shadow,scc_del,(reg),1)
+#define	SCCread_NB(reg)		_SCCread(port,_scc_shadow,scc_del,(reg),0)
+
+#define SCCmod(reg,and,or)	SCCwrite((reg),(SCCread(reg)&(and))|(or))
+
+#endif /* _SCC_H */
diff --git a/drivers/char/scx200_gpio.c b/drivers/char/scx200_gpio.c
new file mode 100644
index 0000000..664a6e9
--- /dev/null
+++ b/drivers/char/scx200_gpio.c
@@ -0,0 +1,149 @@
+/* linux/drivers/char/scx200_gpio.c 
+
+   National Semiconductor SCx200 GPIO driver.  Allows a user space
+   process to play with the GPIO pins.
+
+   Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> */
+
+#include <linux/config.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/scx200_gpio.h>
+
+#define NAME "scx200_gpio"
+
+MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
+MODULE_DESCRIPTION("NatSemi SCx200 GPIO Pin Driver");
+MODULE_LICENSE("GPL");
+
+static int major = 0;		/* default to dynamic major */
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+
+static ssize_t scx200_gpio_write(struct file *file, const char __user *data, 
+				 size_t len, loff_t *ppos)
+{
+	unsigned m = iminor(file->f_dentry->d_inode);
+	size_t i;
+
+	for (i = 0; i < len; ++i) {
+		char c;
+		if (get_user(c, data+i))
+			return -EFAULT;
+		switch (c)
+		{
+		case '0': 
+			scx200_gpio_set(m, 0); 
+			break;
+		case '1': 
+			scx200_gpio_set(m, 1); 
+			break;
+		case 'O':
+			printk(KERN_INFO NAME ": GPIO%d output enabled\n", m);
+			scx200_gpio_configure(m, ~1, 1);
+			break;
+		case 'o':
+			printk(KERN_INFO NAME ": GPIO%d output disabled\n", m);
+			scx200_gpio_configure(m, ~1, 0);
+			break;
+		case 'T':
+			printk(KERN_INFO NAME ": GPIO%d output is push pull\n", m);
+			scx200_gpio_configure(m, ~2, 2);
+			break;
+		case 't':
+			printk(KERN_INFO NAME ": GPIO%d output is open drain\n", m);
+			scx200_gpio_configure(m, ~2, 0);
+			break;
+		case 'P':
+			printk(KERN_INFO NAME ": GPIO%d pull up enabled\n", m);
+			scx200_gpio_configure(m, ~4, 4);
+			break;
+		case 'p':
+			printk(KERN_INFO NAME ": GPIO%d pull up disabled\n", m);
+			scx200_gpio_configure(m, ~4, 0);
+			break;
+		}
+	}
+
+	return len;
+}
+
+static ssize_t scx200_gpio_read(struct file *file, char __user *buf,
+				size_t len, loff_t *ppos)
+{
+	unsigned m = iminor(file->f_dentry->d_inode);
+	int value;
+
+	value = scx200_gpio_get(m);
+	if (put_user(value ? '1' : '0', buf))
+		return -EFAULT;
+	
+	return 1;
+}
+
+static int scx200_gpio_open(struct inode *inode, struct file *file)
+{
+	unsigned m = iminor(inode);
+	if (m > 63)
+		return -EINVAL;
+	return nonseekable_open(inode, file);
+}
+
+static int scx200_gpio_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+
+static struct file_operations scx200_gpio_fops = {
+	.owner   = THIS_MODULE,
+	.write   = scx200_gpio_write,
+	.read    = scx200_gpio_read,
+	.open    = scx200_gpio_open,
+	.release = scx200_gpio_release,
+};
+
+static int __init scx200_gpio_init(void)
+{
+	int r;
+
+	printk(KERN_DEBUG NAME ": NatSemi SCx200 GPIO Driver\n");
+
+	if (!scx200_gpio_present()) {
+		printk(KERN_ERR NAME ": no SCx200 gpio pins available\n");
+		return -ENODEV;
+	}
+
+	r = register_chrdev(major, NAME, &scx200_gpio_fops);
+	if (r < 0) {
+		printk(KERN_ERR NAME ": unable to register character device\n");
+		return r;
+	}
+	if (!major) {
+		major = r;
+		printk(KERN_DEBUG NAME ": got dynamic major %d\n", major);
+	}
+
+	return 0;
+}
+
+static void __exit scx200_gpio_cleanup(void)
+{
+	unregister_chrdev(major, NAME);
+}
+
+module_init(scx200_gpio_init);
+module_exit(scx200_gpio_cleanup);
+
+/*
+    Local variables:
+        compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
+        c-basic-offset: 8
+    End:
+*/
diff --git a/drivers/char/selection.c b/drivers/char/selection.c
new file mode 100644
index 0000000..16d630f
--- /dev/null
+++ b/drivers/char/selection.c
@@ -0,0 +1,306 @@
+/*
+ * linux/drivers/char/selection.c
+ *
+ * This module exports the functions:
+ *
+ *     'int set_selection(struct tiocl_selection __user *, struct tty_struct *)'
+ *     'void clear_selection(void)'
+ *     'int paste_selection(struct tty_struct *)'
+ *     'int sel_loadlut(char __user *)'
+ *
+ * Now that /dev/vcs exists, most of this can disappear again.
+ */
+
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/uaccess.h>
+
+#include <linux/vt_kern.h>
+#include <linux/consolemap.h>
+#include <linux/selection.h>
+#include <linux/tiocl.h>
+#include <linux/console.h>
+
+/* Don't take this from <ctype.h>: 011-015 on the screen aren't spaces */
+#define isspace(c)	((c) == ' ')
+
+extern void poke_blanked_console(void);
+
+/* Variables for selection control. */
+/* Use a dynamic buffer, instead of static (Dec 1994) */
+struct vc_data *sel_cons;		/* must not be disallocated */
+static volatile int sel_start = -1; 	/* cleared by clear_selection */
+static int sel_end;
+static int sel_buffer_lth;
+static char *sel_buffer;
+
+/* clear_selection, highlight and highlight_pointer can be called
+   from interrupt (via scrollback/front) */
+
+/* set reverse video on characters s-e of console with selection. */
+static inline void highlight(const int s, const int e)
+{
+	invert_screen(sel_cons, s, e-s+2, 1);
+}
+
+/* use complementary color to show the pointer */
+static inline void highlight_pointer(const int where)
+{
+	complement_pos(sel_cons, where);
+}
+
+static unsigned char
+sel_pos(int n)
+{
+	return inverse_translate(sel_cons, screen_glyph(sel_cons, n));
+}
+
+/* remove the current selection highlight, if any,
+   from the console holding the selection. */
+void
+clear_selection(void) {
+	highlight_pointer(-1); /* hide the pointer */
+	if (sel_start != -1) {
+		highlight(sel_start, sel_end);
+		sel_start = -1;
+	}
+}
+
+/*
+ * User settable table: what characters are to be considered alphabetic?
+ * 256 bits
+ */
+static u32 inwordLut[8]={
+  0x00000000, /* control chars     */
+  0x03FF0000, /* digits            */
+  0x87FFFFFE, /* uppercase and '_' */
+  0x07FFFFFE, /* lowercase         */
+  0x00000000,
+  0x00000000,
+  0xFF7FFFFF, /* latin-1 accented letters, not multiplication sign */
+  0xFF7FFFFF  /* latin-1 accented letters, not division sign */
+};
+
+static inline int inword(const unsigned char c) {
+	return ( inwordLut[c>>5] >> (c & 0x1F) ) & 1;
+}
+
+/* set inwordLut contents. Invoked by ioctl(). */
+int sel_loadlut(char __user *p)
+{
+	return copy_from_user(inwordLut, (u32 __user *)(p+4), 32) ? -EFAULT : 0;
+}
+
+/* does screen address p correspond to character at LH/RH edge of screen? */
+static inline int atedge(const int p, int size_row)
+{
+	return (!(p % size_row)	|| !((p + 2) % size_row));
+}
+
+/* constrain v such that v <= u */
+static inline unsigned short limit(const unsigned short v, const unsigned short u)
+{
+	return (v > u) ? u : v;
+}
+
+/* set the current selection. Invoked by ioctl() or by kernel code. */
+int set_selection(const struct tiocl_selection __user *sel, struct tty_struct *tty)
+{
+	struct vc_data *vc = vc_cons[fg_console].d;
+	int sel_mode, new_sel_start, new_sel_end, spc;
+	char *bp, *obp;
+	int i, ps, pe;
+
+	poke_blanked_console();
+
+	{ unsigned short xs, ys, xe, ye;
+
+	  if (!access_ok(VERIFY_READ, sel, sizeof(*sel)))
+		return -EFAULT;
+	  __get_user(xs, &sel->xs);
+	  __get_user(ys, &sel->ys);
+	  __get_user(xe, &sel->xe);
+	  __get_user(ye, &sel->ye);
+	  __get_user(sel_mode, &sel->sel_mode);
+	  xs--; ys--; xe--; ye--;
+	  xs = limit(xs, vc->vc_cols - 1);
+	  ys = limit(ys, vc->vc_rows - 1);
+	  xe = limit(xe, vc->vc_cols - 1);
+	  ye = limit(ye, vc->vc_rows - 1);
+	  ps = ys * vc->vc_size_row + (xs << 1);
+	  pe = ye * vc->vc_size_row + (xe << 1);
+
+	  if (sel_mode == TIOCL_SELCLEAR) {
+	      /* useful for screendump without selection highlights */
+	      clear_selection();
+	      return 0;
+	  }
+
+	  if (mouse_reporting() && (sel_mode & TIOCL_SELMOUSEREPORT)) {
+	      mouse_report(tty, sel_mode & TIOCL_SELBUTTONMASK, xs, ys);
+	      return 0;
+	  }
+        }
+
+	if (ps > pe)	/* make sel_start <= sel_end */
+	{
+		int tmp = ps;
+		ps = pe;
+		pe = tmp;
+	}
+
+	if (sel_cons != vc_cons[fg_console].d) {
+		clear_selection();
+		sel_cons = vc_cons[fg_console].d;
+	}
+
+	switch (sel_mode)
+	{
+		case TIOCL_SELCHAR:	/* character-by-character selection */
+			new_sel_start = ps;
+			new_sel_end = pe;
+			break;
+		case TIOCL_SELWORD:	/* word-by-word selection */
+			spc = isspace(sel_pos(ps));
+			for (new_sel_start = ps; ; ps -= 2)
+			{
+				if ((spc && !isspace(sel_pos(ps))) ||
+				    (!spc && !inword(sel_pos(ps))))
+					break;
+				new_sel_start = ps;
+				if (!(ps % vc->vc_size_row))
+					break;
+			}
+			spc = isspace(sel_pos(pe));
+			for (new_sel_end = pe; ; pe += 2)
+			{
+				if ((spc && !isspace(sel_pos(pe))) ||
+				    (!spc && !inword(sel_pos(pe))))
+					break;
+				new_sel_end = pe;
+				if (!((pe + 2) % vc->vc_size_row))
+					break;
+			}
+			break;
+		case TIOCL_SELLINE:	/* line-by-line selection */
+			new_sel_start = ps - ps % vc->vc_size_row;
+			new_sel_end = pe + vc->vc_size_row
+				    - pe % vc->vc_size_row - 2;
+			break;
+		case TIOCL_SELPOINTER:
+			highlight_pointer(pe);
+			return 0;
+		default:
+			return -EINVAL;
+	}
+
+	/* remove the pointer */
+	highlight_pointer(-1);
+
+	/* select to end of line if on trailing space */
+	if (new_sel_end > new_sel_start &&
+		!atedge(new_sel_end, vc->vc_size_row) &&
+		isspace(sel_pos(new_sel_end))) {
+		for (pe = new_sel_end + 2; ; pe += 2)
+			if (!isspace(sel_pos(pe)) ||
+			    atedge(pe, vc->vc_size_row))
+				break;
+		if (isspace(sel_pos(pe)))
+			new_sel_end = pe;
+	}
+	if (sel_start == -1)	/* no current selection */
+		highlight(new_sel_start, new_sel_end);
+	else if (new_sel_start == sel_start)
+	{
+		if (new_sel_end == sel_end)	/* no action required */
+			return 0;
+		else if (new_sel_end > sel_end)	/* extend to right */
+			highlight(sel_end + 2, new_sel_end);
+		else				/* contract from right */
+			highlight(new_sel_end + 2, sel_end);
+	}
+	else if (new_sel_end == sel_end)
+	{
+		if (new_sel_start < sel_start)	/* extend to left */
+			highlight(new_sel_start, sel_start - 2);
+		else				/* contract from left */
+			highlight(sel_start, new_sel_start - 2);
+	}
+	else	/* some other case; start selection from scratch */
+	{
+		clear_selection();
+		highlight(new_sel_start, new_sel_end);
+	}
+	sel_start = new_sel_start;
+	sel_end = new_sel_end;
+
+	/* Allocate a new buffer before freeing the old one ... */
+	bp = kmalloc((sel_end-sel_start)/2+1, GFP_KERNEL);
+	if (!bp) {
+		printk(KERN_WARNING "selection: kmalloc() failed\n");
+		clear_selection();
+		return -ENOMEM;
+	}
+	if (sel_buffer)
+		kfree(sel_buffer);
+	sel_buffer = bp;
+
+	obp = bp;
+	for (i = sel_start; i <= sel_end; i += 2) {
+		*bp = sel_pos(i);
+		if (!isspace(*bp++))
+			obp = bp;
+		if (! ((i + 2) % vc->vc_size_row)) {
+			/* strip trailing blanks from line and add newline,
+			   unless non-space at end of line. */
+			if (obp != bp) {
+				bp = obp;
+				*bp++ = '\r';
+			}
+			obp = bp;
+		}
+	}
+	sel_buffer_lth = bp - sel_buffer;
+	return 0;
+}
+
+/* Insert the contents of the selection buffer into the
+ * queue of the tty associated with the current console.
+ * Invoked by ioctl().
+ */
+int paste_selection(struct tty_struct *tty)
+{
+	struct vc_data *vc = (struct vc_data *)tty->driver_data;
+	int	pasted = 0, count;
+	struct  tty_ldisc *ld;
+	DECLARE_WAITQUEUE(wait, current);
+
+	acquire_console_sem();
+	poke_blanked_console();
+	release_console_sem();
+
+	ld = tty_ldisc_ref_wait(tty);
+	
+	add_wait_queue(&vc->paste_wait, &wait);
+	while (sel_buffer && sel_buffer_lth > pasted) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (test_bit(TTY_THROTTLED, &tty->flags)) {
+			schedule();
+			continue;
+		}
+		count = sel_buffer_lth - pasted;
+		count = min(count, tty->ldisc.receive_room(tty));
+		tty->ldisc.receive_buf(tty, sel_buffer + pasted, NULL, count);
+		pasted += count;
+	}
+	remove_wait_queue(&vc->paste_wait, &wait);
+	current->state = TASK_RUNNING;
+
+	tty_ldisc_deref(ld);
+	return 0;
+}
diff --git a/drivers/char/ser_a2232.c b/drivers/char/ser_a2232.c
new file mode 100644
index 0000000..6b4e9d1
--- /dev/null
+++ b/drivers/char/ser_a2232.c
@@ -0,0 +1,825 @@
+/* drivers/char/ser_a2232.c */
+
+/* $Id: ser_a2232.c,v 0.4 2000/01/25 12:00:00 ehaase Exp $ */
+
+/* Linux serial driver for the Amiga A2232 board */
+
+/* This driver is MAINTAINED. Before applying any changes, please contact
+ * the author.
+ */
+
+/* Copyright (c) 2000-2001 Enver Haase    <ehaase@inf.fu-berlin.de>
+ *                   alias The A2232 driver project <A2232@gmx.net>
+ * All rights reserved.
+ *
+ *   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.
+ *
+ */
+/***************************** Documentation ************************/
+/*
+ * This driver is in EXPERIMENTAL state. That means I could not find
+ * someone with five A2232 boards with 35 ports running at 19200 bps
+ * at the same time and test the machine's behaviour.
+ * However, I know that you can performance-tweak this driver (see
+ * the source code).
+ * One thing to consider is the time this driver consumes during the
+ * Amiga's vertical blank interrupt. Everything that is to be done
+ * _IS DONE_ when entering the vertical blank interrupt handler of
+ * this driver.
+ * However, it would be more sane to only do the job for only ONE card
+ * instead of ALL cards at a time; or, more generally, to handle only
+ * SOME ports instead of ALL ports at a time.
+ * However, as long as no-one runs into problems I guess I shouldn't
+ * change the driver as it runs fine for me :) .
+ *
+ * Version history of this file:
+ * 0.4	Resolved licensing issues.
+ * 0.3	Inclusion in the Linux/m68k tree, small fixes.
+ * 0.2	Added documentation, minor typo fixes.
+ * 0.1	Initial release.
+ *
+ * TO DO:
+ * -	Handle incoming BREAK events. I guess "Stevens: Advanced
+ *	Programming in the UNIX(R) Environment" is a good reference
+ *	on what is to be done.
+ * -	When installing as a module, don't simply 'printk' text, but
+ *	send it to the TTY used by the user.
+ *
+ * THANKS TO:
+ * -	Jukka Marin (65EC02 code).
+ * -	The other NetBSD developers on whose A2232 driver I had a
+ *	pretty close look. However, I didn't copy any code so it
+ *	is okay to put my code under the GPL and include it into
+ *	Linux.
+ */
+/***************************** End of Documentation *****************/
+
+/***************************** Defines ******************************/
+/*
+ * Enables experimental 115200 (normal) 230400 (turbo) baud rate.
+ * The A2232 specification states it can only operate at speeds up to
+ * 19200 bits per second, and I was not able to send a file via
+ * "sz"/"rz" and a null-modem cable from one A2232 port to another
+ * at 115200 bits per second.
+ * However, this might work for you.
+ */
+#undef A2232_SPEEDHACK
+/*
+ * Default is not to use RTS/CTS so you could be talked to death.
+ */
+#define A2232_SUPPRESS_RTSCTS_WARNING
+/************************* End of Defines ***************************/
+
+/***************************** Includes *****************************/
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+
+#include <asm/setup.h>
+#include <asm/amigaints.h>
+#include <asm/amigahw.h>
+#include <linux/zorro.h>
+#include <asm/irq.h>
+#include <asm/semaphore.h>
+
+#include <linux/delay.h>
+
+#include <linux/serial.h>
+#include <linux/generic_serial.h>
+
+#include "ser_a2232.h"
+#include "ser_a2232fw.h"
+/************************* End of Includes **************************/
+
+/***************************** Prototypes ***************************/
+/* The interrupt service routine */
+static irqreturn_t a2232_vbl_inter(int irq, void *data, struct pt_regs *fp);
+/* Initialize the port structures */
+static void a2232_init_portstructs(void);
+/* Initialize and register TTY drivers. */
+/* returns 0 IFF successful */
+static int a2232_init_drivers(void); 
+
+/* BEGIN GENERIC_SERIAL PROTOTYPES */
+static void a2232_disable_tx_interrupts(void *ptr);
+static void a2232_enable_tx_interrupts(void *ptr);
+static void a2232_disable_rx_interrupts(void *ptr);
+static void a2232_enable_rx_interrupts(void *ptr);
+static int  a2232_get_CD(void *ptr);
+static void a2232_shutdown_port(void *ptr);
+static int  a2232_set_real_termios(void *ptr);
+static int  a2232_chars_in_buffer(void *ptr);
+static void a2232_close(void *ptr);
+static void a2232_hungup(void *ptr);
+/* static void a2232_getserial (void *ptr, struct serial_struct *sp); */
+/* END GENERIC_SERIAL PROTOTYPES */
+
+/* Functions that the TTY driver struct expects */
+static int  a2232_ioctl(struct tty_struct *tty, struct file *file,
+										unsigned int cmd, unsigned long arg);
+static void a2232_throttle(struct tty_struct *tty);
+static void a2232_unthrottle(struct tty_struct *tty);
+static int  a2232_open(struct tty_struct * tty, struct file * filp);
+/************************* End of Prototypes ************************/
+
+/***************************** Global variables *********************/
+/*---------------------------------------------------------------------------
+ * Interface from generic_serial.c back here
+ *--------------------------------------------------------------------------*/
+static struct real_driver a2232_real_driver = {
+        a2232_disable_tx_interrupts,
+        a2232_enable_tx_interrupts,
+        a2232_disable_rx_interrupts,
+        a2232_enable_rx_interrupts,
+        a2232_get_CD,
+        a2232_shutdown_port,
+        a2232_set_real_termios,
+        a2232_chars_in_buffer,
+        a2232_close,
+        a2232_hungup,
+	NULL	/* a2232_getserial */
+};
+
+static void *a2232_driver_ID = &a2232_driver_ID; // Some memory address WE own.
+
+/* Ports structs */
+static struct a2232_port a2232_ports[MAX_A2232_BOARDS*NUMLINES];
+
+/* TTY driver structs */
+static struct tty_driver *a2232_driver;
+
+/* nr of cards completely (all ports) and correctly configured */
+static int nr_a2232; 
+
+/* zorro_dev structs for the A2232's */
+static struct zorro_dev *zd_a2232[MAX_A2232_BOARDS]; 
+/***************************** End of Global variables **************/
+
+/* Helper functions */
+
+static inline volatile struct a2232memory *a2232mem(unsigned int board)
+{
+	return (volatile struct a2232memory *)ZTWO_VADDR(zd_a2232[board]->resource.start);
+}
+
+static inline volatile struct a2232status *a2232stat(unsigned int board,
+						     unsigned int portonboard)
+{
+	volatile struct a2232memory *mem = a2232mem(board);
+	return &(mem->Status[portonboard]);
+}
+
+static inline void a2232_receive_char(struct a2232_port *port, int ch, int err)
+{
+/* 	Mostly stolen from other drivers.
+	Maybe one could implement a more efficient version by not only
+	transferring one character at a time.
+*/
+	struct tty_struct *tty = port->gs.tty;
+
+	if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+		return;
+
+	tty->flip.count++;
+
+#if 0
+	switch(err) {
+	case TTY_BREAK:
+		break;
+	case TTY_PARITY:
+		break;
+	case TTY_OVERRUN:
+		break;
+	case TTY_FRAME:
+		break;
+	}
+#endif
+
+	*tty->flip.flag_buf_ptr++ = err;
+	*tty->flip.char_buf_ptr++ = ch;
+	tty_flip_buffer_push(tty);
+}
+
+/***************************** Functions ****************************/
+/*** BEGIN OF REAL_DRIVER FUNCTIONS ***/
+
+static void a2232_disable_tx_interrupts(void *ptr)
+{
+	struct a2232_port *port;
+	volatile struct a2232status *stat;
+	unsigned long flags;
+  
+	port = ptr;
+	stat = a2232stat(port->which_a2232, port->which_port_on_a2232);
+	stat->OutDisable = -1;
+
+	/* Does this here really have to be? */
+	local_irq_save(flags);
+	port->gs.flags &= ~GS_TX_INTEN;
+	local_irq_restore(flags);
+}
+
+static void a2232_enable_tx_interrupts(void *ptr)
+{
+	struct a2232_port *port;
+	volatile struct a2232status *stat;
+	unsigned long flags;
+
+	port = ptr;
+	stat = a2232stat(port->which_a2232, port->which_port_on_a2232);
+	stat->OutDisable = 0;
+
+	/* Does this here really have to be? */
+	local_irq_save(flags);
+	port->gs.flags |= GS_TX_INTEN;
+	local_irq_restore(flags);
+}
+
+static void a2232_disable_rx_interrupts(void *ptr)
+{
+	struct a2232_port *port;
+	port = ptr;
+	port->disable_rx = -1;
+}
+
+static void a2232_enable_rx_interrupts(void *ptr)
+{
+	struct a2232_port *port;
+	port = ptr;
+	port->disable_rx = 0;
+}
+
+static int  a2232_get_CD(void *ptr)
+{
+	return ((struct a2232_port *) ptr)->cd_status;
+}
+
+static void a2232_shutdown_port(void *ptr)
+{
+	struct a2232_port *port;
+	volatile struct a2232status *stat;
+	unsigned long flags;
+
+	port = ptr;
+	stat = a2232stat(port->which_a2232, port->which_port_on_a2232);
+
+	local_irq_save(flags);
+
+	port->gs.flags &= ~GS_ACTIVE;
+	
+	if (port->gs.tty && port->gs.tty->termios->c_cflag & HUPCL) {
+		/* Set DTR and RTS to Low, flush output.
+		   The NetBSD driver "msc.c" does it this way. */
+		stat->Command = (	(stat->Command & ~A2232CMD_CMask) | 
+					A2232CMD_Close );
+		stat->OutFlush = -1;
+		stat->Setup = -1;
+	}
+
+	local_irq_restore(flags);
+	
+	/* After analyzing control flow, I think a2232_shutdown_port
+		is actually the last call from the system when at application
+		level someone issues a "echo Hello >>/dev/ttyY0".
+		Therefore I think the MOD_DEC_USE_COUNT should be here and
+		not in "a2232_close()". See the comment in "sx.c", too.
+		If you run into problems, compile this driver into the
+		kernel instead of compiling it as a module. */
+}
+
+static int  a2232_set_real_termios(void *ptr)
+{
+	unsigned int cflag, baud, chsize, stopb, parity, softflow;
+	int rate;
+	int a2232_param, a2232_cmd;
+	unsigned long flags;
+	unsigned int i;
+	struct a2232_port *port = ptr;
+	volatile struct a2232status *status;
+	volatile struct a2232memory *mem;
+
+	if (!port->gs.tty || !port->gs.tty->termios) return 0;
+
+	status = a2232stat(port->which_a2232, port->which_port_on_a2232);
+	mem = a2232mem(port->which_a2232);
+	
+	a2232_param = a2232_cmd = 0;
+
+	// get baud rate
+	baud = port->gs.baud;
+	if (baud == 0) {
+		/* speed == 0 -> drop DTR, do nothing else */
+		local_irq_save(flags);
+		// Clear DTR (and RTS... mhhh).
+		status->Command = (	(status->Command & ~A2232CMD_CMask) |
+					A2232CMD_Close );
+		status->OutFlush = -1;
+		status->Setup = -1;
+		
+		local_irq_restore(flags);
+		return 0;
+	}
+	
+	rate = A2232_BAUD_TABLE_NOAVAIL;
+	for (i=0; i < A2232_BAUD_TABLE_NUM_RATES * 3; i += 3){
+		if (a2232_baud_table[i] == baud){
+			if (mem->Common.Crystal == A2232_TURBO) rate = a2232_baud_table[i+2];
+			else                                    rate = a2232_baud_table[i+1];
+		}
+	}
+	if (rate == A2232_BAUD_TABLE_NOAVAIL){
+		printk("a2232: Board %d Port %d unsupported baud rate: %d baud. Using another.\n",port->which_a2232,port->which_port_on_a2232,baud);
+		// This is useful for both (turbo or normal) Crystal versions.
+		rate = A2232PARAM_B9600;
+	}
+	a2232_param |= rate;
+
+	cflag  = port->gs.tty->termios->c_cflag;
+
+	// get character size
+	chsize = cflag & CSIZE;
+	switch (chsize){
+		case CS8: 	a2232_param |= A2232PARAM_8Bit; break;
+		case CS7: 	a2232_param |= A2232PARAM_7Bit; break;
+		case CS6: 	a2232_param |= A2232PARAM_6Bit; break;
+		case CS5: 	a2232_param |= A2232PARAM_5Bit; break;
+		default:	printk("a2232: Board %d Port %d unsupported character size: %d. Using 8 data bits.\n",
+					port->which_a2232,port->which_port_on_a2232,chsize);
+				a2232_param |= A2232PARAM_8Bit; break;
+	}
+
+	// get number of stop bits
+	stopb  = cflag & CSTOPB;
+	if (stopb){ // two stop bits instead of one
+		printk("a2232: Board %d Port %d 2 stop bits unsupported. Using 1 stop bit.\n",
+			port->which_a2232,port->which_port_on_a2232);
+	}
+
+	// Warn if RTS/CTS not wanted
+	if (!(cflag & CRTSCTS)){
+#ifndef A2232_SUPPRESS_RTSCTS_WARNING
+		printk("a2232: Board %d Port %d cannot switch off firmware-implemented RTS/CTS hardware flow control.\n",
+			port->which_a2232,port->which_port_on_a2232);
+#endif
+	}
+
+	/*	I think this is correct.
+		However, IXOFF means _input_ flow control and I wonder
+		if one should care about IXON _output_ flow control,
+		too. If this makes problems, one should turn the A2232
+		firmware XON/XOFF "SoftFlow" flow control off and use
+		the conventional way of inserting START/STOP characters
+		by hand in throttle()/unthrottle().
+	*/
+	softflow = !!( port->gs.tty->termios->c_iflag & IXOFF );
+
+	// get Parity (Enabled/Disabled? If Enabled, Odd or Even?)
+	parity = cflag & (PARENB | PARODD);
+	if (parity & PARENB){
+		if (parity & PARODD){
+			a2232_cmd |= A2232CMD_OddParity;
+		}
+		else{
+			a2232_cmd |= A2232CMD_EvenParity;
+		}
+	}
+	else a2232_cmd |= A2232CMD_NoParity;
+
+
+	/*	Hmm. Maybe an own a2232_port structure
+		member would be cleaner?	*/
+	if (cflag & CLOCAL)
+		port->gs.flags &= ~ASYNC_CHECK_CD;
+	else
+		port->gs.flags |= ASYNC_CHECK_CD;
+
+
+	/* Now we have all parameters and can go to set them: */
+	local_irq_save(flags);
+
+	status->Param = a2232_param | A2232PARAM_RcvBaud;
+	status->Command = a2232_cmd | A2232CMD_Open |  A2232CMD_Enable;
+	status->SoftFlow = softflow;
+	status->OutDisable = 0;
+	status->Setup = -1;
+
+	local_irq_restore(flags);
+	return 0;
+}
+
+static int  a2232_chars_in_buffer(void *ptr)
+{
+	struct a2232_port *port;
+	volatile struct a2232status *status; 
+	unsigned char ret; /* we need modulo-256 arithmetics */
+	port = ptr;
+	status = a2232stat(port->which_a2232, port->which_port_on_a2232);
+#if A2232_IOBUFLEN != 256
+#error "Re-Implement a2232_chars_in_buffer()!"
+#endif
+	ret = (status->OutHead - status->OutTail);
+	return ret;
+}
+
+static void a2232_close(void *ptr)
+{
+	a2232_disable_tx_interrupts(ptr);
+	a2232_disable_rx_interrupts(ptr);
+	/* see the comment in a2232_shutdown_port above. */
+}
+
+static void a2232_hungup(void *ptr)
+{
+	a2232_close(ptr);
+}
+/*** END   OF REAL_DRIVER FUNCTIONS ***/
+
+/*** BEGIN  FUNCTIONS EXPECTED BY TTY DRIVER STRUCTS ***/
+static int a2232_ioctl(	struct tty_struct *tty, struct file *file,
+			unsigned int cmd, unsigned long arg)
+{
+	return -ENOIOCTLCMD;
+}
+
+static void a2232_throttle(struct tty_struct *tty)
+{
+/* Throttle: System cannot take another chars: Drop RTS or
+             send the STOP char or whatever.
+   The A2232 firmware does RTS/CTS anyway, and XON/XOFF
+   if switched on. So the only thing we can do at this
+   layer here is not taking any characters out of the
+   A2232 buffer any more. */
+	struct a2232_port *port = (struct a2232_port *) tty->driver_data;
+	port->throttle_input = -1;
+}
+
+static void a2232_unthrottle(struct tty_struct *tty)
+{
+/* Unthrottle: dual to "throttle()" above. */
+	struct a2232_port *port = (struct a2232_port *) tty->driver_data;
+	port->throttle_input = 0;
+}
+
+static int  a2232_open(struct tty_struct * tty, struct file * filp)
+{
+/* More or less stolen from other drivers. */
+	int line;
+	int retval;
+	struct a2232_port *port;
+
+	line = tty->index;
+	port = &a2232_ports[line];
+	
+	tty->driver_data = port;
+	port->gs.tty = tty;
+	port->gs.count++;
+	retval = gs_init_port(&port->gs);
+	if (retval) {
+		port->gs.count--;
+		return retval;
+	}
+	port->gs.flags |= GS_ACTIVE;
+	retval = gs_block_til_ready(port, filp);
+
+	if (retval) {
+		port->gs.count--;
+		return retval;
+	}
+
+	a2232_enable_rx_interrupts(port);
+	
+	return 0;
+}
+/*** END OF FUNCTIONS EXPECTED BY TTY DRIVER STRUCTS ***/
+
+static irqreturn_t a2232_vbl_inter(int irq, void *data, struct pt_regs *fp)
+{
+#if A2232_IOBUFLEN != 256
+#error "Re-Implement a2232_vbl_inter()!"
+#endif
+
+struct a2232_port *port;
+volatile struct a2232memory *mem;
+volatile struct a2232status *status;
+unsigned char newhead;
+unsigned char bufpos; /* Must be unsigned char. We need the modulo-256 arithmetics */
+unsigned char ncd, ocd, ccd; /* names consistent with the NetBSD driver */
+volatile u_char *ibuf, *cbuf, *obuf;
+int ch, err, n, p;
+	for (n = 0; n < nr_a2232; n++){		/* for every completely initialized A2232 board */
+		mem = a2232mem(n);
+		for (p = 0; p < NUMLINES; p++){	/* for every port on this board */
+			err = 0;
+			port = &a2232_ports[n*NUMLINES+p];
+			if ( port->gs.flags & GS_ACTIVE ){ /* if the port is used */
+
+				status = a2232stat(n,p);
+
+				if (!port->disable_rx && !port->throttle_input){ /* If input is not disabled */
+					newhead = status->InHead;               /* 65EC02 write pointer */
+					bufpos = status->InTail;
+
+					/* check for input for this port */
+					if (newhead != bufpos) {
+						/* buffer for input chars/events */
+						ibuf = mem->InBuf[p];
+ 
+						/* data types of bytes in ibuf */
+						cbuf = mem->InCtl[p];
+ 
+						/* do for all chars */
+						while (bufpos != newhead) {
+							/* which type of input data? */
+							switch (cbuf[bufpos]) {
+								/* switch on input event (CD, BREAK, etc.) */
+							case A2232INCTL_EVENT:
+								switch (ibuf[bufpos++]) {
+								case A2232EVENT_Break:
+									/* TODO: Handle BREAK signal */
+									break;
+									/*	A2232EVENT_CarrierOn and A2232EVENT_CarrierOff are
+										handled in a separate queue and should not occur here. */
+								case A2232EVENT_Sync:
+									printk("A2232: 65EC02 software sent SYNC event, don't know what to do. Ignoring.");
+									break;
+								default:
+									printk("A2232: 65EC02 software broken, unknown event type %d occurred.\n",ibuf[bufpos-1]);
+								} /* event type switch */
+								break;
+ 							case A2232INCTL_CHAR:
+								/* Receive incoming char */
+								a2232_receive_char(port, ibuf[bufpos], err);
+								bufpos++;
+								break;
+ 							default:
+								printk("A2232: 65EC02 software broken, unknown data type %d occurred.\n",cbuf[bufpos]);
+								bufpos++;
+							} /* switch on input data type */
+						} /* while there's something in the buffer */
+
+						status->InTail = bufpos;            /* tell 65EC02 what we've read */
+						
+					} /* if there was something in the buffer */                          
+				} /* If input is not disabled */
+
+				/* Now check if there's something to output */
+				obuf = mem->OutBuf[p];
+				bufpos = status->OutHead;
+				while ( (port->gs.xmit_cnt > 0)		&&
+					(!port->gs.tty->stopped)	&&
+					(!port->gs.tty->hw_stopped) ){	/* While there are chars to transmit */
+					if (((bufpos+1) & A2232_IOBUFLENMASK) != status->OutTail) { /* If the A2232 buffer is not full */
+						ch = port->gs.xmit_buf[port->gs.xmit_tail];					/* get the next char to transmit */
+						port->gs.xmit_tail = (port->gs.xmit_tail+1) & (SERIAL_XMIT_SIZE-1); /* modulo-addition for the gs.xmit_buf ring-buffer */
+						obuf[bufpos++] = ch;																/* put it into the A2232 buffer */
+						port->gs.xmit_cnt--;
+					}
+					else{																									/* If A2232 the buffer is full */
+						break;																							/* simply stop filling it. */
+					}													
+				}					
+				status->OutHead = bufpos;
+					
+				/* WakeUp if output buffer runs low */
+				if ((port->gs.xmit_cnt <= port->gs.wakeup_chars) && port->gs.tty) {
+					tty_wakeup(port->gs.tty);
+				}
+			} // if the port is used
+		} // for every port on the board
+			
+		/* Now check the CD message queue */
+		newhead = mem->Common.CDHead;
+		bufpos = mem->Common.CDTail;
+		if (newhead != bufpos){				/* There are CD events in queue */
+			ocd = mem->Common.CDStatus; 		/* get old status bits */
+			while (newhead != bufpos){		/* read all events */
+				ncd = mem->CDBuf[bufpos++]; 	/* get one event */
+				ccd = ncd ^ ocd; 		/* mask of changed lines */
+				ocd = ncd; 			/* save new status bits */
+				for(p=0; p < NUMLINES; p++){	/* for all ports */
+					if (ccd & 1){		/* this one changed */
+
+						struct a2232_port *port = &a2232_ports[n*7+p];
+						port->cd_status = !(ncd & 1); /* ncd&1 <=> CD is now off */
+
+						if (!(port->gs.flags & ASYNC_CHECK_CD))
+							;	/* Don't report DCD changes */
+						else if (port->cd_status) { // if DCD on: DCD went UP!
+							
+							/* Are we blocking in open?*/
+							wake_up_interruptible(&port->gs.open_wait);
+						}
+						else { // if DCD off: DCD went DOWN!
+							if (port->gs.tty)
+								tty_hangup (port->gs.tty);
+						}
+						
+					} // if CD changed for this port
+					ccd >>= 1;
+					ncd >>= 1;									/* Shift bits for next line */
+				} // for every port
+			} // while CD events in queue
+			mem->Common.CDStatus = ocd; /* save new status */
+			mem->Common.CDTail = bufpos; /* remove events */
+		} // if events in CD queue
+		
+	} // for every completely initialized A2232 board
+	return IRQ_HANDLED;
+}
+
+static void a2232_init_portstructs(void)
+{
+	struct a2232_port *port;
+	int i;
+
+	for (i = 0; i < MAX_A2232_BOARDS*NUMLINES; i++) {
+		port = a2232_ports + i;
+		port->which_a2232 = i/NUMLINES;
+		port->which_port_on_a2232 = i%NUMLINES;
+		port->disable_rx = port->throttle_input = port->cd_status = 0;
+		port->gs.magic = A2232_MAGIC;
+		port->gs.close_delay = HZ/2;
+		port->gs.closing_wait = 30 * HZ;
+		port->gs.rd = &a2232_real_driver;
+#ifdef NEW_WRITE_LOCKING
+		init_MUTEX(&(port->gs.port_write_sem));
+#endif
+		init_waitqueue_head(&port->gs.open_wait);
+		init_waitqueue_head(&port->gs.close_wait);
+	}
+}
+
+static struct tty_operations a2232_ops = {
+	.open = a2232_open,
+	.close = gs_close,
+	.write = gs_write,
+	.put_char = gs_put_char,
+	.flush_chars = gs_flush_chars,
+	.write_room = gs_write_room,
+	.chars_in_buffer = gs_chars_in_buffer,
+	.flush_buffer = gs_flush_buffer,
+	.ioctl = a2232_ioctl,
+	.throttle = a2232_throttle,
+	.unthrottle = a2232_unthrottle,
+	.set_termios = gs_set_termios,
+	.stop = gs_stop,
+	.start = gs_start,
+	.hangup = gs_hangup,
+};
+
+static int a2232_init_drivers(void)
+{
+	int error;
+
+	a2232_driver = alloc_tty_driver(NUMLINES * nr_a2232);
+	if (!a2232_driver)
+		return -ENOMEM;
+	a2232_driver->owner = THIS_MODULE;
+	a2232_driver->driver_name = "commodore_a2232";
+	a2232_driver->name = "ttyY";
+	a2232_driver->major = A2232_NORMAL_MAJOR;
+	a2232_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	a2232_driver->subtype = SERIAL_TYPE_NORMAL;
+	a2232_driver->init_termios = tty_std_termios;
+	a2232_driver->init_termios.c_cflag =
+		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	a2232_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(a2232_driver, &a2232_ops);
+	if ((error = tty_register_driver(a2232_driver))) {
+		printk(KERN_ERR "A2232: Couldn't register A2232 driver, error = %d\n",
+		       error);
+		put_tty_driver(a2232_driver);
+		return 1;
+	}
+	return 0;
+}
+
+static int __init a2232board_init(void)
+{
+	struct zorro_dev *z;
+
+	unsigned int boardaddr;
+	int bcount;
+	short start;
+	u_char *from;
+	volatile u_char *to;
+	volatile struct a2232memory *mem;
+
+#ifdef CONFIG_SMP
+	return -ENODEV;	/* This driver is not SMP aware. Is there an SMP ZorroII-bus-machine? */
+#endif
+
+	if (!MACH_IS_AMIGA){
+		return -ENODEV;
+	}
+
+	printk("Commodore A2232 driver initializing.\n"); /* Say that we're alive. */
+
+	z = NULL;
+	nr_a2232 = 0;
+	while ( (z = zorro_find_device(ZORRO_WILDCARD, z)) ){
+		if (	(z->id != ZORRO_PROD_CBM_A2232_PROTOTYPE) && 
+			(z->id != ZORRO_PROD_CBM_A2232)	){
+			continue;	// The board found was no A2232
+		}
+		if (!zorro_request_device(z,"A2232 driver"))
+			continue;
+
+		printk("Commodore A2232 found (#%d).\n",nr_a2232);
+
+		zd_a2232[nr_a2232] = z;
+
+		boardaddr = ZTWO_VADDR( z->resource.start );
+		printk("Board is located at address 0x%x, size is 0x%x.\n", boardaddr, (unsigned int) ((z->resource.end+1) - (z->resource.start)));
+
+		mem = (volatile struct a2232memory *) boardaddr;
+
+		(void) mem->Enable6502Reset;   /* copy the code across to the board */
+		to = (u_char *)mem;  from = a2232_65EC02code; bcount = sizeof(a2232_65EC02code) - 2;
+		start = *(short *)from;
+		from += sizeof(start);
+		to += start;
+		while(bcount--) *to++ = *from++;
+		printk("65EC02 software uploaded to the A2232 memory.\n");
+  
+		mem->Common.Crystal = A2232_UNKNOWN;  /* use automatic speed check */
+  
+		/* start 6502 running */
+		(void) mem->ResetBoard;
+		printk("A2232's 65EC02 CPU up and running.\n");
+  
+		/* wait until speed detector has finished */
+		for (bcount = 0; bcount < 2000; bcount++) {
+			udelay(1000);
+			if (mem->Common.Crystal)
+				break;
+		}
+		printk((mem->Common.Crystal?"A2232 oscillator crystal detected by 65EC02 software: ":"65EC02 software could not determine A2232 oscillator crystal: "));
+		switch (mem->Common.Crystal){
+		case A2232_UNKNOWN:
+			printk("Unknown crystal.\n");
+			break;
+ 		case A2232_NORMAL:
+			printk ("Normal crystal.\n");
+			break;
+		case A2232_TURBO:
+			printk ("Turbo crystal.\n");
+			break;
+		default:
+			printk ("0x%x. Huh?\n",mem->Common.Crystal);
+		}
+
+		nr_a2232++;
+
+	}	
+
+	printk("Total: %d A2232 boards initialized.\n.", nr_a2232); /* Some status report if no card was found */
+
+	a2232_init_portstructs();
+
+	/*
+		a2232_init_drivers also registers the drivers. Must be here because all boards
+		have to be detected first.
+	*/
+	if (a2232_init_drivers()) return -ENODEV; // maybe we should use a different -Exxx?
+
+	request_irq(IRQ_AMIGA_VERTB, a2232_vbl_inter, 0, "A2232 serial VBL", a2232_driver_ID);
+	return 0;
+}
+
+static void __exit a2232board_exit(void)
+{
+	int i;
+
+	for (i = 0; i < nr_a2232; i++) {
+		zorro_release_device(zd_a2232[i]);
+	}
+
+	tty_unregister_driver(a2232_driver);
+	put_tty_driver(a2232_driver);
+	free_irq(IRQ_AMIGA_VERTB, a2232_driver_ID);
+}
+
+module_init(a2232board_init);
+module_exit(a2232board_exit);
+
+MODULE_AUTHOR("Enver Haase");
+MODULE_DESCRIPTION("Amiga A2232 multi-serial board driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/ser_a2232.h b/drivers/char/ser_a2232.h
new file mode 100644
index 0000000..bc09eb9
--- /dev/null
+++ b/drivers/char/ser_a2232.h
@@ -0,0 +1,202 @@
+/* drivers/char/ser_a2232.h */
+
+/* $Id: ser_a2232.h,v 0.4 2000/01/25 12:00:00 ehaase Exp $ */
+
+/* Linux serial driver for the Amiga A2232 board */
+
+/* This driver is MAINTAINED. Before applying any changes, please contact
+ * the author.
+ */
+   
+/* Copyright (c) 2000-2001 Enver Haase    <ehaase@inf.fu-berlin.de>
+ *                   alias The A2232 driver project <A2232@gmx.net>
+ * All rights reserved.
+ *
+ *   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.
+ *  
+ */
+
+#ifndef _SER_A2232_H_
+#define _SER_A2232_H_
+
+/*
+	How many boards are to be supported at maximum;
+	"up to five A2232 Multiport Serial Cards may be installed in a
+	single Amiga 2000" states the A2232 User's Guide. If you have
+	more slots available, you might want to change the value below.
+*/
+#define MAX_A2232_BOARDS 5
+
+#ifndef A2232_NORMAL_MAJOR
+/* This allows overriding on the compiler commandline, or in a "major.h" 
+   include or something like that */
+#define A2232_NORMAL_MAJOR  224	/* /dev/ttyY* */
+#define A2232_CALLOUT_MAJOR 225	/* /dev/cuy*  */
+#endif
+
+/* Some magic is always good - Who knows :) */
+#define A2232_MAGIC 0x000a2232
+
+/* A2232 port structure to keep track of the
+   status of every single line used */
+struct a2232_port{
+	struct gs_port gs;
+	unsigned int which_a2232;
+	unsigned int which_port_on_a2232;
+	short disable_rx;
+	short throttle_input;
+	short cd_status;
+};
+
+#define	NUMLINES		7	/* number of lines per board */
+#define	A2232_IOBUFLEN		256	/* number of bytes per buffer */
+#define	A2232_IOBUFLENMASK	0xff	/* mask for maximum number of bytes */
+
+
+#define	A2232_UNKNOWN	0	/* crystal not known */
+#define	A2232_NORMAL	1	/* normal A2232 (1.8432 MHz oscillator) */
+#define	A2232_TURBO	2	/* turbo A2232 (3.6864 MHz oscillator) */
+
+
+struct a2232common {
+	char   Crystal;	/* normal (1) or turbo (2) board? */
+	u_char Pad_a;
+	u_char TimerH;	/* timer value after speed check */
+	u_char TimerL;
+	u_char CDHead;	/* head pointer for CD message queue */
+	u_char CDTail;	/* tail pointer for CD message queue */
+	u_char CDStatus;
+	u_char Pad_b;
+};
+
+struct a2232status {
+	u_char InHead;		/* input queue head */
+	u_char InTail;		/* input queue tail */
+	u_char OutDisable;	/* disables output */
+	u_char OutHead;		/* output queue head */
+	u_char OutTail;		/* output queue tail */
+	u_char OutCtrl;		/* soft flow control character to send */
+	u_char OutFlush;	/* flushes output buffer */
+	u_char Setup;		/* causes reconfiguration */
+	u_char Param;		/* parameter byte - see A2232PARAM */
+	u_char Command;		/* command byte - see A2232CMD */
+	u_char SoftFlow;	/* enables xon/xoff flow control */
+	/* private 65EC02 fields: */
+	u_char XonOff;		/* stores XON/XOFF enable/disable */
+};
+
+#define	A2232_MEMPAD1	\
+	(0x0200 - NUMLINES * sizeof(struct a2232status)	-	\
+	sizeof(struct a2232common))
+#define	A2232_MEMPAD2	(0x2000 - NUMLINES * A2232_IOBUFLEN - A2232_IOBUFLEN)
+
+struct a2232memory {
+	struct a2232status Status[NUMLINES];	/* 0x0000-0x006f status areas */
+	struct a2232common Common;		/* 0x0070-0x0077 common flags */
+	u_char Dummy1[A2232_MEMPAD1];		/* 0x00XX-0x01ff */
+	u_char OutBuf[NUMLINES][A2232_IOBUFLEN];/* 0x0200-0x08ff output bufs */
+	u_char InBuf[NUMLINES][A2232_IOBUFLEN];	/* 0x0900-0x0fff input bufs */
+	u_char InCtl[NUMLINES][A2232_IOBUFLEN];	/* 0x1000-0x16ff control data */
+	u_char CDBuf[A2232_IOBUFLEN];		/* 0x1700-0x17ff CD event buffer */
+	u_char Dummy2[A2232_MEMPAD2];		/* 0x1800-0x2fff */
+	u_char Code[0x1000];			/* 0x3000-0x3fff code area */
+	u_short InterruptAck;			/* 0x4000        intr ack */
+	u_char Dummy3[0x3ffe];			/* 0x4002-0x7fff */
+	u_short Enable6502Reset;		/* 0x8000 Stop board, */
+						/*  6502 RESET line held low */
+	u_char Dummy4[0x3ffe];			/* 0x8002-0xbfff */
+	u_short ResetBoard;			/* 0xc000 reset board & run, */
+						/*  6502 RESET line held high */
+};
+
+#undef A2232_MEMPAD1
+#undef A2232_MEMPAD2
+
+#define	A2232INCTL_CHAR		0	/* corresponding byte in InBuf is a character */
+#define	A2232INCTL_EVENT	1	/* corresponding byte in InBuf is an event */
+
+#define	A2232EVENT_Break	1	/* break set */
+#define	A2232EVENT_CarrierOn	2	/* carrier raised */
+#define	A2232EVENT_CarrierOff	3	/* carrier dropped */
+#define A2232EVENT_Sync		4	/* don't know, defined in 2232.ax */
+
+#define	A2232CMD_Enable		0x1	/* enable/DTR bit */
+#define	A2232CMD_Close		0x2	/* close the device */
+#define	A2232CMD_Open		0xb	/* open the device */
+#define	A2232CMD_CMask		0xf	/* command mask */
+#define	A2232CMD_RTSOff		0x0  	/* turn off RTS */
+#define	A2232CMD_RTSOn		0x8	/* turn on RTS */
+#define	A2232CMD_Break		0xd	/* transmit a break */
+#define	A2232CMD_RTSMask	0xc	/* mask for RTS stuff */
+#define	A2232CMD_NoParity	0x00	/* don't use parity */
+#define	A2232CMD_OddParity	0x20	/* odd parity */
+#define	A2232CMD_EvenParity	0x60	/* even parity */
+#define	A2232CMD_ParityMask	0xe0	/* parity mask */
+
+#define	A2232PARAM_B115200	0x0	/* baud rates */
+#define	A2232PARAM_B50		0x1
+#define	A2232PARAM_B75		0x2
+#define	A2232PARAM_B110		0x3
+#define	A2232PARAM_B134		0x4
+#define	A2232PARAM_B150		0x5
+#define	A2232PARAM_B300		0x6
+#define	A2232PARAM_B600		0x7
+#define	A2232PARAM_B1200	0x8
+#define	A2232PARAM_B1800	0x9
+#define	A2232PARAM_B2400	0xa
+#define	A2232PARAM_B3600	0xb
+#define	A2232PARAM_B4800	0xc
+#define	A2232PARAM_B7200	0xd
+#define	A2232PARAM_B9600	0xe
+#define	A2232PARAM_B19200	0xf
+#define	A2232PARAM_BaudMask	0xf	/* baud rate mask */
+#define	A2232PARAM_RcvBaud	0x10	/* enable receive baud rate */
+#define	A2232PARAM_8Bit		0x00	/* numbers of bits */
+#define	A2232PARAM_7Bit		0x20
+#define	A2232PARAM_6Bit		0x40
+#define	A2232PARAM_5Bit		0x60
+#define	A2232PARAM_BitMask	0x60	/* numbers of bits mask */
+
+
+/* Standard speeds tables, -1 means unavailable, -2 means 0 baud: switch off line */
+#define A2232_BAUD_TABLE_NOAVAIL -1
+#define A2232_BAUD_TABLE_NUM_RATES (18)
+static int a2232_baud_table[A2232_BAUD_TABLE_NUM_RATES*3] = {
+	//Baud	//Normal			//Turbo
+	50,	A2232PARAM_B50,			A2232_BAUD_TABLE_NOAVAIL,
+	75,	A2232PARAM_B75,			A2232_BAUD_TABLE_NOAVAIL,
+	110,	A2232PARAM_B110,		A2232_BAUD_TABLE_NOAVAIL,
+	134,	A2232PARAM_B134,		A2232_BAUD_TABLE_NOAVAIL,
+	150,	A2232PARAM_B150,		A2232PARAM_B75,
+	200,	A2232_BAUD_TABLE_NOAVAIL,	A2232_BAUD_TABLE_NOAVAIL,
+	300,	A2232PARAM_B300,		A2232PARAM_B150,
+	600,	A2232PARAM_B600,		A2232PARAM_B300,
+	1200,	A2232PARAM_B1200,		A2232PARAM_B600,
+	1800,	A2232PARAM_B1800,		A2232_BAUD_TABLE_NOAVAIL,
+	2400,	A2232PARAM_B2400,		A2232PARAM_B1200,
+	4800,	A2232PARAM_B4800,		A2232PARAM_B2400,
+	9600,	A2232PARAM_B9600,		A2232PARAM_B4800,
+	19200,	A2232PARAM_B19200,		A2232PARAM_B9600,
+	38400,	A2232_BAUD_TABLE_NOAVAIL,	A2232PARAM_B19200,
+	57600,	A2232_BAUD_TABLE_NOAVAIL,	A2232_BAUD_TABLE_NOAVAIL,
+#ifdef A2232_SPEEDHACK
+	115200,	A2232PARAM_B115200,		A2232_BAUD_TABLE_NOAVAIL,
+	230400,	A2232_BAUD_TABLE_NOAVAIL,	A2232PARAM_B115200
+#else
+	115200,	A2232_BAUD_TABLE_NOAVAIL,	A2232_BAUD_TABLE_NOAVAIL,
+	230400,	A2232_BAUD_TABLE_NOAVAIL,	A2232_BAUD_TABLE_NOAVAIL
+#endif
+};
+#endif
diff --git a/drivers/char/ser_a2232fw.ax b/drivers/char/ser_a2232fw.ax
new file mode 100644
index 0000000..7364380
--- /dev/null
+++ b/drivers/char/ser_a2232fw.ax
@@ -0,0 +1,529 @@
+;.lib "axm"
+;
+;begin
+;title "A2232 serial board driver"
+;
+;set modules "2232"
+;set executable "2232.bin"
+;
+;;;;set nolink
+;
+;set temporary directory "t:"
+;
+;set assembly options "-m6502 -l60:t:list"
+;set link options "bin"; loadadr"
+;;;bin2c 2232.bin msc6502.h msc6502code
+;end
+;
+;
+; ### Commodore A2232 serial board driver for NetBSD by JM v1.3 ###
+;
+; - Created 950501 by JM -
+;
+;
+; Serial board driver software.
+;
+;
+% Copyright (c) 1995 Jukka Marin <jmarin@jmp.fi>.
+% All rights reserved.
+%
+% Redistribution and use in source and binary forms, with or without
+% modification, are permitted provided that the following conditions
+% are met:
+% 1. Redistributions of source code must retain the above copyright
+%    notice, and the entire permission notice in its entirety,
+%    including the disclaimer of warranties.
+% 2. Redistributions in binary form must reproduce the above copyright
+%    notice, this list of conditions and the following disclaimer in the
+%    documentation and/or other materials provided with the distribution.
+% 3. The name of the author may not be used to endorse or promote
+%    products derived from this software without specific prior
+%    written permission.
+%
+% ALTERNATIVELY, this product may be distributed under the terms of
+% the GNU General Public License, in which case the provisions of the
+% GPL are required INSTEAD OF the above restrictions.  (This clause is
+% necessary due to a potential bad interaction between the GPL and
+% the restrictions contained in a BSD-style copyright.)
+%
+% THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
+% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+% DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+% INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+% (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+% SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+% STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+% OF THE POSSIBILITY OF SUCH DAMAGE.
+;
+;
+; Bugs:
+;
+; - Can't send a break yet
+;
+;
+;
+; Edited:
+;
+; - 950501 by JM -> v0.1	- Created this file.
+; - 951029 by JM -> v1.3	- Carrier Detect events now queued in a separate
+;				  queue.
+;
+;
+
+
+CODE		equ	$3800		; start address for program code
+
+
+CTL_CHAR	equ	$00		; byte in ibuf is a character
+CTL_EVENT	equ	$01		; byte in ibuf is an event
+
+EVENT_BREAK	equ	$01
+EVENT_CDON	equ	$02
+EVENT_CDOFF	equ	$03
+EVENT_SYNC	equ	$04
+
+XON		equ	$11
+XOFF		equ	$13
+
+
+VARBASE		macro	*starting_address	; was VARINIT
+_varbase	set	\1
+		endm
+
+VARDEF		macro	*name space_needs
+\1		equ	_varbase
+_varbase	set	_varbase+\2
+		endm
+
+
+stz		macro	* address
+		 db	$64,\1
+		endm
+
+stzax		macro	* address
+		 db	$9e,<\1,>\1
+		endm
+
+
+biti		macro	* immediate value
+		db	$89,\1
+		endm
+
+smb0		macro	* address
+		db	$87,\1
+		endm
+smb1		macro	* address
+		db	$97,\1
+		endm
+smb2		macro	* address
+		db	$a7,\1
+		endm
+smb3		macro	* address
+		db	$b7,\1
+		endm
+smb4		macro	* address
+		db	$c7,\1
+		endm
+smb5		macro	* address
+		db	$d7,\1
+		endm
+smb6		macro	* address
+		db	$e7,\1
+		endm
+smb7		macro	* address
+		db	$f7,\1
+		endm
+
+
+
+;-----------------------------------------------------------------------;
+;									;
+; stuff common for all ports, non-critical (run once / loop)		;
+;									;
+DO_SLOW		macro	* port_number					;
+		.local			;				;
+		lda	CIA+C_PA	; check all CD inputs		;
+		cmp	CommonCDo	; changed from previous accptd?	;
+		beq	=over		; nope, do nothing else here	;
+					;				;
+		cmp	CommonCDb	; bouncing?			;
+		beq	=nobounce	; nope ->			;
+					;				;
+		sta	CommonCDb	; save current state		;
+		lda	#64		; reinitialize counter		;
+		sta	CommonCDc	;				;
+		jmp	=over		; skip CD save			;
+					;				;
+=nobounce	dec	CommonCDc	; no, decrement bounce counter	;
+		bpl	=over		; not done yet, so skip CD save	;
+					;				;
+=saveCD		ldx	CDHead		; get write index		;
+		sta	cdbuf,x		; save status in buffer		;
+		inx			;				;
+		cpx	CDTail		; buffer full?			;
+		.if	ne		; no: preserve status:		;
+		 stx	CDHead		; update index in RAM		;
+		 sta	CommonCDo	; save state for the next check	;
+		.end			;				;
+=over		.end	local						;
+		endm							;
+									;
+;-----------------------------------------------------------------------;
+
+
+; port specific stuff (no data transfer)
+
+DO_PORT		macro	* port_number
+		.local			;				;
+		lda	SetUp\1		; reconfiguration request?	;
+		.if	ne		; yes:				;
+		 lda	SoftFlow\1	; get XON/XOFF flag		;
+		 sta	XonOff\1	; save it			;
+		 lda	Param\1		; get parameter			;
+		 ora	#%00010000	; use baud generator for Rx	;
+		 sta	ACIA\1+A_CTRL	; store in control register	;
+		 stz	OutDisable\1	; enable transmit output	;
+		 stz	SetUp\1		; no reconfiguration no more	;
+		.end			;				;
+					;				;
+		lda	InHead\1	; get write index		;
+		sbc	InTail\1	; buffer full soon?		;
+		cmp	#200		; 200 chars or more in buffer?	;
+		lda	Command\1	; get Command reg value		;
+		and	#%11110011	; turn RTS OFF by default	;
+		.if	cc		; still room in buffer:		;
+		 ora	#%00001000	; turn RTS ON			;
+		.end			;				;
+		sta	ACIA\1+A_CMD	; set/clear RTS			;
+					;				;
+		lda	OutFlush\1	; request to flush output buffer;
+		.if	ne		; yessh!			;
+		 lda	OutHead\1	; get head			;
+		 sta	OutTail\1	; save as tail			;
+		 stz	OutDisable\1	; enable transmit output	;
+		 stz	OutFlush\1	; clear request			;
+		.end
+		.end	local
+		endm
+
+
+DO_DATA		macro	* port number
+		.local
+		lda	ACIA\1+A_SR	; read ACIA status register	;
+		biti	[1<<3]		; something received?		;
+		.if	ne		; yes:				;
+		 biti	[1<<1]		; framing error?		;
+		 .if	ne		; yes:				;
+		  lda	ACIA\1+A_DATA	; read received character	;
+		  bne	=SEND		; not break -> ignore it	;
+		  ldx	InHead\1	; get write pointer		;
+		  lda	#CTL_EVENT	; get type of byte		;
+		  sta	ictl\1,x	; save it in InCtl buffer	;
+		  lda	#EVENT_BREAK	; event code			;
+		  sta	ibuf\1,x	; save it as well		;
+		  inx			;				;
+		  cpx	InTail\1	; still room in buffer?		;
+		  .if	ne		; absolutely:			;
+		   stx	InHead\1	; update index in memory	;
+		  .end			;				;
+		  jmp	=SEND		; go check if anything to send	;
+		 .end			;				;
+		 			; normal char received:		;
+		 ldx	InHead\1	; get write index		;
+		 lda	ACIA\1+A_DATA	; read received character	;
+		 sta	ibuf\1,x	; save char in buffer		;
+		 stzax	ictl\1		; set type to CTL_CHAR		;
+		 inx			;				;
+		 cpx	InTail\1	; buffer full?			;
+		 .if	ne		; no: preserve character:	;
+		  stx	InHead\1	; update index in RAM		;
+		 .end			;				;
+		 and	#$7f		; mask off parity if any	;
+		 cmp	#XOFF		; XOFF from remote host?	;
+		 .if	eq		; yes:				;
+		  lda	XonOff\1	; if XON/XOFF handshaking..	;
+		  sta	OutDisable\1	; ..disable transmitter		;
+		 .end			;				;
+		.end			;				;
+					;				;
+					; BUFFER FULL CHECK WAS HERE	;
+					;				;
+=SEND		lda	ACIA\1+A_SR	; transmit register empty?	;
+		and	#[1<<4]		;				;
+		.if	ne		; yes:				;
+		 ldx	OutCtrl\1	; sending out XON/XOFF?		;
+		 .if	ne		; yes:				;
+		  lda	CIA+C_PB	; check CTS signal		;
+		  and	#[1<<\1]	; (for this port only)		;
+		  bne	=DONE		; not allowed to send -> done	;
+		  stx	ACIA\1+A_DATA	; transmit control char		;
+		  stz	OutCtrl\1	; clear flag			;
+		  jmp	=DONE		; and we're done		;
+		 .end			;				;
+					;				;
+		 ldx	OutTail\1	; anything to transmit?		;
+		 cpx	OutHead\1	;				;
+		 .if	ne		; yes:				;
+		  lda	OutDisable\1	; allowed to transmit?		;
+		  .if	eq		; yes:				;
+		   lda	CIA+C_PB	; check CTS signal		;
+		   and	#[1<<\1]	; (for this port only)		;
+		   bne	=DONE		; not allowed to send -> done	;
+		   lda	obuf\1,x	; get a char from buffer	;
+		   sta	ACIA\1+A_DATA	; send it away			;
+		   inc	OutTail\1	; update read index		;
+		  .end			;				;
+		 .end			;				;
+		.end			;				;
+=DONE		.end	local
+		endm
+
+
+
+PORTVAR		macro	* port number
+		VARDEF	InHead\1 1
+		VARDEF	InTail\1 1
+		VARDEF	OutDisable\1 1
+		VARDEF	OutHead\1 1
+		VARDEF	OutTail\1 1
+		VARDEF	OutCtrl\1 1
+		VARDEF	OutFlush\1 1
+		VARDEF	SetUp\1 1
+		VARDEF	Param\1 1
+		VARDEF	Command\1 1
+		VARDEF	SoftFlow\1 1
+		; private:
+		VARDEF	XonOff\1 1
+		endm
+
+
+ VARBASE 0	; start variables at address $0000
+ PORTVAR 0	; define variables for port 0
+ PORTVAR 1	; define variables for port 1
+ PORTVAR 2	; define variables for port 2
+ PORTVAR 3	; define variables for port 3
+ PORTVAR 4	; define variables for port 4
+ PORTVAR 5	; define variables for port 5
+ PORTVAR 6	; define variables for port 6
+
+
+
+ VARDEF	Crystal	1	; 0 = unknown, 1 = normal, 2 = turbo
+ VARDEF	Pad_a	1
+ VARDEF	TimerH	1
+ VARDEF	TimerL	1
+ VARDEF	CDHead	1
+ VARDEF	CDTail	1
+ VARDEF	CDStatus 1
+ VARDEF	Pad_b	1
+
+ VARDEF	CommonCDo 1	; for carrier detect optimization
+ VARDEF	CommonCDc 1	; for carrier detect debouncing
+ VARDEF	CommonCDb 1	; for carrier detect debouncing
+
+
+ VARBASE $0200
+ VARDEF	obuf0 256	; output data (characters only)
+ VARDEF	obuf1 256
+ VARDEF	obuf2 256
+ VARDEF	obuf3 256
+ VARDEF	obuf4 256
+ VARDEF	obuf5 256
+ VARDEF	obuf6 256
+
+ VARDEF	ibuf0 256	; input data (characters, events etc - see ictl)
+ VARDEF	ibuf1 256
+ VARDEF	ibuf2 256
+ VARDEF	ibuf3 256
+ VARDEF	ibuf4 256
+ VARDEF	ibuf5 256
+ VARDEF	ibuf6 256
+
+ VARDEF	ictl0 256	; input control information (type of data in ibuf)
+ VARDEF	ictl1 256
+ VARDEF	ictl2 256
+ VARDEF	ictl3 256
+ VARDEF	ictl4 256
+ VARDEF	ictl5 256
+ VARDEF	ictl6 256
+
+ VARDEF	cdbuf 256	; CD event queue
+
+
+ACIA0		equ	$4400
+ACIA1		equ	$4c00
+ACIA2		equ	$5400
+ACIA3		equ	$5c00
+ACIA4		equ	$6400
+ACIA5		equ	$6c00
+ACIA6		equ	$7400
+
+A_DATA		equ	$00
+A_SR		equ	$02
+A_CMD		equ	$04
+A_CTRL		equ	$06
+;  00	write transmit data	read received data
+;  02	reset ACIA		read status register
+;  04	write command register	read command register
+;  06	write control register	read control register
+
+CIA		equ	$7c00		; 8520 CIA
+C_PA		equ	$00		; port A data register
+C_PB		equ	$02		; port B data register
+C_DDRA		equ	$04		; data direction register for port A
+C_DDRB		equ	$06		; data direction register for port B
+C_TAL		equ	$08		; timer A
+C_TAH		equ	$0a
+C_TBL		equ	$0c		; timer B
+C_TBH		equ	$0e
+C_TODL		equ	$10		; TOD LSB
+C_TODM		equ	$12		; TOD middle byte
+C_TODH		equ	$14		; TOD MSB
+C_DATA		equ	$18		; serial data register
+C_INTCTRL	equ	$1a		; interrupt control register
+C_CTRLA		equ	$1c		; control register A
+C_CTRLB		equ	$1e		; control register B
+
+
+
+
+
+		section	main,code,CODE-2
+
+		db	>CODE,<CODE
+
+;-----------------------------------------------------------------------;
+; here's the initialization code:					;
+;									;
+R_RESET		ldx	#$ff						;
+		txs			; initialize stack pointer	;
+		cld			; in case a 6502 is used...	;
+		ldx	#0		;				;
+		lda	#0		;				;
+		ldy	#Crystal	; this many bytes to clear	;
+clr_loop	sta	0,x		; clear zero page variables	;
+		inx			;				;
+		dey			;				;
+		bne	clr_loop	;				;
+					;				;
+		stz	CommonCDo	; force CD test at boot		;
+		stz	CommonCDb	;				;
+		stz	CDHead		; clear queue			;
+		stz	CDTail		;				;
+					;				;
+		lda	#0		;				;
+		sta	Pad_a		;				;
+		lda	#170		; test cmp			;
+		cmp	#100		;				;
+		.if	cs		;				;
+		 inc	Pad_a		; C was set			;
+		.end			;				;
+									;
+;-----------------------------------------------------------------------;
+; Speed check								;
+;-----------------------------------------------------------------------;
+									;
+		lda	Crystal		; speed already set?		;
+		beq	DoSpeedy	;				;
+		jmp	LOOP		; yes, skip speed test		;
+					;				;
+DoSpeedy	lda	#%10011000	; 8N1, 1200/2400 bps		;
+		sta	ACIA0+A_CTRL	;				;
+		lda	#%00001011	; enable DTR			;
+		sta	ACIA0+A_CMD	;				;
+		lda	ACIA0+A_SR	; read status register		;
+					;				;
+		lda	#%10000000	; disable all ints (unnecessary);
+		sta	CIA+C_INTCTRL	;				;
+		lda	#255		; program the timer		;
+		sta	CIA+C_TAL	;				;
+		sta	CIA+C_TAH	;				;
+					;				;
+		ldx	#0		;				;
+		stx	ACIA0+A_DATA	; transmit a zero		;
+		nop			;				;
+		nop			;				;
+		lda	ACIA0+A_SR	; read status			;
+		nop			;				;
+		nop			;				;
+		stx	ACIA0+A_DATA	; transmit a zero		;
+Speedy1		lda	ACIA0+A_SR	; read status			;
+		and	#[1<<4]		; transmit data reg empty?	;
+		beq	Speedy1		; not yet, wait more		;
+					;				;
+		lda	#%00010001	; load & start the timer	;
+		stx	ACIA0+A_DATA	; transmit one more zero	;
+		sta	CIA+C_CTRLA	;				;
+Speedy2		lda	ACIA0+A_SR	; read status			;
+		and	#[1<<4]		; transmit data reg empty?	;
+		beq	Speedy2		; not yet, wait more		;
+		stx	CIA+C_CTRLA	; stop the timer		;
+					;				;
+		lda	CIA+C_TAL	; copy timer value for 68k	;
+		sta	TimerL		;				;
+		lda	CIA+C_TAH	;				;
+		sta	TimerH		;				;
+		cmp	#$d0		; turbo or normal?		;
+		.if	cs		;				;
+		 lda	#2		; turbo! :-)			;
+		.else			;				;
+		 lda	#1		; normal :-(			;
+		.end			;				;
+		sta	Crystal		;				;
+		lda	#0		;				;
+		sta	ACIA0+A_SR	;				;
+		sta	ACIA0+A_CTRL	; reset UART			;
+		sta	ACIA0+A_CMD	;				;
+									;
+		jmp	LOOP						;
+									;
+;									;
+;-----------------------------------------------------------------------;
+;									;
+; The Real Thing:							;
+;									;
+LOOP		DO_SLOW			; do non-critical things	;
+		jsr	do_input	; check for received data
+		DO_PORT	0
+		jsr	do_input
+		DO_PORT	1
+		jsr	do_input
+		DO_PORT	2
+		jsr	do_input
+		DO_PORT	3
+		jsr	do_input
+		DO_PORT	4
+		jsr	do_input
+		DO_PORT	5
+		jsr	do_input
+		DO_PORT	6
+		jsr	do_input
+		jmp	LOOP
+
+
+do_input	DO_DATA	0
+		DO_DATA	1
+		DO_DATA	2
+		DO_DATA	3
+		DO_DATA	4
+		DO_DATA	5
+		DO_DATA	6
+		rts
+
+
+;-----------------------------------------------------------------------;
+		section	vectors,data,$3ffa
+		dw	$d0d0
+		dw	R_RESET
+		dw	$c0ce
+;-----------------------------------------------------------------------;
+
+
+
+		end
+
+
+
diff --git a/drivers/char/ser_a2232fw.h b/drivers/char/ser_a2232fw.h
new file mode 100644
index 0000000..e09a30a
--- /dev/null
+++ b/drivers/char/ser_a2232fw.h
@@ -0,0 +1,306 @@
+/* drivers/char/ser_a2232fw.h */
+
+/* $Id: ser_a2232fw.h,v 0.4 2000/01/25 12:00:00 ehaase Exp $ */
+
+/*
+ * Copyright (c) 1995 Jukka Marin <jmarin@jmp.fi>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, and the entire permission notice in its entirety,
+ *    including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ *    products derived from this software without specific prior
+ *    written permission.
+ *
+ * ALTERNATIVELY, this product may be distributed under the terms of
+ * the GNU Public License, in which case the provisions of the GPL are
+ * required INSTEAD OF the above restrictions.  (This clause is
+ * necessary due to a potential bad interaction between the GPL and
+ * the restrictions contained in a BSD-style copyright.)
+ *
+ * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+                                      
+/* This is the 65EC02 code by Jukka Marin that is executed by
+   the A2232's 65EC02 processor (base address: 0x3800)
+   Source file:	ser_a2232fw.ax
+   Version:	1.3 (951029)
+   Known Bugs:	Cannot send a break yet
+*/
+static unsigned char a2232_65EC02code[] = {
+	0x38, 0x00, 0xA2, 0xFF, 0x9A, 0xD8, 0xA2, 0x00, 
+	0xA9, 0x00, 0xA0, 0x54, 0x95, 0x00, 0xE8, 0x88, 
+	0xD0, 0xFA, 0x64, 0x5C, 0x64, 0x5E, 0x64, 0x58, 
+	0x64, 0x59, 0xA9, 0x00, 0x85, 0x55, 0xA9, 0xAA, 
+	0xC9, 0x64, 0x90, 0x02, 0xE6, 0x55, 0xA5, 0x54, 
+	0xF0, 0x03, 0x4C, 0x92, 0x38, 0xA9, 0x98, 0x8D, 
+	0x06, 0x44, 0xA9, 0x0B, 0x8D, 0x04, 0x44, 0xAD, 
+	0x02, 0x44, 0xA9, 0x80, 0x8D, 0x1A, 0x7C, 0xA9, 
+	0xFF, 0x8D, 0x08, 0x7C, 0x8D, 0x0A, 0x7C, 0xA2, 
+	0x00, 0x8E, 0x00, 0x44, 0xEA, 0xEA, 0xAD, 0x02, 
+	0x44, 0xEA, 0xEA, 0x8E, 0x00, 0x44, 0xAD, 0x02, 
+	0x44, 0x29, 0x10, 0xF0, 0xF9, 0xA9, 0x11, 0x8E, 
+	0x00, 0x44, 0x8D, 0x1C, 0x7C, 0xAD, 0x02, 0x44, 
+	0x29, 0x10, 0xF0, 0xF9, 0x8E, 0x1C, 0x7C, 0xAD, 
+	0x08, 0x7C, 0x85, 0x57, 0xAD, 0x0A, 0x7C, 0x85, 
+	0x56, 0xC9, 0xD0, 0x90, 0x05, 0xA9, 0x02, 0x4C, 
+	0x82, 0x38, 0xA9, 0x01, 0x85, 0x54, 0xA9, 0x00, 
+	0x8D, 0x02, 0x44, 0x8D, 0x06, 0x44, 0x8D, 0x04, 
+	0x44, 0x4C, 0x92, 0x38, 0xAD, 0x00, 0x7C, 0xC5, 
+	0x5C, 0xF0, 0x1F, 0xC5, 0x5E, 0xF0, 0x09, 0x85, 
+	0x5E, 0xA9, 0x40, 0x85, 0x5D, 0x4C, 0xB8, 0x38, 
+	0xC6, 0x5D, 0x10, 0x0E, 0xA6, 0x58, 0x9D, 0x00, 
+	0x17, 0xE8, 0xE4, 0x59, 0xF0, 0x04, 0x86, 0x58, 
+	0x85, 0x5C, 0x20, 0x23, 0x3A, 0xA5, 0x07, 0xF0, 
+	0x0F, 0xA5, 0x0A, 0x85, 0x0B, 0xA5, 0x08, 0x09, 
+	0x10, 0x8D, 0x06, 0x44, 0x64, 0x02, 0x64, 0x07, 
+	0xA5, 0x00, 0xE5, 0x01, 0xC9, 0xC8, 0xA5, 0x09, 
+	0x29, 0xF3, 0xB0, 0x02, 0x09, 0x08, 0x8D, 0x04, 
+	0x44, 0xA5, 0x06, 0xF0, 0x08, 0xA5, 0x03, 0x85, 
+	0x04, 0x64, 0x02, 0x64, 0x06, 0x20, 0x23, 0x3A, 
+	0xA5, 0x13, 0xF0, 0x0F, 0xA5, 0x16, 0x85, 0x17, 
+	0xA5, 0x14, 0x09, 0x10, 0x8D, 0x06, 0x4C, 0x64, 
+	0x0E, 0x64, 0x13, 0xA5, 0x0C, 0xE5, 0x0D, 0xC9, 
+	0xC8, 0xA5, 0x15, 0x29, 0xF3, 0xB0, 0x02, 0x09, 
+	0x08, 0x8D, 0x04, 0x4C, 0xA5, 0x12, 0xF0, 0x08, 
+	0xA5, 0x0F, 0x85, 0x10, 0x64, 0x0E, 0x64, 0x12, 
+	0x20, 0x23, 0x3A, 0xA5, 0x1F, 0xF0, 0x0F, 0xA5, 
+	0x22, 0x85, 0x23, 0xA5, 0x20, 0x09, 0x10, 0x8D, 
+	0x06, 0x54, 0x64, 0x1A, 0x64, 0x1F, 0xA5, 0x18, 
+	0xE5, 0x19, 0xC9, 0xC8, 0xA5, 0x21, 0x29, 0xF3, 
+	0xB0, 0x02, 0x09, 0x08, 0x8D, 0x04, 0x54, 0xA5, 
+	0x1E, 0xF0, 0x08, 0xA5, 0x1B, 0x85, 0x1C, 0x64, 
+	0x1A, 0x64, 0x1E, 0x20, 0x23, 0x3A, 0xA5, 0x2B, 
+	0xF0, 0x0F, 0xA5, 0x2E, 0x85, 0x2F, 0xA5, 0x2C, 
+	0x09, 0x10, 0x8D, 0x06, 0x5C, 0x64, 0x26, 0x64, 
+	0x2B, 0xA5, 0x24, 0xE5, 0x25, 0xC9, 0xC8, 0xA5, 
+	0x2D, 0x29, 0xF3, 0xB0, 0x02, 0x09, 0x08, 0x8D, 
+	0x04, 0x5C, 0xA5, 0x2A, 0xF0, 0x08, 0xA5, 0x27, 
+	0x85, 0x28, 0x64, 0x26, 0x64, 0x2A, 0x20, 0x23, 
+	0x3A, 0xA5, 0x37, 0xF0, 0x0F, 0xA5, 0x3A, 0x85, 
+	0x3B, 0xA5, 0x38, 0x09, 0x10, 0x8D, 0x06, 0x64, 
+	0x64, 0x32, 0x64, 0x37, 0xA5, 0x30, 0xE5, 0x31, 
+	0xC9, 0xC8, 0xA5, 0x39, 0x29, 0xF3, 0xB0, 0x02, 
+	0x09, 0x08, 0x8D, 0x04, 0x64, 0xA5, 0x36, 0xF0, 
+	0x08, 0xA5, 0x33, 0x85, 0x34, 0x64, 0x32, 0x64, 
+	0x36, 0x20, 0x23, 0x3A, 0xA5, 0x43, 0xF0, 0x0F, 
+	0xA5, 0x46, 0x85, 0x47, 0xA5, 0x44, 0x09, 0x10, 
+	0x8D, 0x06, 0x6C, 0x64, 0x3E, 0x64, 0x43, 0xA5, 
+	0x3C, 0xE5, 0x3D, 0xC9, 0xC8, 0xA5, 0x45, 0x29, 
+	0xF3, 0xB0, 0x02, 0x09, 0x08, 0x8D, 0x04, 0x6C, 
+	0xA5, 0x42, 0xF0, 0x08, 0xA5, 0x3F, 0x85, 0x40, 
+	0x64, 0x3E, 0x64, 0x42, 0x20, 0x23, 0x3A, 0xA5, 
+	0x4F, 0xF0, 0x0F, 0xA5, 0x52, 0x85, 0x53, 0xA5, 
+	0x50, 0x09, 0x10, 0x8D, 0x06, 0x74, 0x64, 0x4A, 
+	0x64, 0x4F, 0xA5, 0x48, 0xE5, 0x49, 0xC9, 0xC8, 
+	0xA5, 0x51, 0x29, 0xF3, 0xB0, 0x02, 0x09, 0x08, 
+	0x8D, 0x04, 0x74, 0xA5, 0x4E, 0xF0, 0x08, 0xA5, 
+	0x4B, 0x85, 0x4C, 0x64, 0x4A, 0x64, 0x4E, 0x20, 
+	0x23, 0x3A, 0x4C, 0x92, 0x38, 0xAD, 0x02, 0x44, 
+	0x89, 0x08, 0xF0, 0x3B, 0x89, 0x02, 0xF0, 0x1B, 
+	0xAD, 0x00, 0x44, 0xD0, 0x32, 0xA6, 0x00, 0xA9, 
+	0x01, 0x9D, 0x00, 0x10, 0xA9, 0x01, 0x9D, 0x00, 
+	0x09, 0xE8, 0xE4, 0x01, 0xF0, 0x02, 0x86, 0x00, 
+	0x4C, 0x65, 0x3A, 0xA6, 0x00, 0xAD, 0x00, 0x44, 
+	0x9D, 0x00, 0x09, 0x9E, 0x00, 0x10, 0xE8, 0xE4, 
+	0x01, 0xF0, 0x02, 0x86, 0x00, 0x29, 0x7F, 0xC9, 
+	0x13, 0xD0, 0x04, 0xA5, 0x0B, 0x85, 0x02, 0xAD, 
+	0x02, 0x44, 0x29, 0x10, 0xF0, 0x2C, 0xA6, 0x05, 
+	0xF0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x01, 0xD0, 
+	0x21, 0x8E, 0x00, 0x44, 0x64, 0x05, 0x4C, 0x98, 
+	0x3A, 0xA6, 0x04, 0xE4, 0x03, 0xF0, 0x13, 0xA5, 
+	0x02, 0xD0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x01, 
+	0xD0, 0x08, 0xBD, 0x00, 0x02, 0x8D, 0x00, 0x44, 
+	0xE6, 0x04, 0xAD, 0x02, 0x4C, 0x89, 0x08, 0xF0, 
+	0x3B, 0x89, 0x02, 0xF0, 0x1B, 0xAD, 0x00, 0x4C, 
+	0xD0, 0x32, 0xA6, 0x0C, 0xA9, 0x01, 0x9D, 0x00, 
+	0x11, 0xA9, 0x01, 0x9D, 0x00, 0x0A, 0xE8, 0xE4, 
+	0x0D, 0xF0, 0x02, 0x86, 0x0C, 0x4C, 0xDA, 0x3A, 
+	0xA6, 0x0C, 0xAD, 0x00, 0x4C, 0x9D, 0x00, 0x0A, 
+	0x9E, 0x00, 0x11, 0xE8, 0xE4, 0x0D, 0xF0, 0x02, 
+	0x86, 0x0C, 0x29, 0x7F, 0xC9, 0x13, 0xD0, 0x04, 
+	0xA5, 0x17, 0x85, 0x0E, 0xAD, 0x02, 0x4C, 0x29, 
+	0x10, 0xF0, 0x2C, 0xA6, 0x11, 0xF0, 0x0F, 0xAD, 
+	0x02, 0x7C, 0x29, 0x02, 0xD0, 0x21, 0x8E, 0x00, 
+	0x4C, 0x64, 0x11, 0x4C, 0x0D, 0x3B, 0xA6, 0x10, 
+	0xE4, 0x0F, 0xF0, 0x13, 0xA5, 0x0E, 0xD0, 0x0F, 
+	0xAD, 0x02, 0x7C, 0x29, 0x02, 0xD0, 0x08, 0xBD, 
+	0x00, 0x03, 0x8D, 0x00, 0x4C, 0xE6, 0x10, 0xAD, 
+	0x02, 0x54, 0x89, 0x08, 0xF0, 0x3B, 0x89, 0x02, 
+	0xF0, 0x1B, 0xAD, 0x00, 0x54, 0xD0, 0x32, 0xA6, 
+	0x18, 0xA9, 0x01, 0x9D, 0x00, 0x12, 0xA9, 0x01, 
+	0x9D, 0x00, 0x0B, 0xE8, 0xE4, 0x19, 0xF0, 0x02, 
+	0x86, 0x18, 0x4C, 0x4F, 0x3B, 0xA6, 0x18, 0xAD, 
+	0x00, 0x54, 0x9D, 0x00, 0x0B, 0x9E, 0x00, 0x12, 
+	0xE8, 0xE4, 0x19, 0xF0, 0x02, 0x86, 0x18, 0x29, 
+	0x7F, 0xC9, 0x13, 0xD0, 0x04, 0xA5, 0x23, 0x85, 
+	0x1A, 0xAD, 0x02, 0x54, 0x29, 0x10, 0xF0, 0x2C, 
+	0xA6, 0x1D, 0xF0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 
+	0x04, 0xD0, 0x21, 0x8E, 0x00, 0x54, 0x64, 0x1D, 
+	0x4C, 0x82, 0x3B, 0xA6, 0x1C, 0xE4, 0x1B, 0xF0, 
+	0x13, 0xA5, 0x1A, 0xD0, 0x0F, 0xAD, 0x02, 0x7C, 
+	0x29, 0x04, 0xD0, 0x08, 0xBD, 0x00, 0x04, 0x8D, 
+	0x00, 0x54, 0xE6, 0x1C, 0xAD, 0x02, 0x5C, 0x89, 
+	0x08, 0xF0, 0x3B, 0x89, 0x02, 0xF0, 0x1B, 0xAD, 
+	0x00, 0x5C, 0xD0, 0x32, 0xA6, 0x24, 0xA9, 0x01, 
+	0x9D, 0x00, 0x13, 0xA9, 0x01, 0x9D, 0x00, 0x0C, 
+	0xE8, 0xE4, 0x25, 0xF0, 0x02, 0x86, 0x24, 0x4C, 
+	0xC4, 0x3B, 0xA6, 0x24, 0xAD, 0x00, 0x5C, 0x9D, 
+	0x00, 0x0C, 0x9E, 0x00, 0x13, 0xE8, 0xE4, 0x25, 
+	0xF0, 0x02, 0x86, 0x24, 0x29, 0x7F, 0xC9, 0x13, 
+	0xD0, 0x04, 0xA5, 0x2F, 0x85, 0x26, 0xAD, 0x02, 
+	0x5C, 0x29, 0x10, 0xF0, 0x2C, 0xA6, 0x29, 0xF0, 
+	0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x08, 0xD0, 0x21, 
+	0x8E, 0x00, 0x5C, 0x64, 0x29, 0x4C, 0xF7, 0x3B, 
+	0xA6, 0x28, 0xE4, 0x27, 0xF0, 0x13, 0xA5, 0x26, 
+	0xD0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x08, 0xD0, 
+	0x08, 0xBD, 0x00, 0x05, 0x8D, 0x00, 0x5C, 0xE6, 
+	0x28, 0xAD, 0x02, 0x64, 0x89, 0x08, 0xF0, 0x3B, 
+	0x89, 0x02, 0xF0, 0x1B, 0xAD, 0x00, 0x64, 0xD0, 
+	0x32, 0xA6, 0x30, 0xA9, 0x01, 0x9D, 0x00, 0x14, 
+	0xA9, 0x01, 0x9D, 0x00, 0x0D, 0xE8, 0xE4, 0x31, 
+	0xF0, 0x02, 0x86, 0x30, 0x4C, 0x39, 0x3C, 0xA6, 
+	0x30, 0xAD, 0x00, 0x64, 0x9D, 0x00, 0x0D, 0x9E, 
+	0x00, 0x14, 0xE8, 0xE4, 0x31, 0xF0, 0x02, 0x86, 
+	0x30, 0x29, 0x7F, 0xC9, 0x13, 0xD0, 0x04, 0xA5, 
+	0x3B, 0x85, 0x32, 0xAD, 0x02, 0x64, 0x29, 0x10, 
+	0xF0, 0x2C, 0xA6, 0x35, 0xF0, 0x0F, 0xAD, 0x02, 
+	0x7C, 0x29, 0x10, 0xD0, 0x21, 0x8E, 0x00, 0x64, 
+	0x64, 0x35, 0x4C, 0x6C, 0x3C, 0xA6, 0x34, 0xE4, 
+	0x33, 0xF0, 0x13, 0xA5, 0x32, 0xD0, 0x0F, 0xAD, 
+	0x02, 0x7C, 0x29, 0x10, 0xD0, 0x08, 0xBD, 0x00, 
+	0x06, 0x8D, 0x00, 0x64, 0xE6, 0x34, 0xAD, 0x02, 
+	0x6C, 0x89, 0x08, 0xF0, 0x3B, 0x89, 0x02, 0xF0, 
+	0x1B, 0xAD, 0x00, 0x6C, 0xD0, 0x32, 0xA6, 0x3C, 
+	0xA9, 0x01, 0x9D, 0x00, 0x15, 0xA9, 0x01, 0x9D, 
+	0x00, 0x0E, 0xE8, 0xE4, 0x3D, 0xF0, 0x02, 0x86, 
+	0x3C, 0x4C, 0xAE, 0x3C, 0xA6, 0x3C, 0xAD, 0x00, 
+	0x6C, 0x9D, 0x00, 0x0E, 0x9E, 0x00, 0x15, 0xE8, 
+	0xE4, 0x3D, 0xF0, 0x02, 0x86, 0x3C, 0x29, 0x7F, 
+	0xC9, 0x13, 0xD0, 0x04, 0xA5, 0x47, 0x85, 0x3E, 
+	0xAD, 0x02, 0x6C, 0x29, 0x10, 0xF0, 0x2C, 0xA6, 
+	0x41, 0xF0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x20, 
+	0xD0, 0x21, 0x8E, 0x00, 0x6C, 0x64, 0x41, 0x4C, 
+	0xE1, 0x3C, 0xA6, 0x40, 0xE4, 0x3F, 0xF0, 0x13, 
+	0xA5, 0x3E, 0xD0, 0x0F, 0xAD, 0x02, 0x7C, 0x29, 
+	0x20, 0xD0, 0x08, 0xBD, 0x00, 0x07, 0x8D, 0x00, 
+	0x6C, 0xE6, 0x40, 0xAD, 0x02, 0x74, 0x89, 0x08, 
+	0xF0, 0x3B, 0x89, 0x02, 0xF0, 0x1B, 0xAD, 0x00, 
+	0x74, 0xD0, 0x32, 0xA6, 0x48, 0xA9, 0x01, 0x9D, 
+	0x00, 0x16, 0xA9, 0x01, 0x9D, 0x00, 0x0F, 0xE8, 
+	0xE4, 0x49, 0xF0, 0x02, 0x86, 0x48, 0x4C, 0x23, 
+	0x3D, 0xA6, 0x48, 0xAD, 0x00, 0x74, 0x9D, 0x00, 
+	0x0F, 0x9E, 0x00, 0x16, 0xE8, 0xE4, 0x49, 0xF0, 
+	0x02, 0x86, 0x48, 0x29, 0x7F, 0xC9, 0x13, 0xD0, 
+	0x04, 0xA5, 0x53, 0x85, 0x4A, 0xAD, 0x02, 0x74, 
+	0x29, 0x10, 0xF0, 0x2C, 0xA6, 0x4D, 0xF0, 0x0F, 
+	0xAD, 0x02, 0x7C, 0x29, 0x40, 0xD0, 0x21, 0x8E, 
+	0x00, 0x74, 0x64, 0x4D, 0x4C, 0x56, 0x3D, 0xA6, 
+	0x4C, 0xE4, 0x4B, 0xF0, 0x13, 0xA5, 0x4A, 0xD0, 
+	0x0F, 0xAD, 0x02, 0x7C, 0x29, 0x40, 0xD0, 0x08, 
+	0xBD, 0x00, 0x08, 0x8D, 0x00, 0x74, 0xE6, 0x4C, 
+	0x60, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 
+	0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0xD0, 0x00, 0x38, 
+	0xCE, 0xC0, 
+};
diff --git a/drivers/char/serial167.c b/drivers/char/serial167.c
new file mode 100644
index 0000000..c2deac9
--- /dev/null
+++ b/drivers/char/serial167.c
@@ -0,0 +1,2858 @@
+/*
+ * linux/drivers/char/serial167.c
+ *
+ * Driver for MVME166/7 board serial ports, which are via a CD2401.
+ * Based very much on cyclades.c.
+ *
+ * MVME166/7 work by Richard Hirst [richard@sleepie.demon.co.uk]
+ *
+ * ==============================================================
+ *
+ * static char rcsid[] =
+ * "$Revision: 1.36.1.4 $$Date: 1995/03/29 06:14:14 $";
+ *
+ *  linux/kernel/cyclades.c
+ *
+ * Maintained by Marcio Saito (cyclades@netcom.com) and
+ * Randolph Bentson (bentson@grieg.seaslug.org)
+ *
+ * Much of the design and some of the code came from serial.c
+ * which was copyright (C) 1991, 1992  Linus Torvalds.  It was
+ * extensively rewritten by Theodore Ts'o, 8/16/92 -- 9/14/92,
+ * and then fixed as suggested by Michael K. Johnson 12/12/92.
+ *
+ * This version does not support shared irq's.
+ *
+ * $Log: cyclades.c,v $
+ * Revision 1.36.1.4  1995/03/29  06:14:14  bentson
+ * disambiguate between Cyclom-16Y and Cyclom-32Ye;
+ *
+ * Changes:
+ *
+ * 200 lines of changes record removed - RGH 11-10-95, starting work on
+ * converting this to drive serial ports on mvme166 (cd2401).
+ *
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 2000/08/25
+ * - get rid of verify_area
+ * - use get_user to access memory from userspace in set_threshold,
+ *   set_default_threshold and set_timeout
+ * - don't use the panic function in serial167_init
+ * - do resource release on failure on serial167_init
+ * - include missing restore_flags in mvme167_serial_console_setup
+ *
+ * Kars de Jong <jongk@linux-m68k.org> - 2004/09/06
+ * - replace bottom half handler with task queue handler
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/tty.h>
+#include <linux/interrupt.h>
+#include <linux/serial.h>
+#include <linux/serialP.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/serial167.h>
+#include <linux/delay.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/console.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/mvme16xhw.h>
+#include <asm/bootinfo.h>
+#include <asm/setup.h>
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+#include <asm/uaccess.h>
+#include <linux/init.h>
+
+#define SERIAL_PARANOIA_CHECK
+#undef  SERIAL_DEBUG_OPEN
+#undef  SERIAL_DEBUG_THROTTLE
+#undef  SERIAL_DEBUG_OTHER
+#undef  SERIAL_DEBUG_IO
+#undef  SERIAL_DEBUG_COUNT
+#undef  SERIAL_DEBUG_DTR
+#undef  CYCLOM_16Y_HACK
+#define  CYCLOM_ENABLE_MONITORING
+
+#define WAKEUP_CHARS 256
+
+#define STD_COM_FLAGS (0)
+
+#define SERIAL_TYPE_NORMAL  1
+
+static struct tty_driver *cy_serial_driver;
+extern int serial_console;
+static struct cyclades_port *serial_console_info = NULL;
+static unsigned int serial_console_cflag = 0;
+u_char initial_console_speed;
+
+/* Base address of cd2401 chip on mvme166/7 */
+
+#define BASE_ADDR (0xfff45000)
+#define pcc2chip	((volatile u_char *)0xfff42000)
+#define PccSCCMICR	0x1d
+#define PccSCCTICR	0x1e
+#define PccSCCRICR	0x1f
+#define PccTPIACKR	0x25
+#define PccRPIACKR	0x27
+#define PccIMLR		0x3f
+
+/* This is the per-port data structure */
+struct cyclades_port cy_port[] = {
+      /* CARD#  */
+        {-1 },      /* ttyS0 */
+        {-1 },      /* ttyS1 */
+        {-1 },      /* ttyS2 */
+        {-1 },      /* ttyS3 */
+};
+#define NR_PORTS        (sizeof(cy_port)/sizeof(struct cyclades_port))
+
+/*
+ * tmp_buf is used as a temporary buffer by serial_write.  We need to
+ * lock it in case the copy_from_user blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char *tmp_buf = 0;
+DECLARE_MUTEX(tmp_buf_sem);
+
+/*
+ * This is used to look up the divisor speeds and the timeouts
+ * We're normally limited to 15 distinct baud rates.  The extra
+ * are accessed via settings in info->flags.
+ *         0,     1,     2,     3,     4,     5,     6,     7,     8,     9,
+ *        10,    11,    12,    13,    14,    15,    16,    17,    18,    19,
+ *                                                  HI            VHI
+ */
+static int baud_table[] = {
+           0,    50,    75,   110,   134,   150,   200,   300,   600,  1200,
+        1800,  2400,  4800,  9600, 19200, 38400, 57600, 76800,115200,150000,
+        0};
+
+#if 0
+static char baud_co[] = {  /* 25 MHz clock option table */
+        /* value =>    00    01   02    03    04 */
+        /* divide by    8    32   128   512  2048 */
+        0x00,  0x04,  0x04,  0x04,  0x04,  0x04,  0x03,  0x03,  0x03,  0x02,
+        0x02,  0x02,  0x01,  0x01,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00};
+
+static char baud_bpr[] = {  /* 25 MHz baud rate period table */
+        0x00,  0xf5,  0xa3,  0x6f,  0x5c,  0x51,  0xf5,  0xa3,  0x51,  0xa3,
+        0x6d,  0x51,  0xa3,  0x51,  0xa3,  0x51,  0x36,  0x29,  0x1b,  0x15};
+#endif
+
+/* I think 166 brd clocks 2401 at 20MHz.... */
+
+/* These values are written directly to tcor, and >> 5 for writing to rcor */
+static u_char baud_co[] = {  /* 20 MHz clock option table */
+        0x00,  0x80,  0x80,  0x80,  0x80,  0x80,  0x80,  0x60,  0x60,  0x40,
+        0x40,  0x40,  0x20,  0x20,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00};
+
+/* These values written directly to tbpr/rbpr */
+static u_char baud_bpr[] = {  /* 20 MHz baud rate period table */
+        0x00,  0xc0,  0x80,  0x58,  0x6c,  0x40,  0xc0,  0x81,  0x40,  0x81,
+        0x57,  0x40,  0x81,  0x40,  0x81,  0x40,  0x2b,  0x20,  0x15,  0x10};
+
+static u_char baud_cor4[] = {  /* receive threshold */
+        0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,  0x0a,
+        0x0a,  0x0a,  0x0a,  0x09,  0x09,  0x08,  0x08,  0x08,  0x08,  0x07};
+
+
+
+static void shutdown(struct cyclades_port *);
+static int startup (struct cyclades_port *);
+static void cy_throttle(struct tty_struct *);
+static void cy_unthrottle(struct tty_struct *);
+static void config_setup(struct cyclades_port *);
+extern void console_print(const char *);
+#ifdef CYCLOM_SHOW_STATUS
+static void show_status(int);
+#endif
+
+#ifdef CONFIG_REMOTE_DEBUG
+static void debug_setup(void);
+void queueDebugChar (int c);
+int getDebugChar(void);
+
+#define DEBUG_PORT	1
+#define DEBUG_LEN	256
+
+typedef struct {
+	int	in;
+	int	out;
+	unsigned char	buf[DEBUG_LEN];
+} debugq;
+
+debugq debugiq;
+#endif
+
+/*
+ * I have my own version of udelay(), as it is needed when initialising
+ * the chip, before the delay loop has been calibrated.  Should probably
+ * reference one of the vmechip2 or pccchip2 counter for an accurate
+ * delay, but this wild guess will do for now.
+ */
+
+void my_udelay (long us)
+{
+	u_char x;
+	volatile u_char *p = &x;
+	int i;
+
+	while (us--)
+		for (i = 100; i; i--)
+			x |= *p;
+}
+
+static inline int
+serial_paranoia_check(struct cyclades_port *info, char *name,
+		      const char *routine)
+{
+#ifdef SERIAL_PARANOIA_CHECK
+    static const char *badmagic =
+	"Warning: bad magic number for serial struct (%s) in %s\n";
+    static const char *badinfo =
+	"Warning: null cyclades_port for (%s) in %s\n";
+    static const char *badrange =
+	"Warning: cyclades_port out of range for (%s) in %s\n";
+
+    if (!info) {
+	printk(badinfo, name, routine);
+	return 1;
+    }
+
+    if( (long)info < (long)(&cy_port[0])
+    || (long)(&cy_port[NR_PORTS]) < (long)info ){
+	printk(badrange, name, routine);
+	return 1;
+    }
+
+    if (info->magic != CYCLADES_MAGIC) {
+	printk(badmagic, name, routine);
+	return 1;
+    }
+#endif
+	return 0;
+} /* serial_paranoia_check */
+
+#if 0
+/* The following diagnostic routines allow the driver to spew
+   information on the screen, even (especially!) during interrupts.
+ */
+void
+SP(char *data){
+  unsigned long flags;
+    local_irq_save(flags);
+        console_print(data);
+    local_irq_restore(flags);
+}
+char scrn[2];
+void
+CP(char data){
+  unsigned long flags;
+    local_irq_save(flags);
+        scrn[0] = data;
+        console_print(scrn);
+    local_irq_restore(flags);
+}/* CP */
+
+void CP1(int data) { (data<10)?  CP(data+'0'): CP(data+'A'-10); }/* CP1 */
+void CP2(int data) { CP1((data>>4) & 0x0f); CP1( data & 0x0f); }/* CP2 */
+void CP4(int data) { CP2((data>>8) & 0xff); CP2(data & 0xff); }/* CP4 */
+void CP8(long data) { CP4((data>>16) & 0xffff); CP4(data & 0xffff); }/* CP8 */
+#endif
+
+/* This routine waits up to 1000 micro-seconds for the previous
+   command to the Cirrus chip to complete and then issues the
+   new command.  An error is returned if the previous command
+   didn't finish within the time limit.
+ */
+u_short
+write_cy_cmd(volatile u_char *base_addr, u_char cmd)
+{
+  unsigned long flags;
+  volatile int  i;
+
+    local_irq_save(flags);
+	/* Check to see that the previous command has completed */
+	for(i = 0 ; i < 100 ; i++){
+	    if (base_addr[CyCCR] == 0){
+		break;
+	    }
+	    my_udelay(10L);
+	}
+	/* if the CCR never cleared, the previous command
+	    didn't finish within the "reasonable time" */
+	if ( i == 10 ) {
+	    local_irq_restore(flags);
+	    return (-1);
+	}
+
+	/* Issue the new command */
+	base_addr[CyCCR] = cmd;
+    local_irq_restore(flags);
+    return(0);
+} /* write_cy_cmd */
+
+
+/* cy_start and cy_stop provide software output flow control as a
+   function of XON/XOFF, software CTS, and other such stuff. */
+
+static void
+cy_stop(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+  int channel;
+  unsigned long flags;
+
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_stop %s\n", tty->name); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_stop"))
+	return;
+	
+    channel = info->line;
+
+    local_irq_save(flags);
+        base_addr[CyCAR] = (u_char)(channel); /* index channel */
+        base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+    local_irq_restore(flags);
+
+    return;
+} /* cy_stop */
+
+static void
+cy_start(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+  int channel;
+  unsigned long flags;
+
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_start %s\n", tty->name); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_start"))
+	return;
+	
+    channel = info->line;
+
+    local_irq_save(flags);
+        base_addr[CyCAR] = (u_char)(channel);
+        base_addr[CyIER] |= CyTxMpty;
+    local_irq_restore(flags);
+
+    return;
+} /* cy_start */
+
+
+/*
+ * This routine is used by the interrupt handler to schedule
+ * processing in the software interrupt portion of the driver
+ * (also known as the "bottom half").  This can be called any
+ * number of times for any channel without harm.
+ */
+static inline void
+cy_sched_event(struct cyclades_port *info, int event)
+{
+    info->event |= 1 << event; /* remember what kind of event and who */
+    schedule_work(&info->tqueue);
+} /* cy_sched_event */
+
+
+/* The real interrupt service routines are called
+   whenever the card wants its hand held--chars
+   received, out buffer empty, modem change, etc.
+ */
+static irqreturn_t
+cd2401_rxerr_interrupt(int irq, void *dev_id, struct pt_regs *fp)
+{
+    struct tty_struct *tty;
+    struct cyclades_port *info;
+    volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+    unsigned char err, rfoc;
+    int channel;
+    char data;
+
+    /* determine the channel and change to that context */
+    channel = (u_short ) (base_addr[CyLICR] >> 2);
+    info = &cy_port[channel];
+    info->last_active = jiffies;
+
+    if ((err = base_addr[CyRISR]) & CyTIMEOUT) {
+	/* This is a receive timeout interrupt, ignore it */
+	base_addr[CyREOIR] = CyNOTRANS;
+	return IRQ_HANDLED;
+    }
+
+    /* Read a byte of data if there is any - assume the error
+     * is associated with this character */
+
+    if ((rfoc = base_addr[CyRFOC]) != 0)
+	data = base_addr[CyRDR];
+    else
+	data = 0;
+
+    /* if there is nowhere to put the data, discard it */
+    if(info->tty == 0) {
+	base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS;
+	return IRQ_HANDLED;
+    }
+    else { /* there is an open port for this data */
+	tty = info->tty;
+	if(err & info->ignore_status_mask){
+	    base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS;
+	    return IRQ_HANDLED;
+	}
+	if (tty->flip.count < TTY_FLIPBUF_SIZE){
+	    tty->flip.count++;
+	    if (err & info->read_status_mask){
+		if(err & CyBREAK){
+		    *tty->flip.flag_buf_ptr++ = TTY_BREAK;
+		    *tty->flip.char_buf_ptr++ = data;
+		    if (info->flags & ASYNC_SAK){
+			do_SAK(tty);
+		    }
+		}else if(err & CyFRAME){
+		    *tty->flip.flag_buf_ptr++ = TTY_FRAME;
+		    *tty->flip.char_buf_ptr++ = data;
+		}else if(err & CyPARITY){
+		    *tty->flip.flag_buf_ptr++ = TTY_PARITY;
+		    *tty->flip.char_buf_ptr++ = data;
+		}else if(err & CyOVERRUN){
+		    *tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
+		    *tty->flip.char_buf_ptr++ = 0;
+		    /*
+		       If the flip buffer itself is
+		       overflowing, we still loose
+		       the next incoming character.
+		     */
+		    if(tty->flip.count < TTY_FLIPBUF_SIZE){
+			tty->flip.count++;
+			*tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+			*tty->flip.char_buf_ptr++ = data;
+		    }
+		/* These two conditions may imply */
+		/* a normal read should be done. */
+		/* else if(data & CyTIMEOUT) */
+		/* else if(data & CySPECHAR) */
+		}else{
+		    *tty->flip.flag_buf_ptr++ = 0;
+		    *tty->flip.char_buf_ptr++ = 0;
+		}
+	    }else{
+		*tty->flip.flag_buf_ptr++ = 0;
+		*tty->flip.char_buf_ptr++ = 0;
+	    }
+	}else{
+	    /* there was a software buffer overrun
+	       and nothing could be done about it!!! */
+	}
+    }
+    schedule_delayed_work(&tty->flip.work, 1);
+    /* end of service */
+    base_addr[CyREOIR] = rfoc ? 0 : CyNOTRANS;
+    return IRQ_HANDLED;
+} /* cy_rxerr_interrupt */
+
+static irqreturn_t
+cd2401_modem_interrupt(int irq, void *dev_id, struct pt_regs *fp)
+{
+    struct cyclades_port *info;
+    volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+    int channel;
+    int mdm_change;
+    int mdm_status;
+
+
+    /* determine the channel and change to that context */
+    channel = (u_short ) (base_addr[CyLICR] >> 2);
+    info = &cy_port[channel];
+    info->last_active = jiffies;
+
+    mdm_change = base_addr[CyMISR];
+    mdm_status = base_addr[CyMSVR1];
+
+    if(info->tty == 0){ /* nowhere to put the data, ignore it */
+	;
+    }else{
+	if((mdm_change & CyDCD)
+	&& (info->flags & ASYNC_CHECK_CD)){
+	    if(mdm_status & CyDCD){
+/* CP('!'); */
+		cy_sched_event(info, Cy_EVENT_OPEN_WAKEUP);
+	    } else {
+/* CP('@'); */
+		cy_sched_event(info, Cy_EVENT_HANGUP);
+	    }
+	}
+	if((mdm_change & CyCTS)
+	&& (info->flags & ASYNC_CTS_FLOW)){
+	    if(info->tty->stopped){
+		if(mdm_status & CyCTS){
+		    /* !!! cy_start isn't used because... */
+		    info->tty->stopped = 0;
+        	    base_addr[CyIER] |= CyTxMpty;
+		    cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+		}
+	    }else{
+		if(!(mdm_status & CyCTS)){
+		    /* !!! cy_stop isn't used because... */
+		    info->tty->stopped = 1;
+        	    base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+		}
+	    }
+	}
+	if(mdm_status & CyDSR){
+	}
+    }
+    base_addr[CyMEOIR] = 0;
+    return IRQ_HANDLED;
+} /* cy_modem_interrupt */
+
+static irqreturn_t
+cd2401_tx_interrupt(int irq, void *dev_id, struct pt_regs *fp)
+{
+    struct cyclades_port *info;
+    volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+    int channel;
+    int char_count, saved_cnt;
+    int outch;
+
+    /* determine the channel and change to that context */
+    channel = (u_short ) (base_addr[CyLICR] >> 2);
+
+#ifdef CONFIG_REMOTE_DEBUG
+    if (channel == DEBUG_PORT) {
+	panic ("TxInt on debug port!!!");
+    }
+#endif
+
+    info = &cy_port[channel];
+
+    /* validate the port number (as configured and open) */
+    if( (channel < 0) || (NR_PORTS <= channel) ){
+	base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+	base_addr[CyTEOIR] = CyNOTRANS;
+	return IRQ_HANDLED;
+    }
+    info->last_active = jiffies;
+    if(info->tty == 0){
+	base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+        if (info->xmit_cnt < WAKEUP_CHARS) {
+	    cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+        }
+	base_addr[CyTEOIR] = CyNOTRANS;
+	return IRQ_HANDLED;
+    }
+
+    /* load the on-chip space available for outbound data */
+    saved_cnt = char_count = base_addr[CyTFTC];
+
+    if(info->x_char) { /* send special char */
+	outch = info->x_char;
+	base_addr[CyTDR] = outch;
+	char_count--;
+	info->x_char = 0;
+    }
+
+    if (info->x_break){
+	/*  The Cirrus chip requires the "Embedded Transmit
+	    Commands" of start break, delay, and end break
+	    sequences to be sent.  The duration of the
+	    break is given in TICs, which runs at HZ
+	    (typically 100) and the PPR runs at 200 Hz,
+	    so the delay is duration * 200/HZ, and thus a
+	    break can run from 1/100 sec to about 5/4 sec.
+	    Need to check these values - RGH 141095.
+	 */
+	base_addr[CyTDR] = 0; /* start break */
+	base_addr[CyTDR] = 0x81;
+	base_addr[CyTDR] = 0; /* delay a bit */
+	base_addr[CyTDR] = 0x82;
+	base_addr[CyTDR] = info->x_break*200/HZ;
+	base_addr[CyTDR] = 0; /* terminate break */
+	base_addr[CyTDR] = 0x83;
+	char_count -= 7;
+	info->x_break = 0;
+    }
+
+    while (char_count > 0){
+	if (!info->xmit_cnt){
+	    base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+	    break;
+	}
+	if (info->xmit_buf == 0){
+	    base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+	    break;
+	}
+	if (info->tty->stopped || info->tty->hw_stopped){
+	    base_addr[CyIER] &= ~(CyTxMpty|CyTxRdy);
+	    break;
+	}
+	/* Because the Embedded Transmit Commands have been
+	   enabled, we must check to see if the escape
+	   character, NULL, is being sent.  If it is, we
+	   must ensure that there is room for it to be
+	   doubled in the output stream.  Therefore we
+	   no longer advance the pointer when the character
+	   is fetched, but rather wait until after the check
+	   for a NULL output character. (This is necessary
+	   because there may not be room for the two chars
+	   needed to send a NULL.
+	 */
+	outch = info->xmit_buf[info->xmit_tail];
+	if( outch ){
+	    info->xmit_cnt--;
+	    info->xmit_tail = (info->xmit_tail + 1)
+				      & (PAGE_SIZE - 1);
+	    base_addr[CyTDR] = outch;
+	    char_count--;
+	}else{
+	    if(char_count > 1){
+		info->xmit_cnt--;
+		info->xmit_tail = (info->xmit_tail + 1)
+					  & (PAGE_SIZE - 1);
+		base_addr[CyTDR] = outch;
+		base_addr[CyTDR] = 0;
+		char_count--;
+		char_count--;
+	    }else{
+		break;
+	    }
+	}
+    }
+
+    if (info->xmit_cnt < WAKEUP_CHARS) {
+	cy_sched_event(info, Cy_EVENT_WRITE_WAKEUP);
+    }
+    base_addr[CyTEOIR] = (char_count != saved_cnt) ? 0 : CyNOTRANS;
+    return IRQ_HANDLED;
+} /* cy_tx_interrupt */
+
+static irqreturn_t
+cd2401_rx_interrupt(int irq, void *dev_id, struct pt_regs *fp)
+{
+    struct tty_struct *tty;
+    struct cyclades_port *info;
+    volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+    int channel;
+    char data;
+    int char_count;
+    int save_cnt;
+
+    /* determine the channel and change to that context */
+    channel = (u_short ) (base_addr[CyLICR] >> 2);
+    info = &cy_port[channel];
+    info->last_active = jiffies;
+    save_cnt = char_count = base_addr[CyRFOC];
+
+#ifdef CONFIG_REMOTE_DEBUG
+    if (channel == DEBUG_PORT) {
+	while (char_count--) {
+            data = base_addr[CyRDR];
+	    queueDebugChar(data);
+	}
+    }
+    else
+#endif
+    /* if there is nowhere to put the data, discard it */
+    if(info->tty == 0){
+	while(char_count--){
+	    data = base_addr[CyRDR];
+	}
+    }else{ /* there is an open port for this data */
+	tty = info->tty;
+	/* load # characters available from the chip */
+
+#ifdef CYCLOM_ENABLE_MONITORING
+	++info->mon.int_count;
+	info->mon.char_count += char_count;
+	if (char_count > info->mon.char_max)
+	    info->mon.char_max = char_count;
+	info->mon.char_last = char_count;
+#endif
+	while(char_count--){
+	    data = base_addr[CyRDR];
+	    if (tty->flip.count >= TTY_FLIPBUF_SIZE){
+		continue;
+	    }
+	    tty->flip.count++;
+	    *tty->flip.flag_buf_ptr++ = TTY_NORMAL;
+	    *tty->flip.char_buf_ptr++ = data;
+#ifdef CYCLOM_16Y_HACK
+	    udelay(10L);
+#endif
+        }
+	schedule_delayed_work(&tty->flip.work, 1);
+    }
+    /* end of service */
+    base_addr[CyREOIR] = save_cnt ? 0 : CyNOTRANS;
+    return IRQ_HANDLED;
+} /* cy_rx_interrupt */
+
+/*
+ * This routine is used to handle the "bottom half" processing for the
+ * serial driver, known also the "software interrupt" processing.
+ * This processing is done at the kernel interrupt level, after the
+ * cy#/_interrupt() has returned, BUT WITH INTERRUPTS TURNED ON.  This
+ * is where time-consuming activities which can not be done in the
+ * interrupt driver proper are done; the interrupt driver schedules
+ * them using cy_sched_event(), and they get done here.
+ *
+ * This is done through one level of indirection--the task queue.
+ * When a hardware interrupt service routine wants service by the
+ * driver's bottom half, it enqueues the appropriate tq_struct (one
+ * per port) to the keventd work queue and sets a request flag
+ * that the work queue be processed.
+ *
+ * Although this may seem unwieldy, it gives the system a way to
+ * pass an argument (in this case the pointer to the cyclades_port
+ * structure) to the bottom half of the driver.  Previous kernels
+ * had to poll every port to see if that port needed servicing.
+ */
+static void
+do_softint(void *private_)
+{
+  struct cyclades_port *info = (struct cyclades_port *) private_;
+  struct tty_struct    *tty;
+
+    tty = info->tty;
+    if (!tty)
+	return;
+
+    if (test_and_clear_bit(Cy_EVENT_HANGUP, &info->event)) {
+	tty_hangup(info->tty);
+	wake_up_interruptible(&info->open_wait);
+	info->flags &= ~ASYNC_NORMAL_ACTIVE;
+    }
+    if (test_and_clear_bit(Cy_EVENT_OPEN_WAKEUP, &info->event)) {
+	wake_up_interruptible(&info->open_wait);
+    }
+    if (test_and_clear_bit(Cy_EVENT_WRITE_WAKEUP, &info->event)) {
+    	tty_wakeup(tty);
+    }
+} /* do_softint */
+
+
+/* This is called whenever a port becomes active;
+   interrupts are enabled and DTR & RTS are turned on.
+ */
+static int
+startup(struct cyclades_port * info)
+{
+  unsigned long flags;
+  volatile unsigned char *base_addr = (unsigned char *)BASE_ADDR;
+  int channel;
+
+    if (info->flags & ASYNC_INITIALIZED){
+	return 0;
+    }
+
+    if (!info->type){
+	if (info->tty){
+	    set_bit(TTY_IO_ERROR, &info->tty->flags);
+	}
+	return 0;
+    }
+    if (!info->xmit_buf){
+	info->xmit_buf = (unsigned char *) get_zeroed_page (GFP_KERNEL);
+	if (!info->xmit_buf){
+	    return -ENOMEM;
+	}
+    }
+
+    config_setup(info);
+
+    channel = info->line;
+
+#ifdef SERIAL_DEBUG_OPEN
+    printk("startup channel %d\n", channel);
+#endif
+
+    local_irq_save(flags);
+	base_addr[CyCAR] = (u_char)channel;
+	write_cy_cmd(base_addr,CyENB_RCVR|CyENB_XMTR);
+
+	base_addr[CyCAR] = (u_char)channel; /* !!! Is this needed? */
+	base_addr[CyMSVR1] = CyRTS;
+/* CP('S');CP('1'); */
+	base_addr[CyMSVR2] = CyDTR;
+
+#ifdef SERIAL_DEBUG_DTR
+        printk("cyc: %d: raising DTR\n", __LINE__);
+        printk("     status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+
+	base_addr[CyIER] |= CyRxData;
+	info->flags |= ASYNC_INITIALIZED;
+
+	if (info->tty){
+	    clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	}
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+    local_irq_restore(flags);
+
+#ifdef SERIAL_DEBUG_OPEN
+    printk(" done\n");
+#endif
+    return 0;
+} /* startup */
+
+void
+start_xmit( struct cyclades_port *info )
+{
+  unsigned long flags;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  int channel;
+
+    channel = info->line;
+    local_irq_save(flags);
+	base_addr[CyCAR] = channel;
+	base_addr[CyIER] |= CyTxMpty;
+    local_irq_restore(flags);
+} /* start_xmit */
+
+/*
+ * This routine shuts down a serial port; interrupts are disabled,
+ * and DTR is dropped if the hangup on close termio flag is on.
+ */
+static void
+shutdown(struct cyclades_port * info)
+{
+  unsigned long flags;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  int channel;
+
+    if (!(info->flags & ASYNC_INITIALIZED)){
+/* CP('$'); */
+	return;
+    }
+
+    channel = info->line;
+
+#ifdef SERIAL_DEBUG_OPEN
+    printk("shutdown channel %d\n", channel);
+#endif
+
+    /* !!! REALLY MUST WAIT FOR LAST CHARACTER TO BE
+       SENT BEFORE DROPPING THE LINE !!!  (Perhaps
+       set some flag that is read when XMTY happens.)
+       Other choices are to delay some fixed interval
+       or schedule some later processing.
+     */
+    local_irq_save(flags);
+	if (info->xmit_buf){
+	    free_page((unsigned long) info->xmit_buf);
+	    info->xmit_buf = 0;
+	}
+
+	base_addr[CyCAR] = (u_char)channel;
+	if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) {
+	    base_addr[CyMSVR1] = 0;
+/* CP('C');CP('1'); */
+	    base_addr[CyMSVR2] = 0;
+#ifdef SERIAL_DEBUG_DTR
+            printk("cyc: %d: dropping DTR\n", __LINE__);
+            printk("     status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+        }
+	write_cy_cmd(base_addr,CyDIS_RCVR);
+         /* it may be appropriate to clear _XMIT at
+           some later date (after testing)!!! */
+
+	if (info->tty){
+	    set_bit(TTY_IO_ERROR, &info->tty->flags);
+	}
+	info->flags &= ~ASYNC_INITIALIZED;
+    local_irq_restore(flags);
+
+#ifdef SERIAL_DEBUG_OPEN
+    printk(" done\n");
+#endif
+    return;
+} /* shutdown */
+
+/*
+ * This routine finds or computes the various line characteristics.
+ */
+static void
+config_setup(struct cyclades_port * info)
+{
+  unsigned long flags;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  int channel;
+  unsigned cflag;
+  int   i;
+  unsigned char ti, need_init_chan = 0;
+
+    if (!info->tty || !info->tty->termios){
+        return;
+    }
+    if (info->line == -1){
+        return;
+    }
+    cflag = info->tty->termios->c_cflag;
+
+    /* baud rate */
+    i = cflag & CBAUD;
+#ifdef CBAUDEX
+/* Starting with kernel 1.1.65, there is direct support for
+   higher baud rates.  The following code supports those
+   changes.  The conditional aspect allows this driver to be
+   used for earlier as well as later kernel versions.  (The
+   mapping is slightly different from serial.c because there
+   is still the possibility of supporting 75 kbit/sec with
+   the Cyclades board.)
+ */
+    if (i & CBAUDEX) {
+	if (i == B57600)
+	    i = 16;
+	else if(i == B115200) 
+	    i = 18;
+#ifdef B78600
+	else if(i == B78600) 
+	    i = 17;
+#endif
+	else
+	    info->tty->termios->c_cflag &= ~CBAUDEX;
+    }
+#endif
+    if (i == 15) {
+	    if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+		    i += 1;
+	    if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+		    i += 3;
+    }
+    /* Don't ever change the speed of the console port.  It will
+     * run at the speed specified in bootinfo, or at 19.2K */
+    /* Actually, it should run at whatever speed 166Bug was using */
+    /* Note info->timeout isn't used at present */
+    if (info != serial_console_info) {
+	info->tbpr = baud_bpr[i]; /* Tx BPR */
+	info->tco = baud_co[i]; /* Tx CO */
+	info->rbpr = baud_bpr[i]; /* Rx BPR */
+	info->rco = baud_co[i] >> 5; /* Rx CO */
+	if (baud_table[i] == 134) {
+            info->timeout = (info->xmit_fifo_size*HZ*30/269) + 2;
+            /* get it right for 134.5 baud */
+	} else if (baud_table[i]) {
+            info->timeout = (info->xmit_fifo_size*HZ*15/baud_table[i]) + 2;
+        /* this needs to be propagated into the card info */
+	} else {
+            info->timeout = 0;
+	}
+    }
+    /* By tradition (is it a standard?) a baud rate of zero
+       implies the line should be/has been closed.  A bit
+       later in this routine such a test is performed. */
+
+    /* byte size and parity */
+    info->cor7 = 0;
+    info->cor6 = 0;
+    info->cor5 = 0;
+    info->cor4 = (info->default_threshold
+		  ? info->default_threshold
+		  : baud_cor4[i]); /* receive threshold */
+    /* Following two lines added 101295, RGH. */
+    /* It is obviously wrong to access CyCORx, and not info->corx here,
+     * try and remember to fix it later! */
+    channel = info->line;
+    base_addr[CyCAR] = (u_char)channel;
+    if (C_CLOCAL(info->tty)) {
+	if (base_addr[CyIER] & CyMdmCh)
+	    base_addr[CyIER] &= ~CyMdmCh; /* without modem intr */
+			       /* ignore 1->0 modem transitions */
+	if (base_addr[CyCOR4] & (CyDSR|CyCTS|CyDCD))
+	    base_addr[CyCOR4] &= ~(CyDSR|CyCTS|CyDCD);
+			       /* ignore 0->1 modem transitions */
+	if (base_addr[CyCOR5] & (CyDSR|CyCTS|CyDCD))
+	    base_addr[CyCOR5] &= ~(CyDSR|CyCTS|CyDCD);
+    } else {
+	if ((base_addr[CyIER] & CyMdmCh) != CyMdmCh)
+	    base_addr[CyIER] |= CyMdmCh; /* with modem intr */
+			       /* act on 1->0 modem transitions */
+	if ((base_addr[CyCOR4] & (CyDSR|CyCTS|CyDCD)) != (CyDSR|CyCTS|CyDCD))
+	    base_addr[CyCOR4] |= CyDSR|CyCTS|CyDCD;
+			       /* act on 0->1 modem transitions */
+	if ((base_addr[CyCOR5] & (CyDSR|CyCTS|CyDCD)) != (CyDSR|CyCTS|CyDCD))
+	    base_addr[CyCOR5] |= CyDSR|CyCTS|CyDCD;
+    }
+    info->cor3 = (cflag & CSTOPB) ? Cy_2_STOP : Cy_1_STOP;
+    info->cor2 = CyETC;
+    switch(cflag & CSIZE){
+    case CS5:
+        info->cor1 = Cy_5_BITS;
+        break;
+    case CS6:
+        info->cor1 = Cy_6_BITS;
+        break;
+    case CS7:
+        info->cor1 = Cy_7_BITS;
+        break;
+    case CS8:
+        info->cor1 = Cy_8_BITS;
+        break;
+    }
+    if (cflag & PARENB){
+        if (cflag & PARODD){
+            info->cor1 |= CyPARITY_O;
+        }else{
+            info->cor1 |= CyPARITY_E;
+        }
+    }else{
+        info->cor1 |= CyPARITY_NONE;
+    }
+	
+    /* CTS flow control flag */
+#if 0
+    /* Don't complcate matters for now! RGH 141095 */
+    if (cflag & CRTSCTS){
+	info->flags |= ASYNC_CTS_FLOW;
+	info->cor2 |= CyCtsAE;
+    }else{
+	info->flags &= ~ASYNC_CTS_FLOW;
+	info->cor2 &= ~CyCtsAE;
+    }
+#endif
+    if (cflag & CLOCAL)
+	info->flags &= ~ASYNC_CHECK_CD;
+    else
+	info->flags |= ASYNC_CHECK_CD;
+
+     /***********************************************
+	The hardware option, CyRtsAO, presents RTS when
+	the chip has characters to send.  Since most modems
+	use RTS as reverse (inbound) flow control, this
+	option is not used.  If inbound flow control is
+	necessary, DTR can be programmed to provide the
+	appropriate signals for use with a non-standard
+	cable.  Contact Marcio Saito for details.
+     ***********************************************/
+
+    channel = info->line;
+
+    local_irq_save(flags);
+	base_addr[CyCAR] = (u_char)channel;
+
+	/* CyCMR set once only in mvme167_init_serial() */
+	if (base_addr[CyLICR] != channel << 2)
+	    base_addr[CyLICR] = channel << 2;
+	if (base_addr[CyLIVR] != 0x5c)
+	    base_addr[CyLIVR] = 0x5c;
+
+       /* tx and rx baud rate */
+
+	if (base_addr[CyCOR1] != info->cor1)
+	    need_init_chan = 1;
+	if (base_addr[CyTCOR] != info->tco)
+	    base_addr[CyTCOR] = info->tco;
+	if (base_addr[CyTBPR] != info->tbpr)
+	    base_addr[CyTBPR] = info->tbpr;
+	if (base_addr[CyRCOR] != info->rco)
+	    base_addr[CyRCOR] = info->rco;
+	if (base_addr[CyRBPR] != info->rbpr)
+	    base_addr[CyRBPR] = info->rbpr;
+
+	/* set line characteristics  according configuration */
+
+	if (base_addr[CySCHR1] != START_CHAR(info->tty))
+	    base_addr[CySCHR1] = START_CHAR(info->tty);
+	if (base_addr[CySCHR2] != STOP_CHAR(info->tty))
+	    base_addr[CySCHR2] = STOP_CHAR(info->tty);
+	if (base_addr[CySCRL] != START_CHAR(info->tty))
+	    base_addr[CySCRL] = START_CHAR(info->tty);
+	if (base_addr[CySCRH] != START_CHAR(info->tty))
+	    base_addr[CySCRH] = START_CHAR(info->tty);
+	if (base_addr[CyCOR1] != info->cor1)
+	    base_addr[CyCOR1] = info->cor1;
+	if (base_addr[CyCOR2] != info->cor2)
+	    base_addr[CyCOR2] = info->cor2;
+	if (base_addr[CyCOR3] != info->cor3)
+	    base_addr[CyCOR3] = info->cor3;
+	if (base_addr[CyCOR4] != info->cor4)
+	    base_addr[CyCOR4] = info->cor4;
+	if (base_addr[CyCOR5] != info->cor5)
+	    base_addr[CyCOR5] = info->cor5;
+	if (base_addr[CyCOR6] != info->cor6)
+	    base_addr[CyCOR6] = info->cor6;
+	if (base_addr[CyCOR7] != info->cor7)
+	    base_addr[CyCOR7] = info->cor7;
+
+	if (need_init_chan)
+	    write_cy_cmd(base_addr,CyINIT_CHAN);
+
+	base_addr[CyCAR] = (u_char)channel; /* !!! Is this needed? */
+
+	/* 2ms default rx timeout */
+	ti = info->default_timeout ? info->default_timeout : 0x02;
+	if (base_addr[CyRTPRL] != ti)
+	    base_addr[CyRTPRL] = ti;
+	if (base_addr[CyRTPRH] != 0)
+	    base_addr[CyRTPRH] = 0;
+
+	/* Set up RTS here also ????? RGH 141095 */
+	if(i == 0){ /* baud rate is zero, turn off line */
+	    if ((base_addr[CyMSVR2] & CyDTR) == CyDTR)
+	        base_addr[CyMSVR2] = 0;
+#ifdef SERIAL_DEBUG_DTR
+            printk("cyc: %d: dropping DTR\n", __LINE__);
+            printk("     status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+	}else{
+	    if ((base_addr[CyMSVR2] & CyDTR) != CyDTR)
+	        base_addr[CyMSVR2] = CyDTR;
+#ifdef SERIAL_DEBUG_DTR
+            printk("cyc: %d: raising DTR\n", __LINE__);
+            printk("     status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+	}
+
+	if (info->tty){
+	    clear_bit(TTY_IO_ERROR, &info->tty->flags);
+	}
+
+    local_irq_restore(flags);
+
+} /* config_setup */
+
+
+static void
+cy_put_char(struct tty_struct *tty, unsigned char ch)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+
+#ifdef SERIAL_DEBUG_IO
+    printk("cy_put_char %s(0x%02x)\n", tty->name, ch);
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_put_char"))
+	return;
+
+    if (!tty || !info->xmit_buf)
+	return;
+
+    local_irq_save(flags);
+	if (info->xmit_cnt >= PAGE_SIZE - 1) {
+	    local_irq_restore(flags);
+	    return;
+	}
+
+	info->xmit_buf[info->xmit_head++] = ch;
+	info->xmit_head &= PAGE_SIZE - 1;
+	info->xmit_cnt++;
+    local_irq_restore(flags);
+} /* cy_put_char */
+
+
+static void
+cy_flush_chars(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  int channel;
+				
+#ifdef SERIAL_DEBUG_IO
+    printk("cy_flush_chars %s\n", tty->name); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_flush_chars"))
+	return;
+
+    if (info->xmit_cnt <= 0 || tty->stopped
+    || tty->hw_stopped || !info->xmit_buf)
+	return;
+
+    channel = info->line;
+
+    local_irq_save(flags);
+	base_addr[CyCAR] = channel;
+	base_addr[CyIER] |= CyTxMpty;
+    local_irq_restore(flags);
+} /* cy_flush_chars */
+
+
+/* This routine gets called when tty_write has put something into
+    the write_queue.  If the port is not already transmitting stuff,
+    start it off by enabling interrupts.  The interrupt service
+    routine will then ensure that the characters are sent.  If the
+    port is already active, there is no need to kick it.
+ */
+static int
+cy_write(struct tty_struct * tty,
+           const unsigned char *buf, int count)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+  int c, total = 0;
+
+#ifdef SERIAL_DEBUG_IO
+    printk("cy_write %s\n", tty->name); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_write")){
+	return 0;
+    }
+	
+    if (!tty || !info->xmit_buf || !tmp_buf){
+        return 0;
+    }
+
+    while (1) {
+	    local_irq_save(flags);
+	    c = min_t(int, count, min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
+				      SERIAL_XMIT_SIZE - info->xmit_head));
+	    if (c <= 0) {
+		    local_irq_restore(flags);
+		    break;
+	    }
+
+	    memcpy(info->xmit_buf + info->xmit_head, buf, c);
+	    info->xmit_head = (info->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+	    info->xmit_cnt += c;
+	    local_irq_restore(flags);
+
+	    buf += c;
+	    count -= c;
+	    total += c;
+    }
+
+    if (info->xmit_cnt
+    && !tty->stopped
+    && !tty->hw_stopped ) {
+        start_xmit(info);
+    }
+    return total;
+} /* cy_write */
+
+
+static int
+cy_write_room(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  int	ret;
+				
+#ifdef SERIAL_DEBUG_IO
+    printk("cy_write_room %s\n", tty->name); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_write_room"))
+	return 0;
+    ret = PAGE_SIZE - info->xmit_cnt - 1;
+    if (ret < 0)
+	ret = 0;
+    return ret;
+} /* cy_write_room */
+
+
+static int
+cy_chars_in_buffer(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+				
+#ifdef SERIAL_DEBUG_IO
+    printk("cy_chars_in_buffer %s %d\n", tty->name, info->xmit_cnt); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_chars_in_buffer"))
+	return 0;
+
+    return info->xmit_cnt;
+} /* cy_chars_in_buffer */
+
+
+static void
+cy_flush_buffer(struct tty_struct *tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+				
+#ifdef SERIAL_DEBUG_IO
+    printk("cy_flush_buffer %s\n", tty->name); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_flush_buffer"))
+	return;
+    local_irq_save(flags);
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+    local_irq_restore(flags);
+    tty_wakeup(tty);
+} /* cy_flush_buffer */
+
+
+/* This routine is called by the upper-layer tty layer to signal
+   that incoming characters should be throttled or that the
+   throttle should be released.
+ */
+static void
+cy_throttle(struct tty_struct * tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  int channel;
+
+#ifdef SERIAL_DEBUG_THROTTLE
+  char buf[64];
+	
+    printk("throttle %s: %d....\n", tty_name(tty, buf),
+	   tty->ldisc.chars_in_buffer(tty));
+    printk("cy_throttle %s\n", tty->name);
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_nthrottle")){
+	    return;
+    }
+
+    if (I_IXOFF(tty)) {
+	info->x_char = STOP_CHAR(tty);
+	    /* Should use the "Send Special Character" feature!!! */
+    }
+
+    channel = info->line;
+
+    local_irq_save(flags);
+	base_addr[CyCAR] = (u_char)channel;
+	base_addr[CyMSVR1] = 0;
+    local_irq_restore(flags);
+
+    return;
+} /* cy_throttle */
+
+
+static void
+cy_unthrottle(struct tty_struct * tty)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+  unsigned long flags;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  int channel;
+
+#ifdef SERIAL_DEBUG_THROTTLE
+  char buf[64];
+	
+    printk("throttle %s: %d....\n", tty_name(tty, buf),
+	   tty->ldisc.chars_in_buffer(tty));
+    printk("cy_unthrottle %s\n", tty->name);
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_nthrottle")){
+	    return;
+    }
+
+    if (I_IXOFF(tty)) {
+	info->x_char = START_CHAR(tty);
+	/* Should use the "Send Special Character" feature!!! */
+    }
+
+    channel = info->line;
+
+    local_irq_save(flags);
+	base_addr[CyCAR] = (u_char)channel;
+	base_addr[CyMSVR1] = CyRTS;
+    local_irq_restore(flags);
+
+    return;
+} /* cy_unthrottle */
+
+static int
+get_serial_info(struct cyclades_port * info,
+                           struct serial_struct * retinfo)
+{
+  struct serial_struct tmp;
+
+/* CP('g'); */
+    if (!retinfo)
+            return -EFAULT;
+    memset(&tmp, 0, sizeof(tmp));
+    tmp.type = info->type;
+    tmp.line = info->line;
+    tmp.port = info->line;
+    tmp.irq = 0;
+    tmp.flags = info->flags;
+    tmp.baud_base = 0;          /*!!!*/
+    tmp.close_delay = info->close_delay;
+    tmp.custom_divisor = 0;     /*!!!*/
+    tmp.hub6 = 0;               /*!!!*/
+    return copy_to_user(retinfo,&tmp,sizeof(*retinfo)) ? -EFAULT : 0;
+} /* get_serial_info */
+
+static int
+set_serial_info(struct cyclades_port * info,
+                           struct serial_struct * new_info)
+{
+  struct serial_struct new_serial;
+  struct cyclades_port old_info;
+
+/* CP('s'); */
+    if (!new_info)
+	    return -EFAULT;
+    if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+	    return -EFAULT;
+    old_info = *info;
+
+    if (!capable(CAP_SYS_ADMIN)) {
+	    if ((new_serial.close_delay != info->close_delay) ||
+		((new_serial.flags & ASYNC_FLAGS & ~ASYNC_USR_MASK) !=
+		 (info->flags & ASYNC_FLAGS & ~ASYNC_USR_MASK)))
+		    return -EPERM;
+	    info->flags = ((info->flags & ~ASYNC_USR_MASK) |
+			   (new_serial.flags & ASYNC_USR_MASK));
+	    goto check_and_exit;
+    }
+
+
+    /*
+     * OK, past this point, all the error checking has been done.
+     * At this point, we start making changes.....
+     */
+
+    info->flags = ((info->flags & ~ASYNC_FLAGS) |
+		    (new_serial.flags & ASYNC_FLAGS));
+    info->close_delay = new_serial.close_delay;
+
+
+check_and_exit:
+    if (info->flags & ASYNC_INITIALIZED){
+	config_setup(info);
+	return 0;
+    }else{
+        return startup(info);
+    }
+} /* set_serial_info */
+
+static int
+cy_tiocmget(struct tty_struct *tty, struct file *file)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+  int channel;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  unsigned long flags;
+  unsigned char status;
+  unsigned int result;
+
+    channel = info->line;
+
+    local_irq_save(flags);
+        base_addr[CyCAR] = (u_char)channel;
+        status = base_addr[CyMSVR1] | base_addr[CyMSVR2];
+    local_irq_restore(flags);
+
+    return    ((status  & CyRTS) ? TIOCM_RTS : 0)
+            | ((status  & CyDTR) ? TIOCM_DTR : 0)
+            | ((status  & CyDCD) ? TIOCM_CAR : 0)
+            | ((status  & CyDSR) ? TIOCM_DSR : 0)
+            | ((status  & CyCTS) ? TIOCM_CTS : 0);
+} /* cy_tiocmget */
+
+static int
+cy_tiocmset(struct tty_struct *tty, struct file *file,
+	    unsigned int set, unsigned int clear)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+  int channel;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  unsigned long flags;
+  unsigned int arg;
+	  
+    channel = info->line;
+
+	if (set & TIOCM_RTS){
+	    local_irq_save(flags);
+		base_addr[CyCAR] = (u_char)channel;
+		base_addr[CyMSVR1] = CyRTS;
+	    local_irq_restore(flags);
+	}
+	if (set & TIOCM_DTR){
+	    local_irq_save(flags);
+	    base_addr[CyCAR] = (u_char)channel;
+/* CP('S');CP('2'); */
+	    base_addr[CyMSVR2] = CyDTR;
+#ifdef SERIAL_DEBUG_DTR
+            printk("cyc: %d: raising DTR\n", __LINE__);
+            printk("     status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+	    local_irq_restore(flags);
+	}
+
+	if (clear & TIOCM_RTS){
+	    local_irq_save(flags);
+		base_addr[CyCAR] = (u_char)channel;
+		base_addr[CyMSVR1] = 0;
+	    local_irq_restore(flags);
+	}
+	if (clear & TIOCM_DTR){
+	    local_irq_save(flags);
+	    base_addr[CyCAR] = (u_char)channel;
+/* CP('C');CP('2'); */
+	    base_addr[CyMSVR2] = 0;
+#ifdef SERIAL_DEBUG_DTR
+            printk("cyc: %d: dropping DTR\n", __LINE__);
+            printk("     status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+	    local_irq_restore(flags);
+	}
+
+    return 0;
+} /* set_modem_info */
+
+static void
+send_break( struct cyclades_port * info, int duration)
+{ /* Let the transmit ISR take care of this (since it
+     requires stuffing characters into the output stream).
+   */
+    info->x_break = duration;
+    if (!info->xmit_cnt ) {
+	start_xmit(info);
+    }
+} /* send_break */
+
+static int
+get_mon_info(struct cyclades_port * info, struct cyclades_monitor * mon)
+{
+
+   if (copy_to_user(mon, &info->mon, sizeof(struct cyclades_monitor)))
+	   return -EFAULT;
+   info->mon.int_count  = 0;
+   info->mon.char_count = 0;
+   info->mon.char_max   = 0;
+   info->mon.char_last  = 0;
+   return 0;
+}
+
+static int
+set_threshold(struct cyclades_port * info, unsigned long *arg)
+{
+   volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+   unsigned long value;
+   int channel;
+   
+   if (get_user(value, arg))
+	   return -EFAULT;
+
+   channel = info->line;
+   info->cor4 &= ~CyREC_FIFO;
+   info->cor4 |= value & CyREC_FIFO;
+   base_addr[CyCOR4] = info->cor4;
+   return 0;
+}
+
+static int
+get_threshold(struct cyclades_port * info, unsigned long *value)
+{
+   volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+   int channel;
+   unsigned long tmp;
+   
+   channel = info->line;
+
+   tmp = base_addr[CyCOR4] & CyREC_FIFO;
+   return put_user(tmp,value);
+}
+
+static int
+set_default_threshold(struct cyclades_port * info, unsigned long *arg)
+{
+   unsigned long value;
+
+   if (get_user(value, arg))
+	return -EFAULT;
+
+   info->default_threshold = value & 0x0f;
+   return 0;
+}
+
+static int
+get_default_threshold(struct cyclades_port * info, unsigned long *value)
+{
+   return put_user(info->default_threshold,value);
+}
+
+static int
+set_timeout(struct cyclades_port * info, unsigned long *arg)
+{
+   volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+   int channel;
+   unsigned long value;
+
+   if (get_user(value, arg))
+	   return -EFAULT;
+   
+   channel = info->line;
+
+   base_addr[CyRTPRL] = value & 0xff;
+   base_addr[CyRTPRH] = (value >> 8) & 0xff;
+   return 0;
+}
+
+static int
+get_timeout(struct cyclades_port * info, unsigned long *value)
+{
+   volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+   int channel;
+   unsigned long tmp;
+   
+   channel = info->line;
+
+   tmp = base_addr[CyRTPRL];
+   return put_user(tmp,value);
+}
+
+static int
+set_default_timeout(struct cyclades_port * info, unsigned long value)
+{
+   info->default_timeout = value & 0xff;
+   return 0;
+}
+
+static int
+get_default_timeout(struct cyclades_port * info, unsigned long *value)
+{
+   return put_user(info->default_timeout,value);
+}
+
+static int
+cy_ioctl(struct tty_struct *tty, struct file * file,
+            unsigned int cmd, unsigned long arg)
+{
+  unsigned long val;
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+  int ret_val = 0;
+
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_ioctl %s, cmd = %x arg = %lx\n", tty->name, cmd, arg); /* */
+#endif
+
+    switch (cmd) {
+        case CYGETMON:
+            ret_val = get_mon_info(info, (struct cyclades_monitor *)arg);
+	    break;
+        case CYGETTHRESH:
+	    ret_val = get_threshold(info, (unsigned long *)arg);
+ 	    break;
+        case CYSETTHRESH:
+            ret_val = set_threshold(info, (unsigned long *)arg);
+	    break;
+        case CYGETDEFTHRESH:
+	    ret_val = get_default_threshold(info, (unsigned long *)arg);
+ 	    break;
+        case CYSETDEFTHRESH:
+            ret_val = set_default_threshold(info, (unsigned long *)arg);
+	    break;
+        case CYGETTIMEOUT:
+	    ret_val = get_timeout(info, (unsigned long *)arg);
+ 	    break;
+        case CYSETTIMEOUT:
+            ret_val = set_timeout(info, (unsigned long *)arg);
+	    break;
+        case CYGETDEFTIMEOUT:
+	    ret_val = get_default_timeout(info, (unsigned long *)arg);
+ 	    break;
+        case CYSETDEFTIMEOUT:
+            ret_val = set_default_timeout(info, (unsigned long)arg);
+	    break;
+        case TCSBRK:    /* SVID version: non-zero arg --> no break */
+	    ret_val = tty_check_change(tty);
+	    if (ret_val)
+		    break;
+            tty_wait_until_sent(tty,0);
+            if (!arg)
+                send_break(info, HZ/4); /* 1/4 second */
+            break;
+        case TCSBRKP:   /* support for POSIX tcsendbreak() */
+	    ret_val = tty_check_change(tty);
+	    if (ret_val)
+		break;
+            tty_wait_until_sent(tty,0);
+            send_break(info, arg ? arg*(HZ/10) : HZ/4);
+            break;
+
+/* The following commands are incompletely implemented!!! */
+        case TIOCGSOFTCAR:
+            ret_val = put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *) arg);
+            break;
+        case TIOCSSOFTCAR:
+            ret_val = get_user(val, (unsigned long *) arg);
+	    if (ret_val)
+		    break;
+            tty->termios->c_cflag =
+                    ((tty->termios->c_cflag & ~CLOCAL) | (val ? CLOCAL : 0));
+            break;
+        case TIOCGSERIAL:
+            ret_val = get_serial_info(info, (struct serial_struct *) arg);
+            break;
+        case TIOCSSERIAL:
+            ret_val = set_serial_info(info,
+                                   (struct serial_struct *) arg);
+            break;
+        default:
+	    ret_val = -ENOIOCTLCMD;
+    }
+
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_ioctl done\n");
+#endif
+
+    return ret_val;
+} /* cy_ioctl */
+
+
+
+
+static void
+cy_set_termios(struct tty_struct *tty, struct termios * old_termios)
+{
+  struct cyclades_port *info = (struct cyclades_port *)tty->driver_data;
+
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_set_termios %s\n", tty->name);
+#endif
+
+    if (tty->termios->c_cflag == old_termios->c_cflag)
+        return;
+    config_setup(info);
+
+    if ((old_termios->c_cflag & CRTSCTS) &&
+        !(tty->termios->c_cflag & CRTSCTS)) {
+            tty->stopped = 0;
+            cy_start(tty);
+    }
+#ifdef tytso_patch_94Nov25_1726
+    if (!(old_termios->c_cflag & CLOCAL) &&
+        (tty->termios->c_cflag & CLOCAL))
+            wake_up_interruptible(&info->open_wait);
+#endif
+
+    return;
+} /* cy_set_termios */
+
+
+static void
+cy_close(struct tty_struct * tty, struct file * filp)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+
+/* CP('C'); */
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_close %s\n", tty->name);
+#endif
+
+    if (!info
+    || serial_paranoia_check(info, tty->name, "cy_close")){
+        return;
+    }
+#ifdef SERIAL_DEBUG_OPEN
+    printk("cy_close %s, count = %d\n", tty->name, info->count);
+#endif
+
+    if ((tty->count == 1) && (info->count != 1)) {
+	/*
+	 * Uh, oh.  tty->count is 1, which means that the tty
+	 * structure will be freed.  Info->count should always
+	 * be one in these conditions.  If it's greater than
+	 * one, we've got real problems, since it means the
+	 * serial port won't be shutdown.
+	 */
+	printk("cy_close: bad serial port count; tty->count is 1, "
+	   "info->count is %d\n", info->count);
+	info->count = 1;
+    }
+#ifdef SERIAL_DEBUG_COUNT
+    printk("cyc: %d: decrementing count to %d\n", __LINE__, info->count - 1);
+#endif
+    if (--info->count < 0) {
+	printk("cy_close: bad serial port count for ttys%d: %d\n",
+	       info->line, info->count);
+#ifdef SERIAL_DEBUG_COUNT
+    printk("cyc: %d: setting count to 0\n", __LINE__);
+#endif
+	info->count = 0;
+    }
+    if (info->count)
+	return;
+    info->flags |= ASYNC_CLOSING;
+    if (info->flags & ASYNC_INITIALIZED)
+	tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */
+    shutdown(info);
+    if (tty->driver->flush_buffer)
+	tty->driver->flush_buffer(tty);
+    tty_ldisc_flush(tty);
+    info->event = 0;
+    info->tty = 0;
+    if (info->blocked_open) {
+	if (info->close_delay) {
+	    msleep_interruptible(jiffies_to_msecs(info->close_delay));
+	}
+	wake_up_interruptible(&info->open_wait);
+    }
+    info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+    wake_up_interruptible(&info->close_wait);
+
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_close done\n");
+#endif
+
+    return;
+} /* cy_close */
+
+/*
+ * cy_hangup() --- called by tty_hangup() when a hangup is signaled.
+ */
+void
+cy_hangup(struct tty_struct *tty)
+{
+  struct cyclades_port * info = (struct cyclades_port *)tty->driver_data;
+	
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_hangup %s\n", tty->name); /* */
+#endif
+
+    if (serial_paranoia_check(info, tty->name, "cy_hangup"))
+	return;
+    
+    shutdown(info);
+#if 0
+    info->event = 0;
+    info->count = 0;
+#ifdef SERIAL_DEBUG_COUNT
+    printk("cyc: %d: setting count to 0\n", __LINE__);
+#endif
+    info->tty = 0;
+#endif
+    info->flags &= ~ASYNC_NORMAL_ACTIVE;
+    wake_up_interruptible(&info->open_wait);
+} /* cy_hangup */
+
+
+
+/*
+ * ------------------------------------------------------------
+ * cy_open() and friends
+ * ------------------------------------------------------------
+ */
+
+static int
+block_til_ready(struct tty_struct *tty, struct file * filp,
+                           struct cyclades_port *info)
+{
+  DECLARE_WAITQUEUE(wait, current);
+  unsigned long flags;
+  int channel;
+  int retval;
+  volatile u_char *base_addr = (u_char *)BASE_ADDR;
+
+    /*
+     * If the device is in the middle of being closed, then block
+     * until it's done, and then try again.
+     */
+    if (info->flags & ASYNC_CLOSING) {
+	interruptible_sleep_on(&info->close_wait);
+	if (info->flags & ASYNC_HUP_NOTIFY){
+	    return -EAGAIN;
+	}else{
+	    return -ERESTARTSYS;
+	}
+    }
+
+    /*
+     * If non-blocking mode is set, then make the check up front
+     * and then exit.
+     */
+    if (filp->f_flags & O_NONBLOCK) {
+	info->flags |= ASYNC_NORMAL_ACTIVE;
+	return 0;
+    }
+
+    /*
+     * 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, info->count is dropped by one, so that
+     * cy_close() knows when to free things.  We restore it upon
+     * exit, either normal or abnormal.
+     */
+    retval = 0;
+    add_wait_queue(&info->open_wait, &wait);
+#ifdef SERIAL_DEBUG_OPEN
+    printk("block_til_ready before block: %s, count = %d\n",
+	   tty->name, info->count);/**/
+#endif
+    info->count--;
+#ifdef SERIAL_DEBUG_COUNT
+    printk("cyc: %d: decrementing count to %d\n", __LINE__, info->count);
+#endif
+    info->blocked_open++;
+
+    channel = info->line;
+
+    while (1) {
+	local_irq_save(flags);
+	base_addr[CyCAR] = (u_char)channel;
+	base_addr[CyMSVR1] = CyRTS;
+/* CP('S');CP('4'); */
+	base_addr[CyMSVR2] = CyDTR;
+#ifdef SERIAL_DEBUG_DTR
+	printk("cyc: %d: raising DTR\n", __LINE__);
+	printk("     status: 0x%x, 0x%x\n", base_addr[CyMSVR1], base_addr[CyMSVR2]);
+#endif
+	local_irq_restore(flags);
+	set_current_state(TASK_INTERRUPTIBLE);
+	if (tty_hung_up_p(filp)
+	|| !(info->flags & ASYNC_INITIALIZED) ){
+	    if (info->flags & ASYNC_HUP_NOTIFY) {
+		retval = -EAGAIN;
+	    }else{
+		retval = -ERESTARTSYS;
+	    }
+	    break;
+	}
+	local_irq_save(flags);
+	    base_addr[CyCAR] = (u_char)channel;
+/* CP('L');CP1(1 && C_CLOCAL(tty)); CP1(1 && (base_addr[CyMSVR1] & CyDCD) ); */
+	    if (!(info->flags & ASYNC_CLOSING)
+	    && (C_CLOCAL(tty)
+	        || (base_addr[CyMSVR1] & CyDCD))) {
+		    local_irq_restore(flags);
+		    break;
+	    }
+	local_irq_restore(flags);
+	if (signal_pending(current)) {
+	    retval = -ERESTARTSYS;
+	    break;
+	}
+#ifdef SERIAL_DEBUG_OPEN
+	printk("block_til_ready blocking: %s, count = %d\n",
+	       tty->name, info->count);/**/
+#endif
+	schedule();
+    }
+    current->state = TASK_RUNNING;
+    remove_wait_queue(&info->open_wait, &wait);
+    if (!tty_hung_up_p(filp)){
+	info->count++;
+#ifdef SERIAL_DEBUG_COUNT
+    printk("cyc: %d: incrementing count to %d\n", __LINE__, info->count);
+#endif
+    }
+    info->blocked_open--;
+#ifdef SERIAL_DEBUG_OPEN
+    printk("block_til_ready after blocking: %s, count = %d\n",
+	   tty->name, info->count);/**/
+#endif
+    if (retval)
+	    return retval;
+    info->flags |= ASYNC_NORMAL_ACTIVE;
+    return 0;
+} /* block_til_ready */
+
+/*
+ * This routine is called whenever a serial port is opened.  It
+ * performs the serial-specific initialization for the tty structure.
+ */
+int
+cy_open(struct tty_struct *tty, struct file * filp)
+{
+  struct cyclades_port  *info;
+  int retval, line;
+
+/* CP('O'); */
+    line = tty->index;
+    if ((line < 0) || (NR_PORTS <= line)){
+        return -ENODEV;
+    }
+    info = &cy_port[line];
+    if (info->line < 0){
+        return -ENODEV;
+    }
+#ifdef SERIAL_DEBUG_OTHER
+    printk("cy_open %s\n", tty->name); /* */
+#endif
+    if (serial_paranoia_check(info, tty->name, "cy_open")){
+        return -ENODEV;
+    }
+#ifdef SERIAL_DEBUG_OPEN
+    printk("cy_open %s, count = %d\n", tty->name, info->count);/**/
+#endif
+    info->count++;
+#ifdef SERIAL_DEBUG_COUNT
+    printk("cyc: %d: incrementing count to %d\n", __LINE__, info->count);
+#endif
+    tty->driver_data = info;
+    info->tty = tty;
+
+    if (!tmp_buf) {
+	tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL);
+	if (!tmp_buf){
+	    return -ENOMEM;
+        }
+    }
+
+    /*
+     * Start up serial port
+     */
+    retval = startup(info);
+    if (retval){
+	return retval;
+    }
+
+    retval = block_til_ready(tty, filp, info);
+    if (retval) {
+#ifdef SERIAL_DEBUG_OPEN
+	printk("cy_open returning after block_til_ready with %d\n",
+	       retval);
+#endif
+	return retval;
+    }
+
+#ifdef SERIAL_DEBUG_OPEN
+    printk("cy_open done\n");/**/
+#endif
+    return 0;
+} /* cy_open */
+
+
+
+/*
+ * ---------------------------------------------------------------------
+ * serial167_init() and friends
+ *
+ * serial167_init() is called at boot-time to initialize the serial driver.
+ * ---------------------------------------------------------------------
+ */
+
+/*
+ * This routine prints out the appropriate serial driver version
+ * number, and identifies which options were configured into this
+ * driver.
+ */
+static void
+show_version(void)
+{
+    printk("MVME166/167 cd2401 driver\n");
+} /* show_version */
+
+/* initialize chips on card -- return number of valid
+   chips (which is number of ports/4) */
+
+/*
+ * This initialises the hardware to a reasonable state.  It should
+ * probe the chip first so as to copy 166-Bug setup as a default for
+ * port 0.  It initialises CMR to CyASYNC; that is never done again, so
+ * as to limit the number of CyINIT_CHAN commands in normal running.
+ *
+ * ... I wonder what I should do if this fails ...
+ */
+
+void
+mvme167_serial_console_setup(int cflag)
+{
+	volatile unsigned char* base_addr = (u_char *)BASE_ADDR;
+	int ch;
+	u_char spd;
+	u_char rcor, rbpr, badspeed = 0;
+	unsigned long flags;
+
+	local_irq_save(flags);
+
+	/*
+	 * First probe channel zero of the chip, to see what speed has
+	 * been selected.
+	 */
+
+	base_addr[CyCAR] = 0;
+
+	rcor = base_addr[CyRCOR] << 5;
+	rbpr = base_addr[CyRBPR];
+
+	for (spd = 0; spd < sizeof(baud_bpr); spd++)
+		if (rbpr == baud_bpr[spd] && rcor == baud_co[spd])
+			break;
+	if (spd >= sizeof(baud_bpr)) {
+		spd = 14;	/* 19200 */
+		badspeed = 1;	/* Failed to identify speed */
+	}
+	initial_console_speed = spd;
+
+	/* OK, we have chosen a speed, now reset and reinitialise */
+
+        my_udelay(20000L);	/* Allow time for any active o/p to complete */
+        if(base_addr[CyCCR] != 0x00){
+            local_irq_restore(flags);
+            /* printk(" chip is never idle (CCR != 0)\n"); */
+            return;
+        }
+
+        base_addr[CyCCR] = CyCHIP_RESET;	/* Reset the chip */
+        my_udelay(1000L);
+
+        if(base_addr[CyGFRCR] == 0x00){
+            local_irq_restore(flags);
+            /* printk(" chip is not responding (GFRCR stayed 0)\n"); */
+            return;
+        }
+
+	/*
+	 * System clock is 20Mhz, divided by 2048, so divide by 10 for a 1.0ms
+	 * tick
+	 */
+
+	base_addr[CyTPR] = 10;
+
+	base_addr[CyPILR1] = 0x01;    /* Interrupt level for modem change */
+	base_addr[CyPILR2] = 0x02;    /* Interrupt level for tx ints */
+	base_addr[CyPILR3] = 0x03;    /* Interrupt level for rx ints */
+
+	/*
+	 * Attempt to set up all channels to something reasonable, and
+	 * bang out a INIT_CHAN command.  We should then be able to limit
+	 * the ammount of fiddling we have to do in normal running.
+	 */
+
+	for (ch = 3; ch >= 0 ; ch--) {
+		base_addr[CyCAR] = (u_char)ch;
+		base_addr[CyIER] = 0;
+		base_addr[CyCMR] = CyASYNC;
+		base_addr[CyLICR] = (u_char)ch << 2;
+		base_addr[CyLIVR] = 0x5c;
+		base_addr[CyTCOR] = baud_co[spd];
+		base_addr[CyTBPR] = baud_bpr[spd];
+		base_addr[CyRCOR] = baud_co[spd] >> 5;
+		base_addr[CyRBPR] = baud_bpr[spd];
+		base_addr[CySCHR1] = 'Q' & 0x1f;
+		base_addr[CySCHR2] = 'X' & 0x1f;
+		base_addr[CySCRL] = 0;
+		base_addr[CySCRH] = 0;
+		base_addr[CyCOR1] = Cy_8_BITS | CyPARITY_NONE;
+		base_addr[CyCOR2] = 0;
+		base_addr[CyCOR3] = Cy_1_STOP;
+		base_addr[CyCOR4] = baud_cor4[spd];
+		base_addr[CyCOR5] = 0;
+		base_addr[CyCOR6] = 0;
+		base_addr[CyCOR7] = 0;
+		base_addr[CyRTPRL] = 2;
+		base_addr[CyRTPRH] = 0;
+	        base_addr[CyMSVR1] = 0;
+	        base_addr[CyMSVR2] = 0;
+		write_cy_cmd(base_addr,CyINIT_CHAN|CyDIS_RCVR|CyDIS_XMTR);
+	}
+
+	/*
+	 * Now do specials for channel zero....
+	 */
+
+        base_addr[CyMSVR1] = CyRTS;
+        base_addr[CyMSVR2] = CyDTR;
+	base_addr[CyIER] = CyRxData;
+	write_cy_cmd(base_addr,CyENB_RCVR|CyENB_XMTR);
+
+	local_irq_restore(flags);
+
+	my_udelay(20000L);	/* Let it all settle down */
+
+        printk("CD2401 initialised,  chip is rev 0x%02x\n", base_addr[CyGFRCR]);
+	if (badspeed)
+        	printk("  WARNING:  Failed to identify line speed, rcor=%02x,rbpr=%02x\n",
+					rcor >> 5, rbpr);
+} /* serial_console_init */
+
+static struct tty_operations cy_ops = {
+	.open = cy_open,
+	.close = cy_close,
+	.write = cy_write,
+	.put_char = cy_put_char,
+	.flush_chars = cy_flush_chars,
+	.write_room = cy_write_room,
+	.chars_in_buffer = cy_chars_in_buffer,
+	.flush_buffer = cy_flush_buffer,
+	.ioctl = cy_ioctl,
+	.throttle = cy_throttle,
+	.unthrottle = cy_unthrottle,
+	.set_termios = cy_set_termios,
+	.stop = cy_stop,
+	.start = cy_start,
+	.hangup = cy_hangup,
+	.tiocmget = cy_tiocmget,
+	.tiocmset = cy_tiocmset,
+};
+/* The serial driver boot-time initialization code!
+    Hardware I/O ports are mapped to character special devices on a
+    first found, first allocated manner.  That is, this code searches
+    for Cyclom cards in the system.  As each is found, it is probed
+    to discover how many chips (and thus how many ports) are present.
+    These ports are mapped to the tty ports 64 and upward in monotonic
+    fashion.  If an 8-port card is replaced with a 16-port card, the
+    port mapping on a following card will shift.
+
+    This approach is different from what is used in the other serial
+    device driver because the Cyclom is more properly a multiplexer,
+    not just an aggregation of serial ports on one card.
+
+    If there are more cards with more ports than have been statically
+    allocated above, a warning is printed and the extra ports are ignored.
+ */
+static int __init
+serial167_init(void)
+{
+  struct cyclades_port *info;
+  int ret = 0;
+  int good_ports = 0;
+  int port_num = 0;
+  int index;
+  int DefSpeed;
+#ifdef notyet
+  struct sigaction sa;
+#endif
+
+    if (!(mvme16x_config &MVME16x_CONFIG_GOT_CD2401))
+	return 0;
+
+    cy_serial_driver = alloc_tty_driver(NR_PORTS);
+    if (!cy_serial_driver)
+	return -ENOMEM;
+
+#if 0
+scrn[1] = '\0';
+#endif
+
+    show_version();
+
+    /* Has "console=0,9600n8" been used in bootinfo to change speed? */
+    if (serial_console_cflag)
+	DefSpeed = serial_console_cflag & 0017;
+    else {
+	DefSpeed = initial_console_speed;
+	serial_console_info = &cy_port[0];
+	serial_console_cflag = DefSpeed | CS8;
+#if 0
+	serial_console = 64; /*callout_driver.minor_start*/
+#endif
+    }
+
+    /* Initialize the tty_driver structure */
+    
+    cy_serial_driver->owner = THIS_MODULE;
+    cy_serial_driver->devfs_name = "tts/";
+    cy_serial_driver->name = "ttyS";
+    cy_serial_driver->major = TTY_MAJOR;
+    cy_serial_driver->minor_start = 64;
+    cy_serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+    cy_serial_driver->subtype = SERIAL_TYPE_NORMAL;
+    cy_serial_driver->init_termios = tty_std_termios;
+    cy_serial_driver->init_termios.c_cflag =
+	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+    cy_serial_driver->flags = TTY_DRIVER_REAL_RAW;
+    tty_set_operations(cy_serial_driver, &cy_ops);
+
+    ret = tty_register_driver(cy_serial_driver);
+    if (ret) {
+	    printk(KERN_ERR "Couldn't register MVME166/7 serial driver\n");
+	    put_tty_driver(cy_serial_driver);
+	    return ret;
+    }
+
+    port_num = 0;
+    info = cy_port;
+    for (index = 0; index < 1; index++) {
+
+	good_ports = 4;
+
+	if(port_num < NR_PORTS){
+	    while( good_ports-- && port_num < NR_PORTS){
+		/*** initialize port ***/
+		info->magic = CYCLADES_MAGIC;
+		info->type = PORT_CIRRUS;
+		info->card = index;
+		info->line = port_num;
+		info->flags = STD_COM_FLAGS;
+		info->tty = 0;
+		info->xmit_fifo_size = 12;
+		info->cor1 = CyPARITY_NONE|Cy_8_BITS;
+		info->cor2 = CyETC;
+		info->cor3 = Cy_1_STOP;
+		info->cor4 = 0x08; /* _very_ small receive threshold */
+		info->cor5 = 0;
+		info->cor6 = 0;
+		info->cor7 = 0;
+		info->tbpr = baud_bpr[DefSpeed]; /* Tx BPR */
+		info->tco = baud_co[DefSpeed]; /* Tx CO */
+		info->rbpr = baud_bpr[DefSpeed]; /* Rx BPR */
+		info->rco = baud_co[DefSpeed] >> 5; /* Rx CO */
+		info->close_delay = 0;
+		info->x_char = 0;
+		info->event = 0;
+		info->count = 0;
+#ifdef SERIAL_DEBUG_COUNT
+    printk("cyc: %d: setting count to 0\n", __LINE__);
+#endif
+		info->blocked_open = 0;
+		info->default_threshold = 0;
+		info->default_timeout = 0;
+		INIT_WORK(&info->tqueue, do_softint, info);
+		init_waitqueue_head(&info->open_wait);
+		init_waitqueue_head(&info->close_wait);
+		/* info->session */
+		/* info->pgrp */
+/*** !!!!!!!! this may expose new bugs !!!!!!!!! *********/
+		info->read_status_mask = CyTIMEOUT| CySPECHAR| CyBREAK
+                                       | CyPARITY| CyFRAME| CyOVERRUN;
+		/* info->timeout */
+
+		printk("ttyS%d ", info->line);
+		port_num++;info++;
+		if(!(port_num & 7)){
+		    printk("\n               ");
+		}
+	    }
+	}
+	printk("\n");
+    }
+    while( port_num < NR_PORTS){
+	info->line = -1;
+	port_num++;info++;
+    }
+#ifdef CONFIG_REMOTE_DEBUG
+    debug_setup();
+#endif
+    ret = request_irq(MVME167_IRQ_SER_ERR, cd2401_rxerr_interrupt, 0,
+				"cd2401_errors", cd2401_rxerr_interrupt);
+    if (ret) {
+	    printk(KERN_ERR "Could't get cd2401_errors IRQ");
+	    goto cleanup_serial_driver;
+    }
+
+    ret = request_irq(MVME167_IRQ_SER_MODEM, cd2401_modem_interrupt, 0,
+				"cd2401_modem", cd2401_modem_interrupt);
+    if (ret) {
+	    printk(KERN_ERR "Could't get cd2401_modem IRQ");
+	    goto cleanup_irq_cd2401_errors;
+    }
+
+    ret = request_irq(MVME167_IRQ_SER_TX, cd2401_tx_interrupt, 0,
+				"cd2401_txints", cd2401_tx_interrupt);
+    if (ret) {
+	    printk(KERN_ERR "Could't get cd2401_txints IRQ");
+	    goto cleanup_irq_cd2401_modem;
+    }
+
+    ret = request_irq(MVME167_IRQ_SER_RX, cd2401_rx_interrupt, 0,
+				"cd2401_rxints", cd2401_rx_interrupt);
+    if (ret) {
+	    printk(KERN_ERR "Could't get cd2401_rxints IRQ");
+	    goto cleanup_irq_cd2401_txints;
+    }
+
+    /* Now we have registered the interrupt handlers, allow the interrupts */
+
+    pcc2chip[PccSCCMICR] = 0x15;		/* Serial ints are level 5 */
+    pcc2chip[PccSCCTICR] = 0x15;
+    pcc2chip[PccSCCRICR] = 0x15;
+
+    pcc2chip[PccIMLR] = 3;			/* Allow PCC2 ints above 3!? */
+
+    return 0;
+cleanup_irq_cd2401_txints:
+    free_irq(MVME167_IRQ_SER_TX, cd2401_tx_interrupt);
+cleanup_irq_cd2401_modem:
+    free_irq(MVME167_IRQ_SER_MODEM, cd2401_modem_interrupt);
+cleanup_irq_cd2401_errors:
+    free_irq(MVME167_IRQ_SER_ERR, cd2401_rxerr_interrupt);
+cleanup_serial_driver:
+    if (tty_unregister_driver(cy_serial_driver))
+	    printk(KERN_ERR "Couldn't unregister MVME166/7 serial driver\n");
+    put_tty_driver(cy_serial_driver);
+    return ret;
+} /* serial167_init */
+
+module_init(serial167_init);
+
+
+#ifdef CYCLOM_SHOW_STATUS
+static void
+show_status(int line_num)
+{
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  int channel;
+  struct cyclades_port * info;
+  unsigned long flags;
+
+    info = &cy_port[line_num];
+    channel = info->line;
+    printk("  channel %d\n", channel);/**/
+
+    printk(" cy_port\n");
+    printk("  card line flags = %d %d %x\n",
+                 info->card, info->line, info->flags);
+    printk("  *tty read_status_mask timeout xmit_fifo_size = %lx %x %x %x\n",
+                 (long)info->tty, info->read_status_mask,
+                 info->timeout, info->xmit_fifo_size);
+    printk("  cor1,cor2,cor3,cor4,cor5,cor6,cor7 = %x %x %x %x %x %x %x\n",
+             info->cor1, info->cor2, info->cor3, info->cor4, info->cor5,
+			info->cor6, info->cor7);
+    printk("  tbpr,tco,rbpr,rco = %d %d %d %d\n",
+             info->tbpr, info->tco, info->rbpr, info->rco);
+    printk("  close_delay event count = %d %d %d\n",
+             info->close_delay, info->event, info->count);
+    printk("  x_char blocked_open = %x %x\n",
+             info->x_char, info->blocked_open);
+    printk("  open_wait = %lx %lx %lx\n",
+             (long)info->open_wait);
+
+
+    local_irq_save(flags);
+
+/* Global Registers */
+
+	printk(" CyGFRCR %x\n", base_addr[CyGFRCR]);
+	printk(" CyCAR %x\n", base_addr[CyCAR]);
+	printk(" CyRISR %x\n", base_addr[CyRISR]);
+	printk(" CyTISR %x\n", base_addr[CyTISR]);
+	printk(" CyMISR %x\n", base_addr[CyMISR]);
+	printk(" CyRIR %x\n", base_addr[CyRIR]);
+	printk(" CyTIR %x\n", base_addr[CyTIR]);
+	printk(" CyMIR %x\n", base_addr[CyMIR]);
+	printk(" CyTPR %x\n", base_addr[CyTPR]);
+
+	base_addr[CyCAR] = (u_char)channel;
+
+/* Virtual Registers */
+
+#if 0
+	printk(" CyRIVR %x\n", base_addr[CyRIVR]);
+	printk(" CyTIVR %x\n", base_addr[CyTIVR]);
+	printk(" CyMIVR %x\n", base_addr[CyMIVR]);
+	printk(" CyMISR %x\n", base_addr[CyMISR]);
+#endif
+
+/* Channel Registers */
+
+	printk(" CyCCR %x\n", base_addr[CyCCR]);
+	printk(" CyIER %x\n", base_addr[CyIER]);
+	printk(" CyCOR1 %x\n", base_addr[CyCOR1]);
+	printk(" CyCOR2 %x\n", base_addr[CyCOR2]);
+	printk(" CyCOR3 %x\n", base_addr[CyCOR3]);
+	printk(" CyCOR4 %x\n", base_addr[CyCOR4]);
+	printk(" CyCOR5 %x\n", base_addr[CyCOR5]);
+#if 0
+	printk(" CyCCSR %x\n", base_addr[CyCCSR]);
+	printk(" CyRDCR %x\n", base_addr[CyRDCR]);
+#endif
+	printk(" CySCHR1 %x\n", base_addr[CySCHR1]);
+	printk(" CySCHR2 %x\n", base_addr[CySCHR2]);
+#if 0
+	printk(" CySCHR3 %x\n", base_addr[CySCHR3]);
+	printk(" CySCHR4 %x\n", base_addr[CySCHR4]);
+	printk(" CySCRL %x\n", base_addr[CySCRL]);
+	printk(" CySCRH %x\n", base_addr[CySCRH]);
+	printk(" CyLNC %x\n", base_addr[CyLNC]);
+	printk(" CyMCOR1 %x\n", base_addr[CyMCOR1]);
+	printk(" CyMCOR2 %x\n", base_addr[CyMCOR2]);
+#endif
+	printk(" CyRTPRL %x\n", base_addr[CyRTPRL]);
+	printk(" CyRTPRH %x\n", base_addr[CyRTPRH]);
+	printk(" CyMSVR1 %x\n", base_addr[CyMSVR1]);
+	printk(" CyMSVR2 %x\n", base_addr[CyMSVR2]);
+	printk(" CyRBPR %x\n", base_addr[CyRBPR]);
+	printk(" CyRCOR %x\n", base_addr[CyRCOR]);
+	printk(" CyTBPR %x\n", base_addr[CyTBPR]);
+	printk(" CyTCOR %x\n", base_addr[CyTCOR]);
+
+    local_irq_restore(flags);
+} /* show_status */
+#endif
+
+
+#if 0
+/* Dummy routine in mvme16x/config.c for now */
+
+/* Serial console setup. Called from linux/init/main.c */
+
+void console_setup(char *str, int *ints)
+{
+	char *s;
+	int baud, bits, parity;
+	int cflag = 0;
+
+	/* Sanity check. */
+	if (ints[0] > 3 || ints[1] > 3) return;
+
+	/* Get baud, bits and parity */
+	baud = 2400;
+	bits = 8;
+	parity = 'n';
+	if (ints[2]) baud = ints[2];
+	if ((s = strchr(str, ','))) {
+		do {
+			s++;
+		} while(*s >= '0' && *s <= '9');
+		if (*s) parity = *s++;
+		if (*s) bits   = *s - '0';
+	}
+
+	/* Now construct a cflag setting. */
+	switch(baud) {
+		case 1200:
+			cflag |= B1200;
+			break;
+		case 9600:
+			cflag |= B9600;
+			break;
+		case 19200:
+			cflag |= B19200;
+			break;
+		case 38400:
+			cflag |= B38400;
+			break;
+		case 2400:
+		default:
+			cflag |= B2400;
+			break;
+	}
+	switch(bits) {
+		case 7:
+			cflag |= CS7;
+			break;
+		default:
+		case 8:
+			cflag |= CS8;
+			break;
+	}
+	switch(parity) {
+		case 'o': case 'O':
+			cflag |= PARODD;
+			break;
+		case 'e': case 'E':
+			cflag |= PARENB;
+			break;
+	}
+
+	serial_console_info = &cy_port[ints[1]];
+	serial_console_cflag = cflag;
+	serial_console = ints[1] + 64; /*callout_driver.minor_start*/
+}
+#endif
+
+/*
+ * The following is probably out of date for 2.1.x serial console stuff.
+ *
+ * The console is registered early on from arch/m68k/kernel/setup.c, and
+ * it therefore relies on the chip being setup correctly by 166-Bug.  This
+ * seems reasonable, as the serial port has been used to invoke the system
+ * boot.  It also means that this function must not rely on any data
+ * initialisation performed by serial167_init() etc.
+ *
+ * Of course, once the console has been registered, we had better ensure
+ * that serial167_init() doesn't leave the chip non-functional.
+ *
+ * The console must be locked when we get here.
+ */
+
+void serial167_console_write(struct console *co, const char *str, unsigned count)
+{
+	volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+	unsigned long flags;
+	volatile u_char sink;
+	u_char ier;
+	int port;
+	u_char do_lf = 0;
+	int i = 0;
+
+	local_irq_save(flags);
+
+	/* Ensure transmitter is enabled! */
+
+	port = 0;
+	base_addr[CyCAR] = (u_char)port;
+	while (base_addr[CyCCR])
+		;
+	base_addr[CyCCR] = CyENB_XMTR;
+
+	ier = base_addr[CyIER];
+	base_addr[CyIER] = CyTxMpty;
+
+	while (1) {
+		if (pcc2chip[PccSCCTICR] & 0x20)
+		{
+			/* We have a Tx int. Acknowledge it */
+			sink = pcc2chip[PccTPIACKR];
+			if ((base_addr[CyLICR] >> 2) == port) {
+				if (i == count) {
+					/* Last char of string is now output */
+					base_addr[CyTEOIR] = CyNOTRANS;
+					break;
+				}
+				if (do_lf) {
+					base_addr[CyTDR] = '\n';
+					str++;
+					i++;
+					do_lf = 0;
+				}
+				else if (*str == '\n') {
+					base_addr[CyTDR] = '\r';
+					do_lf = 1;
+				}
+				else {
+					base_addr[CyTDR] = *str++;
+					i++;
+				}
+				base_addr[CyTEOIR] = 0;
+			}
+			else
+				base_addr[CyTEOIR] = CyNOTRANS;
+		}
+	}
+
+	base_addr[CyIER] = ier;
+
+	local_irq_restore(flags);
+}
+
+static struct tty_driver *serial167_console_device(struct console *c, int *index)
+{
+	*index = c->index;
+	return cy_serial_driver;
+}
+
+
+static int __init serial167_console_setup(struct console *co, char *options)
+{
+	return 0;
+}
+
+
+static struct console sercons = {
+	.name		= "ttyS",
+	.write		= serial167_console_write,
+	.device		= serial167_console_device,
+	.setup		= serial167_console_setup,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+};
+
+
+static int __init serial167_console_init(void)
+{
+	if (vme_brdtype == VME_TYPE_MVME166 ||
+			vme_brdtype == VME_TYPE_MVME167 ||
+			vme_brdtype == VME_TYPE_MVME177) {
+		mvme167_serial_console_setup(0);
+		register_console(&sercons);
+	}
+	return 0;
+}
+console_initcall(serial167_console_init);
+
+#ifdef CONFIG_REMOTE_DEBUG
+void putDebugChar (int c)
+{
+	volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+	unsigned long flags;
+	volatile u_char sink;
+	u_char ier;
+	int port;
+
+	local_irq_save(flags);
+
+	/* Ensure transmitter is enabled! */
+
+	port = DEBUG_PORT;
+	base_addr[CyCAR] = (u_char)port;
+	while (base_addr[CyCCR])
+		;
+	base_addr[CyCCR] = CyENB_XMTR;
+
+	ier = base_addr[CyIER];
+	base_addr[CyIER] = CyTxMpty;
+
+	while (1) {
+		if (pcc2chip[PccSCCTICR] & 0x20)
+		{
+			/* We have a Tx int. Acknowledge it */
+			sink = pcc2chip[PccTPIACKR];
+			if ((base_addr[CyLICR] >> 2) == port) {
+				base_addr[CyTDR] = c;
+				base_addr[CyTEOIR] = 0;
+				break;
+			}
+			else
+				base_addr[CyTEOIR] = CyNOTRANS;
+		}
+	}
+
+	base_addr[CyIER] = ier;
+
+	local_irq_restore(flags);
+}
+
+int getDebugChar()
+{
+	volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+	unsigned long flags;
+	volatile u_char sink;
+	u_char ier;
+	int port;
+	int i, c;
+
+	i = debugiq.out;
+	if (i != debugiq.in) {
+		c = debugiq.buf[i];
+		if (++i == DEBUG_LEN)
+			i = 0;
+		debugiq.out = i;
+		return c;
+	}
+	/* OK, nothing in queue, wait in poll loop */
+
+	local_irq_save(flags);
+
+	/* Ensure receiver is enabled! */
+
+	port = DEBUG_PORT;
+	base_addr[CyCAR] = (u_char)port;
+#if 0
+	while (base_addr[CyCCR])
+		;
+	base_addr[CyCCR] = CyENB_RCVR;
+#endif
+	ier = base_addr[CyIER];
+	base_addr[CyIER] = CyRxData;
+
+	while (1) {
+		if (pcc2chip[PccSCCRICR] & 0x20)
+		{
+			/* We have a Rx int. Acknowledge it */
+			sink = pcc2chip[PccRPIACKR];
+			if ((base_addr[CyLICR] >> 2) == port) {
+				int cnt = base_addr[CyRFOC];
+				while (cnt-- > 0)
+				{
+					c = base_addr[CyRDR];
+					if (c == 0)
+						printk ("!! debug char is null (cnt=%d) !!", cnt);
+					else
+						queueDebugChar (c);
+				}
+				base_addr[CyREOIR] = 0;
+				i = debugiq.out;
+				if (i == debugiq.in)
+					panic ("Debug input queue empty!");
+				c = debugiq.buf[i];
+				if (++i == DEBUG_LEN)
+					i = 0;
+				debugiq.out = i;
+				break;
+			}
+			else
+				base_addr[CyREOIR] = CyNOTRANS;
+		}
+	}
+
+	base_addr[CyIER] = ier;
+
+	local_irq_restore(flags);
+
+	return (c);
+}
+
+void queueDebugChar (int c)
+{
+	int i;
+
+	i = debugiq.in;
+	debugiq.buf[i] = c;
+	if (++i == DEBUG_LEN)
+		i = 0;
+	if (i != debugiq.out)
+		debugiq.in = i;
+}
+
+static void
+debug_setup()
+{
+  unsigned long flags;
+  volatile unsigned char *base_addr = (u_char *)BASE_ADDR;
+  int   i, cflag;
+
+    cflag = B19200;
+
+    local_irq_save(flags);
+
+    for (i = 0; i < 4; i++)
+    {
+	base_addr[CyCAR] = i;
+	base_addr[CyLICR] = i << 2;
+    }
+
+    debugiq.in = debugiq.out = 0;
+
+    base_addr[CyCAR] = DEBUG_PORT;
+
+    /* baud rate */
+    i = cflag & CBAUD;
+
+    base_addr[CyIER] = 0;
+
+    base_addr[CyCMR] = CyASYNC;
+    base_addr[CyLICR] = DEBUG_PORT << 2;
+    base_addr[CyLIVR] = 0x5c;
+
+    /* tx and rx baud rate */
+
+    base_addr[CyTCOR] = baud_co[i];
+    base_addr[CyTBPR] = baud_bpr[i];
+    base_addr[CyRCOR] = baud_co[i] >> 5;
+    base_addr[CyRBPR] = baud_bpr[i];
+
+    /* set line characteristics  according configuration */
+
+    base_addr[CySCHR1] = 0;
+    base_addr[CySCHR2] = 0;
+    base_addr[CySCRL] = 0;
+    base_addr[CySCRH] = 0;
+    base_addr[CyCOR1] = Cy_8_BITS | CyPARITY_NONE;
+    base_addr[CyCOR2] = 0;
+    base_addr[CyCOR3] = Cy_1_STOP;
+    base_addr[CyCOR4] = baud_cor4[i];
+    base_addr[CyCOR5] = 0;
+    base_addr[CyCOR6] = 0;
+    base_addr[CyCOR7] = 0;
+
+    write_cy_cmd(base_addr,CyINIT_CHAN);
+    write_cy_cmd(base_addr,CyENB_RCVR);
+
+    base_addr[CyCAR] = DEBUG_PORT; /* !!! Is this needed? */
+
+    base_addr[CyRTPRL] = 2;
+    base_addr[CyRTPRH] = 0;
+
+    base_addr[CyMSVR1] = CyRTS;
+    base_addr[CyMSVR2] = CyDTR;
+
+    base_addr[CyIER] = CyRxData;
+
+    local_irq_restore(flags);
+
+} /* debug_setup */
+
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/snsc.c b/drivers/char/snsc.c
new file mode 100644
index 0000000..ffb9143
--- /dev/null
+++ b/drivers/char/snsc.c
@@ -0,0 +1,448 @@
+/*
+ * SN Platform system controller communication support
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2004 Silicon Graphics, Inc. All rights reserved.
+ */
+
+/*
+ * System controller communication driver
+ *
+ * This driver allows a user process to communicate with the system
+ * controller (a.k.a. "IRouter") network in an SGI SN system.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/device.h>
+#include <linux/poll.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/sn/io.h>
+#include <asm/sn/sn_sal.h>
+#include <asm/sn/module.h>
+#include <asm/sn/geo.h>
+#include <asm/sn/nodepda.h>
+#include "snsc.h"
+
+#define SYSCTL_BASENAME	"snsc"
+
+#define SCDRV_BUFSZ	2048
+#define SCDRV_TIMEOUT	1000
+
+static irqreturn_t
+scdrv_interrupt(int irq, void *subch_data, struct pt_regs *regs)
+{
+	struct subch_data_s *sd = subch_data;
+	unsigned long flags;
+	int status;
+
+	spin_lock_irqsave(&sd->sd_rlock, flags);
+	spin_lock(&sd->sd_wlock);
+	status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch);
+
+	if (status > 0) {
+		if (status & SAL_IROUTER_INTR_RECV) {
+			wake_up(&sd->sd_rq);
+		}
+		if (status & SAL_IROUTER_INTR_XMIT) {
+			ia64_sn_irtr_intr_disable
+			    (sd->sd_nasid, sd->sd_subch,
+			     SAL_IROUTER_INTR_XMIT);
+			wake_up(&sd->sd_wq);
+		}
+	}
+	spin_unlock(&sd->sd_wlock);
+	spin_unlock_irqrestore(&sd->sd_rlock, flags);
+	return IRQ_HANDLED;
+}
+
+/*
+ * scdrv_open
+ *
+ * Reserve a subchannel for system controller communication.
+ */
+
+static int
+scdrv_open(struct inode *inode, struct file *file)
+{
+	struct sysctl_data_s *scd;
+	struct subch_data_s *sd;
+	int rv;
+
+	/* look up device info for this device file */
+	scd = container_of(inode->i_cdev, struct sysctl_data_s, scd_cdev);
+
+	/* allocate memory for subchannel data */
+	sd = kmalloc(sizeof (struct subch_data_s), GFP_KERNEL);
+	if (sd == NULL) {
+		printk("%s: couldn't allocate subchannel data\n",
+		       __FUNCTION__);
+		return -ENOMEM;
+	}
+
+	/* initialize subch_data_s fields */
+	memset(sd, 0, sizeof (struct subch_data_s));
+	sd->sd_nasid = scd->scd_nasid;
+	sd->sd_subch = ia64_sn_irtr_open(scd->scd_nasid);
+
+	if (sd->sd_subch < 0) {
+		kfree(sd);
+		printk("%s: couldn't allocate subchannel\n", __FUNCTION__);
+		return -EBUSY;
+	}
+
+	spin_lock_init(&sd->sd_rlock);
+	spin_lock_init(&sd->sd_wlock);
+	init_waitqueue_head(&sd->sd_rq);
+	init_waitqueue_head(&sd->sd_wq);
+	sema_init(&sd->sd_rbs, 1);
+	sema_init(&sd->sd_wbs, 1);
+
+	file->private_data = sd;
+
+	/* hook this subchannel up to the system controller interrupt */
+	rv = request_irq(SGI_UART_VECTOR, scdrv_interrupt,
+			 SA_SHIRQ | SA_INTERRUPT,
+			 SYSCTL_BASENAME, sd);
+	if (rv) {
+		ia64_sn_irtr_close(sd->sd_nasid, sd->sd_subch);
+		kfree(sd);
+		printk("%s: irq request failed (%d)\n", __FUNCTION__, rv);
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+/*
+ * scdrv_release
+ *
+ * Release a previously-reserved subchannel.
+ */
+
+static int
+scdrv_release(struct inode *inode, struct file *file)
+{
+	struct subch_data_s *sd = (struct subch_data_s *) file->private_data;
+	int rv;
+
+	/* free the interrupt */
+	free_irq(SGI_UART_VECTOR, sd);
+
+	/* ask SAL to close the subchannel */
+	rv = ia64_sn_irtr_close(sd->sd_nasid, sd->sd_subch);
+
+	kfree(sd);
+	return rv;
+}
+
+/*
+ * scdrv_read
+ *
+ * Called to read bytes from the open IRouter pipe.
+ *
+ */
+
+static inline int
+read_status_check(struct subch_data_s *sd, int *len)
+{
+	return ia64_sn_irtr_recv(sd->sd_nasid, sd->sd_subch, sd->sd_rb, len);
+}
+
+static ssize_t
+scdrv_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
+{
+	int status;
+	int len;
+	unsigned long flags;
+	struct subch_data_s *sd = (struct subch_data_s *) file->private_data;
+
+	/* try to get control of the read buffer */
+	if (down_trylock(&sd->sd_rbs)) {
+		/* somebody else has it now;
+		 * if we're non-blocking, then exit...
+		 */
+		if (file->f_flags & O_NONBLOCK) {
+			return -EAGAIN;
+		}
+		/* ...or if we want to block, then do so here */
+		if (down_interruptible(&sd->sd_rbs)) {
+			/* something went wrong with wait */
+			return -ERESTARTSYS;
+		}
+	}
+
+	/* anything to read? */
+	len = CHUNKSIZE;
+	spin_lock_irqsave(&sd->sd_rlock, flags);
+	status = read_status_check(sd, &len);
+
+	/* if not, and we're blocking I/O, loop */
+	while (status < 0) {
+		DECLARE_WAITQUEUE(wait, current);
+
+		if (file->f_flags & O_NONBLOCK) {
+			spin_unlock_irqrestore(&sd->sd_rlock, flags);
+			up(&sd->sd_rbs);
+			return -EAGAIN;
+		}
+
+		len = CHUNKSIZE;
+		set_current_state(TASK_INTERRUPTIBLE);
+		add_wait_queue(&sd->sd_rq, &wait);
+		spin_unlock_irqrestore(&sd->sd_rlock, flags);
+
+		schedule_timeout(SCDRV_TIMEOUT);
+
+		remove_wait_queue(&sd->sd_rq, &wait);
+		if (signal_pending(current)) {
+			/* wait was interrupted */
+			up(&sd->sd_rbs);
+			return -ERESTARTSYS;
+		}
+
+		spin_lock_irqsave(&sd->sd_rlock, flags);
+		status = read_status_check(sd, &len);
+	}
+	spin_unlock_irqrestore(&sd->sd_rlock, flags);
+
+	if (len > 0) {
+		/* we read something in the last read_status_check(); copy
+		 * it out to user space
+		 */
+		if (count < len) {
+			pr_debug("%s: only accepting %d of %d bytes\n",
+				 __FUNCTION__, (int) count, len);
+		}
+		len = min((int) count, len);
+		if (copy_to_user(buf, sd->sd_rb, len))
+			len = -EFAULT;
+	}
+
+	/* release the read buffer and wake anyone who might be
+	 * waiting for it
+	 */
+	up(&sd->sd_rbs);
+
+	/* return the number of characters read in */
+	return len;
+}
+
+/*
+ * scdrv_write
+ *
+ * Writes a chunk of an IRouter packet (or other system controller data)
+ * to the system controller.
+ *
+ */
+static inline int
+write_status_check(struct subch_data_s *sd, int count)
+{
+	return ia64_sn_irtr_send(sd->sd_nasid, sd->sd_subch, sd->sd_wb, count);
+}
+
+static ssize_t
+scdrv_write(struct file *file, const char __user *buf,
+	    size_t count, loff_t *f_pos)
+{
+	unsigned long flags;
+	int status;
+	struct subch_data_s *sd = (struct subch_data_s *) file->private_data;
+
+	/* try to get control of the write buffer */
+	if (down_trylock(&sd->sd_wbs)) {
+		/* somebody else has it now;
+		 * if we're non-blocking, then exit...
+		 */
+		if (file->f_flags & O_NONBLOCK) {
+			return -EAGAIN;
+		}
+		/* ...or if we want to block, then do so here */
+		if (down_interruptible(&sd->sd_wbs)) {
+			/* something went wrong with wait */
+			return -ERESTARTSYS;
+		}
+	}
+
+	count = min((int) count, CHUNKSIZE);
+	if (copy_from_user(sd->sd_wb, buf, count)) {
+		up(&sd->sd_wbs);
+		return -EFAULT;
+	}
+
+	/* try to send the buffer */
+	spin_lock_irqsave(&sd->sd_wlock, flags);
+	status = write_status_check(sd, count);
+
+	/* if we failed, and we want to block, then loop */
+	while (status <= 0) {
+		DECLARE_WAITQUEUE(wait, current);
+
+		if (file->f_flags & O_NONBLOCK) {
+			spin_unlock(&sd->sd_wlock);
+			up(&sd->sd_wbs);
+			return -EAGAIN;
+		}
+
+		set_current_state(TASK_INTERRUPTIBLE);
+		add_wait_queue(&sd->sd_wq, &wait);
+		spin_unlock_irqrestore(&sd->sd_wlock, flags);
+
+		schedule_timeout(SCDRV_TIMEOUT);
+
+		remove_wait_queue(&sd->sd_wq, &wait);
+		if (signal_pending(current)) {
+			/* wait was interrupted */
+			up(&sd->sd_wbs);
+			return -ERESTARTSYS;
+		}
+
+		spin_lock_irqsave(&sd->sd_wlock, flags);
+		status = write_status_check(sd, count);
+	}
+	spin_unlock_irqrestore(&sd->sd_wlock, flags);
+
+	/* release the write buffer and wake anyone who's waiting for it */
+	up(&sd->sd_wbs);
+
+	/* return the number of characters accepted (should be the complete
+	 * "chunk" as requested)
+	 */
+	if ((status >= 0) && (status < count)) {
+		pr_debug("Didn't accept the full chunk; %d of %d\n",
+			 status, (int) count);
+	}
+	return status;
+}
+
+static unsigned int
+scdrv_poll(struct file *file, struct poll_table_struct *wait)
+{
+	unsigned int mask = 0;
+	int status = 0;
+	struct subch_data_s *sd = (struct subch_data_s *) file->private_data;
+	unsigned long flags;
+
+	poll_wait(file, &sd->sd_rq, wait);
+	poll_wait(file, &sd->sd_wq, wait);
+
+	spin_lock_irqsave(&sd->sd_rlock, flags);
+	spin_lock(&sd->sd_wlock);
+	status = ia64_sn_irtr_intr(sd->sd_nasid, sd->sd_subch);
+	spin_unlock(&sd->sd_wlock);
+	spin_unlock_irqrestore(&sd->sd_rlock, flags);
+
+	if (status > 0) {
+		if (status & SAL_IROUTER_INTR_RECV) {
+			mask |= POLLIN | POLLRDNORM;
+		}
+		if (status & SAL_IROUTER_INTR_XMIT) {
+			mask |= POLLOUT | POLLWRNORM;
+		}
+	}
+
+	return mask;
+}
+
+static struct file_operations scdrv_fops = {
+	.owner =	THIS_MODULE,
+	.read =		scdrv_read,
+	.write =	scdrv_write,
+	.poll =		scdrv_poll,
+	.open =		scdrv_open,
+	.release =	scdrv_release,
+};
+
+/*
+ * scdrv_init
+ *
+ * Called at boot time to initialize the system controller communication
+ * facility.
+ */
+int __init
+scdrv_init(void)
+{
+	geoid_t geoid;
+	cnodeid_t cnode;
+	char devname[32];
+	char *devnamep;
+	struct sysctl_data_s *scd;
+	void *salbuf;
+	struct class_simple *snsc_class;
+	dev_t first_dev, dev;
+
+	if (alloc_chrdev_region(&first_dev, 0, numionodes,
+				SYSCTL_BASENAME) < 0) {
+		printk("%s: failed to register SN system controller device\n",
+		       __FUNCTION__);
+		return -ENODEV;
+	}
+	snsc_class = class_simple_create(THIS_MODULE, SYSCTL_BASENAME);
+
+	for (cnode = 0; cnode < numionodes; cnode++) {
+			geoid = cnodeid_get_geoid(cnode);
+			devnamep = devname;
+			format_module_id(devnamep, geo_module(geoid),
+					 MODULE_FORMAT_BRIEF);
+			devnamep = devname + strlen(devname);
+			sprintf(devnamep, "#%d", geo_slab(geoid));
+
+			/* allocate sysctl device data */
+			scd = kmalloc(sizeof (struct sysctl_data_s),
+				      GFP_KERNEL);
+			if (!scd) {
+				printk("%s: failed to allocate device info"
+				       "for %s/%s\n", __FUNCTION__,
+				       SYSCTL_BASENAME, devname);
+				continue;
+			}
+			memset(scd, 0, sizeof (struct sysctl_data_s));
+
+			/* initialize sysctl device data fields */
+			scd->scd_nasid = cnodeid_to_nasid(cnode);
+			if (!(salbuf = kmalloc(SCDRV_BUFSZ, GFP_KERNEL))) {
+				printk("%s: failed to allocate driver buffer"
+				       "(%s%s)\n", __FUNCTION__,
+				       SYSCTL_BASENAME, devname);
+				kfree(scd);
+				continue;
+			}
+
+			if (ia64_sn_irtr_init(scd->scd_nasid, salbuf,
+					      SCDRV_BUFSZ) < 0) {
+				printk
+				    ("%s: failed to initialize SAL for"
+				     " system controller communication"
+				     " (%s/%s): outdated PROM?\n",
+				     __FUNCTION__, SYSCTL_BASENAME, devname);
+				kfree(scd);
+				kfree(salbuf);
+				continue;
+			}
+
+			dev = first_dev + cnode;
+			cdev_init(&scd->scd_cdev, &scdrv_fops);
+			if (cdev_add(&scd->scd_cdev, dev, 1)) {
+				printk("%s: failed to register system"
+				       " controller device (%s%s)\n",
+				       __FUNCTION__, SYSCTL_BASENAME, devname);
+				kfree(scd);
+				kfree(salbuf);
+				continue;
+			}
+
+			class_simple_device_add(snsc_class, dev, NULL,
+						"%s", devname);
+
+			ia64_sn_irtr_intr_enable(scd->scd_nasid,
+						 0 /*ignored */ ,
+						 SAL_IROUTER_INTR_RECV);
+	}
+	return 0;
+}
+
+module_init(scdrv_init);
diff --git a/drivers/char/snsc.h b/drivers/char/snsc.h
new file mode 100644
index 0000000..c22c6c5
--- /dev/null
+++ b/drivers/char/snsc.h
@@ -0,0 +1,50 @@
+/*
+ * SN Platform system controller communication support
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2004 Silicon Graphics, Inc. All rights reserved.
+ */
+
+/*
+ * This file contains macros and data types for communication with the
+ * system controllers in SGI SN systems.
+ */
+
+#ifndef _SN_SYSCTL_H_
+#define _SN_SYSCTL_H_
+
+#include <linux/types.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/kobject.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <asm/sn/types.h>
+#include <asm/semaphore.h>
+
+#define CHUNKSIZE 127
+
+/* This structure is used to track an open subchannel. */
+struct subch_data_s {
+	nasid_t sd_nasid;	/* node on which the subchannel was opened */
+	int sd_subch;		/* subchannel number */
+	spinlock_t sd_rlock;	/* monitor lock for rsv */
+	spinlock_t sd_wlock;	/* monitor lock for wsv */
+	wait_queue_head_t sd_rq;	/* wait queue for readers */
+	wait_queue_head_t sd_wq;	/* wait queue for writers */
+	struct semaphore sd_rbs;	/* semaphore for read buffer */
+	struct semaphore sd_wbs;	/* semaphore for write buffer */
+
+	char sd_rb[CHUNKSIZE];	/* read buffer */
+	char sd_wb[CHUNKSIZE];	/* write buffer */
+};
+
+struct sysctl_data_s {
+	struct cdev scd_cdev;	/* Character device info */
+	nasid_t scd_nasid;	/* Node on which subchannels are opened. */
+};
+
+#endif /* _SN_SYSCTL_H_ */
diff --git a/drivers/char/sonypi.c b/drivers/char/sonypi.c
new file mode 100644
index 0000000..f97a8a9
--- /dev/null
+++ b/drivers/char/sonypi.c
@@ -0,0 +1,1403 @@
+/*
+ * Sony Programmable I/O Control Device driver for VAIO
+ *
+ * Copyright (C) 2001-2005 Stelian Pop <stelian@popies.net>
+ *
+ * Copyright (C) 2005 Narayanan R S <nars@kadamba.org>
+ *
+ * Copyright (C) 2001-2002 Alcôve <www.alcove.com>
+ *
+ * Copyright (C) 2001 Michael Ashley <m.ashley@unsw.edu.au>
+ *
+ * Copyright (C) 2001 Junichi Morita <jun1m@mars.dti.ne.jp>
+ *
+ * Copyright (C) 2000 Takaya Kinjo <t-kinjo@tc4.so-net.ne.jp>
+ *
+ * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com>
+ *
+ * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras.
+ *
+ * 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.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/acpi.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/kfifo.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/system.h>
+
+#include <linux/sonypi.h>
+
+#define SONYPI_DRIVER_VERSION	 "1.26"
+
+MODULE_AUTHOR("Stelian Pop <stelian@popies.net>");
+MODULE_DESCRIPTION("Sony Programmable I/O Control Device driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(SONYPI_DRIVER_VERSION);
+
+static int minor = -1;
+module_param(minor, int, 0);
+MODULE_PARM_DESC(minor,
+		 "minor number of the misc device, default is -1 (automatic)");
+
+static int verbose;		/* = 0 */
+module_param(verbose, int, 0644);
+MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)");
+
+static int fnkeyinit;		/* = 0 */
+module_param(fnkeyinit, int, 0444);
+MODULE_PARM_DESC(fnkeyinit,
+		 "set this if your Fn keys do not generate any event");
+
+static int camera;		/* = 0 */
+module_param(camera, int, 0444);
+MODULE_PARM_DESC(camera,
+		 "set this if you have a MotionEye camera (PictureBook series)");
+
+static int compat;		/* = 0 */
+module_param(compat, int, 0444);
+MODULE_PARM_DESC(compat,
+		 "set this if you want to enable backward compatibility mode");
+
+static unsigned long mask = 0xffffffff;
+module_param(mask, ulong, 0644);
+MODULE_PARM_DESC(mask,
+		 "set this to the mask of event you want to enable (see doc)");
+
+static int useinput = 1;
+module_param(useinput, int, 0444);
+MODULE_PARM_DESC(useinput,
+		 "set this if you would like sonypi to feed events to the input subsystem");
+
+#define SONYPI_DEVICE_MODEL_TYPE1	1
+#define SONYPI_DEVICE_MODEL_TYPE2	2
+
+/* type1 models use those */
+#define SONYPI_IRQ_PORT			0x8034
+#define SONYPI_IRQ_SHIFT		22
+#define SONYPI_BASE			0x50
+#define SONYPI_G10A			(SONYPI_BASE+0x14)
+#define SONYPI_TYPE1_REGION_SIZE	0x08
+#define SONYPI_TYPE1_EVTYPE_OFFSET	0x04
+
+/* type2 series specifics */
+#define SONYPI_SIRQ			0x9b
+#define SONYPI_SLOB			0x9c
+#define SONYPI_SHIB			0x9d
+#define SONYPI_TYPE2_REGION_SIZE	0x20
+#define SONYPI_TYPE2_EVTYPE_OFFSET	0x12
+
+/* battery / brightness addresses */
+#define SONYPI_BAT_FLAGS	0x81
+#define SONYPI_LCD_LIGHT	0x96
+#define SONYPI_BAT1_PCTRM	0xa0
+#define SONYPI_BAT1_LEFT	0xa2
+#define SONYPI_BAT1_MAXRT	0xa4
+#define SONYPI_BAT2_PCTRM	0xa8
+#define SONYPI_BAT2_LEFT	0xaa
+#define SONYPI_BAT2_MAXRT	0xac
+#define SONYPI_BAT1_MAXTK	0xb0
+#define SONYPI_BAT1_FULL	0xb2
+#define SONYPI_BAT2_MAXTK	0xb8
+#define SONYPI_BAT2_FULL	0xba
+
+/* FAN0 information (reverse engineered from ACPI tables) */
+#define SONYPI_FAN0_STATUS	0x93
+#define SONYPI_TEMP_STATUS	0xC1
+
+/* ioports used for brightness and type2 events */
+#define SONYPI_DATA_IOPORT	0x62
+#define SONYPI_CST_IOPORT	0x66
+
+/* The set of possible ioports */
+struct sonypi_ioport_list {
+	u16	port1;
+	u16	port2;
+};
+
+static struct sonypi_ioport_list sonypi_type1_ioport_list[] = {
+	{ 0x10c0, 0x10c4 },	/* looks like the default on C1Vx */
+	{ 0x1080, 0x1084 },
+	{ 0x1090, 0x1094 },
+	{ 0x10a0, 0x10a4 },
+	{ 0x10b0, 0x10b4 },
+	{ 0x0, 0x0 }
+};
+
+static struct sonypi_ioport_list sonypi_type2_ioport_list[] = {
+	{ 0x1080, 0x1084 },
+	{ 0x10a0, 0x10a4 },
+	{ 0x10c0, 0x10c4 },
+	{ 0x10e0, 0x10e4 },
+	{ 0x0, 0x0 }
+};
+
+/* The set of possible interrupts */
+struct sonypi_irq_list {
+	u16	irq;
+	u16	bits;
+};
+
+static struct sonypi_irq_list sonypi_type1_irq_list[] = {
+	{ 11, 0x2 },	/* IRQ 11, GO22=0,GO23=1 in AML */
+	{ 10, 0x1 },	/* IRQ 10, GO22=1,GO23=0 in AML */
+	{  5, 0x0 },	/* IRQ  5, GO22=0,GO23=0 in AML */
+	{  0, 0x3 }	/* no IRQ, GO22=1,GO23=1 in AML */
+};
+
+static struct sonypi_irq_list sonypi_type2_irq_list[] = {
+	{ 11, 0x80 },	/* IRQ 11, 0x80 in SIRQ in AML */
+	{ 10, 0x40 },	/* IRQ 10, 0x40 in SIRQ in AML */
+	{  9, 0x20 },	/* IRQ  9, 0x20 in SIRQ in AML */
+	{  6, 0x10 },	/* IRQ  6, 0x10 in SIRQ in AML */
+	{  0, 0x00 }	/* no IRQ, 0x00 in SIRQ in AML */
+};
+
+#define SONYPI_CAMERA_BRIGHTNESS		0
+#define SONYPI_CAMERA_CONTRAST			1
+#define SONYPI_CAMERA_HUE			2
+#define SONYPI_CAMERA_COLOR			3
+#define SONYPI_CAMERA_SHARPNESS			4
+
+#define SONYPI_CAMERA_PICTURE			5
+#define SONYPI_CAMERA_EXPOSURE_MASK		0xC
+#define SONYPI_CAMERA_WHITE_BALANCE_MASK	0x3
+#define SONYPI_CAMERA_PICTURE_MODE_MASK		0x30
+#define SONYPI_CAMERA_MUTE_MASK			0x40
+
+/* the rest don't need a loop until not 0xff */
+#define SONYPI_CAMERA_AGC			6
+#define SONYPI_CAMERA_AGC_MASK			0x30
+#define SONYPI_CAMERA_SHUTTER_MASK 		0x7
+
+#define SONYPI_CAMERA_SHUTDOWN_REQUEST		7
+#define SONYPI_CAMERA_CONTROL			0x10
+
+#define SONYPI_CAMERA_STATUS 			7
+#define SONYPI_CAMERA_STATUS_READY 		0x2
+#define SONYPI_CAMERA_STATUS_POSITION		0x4
+
+#define SONYPI_DIRECTION_BACKWARDS 		0x4
+
+#define SONYPI_CAMERA_REVISION 			8
+#define SONYPI_CAMERA_ROMVERSION 		9
+
+/* Event masks */
+#define SONYPI_JOGGER_MASK			0x00000001
+#define SONYPI_CAPTURE_MASK			0x00000002
+#define SONYPI_FNKEY_MASK			0x00000004
+#define SONYPI_BLUETOOTH_MASK			0x00000008
+#define SONYPI_PKEY_MASK			0x00000010
+#define SONYPI_BACK_MASK			0x00000020
+#define SONYPI_HELP_MASK			0x00000040
+#define SONYPI_LID_MASK				0x00000080
+#define SONYPI_ZOOM_MASK			0x00000100
+#define SONYPI_THUMBPHRASE_MASK			0x00000200
+#define SONYPI_MEYE_MASK			0x00000400
+#define SONYPI_MEMORYSTICK_MASK			0x00000800
+#define SONYPI_BATTERY_MASK			0x00001000
+
+struct sonypi_event {
+	u8	data;
+	u8	event;
+};
+
+/* The set of possible button release events */
+static struct sonypi_event sonypi_releaseev[] = {
+	{ 0x00, SONYPI_EVENT_ANYBUTTON_RELEASED },
+	{ 0, 0 }
+};
+
+/* The set of possible jogger events  */
+static struct sonypi_event sonypi_joggerev[] = {
+	{ 0x1f, SONYPI_EVENT_JOGDIAL_UP },
+	{ 0x01, SONYPI_EVENT_JOGDIAL_DOWN },
+	{ 0x5f, SONYPI_EVENT_JOGDIAL_UP_PRESSED },
+	{ 0x41, SONYPI_EVENT_JOGDIAL_DOWN_PRESSED },
+	{ 0x1e, SONYPI_EVENT_JOGDIAL_FAST_UP },
+	{ 0x02, SONYPI_EVENT_JOGDIAL_FAST_DOWN },
+	{ 0x5e, SONYPI_EVENT_JOGDIAL_FAST_UP_PRESSED },
+	{ 0x42, SONYPI_EVENT_JOGDIAL_FAST_DOWN_PRESSED },
+	{ 0x1d, SONYPI_EVENT_JOGDIAL_VFAST_UP },
+	{ 0x03, SONYPI_EVENT_JOGDIAL_VFAST_DOWN },
+	{ 0x5d, SONYPI_EVENT_JOGDIAL_VFAST_UP_PRESSED },
+	{ 0x43, SONYPI_EVENT_JOGDIAL_VFAST_DOWN_PRESSED },
+	{ 0x40, SONYPI_EVENT_JOGDIAL_PRESSED },
+	{ 0, 0 }
+};
+
+/* The set of possible capture button events */
+static struct sonypi_event sonypi_captureev[] = {
+	{ 0x05, SONYPI_EVENT_CAPTURE_PARTIALPRESSED },
+	{ 0x07, SONYPI_EVENT_CAPTURE_PRESSED },
+	{ 0x01, SONYPI_EVENT_CAPTURE_PARTIALRELEASED },
+	{ 0, 0 }
+};
+
+/* The set of possible fnkeys events */
+static struct sonypi_event sonypi_fnkeyev[] = {
+	{ 0x10, SONYPI_EVENT_FNKEY_ESC },
+	{ 0x11, SONYPI_EVENT_FNKEY_F1 },
+	{ 0x12, SONYPI_EVENT_FNKEY_F2 },
+	{ 0x13, SONYPI_EVENT_FNKEY_F3 },
+	{ 0x14, SONYPI_EVENT_FNKEY_F4 },
+	{ 0x15, SONYPI_EVENT_FNKEY_F5 },
+	{ 0x16, SONYPI_EVENT_FNKEY_F6 },
+	{ 0x17, SONYPI_EVENT_FNKEY_F7 },
+	{ 0x18, SONYPI_EVENT_FNKEY_F8 },
+	{ 0x19, SONYPI_EVENT_FNKEY_F9 },
+	{ 0x1a, SONYPI_EVENT_FNKEY_F10 },
+	{ 0x1b, SONYPI_EVENT_FNKEY_F11 },
+	{ 0x1c, SONYPI_EVENT_FNKEY_F12 },
+	{ 0x1f, SONYPI_EVENT_FNKEY_RELEASED },
+	{ 0x21, SONYPI_EVENT_FNKEY_1 },
+	{ 0x22, SONYPI_EVENT_FNKEY_2 },
+	{ 0x31, SONYPI_EVENT_FNKEY_D },
+	{ 0x32, SONYPI_EVENT_FNKEY_E },
+	{ 0x33, SONYPI_EVENT_FNKEY_F },
+	{ 0x34, SONYPI_EVENT_FNKEY_S },
+	{ 0x35, SONYPI_EVENT_FNKEY_B },
+	{ 0x36, SONYPI_EVENT_FNKEY_ONLY },
+	{ 0, 0 }
+};
+
+/* The set of possible program key events */
+static struct sonypi_event sonypi_pkeyev[] = {
+	{ 0x01, SONYPI_EVENT_PKEY_P1 },
+	{ 0x02, SONYPI_EVENT_PKEY_P2 },
+	{ 0x04, SONYPI_EVENT_PKEY_P3 },
+	{ 0x5c, SONYPI_EVENT_PKEY_P1 },
+	{ 0, 0 }
+};
+
+/* The set of possible bluetooth events */
+static struct sonypi_event sonypi_blueev[] = {
+	{ 0x55, SONYPI_EVENT_BLUETOOTH_PRESSED },
+	{ 0x59, SONYPI_EVENT_BLUETOOTH_ON },
+	{ 0x5a, SONYPI_EVENT_BLUETOOTH_OFF },
+	{ 0, 0 }
+};
+
+/* The set of possible back button events */
+static struct sonypi_event sonypi_backev[] = {
+	{ 0x20, SONYPI_EVENT_BACK_PRESSED },
+	{ 0, 0 }
+};
+
+/* The set of possible help button events */
+static struct sonypi_event sonypi_helpev[] = {
+	{ 0x3b, SONYPI_EVENT_HELP_PRESSED },
+	{ 0, 0 }
+};
+
+
+/* The set of possible lid events */
+static struct sonypi_event sonypi_lidev[] = {
+	{ 0x51, SONYPI_EVENT_LID_CLOSED },
+	{ 0x50, SONYPI_EVENT_LID_OPENED },
+	{ 0, 0 }
+};
+
+/* The set of possible zoom events */
+static struct sonypi_event sonypi_zoomev[] = {
+	{ 0x39, SONYPI_EVENT_ZOOM_PRESSED },
+	{ 0, 0 }
+};
+
+/* The set of possible thumbphrase events */
+static struct sonypi_event sonypi_thumbphraseev[] = {
+	{ 0x3a, SONYPI_EVENT_THUMBPHRASE_PRESSED },
+	{ 0, 0 }
+};
+
+/* The set of possible motioneye camera events */
+static struct sonypi_event sonypi_meyeev[] = {
+	{ 0x00, SONYPI_EVENT_MEYE_FACE },
+	{ 0x01, SONYPI_EVENT_MEYE_OPPOSITE },
+	{ 0, 0 }
+};
+
+/* The set of possible memorystick events */
+static struct sonypi_event sonypi_memorystickev[] = {
+	{ 0x53, SONYPI_EVENT_MEMORYSTICK_INSERT },
+	{ 0x54, SONYPI_EVENT_MEMORYSTICK_EJECT },
+	{ 0, 0 }
+};
+
+/* The set of possible battery events */
+static struct sonypi_event sonypi_batteryev[] = {
+	{ 0x20, SONYPI_EVENT_BATTERY_INSERT },
+	{ 0x30, SONYPI_EVENT_BATTERY_REMOVE },
+	{ 0, 0 }
+};
+
+static struct sonypi_eventtypes {
+	int			model;
+	u8			data;
+	unsigned long		mask;
+	struct sonypi_event *	events;
+} sonypi_eventtypes[] = {
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0, 0xffffffff, sonypi_releaseev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x70, SONYPI_MEYE_MASK, sonypi_meyeev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x30, SONYPI_LID_MASK, sonypi_lidev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x60, SONYPI_CAPTURE_MASK, sonypi_captureev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x10, SONYPI_JOGGER_MASK, sonypi_joggerev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x20, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x30, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x40, SONYPI_PKEY_MASK, sonypi_pkeyev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x30, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+	{ SONYPI_DEVICE_MODEL_TYPE1, 0x40, SONYPI_BATTERY_MASK, sonypi_batteryev },
+
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0, 0xffffffff, sonypi_releaseev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x38, SONYPI_LID_MASK, sonypi_lidev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x11, SONYPI_JOGGER_MASK, sonypi_joggerev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x61, SONYPI_CAPTURE_MASK, sonypi_captureev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x21, SONYPI_FNKEY_MASK, sonypi_fnkeyev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x31, SONYPI_BLUETOOTH_MASK, sonypi_blueev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x08, SONYPI_PKEY_MASK, sonypi_pkeyev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x11, SONYPI_BACK_MASK, sonypi_backev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x08, SONYPI_HELP_MASK, sonypi_helpev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x21, SONYPI_HELP_MASK, sonypi_helpev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x21, SONYPI_ZOOM_MASK, sonypi_zoomev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x20, SONYPI_THUMBPHRASE_MASK, sonypi_thumbphraseev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x31, SONYPI_MEMORYSTICK_MASK, sonypi_memorystickev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x41, SONYPI_BATTERY_MASK, sonypi_batteryev },
+	{ SONYPI_DEVICE_MODEL_TYPE2, 0x31, SONYPI_PKEY_MASK, sonypi_pkeyev },
+
+	{ 0 }
+};
+
+#define SONYPI_BUF_SIZE	128
+
+/* The name of the devices for the input device drivers */
+#define SONYPI_JOG_INPUTNAME	"Sony Vaio Jogdial"
+#define SONYPI_KEY_INPUTNAME	"Sony Vaio Keys"
+
+/* Correspondance table between sonypi events and input layer events */
+static struct {
+	int sonypiev;
+	int inputev;
+} sonypi_inputkeys[] = {
+	{ SONYPI_EVENT_CAPTURE_PRESSED,	 	KEY_CAMERA },
+	{ SONYPI_EVENT_FNKEY_ONLY, 		KEY_FN },
+	{ SONYPI_EVENT_FNKEY_ESC, 		KEY_FN_ESC },
+	{ SONYPI_EVENT_FNKEY_F1, 		KEY_FN_F1 },
+	{ SONYPI_EVENT_FNKEY_F2, 		KEY_FN_F2 },
+	{ SONYPI_EVENT_FNKEY_F3, 		KEY_FN_F3 },
+	{ SONYPI_EVENT_FNKEY_F4, 		KEY_FN_F4 },
+	{ SONYPI_EVENT_FNKEY_F5, 		KEY_FN_F5 },
+	{ SONYPI_EVENT_FNKEY_F6, 		KEY_FN_F6 },
+	{ SONYPI_EVENT_FNKEY_F7, 		KEY_FN_F7 },
+	{ SONYPI_EVENT_FNKEY_F8, 		KEY_FN_F8 },
+	{ SONYPI_EVENT_FNKEY_F9,		KEY_FN_F9 },
+	{ SONYPI_EVENT_FNKEY_F10,		KEY_FN_F10 },
+	{ SONYPI_EVENT_FNKEY_F11, 		KEY_FN_F11 },
+	{ SONYPI_EVENT_FNKEY_F12,		KEY_FN_F12 },
+	{ SONYPI_EVENT_FNKEY_1, 		KEY_FN_1 },
+	{ SONYPI_EVENT_FNKEY_2, 		KEY_FN_2 },
+	{ SONYPI_EVENT_FNKEY_D,			KEY_FN_D },
+	{ SONYPI_EVENT_FNKEY_E,			KEY_FN_E },
+	{ SONYPI_EVENT_FNKEY_F,			KEY_FN_F },
+	{ SONYPI_EVENT_FNKEY_S,			KEY_FN_S },
+	{ SONYPI_EVENT_FNKEY_B,			KEY_FN_B },
+	{ SONYPI_EVENT_BLUETOOTH_PRESSED, 	KEY_BLUE },
+	{ SONYPI_EVENT_BLUETOOTH_ON, 		KEY_BLUE },
+	{ SONYPI_EVENT_PKEY_P1, 		KEY_PROG1 },
+	{ SONYPI_EVENT_PKEY_P2, 		KEY_PROG2 },
+	{ SONYPI_EVENT_PKEY_P3, 		KEY_PROG3 },
+	{ SONYPI_EVENT_BACK_PRESSED, 		KEY_BACK },
+	{ SONYPI_EVENT_HELP_PRESSED, 		KEY_HELP },
+	{ SONYPI_EVENT_ZOOM_PRESSED, 		KEY_ZOOM },
+	{ SONYPI_EVENT_THUMBPHRASE_PRESSED, 	BTN_THUMB },
+	{ 0, 0 },
+};
+
+static struct sonypi_device {
+	struct pci_dev *dev;
+	struct platform_device *pdev;
+	u16 irq;
+	u16 bits;
+	u16 ioport1;
+	u16 ioport2;
+	u16 region_size;
+	u16 evtype_offset;
+	int camera_power;
+	int bluetooth_power;
+	struct semaphore lock;
+	struct kfifo *fifo;
+	spinlock_t fifo_lock;
+	wait_queue_head_t fifo_proc_list;
+	struct fasync_struct *fifo_async;
+	int open_count;
+	int model;
+	struct input_dev input_jog_dev;
+	struct input_dev input_key_dev;
+	struct work_struct input_work;
+	struct kfifo *input_fifo;
+	spinlock_t input_fifo_lock;
+} sonypi_device;
+
+#define ITERATIONS_LONG		10000
+#define ITERATIONS_SHORT	10
+
+#define wait_on_command(quiet, command, iterations) { \
+	unsigned int n = iterations; \
+	while (--n && (command)) \
+		udelay(1); \
+	if (!n && (verbose || !quiet)) \
+		printk(KERN_WARNING "sonypi command failed at %s : %s (line %d)\n", __FILE__, __FUNCTION__, __LINE__); \
+}
+
+#ifdef CONFIG_ACPI
+#define SONYPI_ACPI_ACTIVE (!acpi_disabled)
+#else
+#define SONYPI_ACPI_ACTIVE 0
+#endif				/* CONFIG_ACPI */
+
+static int sonypi_ec_write(u8 addr, u8 value)
+{
+#ifdef CONFIG_ACPI_EC
+	if (SONYPI_ACPI_ACTIVE)
+		return ec_write(addr, value);
+#endif
+	wait_on_command(1, inb_p(SONYPI_CST_IOPORT) & 3, ITERATIONS_LONG);
+	outb_p(0x81, SONYPI_CST_IOPORT);
+	wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+	outb_p(addr, SONYPI_DATA_IOPORT);
+	wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+	outb_p(value, SONYPI_DATA_IOPORT);
+	wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+	return 0;
+}
+
+static int sonypi_ec_read(u8 addr, u8 *value)
+{
+#ifdef CONFIG_ACPI_EC
+	if (SONYPI_ACPI_ACTIVE)
+		return ec_read(addr, value);
+#endif
+	wait_on_command(1, inb_p(SONYPI_CST_IOPORT) & 3, ITERATIONS_LONG);
+	outb_p(0x80, SONYPI_CST_IOPORT);
+	wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+	outb_p(addr, SONYPI_DATA_IOPORT);
+	wait_on_command(0, inb_p(SONYPI_CST_IOPORT) & 2, ITERATIONS_LONG);
+	*value = inb_p(SONYPI_DATA_IOPORT);
+	return 0;
+}
+
+static int ec_read16(u8 addr, u16 *value)
+{
+	u8 val_lb, val_hb;
+	if (sonypi_ec_read(addr, &val_lb))
+		return -1;
+	if (sonypi_ec_read(addr + 1, &val_hb))
+		return -1;
+	*value = val_lb | (val_hb << 8);
+	return 0;
+}
+
+/* Initializes the device - this comes from the AML code in the ACPI bios */
+static void sonypi_type1_srs(void)
+{
+	u32 v;
+
+	pci_read_config_dword(sonypi_device.dev, SONYPI_G10A, &v);
+	v = (v & 0xFFFF0000) | ((u32) sonypi_device.ioport1);
+	pci_write_config_dword(sonypi_device.dev, SONYPI_G10A, v);
+
+	pci_read_config_dword(sonypi_device.dev, SONYPI_G10A, &v);
+	v = (v & 0xFFF0FFFF) |
+	    (((u32) sonypi_device.ioport1 ^ sonypi_device.ioport2) << 16);
+	pci_write_config_dword(sonypi_device.dev, SONYPI_G10A, v);
+
+	v = inl(SONYPI_IRQ_PORT);
+	v &= ~(((u32) 0x3) << SONYPI_IRQ_SHIFT);
+	v |= (((u32) sonypi_device.bits) << SONYPI_IRQ_SHIFT);
+	outl(v, SONYPI_IRQ_PORT);
+
+	pci_read_config_dword(sonypi_device.dev, SONYPI_G10A, &v);
+	v = (v & 0xFF1FFFFF) | 0x00C00000;
+	pci_write_config_dword(sonypi_device.dev, SONYPI_G10A, v);
+}
+
+static void sonypi_type2_srs(void)
+{
+	if (sonypi_ec_write(SONYPI_SHIB, (sonypi_device.ioport1 & 0xFF00) >> 8))
+		printk(KERN_WARNING "ec_write failed\n");
+	if (sonypi_ec_write(SONYPI_SLOB, sonypi_device.ioport1 & 0x00FF))
+		printk(KERN_WARNING "ec_write failed\n");
+	if (sonypi_ec_write(SONYPI_SIRQ, sonypi_device.bits))
+		printk(KERN_WARNING "ec_write failed\n");
+	udelay(10);
+}
+
+/* Disables the device - this comes from the AML code in the ACPI bios */
+static void sonypi_type1_dis(void)
+{
+	u32 v;
+
+	pci_read_config_dword(sonypi_device.dev, SONYPI_G10A, &v);
+	v = v & 0xFF3FFFFF;
+	pci_write_config_dword(sonypi_device.dev, SONYPI_G10A, v);
+
+	v = inl(SONYPI_IRQ_PORT);
+	v |= (0x3 << SONYPI_IRQ_SHIFT);
+	outl(v, SONYPI_IRQ_PORT);
+}
+
+static void sonypi_type2_dis(void)
+{
+	if (sonypi_ec_write(SONYPI_SHIB, 0))
+		printk(KERN_WARNING "ec_write failed\n");
+	if (sonypi_ec_write(SONYPI_SLOB, 0))
+		printk(KERN_WARNING "ec_write failed\n");
+	if (sonypi_ec_write(SONYPI_SIRQ, 0))
+		printk(KERN_WARNING "ec_write failed\n");
+}
+
+static u8 sonypi_call1(u8 dev)
+{
+	u8 v1, v2;
+
+	wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+	outb(dev, sonypi_device.ioport2);
+	v1 = inb_p(sonypi_device.ioport2);
+	v2 = inb_p(sonypi_device.ioport1);
+	return v2;
+}
+
+static u8 sonypi_call2(u8 dev, u8 fn)
+{
+	u8 v1;
+
+	wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+	outb(dev, sonypi_device.ioport2);
+	wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+	outb(fn, sonypi_device.ioport1);
+	v1 = inb_p(sonypi_device.ioport1);
+	return v1;
+}
+
+static u8 sonypi_call3(u8 dev, u8 fn, u8 v)
+{
+	u8 v1;
+
+	wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+	outb(dev, sonypi_device.ioport2);
+	wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+	outb(fn, sonypi_device.ioport1);
+	wait_on_command(0, inb_p(sonypi_device.ioport2) & 2, ITERATIONS_LONG);
+	outb(v, sonypi_device.ioport1);
+	v1 = inb_p(sonypi_device.ioport1);
+	return v1;
+}
+
+#if 0
+/* Get brightness, hue etc. Unreliable... */
+static u8 sonypi_read(u8 fn)
+{
+	u8 v1, v2;
+	int n = 100;
+
+	while (n--) {
+		v1 = sonypi_call2(0x8f, fn);
+		v2 = sonypi_call2(0x8f, fn);
+		if (v1 == v2 && v1 != 0xff)
+			return v1;
+	}
+	return 0xff;
+}
+#endif
+
+/* Set brightness, hue etc */
+static void sonypi_set(u8 fn, u8 v)
+{
+	wait_on_command(0, sonypi_call3(0x90, fn, v), ITERATIONS_SHORT);
+}
+
+/* Tests if the camera is ready */
+static int sonypi_camera_ready(void)
+{
+	u8 v;
+
+	v = sonypi_call2(0x8f, SONYPI_CAMERA_STATUS);
+	return (v != 0xff && (v & SONYPI_CAMERA_STATUS_READY));
+}
+
+/* Turns the camera off */
+static void sonypi_camera_off(void)
+{
+	sonypi_set(SONYPI_CAMERA_PICTURE, SONYPI_CAMERA_MUTE_MASK);
+
+	if (!sonypi_device.camera_power)
+		return;
+
+	sonypi_call2(0x91, 0);
+	sonypi_device.camera_power = 0;
+}
+
+/* Turns the camera on */
+static void sonypi_camera_on(void)
+{
+	int i, j;
+
+	if (sonypi_device.camera_power)
+		return;
+
+	for (j = 5; j > 0; j--) {
+
+		while (sonypi_call2(0x91, 0x1))
+			msleep(10);
+		sonypi_call1(0x93);
+
+		for (i = 400; i > 0; i--) {
+			if (sonypi_camera_ready())
+				break;
+			msleep(10);
+		}
+		if (i)
+			break;
+	}
+
+	if (j == 0) {
+		printk(KERN_WARNING "sonypi: failed to power on camera\n");
+		return;
+	}
+
+	sonypi_set(0x10, 0x5a);
+	sonypi_device.camera_power = 1;
+}
+
+/* sets the bluetooth subsystem power state */
+static void sonypi_setbluetoothpower(u8 state)
+{
+	state = !!state;
+
+	if (sonypi_device.bluetooth_power == state)
+		return;
+
+	sonypi_call2(0x96, state);
+	sonypi_call1(0x82);
+	sonypi_device.bluetooth_power = state;
+}
+
+static void input_keyrelease(void *data)
+{
+	struct input_dev *input_dev;
+	int key;
+
+	while (1) {
+		if (kfifo_get(sonypi_device.input_fifo,
+			      (unsigned char *)&input_dev,
+			      sizeof(input_dev)) != sizeof(input_dev))
+			return;
+		if (kfifo_get(sonypi_device.input_fifo,
+			      (unsigned char *)&key,
+			      sizeof(key)) != sizeof(key))
+			return;
+
+		msleep(10);
+		input_report_key(input_dev, key, 0);
+		input_sync(input_dev);
+	}
+}
+
+/* Interrupt handler: some event is available */
+static irqreturn_t sonypi_irq(int irq, void *dev_id, struct pt_regs *regs)
+{
+	u8 v1, v2, event = 0;
+	int i, j;
+
+	v1 = inb_p(sonypi_device.ioport1);
+	v2 = inb_p(sonypi_device.ioport1 + sonypi_device.evtype_offset);
+
+	for (i = 0; sonypi_eventtypes[i].model; i++) {
+		if (sonypi_device.model != sonypi_eventtypes[i].model)
+			continue;
+		if ((v2 & sonypi_eventtypes[i].data) !=
+		    sonypi_eventtypes[i].data)
+			continue;
+		if (!(mask & sonypi_eventtypes[i].mask))
+			continue;
+		for (j = 0; sonypi_eventtypes[i].events[j].event; j++) {
+			if (v1 == sonypi_eventtypes[i].events[j].data) {
+				event = sonypi_eventtypes[i].events[j].event;
+				goto found;
+			}
+		}
+	}
+
+	if (verbose)
+		printk(KERN_WARNING
+		       "sonypi: unknown event port1=0x%02x,port2=0x%02x\n",
+		       v1, v2);
+	/* We need to return IRQ_HANDLED here because there *are*
+	 * events belonging to the sonypi device we don't know about,
+	 * but we still don't want those to pollute the logs... */
+	return IRQ_HANDLED;
+
+found:
+	if (verbose > 1)
+		printk(KERN_INFO
+		       "sonypi: event port1=0x%02x,port2=0x%02x\n", v1, v2);
+
+	if (useinput) {
+		struct input_dev *input_jog_dev = &sonypi_device.input_jog_dev;
+		struct input_dev *input_key_dev = &sonypi_device.input_key_dev;
+		switch (event) {
+		case SONYPI_EVENT_JOGDIAL_UP:
+		case SONYPI_EVENT_JOGDIAL_UP_PRESSED:
+			input_report_rel(input_jog_dev, REL_WHEEL, 1);
+			break;
+		case SONYPI_EVENT_JOGDIAL_DOWN:
+		case SONYPI_EVENT_JOGDIAL_DOWN_PRESSED:
+			input_report_rel(input_jog_dev, REL_WHEEL, -1);
+			break;
+		case SONYPI_EVENT_JOGDIAL_PRESSED: {
+			int key = BTN_MIDDLE;
+			input_report_key(input_jog_dev, key, 1);
+			kfifo_put(sonypi_device.input_fifo,
+				  (unsigned char *)&input_jog_dev,
+				  sizeof(input_jog_dev));
+			kfifo_put(sonypi_device.input_fifo,
+				  (unsigned char *)&key, sizeof(key));
+			break;
+		}
+		case SONYPI_EVENT_FNKEY_RELEASED:
+			/* Nothing, not all VAIOs generate this event */
+			break;
+		}
+		input_sync(input_jog_dev);
+
+		for (i = 0; sonypi_inputkeys[i].sonypiev; i++) {
+			int key;
+
+			if (event != sonypi_inputkeys[i].sonypiev)
+				continue;
+
+			key = sonypi_inputkeys[i].inputev;
+			input_report_key(input_key_dev, key, 1);
+			kfifo_put(sonypi_device.input_fifo,
+				  (unsigned char *)&input_key_dev,
+				  sizeof(input_key_dev));
+			kfifo_put(sonypi_device.input_fifo,
+				  (unsigned char *)&key, sizeof(key));
+		}
+		input_sync(input_key_dev);
+		schedule_work(&sonypi_device.input_work);
+	}
+
+	kfifo_put(sonypi_device.fifo, (unsigned char *)&event, sizeof(event));
+	kill_fasync(&sonypi_device.fifo_async, SIGIO, POLL_IN);
+	wake_up_interruptible(&sonypi_device.fifo_proc_list);
+
+	return IRQ_HANDLED;
+}
+
+/* External camera command (exported to the motion eye v4l driver) */
+int sonypi_camera_command(int command, u8 value)
+{
+	if (!camera)
+		return -EIO;
+
+	down(&sonypi_device.lock);
+
+	switch (command) {
+	case SONYPI_COMMAND_SETCAMERA:
+		if (value)
+			sonypi_camera_on();
+		else
+			sonypi_camera_off();
+		break;
+	case SONYPI_COMMAND_SETCAMERABRIGHTNESS:
+		sonypi_set(SONYPI_CAMERA_BRIGHTNESS, value);
+		break;
+	case SONYPI_COMMAND_SETCAMERACONTRAST:
+		sonypi_set(SONYPI_CAMERA_CONTRAST, value);
+		break;
+	case SONYPI_COMMAND_SETCAMERAHUE:
+		sonypi_set(SONYPI_CAMERA_HUE, value);
+		break;
+	case SONYPI_COMMAND_SETCAMERACOLOR:
+		sonypi_set(SONYPI_CAMERA_COLOR, value);
+		break;
+	case SONYPI_COMMAND_SETCAMERASHARPNESS:
+		sonypi_set(SONYPI_CAMERA_SHARPNESS, value);
+		break;
+	case SONYPI_COMMAND_SETCAMERAPICTURE:
+		sonypi_set(SONYPI_CAMERA_PICTURE, value);
+		break;
+	case SONYPI_COMMAND_SETCAMERAAGC:
+		sonypi_set(SONYPI_CAMERA_AGC, value);
+		break;
+	default:
+		printk(KERN_ERR "sonypi: sonypi_camera_command invalid: %d\n",
+		       command);
+		break;
+	}
+	up(&sonypi_device.lock);
+	return 0;
+}
+
+EXPORT_SYMBOL(sonypi_camera_command);
+
+static int sonypi_misc_fasync(int fd, struct file *filp, int on)
+{
+	int retval;
+
+	retval = fasync_helper(fd, filp, on, &sonypi_device.fifo_async);
+	if (retval < 0)
+		return retval;
+	return 0;
+}
+
+static int sonypi_misc_release(struct inode *inode, struct file *file)
+{
+	sonypi_misc_fasync(-1, file, 0);
+	down(&sonypi_device.lock);
+	sonypi_device.open_count--;
+	up(&sonypi_device.lock);
+	return 0;
+}
+
+static int sonypi_misc_open(struct inode *inode, struct file *file)
+{
+	down(&sonypi_device.lock);
+	/* Flush input queue on first open */
+	if (!sonypi_device.open_count)
+		kfifo_reset(sonypi_device.fifo);
+	sonypi_device.open_count++;
+	up(&sonypi_device.lock);
+	return 0;
+}
+
+static ssize_t sonypi_misc_read(struct file *file, char __user *buf,
+				size_t count, loff_t *pos)
+{
+	ssize_t ret;
+	unsigned char c;
+
+	if ((kfifo_len(sonypi_device.fifo) == 0) &&
+	    (file->f_flags & O_NONBLOCK))
+		return -EAGAIN;
+
+	ret = wait_event_interruptible(sonypi_device.fifo_proc_list,
+				       kfifo_len(sonypi_device.fifo) != 0);
+	if (ret)
+		return ret;
+
+	while (ret < count &&
+	       (kfifo_get(sonypi_device.fifo, &c, sizeof(c)) == sizeof(c))) {
+		if (put_user(c, buf++))
+			return -EFAULT;
+		ret++;
+	}
+
+	if (ret > 0) {
+		struct inode *inode = file->f_dentry->d_inode;
+		inode->i_atime = current_fs_time(inode->i_sb);
+	}
+
+	return ret;
+}
+
+static unsigned int sonypi_misc_poll(struct file *file, poll_table *wait)
+{
+	poll_wait(file, &sonypi_device.fifo_proc_list, wait);
+	if (kfifo_len(sonypi_device.fifo))
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+static int sonypi_misc_ioctl(struct inode *ip, struct file *fp,
+			     unsigned int cmd, unsigned long arg)
+{
+	int ret = 0;
+	void __user *argp = (void __user *)arg;
+	u8 val8;
+	u16 val16;
+
+	down(&sonypi_device.lock);
+	switch (cmd) {
+	case SONYPI_IOCGBRT:
+		if (sonypi_ec_read(SONYPI_LCD_LIGHT, &val8)) {
+			ret = -EIO;
+			break;
+		}
+		if (copy_to_user(argp, &val8, sizeof(val8)))
+			ret = -EFAULT;
+		break;
+	case SONYPI_IOCSBRT:
+		if (copy_from_user(&val8, argp, sizeof(val8))) {
+			ret = -EFAULT;
+			break;
+		}
+		if (sonypi_ec_write(SONYPI_LCD_LIGHT, val8))
+			ret = -EIO;
+		break;
+	case SONYPI_IOCGBAT1CAP:
+		if (ec_read16(SONYPI_BAT1_FULL, &val16)) {
+			ret = -EIO;
+			break;
+		}
+		if (copy_to_user(argp, &val16, sizeof(val16)))
+			ret = -EFAULT;
+		break;
+	case SONYPI_IOCGBAT1REM:
+		if (ec_read16(SONYPI_BAT1_LEFT, &val16)) {
+			ret = -EIO;
+			break;
+		}
+		if (copy_to_user(argp, &val16, sizeof(val16)))
+			ret = -EFAULT;
+		break;
+	case SONYPI_IOCGBAT2CAP:
+		if (ec_read16(SONYPI_BAT2_FULL, &val16)) {
+			ret = -EIO;
+			break;
+		}
+		if (copy_to_user(argp, &val16, sizeof(val16)))
+			ret = -EFAULT;
+		break;
+	case SONYPI_IOCGBAT2REM:
+		if (ec_read16(SONYPI_BAT2_LEFT, &val16)) {
+			ret = -EIO;
+			break;
+		}
+		if (copy_to_user(argp, &val16, sizeof(val16)))
+			ret = -EFAULT;
+		break;
+	case SONYPI_IOCGBATFLAGS:
+		if (sonypi_ec_read(SONYPI_BAT_FLAGS, &val8)) {
+			ret = -EIO;
+			break;
+		}
+		val8 &= 0x07;
+		if (copy_to_user(argp, &val8, sizeof(val8)))
+			ret = -EFAULT;
+		break;
+	case SONYPI_IOCGBLUE:
+		val8 = sonypi_device.bluetooth_power;
+		if (copy_to_user(argp, &val8, sizeof(val8)))
+			ret = -EFAULT;
+		break;
+	case SONYPI_IOCSBLUE:
+		if (copy_from_user(&val8, argp, sizeof(val8))) {
+			ret = -EFAULT;
+			break;
+		}
+		sonypi_setbluetoothpower(val8);
+		break;
+	/* FAN Controls */
+	case SONYPI_IOCGFAN:
+		if (sonypi_ec_read(SONYPI_FAN0_STATUS, &val8)) {
+			ret = -EIO;
+			break;
+		}
+		if (copy_to_user((u8 *)arg, &val8, sizeof(val8)))
+			ret = -EFAULT;
+		break;
+	case SONYPI_IOCSFAN:
+		if (copy_from_user(&val8, (u8 *)arg, sizeof(val8))) {
+			ret = -EFAULT;
+			break;
+		}
+		if (sonypi_ec_write(SONYPI_FAN0_STATUS, val8))
+			ret = -EIO;
+		break;
+	/* GET Temperature (useful under APM) */
+	case SONYPI_IOCGTEMP:
+		if (sonypi_ec_read(SONYPI_TEMP_STATUS, &val8)) {
+			ret = -EIO;
+			break;
+		}
+		if (copy_to_user((u8 *)arg, &val8, sizeof(val8)))
+			ret = -EFAULT;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+	up(&sonypi_device.lock);
+	return ret;
+}
+
+static struct file_operations sonypi_misc_fops = {
+	.owner		= THIS_MODULE,
+	.read		= sonypi_misc_read,
+	.poll		= sonypi_misc_poll,
+	.open		= sonypi_misc_open,
+	.release	= sonypi_misc_release,
+	.fasync		= sonypi_misc_fasync,
+	.ioctl		= sonypi_misc_ioctl,
+};
+
+static struct miscdevice sonypi_misc_device = {
+	.minor		= MISC_DYNAMIC_MINOR,
+	.name		= "sonypi",
+	.fops		= &sonypi_misc_fops,
+};
+
+static void sonypi_enable(unsigned int camera_on)
+{
+	if (sonypi_device.model == SONYPI_DEVICE_MODEL_TYPE2)
+		sonypi_type2_srs();
+	else
+		sonypi_type1_srs();
+
+	sonypi_call1(0x82);
+	sonypi_call2(0x81, 0xff);
+	sonypi_call1(compat ? 0x92 : 0x82);
+
+	/* Enable ACPI mode to get Fn key events */
+	if (!SONYPI_ACPI_ACTIVE && fnkeyinit)
+		outb(0xf0, 0xb2);
+
+	if (camera && camera_on)
+		sonypi_camera_on();
+}
+
+static int sonypi_disable(void)
+{
+	sonypi_call2(0x81, 0);	/* make sure we don't get any more events */
+	if (camera)
+		sonypi_camera_off();
+
+	/* disable ACPI mode */
+	if (!SONYPI_ACPI_ACTIVE && fnkeyinit)
+		outb(0xf1, 0xb2);
+
+	if (sonypi_device.model == SONYPI_DEVICE_MODEL_TYPE2)
+		sonypi_type2_dis();
+	else
+		sonypi_type1_dis();
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int old_camera_power;
+
+static int sonypi_suspend(struct device *dev, u32 state, u32 level)
+{
+	if (level == SUSPEND_DISABLE) {
+		old_camera_power = sonypi_device.camera_power;
+		sonypi_disable();
+	}
+	return 0;
+}
+
+static int sonypi_resume(struct device *dev, u32 level)
+{
+	if (level == RESUME_ENABLE)
+		sonypi_enable(old_camera_power);
+	return 0;
+}
+#endif
+
+static void sonypi_shutdown(struct device *dev)
+{
+	sonypi_disable();
+}
+
+static struct device_driver sonypi_driver = {
+	.name		= "sonypi",
+	.bus		= &platform_bus_type,
+#ifdef CONFIG_PM
+	.suspend	= sonypi_suspend,
+	.resume		= sonypi_resume,
+#endif
+	.shutdown	= sonypi_shutdown,
+};
+
+static int __devinit sonypi_probe(void)
+{
+	int i, ret;
+	struct sonypi_ioport_list *ioport_list;
+	struct sonypi_irq_list *irq_list;
+	struct pci_dev *pcidev;
+
+	pcidev = pci_get_device(PCI_VENDOR_ID_INTEL,
+				PCI_DEVICE_ID_INTEL_82371AB_3, NULL);
+
+	sonypi_device.dev = pcidev;
+	sonypi_device.model = pcidev ?
+		SONYPI_DEVICE_MODEL_TYPE1 : SONYPI_DEVICE_MODEL_TYPE2;
+
+	spin_lock_init(&sonypi_device.fifo_lock);
+	sonypi_device.fifo = kfifo_alloc(SONYPI_BUF_SIZE, GFP_KERNEL,
+					 &sonypi_device.fifo_lock);
+	if (IS_ERR(sonypi_device.fifo)) {
+		printk(KERN_ERR "sonypi: kfifo_alloc failed\n");
+		ret = PTR_ERR(sonypi_device.fifo);
+		goto out_fifo;
+	}
+
+	init_waitqueue_head(&sonypi_device.fifo_proc_list);
+	init_MUTEX(&sonypi_device.lock);
+	sonypi_device.bluetooth_power = -1;
+
+	if (pcidev && pci_enable_device(pcidev)) {
+		printk(KERN_ERR "sonypi: pci_enable_device failed\n");
+		ret = -EIO;
+		goto out_pcienable;
+	}
+
+	if (minor != -1)
+		sonypi_misc_device.minor = minor;
+	if ((ret = misc_register(&sonypi_misc_device))) {
+		printk(KERN_ERR "sonypi: misc_register failed\n");
+		goto out_miscreg;
+	}
+
+	if (sonypi_device.model == SONYPI_DEVICE_MODEL_TYPE2) {
+		ioport_list = sonypi_type2_ioport_list;
+		sonypi_device.region_size = SONYPI_TYPE2_REGION_SIZE;
+		sonypi_device.evtype_offset = SONYPI_TYPE2_EVTYPE_OFFSET;
+		irq_list = sonypi_type2_irq_list;
+	} else {
+		ioport_list = sonypi_type1_ioport_list;
+		sonypi_device.region_size = SONYPI_TYPE1_REGION_SIZE;
+		sonypi_device.evtype_offset = SONYPI_TYPE1_EVTYPE_OFFSET;
+		irq_list = sonypi_type1_irq_list;
+	}
+
+	for (i = 0; ioport_list[i].port1; i++) {
+		if (request_region(ioport_list[i].port1,
+				   sonypi_device.region_size,
+				   "Sony Programable I/O Device")) {
+			/* get the ioport */
+			sonypi_device.ioport1 = ioport_list[i].port1;
+			sonypi_device.ioport2 = ioport_list[i].port2;
+			break;
+		}
+	}
+	if (!sonypi_device.ioport1) {
+		printk(KERN_ERR "sonypi: request_region failed\n");
+		ret = -ENODEV;
+		goto out_reqreg;
+	}
+
+	for (i = 0; irq_list[i].irq; i++) {
+
+		sonypi_device.irq = irq_list[i].irq;
+		sonypi_device.bits = irq_list[i].bits;
+
+		if (!request_irq(sonypi_device.irq, sonypi_irq,
+				 SA_SHIRQ, "sonypi", sonypi_irq))
+			break;
+	}
+
+	if (!irq_list[i].irq) {
+		printk(KERN_ERR "sonypi: request_irq failed\n");
+		ret = -ENODEV;
+		goto out_reqirq;
+	}
+
+	if (useinput) {
+		/* Initialize the Input Drivers: jogdial */
+		int i;
+		sonypi_device.input_jog_dev.evbit[0] =
+			BIT(EV_KEY) | BIT(EV_REL);
+		sonypi_device.input_jog_dev.keybit[LONG(BTN_MOUSE)] =
+			BIT(BTN_MIDDLE);
+		sonypi_device.input_jog_dev.relbit[0] = BIT(REL_WHEEL);
+		sonypi_device.input_jog_dev.name =
+			kmalloc(sizeof(SONYPI_JOG_INPUTNAME), GFP_KERNEL);
+		if (!sonypi_device.input_jog_dev.name) {
+			printk(KERN_ERR "sonypi: kmalloc failed\n");
+			ret = -ENOMEM;
+			goto out_inkmallocinput1;
+		}
+		sprintf(sonypi_device.input_jog_dev.name, SONYPI_JOG_INPUTNAME);
+		sonypi_device.input_jog_dev.id.bustype = BUS_ISA;
+		sonypi_device.input_jog_dev.id.vendor = PCI_VENDOR_ID_SONY;
+
+		input_register_device(&sonypi_device.input_jog_dev);
+		printk(KERN_INFO "%s input method installed.\n",
+		       sonypi_device.input_jog_dev.name);
+
+		/* Initialize the Input Drivers: special keys */
+		sonypi_device.input_key_dev.evbit[0] = BIT(EV_KEY);
+		for (i = 0; sonypi_inputkeys[i].sonypiev; i++)
+			if (sonypi_inputkeys[i].inputev)
+				set_bit(sonypi_inputkeys[i].inputev,
+					sonypi_device.input_key_dev.keybit);
+		sonypi_device.input_key_dev.name =
+			kmalloc(sizeof(SONYPI_KEY_INPUTNAME), GFP_KERNEL);
+		if (!sonypi_device.input_key_dev.name) {
+			printk(KERN_ERR "sonypi: kmalloc failed\n");
+			ret = -ENOMEM;
+			goto out_inkmallocinput2;
+		}
+		sprintf(sonypi_device.input_key_dev.name, SONYPI_KEY_INPUTNAME);
+		sonypi_device.input_key_dev.id.bustype = BUS_ISA;
+		sonypi_device.input_key_dev.id.vendor = PCI_VENDOR_ID_SONY;
+
+		input_register_device(&sonypi_device.input_key_dev);
+		printk(KERN_INFO "%s input method installed.\n",
+		       sonypi_device.input_key_dev.name);
+
+		spin_lock_init(&sonypi_device.input_fifo_lock);
+		sonypi_device.input_fifo =
+			kfifo_alloc(SONYPI_BUF_SIZE, GFP_KERNEL,
+				    &sonypi_device.input_fifo_lock);
+		if (IS_ERR(sonypi_device.input_fifo)) {
+			printk(KERN_ERR "sonypi: kfifo_alloc failed\n");
+			ret = PTR_ERR(sonypi_device.input_fifo);
+			goto out_infifo;
+		}
+
+		INIT_WORK(&sonypi_device.input_work, input_keyrelease, NULL);
+	}
+
+	sonypi_device.pdev = platform_device_register_simple("sonypi", -1,
+							     NULL, 0);
+	if (IS_ERR(sonypi_device.pdev)) {
+		ret = PTR_ERR(sonypi_device.pdev);
+		goto out_platformdev;
+	}
+
+	sonypi_enable(0);
+
+	printk(KERN_INFO "sonypi: Sony Programmable I/O Controller Driver"
+	       "v%s.\n", SONYPI_DRIVER_VERSION);
+	printk(KERN_INFO "sonypi: detected %s model, "
+	       "verbose = %d, fnkeyinit = %s, camera = %s, "
+	       "compat = %s, mask = 0x%08lx, useinput = %s, acpi = %s\n",
+	       (sonypi_device.model == SONYPI_DEVICE_MODEL_TYPE1) ?
+			"type1" : "type2",
+	       verbose,
+	       fnkeyinit ? "on" : "off",
+	       camera ? "on" : "off",
+	       compat ? "on" : "off",
+	       mask,
+	       useinput ? "on" : "off",
+	       SONYPI_ACPI_ACTIVE ? "on" : "off");
+	printk(KERN_INFO "sonypi: enabled at irq=%d, port1=0x%x, port2=0x%x\n",
+	       sonypi_device.irq,
+	       sonypi_device.ioport1, sonypi_device.ioport2);
+
+	if (minor == -1)
+		printk(KERN_INFO "sonypi: device allocated minor is %d\n",
+		       sonypi_misc_device.minor);
+
+	return 0;
+
+out_platformdev:
+	kfifo_free(sonypi_device.input_fifo);
+out_infifo:
+	input_unregister_device(&sonypi_device.input_key_dev);
+	kfree(sonypi_device.input_key_dev.name);
+out_inkmallocinput2:
+	input_unregister_device(&sonypi_device.input_jog_dev);
+	kfree(sonypi_device.input_jog_dev.name);
+out_inkmallocinput1:
+	free_irq(sonypi_device.irq, sonypi_irq);
+out_reqirq:
+	release_region(sonypi_device.ioport1, sonypi_device.region_size);
+out_reqreg:
+	misc_deregister(&sonypi_misc_device);
+out_miscreg:
+	if (pcidev)
+		pci_disable_device(pcidev);
+out_pcienable:
+	kfifo_free(sonypi_device.fifo);
+out_fifo:
+	pci_dev_put(sonypi_device.dev);
+	return ret;
+}
+
+static void __devexit sonypi_remove(void)
+{
+	sonypi_disable();
+
+	platform_device_unregister(sonypi_device.pdev);
+
+	if (useinput) {
+		input_unregister_device(&sonypi_device.input_key_dev);
+		kfree(sonypi_device.input_key_dev.name);
+		input_unregister_device(&sonypi_device.input_jog_dev);
+		kfree(sonypi_device.input_jog_dev.name);
+		kfifo_free(sonypi_device.input_fifo);
+	}
+
+	free_irq(sonypi_device.irq, sonypi_irq);
+	release_region(sonypi_device.ioport1, sonypi_device.region_size);
+	misc_deregister(&sonypi_misc_device);
+	if (sonypi_device.dev)
+		pci_disable_device(sonypi_device.dev);
+	kfifo_free(sonypi_device.fifo);
+	pci_dev_put(sonypi_device.dev);
+	printk(KERN_INFO "sonypi: removed.\n");
+}
+
+static struct dmi_system_id __initdata sonypi_dmi_table[] = {
+	{
+		.ident = "Sony Vaio",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "PCG-"),
+		},
+	},
+	{
+		.ident = "Sony Vaio",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Sony Corporation"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "VGN-"),
+		},
+	},
+	{ }
+};
+
+static int __init sonypi_init(void)
+{
+	int ret;
+
+	if (!dmi_check_system(sonypi_dmi_table))
+		return -ENODEV;
+
+	ret = driver_register(&sonypi_driver);
+	if (ret)
+		return ret;
+
+	ret = sonypi_probe();
+	if (ret)
+		driver_unregister(&sonypi_driver);
+
+	return ret;
+}
+
+static void __exit sonypi_exit(void)
+{
+	driver_unregister(&sonypi_driver);
+	sonypi_remove();
+}
+
+module_init(sonypi_init);
+module_exit(sonypi_exit);
diff --git a/drivers/char/specialix.c b/drivers/char/specialix.c
new file mode 100644
index 0000000..c789d5c
--- /dev/null
+++ b/drivers/char/specialix.c
@@ -0,0 +1,2610 @@
+/*
+ *      specialix.c  -- specialix IO8+ multiport serial driver.
+ *
+ *      Copyright (C) 1997  Roger Wolff (R.E.Wolff@BitWizard.nl)
+ *      Copyright (C) 1994-1996  Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ *      Specialix pays for the development and support of this driver.
+ *      Please DO contact io8-linux@specialix.co.uk if you require
+ *      support. But please read the documentation (specialix.txt)
+ *      first.
+ *
+ *      This driver was developped in the BitWizard linux device
+ *      driver service. If you require a linux device driver for your
+ *      product, please contact devices@BitWizard.nl for a quote.
+ *
+ *      This code is firmly based on the riscom/8 serial driver,
+ *      written by Dmitry Gorodchanin. The specialix IO8+ card
+ *      programming information was obtained from the CL-CD1865 Data
+ *      Book, and Specialix document number 6200059: IO8+ Hardware
+ *      Functional Specification.
+ *
+ *      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.
+ *
+ * Revision history:
+ *
+ * Revision 1.0:  April 1st 1997.
+ *                Initial release for alpha testing.
+ * Revision 1.1:  April 14th 1997. 
+ *                Incorporated Richard Hudsons suggestions, 
+ *                removed some debugging printk's.
+ * Revision 1.2:  April 15th 1997.
+ *                Ported to 2.1.x kernels.
+ * Revision 1.3:  April 17th 1997 
+ *                Backported to 2.0. (Compatibility macros). 
+ * Revision 1.4:  April 18th 1997
+ *                Fixed DTR/RTS bug that caused the card to indicate 
+ *                "don't send data" to a modem after the password prompt.  
+ *                Fixed bug for premature (fake) interrupts.
+ * Revision 1.5:  April 19th 1997
+ *                fixed a minor typo in the header file, cleanup a little. 
+ *                performance warnings are now MAXed at once per minute.
+ * Revision 1.6:  May 23 1997
+ *                Changed the specialix=... format to include interrupt.
+ * Revision 1.7:  May 27 1997
+ *                Made many more debug printk's a compile time option.
+ * Revision 1.8:  Jul 1  1997
+ *                port to linux-2.1.43 kernel.
+ * Revision 1.9:  Oct 9  1998
+ *                Added stuff for the IO8+/PCI version.
+ * Revision 1.10: Oct 22  1999 / Jan 21 2000. 
+ *                Added stuff for setserial. 
+ *                Nicolas Mailhot (Nicolas.Mailhot@email.enst.fr)
+ * 
+ */
+
+#define VERSION "1.11"
+
+
+/*
+ * There is a bunch of documentation about the card, jumpers, config
+ * settings, restrictions, cables, device names and numbers in
+ * Documentation/specialix.txt
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/version.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+#include "specialix_io8.h"
+#include "cd1865.h"
+
+
+/*
+   This driver can spew a whole lot of debugging output at you. If you
+   need maximum performance, you should disable the DEBUG define. To
+   aid in debugging in the field, I'm leaving the compile-time debug
+   features enabled, and disable them "runtime". That allows me to
+   instruct people with problems to enable debugging without requiring
+   them to recompile...
+*/
+#define DEBUG
+
+static int sx_debug;
+static int sx_rxfifo = SPECIALIX_RXFIFO;
+
+#ifdef DEBUG
+#define dprintk(f, str...) if (sx_debug & f) printk (str)
+#else
+#define dprintk(f, str...) /* nothing */
+#endif
+
+#define SX_DEBUG_FLOW    0x0001
+#define SX_DEBUG_DATA    0x0002
+#define SX_DEBUG_PROBE   0x0004
+#define SX_DEBUG_CHAN    0x0008
+#define SX_DEBUG_INIT    0x0010
+#define SX_DEBUG_RX      0x0020
+#define SX_DEBUG_TX      0x0040
+#define SX_DEBUG_IRQ     0x0080
+#define SX_DEBUG_OPEN    0x0100
+#define SX_DEBUG_TERMIOS 0x0200
+#define SX_DEBUG_SIGNALS 0x0400
+#define SX_DEBUG_FIFO    0x0800
+
+
+#define func_enter() dprintk (SX_DEBUG_FLOW, "io8: enter %s\n",__FUNCTION__)
+#define func_exit()  dprintk (SX_DEBUG_FLOW, "io8: exit  %s\n", __FUNCTION__)
+
+#define jiffies_from_ms(a) ((((a) * HZ)/1000)+1)
+
+
+/* Configurable options: */
+
+/* Am I paranoid or not ? ;-) */
+#define SPECIALIX_PARANOIA_CHECK
+
+/* Do I trust the IRQ from the card? (enabeling it doesn't seem to help)
+   When the IRQ routine leaves the chip in a state that is keeps on
+   requiring attention, the timer doesn't help either. */
+#undef SPECIALIX_TIMER
+
+#ifdef SPECIALIX_TIMER
+static int sx_poll = HZ;
+#endif
+
+
+
+/* 
+ * The following defines are mostly for testing purposes. But if you need
+ * some nice reporting in your syslog, you can define them also.
+ */
+#undef SX_REPORT_FIFO
+#undef SX_REPORT_OVERRUN
+
+
+
+#ifdef CONFIG_SPECIALIX_RTSCTS
+#define SX_CRTSCTS(bla) 1
+#else
+#define SX_CRTSCTS(tty) C_CRTSCTS(tty)
+#endif
+
+
+/* Used to be outb (0xff, 0x80); */
+#define short_pause() udelay (1)
+
+
+#define SPECIALIX_LEGAL_FLAGS \
+	(ASYNC_HUP_NOTIFY   | ASYNC_SAK          | ASYNC_SPLIT_TERMIOS   | \
+	 ASYNC_SPD_HI       | ASYNC_SPEED_VHI    | ASYNC_SESSION_LOCKOUT | \
+	 ASYNC_PGRP_LOCKOUT | ASYNC_CALLOUT_NOHUP)
+
+#undef RS_EVENT_WRITE_WAKEUP
+#define RS_EVENT_WRITE_WAKEUP	0
+
+static struct tty_driver *specialix_driver;
+static unsigned char * tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static unsigned long baud_table[] =  {
+	0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+	9600, 19200, 38400, 57600, 115200, 0, 
+};
+
+static struct specialix_board sx_board[SX_NBOARD] =  {
+	{ 0, SX_IOBASE1,  9, },
+	{ 0, SX_IOBASE2, 11, },
+	{ 0, SX_IOBASE3, 12, },
+	{ 0, SX_IOBASE4, 15, },
+};
+
+static struct specialix_port sx_port[SX_NBOARD * SX_NPORT];
+
+
+#ifdef SPECIALIX_TIMER
+static struct timer_list missed_irq_timer;
+static irqreturn_t sx_interrupt(int irq, void * dev_id, struct pt_regs * regs);
+#endif
+
+
+
+static inline int sx_paranoia_check(struct specialix_port const * port,
+				    char *name, const char *routine)
+{
+#ifdef SPECIALIX_PARANOIA_CHECK
+	static const char *badmagic =
+		KERN_ERR "sx: Warning: bad specialix port magic number for device %s in %s\n";
+	static const char *badinfo =
+		KERN_ERR "sx: Warning: null specialix port for device %s in %s\n";
+ 
+	if (!port) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (port->magic != SPECIALIX_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+#endif
+	return 0;
+}
+
+
+/*
+ * 
+ *  Service functions for specialix IO8+ driver.
+ * 
+ */
+
+/* Get board number from pointer */
+static inline int board_No (struct specialix_board * bp)
+{
+	return bp - sx_board;
+}
+
+
+/* Get port number from pointer */
+static inline int port_No (struct specialix_port const * port)
+{
+	return SX_PORT(port - sx_port); 
+}
+
+
+/* Get pointer to board from pointer to port */
+static inline struct specialix_board * port_Board(struct specialix_port const * port)
+{
+	return &sx_board[SX_BOARD(port - sx_port)];
+}
+
+
+/* Input Byte from CL CD186x register */
+static inline unsigned char sx_in(struct specialix_board  * bp, unsigned short reg)
+{
+	bp->reg = reg | 0x80;
+	outb (reg | 0x80, bp->base + SX_ADDR_REG);
+	return inb  (bp->base + SX_DATA_REG);
+}
+
+
+/* Output Byte to CL CD186x register */
+static inline void sx_out(struct specialix_board  * bp, unsigned short reg,
+			  unsigned char val)
+{
+	bp->reg = reg | 0x80;
+	outb (reg | 0x80, bp->base + SX_ADDR_REG);
+	outb (val, bp->base + SX_DATA_REG);
+}
+
+
+/* Input Byte from CL CD186x register */
+static inline unsigned char sx_in_off(struct specialix_board  * bp, unsigned short reg)
+{
+	bp->reg = reg;
+	outb (reg, bp->base + SX_ADDR_REG);
+	return inb  (bp->base + SX_DATA_REG);
+}
+
+
+/* Output Byte to CL CD186x register */
+static inline void sx_out_off(struct specialix_board  * bp, unsigned short reg,
+			  unsigned char val)
+{
+	bp->reg = reg;
+	outb (reg, bp->base + SX_ADDR_REG);
+	outb (val, bp->base + SX_DATA_REG);
+}
+
+
+/* Wait for Channel Command Register ready */
+static inline void sx_wait_CCR(struct specialix_board  * bp)
+{
+	unsigned long delay, flags;
+	unsigned char ccr;
+
+	for (delay = SX_CCR_TIMEOUT; delay; delay--) {
+		spin_lock_irqsave(&bp->lock, flags);
+		ccr = sx_in(bp, CD186x_CCR);
+		spin_unlock_irqrestore(&bp->lock, flags);
+		if (!ccr)
+			return;
+		udelay (1);
+	}
+	
+	printk(KERN_ERR "sx%d: Timeout waiting for CCR.\n", board_No(bp));
+}
+
+
+/* Wait for Channel Command Register ready */
+static inline void sx_wait_CCR_off(struct specialix_board  * bp)
+{
+	unsigned long delay;
+	unsigned char crr;
+	unsigned long flags;
+
+	for (delay = SX_CCR_TIMEOUT; delay; delay--) {
+		spin_lock_irqsave(&bp->lock, flags);
+		crr = sx_in_off(bp, CD186x_CCR);
+		spin_unlock_irqrestore(&bp->lock, flags);
+		if (!crr)
+			return;
+		udelay (1);
+	}
+	
+	printk(KERN_ERR "sx%d: Timeout waiting for CCR.\n", board_No(bp));
+}
+
+
+/*
+ *  specialix IO8+ IO range functions.
+ */
+
+static inline int sx_check_io_range(struct specialix_board * bp)
+{
+	return check_region (bp->base, SX_IO_SPACE);
+}
+
+
+static inline void sx_request_io_range(struct specialix_board * bp)
+{
+	request_region(bp->base, 
+	               bp->flags&SX_BOARD_IS_PCI?SX_PCI_IO_SPACE:SX_IO_SPACE,
+	               "specialix IO8+" );
+}
+
+
+static inline void sx_release_io_range(struct specialix_board * bp)
+{
+	release_region(bp->base, 
+	               bp->flags&SX_BOARD_IS_PCI?SX_PCI_IO_SPACE:SX_IO_SPACE);
+}
+
+	
+/* Must be called with enabled interrupts */
+/* Ugly. Very ugly. Don't use this for anything else than initialization 
+   code */
+static inline void sx_long_delay(unsigned long delay)
+{
+	unsigned long i;
+	
+	for (i = jiffies + delay; time_after(i, jiffies); ) ;
+}
+
+
+
+/* Set the IRQ using the RTS lines that run to the PAL on the board.... */
+static int sx_set_irq ( struct specialix_board *bp)
+{
+	int virq;
+	int i;
+	unsigned long flags;
+
+	if (bp->flags & SX_BOARD_IS_PCI) 
+		return 1;
+	switch (bp->irq) {
+	/* In the same order as in the docs... */
+	case 15: virq = 0;break;
+	case 12: virq = 1;break;
+	case 11: virq = 2;break;
+	case 9:  virq = 3;break;
+	default: printk (KERN_ERR "Speclialix: cannot set irq to %d.\n", bp->irq);
+	         return 0;
+	}
+	spin_lock_irqsave(&bp->lock, flags);
+	for (i=0;i<2;i++) {
+		sx_out(bp, CD186x_CAR, i);
+		sx_out(bp, CD186x_MSVRTS, ((virq >> i) & 0x1)? MSVR_RTS:0);
+	}
+	spin_unlock_irqrestore(&bp->lock, flags);
+	return 1;
+}
+
+
+/* Reset and setup CD186x chip */
+static int sx_init_CD186x(struct specialix_board  * bp)
+{
+	unsigned long flags;
+	int scaler;
+	int rv = 1;
+
+	func_enter();
+	sx_wait_CCR_off(bp);			   /* Wait for CCR ready        */
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out_off(bp, CD186x_CCR, CCR_HARDRESET);      /* Reset CD186x chip          */
+	spin_unlock_irqrestore(&bp->lock, flags);
+	sx_long_delay(HZ/20);                      /* Delay 0.05 sec            */
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out_off(bp, CD186x_GIVR, SX_ID);             /* Set ID for this chip      */
+	sx_out_off(bp, CD186x_GICR, 0);                 /* Clear all bits            */
+	sx_out_off(bp, CD186x_PILR1, SX_ACK_MINT);      /* Prio for modem intr       */
+	sx_out_off(bp, CD186x_PILR2, SX_ACK_TINT);      /* Prio for transmitter intr */
+	sx_out_off(bp, CD186x_PILR3, SX_ACK_RINT);      /* Prio for receiver intr    */
+	/* Set RegAckEn */
+	sx_out_off(bp, CD186x_SRCR, sx_in (bp, CD186x_SRCR) | SRCR_REGACKEN);
+	
+	/* Setting up prescaler. We need 4 ticks per 1 ms */
+	scaler =  SX_OSCFREQ/SPECIALIX_TPS;
+
+	sx_out_off(bp, CD186x_PPRH, scaler >> 8);
+	sx_out_off(bp, CD186x_PPRL, scaler & 0xff);
+	spin_unlock_irqrestore(&bp->lock, flags);
+
+	if (!sx_set_irq (bp)) {
+		/* Figure out how to pass this along... */
+		printk (KERN_ERR "Cannot set irq to %d.\n", bp->irq);
+		rv = 0;
+	}
+
+	func_exit();
+	return rv;
+}
+
+
+static int read_cross_byte (struct specialix_board *bp, int reg, int bit)
+{
+	int i;
+	int t;
+	unsigned long flags;
+
+	spin_lock_irqsave(&bp->lock, flags);
+	for (i=0, t=0;i<8;i++) {
+		sx_out_off (bp, CD186x_CAR, i);
+		if (sx_in_off (bp, reg) & bit) 
+			t |= 1 << i;
+	}
+	spin_unlock_irqrestore(&bp->lock, flags);
+
+	return t;
+}
+
+
+#ifdef SPECIALIX_TIMER
+void missed_irq (unsigned long data)
+{
+	unsigned char irq;
+	unsigned long flags;
+	struct specialix_board  *bp = (struct specialix_board *)data;
+
+	spin_lock_irqsave(&bp->lock, flags);
+	irq = sx_in ((struct specialix_board *)data, CD186x_SRSR) &
+	                                            (SRSR_RREQint |
+	                                             SRSR_TREQint |
+	                                             SRSR_MREQint);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	if (irq) {
+		printk (KERN_INFO "Missed interrupt... Calling int from timer. \n");
+		sx_interrupt (((struct specialix_board *)data)->irq, 
+		              (void*)data, NULL);
+	}
+	missed_irq_timer.expires = jiffies + sx_poll;
+	add_timer (&missed_irq_timer);
+}
+#endif
+
+
+
+/* Main probing routine, also sets irq. */
+static int sx_probe(struct specialix_board *bp)
+{
+	unsigned char val1, val2;
+#if 0
+	int irqs = 0;
+	int retries;
+#endif
+	int rev;
+	int chip;
+
+	func_enter();
+
+	if (sx_check_io_range(bp)) {
+		func_exit();
+		return 1;
+	}
+
+	/* Are the I/O ports here ? */
+	sx_out_off(bp, CD186x_PPRL, 0x5a);
+	short_pause ();
+	val1 = sx_in_off(bp, CD186x_PPRL);
+
+	sx_out_off(bp, CD186x_PPRL, 0xa5);
+	short_pause ();
+	val2 = sx_in_off(bp, CD186x_PPRL);
+
+	
+	if ((val1 != 0x5a) || (val2 != 0xa5)) {
+		printk(KERN_INFO "sx%d: specialix IO8+ Board at 0x%03x not found.\n",
+		       board_No(bp), bp->base);
+		func_exit();
+		return 1;
+	}
+
+	/* Check the DSR lines that Specialix uses as board 
+	   identification */
+	val1 = read_cross_byte (bp, CD186x_MSVR, MSVR_DSR);
+	val2 = read_cross_byte (bp, CD186x_MSVR, MSVR_RTS);
+	dprintk (SX_DEBUG_INIT, "sx%d: DSR lines are: %02x, rts lines are: %02x\n",
+	        board_No(bp),  val1, val2);
+
+	/* They managed to switch the bit order between the docs and
+	   the IO8+ card. The new PCI card now conforms to old docs.
+	   They changed the PCI docs to reflect the situation on the
+	   old card. */
+	val2 = (bp->flags & SX_BOARD_IS_PCI)?0x4d : 0xb2;
+	if (val1 != val2) {
+		printk(KERN_INFO "sx%d: specialix IO8+ ID %02x at 0x%03x not found (%02x).\n",
+		       board_No(bp), val2, bp->base, val1);
+		func_exit();
+		return 1;
+	}
+
+
+#if 0
+	/* It's time to find IRQ for this board */
+	for (retries = 0; retries < 5 && irqs <= 0; retries++) {
+		irqs = probe_irq_on();
+		sx_init_CD186x(bp);	       		/* Reset CD186x chip       */
+		sx_out(bp, CD186x_CAR, 2);               /* Select port 2          */
+		sx_wait_CCR(bp);
+		sx_out(bp, CD186x_CCR, CCR_TXEN);        /* Enable transmitter     */
+		sx_out(bp, CD186x_IER, IER_TXRDY);       /* Enable tx empty intr   */
+		sx_long_delay(HZ/20);	       		
+		irqs = probe_irq_off(irqs);
+
+		dprintk (SX_DEBUG_INIT, "SRSR = %02x, ", sx_in(bp, CD186x_SRSR));
+		dprintk (SX_DEBUG_INIT, "TRAR = %02x, ", sx_in(bp, CD186x_TRAR));
+		dprintk (SX_DEBUG_INIT, "GIVR = %02x, ", sx_in(bp, CD186x_GIVR));
+		dprintk (SX_DEBUG_INIT, "GICR = %02x, ", sx_in(bp, CD186x_GICR));
+		dprintk (SX_DEBUG_INIT, "\n");
+
+		/* Reset CD186x again      */
+		if (!sx_init_CD186x(bp)) {
+			/* Hmmm. This is dead code anyway. */
+		}
+
+		dprintk (SX_DEBUG_INIT "val1 = %02x, val2 = %02x, val3 = %02x.\n",
+		        val1, val2, val3); 
+	
+	}
+	
+#if 0
+	if (irqs <= 0) {
+		printk(KERN_ERR "sx%d: Can't find IRQ for specialix IO8+ board at 0x%03x.\n",
+		       board_No(bp), bp->base);
+		func_exit();
+		return 1;
+	}
+#endif
+	printk (KERN_INFO "Started with irq=%d, but now have irq=%d.\n", bp->irq, irqs);
+	if (irqs > 0)
+		bp->irq = irqs;
+#endif
+	/* Reset CD186x again  */
+	if (!sx_init_CD186x(bp)) {
+		func_exit();
+		return -EIO;
+	}
+
+	sx_request_io_range(bp);
+	bp->flags |= SX_BOARD_PRESENT;
+	
+	/* Chip           revcode   pkgtype
+	                  GFRCR     SRCR bit 7
+	   CD180 rev B    0x81      0
+	   CD180 rev C    0x82      0
+	   CD1864 rev A   0x82      1
+	   CD1865 rev A   0x83      1  -- Do not use!!! Does not work. 
+	   CD1865 rev B   0x84      1
+	 -- Thanks to Gwen Wang, Cirrus Logic.
+	 */
+
+	switch (sx_in_off(bp, CD186x_GFRCR)) {
+	case 0x82:chip = 1864;rev='A';break;
+	case 0x83:chip = 1865;rev='A';break;
+	case 0x84:chip = 1865;rev='B';break;
+	case 0x85:chip = 1865;rev='C';break; /* Does not exist at this time */
+	default:chip=-1;rev='x';
+	}
+
+	dprintk (SX_DEBUG_INIT, " GFCR = 0x%02x\n", sx_in_off(bp, CD186x_GFRCR) );
+
+#ifdef SPECIALIX_TIMER
+	init_timer (&missed_irq_timer);
+	missed_irq_timer.function = missed_irq;
+	missed_irq_timer.data = (unsigned long) bp;
+	missed_irq_timer.expires = jiffies + sx_poll;
+	add_timer (&missed_irq_timer);
+#endif
+
+	printk(KERN_INFO"sx%d: specialix IO8+ board detected at 0x%03x, IRQ %d, CD%d Rev. %c.\n",
+	       board_No(bp),
+	       bp->base, bp->irq,
+	       chip, rev);
+
+	func_exit();
+	return 0;
+}
+
+/* 
+ * 
+ *  Interrupt processing routines.
+ * */
+
+static inline void sx_mark_event(struct specialix_port * port, int event)
+{
+	func_enter();
+
+	set_bit(event, &port->event);
+	schedule_work(&port->tqueue);
+
+	func_exit();
+}
+
+
+static inline struct specialix_port * sx_get_port(struct specialix_board * bp,
+					       unsigned char const * what)
+{
+	unsigned char channel;
+	struct specialix_port * port = NULL;
+
+	channel = sx_in(bp, CD186x_GICR) >> GICR_CHAN_OFF;
+	dprintk (SX_DEBUG_CHAN, "channel: %d\n", channel);
+	if (channel < CD186x_NCH) {
+		port = &sx_port[board_No(bp) * SX_NPORT + channel];
+		dprintk (SX_DEBUG_CHAN, "port: %d %p flags: 0x%x\n",board_No(bp) * SX_NPORT + channel,  port, port->flags & ASYNC_INITIALIZED);
+
+		if (port->flags & ASYNC_INITIALIZED) {
+			dprintk (SX_DEBUG_CHAN, "port: %d %p\n", channel, port);
+			func_exit();
+			return port;
+		}
+	}
+	printk(KERN_INFO "sx%d: %s interrupt from invalid port %d\n", 
+	       board_No(bp), what, channel);
+	return NULL;
+}
+
+
+static inline void sx_receive_exc(struct specialix_board * bp)
+{
+	struct specialix_port *port;
+	struct tty_struct *tty;
+	unsigned char status;
+	unsigned char ch;
+
+	func_enter();
+
+	port = sx_get_port(bp, "Receive");
+	if (!port) {
+		dprintk (SX_DEBUG_RX, "Hmm, couldn't find port.\n");
+		func_exit();
+		return;
+	}
+	tty = port->tty;
+	dprintk (SX_DEBUG_RX, "port: %p count: %d BUFF_SIZE: %d\n",
+		 port,  tty->flip.count, TTY_FLIPBUF_SIZE);
+	
+	status = sx_in(bp, CD186x_RCSR);
+
+	dprintk (SX_DEBUG_RX, "status: 0x%x\n", status);
+	if (status & RCSR_OE) {
+		port->overrun++;
+		dprintk(SX_DEBUG_FIFO, "sx%d: port %d: Overrun. Total %ld overruns.\n",
+		       board_No(bp), port_No(port), port->overrun);
+	}
+	status &= port->mark_mask;
+
+	/* This flip buffer check needs to be below the reading of the
+	   status register to reset the chip's IRQ.... */
+	if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+		dprintk(SX_DEBUG_FIFO, "sx%d: port %d: Working around flip buffer overflow.\n",
+		       board_No(bp), port_No(port));
+		func_exit();
+		return;
+	}
+
+	ch = sx_in(bp, CD186x_RDR);
+	if (!status) {
+		func_exit();
+		return;
+	}
+	if (status & RCSR_TOUT) {
+		printk(KERN_INFO "sx%d: port %d: Receiver timeout. Hardware problems ?\n", 
+		       board_No(bp), port_No(port));
+		func_exit();
+		return;
+		
+	} else if (status & RCSR_BREAK) {
+		dprintk(SX_DEBUG_RX, "sx%d: port %d: Handling break...\n",
+		       board_No(bp), port_No(port));
+		*tty->flip.flag_buf_ptr++ = TTY_BREAK;
+		if (port->flags & ASYNC_SAK)
+			do_SAK(tty);
+		
+	} else if (status & RCSR_PE) 
+		*tty->flip.flag_buf_ptr++ = TTY_PARITY;
+	
+	else if (status & RCSR_FE) 
+		*tty->flip.flag_buf_ptr++ = TTY_FRAME;
+	
+	else if (status & RCSR_OE)
+		*tty->flip.flag_buf_ptr++ = TTY_OVERRUN;
+	
+	else
+		*tty->flip.flag_buf_ptr++ = 0;
+	
+	*tty->flip.char_buf_ptr++ = ch;
+	tty->flip.count++;
+	schedule_delayed_work(&tty->flip.work, 1);
+
+	func_exit();
+}
+
+
+static inline void sx_receive(struct specialix_board * bp)
+{
+	struct specialix_port *port;
+	struct tty_struct *tty;
+	unsigned char count;
+
+	func_enter();
+	
+	if (!(port = sx_get_port(bp, "Receive"))) {
+		dprintk (SX_DEBUG_RX, "Hmm, couldn't find port.\n");
+		func_exit();
+		return;
+	}
+	tty = port->tty;
+	
+	count = sx_in(bp, CD186x_RDCR);
+	dprintk (SX_DEBUG_RX, "port: %p: count: %d\n", port, count);
+	port->hits[count > 8 ? 9 : count]++;
+	
+	while (count--) {
+		if (tty->flip.count >= TTY_FLIPBUF_SIZE) {
+			printk(KERN_INFO "sx%d: port %d: Working around flip buffer overflow.\n",
+			       board_No(bp), port_No(port));
+			break;
+		}
+		*tty->flip.char_buf_ptr++ = sx_in(bp, CD186x_RDR);
+		*tty->flip.flag_buf_ptr++ = 0;
+		tty->flip.count++;
+	}
+	schedule_delayed_work(&tty->flip.work, 1);
+
+	func_exit();
+}
+
+
+static inline void sx_transmit(struct specialix_board * bp)
+{
+	struct specialix_port *port;
+	struct tty_struct *tty;
+	unsigned char count;
+
+	func_enter();
+	if (!(port = sx_get_port(bp, "Transmit"))) {
+		func_exit();
+		return;
+	}
+	dprintk (SX_DEBUG_TX, "port: %p\n", port);
+	tty = port->tty;
+	
+	if (port->IER & IER_TXEMPTY) {
+		/* FIFO drained */
+		sx_out(bp, CD186x_CAR, port_No(port));
+		port->IER &= ~IER_TXEMPTY;
+		sx_out(bp, CD186x_IER, port->IER);
+		func_exit();
+		return;
+	}
+	
+	if ((port->xmit_cnt <= 0 && !port->break_length)
+	    || tty->stopped || tty->hw_stopped) {
+		sx_out(bp, CD186x_CAR, port_No(port));
+		port->IER &= ~IER_TXRDY;
+		sx_out(bp, CD186x_IER, port->IER);
+		func_exit();
+		return;
+	}
+	
+	if (port->break_length) {
+		if (port->break_length > 0) {
+			if (port->COR2 & COR2_ETC) {
+				sx_out(bp, CD186x_TDR, CD186x_C_ESC);
+				sx_out(bp, CD186x_TDR, CD186x_C_SBRK);
+				port->COR2 &= ~COR2_ETC;
+			}
+			count = min_t(int, port->break_length, 0xff);
+			sx_out(bp, CD186x_TDR, CD186x_C_ESC);
+			sx_out(bp, CD186x_TDR, CD186x_C_DELAY);
+			sx_out(bp, CD186x_TDR, count);
+			if (!(port->break_length -= count))
+				port->break_length--;
+		} else {
+			sx_out(bp, CD186x_TDR, CD186x_C_ESC);
+			sx_out(bp, CD186x_TDR, CD186x_C_EBRK);
+			sx_out(bp, CD186x_COR2, port->COR2);
+			sx_wait_CCR(bp);
+			sx_out(bp, CD186x_CCR, CCR_CORCHG2);
+			port->break_length = 0;
+		}
+
+		func_exit();
+		return;
+	}
+	
+	count = CD186x_NFIFO;
+	do {
+		sx_out(bp, CD186x_TDR, port->xmit_buf[port->xmit_tail++]);
+		port->xmit_tail = port->xmit_tail & (SERIAL_XMIT_SIZE-1);
+		if (--port->xmit_cnt <= 0)
+			break;
+	} while (--count > 0);
+	
+	if (port->xmit_cnt <= 0) {
+		sx_out(bp, CD186x_CAR, port_No(port));
+		port->IER &= ~IER_TXRDY;
+		sx_out(bp, CD186x_IER, port->IER);
+	}
+	if (port->xmit_cnt <= port->wakeup_chars)
+		sx_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+
+	func_exit();
+}
+
+
+static inline void sx_check_modem(struct specialix_board * bp)
+{
+	struct specialix_port *port;
+	struct tty_struct *tty;
+	unsigned char mcr;
+	int msvr_cd;
+
+	dprintk (SX_DEBUG_SIGNALS, "Modem intr. ");
+	if (!(port = sx_get_port(bp, "Modem")))
+		return;
+	
+	tty = port->tty;
+	
+	mcr = sx_in(bp, CD186x_MCR);
+	printk ("mcr = %02x.\n", mcr);
+
+	if ((mcr & MCR_CDCHG)) {
+		dprintk (SX_DEBUG_SIGNALS, "CD just changed... ");
+		msvr_cd = sx_in(bp, CD186x_MSVR) & MSVR_CD;
+		if (msvr_cd) {
+			dprintk (SX_DEBUG_SIGNALS, "Waking up guys in open.\n");
+			wake_up_interruptible(&port->open_wait);
+		} else {
+			dprintk (SX_DEBUG_SIGNALS, "Sending HUP.\n");
+			schedule_work(&port->tqueue_hangup);
+		}
+	}
+	
+#ifdef SPECIALIX_BRAIN_DAMAGED_CTS
+	if (mcr & MCR_CTSCHG) {
+		if (sx_in(bp, CD186x_MSVR) & MSVR_CTS) {
+			tty->hw_stopped = 0;
+			port->IER |= IER_TXRDY;
+			if (port->xmit_cnt <= port->wakeup_chars)
+				sx_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+		} else {
+			tty->hw_stopped = 1;
+			port->IER &= ~IER_TXRDY;
+		}
+		sx_out(bp, CD186x_IER, port->IER);
+	}
+	if (mcr & MCR_DSSXHG) {
+		if (sx_in(bp, CD186x_MSVR) & MSVR_DSR) {
+			tty->hw_stopped = 0;
+			port->IER |= IER_TXRDY;
+			if (port->xmit_cnt <= port->wakeup_chars)
+				sx_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+		} else {
+			tty->hw_stopped = 1;
+			port->IER &= ~IER_TXRDY;
+		}
+		sx_out(bp, CD186x_IER, port->IER);
+	}
+#endif /* SPECIALIX_BRAIN_DAMAGED_CTS */
+	
+	/* Clear change bits */
+	sx_out(bp, CD186x_MCR, 0);
+}
+
+
+/* The main interrupt processing routine */
+static irqreturn_t sx_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned char status;
+	unsigned char ack;
+	struct specialix_board *bp;
+	unsigned long loop = 0;
+	int saved_reg;
+	unsigned long flags;
+
+	func_enter();
+
+	bp = dev_id;
+	spin_lock_irqsave(&bp->lock, flags);
+
+	dprintk (SX_DEBUG_FLOW, "enter %s port %d room: %ld\n", __FUNCTION__, port_No(sx_get_port(bp, "INT")), SERIAL_XMIT_SIZE - sx_get_port(bp, "ITN")->xmit_cnt - 1);
+	if (!bp || !(bp->flags & SX_BOARD_ACTIVE)) {
+		dprintk (SX_DEBUG_IRQ, "sx: False interrupt. irq %d.\n", irq);
+		spin_unlock_irqrestore(&bp->lock, flags);
+		func_exit();
+		return IRQ_NONE;
+	}
+
+	saved_reg = bp->reg;
+
+	while ((++loop < 16) && (status = (sx_in(bp, CD186x_SRSR) &
+	                                   (SRSR_RREQint |
+		                            SRSR_TREQint |
+	                                    SRSR_MREQint)))) {	
+		if (status & SRSR_RREQint) {
+			ack = sx_in(bp, CD186x_RRAR);
+
+			if (ack == (SX_ID | GIVR_IT_RCV))
+				sx_receive(bp);
+			else if (ack == (SX_ID | GIVR_IT_REXC))
+				sx_receive_exc(bp);
+			else
+				printk(KERN_ERR "sx%d: status: 0x%x Bad receive ack 0x%02x.\n",
+				       board_No(bp), status, ack);
+		
+		} else if (status & SRSR_TREQint) {
+			ack = sx_in(bp, CD186x_TRAR);
+
+			if (ack == (SX_ID | GIVR_IT_TX))
+				sx_transmit(bp);
+			else
+				printk(KERN_ERR "sx%d: status: 0x%x Bad transmit ack 0x%02x. port: %d\n",
+				       board_No(bp), status, ack, port_No (sx_get_port (bp, "Int")));
+		} else if (status & SRSR_MREQint) {
+			ack = sx_in(bp, CD186x_MRAR);
+
+			if (ack == (SX_ID | GIVR_IT_MODEM)) 
+				sx_check_modem(bp);
+			else
+				printk(KERN_ERR "sx%d: status: 0x%x Bad modem ack 0x%02x.\n",
+				       board_No(bp), status, ack);
+		
+		} 
+
+		sx_out(bp, CD186x_EOIR, 0);   /* Mark end of interrupt */
+	}
+	bp->reg = saved_reg;
+	outb (bp->reg, bp->base + SX_ADDR_REG);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	func_exit();
+	return IRQ_HANDLED;
+}
+
+
+/*
+ *  Routines for open & close processing.
+ */
+
+static void turn_ints_off (struct specialix_board *bp)
+{
+	unsigned long flags;
+
+	func_enter();
+	if (bp->flags & SX_BOARD_IS_PCI) {
+		/* This was intended for enabeling the interrupt on the
+		 * PCI card. However it seems that it's already enabled
+		 * and as PCI interrupts can be shared, there is no real
+		 * reason to have to turn it off. */
+	}
+
+	spin_lock_irqsave(&bp->lock, flags);
+	(void) sx_in_off (bp, 0); /* Turn off interrupts. */
+	spin_unlock_irqrestore(&bp->lock, flags);
+
+	func_exit();
+}
+
+static void turn_ints_on (struct specialix_board *bp)
+{
+	unsigned long flags;
+
+	func_enter();
+
+	if (bp->flags & SX_BOARD_IS_PCI) {
+		/* play with the PCI chip. See comment above. */
+	}
+	spin_lock_irqsave(&bp->lock, flags);
+	(void) sx_in (bp, 0); /* Turn ON interrupts. */
+	spin_unlock_irqrestore(&bp->lock, flags);
+
+	func_exit();
+}
+
+
+/* Called with disabled interrupts */
+static inline int sx_setup_board(struct specialix_board * bp)
+{
+	int error;
+
+	if (bp->flags & SX_BOARD_ACTIVE) 
+		return 0;
+
+	if (bp->flags & SX_BOARD_IS_PCI)
+		error = request_irq(bp->irq, sx_interrupt, SA_INTERRUPT | SA_SHIRQ, "specialix IO8+", bp);
+	else
+		error = request_irq(bp->irq, sx_interrupt, SA_INTERRUPT, "specialix IO8+", bp);
+
+	if (error) 
+		return error;
+
+	turn_ints_on (bp);
+	bp->flags |= SX_BOARD_ACTIVE;
+
+	return 0;
+}
+
+
+/* Called with disabled interrupts */
+static inline void sx_shutdown_board(struct specialix_board *bp)
+{
+	func_enter();
+
+	if (!(bp->flags & SX_BOARD_ACTIVE)) {
+		func_exit();
+		return;
+	}
+
+	bp->flags &= ~SX_BOARD_ACTIVE;
+	
+	dprintk (SX_DEBUG_IRQ, "Freeing IRQ%d for board %d.\n",
+		 bp->irq, board_No (bp));
+	free_irq(bp->irq, bp);
+
+	turn_ints_off (bp);
+
+
+	func_exit();
+}
+
+
+/*
+ * Setting up port characteristics. 
+ * Must be called with disabled interrupts
+ */
+static void sx_change_speed(struct specialix_board *bp, struct specialix_port *port)
+{
+	struct tty_struct *tty;
+	unsigned long baud;
+	long tmp;
+	unsigned char cor1 = 0, cor3 = 0;
+	unsigned char mcor1 = 0, mcor2 = 0;
+	static unsigned long again;
+	unsigned long flags;
+
+	func_enter();
+
+	if (!(tty = port->tty) || !tty->termios) {
+		func_exit();
+		return;
+	}
+
+	port->IER  = 0;
+	port->COR2 = 0;
+	/* Select port on the board */
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CAR, port_No(port));
+
+	/* The Specialix board doens't implement the RTS lines.
+	   They are used to set the IRQ level. Don't touch them. */
+	if (SX_CRTSCTS(tty))
+		port->MSVR = MSVR_DTR | (sx_in(bp, CD186x_MSVR) & MSVR_RTS);
+	else
+		port->MSVR =  (sx_in(bp, CD186x_MSVR) & MSVR_RTS);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	dprintk (SX_DEBUG_TERMIOS, "sx: got MSVR=%02x.\n", port->MSVR);
+	baud = C_BAUD(tty);
+	
+	if (baud & CBAUDEX) {
+		baud &= ~CBAUDEX;
+		if (baud < 1 || baud > 2) 
+			port->tty->termios->c_cflag &= ~CBAUDEX;
+		else
+			baud += 15;
+	}
+	if (baud == 15) {
+		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			baud ++;
+		if ((port->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			baud += 2;
+	}
+	
+	
+	if (!baud_table[baud]) {
+		/* Drop DTR & exit */
+		dprintk (SX_DEBUG_TERMIOS, "Dropping DTR...  Hmm....\n");
+		if (!SX_CRTSCTS (tty)) {
+			port -> MSVR &= ~ MSVR_DTR;
+			spin_lock_irqsave(&bp->lock, flags);
+			sx_out(bp, CD186x_MSVR, port->MSVR );
+			spin_unlock_irqrestore(&bp->lock, flags);
+		} 
+		else
+			dprintk (SX_DEBUG_TERMIOS, "Can't drop DTR: no DTR.\n");
+		return;
+	} else {
+		/* Set DTR on */
+		if (!SX_CRTSCTS (tty)) {
+			port ->MSVR |= MSVR_DTR;
+		}
+	}
+	
+	/*
+	 * Now we must calculate some speed depended things 
+	 */
+
+	/* Set baud rate for port */
+	tmp = port->custom_divisor ;
+	if ( tmp )
+		printk (KERN_INFO "sx%d: Using custom baud rate divisor %ld. \n"
+		                  "This is an untested option, please be carefull.\n",
+		                  port_No (port), tmp);
+	else
+		tmp = (((SX_OSCFREQ + baud_table[baud]/2) / baud_table[baud] +
+		         CD186x_TPC/2) / CD186x_TPC);
+
+	if ((tmp < 0x10) && time_before(again, jiffies)) { 
+		again = jiffies + HZ * 60;
+		/* Page 48 of version 2.0 of the CL-CD1865 databook */
+		if (tmp >= 12) {
+			printk (KERN_INFO "sx%d: Baud rate divisor is %ld. \n"
+			        "Performance degradation is possible.\n"
+			        "Read specialix.txt for more info.\n",
+			        port_No (port), tmp);
+		} else {
+			printk (KERN_INFO "sx%d: Baud rate divisor is %ld. \n"
+			        "Warning: overstressing Cirrus chip. "
+			        "This might not work.\n"
+			        "Read specialix.txt for more info.\n", 
+			        port_No (port), tmp);
+		}
+	}
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_RBPRH, (tmp >> 8) & 0xff); 
+	sx_out(bp, CD186x_TBPRH, (tmp >> 8) & 0xff); 
+	sx_out(bp, CD186x_RBPRL, tmp & 0xff); 
+	sx_out(bp, CD186x_TBPRL, tmp & 0xff);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	if (port->custom_divisor) {
+		baud = (SX_OSCFREQ + port->custom_divisor/2) / port->custom_divisor;
+		baud = ( baud + 5 ) / 10;
+	} else 
+		baud = (baud_table[baud] + 5) / 10;   /* Estimated CPS */
+
+	/* Two timer ticks seems enough to wakeup something like SLIP driver */
+	tmp = ((baud + HZ/2) / HZ) * 2 - CD186x_NFIFO;		
+	port->wakeup_chars = (tmp < 0) ? 0 : ((tmp >= SERIAL_XMIT_SIZE) ?
+					      SERIAL_XMIT_SIZE - 1 : tmp);
+	
+	/* Receiver timeout will be transmission time for 1.5 chars */
+	tmp = (SPECIALIX_TPS + SPECIALIX_TPS/2 + baud/2) / baud;
+	tmp = (tmp > 0xff) ? 0xff : tmp;
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_RTPR, tmp);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	switch (C_CSIZE(tty)) {
+	 case CS5:
+		cor1 |= COR1_5BITS;
+		break;
+	 case CS6:
+		cor1 |= COR1_6BITS;
+		break;
+	 case CS7:
+		cor1 |= COR1_7BITS;
+		break;
+	 case CS8:
+		cor1 |= COR1_8BITS;
+		break;
+	}
+	
+	if (C_CSTOPB(tty)) 
+		cor1 |= COR1_2SB;
+	
+	cor1 |= COR1_IGNORE;
+	if (C_PARENB(tty)) {
+		cor1 |= COR1_NORMPAR;
+		if (C_PARODD(tty)) 
+			cor1 |= COR1_ODDP;
+		if (I_INPCK(tty)) 
+			cor1 &= ~COR1_IGNORE;
+	}
+	/* Set marking of some errors */
+	port->mark_mask = RCSR_OE | RCSR_TOUT;
+	if (I_INPCK(tty)) 
+		port->mark_mask |= RCSR_FE | RCSR_PE;
+	if (I_BRKINT(tty) || I_PARMRK(tty)) 
+		port->mark_mask |= RCSR_BREAK;
+	if (I_IGNPAR(tty)) 
+		port->mark_mask &= ~(RCSR_FE | RCSR_PE);
+	if (I_IGNBRK(tty)) {
+		port->mark_mask &= ~RCSR_BREAK;
+		if (I_IGNPAR(tty)) 
+			/* Real raw mode. Ignore all */
+			port->mark_mask &= ~RCSR_OE;
+	}
+	/* Enable Hardware Flow Control */
+	if (C_CRTSCTS(tty)) {
+#ifdef SPECIALIX_BRAIN_DAMAGED_CTS
+		port->IER |= IER_DSR | IER_CTS;
+		mcor1 |= MCOR1_DSRZD | MCOR1_CTSZD;
+		mcor2 |= MCOR2_DSROD | MCOR2_CTSOD;
+		spin_lock_irqsave(&bp->lock, flags);
+		tty->hw_stopped = !(sx_in(bp, CD186x_MSVR) & (MSVR_CTS|MSVR_DSR));
+		spin_unlock_irqrestore(&bp->lock, flags);
+#else
+		port->COR2 |= COR2_CTSAE; 
+#endif
+	}
+	/* Enable Software Flow Control. FIXME: I'm not sure about this */
+	/* Some people reported that it works, but I still doubt it */
+	if (I_IXON(tty)) {
+		port->COR2 |= COR2_TXIBE;
+		cor3 |= (COR3_FCT | COR3_SCDE);
+		if (I_IXANY(tty))
+			port->COR2 |= COR2_IXM;
+		spin_lock_irqsave(&bp->lock, flags);
+		sx_out(bp, CD186x_SCHR1, START_CHAR(tty));
+		sx_out(bp, CD186x_SCHR2, STOP_CHAR(tty));
+		sx_out(bp, CD186x_SCHR3, START_CHAR(tty));
+		sx_out(bp, CD186x_SCHR4, STOP_CHAR(tty));
+		spin_unlock_irqrestore(&bp->lock, flags);
+	}
+	if (!C_CLOCAL(tty)) {
+		/* Enable CD check */
+		port->IER |= IER_CD;
+		mcor1 |= MCOR1_CDZD;
+		mcor2 |= MCOR2_CDOD;
+	}
+	
+	if (C_CREAD(tty)) 
+		/* Enable receiver */
+		port->IER |= IER_RXD;
+	
+	/* Set input FIFO size (1-8 bytes) */
+	cor3 |= sx_rxfifo;
+	/* Setting up CD186x channel registers */
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_COR1, cor1);
+	sx_out(bp, CD186x_COR2, port->COR2);
+	sx_out(bp, CD186x_COR3, cor3);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	/* Make CD186x know about registers change */
+	sx_wait_CCR(bp);
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CCR, CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3);
+	/* Setting up modem option registers */
+	dprintk (SX_DEBUG_TERMIOS, "Mcor1 = %02x, mcor2 = %02x.\n", mcor1, mcor2);
+	sx_out(bp, CD186x_MCOR1, mcor1);
+	sx_out(bp, CD186x_MCOR2, mcor2);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	/* Enable CD186x transmitter & receiver */
+	sx_wait_CCR(bp);
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CCR, CCR_TXEN | CCR_RXEN);
+	/* Enable interrupts */
+	sx_out(bp, CD186x_IER, port->IER);
+	/* And finally set the modem lines... */
+	sx_out(bp, CD186x_MSVR, port->MSVR);
+	spin_unlock_irqrestore(&bp->lock, flags);
+
+	func_exit();
+}
+
+
+/* Must be called with interrupts enabled */
+static int sx_setup_port(struct specialix_board *bp, struct specialix_port *port)
+{
+	unsigned long flags;
+
+	func_enter();
+
+	if (port->flags & ASYNC_INITIALIZED) {
+		func_exit();
+		return 0;
+	}
+	
+	if (!port->xmit_buf) {
+		/* We may sleep in get_zeroed_page() */
+		unsigned long tmp;
+		
+		if (!(tmp = get_zeroed_page(GFP_KERNEL))) {
+			func_exit();
+			return -ENOMEM;
+		}
+
+		if (port->xmit_buf) {
+			free_page(tmp);
+			func_exit();
+			return -ERESTARTSYS;
+		}
+		port->xmit_buf = (unsigned char *) tmp;
+	}
+		
+	spin_lock_irqsave(&port->lock, flags);
+
+	if (port->tty) 
+		clear_bit(TTY_IO_ERROR, &port->tty->flags);
+
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	sx_change_speed(bp, port);
+	port->flags |= ASYNC_INITIALIZED;
+
+	spin_unlock_irqrestore(&port->lock, flags);
+
+		
+	func_exit();
+	return 0;
+}
+
+
+/* Must be called with interrupts disabled */
+static void sx_shutdown_port(struct specialix_board *bp, struct specialix_port *port)
+{
+	struct tty_struct *tty;
+	int i;
+	unsigned long flags;
+	
+	func_enter();
+
+	if (!(port->flags & ASYNC_INITIALIZED)) {
+		func_exit();
+		return;
+	}
+	
+	if (sx_debug & SX_DEBUG_FIFO) {
+		dprintk(SX_DEBUG_FIFO, "sx%d: port %d: %ld overruns, FIFO hits [ ",
+			board_No(bp), port_No(port), port->overrun);
+		for (i = 0; i < 10; i++) {
+			dprintk(SX_DEBUG_FIFO, "%ld ", port->hits[i]);
+		}
+		dprintk(SX_DEBUG_FIFO, "].\n");
+	}
+
+	if (port->xmit_buf) {
+		free_page((unsigned long) port->xmit_buf);
+		port->xmit_buf = NULL;
+	}
+
+	/* Select port */
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CAR, port_No(port));
+
+	if (!(tty = port->tty) || C_HUPCL(tty)) {
+		/* Drop DTR */
+		sx_out(bp, CD186x_MSVDTR, 0);
+	}
+	spin_unlock_irqrestore(&bp->lock, flags);
+	/* Reset port */
+	sx_wait_CCR(bp);
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CCR, CCR_SOFTRESET);
+	/* Disable all interrupts from this port */
+	port->IER = 0;
+	sx_out(bp, CD186x_IER, port->IER);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	if (tty)
+		set_bit(TTY_IO_ERROR, &tty->flags);
+	port->flags &= ~ASYNC_INITIALIZED;
+	
+	if (!bp->count) 
+		sx_shutdown_board(bp);
+	func_exit();
+}
+
+	
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+                           struct specialix_port *port)
+{
+	DECLARE_WAITQUEUE(wait,  current);
+	struct specialix_board *bp = port_Board(port);
+	int    retval;
+	int    do_clocal = 0;
+	int    CD;
+	unsigned long flags;
+
+	func_enter();
+
+	/*
+	 * 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 & ASYNC_CLOSING) {
+		interruptible_sleep_on(&port->close_wait);
+		if (port->flags & ASYNC_HUP_NOTIFY) {
+			func_exit();
+			return -EAGAIN;
+		} else {
+			func_exit();
+			return -ERESTARTSYS;
+		}
+	}
+	
+	/*
+	 * 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))) {
+		port->flags |= ASYNC_NORMAL_ACTIVE;
+		func_exit();
+		return 0;
+	}
+
+	if (C_CLOCAL(tty))
+		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, info->count is dropped by one, so that
+	 * rs_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	retval = 0;
+	add_wait_queue(&port->open_wait, &wait);
+	spin_lock_irqsave(&port->lock, flags);
+	if (!tty_hung_up_p(filp)) {
+		port->count--;
+	}
+	spin_unlock_irqrestore(&port->lock, flags);
+	port->blocked_open++;
+	while (1) {
+		spin_lock_irqsave(&bp->lock, flags);
+		sx_out(bp, CD186x_CAR, port_No(port));
+		CD = sx_in(bp, CD186x_MSVR) & MSVR_CD;
+		if (SX_CRTSCTS (tty)) {
+			/* Activate RTS */
+			port->MSVR |= MSVR_DTR;		/* WTF? */
+			sx_out (bp, CD186x_MSVR, port->MSVR);
+		} else {
+			/* Activate DTR */
+			port->MSVR |= MSVR_DTR;
+			sx_out (bp, CD186x_MSVR, port->MSVR);
+		}
+		spin_unlock_irqrestore(&bp->lock, flags);
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (tty_hung_up_p(filp) ||
+		    !(port->flags & ASYNC_INITIALIZED)) {
+			if (port->flags & ASYNC_HUP_NOTIFY)
+				retval = -EAGAIN;
+			else
+				retval = -ERESTARTSYS;	
+			break;
+		}
+		if (!(port->flags & ASYNC_CLOSING) &&
+		    (do_clocal || CD))
+			break;
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();
+	}
+
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&port->open_wait, &wait);
+	spin_lock_irqsave(&port->lock, flags);
+	if (!tty_hung_up_p(filp)) {
+		port->count++;
+	}
+	port->blocked_open--;
+	spin_unlock_irqrestore(&port->lock, flags);
+	if (retval) {
+		func_exit();
+		return retval;
+	}
+
+	port->flags |= ASYNC_NORMAL_ACTIVE;
+	func_exit();
+	return 0;
+}	
+
+
+static int sx_open(struct tty_struct * tty, struct file * filp)
+{
+	int board;
+	int error;
+	struct specialix_port * port;
+	struct specialix_board * bp;
+	int i;
+	unsigned long flags;
+
+	func_enter();
+
+	board = SX_BOARD(tty->index);
+
+	if (board >= SX_NBOARD || !(sx_board[board].flags & SX_BOARD_PRESENT)) {
+		func_exit();
+		return -ENODEV;
+	}
+	
+	bp = &sx_board[board];
+	port = sx_port + board * SX_NPORT + SX_PORT(tty->index);
+	port->overrun = 0;
+	for (i = 0; i < 10; i++)
+		port->hits[i]=0;
+
+	dprintk (SX_DEBUG_OPEN, "Board = %d, bp = %p, port = %p, portno = %d.\n",
+	        board, bp, port, SX_PORT(tty->index));
+
+	if (sx_paranoia_check(port, tty->name, "sx_open")) {
+		func_enter();
+		return -ENODEV;
+	}
+
+	if ((error = sx_setup_board(bp))) {
+		func_exit();
+		return error;
+	}
+
+	spin_lock_irqsave(&bp->lock, flags);
+	port->count++;
+	bp->count++;
+	tty->driver_data = port;
+	port->tty = tty;
+	spin_unlock_irqrestore(&bp->lock, flags);
+
+	if ((error = sx_setup_port(bp, port))) {
+		func_enter();
+		return error;
+	}
+	
+	if ((error = block_til_ready(tty, filp, port))) {
+		func_enter();
+		return error;
+	}
+
+	func_exit();
+	return 0;
+}
+
+
+static void sx_close(struct tty_struct * tty, struct file * filp)
+{
+	struct specialix_port *port = (struct specialix_port *) tty->driver_data;
+	struct specialix_board *bp;
+	unsigned long flags;
+	unsigned long timeout;
+	
+	func_enter();
+	if (!port || sx_paranoia_check(port, tty->name, "close")) {
+		func_exit();
+		return;
+	}
+	spin_lock_irqsave(&port->lock, flags);
+
+	if (tty_hung_up_p(filp)) {
+		spin_unlock_irqrestore(&port->lock, flags);
+		func_exit();
+		return;
+	}
+	
+	bp = port_Board(port);
+	if ((tty->count == 1) && (port->count != 1)) {
+		printk(KERN_ERR "sx%d: sx_close: bad port count;"
+		       " tty->count is 1, port count is %d\n",
+		       board_No(bp), port->count);
+		port->count = 1;
+	}
+
+	if (port->count > 1) {
+		port->count--;
+		bp->count--;
+
+		spin_unlock_irqrestore(&port->lock, flags);
+
+		func_exit();
+		return;
+	}
+	port->flags |= ASYNC_CLOSING;
+	/*
+	 * Now we wait for the transmit buffer to clear; and we notify 
+	 * the line discipline to only process XON/XOFF characters.
+	 */
+	tty->closing = 1;
+	spin_unlock_irqrestore(&port->lock, flags);
+	dprintk (SX_DEBUG_OPEN, "Closing\n");
+	if (port->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+		tty_wait_until_sent(tty, port->closing_wait);
+	}
+	/*
+	 * At this point we stop accepting input.  To do this, we
+	 * disable the receive line status interrupts, and tell the
+	 * interrupt driver to stop checking the data ready bit in the
+	 * line status register.
+	 */
+	dprintk (SX_DEBUG_OPEN, "Closed\n");
+	port->IER &= ~IER_RXD;
+	if (port->flags & ASYNC_INITIALIZED) {
+		port->IER &= ~IER_TXRDY;
+		port->IER |= IER_TXEMPTY;
+		spin_lock_irqsave(&bp->lock, flags);
+		sx_out(bp, CD186x_CAR, port_No(port));
+		sx_out(bp, CD186x_IER, port->IER);
+		spin_unlock_irqrestore(&bp->lock, flags);
+		/*
+		 * Before we drop DTR, make sure the UART transmitter
+		 * has completely drained; this is especially
+		 * important if there is a transmit FIFO!
+		 */
+		timeout = jiffies+HZ;
+		while(port->IER & IER_TXEMPTY) {
+			set_current_state (TASK_INTERRUPTIBLE);
+			msleep_interruptible(jiffies_to_msecs(port->timeout));
+			if (time_after(jiffies, timeout)) {
+				printk (KERN_INFO "Timeout waiting for close\n");
+				break;
+			}
+		}
+
+	}
+
+	if (--bp->count < 0) {
+		printk(KERN_ERR "sx%d: sx_shutdown_port: bad board count: %d port: %d\n",
+		       board_No(bp), bp->count, tty->index);
+		bp->count = 0;
+	}
+	if (--port->count < 0) {
+		printk(KERN_ERR "sx%d: sx_close: bad port count for tty%d: %d\n",
+		       board_No(bp), port_No(port), port->count);
+		port->count = 0;
+	}
+
+	sx_shutdown_port(bp, port);
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+	tty_ldisc_flush(tty);
+	spin_lock_irqsave(&port->lock, flags);
+	tty->closing = 0;
+	port->event = 0;
+	port->tty = NULL;
+	spin_unlock_irqrestore(&port->lock, flags);
+	if (port->blocked_open) {
+		if (port->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(port->close_delay));
+		}
+		wake_up_interruptible(&port->open_wait);
+	}
+	port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+	wake_up_interruptible(&port->close_wait);
+
+	func_exit();
+}
+
+
+static int sx_write(struct tty_struct * tty, 
+                    const unsigned char *buf, int count)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	struct specialix_board *bp;
+	int c, total = 0;
+	unsigned long flags;
+
+	func_enter();
+	if (sx_paranoia_check(port, tty->name, "sx_write")) {
+		func_exit();
+		return 0;
+	}
+	
+	bp = port_Board(port);
+
+	if (!tty || !port->xmit_buf || !tmp_buf) {
+		func_exit();
+		return 0;
+	}
+
+	while (1) {
+		spin_lock_irqsave(&port->lock, flags);
+		c = min_t(int, count, min(SERIAL_XMIT_SIZE - port->xmit_cnt - 1,
+				   SERIAL_XMIT_SIZE - port->xmit_head));
+		if (c <= 0) {
+			spin_unlock_irqrestore(&port->lock, flags);
+			break;
+		}
+		memcpy(port->xmit_buf + port->xmit_head, buf, c);
+		port->xmit_head = (port->xmit_head + c) & (SERIAL_XMIT_SIZE-1);
+		port->xmit_cnt += c;
+		spin_unlock_irqrestore(&port->lock, flags);
+
+		buf += c;
+		count -= c;
+		total += c;
+	}
+
+	spin_lock_irqsave(&bp->lock, flags);
+	if (port->xmit_cnt && !tty->stopped && !tty->hw_stopped &&
+	    !(port->IER & IER_TXRDY)) {
+		port->IER |= IER_TXRDY;
+		sx_out(bp, CD186x_CAR, port_No(port));
+		sx_out(bp, CD186x_IER, port->IER);
+	}
+	spin_unlock_irqrestore(&bp->lock, flags);
+	func_exit();
+
+	return total;
+}
+
+
+static void sx_put_char(struct tty_struct * tty, unsigned char ch)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	unsigned long flags;
+	struct specialix_board  * bp;
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, "sx_put_char")) {
+		func_exit();
+		return;
+	}
+	dprintk (SX_DEBUG_TX, "check tty: %p %p\n", tty, port->xmit_buf);
+	if (!tty || !port->xmit_buf) {
+		func_exit();
+		return;
+	}
+	bp = port_Board(port);
+	spin_lock_irqsave(&port->lock, flags);
+
+	dprintk (SX_DEBUG_TX, "xmit_cnt: %d xmit_buf: %p\n", port->xmit_cnt, port->xmit_buf);
+	if ((port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) || (!port->xmit_buf)) {
+		spin_unlock_irqrestore(&port->lock, flags);
+		dprintk (SX_DEBUG_TX, "Exit size\n");
+		func_exit();
+		return;
+	}
+	dprintk (SX_DEBUG_TX, "Handle xmit: %p %p\n", port, port->xmit_buf);
+	port->xmit_buf[port->xmit_head++] = ch;
+	port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+	port->xmit_cnt++;
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	func_exit();
+}
+
+
+static void sx_flush_chars(struct tty_struct * tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	unsigned long flags;
+	struct specialix_board  * bp = port_Board(port);
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, "sx_flush_chars")) {
+		func_exit();
+		return;
+	}
+	if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+	    !port->xmit_buf) {
+		func_exit();
+		return;
+	}
+	spin_lock_irqsave(&bp->lock, flags);
+	port->IER |= IER_TXRDY;
+	sx_out(port_Board(port), CD186x_CAR, port_No(port));
+	sx_out(port_Board(port), CD186x_IER, port->IER);
+	spin_unlock_irqrestore(&bp->lock, flags);
+
+	func_exit();
+}
+
+
+static int sx_write_room(struct tty_struct * tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	int	ret;
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, "sx_write_room")) {
+		func_exit();
+		return 0;
+	}
+
+	ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+
+	func_exit();
+	return ret;
+}
+
+
+static int sx_chars_in_buffer(struct tty_struct *tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+
+	func_enter();
+	
+	if (sx_paranoia_check(port, tty->name, "sx_chars_in_buffer")) {
+		func_exit();
+		return 0;
+	}
+	func_exit();
+	return port->xmit_cnt;
+}
+
+
+static void sx_flush_buffer(struct tty_struct *tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	unsigned long flags;
+	struct specialix_board  * bp;
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, "sx_flush_buffer")) {
+		func_exit();
+		return;
+	}
+
+	bp = port_Board(port);
+	spin_lock_irqsave(&port->lock, flags);
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	spin_unlock_irqrestore(&port->lock, flags);
+	tty_wakeup(tty);
+
+	func_exit();
+}
+
+
+static int sx_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	struct specialix_board * bp;
+	unsigned char status;
+	unsigned int result;
+	unsigned long flags;
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, __FUNCTION__)) {
+		func_exit();
+		return -ENODEV;
+	}
+
+	bp = port_Board(port);
+	spin_lock_irqsave (&bp->lock, flags);
+	sx_out(bp, CD186x_CAR, port_No(port));
+	status = sx_in(bp, CD186x_MSVR);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	dprintk (SX_DEBUG_INIT, "Got msvr[%d] = %02x, car = %d.\n",
+		port_No(port), status, sx_in (bp, CD186x_CAR));
+	dprintk (SX_DEBUG_INIT, "sx_port = %p, port = %p\n", sx_port, port);
+	if (SX_CRTSCTS(port->tty)) {
+		result  = /*   (status & MSVR_RTS) ? */ TIOCM_DTR /* : 0) */ 
+		          |   ((status & MSVR_DTR) ? TIOCM_RTS : 0)
+		          |   ((status & MSVR_CD)  ? TIOCM_CAR : 0)
+		          |/* ((status & MSVR_DSR) ? */ TIOCM_DSR /* : 0) */
+		          |   ((status & MSVR_CTS) ? TIOCM_CTS : 0);
+	} else {
+		result  = /*   (status & MSVR_RTS) ? */ TIOCM_RTS /* : 0) */ 
+		          |   ((status & MSVR_DTR) ? TIOCM_DTR : 0)
+		          |   ((status & MSVR_CD)  ? TIOCM_CAR : 0)
+		          |/* ((status & MSVR_DSR) ? */ TIOCM_DSR /* : 0) */
+		          |   ((status & MSVR_CTS) ? TIOCM_CTS : 0);
+	}
+
+	func_exit();
+
+	return result;
+}
+
+
+static int sx_tiocmset(struct tty_struct *tty, struct file *file,
+		       unsigned int set, unsigned int clear)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	unsigned long flags;
+	struct specialix_board *bp;
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, __FUNCTION__)) {
+		func_exit();
+		return -ENODEV;
+	}
+
+	bp = port_Board(port);
+
+	spin_lock_irqsave(&port->lock, flags);
+   /*	if (set & TIOCM_RTS)
+		port->MSVR |= MSVR_RTS; */
+   /*   if (set & TIOCM_DTR)
+		port->MSVR |= MSVR_DTR; */
+
+	if (SX_CRTSCTS(port->tty)) {
+		if (set & TIOCM_RTS)
+			port->MSVR |= MSVR_DTR;
+	} else {
+		if (set & TIOCM_DTR)
+			port->MSVR |= MSVR_DTR;
+	}
+
+  /*	if (clear & TIOCM_RTS)
+		port->MSVR &= ~MSVR_RTS; */
+  /*    if (clear & TIOCM_DTR)
+		port->MSVR &= ~MSVR_DTR; */
+	if (SX_CRTSCTS(port->tty)) {
+		if (clear & TIOCM_RTS)
+			port->MSVR &= ~MSVR_DTR;
+	} else {
+		if (clear & TIOCM_DTR)
+			port->MSVR &= ~MSVR_DTR;
+	}
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CAR, port_No(port));
+	sx_out(bp, CD186x_MSVR, port->MSVR);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	spin_unlock_irqrestore(&port->lock, flags);
+	func_exit();
+	return 0;
+}
+
+
+static inline void sx_send_break(struct specialix_port * port, unsigned long length)
+{
+	struct specialix_board *bp = port_Board(port);
+	unsigned long flags;
+	
+	func_enter();
+
+	spin_lock_irqsave (&port->lock, flags);
+	port->break_length = SPECIALIX_TPS / HZ * length;
+	port->COR2 |= COR2_ETC;
+	port->IER  |= IER_TXRDY;
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CAR, port_No(port));
+	sx_out(bp, CD186x_COR2, port->COR2);
+	sx_out(bp, CD186x_IER, port->IER);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	spin_unlock_irqrestore (&port->lock, flags);
+	sx_wait_CCR(bp);
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CCR, CCR_CORCHG2);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	sx_wait_CCR(bp);
+
+	func_exit();
+}
+
+
+static inline int sx_set_serial_info(struct specialix_port * port,
+                                     struct serial_struct __user * newinfo)
+{
+	struct serial_struct tmp;
+	struct specialix_board *bp = port_Board(port);
+	int change_speed;
+
+	func_enter();
+	/*
+	error = verify_area(VERIFY_READ, (void *) newinfo, sizeof(tmp));
+	if (error) {
+		func_exit();
+		return error;
+	}
+	*/
+	if (copy_from_user(&tmp, newinfo, sizeof(tmp))) {
+		func_enter();
+		return -EFAULT;
+	}
+	
+#if 0	
+	if ((tmp.irq != bp->irq) ||
+	    (tmp.port != bp->base) ||
+	    (tmp.type != PORT_CIRRUS) ||
+	    (tmp.baud_base != (SX_OSCFREQ + CD186x_TPC/2) / CD186x_TPC) ||
+	    (tmp.custom_divisor != 0) ||
+	    (tmp.xmit_fifo_size != CD186x_NFIFO) ||
+	    (tmp.flags & ~SPECIALIX_LEGAL_FLAGS)) {
+		func_exit();
+		return -EINVAL;
+	}
+#endif	
+
+	change_speed = ((port->flags & ASYNC_SPD_MASK) !=
+			(tmp.flags & ASYNC_SPD_MASK));
+	change_speed |= (tmp.custom_divisor != port->custom_divisor);
+	
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((tmp.close_delay != port->close_delay) ||
+		    (tmp.closing_wait != port->closing_wait) ||
+		    ((tmp.flags & ~ASYNC_USR_MASK) !=
+		     (port->flags & ~ASYNC_USR_MASK))) {
+			func_exit();
+			return -EPERM;
+		}
+		port->flags = ((port->flags & ~ASYNC_USR_MASK) |
+		                  (tmp.flags & ASYNC_USR_MASK));
+		port->custom_divisor = tmp.custom_divisor;
+	} else {
+		port->flags = ((port->flags & ~ASYNC_FLAGS) |
+		                  (tmp.flags & ASYNC_FLAGS));
+		port->close_delay = tmp.close_delay;
+		port->closing_wait = tmp.closing_wait;
+		port->custom_divisor = tmp.custom_divisor;
+	}
+	if (change_speed) {
+		sx_change_speed(bp, port);
+	}
+	func_exit();
+	return 0;
+}
+
+
+static inline int sx_get_serial_info(struct specialix_port * port,
+				     struct serial_struct __user *retinfo)
+{
+	struct serial_struct tmp;
+	struct specialix_board *bp = port_Board(port);
+	//	int error;
+	
+	func_enter();
+
+	/*
+	error = verify_area(VERIFY_WRITE, (void *) retinfo, sizeof(tmp));
+	if (error)
+		return error;
+	*/
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.type = PORT_CIRRUS;
+	tmp.line = port - sx_port;
+	tmp.port = bp->base;
+	tmp.irq  = bp->irq;
+	tmp.flags = port->flags;
+	tmp.baud_base = (SX_OSCFREQ + CD186x_TPC/2) / CD186x_TPC;
+	tmp.close_delay = port->close_delay * HZ/100;
+	tmp.closing_wait = port->closing_wait * HZ/100;
+	tmp.custom_divisor =  port->custom_divisor;
+	tmp.xmit_fifo_size = CD186x_NFIFO;
+	if (copy_to_user(retinfo, &tmp, sizeof(tmp))) {
+		func_exit();
+		return -EFAULT;
+	}
+
+	func_exit();
+	return 0;
+}
+
+
+static int sx_ioctl(struct tty_struct * tty, struct file * filp, 
+                    unsigned int cmd, unsigned long arg)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	int retval;
+	void __user *argp = (void __user *)arg;
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, "sx_ioctl")) {
+		func_exit();
+		return -ENODEV;
+	}
+	
+	switch (cmd) {
+	 case TCSBRK:	/* SVID version: non-zero arg --> no break */
+		retval = tty_check_change(tty);
+		if (retval) {
+			func_exit();
+			return retval;
+		}
+		tty_wait_until_sent(tty, 0);
+		if (!arg)
+			sx_send_break(port, HZ/4);	/* 1/4 second */
+		return 0;
+	 case TCSBRKP:	/* support for POSIX tcsendbreak() */
+		retval = tty_check_change(tty);
+		if (retval) {
+			func_exit();
+			return retval;
+		}
+		tty_wait_until_sent(tty, 0);
+		sx_send_break(port, arg ? arg*(HZ/10) : HZ/4);
+		func_exit();
+		return 0;
+	 case TIOCGSOFTCAR:
+		 if (put_user(C_CLOCAL(tty)?1:0, (unsigned long __user *)argp)) {
+			 func_exit();
+			 return -EFAULT;
+		 }
+		 func_exit();
+		return 0;
+	 case TIOCSSOFTCAR:
+		 if (get_user(arg, (unsigned long __user *) argp)) {
+			 func_exit();
+			 return -EFAULT;
+		 }
+		tty->termios->c_cflag =
+			((tty->termios->c_cflag & ~CLOCAL) |
+			(arg ? CLOCAL : 0));
+		func_exit();
+		return 0;
+	 case TIOCGSERIAL:
+		 func_exit();
+		return sx_get_serial_info(port, argp);
+	 case TIOCSSERIAL:	
+		 func_exit();
+		return sx_set_serial_info(port, argp);
+	 default:
+		 func_exit();
+		return -ENOIOCTLCMD;
+	}
+	func_exit();
+	return 0;
+}
+
+
+static void sx_throttle(struct tty_struct * tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	struct specialix_board *bp;
+	unsigned long flags;
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, "sx_throttle")) {
+		func_exit();
+		return;
+	}
+	
+	bp = port_Board(port);
+	
+	/* Use DTR instead of RTS ! */
+	if (SX_CRTSCTS (tty)) 
+		port->MSVR &= ~MSVR_DTR;
+	else {
+		/* Auch!!! I think the system shouldn't call this then. */
+		/* Or maybe we're supposed (allowed?) to do our side of hw
+		   handshake anyway, even when hardware handshake is off. 
+		   When you see this in your logs, please report.... */
+		printk (KERN_ERR "sx%d: Need to throttle, but can't (hardware hs is off)\n",
+	                 port_No (port));
+	}
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CAR, port_No(port));
+	spin_unlock_irqrestore(&bp->lock, flags);
+	if (I_IXOFF(tty)) {
+		spin_unlock_irqrestore(&bp->lock, flags);
+		sx_wait_CCR(bp);
+		spin_lock_irqsave(&bp->lock, flags);
+		sx_out(bp, CD186x_CCR, CCR_SSCH2);
+		spin_unlock_irqrestore(&bp->lock, flags);
+		sx_wait_CCR(bp);
+	}
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_MSVR, port->MSVR);
+	spin_unlock_irqrestore(&bp->lock, flags);
+
+	func_exit();
+}
+
+
+static void sx_unthrottle(struct tty_struct * tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	struct specialix_board *bp;
+	unsigned long flags;
+
+	func_enter();
+				
+	if (sx_paranoia_check(port, tty->name, "sx_unthrottle")) {
+		func_exit();
+		return;
+	}
+	
+	bp = port_Board(port);
+	
+	spin_lock_irqsave(&port->lock, flags);
+	/* XXXX Use DTR INSTEAD???? */
+	if (SX_CRTSCTS(tty)) {
+		port->MSVR |= MSVR_DTR;
+	} /* Else clause: see remark in "sx_throttle"... */
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CAR, port_No(port));
+	spin_unlock_irqrestore(&bp->lock, flags);
+	if (I_IXOFF(tty)) {
+		spin_unlock_irqrestore(&port->lock, flags);
+		sx_wait_CCR(bp);
+		spin_lock_irqsave(&bp->lock, flags);
+		sx_out(bp, CD186x_CCR, CCR_SSCH1);
+		spin_unlock_irqrestore(&bp->lock, flags);
+		sx_wait_CCR(bp);
+		spin_lock_irqsave(&port->lock, flags);
+	}
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_MSVR, port->MSVR);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	func_exit();
+}
+
+
+static void sx_stop(struct tty_struct * tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	struct specialix_board *bp;
+	unsigned long flags;
+
+	func_enter();
+	
+	if (sx_paranoia_check(port, tty->name, "sx_stop")) {
+		func_exit();
+		return;
+	}
+
+	bp = port_Board(port);
+	
+	spin_lock_irqsave(&port->lock, flags);
+	port->IER &= ~IER_TXRDY;
+	spin_lock_irqsave(&bp->lock, flags);
+	sx_out(bp, CD186x_CAR, port_No(port));
+	sx_out(bp, CD186x_IER, port->IER);
+	spin_unlock_irqrestore(&bp->lock, flags);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	func_exit();
+}
+
+
+static void sx_start(struct tty_struct * tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	struct specialix_board *bp;
+	unsigned long flags;
+
+	func_enter();
+				
+	if (sx_paranoia_check(port, tty->name, "sx_start")) {
+		func_exit();
+		return;
+	}
+	
+	bp = port_Board(port);
+	
+	spin_lock_irqsave(&port->lock, flags);
+	if (port->xmit_cnt && port->xmit_buf && !(port->IER & IER_TXRDY)) {
+		port->IER |= IER_TXRDY;
+		spin_lock_irqsave(&bp->lock, flags);
+		sx_out(bp, CD186x_CAR, port_No(port));
+		sx_out(bp, CD186x_IER, port->IER);
+		spin_unlock_irqrestore(&bp->lock, flags);
+	}
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	func_exit();
+}
+
+
+/*
+ * This routine is called from the work-queue when the interrupt
+ * routine has signalled that a hangup has occurred.  The path of
+ * hangup processing is:
+ *
+ * 	serial interrupt routine -> (workqueue) ->
+ * 	do_sx_hangup() -> tty->hangup() -> sx_hangup()
+ * 
+ */
+static void do_sx_hangup(void *private_)
+{
+	struct specialix_port	*port = (struct specialix_port *) private_;
+	struct tty_struct	*tty;
+	
+	func_enter();
+
+	tty = port->tty;
+	if (tty)
+		tty_hangup(tty);	/* FIXME: module removal race here */
+
+	func_exit();
+}
+
+
+static void sx_hangup(struct tty_struct * tty)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	struct specialix_board *bp;
+	unsigned long flags;
+
+	func_enter();
+
+	if (sx_paranoia_check(port, tty->name, "sx_hangup")) {
+		func_exit();
+		return;
+	}
+	
+	bp = port_Board(port);
+	
+	sx_shutdown_port(bp, port);
+	spin_lock_irqsave(&port->lock, flags);
+	port->event = 0;
+	bp->count -= port->count;
+	if (bp->count < 0) {
+		printk(KERN_ERR "sx%d: sx_hangup: bad board count: %d port: %d\n",
+			board_No(bp), bp->count, tty->index);
+		bp->count = 0;
+	}
+	port->count = 0;
+	port->flags &= ~ASYNC_NORMAL_ACTIVE;
+	port->tty = NULL;
+	spin_unlock_irqrestore(&port->lock, flags);
+	wake_up_interruptible(&port->open_wait);
+
+	func_exit();
+}
+
+
+static void sx_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+	struct specialix_port *port = (struct specialix_port *)tty->driver_data;
+	unsigned long flags;
+	struct specialix_board  * bp;
+				
+	if (sx_paranoia_check(port, tty->name, "sx_set_termios"))
+		return;
+	
+	if (tty->termios->c_cflag == old_termios->c_cflag &&
+	    tty->termios->c_iflag == old_termios->c_iflag)
+		return;
+
+	bp = port_Board(port);
+	spin_lock_irqsave(&port->lock, flags);
+	sx_change_speed(port_Board(port), port);
+	spin_unlock_irqrestore(&port->lock, flags);
+
+	if ((old_termios->c_cflag & CRTSCTS) &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {
+		tty->hw_stopped = 0;
+		sx_start(tty);
+	}
+}
+
+
+static void do_softint(void *private_)
+{
+	struct specialix_port	*port = (struct specialix_port *) private_;
+	struct tty_struct	*tty;
+
+	func_enter();
+
+	if(!(tty = port->tty)) {
+		func_exit();
+		return;
+	}
+
+	if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &port->event)) {
+ 		tty_wakeup(tty);
+		//wake_up_interruptible(&tty->write_wait);
+	}
+
+	func_exit();
+}
+
+static struct tty_operations sx_ops = {
+	.open  = sx_open,
+	.close = sx_close,
+	.write = sx_write,
+	.put_char = sx_put_char,
+	.flush_chars = sx_flush_chars,
+	.write_room = sx_write_room,
+	.chars_in_buffer = sx_chars_in_buffer,
+	.flush_buffer = sx_flush_buffer,
+	.ioctl = sx_ioctl,
+	.throttle = sx_throttle,
+	.unthrottle = sx_unthrottle,
+	.set_termios = sx_set_termios,
+	.stop = sx_stop,
+	.start = sx_start,
+	.hangup = sx_hangup,
+	.tiocmget = sx_tiocmget,
+	.tiocmset = sx_tiocmset,
+};
+
+static int sx_init_drivers(void)
+{
+	int error;
+	int i;
+
+	func_enter();
+
+	specialix_driver = alloc_tty_driver(SX_NBOARD * SX_NPORT);
+	if (!specialix_driver) {
+		printk(KERN_ERR "sx: Couldn't allocate tty_driver.\n");
+		func_exit();
+		return 1;
+	}
+	
+	if (!(tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL))) {
+		printk(KERN_ERR "sx: Couldn't get free page.\n");
+		put_tty_driver(specialix_driver);
+		func_exit();
+		return 1;
+	}
+	specialix_driver->owner = THIS_MODULE;
+	specialix_driver->name = "ttyW";
+	specialix_driver->major = SPECIALIX_NORMAL_MAJOR;
+	specialix_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	specialix_driver->subtype = SERIAL_TYPE_NORMAL;
+	specialix_driver->init_termios = tty_std_termios;
+	specialix_driver->init_termios.c_cflag =
+		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	specialix_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(specialix_driver, &sx_ops);
+
+	if ((error = tty_register_driver(specialix_driver))) {
+		put_tty_driver(specialix_driver);
+		free_page((unsigned long)tmp_buf);
+		printk(KERN_ERR "sx: Couldn't register specialix IO8+ driver, error = %d\n",
+		       error);
+		func_exit();
+		return 1;
+	}
+	memset(sx_port, 0, sizeof(sx_port));
+	for (i = 0; i < SX_NPORT * SX_NBOARD; i++) {
+		sx_port[i].magic = SPECIALIX_MAGIC;
+		INIT_WORK(&sx_port[i].tqueue, do_softint, &sx_port[i]);
+		INIT_WORK(&sx_port[i].tqueue_hangup, do_sx_hangup, &sx_port[i]);
+		sx_port[i].close_delay = 50 * HZ/100;
+		sx_port[i].closing_wait = 3000 * HZ/100;
+		init_waitqueue_head(&sx_port[i].open_wait);
+		init_waitqueue_head(&sx_port[i].close_wait);
+		spin_lock_init(&sx_port[i].lock);
+	}
+	
+	func_exit();
+	return 0;
+}
+
+static void sx_release_drivers(void)
+{
+	func_enter();
+
+	free_page((unsigned long)tmp_buf);
+	tty_unregister_driver(specialix_driver);
+	put_tty_driver(specialix_driver);
+	func_exit();
+}
+
+/* 
+ * This routine must be called by kernel at boot time 
+ */
+static int __init specialix_init(void)
+{
+	int i;
+	int found = 0;
+
+	func_enter();
+
+	printk(KERN_INFO "sx: Specialix IO8+ driver v" VERSION ", (c) R.E.Wolff 1997/1998.\n");
+	printk(KERN_INFO "sx: derived from work (c) D.Gorodchanin 1994-1996.\n");
+#ifdef CONFIG_SPECIALIX_RTSCTS
+	printk (KERN_INFO "sx: DTR/RTS pin is always RTS.\n");
+#else
+	printk (KERN_INFO "sx: DTR/RTS pin is RTS when CRTSCTS is on.\n");
+#endif
+	
+	for (i = 0; i < SX_NBOARD; i++)
+		sx_board[i].lock = SPIN_LOCK_UNLOCKED;
+
+	if (sx_init_drivers()) {
+		func_exit();
+		return -EIO;
+	}
+
+	for (i = 0; i < SX_NBOARD; i++) 
+		if (sx_board[i].base && !sx_probe(&sx_board[i]))
+			found++;
+
+#ifdef CONFIG_PCI
+	{
+		struct pci_dev *pdev = NULL;
+
+		i=0;
+		while (i < SX_NBOARD) {
+			if (sx_board[i].flags & SX_BOARD_PRESENT) {
+				i++;
+				continue;
+			}
+			pdev = pci_find_device (PCI_VENDOR_ID_SPECIALIX, 
+			                        PCI_DEVICE_ID_SPECIALIX_IO8, 
+			                        pdev);
+			if (!pdev) break;
+
+			if (pci_enable_device(pdev))
+				continue;
+
+			sx_board[i].irq = pdev->irq;
+
+			sx_board[i].base = pci_resource_start (pdev, 2);
+
+			sx_board[i].flags |= SX_BOARD_IS_PCI;
+			if (!sx_probe(&sx_board[i]))
+				found ++;
+		}
+	}
+#endif
+
+	if (!found) {
+		sx_release_drivers();
+		printk(KERN_INFO "sx: No specialix IO8+ boards detected.\n");
+		func_exit();
+		return -EIO;
+	}
+
+	func_exit();
+	return 0;
+}
+
+static int iobase[SX_NBOARD]  = {0,};
+
+static int irq [SX_NBOARD] = {0,};
+
+module_param_array(iobase, int, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+module_param(sx_debug, int, 0);
+module_param(sx_rxfifo, int, 0);
+#ifdef SPECIALIX_TIMER
+module_param(sx_poll, int, 0);
+#endif
+
+/*
+ * You can setup up to 4 boards.
+ * by specifying "iobase=0xXXX,0xXXX ..." as insmod parameter.
+ * You should specify the IRQs too in that case "irq=....,...". 
+ * 
+ * More than 4 boards in one computer is not possible, as the card can
+ * only use 4 different interrupts. 
+ *
+ */
+static int __init specialix_init_module(void)
+{
+	int i;
+
+	func_enter();
+
+	init_MUTEX(&tmp_buf_sem); /* Init de the semaphore - pvdl */
+
+	if (iobase[0] || iobase[1] || iobase[2] || iobase[3]) {
+		for(i = 0; i < SX_NBOARD; i++) {
+			sx_board[i].base = iobase[i];
+			sx_board[i].irq = irq[i];
+			sx_board[i].count= 0;
+		}
+	}
+
+	func_exit();
+
+	return specialix_init();
+}
+	
+static void __exit specialix_exit_module(void)
+{
+	int i;
+	
+	func_enter();
+
+	sx_release_drivers();
+	for (i = 0; i < SX_NBOARD; i++)
+		if (sx_board[i].flags & SX_BOARD_PRESENT) 
+			sx_release_io_range(&sx_board[i]);
+#ifdef SPECIALIX_TIMER
+	del_timer (&missed_irq_timer);
+#endif
+
+	func_exit();
+}
+
+module_init(specialix_init_module);
+module_exit(specialix_exit_module);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/specialix_io8.h b/drivers/char/specialix_io8.h
new file mode 100644
index 0000000..895bd90
--- /dev/null
+++ b/drivers/char/specialix_io8.h
@@ -0,0 +1,149 @@
+/*
+ *      linux/drivers/char/specialix_io8.h  -- 
+ *                                   Specialix IO8+ multiport serial driver.
+ *
+ *      Copyright (C) 1997 Roger Wolff (R.E.Wolff@BitWizard.nl)
+ *      Copyright (C) 1994-1996  Dmitry Gorodchanin (pgmdsg@ibi.com)
+ *
+ *
+ *      Specialix pays for the development and support of this driver.
+ *      Please DO contact io8-linux@specialix.co.uk if you require
+ *      support.
+ *
+ *      This driver was developped in the BitWizard linux device
+ *      driver service. If you require a linux device driver for your
+ *      product, please contact devices@BitWizard.nl for a quote.
+ *
+ *      This code is firmly based on the riscom/8 serial driver,
+ *      written by Dmitry Gorodchanin. The specialix IO8+ card
+ *      programming information was obtained from the CL-CD1865 Data
+ *      Book, and Specialix document number 6200059: IO8+ Hardware
+ *      Functional Specification.
+ *
+ *      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.
+ * */
+
+#ifndef __LINUX_SPECIALIX_H
+#define __LINUX_SPECIALIX_H
+
+#include <linux/serial.h>
+
+#ifdef __KERNEL__
+
+/* You can have max 4 ISA cards in one PC, and I recommend not much 
+more than a few  PCI versions of the card. */
+
+#define SX_NBOARD		8
+
+/* NOTE: Specialix decoder recognizes 4 addresses, but only two are used.... */
+#define SX_IO_SPACE             4
+/* The PCI version decodes 8 addresses, but still only 2 are used. */
+#define SX_PCI_IO_SPACE         8
+
+/* eight ports per board. */
+#define SX_NPORT        	8
+#define SX_BOARD(line)		((line) / SX_NPORT)
+#define SX_PORT(line)		((line) & (SX_NPORT - 1))
+
+
+#define SX_DATA_REG 0     /* Base+0 : Data register */
+#define SX_ADDR_REG 1     /* base+1 : Address register. */
+
+#define MHz *1000000	/* I'm ashamed of myself. */
+
+/* On-board oscillator frequency */
+#define SX_OSCFREQ      (25 MHz/2)
+/* There is a 25MHz crystal on the board, but the chip is in /2 mode */
+
+
+/* Ticks per sec. Used for setting receiver timeout and break length */
+#define SPECIALIX_TPS		4000
+
+/* Yeah, after heavy testing I decided it must be 6.
+ * Sure, You can change it if needed.
+ */
+#define SPECIALIX_RXFIFO	6	/* Max. receiver FIFO size (1-8) */
+
+#define SPECIALIX_MAGIC		0x0907
+
+#define SX_CCR_TIMEOUT 10000   /* CCR timeout. You may need to wait upto
+                                  10 milliseconds before the internal
+                                  processor is available again after
+                                  you give it a command */
+
+#define SX_IOBASE1	0x100
+#define SX_IOBASE2	0x180
+#define SX_IOBASE3	0x250
+#define SX_IOBASE4	0x260
+
+struct specialix_board {
+	unsigned long   flags;
+	unsigned short	base;
+	unsigned char 	irq;
+	//signed   char	count;
+	int count;
+	unsigned char	DTR;
+        int reg;
+	spinlock_t lock;
+};
+
+#define SX_BOARD_PRESENT	0x00000001
+#define SX_BOARD_ACTIVE		0x00000002
+#define SX_BOARD_IS_PCI		0x00000004
+
+
+struct specialix_port {
+	int			magic;
+	int			baud_base;
+	int			flags;
+	struct tty_struct 	* tty;
+	int			count;
+	int			blocked_open;
+	ulong			event;
+	int			timeout;
+	int			close_delay;
+	unsigned char 		* xmit_buf;
+	int			custom_divisor;
+	int			xmit_head;
+	int			xmit_tail;
+	int			xmit_cnt;
+	wait_queue_head_t	open_wait;
+	wait_queue_head_t	close_wait;
+	struct work_struct	tqueue;
+	struct work_struct	tqueue_hangup;
+	short			wakeup_chars;
+	short			break_length;
+	unsigned short		closing_wait;
+	unsigned char		mark_mask;
+	unsigned char		IER;
+	unsigned char		MSVR;
+	unsigned char		COR2;
+	unsigned long		overrun;
+	unsigned long		hits[10];
+	spinlock_t lock;
+};
+
+#endif /* __KERNEL__ */
+#endif /* __LINUX_SPECIALIX_H */
+
+
+
+
+
+
+
+
+
diff --git a/drivers/char/stallion.c b/drivers/char/stallion.c
new file mode 100644
index 0000000..de16660
--- /dev/null
+++ b/drivers/char/stallion.c
@@ -0,0 +1,5197 @@
+/*****************************************************************************/
+
+/*
+ *	stallion.c  -- stallion multiport serial driver.
+ *
+ *	Copyright (C) 1996-1999  Stallion Technologies
+ *	Copyright (C) 1994-1996  Greg Ungerer.
+ *
+ *	This code is loosely based on the Linux serial driver, written by
+ *	Linus Torvalds, Theodore T'so and others.
+ *
+ *	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.
+ */
+
+/*****************************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/cd1400.h>
+#include <linux/sc26198.h>
+#include <linux/comstats.h>
+#include <linux/stallion.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Define different board types. Use the standard Stallion "assigned"
+ *	board numbers. Boards supported in this driver are abbreviated as
+ *	EIO = EasyIO and ECH = EasyConnection 8/32.
+ */
+#define	BRD_EASYIO	20
+#define	BRD_ECH		21
+#define	BRD_ECHMC	22
+#define	BRD_ECHPCI	26
+#define	BRD_ECH64PCI	27
+#define	BRD_EASYIOPCI	28
+
+/*
+ *	Define a configuration structure to hold the board configuration.
+ *	Need to set this up in the code (for now) with the boards that are
+ *	to be configured into the system. This is what needs to be modified
+ *	when adding/removing/modifying boards. Each line entry in the
+ *	stl_brdconf[] array is a board. Each line contains io/irq/memory
+ *	ranges for that board (as well as what type of board it is).
+ *	Some examples:
+ *		{ BRD_EASYIO, 0x2a0, 0, 0, 10, 0 },
+ *	This line would configure an EasyIO board (4 or 8, no difference),
+ *	at io address 2a0 and irq 10.
+ *	Another example:
+ *		{ BRD_ECH, 0x2a8, 0x280, 0, 12, 0 },
+ *	This line will configure an EasyConnection 8/32 board at primary io
+ *	address 2a8, secondary io address 280 and irq 12.
+ *	Enter as many lines into this array as you want (only the first 4
+ *	will actually be used!). Any combination of EasyIO and EasyConnection
+ *	boards can be specified. EasyConnection 8/32 boards can share their
+ *	secondary io addresses between each other.
+ *
+ *	NOTE: there is no need to put any entries in this table for PCI
+ *	boards. They will be found automatically by the driver - provided
+ *	PCI BIOS32 support is compiled into the kernel.
+ */
+
+typedef struct {
+	int		brdtype;
+	int		ioaddr1;
+	int		ioaddr2;
+	unsigned long	memaddr;
+	int		irq;
+	int		irqtype;
+} stlconf_t;
+
+static stlconf_t	stl_brdconf[] = {
+	/*{ BRD_EASYIO, 0x2a0, 0, 0, 10, 0 },*/
+};
+
+static int	stl_nrbrds = sizeof(stl_brdconf) / sizeof(stlconf_t);
+
+/*****************************************************************************/
+
+/*
+ *	Define some important driver characteristics. Device major numbers
+ *	allocated as per Linux Device Registry.
+ */
+#ifndef	STL_SIOMEMMAJOR
+#define	STL_SIOMEMMAJOR		28
+#endif
+#ifndef	STL_SERIALMAJOR
+#define	STL_SERIALMAJOR		24
+#endif
+#ifndef	STL_CALLOUTMAJOR
+#define	STL_CALLOUTMAJOR	25
+#endif
+
+/*
+ *	Set the TX buffer size. Bigger is better, but we don't want
+ *	to chew too much memory with buffers!
+ */
+#define	STL_TXBUFLOW		512
+#define	STL_TXBUFSIZE		4096
+
+/*****************************************************************************/
+
+/*
+ *	Define our local driver identity first. Set up stuff to deal with
+ *	all the local structures required by a serial tty driver.
+ */
+static char	*stl_drvtitle = "Stallion Multiport Serial Driver";
+static char	*stl_drvname = "stallion";
+static char	*stl_drvversion = "5.6.0";
+
+static struct tty_driver	*stl_serial;
+
+/*
+ *	We will need to allocate a temporary write buffer for chars that
+ *	come direct from user space. The problem is that a copy from user
+ *	space might cause a page fault (typically on a system that is
+ *	swapping!). All ports will share one buffer - since if the system
+ *	is already swapping a shared buffer won't make things any worse.
+ */
+static char			*stl_tmpwritebuf;
+static DECLARE_MUTEX(stl_tmpwritesem);
+
+/*
+ *	Define a local default termios struct. All ports will be created
+ *	with this termios initially. Basically all it defines is a raw port
+ *	at 9600, 8 data bits, 1 stop bit.
+ */
+static struct termios		stl_deftermios = {
+	.c_cflag	= (B9600 | CS8 | CREAD | HUPCL | CLOCAL),
+	.c_cc		= INIT_C_CC,
+};
+
+/*
+ *	Define global stats structures. Not used often, and can be
+ *	re-used for each stats call.
+ */
+static comstats_t	stl_comstats;
+static combrd_t		stl_brdstats;
+static stlbrd_t		stl_dummybrd;
+static stlport_t	stl_dummyport;
+
+/*
+ *	Define global place to put buffer overflow characters.
+ */
+static char		stl_unwanted[SC26198_RXFIFOSIZE];
+
+/*****************************************************************************/
+
+static stlbrd_t		*stl_brds[STL_MAXBRDS];
+
+/*
+ *	Per board state flags. Used with the state field of the board struct.
+ *	Not really much here!
+ */
+#define	BRD_FOUND	0x1
+
+/*
+ *	Define the port structure istate flags. These set of flags are
+ *	modified at interrupt time - so setting and reseting them needs
+ *	to be atomic. Use the bit clear/setting routines for this.
+ */
+#define	ASYI_TXBUSY	1
+#define	ASYI_TXLOW	2
+#define	ASYI_DCDCHANGE	3
+#define	ASYI_TXFLOWED	4
+
+/*
+ *	Define an array of board names as printable strings. Handy for
+ *	referencing boards when printing trace and stuff.
+ */
+static char	*stl_brdnames[] = {
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	"EasyIO",
+	"EC8/32-AT",
+	"EC8/32-MC",
+	(char *) NULL,
+	(char *) NULL,
+	(char *) NULL,
+	"EC8/32-PCI",
+	"EC8/64-PCI",
+	"EasyIO-PCI",
+};
+
+/*****************************************************************************/
+
+/*
+ *	Define some string labels for arguments passed from the module
+ *	load line. These allow for easy board definitions, and easy
+ *	modification of the io, memory and irq resoucres.
+ */
+static int	stl_nargs = 0;
+static char	*board0[4];
+static char	*board1[4];
+static char	*board2[4];
+static char	*board3[4];
+
+static char	**stl_brdsp[] = {
+	(char **) &board0,
+	(char **) &board1,
+	(char **) &board2,
+	(char **) &board3
+};
+
+/*
+ *	Define a set of common board names, and types. This is used to
+ *	parse any module arguments.
+ */
+
+typedef struct stlbrdtype {
+	char	*name;
+	int	type;
+} stlbrdtype_t;
+
+static stlbrdtype_t	stl_brdstr[] = {
+	{ "easyio", BRD_EASYIO },
+	{ "eio", BRD_EASYIO },
+	{ "20", BRD_EASYIO },
+	{ "ec8/32", BRD_ECH },
+	{ "ec8/32-at", BRD_ECH },
+	{ "ec8/32-isa", BRD_ECH },
+	{ "ech", BRD_ECH },
+	{ "echat", BRD_ECH },
+	{ "21", BRD_ECH },
+	{ "ec8/32-mc", BRD_ECHMC },
+	{ "ec8/32-mca", BRD_ECHMC },
+	{ "echmc", BRD_ECHMC },
+	{ "echmca", BRD_ECHMC },
+	{ "22", BRD_ECHMC },
+	{ "ec8/32-pc", BRD_ECHPCI },
+	{ "ec8/32-pci", BRD_ECHPCI },
+	{ "26", BRD_ECHPCI },
+	{ "ec8/64-pc", BRD_ECH64PCI },
+	{ "ec8/64-pci", BRD_ECH64PCI },
+	{ "ech-pci", BRD_ECH64PCI },
+	{ "echpci", BRD_ECH64PCI },
+	{ "echpc", BRD_ECH64PCI },
+	{ "27", BRD_ECH64PCI },
+	{ "easyio-pc", BRD_EASYIOPCI },
+	{ "easyio-pci", BRD_EASYIOPCI },
+	{ "eio-pci", BRD_EASYIOPCI },
+	{ "eiopci", BRD_EASYIOPCI },
+	{ "28", BRD_EASYIOPCI },
+};
+
+/*
+ *	Define the module agruments.
+ */
+MODULE_AUTHOR("Greg Ungerer");
+MODULE_DESCRIPTION("Stallion Multiport Serial Driver");
+MODULE_LICENSE("GPL");
+
+module_param_array(board0, charp, &stl_nargs, 0);
+MODULE_PARM_DESC(board0, "Board 0 config -> name[,ioaddr[,ioaddr2][,irq]]");
+module_param_array(board1, charp, &stl_nargs, 0);
+MODULE_PARM_DESC(board1, "Board 1 config -> name[,ioaddr[,ioaddr2][,irq]]");
+module_param_array(board2, charp, &stl_nargs, 0);
+MODULE_PARM_DESC(board2, "Board 2 config -> name[,ioaddr[,ioaddr2][,irq]]");
+module_param_array(board3, charp, &stl_nargs, 0);
+MODULE_PARM_DESC(board3, "Board 3 config -> name[,ioaddr[,ioaddr2][,irq]]");
+
+/*****************************************************************************/
+
+/*
+ *	Hardware ID bits for the EasyIO and ECH boards. These defines apply
+ *	to the directly accessible io ports of these boards (not the uarts -
+ *	they are in cd1400.h and sc26198.h).
+ */
+#define	EIO_8PORTRS	0x04
+#define	EIO_4PORTRS	0x05
+#define	EIO_8PORTDI	0x00
+#define	EIO_8PORTM	0x06
+#define	EIO_MK3		0x03
+#define	EIO_IDBITMASK	0x07
+
+#define	EIO_BRDMASK	0xf0
+#define	ID_BRD4		0x10
+#define	ID_BRD8		0x20
+#define	ID_BRD16	0x30
+
+#define	EIO_INTRPEND	0x08
+#define	EIO_INTEDGE	0x00
+#define	EIO_INTLEVEL	0x08
+#define	EIO_0WS		0x10
+
+#define	ECH_ID		0xa0
+#define	ECH_IDBITMASK	0xe0
+#define	ECH_BRDENABLE	0x08
+#define	ECH_BRDDISABLE	0x00
+#define	ECH_INTENABLE	0x01
+#define	ECH_INTDISABLE	0x00
+#define	ECH_INTLEVEL	0x02
+#define	ECH_INTEDGE	0x00
+#define	ECH_INTRPEND	0x01
+#define	ECH_BRDRESET	0x01
+
+#define	ECHMC_INTENABLE	0x01
+#define	ECHMC_BRDRESET	0x02
+
+#define	ECH_PNLSTATUS	2
+#define	ECH_PNL16PORT	0x20
+#define	ECH_PNLIDMASK	0x07
+#define	ECH_PNLXPID	0x40
+#define	ECH_PNLINTRPEND	0x80
+
+#define	ECH_ADDR2MASK	0x1e0
+
+/*
+ *	Define the vector mapping bits for the programmable interrupt board
+ *	hardware. These bits encode the interrupt for the board to use - it
+ *	is software selectable (except the EIO-8M).
+ */
+static unsigned char	stl_vecmap[] = {
+	0xff, 0xff, 0xff, 0x04, 0x06, 0x05, 0xff, 0x07,
+	0xff, 0xff, 0x00, 0x02, 0x01, 0xff, 0xff, 0x03
+};
+
+/*
+ *	Set up enable and disable macros for the ECH boards. They require
+ *	the secondary io address space to be activated and deactivated.
+ *	This way all ECH boards can share their secondary io region.
+ *	If this is an ECH-PCI board then also need to set the page pointer
+ *	to point to the correct page.
+ */
+#define	BRDENABLE(brdnr,pagenr)						\
+	if (stl_brds[(brdnr)]->brdtype == BRD_ECH)			\
+		outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDENABLE),	\
+			stl_brds[(brdnr)]->ioctrl);			\
+	else if (stl_brds[(brdnr)]->brdtype == BRD_ECHPCI)		\
+		outb((pagenr), stl_brds[(brdnr)]->ioctrl);
+
+#define	BRDDISABLE(brdnr)						\
+	if (stl_brds[(brdnr)]->brdtype == BRD_ECH)			\
+		outb((stl_brds[(brdnr)]->ioctrlval | ECH_BRDDISABLE),	\
+			stl_brds[(brdnr)]->ioctrl);
+
+#define	STL_CD1400MAXBAUD	230400
+#define	STL_SC26198MAXBAUD	460800
+
+#define	STL_BAUDBASE		115200
+#define	STL_CLOSEDELAY		(5 * HZ / 10)
+
+/*****************************************************************************/
+
+#ifdef CONFIG_PCI
+
+/*
+ *	Define the Stallion PCI vendor and device IDs.
+ */
+#ifndef	PCI_VENDOR_ID_STALLION
+#define	PCI_VENDOR_ID_STALLION		0x124d
+#endif
+#ifndef PCI_DEVICE_ID_ECHPCI832
+#define	PCI_DEVICE_ID_ECHPCI832		0x0000
+#endif
+#ifndef PCI_DEVICE_ID_ECHPCI864
+#define	PCI_DEVICE_ID_ECHPCI864		0x0002
+#endif
+#ifndef PCI_DEVICE_ID_EIOPCI
+#define	PCI_DEVICE_ID_EIOPCI		0x0003
+#endif
+
+/*
+ *	Define structure to hold all Stallion PCI boards.
+ */
+typedef struct stlpcibrd {
+	unsigned short		vendid;
+	unsigned short		devid;
+	int			brdtype;
+} stlpcibrd_t;
+
+static stlpcibrd_t	stl_pcibrds[] = {
+	{ PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI864, BRD_ECH64PCI },
+	{ PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_EIOPCI, BRD_EASYIOPCI },
+	{ PCI_VENDOR_ID_STALLION, PCI_DEVICE_ID_ECHPCI832, BRD_ECHPCI },
+	{ PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_87410, BRD_ECHPCI },
+};
+
+static int	stl_nrpcibrds = sizeof(stl_pcibrds) / sizeof(stlpcibrd_t);
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Define macros to extract a brd/port number from a minor number.
+ */
+#define	MINOR2BRD(min)		(((min) & 0xc0) >> 6)
+#define	MINOR2PORT(min)		((min) & 0x3f)
+
+/*
+ *	Define a baud rate table that converts termios baud rate selector
+ *	into the actual baud rate value. All baud rate calculations are
+ *	based on the actual baud rate required.
+ */
+static unsigned int	stl_baudrates[] = {
+	0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+	9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600
+};
+
+/*
+ *	Define some handy local macros...
+ */
+#undef	MIN
+#define	MIN(a,b)	(((a) <= (b)) ? (a) : (b))
+
+#undef	TOLOWER
+#define	TOLOWER(x)	((((x) >= 'A') && ((x) <= 'Z')) ? ((x) + 0x20) : (x))
+
+/*****************************************************************************/
+
+/*
+ *	Declare all those functions in this driver!
+ */
+
+static void	stl_argbrds(void);
+static int	stl_parsebrd(stlconf_t *confp, char **argp);
+
+static unsigned long stl_atol(char *str);
+
+int		stl_init(void);
+static int	stl_open(struct tty_struct *tty, struct file *filp);
+static void	stl_close(struct tty_struct *tty, struct file *filp);
+static int	stl_write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void	stl_putchar(struct tty_struct *tty, unsigned char ch);
+static void	stl_flushchars(struct tty_struct *tty);
+static int	stl_writeroom(struct tty_struct *tty);
+static int	stl_charsinbuffer(struct tty_struct *tty);
+static int	stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static void	stl_settermios(struct tty_struct *tty, struct termios *old);
+static void	stl_throttle(struct tty_struct *tty);
+static void	stl_unthrottle(struct tty_struct *tty);
+static void	stl_stop(struct tty_struct *tty);
+static void	stl_start(struct tty_struct *tty);
+static void	stl_flushbuffer(struct tty_struct *tty);
+static void	stl_breakctl(struct tty_struct *tty, int state);
+static void	stl_waituntilsent(struct tty_struct *tty, int timeout);
+static void	stl_sendxchar(struct tty_struct *tty, char ch);
+static void	stl_hangup(struct tty_struct *tty);
+static int	stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg);
+static int	stl_portinfo(stlport_t *portp, int portnr, char *pos);
+static int	stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data);
+
+static int	stl_brdinit(stlbrd_t *brdp);
+static int	stl_initports(stlbrd_t *brdp, stlpanel_t *panelp);
+static int	stl_getserial(stlport_t *portp, struct serial_struct __user *sp);
+static int	stl_setserial(stlport_t *portp, struct serial_struct __user *sp);
+static int	stl_getbrdstats(combrd_t __user *bp);
+static int	stl_getportstats(stlport_t *portp, comstats_t __user *cp);
+static int	stl_clrportstats(stlport_t *portp, comstats_t __user *cp);
+static int	stl_getportstruct(stlport_t __user *arg);
+static int	stl_getbrdstruct(stlbrd_t __user *arg);
+static int	stl_waitcarrier(stlport_t *portp, struct file *filp);
+static int	stl_eiointr(stlbrd_t *brdp);
+static int	stl_echatintr(stlbrd_t *brdp);
+static int	stl_echmcaintr(stlbrd_t *brdp);
+static int	stl_echpciintr(stlbrd_t *brdp);
+static int	stl_echpci64intr(stlbrd_t *brdp);
+static void	stl_offintr(void *private);
+static void	*stl_memalloc(int len);
+static stlbrd_t *stl_allocbrd(void);
+static stlport_t *stl_getport(int brdnr, int panelnr, int portnr);
+
+static inline int	stl_initbrds(void);
+static inline int	stl_initeio(stlbrd_t *brdp);
+static inline int	stl_initech(stlbrd_t *brdp);
+static inline int	stl_getbrdnr(void);
+
+#ifdef	CONFIG_PCI
+static inline int	stl_findpcibrds(void);
+static inline int	stl_initpcibrd(int brdtype, struct pci_dev *devp);
+#endif
+
+/*
+ *	CD1400 uart specific handling functions.
+ */
+static void	stl_cd1400setreg(stlport_t *portp, int regnr, int value);
+static int	stl_cd1400getreg(stlport_t *portp, int regnr);
+static int	stl_cd1400updatereg(stlport_t *portp, int regnr, int value);
+static int	stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp);
+static void	stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+static void	stl_cd1400setport(stlport_t *portp, struct termios *tiosp);
+static int	stl_cd1400getsignals(stlport_t *portp);
+static void	stl_cd1400setsignals(stlport_t *portp, int dtr, int rts);
+static void	stl_cd1400ccrwait(stlport_t *portp);
+static void	stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx);
+static void	stl_cd1400startrxtx(stlport_t *portp, int rx, int tx);
+static void	stl_cd1400disableintrs(stlport_t *portp);
+static void	stl_cd1400sendbreak(stlport_t *portp, int len);
+static void	stl_cd1400flowctrl(stlport_t *portp, int state);
+static void	stl_cd1400sendflow(stlport_t *portp, int state);
+static void	stl_cd1400flush(stlport_t *portp);
+static int	stl_cd1400datastate(stlport_t *portp);
+static void	stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase);
+static void	stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase);
+static void	stl_cd1400txisr(stlpanel_t *panelp, int ioaddr);
+static void	stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr);
+static void	stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr);
+
+static inline int	stl_cd1400breakisr(stlport_t *portp, int ioaddr);
+
+/*
+ *	SC26198 uart specific handling functions.
+ */
+static void	stl_sc26198setreg(stlport_t *portp, int regnr, int value);
+static int	stl_sc26198getreg(stlport_t *portp, int regnr);
+static int	stl_sc26198updatereg(stlport_t *portp, int regnr, int value);
+static int	stl_sc26198getglobreg(stlport_t *portp, int regnr);
+static int	stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp);
+static void	stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+static void	stl_sc26198setport(stlport_t *portp, struct termios *tiosp);
+static int	stl_sc26198getsignals(stlport_t *portp);
+static void	stl_sc26198setsignals(stlport_t *portp, int dtr, int rts);
+static void	stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx);
+static void	stl_sc26198startrxtx(stlport_t *portp, int rx, int tx);
+static void	stl_sc26198disableintrs(stlport_t *portp);
+static void	stl_sc26198sendbreak(stlport_t *portp, int len);
+static void	stl_sc26198flowctrl(stlport_t *portp, int state);
+static void	stl_sc26198sendflow(stlport_t *portp, int state);
+static void	stl_sc26198flush(stlport_t *portp);
+static int	stl_sc26198datastate(stlport_t *portp);
+static void	stl_sc26198wait(stlport_t *portp);
+static void	stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty);
+static void	stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase);
+static void	stl_sc26198txisr(stlport_t *port);
+static void	stl_sc26198rxisr(stlport_t *port, unsigned int iack);
+static void	stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch);
+static void	stl_sc26198rxbadchars(stlport_t *portp);
+static void	stl_sc26198otherisr(stlport_t *port, unsigned int iack);
+
+/*****************************************************************************/
+
+/*
+ *	Generic UART support structure.
+ */
+typedef struct uart {
+	int	(*panelinit)(stlbrd_t *brdp, stlpanel_t *panelp);
+	void	(*portinit)(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp);
+	void	(*setport)(stlport_t *portp, struct termios *tiosp);
+	int	(*getsignals)(stlport_t *portp);
+	void	(*setsignals)(stlport_t *portp, int dtr, int rts);
+	void	(*enablerxtx)(stlport_t *portp, int rx, int tx);
+	void	(*startrxtx)(stlport_t *portp, int rx, int tx);
+	void	(*disableintrs)(stlport_t *portp);
+	void	(*sendbreak)(stlport_t *portp, int len);
+	void	(*flowctrl)(stlport_t *portp, int state);
+	void	(*sendflow)(stlport_t *portp, int state);
+	void	(*flush)(stlport_t *portp);
+	int	(*datastate)(stlport_t *portp);
+	void	(*intr)(stlpanel_t *panelp, unsigned int iobase);
+} uart_t;
+
+/*
+ *	Define some macros to make calling these functions nice and clean.
+ */
+#define	stl_panelinit		(* ((uart_t *) panelp->uartp)->panelinit)
+#define	stl_portinit		(* ((uart_t *) portp->uartp)->portinit)
+#define	stl_setport		(* ((uart_t *) portp->uartp)->setport)
+#define	stl_getsignals		(* ((uart_t *) portp->uartp)->getsignals)
+#define	stl_setsignals		(* ((uart_t *) portp->uartp)->setsignals)
+#define	stl_enablerxtx		(* ((uart_t *) portp->uartp)->enablerxtx)
+#define	stl_startrxtx		(* ((uart_t *) portp->uartp)->startrxtx)
+#define	stl_disableintrs	(* ((uart_t *) portp->uartp)->disableintrs)
+#define	stl_sendbreak		(* ((uart_t *) portp->uartp)->sendbreak)
+#define	stl_flowctrl		(* ((uart_t *) portp->uartp)->flowctrl)
+#define	stl_sendflow		(* ((uart_t *) portp->uartp)->sendflow)
+#define	stl_flush		(* ((uart_t *) portp->uartp)->flush)
+#define	stl_datastate		(* ((uart_t *) portp->uartp)->datastate)
+
+/*****************************************************************************/
+
+/*
+ *	CD1400 UART specific data initialization.
+ */
+static uart_t stl_cd1400uart = {
+	stl_cd1400panelinit,
+	stl_cd1400portinit,
+	stl_cd1400setport,
+	stl_cd1400getsignals,
+	stl_cd1400setsignals,
+	stl_cd1400enablerxtx,
+	stl_cd1400startrxtx,
+	stl_cd1400disableintrs,
+	stl_cd1400sendbreak,
+	stl_cd1400flowctrl,
+	stl_cd1400sendflow,
+	stl_cd1400flush,
+	stl_cd1400datastate,
+	stl_cd1400eiointr
+};
+
+/*
+ *	Define the offsets within the register bank of a cd1400 based panel.
+ *	These io address offsets are common to the EasyIO board as well.
+ */
+#define	EREG_ADDR	0
+#define	EREG_DATA	4
+#define	EREG_RXACK	5
+#define	EREG_TXACK	6
+#define	EREG_MDACK	7
+
+#define	EREG_BANKSIZE	8
+
+#define	CD1400_CLK	25000000
+#define	CD1400_CLK8M	20000000
+
+/*
+ *	Define the cd1400 baud rate clocks. These are used when calculating
+ *	what clock and divisor to use for the required baud rate. Also
+ *	define the maximum baud rate allowed, and the default base baud.
+ */
+static int	stl_cd1400clkdivs[] = {
+	CD1400_CLK0, CD1400_CLK1, CD1400_CLK2, CD1400_CLK3, CD1400_CLK4
+};
+
+/*****************************************************************************/
+
+/*
+ *	SC26198 UART specific data initization.
+ */
+static uart_t stl_sc26198uart = {
+	stl_sc26198panelinit,
+	stl_sc26198portinit,
+	stl_sc26198setport,
+	stl_sc26198getsignals,
+	stl_sc26198setsignals,
+	stl_sc26198enablerxtx,
+	stl_sc26198startrxtx,
+	stl_sc26198disableintrs,
+	stl_sc26198sendbreak,
+	stl_sc26198flowctrl,
+	stl_sc26198sendflow,
+	stl_sc26198flush,
+	stl_sc26198datastate,
+	stl_sc26198intr
+};
+
+/*
+ *	Define the offsets within the register bank of a sc26198 based panel.
+ */
+#define	XP_DATA		0
+#define	XP_ADDR		1
+#define	XP_MODID	2
+#define	XP_STATUS	2
+#define	XP_IACK		3
+
+#define	XP_BANKSIZE	4
+
+/*
+ *	Define the sc26198 baud rate table. Offsets within the table
+ *	represent the actual baud rate selector of sc26198 registers.
+ */
+static unsigned int	sc26198_baudtable[] = {
+	50, 75, 150, 200, 300, 450, 600, 900, 1200, 1800, 2400, 3600,
+	4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 115200,
+	230400, 460800, 921600
+};
+
+#define	SC26198_NRBAUDS		(sizeof(sc26198_baudtable) / sizeof(unsigned int))
+
+/*****************************************************************************/
+
+/*
+ *	Define the driver info for a user level control device. Used mainly
+ *	to get at port stats - only not using the port device itself.
+ */
+static struct file_operations	stl_fsiomem = {
+	.owner		= THIS_MODULE,
+	.ioctl		= stl_memioctl,
+};
+
+/*****************************************************************************/
+
+static struct class_simple *stallion_class;
+
+/*
+ *	Loadable module initialization stuff.
+ */
+
+static int __init stallion_module_init(void)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("init_module()\n");
+#endif
+
+	save_flags(flags);
+	cli();
+	stl_init();
+	restore_flags(flags);
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+static void __exit stallion_module_exit(void)
+{
+	stlbrd_t	*brdp;
+	stlpanel_t	*panelp;
+	stlport_t	*portp;
+	unsigned long	flags;
+	int		i, j, k;
+
+#ifdef DEBUG
+	printk("cleanup_module()\n");
+#endif
+
+	printk(KERN_INFO "Unloading %s: version %s\n", stl_drvtitle,
+		stl_drvversion);
+
+	save_flags(flags);
+	cli();
+
+/*
+ *	Free up all allocated resources used by the ports. This includes
+ *	memory and interrupts. As part of this process we will also do
+ *	a hangup on every open port - to try to flush out any processes
+ *	hanging onto ports.
+ */
+	i = tty_unregister_driver(stl_serial);
+	put_tty_driver(stl_serial);
+	if (i) {
+		printk("STALLION: failed to un-register tty driver, "
+			"errno=%d\n", -i);
+		restore_flags(flags);
+		return;
+	}
+	for (i = 0; i < 4; i++) {
+		devfs_remove("staliomem/%d", i);
+		class_simple_device_remove(MKDEV(STL_SIOMEMMAJOR, i));
+	}
+	devfs_remove("staliomem");
+	if ((i = unregister_chrdev(STL_SIOMEMMAJOR, "staliomem")))
+		printk("STALLION: failed to un-register serial memory device, "
+			"errno=%d\n", -i);
+	class_simple_destroy(stallion_class);
+
+	if (stl_tmpwritebuf != (char *) NULL)
+		kfree(stl_tmpwritebuf);
+
+	for (i = 0; (i < stl_nrbrds); i++) {
+		if ((brdp = stl_brds[i]) == (stlbrd_t *) NULL)
+			continue;
+
+		free_irq(brdp->irq, brdp);
+
+		for (j = 0; (j < STL_MAXPANELS); j++) {
+			panelp = brdp->panels[j];
+			if (panelp == (stlpanel_t *) NULL)
+				continue;
+			for (k = 0; (k < STL_PORTSPERPANEL); k++) {
+				portp = panelp->ports[k];
+				if (portp == (stlport_t *) NULL)
+					continue;
+				if (portp->tty != (struct tty_struct *) NULL)
+					stl_hangup(portp->tty);
+				if (portp->tx.buf != (char *) NULL)
+					kfree(portp->tx.buf);
+				kfree(portp);
+			}
+			kfree(panelp);
+		}
+
+		release_region(brdp->ioaddr1, brdp->iosize1);
+		if (brdp->iosize2 > 0)
+			release_region(brdp->ioaddr2, brdp->iosize2);
+
+		kfree(brdp);
+		stl_brds[i] = (stlbrd_t *) NULL;
+	}
+
+	restore_flags(flags);
+}
+
+module_init(stallion_module_init);
+module_exit(stallion_module_exit);
+
+/*****************************************************************************/
+
+/*
+ *	Check for any arguments passed in on the module load command line.
+ */
+
+static void stl_argbrds(void)
+{
+	stlconf_t	conf;
+	stlbrd_t	*brdp;
+	int		i;
+
+#ifdef DEBUG
+	printk("stl_argbrds()\n");
+#endif
+
+	for (i = stl_nrbrds; (i < stl_nargs); i++) {
+		memset(&conf, 0, sizeof(conf));
+		if (stl_parsebrd(&conf, stl_brdsp[i]) == 0)
+			continue;
+		if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+			continue;
+		stl_nrbrds = i + 1;
+		brdp->brdnr = i;
+		brdp->brdtype = conf.brdtype;
+		brdp->ioaddr1 = conf.ioaddr1;
+		brdp->ioaddr2 = conf.ioaddr2;
+		brdp->irq = conf.irq;
+		brdp->irqtype = conf.irqtype;
+		stl_brdinit(brdp);
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Convert an ascii string number into an unsigned long.
+ */
+
+static unsigned long stl_atol(char *str)
+{
+	unsigned long	val;
+	int		base, c;
+	char		*sp;
+
+	val = 0;
+	sp = str;
+	if ((*sp == '0') && (*(sp+1) == 'x')) {
+		base = 16;
+		sp += 2;
+	} else if (*sp == '0') {
+		base = 8;
+		sp++;
+	} else {
+		base = 10;
+	}
+
+	for (; (*sp != 0); sp++) {
+		c = (*sp > '9') ? (TOLOWER(*sp) - 'a' + 10) : (*sp - '0');
+		if ((c < 0) || (c >= base)) {
+			printk("STALLION: invalid argument %s\n", str);
+			val = 0;
+			break;
+		}
+		val = (val * base) + c;
+	}
+	return(val);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Parse the supplied argument string, into the board conf struct.
+ */
+
+static int stl_parsebrd(stlconf_t *confp, char **argp)
+{
+	char	*sp;
+	int	nrbrdnames, i;
+
+#ifdef DEBUG
+	printk("stl_parsebrd(confp=%x,argp=%x)\n", (int) confp, (int) argp);
+#endif
+
+	if ((argp[0] == (char *) NULL) || (*argp[0] == 0))
+		return(0);
+
+	for (sp = argp[0], i = 0; ((*sp != 0) && (i < 25)); sp++, i++)
+		*sp = TOLOWER(*sp);
+
+	nrbrdnames = sizeof(stl_brdstr) / sizeof(stlbrdtype_t);
+	for (i = 0; (i < nrbrdnames); i++) {
+		if (strcmp(stl_brdstr[i].name, argp[0]) == 0)
+			break;
+	}
+	if (i >= nrbrdnames) {
+		printk("STALLION: unknown board name, %s?\n", argp[0]);
+		return(0);
+	}
+
+	confp->brdtype = stl_brdstr[i].type;
+
+	i = 1;
+	if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+		confp->ioaddr1 = stl_atol(argp[i]);
+	i++;
+	if (confp->brdtype == BRD_ECH) {
+		if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+			confp->ioaddr2 = stl_atol(argp[i]);
+		i++;
+	}
+	if ((argp[i] != (char *) NULL) && (*argp[i] != 0))
+		confp->irq = stl_atol(argp[i]);
+	return(1);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Local driver kernel memory allocation routine.
+ */
+
+static void *stl_memalloc(int len)
+{
+	return((void *) kmalloc(len, GFP_KERNEL));
+}
+
+/*****************************************************************************/
+
+/*
+ *	Allocate a new board structure. Fill out the basic info in it.
+ */
+
+static stlbrd_t *stl_allocbrd(void)
+{
+	stlbrd_t	*brdp;
+
+	brdp = (stlbrd_t *) stl_memalloc(sizeof(stlbrd_t));
+	if (brdp == (stlbrd_t *) NULL) {
+		printk("STALLION: failed to allocate memory (size=%d)\n",
+			sizeof(stlbrd_t));
+		return((stlbrd_t *) NULL);
+	}
+
+	memset(brdp, 0, sizeof(stlbrd_t));
+	brdp->magic = STL_BOARDMAGIC;
+	return(brdp);
+}
+
+/*****************************************************************************/
+
+static int stl_open(struct tty_struct *tty, struct file *filp)
+{
+	stlport_t	*portp;
+	stlbrd_t	*brdp;
+	unsigned int	minordev;
+	int		brdnr, panelnr, portnr, rc;
+
+#ifdef DEBUG
+	printk("stl_open(tty=%x,filp=%x): device=%s\n", (int) tty,
+		(int) filp, tty->name);
+#endif
+
+	minordev = tty->index;
+	brdnr = MINOR2BRD(minordev);
+	if (brdnr >= stl_nrbrds)
+		return(-ENODEV);
+	brdp = stl_brds[brdnr];
+	if (brdp == (stlbrd_t *) NULL)
+		return(-ENODEV);
+	minordev = MINOR2PORT(minordev);
+	for (portnr = -1, panelnr = 0; (panelnr < STL_MAXPANELS); panelnr++) {
+		if (brdp->panels[panelnr] == (stlpanel_t *) NULL)
+			break;
+		if (minordev < brdp->panels[panelnr]->nrports) {
+			portnr = minordev;
+			break;
+		}
+		minordev -= brdp->panels[panelnr]->nrports;
+	}
+	if (portnr < 0)
+		return(-ENODEV);
+
+	portp = brdp->panels[panelnr]->ports[portnr];
+	if (portp == (stlport_t *) NULL)
+		return(-ENODEV);
+
+/*
+ *	On the first open of the device setup the port hardware, and
+ *	initialize the per port data structure.
+ */
+	portp->tty = tty;
+	tty->driver_data = portp;
+	portp->refcount++;
+
+	if ((portp->flags & ASYNC_INITIALIZED) == 0) {
+		if (portp->tx.buf == (char *) NULL) {
+			portp->tx.buf = (char *) stl_memalloc(STL_TXBUFSIZE);
+			if (portp->tx.buf == (char *) NULL)
+				return(-ENOMEM);
+			portp->tx.head = portp->tx.buf;
+			portp->tx.tail = portp->tx.buf;
+		}
+		stl_setport(portp, tty->termios);
+		portp->sigs = stl_getsignals(portp);
+		stl_setsignals(portp, 1, 1);
+		stl_enablerxtx(portp, 1, 1);
+		stl_startrxtx(portp, 1, 0);
+		clear_bit(TTY_IO_ERROR, &tty->flags);
+		portp->flags |= ASYNC_INITIALIZED;
+	}
+
+/*
+ *	Check if this port is in the middle of closing. If so then wait
+ *	until it is closed then return error status, based on flag settings.
+ *	The sleep here does not need interrupt protection since the wakeup
+ *	for it is done with the same context.
+ */
+	if (portp->flags & ASYNC_CLOSING) {
+		interruptible_sleep_on(&portp->close_wait);
+		if (portp->flags & ASYNC_HUP_NOTIFY)
+			return(-EAGAIN);
+		return(-ERESTARTSYS);
+	}
+
+/*
+ *	Based on type of open being done check if it can overlap with any
+ *	previous opens still in effect. If we are a normal serial device
+ *	then also we might have to wait for carrier.
+ */
+	if (!(filp->f_flags & O_NONBLOCK)) {
+		if ((rc = stl_waitcarrier(portp, filp)) != 0)
+			return(rc);
+	}
+	portp->flags |= ASYNC_NORMAL_ACTIVE;
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Possibly need to wait for carrier (DCD signal) to come high. Say
+ *	maybe because if we are clocal then we don't need to wait...
+ */
+
+static int stl_waitcarrier(stlport_t *portp, struct file *filp)
+{
+	unsigned long	flags;
+	int		rc, doclocal;
+
+#ifdef DEBUG
+	printk("stl_waitcarrier(portp=%x,filp=%x)\n", (int) portp, (int) filp);
+#endif
+
+	rc = 0;
+	doclocal = 0;
+
+	if (portp->tty->termios->c_cflag & CLOCAL)
+		doclocal++;
+
+	save_flags(flags);
+	cli();
+	portp->openwaitcnt++;
+	if (! tty_hung_up_p(filp))
+		portp->refcount--;
+
+	for (;;) {
+		stl_setsignals(portp, 1, 1);
+		if (tty_hung_up_p(filp) ||
+		    ((portp->flags & ASYNC_INITIALIZED) == 0)) {
+			if (portp->flags & ASYNC_HUP_NOTIFY)
+				rc = -EBUSY;
+			else
+				rc = -ERESTARTSYS;
+			break;
+		}
+		if (((portp->flags & ASYNC_CLOSING) == 0) &&
+		    (doclocal || (portp->sigs & TIOCM_CD))) {
+			break;
+		}
+		if (signal_pending(current)) {
+			rc = -ERESTARTSYS;
+			break;
+		}
+		interruptible_sleep_on(&portp->open_wait);
+	}
+
+	if (! tty_hung_up_p(filp))
+		portp->refcount++;
+	portp->openwaitcnt--;
+	restore_flags(flags);
+
+	return(rc);
+}
+
+/*****************************************************************************/
+
+static void stl_close(struct tty_struct *tty, struct file *filp)
+{
+	stlport_t	*portp;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_close(tty=%x,filp=%x)\n", (int) tty, (int) filp);
+#endif
+
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	if (tty_hung_up_p(filp)) {
+		restore_flags(flags);
+		return;
+	}
+	if ((tty->count == 1) && (portp->refcount != 1))
+		portp->refcount = 1;
+	if (portp->refcount-- > 1) {
+		restore_flags(flags);
+		return;
+	}
+
+	portp->refcount = 0;
+	portp->flags |= ASYNC_CLOSING;
+
+/*
+ *	May want to wait for any data to drain before closing. The BUSY
+ *	flag keeps track of whether we are still sending or not - it is
+ *	very accurate for the cd1400, not quite so for the sc26198.
+ *	(The sc26198 has no "end-of-data" interrupt only empty FIFO)
+ */
+	tty->closing = 1;
+	if (portp->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+		tty_wait_until_sent(tty, portp->closing_wait);
+	stl_waituntilsent(tty, (HZ / 2));
+
+	portp->flags &= ~ASYNC_INITIALIZED;
+	stl_disableintrs(portp);
+	if (tty->termios->c_cflag & HUPCL)
+		stl_setsignals(portp, 0, 0);
+	stl_enablerxtx(portp, 0, 0);
+	stl_flushbuffer(tty);
+	portp->istate = 0;
+	if (portp->tx.buf != (char *) NULL) {
+		kfree(portp->tx.buf);
+		portp->tx.buf = (char *) NULL;
+		portp->tx.head = (char *) NULL;
+		portp->tx.tail = (char *) NULL;
+	}
+	set_bit(TTY_IO_ERROR, &tty->flags);
+	tty_ldisc_flush(tty);
+
+	tty->closing = 0;
+	portp->tty = (struct tty_struct *) NULL;
+
+	if (portp->openwaitcnt) {
+		if (portp->close_delay)
+			msleep_interruptible(jiffies_to_msecs(portp->close_delay));
+		wake_up_interruptible(&portp->open_wait);
+	}
+
+	portp->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+	wake_up_interruptible(&portp->close_wait);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Write routine. Take data and stuff it in to the TX ring queue.
+ *	If transmit interrupts are not running then start them.
+ */
+
+static int stl_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	stlport_t	*portp;
+	unsigned int	len, stlen;
+	unsigned char	*chbuf;
+	char		*head, *tail;
+
+#ifdef DEBUG
+	printk("stl_write(tty=%x,buf=%x,count=%d)\n",
+		(int) tty, (int) buf, count);
+#endif
+
+	if ((tty == (struct tty_struct *) NULL) ||
+	    (stl_tmpwritebuf == (char *) NULL))
+		return(0);
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return(0);
+	if (portp->tx.buf == (char *) NULL)
+		return(0);
+
+/*
+ *	If copying direct from user space we must cater for page faults,
+ *	causing us to "sleep" here for a while. To handle this copy in all
+ *	the data we need now, into a local buffer. Then when we got it all
+ *	copy it into the TX buffer.
+ */
+	chbuf = (unsigned char *) buf;
+
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+	if (head >= tail) {
+		len = STL_TXBUFSIZE - (head - tail) - 1;
+		stlen = STL_TXBUFSIZE - (head - portp->tx.buf);
+	} else {
+		len = tail - head - 1;
+		stlen = len;
+	}
+
+	len = MIN(len, count);
+	count = 0;
+	while (len > 0) {
+		stlen = MIN(len, stlen);
+		memcpy(head, chbuf, stlen);
+		len -= stlen;
+		chbuf += stlen;
+		count += stlen;
+		head += stlen;
+		if (head >= (portp->tx.buf + STL_TXBUFSIZE)) {
+			head = portp->tx.buf;
+			stlen = tail - head;
+		}
+	}
+	portp->tx.head = head;
+
+	clear_bit(ASYI_TXLOW, &portp->istate);
+	stl_startrxtx(portp, -1, 1);
+
+	return(count);
+}
+
+/*****************************************************************************/
+
+static void stl_putchar(struct tty_struct *tty, unsigned char ch)
+{
+	stlport_t	*portp;
+	unsigned int	len;
+	char		*head, *tail;
+
+#ifdef DEBUG
+	printk("stl_putchar(tty=%x,ch=%x)\n", (int) tty, (int) ch);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+	if (portp->tx.buf == (char *) NULL)
+		return;
+
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+
+	len = (head >= tail) ? (STL_TXBUFSIZE - (head - tail)) : (tail - head);
+	len--;
+
+	if (len > 0) {
+		*head++ = ch;
+		if (head >= (portp->tx.buf + STL_TXBUFSIZE))
+			head = portp->tx.buf;
+	}	
+	portp->tx.head = head;
+}
+
+/*****************************************************************************/
+
+/*
+ *	If there are any characters in the buffer then make sure that TX
+ *	interrupts are on and get'em out. Normally used after the putchar
+ *	routine has been called.
+ */
+
+static void stl_flushchars(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_flushchars(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+	if (portp->tx.buf == (char *) NULL)
+		return;
+
+#if 0
+	if (tty->stopped || tty->hw_stopped ||
+	    (portp->tx.head == portp->tx.tail))
+		return;
+#endif
+	stl_startrxtx(portp, -1, 1);
+}
+
+/*****************************************************************************/
+
+static int stl_writeroom(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+	char		*head, *tail;
+
+#ifdef DEBUG
+	printk("stl_writeroom(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return(0);
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return(0);
+	if (portp->tx.buf == (char *) NULL)
+		return(0);
+
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+	return((head >= tail) ? (STL_TXBUFSIZE - (head - tail) - 1) : (tail - head - 1));
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return number of chars in the TX buffer. Normally we would just
+ *	calculate the number of chars in the buffer and return that, but if
+ *	the buffer is empty and TX interrupts are still on then we return
+ *	that the buffer still has 1 char in it. This way whoever called us
+ *	will not think that ALL chars have drained - since the UART still
+ *	must have some chars in it (we are busy after all).
+ */
+
+static int stl_charsinbuffer(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+	unsigned int	size;
+	char		*head, *tail;
+
+#ifdef DEBUG
+	printk("stl_charsinbuffer(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return(0);
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return(0);
+	if (portp->tx.buf == (char *) NULL)
+		return(0);
+
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+	size = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+	if ((size == 0) && test_bit(ASYI_TXBUSY, &portp->istate))
+		size = 1;
+	return(size);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Generate the serial struct info.
+ */
+
+static int stl_getserial(stlport_t *portp, struct serial_struct __user *sp)
+{
+	struct serial_struct	sio;
+	stlbrd_t		*brdp;
+
+#ifdef DEBUG
+	printk("stl_getserial(portp=%x,sp=%x)\n", (int) portp, (int) sp);
+#endif
+
+	memset(&sio, 0, sizeof(struct serial_struct));
+	sio.line = portp->portnr;
+	sio.port = portp->ioaddr;
+	sio.flags = portp->flags;
+	sio.baud_base = portp->baud_base;
+	sio.close_delay = portp->close_delay;
+	sio.closing_wait = portp->closing_wait;
+	sio.custom_divisor = portp->custom_divisor;
+	sio.hub6 = 0;
+	if (portp->uartp == &stl_cd1400uart) {
+		sio.type = PORT_CIRRUS;
+		sio.xmit_fifo_size = CD1400_TXFIFOSIZE;
+	} else {
+		sio.type = PORT_UNKNOWN;
+		sio.xmit_fifo_size = SC26198_TXFIFOSIZE;
+	}
+
+	brdp = stl_brds[portp->brdnr];
+	if (brdp != (stlbrd_t *) NULL)
+		sio.irq = brdp->irq;
+
+	return copy_to_user(sp, &sio, sizeof(struct serial_struct)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Set port according to the serial struct info.
+ *	At this point we do not do any auto-configure stuff, so we will
+ *	just quietly ignore any requests to change irq, etc.
+ */
+
+static int stl_setserial(stlport_t *portp, struct serial_struct __user *sp)
+{
+	struct serial_struct	sio;
+
+#ifdef DEBUG
+	printk("stl_setserial(portp=%x,sp=%x)\n", (int) portp, (int) sp);
+#endif
+
+	if (copy_from_user(&sio, sp, sizeof(struct serial_struct)))
+		return -EFAULT;
+	if (!capable(CAP_SYS_ADMIN)) {
+		if ((sio.baud_base != portp->baud_base) ||
+		    (sio.close_delay != portp->close_delay) ||
+		    ((sio.flags & ~ASYNC_USR_MASK) !=
+		    (portp->flags & ~ASYNC_USR_MASK)))
+			return(-EPERM);
+	} 
+
+	portp->flags = (portp->flags & ~ASYNC_USR_MASK) |
+		(sio.flags & ASYNC_USR_MASK);
+	portp->baud_base = sio.baud_base;
+	portp->close_delay = sio.close_delay;
+	portp->closing_wait = sio.closing_wait;
+	portp->custom_divisor = sio.custom_divisor;
+	stl_setport(portp, portp->tty->termios);
+	return(0);
+}
+
+/*****************************************************************************/
+
+static int stl_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	stlport_t	*portp;
+
+	if (tty == (struct tty_struct *) NULL)
+		return(-ENODEV);
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return(-ENODEV);
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return(-EIO);
+
+	return stl_getsignals(portp);
+}
+
+static int stl_tiocmset(struct tty_struct *tty, struct file *file,
+			unsigned int set, unsigned int clear)
+{
+	stlport_t	*portp;
+	int rts = -1, dtr = -1;
+
+	if (tty == (struct tty_struct *) NULL)
+		return(-ENODEV);
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return(-ENODEV);
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return(-EIO);
+
+	if (set & TIOCM_RTS)
+		rts = 1;
+	if (set & TIOCM_DTR)
+		dtr = 1;
+	if (clear & TIOCM_RTS)
+		rts = 0;
+	if (clear & TIOCM_DTR)
+		dtr = 0;
+
+	stl_setsignals(portp, dtr, rts);
+	return 0;
+}
+
+static int stl_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	stlport_t	*portp;
+	unsigned int	ival;
+	int		rc;
+	void __user *argp = (void __user *)arg;
+
+#ifdef DEBUG
+	printk("stl_ioctl(tty=%x,file=%x,cmd=%x,arg=%x)\n",
+		(int) tty, (int) file, cmd, (int) arg);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return(-ENODEV);
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return(-ENODEV);
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ 	    (cmd != COM_GETPORTSTATS) && (cmd != COM_CLRPORTSTATS)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+			return(-EIO);
+	}
+
+	rc = 0;
+
+	switch (cmd) {
+	case TIOCGSOFTCAR:
+		rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+			(unsigned __user *) argp);
+		break;
+	case TIOCSSOFTCAR:
+		if (get_user(ival, (unsigned int __user *) arg))
+			return -EFAULT;
+		tty->termios->c_cflag =
+				(tty->termios->c_cflag & ~CLOCAL) |
+				(ival ? CLOCAL : 0);
+		break;
+	case TIOCGSERIAL:
+		rc = stl_getserial(portp, argp);
+		break;
+	case TIOCSSERIAL:
+		rc = stl_setserial(portp, argp);
+		break;
+	case COM_GETPORTSTATS:
+		rc = stl_getportstats(portp, argp);
+		break;
+	case COM_CLRPORTSTATS:
+		rc = stl_clrportstats(portp, argp);
+		break;
+	case TIOCSERCONFIG:
+	case TIOCSERGWILD:
+	case TIOCSERSWILD:
+	case TIOCSERGETLSR:
+	case TIOCSERGSTRUCT:
+	case TIOCSERGETMULTI:
+	case TIOCSERSETMULTI:
+	default:
+		rc = -ENOIOCTLCMD;
+		break;
+	}
+
+	return(rc);
+}
+
+/*****************************************************************************/
+
+static void stl_settermios(struct tty_struct *tty, struct termios *old)
+{
+	stlport_t	*portp;
+	struct termios	*tiosp;
+
+#ifdef DEBUG
+	printk("stl_settermios(tty=%x,old=%x)\n", (int) tty, (int) old);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	tiosp = tty->termios;
+	if ((tiosp->c_cflag == old->c_cflag) &&
+	    (tiosp->c_iflag == old->c_iflag))
+		return;
+
+	stl_setport(portp, tiosp);
+	stl_setsignals(portp, ((tiosp->c_cflag & (CBAUD & ~CBAUDEX)) ? 1 : 0),
+		-1);
+	if ((old->c_cflag & CRTSCTS) && ((tiosp->c_cflag & CRTSCTS) == 0)) {
+		tty->hw_stopped = 0;
+		stl_start(tty);
+	}
+	if (((old->c_cflag & CLOCAL) == 0) && (tiosp->c_cflag & CLOCAL))
+		wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Attempt to flow control who ever is sending us data. Based on termios
+ *	settings use software or/and hardware flow control.
+ */
+
+static void stl_throttle(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_throttle(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+	stl_flowctrl(portp, 0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Unflow control the device sending us data...
+ */
+
+static void stl_unthrottle(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_unthrottle(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+	stl_flowctrl(portp, 1);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Stop the transmitter. Basically to do this we will just turn TX
+ *	interrupts off.
+ */
+
+static void stl_stop(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_stop(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+	stl_startrxtx(portp, -1, 0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Start the transmitter again. Just turn TX interrupts back on.
+ */
+
+static void stl_start(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_start(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+	stl_startrxtx(portp, -1, 1);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Hangup this port. This is pretty much like closing the port, only
+ *	a little more brutal. No waiting for data to drain. Shutdown the
+ *	port and maybe drop signals.
+ */
+
+static void stl_hangup(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_hangup(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	portp->flags &= ~ASYNC_INITIALIZED;
+	stl_disableintrs(portp);
+	if (tty->termios->c_cflag & HUPCL)
+		stl_setsignals(portp, 0, 0);
+	stl_enablerxtx(portp, 0, 0);
+	stl_flushbuffer(tty);
+	portp->istate = 0;
+	set_bit(TTY_IO_ERROR, &tty->flags);
+	if (portp->tx.buf != (char *) NULL) {
+		kfree(portp->tx.buf);
+		portp->tx.buf = (char *) NULL;
+		portp->tx.head = (char *) NULL;
+		portp->tx.tail = (char *) NULL;
+	}
+	portp->tty = (struct tty_struct *) NULL;
+	portp->flags &= ~ASYNC_NORMAL_ACTIVE;
+	portp->refcount = 0;
+	wake_up_interruptible(&portp->open_wait);
+}
+
+/*****************************************************************************/
+
+static void stl_flushbuffer(struct tty_struct *tty)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_flushbuffer(tty=%x)\n", (int) tty);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	stl_flush(portp);
+	tty_wakeup(tty);
+}
+
+/*****************************************************************************/
+
+static void stl_breakctl(struct tty_struct *tty, int state)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_breakctl(tty=%x,state=%d)\n", (int) tty, state);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	stl_sendbreak(portp, ((state == -1) ? 1 : 2));
+}
+
+/*****************************************************************************/
+
+static void stl_waituntilsent(struct tty_struct *tty, int timeout)
+{
+	stlport_t	*portp;
+	unsigned long	tend;
+
+#ifdef DEBUG
+	printk("stl_waituntilsent(tty=%x,timeout=%d)\n", (int) tty, timeout);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	if (timeout == 0)
+		timeout = HZ;
+	tend = jiffies + timeout;
+
+	while (stl_datastate(portp)) {
+		if (signal_pending(current))
+			break;
+		msleep_interruptible(20);
+		if (time_after_eq(jiffies, tend))
+			break;
+	}
+}
+
+/*****************************************************************************/
+
+static void stl_sendxchar(struct tty_struct *tty, char ch)
+{
+	stlport_t	*portp;
+
+#ifdef DEBUG
+	printk("stl_sendxchar(tty=%x,ch=%x)\n", (int) tty, ch);
+#endif
+
+	if (tty == (struct tty_struct *) NULL)
+		return;
+	portp = tty->driver_data;
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	if (ch == STOP_CHAR(tty))
+		stl_sendflow(portp, 0);
+	else if (ch == START_CHAR(tty))
+		stl_sendflow(portp, 1);
+	else
+		stl_putchar(tty, ch);
+}
+
+/*****************************************************************************/
+
+#define	MAXLINE		80
+
+/*
+ *	Format info for a specified port. The line is deliberately limited
+ *	to 80 characters. (If it is too long it will be truncated, if too
+ *	short then padded with spaces).
+ */
+
+static int stl_portinfo(stlport_t *portp, int portnr, char *pos)
+{
+	char	*sp;
+	int	sigs, cnt;
+
+	sp = pos;
+	sp += sprintf(sp, "%d: uart:%s tx:%d rx:%d",
+		portnr, (portp->hwid == 1) ? "SC26198" : "CD1400",
+		(int) portp->stats.txtotal, (int) portp->stats.rxtotal);
+
+	if (portp->stats.rxframing)
+		sp += sprintf(sp, " fe:%d", (int) portp->stats.rxframing);
+	if (portp->stats.rxparity)
+		sp += sprintf(sp, " pe:%d", (int) portp->stats.rxparity);
+	if (portp->stats.rxbreaks)
+		sp += sprintf(sp, " brk:%d", (int) portp->stats.rxbreaks);
+	if (portp->stats.rxoverrun)
+		sp += sprintf(sp, " oe:%d", (int) portp->stats.rxoverrun);
+
+	sigs = stl_getsignals(portp);
+	cnt = sprintf(sp, "%s%s%s%s%s ",
+		(sigs & TIOCM_RTS) ? "|RTS" : "",
+		(sigs & TIOCM_CTS) ? "|CTS" : "",
+		(sigs & TIOCM_DTR) ? "|DTR" : "",
+		(sigs & TIOCM_CD) ? "|DCD" : "",
+		(sigs & TIOCM_DSR) ? "|DSR" : "");
+	*sp = ' ';
+	sp += cnt;
+
+	for (cnt = (sp - pos); (cnt < (MAXLINE - 1)); cnt++)
+		*sp++ = ' ';
+	if (cnt >= MAXLINE)
+		pos[(MAXLINE - 2)] = '+';
+	pos[(MAXLINE - 1)] = '\n';
+
+	return(MAXLINE);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Port info, read from the /proc file system.
+ */
+
+static int stl_readproc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+	stlbrd_t	*brdp;
+	stlpanel_t	*panelp;
+	stlport_t	*portp;
+	int		brdnr, panelnr, portnr, totalport;
+	int		curoff, maxoff;
+	char		*pos;
+
+#ifdef DEBUG
+	printk("stl_readproc(page=%x,start=%x,off=%x,count=%d,eof=%x,"
+		"data=%x\n", (int) page, (int) start, (int) off, count,
+		(int) eof, (int) data);
+#endif
+
+	pos = page;
+	totalport = 0;
+	curoff = 0;
+
+	if (off == 0) {
+		pos += sprintf(pos, "%s: version %s", stl_drvtitle,
+			stl_drvversion);
+		while (pos < (page + MAXLINE - 1))
+			*pos++ = ' ';
+		*pos++ = '\n';
+	}
+	curoff =  MAXLINE;
+
+/*
+ *	We scan through for each board, panel and port. The offset is
+ *	calculated on the fly, and irrelevant ports are skipped.
+ */
+	for (brdnr = 0; (brdnr < stl_nrbrds); brdnr++) {
+		brdp = stl_brds[brdnr];
+		if (brdp == (stlbrd_t *) NULL)
+			continue;
+		if (brdp->state == 0)
+			continue;
+
+		maxoff = curoff + (brdp->nrports * MAXLINE);
+		if (off >= maxoff) {
+			curoff = maxoff;
+			continue;
+		}
+
+		totalport = brdnr * STL_MAXPORTS;
+		for (panelnr = 0; (panelnr < brdp->nrpanels); panelnr++) {
+			panelp = brdp->panels[panelnr];
+			if (panelp == (stlpanel_t *) NULL)
+				continue;
+
+			maxoff = curoff + (panelp->nrports * MAXLINE);
+			if (off >= maxoff) {
+				curoff = maxoff;
+				totalport += panelp->nrports;
+				continue;
+			}
+
+			for (portnr = 0; (portnr < panelp->nrports); portnr++,
+			    totalport++) {
+				portp = panelp->ports[portnr];
+				if (portp == (stlport_t *) NULL)
+					continue;
+				if (off >= (curoff += MAXLINE))
+					continue;
+				if ((pos - page + MAXLINE) > count)
+					goto stl_readdone;
+				pos += stl_portinfo(portp, totalport, pos);
+			}
+		}
+	}
+
+	*eof = 1;
+
+stl_readdone:
+	*start = page;
+	return(pos - page);
+}
+
+/*****************************************************************************/
+
+/*
+ *	All board interrupts are vectored through here first. This code then
+ *	calls off to the approrpriate board interrupt handlers.
+ */
+
+static irqreturn_t stl_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	stlbrd_t	*brdp = (stlbrd_t *) dev_id;
+
+#ifdef DEBUG
+	printk("stl_intr(brdp=%x,irq=%d,regs=%x)\n", (int) brdp, irq,
+	    (int) regs);
+#endif
+
+	return IRQ_RETVAL((* brdp->isr)(brdp));
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt service routine for EasyIO board types.
+ */
+
+static int stl_eiointr(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	iobase;
+	int		handled = 0;
+
+	panelp = brdp->panels[0];
+	iobase = panelp->iobase;
+	while (inb(brdp->iostatus) & EIO_INTRPEND) {
+		handled = 1;
+		(* panelp->isr)(panelp, iobase);
+	}
+	return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt service routine for ECH-AT board types.
+ */
+
+static int stl_echatintr(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	ioaddr;
+	int		bnknr;
+	int		handled = 0;
+
+	outb((brdp->ioctrlval | ECH_BRDENABLE), brdp->ioctrl);
+
+	while (inb(brdp->iostatus) & ECH_INTRPEND) {
+		handled = 1;
+		for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+			ioaddr = brdp->bnkstataddr[bnknr];
+			if (inb(ioaddr) & ECH_PNLINTRPEND) {
+				panelp = brdp->bnk2panel[bnknr];
+				(* panelp->isr)(panelp, (ioaddr & 0xfffc));
+			}
+		}
+	}
+
+	outb((brdp->ioctrlval | ECH_BRDDISABLE), brdp->ioctrl);
+
+	return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt service routine for ECH-MCA board types.
+ */
+
+static int stl_echmcaintr(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	ioaddr;
+	int		bnknr;
+	int		handled = 0;
+
+	while (inb(brdp->iostatus) & ECH_INTRPEND) {
+		handled = 1;
+		for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+			ioaddr = brdp->bnkstataddr[bnknr];
+			if (inb(ioaddr) & ECH_PNLINTRPEND) {
+				panelp = brdp->bnk2panel[bnknr];
+				(* panelp->isr)(panelp, (ioaddr & 0xfffc));
+			}
+		}
+	}
+	return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt service routine for ECH-PCI board types.
+ */
+
+static int stl_echpciintr(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	ioaddr;
+	int		bnknr, recheck;
+	int		handled = 0;
+
+	while (1) {
+		recheck = 0;
+		for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+			outb(brdp->bnkpageaddr[bnknr], brdp->ioctrl);
+			ioaddr = brdp->bnkstataddr[bnknr];
+			if (inb(ioaddr) & ECH_PNLINTRPEND) {
+				panelp = brdp->bnk2panel[bnknr];
+				(* panelp->isr)(panelp, (ioaddr & 0xfffc));
+				recheck++;
+				handled = 1;
+			}
+		}
+		if (! recheck)
+			break;
+	}
+	return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt service routine for ECH-8/64-PCI board types.
+ */
+
+static int stl_echpci64intr(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	ioaddr;
+	int		bnknr;
+	int		handled = 0;
+
+	while (inb(brdp->ioctrl) & 0x1) {
+		handled = 1;
+		for (bnknr = 0; (bnknr < brdp->nrbnks); bnknr++) {
+			ioaddr = brdp->bnkstataddr[bnknr];
+			if (inb(ioaddr) & ECH_PNLINTRPEND) {
+				panelp = brdp->bnk2panel[bnknr];
+				(* panelp->isr)(panelp, (ioaddr & 0xfffc));
+			}
+		}
+	}
+
+	return handled;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Service an off-level request for some channel.
+ */
+static void stl_offintr(void *private)
+{
+	stlport_t		*portp;
+	struct tty_struct	*tty;
+	unsigned int		oldsigs;
+
+	portp = private;
+
+#ifdef DEBUG
+	printk("stl_offintr(portp=%x)\n", (int) portp);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	tty = portp->tty;
+	if (tty == (struct tty_struct *) NULL)
+		return;
+
+	lock_kernel();
+	if (test_bit(ASYI_TXLOW, &portp->istate)) {
+		tty_wakeup(tty);
+	}
+	if (test_bit(ASYI_DCDCHANGE, &portp->istate)) {
+		clear_bit(ASYI_DCDCHANGE, &portp->istate);
+		oldsigs = portp->sigs;
+		portp->sigs = stl_getsignals(portp);
+		if ((portp->sigs & TIOCM_CD) && ((oldsigs & TIOCM_CD) == 0))
+			wake_up_interruptible(&portp->open_wait);
+		if ((oldsigs & TIOCM_CD) && ((portp->sigs & TIOCM_CD) == 0)) {
+			if (portp->flags & ASYNC_CHECK_CD)
+				tty_hangup(tty);	/* FIXME: module removal race here - AKPM */
+		}
+	}
+	unlock_kernel();
+}
+
+/*****************************************************************************/
+
+/*
+ *	Initialize all the ports on a panel.
+ */
+
+static int __init stl_initports(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+	stlport_t	*portp;
+	int		chipmask, i;
+
+#ifdef DEBUG
+	printk("stl_initports(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp);
+#endif
+
+	chipmask = stl_panelinit(brdp, panelp);
+
+/*
+ *	All UART's are initialized (if found!). Now go through and setup
+ *	each ports data structures.
+ */
+	for (i = 0; (i < panelp->nrports); i++) {
+		portp = (stlport_t *) stl_memalloc(sizeof(stlport_t));
+		if (portp == (stlport_t *) NULL) {
+			printk("STALLION: failed to allocate memory "
+				"(size=%d)\n", sizeof(stlport_t));
+			break;
+		}
+		memset(portp, 0, sizeof(stlport_t));
+
+		portp->magic = STL_PORTMAGIC;
+		portp->portnr = i;
+		portp->brdnr = panelp->brdnr;
+		portp->panelnr = panelp->panelnr;
+		portp->uartp = panelp->uartp;
+		portp->clk = brdp->clk;
+		portp->baud_base = STL_BAUDBASE;
+		portp->close_delay = STL_CLOSEDELAY;
+		portp->closing_wait = 30 * HZ;
+		INIT_WORK(&portp->tqueue, stl_offintr, portp);
+		init_waitqueue_head(&portp->open_wait);
+		init_waitqueue_head(&portp->close_wait);
+		portp->stats.brd = portp->brdnr;
+		portp->stats.panel = portp->panelnr;
+		portp->stats.port = portp->portnr;
+		panelp->ports[i] = portp;
+		stl_portinit(brdp, panelp, portp);
+	}
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Try to find and initialize an EasyIO board.
+ */
+
+static inline int stl_initeio(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	status;
+	char		*name;
+	int		rc;
+
+#ifdef DEBUG
+	printk("stl_initeio(brdp=%x)\n", (int) brdp);
+#endif
+
+	brdp->ioctrl = brdp->ioaddr1 + 1;
+	brdp->iostatus = brdp->ioaddr1 + 2;
+
+	status = inb(brdp->iostatus);
+	if ((status & EIO_IDBITMASK) == EIO_MK3)
+		brdp->ioctrl++;
+
+/*
+ *	Handle board specific stuff now. The real difference is PCI
+ *	or not PCI.
+ */
+	if (brdp->brdtype == BRD_EASYIOPCI) {
+		brdp->iosize1 = 0x80;
+		brdp->iosize2 = 0x80;
+		name = "serial(EIO-PCI)";
+		outb(0x41, (brdp->ioaddr2 + 0x4c));
+	} else {
+		brdp->iosize1 = 8;
+		name = "serial(EIO)";
+		if ((brdp->irq < 0) || (brdp->irq > 15) ||
+		    (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+			printk("STALLION: invalid irq=%d for brd=%d\n",
+				brdp->irq, brdp->brdnr);
+			return(-EINVAL);
+		}
+		outb((stl_vecmap[brdp->irq] | EIO_0WS |
+			((brdp->irqtype) ? EIO_INTLEVEL : EIO_INTEDGE)),
+			brdp->ioctrl);
+	}
+
+	if (!request_region(brdp->ioaddr1, brdp->iosize1, name)) {
+		printk(KERN_WARNING "STALLION: Warning, board %d I/O address "
+			"%x conflicts with another device\n", brdp->brdnr, 
+			brdp->ioaddr1);
+		return(-EBUSY);
+	}
+	
+	if (brdp->iosize2 > 0)
+		if (!request_region(brdp->ioaddr2, brdp->iosize2, name)) {
+			printk(KERN_WARNING "STALLION: Warning, board %d I/O "
+				"address %x conflicts with another device\n",
+				brdp->brdnr, brdp->ioaddr2);
+			printk(KERN_WARNING "STALLION: Warning, also "
+				"releasing board %d I/O address %x \n", 
+				brdp->brdnr, brdp->ioaddr1);
+			release_region(brdp->ioaddr1, brdp->iosize1);
+        		return(-EBUSY);
+		}
+
+/*
+ *	Everything looks OK, so let's go ahead and probe for the hardware.
+ */
+	brdp->clk = CD1400_CLK;
+	brdp->isr = stl_eiointr;
+
+	switch (status & EIO_IDBITMASK) {
+	case EIO_8PORTM:
+		brdp->clk = CD1400_CLK8M;
+		/* fall thru */
+	case EIO_8PORTRS:
+	case EIO_8PORTDI:
+		brdp->nrports = 8;
+		break;
+	case EIO_4PORTRS:
+		brdp->nrports = 4;
+		break;
+	case EIO_MK3:
+		switch (status & EIO_BRDMASK) {
+		case ID_BRD4:
+			brdp->nrports = 4;
+			break;
+		case ID_BRD8:
+			brdp->nrports = 8;
+			break;
+		case ID_BRD16:
+			brdp->nrports = 16;
+			break;
+		default:
+			return(-ENODEV);
+		}
+		break;
+	default:
+		return(-ENODEV);
+	}
+
+/*
+ *	We have verified that the board is actually present, so now we
+ *	can complete the setup.
+ */
+
+	panelp = (stlpanel_t *) stl_memalloc(sizeof(stlpanel_t));
+	if (panelp == (stlpanel_t *) NULL) {
+		printk(KERN_WARNING "STALLION: failed to allocate memory "
+			"(size=%d)\n", sizeof(stlpanel_t));
+		return(-ENOMEM);
+	}
+	memset(panelp, 0, sizeof(stlpanel_t));
+
+	panelp->magic = STL_PANELMAGIC;
+	panelp->brdnr = brdp->brdnr;
+	panelp->panelnr = 0;
+	panelp->nrports = brdp->nrports;
+	panelp->iobase = brdp->ioaddr1;
+	panelp->hwid = status;
+	if ((status & EIO_IDBITMASK) == EIO_MK3) {
+		panelp->uartp = (void *) &stl_sc26198uart;
+		panelp->isr = stl_sc26198intr;
+	} else {
+		panelp->uartp = (void *) &stl_cd1400uart;
+		panelp->isr = stl_cd1400eiointr;
+	}
+
+	brdp->panels[0] = panelp;
+	brdp->nrpanels = 1;
+	brdp->state |= BRD_FOUND;
+	brdp->hwid = status;
+	if (request_irq(brdp->irq, stl_intr, SA_SHIRQ, name, brdp) != 0) {
+		printk("STALLION: failed to register interrupt "
+		    "routine for %s irq=%d\n", name, brdp->irq);
+		rc = -ENODEV;
+	} else {
+		rc = 0;
+	}
+	return(rc);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Try to find an ECH board and initialize it. This code is capable of
+ *	dealing with all types of ECH board.
+ */
+
+static inline int stl_initech(stlbrd_t *brdp)
+{
+	stlpanel_t	*panelp;
+	unsigned int	status, nxtid, ioaddr, conflict;
+	int		panelnr, banknr, i;
+	char		*name;
+
+#ifdef DEBUG
+	printk("stl_initech(brdp=%x)\n", (int) brdp);
+#endif
+
+	status = 0;
+	conflict = 0;
+
+/*
+ *	Set up the initial board register contents for boards. This varies a
+ *	bit between the different board types. So we need to handle each
+ *	separately. Also do a check that the supplied IRQ is good.
+ */
+	switch (brdp->brdtype) {
+
+	case BRD_ECH:
+		brdp->isr = stl_echatintr;
+		brdp->ioctrl = brdp->ioaddr1 + 1;
+		brdp->iostatus = brdp->ioaddr1 + 1;
+		status = inb(brdp->iostatus);
+		if ((status & ECH_IDBITMASK) != ECH_ID)
+			return(-ENODEV);
+		if ((brdp->irq < 0) || (brdp->irq > 15) ||
+		    (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+			printk("STALLION: invalid irq=%d for brd=%d\n",
+				brdp->irq, brdp->brdnr);
+			return(-EINVAL);
+		}
+		status = ((brdp->ioaddr2 & ECH_ADDR2MASK) >> 1);
+		status |= (stl_vecmap[brdp->irq] << 1);
+		outb((status | ECH_BRDRESET), brdp->ioaddr1);
+		brdp->ioctrlval = ECH_INTENABLE |
+			((brdp->irqtype) ? ECH_INTLEVEL : ECH_INTEDGE);
+		for (i = 0; (i < 10); i++)
+			outb((brdp->ioctrlval | ECH_BRDENABLE), brdp->ioctrl);
+		brdp->iosize1 = 2;
+		brdp->iosize2 = 32;
+		name = "serial(EC8/32)";
+		outb(status, brdp->ioaddr1);
+		break;
+
+	case BRD_ECHMC:
+		brdp->isr = stl_echmcaintr;
+		brdp->ioctrl = brdp->ioaddr1 + 0x20;
+		brdp->iostatus = brdp->ioctrl;
+		status = inb(brdp->iostatus);
+		if ((status & ECH_IDBITMASK) != ECH_ID)
+			return(-ENODEV);
+		if ((brdp->irq < 0) || (brdp->irq > 15) ||
+		    (stl_vecmap[brdp->irq] == (unsigned char) 0xff)) {
+			printk("STALLION: invalid irq=%d for brd=%d\n",
+				brdp->irq, brdp->brdnr);
+			return(-EINVAL);
+		}
+		outb(ECHMC_BRDRESET, brdp->ioctrl);
+		outb(ECHMC_INTENABLE, brdp->ioctrl);
+		brdp->iosize1 = 64;
+		name = "serial(EC8/32-MC)";
+		break;
+
+	case BRD_ECHPCI:
+		brdp->isr = stl_echpciintr;
+		brdp->ioctrl = brdp->ioaddr1 + 2;
+		brdp->iosize1 = 4;
+		brdp->iosize2 = 8;
+		name = "serial(EC8/32-PCI)";
+		break;
+
+	case BRD_ECH64PCI:
+		brdp->isr = stl_echpci64intr;
+		brdp->ioctrl = brdp->ioaddr2 + 0x40;
+		outb(0x43, (brdp->ioaddr1 + 0x4c));
+		brdp->iosize1 = 0x80;
+		brdp->iosize2 = 0x80;
+		name = "serial(EC8/64-PCI)";
+		break;
+
+	default:
+		printk("STALLION: unknown board type=%d\n", brdp->brdtype);
+		return(-EINVAL);
+		break;
+	}
+
+/*
+ *	Check boards for possible IO address conflicts and return fail status 
+ * 	if an IO conflict found.
+ */
+	if (!request_region(brdp->ioaddr1, brdp->iosize1, name)) {
+		printk(KERN_WARNING "STALLION: Warning, board %d I/O address "
+			"%x conflicts with another device\n", brdp->brdnr, 
+			brdp->ioaddr1);
+		return(-EBUSY);
+	}
+	
+	if (brdp->iosize2 > 0)
+		if (!request_region(brdp->ioaddr2, brdp->iosize2, name)) {
+			printk(KERN_WARNING "STALLION: Warning, board %d I/O "
+				"address %x conflicts with another device\n",
+				brdp->brdnr, brdp->ioaddr2);
+			printk(KERN_WARNING "STALLION: Warning, also "
+				"releasing board %d I/O address %x \n", 
+				brdp->brdnr, brdp->ioaddr1);
+			release_region(brdp->ioaddr1, brdp->iosize1);
+			return(-EBUSY);
+		}
+
+/*
+ *	Scan through the secondary io address space looking for panels.
+ *	As we find'em allocate and initialize panel structures for each.
+ */
+	brdp->clk = CD1400_CLK;
+	brdp->hwid = status;
+
+	ioaddr = brdp->ioaddr2;
+	banknr = 0;
+	panelnr = 0;
+	nxtid = 0;
+
+	for (i = 0; (i < STL_MAXPANELS); i++) {
+		if (brdp->brdtype == BRD_ECHPCI) {
+			outb(nxtid, brdp->ioctrl);
+			ioaddr = brdp->ioaddr2;
+		}
+		status = inb(ioaddr + ECH_PNLSTATUS);
+		if ((status & ECH_PNLIDMASK) != nxtid)
+			break;
+		panelp = (stlpanel_t *) stl_memalloc(sizeof(stlpanel_t));
+		if (panelp == (stlpanel_t *) NULL) {
+			printk("STALLION: failed to allocate memory "
+				"(size=%d)\n", sizeof(stlpanel_t));
+			break;
+		}
+		memset(panelp, 0, sizeof(stlpanel_t));
+		panelp->magic = STL_PANELMAGIC;
+		panelp->brdnr = brdp->brdnr;
+		panelp->panelnr = panelnr;
+		panelp->iobase = ioaddr;
+		panelp->pagenr = nxtid;
+		panelp->hwid = status;
+		brdp->bnk2panel[banknr] = panelp;
+		brdp->bnkpageaddr[banknr] = nxtid;
+		brdp->bnkstataddr[banknr++] = ioaddr + ECH_PNLSTATUS;
+
+		if (status & ECH_PNLXPID) {
+			panelp->uartp = (void *) &stl_sc26198uart;
+			panelp->isr = stl_sc26198intr;
+			if (status & ECH_PNL16PORT) {
+				panelp->nrports = 16;
+				brdp->bnk2panel[banknr] = panelp;
+				brdp->bnkpageaddr[banknr] = nxtid;
+				brdp->bnkstataddr[banknr++] = ioaddr + 4 +
+					ECH_PNLSTATUS;
+			} else {
+				panelp->nrports = 8;
+			}
+		} else {
+			panelp->uartp = (void *) &stl_cd1400uart;
+			panelp->isr = stl_cd1400echintr;
+			if (status & ECH_PNL16PORT) {
+				panelp->nrports = 16;
+				panelp->ackmask = 0x80;
+				if (brdp->brdtype != BRD_ECHPCI)
+					ioaddr += EREG_BANKSIZE;
+				brdp->bnk2panel[banknr] = panelp;
+				brdp->bnkpageaddr[banknr] = ++nxtid;
+				brdp->bnkstataddr[banknr++] = ioaddr +
+					ECH_PNLSTATUS;
+			} else {
+				panelp->nrports = 8;
+				panelp->ackmask = 0xc0;
+			}
+		}
+
+		nxtid++;
+		ioaddr += EREG_BANKSIZE;
+		brdp->nrports += panelp->nrports;
+		brdp->panels[panelnr++] = panelp;
+		if ((brdp->brdtype != BRD_ECHPCI) &&
+		    (ioaddr >= (brdp->ioaddr2 + brdp->iosize2)))
+			break;
+	}
+
+	brdp->nrpanels = panelnr;
+	brdp->nrbnks = banknr;
+	if (brdp->brdtype == BRD_ECH)
+		outb((brdp->ioctrlval | ECH_BRDDISABLE), brdp->ioctrl);
+
+	brdp->state |= BRD_FOUND;
+	if (request_irq(brdp->irq, stl_intr, SA_SHIRQ, name, brdp) != 0) {
+		printk("STALLION: failed to register interrupt "
+		    "routine for %s irq=%d\n", name, brdp->irq);
+		i = -ENODEV;
+	} else {
+		i = 0;
+	}
+
+	return(i);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Initialize and configure the specified board.
+ *	Scan through all the boards in the configuration and see what we
+ *	can find. Handle EIO and the ECH boards a little differently here
+ *	since the initial search and setup is very different.
+ */
+
+static int __init stl_brdinit(stlbrd_t *brdp)
+{
+	int	i;
+
+#ifdef DEBUG
+	printk("stl_brdinit(brdp=%x)\n", (int) brdp);
+#endif
+
+	switch (brdp->brdtype) {
+	case BRD_EASYIO:
+	case BRD_EASYIOPCI:
+		stl_initeio(brdp);
+		break;
+	case BRD_ECH:
+	case BRD_ECHMC:
+	case BRD_ECHPCI:
+	case BRD_ECH64PCI:
+		stl_initech(brdp);
+		break;
+	default:
+		printk("STALLION: board=%d is unknown board type=%d\n",
+			brdp->brdnr, brdp->brdtype);
+		return(ENODEV);
+	}
+
+	stl_brds[brdp->brdnr] = brdp;
+	if ((brdp->state & BRD_FOUND) == 0) {
+		printk("STALLION: %s board not found, board=%d io=%x irq=%d\n",
+			stl_brdnames[brdp->brdtype], brdp->brdnr,
+			brdp->ioaddr1, brdp->irq);
+		return(ENODEV);
+	}
+
+	for (i = 0; (i < STL_MAXPANELS); i++)
+		if (brdp->panels[i] != (stlpanel_t *) NULL)
+			stl_initports(brdp, brdp->panels[i]);
+
+	printk("STALLION: %s found, board=%d io=%x irq=%d "
+		"nrpanels=%d nrports=%d\n", stl_brdnames[brdp->brdtype],
+		brdp->brdnr, brdp->ioaddr1, brdp->irq, brdp->nrpanels,
+		brdp->nrports);
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Find the next available board number that is free.
+ */
+
+static inline int stl_getbrdnr(void)
+{
+	int	i;
+
+	for (i = 0; (i < STL_MAXBRDS); i++) {
+		if (stl_brds[i] == (stlbrd_t *) NULL) {
+			if (i >= stl_nrbrds)
+				stl_nrbrds = i + 1;
+			return(i);
+		}
+	}
+	return(-1);
+}
+
+/*****************************************************************************/
+
+#ifdef	CONFIG_PCI
+
+/*
+ *	We have a Stallion board. Allocate a board structure and
+ *	initialize it. Read its IO and IRQ resources from PCI
+ *	configuration space.
+ */
+
+static inline int stl_initpcibrd(int brdtype, struct pci_dev *devp)
+{
+	stlbrd_t	*brdp;
+
+#ifdef DEBUG
+	printk("stl_initpcibrd(brdtype=%d,busnr=%x,devnr=%x)\n", brdtype,
+		devp->bus->number, devp->devfn);
+#endif
+
+	if (pci_enable_device(devp))
+		return(-EIO);
+	if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+		return(-ENOMEM);
+	if ((brdp->brdnr = stl_getbrdnr()) < 0) {
+		printk("STALLION: too many boards found, "
+			"maximum supported %d\n", STL_MAXBRDS);
+		return(0);
+	}
+	brdp->brdtype = brdtype;
+
+/*
+ *	Different Stallion boards use the BAR registers in different ways,
+ *	so set up io addresses based on board type.
+ */
+#ifdef DEBUG
+	printk("%s(%d): BAR[]=%x,%x,%x,%x IRQ=%x\n", __FILE__, __LINE__,
+		pci_resource_start(devp, 0), pci_resource_start(devp, 1),
+		pci_resource_start(devp, 2), pci_resource_start(devp, 3), devp->irq);
+#endif
+
+/*
+ *	We have all resources from the board, so let's setup the actual
+ *	board structure now.
+ */
+	switch (brdtype) {
+	case BRD_ECHPCI:
+		brdp->ioaddr2 = pci_resource_start(devp, 0);
+		brdp->ioaddr1 = pci_resource_start(devp, 1);
+		break;
+	case BRD_ECH64PCI:
+		brdp->ioaddr2 = pci_resource_start(devp, 2);
+		brdp->ioaddr1 = pci_resource_start(devp, 1);
+		break;
+	case BRD_EASYIOPCI:
+		brdp->ioaddr1 = pci_resource_start(devp, 2);
+		brdp->ioaddr2 = pci_resource_start(devp, 1);
+		break;
+	default:
+		printk("STALLION: unknown PCI board type=%d\n", brdtype);
+		break;
+	}
+
+	brdp->irq = devp->irq;
+	stl_brdinit(brdp);
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Find all Stallion PCI boards that might be installed. Initialize each
+ *	one as it is found.
+ */
+
+
+static inline int stl_findpcibrds(void)
+{
+	struct pci_dev	*dev = NULL;
+	int		i, rc;
+
+#ifdef DEBUG
+	printk("stl_findpcibrds()\n");
+#endif
+
+	for (i = 0; (i < stl_nrpcibrds); i++)
+		while ((dev = pci_find_device(stl_pcibrds[i].vendid,
+		    stl_pcibrds[i].devid, dev))) {
+
+/*
+ *			Found a device on the PCI bus that has our vendor and
+ *			device ID. Need to check now that it is really us.
+ */
+			if ((dev->class >> 8) == PCI_CLASS_STORAGE_IDE)
+				continue;
+
+			rc = stl_initpcibrd(stl_pcibrds[i].brdtype, dev);
+			if (rc)
+				return(rc);
+		}
+
+	return(0);
+}
+
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Scan through all the boards in the configuration and see what we
+ *	can find. Handle EIO and the ECH boards a little differently here
+ *	since the initial search and setup is too different.
+ */
+
+static inline int stl_initbrds(void)
+{
+	stlbrd_t	*brdp;
+	stlconf_t	*confp;
+	int		i;
+
+#ifdef DEBUG
+	printk("stl_initbrds()\n");
+#endif
+
+	if (stl_nrbrds > STL_MAXBRDS) {
+		printk("STALLION: too many boards in configuration table, "
+			"truncating to %d\n", STL_MAXBRDS);
+		stl_nrbrds = STL_MAXBRDS;
+	}
+
+/*
+ *	Firstly scan the list of static boards configured. Allocate
+ *	resources and initialize the boards as found.
+ */
+	for (i = 0; (i < stl_nrbrds); i++) {
+		confp = &stl_brdconf[i];
+		stl_parsebrd(confp, stl_brdsp[i]);
+		if ((brdp = stl_allocbrd()) == (stlbrd_t *) NULL)
+			return(-ENOMEM);
+		brdp->brdnr = i;
+		brdp->brdtype = confp->brdtype;
+		brdp->ioaddr1 = confp->ioaddr1;
+		brdp->ioaddr2 = confp->ioaddr2;
+		brdp->irq = confp->irq;
+		brdp->irqtype = confp->irqtype;
+		stl_brdinit(brdp);
+	}
+
+/*
+ *	Find any dynamically supported boards. That is via module load
+ *	line options or auto-detected on the PCI bus.
+ */
+	stl_argbrds();
+#ifdef CONFIG_PCI
+	stl_findpcibrds();
+#endif
+
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the board stats structure to user app.
+ */
+
+static int stl_getbrdstats(combrd_t __user *bp)
+{
+	stlbrd_t	*brdp;
+	stlpanel_t	*panelp;
+	int		i;
+
+	if (copy_from_user(&stl_brdstats, bp, sizeof(combrd_t)))
+		return -EFAULT;
+	if (stl_brdstats.brd >= STL_MAXBRDS)
+		return(-ENODEV);
+	brdp = stl_brds[stl_brdstats.brd];
+	if (brdp == (stlbrd_t *) NULL)
+		return(-ENODEV);
+
+	memset(&stl_brdstats, 0, sizeof(combrd_t));
+	stl_brdstats.brd = brdp->brdnr;
+	stl_brdstats.type = brdp->brdtype;
+	stl_brdstats.hwid = brdp->hwid;
+	stl_brdstats.state = brdp->state;
+	stl_brdstats.ioaddr = brdp->ioaddr1;
+	stl_brdstats.ioaddr2 = brdp->ioaddr2;
+	stl_brdstats.irq = brdp->irq;
+	stl_brdstats.nrpanels = brdp->nrpanels;
+	stl_brdstats.nrports = brdp->nrports;
+	for (i = 0; (i < brdp->nrpanels); i++) {
+		panelp = brdp->panels[i];
+		stl_brdstats.panels[i].panel = i;
+		stl_brdstats.panels[i].hwid = panelp->hwid;
+		stl_brdstats.panels[i].nrports = panelp->nrports;
+	}
+
+	return copy_to_user(bp, &stl_brdstats, sizeof(combrd_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Resolve the referenced port number into a port struct pointer.
+ */
+
+static stlport_t *stl_getport(int brdnr, int panelnr, int portnr)
+{
+	stlbrd_t	*brdp;
+	stlpanel_t	*panelp;
+
+	if ((brdnr < 0) || (brdnr >= STL_MAXBRDS))
+		return((stlport_t *) NULL);
+	brdp = stl_brds[brdnr];
+	if (brdp == (stlbrd_t *) NULL)
+		return((stlport_t *) NULL);
+	if ((panelnr < 0) || (panelnr >= brdp->nrpanels))
+		return((stlport_t *) NULL);
+	panelp = brdp->panels[panelnr];
+	if (panelp == (stlpanel_t *) NULL)
+		return((stlport_t *) NULL);
+	if ((portnr < 0) || (portnr >= panelp->nrports))
+		return((stlport_t *) NULL);
+	return(panelp->ports[portnr]);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the port stats structure to user app. A NULL port struct
+ *	pointer passed in means that we need to find out from the app
+ *	what port to get stats for (used through board control device).
+ */
+
+static int stl_getportstats(stlport_t *portp, comstats_t __user *cp)
+{
+	unsigned char	*head, *tail;
+	unsigned long	flags;
+
+	if (!portp) {
+		if (copy_from_user(&stl_comstats, cp, sizeof(comstats_t)))
+			return -EFAULT;
+		portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
+			stl_comstats.port);
+		if (portp == (stlport_t *) NULL)
+			return(-ENODEV);
+	}
+
+	portp->stats.state = portp->istate;
+	portp->stats.flags = portp->flags;
+	portp->stats.hwid = portp->hwid;
+
+	portp->stats.ttystate = 0;
+	portp->stats.cflags = 0;
+	portp->stats.iflags = 0;
+	portp->stats.oflags = 0;
+	portp->stats.lflags = 0;
+	portp->stats.rxbuffered = 0;
+
+	save_flags(flags);
+	cli();
+	if (portp->tty != (struct tty_struct *) NULL) {
+		if (portp->tty->driver_data == portp) {
+			portp->stats.ttystate = portp->tty->flags;
+			portp->stats.rxbuffered = portp->tty->flip.count;
+			if (portp->tty->termios != (struct termios *) NULL) {
+				portp->stats.cflags = portp->tty->termios->c_cflag;
+				portp->stats.iflags = portp->tty->termios->c_iflag;
+				portp->stats.oflags = portp->tty->termios->c_oflag;
+				portp->stats.lflags = portp->tty->termios->c_lflag;
+			}
+		}
+	}
+	restore_flags(flags);
+
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+	portp->stats.txbuffered = ((head >= tail) ? (head - tail) :
+		(STL_TXBUFSIZE - (tail - head)));
+
+	portp->stats.signals = (unsigned long) stl_getsignals(portp);
+
+	return copy_to_user(cp, &portp->stats,
+			    sizeof(comstats_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Clear the port stats structure. We also return it zeroed out...
+ */
+
+static int stl_clrportstats(stlport_t *portp, comstats_t __user *cp)
+{
+	if (!portp) {
+		if (copy_from_user(&stl_comstats, cp, sizeof(comstats_t)))
+			return -EFAULT;
+		portp = stl_getport(stl_comstats.brd, stl_comstats.panel,
+			stl_comstats.port);
+		if (portp == (stlport_t *) NULL)
+			return(-ENODEV);
+	}
+
+	memset(&portp->stats, 0, sizeof(comstats_t));
+	portp->stats.brd = portp->brdnr;
+	portp->stats.panel = portp->panelnr;
+	portp->stats.port = portp->portnr;
+	return copy_to_user(cp, &portp->stats,
+			    sizeof(comstats_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the entire driver ports structure to a user app.
+ */
+
+static int stl_getportstruct(stlport_t __user *arg)
+{
+	stlport_t	*portp;
+
+	if (copy_from_user(&stl_dummyport, arg, sizeof(stlport_t)))
+		return -EFAULT;
+	portp = stl_getport(stl_dummyport.brdnr, stl_dummyport.panelnr,
+		 stl_dummyport.portnr);
+	if (!portp)
+		return -ENODEV;
+	return copy_to_user(arg, portp, sizeof(stlport_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the entire driver board structure to a user app.
+ */
+
+static int stl_getbrdstruct(stlbrd_t __user *arg)
+{
+	stlbrd_t	*brdp;
+
+	if (copy_from_user(&stl_dummybrd, arg, sizeof(stlbrd_t)))
+		return -EFAULT;
+	if ((stl_dummybrd.brdnr < 0) || (stl_dummybrd.brdnr >= STL_MAXBRDS))
+		return -ENODEV;
+	brdp = stl_brds[stl_dummybrd.brdnr];
+	if (!brdp)
+		return(-ENODEV);
+	return copy_to_user(arg, brdp, sizeof(stlbrd_t)) ? -EFAULT : 0;
+}
+
+/*****************************************************************************/
+
+/*
+ *	The "staliomem" device is also required to do some special operations
+ *	on the board and/or ports. In this driver it is mostly used for stats
+ *	collection.
+ */
+
+static int stl_memioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	int	brdnr, rc;
+	void __user *argp = (void __user *)arg;
+
+#ifdef DEBUG
+	printk("stl_memioctl(ip=%x,fp=%x,cmd=%x,arg=%x)\n", (int) ip,
+		(int) fp, cmd, (int) arg);
+#endif
+
+	brdnr = iminor(ip);
+	if (brdnr >= STL_MAXBRDS)
+		return(-ENODEV);
+	rc = 0;
+
+	switch (cmd) {
+	case COM_GETPORTSTATS:
+		rc = stl_getportstats(NULL, argp);
+		break;
+	case COM_CLRPORTSTATS:
+		rc = stl_clrportstats(NULL, argp);
+		break;
+	case COM_GETBRDSTATS:
+		rc = stl_getbrdstats(argp);
+		break;
+	case COM_READPORT:
+		rc = stl_getportstruct(argp);
+		break;
+	case COM_READBOARD:
+		rc = stl_getbrdstruct(argp);
+		break;
+	default:
+		rc = -ENOIOCTLCMD;
+		break;
+	}
+
+	return(rc);
+}
+
+static struct tty_operations stl_ops = {
+	.open = stl_open,
+	.close = stl_close,
+	.write = stl_write,
+	.put_char = stl_putchar,
+	.flush_chars = stl_flushchars,
+	.write_room = stl_writeroom,
+	.chars_in_buffer = stl_charsinbuffer,
+	.ioctl = stl_ioctl,
+	.set_termios = stl_settermios,
+	.throttle = stl_throttle,
+	.unthrottle = stl_unthrottle,
+	.stop = stl_stop,
+	.start = stl_start,
+	.hangup = stl_hangup,
+	.flush_buffer = stl_flushbuffer,
+	.break_ctl = stl_breakctl,
+	.wait_until_sent = stl_waituntilsent,
+	.send_xchar = stl_sendxchar,
+	.read_proc = stl_readproc,
+	.tiocmget = stl_tiocmget,
+	.tiocmset = stl_tiocmset,
+};
+
+/*****************************************************************************/
+
+int __init stl_init(void)
+{
+	int i;
+	printk(KERN_INFO "%s: version %s\n", stl_drvtitle, stl_drvversion);
+
+	stl_initbrds();
+
+	stl_serial = alloc_tty_driver(STL_MAXBRDS * STL_MAXPORTS);
+	if (!stl_serial)
+		return -1;
+
+/*
+ *	Allocate a temporary write buffer.
+ */
+	stl_tmpwritebuf = (char *) stl_memalloc(STL_TXBUFSIZE);
+	if (stl_tmpwritebuf == (char *) NULL)
+		printk("STALLION: failed to allocate memory (size=%d)\n",
+			STL_TXBUFSIZE);
+
+/*
+ *	Set up a character driver for per board stuff. This is mainly used
+ *	to do stats ioctls on the ports.
+ */
+	if (register_chrdev(STL_SIOMEMMAJOR, "staliomem", &stl_fsiomem))
+		printk("STALLION: failed to register serial board device\n");
+	devfs_mk_dir("staliomem");
+
+	stallion_class = class_simple_create(THIS_MODULE, "staliomem");
+	for (i = 0; i < 4; i++) {
+		devfs_mk_cdev(MKDEV(STL_SIOMEMMAJOR, i),
+				S_IFCHR|S_IRUSR|S_IWUSR,
+				"staliomem/%d", i);
+		class_simple_device_add(stallion_class, MKDEV(STL_SIOMEMMAJOR, i), NULL, "staliomem%d", i);
+	}
+
+	stl_serial->owner = THIS_MODULE;
+	stl_serial->driver_name = stl_drvname;
+	stl_serial->name = "ttyE";
+	stl_serial->devfs_name = "tts/E";
+	stl_serial->major = STL_SERIALMAJOR;
+	stl_serial->minor_start = 0;
+	stl_serial->type = TTY_DRIVER_TYPE_SERIAL;
+	stl_serial->subtype = SERIAL_TYPE_NORMAL;
+	stl_serial->init_termios = stl_deftermios;
+	stl_serial->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(stl_serial, &stl_ops);
+
+	if (tty_register_driver(stl_serial)) {
+		put_tty_driver(stl_serial);
+		printk("STALLION: failed to register serial driver\n");
+		return -1;
+	}
+
+	return(0);
+}
+
+/*****************************************************************************/
+/*                       CD1400 HARDWARE FUNCTIONS                           */
+/*****************************************************************************/
+
+/*
+ *	These functions get/set/update the registers of the cd1400 UARTs.
+ *	Access to the cd1400 registers is via an address/data io port pair.
+ *	(Maybe should make this inline...)
+ */
+
+static int stl_cd1400getreg(stlport_t *portp, int regnr)
+{
+	outb((regnr + portp->uartaddr), portp->ioaddr);
+	return(inb(portp->ioaddr + EREG_DATA));
+}
+
+static void stl_cd1400setreg(stlport_t *portp, int regnr, int value)
+{
+	outb((regnr + portp->uartaddr), portp->ioaddr);
+	outb(value, portp->ioaddr + EREG_DATA);
+}
+
+static int stl_cd1400updatereg(stlport_t *portp, int regnr, int value)
+{
+	outb((regnr + portp->uartaddr), portp->ioaddr);
+	if (inb(portp->ioaddr + EREG_DATA) != value) {
+		outb(value, portp->ioaddr + EREG_DATA);
+		return(1);
+	}
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Inbitialize the UARTs in a panel. We don't care what sort of board
+ *	these ports are on - since the port io registers are almost
+ *	identical when dealing with ports.
+ */
+
+static int stl_cd1400panelinit(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+	unsigned int	gfrcr;
+	int		chipmask, i, j;
+	int		nrchips, uartaddr, ioaddr;
+
+#ifdef DEBUG
+	printk("stl_panelinit(brdp=%x,panelp=%x)\n", (int) brdp, (int) panelp);
+#endif
+
+	BRDENABLE(panelp->brdnr, panelp->pagenr);
+
+/*
+ *	Check that each chip is present and started up OK.
+ */
+	chipmask = 0;
+	nrchips = panelp->nrports / CD1400_PORTS;
+	for (i = 0; (i < nrchips); i++) {
+		if (brdp->brdtype == BRD_ECHPCI) {
+			outb((panelp->pagenr + (i >> 1)), brdp->ioctrl);
+			ioaddr = panelp->iobase;
+		} else {
+			ioaddr = panelp->iobase + (EREG_BANKSIZE * (i >> 1));
+		}
+		uartaddr = (i & 0x01) ? 0x080 : 0;
+		outb((GFRCR + uartaddr), ioaddr);
+		outb(0, (ioaddr + EREG_DATA));
+		outb((CCR + uartaddr), ioaddr);
+		outb(CCR_RESETFULL, (ioaddr + EREG_DATA));
+		outb(CCR_RESETFULL, (ioaddr + EREG_DATA));
+		outb((GFRCR + uartaddr), ioaddr);
+		for (j = 0; (j < CCR_MAXWAIT); j++) {
+			if ((gfrcr = inb(ioaddr + EREG_DATA)) != 0)
+				break;
+		}
+		if ((j >= CCR_MAXWAIT) || (gfrcr < 0x40) || (gfrcr > 0x60)) {
+			printk("STALLION: cd1400 not responding, "
+				"brd=%d panel=%d chip=%d\n",
+				panelp->brdnr, panelp->panelnr, i);
+			continue;
+		}
+		chipmask |= (0x1 << i);
+		outb((PPR + uartaddr), ioaddr);
+		outb(PPR_SCALAR, (ioaddr + EREG_DATA));
+	}
+
+	BRDDISABLE(panelp->brdnr);
+	return(chipmask);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Initialize hardware specific port registers.
+ */
+
+static void stl_cd1400portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp)
+{
+#ifdef DEBUG
+	printk("stl_cd1400portinit(brdp=%x,panelp=%x,portp=%x)\n",
+		(int) brdp, (int) panelp, (int) portp);
+#endif
+
+	if ((brdp == (stlbrd_t *) NULL) || (panelp == (stlpanel_t *) NULL) ||
+	    (portp == (stlport_t *) NULL))
+		return;
+
+	portp->ioaddr = panelp->iobase + (((brdp->brdtype == BRD_ECHPCI) ||
+		(portp->portnr < 8)) ? 0 : EREG_BANKSIZE);
+	portp->uartaddr = (portp->portnr & 0x04) << 5;
+	portp->pagenr = panelp->pagenr + (portp->portnr >> 3);
+
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	stl_cd1400setreg(portp, LIVR, (portp->portnr << 3));
+	portp->hwid = stl_cd1400getreg(portp, GFRCR);
+	BRDDISABLE(portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Wait for the command register to be ready. We will poll this,
+ *	since it won't usually take too long to be ready.
+ */
+
+static void stl_cd1400ccrwait(stlport_t *portp)
+{
+	int	i;
+
+	for (i = 0; (i < CCR_MAXWAIT); i++) {
+		if (stl_cd1400getreg(portp, CCR) == 0) {
+			return;
+		}
+	}
+
+	printk("STALLION: cd1400 not responding, port=%d panel=%d brd=%d\n",
+		portp->portnr, portp->panelnr, portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Set up the cd1400 registers for a port based on the termios port
+ *	settings.
+ */
+
+static void stl_cd1400setport(stlport_t *portp, struct termios *tiosp)
+{
+	stlbrd_t	*brdp;
+	unsigned long	flags;
+	unsigned int	clkdiv, baudrate;
+	unsigned char	cor1, cor2, cor3;
+	unsigned char	cor4, cor5, ccr;
+	unsigned char	srer, sreron, sreroff;
+	unsigned char	mcor1, mcor2, rtpr;
+	unsigned char	clk, div;
+
+	cor1 = 0;
+	cor2 = 0;
+	cor3 = 0;
+	cor4 = 0;
+	cor5 = 0;
+	ccr = 0;
+	rtpr = 0;
+	clk = 0;
+	div = 0;
+	mcor1 = 0;
+	mcor2 = 0;
+	sreron = 0;
+	sreroff = 0;
+
+	brdp = stl_brds[portp->brdnr];
+	if (brdp == (stlbrd_t *) NULL)
+		return;
+
+/*
+ *	Set up the RX char ignore mask with those RX error types we
+ *	can ignore. We can get the cd1400 to help us out a little here,
+ *	it will ignore parity errors and breaks for us.
+ */
+	portp->rxignoremsk = 0;
+	if (tiosp->c_iflag & IGNPAR) {
+		portp->rxignoremsk |= (ST_PARITY | ST_FRAMING | ST_OVERRUN);
+		cor1 |= COR1_PARIGNORE;
+	}
+	if (tiosp->c_iflag & IGNBRK) {
+		portp->rxignoremsk |= ST_BREAK;
+		cor4 |= COR4_IGNBRK;
+	}
+
+	portp->rxmarkmsk = ST_OVERRUN;
+	if (tiosp->c_iflag & (INPCK | PARMRK))
+		portp->rxmarkmsk |= (ST_PARITY | ST_FRAMING);
+	if (tiosp->c_iflag & BRKINT)
+		portp->rxmarkmsk |= ST_BREAK;
+
+/*
+ *	Go through the char size, parity and stop bits and set all the
+ *	option register appropriately.
+ */
+	switch (tiosp->c_cflag & CSIZE) {
+	case CS5:
+		cor1 |= COR1_CHL5;
+		break;
+	case CS6:
+		cor1 |= COR1_CHL6;
+		break;
+	case CS7:
+		cor1 |= COR1_CHL7;
+		break;
+	default:
+		cor1 |= COR1_CHL8;
+		break;
+	}
+
+	if (tiosp->c_cflag & CSTOPB)
+		cor1 |= COR1_STOP2;
+	else
+		cor1 |= COR1_STOP1;
+
+	if (tiosp->c_cflag & PARENB) {
+		if (tiosp->c_cflag & PARODD)
+			cor1 |= (COR1_PARENB | COR1_PARODD);
+		else
+			cor1 |= (COR1_PARENB | COR1_PAREVEN);
+	} else {
+		cor1 |= COR1_PARNONE;
+	}
+
+/*
+ *	Set the RX FIFO threshold at 6 chars. This gives a bit of breathing
+ *	space for hardware flow control and the like. This should be set to
+ *	VMIN. Also here we will set the RX data timeout to 10ms - this should
+ *	really be based on VTIME.
+ */
+	cor3 |= FIFO_RXTHRESHOLD;
+	rtpr = 2;
+
+/*
+ *	Calculate the baud rate timers. For now we will just assume that
+ *	the input and output baud are the same. Could have used a baud
+ *	table here, but this way we can generate virtually any baud rate
+ *	we like!
+ */
+	baudrate = tiosp->c_cflag & CBAUD;
+	if (baudrate & CBAUDEX) {
+		baudrate &= ~CBAUDEX;
+		if ((baudrate < 1) || (baudrate > 4))
+			tiosp->c_cflag &= ~CBAUDEX;
+		else
+			baudrate += 15;
+	}
+	baudrate = stl_baudrates[baudrate];
+	if ((tiosp->c_cflag & CBAUD) == B38400) {
+		if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			baudrate = 57600;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			baudrate = 115200;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+			baudrate = 230400;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+			baudrate = 460800;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+			baudrate = (portp->baud_base / portp->custom_divisor);
+	}
+	if (baudrate > STL_CD1400MAXBAUD)
+		baudrate = STL_CD1400MAXBAUD;
+
+	if (baudrate > 0) {
+		for (clk = 0; (clk < CD1400_NUMCLKS); clk++) {
+			clkdiv = ((portp->clk / stl_cd1400clkdivs[clk]) / baudrate);
+			if (clkdiv < 0x100)
+				break;
+		}
+		div = (unsigned char) clkdiv;
+	}
+
+/*
+ *	Check what form of modem signaling is required and set it up.
+ */
+	if ((tiosp->c_cflag & CLOCAL) == 0) {
+		mcor1 |= MCOR1_DCD;
+		mcor2 |= MCOR2_DCD;
+		sreron |= SRER_MODEM;
+		portp->flags |= ASYNC_CHECK_CD;
+	} else {
+		portp->flags &= ~ASYNC_CHECK_CD;
+	}
+
+/*
+ *	Setup cd1400 enhanced modes if we can. In particular we want to
+ *	handle as much of the flow control as possible automatically. As
+ *	well as saving a few CPU cycles it will also greatly improve flow
+ *	control reliability.
+ */
+	if (tiosp->c_iflag & IXON) {
+		cor2 |= COR2_TXIBE;
+		cor3 |= COR3_SCD12;
+		if (tiosp->c_iflag & IXANY)
+			cor2 |= COR2_IXM;
+	}
+
+	if (tiosp->c_cflag & CRTSCTS) {
+		cor2 |= COR2_CTSAE;
+		mcor1 |= FIFO_RTSTHRESHOLD;
+	}
+
+/*
+ *	All cd1400 register values calculated so go through and set
+ *	them all up.
+ */
+
+#ifdef DEBUG
+	printk("SETPORT: portnr=%d panelnr=%d brdnr=%d\n",
+		portp->portnr, portp->panelnr, portp->brdnr);
+	printk("    cor1=%x cor2=%x cor3=%x cor4=%x cor5=%x\n",
+		cor1, cor2, cor3, cor4, cor5);
+	printk("    mcor1=%x mcor2=%x rtpr=%x sreron=%x sreroff=%x\n",
+		mcor1, mcor2, rtpr, sreron, sreroff);
+	printk("    tcor=%x tbpr=%x rcor=%x rbpr=%x\n", clk, div, clk, div);
+	printk("    schr1=%x schr2=%x schr3=%x schr4=%x\n",
+		tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP],
+		tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]);
+#endif
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x3));
+	srer = stl_cd1400getreg(portp, SRER);
+	stl_cd1400setreg(portp, SRER, 0);
+	if (stl_cd1400updatereg(portp, COR1, cor1))
+		ccr = 1;
+	if (stl_cd1400updatereg(portp, COR2, cor2))
+		ccr = 1;
+	if (stl_cd1400updatereg(portp, COR3, cor3))
+		ccr = 1;
+	if (ccr) {
+		stl_cd1400ccrwait(portp);
+		stl_cd1400setreg(portp, CCR, CCR_CORCHANGE);
+	}
+	stl_cd1400setreg(portp, COR4, cor4);
+	stl_cd1400setreg(portp, COR5, cor5);
+	stl_cd1400setreg(portp, MCOR1, mcor1);
+	stl_cd1400setreg(portp, MCOR2, mcor2);
+	if (baudrate > 0) {
+		stl_cd1400setreg(portp, TCOR, clk);
+		stl_cd1400setreg(portp, TBPR, div);
+		stl_cd1400setreg(portp, RCOR, clk);
+		stl_cd1400setreg(portp, RBPR, div);
+	}
+	stl_cd1400setreg(portp, SCHR1, tiosp->c_cc[VSTART]);
+	stl_cd1400setreg(portp, SCHR2, tiosp->c_cc[VSTOP]);
+	stl_cd1400setreg(portp, SCHR3, tiosp->c_cc[VSTART]);
+	stl_cd1400setreg(portp, SCHR4, tiosp->c_cc[VSTOP]);
+	stl_cd1400setreg(portp, RTPR, rtpr);
+	mcor1 = stl_cd1400getreg(portp, MSVR1);
+	if (mcor1 & MSVR1_DCD)
+		portp->sigs |= TIOCM_CD;
+	else
+		portp->sigs &= ~TIOCM_CD;
+	stl_cd1400setreg(portp, SRER, ((srer & ~sreroff) | sreron));
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Set the state of the DTR and RTS signals.
+ */
+
+static void stl_cd1400setsignals(stlport_t *portp, int dtr, int rts)
+{
+	unsigned char	msvr1, msvr2;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_cd1400setsignals(portp=%x,dtr=%d,rts=%d)\n",
+		(int) portp, dtr, rts);
+#endif
+
+	msvr1 = 0;
+	msvr2 = 0;
+	if (dtr > 0)
+		msvr1 = MSVR1_DTR;
+	if (rts > 0)
+		msvr2 = MSVR2_RTS;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	if (rts >= 0)
+		stl_cd1400setreg(portp, MSVR2, msvr2);
+	if (dtr >= 0)
+		stl_cd1400setreg(portp, MSVR1, msvr1);
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the state of the signals.
+ */
+
+static int stl_cd1400getsignals(stlport_t *portp)
+{
+	unsigned char	msvr1, msvr2;
+	unsigned long	flags;
+	int		sigs;
+
+#ifdef DEBUG
+	printk("stl_cd1400getsignals(portp=%x)\n", (int) portp);
+#endif
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	msvr1 = stl_cd1400getreg(portp, MSVR1);
+	msvr2 = stl_cd1400getreg(portp, MSVR2);
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+
+	sigs = 0;
+	sigs |= (msvr1 & MSVR1_DCD) ? TIOCM_CD : 0;
+	sigs |= (msvr1 & MSVR1_CTS) ? TIOCM_CTS : 0;
+	sigs |= (msvr1 & MSVR1_DTR) ? TIOCM_DTR : 0;
+	sigs |= (msvr2 & MSVR2_RTS) ? TIOCM_RTS : 0;
+#if 0
+	sigs |= (msvr1 & MSVR1_RI) ? TIOCM_RI : 0;
+	sigs |= (msvr1 & MSVR1_DSR) ? TIOCM_DSR : 0;
+#else
+	sigs |= TIOCM_DSR;
+#endif
+	return(sigs);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Enable/Disable the Transmitter and/or Receiver.
+ */
+
+static void stl_cd1400enablerxtx(stlport_t *portp, int rx, int tx)
+{
+	unsigned char	ccr;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_cd1400enablerxtx(portp=%x,rx=%d,tx=%d)\n",
+		(int) portp, rx, tx);
+#endif
+	ccr = 0;
+
+	if (tx == 0)
+		ccr |= CCR_TXDISABLE;
+	else if (tx > 0)
+		ccr |= CCR_TXENABLE;
+	if (rx == 0)
+		ccr |= CCR_RXDISABLE;
+	else if (rx > 0)
+		ccr |= CCR_RXENABLE;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	stl_cd1400ccrwait(portp);
+	stl_cd1400setreg(portp, CCR, ccr);
+	stl_cd1400ccrwait(portp);
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Start/stop the Transmitter and/or Receiver.
+ */
+
+static void stl_cd1400startrxtx(stlport_t *portp, int rx, int tx)
+{
+	unsigned char	sreron, sreroff;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_cd1400startrxtx(portp=%x,rx=%d,tx=%d)\n",
+		(int) portp, rx, tx);
+#endif
+
+	sreron = 0;
+	sreroff = 0;
+	if (tx == 0)
+		sreroff |= (SRER_TXDATA | SRER_TXEMPTY);
+	else if (tx == 1)
+		sreron |= SRER_TXDATA;
+	else if (tx >= 2)
+		sreron |= SRER_TXEMPTY;
+	if (rx == 0)
+		sreroff |= SRER_RXDATA;
+	else if (rx > 0)
+		sreron |= SRER_RXDATA;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	stl_cd1400setreg(portp, SRER,
+		((stl_cd1400getreg(portp, SRER) & ~sreroff) | sreron));
+	BRDDISABLE(portp->brdnr);
+	if (tx > 0)
+		set_bit(ASYI_TXBUSY, &portp->istate);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Disable all interrupts from this port.
+ */
+
+static void stl_cd1400disableintrs(stlport_t *portp)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_cd1400disableintrs(portp=%x)\n", (int) portp);
+#endif
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	stl_cd1400setreg(portp, SRER, 0);
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_cd1400sendbreak(stlport_t *portp, int len)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_cd1400sendbreak(portp=%x,len=%d)\n", (int) portp, len);
+#endif
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	stl_cd1400setreg(portp, SRER,
+		((stl_cd1400getreg(portp, SRER) & ~SRER_TXDATA) |
+		SRER_TXEMPTY));
+	BRDDISABLE(portp->brdnr);
+	portp->brklen = len;
+	if (len == 1)
+		portp->stats.txbreaks++;
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Take flow control actions...
+ */
+
+static void stl_cd1400flowctrl(stlport_t *portp, int state)
+{
+	struct tty_struct	*tty;
+	unsigned long		flags;
+
+#ifdef DEBUG
+	printk("stl_cd1400flowctrl(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+	tty = portp->tty;
+	if (tty == (struct tty_struct *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+
+	if (state) {
+		if (tty->termios->c_iflag & IXOFF) {
+			stl_cd1400ccrwait(portp);
+			stl_cd1400setreg(portp, CCR, CCR_SENDSCHR1);
+			portp->stats.rxxon++;
+			stl_cd1400ccrwait(portp);
+		}
+/*
+ *		Question: should we return RTS to what it was before? It may
+ *		have been set by an ioctl... Suppose not, since if you have
+ *		hardware flow control set then it is pretty silly to go and
+ *		set the RTS line by hand.
+ */
+		if (tty->termios->c_cflag & CRTSCTS) {
+			stl_cd1400setreg(portp, MCOR1,
+				(stl_cd1400getreg(portp, MCOR1) |
+				FIFO_RTSTHRESHOLD));
+			stl_cd1400setreg(portp, MSVR2, MSVR2_RTS);
+			portp->stats.rxrtson++;
+		}
+	} else {
+		if (tty->termios->c_iflag & IXOFF) {
+			stl_cd1400ccrwait(portp);
+			stl_cd1400setreg(portp, CCR, CCR_SENDSCHR2);
+			portp->stats.rxxoff++;
+			stl_cd1400ccrwait(portp);
+		}
+		if (tty->termios->c_cflag & CRTSCTS) {
+			stl_cd1400setreg(portp, MCOR1,
+				(stl_cd1400getreg(portp, MCOR1) & 0xf0));
+			stl_cd1400setreg(portp, MSVR2, 0);
+			portp->stats.rxrtsoff++;
+		}
+	}
+
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Send a flow control character...
+ */
+
+static void stl_cd1400sendflow(stlport_t *portp, int state)
+{
+	struct tty_struct	*tty;
+	unsigned long		flags;
+
+#ifdef DEBUG
+	printk("stl_cd1400sendflow(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+	tty = portp->tty;
+	if (tty == (struct tty_struct *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	if (state) {
+		stl_cd1400ccrwait(portp);
+		stl_cd1400setreg(portp, CCR, CCR_SENDSCHR1);
+		portp->stats.rxxon++;
+		stl_cd1400ccrwait(portp);
+	} else {
+		stl_cd1400ccrwait(portp);
+		stl_cd1400setreg(portp, CCR, CCR_SENDSCHR2);
+		portp->stats.rxxoff++;
+		stl_cd1400ccrwait(portp);
+	}
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_cd1400flush(stlport_t *portp)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_cd1400flush(portp=%x)\n", (int) portp);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_cd1400setreg(portp, CAR, (portp->portnr & 0x03));
+	stl_cd1400ccrwait(portp);
+	stl_cd1400setreg(portp, CCR, CCR_TXFLUSHFIFO);
+	stl_cd1400ccrwait(portp);
+	portp->tx.tail = portp->tx.head;
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the current state of data flow on this port. This is only
+ *	really interresting when determining if data has fully completed
+ *	transmission or not... This is easy for the cd1400, it accurately
+ *	maintains the busy port flag.
+ */
+
+static int stl_cd1400datastate(stlport_t *portp)
+{
+#ifdef DEBUG
+	printk("stl_cd1400datastate(portp=%x)\n", (int) portp);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return(0);
+
+	return(test_bit(ASYI_TXBUSY, &portp->istate) ? 1 : 0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt service routine for cd1400 EasyIO boards.
+ */
+
+static void stl_cd1400eiointr(stlpanel_t *panelp, unsigned int iobase)
+{
+	unsigned char	svrtype;
+
+#ifdef DEBUG
+	printk("stl_cd1400eiointr(panelp=%x,iobase=%x)\n",
+		(int) panelp, iobase);
+#endif
+
+	outb(SVRR, iobase);
+	svrtype = inb(iobase + EREG_DATA);
+	if (panelp->nrports > 4) {
+		outb((SVRR + 0x80), iobase);
+		svrtype |= inb(iobase + EREG_DATA);
+	}
+
+	if (svrtype & SVRR_RX)
+		stl_cd1400rxisr(panelp, iobase);
+	else if (svrtype & SVRR_TX)
+		stl_cd1400txisr(panelp, iobase);
+	else if (svrtype & SVRR_MDM)
+		stl_cd1400mdmisr(panelp, iobase);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt service routine for cd1400 panels.
+ */
+
+static void stl_cd1400echintr(stlpanel_t *panelp, unsigned int iobase)
+{
+	unsigned char	svrtype;
+
+#ifdef DEBUG
+	printk("stl_cd1400echintr(panelp=%x,iobase=%x)\n", (int) panelp,
+		iobase);
+#endif
+
+	outb(SVRR, iobase);
+	svrtype = inb(iobase + EREG_DATA);
+	outb((SVRR + 0x80), iobase);
+	svrtype |= inb(iobase + EREG_DATA);
+	if (svrtype & SVRR_RX)
+		stl_cd1400rxisr(panelp, iobase);
+	else if (svrtype & SVRR_TX)
+		stl_cd1400txisr(panelp, iobase);
+	else if (svrtype & SVRR_MDM)
+		stl_cd1400mdmisr(panelp, iobase);
+}
+
+
+/*****************************************************************************/
+
+/*
+ *	Unfortunately we need to handle breaks in the TX data stream, since
+ *	this is the only way to generate them on the cd1400.
+ */
+
+static inline int stl_cd1400breakisr(stlport_t *portp, int ioaddr)
+{
+	if (portp->brklen == 1) {
+		outb((COR2 + portp->uartaddr), ioaddr);
+		outb((inb(ioaddr + EREG_DATA) | COR2_ETC),
+			(ioaddr + EREG_DATA));
+		outb((TDR + portp->uartaddr), ioaddr);
+		outb(ETC_CMD, (ioaddr + EREG_DATA));
+		outb(ETC_STARTBREAK, (ioaddr + EREG_DATA));
+		outb((SRER + portp->uartaddr), ioaddr);
+		outb((inb(ioaddr + EREG_DATA) & ~(SRER_TXDATA | SRER_TXEMPTY)),
+			(ioaddr + EREG_DATA));
+		return(1);
+	} else if (portp->brklen > 1) {
+		outb((TDR + portp->uartaddr), ioaddr);
+		outb(ETC_CMD, (ioaddr + EREG_DATA));
+		outb(ETC_STOPBREAK, (ioaddr + EREG_DATA));
+		portp->brklen = -1;
+		return(1);
+	} else {
+		outb((COR2 + portp->uartaddr), ioaddr);
+		outb((inb(ioaddr + EREG_DATA) & ~COR2_ETC),
+			(ioaddr + EREG_DATA));
+		portp->brklen = 0;
+	}
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Transmit interrupt handler. This has gotta be fast!  Handling TX
+ *	chars is pretty simple, stuff as many as possible from the TX buffer
+ *	into the cd1400 FIFO. Must also handle TX breaks here, since they
+ *	are embedded as commands in the data stream. Oh no, had to use a goto!
+ *	This could be optimized more, will do when I get time...
+ *	In practice it is possible that interrupts are enabled but that the
+ *	port has been hung up. Need to handle not having any TX buffer here,
+ *	this is done by using the side effect that head and tail will also
+ *	be NULL if the buffer has been freed.
+ */
+
+static void stl_cd1400txisr(stlpanel_t *panelp, int ioaddr)
+{
+	stlport_t	*portp;
+	int		len, stlen;
+	char		*head, *tail;
+	unsigned char	ioack, srer;
+
+#ifdef DEBUG
+	printk("stl_cd1400txisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+	ioack = inb(ioaddr + EREG_TXACK);
+	if (((ioack & panelp->ackmask) != 0) ||
+	    ((ioack & ACK_TYPMASK) != ACK_TYPTX)) {
+		printk("STALLION: bad TX interrupt ack value=%x\n", ioack);
+		return;
+	}
+	portp = panelp->ports[(ioack >> 3)];
+
+/*
+ *	Unfortunately we need to handle breaks in the data stream, since
+ *	this is the only way to generate them on the cd1400. Do it now if
+ *	a break is to be sent.
+ */
+	if (portp->brklen != 0)
+		if (stl_cd1400breakisr(portp, ioaddr))
+			goto stl_txalldone;
+
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+	len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+	if ((len == 0) || ((len < STL_TXBUFLOW) &&
+	    (test_bit(ASYI_TXLOW, &portp->istate) == 0))) {
+		set_bit(ASYI_TXLOW, &portp->istate);
+		schedule_work(&portp->tqueue);
+	}
+
+	if (len == 0) {
+		outb((SRER + portp->uartaddr), ioaddr);
+		srer = inb(ioaddr + EREG_DATA);
+		if (srer & SRER_TXDATA) {
+			srer = (srer & ~SRER_TXDATA) | SRER_TXEMPTY;
+		} else {
+			srer &= ~(SRER_TXDATA | SRER_TXEMPTY);
+			clear_bit(ASYI_TXBUSY, &portp->istate);
+		}
+		outb(srer, (ioaddr + EREG_DATA));
+	} else {
+		len = MIN(len, CD1400_TXFIFOSIZE);
+		portp->stats.txtotal += len;
+		stlen = MIN(len, ((portp->tx.buf + STL_TXBUFSIZE) - tail));
+		outb((TDR + portp->uartaddr), ioaddr);
+		outsb((ioaddr + EREG_DATA), tail, stlen);
+		len -= stlen;
+		tail += stlen;
+		if (tail >= (portp->tx.buf + STL_TXBUFSIZE))
+			tail = portp->tx.buf;
+		if (len > 0) {
+			outsb((ioaddr + EREG_DATA), tail, len);
+			tail += len;
+		}
+		portp->tx.tail = tail;
+	}
+
+stl_txalldone:
+	outb((EOSRR + portp->uartaddr), ioaddr);
+	outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+
+/*
+ *	Receive character interrupt handler. Determine if we have good chars
+ *	or bad chars and then process appropriately. Good chars are easy
+ *	just shove the lot into the RX buffer and set all status byte to 0.
+ *	If a bad RX char then process as required. This routine needs to be
+ *	fast!  In practice it is possible that we get an interrupt on a port
+ *	that is closed. This can happen on hangups - since they completely
+ *	shutdown a port not in user context. Need to handle this case.
+ */
+
+static void stl_cd1400rxisr(stlpanel_t *panelp, int ioaddr)
+{
+	stlport_t		*portp;
+	struct tty_struct	*tty;
+	unsigned int		ioack, len, buflen;
+	unsigned char		status;
+	char			ch;
+
+#ifdef DEBUG
+	printk("stl_cd1400rxisr(panelp=%x,ioaddr=%x)\n", (int) panelp, ioaddr);
+#endif
+
+	ioack = inb(ioaddr + EREG_RXACK);
+	if ((ioack & panelp->ackmask) != 0) {
+		printk("STALLION: bad RX interrupt ack value=%x\n", ioack);
+		return;
+	}
+	portp = panelp->ports[(ioack >> 3)];
+	tty = portp->tty;
+
+	if ((ioack & ACK_TYPMASK) == ACK_TYPRXGOOD) {
+		outb((RDCR + portp->uartaddr), ioaddr);
+		len = inb(ioaddr + EREG_DATA);
+		if ((tty == (struct tty_struct *) NULL) ||
+		    (tty->flip.char_buf_ptr == (char *) NULL) ||
+		    ((buflen = TTY_FLIPBUF_SIZE - tty->flip.count) == 0)) {
+			len = MIN(len, sizeof(stl_unwanted));
+			outb((RDSR + portp->uartaddr), ioaddr);
+			insb((ioaddr + EREG_DATA), &stl_unwanted[0], len);
+			portp->stats.rxlost += len;
+			portp->stats.rxtotal += len;
+		} else {
+			len = MIN(len, buflen);
+			if (len > 0) {
+				outb((RDSR + portp->uartaddr), ioaddr);
+				insb((ioaddr + EREG_DATA), tty->flip.char_buf_ptr, len);
+				memset(tty->flip.flag_buf_ptr, 0, len);
+				tty->flip.flag_buf_ptr += len;
+				tty->flip.char_buf_ptr += len;
+				tty->flip.count += len;
+				tty_schedule_flip(tty);
+				portp->stats.rxtotal += len;
+			}
+		}
+	} else if ((ioack & ACK_TYPMASK) == ACK_TYPRXBAD) {
+		outb((RDSR + portp->uartaddr), ioaddr);
+		status = inb(ioaddr + EREG_DATA);
+		ch = inb(ioaddr + EREG_DATA);
+		if (status & ST_PARITY)
+			portp->stats.rxparity++;
+		if (status & ST_FRAMING)
+			portp->stats.rxframing++;
+		if (status & ST_OVERRUN)
+			portp->stats.rxoverrun++;
+		if (status & ST_BREAK)
+			portp->stats.rxbreaks++;
+		if (status & ST_SCHARMASK) {
+			if ((status & ST_SCHARMASK) == ST_SCHAR1)
+				portp->stats.txxon++;
+			if ((status & ST_SCHARMASK) == ST_SCHAR2)
+				portp->stats.txxoff++;
+			goto stl_rxalldone;
+		}
+		if ((tty != (struct tty_struct *) NULL) &&
+		    ((portp->rxignoremsk & status) == 0)) {
+			if (portp->rxmarkmsk & status) {
+				if (status & ST_BREAK) {
+					status = TTY_BREAK;
+					if (portp->flags & ASYNC_SAK) {
+						do_SAK(tty);
+						BRDENABLE(portp->brdnr, portp->pagenr);
+					}
+				} else if (status & ST_PARITY) {
+					status = TTY_PARITY;
+				} else if (status & ST_FRAMING) {
+					status = TTY_FRAME;
+				} else if(status & ST_OVERRUN) {
+					status = TTY_OVERRUN;
+				} else {
+					status = 0;
+				}
+			} else {
+				status = 0;
+			}
+			if (tty->flip.char_buf_ptr != (char *) NULL) {
+				if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+					*tty->flip.flag_buf_ptr++ = status;
+					*tty->flip.char_buf_ptr++ = ch;
+					tty->flip.count++;
+				}
+				tty_schedule_flip(tty);
+			}
+		}
+	} else {
+		printk("STALLION: bad RX interrupt ack value=%x\n", ioack);
+		return;
+	}
+
+stl_rxalldone:
+	outb((EOSRR + portp->uartaddr), ioaddr);
+	outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+
+/*
+ *	Modem interrupt handler. The is called when the modem signal line
+ *	(DCD) has changed state. Leave most of the work to the off-level
+ *	processing routine.
+ */
+
+static void stl_cd1400mdmisr(stlpanel_t *panelp, int ioaddr)
+{
+	stlport_t	*portp;
+	unsigned int	ioack;
+	unsigned char	misr;
+
+#ifdef DEBUG
+	printk("stl_cd1400mdmisr(panelp=%x)\n", (int) panelp);
+#endif
+
+	ioack = inb(ioaddr + EREG_MDACK);
+	if (((ioack & panelp->ackmask) != 0) ||
+	    ((ioack & ACK_TYPMASK) != ACK_TYPMDM)) {
+		printk("STALLION: bad MODEM interrupt ack value=%x\n", ioack);
+		return;
+	}
+	portp = panelp->ports[(ioack >> 3)];
+
+	outb((MISR + portp->uartaddr), ioaddr);
+	misr = inb(ioaddr + EREG_DATA);
+	if (misr & MISR_DCD) {
+		set_bit(ASYI_DCDCHANGE, &portp->istate);
+		schedule_work(&portp->tqueue);
+		portp->stats.modem++;
+	}
+
+	outb((EOSRR + portp->uartaddr), ioaddr);
+	outb(0, (ioaddr + EREG_DATA));
+}
+
+/*****************************************************************************/
+/*                      SC26198 HARDWARE FUNCTIONS                           */
+/*****************************************************************************/
+
+/*
+ *	These functions get/set/update the registers of the sc26198 UARTs.
+ *	Access to the sc26198 registers is via an address/data io port pair.
+ *	(Maybe should make this inline...)
+ */
+
+static int stl_sc26198getreg(stlport_t *portp, int regnr)
+{
+	outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+	return(inb(portp->ioaddr + XP_DATA));
+}
+
+static void stl_sc26198setreg(stlport_t *portp, int regnr, int value)
+{
+	outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+	outb(value, (portp->ioaddr + XP_DATA));
+}
+
+static int stl_sc26198updatereg(stlport_t *portp, int regnr, int value)
+{
+	outb((regnr | portp->uartaddr), (portp->ioaddr + XP_ADDR));
+	if (inb(portp->ioaddr + XP_DATA) != value) {
+		outb(value, (portp->ioaddr + XP_DATA));
+		return(1);
+	}
+	return(0);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Functions to get and set the sc26198 global registers.
+ */
+
+static int stl_sc26198getglobreg(stlport_t *portp, int regnr)
+{
+	outb(regnr, (portp->ioaddr + XP_ADDR));
+	return(inb(portp->ioaddr + XP_DATA));
+}
+
+#if 0
+static void stl_sc26198setglobreg(stlport_t *portp, int regnr, int value)
+{
+	outb(regnr, (portp->ioaddr + XP_ADDR));
+	outb(value, (portp->ioaddr + XP_DATA));
+}
+#endif
+
+/*****************************************************************************/
+
+/*
+ *	Inbitialize the UARTs in a panel. We don't care what sort of board
+ *	these ports are on - since the port io registers are almost
+ *	identical when dealing with ports.
+ */
+
+static int stl_sc26198panelinit(stlbrd_t *brdp, stlpanel_t *panelp)
+{
+	int	chipmask, i;
+	int	nrchips, ioaddr;
+
+#ifdef DEBUG
+	printk("stl_sc26198panelinit(brdp=%x,panelp=%x)\n",
+		(int) brdp, (int) panelp);
+#endif
+
+	BRDENABLE(panelp->brdnr, panelp->pagenr);
+
+/*
+ *	Check that each chip is present and started up OK.
+ */
+	chipmask = 0;
+	nrchips = (panelp->nrports + 4) / SC26198_PORTS;
+	if (brdp->brdtype == BRD_ECHPCI)
+		outb(panelp->pagenr, brdp->ioctrl);
+
+	for (i = 0; (i < nrchips); i++) {
+		ioaddr = panelp->iobase + (i * 4); 
+		outb(SCCR, (ioaddr + XP_ADDR));
+		outb(CR_RESETALL, (ioaddr + XP_DATA));
+		outb(TSTR, (ioaddr + XP_ADDR));
+		if (inb(ioaddr + XP_DATA) != 0) {
+			printk("STALLION: sc26198 not responding, "
+				"brd=%d panel=%d chip=%d\n",
+				panelp->brdnr, panelp->panelnr, i);
+			continue;
+		}
+		chipmask |= (0x1 << i);
+		outb(GCCR, (ioaddr + XP_ADDR));
+		outb(GCCR_IVRTYPCHANACK, (ioaddr + XP_DATA));
+		outb(WDTRCR, (ioaddr + XP_ADDR));
+		outb(0xff, (ioaddr + XP_DATA));
+	}
+
+	BRDDISABLE(panelp->brdnr);
+	return(chipmask);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Initialize hardware specific port registers.
+ */
+
+static void stl_sc26198portinit(stlbrd_t *brdp, stlpanel_t *panelp, stlport_t *portp)
+{
+#ifdef DEBUG
+	printk("stl_sc26198portinit(brdp=%x,panelp=%x,portp=%x)\n",
+		(int) brdp, (int) panelp, (int) portp);
+#endif
+
+	if ((brdp == (stlbrd_t *) NULL) || (panelp == (stlpanel_t *) NULL) ||
+	    (portp == (stlport_t *) NULL))
+		return;
+
+	portp->ioaddr = panelp->iobase + ((portp->portnr < 8) ? 0 : 4);
+	portp->uartaddr = (portp->portnr & 0x07) << 4;
+	portp->pagenr = panelp->pagenr;
+	portp->hwid = 0x1;
+
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_sc26198setreg(portp, IOPCR, IOPCR_SETSIGS);
+	BRDDISABLE(portp->brdnr);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Set up the sc26198 registers for a port based on the termios port
+ *	settings.
+ */
+
+static void stl_sc26198setport(stlport_t *portp, struct termios *tiosp)
+{
+	stlbrd_t	*brdp;
+	unsigned long	flags;
+	unsigned int	baudrate;
+	unsigned char	mr0, mr1, mr2, clk;
+	unsigned char	imron, imroff, iopr, ipr;
+
+	mr0 = 0;
+	mr1 = 0;
+	mr2 = 0;
+	clk = 0;
+	iopr = 0;
+	imron = 0;
+	imroff = 0;
+
+	brdp = stl_brds[portp->brdnr];
+	if (brdp == (stlbrd_t *) NULL)
+		return;
+
+/*
+ *	Set up the RX char ignore mask with those RX error types we
+ *	can ignore.
+ */
+	portp->rxignoremsk = 0;
+	if (tiosp->c_iflag & IGNPAR)
+		portp->rxignoremsk |= (SR_RXPARITY | SR_RXFRAMING |
+			SR_RXOVERRUN);
+	if (tiosp->c_iflag & IGNBRK)
+		portp->rxignoremsk |= SR_RXBREAK;
+
+	portp->rxmarkmsk = SR_RXOVERRUN;
+	if (tiosp->c_iflag & (INPCK | PARMRK))
+		portp->rxmarkmsk |= (SR_RXPARITY | SR_RXFRAMING);
+	if (tiosp->c_iflag & BRKINT)
+		portp->rxmarkmsk |= SR_RXBREAK;
+
+/*
+ *	Go through the char size, parity and stop bits and set all the
+ *	option register appropriately.
+ */
+	switch (tiosp->c_cflag & CSIZE) {
+	case CS5:
+		mr1 |= MR1_CS5;
+		break;
+	case CS6:
+		mr1 |= MR1_CS6;
+		break;
+	case CS7:
+		mr1 |= MR1_CS7;
+		break;
+	default:
+		mr1 |= MR1_CS8;
+		break;
+	}
+
+	if (tiosp->c_cflag & CSTOPB)
+		mr2 |= MR2_STOP2;
+	else
+		mr2 |= MR2_STOP1;
+
+	if (tiosp->c_cflag & PARENB) {
+		if (tiosp->c_cflag & PARODD)
+			mr1 |= (MR1_PARENB | MR1_PARODD);
+		else
+			mr1 |= (MR1_PARENB | MR1_PAREVEN);
+	} else {
+		mr1 |= MR1_PARNONE;
+	}
+
+	mr1 |= MR1_ERRBLOCK;
+
+/*
+ *	Set the RX FIFO threshold at 8 chars. This gives a bit of breathing
+ *	space for hardware flow control and the like. This should be set to
+ *	VMIN.
+ */
+	mr2 |= MR2_RXFIFOHALF;
+
+/*
+ *	Calculate the baud rate timers. For now we will just assume that
+ *	the input and output baud are the same. The sc26198 has a fixed
+ *	baud rate table, so only discrete baud rates possible.
+ */
+	baudrate = tiosp->c_cflag & CBAUD;
+	if (baudrate & CBAUDEX) {
+		baudrate &= ~CBAUDEX;
+		if ((baudrate < 1) || (baudrate > 4))
+			tiosp->c_cflag &= ~CBAUDEX;
+		else
+			baudrate += 15;
+	}
+	baudrate = stl_baudrates[baudrate];
+	if ((tiosp->c_cflag & CBAUD) == B38400) {
+		if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+			baudrate = 57600;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+			baudrate = 115200;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+			baudrate = 230400;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+			baudrate = 460800;
+		else if ((portp->flags & ASYNC_SPD_MASK) == ASYNC_SPD_CUST)
+			baudrate = (portp->baud_base / portp->custom_divisor);
+	}
+	if (baudrate > STL_SC26198MAXBAUD)
+		baudrate = STL_SC26198MAXBAUD;
+
+	if (baudrate > 0) {
+		for (clk = 0; (clk < SC26198_NRBAUDS); clk++) {
+			if (baudrate <= sc26198_baudtable[clk])
+				break;
+		}
+	}
+
+/*
+ *	Check what form of modem signaling is required and set it up.
+ */
+	if (tiosp->c_cflag & CLOCAL) {
+		portp->flags &= ~ASYNC_CHECK_CD;
+	} else {
+		iopr |= IOPR_DCDCOS;
+		imron |= IR_IOPORT;
+		portp->flags |= ASYNC_CHECK_CD;
+	}
+
+/*
+ *	Setup sc26198 enhanced modes if we can. In particular we want to
+ *	handle as much of the flow control as possible automatically. As
+ *	well as saving a few CPU cycles it will also greatly improve flow
+ *	control reliability.
+ */
+	if (tiosp->c_iflag & IXON) {
+		mr0 |= MR0_SWFTX | MR0_SWFT;
+		imron |= IR_XONXOFF;
+	} else {
+		imroff |= IR_XONXOFF;
+	}
+	if (tiosp->c_iflag & IXOFF)
+		mr0 |= MR0_SWFRX;
+
+	if (tiosp->c_cflag & CRTSCTS) {
+		mr2 |= MR2_AUTOCTS;
+		mr1 |= MR1_AUTORTS;
+	}
+
+/*
+ *	All sc26198 register values calculated so go through and set
+ *	them all up.
+ */
+
+#ifdef DEBUG
+	printk("SETPORT: portnr=%d panelnr=%d brdnr=%d\n",
+		portp->portnr, portp->panelnr, portp->brdnr);
+	printk("    mr0=%x mr1=%x mr2=%x clk=%x\n", mr0, mr1, mr2, clk);
+	printk("    iopr=%x imron=%x imroff=%x\n", iopr, imron, imroff);
+	printk("    schr1=%x schr2=%x schr3=%x schr4=%x\n",
+		tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP],
+		tiosp->c_cc[VSTART], tiosp->c_cc[VSTOP]);
+#endif
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_sc26198setreg(portp, IMR, 0);
+	stl_sc26198updatereg(portp, MR0, mr0);
+	stl_sc26198updatereg(portp, MR1, mr1);
+	stl_sc26198setreg(portp, SCCR, CR_RXERRBLOCK);
+	stl_sc26198updatereg(portp, MR2, mr2);
+	stl_sc26198updatereg(portp, IOPIOR,
+		((stl_sc26198getreg(portp, IOPIOR) & ~IPR_CHANGEMASK) | iopr));
+
+	if (baudrate > 0) {
+		stl_sc26198setreg(portp, TXCSR, clk);
+		stl_sc26198setreg(portp, RXCSR, clk);
+	}
+
+	stl_sc26198setreg(portp, XONCR, tiosp->c_cc[VSTART]);
+	stl_sc26198setreg(portp, XOFFCR, tiosp->c_cc[VSTOP]);
+
+	ipr = stl_sc26198getreg(portp, IPR);
+	if (ipr & IPR_DCD)
+		portp->sigs &= ~TIOCM_CD;
+	else
+		portp->sigs |= TIOCM_CD;
+
+	portp->imr = (portp->imr & ~imroff) | imron;
+	stl_sc26198setreg(portp, IMR, portp->imr);
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Set the state of the DTR and RTS signals.
+ */
+
+static void stl_sc26198setsignals(stlport_t *portp, int dtr, int rts)
+{
+	unsigned char	iopioron, iopioroff;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_sc26198setsignals(portp=%x,dtr=%d,rts=%d)\n",
+		(int) portp, dtr, rts);
+#endif
+
+	iopioron = 0;
+	iopioroff = 0;
+	if (dtr == 0)
+		iopioroff |= IPR_DTR;
+	else if (dtr > 0)
+		iopioron |= IPR_DTR;
+	if (rts == 0)
+		iopioroff |= IPR_RTS;
+	else if (rts > 0)
+		iopioron |= IPR_RTS;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_sc26198setreg(portp, IOPIOR,
+		((stl_sc26198getreg(portp, IOPIOR) & ~iopioroff) | iopioron));
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the state of the signals.
+ */
+
+static int stl_sc26198getsignals(stlport_t *portp)
+{
+	unsigned char	ipr;
+	unsigned long	flags;
+	int		sigs;
+
+#ifdef DEBUG
+	printk("stl_sc26198getsignals(portp=%x)\n", (int) portp);
+#endif
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	ipr = stl_sc26198getreg(portp, IPR);
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+
+	sigs = 0;
+	sigs |= (ipr & IPR_DCD) ? 0 : TIOCM_CD;
+	sigs |= (ipr & IPR_CTS) ? 0 : TIOCM_CTS;
+	sigs |= (ipr & IPR_DTR) ? 0: TIOCM_DTR;
+	sigs |= (ipr & IPR_RTS) ? 0: TIOCM_RTS;
+	sigs |= TIOCM_DSR;
+	return(sigs);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Enable/Disable the Transmitter and/or Receiver.
+ */
+
+static void stl_sc26198enablerxtx(stlport_t *portp, int rx, int tx)
+{
+	unsigned char	ccr;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_sc26198enablerxtx(portp=%x,rx=%d,tx=%d)\n",
+		(int) portp, rx, tx);
+#endif
+
+	ccr = portp->crenable;
+	if (tx == 0)
+		ccr &= ~CR_TXENABLE;
+	else if (tx > 0)
+		ccr |= CR_TXENABLE;
+	if (rx == 0)
+		ccr &= ~CR_RXENABLE;
+	else if (rx > 0)
+		ccr |= CR_RXENABLE;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_sc26198setreg(portp, SCCR, ccr);
+	BRDDISABLE(portp->brdnr);
+	portp->crenable = ccr;
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Start/stop the Transmitter and/or Receiver.
+ */
+
+static void stl_sc26198startrxtx(stlport_t *portp, int rx, int tx)
+{
+	unsigned char	imr;
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_sc26198startrxtx(portp=%x,rx=%d,tx=%d)\n",
+		(int) portp, rx, tx);
+#endif
+
+	imr = portp->imr;
+	if (tx == 0)
+		imr &= ~IR_TXRDY;
+	else if (tx == 1)
+		imr |= IR_TXRDY;
+	if (rx == 0)
+		imr &= ~(IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG);
+	else if (rx > 0)
+		imr |= IR_RXRDY | IR_RXBREAK | IR_RXWATCHDOG;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_sc26198setreg(portp, IMR, imr);
+	BRDDISABLE(portp->brdnr);
+	portp->imr = imr;
+	if (tx > 0)
+		set_bit(ASYI_TXBUSY, &portp->istate);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Disable all interrupts from this port.
+ */
+
+static void stl_sc26198disableintrs(stlport_t *portp)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_sc26198disableintrs(portp=%x)\n", (int) portp);
+#endif
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	portp->imr = 0;
+	stl_sc26198setreg(portp, IMR, 0);
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_sc26198sendbreak(stlport_t *portp, int len)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_sc26198sendbreak(portp=%x,len=%d)\n", (int) portp, len);
+#endif
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	if (len == 1) {
+		stl_sc26198setreg(portp, SCCR, CR_TXSTARTBREAK);
+		portp->stats.txbreaks++;
+	} else {
+		stl_sc26198setreg(portp, SCCR, CR_TXSTOPBREAK);
+	}
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Take flow control actions...
+ */
+
+static void stl_sc26198flowctrl(stlport_t *portp, int state)
+{
+	struct tty_struct	*tty;
+	unsigned long		flags;
+	unsigned char		mr0;
+
+#ifdef DEBUG
+	printk("stl_sc26198flowctrl(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+	tty = portp->tty;
+	if (tty == (struct tty_struct *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+
+	if (state) {
+		if (tty->termios->c_iflag & IXOFF) {
+			mr0 = stl_sc26198getreg(portp, MR0);
+			stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+			stl_sc26198setreg(portp, SCCR, CR_TXSENDXON);
+			mr0 |= MR0_SWFRX;
+			portp->stats.rxxon++;
+			stl_sc26198wait(portp);
+			stl_sc26198setreg(portp, MR0, mr0);
+		}
+/*
+ *		Question: should we return RTS to what it was before? It may
+ *		have been set by an ioctl... Suppose not, since if you have
+ *		hardware flow control set then it is pretty silly to go and
+ *		set the RTS line by hand.
+ */
+		if (tty->termios->c_cflag & CRTSCTS) {
+			stl_sc26198setreg(portp, MR1,
+				(stl_sc26198getreg(portp, MR1) | MR1_AUTORTS));
+			stl_sc26198setreg(portp, IOPIOR,
+				(stl_sc26198getreg(portp, IOPIOR) | IOPR_RTS));
+			portp->stats.rxrtson++;
+		}
+	} else {
+		if (tty->termios->c_iflag & IXOFF) {
+			mr0 = stl_sc26198getreg(portp, MR0);
+			stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+			stl_sc26198setreg(portp, SCCR, CR_TXSENDXOFF);
+			mr0 &= ~MR0_SWFRX;
+			portp->stats.rxxoff++;
+			stl_sc26198wait(portp);
+			stl_sc26198setreg(portp, MR0, mr0);
+		}
+		if (tty->termios->c_cflag & CRTSCTS) {
+			stl_sc26198setreg(portp, MR1,
+				(stl_sc26198getreg(portp, MR1) & ~MR1_AUTORTS));
+			stl_sc26198setreg(portp, IOPIOR,
+				(stl_sc26198getreg(portp, IOPIOR) & ~IOPR_RTS));
+			portp->stats.rxrtsoff++;
+		}
+	}
+
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Send a flow control character.
+ */
+
+static void stl_sc26198sendflow(stlport_t *portp, int state)
+{
+	struct tty_struct	*tty;
+	unsigned long		flags;
+	unsigned char		mr0;
+
+#ifdef DEBUG
+	printk("stl_sc26198sendflow(portp=%x,state=%x)\n", (int) portp, state);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+	tty = portp->tty;
+	if (tty == (struct tty_struct *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	if (state) {
+		mr0 = stl_sc26198getreg(portp, MR0);
+		stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+		stl_sc26198setreg(portp, SCCR, CR_TXSENDXON);
+		mr0 |= MR0_SWFRX;
+		portp->stats.rxxon++;
+		stl_sc26198wait(portp);
+		stl_sc26198setreg(portp, MR0, mr0);
+	} else {
+		mr0 = stl_sc26198getreg(portp, MR0);
+		stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+		stl_sc26198setreg(portp, SCCR, CR_TXSENDXOFF);
+		mr0 &= ~MR0_SWFRX;
+		portp->stats.rxxoff++;
+		stl_sc26198wait(portp);
+		stl_sc26198setreg(portp, MR0, mr0);
+	}
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+static void stl_sc26198flush(stlport_t *portp)
+{
+	unsigned long	flags;
+
+#ifdef DEBUG
+	printk("stl_sc26198flush(portp=%x)\n", (int) portp);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	stl_sc26198setreg(portp, SCCR, CR_TXRESET);
+	stl_sc26198setreg(portp, SCCR, portp->crenable);
+	BRDDISABLE(portp->brdnr);
+	portp->tx.tail = portp->tx.head;
+	restore_flags(flags);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Return the current state of data flow on this port. This is only
+ *	really interresting when determining if data has fully completed
+ *	transmission or not... The sc26198 interrupt scheme cannot
+ *	determine when all data has actually drained, so we need to
+ *	check the port statusy register to be sure.
+ */
+
+static int stl_sc26198datastate(stlport_t *portp)
+{
+	unsigned long	flags;
+	unsigned char	sr;
+
+#ifdef DEBUG
+	printk("stl_sc26198datastate(portp=%x)\n", (int) portp);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return(0);
+	if (test_bit(ASYI_TXBUSY, &portp->istate))
+		return(1);
+
+	save_flags(flags);
+	cli();
+	BRDENABLE(portp->brdnr, portp->pagenr);
+	sr = stl_sc26198getreg(portp, SR);
+	BRDDISABLE(portp->brdnr);
+	restore_flags(flags);
+
+	return((sr & SR_TXEMPTY) ? 0 : 1);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Delay for a small amount of time, to give the sc26198 a chance
+ *	to process a command...
+ */
+
+static void stl_sc26198wait(stlport_t *portp)
+{
+	int	i;
+
+#ifdef DEBUG
+	printk("stl_sc26198wait(portp=%x)\n", (int) portp);
+#endif
+
+	if (portp == (stlport_t *) NULL)
+		return;
+
+	for (i = 0; (i < 20); i++)
+		stl_sc26198getglobreg(portp, TSTR);
+}
+
+/*****************************************************************************/
+
+/*
+ *	If we are TX flow controlled and in IXANY mode then we may
+ *	need to unflow control here. We gotta do this because of the
+ *	automatic flow control modes of the sc26198.
+ */
+
+static inline void stl_sc26198txunflow(stlport_t *portp, struct tty_struct *tty)
+{
+	unsigned char	mr0;
+
+	mr0 = stl_sc26198getreg(portp, MR0);
+	stl_sc26198setreg(portp, MR0, (mr0 & ~MR0_SWFRXTX));
+	stl_sc26198setreg(portp, SCCR, CR_HOSTXON);
+	stl_sc26198wait(portp);
+	stl_sc26198setreg(portp, MR0, mr0);
+	clear_bit(ASYI_TXFLOWED, &portp->istate);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Interrupt service routine for sc26198 panels.
+ */
+
+static void stl_sc26198intr(stlpanel_t *panelp, unsigned int iobase)
+{
+	stlport_t	*portp;
+	unsigned int	iack;
+
+/* 
+ *	Work around bug in sc26198 chip... Cannot have A6 address
+ *	line of UART high, else iack will be returned as 0.
+ */
+	outb(0, (iobase + 1));
+
+	iack = inb(iobase + XP_IACK);
+	portp = panelp->ports[(iack & IVR_CHANMASK) + ((iobase & 0x4) << 1)];
+
+	if (iack & IVR_RXDATA)
+		stl_sc26198rxisr(portp, iack);
+	else if (iack & IVR_TXDATA)
+		stl_sc26198txisr(portp);
+	else
+		stl_sc26198otherisr(portp, iack);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Transmit interrupt handler. This has gotta be fast!  Handling TX
+ *	chars is pretty simple, stuff as many as possible from the TX buffer
+ *	into the sc26198 FIFO.
+ *	In practice it is possible that interrupts are enabled but that the
+ *	port has been hung up. Need to handle not having any TX buffer here,
+ *	this is done by using the side effect that head and tail will also
+ *	be NULL if the buffer has been freed.
+ */
+
+static void stl_sc26198txisr(stlport_t *portp)
+{
+	unsigned int	ioaddr;
+	unsigned char	mr0;
+	int		len, stlen;
+	char		*head, *tail;
+
+#ifdef DEBUG
+	printk("stl_sc26198txisr(portp=%x)\n", (int) portp);
+#endif
+
+	ioaddr = portp->ioaddr;
+	head = portp->tx.head;
+	tail = portp->tx.tail;
+	len = (head >= tail) ? (head - tail) : (STL_TXBUFSIZE - (tail - head));
+	if ((len == 0) || ((len < STL_TXBUFLOW) &&
+	    (test_bit(ASYI_TXLOW, &portp->istate) == 0))) {
+		set_bit(ASYI_TXLOW, &portp->istate);
+		schedule_work(&portp->tqueue); 
+	}
+
+	if (len == 0) {
+		outb((MR0 | portp->uartaddr), (ioaddr + XP_ADDR));
+		mr0 = inb(ioaddr + XP_DATA);
+		if ((mr0 & MR0_TXMASK) == MR0_TXEMPTY) {
+			portp->imr &= ~IR_TXRDY;
+			outb((IMR | portp->uartaddr), (ioaddr + XP_ADDR));
+			outb(portp->imr, (ioaddr + XP_DATA));
+			clear_bit(ASYI_TXBUSY, &portp->istate);
+		} else {
+			mr0 |= ((mr0 & ~MR0_TXMASK) | MR0_TXEMPTY);
+			outb(mr0, (ioaddr + XP_DATA));
+		}
+	} else {
+		len = MIN(len, SC26198_TXFIFOSIZE);
+		portp->stats.txtotal += len;
+		stlen = MIN(len, ((portp->tx.buf + STL_TXBUFSIZE) - tail));
+		outb(GTXFIFO, (ioaddr + XP_ADDR));
+		outsb((ioaddr + XP_DATA), tail, stlen);
+		len -= stlen;
+		tail += stlen;
+		if (tail >= (portp->tx.buf + STL_TXBUFSIZE))
+			tail = portp->tx.buf;
+		if (len > 0) {
+			outsb((ioaddr + XP_DATA), tail, len);
+			tail += len;
+		}
+		portp->tx.tail = tail;
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Receive character interrupt handler. Determine if we have good chars
+ *	or bad chars and then process appropriately. Good chars are easy
+ *	just shove the lot into the RX buffer and set all status byte to 0.
+ *	If a bad RX char then process as required. This routine needs to be
+ *	fast!  In practice it is possible that we get an interrupt on a port
+ *	that is closed. This can happen on hangups - since they completely
+ *	shutdown a port not in user context. Need to handle this case.
+ */
+
+static void stl_sc26198rxisr(stlport_t *portp, unsigned int iack)
+{
+	struct tty_struct	*tty;
+	unsigned int		len, buflen, ioaddr;
+
+#ifdef DEBUG
+	printk("stl_sc26198rxisr(portp=%x,iack=%x)\n", (int) portp, iack);
+#endif
+
+	tty = portp->tty;
+	ioaddr = portp->ioaddr;
+	outb(GIBCR, (ioaddr + XP_ADDR));
+	len = inb(ioaddr + XP_DATA) + 1;
+
+	if ((iack & IVR_TYPEMASK) == IVR_RXDATA) {
+		if ((tty == (struct tty_struct *) NULL) ||
+		    (tty->flip.char_buf_ptr == (char *) NULL) ||
+		    ((buflen = TTY_FLIPBUF_SIZE - tty->flip.count) == 0)) {
+			len = MIN(len, sizeof(stl_unwanted));
+			outb(GRXFIFO, (ioaddr + XP_ADDR));
+			insb((ioaddr + XP_DATA), &stl_unwanted[0], len);
+			portp->stats.rxlost += len;
+			portp->stats.rxtotal += len;
+		} else {
+			len = MIN(len, buflen);
+			if (len > 0) {
+				outb(GRXFIFO, (ioaddr + XP_ADDR));
+				insb((ioaddr + XP_DATA), tty->flip.char_buf_ptr, len);
+				memset(tty->flip.flag_buf_ptr, 0, len);
+				tty->flip.flag_buf_ptr += len;
+				tty->flip.char_buf_ptr += len;
+				tty->flip.count += len;
+				tty_schedule_flip(tty);
+				portp->stats.rxtotal += len;
+			}
+		}
+	} else {
+		stl_sc26198rxbadchars(portp);
+	}
+
+/*
+ *	If we are TX flow controlled and in IXANY mode then we may need
+ *	to unflow control here. We gotta do this because of the automatic
+ *	flow control modes of the sc26198.
+ */
+	if (test_bit(ASYI_TXFLOWED, &portp->istate)) {
+		if ((tty != (struct tty_struct *) NULL) &&
+		    (tty->termios != (struct termios *) NULL) &&
+		    (tty->termios->c_iflag & IXANY)) {
+			stl_sc26198txunflow(portp, tty);
+		}
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Process an RX bad character.
+ */
+
+static inline void stl_sc26198rxbadch(stlport_t *portp, unsigned char status, char ch)
+{
+	struct tty_struct	*tty;
+	unsigned int		ioaddr;
+
+	tty = portp->tty;
+	ioaddr = portp->ioaddr;
+
+	if (status & SR_RXPARITY)
+		portp->stats.rxparity++;
+	if (status & SR_RXFRAMING)
+		portp->stats.rxframing++;
+	if (status & SR_RXOVERRUN)
+		portp->stats.rxoverrun++;
+	if (status & SR_RXBREAK)
+		portp->stats.rxbreaks++;
+
+	if ((tty != (struct tty_struct *) NULL) &&
+	    ((portp->rxignoremsk & status) == 0)) {
+		if (portp->rxmarkmsk & status) {
+			if (status & SR_RXBREAK) {
+				status = TTY_BREAK;
+				if (portp->flags & ASYNC_SAK) {
+					do_SAK(tty);
+					BRDENABLE(portp->brdnr, portp->pagenr);
+				}
+			} else if (status & SR_RXPARITY) {
+				status = TTY_PARITY;
+			} else if (status & SR_RXFRAMING) {
+				status = TTY_FRAME;
+			} else if(status & SR_RXOVERRUN) {
+				status = TTY_OVERRUN;
+			} else {
+				status = 0;
+			}
+		} else {
+			status = 0;
+		}
+
+		if (tty->flip.char_buf_ptr != (char *) NULL) {
+			if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+				*tty->flip.flag_buf_ptr++ = status;
+				*tty->flip.char_buf_ptr++ = ch;
+				tty->flip.count++;
+			}
+			tty_schedule_flip(tty);
+		}
+
+		if (status == 0)
+			portp->stats.rxtotal++;
+	}
+}
+
+/*****************************************************************************/
+
+/*
+ *	Process all characters in the RX FIFO of the UART. Check all char
+ *	status bytes as well, and process as required. We need to check
+ *	all bytes in the FIFO, in case some more enter the FIFO while we
+ *	are here. To get the exact character error type we need to switch
+ *	into CHAR error mode (that is why we need to make sure we empty
+ *	the FIFO).
+ */
+
+static void stl_sc26198rxbadchars(stlport_t *portp)
+{
+	unsigned char	status, mr1;
+	char		ch;
+
+/*
+ *	To get the precise error type for each character we must switch
+ *	back into CHAR error mode.
+ */
+	mr1 = stl_sc26198getreg(portp, MR1);
+	stl_sc26198setreg(portp, MR1, (mr1 & ~MR1_ERRBLOCK));
+
+	while ((status = stl_sc26198getreg(portp, SR)) & SR_RXRDY) {
+		stl_sc26198setreg(portp, SCCR, CR_CLEARRXERR);
+		ch = stl_sc26198getreg(portp, RXFIFO);
+		stl_sc26198rxbadch(portp, status, ch);
+	}
+
+/*
+ *	To get correct interrupt class we must switch back into BLOCK
+ *	error mode.
+ */
+	stl_sc26198setreg(portp, MR1, mr1);
+}
+
+/*****************************************************************************/
+
+/*
+ *	Other interrupt handler. This includes modem signals, flow
+ *	control actions, etc. Most stuff is left to off-level interrupt
+ *	processing time.
+ */
+
+static void stl_sc26198otherisr(stlport_t *portp, unsigned int iack)
+{
+	unsigned char	cir, ipr, xisr;
+
+#ifdef DEBUG
+	printk("stl_sc26198otherisr(portp=%x,iack=%x)\n", (int) portp, iack);
+#endif
+
+	cir = stl_sc26198getglobreg(portp, CIR);
+
+	switch (cir & CIR_SUBTYPEMASK) {
+	case CIR_SUBCOS:
+		ipr = stl_sc26198getreg(portp, IPR);
+		if (ipr & IPR_DCDCHANGE) {
+			set_bit(ASYI_DCDCHANGE, &portp->istate);
+			schedule_work(&portp->tqueue); 
+			portp->stats.modem++;
+		}
+		break;
+	case CIR_SUBXONXOFF:
+		xisr = stl_sc26198getreg(portp, XISR);
+		if (xisr & XISR_RXXONGOT) {
+			set_bit(ASYI_TXFLOWED, &portp->istate);
+			portp->stats.txxoff++;
+		}
+		if (xisr & XISR_RXXOFFGOT) {
+			clear_bit(ASYI_TXFLOWED, &portp->istate);
+			portp->stats.txxon++;
+		}
+		break;
+	case CIR_SUBBREAK:
+		stl_sc26198setreg(portp, SCCR, CR_BREAKRESET);
+		stl_sc26198rxbadchars(portp);
+		break;
+	default:
+		break;
+	}
+}
+
+/*****************************************************************************/
diff --git a/drivers/char/sx.c b/drivers/char/sx.c
new file mode 100644
index 0000000..3ad758a
--- /dev/null
+++ b/drivers/char/sx.c
@@ -0,0 +1,2621 @@
+
+/* sx.c -- driver for the Specialix SX series cards. 
+ *
+ *  This driver will also support the older SI, and XIO cards.
+ *
+ *
+ *   (C) 1998 - 2004  R.E.Wolff@BitWizard.nl
+ *
+ *  Simon Allen (simonallen@cix.compulink.co.uk) wrote a previous
+ *  version of this driver. Some fragments may have been copied. (none
+ *  yet :-)
+ *
+ * Specialix pays for the development and support of this driver.
+ * Please DO contact support@specialix.co.uk if you require
+ * support. But please read the documentation (sx.txt) first.
+ *
+ *
+ *
+ *      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.
+ *
+ * Revision history:
+ * $Log: sx.c,v $
+ * Revision 1.33  2000/03/09 10:00:00  pvdl,wolff
+ * - Fixed module and port counting
+ * - Fixed signal handling
+ * - Fixed an Ooops
+ * 
+ * Revision 1.32  2000/03/07 09:00:00  wolff,pvdl
+ * - Fixed some sx_dprintk typos
+ * - added detection for an invalid board/module configuration
+ *
+ * Revision 1.31  2000/03/06 12:00:00  wolff,pvdl
+ * - Added support for EISA
+ *
+ * Revision 1.30  2000/01/21 17:43:06  wolff
+ * - Added support for SX+
+ *
+ * Revision 1.26  1999/08/05 15:22:14  wolff
+ * - Port to 2.3.x
+ * - Reformatted to Linus' liking.
+ *
+ * Revision 1.25  1999/07/30 14:24:08  wolff
+ * Had accidentally left "gs_debug" set to "-1" instead of "off" (=0).
+ *
+ * Revision 1.24  1999/07/28 09:41:52  wolff
+ * - I noticed the remark about use-count straying in sx.txt. I checked
+ *   sx_open, and found a few places where that could happen. I hope it's
+ *   fixed now.
+ *
+ * Revision 1.23  1999/07/28 08:56:06  wolff
+ * - Fixed crash when sx_firmware run twice.
+ * - Added sx_slowpoll as a module parameter (I guess nobody really wanted
+ *   to change it from the default... )
+ * - Fixed a stupid editing problem I introduced in 1.22.
+ * - Fixed dropping characters on a termios change.
+ *
+ * Revision 1.22  1999/07/26 21:01:43  wolff
+ * Russell Brown noticed that I had overlooked 4 out of six modem control
+ * signals in sx_getsignals. Ooops.
+ *
+ * Revision 1.21  1999/07/23 09:11:33  wolff
+ * I forgot to free dynamically allocated memory when the driver is unloaded.
+ *
+ * Revision 1.20  1999/07/20 06:25:26  wolff
+ * The "closing wait" wasn't honoured. Thanks to James Griffiths for
+ * reporting this.
+ *
+ * Revision 1.19  1999/07/11 08:59:59  wolff
+ * Fixed an oops in close, when an open was pending. Changed the memtest
+ * a bit. Should also test the board in word-mode, however my card fails the
+ * memtest then. I still have to figure out what is wrong...
+ *
+ * Revision 1.18  1999/06/10 09:38:42  wolff
+ * Changed the format of the firmware revision from %04x to %x.%02x .
+ *
+ * Revision 1.17  1999/06/04 09:44:35  wolff
+ * fixed problem: reference to pci stuff when config_pci was off...
+ * Thanks to Jorge Novo for noticing this.
+ *
+ * Revision 1.16  1999/06/02 08:30:15  wolff
+ * added/removed the workaround for the DCD bug in the Firmware.
+ * A bit more debugging code to locate that...
+ *
+ * Revision 1.15  1999/06/01 11:35:30  wolff
+ * when DCD is left low (floating?), on TA's the firmware first tells us
+ * that DCD is high, but after a short while suddenly comes to the
+ * conclusion that it is low. All this would be fine, if it weren't that
+ * Unix requires us to send a "hangup" signal in that case. This usually
+ * all happens BEFORE the program has had a chance to ioctl the device
+ * into clocal mode..
+ *
+ * Revision 1.14  1999/05/25 11:18:59  wolff
+ * Added PCI-fix.
+ * Added checks for return code of sx_sendcommand.
+ * Don't issue "reconfig" if port isn't open yet. (bit us on TA modules...)
+ *
+ * Revision 1.13  1999/04/29 15:18:01  wolff
+ * Fixed an "oops" that showed on SuSE 6.0 systems.
+ * Activate DTR again after stty 0.
+ *
+ * Revision 1.12  1999/04/29 07:49:52  wolff
+ * Improved "stty 0" handling a bit. (used to change baud to 9600 assuming
+ *     the connection would be dropped anyway. That is not always the case,
+ *     and confuses people).
+ * Told the card to always monitor the modem signals.
+ * Added support for dynamic  gs_debug adjustments.
+ * Now tells the rest of the system the number of ports.
+ *
+ * Revision 1.11  1999/04/24 11:11:30  wolff
+ * Fixed two stupid typos in the memory test.
+ *
+ * Revision 1.10  1999/04/24 10:53:39  wolff
+ * Added some of Christian's suggestions.
+ * Fixed an HW_COOK_IN bug (ISIG was not in I_OTHER. We used to trust the
+ * card to send the signal to the process.....)
+ *
+ * Revision 1.9  1999/04/23 07:26:38  wolff
+ * Included Christian Lademann's 2.0 compile-warning fixes and interrupt
+ *    assignment redesign.
+ * Cleanup of some other stuff.
+ *
+ * Revision 1.8  1999/04/16 13:05:30  wolff
+ * fixed a DCD change unnoticed bug.
+ *
+ * Revision 1.7  1999/04/14 22:19:51  wolff
+ * Fixed typo that showed up in 2.0.x builds (get_user instead of Get_user!)
+ *
+ * Revision 1.6  1999/04/13 18:40:20  wolff
+ * changed misc-minor to 161, as assigned by HPA.
+ *
+ * Revision 1.5  1999/04/13 15:12:25  wolff
+ * Fixed use-count leak when "hangup" occurred.
+ * Added workaround for a stupid-PCIBIOS bug.
+ *
+ *
+ * Revision 1.4  1999/04/01 22:47:40  wolff
+ * Fixed < 1M linux-2.0 problem.
+ * (vremap isn't compatible with ioremap in that case)
+ *
+ * Revision 1.3  1999/03/31 13:45:45  wolff
+ * Firmware loading is now done through a separate IOCTL.
+ *
+ * Revision 1.2  1999/03/28 12:22:29  wolff
+ * rcs cleanup
+ *
+ * Revision 1.1  1999/03/28 12:10:34  wolff
+ * Readying for release on 2.0.x (sorry David, 1.01 becomes 1.1 for RCS). 
+ *
+ * Revision 0.12  1999/03/28 09:20:10  wolff
+ * Fixed problem in 0.11, continueing cleanup.
+ *
+ * Revision 0.11  1999/03/28 08:46:44  wolff
+ * cleanup. Not good.
+ *
+ * Revision 0.10  1999/03/28 08:09:43  wolff
+ * Fixed loosing characters on close.
+ *
+ * Revision 0.9  1999/03/21 22:52:01  wolff
+ * Ported back to 2.2.... (minor things)
+ *
+ * Revision 0.8  1999/03/21 22:40:33  wolff
+ * Port to 2.0
+ *
+ * Revision 0.7  1999/03/21 19:06:34  wolff
+ * Fixed hangup processing.
+ *
+ * Revision 0.6  1999/02/05 08:45:14  wolff
+ * fixed real_raw problems. Inclusion into kernel imminent.
+ *
+ * Revision 0.5  1998/12/21 23:51:06  wolff
+ * Snatched a nasty bug: sx_transmit_chars was getting re-entered, and it
+ * shouldn't have. THATs why I want to have transmit interrupts even when
+ * the buffer is empty.
+ *
+ * Revision 0.4  1998/12/17 09:34:46  wolff
+ * PPP works. ioctl works. Basically works!
+ *
+ * Revision 0.3  1998/12/15 13:05:18  wolff
+ * It works! Wow! Gotta start implementing IOCTL and stuff....
+ *
+ * Revision 0.2  1998/12/01 08:33:53  wolff
+ * moved over to 2.1.130
+ *
+ * Revision 0.1  1998/11/03 21:23:51  wolff
+ * Initial revision. Detects SX card.
+ *
+ * */
+
+
+#define RCS_ID "$Id: sx.c,v 1.33 2000/03/08 10:01:02 wolff, pvdl Exp $"
+#define RCS_REV "$Revision: 1.33 $"
+
+
+#include <linux/module.h>
+#include <linux/config.h> 
+#include <linux/kdev_t.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+/* The 3.0.0 version of sxboards/sxwindow.h  uses BYTE and WORD.... */
+#define BYTE u8
+#define WORD u16
+
+/* .... but the 3.0.4 version uses _u8 and _u16. */
+#define _u8 u8
+#define _u16 u16
+
+#include "sxboards.h"
+#include "sxwindow.h"
+
+#include <linux/generic_serial.h>
+#include "sx.h"
+
+
+/* I don't think that this driver can handle more than 256 ports on
+   one machine. You'll have to increase the number of boards in sx.h
+   if you want more than 4 boards.  */
+
+#ifndef PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8
+#define PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8 0x2000
+#endif
+
+#ifdef CONFIG_PCI
+static struct pci_device_id sx_pci_tbl[] = {
+	{ PCI_VENDOR_ID_SPECIALIX, PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8, PCI_ANY_ID, PCI_ANY_ID },
+	{ 0 }
+};
+MODULE_DEVICE_TABLE(pci, sx_pci_tbl);
+#endif /* CONFIG_PCI */
+
+/* Configurable options: 
+   (Don't be too sure that it'll work if you toggle them) */
+
+/* Am I paranoid or not ? ;-) */
+#undef SX_PARANOIA_CHECK
+
+
+/* 20 -> 2000 per second. The card should rate-limit interrupts at 100
+   Hz, but it is user configurable. I don't recommend going above 1000
+   Hz. The interrupt ratelimit might trigger if the interrupt is
+   shared with a very active other device. */
+#define IRQ_RATE_LIMIT 20
+
+/* Sharing interrupts is possible now. If the other device wants more
+   than 2000 interrupts per second, we'd gracefully decline further
+   interrupts. That's not what we want. On the other hand, if the
+   other device interrupts 2000 times a second, don't use the SX
+   interrupt. Use polling. */
+#undef IRQ_RATE_LIMIT
+
+
+#if 0
+/* Not implemented */
+/* 
+ * The following defines are mostly for testing purposes. But if you need
+ * some nice reporting in your syslog, you can define them also.
+ */
+#define SX_REPORT_FIFO
+#define SX_REPORT_OVERRUN
+#endif 
+
+
+/* Function prototypes */
+static void sx_disable_tx_interrupts (void * ptr); 
+static void sx_enable_tx_interrupts (void * ptr); 
+static void sx_disable_rx_interrupts (void * ptr); 
+static void sx_enable_rx_interrupts (void * ptr); 
+static int  sx_get_CD (void * ptr); 
+static void sx_shutdown_port (void * ptr);
+static int  sx_set_real_termios (void  *ptr);
+static void sx_close (void  *ptr);
+static int sx_chars_in_buffer (void * ptr);
+static int sx_init_board (struct sx_board *board);
+static int sx_init_portstructs (int nboards, int nports);
+static int sx_fw_ioctl (struct inode *inode, struct file *filp,
+                        unsigned int cmd, unsigned long arg);
+static int sx_init_drivers(void);
+
+
+static struct tty_driver *sx_driver;
+
+static struct sx_board boards[SX_NBOARDS];
+static struct sx_port *sx_ports;
+static int sx_initialized;
+static int sx_nports;
+static int sx_debug;
+
+
+/* You can have the driver poll your card. 
+    - Set sx_poll to 1 to poll every timer tick (10ms on Intel). 
+      This is used when the card cannot use an interrupt for some reason.
+
+    - set sx_slowpoll to 100 to do an extra poll once a second (on Intel). If 
+      the driver misses an interrupt (report this if it DOES happen to you!)
+      everything will continue to work.... 
+ */
+static int sx_poll = 1;
+static int sx_slowpoll;
+
+/* The card limits the number of interrupts per second. 
+   At 115k2 "100" should be sufficient. 
+   If you're using higher baudrates, you can increase this...
+ */
+
+static int sx_maxints = 100;
+
+/* These are the only open spaces in my computer. Yours may have more
+   or less.... -- REW 
+   duh: Card at 0xa0000 is possible on HP Netserver?? -- pvdl
+*/
+static int sx_probe_addrs[]= {0xc0000, 0xd0000, 0xe0000, 
+                              0xc8000, 0xd8000, 0xe8000};
+static int si_probe_addrs[]= {0xc0000, 0xd0000, 0xe0000, 
+                              0xc8000, 0xd8000, 0xe8000, 0xa0000};
+static int si1_probe_addrs[]= { 0xd0000};
+
+#define NR_SX_ADDRS (sizeof(sx_probe_addrs)/sizeof (int))
+#define NR_SI_ADDRS (sizeof(si_probe_addrs)/sizeof (int))
+#define NR_SI1_ADDRS (sizeof(si1_probe_addrs)/sizeof (int))
+
+
+/* Set the mask to all-ones. This alas, only supports 32 interrupts. 
+   Some architectures may need more. */
+static int sx_irqmask = -1;
+
+module_param_array(sx_probe_addrs, int, NULL, 0);
+module_param_array(si_probe_addrs, int, NULL, 0);
+module_param(sx_poll, int, 0);
+module_param(sx_slowpoll, int, 0);
+module_param(sx_maxints, int, 0);
+module_param(sx_debug, int, 0);
+module_param(sx_irqmask, int, 0);
+
+MODULE_LICENSE("GPL");
+
+static struct real_driver sx_real_driver = {
+	sx_disable_tx_interrupts,
+	sx_enable_tx_interrupts,
+	sx_disable_rx_interrupts,
+	sx_enable_rx_interrupts,
+	sx_get_CD,
+	sx_shutdown_port, 
+	sx_set_real_termios, 
+	sx_chars_in_buffer,
+	sx_close,
+};
+
+
+/* 
+   This driver can spew a whole lot of debugging output at you. If you
+   need maximum performance, you should disable the DEBUG define. To
+   aid in debugging in the field, I'm leaving the compile-time debug
+   features enabled, and disable them "runtime". That allows me to
+   instruct people with problems to enable debugging without requiring
+   them to recompile... 
+*/
+#define DEBUG
+
+
+#ifdef DEBUG
+#define sx_dprintk(f, str...) if (sx_debug & f) printk (str)
+#else
+#define sx_dprintk(f, str...) /* nothing */
+#endif
+
+
+
+#define func_enter() sx_dprintk (SX_DEBUG_FLOW, "sx: enter %s\n",__FUNCTION__)
+#define func_exit()  sx_dprintk (SX_DEBUG_FLOW, "sx: exit  %s\n", __FUNCTION__)
+
+#define func_enter2() sx_dprintk (SX_DEBUG_FLOW, "sx: enter %s (port %d)\n", \
+					__FUNCTION__, port->line)
+
+
+
+
+/* 
+ *  Firmware loader driver specific routines
+ *
+ */
+
+static struct file_operations sx_fw_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= sx_fw_ioctl,
+};
+
+static struct miscdevice sx_fw_device = {
+	SXCTL_MISC_MINOR, "sxctl", &sx_fw_fops
+};
+
+
+
+
+
+#ifdef SX_PARANOIA_CHECK
+
+/* This doesn't work. Who's paranoid around here? Not me! */
+
+static inline int sx_paranoia_check(struct sx_port const * port,
+				    char *name, const char *routine)
+{
+
+	static const char *badmagic =
+	  KERN_ERR "sx: Warning: bad sx port magic number for device %s in %s\n";
+	static const char *badinfo =
+	  KERN_ERR "sx: Warning: null sx port for device %s in %s\n";
+ 
+	if (!port) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (port->magic != SX_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+
+	return 0;
+}
+#else
+#define sx_paranoia_check(a,b,c) 0
+#endif
+
+/* The timeouts. First try 30 times as fast as possible. Then give
+   the card some time to breathe between accesses. (Otherwise the
+   processor on the card might not be able to access its OWN bus... */
+
+#define TIMEOUT_1 30
+#define TIMEOUT_2 1000000
+
+
+#ifdef DEBUG
+static void my_hd_io(void __iomem *p, int len)
+{
+	int i, j, ch;
+	unsigned char __iomem *addr = p;
+
+	for (i=0;i<len;i+=16) {
+		printk ("%p ", addr+i);
+		for (j=0;j<16;j++) {
+			printk ("%02x %s", readb(addr+j+i), (j==7)?" ":"");
+		}
+		for (j=0;j<16;j++) {
+			ch = readb(addr+j+i);
+			printk ("%c", (ch < 0x20)?'.':((ch > 0x7f)?'.':ch));
+		}
+		printk ("\n");
+	}
+}
+static void my_hd(void *p, int len)
+{
+	int i, j, ch;
+	unsigned char *addr = p;
+
+	for (i=0;i<len;i+=16) {
+		printk ("%p ", addr+i);
+		for (j=0;j<16;j++) {
+			printk ("%02x %s", addr[j+i], (j==7)?" ":"");
+		}
+		for (j=0;j<16;j++) {
+			ch = addr[j+i];
+			printk ("%c", (ch < 0x20)?'.':((ch > 0x7f)?'.':ch));
+		}
+		printk ("\n");
+	}
+}
+#endif
+
+
+
+/* This needs redoing for Alpha -- REW -- Done. */
+
+static inline void write_sx_byte (struct sx_board *board, int offset, u8 byte)
+{
+	writeb (byte, board->base+offset);
+}
+
+static inline u8 read_sx_byte (struct sx_board *board, int offset)
+{
+	return readb (board->base+offset);
+}
+
+
+static inline void write_sx_word (struct sx_board *board, int offset, u16 word)
+{
+	writew (word, board->base+offset);
+}
+
+static inline u16 read_sx_word (struct sx_board *board, int offset)
+{
+	return readw (board->base + offset);
+}
+
+
+static int sx_busy_wait_eq (struct sx_board *board, 
+                     	    int offset, int mask, int correctval)
+{
+	int i;
+
+	func_enter ();
+
+	for (i=0; i < TIMEOUT_1 ;i++)
+		if ((read_sx_byte (board, offset) & mask) == correctval) {
+			func_exit ();
+			return 1;
+		}
+
+	for (i=0; i < TIMEOUT_2 ;i++) {
+		if ((read_sx_byte (board, offset) & mask) == correctval) {
+			func_exit ();
+			return 1;
+		}
+		udelay (1);
+	}
+
+	func_exit ();
+	return 0;
+}
+
+
+static int sx_busy_wait_neq (struct sx_board *board, 
+                      	     int offset, int mask, int badval)
+{
+	int i;
+
+	func_enter ();
+
+	for (i=0; i < TIMEOUT_1 ;i++)
+		if ((read_sx_byte (board, offset) & mask) != badval) {
+			func_exit ();
+			return 1;
+		}
+
+	for (i=0; i < TIMEOUT_2 ;i++) {
+		if ((read_sx_byte (board, offset) & mask) != badval) {
+			func_exit ();
+			return 1;
+		}
+		udelay (1);
+	}
+
+	func_exit ();
+	return 0;
+}
+
+
+
+/* 5.6.4 of 6210028 r2.3 */
+static int sx_reset (struct sx_board *board)
+{
+	func_enter ();
+
+	if (IS_SX_BOARD (board)) {
+
+		write_sx_byte (board, SX_CONFIG, 0);
+		write_sx_byte (board, SX_RESET, 1); /* Value doesn't matter */
+
+		if (!sx_busy_wait_eq (board, SX_RESET_STATUS, 1, 0)) {
+			printk (KERN_INFO "sx: Card doesn't respond to reset....\n");
+			return 0;
+		}
+	} else if (IS_EISA_BOARD(board)) {
+		outb(board->irq<<4, board->eisa_base+0xc02);
+	} else if (IS_SI1_BOARD(board)) {
+	        write_sx_byte (board, SI1_ISA_RESET,   0); // value does not matter
+	} else {
+		/* Gory details of the SI/ISA board */
+		write_sx_byte (board, SI2_ISA_RESET,    SI2_ISA_RESET_SET);
+		write_sx_byte (board, SI2_ISA_IRQ11,    SI2_ISA_IRQ11_CLEAR);
+		write_sx_byte (board, SI2_ISA_IRQ12,    SI2_ISA_IRQ12_CLEAR);
+		write_sx_byte (board, SI2_ISA_IRQ15,    SI2_ISA_IRQ15_CLEAR);
+		write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_CLEAR);
+		write_sx_byte (board, SI2_ISA_IRQSET,   SI2_ISA_IRQSET_CLEAR);
+	}
+
+	func_exit ();
+	return 1;
+}
+
+
+/* This doesn't work on machines where "NULL" isn't 0 */
+/* If you have one of those, someone will need to write 
+   the equivalent of this, which will amount to about 3 lines. I don't
+   want to complicate this right now. -- REW
+   (See, I do write comments every now and then :-) */
+#define OFFSETOF(strct, elem) ((long)&(((struct strct *)NULL)->elem))
+
+
+#define CHAN_OFFSET(port,elem) (port->ch_base + OFFSETOF (_SXCHANNEL, elem))
+#define MODU_OFFSET(board,addr,elem)    (addr + OFFSETOF (_SXMODULE, elem))
+#define  BRD_OFFSET(board,elem)                (OFFSETOF (_SXCARD, elem))
+
+
+#define sx_write_channel_byte(port, elem, val) \
+   write_sx_byte (port->board, CHAN_OFFSET (port, elem), val)
+
+#define sx_read_channel_byte(port, elem) \
+   read_sx_byte (port->board, CHAN_OFFSET (port, elem))
+
+#define sx_write_channel_word(port, elem, val) \
+   write_sx_word (port->board, CHAN_OFFSET (port, elem), val)
+
+#define sx_read_channel_word(port, elem) \
+   read_sx_word (port->board, CHAN_OFFSET (port, elem))
+
+
+#define sx_write_module_byte(board, addr, elem, val) \
+   write_sx_byte (board, MODU_OFFSET (board, addr, elem), val)
+
+#define sx_read_module_byte(board, addr, elem) \
+   read_sx_byte (board, MODU_OFFSET (board, addr, elem))
+
+#define sx_write_module_word(board, addr, elem, val) \
+   write_sx_word (board, MODU_OFFSET (board, addr, elem), val)
+
+#define sx_read_module_word(board, addr, elem) \
+   read_sx_word (board, MODU_OFFSET (board, addr, elem))
+
+
+#define sx_write_board_byte(board, elem, val) \
+   write_sx_byte (board, BRD_OFFSET (board, elem), val)
+
+#define sx_read_board_byte(board, elem) \
+   read_sx_byte (board, BRD_OFFSET (board, elem))
+
+#define sx_write_board_word(board, elem, val) \
+   write_sx_word (board, BRD_OFFSET (board, elem), val)
+
+#define sx_read_board_word(board, elem) \
+   read_sx_word (board, BRD_OFFSET (board, elem))
+
+
+static int sx_start_board (struct sx_board *board)
+{
+	if (IS_SX_BOARD (board)) {
+		write_sx_byte (board, SX_CONFIG, SX_CONF_BUSEN);
+	} else if (IS_EISA_BOARD(board)) {
+		write_sx_byte(board, SI2_EISA_OFF, SI2_EISA_VAL);
+		outb((board->irq<<4)|4, board->eisa_base+0xc02);
+	} else if (IS_SI1_BOARD(board)) {
+		write_sx_byte (board, SI1_ISA_RESET_CLEAR, 0);
+		write_sx_byte (board, SI1_ISA_INTCL, 0);
+	} else {
+		/* Don't bug me about the clear_set. 
+		   I haven't the foggiest idea what it's about -- REW */
+		write_sx_byte (board, SI2_ISA_RESET,    SI2_ISA_RESET_CLEAR);
+		write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_SET);
+	}
+	return 1;
+}
+
+#define SX_IRQ_REG_VAL(board) \
+        ((board->flags & SX_ISA_BOARD)?(board->irq << 4):0)
+
+/* Note. The SX register is write-only. Therefore, we have to enable the
+   bus too. This is a no-op, if you don't mess with this driver... */
+static int sx_start_interrupts (struct sx_board *board)
+{
+
+	/* Don't call this with board->irq == 0 */
+
+	if (IS_SX_BOARD(board)) {
+		write_sx_byte (board, SX_CONFIG, SX_IRQ_REG_VAL (board) | 
+		                                 SX_CONF_BUSEN | 
+		                                 SX_CONF_HOSTIRQ);
+	} else if (IS_EISA_BOARD(board)) {
+		inb(board->eisa_base+0xc03);  
+	} else if (IS_SI1_BOARD(board)) {
+	       write_sx_byte (board, SI1_ISA_INTCL,0);
+	       write_sx_byte (board, SI1_ISA_INTCL_CLEAR,0);
+	} else {
+		switch (board->irq) {
+		case 11:write_sx_byte (board, SI2_ISA_IRQ11, SI2_ISA_IRQ11_SET);break;
+		case 12:write_sx_byte (board, SI2_ISA_IRQ12, SI2_ISA_IRQ12_SET);break;
+		case 15:write_sx_byte (board, SI2_ISA_IRQ15, SI2_ISA_IRQ15_SET);break;
+		default:printk (KERN_INFO "sx: SI/XIO card doesn't support interrupt %d.\n", 
+		                board->irq);
+		return 0;
+		}
+		write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_SET);
+	}
+
+	return 1;
+}
+
+
+static int sx_send_command (struct sx_port *port, 
+                     	    int command, int mask, int newstat)
+{
+	func_enter2 ();
+	write_sx_byte (port->board, CHAN_OFFSET (port, hi_hstat), command);
+	func_exit ();
+	return sx_busy_wait_eq (port->board, CHAN_OFFSET (port, hi_hstat), mask, newstat);
+}
+
+
+static char *mod_type_s (int module_type)
+{
+	switch (module_type) {
+	case TA4:       return "TA4";
+	case TA8:       return "TA8";
+	case TA4_ASIC:  return "TA4_ASIC";
+	case TA8_ASIC:  return "TA8_ASIC";
+	case MTA_CD1400:return "MTA_CD1400";
+	case SXDC:      return "SXDC";
+	default:return "Unknown/invalid";
+	}
+}
+
+
+static char *pan_type_s (int pan_type)
+{
+	switch (pan_type) {
+	case MOD_RS232DB25:     return "MOD_RS232DB25";
+	case MOD_RS232RJ45:     return "MOD_RS232RJ45";
+	case MOD_RS422DB25:     return "MOD_RS422DB25";
+	case MOD_PARALLEL:      return "MOD_PARALLEL";
+	case MOD_2_RS232DB25:   return "MOD_2_RS232DB25";
+	case MOD_2_RS232RJ45:   return "MOD_2_RS232RJ45";
+	case MOD_2_RS422DB25:   return "MOD_2_RS422DB25";
+	case MOD_RS232DB25MALE: return "MOD_RS232DB25MALE";
+	case MOD_2_PARALLEL:    return "MOD_2_PARALLEL";
+	case MOD_BLANK:         return "empty";
+	default:return "invalid";
+	}
+}
+
+
+static int mod_compat_type (int module_type)
+{
+	return module_type >> 4;
+}
+
+static void sx_reconfigure_port(struct sx_port *port)
+{
+	if (sx_read_channel_byte (port, hi_hstat) == HS_IDLE_OPEN) {
+		if (sx_send_command (port, HS_CONFIG, -1, HS_IDLE_OPEN) != 1) {
+			printk (KERN_WARNING "sx: Sent reconfigure command, but card didn't react.\n");
+		}
+	} else {
+		sx_dprintk (SX_DEBUG_TERMIOS, 
+		            "sx: Not sending reconfigure: port isn't open (%02x).\n", 
+		            sx_read_channel_byte (port, hi_hstat));
+	}	
+}
+
+static void sx_setsignals (struct sx_port *port, int dtr, int rts)
+{
+	int t;
+	func_enter2 ();
+
+	t = sx_read_channel_byte (port, hi_op);
+	if (dtr >= 0) t = dtr? (t | OP_DTR): (t & ~OP_DTR);
+	if (rts >= 0) t = rts? (t | OP_RTS): (t & ~OP_RTS);
+	sx_write_channel_byte (port, hi_op, t);
+	sx_dprintk (SX_DEBUG_MODEMSIGNALS, "setsignals: %d/%d\n", dtr, rts);
+
+	func_exit ();
+}
+
+
+
+static int sx_getsignals (struct sx_port *port)
+{
+	int i_stat,o_stat;
+
+	o_stat = sx_read_channel_byte (port, hi_op);
+	i_stat = sx_read_channel_byte (port, hi_ip);
+
+	sx_dprintk (SX_DEBUG_MODEMSIGNALS, "getsignals: %d/%d  (%d/%d) %02x/%02x\n",
+	            (o_stat & OP_DTR) != 0, (o_stat & OP_RTS) != 0,
+	            port->c_dcd, sx_get_CD (port),
+	            sx_read_channel_byte (port, hi_ip),
+	            sx_read_channel_byte (port, hi_state));
+
+	return (((o_stat & OP_DTR)?TIOCM_DTR:0) |
+	        ((o_stat & OP_RTS)?TIOCM_RTS:0) |
+	        ((i_stat & IP_CTS)?TIOCM_CTS:0) |
+	        ((i_stat & IP_DCD)?TIOCM_CAR:0) |
+	        ((i_stat & IP_DSR)?TIOCM_DSR:0) |
+	        ((i_stat & IP_RI)?TIOCM_RNG:0)
+	        );
+}
+
+
+static void sx_set_baud (struct sx_port *port)
+{
+	int t;
+
+	if (port->board->ta_type == MOD_SXDC) {
+		switch (port->gs.baud) {
+		  /* Save some typing work... */
+#define e(x) case x:t= BAUD_ ## x ; break
+			e(50);e(75);e(110);e(150);e(200);e(300);e(600);
+                        e(1200);e(1800);e(2000);e(2400);e(4800);e(7200);
+                        e(9600);e(14400);e(19200);e(28800);e(38400);
+                        e(56000);e(57600);e(64000);e(76800);e(115200);
+			e(128000);e(150000);e(230400);e(256000);e(460800);
+                        e(921600);
+		case 134    :t = BAUD_134_5;   break;
+		case 0      :t = -1;
+								 break;
+		default:
+			/* Can I return "invalid"? */
+			t = BAUD_9600;
+			printk (KERN_INFO "sx: unsupported baud rate: %d.\n", port->gs.baud);
+			break;
+		}
+#undef e
+		if (t > 0) {
+			/* The baud rate is not set to 0, so we're enabeling DTR... -- REW */
+			sx_setsignals (port, 1, -1); 
+			/* XXX This is not TA & MTA compatible */
+			sx_write_channel_byte (port, hi_csr, 0xff);
+
+			sx_write_channel_byte (port, hi_txbaud, t);
+			sx_write_channel_byte (port, hi_rxbaud, t);
+		} else {
+			sx_setsignals (port, 0, -1);
+		}
+	} else {
+		switch (port->gs.baud) {
+#define e(x) case x:t= CSR_ ## x ; break
+			e(75);e(150);e(300);e(600);e(1200);e(2400);e(4800);
+                        e(1800);e(9600);
+			e(19200);e(57600);e(38400);
+			/* TA supports 110, but not 115200, MTA supports 115200, but not 110 */
+		case 110: 
+			if (port->board->ta_type == MOD_TA) {
+				t = CSR_110;
+				break;
+			} else {
+				t = CSR_9600;
+				printk (KERN_INFO "sx: Unsupported baud rate: %d.\n", port->gs.baud);
+				break;
+			}
+		case 115200: 
+			if (port->board->ta_type == MOD_TA) {
+				t = CSR_9600;
+				printk (KERN_INFO "sx: Unsupported baud rate: %d.\n", port->gs.baud);
+				break;
+			} else {
+				t = CSR_110;
+				break;
+			}
+		case 0      :t = -1;
+								 break;
+		default:
+			t = CSR_9600;
+			printk (KERN_INFO "sx: Unsupported baud rate: %d.\n", port->gs.baud);
+			break;
+		}
+#undef e
+		if (t >= 0) {
+			sx_setsignals (port, 1, -1);
+			sx_write_channel_byte (port, hi_csr, t * 0x11);
+		} else {
+			sx_setsignals (port, 0, -1);
+		}
+	}
+}
+
+
+/* Simon Allen's version of this routine was 225 lines long. 85 is a lot
+   better. -- REW */
+
+static int sx_set_real_termios (void *ptr)
+{
+	struct sx_port *port = ptr;
+
+	func_enter2();
+
+	if (!port->gs.tty)
+		return 0;
+
+	/* What is this doing here? -- REW
+	   Ha! figured it out. It is to allow you to get DTR active again
+	   if you've dropped it with stty 0. Moved to set_baud, where it
+	   belongs (next to the drop dtr if baud == 0) -- REW */
+	/* sx_setsignals (port, 1, -1); */
+
+	sx_set_baud (port);
+
+#define CFLAG port->gs.tty->termios->c_cflag
+	sx_write_channel_byte (port, hi_mr1,
+	                       (C_PARENB (port->gs.tty)? MR1_WITH:MR1_NONE) |
+	                       (C_PARODD (port->gs.tty)? MR1_ODD:MR1_EVEN) |
+	                       (C_CRTSCTS(port->gs.tty)? MR1_RTS_RXFLOW:0) |
+	                       (((CFLAG & CSIZE)==CS8) ? MR1_8_BITS:0) |
+	                       (((CFLAG & CSIZE)==CS7) ? MR1_7_BITS:0) |
+	                       (((CFLAG & CSIZE)==CS6) ? MR1_6_BITS:0) |
+	                       (((CFLAG & CSIZE)==CS5) ? MR1_5_BITS:0) );
+
+	sx_write_channel_byte (port, hi_mr2,
+	                       (C_CRTSCTS(port->gs.tty)?MR2_CTS_TXFLOW:0) |
+	                       (C_CSTOPB (port->gs.tty)?MR2_2_STOP:MR2_1_STOP));
+
+	switch (CFLAG & CSIZE) {
+	case CS8:sx_write_channel_byte (port, hi_mask, 0xff);break;
+	case CS7:sx_write_channel_byte (port, hi_mask, 0x7f);break;
+	case CS6:sx_write_channel_byte (port, hi_mask, 0x3f);break;
+	case CS5:sx_write_channel_byte (port, hi_mask, 0x1f);break;
+	default:
+		printk (KERN_INFO "sx: Invalid wordsize: %d\n", CFLAG & CSIZE);
+		break;
+	}
+
+	sx_write_channel_byte (port, hi_prtcl, 
+	                       (I_IXON   (port->gs.tty)?SP_TXEN:0) |
+	                       (I_IXOFF  (port->gs.tty)?SP_RXEN:0) |
+	                       (I_IXANY  (port->gs.tty)?SP_TANY:0) |
+	                       SP_DCEN);
+
+	sx_write_channel_byte (port, hi_break, 
+	                       (I_IGNBRK(port->gs.tty)?BR_IGN:0 |
+	                        I_BRKINT(port->gs.tty)?BR_INT:0));
+
+	sx_write_channel_byte (port, hi_txon,  START_CHAR (port->gs.tty));
+	sx_write_channel_byte (port, hi_rxon,  START_CHAR (port->gs.tty));
+	sx_write_channel_byte (port, hi_txoff, STOP_CHAR  (port->gs.tty));
+	sx_write_channel_byte (port, hi_rxoff, STOP_CHAR  (port->gs.tty));
+
+	sx_reconfigure_port(port);
+
+	/* Tell line discipline whether we will do input cooking */
+	if(I_OTHER(port->gs.tty)) {
+		clear_bit(TTY_HW_COOK_IN, &port->gs.tty->flags);
+	} else {
+		set_bit(TTY_HW_COOK_IN, &port->gs.tty->flags);
+	}
+	sx_dprintk (SX_DEBUG_TERMIOS, "iflags: %x(%d) ", 
+	            port->gs.tty->termios->c_iflag, 
+	            I_OTHER(port->gs.tty));
+
+
+/* Tell line discipline whether we will do output cooking.
+ * If OPOST is set and no other output flags are set then we can do output
+ * processing.  Even if only *one* other flag in the O_OTHER group is set
+ * we do cooking in software.
+ */
+	if(O_OPOST(port->gs.tty) && !O_OTHER(port->gs.tty)) {
+		set_bit(TTY_HW_COOK_OUT, &port->gs.tty->flags);
+	} else {
+		clear_bit(TTY_HW_COOK_OUT, &port->gs.tty->flags);
+	}
+	sx_dprintk (SX_DEBUG_TERMIOS, "oflags: %x(%d)\n", 
+	            port->gs.tty->termios->c_oflag, 
+	            O_OTHER(port->gs.tty));
+	/* port->c_dcd = sx_get_CD (port); */
+	func_exit ();
+	return 0;
+}
+
+
+
+/* ********************************************************************** *
+ *                   the interrupt related routines                       *
+ * ********************************************************************** */
+
+/* Note:
+   Other drivers use the macro "MIN" to calculate how much to copy.
+   This has the disadvantage that it will evaluate parts twice. That's
+   expensive when it's IO (and the compiler cannot optimize those away!).
+   Moreover, I'm not sure that you're race-free. 
+
+   I assign a value, and then only allow the value to decrease. This
+   is always safe. This makes the code a few lines longer, and you
+   know I'm dead against that, but I think it is required in this
+   case.  */
+
+
+static void sx_transmit_chars (struct sx_port *port)
+{
+	int c;
+	int tx_ip;
+	int txroom;
+
+	func_enter2 ();
+	sx_dprintk (SX_DEBUG_TRANSMIT, "Port %p: transmit %d chars\n", 
+	            port, port->gs.xmit_cnt);
+
+	if (test_and_set_bit (SX_PORT_TRANSMIT_LOCK, &port->locks)) {
+		return;
+	}
+
+	while (1) {
+		c = port->gs.xmit_cnt;
+
+		sx_dprintk (SX_DEBUG_TRANSMIT, "Copying %d ", c);
+		tx_ip  = sx_read_channel_byte (port, hi_txipos);
+
+		/* Took me 5 minutes to deduce this formula. 
+		   Luckily it is literally in the manual in section 6.5.4.3.5 */
+		txroom = (sx_read_channel_byte (port, hi_txopos) - tx_ip - 1) & 0xff;
+
+		/* Don't copy more bytes than there is room for in the buffer */
+		if (c > txroom)
+			c = txroom;
+		sx_dprintk (SX_DEBUG_TRANSMIT, " %d(%d) ", c, txroom );
+
+		/* Don't copy past the end of the hardware transmit buffer */
+		if (c > 0x100 - tx_ip) 
+			c = 0x100 - tx_ip;
+
+		sx_dprintk (SX_DEBUG_TRANSMIT, " %d(%d) ", c, 0x100-tx_ip );
+
+		/* Don't copy pas the end of the source buffer */
+		if (c > SERIAL_XMIT_SIZE - port->gs.xmit_tail) 
+			c = SERIAL_XMIT_SIZE - port->gs.xmit_tail;
+
+		sx_dprintk (SX_DEBUG_TRANSMIT, " %d(%ld) \n", 
+		            c, SERIAL_XMIT_SIZE- port->gs.xmit_tail);
+
+		/* If for one reason or another, we can't copy more data, we're done! */
+		if (c == 0) break;
+
+
+		memcpy_toio (port->board->base + CHAN_OFFSET(port,hi_txbuf) + tx_ip, 
+		             port->gs.xmit_buf + port->gs.xmit_tail, c);
+
+		/* Update the pointer in the card */
+		sx_write_channel_byte (port, hi_txipos, (tx_ip+c) & 0xff);
+
+		/* Update the kernel buffer end */
+		port->gs.xmit_tail = (port->gs.xmit_tail + c) & (SERIAL_XMIT_SIZE-1);
+
+		/* This one last. (this is essential)
+		   It would allow others to start putting more data into the buffer! */
+		port->gs.xmit_cnt -= c;
+	}
+
+	if (port->gs.xmit_cnt == 0) {
+		sx_disable_tx_interrupts (port);
+	}
+
+	if ((port->gs.xmit_cnt <= port->gs.wakeup_chars) && port->gs.tty) {
+		tty_wakeup(port->gs.tty);
+		sx_dprintk (SX_DEBUG_TRANSMIT, "Waking up.... ldisc (%d)....\n",
+		            port->gs.wakeup_chars); 
+	}
+
+	clear_bit (SX_PORT_TRANSMIT_LOCK, &port->locks);
+	func_exit ();
+}
+
+
+/* Note the symmetry between receiving chars and transmitting them!
+   Note: The kernel should have implemented both a receive buffer and
+   a transmit buffer. */
+
+/* Inlined: Called only once. Remove the inline when you add another call */
+static inline void sx_receive_chars (struct sx_port *port)
+{
+	int c;
+	int rx_op;
+	struct tty_struct *tty;
+	int copied=0;
+
+	func_enter2 ();
+	tty = port->gs.tty;
+	while (1) {
+		rx_op = sx_read_channel_byte (port, hi_rxopos);
+		c = (sx_read_channel_byte (port, hi_rxipos) - rx_op) & 0xff;
+
+		sx_dprintk (SX_DEBUG_RECEIVE, "rxop=%d, c = %d.\n", rx_op, c); 
+
+		/* Don't copy more bytes than there is room for in the buffer */
+		if (tty->flip.count + c > TTY_FLIPBUF_SIZE) 
+			c = TTY_FLIPBUF_SIZE - tty->flip.count;
+
+		sx_dprintk (SX_DEBUG_RECEIVE, "c = %d.\n", c); 
+
+		/* Don't copy past the end of the hardware receive buffer */
+		if (rx_op + c > 0x100) c = 0x100 - rx_op;
+
+		sx_dprintk (SX_DEBUG_RECEIVE, "c = %d.\n", c);
+
+		/* If for one reason or another, we can't copy more data, we're done! */
+		if (c == 0) break;
+
+		sx_dprintk (SX_DEBUG_RECEIVE , "Copying over %d chars. First is %d at %lx\n", c, 
+		            read_sx_byte (port->board, CHAN_OFFSET(port,hi_rxbuf) + rx_op),
+		            CHAN_OFFSET(port, hi_rxbuf)); 
+		memcpy_fromio (tty->flip.char_buf_ptr, 
+		               port->board->base + CHAN_OFFSET(port,hi_rxbuf) + rx_op, c);
+		memset(tty->flip.flag_buf_ptr, TTY_NORMAL, c);
+
+		/* Update the kernel buffer end */
+		tty->flip.count += c;
+		tty->flip.char_buf_ptr += c;
+		tty->flip.flag_buf_ptr += c;
+
+		/* This one last. ( Not essential.)
+		   It allows the card to start putting more data into the buffer! 
+		   Update the pointer in the card */
+		sx_write_channel_byte (port, hi_rxopos, (rx_op + c) & 0xff);
+
+		copied += c;
+	}
+	if (copied) {
+		struct timeval tv;
+
+		do_gettimeofday (&tv);
+		sx_dprintk (SX_DEBUG_RECEIVE, 
+		            "pushing flipq port %d (%3d chars): %d.%06d  (%d/%d)\n", 
+		            port->line, copied, 
+		            (int) (tv.tv_sec % 60), (int)tv.tv_usec, tty->raw, tty->real_raw);
+
+		/* Tell the rest of the system the news. Great news. New characters! */
+		tty_flip_buffer_push (tty);
+		/*    tty_schedule_flip (tty); */
+	}
+
+	func_exit ();
+}
+
+/* Inlined: it is called only once. Remove the inline if you add another 
+   call */
+static inline void sx_check_modem_signals (struct sx_port *port)
+{
+	int hi_state;
+	int c_dcd;
+
+	hi_state = sx_read_channel_byte (port, hi_state);
+	sx_dprintk (SX_DEBUG_MODEMSIGNALS, "Checking modem signals (%d/%d)\n",
+	            port->c_dcd, sx_get_CD (port));
+
+	if (hi_state & ST_BREAK) {
+		hi_state &= ~ST_BREAK;
+		sx_dprintk (SX_DEBUG_MODEMSIGNALS, "got a break.\n");
+		sx_write_channel_byte (port, hi_state, hi_state);
+		gs_got_break (&port->gs);
+	}
+	if (hi_state & ST_DCD) {
+		hi_state &= ~ST_DCD;
+		sx_dprintk (SX_DEBUG_MODEMSIGNALS, "got a DCD change.\n");
+		sx_write_channel_byte (port, hi_state, hi_state);
+		c_dcd = sx_get_CD (port);
+		sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD is now %d\n", c_dcd);
+		if (c_dcd != port->c_dcd) {
+			port->c_dcd = c_dcd;
+			if (sx_get_CD (port)) {
+				/* DCD went UP */
+				if ((sx_read_channel_byte(port, hi_hstat) != HS_IDLE_CLOSED) &&
+						!(port->gs.tty->termios->c_cflag & CLOCAL) ) {
+					/* Are we blocking in open?*/
+					sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD active, unblocking open\n");
+					wake_up_interruptible(&port->gs.open_wait);
+				} else {
+					sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD raised. Ignoring.\n");
+				}
+			} else {
+				/* DCD went down! */
+				if (!(port->gs.tty->termios->c_cflag & CLOCAL) ) {
+					sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD dropped. hanging up....\n");
+					tty_hangup (port->gs.tty);
+				} else {
+					sx_dprintk (SX_DEBUG_MODEMSIGNALS, "DCD dropped. ignoring.\n");
+				}
+			}
+		} else {
+			sx_dprintk (SX_DEBUG_MODEMSIGNALS, "Hmmm. card told us DCD changed, but it didn't.\n");
+		}
+	}
+}
+
+
+/* This is what an interrupt routine should look like. 
+ * Small, elegant, clear.
+ */
+
+static irqreturn_t sx_interrupt (int irq, void *ptr, struct pt_regs *regs)
+{
+	struct sx_board *board = ptr;
+	struct sx_port *port;
+	int i;
+
+	func_enter ();
+	sx_dprintk (SX_DEBUG_FLOW, "sx: enter sx_interrupt (%d/%d)\n", irq, board->irq); 
+
+	/* AAargh! The order in which to do these things is essential and
+	   not trivial. 
+
+	   - Rate limit goes before "recursive". Otherwise a series of
+	     recursive calls will hang the machine in the interrupt routine. 
+
+	   - hardware twiddling goes before "recursive". Otherwise when we
+	     poll the card, and a recursive interrupt happens, we won't
+	     ack the card, so it might keep on interrupting us. (especially
+	     level sensitive interrupt systems like PCI).
+
+	   - Rate limit goes before hardware twiddling. Otherwise we won't
+	     catch a card that has gone bonkers.
+
+	   - The "initialized" test goes after the hardware twiddling. Otherwise
+	     the card will stick us in the interrupt routine again.
+
+	   - The initialized test goes before recursive. 
+	*/
+
+
+
+#ifdef IRQ_RATE_LIMIT
+	/* Aaargh! I'm ashamed. This costs more lines-of-code than the
+	   actual interrupt routine!. (Well, used to when I wrote that comment) */
+	{
+		static int lastjif;
+		static int nintr=0;
+
+		if (lastjif == jiffies) {
+			if (++nintr > IRQ_RATE_LIMIT) {
+				free_irq (board->irq, board);
+				printk (KERN_ERR "sx: Too many interrupts. Turning off interrupt %d.\n", 
+					      board->irq);
+			}
+		} else {
+			lastjif = jiffies;
+			nintr = 0;
+		}
+	}
+#endif
+
+
+	if (board->irq == irq) {
+		/* Tell the card we've noticed the interrupt. */
+
+		sx_write_board_word (board, cc_int_pending, 0);
+		if (IS_SX_BOARD (board)) {
+			write_sx_byte (board, SX_RESET_IRQ, 1);
+		} else if (IS_EISA_BOARD(board)) {
+			inb(board->eisa_base+0xc03);
+			write_sx_word(board, 8, 0); 
+		} else {
+			write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_CLEAR);
+			write_sx_byte (board, SI2_ISA_INTCLEAR, SI2_ISA_INTCLEAR_SET);
+		}
+	}
+
+	if (!sx_initialized)
+		return IRQ_HANDLED;
+	if (!(board->flags & SX_BOARD_INITIALIZED))
+		return IRQ_HANDLED;
+
+	if (test_and_set_bit (SX_BOARD_INTR_LOCK, &board->locks)) {
+		printk (KERN_ERR "Recursive interrupt! (%d)\n", board->irq);
+		return IRQ_HANDLED;
+	}
+
+	 for (i=0;i<board->nports;i++) {
+		port = &board->ports[i];
+		if (port->gs.flags & GS_ACTIVE) {
+			if (sx_read_channel_byte (port, hi_state)) {
+				sx_dprintk (SX_DEBUG_INTERRUPTS, 
+				            "Port %d: modem signal change?... \n", i);
+				sx_check_modem_signals (port); 
+			}
+			if (port->gs.xmit_cnt) {
+				sx_transmit_chars (port);
+			}
+			if (!(port->gs.flags & SX_RX_THROTTLE)) {
+				sx_receive_chars (port);
+			}
+		}
+	}
+
+	clear_bit (SX_BOARD_INTR_LOCK, &board->locks);
+
+	sx_dprintk (SX_DEBUG_FLOW, "sx: exit sx_interrupt (%d/%d)\n", irq, board->irq); 
+        func_exit ();
+	return IRQ_HANDLED;
+}
+
+
+static void sx_pollfunc (unsigned long data)
+{
+	struct sx_board *board = (struct sx_board *) data;
+
+	func_enter ();
+
+	sx_interrupt (0, board, NULL);
+
+	init_timer(&board->timer);
+
+	board->timer.expires = jiffies + sx_poll;
+	add_timer (&board->timer);
+	func_exit ();
+}
+
+
+
+/* ********************************************************************** *
+ *                Here are the routines that actually                     *
+ *              interface with the generic_serial driver                  *
+ * ********************************************************************** */
+
+/* Ehhm. I don't know how to fiddle with interrupts on the SX card. --REW */
+/* Hmm. Ok I figured it out. You don't.  */
+
+static void sx_disable_tx_interrupts (void * ptr) 
+{
+	struct sx_port *port = ptr; 
+	func_enter2();
+
+	port->gs.flags &= ~GS_TX_INTEN;
+
+	func_exit();
+}
+
+
+static void sx_enable_tx_interrupts (void * ptr) 
+{
+	struct sx_port *port = ptr; 
+	int data_in_buffer;
+	func_enter2();
+
+	/* First transmit the characters that we're supposed to */
+	sx_transmit_chars (port);
+
+	/* The sx card will never interrupt us if we don't fill the buffer
+	   past 25%. So we keep considering interrupts off if that's the case. */
+	data_in_buffer = (sx_read_channel_byte (port, hi_txipos) - 
+	                  sx_read_channel_byte (port, hi_txopos)) & 0xff;
+
+	/* XXX Must be "HIGH_WATER" for SI card according to doc. */
+	if (data_in_buffer < LOW_WATER) 
+		port->gs.flags &= ~GS_TX_INTEN;
+
+	func_exit();
+}
+
+
+static void sx_disable_rx_interrupts (void * ptr) 
+{
+	/*  struct sx_port *port = ptr; */
+	func_enter();
+
+	func_exit();
+}
+
+static void sx_enable_rx_interrupts (void * ptr) 
+{
+	/*  struct sx_port *port = ptr; */
+	func_enter();
+
+	func_exit();
+}
+
+
+/* Jeez. Isn't this simple? */
+static int sx_get_CD (void * ptr) 
+{
+	struct sx_port *port = ptr;
+	func_enter2();
+
+	func_exit();
+	return ((sx_read_channel_byte (port, hi_ip) & IP_DCD) != 0);
+}
+
+
+/* Jeez. Isn't this simple? */
+static int sx_chars_in_buffer (void * ptr) 
+{
+	struct sx_port *port = ptr;
+	func_enter2();
+
+	func_exit();
+	return ((sx_read_channel_byte (port, hi_txipos) - 
+	         sx_read_channel_byte (port, hi_txopos)) & 0xff);
+}
+
+
+static void sx_shutdown_port (void * ptr) 
+{
+	struct sx_port *port = ptr; 
+
+	func_enter();
+
+	port->gs.flags &= ~ GS_ACTIVE;
+	if (port->gs.tty && (port->gs.tty->termios->c_cflag & HUPCL)) {
+		sx_setsignals (port, 0, 0);
+		sx_reconfigure_port(port);
+	}
+
+	func_exit();
+}
+
+
+
+
+
+/* ********************************************************************** *
+ *                Here are the routines that actually                     *
+ *               interface with the rest of the system                    *
+ * ********************************************************************** */
+
+static int sx_open  (struct tty_struct * tty, struct file * filp)
+{
+	struct sx_port *port;
+	int retval, line;
+	unsigned long flags;
+
+	func_enter();
+
+	if (!sx_initialized) {
+		return -EIO;
+	}
+
+	line = tty->index;
+	sx_dprintk (SX_DEBUG_OPEN, "%d: opening line %d. tty=%p ctty=%p, np=%d)\n", 
+	            current->pid, line, tty, current->signal->tty, sx_nports);
+
+	if ((line < 0) || (line >= SX_NPORTS) || (line >= sx_nports))
+		return -ENODEV;
+
+	port = & sx_ports[line];
+	port->c_dcd = 0; /* Make sure that the first interrupt doesn't detect a
+	                    1 -> 0 transition. */
+
+
+	sx_dprintk (SX_DEBUG_OPEN, "port = %p c_dcd = %d\n", port, port->c_dcd);
+
+	spin_lock_irqsave(&port->gs.driver_lock, flags);
+
+	tty->driver_data = port;
+	port->gs.tty = tty;
+	port->gs.count++;
+	spin_unlock_irqrestore(&port->gs.driver_lock, flags);
+
+	sx_dprintk (SX_DEBUG_OPEN, "starting port\n");
+
+	/*
+	 * Start up serial port
+	 */
+	retval = gs_init_port(&port->gs);
+	sx_dprintk (SX_DEBUG_OPEN, "done gs_init\n");
+	if (retval) {
+		port->gs.count--;
+		return retval;
+	}
+
+	port->gs.flags |= GS_ACTIVE;
+	if (port->gs.count <= 1)
+		sx_setsignals (port, 1,1);
+
+#if 0
+	if (sx_debug & SX_DEBUG_OPEN)
+		my_hd (port, sizeof (*port));
+#else
+	if (sx_debug & SX_DEBUG_OPEN)
+		my_hd_io (port->board->base + port->ch_base, sizeof (*port));
+#endif
+
+	if (port->gs.count <= 1) {
+		if (sx_send_command (port, HS_LOPEN, -1, HS_IDLE_OPEN) != 1) {
+			printk (KERN_ERR "sx: Card didn't respond to LOPEN command.\n");
+			spin_lock_irqsave(&port->gs.driver_lock, flags);
+			port->gs.count--;
+			spin_unlock_irqrestore(&port->gs.driver_lock, flags);
+			return -EIO;
+		}
+	}
+
+	retval = gs_block_til_ready(port, filp);
+	sx_dprintk (SX_DEBUG_OPEN, "Block til ready returned %d. Count=%d\n", 
+	            retval, port->gs.count);
+
+	if (retval) {
+		/* 
+		 * Don't lower gs.count here because sx_close() will be called later
+		 */ 
+
+		return retval;
+	}
+	/* tty->low_latency = 1; */
+
+	port->c_dcd = sx_get_CD (port);
+	sx_dprintk (SX_DEBUG_OPEN, "at open: cd=%d\n", port->c_dcd);
+
+	func_exit();
+	return 0;
+
+}
+
+
+static void sx_close (void *ptr)
+{
+	struct sx_port *port = ptr; 
+	/* Give the port 5 seconds to close down. */
+	int to = 5 * HZ; 
+
+	func_enter ();
+
+	sx_setsignals (port, 0, 0);
+	sx_reconfigure_port(port);	
+	sx_send_command (port, HS_CLOSE, 0, 0);
+
+	while (to-- && (sx_read_channel_byte (port, hi_hstat) != HS_IDLE_CLOSED))
+		if (msleep_interruptible(10))
+			break;
+	if (sx_read_channel_byte (port, hi_hstat) != HS_IDLE_CLOSED) {
+		if (sx_send_command (port, HS_FORCE_CLOSED, -1, HS_IDLE_CLOSED) != 1) {
+			printk (KERN_ERR 
+			        "sx: sent the force_close command, but card didn't react\n");
+		} else
+			sx_dprintk (SX_DEBUG_CLOSE, "sent the force_close command.\n");
+	}
+
+	sx_dprintk (SX_DEBUG_CLOSE, "waited %d jiffies for close. count=%d\n", 
+	            5 * HZ - to - 1, port->gs.count);
+
+	if(port->gs.count) {
+		sx_dprintk(SX_DEBUG_CLOSE, "WARNING port count:%d\n", port->gs.count);
+		//printk ("%s SETTING port count to zero: %p count: %d\n", __FUNCTION__, port, port->gs.count);
+		//port->gs.count = 0;
+	}
+
+	func_exit ();
+}
+
+
+
+/* This is relatively thorough. But then again it is only 20 lines. */
+#define MARCHUP    for (i=min;i<max;i++) 
+#define MARCHDOWN  for (i=max-1;i>=min;i--)
+#define W0         write_sx_byte (board, i, 0x55)
+#define W1         write_sx_byte (board, i, 0xaa)
+#define R0         if (read_sx_byte (board, i) != 0x55) return 1
+#define R1         if (read_sx_byte (board, i) != 0xaa) return 1
+
+/* This memtest takes a human-noticable time. You normally only do it
+   once a boot, so I guess that it is worth it. */
+static int do_memtest (struct sx_board *board, int min, int max)
+{
+	int i;
+
+	/* This is a marchb. Theoretically, marchb catches much more than
+	   simpler tests. In practise, the longer test just catches more
+	   intermittent errors. -- REW
+	   (For the theory behind memory testing see: 
+	   Testing Semiconductor Memories by A.J. van de Goor.) */
+	MARCHUP	 {W0;}
+	MARCHUP   {R0;W1;R1;W0;R0;W1;}
+	MARCHUP   {R1;W0;W1;}
+	MARCHDOWN {R1;W0;W1;W0;}
+	MARCHDOWN {R0;W1;W0;}
+
+	return 0;
+}
+
+
+#undef MARCHUP
+#undef MARCHDOWN
+#undef W0
+#undef W1
+#undef R0
+#undef R1
+
+#define MARCHUP    for (i=min;i<max;i+=2) 
+#define MARCHDOWN  for (i=max-1;i>=min;i-=2)
+#define W0         write_sx_word (board, i, 0x55aa)
+#define W1         write_sx_word (board, i, 0xaa55)
+#define R0         if (read_sx_word (board, i) != 0x55aa) return 1
+#define R1         if (read_sx_word (board, i) != 0xaa55) return 1
+
+#if 0
+/* This memtest takes a human-noticable time. You normally only do it
+   once a boot, so I guess that it is worth it. */
+static int do_memtest_w (struct sx_board *board, int min, int max)
+{
+	int i;
+
+	MARCHUP   {W0;}
+	MARCHUP   {R0;W1;R1;W0;R0;W1;}
+	MARCHUP   {R1;W0;W1;}
+	MARCHDOWN {R1;W0;W1;W0;}
+	MARCHDOWN {R0;W1;W0;}
+
+	return 0;
+}
+#endif
+
+
+static int sx_fw_ioctl (struct inode *inode, struct file *filp,
+                        unsigned int cmd, unsigned long arg)
+{
+	int rc = 0;
+	int __user *descr = (int __user *)arg;
+	int i;
+	static struct sx_board *board = NULL;
+	int nbytes, offset;
+	unsigned long data;
+	char *tmp;
+
+	func_enter();
+
+#if 0 
+	/* Removed superuser check: Sysops can use the permissions on the device
+	   file to restrict access. Recommendation: Root only. (root.root 600) */
+	if (!capable(CAP_SYS_ADMIN)) {
+		return -EPERM;
+	}
+#endif
+
+	sx_dprintk (SX_DEBUG_FIRMWARE, "IOCTL %x: %lx\n", cmd, arg);
+
+	if (!board) board = &boards[0];
+	if (board->flags & SX_BOARD_PRESENT) {
+		sx_dprintk (SX_DEBUG_FIRMWARE, "Board present! (%x)\n", 
+		            board->flags);
+	} else {
+		sx_dprintk (SX_DEBUG_FIRMWARE, "Board not present! (%x) all:", 
+		            board->flags);
+		for (i=0;i< SX_NBOARDS;i++)
+			sx_dprintk (SX_DEBUG_FIRMWARE, "<%x> ", boards[i].flags);
+		sx_dprintk (SX_DEBUG_FIRMWARE, "\n");
+		return -EIO;
+	}
+
+	switch (cmd) {
+	case SXIO_SET_BOARD:
+		sx_dprintk (SX_DEBUG_FIRMWARE, "set board to %ld\n", arg);
+		if (arg >= SX_NBOARDS) return -EIO;
+		sx_dprintk (SX_DEBUG_FIRMWARE, "not out of range\n");
+		if (!(boards[arg].flags	& SX_BOARD_PRESENT)) return -EIO;
+		sx_dprintk (SX_DEBUG_FIRMWARE, ".. and present!\n");
+		board = &boards[arg];
+		break;
+	case SXIO_GET_TYPE:
+		rc = -ENOENT; /* If we manage to miss one, return error. */
+		if (IS_SX_BOARD (board)) rc = SX_TYPE_SX;
+		if (IS_CF_BOARD (board)) rc = SX_TYPE_CF;
+		if (IS_SI_BOARD (board)) rc = SX_TYPE_SI;
+		if (IS_SI1_BOARD (board)) rc = SX_TYPE_SI;
+		if (IS_EISA_BOARD (board)) rc = SX_TYPE_SI;
+		sx_dprintk (SX_DEBUG_FIRMWARE, "returning type= %d\n", rc);
+		break;
+	case SXIO_DO_RAMTEST:
+		if (sx_initialized) /* Already initialized: better not ramtest the board.  */
+			return -EPERM;
+		if (IS_SX_BOARD (board)) {
+			rc          = do_memtest   (board, 0, 0x7000);
+			if (!rc) rc = do_memtest   (board, 0, 0x7000);
+			/*if (!rc) rc = do_memtest_w (board, 0, 0x7000);*/
+		} else {
+			rc             = do_memtest   (board, 0, 0x7ff8);
+			/* if (!rc) rc = do_memtest_w (board, 0, 0x7ff8); */
+		}
+		sx_dprintk (SX_DEBUG_FIRMWARE, "returning memtest result= %d\n", rc);
+		break;
+	case SXIO_DOWNLOAD:
+		if (sx_initialized) /* Already initialized */
+			return -EEXIST;
+		if (!sx_reset (board)) 
+			return -EIO;
+		sx_dprintk (SX_DEBUG_INIT, "reset the board...\n");
+
+		tmp = kmalloc (SX_CHUNK_SIZE, GFP_USER);
+		if (!tmp) return -ENOMEM;
+		get_user (nbytes, descr++);
+		get_user (offset, descr++); 
+		get_user (data,	 descr++);
+		while (nbytes && data) {
+			for (i=0;i<nbytes;i += SX_CHUNK_SIZE) {
+				if (copy_from_user(tmp, (char __user *)data+i, 
+						   (i + SX_CHUNK_SIZE >
+						    nbytes) ? nbytes - i :
+						   	      SX_CHUNK_SIZE)) {
+					kfree (tmp);
+					return -EFAULT;
+				}
+				memcpy_toio(board->base2 + offset + i, tmp, 
+				                (i+SX_CHUNK_SIZE>nbytes)?nbytes-i:SX_CHUNK_SIZE);
+			}
+
+			get_user (nbytes, descr++);
+			get_user (offset, descr++); 
+			get_user (data,   descr++);
+		}
+		kfree (tmp);
+		sx_nports += sx_init_board (board);
+		rc = sx_nports;
+		break;
+	case SXIO_INIT:
+		if (sx_initialized) /* Already initialized */
+			return -EEXIST;
+		/* This is not allowed until all boards are initialized... */
+		for (i=0;i<SX_NBOARDS;i++) {
+			if ( (boards[i].flags & SX_BOARD_PRESENT) &&
+			     !(boards[i].flags & SX_BOARD_INITIALIZED))
+				return -EIO;
+		}
+		for (i=0;i<SX_NBOARDS;i++)
+			if (!(boards[i].flags & SX_BOARD_PRESENT)) break;
+
+		sx_dprintk (SX_DEBUG_FIRMWARE, "initing portstructs, %d boards, "
+		            "%d channels, first board: %d ports\n", 
+		            i, sx_nports, boards[0].nports);
+		rc = sx_init_portstructs (i, sx_nports);
+		sx_init_drivers ();
+		if (rc >= 0) 
+			sx_initialized++;
+		break;
+	case SXIO_SETDEBUG:
+		sx_debug = arg;
+		break;
+	case SXIO_GETDEBUG:
+		rc = sx_debug;
+		break;
+	case SXIO_GETGSDEBUG:
+	case SXIO_SETGSDEBUG:
+		rc = -EINVAL;
+		break;
+	case SXIO_GETNPORTS:
+		rc = sx_nports;
+		break;
+	default:
+		printk (KERN_WARNING "Unknown ioctl on firmware device (%x).\n", cmd);
+		break;
+	}
+	func_exit ();
+	return rc;
+}
+
+
+static void sx_break (struct tty_struct * tty, int flag)
+{
+	struct sx_port *port = tty->driver_data;
+	int rv;
+
+	func_enter ();
+
+	if (flag) 
+		rv = sx_send_command (port, HS_START, -1, HS_IDLE_BREAK);
+	else 
+		rv = sx_send_command (port, HS_STOP, -1, HS_IDLE_OPEN);
+	if (rv != 1) printk (KERN_ERR "sx: couldn't send break (%x).\n",
+			read_sx_byte (port->board, CHAN_OFFSET (port, hi_hstat)));
+
+	func_exit ();
+}
+
+
+static int sx_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct sx_port *port = tty->driver_data;
+	return sx_getsignals(port);
+}
+
+static int sx_tiocmset(struct tty_struct *tty, struct file *file,
+		       unsigned int set, unsigned int clear)
+{
+	struct sx_port *port = tty->driver_data;
+	int rts = -1, dtr = -1;
+
+	if (set & TIOCM_RTS)
+		rts = 1;
+	if (set & TIOCM_DTR)
+		dtr = 1;
+	if (clear & TIOCM_RTS)
+		rts = 0;
+	if (clear & TIOCM_DTR)
+		dtr = 0;
+
+	sx_setsignals(port, dtr, rts);
+	sx_reconfigure_port(port);
+	return 0;
+}
+
+static int sx_ioctl (struct tty_struct * tty, struct file * filp, 
+                     unsigned int cmd, unsigned long arg)
+{
+	int rc;
+	struct sx_port *port = tty->driver_data;
+	void __user *argp = (void __user *)arg;
+	int ival;
+
+	/* func_enter2(); */
+
+	rc = 0;
+	switch (cmd) {
+	case TIOCGSOFTCAR:
+		rc = put_user(((tty->termios->c_cflag & CLOCAL) ? 1 : 0),
+		              (unsigned __user *) argp);
+		break;
+	case TIOCSSOFTCAR:
+		if ((rc = get_user(ival, (unsigned __user *) argp)) == 0) {
+			tty->termios->c_cflag =
+				(tty->termios->c_cflag & ~CLOCAL) |
+				(ival ? CLOCAL : 0);
+		}
+		break;
+	case TIOCGSERIAL:
+		rc = gs_getserial(&port->gs, argp);
+		break;
+	case TIOCSSERIAL:
+		rc = gs_setserial(&port->gs, argp);
+		break;
+	default:
+		rc = -ENOIOCTLCMD;
+		break;
+	}
+
+	/* func_exit(); */
+	return rc;
+}
+
+
+/* The throttle/unthrottle scheme for the Specialix card is different
+ * from other drivers and deserves some explanation. 
+ * The Specialix hardware takes care of XON/XOFF
+ * and CTS/RTS flow control itself.  This means that all we have to
+ * do when signalled by the upper tty layer to throttle/unthrottle is
+ * to make a note of it here.  When we come to read characters from the
+ * rx buffers on the card (sx_receive_chars()) we look to see if the
+ * upper layer can accept more (as noted here in sx_rx_throt[]). 
+ * If it can't we simply don't remove chars from the cards buffer. 
+ * When the tty layer can accept chars, we again note that here and when
+ * sx_receive_chars() is called it will remove them from the cards buffer.
+ * The card will notice that a ports buffer has drained below some low
+ * water mark and will unflow control the line itself, using whatever
+ * flow control scheme is in use for that port. -- Simon Allen
+ */
+
+static void sx_throttle (struct tty_struct * tty)
+{
+	struct sx_port *port = (struct sx_port *)tty->driver_data;
+
+	func_enter2();
+	/* If the port is using any type of input flow
+	 * control then throttle the port.
+	 */
+	if((tty->termios->c_cflag & CRTSCTS) || (I_IXOFF(tty)) ) {
+		port->gs.flags |= SX_RX_THROTTLE;
+	}
+	func_exit();
+}
+
+
+static void sx_unthrottle (struct tty_struct * tty)
+{
+	struct sx_port *port = (struct sx_port *)tty->driver_data;
+
+	func_enter2();
+	/* Always unthrottle even if flow control is not enabled on
+	 * this port in case we disabled flow control while the port
+	 * was throttled
+	 */
+	port->gs.flags &= ~SX_RX_THROTTLE;
+	func_exit();
+	return;
+}
+
+
+/* ********************************************************************** *
+ *                    Here are the initialization routines.               *
+ * ********************************************************************** */
+
+
+
+
+static int sx_init_board (struct sx_board *board)
+{
+	int addr;
+	int chans;
+	int type;
+
+	func_enter();
+
+	/* This is preceded by downloading the download code. */
+
+	board->flags |= SX_BOARD_INITIALIZED;
+
+	if (read_sx_byte (board, 0))
+		/* CF boards may need this. */
+		write_sx_byte(board,0, 0);
+
+	/* This resets the processor again, to make sure it didn't do any
+	   foolish things while we were downloading the image */
+	if (!sx_reset (board))
+		return 0;
+
+	sx_start_board (board);
+	udelay (10);
+	if (!sx_busy_wait_neq (board, 0, 0xff, 0)) {
+		printk (KERN_ERR "sx: Ooops. Board won't initialize.\n");
+		return 0;
+	}
+
+	/* Ok. So now the processor on the card is running. It gathered
+	   some info for us... */
+	sx_dprintk (SX_DEBUG_INIT, "The sxcard structure:\n");
+	if (sx_debug & SX_DEBUG_INIT) my_hd_io (board->base, 0x10);
+	sx_dprintk (SX_DEBUG_INIT, "the first sx_module structure:\n");
+	if (sx_debug & SX_DEBUG_INIT) my_hd_io (board->base + 0x80, 0x30);
+
+	sx_dprintk (SX_DEBUG_INIT, 
+	            "init_status: %x, %dk memory, firmware V%x.%02x,\n", 
+	            read_sx_byte (board, 0), read_sx_byte(board, 1), 
+	            read_sx_byte (board, 5), read_sx_byte(board, 4));
+
+	if (read_sx_byte (board, 0) == 0xff) {
+		printk (KERN_INFO "sx: No modules found. Sorry.\n");
+		board->nports = 0;
+		return 0;
+	}
+
+	chans = 0;
+
+	if (IS_SX_BOARD(board)) {
+		sx_write_board_word (board, cc_int_count, sx_maxints);
+	} else {
+		if (sx_maxints)
+			sx_write_board_word (board, cc_int_count, SI_PROCESSOR_CLOCK/8/sx_maxints);
+	}
+
+	/* grab the first module type... */
+	/*  board->ta_type = mod_compat_type (read_sx_byte (board, 0x80 + 0x08)); */
+	board->ta_type = mod_compat_type (sx_read_module_byte (board, 0x80, mc_chip));
+
+	/* XXX byteorder */
+	for (addr = 0x80;addr != 0;addr = read_sx_word (board, addr) & 0x7fff) {
+		type = sx_read_module_byte (board, addr, mc_chip);
+		sx_dprintk (SX_DEBUG_INIT, "Module at %x: %d channels\n", 
+		            addr, read_sx_byte (board, addr + 2));
+
+		chans += sx_read_module_byte (board, addr, mc_type);
+
+		sx_dprintk (SX_DEBUG_INIT, "module is an %s, which has %s/%s panels\n", 
+		            mod_type_s (type),
+		            pan_type_s (sx_read_module_byte (board, addr, mc_mods) & 0xf),
+		            pan_type_s (sx_read_module_byte (board, addr, mc_mods) >> 4));
+
+		sx_dprintk (SX_DEBUG_INIT, "CD1400 versions: %x/%x, ASIC version: %x\n", 
+		            sx_read_module_byte (board, addr, mc_rev1),
+		            sx_read_module_byte (board, addr, mc_rev2),
+		            sx_read_module_byte (board, addr, mc_mtaasic_rev));
+
+		/* The following combinations are illegal: It should theoretically
+		   work, but timing problems make the bus HANG. */
+
+		if (mod_compat_type (type) != board->ta_type) {
+			printk (KERN_ERR "sx: This is an invalid configuration.\n"
+			        "Don't mix TA/MTA/SXDC on the same hostadapter.\n");
+			chans=0;
+			break;
+		}
+		if ((IS_EISA_BOARD(board) || 
+		     IS_SI_BOARD(board)) && (mod_compat_type(type) == 4)) {
+			printk (KERN_ERR "sx: This is an invalid configuration.\n"
+			        "Don't use SXDCs on an SI/XIO adapter.\n");
+			chans=0;
+			break;
+		}
+#if 0 /* Problem fixed: firmware 3.05 */
+		if (IS_SX_BOARD(board) && (type == TA8)) {
+			/* There are some issues with the firmware and the DCD/RTS
+			   lines. It might work if you tie them together or something.
+			   It might also work if you get a newer sx_firmware.	Therefore
+			   this is just a warning. */
+			printk (KERN_WARNING "sx: The SX host doesn't work too well "
+			        "with the TA8 adapters.\nSpecialix is working on it.\n");
+		}
+#endif
+	}
+
+	if (chans) {
+		/* board->flags |= SX_BOARD_PRESENT; */
+		if(board->irq > 0) {
+			/* fixed irq, probably PCI */
+			if(sx_irqmask & (1 << board->irq)) { /* may we use this irq? */
+				if(request_irq(board->irq, sx_interrupt, SA_SHIRQ | SA_INTERRUPT, "sx", board)) {
+					printk(KERN_ERR "sx: Cannot allocate irq %d.\n", board->irq);
+					board->irq = 0;
+				}
+			} else
+				board->irq = 0;
+		} else if(board->irq < 0 && sx_irqmask) {
+			/* auto-allocate irq */
+			int irqnr;
+			int irqmask = sx_irqmask & (IS_SX_BOARD(board) ? SX_ISA_IRQ_MASK : SI2_ISA_IRQ_MASK);
+			for(irqnr = 15; irqnr > 0; irqnr--)
+				if(irqmask & (1 << irqnr))
+					if(! request_irq(irqnr, sx_interrupt, SA_SHIRQ | SA_INTERRUPT, "sx", board))
+						break;
+			if(! irqnr)
+				printk(KERN_ERR "sx: Cannot allocate IRQ.\n");
+			board->irq = irqnr;
+		} else
+			board->irq = 0;
+
+		if (board->irq) {
+			/* Found a valid interrupt, start up interrupts! */
+			sx_dprintk (SX_DEBUG_INIT, "Using irq %d.\n", board->irq);
+			sx_start_interrupts (board);
+			board->poll = sx_slowpoll;
+			board->flags |= SX_IRQ_ALLOCATED;
+		} else {
+			/* no irq: setup board for polled operation */
+			board->poll = sx_poll;
+			sx_dprintk (SX_DEBUG_INIT, "Using poll-interval %d.\n", board->poll);
+		}
+
+		/* The timer should be initialized anyway: That way we can safely
+			 del_timer it when the module is unloaded. */
+		init_timer (&board->timer);
+
+		if (board->poll) {
+			board->timer.data = (unsigned long) board;
+			board->timer.function = sx_pollfunc;
+			board->timer.expires = jiffies + board->poll;
+			add_timer (&board->timer);
+		}
+	} else {
+		board->irq = 0;
+	}
+
+	board->nports = chans;
+	sx_dprintk (SX_DEBUG_INIT, "returning %d ports.", board->nports);
+
+	func_exit();
+	return chans;
+}
+
+
+static void printheader(void)
+{
+	static int header_printed;
+
+	if (!header_printed) {
+		printk (KERN_INFO "Specialix SX driver "
+		        "(C) 1998/1999 R.E.Wolff@BitWizard.nl \n");
+		printk (KERN_INFO "sx: version %s\n", RCS_ID);
+		header_printed = 1;
+	}
+}
+
+
+static int probe_sx (struct sx_board *board)
+{
+	struct vpd_prom vpdp;
+	char *p;
+	int i;
+
+	func_enter();
+
+	if (!IS_CF_BOARD (board)) {    
+		sx_dprintk (SX_DEBUG_PROBE, "Going to verify vpd prom at %p.\n", 
+		            board->base + SX_VPD_ROM);
+
+		if (sx_debug & SX_DEBUG_PROBE)
+			my_hd_io(board->base + SX_VPD_ROM, 0x40);
+
+		p = (char *) &vpdp;
+		for (i=0;i< sizeof (struct vpd_prom);i++)
+			*p++ = read_sx_byte (board, SX_VPD_ROM + i*2);
+
+		if (sx_debug & SX_DEBUG_PROBE)
+			my_hd (&vpdp, 0x20);
+
+		sx_dprintk (SX_DEBUG_PROBE, "checking identifier...\n");
+
+		if (strncmp (vpdp.identifier, SX_VPD_IDENT_STRING, 16) != 0) {
+			sx_dprintk (SX_DEBUG_PROBE, "Got non-SX identifier: '%s'\n", 
+			            vpdp.identifier); 
+			return 0;
+		}
+	}
+
+	printheader ();
+
+	if (!IS_CF_BOARD (board)) {
+		printk (KERN_DEBUG "sx: Found an SX board at %lx\n", board->hw_base);
+		printk (KERN_DEBUG "sx: hw_rev: %d, assembly level: %d, uniq ID:%08x, ", 
+		        vpdp.hwrev, vpdp.hwass, vpdp.uniqid);
+		printk (           "Manufactured: %d/%d\n", 
+		        1970 + vpdp.myear, vpdp.mweek);
+
+
+		if ((((vpdp.uniqid >> 24) & SX_UNIQUEID_MASK) != SX_PCI_UNIQUEID1) &&
+		    (((vpdp.uniqid >> 24) & SX_UNIQUEID_MASK) != SX_ISA_UNIQUEID1)) {
+			/* This might be a bit harsh. This was the primary reason the
+			   SX/ISA card didn't work at first... */
+			printk (KERN_ERR "sx: Hmm. Not an SX/PCI or SX/ISA card. Sorry: giving up.\n");
+			return (0);
+		}
+
+		if (((vpdp.uniqid >> 24) & SX_UNIQUEID_MASK) == SX_ISA_UNIQUEID1) {
+			if (((unsigned long)board->hw_base) & 0x8000) {
+				printk (KERN_WARNING "sx: Warning: There may be hardware problems with the card at %lx.\n", board->hw_base);
+				printk (KERN_WARNING "sx: Read sx.txt for more info.\n");
+			}
+		}
+	}
+
+	board->nports = -1;
+
+	/* This resets the processor, and keeps it off the bus. */
+	if (!sx_reset (board)) 
+		return 0;
+	sx_dprintk (SX_DEBUG_INIT, "reset the board...\n");
+
+	board->flags |= SX_BOARD_PRESENT;
+
+	func_exit();
+	return 1;
+}
+
+
+
+/* Specialix probes for this card at 32k increments from 640k to 16M.
+   I consider machines with less than 16M unlikely nowadays, so I'm
+   not probing above 1Mb. Also, 0xa0000, 0xb0000, are taken by the VGA
+   card. 0xe0000 and 0xf0000 are taken by the BIOS. That only leaves 
+   0xc0000, 0xc8000, 0xd0000 and 0xd8000 . */
+
+static int probe_si (struct sx_board *board)
+{
+	int i;
+
+	func_enter();
+	sx_dprintk (SX_DEBUG_PROBE, "Going to verify SI signature hw %lx at %p.\n", board->hw_base,
+	            board->base + SI2_ISA_ID_BASE);
+
+	if (sx_debug & SX_DEBUG_PROBE)
+		my_hd_io(board->base + SI2_ISA_ID_BASE, 0x8);
+
+	if (!IS_EISA_BOARD(board)) {
+	  if( IS_SI1_BOARD(board) ) 
+	    {
+		for (i=0;i<8;i++) {
+		  write_sx_byte (board, SI2_ISA_ID_BASE+7-i,i); 
+
+		}
+	    }
+		for (i=0;i<8;i++) {
+			if ((read_sx_byte (board, SI2_ISA_ID_BASE+7-i) & 7) != i) {
+				func_exit ();
+				return 0;
+			}
+		}
+	}
+
+	/* Now we're pretty much convinced that there is an SI board here, 
+	   but to prevent trouble, we'd better double check that we don't
+	   have an SI1 board when we're probing for an SI2 board.... */
+
+	write_sx_byte (board, SI2_ISA_ID_BASE,0x10); 
+	if ( IS_SI1_BOARD(board)) {
+		/* This should be an SI1 board, which has this
+		   location writable... */
+		if (read_sx_byte (board, SI2_ISA_ID_BASE) != 0x10)
+			func_exit ();
+			return 0; 
+	} else {
+		/* This should be an SI2 board, which has the bottom
+		   3 bits non-writable... */
+		if (read_sx_byte (board, SI2_ISA_ID_BASE) == 0x10)
+			func_exit ();
+			return 0; 
+	}
+
+	/* Now we're pretty much convinced that there is an SI board here, 
+	   but to prevent trouble, we'd better double check that we don't
+	   have an SI1 board when we're probing for an SI2 board.... */
+
+	write_sx_byte (board, SI2_ISA_ID_BASE,0x10); 
+	if ( IS_SI1_BOARD(board)) {
+		/* This should be an SI1 board, which has this
+		   location writable... */
+		if (read_sx_byte (board, SI2_ISA_ID_BASE) != 0x10)
+			func_exit();
+			return 0; 
+	} else {
+		/* This should be an SI2 board, which has the bottom
+		   3 bits non-writable... */
+		if (read_sx_byte (board, SI2_ISA_ID_BASE) == 0x10)
+			func_exit ();
+			return 0; 
+	}
+
+	printheader ();
+
+	printk (KERN_DEBUG "sx: Found an SI board at %lx\n", board->hw_base);
+	/* Compared to the SX boards, it is a complete guess as to what
+		 this card is up to... */
+
+	board->nports = -1;
+
+	/* This resets the processor, and keeps it off the bus. */
+	if (!sx_reset (board)) 
+		return 0;
+	sx_dprintk (SX_DEBUG_INIT, "reset the board...\n");
+
+	board->flags |= SX_BOARD_PRESENT;
+
+	func_exit();
+	return 1;
+}
+
+static struct tty_operations sx_ops = {
+	.break_ctl = sx_break,
+	.open	= sx_open,
+	.close = gs_close,
+	.write = gs_write,
+	.put_char = gs_put_char,
+	.flush_chars = gs_flush_chars,
+	.write_room = gs_write_room,
+	.chars_in_buffer = gs_chars_in_buffer,
+	.flush_buffer = gs_flush_buffer,
+	.ioctl = sx_ioctl,
+	.throttle = sx_throttle,
+	.unthrottle = sx_unthrottle,
+	.set_termios = gs_set_termios,
+	.stop = gs_stop,
+	.start = gs_start,
+	.hangup = gs_hangup,
+	.tiocmget = sx_tiocmget,
+	.tiocmset = sx_tiocmset,
+};
+
+static int sx_init_drivers(void)
+{
+	int error;
+
+	func_enter();
+
+	sx_driver = alloc_tty_driver(sx_nports);
+	if (!sx_driver)
+		return 1;
+	sx_driver->owner = THIS_MODULE;
+	sx_driver->driver_name = "specialix_sx";
+	sx_driver->name = "ttyX";
+	sx_driver->major = SX_NORMAL_MAJOR;
+	sx_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	sx_driver->subtype = SERIAL_TYPE_NORMAL;
+	sx_driver->init_termios = tty_std_termios;
+	sx_driver->init_termios.c_cflag =
+	  B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	sx_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(sx_driver, &sx_ops);
+
+	if ((error = tty_register_driver(sx_driver))) {
+		put_tty_driver(sx_driver);
+		printk(KERN_ERR "sx: Couldn't register sx driver, error = %d\n",
+		       error);
+		return 1;
+	}
+	func_exit();
+	return 0;
+}
+
+
+static void * ckmalloc (int size)
+{
+	void *p;
+
+	p = kmalloc(size, GFP_KERNEL);
+	if (p) 
+		memset(p, 0, size);
+	return p;
+}
+
+
+static int sx_init_portstructs (int nboards, int nports)
+{
+	struct sx_board *board;
+	struct sx_port *port;
+	int i, j;
+	int addr, chans;
+	int portno;
+
+	func_enter();
+
+	/* Many drivers statically allocate the maximum number of ports
+	   There is no reason not to allocate them dynamically. Is there? -- REW */
+	sx_ports          = ckmalloc(nports * sizeof (struct sx_port));
+	if (!sx_ports)
+		return -ENOMEM;
+
+	port = sx_ports;
+	for (i = 0; i < nboards; i++) {
+		board = &boards[i];
+		board->ports = port;
+		for (j=0; j < boards[i].nports;j++) {
+			sx_dprintk (SX_DEBUG_INIT, "initing port %d\n", j);
+			port->gs.magic = SX_MAGIC;
+			port->gs.close_delay = HZ/2;
+			port->gs.closing_wait = 30 * HZ;
+			port->board = board;
+			port->gs.rd = &sx_real_driver;
+#ifdef NEW_WRITE_LOCKING
+			port->gs.port_write_sem = MUTEX;
+#endif
+			port->gs.driver_lock = SPIN_LOCK_UNLOCKED;
+			/*
+			 * Initializing wait queue
+			 */
+			init_waitqueue_head(&port->gs.open_wait);
+			init_waitqueue_head(&port->gs.close_wait); 		
+			
+			port++;
+		}
+	}
+
+	port = sx_ports;
+	portno = 0;
+	for (i = 0; i < nboards; i++) {
+		board = &boards[i];
+		board->port_base = portno;
+		/* Possibly the configuration was rejected. */
+		sx_dprintk (SX_DEBUG_PROBE, "Board has %d channels\n", board->nports);
+		if (board->nports <= 0) continue;
+		/* XXX byteorder ?? */
+		for (addr = 0x80;addr != 0;addr = read_sx_word (board, addr) & 0x7fff) {
+			chans = sx_read_module_byte (board, addr, mc_type); 
+			sx_dprintk (SX_DEBUG_PROBE, "Module at %x: %d channels\n", addr, chans);
+			sx_dprintk (SX_DEBUG_PROBE, "Port at");
+			for (j=0;j<chans;j++) {
+				/* The "sx-way" is the way it SHOULD be done. That way in the 
+				   future, the firmware may for example pack the structures a bit
+				   more efficient. Neil tells me it isn't going to happen anytime
+				   soon though. */
+				if (IS_SX_BOARD(board))
+					port->ch_base = sx_read_module_word (board, addr+j*2, mc_chan_pointer);
+				else
+					port->ch_base = addr + 0x100 + 0x300*j;
+
+				sx_dprintk (SX_DEBUG_PROBE, " %x", port->ch_base);
+				port->line = portno++;
+				port++;
+			}
+			sx_dprintk (SX_DEBUG_PROBE, "\n");
+		}
+		/* This has to be done earlier. */
+		/* board->flags |= SX_BOARD_INITIALIZED; */
+	}
+
+	func_exit();
+	return 0;
+}
+
+static void __exit sx_release_drivers(void)
+{
+	func_enter();
+	tty_unregister_driver(sx_driver);
+	put_tty_driver(sx_driver);
+	func_exit();
+}
+
+#ifdef CONFIG_PCI
+ /******************************************************** 
+ * Setting bit 17 in the CNTRL register of the PLX 9050  * 
+ * chip forces a retry on writes while a read is pending.*
+ * This is to prevent the card locking up on Intel Xeon  *
+ * multiprocessor systems with the NX chipset.    -- NV  *
+ ********************************************************/
+
+/* Newer cards are produced with this bit set from the configuration
+   EEprom.  As the bit is read/write for the CPU, we can fix it here,
+   if we detect that it isn't set correctly. -- REW */
+
+static void fix_sx_pci (struct pci_dev *pdev, struct sx_board *board)
+{
+	unsigned int hwbase;
+	void __iomem *rebase;
+	unsigned int t;
+
+#define CNTRL_REG_OFFSET        0x50
+#define CNTRL_REG_GOODVALUE     0x18260000
+
+	pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &hwbase);
+	hwbase &= PCI_BASE_ADDRESS_MEM_MASK;
+	rebase = ioremap(hwbase, 0x80);
+	t = readl (rebase + CNTRL_REG_OFFSET);
+	if (t != CNTRL_REG_GOODVALUE) {
+		printk (KERN_DEBUG "sx: performing cntrl reg fix: %08x -> %08x\n", t, CNTRL_REG_GOODVALUE); 
+		writel (CNTRL_REG_GOODVALUE, rebase + CNTRL_REG_OFFSET);
+	}
+	iounmap(rebase);
+}
+#endif
+
+
+static int __init sx_init(void) 
+{
+	int i;
+	int found = 0;
+	int eisa_slot;
+	struct sx_board *board;
+
+#ifdef CONFIG_PCI
+	struct pci_dev *pdev = NULL;
+	unsigned int tint;
+	unsigned short tshort;
+#endif
+
+	func_enter();
+	sx_dprintk (SX_DEBUG_INIT, "Initing sx module... (sx_debug=%d)\n", sx_debug);
+	if (abs ((long) (&sx_debug) - sx_debug) < 0x10000) {
+		printk (KERN_WARNING "sx: sx_debug is an address, instead of a value. "
+		        "Assuming -1.\n");
+		printk ("(%p)\n", &sx_debug);
+		sx_debug=-1;
+	}
+
+	if (misc_register(&sx_fw_device) < 0) {
+		printk(KERN_ERR "SX: Unable to register firmware loader driver.\n");
+		return -EIO;
+	}
+
+#ifdef CONFIG_PCI
+	while ((pdev = pci_find_device (PCI_VENDOR_ID_SPECIALIX, 
+					PCI_DEVICE_ID_SPECIALIX_SX_XIO_IO8, 
+					      pdev))) {
+		if (pci_enable_device(pdev))
+			continue;
+
+		/* Specialix has a whole bunch of cards with
+		   0x2000 as the device ID. They say its because
+		   the standard requires it. Stupid standard. */
+		/* It seems that reading a word doesn't work reliably on 2.0.
+		   Also, reading a non-aligned dword doesn't work. So we read the
+		   whole dword at 0x2c and extract the word at 0x2e (SUBSYSTEM_ID)
+		   ourselves */
+		/* I don't know why the define doesn't work, constant 0x2c does --REW */ 
+		pci_read_config_dword (pdev, 0x2c, &tint);
+		tshort = (tint >> 16) & 0xffff;
+		sx_dprintk (SX_DEBUG_PROBE, "Got a specialix card: %x.\n", tint);
+		/* sx_dprintk (SX_DEBUG_PROBE, "pdev = %d/%d	(%x)\n", pdev, tint); */ 
+		if ((tshort != 0x0200) && (tshort != 0x0300)) {
+			sx_dprintk (SX_DEBUG_PROBE, "But it's not an SX card (%d)...\n", 
+				    tshort);
+			continue;
+		}
+		board = &boards[found];
+
+		board->flags &= ~SX_BOARD_TYPE;
+		board->flags |= (tshort == 0x200)?SX_PCI_BOARD:
+						  SX_CFPCI_BOARD;
+
+		/* CF boards use base address 3.... */
+		if (IS_CF_BOARD (board))
+			board->hw_base = pci_resource_start (pdev, 3);
+		else
+			board->hw_base = pci_resource_start (pdev, 2);
+		board->base2 = 
+		board->base = ioremap(board->hw_base, WINDOW_LEN (board));
+		if (!board->base) {
+			printk(KERN_ERR "ioremap failed\n");
+			/* XXX handle error */
+		}
+
+		/* Most of the stuff on the CF board is offset by
+		   0x18000 ....  */
+		if (IS_CF_BOARD (board)) board->base += 0x18000;
+
+		board->irq = pdev->irq;
+
+		sx_dprintk (SX_DEBUG_PROBE, "Got a specialix card: %x/%p(%d) %x.\n", 
+			    tint, boards[found].base, board->irq, board->flags);
+
+		if (probe_sx (board)) {
+			found++;
+			fix_sx_pci (pdev, board);
+		} else 
+			iounmap(board->base2);
+	}
+#endif
+
+	for (i=0;i<NR_SX_ADDRS;i++) {
+		board = &boards[found];
+		board->hw_base = sx_probe_addrs[i];
+		board->base2 =
+		board->base = ioremap(board->hw_base, SX_WINDOW_LEN);
+		board->flags &= ~SX_BOARD_TYPE;
+		board->flags |=	SX_ISA_BOARD;
+		board->irq = sx_irqmask?-1:0;
+
+		if (probe_sx (board)) {
+			found++;
+		} else {
+			iounmap(board->base);
+		}
+	}
+
+	for (i=0;i<NR_SI_ADDRS;i++) {
+		board = &boards[found];
+		board->hw_base = si_probe_addrs[i];
+		board->base2 =
+		board->base = ioremap(board->hw_base, SI2_ISA_WINDOW_LEN);
+		board->flags &= ~SX_BOARD_TYPE;
+		board->flags |=  SI_ISA_BOARD;
+		board->irq = sx_irqmask ?-1:0;
+
+		if (probe_si (board)) {
+			found++;
+		} else {
+			iounmap (board->base);
+		}
+	}
+	for (i=0;i<NR_SI1_ADDRS;i++) {
+		board = &boards[found];
+		board->hw_base = si1_probe_addrs[i];
+		board->base2 =
+		board->base = ioremap(board->hw_base, SI1_ISA_WINDOW_LEN);
+		board->flags &= ~SX_BOARD_TYPE;
+		board->flags |=  SI1_ISA_BOARD;
+		board->irq = sx_irqmask ?-1:0;
+
+		if (probe_si (board)) {
+			found++;
+		} else {
+			iounmap (board->base);
+		}
+	}
+
+        sx_dprintk(SX_DEBUG_PROBE, "Probing for EISA cards\n");
+        for(eisa_slot=0x1000; eisa_slot<0x10000; eisa_slot+=0x1000)
+        {
+                if((inb(eisa_slot+0xc80)==0x4d) &&
+                   (inb(eisa_slot+0xc81)==0x98))
+                {
+			sx_dprintk(SX_DEBUG_PROBE, "%s : Signature found in EISA slot %d, Product %d Rev %d\n",
+			                        "XIO", (eisa_slot>>12), inb(eisa_slot+0xc82), inb(eisa_slot+0xc83));
+
+			board = &boards[found];
+			board->eisa_base = eisa_slot;
+			board->flags &= ~SX_BOARD_TYPE;
+			board->flags |= SI_EISA_BOARD;
+
+			board->hw_base = (((inb(0xc01+eisa_slot) << 8) + inb(0xc00+eisa_slot)) << 16);
+			board->base2 =
+			board->base = ioremap(board->hw_base, SI2_EISA_WINDOW_LEN);
+
+			sx_dprintk(SX_DEBUG_PROBE, "IO hw_base address: %lx\n", board->hw_base);
+			sx_dprintk(SX_DEBUG_PROBE, "base: %p\n", board->base);
+			board->irq = inb(board->eisa_base+0xc02)>>4; 
+			sx_dprintk(SX_DEBUG_PROBE, "IRQ: %d\n", board->irq);
+			
+			probe_si(board);
+
+			found++;
+		}
+	}
+	if (found) {
+		printk (KERN_INFO "sx: total of %d boards detected.\n", found);
+	} else {
+		misc_deregister(&sx_fw_device);
+	}
+
+	func_exit();
+	return found?0:-EIO;
+}
+
+
+static void __exit sx_exit (void)
+{
+	int i; 
+	struct sx_board *board;
+
+	func_enter();
+	for (i = 0; i < SX_NBOARDS; i++) {
+		board = &boards[i];
+		if (board->flags & SX_BOARD_INITIALIZED) {
+			sx_dprintk (SX_DEBUG_CLEANUP, "Cleaning up board at %p\n", board->base);
+			/* The board should stop messing with us.
+			   (actually I mean the interrupt) */
+			sx_reset (board);
+			if ((board->irq) && (board->flags & SX_IRQ_ALLOCATED))
+				free_irq (board->irq, board);
+
+			/* It is safe/allowed to del_timer a non-active timer */
+			del_timer (& board->timer);
+			iounmap(board->base);
+		}
+	}
+	if (misc_deregister(&sx_fw_device) < 0) {
+		printk (KERN_INFO "sx: couldn't deregister firmware loader devic\n");
+	}
+	sx_dprintk (SX_DEBUG_CLEANUP, "Cleaning up drivers (%d)\n", sx_initialized);
+	if (sx_initialized)
+		sx_release_drivers ();
+
+	kfree (sx_ports);
+	func_exit();
+}
+
+module_init(sx_init);
+module_exit(sx_exit);
+
+
diff --git a/drivers/char/sx.h b/drivers/char/sx.h
new file mode 100644
index 0000000..e01f83c
--- /dev/null
+++ b/drivers/char/sx.h
@@ -0,0 +1,202 @@
+
+/*
+ *  sx.h
+ *
+ *  Copyright (C) 1998/1999 R.E.Wolff@BitWizard.nl
+ *
+ *  SX serial driver.
+ *  -- Supports SI, XIO and SX host cards. 
+ *  -- Supports TAs, MTAs and SXDCs.
+ *
+ *  Version 1.3 -- March, 1999. 
+ * 
+ */
+
+#define SX_NBOARDS        4
+#define SX_PORTSPERBOARD 32
+#define SX_NPORTS        (SX_NBOARDS * SX_PORTSPERBOARD)
+
+#ifdef __KERNEL__
+
+#define SX_MAGIC 0x12345678
+
+struct sx_port {
+  struct gs_port          gs;
+  struct wait_queue       *shutdown_wait;
+  int                     ch_base;
+  int                     c_dcd;
+  struct sx_board         *board;
+  int                     line;
+  long                    locks;
+};
+
+struct sx_board {
+  int magic;
+  void __iomem *base;
+  void __iomem *base2;
+  unsigned long hw_base;
+  int eisa_base;
+  int port_base; /* Number of the first port */
+  struct sx_port *ports;
+  int nports;
+  int flags;
+  int irq;
+  int poll;
+  int ta_type;
+  struct timer_list       timer;
+  long                    locks;
+};
+
+struct vpd_prom {
+  unsigned short id;
+  char hwrev;
+  char hwass;
+  int uniqid;
+  char myear;
+  char mweek;
+  char hw_feature[5];
+  char oem_id;
+  char identifier[16];
+};
+
+#ifndef MOD_RS232DB25MALE
+#define MOD_RS232DB25MALE 0x0a
+#endif
+
+#define SI_ISA_BOARD         0x00000001
+#define SX_ISA_BOARD         0x00000002
+#define SX_PCI_BOARD         0x00000004
+#define SX_CFPCI_BOARD       0x00000008
+#define SX_CFISA_BOARD       0x00000010
+#define SI_EISA_BOARD        0x00000020
+#define SI1_ISA_BOARD        0x00000040
+
+#define SX_BOARD_PRESENT     0x00001000
+#define SX_BOARD_INITIALIZED 0x00002000
+#define SX_IRQ_ALLOCATED     0x00004000
+
+#define SX_BOARD_TYPE        0x000000ff
+
+#define IS_SX_BOARD(board) (board->flags & (SX_PCI_BOARD | SX_CFPCI_BOARD | \
+                                            SX_ISA_BOARD | SX_CFISA_BOARD))
+
+#define IS_SI_BOARD(board) (board->flags & SI_ISA_BOARD)
+#define IS_SI1_BOARD(board) (board->flags & SI1_ISA_BOARD)
+
+#define IS_EISA_BOARD(board) (board->flags & SI_EISA_BOARD)
+
+#define IS_CF_BOARD(board) (board->flags & (SX_CFISA_BOARD | SX_CFPCI_BOARD))
+
+#define SERIAL_TYPE_NORMAL 1
+
+/* The SI processor clock is required to calculate the cc_int_count register
+   value for the SI cards. */
+#define SI_PROCESSOR_CLOCK 25000000
+
+
+/* port flags */
+/* Make sure these don't clash with gs flags or async flags */
+#define SX_RX_THROTTLE        0x0000001
+
+
+
+#define SX_PORT_TRANSMIT_LOCK  0
+#define SX_BOARD_INTR_LOCK     0
+
+
+
+/* Debug flags. Add these together to get more debug info. */
+
+#define SX_DEBUG_OPEN          0x00000001
+#define SX_DEBUG_SETTING       0x00000002
+#define SX_DEBUG_FLOW          0x00000004
+#define SX_DEBUG_MODEMSIGNALS  0x00000008
+#define SX_DEBUG_TERMIOS       0x00000010
+#define SX_DEBUG_TRANSMIT      0x00000020
+#define SX_DEBUG_RECEIVE       0x00000040
+#define SX_DEBUG_INTERRUPTS    0x00000080
+#define SX_DEBUG_PROBE         0x00000100
+#define SX_DEBUG_INIT          0x00000200
+#define SX_DEBUG_CLEANUP       0x00000400
+#define SX_DEBUG_CLOSE         0x00000800
+#define SX_DEBUG_FIRMWARE      0x00001000
+#define SX_DEBUG_MEMTEST       0x00002000
+
+#define SX_DEBUG_ALL           0xffffffff
+
+
+#define O_OTHER(tty)    \
+      ((O_OLCUC(tty))  ||\
+      (O_ONLCR(tty))   ||\
+      (O_OCRNL(tty))   ||\
+      (O_ONOCR(tty))   ||\
+      (O_ONLRET(tty))  ||\
+      (O_OFILL(tty))   ||\
+      (O_OFDEL(tty))   ||\
+      (O_NLDLY(tty))   ||\
+      (O_CRDLY(tty))   ||\
+      (O_TABDLY(tty))  ||\
+      (O_BSDLY(tty))   ||\
+      (O_VTDLY(tty))   ||\
+      (O_FFDLY(tty)))
+
+/* Same for input. */
+#define I_OTHER(tty)    \
+      ((I_INLCR(tty))  ||\
+      (I_IGNCR(tty))   ||\
+      (I_ICRNL(tty))   ||\
+      (I_IUCLC(tty))   ||\
+      (L_ISIG(tty)))
+
+#define MOD_TA   (        TA>>4)
+#define MOD_MTA  (MTA_CD1400>>4)
+#define MOD_SXDC (      SXDC>>4)
+
+
+/* We copy the download code over to the card in chunks of ... bytes */
+#define SX_CHUNK_SIZE 128
+
+#endif /* __KERNEL__ */
+
+
+
+/* Specialix document 6210046-11 page 3 */
+#define SPX(X) (('S'<<24) | ('P' << 16) | (X))
+
+/* Specialix-Linux specific IOCTLS. */
+#define SPXL(X) (SPX(('L' << 8) | (X)))
+
+
+#define SXIO_SET_BOARD      SPXL(0x01)
+#define SXIO_GET_TYPE       SPXL(0x02)
+#define SXIO_DOWNLOAD       SPXL(0x03)
+#define SXIO_INIT           SPXL(0x04)
+#define SXIO_SETDEBUG       SPXL(0x05)
+#define SXIO_GETDEBUG       SPXL(0x06)
+#define SXIO_DO_RAMTEST     SPXL(0x07)
+#define SXIO_SETGSDEBUG     SPXL(0x08)
+#define SXIO_GETGSDEBUG     SPXL(0x09)
+#define SXIO_GETNPORTS      SPXL(0x0a)
+
+
+#ifndef SXCTL_MISC_MINOR 
+/* Allow others to gather this into "major.h" or something like that */
+#define SXCTL_MISC_MINOR    167
+#endif
+
+#ifndef SX_NORMAL_MAJOR
+/* This allows overriding on the compiler commandline, or in a "major.h" 
+   include or something like that */
+#define SX_NORMAL_MAJOR  32
+#define SX_CALLOUT_MAJOR 33
+#endif
+
+
+#define SX_TYPE_SX          0x01
+#define SX_TYPE_SI          0x02
+#define SX_TYPE_CF          0x03
+
+
+#define WINDOW_LEN(board) (IS_CF_BOARD(board)?0x20000:SX_WINDOW_LEN)
+/*                         Need a #define for ^^^^^^^ !!! */
+
diff --git a/drivers/char/sxboards.h b/drivers/char/sxboards.h
new file mode 100644
index 0000000..427927d
--- /dev/null
+++ b/drivers/char/sxboards.h
@@ -0,0 +1,206 @@
+/************************************************************************/
+/*									*/
+/*	Title		:	SX/SI/XIO Board Hardware Definitions	*/
+/*									*/
+/*	Author		:	N.P.Vassallo				*/
+/*									*/
+/*	Creation	:	16th March 1998				*/
+/*									*/
+/*	Version		:	3.0.0					*/
+/*									*/
+/*	Copyright	:	(c) Specialix International Ltd. 1998	*/
+/*									*/
+/*	Description	:	Prototypes, structures and definitions	*/
+/*				describing the SX/SI/XIO board hardware	*/
+/*									*/
+/************************************************************************/
+
+/* History...
+
+3.0.0	16/03/98 NPV	Creation.
+
+*/
+
+#ifndef	_sxboards_h				/* If SXBOARDS.H not already defined */
+#define	_sxboards_h    1
+
+/*****************************************************************************
+*******************************                 ******************************
+*******************************   Board Types   ******************************
+*******************************                 ******************************
+*****************************************************************************/
+
+/* BUS types... */
+#define		BUS_ISA		0
+#define		BUS_MCA		1
+#define		BUS_EISA	2
+#define		BUS_PCI		3
+
+/* Board phases... */
+#define		SI1_Z280	1
+#define		SI2_Z280	2
+#define		SI3_T225	3
+
+/* Board types... */
+#define		CARD_TYPE(bus,phase)	(bus<<4|phase)
+#define		CARD_BUS(type)		((type>>4)&0xF)
+#define		CARD_PHASE(type)	(type&0xF)
+
+#define		TYPE_SI1_ISA		CARD_TYPE(BUS_ISA,SI1_Z280)
+#define		TYPE_SI2_ISA		CARD_TYPE(BUS_ISA,SI2_Z280)
+#define		TYPE_SI2_EISA		CARD_TYPE(BUS_EISA,SI2_Z280)
+#define		TYPE_SI2_PCI		CARD_TYPE(BUS_PCI,SI2_Z280)
+
+#define		TYPE_SX_ISA		CARD_TYPE(BUS_ISA,SI3_T225)
+#define		TYPE_SX_PCI		CARD_TYPE(BUS_PCI,SI3_T225)
+/*****************************************************************************
+******************************                  ******************************
+******************************   Phase 1 Z280   ******************************
+******************************                  ******************************
+*****************************************************************************/
+
+/* ISA board details... */
+#define		SI1_ISA_WINDOW_LEN	0x10000		/* 64 Kbyte shared memory window */
+//#define 	SI1_ISA_MEMORY_LEN	0x8000		/* Usable memory  - unused define*/
+//#define		SI1_ISA_ADDR_LOW	0x0A0000	/* Lowest address = 640 Kbyte */
+//#define		SI1_ISA_ADDR_HIGH	0xFF8000	/* Highest address = 16Mbyte - 32Kbyte */
+//#define		SI2_ISA_ADDR_STEP	SI2_ISA_WINDOW_LEN/* ISA board address step */
+//#define		SI2_ISA_IRQ_MASK	0x9800		/* IRQs 15,12,11 */
+
+/* ISA board, register definitions... */
+//#define		SI2_ISA_ID_BASE		0x7FF8			/* READ:  Board ID string */
+#define		SI1_ISA_RESET		0x8000		/* WRITE: Host Reset */
+#define		SI1_ISA_RESET_CLEAR	0xc000		/* WRITE: Host Reset clear*/
+#define		SI1_ISA_WAIT	        0x9000		/* WRITE: Host wait */
+#define		SI1_ISA_WAIT_CLEAR	0xd000		/* WRITE: Host wait clear */
+#define		SI1_ISA_INTCL        	0xa000		/* WRITE: Host Reset */
+#define		SI1_ISA_INTCL_CLEAR	0xe000		/* WRITE: Host Reset */
+
+
+/*****************************************************************************
+******************************                  ******************************
+******************************   Phase 2 Z280   ******************************
+******************************                  ******************************
+*****************************************************************************/
+
+/* ISA board details... */
+#define		SI2_ISA_WINDOW_LEN	0x8000		/* 32 Kbyte shared memory window */
+#define 	SI2_ISA_MEMORY_LEN	0x7FF8		/* Usable memory */
+#define		SI2_ISA_ADDR_LOW	0x0A0000	/* Lowest address = 640 Kbyte */
+#define		SI2_ISA_ADDR_HIGH	0xFF8000	/* Highest address = 16Mbyte - 32Kbyte */
+#define		SI2_ISA_ADDR_STEP	SI2_ISA_WINDOW_LEN/* ISA board address step */
+#define		SI2_ISA_IRQ_MASK	0x9800		/* IRQs 15,12,11 */
+
+/* ISA board, register definitions... */
+#define		SI2_ISA_ID_BASE		0x7FF8			/* READ:  Board ID string */
+#define		SI2_ISA_RESET		SI2_ISA_ID_BASE		/* WRITE: Host Reset */
+#define		SI2_ISA_IRQ11		(SI2_ISA_ID_BASE+1)	/* WRITE: Set IRQ11 */
+#define		SI2_ISA_IRQ12		(SI2_ISA_ID_BASE+2)	/* WRITE: Set IRQ12 */
+#define		SI2_ISA_IRQ15		(SI2_ISA_ID_BASE+3)	/* WRITE: Set IRQ15 */
+#define		SI2_ISA_IRQSET		(SI2_ISA_ID_BASE+4)	/* WRITE: Set Host Interrupt */
+#define		SI2_ISA_INTCLEAR	(SI2_ISA_ID_BASE+5)	/* WRITE: Enable Host Interrupt */
+
+#define		SI2_ISA_IRQ11_SET	0x10
+#define		SI2_ISA_IRQ11_CLEAR	0x00
+#define		SI2_ISA_IRQ12_SET	0x10
+#define		SI2_ISA_IRQ12_CLEAR	0x00
+#define		SI2_ISA_IRQ15_SET	0x10
+#define		SI2_ISA_IRQ15_CLEAR	0x00
+#define		SI2_ISA_INTCLEAR_SET	0x10
+#define		SI2_ISA_INTCLEAR_CLEAR	0x00
+#define		SI2_ISA_IRQSET_CLEAR	0x10
+#define		SI2_ISA_IRQSET_SET	0x00
+#define		SI2_ISA_RESET_SET	0x00
+#define		SI2_ISA_RESET_CLEAR	0x10
+
+/* PCI board details... */
+#define		SI2_PCI_WINDOW_LEN	0x100000	/* 1 Mbyte memory window */
+
+/* PCI board register definitions... */
+#define		SI2_PCI_SET_IRQ		0x40001		/* Set Host Interrupt  */
+#define		SI2_PCI_RESET		0xC0001		/* Host Reset */
+
+/*****************************************************************************
+******************************                  ******************************
+******************************   Phase 3 T225   ******************************
+******************************                  ******************************
+*****************************************************************************/
+
+/* General board details... */
+#define		SX_WINDOW_LEN		64*1024		/* 64 Kbyte memory window */
+
+/* ISA board details... */
+#define		SX_ISA_ADDR_LOW		0x0A0000	/* Lowest address = 640 Kbyte */
+#define		SX_ISA_ADDR_HIGH	0xFF8000	/* Highest address = 16Mbyte - 32Kbyte */
+#define		SX_ISA_ADDR_STEP	SX_WINDOW_LEN	/* ISA board address step */
+#define		SX_ISA_IRQ_MASK		0x9E00		/* IRQs 15,12,11,10,9 */
+
+/* Hardware register definitions... */
+#define		SX_EVENT_STATUS		0x7800		/* READ:  T225 Event Status */
+#define		SX_EVENT_STROBE		0x7800		/* WRITE: T225 Event Strobe */
+#define		SX_EVENT_ENABLE		0x7880		/* WRITE: T225 Event Enable */
+#define		SX_VPD_ROM		0x7C00		/* READ:  Vital Product Data ROM */
+#define		SX_CONFIG		0x7C00		/* WRITE: Host Configuration Register */
+#define		SX_IRQ_STATUS		0x7C80		/* READ:  Host Interrupt Status */
+#define		SX_SET_IRQ		0x7C80		/* WRITE: Set Host Interrupt */
+#define		SX_RESET_STATUS		0x7D00		/* READ:  Host Reset Status */
+#define		SX_RESET		0x7D00		/* WRITE: Host Reset */
+#define		SX_RESET_IRQ		0x7D80		/* WRITE: Reset Host Interrupt */
+
+/* SX_VPD_ROM definitions... */
+#define		SX_VPD_SLX_ID1		0x00
+#define		SX_VPD_SLX_ID2		0x01
+#define		SX_VPD_HW_REV		0x02
+#define		SX_VPD_HW_ASSEM		0x03
+#define		SX_VPD_UNIQUEID4	0x04
+#define		SX_VPD_UNIQUEID3	0x05
+#define		SX_VPD_UNIQUEID2	0x06
+#define		SX_VPD_UNIQUEID1	0x07
+#define		SX_VPD_MANU_YEAR	0x08
+#define		SX_VPD_MANU_WEEK	0x09
+#define		SX_VPD_IDENT		0x10
+#define		SX_VPD_IDENT_STRING	"JET HOST BY KEV#"
+
+/* SX unique identifiers... */
+#define		SX_UNIQUEID_MASK	0xF0
+#define		SX_ISA_UNIQUEID1	0x20
+#define		SX_PCI_UNIQUEID1	0x50
+
+/* SX_CONFIG definitions... */
+#define		SX_CONF_BUSEN		0x02		/* Enable T225 memory and I/O */
+#define		SX_CONF_HOSTIRQ		0x04		/* Enable board to host interrupt */
+
+/* SX bootstrap... */
+#define		SX_BOOTSTRAP		"\x28\x20\x21\x02\x60\x0a"
+#define		SX_BOOTSTRAP_SIZE	6
+#define		SX_BOOTSTRAP_ADDR	(0x8000-SX_BOOTSTRAP_SIZE)
+
+/*****************************************************************************
+**********************************          **********************************
+**********************************   EISA   **********************************
+**********************************          **********************************
+*****************************************************************************/
+
+#define		SI2_EISA_OFF	 	0x42
+#define		SI2_EISA_VAL	 	0x01
+#define		SI2_EISA_WINDOW_LEN     0x10000
+
+/*****************************************************************************
+***********************************         **********************************
+***********************************   PCI   **********************************
+***********************************         **********************************
+*****************************************************************************/
+
+/* General definitions... */
+
+#define		SPX_VENDOR_ID		0x11CB		/* Assigned by the PCI SIG */
+#define		SPX_DEVICE_ID		0x4000		/* SI/XIO boards */
+#define		SPX_PLXDEVICE_ID	0x2000		/* SX boards */
+
+#define		SPX_SUB_VENDOR_ID	SPX_VENDOR_ID	/* Same as vendor id */
+#define		SI2_SUB_SYS_ID		0x400		/* Phase 2 (Z280) board */
+#define		SX_SUB_SYS_ID		0x200		/* Phase 3 (t225) board */
+
+#endif						/*_sxboards_h */
+
+/* End of SXBOARDS.H */
diff --git a/drivers/char/sxwindow.h b/drivers/char/sxwindow.h
new file mode 100644
index 0000000..cf01b66
--- /dev/null
+++ b/drivers/char/sxwindow.h
@@ -0,0 +1,393 @@
+/************************************************************************/
+/*									*/
+/*	Title		:	SX Shared Memory Window Structure	*/
+/*									*/
+/*	Author		:	N.P.Vassallo				*/
+/*									*/
+/*	Creation	:	16th March 1998				*/
+/*									*/
+/*	Version		:	3.0.0					*/
+/*									*/
+/*	Copyright	:	(c) Specialix International Ltd. 1998	*/
+/*									*/
+/*	Description	:	Prototypes, structures and definitions	*/
+/*				describing the SX/SI/XIO cards shared	*/
+/*				memory window structure:		*/
+/*					SXCARD				*/
+/*					SXMODULE			*/
+/*					SXCHANNEL			*/
+/*									*/
+/************************************************************************/
+
+/* History...
+
+3.0.0	16/03/98 NPV	Creation. (based on STRUCT.H)
+
+*/
+
+#ifndef	_sxwindow_h				/* If SXWINDOW.H not already defined */
+#define	_sxwindow_h    1
+
+/*****************************************************************************
+***************************                        ***************************
+***************************   Common Definitions   ***************************
+***************************                        ***************************
+*****************************************************************************/
+
+typedef	struct	_SXCARD		*PSXCARD;	/* SXCARD structure pointer */
+typedef	struct	_SXMODULE	*PMOD;		/* SXMODULE structure pointer */
+typedef	struct	_SXCHANNEL	*PCHAN;		/* SXCHANNEL structure pointer */
+
+/*****************************************************************************
+*********************************            *********************************
+*********************************   SXCARD   *********************************
+*********************************            *********************************
+*****************************************************************************/
+
+typedef	struct	_SXCARD
+{
+	BYTE	cc_init_status;			/* 0x00 Initialisation status */
+	BYTE	cc_mem_size;			/* 0x01 Size of memory on card */
+	WORD	cc_int_count;			/* 0x02 Interrupt count */
+	WORD	cc_revision;			/* 0x04 Download code revision */
+	BYTE	cc_isr_count;			/* 0x06 Count when ISR is run */
+	BYTE	cc_main_count;			/* 0x07 Count when main loop is run */
+	WORD	cc_int_pending;			/* 0x08 Interrupt pending */
+	WORD	cc_poll_count;			/* 0x0A Count when poll is run */
+	BYTE	cc_int_set_count;		/* 0x0C Count when host interrupt is set */
+	BYTE	cc_rfu[0x80 - 0x0D];		/* 0x0D Pad structure to 128 bytes (0x80) */
+
+} SXCARD;
+
+/* SXCARD.cc_init_status definitions... */
+#define 	ADAPTERS_FOUND		(BYTE)0x01
+#define 	NO_ADAPTERS_FOUND	(BYTE)0xFF
+
+/* SXCARD.cc_mem_size definitions... */
+#define 	SX_MEMORY_SIZE		(BYTE)0x40
+
+/* SXCARD.cc_int_count definitions... */
+#define 	INT_COUNT_DEFAULT	100	/* Hz */
+
+/*****************************************************************************
+********************************              ********************************
+********************************   SXMODULE   ********************************
+********************************              ********************************
+*****************************************************************************/
+
+#define	TOP_POINTER(a)		((a)|0x8000)	/* Sets top bit of word */
+#define UNTOP_POINTER(a)	((a)&~0x8000)	/* Clears top bit of word */
+
+typedef	struct	_SXMODULE
+{
+	WORD	mc_next;			/* 0x00 Next module "pointer" (ORed with 0x8000) */
+	BYTE	mc_type;			/* 0x02 Type of TA in terms of number of channels */
+	BYTE	mc_mod_no;			/* 0x03 Module number on SI bus cable (0 closest to card) */
+	BYTE	mc_dtr;				/* 0x04 Private DTR copy (TA only) */
+	BYTE	mc_rfu1;			/* 0x05 Reserved */
+	WORD	mc_uart;			/* 0x06 UART base address for this module */
+	BYTE	mc_chip;			/* 0x08 Chip type / number of ports */
+	BYTE	mc_current_uart;		/* 0x09 Current uart selected for this module */
+#ifdef	DOWNLOAD
+	PCHAN	mc_chan_pointer[8];		/* 0x0A Pointer to each channel structure */
+#else
+	WORD	mc_chan_pointer[8];		/* 0x0A Define as WORD if not compiling into download */
+#endif
+	WORD	mc_rfu2;			/* 0x1A Reserved */
+	BYTE	mc_opens1;			/* 0x1C Number of open ports on first four ports on MTA/SXDC */
+	BYTE	mc_opens2;			/* 0x1D Number of open ports on second four ports on MTA/SXDC */
+	BYTE	mc_mods;			/* 0x1E Types of connector module attached to MTA/SXDC */
+	BYTE	mc_rev1;			/* 0x1F Revision of first CD1400 on MTA/SXDC */
+	BYTE	mc_rev2;			/* 0x20 Revision of second CD1400 on MTA/SXDC */
+	BYTE	mc_mtaasic_rev;			/* 0x21 Revision of MTA ASIC 1..4 -> A, B, C, D */
+	BYTE	mc_rfu3[0x100 - 0x22];		/* 0x22 Pad structure to 256 bytes (0x100) */
+
+} SXMODULE;
+
+/* SXMODULE.mc_type definitions... */
+#define		FOUR_PORTS	(BYTE)4
+#define 	EIGHT_PORTS	(BYTE)8
+
+/* SXMODULE.mc_chip definitions... */
+#define 	CHIP_MASK	0xF0
+#define		TA		(BYTE)0
+#define 	TA4		(TA | FOUR_PORTS)
+#define 	TA8		(TA | EIGHT_PORTS)
+#define		TA4_ASIC	(BYTE)0x0A
+#define		TA8_ASIC	(BYTE)0x0B
+#define 	MTA_CD1400	(BYTE)0x28
+#define 	SXDC		(BYTE)0x48
+
+/* SXMODULE.mc_mods definitions... */
+#define		MOD_RS232DB25	0x00		/* RS232 DB25 (socket/plug) */
+#define		MOD_RS232RJ45	0x01		/* RS232 RJ45 (shielded/opto-isolated) */
+#define		MOD_RESERVED_2	0x02		/* Reserved (RS485) */
+#define		MOD_RS422DB25	0x03		/* RS422 DB25 Socket */
+#define		MOD_RESERVED_4	0x04		/* Reserved */
+#define		MOD_PARALLEL	0x05		/* Parallel */
+#define		MOD_RESERVED_6	0x06		/* Reserved (RS423) */
+#define		MOD_RESERVED_7	0x07		/* Reserved */
+#define		MOD_2_RS232DB25	0x08		/* Rev 2.0 RS232 DB25 (socket/plug) */
+#define		MOD_2_RS232RJ45	0x09		/* Rev 2.0 RS232 RJ45 */
+#define		MOD_RESERVED_A	0x0A		/* Rev 2.0 Reserved */
+#define		MOD_2_RS422DB25	0x0B		/* Rev 2.0 RS422 DB25 */
+#define		MOD_RESERVED_C	0x0C		/* Rev 2.0 Reserved */
+#define		MOD_2_PARALLEL	0x0D		/* Rev 2.0 Parallel */
+#define		MOD_RESERVED_E	0x0E		/* Rev 2.0 Reserved */
+#define		MOD_BLANK	0x0F		/* Blank Panel */
+
+/*****************************************************************************
+********************************               *******************************
+********************************   SXCHANNEL   *******************************
+********************************               *******************************
+*****************************************************************************/
+
+#define		TX_BUFF_OFFSET		0x60	/* Transmit buffer offset in channel structure */
+#define		BUFF_POINTER(a)		(((a)+TX_BUFF_OFFSET)|0x8000)
+#define		UNBUFF_POINTER(a)	(jet_channel*)(((a)&~0x8000)-TX_BUFF_OFFSET) 
+#define 	BUFFER_SIZE		256
+#define 	HIGH_WATER		((BUFFER_SIZE / 4) * 3)
+#define 	LOW_WATER		(BUFFER_SIZE / 4)
+
+typedef	struct	_SXCHANNEL
+{
+	WORD	next_item;			/* 0x00 Offset from window base of next channels hi_txbuf (ORred with 0x8000) */
+	WORD 	addr_uart;			/* 0x02 INTERNAL pointer to uart address. Includes FASTPATH bit */
+	WORD	module;				/* 0x04 Offset from window base of parent SXMODULE structure */
+	BYTE 	type;				/* 0x06 Chip type / number of ports (copy of mc_chip) */
+	BYTE	chan_number;			/* 0x07 Channel number on the TA/MTA/SXDC */
+	WORD	xc_status;			/* 0x08 Flow control and I/O status */
+	BYTE	hi_rxipos;			/* 0x0A Receive buffer input index */
+	BYTE	hi_rxopos;			/* 0x0B Receive buffer output index */
+	BYTE	hi_txopos;			/* 0x0C Transmit buffer output index */
+	BYTE	hi_txipos;			/* 0x0D Transmit buffer input index */
+	BYTE	hi_hstat;			/* 0x0E Command register */
+	BYTE	dtr_bit;			/* 0x0F INTERNAL DTR control byte (TA only) */
+	BYTE	txon;				/* 0x10 INTERNAL copy of hi_txon */
+	BYTE	txoff;				/* 0x11 INTERNAL copy of hi_txoff */
+	BYTE	rxon;				/* 0x12 INTERNAL copy of hi_rxon */
+	BYTE	rxoff;				/* 0x13 INTERNAL copy of hi_rxoff */
+	BYTE	hi_mr1;				/* 0x14 Mode Register 1 (databits,parity,RTS rx flow)*/
+	BYTE	hi_mr2;				/* 0x15 Mode Register 2 (stopbits,local,CTS tx flow)*/
+	BYTE	hi_csr;				/* 0x16 Clock Select Register (baud rate) */
+	BYTE	hi_op;				/* 0x17 Modem Output Signal */
+	BYTE	hi_ip;				/* 0x18 Modem Input Signal */
+	BYTE	hi_state;			/* 0x19 Channel status */
+	BYTE	hi_prtcl;			/* 0x1A Channel protocol (flow control) */
+	BYTE	hi_txon;			/* 0x1B Transmit XON character */
+	BYTE	hi_txoff;			/* 0x1C Transmit XOFF character */
+	BYTE	hi_rxon;			/* 0x1D Receive XON character */
+	BYTE	hi_rxoff;			/* 0x1E Receive XOFF character */
+	BYTE	close_prev;			/* 0x1F INTERNAL channel previously closed flag */
+	BYTE	hi_break;			/* 0x20 Break and error control */
+	BYTE	break_state;			/* 0x21 INTERNAL copy of hi_break */
+	BYTE	hi_mask;			/* 0x22 Mask for received data */
+	BYTE	mask;				/* 0x23 INTERNAL copy of hi_mask */
+	BYTE	mod_type;			/* 0x24 MTA/SXDC hardware module type */
+	BYTE	ccr_state;			/* 0x25 INTERNAL MTA/SXDC state of CCR register */
+	BYTE	ip_mask;			/* 0x26 Input handshake mask */
+	BYTE	hi_parallel;			/* 0x27 Parallel port flag */
+	BYTE	par_error;			/* 0x28 Error code for parallel loopback test */
+	BYTE	any_sent;			/* 0x29 INTERNAL data sent flag */
+	BYTE	asic_txfifo_size;		/* 0x2A INTERNAL SXDC transmit FIFO size */
+	BYTE	rfu1[2];			/* 0x2B Reserved */
+	BYTE	csr;				/* 0x2D INTERNAL copy of hi_csr */
+#ifdef	DOWNLOAD
+	PCHAN	nextp;				/* 0x2E Offset from window base of next channel structure */
+#else
+	WORD	nextp;				/* 0x2E Define as WORD if not compiling into download */
+#endif
+	BYTE	prtcl;				/* 0x30 INTERNAL copy of hi_prtcl */
+	BYTE	mr1;				/* 0x31 INTERNAL copy of hi_mr1 */
+	BYTE	mr2;				/* 0x32 INTERNAL copy of hi_mr2 */
+	BYTE	hi_txbaud;			/* 0x33 Extended transmit baud rate (SXDC only if((hi_csr&0x0F)==0x0F) */
+	BYTE	hi_rxbaud;			/* 0x34 Extended receive baud rate  (SXDC only if((hi_csr&0xF0)==0xF0) */
+	BYTE	txbreak_state;			/* 0x35 INTERNAL MTA/SXDC transmit break state */
+	BYTE	txbaud;				/* 0x36 INTERNAL copy of hi_txbaud */
+	BYTE	rxbaud;				/* 0x37 INTERNAL copy of hi_rxbaud */
+	WORD	err_framing;			/* 0x38 Count of receive framing errors */
+	WORD	err_parity;			/* 0x3A Count of receive parity errors */
+	WORD	err_overrun;			/* 0x3C Count of receive overrun errors */
+	WORD	err_overflow;			/* 0x3E Count of receive buffer overflow errors */
+	BYTE	rfu2[TX_BUFF_OFFSET - 0x40];	/* 0x40 Reserved until hi_txbuf */
+	BYTE	hi_txbuf[BUFFER_SIZE];		/* 0x060 Transmit buffer */
+	BYTE	hi_rxbuf[BUFFER_SIZE];		/* 0x160 Receive buffer */
+	BYTE	rfu3[0x300 - 0x260];		/* 0x260 Reserved until 768 bytes (0x300) */
+
+} SXCHANNEL;
+
+/* SXCHANNEL.addr_uart definitions... */
+#define		FASTPATH	0x1000		/* Set to indicate fast rx/tx processing (TA only) */
+
+/* SXCHANNEL.xc_status definitions... */
+#define		X_TANY		0x0001		/* XON is any character (TA only) */
+#define		X_TION		0x0001		/* Tx interrupts on (MTA only) */
+#define		X_TXEN		0x0002		/* Tx XON/XOFF enabled (TA only) */
+#define		X_RTSEN		0x0002		/* RTS FLOW enabled (MTA only) */
+#define		X_TXRC		0x0004		/* XOFF received (TA only) */
+#define		X_RTSLOW	0x0004		/* RTS dropped (MTA only) */
+#define		X_RXEN		0x0008		/* Rx XON/XOFF enabled */
+#define		X_ANYXO		0x0010		/* XOFF pending/sent or RTS dropped */
+#define		X_RXSE		0x0020		/* Rx XOFF sent */
+#define		X_NPEND		0x0040		/* Rx XON pending or XOFF pending */
+#define		X_FPEND		0x0080		/* Rx XOFF pending */
+#define		C_CRSE		0x0100		/* Carriage return sent (TA only) */
+#define		C_TEMR		0x0100		/* Tx empty requested (MTA only) */
+#define		C_TEMA		0x0200		/* Tx empty acked (MTA only) */
+#define		C_ANYP		0x0200		/* Any protocol bar tx XON/XOFF (TA only) */
+#define		C_EN		0x0400		/* Cooking enabled (on MTA means port is also || */
+#define		C_HIGH		0x0800		/* Buffer previously hit high water */
+#define		C_CTSEN		0x1000		/* CTS automatic flow-control enabled */
+#define		C_DCDEN		0x2000		/* DCD/DTR checking enabled */
+#define		C_BREAK		0x4000		/* Break detected */
+#define		C_RTSEN		0x8000		/* RTS automatic flow control enabled (MTA only) */
+#define		C_PARITY	0x8000		/* Parity checking enabled (TA only) */
+
+/* SXCHANNEL.hi_hstat definitions... */
+#define		HS_IDLE_OPEN	0x00		/* Channel open state */
+#define		HS_LOPEN	0x02		/* Local open command (no modem monitoring) */
+#define		HS_MOPEN	0x04		/* Modem open command (wait for DCD signal) */
+#define		HS_IDLE_MPEND	0x06		/* Waiting for DCD signal state */
+#define		HS_CONFIG	0x08		/* Configuration command */
+#define		HS_CLOSE	0x0A		/* Close command */
+#define		HS_START	0x0C		/* Start transmit break command */
+#define		HS_STOP		0x0E		/* Stop transmit break command */
+#define		HS_IDLE_CLOSED	0x10		/* Closed channel state */
+#define		HS_IDLE_BREAK	0x12		/* Transmit break state */
+#define		HS_FORCE_CLOSED	0x14		/* Force close command */
+#define		HS_RESUME	0x16		/* Clear pending XOFF command */
+#define		HS_WFLUSH	0x18		/* Flush transmit buffer command */
+#define		HS_RFLUSH	0x1A		/* Flush receive buffer command */
+#define		HS_SUSPEND	0x1C		/* Suspend output command (like XOFF received) */
+#define		PARALLEL	0x1E		/* Parallel port loopback test command (Diagnostics Only) */
+#define		ENABLE_RX_INTS	0x20		/* Enable receive interrupts command (Diagnostics Only) */
+#define		ENABLE_TX_INTS	0x22		/* Enable transmit interrupts command (Diagnostics Only) */
+#define		ENABLE_MDM_INTS	0x24		/* Enable modem interrupts command (Diagnostics Only) */
+#define		DISABLE_INTS	0x26		/* Disable interrupts command (Diagnostics Only) */
+
+/* SXCHANNEL.hi_mr1 definitions... */
+#define		MR1_BITS	0x03		/* Data bits mask */
+#define		MR1_5_BITS	0x00		/* 5 data bits */
+#define		MR1_6_BITS	0x01		/* 6 data bits */
+#define		MR1_7_BITS	0x02		/* 7 data bits */
+#define		MR1_8_BITS	0x03		/* 8 data bits */
+#define		MR1_PARITY	0x1C		/* Parity mask */
+#define		MR1_ODD		0x04		/* Odd parity */
+#define		MR1_EVEN	0x00		/* Even parity */
+#define		MR1_WITH	0x00		/* Parity enabled */
+#define		MR1_FORCE	0x08		/* Force parity */
+#define		MR1_NONE	0x10		/* No parity */
+#define		MR1_NOPARITY	MR1_NONE		/* No parity */
+#define		MR1_ODDPARITY	(MR1_WITH|MR1_ODD)	/* Odd parity */
+#define		MR1_EVENPARITY	(MR1_WITH|MR1_EVEN)	/* Even parity */
+#define		MR1_MARKPARITY	(MR1_FORCE|MR1_ODD)	/* Mark parity */
+#define		MR1_SPACEPARITY	(MR1_FORCE|MR1_EVEN)	/* Space parity */
+#define		MR1_RTS_RXFLOW	0x80		/* RTS receive flow control */
+
+/* SXCHANNEL.hi_mr2 definitions... */
+#define		MR2_STOP	0x0F		/* Stop bits mask */
+#define		MR2_1_STOP	0x07		/* 1 stop bit */
+#define		MR2_2_STOP	0x0F		/* 2 stop bits */
+#define		MR2_CTS_TXFLOW	0x10		/* CTS transmit flow control */
+#define		MR2_RTS_TOGGLE	0x20		/* RTS toggle on transmit */
+#define		MR2_NORMAL	0x00		/* Normal mode */
+#define		MR2_AUTO	0x40		/* Auto-echo mode (TA only) */
+#define		MR2_LOCAL	0x80		/* Local echo mode */
+#define		MR2_REMOTE	0xC0		/* Remote echo mode (TA only) */
+
+/* SXCHANNEL.hi_csr definitions... */
+#define		CSR_75		0x0		/*    75 baud */
+#define		CSR_110		0x1		/*   110 baud (TA), 115200 (MTA/SXDC) */
+#define		CSR_38400	0x2		/* 38400 baud */
+#define		CSR_150		0x3		/*   150 baud */
+#define		CSR_300		0x4		/*   300 baud */
+#define		CSR_600		0x5		/*   600 baud */
+#define		CSR_1200	0x6		/*  1200 baud */
+#define		CSR_2000	0x7		/*  2000 baud */
+#define		CSR_2400	0x8		/*  2400 baud */
+#define		CSR_4800	0x9		/*  4800 baud */
+#define		CSR_1800	0xA		/*  1800 baud */
+#define		CSR_9600	0xB		/*  9600 baud */
+#define		CSR_19200	0xC		/* 19200 baud */
+#define		CSR_57600	0xD		/* 57600 baud */
+#define		CSR_EXTBAUD	0xF		/* Extended baud rate (hi_txbaud/hi_rxbaud) */
+
+/* SXCHANNEL.hi_op definitions... */
+#define		OP_RTS		0x01		/* RTS modem output signal */
+#define		OP_DTR		0x02		/* DTR modem output signal */
+
+/* SXCHANNEL.hi_ip definitions... */
+#define		IP_CTS		0x02		/* CTS modem input signal */
+#define		IP_DCD		0x04		/* DCD modem input signal */
+#define		IP_DSR		0x20		/* DTR modem input signal */
+#define		IP_RI		0x40		/* RI modem input signal */
+
+/* SXCHANNEL.hi_state definitions... */
+#define		ST_BREAK	0x01		/* Break received (clear with config) */
+#define		ST_DCD		0x02		/* DCD signal changed state */
+
+/* SXCHANNEL.hi_prtcl definitions... */
+#define		SP_TANY		0x01		/* Transmit XON/XANY (if SP_TXEN enabled) */
+#define		SP_TXEN		0x02		/* Transmit XON/XOFF flow control */
+#define		SP_CEN		0x04		/* Cooking enabled */
+#define		SP_RXEN		0x08		/* Rx XON/XOFF enabled */
+#define		SP_DCEN		0x20		/* DCD / DTR check */
+#define		SP_DTR_RXFLOW	0x40		/* DTR receive flow control */
+#define		SP_PAEN		0x80		/* Parity checking enabled */
+
+/* SXCHANNEL.hi_break definitions... */
+#define		BR_IGN		0x01		/* Ignore any received breaks */
+#define		BR_INT		0x02		/* Interrupt on received break */
+#define		BR_PARMRK	0x04		/* Enable parmrk parity error processing */
+#define		BR_PARIGN	0x08		/* Ignore chars with parity errors */
+#define 	BR_ERRINT	0x80		/* Treat parity/framing/overrun errors as exceptions */
+
+/* SXCHANNEL.par_error definitions.. */
+#define		DIAG_IRQ_RX	0x01		/* Indicate serial receive interrupt (diags only) */
+#define		DIAG_IRQ_TX	0x02		/* Indicate serial transmit interrupt (diags only) */
+#define		DIAG_IRQ_MD	0x04		/* Indicate serial modem interrupt (diags only) */
+
+/* SXCHANNEL.hi_txbaud/hi_rxbaud definitions... (SXDC only) */
+#define		BAUD_75		0x00		/*     75 baud */
+#define		BAUD_115200	0x01		/* 115200 baud */
+#define		BAUD_38400	0x02		/*  38400 baud */
+#define		BAUD_150	0x03		/*    150 baud */
+#define		BAUD_300	0x04		/*    300 baud */
+#define		BAUD_600	0x05		/*    600 baud */
+#define		BAUD_1200	0x06		/*   1200 baud */
+#define		BAUD_2000	0x07		/*   2000 baud */
+#define		BAUD_2400	0x08		/*   2400 baud */
+#define		BAUD_4800	0x09		/*   4800 baud */
+#define		BAUD_1800	0x0A		/*   1800 baud */
+#define		BAUD_9600	0x0B		/*   9600 baud */
+#define		BAUD_19200	0x0C		/*  19200 baud */
+#define		BAUD_57600	0x0D		/*  57600 baud */
+#define		BAUD_230400	0x0E		/* 230400 baud */
+#define		BAUD_460800	0x0F		/* 460800 baud */
+#define		BAUD_921600	0x10		/* 921600 baud */
+#define		BAUD_50		0x11    	/*     50 baud */
+#define		BAUD_110	0x12		/*    110 baud */
+#define		BAUD_134_5	0x13		/*  134.5 baud */
+#define		BAUD_200	0x14		/*    200 baud */
+#define		BAUD_7200	0x15		/*   7200 baud */
+#define		BAUD_56000	0x16		/*  56000 baud */
+#define		BAUD_64000	0x17		/*  64000 baud */
+#define		BAUD_76800	0x18		/*  76800 baud */
+#define		BAUD_128000	0x19		/* 128000 baud */
+#define		BAUD_150000	0x1A		/* 150000 baud */
+#define		BAUD_14400	0x1B		/*  14400 baud */
+#define		BAUD_256000	0x1C		/* 256000 baud */
+#define		BAUD_28800	0x1D		/*  28800 baud */
+
+/* SXCHANNEL.txbreak_state definiions... */
+#define		TXBREAK_OFF	0		/* Not sending break */
+#define		TXBREAK_START	1		/* Begin sending break */
+#define		TXBREAK_START1	2		/* Begin sending break, part 1 */
+#define		TXBREAK_ON	3		/* Sending break */
+#define		TXBREAK_STOP	4		/* Stop sending break */
+#define		TXBREAK_STOP1	5		/* Stop sending break, part 1 */
+
+#endif						/* _sxwindow_h */
+
+/* End of SXWINDOW.H */
+
diff --git a/drivers/char/synclink.c b/drivers/char/synclink.c
new file mode 100644
index 0000000..37c8bea
--- /dev/null
+++ b/drivers/char/synclink.c
@@ -0,0 +1,8214 @@
+/*
+ * linux/drivers/char/synclink.c
+ *
+ * $Id: synclink.c,v 4.28 2004/08/11 19:30:01 paulkf Exp $
+ *
+ * Device driver for Microgate SyncLink ISA and PCI
+ * high speed multiprotocol serial adapters.
+ *
+ * written by Paul Fulghum for Microgate Corporation
+ * paulkf@microgate.com
+ *
+ * Microgate and SyncLink are trademarks of Microgate Corporation
+ *
+ * Derived from serial.c written by Theodore Ts'o and Linus Torvalds
+ *
+ * Original release 01/11/99
+ *
+ * This code is released under the GNU General Public License (GPL)
+ *
+ * This driver is primarily intended for use in synchronous
+ * HDLC mode. Asynchronous mode is also provided.
+ *
+ * When operating in synchronous mode, each call to mgsl_write()
+ * contains exactly one complete HDLC frame. Calling mgsl_put_char
+ * will start assembling an HDLC frame that will not be sent until
+ * mgsl_flush_chars or mgsl_write is called.
+ * 
+ * Synchronous receive data is reported as complete frames. To accomplish
+ * this, the TTY flip buffer is bypassed (too small to hold largest
+ * frame and may fragment frames) and the line discipline
+ * receive entry point is called directly.
+ *
+ * This driver has been tested with a slightly modified ppp.c driver
+ * for synchronous PPP.
+ *
+ * 2000/02/16
+ * Added interface for syncppp.c driver (an alternate synchronous PPP
+ * implementation that also supports Cisco HDLC). Each device instance
+ * registers as a tty device AND a network device (if dosyncppp option
+ * is set for the device). The functionality is determined by which
+ * device interface is opened.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if defined(__i386__)
+#  define BREAKPOINT() asm("   int $3");
+#else
+#  define BREAKPOINT() { }
+#endif
+
+#define MAX_ISA_DEVICES 10
+#define MAX_PCI_DEVICES 10
+#define MAX_TOTAL_DEVICES 20
+
+#include <linux/config.h>	
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <linux/netdevice.h>
+
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <asm/serial.h>
+
+#include <linux/delay.h>
+#include <linux/ioctl.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <linux/bitops.h>
+#include <asm/types.h>
+#include <linux/termios.h>
+#include <linux/workqueue.h>
+#include <linux/hdlc.h>
+
+#ifdef CONFIG_HDLC_MODULE
+#define CONFIG_HDLC 1
+#endif
+
+#define GET_USER(error,value,addr) error = get_user(value,addr)
+#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0
+#define PUT_USER(error,value,addr) error = put_user(value,addr)
+#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0
+
+#include <asm/uaccess.h>
+
+#include "linux/synclink.h"
+
+#define RCLRVALUE 0xffff
+
+static MGSL_PARAMS default_params = {
+	MGSL_MODE_HDLC,			/* unsigned long mode */
+	0,				/* unsigned char loopback; */
+	HDLC_FLAG_UNDERRUN_ABORT15,	/* unsigned short flags; */
+	HDLC_ENCODING_NRZI_SPACE,	/* unsigned char encoding; */
+	0,				/* unsigned long clock_speed; */
+	0xff,				/* unsigned char addr_filter; */
+	HDLC_CRC_16_CCITT,		/* unsigned short crc_type; */
+	HDLC_PREAMBLE_LENGTH_8BITS,	/* unsigned char preamble_length; */
+	HDLC_PREAMBLE_PATTERN_NONE,	/* unsigned char preamble; */
+	9600,				/* unsigned long data_rate; */
+	8,				/* unsigned char data_bits; */
+	1,				/* unsigned char stop_bits; */
+	ASYNC_PARITY_NONE		/* unsigned char parity; */
+};
+
+#define SHARED_MEM_ADDRESS_SIZE 0x40000
+#define BUFFERLISTSIZE (PAGE_SIZE)
+#define DMABUFFERSIZE (PAGE_SIZE)
+#define MAXRXFRAMES 7
+
+typedef struct _DMABUFFERENTRY
+{
+	u32 phys_addr;	/* 32-bit flat physical address of data buffer */
+	u16 count;	/* buffer size/data count */
+	u16 status;	/* Control/status field */
+	u16 rcc;	/* character count field */
+	u16 reserved;	/* padding required by 16C32 */
+	u32 link;	/* 32-bit flat link to next buffer entry */
+	char *virt_addr;	/* virtual address of data buffer */
+	u32 phys_entry;	/* physical address of this buffer entry */
+} DMABUFFERENTRY, *DMAPBUFFERENTRY;
+
+/* The queue of BH actions to be performed */
+
+#define BH_RECEIVE  1
+#define BH_TRANSMIT 2
+#define BH_STATUS   4
+
+#define IO_PIN_SHUTDOWN_LIMIT 100
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+struct	_input_signal_events {
+	int	ri_up;	
+	int	ri_down;
+	int	dsr_up;
+	int	dsr_down;
+	int	dcd_up;
+	int	dcd_down;
+	int	cts_up;
+	int	cts_down;
+};
+
+/* transmit holding buffer definitions*/
+#define MAX_TX_HOLDING_BUFFERS 5
+struct tx_holding_buffer {
+	int	buffer_size;
+	unsigned char *	buffer;
+};
+
+
+/*
+ * Device instance data structure
+ */
+ 
+struct mgsl_struct {
+	int			magic;
+	int			flags;
+	int			count;		/* count of opens */
+	int			line;
+	int                     hw_version;
+	unsigned short		close_delay;
+	unsigned short		closing_wait;	/* time to wait before closing */
+	
+	struct mgsl_icount	icount;
+	
+	struct tty_struct 	*tty;
+	int			timeout;
+	int			x_char;		/* xon/xoff character */
+	int			blocked_open;	/* # of blocked opens */
+	u16			read_status_mask;
+	u16			ignore_status_mask;	
+	unsigned char 		*xmit_buf;
+	int			xmit_head;
+	int			xmit_tail;
+	int			xmit_cnt;
+	
+	wait_queue_head_t	open_wait;
+	wait_queue_head_t	close_wait;
+	
+	wait_queue_head_t	status_event_wait_q;
+	wait_queue_head_t	event_wait_q;
+	struct timer_list	tx_timer;	/* HDLC transmit timeout timer */
+	struct mgsl_struct	*next_device;	/* device list link */
+	
+	spinlock_t irq_spinlock;		/* spinlock for synchronizing with ISR */
+	struct work_struct task;		/* task structure for scheduling bh */
+
+	u32 EventMask;			/* event trigger mask */
+	u32 RecordedEvents;		/* pending events */
+
+	u32 max_frame_size;		/* as set by device config */
+
+	u32 pending_bh;
+
+	int bh_running;		/* Protection from multiple */
+	int isr_overflow;
+	int bh_requested;
+	
+	int dcd_chkcount;		/* check counts to prevent */
+	int cts_chkcount;		/* too many IRQs if a signal */
+	int dsr_chkcount;		/* is floating */
+	int ri_chkcount;
+
+	char *buffer_list;		/* virtual address of Rx & Tx buffer lists */
+	unsigned long buffer_list_phys;
+
+	unsigned int rx_buffer_count;	/* count of total allocated Rx buffers */
+	DMABUFFERENTRY *rx_buffer_list;	/* list of receive buffer entries */
+	unsigned int current_rx_buffer;
+
+	int num_tx_dma_buffers;		/* number of tx dma frames required */
+ 	int tx_dma_buffers_used;
+	unsigned int tx_buffer_count;	/* count of total allocated Tx buffers */
+	DMABUFFERENTRY *tx_buffer_list;	/* list of transmit buffer entries */
+	int start_tx_dma_buffer;	/* tx dma buffer to start tx dma operation */
+	int current_tx_buffer;          /* next tx dma buffer to be loaded */
+	
+	unsigned char *intermediate_rxbuffer;
+
+	int num_tx_holding_buffers;	/* number of tx holding buffer allocated */
+	int get_tx_holding_index;  	/* next tx holding buffer for adapter to load */
+	int put_tx_holding_index;  	/* next tx holding buffer to store user request */
+	int tx_holding_count;		/* number of tx holding buffers waiting */
+	struct tx_holding_buffer tx_holding_buffers[MAX_TX_HOLDING_BUFFERS];
+
+	int rx_enabled;
+	int rx_overflow;
+	int rx_rcc_underrun;
+
+	int tx_enabled;
+	int tx_active;
+	u32 idle_mode;
+
+	u16 cmr_value;
+	u16 tcsr_value;
+
+	char device_name[25];		/* device instance name */
+
+	unsigned int bus_type;	/* expansion bus type (ISA,EISA,PCI) */
+	unsigned char bus;		/* expansion bus number (zero based) */
+	unsigned char function;		/* PCI device number */
+
+	unsigned int io_base;		/* base I/O address of adapter */
+	unsigned int io_addr_size;	/* size of the I/O address range */
+	int io_addr_requested;		/* nonzero if I/O address requested */
+	
+	unsigned int irq_level;		/* interrupt level */
+	unsigned long irq_flags;
+	int irq_requested;		/* nonzero if IRQ requested */
+	
+	unsigned int dma_level;		/* DMA channel */
+	int dma_requested;		/* nonzero if dma channel requested */
+
+	u16 mbre_bit;
+	u16 loopback_bits;
+	u16 usc_idle_mode;
+
+	MGSL_PARAMS params;		/* communications parameters */
+
+	unsigned char serial_signals;	/* current serial signal states */
+
+	int irq_occurred;		/* for diagnostics use */
+	unsigned int init_error;	/* Initialization startup error 		(DIAGS)	*/
+	int	fDiagnosticsmode;	/* Driver in Diagnostic mode?			(DIAGS)	*/
+
+	u32 last_mem_alloc;
+	unsigned char* memory_base;	/* shared memory address (PCI only) */
+	u32 phys_memory_base;
+	int shared_mem_requested;
+
+	unsigned char* lcr_base;	/* local config registers (PCI only) */
+	u32 phys_lcr_base;
+	u32 lcr_offset;
+	int lcr_mem_requested;
+
+	u32 misc_ctrl_value;
+	char flag_buf[MAX_ASYNC_BUFFER_SIZE];
+	char char_buf[MAX_ASYNC_BUFFER_SIZE];	
+	BOOLEAN drop_rts_on_tx_done;
+
+	BOOLEAN loopmode_insert_requested;
+	BOOLEAN	loopmode_send_done_requested;
+	
+	struct	_input_signal_events	input_signal_events;
+
+	/* generic HDLC device parts */
+	int netcount;
+	int dosyncppp;
+	spinlock_t netlock;
+
+#ifdef CONFIG_HDLC
+	struct net_device *netdev;
+#endif
+};
+
+#define MGSL_MAGIC 0x5401
+
+/*
+ * The size of the serial xmit buffer is 1 page, or 4096 bytes
+ */
+#ifndef SERIAL_XMIT_SIZE
+#define SERIAL_XMIT_SIZE 4096
+#endif
+
+/*
+ * These macros define the offsets used in calculating the
+ * I/O address of the specified USC registers.
+ */
+
+
+#define DCPIN 2		/* Bit 1 of I/O address */
+#define SDPIN 4		/* Bit 2 of I/O address */
+
+#define DCAR 0		/* DMA command/address register */
+#define CCAR SDPIN		/* channel command/address register */
+#define DATAREG DCPIN + SDPIN	/* serial data register */
+#define MSBONLY 0x41
+#define LSBONLY 0x40
+
+/*
+ * These macros define the register address (ordinal number)
+ * used for writing address/value pairs to the USC.
+ */
+
+#define CMR	0x02	/* Channel mode Register */
+#define CCSR	0x04	/* Channel Command/status Register */
+#define CCR	0x06	/* Channel Control Register */
+#define PSR	0x08	/* Port status Register */
+#define PCR	0x0a	/* Port Control Register */
+#define TMDR	0x0c	/* Test mode Data Register */
+#define TMCR	0x0e	/* Test mode Control Register */
+#define CMCR	0x10	/* Clock mode Control Register */
+#define HCR	0x12	/* Hardware Configuration Register */
+#define IVR	0x14	/* Interrupt Vector Register */
+#define IOCR	0x16	/* Input/Output Control Register */
+#define ICR	0x18	/* Interrupt Control Register */
+#define DCCR	0x1a	/* Daisy Chain Control Register */
+#define MISR	0x1c	/* Misc Interrupt status Register */
+#define SICR	0x1e	/* status Interrupt Control Register */
+#define RDR	0x20	/* Receive Data Register */
+#define RMR	0x22	/* Receive mode Register */
+#define RCSR	0x24	/* Receive Command/status Register */
+#define RICR	0x26	/* Receive Interrupt Control Register */
+#define RSR	0x28	/* Receive Sync Register */
+#define RCLR	0x2a	/* Receive count Limit Register */
+#define RCCR	0x2c	/* Receive Character count Register */
+#define TC0R	0x2e	/* Time Constant 0 Register */
+#define TDR	0x30	/* Transmit Data Register */
+#define TMR	0x32	/* Transmit mode Register */
+#define TCSR	0x34	/* Transmit Command/status Register */
+#define TICR	0x36	/* Transmit Interrupt Control Register */
+#define TSR	0x38	/* Transmit Sync Register */
+#define TCLR	0x3a	/* Transmit count Limit Register */
+#define TCCR	0x3c	/* Transmit Character count Register */
+#define TC1R	0x3e	/* Time Constant 1 Register */
+
+
+/*
+ * MACRO DEFINITIONS FOR DMA REGISTERS
+ */
+
+#define DCR	0x06	/* DMA Control Register (shared) */
+#define DACR	0x08	/* DMA Array count Register (shared) */
+#define BDCR	0x12	/* Burst/Dwell Control Register (shared) */
+#define DIVR	0x14	/* DMA Interrupt Vector Register (shared) */	
+#define DICR	0x18	/* DMA Interrupt Control Register (shared) */
+#define CDIR	0x1a	/* Clear DMA Interrupt Register (shared) */
+#define SDIR	0x1c	/* Set DMA Interrupt Register (shared) */
+
+#define TDMR	0x02	/* Transmit DMA mode Register */
+#define TDIAR	0x1e	/* Transmit DMA Interrupt Arm Register */
+#define TBCR	0x2a	/* Transmit Byte count Register */
+#define TARL	0x2c	/* Transmit Address Register (low) */
+#define TARU	0x2e	/* Transmit Address Register (high) */
+#define NTBCR	0x3a	/* Next Transmit Byte count Register */
+#define NTARL	0x3c	/* Next Transmit Address Register (low) */
+#define NTARU	0x3e	/* Next Transmit Address Register (high) */
+
+#define RDMR	0x82	/* Receive DMA mode Register (non-shared) */
+#define RDIAR	0x9e	/* Receive DMA Interrupt Arm Register */
+#define RBCR	0xaa	/* Receive Byte count Register */
+#define RARL	0xac	/* Receive Address Register (low) */
+#define RARU	0xae	/* Receive Address Register (high) */
+#define NRBCR	0xba	/* Next Receive Byte count Register */
+#define NRARL	0xbc	/* Next Receive Address Register (low) */
+#define NRARU	0xbe	/* Next Receive Address Register (high) */
+
+
+/*
+ * MACRO DEFINITIONS FOR MODEM STATUS BITS
+ */
+
+#define MODEMSTATUS_DTR 0x80
+#define MODEMSTATUS_DSR 0x40
+#define MODEMSTATUS_RTS 0x20
+#define MODEMSTATUS_CTS 0x10
+#define MODEMSTATUS_RI  0x04
+#define MODEMSTATUS_DCD 0x01
+
+
+/*
+ * Channel Command/Address Register (CCAR) Command Codes
+ */
+
+#define RTCmd_Null			0x0000
+#define RTCmd_ResetHighestIus		0x1000
+#define RTCmd_TriggerChannelLoadDma	0x2000
+#define RTCmd_TriggerRxDma		0x2800
+#define RTCmd_TriggerTxDma		0x3000
+#define RTCmd_TriggerRxAndTxDma		0x3800
+#define RTCmd_PurgeRxFifo		0x4800
+#define RTCmd_PurgeTxFifo		0x5000
+#define RTCmd_PurgeRxAndTxFifo		0x5800
+#define RTCmd_LoadRcc			0x6800
+#define RTCmd_LoadTcc			0x7000
+#define RTCmd_LoadRccAndTcc		0x7800
+#define RTCmd_LoadTC0			0x8800
+#define RTCmd_LoadTC1			0x9000
+#define RTCmd_LoadTC0AndTC1		0x9800
+#define RTCmd_SerialDataLSBFirst	0xa000
+#define RTCmd_SerialDataMSBFirst	0xa800
+#define RTCmd_SelectBigEndian		0xb000
+#define RTCmd_SelectLittleEndian	0xb800
+
+
+/*
+ * DMA Command/Address Register (DCAR) Command Codes
+ */
+
+#define DmaCmd_Null			0x0000
+#define DmaCmd_ResetTxChannel		0x1000
+#define DmaCmd_ResetRxChannel		0x1200
+#define DmaCmd_StartTxChannel		0x2000
+#define DmaCmd_StartRxChannel		0x2200
+#define DmaCmd_ContinueTxChannel	0x3000
+#define DmaCmd_ContinueRxChannel	0x3200
+#define DmaCmd_PauseTxChannel		0x4000
+#define DmaCmd_PauseRxChannel		0x4200
+#define DmaCmd_AbortTxChannel		0x5000
+#define DmaCmd_AbortRxChannel		0x5200
+#define DmaCmd_InitTxChannel		0x7000
+#define DmaCmd_InitRxChannel		0x7200
+#define DmaCmd_ResetHighestDmaIus	0x8000
+#define DmaCmd_ResetAllChannels		0x9000
+#define DmaCmd_StartAllChannels		0xa000
+#define DmaCmd_ContinueAllChannels	0xb000
+#define DmaCmd_PauseAllChannels		0xc000
+#define DmaCmd_AbortAllChannels		0xd000
+#define DmaCmd_InitAllChannels		0xf000
+
+#define TCmd_Null			0x0000
+#define TCmd_ClearTxCRC			0x2000
+#define TCmd_SelectTicrTtsaData		0x4000
+#define TCmd_SelectTicrTxFifostatus	0x5000
+#define TCmd_SelectTicrIntLevel		0x6000
+#define TCmd_SelectTicrdma_level		0x7000
+#define TCmd_SendFrame			0x8000
+#define TCmd_SendAbort			0x9000
+#define TCmd_EnableDleInsertion		0xc000
+#define TCmd_DisableDleInsertion	0xd000
+#define TCmd_ClearEofEom		0xe000
+#define TCmd_SetEofEom			0xf000
+
+#define RCmd_Null			0x0000
+#define RCmd_ClearRxCRC			0x2000
+#define RCmd_EnterHuntmode		0x3000
+#define RCmd_SelectRicrRtsaData		0x4000
+#define RCmd_SelectRicrRxFifostatus	0x5000
+#define RCmd_SelectRicrIntLevel		0x6000
+#define RCmd_SelectRicrdma_level		0x7000
+
+/*
+ * Bits for enabling and disabling IRQs in Interrupt Control Register (ICR)
+ */
+ 
+#define RECEIVE_STATUS		BIT5
+#define RECEIVE_DATA		BIT4
+#define TRANSMIT_STATUS		BIT3
+#define TRANSMIT_DATA		BIT2
+#define IO_PIN			BIT1
+#define MISC			BIT0
+
+
+/*
+ * Receive status Bits in Receive Command/status Register RCSR
+ */
+
+#define RXSTATUS_SHORT_FRAME		BIT8
+#define RXSTATUS_CODE_VIOLATION		BIT8
+#define RXSTATUS_EXITED_HUNT		BIT7
+#define RXSTATUS_IDLE_RECEIVED		BIT6
+#define RXSTATUS_BREAK_RECEIVED		BIT5
+#define RXSTATUS_ABORT_RECEIVED		BIT5
+#define RXSTATUS_RXBOUND		BIT4
+#define RXSTATUS_CRC_ERROR		BIT3
+#define RXSTATUS_FRAMING_ERROR		BIT3
+#define RXSTATUS_ABORT			BIT2
+#define RXSTATUS_PARITY_ERROR		BIT2
+#define RXSTATUS_OVERRUN		BIT1
+#define RXSTATUS_DATA_AVAILABLE		BIT0
+#define RXSTATUS_ALL			0x01f6
+#define usc_UnlatchRxstatusBits(a,b) usc_OutReg( (a), RCSR, (u16)((b) & RXSTATUS_ALL) )
+
+/*
+ * Values for setting transmit idle mode in 
+ * Transmit Control/status Register (TCSR)
+ */
+#define IDLEMODE_FLAGS			0x0000
+#define IDLEMODE_ALT_ONE_ZERO		0x0100
+#define IDLEMODE_ZERO			0x0200
+#define IDLEMODE_ONE			0x0300
+#define IDLEMODE_ALT_MARK_SPACE		0x0500
+#define IDLEMODE_SPACE			0x0600
+#define IDLEMODE_MARK			0x0700
+#define IDLEMODE_MASK			0x0700
+
+/*
+ * IUSC revision identifiers
+ */
+#define	IUSC_SL1660			0x4d44
+#define IUSC_PRE_SL1660			0x4553
+
+/*
+ * Transmit status Bits in Transmit Command/status Register (TCSR)
+ */
+
+#define TCSR_PRESERVE			0x0F00
+
+#define TCSR_UNDERWAIT			BIT11
+#define TXSTATUS_PREAMBLE_SENT		BIT7
+#define TXSTATUS_IDLE_SENT		BIT6
+#define TXSTATUS_ABORT_SENT		BIT5
+#define TXSTATUS_EOF_SENT		BIT4
+#define TXSTATUS_EOM_SENT		BIT4
+#define TXSTATUS_CRC_SENT		BIT3
+#define TXSTATUS_ALL_SENT		BIT2
+#define TXSTATUS_UNDERRUN		BIT1
+#define TXSTATUS_FIFO_EMPTY		BIT0
+#define TXSTATUS_ALL			0x00fa
+#define usc_UnlatchTxstatusBits(a,b) usc_OutReg( (a), TCSR, (u16)((a)->tcsr_value + ((b) & 0x00FF)) )
+				
+
+#define MISCSTATUS_RXC_LATCHED		BIT15
+#define MISCSTATUS_RXC			BIT14
+#define MISCSTATUS_TXC_LATCHED		BIT13
+#define MISCSTATUS_TXC			BIT12
+#define MISCSTATUS_RI_LATCHED		BIT11
+#define MISCSTATUS_RI			BIT10
+#define MISCSTATUS_DSR_LATCHED		BIT9
+#define MISCSTATUS_DSR			BIT8
+#define MISCSTATUS_DCD_LATCHED		BIT7
+#define MISCSTATUS_DCD			BIT6
+#define MISCSTATUS_CTS_LATCHED		BIT5
+#define MISCSTATUS_CTS			BIT4
+#define MISCSTATUS_RCC_UNDERRUN		BIT3
+#define MISCSTATUS_DPLL_NO_SYNC		BIT2
+#define MISCSTATUS_BRG1_ZERO		BIT1
+#define MISCSTATUS_BRG0_ZERO		BIT0
+
+#define usc_UnlatchIostatusBits(a,b) usc_OutReg((a),MISR,(u16)((b) & 0xaaa0))
+#define usc_UnlatchMiscstatusBits(a,b) usc_OutReg((a),MISR,(u16)((b) & 0x000f))
+
+#define SICR_RXC_ACTIVE			BIT15
+#define SICR_RXC_INACTIVE		BIT14
+#define SICR_RXC			(BIT15+BIT14)
+#define SICR_TXC_ACTIVE			BIT13
+#define SICR_TXC_INACTIVE		BIT12
+#define SICR_TXC			(BIT13+BIT12)
+#define SICR_RI_ACTIVE			BIT11
+#define SICR_RI_INACTIVE		BIT10
+#define SICR_RI				(BIT11+BIT10)
+#define SICR_DSR_ACTIVE			BIT9
+#define SICR_DSR_INACTIVE		BIT8
+#define SICR_DSR			(BIT9+BIT8)
+#define SICR_DCD_ACTIVE			BIT7
+#define SICR_DCD_INACTIVE		BIT6
+#define SICR_DCD			(BIT7+BIT6)
+#define SICR_CTS_ACTIVE			BIT5
+#define SICR_CTS_INACTIVE		BIT4
+#define SICR_CTS			(BIT5+BIT4)
+#define SICR_RCC_UNDERFLOW		BIT3
+#define SICR_DPLL_NO_SYNC		BIT2
+#define SICR_BRG1_ZERO			BIT1
+#define SICR_BRG0_ZERO			BIT0
+
+void usc_DisableMasterIrqBit( struct mgsl_struct *info );
+void usc_EnableMasterIrqBit( struct mgsl_struct *info );
+void usc_EnableInterrupts( struct mgsl_struct *info, u16 IrqMask );
+void usc_DisableInterrupts( struct mgsl_struct *info, u16 IrqMask );
+void usc_ClearIrqPendingBits( struct mgsl_struct *info, u16 IrqMask );
+
+#define usc_EnableInterrupts( a, b ) \
+	usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0xff00) + 0xc0 + (b)) )
+
+#define usc_DisableInterrupts( a, b ) \
+	usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0xff00) + 0x80 + (b)) )
+
+#define usc_EnableMasterIrqBit(a) \
+	usc_OutReg( (a), ICR, (u16)((usc_InReg((a),ICR) & 0x0f00) + 0xb000) )
+
+#define usc_DisableMasterIrqBit(a) \
+	usc_OutReg( (a), ICR, (u16)(usc_InReg((a),ICR) & 0x7f00) )
+
+#define usc_ClearIrqPendingBits( a, b ) usc_OutReg( (a), DCCR, 0x40 + (b) )
+
+/*
+ * Transmit status Bits in Transmit Control status Register (TCSR)
+ * and Transmit Interrupt Control Register (TICR) (except BIT2, BIT0)
+ */
+
+#define TXSTATUS_PREAMBLE_SENT	BIT7
+#define TXSTATUS_IDLE_SENT	BIT6
+#define TXSTATUS_ABORT_SENT	BIT5
+#define TXSTATUS_EOF		BIT4
+#define TXSTATUS_CRC_SENT	BIT3
+#define TXSTATUS_ALL_SENT	BIT2
+#define TXSTATUS_UNDERRUN	BIT1
+#define TXSTATUS_FIFO_EMPTY	BIT0
+
+#define DICR_MASTER		BIT15
+#define DICR_TRANSMIT		BIT0
+#define DICR_RECEIVE		BIT1
+
+#define usc_EnableDmaInterrupts(a,b) \
+	usc_OutDmaReg( (a), DICR, (u16)(usc_InDmaReg((a),DICR) | (b)) )
+
+#define usc_DisableDmaInterrupts(a,b) \
+	usc_OutDmaReg( (a), DICR, (u16)(usc_InDmaReg((a),DICR) & ~(b)) )
+
+#define usc_EnableStatusIrqs(a,b) \
+	usc_OutReg( (a), SICR, (u16)(usc_InReg((a),SICR) | (b)) )
+
+#define usc_DisablestatusIrqs(a,b) \
+	usc_OutReg( (a), SICR, (u16)(usc_InReg((a),SICR) & ~(b)) )
+
+/* Transmit status Bits in Transmit Control status Register (TCSR) */
+/* and Transmit Interrupt Control Register (TICR) (except BIT2, BIT0) */
+
+
+#define DISABLE_UNCONDITIONAL    0
+#define DISABLE_END_OF_FRAME     1
+#define ENABLE_UNCONDITIONAL     2
+#define ENABLE_AUTO_CTS          3
+#define ENABLE_AUTO_DCD          3
+#define usc_EnableTransmitter(a,b) \
+	usc_OutReg( (a), TMR, (u16)((usc_InReg((a),TMR) & 0xfffc) | (b)) )
+#define usc_EnableReceiver(a,b) \
+	usc_OutReg( (a), RMR, (u16)((usc_InReg((a),RMR) & 0xfffc) | (b)) )
+
+static u16  usc_InDmaReg( struct mgsl_struct *info, u16 Port );
+static void usc_OutDmaReg( struct mgsl_struct *info, u16 Port, u16 Value );
+static void usc_DmaCmd( struct mgsl_struct *info, u16 Cmd );
+
+static u16  usc_InReg( struct mgsl_struct *info, u16 Port );
+static void usc_OutReg( struct mgsl_struct *info, u16 Port, u16 Value );
+static void usc_RTCmd( struct mgsl_struct *info, u16 Cmd );
+void usc_RCmd( struct mgsl_struct *info, u16 Cmd );
+void usc_TCmd( struct mgsl_struct *info, u16 Cmd );
+
+#define usc_TCmd(a,b) usc_OutReg((a), TCSR, (u16)((a)->tcsr_value + (b)))
+#define usc_RCmd(a,b) usc_OutReg((a), RCSR, (b))
+
+#define usc_SetTransmitSyncChars(a,s0,s1) usc_OutReg((a), TSR, (u16)(((u16)s0<<8)|(u16)s1))
+
+static void usc_process_rxoverrun_sync( struct mgsl_struct *info );
+static void usc_start_receiver( struct mgsl_struct *info );
+static void usc_stop_receiver( struct mgsl_struct *info );
+
+static void usc_start_transmitter( struct mgsl_struct *info );
+static void usc_stop_transmitter( struct mgsl_struct *info );
+static void usc_set_txidle( struct mgsl_struct *info );
+static void usc_load_txfifo( struct mgsl_struct *info );
+
+static void usc_enable_aux_clock( struct mgsl_struct *info, u32 DataRate );
+static void usc_enable_loopback( struct mgsl_struct *info, int enable );
+
+static void usc_get_serial_signals( struct mgsl_struct *info );
+static void usc_set_serial_signals( struct mgsl_struct *info );
+
+static void usc_reset( struct mgsl_struct *info );
+
+static void usc_set_sync_mode( struct mgsl_struct *info );
+static void usc_set_sdlc_mode( struct mgsl_struct *info );
+static void usc_set_async_mode( struct mgsl_struct *info );
+static void usc_enable_async_clock( struct mgsl_struct *info, u32 DataRate );
+
+static void usc_loopback_frame( struct mgsl_struct *info );
+
+static void mgsl_tx_timeout(unsigned long context);
+
+
+static void usc_loopmode_cancel_transmit( struct mgsl_struct * info );
+static void usc_loopmode_insert_request( struct mgsl_struct * info );
+static int usc_loopmode_active( struct mgsl_struct * info);
+static void usc_loopmode_send_done( struct mgsl_struct * info );
+
+static int mgsl_ioctl_common(struct mgsl_struct *info, unsigned int cmd, unsigned long arg);
+
+#ifdef CONFIG_HDLC
+#define dev_to_port(D) (dev_to_hdlc(D)->priv)
+static void hdlcdev_tx_done(struct mgsl_struct *info);
+static void hdlcdev_rx(struct mgsl_struct *info, char *buf, int size);
+static int  hdlcdev_init(struct mgsl_struct *info);
+static void hdlcdev_exit(struct mgsl_struct *info);
+#endif
+
+/*
+ * Defines a BUS descriptor value for the PCI adapter
+ * local bus address ranges.
+ */
+
+#define BUS_DESCRIPTOR( WrHold, WrDly, RdDly, Nwdd, Nwad, Nxda, Nrdd, Nrad ) \
+(0x00400020 + \
+((WrHold) << 30) + \
+((WrDly)  << 28) + \
+((RdDly)  << 26) + \
+((Nwdd)   << 20) + \
+((Nwad)   << 15) + \
+((Nxda)   << 13) + \
+((Nrdd)   << 11) + \
+((Nrad)   <<  6) )
+
+static void mgsl_trace_block(struct mgsl_struct *info,const char* data, int count, int xmit);
+
+/*
+ * Adapter diagnostic routines
+ */
+static BOOLEAN mgsl_register_test( struct mgsl_struct *info );
+static BOOLEAN mgsl_irq_test( struct mgsl_struct *info );
+static BOOLEAN mgsl_dma_test( struct mgsl_struct *info );
+static BOOLEAN mgsl_memory_test( struct mgsl_struct *info );
+static int mgsl_adapter_test( struct mgsl_struct *info );
+
+/*
+ * device and resource management routines
+ */
+static int mgsl_claim_resources(struct mgsl_struct *info);
+static void mgsl_release_resources(struct mgsl_struct *info);
+static void mgsl_add_device(struct mgsl_struct *info);
+static struct mgsl_struct* mgsl_allocate_device(void);
+
+/*
+ * DMA buffer manupulation functions.
+ */
+static void mgsl_free_rx_frame_buffers( struct mgsl_struct *info, unsigned int StartIndex, unsigned int EndIndex );
+static int  mgsl_get_rx_frame( struct mgsl_struct *info );
+static int  mgsl_get_raw_rx_frame( struct mgsl_struct *info );
+static void mgsl_reset_rx_dma_buffers( struct mgsl_struct *info );
+static void mgsl_reset_tx_dma_buffers( struct mgsl_struct *info );
+static int num_free_tx_dma_buffers(struct mgsl_struct *info);
+static void mgsl_load_tx_dma_buffer( struct mgsl_struct *info, const char *Buffer, unsigned int BufferSize);
+static void mgsl_load_pci_memory(char* TargetPtr, const char* SourcePtr, unsigned short count);
+
+/*
+ * DMA and Shared Memory buffer allocation and formatting
+ */
+static int  mgsl_allocate_dma_buffers(struct mgsl_struct *info);
+static void mgsl_free_dma_buffers(struct mgsl_struct *info);
+static int  mgsl_alloc_frame_memory(struct mgsl_struct *info, DMABUFFERENTRY *BufferList,int Buffercount);
+static void mgsl_free_frame_memory(struct mgsl_struct *info, DMABUFFERENTRY *BufferList,int Buffercount);
+static int  mgsl_alloc_buffer_list_memory(struct mgsl_struct *info);
+static void mgsl_free_buffer_list_memory(struct mgsl_struct *info);
+static int mgsl_alloc_intermediate_rxbuffer_memory(struct mgsl_struct *info);
+static void mgsl_free_intermediate_rxbuffer_memory(struct mgsl_struct *info);
+static int mgsl_alloc_intermediate_txbuffer_memory(struct mgsl_struct *info);
+static void mgsl_free_intermediate_txbuffer_memory(struct mgsl_struct *info);
+static int load_next_tx_holding_buffer(struct mgsl_struct *info);
+static int save_tx_buffer_request(struct mgsl_struct *info,const char *Buffer, unsigned int BufferSize);
+
+/*
+ * Bottom half interrupt handlers
+ */
+static void mgsl_bh_handler(void* Context);
+static void mgsl_bh_receive(struct mgsl_struct *info);
+static void mgsl_bh_transmit(struct mgsl_struct *info);
+static void mgsl_bh_status(struct mgsl_struct *info);
+
+/*
+ * Interrupt handler routines and dispatch table.
+ */
+static void mgsl_isr_null( struct mgsl_struct *info );
+static void mgsl_isr_transmit_data( struct mgsl_struct *info );
+static void mgsl_isr_receive_data( struct mgsl_struct *info );
+static void mgsl_isr_receive_status( struct mgsl_struct *info );
+static void mgsl_isr_transmit_status( struct mgsl_struct *info );
+static void mgsl_isr_io_pin( struct mgsl_struct *info );
+static void mgsl_isr_misc( struct mgsl_struct *info );
+static void mgsl_isr_receive_dma( struct mgsl_struct *info );
+static void mgsl_isr_transmit_dma( struct mgsl_struct *info );
+
+typedef void (*isr_dispatch_func)(struct mgsl_struct *);
+
+static isr_dispatch_func UscIsrTable[7] =
+{
+	mgsl_isr_null,
+	mgsl_isr_misc,
+	mgsl_isr_io_pin,
+	mgsl_isr_transmit_data,
+	mgsl_isr_transmit_status,
+	mgsl_isr_receive_data,
+	mgsl_isr_receive_status
+};
+
+/*
+ * ioctl call handlers
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file);
+static int tiocmset(struct tty_struct *tty, struct file *file,
+		    unsigned int set, unsigned int clear);
+static int mgsl_get_stats(struct mgsl_struct * info, struct mgsl_icount
+	__user *user_icount);
+static int mgsl_get_params(struct mgsl_struct * info, MGSL_PARAMS  __user *user_params);
+static int mgsl_set_params(struct mgsl_struct * info, MGSL_PARAMS  __user *new_params);
+static int mgsl_get_txidle(struct mgsl_struct * info, int __user *idle_mode);
+static int mgsl_set_txidle(struct mgsl_struct * info, int idle_mode);
+static int mgsl_txenable(struct mgsl_struct * info, int enable);
+static int mgsl_txabort(struct mgsl_struct * info);
+static int mgsl_rxenable(struct mgsl_struct * info, int enable);
+static int mgsl_wait_event(struct mgsl_struct * info, int __user *mask);
+static int mgsl_loopmode_send_done( struct mgsl_struct * info );
+
+/* set non-zero on successful registration with PCI subsystem */
+static int pci_registered;
+
+/*
+ * Global linked list of SyncLink devices
+ */
+static struct mgsl_struct *mgsl_device_list;
+static int mgsl_device_count;
+
+/*
+ * Set this param to non-zero to load eax with the
+ * .text section address and breakpoint on module load.
+ * This is useful for use with gdb and add-symbol-file command.
+ */
+static int break_on_load;
+
+/*
+ * Driver major number, defaults to zero to get auto
+ * assigned major number. May be forced as module parameter.
+ */
+static int ttymajor;
+
+/*
+ * Array of user specified options for ISA adapters.
+ */
+static int io[MAX_ISA_DEVICES];
+static int irq[MAX_ISA_DEVICES];
+static int dma[MAX_ISA_DEVICES];
+static int debug_level;
+static int maxframe[MAX_TOTAL_DEVICES];
+static int dosyncppp[MAX_TOTAL_DEVICES];
+static int txdmabufs[MAX_TOTAL_DEVICES];
+static int txholdbufs[MAX_TOTAL_DEVICES];
+	
+module_param(break_on_load, bool, 0);
+module_param(ttymajor, int, 0);
+module_param_array(io, int, NULL, 0);
+module_param_array(irq, int, NULL, 0);
+module_param_array(dma, int, NULL, 0);
+module_param(debug_level, int, 0);
+module_param_array(maxframe, int, NULL, 0);
+module_param_array(dosyncppp, int, NULL, 0);
+module_param_array(txdmabufs, int, NULL, 0);
+module_param_array(txholdbufs, int, NULL, 0);
+
+static char *driver_name = "SyncLink serial driver";
+static char *driver_version = "$Revision: 4.28 $";
+
+static int synclink_init_one (struct pci_dev *dev,
+				     const struct pci_device_id *ent);
+static void synclink_remove_one (struct pci_dev *dev);
+
+static struct pci_device_id synclink_pci_tbl[] = {
+	{ PCI_VENDOR_ID_MICROGATE, PCI_DEVICE_ID_MICROGATE_USC, PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_MICROGATE, 0x0210, PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, synclink_pci_tbl);
+
+MODULE_LICENSE("GPL");
+
+static struct pci_driver synclink_pci_driver = {
+	.name		= "synclink",
+	.id_table	= synclink_pci_tbl,
+	.probe		= synclink_init_one,
+	.remove		= __devexit_p(synclink_remove_one),
+};
+
+static struct tty_driver *serial_driver;
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+
+static void mgsl_change_params(struct mgsl_struct *info);
+static void mgsl_wait_until_sent(struct tty_struct *tty, int timeout);
+
+/*
+ * 1st function defined in .text section. Calling this function in
+ * init_module() followed by a breakpoint allows a remote debugger
+ * (gdb) to get the .text address for the add-symbol-file command.
+ * This allows remote debugging of dynamically loadable modules.
+ */
+static void* mgsl_get_text_ptr(void)
+{
+	return mgsl_get_text_ptr;
+}
+
+/*
+ * tmp_buf is used as a temporary buffer by mgsl_write.  We need to
+ * lock it in case the COPY_FROM_USER blocks while swapping in a page,
+ * and some other program tries to do a serial write at the same time.
+ * Since the lock will only come under contention when the system is
+ * swapping and available memory is low, it makes sense to share one
+ * buffer across all the serial ioports, since it significantly saves
+ * memory if large numbers of serial ports are open.
+ */
+static unsigned char *tmp_buf;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+static inline int mgsl_paranoia_check(struct mgsl_struct *info,
+					char *name, const char *routine)
+{
+#ifdef MGSL_PARANOIA_CHECK
+	static const char *badmagic =
+		"Warning: bad magic number for mgsl struct (%s) in %s\n";
+	static const char *badinfo =
+		"Warning: null mgsl_struct for (%s) in %s\n";
+
+	if (!info) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (info->magic != MGSL_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+#else
+	if (!info)
+		return 1;
+#endif
+	return 0;
+}
+
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_receive_buf  - pass receive data to line discipline
+ */
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+			      const __u8 *data, char *flags, int count)
+{
+	struct tty_ldisc *ld;
+	if (!tty)
+		return;
+	ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->receive_buf)
+			ld->receive_buf(tty, data, flags, count);
+		tty_ldisc_deref(ld);
+	}
+}
+
+/* mgsl_stop()		throttle (stop) transmitter
+ * 	
+ * Arguments:		tty	pointer to tty info structure
+ * Return Value:	None
+ */
+static void mgsl_stop(struct tty_struct *tty)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_stop"))
+		return;
+	
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("mgsl_stop(%s)\n",info->device_name);	
+		
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	if (info->tx_enabled)
+	 	usc_stop_transmitter(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+}	/* end of mgsl_stop() */
+
+/* mgsl_start()		release (start) transmitter
+ * 	
+ * Arguments:		tty	pointer to tty info structure
+ * Return Value:	None
+ */
+static void mgsl_start(struct tty_struct *tty)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_start"))
+		return;
+	
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("mgsl_start(%s)\n",info->device_name);	
+		
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	if (!info->tx_enabled)
+	 	usc_start_transmitter(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+}	/* end of mgsl_start() */
+
+/*
+ * Bottom half work queue access functions
+ */
+
+/* mgsl_bh_action()	Return next bottom half action to perform.
+ * Return Value:	BH action code or 0 if nothing to do.
+ */
+static int mgsl_bh_action(struct mgsl_struct *info)
+{
+	unsigned long flags;
+	int rc = 0;
+	
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+
+	if (info->pending_bh & BH_RECEIVE) {
+		info->pending_bh &= ~BH_RECEIVE;
+		rc = BH_RECEIVE;
+	} else if (info->pending_bh & BH_TRANSMIT) {
+		info->pending_bh &= ~BH_TRANSMIT;
+		rc = BH_TRANSMIT;
+	} else if (info->pending_bh & BH_STATUS) {
+		info->pending_bh &= ~BH_STATUS;
+		rc = BH_STATUS;
+	}
+
+	if (!rc) {
+		/* Mark BH routine as complete */
+		info->bh_running   = 0;
+		info->bh_requested = 0;
+	}
+	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+	return rc;
+}
+
+/*
+ * 	Perform bottom half processing of work items queued by ISR.
+ */
+static void mgsl_bh_handler(void* Context)
+{
+	struct mgsl_struct *info = (struct mgsl_struct*)Context;
+	int action;
+
+	if (!info)
+		return;
+		
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):mgsl_bh_handler(%s) entry\n",
+			__FILE__,__LINE__,info->device_name);
+	
+	info->bh_running = 1;
+
+	while((action = mgsl_bh_action(info)) != 0) {
+	
+		/* Process work item */
+		if ( debug_level >= DEBUG_LEVEL_BH )
+			printk( "%s(%d):mgsl_bh_handler() work item action=%d\n",
+				__FILE__,__LINE__,action);
+
+		switch (action) {
+		
+		case BH_RECEIVE:
+			mgsl_bh_receive(info);
+			break;
+		case BH_TRANSMIT:
+			mgsl_bh_transmit(info);
+			break;
+		case BH_STATUS:
+			mgsl_bh_status(info);
+			break;
+		default:
+			/* unknown work item ID */
+			printk("Unknown work item ID=%08X!\n", action);
+			break;
+		}
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):mgsl_bh_handler(%s) exit\n",
+			__FILE__,__LINE__,info->device_name);
+}
+
+static void mgsl_bh_receive(struct mgsl_struct *info)
+{
+	int (*get_rx_frame)(struct mgsl_struct *info) =
+		(info->params.mode == MGSL_MODE_HDLC ? mgsl_get_rx_frame : mgsl_get_raw_rx_frame);
+
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):mgsl_bh_receive(%s)\n",
+			__FILE__,__LINE__,info->device_name);
+	
+	do
+	{
+		if (info->rx_rcc_underrun) {
+			unsigned long flags;
+			spin_lock_irqsave(&info->irq_spinlock,flags);
+			usc_start_receiver(info);
+			spin_unlock_irqrestore(&info->irq_spinlock,flags);
+			return;
+		}
+	} while(get_rx_frame(info));
+}
+
+static void mgsl_bh_transmit(struct mgsl_struct *info)
+{
+	struct tty_struct *tty = info->tty;
+	unsigned long flags;
+	
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):mgsl_bh_transmit() entry on %s\n",
+			__FILE__,__LINE__,info->device_name);
+
+	if (tty) {
+		tty_wakeup(tty);
+		wake_up_interruptible(&tty->write_wait);
+	}
+
+	/* if transmitter idle and loopmode_send_done_requested
+	 * then start echoing RxD to TxD
+	 */
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+ 	if ( !info->tx_active && info->loopmode_send_done_requested )
+ 		usc_loopmode_send_done( info );
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+}
+
+static void mgsl_bh_status(struct mgsl_struct *info)
+{
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):mgsl_bh_status() entry on %s\n",
+			__FILE__,__LINE__,info->device_name);
+
+	info->ri_chkcount = 0;
+	info->dsr_chkcount = 0;
+	info->dcd_chkcount = 0;
+	info->cts_chkcount = 0;
+}
+
+/* mgsl_isr_receive_status()
+ * 
+ *	Service a receive status interrupt. The type of status
+ *	interrupt is indicated by the state of the RCSR.
+ *	This is only used for HDLC mode.
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_isr_receive_status( struct mgsl_struct *info )
+{
+	u16 status = usc_InReg( info, RCSR );
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_isr_receive_status status=%04X\n",
+			__FILE__,__LINE__,status);
+			
+ 	if ( (status & RXSTATUS_ABORT_RECEIVED) && 
+		info->loopmode_insert_requested &&
+ 		usc_loopmode_active(info) )
+ 	{
+		++info->icount.rxabort;
+	 	info->loopmode_insert_requested = FALSE;
+ 
+ 		/* clear CMR:13 to start echoing RxD to TxD */
+		info->cmr_value &= ~BIT13;
+ 		usc_OutReg(info, CMR, info->cmr_value);
+ 
+		/* disable received abort irq (no longer required) */
+	 	usc_OutReg(info, RICR,
+ 			(usc_InReg(info, RICR) & ~RXSTATUS_ABORT_RECEIVED));
+ 	}
+
+	if (status & (RXSTATUS_EXITED_HUNT + RXSTATUS_IDLE_RECEIVED)) {
+		if (status & RXSTATUS_EXITED_HUNT)
+			info->icount.exithunt++;
+		if (status & RXSTATUS_IDLE_RECEIVED)
+			info->icount.rxidle++;
+		wake_up_interruptible(&info->event_wait_q);
+	}
+
+	if (status & RXSTATUS_OVERRUN){
+		info->icount.rxover++;
+		usc_process_rxoverrun_sync( info );
+	}
+
+	usc_ClearIrqPendingBits( info, RECEIVE_STATUS );
+	usc_UnlatchRxstatusBits( info, status );
+
+}	/* end of mgsl_isr_receive_status() */
+
+/* mgsl_isr_transmit_status()
+ * 
+ * 	Service a transmit status interrupt
+ *	HDLC mode :end of transmit frame
+ *	Async mode:all data is sent
+ * 	transmit status is indicated by bits in the TCSR.
+ * 
+ * Arguments:		info	       pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_isr_transmit_status( struct mgsl_struct *info )
+{
+	u16 status = usc_InReg( info, TCSR );
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_isr_transmit_status status=%04X\n",
+			__FILE__,__LINE__,status);
+	
+	usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
+	usc_UnlatchTxstatusBits( info, status );
+	
+	if ( status & (TXSTATUS_UNDERRUN | TXSTATUS_ABORT_SENT) )
+	{
+		/* finished sending HDLC abort. This may leave	*/
+		/* the TxFifo with data from the aborted frame	*/
+		/* so purge the TxFifo. Also shutdown the DMA	*/
+		/* channel in case there is data remaining in 	*/
+		/* the DMA buffer				*/
+ 		usc_DmaCmd( info, DmaCmd_ResetTxChannel );
+ 		usc_RTCmd( info, RTCmd_PurgeTxFifo );
+	}
+ 
+	if ( status & TXSTATUS_EOF_SENT )
+		info->icount.txok++;
+	else if ( status & TXSTATUS_UNDERRUN )
+		info->icount.txunder++;
+	else if ( status & TXSTATUS_ABORT_SENT )
+		info->icount.txabort++;
+	else
+		info->icount.txunder++;
+			
+	info->tx_active = 0;
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+	del_timer(&info->tx_timer);	
+	
+	if ( info->drop_rts_on_tx_done ) {
+		usc_get_serial_signals( info );
+		if ( info->serial_signals & SerialSignal_RTS ) {
+			info->serial_signals &= ~SerialSignal_RTS;
+			usc_set_serial_signals( info );
+		}
+		info->drop_rts_on_tx_done = 0;
+	}
+
+#ifdef CONFIG_HDLC
+	if (info->netcount)
+		hdlcdev_tx_done(info);
+	else 
+#endif
+	{
+		if (info->tty->stopped || info->tty->hw_stopped) {
+			usc_stop_transmitter(info);
+			return;
+		}
+		info->pending_bh |= BH_TRANSMIT;
+	}
+
+}	/* end of mgsl_isr_transmit_status() */
+
+/* mgsl_isr_io_pin()
+ * 
+ * 	Service an Input/Output pin interrupt. The type of
+ * 	interrupt is indicated by bits in the MISR
+ * 	
+ * Arguments:		info	       pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_isr_io_pin( struct mgsl_struct *info )
+{
+ 	struct	mgsl_icount *icount;
+	u16 status = usc_InReg( info, MISR );
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_isr_io_pin status=%04X\n",
+			__FILE__,__LINE__,status);
+			
+	usc_ClearIrqPendingBits( info, IO_PIN );
+	usc_UnlatchIostatusBits( info, status );
+
+	if (status & (MISCSTATUS_CTS_LATCHED | MISCSTATUS_DCD_LATCHED |
+	              MISCSTATUS_DSR_LATCHED | MISCSTATUS_RI_LATCHED) ) {
+		icount = &info->icount;
+		/* update input line counters */
+		if (status & MISCSTATUS_RI_LATCHED) {
+			if ((info->ri_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+				usc_DisablestatusIrqs(info,SICR_RI);
+			icount->rng++;
+			if ( status & MISCSTATUS_RI )
+				info->input_signal_events.ri_up++;	
+			else
+				info->input_signal_events.ri_down++;	
+		}
+		if (status & MISCSTATUS_DSR_LATCHED) {
+			if ((info->dsr_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+				usc_DisablestatusIrqs(info,SICR_DSR);
+			icount->dsr++;
+			if ( status & MISCSTATUS_DSR )
+				info->input_signal_events.dsr_up++;
+			else
+				info->input_signal_events.dsr_down++;
+		}
+		if (status & MISCSTATUS_DCD_LATCHED) {
+			if ((info->dcd_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+				usc_DisablestatusIrqs(info,SICR_DCD);
+			icount->dcd++;
+			if (status & MISCSTATUS_DCD) {
+				info->input_signal_events.dcd_up++;
+			} else
+				info->input_signal_events.dcd_down++;
+#ifdef CONFIG_HDLC
+			if (info->netcount)
+				hdlc_set_carrier(status & MISCSTATUS_DCD, info->netdev);
+#endif
+		}
+		if (status & MISCSTATUS_CTS_LATCHED)
+		{
+			if ((info->cts_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT)
+				usc_DisablestatusIrqs(info,SICR_CTS);
+			icount->cts++;
+			if ( status & MISCSTATUS_CTS )
+				info->input_signal_events.cts_up++;
+			else
+				info->input_signal_events.cts_down++;
+		}
+		wake_up_interruptible(&info->status_event_wait_q);
+		wake_up_interruptible(&info->event_wait_q);
+
+		if ( (info->flags & ASYNC_CHECK_CD) && 
+		     (status & MISCSTATUS_DCD_LATCHED) ) {
+			if ( debug_level >= DEBUG_LEVEL_ISR )
+				printk("%s CD now %s...", info->device_name,
+				       (status & MISCSTATUS_DCD) ? "on" : "off");
+			if (status & MISCSTATUS_DCD)
+				wake_up_interruptible(&info->open_wait);
+			else {
+				if ( debug_level >= DEBUG_LEVEL_ISR )
+					printk("doing serial hangup...");
+				if (info->tty)
+					tty_hangup(info->tty);
+			}
+		}
+	
+		if ( (info->flags & ASYNC_CTS_FLOW) && 
+		     (status & MISCSTATUS_CTS_LATCHED) ) {
+			if (info->tty->hw_stopped) {
+				if (status & MISCSTATUS_CTS) {
+					if ( debug_level >= DEBUG_LEVEL_ISR )
+						printk("CTS tx start...");
+					if (info->tty)
+						info->tty->hw_stopped = 0;
+					usc_start_transmitter(info);
+					info->pending_bh |= BH_TRANSMIT;
+					return;
+				}
+			} else {
+				if (!(status & MISCSTATUS_CTS)) {
+					if ( debug_level >= DEBUG_LEVEL_ISR )
+						printk("CTS tx stop...");
+					if (info->tty)
+						info->tty->hw_stopped = 1;
+					usc_stop_transmitter(info);
+				}
+			}
+		}
+	}
+
+	info->pending_bh |= BH_STATUS;
+	
+	/* for diagnostics set IRQ flag */
+	if ( status & MISCSTATUS_TXC_LATCHED ){
+		usc_OutReg( info, SICR,
+			(unsigned short)(usc_InReg(info,SICR) & ~(SICR_TXC_ACTIVE+SICR_TXC_INACTIVE)) );
+		usc_UnlatchIostatusBits( info, MISCSTATUS_TXC_LATCHED );
+		info->irq_occurred = 1;
+	}
+
+}	/* end of mgsl_isr_io_pin() */
+
+/* mgsl_isr_transmit_data()
+ * 
+ * 	Service a transmit data interrupt (async mode only).
+ * 
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_isr_transmit_data( struct mgsl_struct *info )
+{
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_isr_transmit_data xmit_cnt=%d\n",
+			__FILE__,__LINE__,info->xmit_cnt);
+			
+	usc_ClearIrqPendingBits( info, TRANSMIT_DATA );
+	
+	if (info->tty->stopped || info->tty->hw_stopped) {
+		usc_stop_transmitter(info);
+		return;
+	}
+	
+	if ( info->xmit_cnt )
+		usc_load_txfifo( info );
+	else
+		info->tx_active = 0;
+		
+	if (info->xmit_cnt < WAKEUP_CHARS)
+		info->pending_bh |= BH_TRANSMIT;
+
+}	/* end of mgsl_isr_transmit_data() */
+
+/* mgsl_isr_receive_data()
+ * 
+ * 	Service a receive data interrupt. This occurs
+ * 	when operating in asynchronous interrupt transfer mode.
+ *	The receive data FIFO is flushed to the receive data buffers. 
+ * 
+ * Arguments:		info		pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_isr_receive_data( struct mgsl_struct *info )
+{
+	int Fifocount;
+	u16 status;
+	unsigned char DataByte;
+ 	struct tty_struct *tty = info->tty;
+ 	struct	mgsl_icount *icount = &info->icount;
+	
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_isr_receive_data\n",
+			__FILE__,__LINE__);
+
+	usc_ClearIrqPendingBits( info, RECEIVE_DATA );
+	
+	/* select FIFO status for RICR readback */
+	usc_RCmd( info, RCmd_SelectRicrRxFifostatus );
+
+	/* clear the Wordstatus bit so that status readback */
+	/* only reflects the status of this byte */
+	usc_OutReg( info, RICR+LSBONLY, (u16)(usc_InReg(info, RICR+LSBONLY) & ~BIT3 ));
+
+	/* flush the receive FIFO */
+
+	while( (Fifocount = (usc_InReg(info,RICR) >> 8)) ) {
+		/* read one byte from RxFIFO */
+		outw( (inw(info->io_base + CCAR) & 0x0780) | (RDR+LSBONLY),
+		      info->io_base + CCAR );
+		DataByte = inb( info->io_base + CCAR );
+
+		/* get the status of the received byte */
+		status = usc_InReg(info, RCSR);
+		if ( status & (RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR +
+				RXSTATUS_OVERRUN + RXSTATUS_BREAK_RECEIVED) )
+			usc_UnlatchRxstatusBits(info,RXSTATUS_ALL);
+		
+		if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+			continue;
+			
+		*tty->flip.char_buf_ptr = DataByte;
+		icount->rx++;
+		
+		*tty->flip.flag_buf_ptr = 0;
+		if ( status & (RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR +
+				RXSTATUS_OVERRUN + RXSTATUS_BREAK_RECEIVED) ) {
+			printk("rxerr=%04X\n",status);					
+			/* update error statistics */
+			if ( status & RXSTATUS_BREAK_RECEIVED ) {
+				status &= ~(RXSTATUS_FRAMING_ERROR + RXSTATUS_PARITY_ERROR);
+				icount->brk++;
+			} else if (status & RXSTATUS_PARITY_ERROR) 
+				icount->parity++;
+			else if (status & RXSTATUS_FRAMING_ERROR)
+				icount->frame++;
+			else if (status & RXSTATUS_OVERRUN) {
+				/* must issue purge fifo cmd before */
+				/* 16C32 accepts more receive chars */
+				usc_RTCmd(info,RTCmd_PurgeRxFifo);
+				icount->overrun++;
+			}
+
+			/* discard char if tty control flags say so */					
+			if (status & info->ignore_status_mask)
+				continue;
+				
+			status &= info->read_status_mask;
+		
+			if (status & RXSTATUS_BREAK_RECEIVED) {
+				*tty->flip.flag_buf_ptr = TTY_BREAK;
+				if (info->flags & ASYNC_SAK)
+					do_SAK(tty);
+			} else if (status & RXSTATUS_PARITY_ERROR)
+				*tty->flip.flag_buf_ptr = TTY_PARITY;
+			else if (status & RXSTATUS_FRAMING_ERROR)
+				*tty->flip.flag_buf_ptr = TTY_FRAME;
+			if (status & RXSTATUS_OVERRUN) {
+				/* Overrun is special, since it's
+				 * reported immediately, and doesn't
+				 * affect the current character
+				 */
+				if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+					tty->flip.count++;
+					tty->flip.flag_buf_ptr++;
+					tty->flip.char_buf_ptr++;
+					*tty->flip.flag_buf_ptr = TTY_OVERRUN;
+				}
+			}
+		}	/* end of if (error) */
+		
+		tty->flip.flag_buf_ptr++;
+		tty->flip.char_buf_ptr++;
+		tty->flip.count++;
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_ISR ) {
+		printk("%s(%d):mgsl_isr_receive_data flip count=%d\n",
+			__FILE__,__LINE__,tty->flip.count);
+		printk("%s(%d):rx=%d brk=%d parity=%d frame=%d overrun=%d\n",
+			__FILE__,__LINE__,icount->rx,icount->brk,
+			icount->parity,icount->frame,icount->overrun);
+	}
+			
+	if ( tty->flip.count )
+		tty_flip_buffer_push(tty);
+}
+
+/* mgsl_isr_misc()
+ * 
+ * 	Service a miscellaneos interrupt source.
+ * 	
+ * Arguments:		info		pointer to device extension (instance data)
+ * Return Value:	None
+ */
+static void mgsl_isr_misc( struct mgsl_struct *info )
+{
+	u16 status = usc_InReg( info, MISR );
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_isr_misc status=%04X\n",
+			__FILE__,__LINE__,status);
+			
+	if ((status & MISCSTATUS_RCC_UNDERRUN) &&
+	    (info->params.mode == MGSL_MODE_HDLC)) {
+
+		/* turn off receiver and rx DMA */
+		usc_EnableReceiver(info,DISABLE_UNCONDITIONAL);
+		usc_DmaCmd(info, DmaCmd_ResetRxChannel);
+		usc_UnlatchRxstatusBits(info, RXSTATUS_ALL);
+		usc_ClearIrqPendingBits(info, RECEIVE_DATA + RECEIVE_STATUS);
+		usc_DisableInterrupts(info, RECEIVE_DATA + RECEIVE_STATUS);
+
+		/* schedule BH handler to restart receiver */
+		info->pending_bh |= BH_RECEIVE;
+		info->rx_rcc_underrun = 1;
+	}
+
+	usc_ClearIrqPendingBits( info, MISC );
+	usc_UnlatchMiscstatusBits( info, status );
+
+}	/* end of mgsl_isr_misc() */
+
+/* mgsl_isr_null()
+ *
+ * 	Services undefined interrupt vectors from the
+ * 	USC. (hence this function SHOULD never be called)
+ * 
+ * Arguments:		info		pointer to device extension (instance data)
+ * Return Value:	None
+ */
+static void mgsl_isr_null( struct mgsl_struct *info )
+{
+
+}	/* end of mgsl_isr_null() */
+
+/* mgsl_isr_receive_dma()
+ * 
+ * 	Service a receive DMA channel interrupt.
+ * 	For this driver there are two sources of receive DMA interrupts
+ * 	as identified in the Receive DMA mode Register (RDMR):
+ * 
+ * 	BIT3	EOA/EOL		End of List, all receive buffers in receive
+ * 				buffer list have been filled (no more free buffers
+ * 				available). The DMA controller has shut down.
+ * 
+ * 	BIT2	EOB		End of Buffer. This interrupt occurs when a receive
+ * 				DMA buffer is terminated in response to completion
+ * 				of a good frame or a frame with errors. The status
+ * 				of the frame is stored in the buffer entry in the
+ * 				list of receive buffer entries.
+ * 
+ * Arguments:		info		pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_isr_receive_dma( struct mgsl_struct *info )
+{
+	u16 status;
+	
+	/* clear interrupt pending and IUS bit for Rx DMA IRQ */
+	usc_OutDmaReg( info, CDIR, BIT9+BIT1 );
+
+	/* Read the receive DMA status to identify interrupt type. */
+	/* This also clears the status bits. */
+	status = usc_InDmaReg( info, RDMR );
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_isr_receive_dma(%s) status=%04X\n",
+			__FILE__,__LINE__,info->device_name,status);
+			
+	info->pending_bh |= BH_RECEIVE;
+	
+	if ( status & BIT3 ) {
+		info->rx_overflow = 1;
+		info->icount.buf_overrun++;
+	}
+
+}	/* end of mgsl_isr_receive_dma() */
+
+/* mgsl_isr_transmit_dma()
+ *
+ *	This function services a transmit DMA channel interrupt.
+ *
+ *	For this driver there is one source of transmit DMA interrupts
+ *	as identified in the Transmit DMA Mode Register (TDMR):
+ *
+ *     	BIT2  EOB       End of Buffer. This interrupt occurs when a
+ *     			transmit DMA buffer has been emptied.
+ *
+ *     	The driver maintains enough transmit DMA buffers to hold at least
+ *     	one max frame size transmit frame. When operating in a buffered
+ *     	transmit mode, there may be enough transmit DMA buffers to hold at
+ *     	least two or more max frame size frames. On an EOB condition,
+ *     	determine if there are any queued transmit buffers and copy into
+ *     	transmit DMA buffers if we have room.
+ *
+ * Arguments:		info		pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_isr_transmit_dma( struct mgsl_struct *info )
+{
+	u16 status;
+
+	/* clear interrupt pending and IUS bit for Tx DMA IRQ */
+	usc_OutDmaReg(info, CDIR, BIT8+BIT0 );
+
+	/* Read the transmit DMA status to identify interrupt type. */
+	/* This also clears the status bits. */
+
+	status = usc_InDmaReg( info, TDMR );
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):mgsl_isr_transmit_dma(%s) status=%04X\n",
+			__FILE__,__LINE__,info->device_name,status);
+
+	if ( status & BIT2 ) {
+		--info->tx_dma_buffers_used;
+
+		/* if there are transmit frames queued,
+		 *  try to load the next one
+		 */
+		if ( load_next_tx_holding_buffer(info) ) {
+			/* if call returns non-zero value, we have
+			 * at least one free tx holding buffer
+			 */
+			info->pending_bh |= BH_TRANSMIT;
+		}
+	}
+
+}	/* end of mgsl_isr_transmit_dma() */
+
+/* mgsl_interrupt()
+ * 
+ * 	Interrupt service routine entry point.
+ * 	
+ * Arguments:
+ * 
+ * 	irq		interrupt number that caused interrupt
+ * 	dev_id		device ID supplied during interrupt registration
+ * 	regs		interrupted processor context
+ * 	
+ * Return Value: None
+ */
+static irqreturn_t mgsl_interrupt(int irq, void *dev_id, struct pt_regs * regs)
+{
+	struct mgsl_struct * info;
+	u16 UscVector;
+	u16 DmaVector;
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_interrupt(%d)entry.\n",
+			__FILE__,__LINE__,irq);
+
+	info = (struct mgsl_struct *)dev_id;	
+	if (!info)
+		return IRQ_NONE;
+		
+	spin_lock(&info->irq_spinlock);
+
+	for(;;) {
+		/* Read the interrupt vectors from hardware. */
+		UscVector = usc_InReg(info, IVR) >> 9;
+		DmaVector = usc_InDmaReg(info, DIVR);
+		
+		if ( debug_level >= DEBUG_LEVEL_ISR )	
+			printk("%s(%d):%s UscVector=%08X DmaVector=%08X\n",
+				__FILE__,__LINE__,info->device_name,UscVector,DmaVector);
+			
+		if ( !UscVector && !DmaVector )
+			break;
+			
+		/* Dispatch interrupt vector */
+		if ( UscVector )
+			(*UscIsrTable[UscVector])(info);
+		else if ( (DmaVector&(BIT10|BIT9)) == BIT10)
+			mgsl_isr_transmit_dma(info);
+		else
+			mgsl_isr_receive_dma(info);
+
+		if ( info->isr_overflow ) {
+			printk(KERN_ERR"%s(%d):%s isr overflow irq=%d\n",
+				__FILE__,__LINE__,info->device_name, irq);
+			usc_DisableMasterIrqBit(info);
+			usc_DisableDmaInterrupts(info,DICR_MASTER);
+			break;
+		}
+	}
+	
+	/* Request bottom half processing if there's something 
+	 * for it to do and the bh is not already running
+	 */
+
+	if ( info->pending_bh && !info->bh_running && !info->bh_requested ) {
+		if ( debug_level >= DEBUG_LEVEL_ISR )	
+			printk("%s(%d):%s queueing bh task.\n",
+				__FILE__,__LINE__,info->device_name);
+		schedule_work(&info->task);
+		info->bh_requested = 1;
+	}
+
+	spin_unlock(&info->irq_spinlock);
+	
+	if ( debug_level >= DEBUG_LEVEL_ISR )	
+		printk("%s(%d):mgsl_interrupt(%d)exit.\n",
+			__FILE__,__LINE__,irq);
+	return IRQ_HANDLED;
+}	/* end of mgsl_interrupt() */
+
+/* startup()
+ * 
+ * 	Initialize and start device.
+ * 	
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	0 if success, otherwise error code
+ */
+static int startup(struct mgsl_struct * info)
+{
+	int retval = 0;
+	
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("%s(%d):mgsl_startup(%s)\n",__FILE__,__LINE__,info->device_name);
+		
+	if (info->flags & ASYNC_INITIALIZED)
+		return 0;
+	
+	if (!info->xmit_buf) {
+		/* allocate a page of memory for a transmit buffer */
+		info->xmit_buf = (unsigned char *)get_zeroed_page(GFP_KERNEL);
+		if (!info->xmit_buf) {
+			printk(KERN_ERR"%s(%d):%s can't allocate transmit buffer\n",
+				__FILE__,__LINE__,info->device_name);
+			return -ENOMEM;
+		}
+	}
+
+	info->pending_bh = 0;
+	
+	init_timer(&info->tx_timer);
+	info->tx_timer.data = (unsigned long)info;
+	info->tx_timer.function = mgsl_tx_timeout;
+	
+	/* Allocate and claim adapter resources */
+	retval = mgsl_claim_resources(info);
+	
+	/* perform existence check and diagnostics */
+	if ( !retval )
+		retval = mgsl_adapter_test(info);
+		
+	if ( retval ) {
+  		if (capable(CAP_SYS_ADMIN) && info->tty)
+			set_bit(TTY_IO_ERROR, &info->tty->flags);
+		mgsl_release_resources(info);
+  		return retval;
+  	}
+
+	/* program hardware for current parameters */
+	mgsl_change_params(info);
+	
+	if (info->tty)
+		clear_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	info->flags |= ASYNC_INITIALIZED;
+	
+	return 0;
+	
+}	/* end of startup() */
+
+/* shutdown()
+ *
+ * Called by mgsl_close() and mgsl_hangup() to shutdown hardware
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void shutdown(struct mgsl_struct * info)
+{
+	unsigned long flags;
+	
+	if (!(info->flags & ASYNC_INITIALIZED))
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_shutdown(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	/* clear status wait queue because status changes */
+	/* can't happen after shutting down the hardware */
+	wake_up_interruptible(&info->status_event_wait_q);
+	wake_up_interruptible(&info->event_wait_q);
+
+	del_timer(&info->tx_timer);	
+
+	if (info->xmit_buf) {
+		free_page((unsigned long) info->xmit_buf);
+		info->xmit_buf = NULL;
+	}
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	usc_DisableMasterIrqBit(info);
+	usc_stop_receiver(info);
+	usc_stop_transmitter(info);
+	usc_DisableInterrupts(info,RECEIVE_DATA + RECEIVE_STATUS +
+		TRANSMIT_DATA + TRANSMIT_STATUS + IO_PIN + MISC );
+	usc_DisableDmaInterrupts(info,DICR_MASTER + DICR_TRANSMIT + DICR_RECEIVE);
+	
+	/* Disable DMAEN (Port 7, Bit 14) */
+	/* This disconnects the DMA request signal from the ISA bus */
+	/* on the ISA adapter. This has no effect for the PCI adapter */
+	usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT15) | BIT14));
+	
+	/* Disable INTEN (Port 6, Bit12) */
+	/* This disconnects the IRQ request signal to the ISA bus */
+	/* on the ISA adapter. This has no effect for the PCI adapter */
+	usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT13) | BIT12));
+	
+ 	if (!info->tty || info->tty->termios->c_cflag & HUPCL) {
+ 		info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
+		usc_set_serial_signals(info);
+	}
+	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	mgsl_release_resources(info);	
+	
+	if (info->tty)
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	info->flags &= ~ASYNC_INITIALIZED;
+	
+}	/* end of shutdown() */
+
+static void mgsl_program_hw(struct mgsl_struct *info)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	
+	usc_stop_receiver(info);
+	usc_stop_transmitter(info);
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+	
+	if (info->params.mode == MGSL_MODE_HDLC ||
+	    info->params.mode == MGSL_MODE_RAW ||
+	    info->netcount)
+		usc_set_sync_mode(info);
+	else
+		usc_set_async_mode(info);
+		
+	usc_set_serial_signals(info);
+	
+	info->dcd_chkcount = 0;
+	info->cts_chkcount = 0;
+	info->ri_chkcount = 0;
+	info->dsr_chkcount = 0;
+
+	usc_EnableStatusIrqs(info,SICR_CTS+SICR_DSR+SICR_DCD+SICR_RI);		
+	usc_EnableInterrupts(info, IO_PIN);
+	usc_get_serial_signals(info);
+		
+	if (info->netcount || info->tty->termios->c_cflag & CREAD)
+		usc_start_receiver(info);
+		
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+}
+
+/* Reconfigure adapter based on new parameters
+ */
+static void mgsl_change_params(struct mgsl_struct *info)
+{
+	unsigned cflag;
+	int bits_per_char;
+
+	if (!info->tty || !info->tty->termios)
+		return;
+		
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_change_params(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	cflag = info->tty->termios->c_cflag;
+
+	/* if B0 rate (hangup) specified then negate DTR and RTS */
+	/* otherwise assert DTR and RTS */
+ 	if (cflag & CBAUD)
+		info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+	else
+		info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+	
+	/* byte size and parity */
+	
+	switch (cflag & CSIZE) {
+	      case CS5: info->params.data_bits = 5; break;
+	      case CS6: info->params.data_bits = 6; break;
+	      case CS7: info->params.data_bits = 7; break;
+	      case CS8: info->params.data_bits = 8; break;
+	      /* Never happens, but GCC is too dumb to figure it out */
+	      default:  info->params.data_bits = 7; break;
+	      }
+	      
+	if (cflag & CSTOPB)
+		info->params.stop_bits = 2;
+	else
+		info->params.stop_bits = 1;
+
+	info->params.parity = ASYNC_PARITY_NONE;
+	if (cflag & PARENB) {
+		if (cflag & PARODD)
+			info->params.parity = ASYNC_PARITY_ODD;
+		else
+			info->params.parity = ASYNC_PARITY_EVEN;
+#ifdef CMSPAR
+		if (cflag & CMSPAR)
+			info->params.parity = ASYNC_PARITY_SPACE;
+#endif
+	}
+
+	/* calculate number of jiffies to transmit a full
+	 * FIFO (32 bytes) at specified data rate
+	 */
+	bits_per_char = info->params.data_bits + 
+			info->params.stop_bits + 1;
+
+	/* if port data rate is set to 460800 or less then
+	 * allow tty settings to override, otherwise keep the
+	 * current data rate.
+	 */
+	if (info->params.data_rate <= 460800)
+		info->params.data_rate = tty_get_baud_rate(info->tty);
+	
+	if ( info->params.data_rate ) {
+		info->timeout = (32*HZ*bits_per_char) / 
+				info->params.data_rate;
+	}
+	info->timeout += HZ/50;		/* Add .02 seconds of slop */
+
+	if (cflag & CRTSCTS)
+		info->flags |= ASYNC_CTS_FLOW;
+	else
+		info->flags &= ~ASYNC_CTS_FLOW;
+		
+	if (cflag & CLOCAL)
+		info->flags &= ~ASYNC_CHECK_CD;
+	else
+		info->flags |= ASYNC_CHECK_CD;
+
+	/* process tty input control flags */
+	
+	info->read_status_mask = RXSTATUS_OVERRUN;
+	if (I_INPCK(info->tty))
+		info->read_status_mask |= RXSTATUS_PARITY_ERROR | RXSTATUS_FRAMING_ERROR;
+ 	if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+ 		info->read_status_mask |= RXSTATUS_BREAK_RECEIVED;
+	
+	if (I_IGNPAR(info->tty))
+		info->ignore_status_mask |= RXSTATUS_PARITY_ERROR | RXSTATUS_FRAMING_ERROR;
+	if (I_IGNBRK(info->tty)) {
+		info->ignore_status_mask |= RXSTATUS_BREAK_RECEIVED;
+		/* If ignoring parity and break indicators, ignore 
+		 * overruns too.  (For real raw support).
+		 */
+		if (I_IGNPAR(info->tty))
+			info->ignore_status_mask |= RXSTATUS_OVERRUN;
+	}
+
+	mgsl_program_hw(info);
+
+}	/* end of mgsl_change_params() */
+
+/* mgsl_put_char()
+ * 
+ * 	Add a character to the transmit buffer.
+ * 	
+ * Arguments:		tty	pointer to tty information structure
+ * 			ch	character to add to transmit buffer
+ * 		
+ * Return Value:	None
+ */
+static void mgsl_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO ) {
+		printk( "%s(%d):mgsl_put_char(%d) on %s\n",
+			__FILE__,__LINE__,ch,info->device_name);
+	}		
+	
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_put_char"))
+		return;
+
+	if (!tty || !info->xmit_buf)
+		return;
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	
+	if ( (info->params.mode == MGSL_MODE_ASYNC ) || !info->tx_active ) {
+	
+		if (info->xmit_cnt < SERIAL_XMIT_SIZE - 1) {
+			info->xmit_buf[info->xmit_head++] = ch;
+			info->xmit_head &= SERIAL_XMIT_SIZE-1;
+			info->xmit_cnt++;
+		}
+	}
+	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+}	/* end of mgsl_put_char() */
+
+/* mgsl_flush_chars()
+ * 
+ * 	Enable transmitter so remaining characters in the
+ * 	transmit buffer are sent.
+ * 	
+ * Arguments:		tty	pointer to tty information structure
+ * Return Value:	None
+ */
+static void mgsl_flush_chars(struct tty_struct *tty)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+				
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):mgsl_flush_chars() entry on %s xmit_cnt=%d\n",
+			__FILE__,__LINE__,info->device_name,info->xmit_cnt);
+	
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_flush_chars"))
+		return;
+
+	if (info->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+	    !info->xmit_buf)
+		return;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):mgsl_flush_chars() entry on %s starting transmitter\n",
+			__FILE__,__LINE__,info->device_name );
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	
+	if (!info->tx_active) {
+		if ( (info->params.mode == MGSL_MODE_HDLC ||
+			info->params.mode == MGSL_MODE_RAW) && info->xmit_cnt ) {
+			/* operating in synchronous (frame oriented) mode */
+			/* copy data from circular xmit_buf to */
+			/* transmit DMA buffer. */
+			mgsl_load_tx_dma_buffer(info,
+				 info->xmit_buf,info->xmit_cnt);
+		}
+	 	usc_start_transmitter(info);
+	}
+	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+}	/* end of mgsl_flush_chars() */
+
+/* mgsl_write()
+ * 
+ * 	Send a block of data
+ * 	
+ * Arguments:
+ * 
+ * 	tty		pointer to tty information structure
+ * 	buf		pointer to buffer containing send data
+ * 	count		size of send data in bytes
+ * 	
+ * Return Value:	number of characters written
+ */
+static int mgsl_write(struct tty_struct * tty,
+		    const unsigned char *buf, int count)
+{
+	int	c, ret = 0;
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):mgsl_write(%s) count=%d\n",
+			__FILE__,__LINE__,info->device_name,count);
+	
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_write"))
+		goto cleanup;
+
+	if (!tty || !info->xmit_buf || !tmp_buf)
+		goto cleanup;
+
+	if ( info->params.mode == MGSL_MODE_HDLC ||
+			info->params.mode == MGSL_MODE_RAW ) {
+		/* operating in synchronous (frame oriented) mode */
+		/* operating in synchronous (frame oriented) mode */
+		if (info->tx_active) {
+
+			if ( info->params.mode == MGSL_MODE_HDLC ) {
+				ret = 0;
+				goto cleanup;
+			}
+			/* transmitter is actively sending data -
+			 * if we have multiple transmit dma and
+			 * holding buffers, attempt to queue this
+			 * frame for transmission at a later time.
+			 */
+			if (info->tx_holding_count >= info->num_tx_holding_buffers ) {
+				/* no tx holding buffers available */
+				ret = 0;
+				goto cleanup;
+			}
+
+			/* queue transmit frame request */
+			ret = count;
+			save_tx_buffer_request(info,buf,count);
+
+			/* if we have sufficient tx dma buffers,
+			 * load the next buffered tx request
+			 */
+			spin_lock_irqsave(&info->irq_spinlock,flags);
+			load_next_tx_holding_buffer(info);
+			spin_unlock_irqrestore(&info->irq_spinlock,flags);
+			goto cleanup;
+		}
+	
+		/* if operating in HDLC LoopMode and the adapter  */
+		/* has yet to be inserted into the loop, we can't */
+		/* transmit					  */
+
+		if ( (info->params.flags & HDLC_FLAG_HDLC_LOOPMODE) &&
+			!usc_loopmode_active(info) )
+		{
+			ret = 0;
+			goto cleanup;
+		}
+
+		if ( info->xmit_cnt ) {
+			/* Send accumulated from send_char() calls */
+			/* as frame and wait before accepting more data. */
+			ret = 0;
+			
+			/* copy data from circular xmit_buf to */
+			/* transmit DMA buffer. */
+			mgsl_load_tx_dma_buffer(info,
+				info->xmit_buf,info->xmit_cnt);
+			if ( debug_level >= DEBUG_LEVEL_INFO )
+				printk( "%s(%d):mgsl_write(%s) sync xmit_cnt flushing\n",
+					__FILE__,__LINE__,info->device_name);
+		} else {
+			if ( debug_level >= DEBUG_LEVEL_INFO )
+				printk( "%s(%d):mgsl_write(%s) sync transmit accepted\n",
+					__FILE__,__LINE__,info->device_name);
+			ret = count;
+			info->xmit_cnt = count;
+			mgsl_load_tx_dma_buffer(info,buf,count);
+		}
+	} else {
+		while (1) {
+			spin_lock_irqsave(&info->irq_spinlock,flags);
+			c = min_t(int, count,
+				min(SERIAL_XMIT_SIZE - info->xmit_cnt - 1,
+				    SERIAL_XMIT_SIZE - info->xmit_head));
+			if (c <= 0) {
+				spin_unlock_irqrestore(&info->irq_spinlock,flags);
+				break;
+			}
+			memcpy(info->xmit_buf + info->xmit_head, buf, c);
+			info->xmit_head = ((info->xmit_head + c) &
+					   (SERIAL_XMIT_SIZE-1));
+			info->xmit_cnt += c;
+			spin_unlock_irqrestore(&info->irq_spinlock,flags);
+			buf += c;
+			count -= c;
+			ret += c;
+		}
+	}	
+	
+ 	if (info->xmit_cnt && !tty->stopped && !tty->hw_stopped) {
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		if (!info->tx_active)
+		 	usc_start_transmitter(info);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+ 	}
+cleanup:	
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):mgsl_write(%s) returning=%d\n",
+			__FILE__,__LINE__,info->device_name,ret);
+			
+	return ret;
+	
+}	/* end of mgsl_write() */
+
+/* mgsl_write_room()
+ *
+ *	Return the count of free bytes in transmit buffer
+ * 	
+ * Arguments:		tty	pointer to tty info structure
+ * Return Value:	None
+ */
+static int mgsl_write_room(struct tty_struct *tty)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	int	ret;
+				
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_write_room"))
+		return 0;
+	ret = SERIAL_XMIT_SIZE - info->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+		
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_write_room(%s)=%d\n",
+			 __FILE__,__LINE__, info->device_name,ret );
+			 
+	if ( info->params.mode == MGSL_MODE_HDLC ||
+		info->params.mode == MGSL_MODE_RAW ) {
+		/* operating in synchronous (frame oriented) mode */
+		if ( info->tx_active )
+			return 0;
+		else
+			return HDLC_MAX_FRAME_SIZE;
+	}
+	
+	return ret;
+	
+}	/* end of mgsl_write_room() */
+
+/* mgsl_chars_in_buffer()
+ *
+ *	Return the count of bytes in transmit buffer
+ * 	
+ * Arguments:		tty	pointer to tty info structure
+ * Return Value:	None
+ */
+static int mgsl_chars_in_buffer(struct tty_struct *tty)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+			 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_chars_in_buffer(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_chars_in_buffer"))
+		return 0;
+		
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_chars_in_buffer(%s)=%d\n",
+			 __FILE__,__LINE__, info->device_name,info->xmit_cnt );
+			 
+	if ( info->params.mode == MGSL_MODE_HDLC ||
+		info->params.mode == MGSL_MODE_RAW ) {
+		/* operating in synchronous (frame oriented) mode */
+		if ( info->tx_active )
+			return info->max_frame_size;
+		else
+			return 0;
+	}
+			 
+	return info->xmit_cnt;
+}	/* end of mgsl_chars_in_buffer() */
+
+/* mgsl_flush_buffer()
+ *
+ *	Discard all data in the send buffer
+ * 	
+ * Arguments:		tty	pointer to tty info structure
+ * Return Value:	None
+ */
+static void mgsl_flush_buffer(struct tty_struct *tty)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_flush_buffer(%s) entry\n",
+			 __FILE__,__LINE__, info->device_name );
+	
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_flush_buffer"))
+		return;
+		
+	spin_lock_irqsave(&info->irq_spinlock,flags); 
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+	del_timer(&info->tx_timer);	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+	wake_up_interruptible(&tty->write_wait);
+	tty_wakeup(tty);
+}
+
+/* mgsl_send_xchar()
+ *
+ *	Send a high-priority XON/XOFF character
+ * 	
+ * Arguments:		tty	pointer to tty info structure
+ *			ch	character to send
+ * Return Value:	None
+ */
+static void mgsl_send_xchar(struct tty_struct *tty, char ch)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_send_xchar(%s,%d)\n",
+			 __FILE__,__LINE__, info->device_name, ch );
+			 
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_send_xchar"))
+		return;
+
+	info->x_char = ch;
+	if (ch) {
+		/* Make sure transmit interrupts are on */
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		if (!info->tx_enabled)
+		 	usc_start_transmitter(info);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	}
+}	/* end of mgsl_send_xchar() */
+
+/* mgsl_throttle()
+ * 
+ * 	Signal remote device to throttle send data (our receive data)
+ * 	
+ * Arguments:		tty	pointer to tty info structure
+ * Return Value:	None
+ */
+static void mgsl_throttle(struct tty_struct * tty)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_throttle(%s) entry\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_throttle"))
+		return;
+	
+	if (I_IXOFF(tty))
+		mgsl_send_xchar(tty, STOP_CHAR(tty));
+ 
+ 	if (tty->termios->c_cflag & CRTSCTS) {
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		info->serial_signals &= ~SerialSignal_RTS;
+	 	usc_set_serial_signals(info);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	}
+}	/* end of mgsl_throttle() */
+
+/* mgsl_unthrottle()
+ * 
+ * 	Signal remote device to stop throttling send data (our receive data)
+ * 	
+ * Arguments:		tty	pointer to tty info structure
+ * Return Value:	None
+ */
+static void mgsl_unthrottle(struct tty_struct * tty)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_unthrottle(%s) entry\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_unthrottle"))
+		return;
+	
+	if (I_IXOFF(tty)) {
+		if (info->x_char)
+			info->x_char = 0;
+		else
+			mgsl_send_xchar(tty, START_CHAR(tty));
+	}
+	
+ 	if (tty->termios->c_cflag & CRTSCTS) {
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		info->serial_signals |= SerialSignal_RTS;
+	 	usc_set_serial_signals(info);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	}
+	
+}	/* end of mgsl_unthrottle() */
+
+/* mgsl_get_stats()
+ * 
+ * 	get the current serial parameters information
+ *
+ * Arguments:	info		pointer to device instance data
+ * 		user_icount	pointer to buffer to hold returned stats
+ * 	
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_get_stats(struct mgsl_struct * info, struct mgsl_icount __user *user_icount)
+{
+	int err;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_get_params(%s)\n",
+			 __FILE__,__LINE__, info->device_name);
+			
+	COPY_TO_USER(err,user_icount, &info->icount, sizeof(struct mgsl_icount));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):mgsl_get_stats(%s) user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+	
+	return 0;
+	
+}	/* end of mgsl_get_stats() */
+
+/* mgsl_get_params()
+ * 
+ * 	get the current serial parameters information
+ *
+ * Arguments:	info		pointer to device instance data
+ * 		user_params	pointer to buffer to hold returned params
+ * 	
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_get_params(struct mgsl_struct * info, MGSL_PARAMS __user *user_params)
+{
+	int err;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_get_params(%s)\n",
+			 __FILE__,__LINE__, info->device_name);
+			
+	COPY_TO_USER(err,user_params, &info->params, sizeof(MGSL_PARAMS));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):mgsl_get_params(%s) user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+	
+	return 0;
+	
+}	/* end of mgsl_get_params() */
+
+/* mgsl_set_params()
+ * 
+ * 	set the serial parameters
+ * 	
+ * Arguments:
+ * 
+ * 	info		pointer to device instance data
+ * 	new_params	user buffer containing new serial params
+ *
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_set_params(struct mgsl_struct * info, MGSL_PARAMS __user *new_params)
+{
+ 	unsigned long flags;
+	MGSL_PARAMS tmp_params;
+	int err;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_set_params %s\n", __FILE__,__LINE__,
+			info->device_name );
+	COPY_FROM_USER(err,&tmp_params, new_params, sizeof(MGSL_PARAMS));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):mgsl_set_params(%s) user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+	
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+ 	mgsl_change_params(info);
+	
+	return 0;
+	
+}	/* end of mgsl_set_params() */
+
+/* mgsl_get_txidle()
+ * 
+ * 	get the current transmit idle mode
+ *
+ * Arguments:	info		pointer to device instance data
+ * 		idle_mode	pointer to buffer to hold returned idle mode
+ * 	
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_get_txidle(struct mgsl_struct * info, int __user *idle_mode)
+{
+	int err;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_get_txidle(%s)=%d\n",
+			 __FILE__,__LINE__, info->device_name, info->idle_mode);
+			
+	COPY_TO_USER(err,idle_mode, &info->idle_mode, sizeof(int));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):mgsl_get_txidle(%s) user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+	
+	return 0;
+	
+}	/* end of mgsl_get_txidle() */
+
+/* mgsl_set_txidle()	service ioctl to set transmit idle mode
+ * 	
+ * Arguments:	 	info		pointer to device instance data
+ * 			idle_mode	new idle mode
+ *
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_set_txidle(struct mgsl_struct * info, int idle_mode)
+{
+ 	unsigned long flags;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_set_txidle(%s,%d)\n", __FILE__,__LINE__,
+			info->device_name, idle_mode );
+			
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	info->idle_mode = idle_mode;
+	usc_set_txidle( info );
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	return 0;
+	
+}	/* end of mgsl_set_txidle() */
+
+/* mgsl_txenable()
+ * 
+ * 	enable or disable the transmitter
+ * 	
+ * Arguments:
+ * 
+ * 	info		pointer to device instance data
+ * 	enable		1 = enable, 0 = disable
+ *
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_txenable(struct mgsl_struct * info, int enable)
+{
+ 	unsigned long flags;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_txenable(%s,%d)\n", __FILE__,__LINE__,
+			info->device_name, enable);
+			
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	if ( enable ) {
+		if ( !info->tx_enabled ) {
+
+			usc_start_transmitter(info);
+			/*--------------------------------------------------
+			 * if HDLC/SDLC Loop mode, attempt to insert the
+			 * station in the 'loop' by setting CMR:13. Upon
+			 * receipt of the next GoAhead (RxAbort) sequence,
+			 * the OnLoop indicator (CCSR:7) should go active
+			 * to indicate that we are on the loop
+			 *--------------------------------------------------*/
+			if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
+				usc_loopmode_insert_request( info );
+		}
+	} else {
+		if ( info->tx_enabled )
+			usc_stop_transmitter(info);
+	}
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	return 0;
+	
+}	/* end of mgsl_txenable() */
+
+/* mgsl_txabort()	abort send HDLC frame
+ * 	
+ * Arguments:	 	info		pointer to device instance data
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_txabort(struct mgsl_struct * info)
+{
+ 	unsigned long flags;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_txabort(%s)\n", __FILE__,__LINE__,
+			info->device_name);
+			
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	if ( info->tx_active && info->params.mode == MGSL_MODE_HDLC )
+	{
+		if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
+			usc_loopmode_cancel_transmit( info );
+		else
+			usc_TCmd(info,TCmd_SendAbort);
+	}
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	return 0;
+	
+}	/* end of mgsl_txabort() */
+
+/* mgsl_rxenable() 	enable or disable the receiver
+ * 	
+ * Arguments:	 	info		pointer to device instance data
+ * 			enable		1 = enable, 0 = disable
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_rxenable(struct mgsl_struct * info, int enable)
+{
+ 	unsigned long flags;
+ 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_rxenable(%s,%d)\n", __FILE__,__LINE__,
+			info->device_name, enable);
+			
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	if ( enable ) {
+		if ( !info->rx_enabled )
+			usc_start_receiver(info);
+	} else {
+		if ( info->rx_enabled )
+			usc_stop_receiver(info);
+	}
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	return 0;
+	
+}	/* end of mgsl_rxenable() */
+
+/* mgsl_wait_event() 	wait for specified event to occur
+ * 	
+ * Arguments:	 	info	pointer to device instance data
+ * 			mask	pointer to bitmask of events to wait for
+ * Return Value:	0 	if successful and bit mask updated with
+ *				of events triggerred,
+ * 			otherwise error code
+ */
+static int mgsl_wait_event(struct mgsl_struct * info, int __user * mask_ptr)
+{
+ 	unsigned long flags;
+	int s;
+	int rc=0;
+	struct mgsl_icount cprev, cnow;
+	int events;
+	int mask;
+	struct	_input_signal_events oldsigs, newsigs;
+	DECLARE_WAITQUEUE(wait, current);
+
+	COPY_FROM_USER(rc,&mask, mask_ptr, sizeof(int));
+	if (rc) {
+		return  -EFAULT;
+	}
+		 
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_wait_event(%s,%d)\n", __FILE__,__LINE__,
+			info->device_name, mask);
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+
+	/* return immediately if state matches requested events */
+	usc_get_serial_signals(info);
+	s = info->serial_signals;
+	events = mask &
+		( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) +
+ 		  ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) +
+		  ((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) +
+		  ((s & SerialSignal_RI)  ? MgslEvent_RiActive :MgslEvent_RiInactive) );
+	if (events) {
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+		goto exit;
+	}
+
+	/* save current irq counts */
+	cprev = info->icount;
+	oldsigs = info->input_signal_events;
+	
+	/* enable hunt and idle irqs if needed */
+	if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
+		u16 oldreg = usc_InReg(info,RICR);
+		u16 newreg = oldreg +
+			 (mask & MgslEvent_ExitHuntMode ? RXSTATUS_EXITED_HUNT:0) +
+			 (mask & MgslEvent_IdleReceived ? RXSTATUS_IDLE_RECEIVED:0);
+		if (oldreg != newreg)
+			usc_OutReg(info, RICR, newreg);
+	}
+	
+	set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&info->event_wait_q, &wait);
+	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+
+	for(;;) {
+		schedule();
+		if (signal_pending(current)) {
+			rc = -ERESTARTSYS;
+			break;
+		}
+			
+		/* get current irq counts */
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		cnow = info->icount;
+		newsigs = info->input_signal_events;
+		set_current_state(TASK_INTERRUPTIBLE);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+		/* if no change, wait aborted for some reason */
+		if (newsigs.dsr_up   == oldsigs.dsr_up   &&
+		    newsigs.dsr_down == oldsigs.dsr_down &&
+		    newsigs.dcd_up   == oldsigs.dcd_up   &&
+		    newsigs.dcd_down == oldsigs.dcd_down &&
+		    newsigs.cts_up   == oldsigs.cts_up   &&
+		    newsigs.cts_down == oldsigs.cts_down &&
+		    newsigs.ri_up    == oldsigs.ri_up    &&
+		    newsigs.ri_down  == oldsigs.ri_down  &&
+		    cnow.exithunt    == cprev.exithunt   &&
+		    cnow.rxidle      == cprev.rxidle) {
+			rc = -EIO;
+			break;
+		}
+
+		events = mask &
+			( (newsigs.dsr_up   != oldsigs.dsr_up   ? MgslEvent_DsrActive:0)   +
+			(newsigs.dsr_down != oldsigs.dsr_down ? MgslEvent_DsrInactive:0) +
+			(newsigs.dcd_up   != oldsigs.dcd_up   ? MgslEvent_DcdActive:0)   +
+			(newsigs.dcd_down != oldsigs.dcd_down ? MgslEvent_DcdInactive:0) +
+			(newsigs.cts_up   != oldsigs.cts_up   ? MgslEvent_CtsActive:0)   +
+			(newsigs.cts_down != oldsigs.cts_down ? MgslEvent_CtsInactive:0) +
+			(newsigs.ri_up    != oldsigs.ri_up    ? MgslEvent_RiActive:0)    +
+			(newsigs.ri_down  != oldsigs.ri_down  ? MgslEvent_RiInactive:0)  +
+			(cnow.exithunt    != cprev.exithunt   ? MgslEvent_ExitHuntMode:0) +
+			  (cnow.rxidle      != cprev.rxidle     ? MgslEvent_IdleReceived:0) );
+		if (events)
+			break;
+		
+		cprev = cnow;
+		oldsigs = newsigs;
+	}
+	
+	remove_wait_queue(&info->event_wait_q, &wait);
+	set_current_state(TASK_RUNNING);
+
+	if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		if (!waitqueue_active(&info->event_wait_q)) {
+			/* disable enable exit hunt mode/idle rcvd IRQs */
+			usc_OutReg(info, RICR, usc_InReg(info,RICR) &
+				~(RXSTATUS_EXITED_HUNT + RXSTATUS_IDLE_RECEIVED));
+		}
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	}
+exit:
+	if ( rc == 0 )
+		PUT_USER(rc, events, mask_ptr);
+		
+	return rc;
+	
+}	/* end of mgsl_wait_event() */
+
+static int modem_input_wait(struct mgsl_struct *info,int arg)
+{
+ 	unsigned long flags;
+	int rc;
+	struct mgsl_icount cprev, cnow;
+	DECLARE_WAITQUEUE(wait, current);
+
+	/* save current irq counts */
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	cprev = info->icount;
+	add_wait_queue(&info->status_event_wait_q, &wait);
+	set_current_state(TASK_INTERRUPTIBLE);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	for(;;) {
+		schedule();
+		if (signal_pending(current)) {
+			rc = -ERESTARTSYS;
+			break;
+		}
+
+		/* get new irq counts */
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		cnow = info->icount;
+		set_current_state(TASK_INTERRUPTIBLE);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+		/* if no change, wait aborted for some reason */
+		if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+		    cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+			rc = -EIO;
+			break;
+		}
+
+		/* check for change in caller specified modem input */
+		if ((arg & TIOCM_RNG && cnow.rng != cprev.rng) ||
+		    (arg & TIOCM_DSR && cnow.dsr != cprev.dsr) ||
+		    (arg & TIOCM_CD  && cnow.dcd != cprev.dcd) ||
+		    (arg & TIOCM_CTS && cnow.cts != cprev.cts)) {
+			rc = 0;
+			break;
+		}
+
+		cprev = cnow;
+	}
+	remove_wait_queue(&info->status_event_wait_q, &wait);
+	set_current_state(TASK_RUNNING);
+	return rc;
+}
+
+/* return the state of the serial control and status signals
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned int result;
+ 	unsigned long flags;
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+ 	usc_get_serial_signals(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	result = ((info->serial_signals & SerialSignal_RTS) ? TIOCM_RTS:0) +
+		((info->serial_signals & SerialSignal_DTR) ? TIOCM_DTR:0) +
+		((info->serial_signals & SerialSignal_DCD) ? TIOCM_CAR:0) +
+		((info->serial_signals & SerialSignal_RI)  ? TIOCM_RNG:0) +
+		((info->serial_signals & SerialSignal_DSR) ? TIOCM_DSR:0) +
+		((info->serial_signals & SerialSignal_CTS) ? TIOCM_CTS:0);
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s tiocmget() value=%08X\n",
+			 __FILE__,__LINE__, info->device_name, result );
+	return result;
+}
+
+/* set modem control signals (DTR/RTS)
+ */
+static int tiocmset(struct tty_struct *tty, struct file *file,
+		    unsigned int set, unsigned int clear)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+ 	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s tiocmset(%x,%x)\n",
+			__FILE__,__LINE__,info->device_name, set, clear);
+
+	if (set & TIOCM_RTS)
+		info->serial_signals |= SerialSignal_RTS;
+	if (set & TIOCM_DTR)
+		info->serial_signals |= SerialSignal_DTR;
+	if (clear & TIOCM_RTS)
+		info->serial_signals &= ~SerialSignal_RTS;
+	if (clear & TIOCM_DTR)
+		info->serial_signals &= ~SerialSignal_DTR;
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+ 	usc_set_serial_signals(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	return 0;
+}
+
+/* mgsl_break()		Set or clear transmit break condition
+ *
+ * Arguments:		tty		pointer to tty instance data
+ *			break_state	-1=set break condition, 0=clear
+ * Return Value:	None
+ */
+static void mgsl_break(struct tty_struct *tty, int break_state)
+{
+	struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_break(%s,%d)\n",
+			 __FILE__,__LINE__, info->device_name, break_state);
+			 
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_break"))
+		return;
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+ 	if (break_state == -1)
+		usc_OutReg(info,IOCR,(u16)(usc_InReg(info,IOCR) | BIT7));
+	else 
+		usc_OutReg(info,IOCR,(u16)(usc_InReg(info,IOCR) & ~BIT7));
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+}	/* end of mgsl_break() */
+
+/* mgsl_ioctl()	Service an IOCTL request
+ * 	
+ * Arguments:
+ * 
+ * 	tty	pointer to tty instance data
+ * 	file	pointer to associated file object for device
+ * 	cmd	IOCTL command code
+ * 	arg	command argument/context
+ * 	
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_ioctl(struct tty_struct *tty, struct file * file,
+		    unsigned int cmd, unsigned long arg)
+{
+	struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_ioctl %s cmd=%08X\n", __FILE__,__LINE__,
+			info->device_name, cmd );
+	
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_ioctl"))
+		return -ENODEV;
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+	    (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+		    return -EIO;
+	}
+
+	return mgsl_ioctl_common(info, cmd, arg);
+}
+
+static int mgsl_ioctl_common(struct mgsl_struct *info, unsigned int cmd, unsigned long arg)
+{
+	int error;
+	struct mgsl_icount cnow;	/* kernel counter temps */
+	void __user *argp = (void __user *)arg;
+	struct serial_icounter_struct __user *p_cuser;	/* user space */
+	unsigned long flags;
+	
+	switch (cmd) {
+		case MGSL_IOCGPARAMS:
+			return mgsl_get_params(info, argp);
+		case MGSL_IOCSPARAMS:
+			return mgsl_set_params(info, argp);
+		case MGSL_IOCGTXIDLE:
+			return mgsl_get_txidle(info, argp);
+		case MGSL_IOCSTXIDLE:
+			return mgsl_set_txidle(info,(int)arg);
+		case MGSL_IOCTXENABLE:
+			return mgsl_txenable(info,(int)arg);
+		case MGSL_IOCRXENABLE:
+			return mgsl_rxenable(info,(int)arg);
+		case MGSL_IOCTXABORT:
+			return mgsl_txabort(info);
+		case MGSL_IOCGSTATS:
+			return mgsl_get_stats(info, argp);
+		case MGSL_IOCWAITEVENT:
+			return mgsl_wait_event(info, argp);
+		case MGSL_IOCLOOPTXDONE:
+			return mgsl_loopmode_send_done(info);
+		/* Wait for modem input (DCD,RI,DSR,CTS) change
+		 * as specified by mask in arg (TIOCM_RNG/DSR/CD/CTS)
+		 */
+		case TIOCMIWAIT:
+			return modem_input_wait(info,(int)arg);
+
+		/* 
+		 * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+		 * Return: write counters to the user passed counter struct
+		 * NB: both 1->0 and 0->1 transitions are counted except for
+		 *     RI where only 0->1 is counted.
+		 */
+		case TIOCGICOUNT:
+			spin_lock_irqsave(&info->irq_spinlock,flags);
+			cnow = info->icount;
+			spin_unlock_irqrestore(&info->irq_spinlock,flags);
+			p_cuser = argp;
+			PUT_USER(error,cnow.cts, &p_cuser->cts);
+			if (error) return error;
+			PUT_USER(error,cnow.dsr, &p_cuser->dsr);
+			if (error) return error;
+			PUT_USER(error,cnow.rng, &p_cuser->rng);
+			if (error) return error;
+			PUT_USER(error,cnow.dcd, &p_cuser->dcd);
+			if (error) return error;
+			PUT_USER(error,cnow.rx, &p_cuser->rx);
+			if (error) return error;
+			PUT_USER(error,cnow.tx, &p_cuser->tx);
+			if (error) return error;
+			PUT_USER(error,cnow.frame, &p_cuser->frame);
+			if (error) return error;
+			PUT_USER(error,cnow.overrun, &p_cuser->overrun);
+			if (error) return error;
+			PUT_USER(error,cnow.parity, &p_cuser->parity);
+			if (error) return error;
+			PUT_USER(error,cnow.brk, &p_cuser->brk);
+			if (error) return error;
+			PUT_USER(error,cnow.buf_overrun, &p_cuser->buf_overrun);
+			if (error) return error;
+			return 0;
+		default:
+			return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+/* mgsl_set_termios()
+ * 
+ * 	Set new termios settings
+ * 	
+ * Arguments:
+ * 
+ * 	tty		pointer to tty structure
+ * 	termios		pointer to buffer to hold returned old termios
+ * 	
+ * Return Value:		None
+ */
+static void mgsl_set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+	struct mgsl_struct *info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_set_termios %s\n", __FILE__,__LINE__,
+			tty->driver->name );
+	
+	/* just return if nothing has changed */
+	if ((tty->termios->c_cflag == old_termios->c_cflag)
+	    && (RELEVANT_IFLAG(tty->termios->c_iflag) 
+		== RELEVANT_IFLAG(old_termios->c_iflag)))
+	  return;
+
+	mgsl_change_params(info);
+
+	/* Handle transition to B0 status */
+	if (old_termios->c_cflag & CBAUD &&
+	    !(tty->termios->c_cflag & CBAUD)) {
+		info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+	 	usc_set_serial_signals(info);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	}
+	
+	/* Handle transition away from B0 status */
+	if (!(old_termios->c_cflag & CBAUD) &&
+	    tty->termios->c_cflag & CBAUD) {
+		info->serial_signals |= SerialSignal_DTR;
+ 		if (!(tty->termios->c_cflag & CRTSCTS) || 
+ 		    !test_bit(TTY_THROTTLED, &tty->flags)) {
+			info->serial_signals |= SerialSignal_RTS;
+ 		}
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+	 	usc_set_serial_signals(info);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	}
+	
+	/* Handle turning off CRTSCTS */
+	if (old_termios->c_cflag & CRTSCTS &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {
+		tty->hw_stopped = 0;
+		mgsl_start(tty);
+	}
+
+}	/* end of mgsl_set_termios() */
+
+/* mgsl_close()
+ * 
+ * 	Called when port is closed. Wait for remaining data to be
+ * 	sent. Disable port and free resources.
+ * 	
+ * Arguments:
+ * 
+ * 	tty	pointer to open tty structure
+ * 	filp	pointer to open file object
+ * 	
+ * Return Value:	None
+ */
+static void mgsl_close(struct tty_struct *tty, struct file * filp)
+{
+	struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_close"))
+		return;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_close(%s) entry, count=%d\n",
+			 __FILE__,__LINE__, info->device_name, info->count);
+			 
+	if (!info->count)
+		return;
+
+	if (tty_hung_up_p(filp))
+		goto cleanup;
+			
+	if ((tty->count == 1) && (info->count != 1)) {
+		/*
+		 * tty->count is 1 and the tty structure will be freed.
+		 * info->count should be one in this case.
+		 * if it's not, correct it so that the port is shutdown.
+		 */
+		printk("mgsl_close: bad refcount; tty->count is 1, "
+		       "info->count is %d\n", info->count);
+		info->count = 1;
+	}
+	
+	info->count--;
+	
+	/* if at least one open remaining, leave hardware active */
+	if (info->count)
+		goto cleanup;
+	
+	info->flags |= ASYNC_CLOSING;
+	
+	/* set tty->closing to notify line discipline to 
+	 * only process XON/XOFF characters. Only the N_TTY
+	 * discipline appears to use this (ppp does not).
+	 */
+	tty->closing = 1;
+	
+	/* wait for transmit data to clear all layers */
+	
+	if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):mgsl_close(%s) calling tty_wait_until_sent\n",
+				 __FILE__,__LINE__, info->device_name );
+		tty_wait_until_sent(tty, info->closing_wait);
+	}
+		
+ 	if (info->flags & ASYNC_INITIALIZED)
+ 		mgsl_wait_until_sent(tty, info->timeout);
+
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+
+	tty_ldisc_flush(tty);
+		
+	shutdown(info);
+	
+	tty->closing = 0;
+	info->tty = NULL;
+	
+	if (info->blocked_open) {
+		if (info->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(info->close_delay));
+		}
+		wake_up_interruptible(&info->open_wait);
+	}
+	
+	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+			 
+	wake_up_interruptible(&info->close_wait);
+	
+cleanup:			
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_close(%s) exit, count=%d\n", __FILE__,__LINE__,
+			tty->driver->name, info->count);
+			
+}	/* end of mgsl_close() */
+
+/* mgsl_wait_until_sent()
+ *
+ *	Wait until the transmitter is empty.
+ *
+ * Arguments:
+ *
+ *	tty		pointer to tty info structure
+ *	timeout		time to wait for send completion
+ *
+ * Return Value:	None
+ */
+static void mgsl_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+	unsigned long orig_jiffies, char_time;
+
+	if (!info )
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_wait_until_sent(%s) entry\n",
+			 __FILE__,__LINE__, info->device_name );
+      
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_wait_until_sent"))
+		return;
+
+	if (!(info->flags & ASYNC_INITIALIZED))
+		goto exit;
+	 
+	orig_jiffies = jiffies;
+      
+	/* Set check interval to 1/5 of estimated time to
+	 * send a character, and make it at least 1. The check
+	 * interval should also be less than the timeout.
+	 * Note: use tight timings here to satisfy the NIST-PCTS.
+	 */ 
+       
+	if ( info->params.data_rate ) {
+	       	char_time = info->timeout/(32 * 5);
+		if (!char_time)
+			char_time++;
+	} else
+		char_time = 1;
+		
+	if (timeout)
+		char_time = min_t(unsigned long, char_time, timeout);
+		
+	if ( info->params.mode == MGSL_MODE_HDLC ||
+		info->params.mode == MGSL_MODE_RAW ) {
+		while (info->tx_active) {
+			msleep_interruptible(jiffies_to_msecs(char_time));
+			if (signal_pending(current))
+				break;
+			if (timeout && time_after(jiffies, orig_jiffies + timeout))
+				break;
+		}
+	} else {
+		while (!(usc_InReg(info,TCSR) & TXSTATUS_ALL_SENT) &&
+			info->tx_enabled) {
+			msleep_interruptible(jiffies_to_msecs(char_time));
+			if (signal_pending(current))
+				break;
+			if (timeout && time_after(jiffies, orig_jiffies + timeout))
+				break;
+		}
+	}
+      
+exit:
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_wait_until_sent(%s) exit\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+}	/* end of mgsl_wait_until_sent() */
+
+/* mgsl_hangup()
+ *
+ *	Called by tty_hangup() when a hangup is signaled.
+ *	This is the same as to closing all open files for the port.
+ *
+ * Arguments:		tty	pointer to associated tty object
+ * Return Value:	None
+ */
+static void mgsl_hangup(struct tty_struct *tty)
+{
+	struct mgsl_struct * info = (struct mgsl_struct *)tty->driver_data;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_hangup(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_hangup"))
+		return;
+
+	mgsl_flush_buffer(tty);
+	shutdown(info);
+	
+	info->count = 0;	
+	info->flags &= ~ASYNC_NORMAL_ACTIVE;
+	info->tty = NULL;
+
+	wake_up_interruptible(&info->open_wait);
+	
+}	/* end of mgsl_hangup() */
+
+/* block_til_ready()
+ * 
+ * 	Block the current process until the specified port
+ * 	is ready to be opened.
+ * 	
+ * Arguments:
+ * 
+ * 	tty		pointer to tty info structure
+ * 	filp		pointer to open file object
+ * 	info		pointer to device instance data
+ * 	
+ * Return Value:	0 if success, otherwise error code
+ */
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+			   struct mgsl_struct *info)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	int		retval;
+	int		do_clocal = 0, extra_count = 0;
+	unsigned long	flags;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):block_til_ready on %s\n",
+			 __FILE__,__LINE__, tty->driver->name );
+
+	if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){
+		/* nonblock mode is set or port is not enabled */
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	if (tty->termios->c_cflag & CLOCAL)
+		do_clocal = 1;
+
+	/* Wait for carrier detect and the line to become
+	 * free (i.e., not in use by the callout).  While we are in
+	 * this loop, info->count is dropped by one, so that
+	 * mgsl_close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+	 
+	retval = 0;
+	add_wait_queue(&info->open_wait, &wait);
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):block_til_ready before block on %s count=%d\n",
+			 __FILE__,__LINE__, tty->driver->name, info->count );
+
+	spin_lock_irqsave(&info->irq_spinlock, flags);
+	if (!tty_hung_up_p(filp)) {
+		extra_count = 1;
+		info->count--;
+	}
+	spin_unlock_irqrestore(&info->irq_spinlock, flags);
+	info->blocked_open++;
+	
+	while (1) {
+		if (tty->termios->c_cflag & CBAUD) {
+			spin_lock_irqsave(&info->irq_spinlock,flags);
+			info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+		 	usc_set_serial_signals(info);
+			spin_unlock_irqrestore(&info->irq_spinlock,flags);
+		}
+		
+		set_current_state(TASK_INTERRUPTIBLE);
+		
+		if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)){
+			retval = (info->flags & ASYNC_HUP_NOTIFY) ?
+					-EAGAIN : -ERESTARTSYS;
+			break;
+		}
+		
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+	 	usc_get_serial_signals(info);
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+		
+ 		if (!(info->flags & ASYNC_CLOSING) &&
+ 		    (do_clocal || (info->serial_signals & SerialSignal_DCD)) ) {
+ 			break;
+		}
+			
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):block_til_ready blocking on %s count=%d\n",
+				 __FILE__,__LINE__, tty->driver->name, info->count );
+				 
+		schedule();
+	}
+	
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&info->open_wait, &wait);
+	
+	if (extra_count)
+		info->count++;
+	info->blocked_open--;
+	
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):block_til_ready after blocking on %s count=%d\n",
+			 __FILE__,__LINE__, tty->driver->name, info->count );
+			 
+	if (!retval)
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+		
+	return retval;
+	
+}	/* end of block_til_ready() */
+
+/* mgsl_open()
+ *
+ *	Called when a port is opened.  Init and enable port.
+ *	Perform serial-specific initialization for the tty structure.
+ *
+ * Arguments:		tty	pointer to tty info structure
+ *			filp	associated file pointer
+ *
+ * Return Value:	0 if success, otherwise error code
+ */
+static int mgsl_open(struct tty_struct *tty, struct file * filp)
+{
+	struct mgsl_struct	*info;
+	int 			retval, line;
+	unsigned long		page;
+	unsigned long flags;
+
+	/* verify range of specified line number */	
+	line = tty->index;
+	if ((line < 0) || (line >= mgsl_device_count)) {
+		printk("%s(%d):mgsl_open with invalid line #%d.\n",
+			__FILE__,__LINE__,line);
+		return -ENODEV;
+	}
+
+	/* find the info structure for the specified line */
+	info = mgsl_device_list;
+	while(info && info->line != line)
+		info = info->next_device;
+	if (mgsl_paranoia_check(info, tty->name, "mgsl_open"))
+		return -ENODEV;
+	
+	tty->driver_data = info;
+	info->tty = tty;
+		
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_open(%s), old ref count = %d\n",
+			 __FILE__,__LINE__,tty->driver->name, info->count);
+
+	/* If port is closing, signal caller to try again */
+	if (tty_hung_up_p(filp) || info->flags & ASYNC_CLOSING){
+		if (info->flags & ASYNC_CLOSING)
+			interruptible_sleep_on(&info->close_wait);
+		retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+			-EAGAIN : -ERESTARTSYS);
+		goto cleanup;
+	}
+	
+	if (!tmp_buf) {
+		page = get_zeroed_page(GFP_KERNEL);
+		if (!page) {
+			retval = -ENOMEM;
+			goto cleanup;
+		}
+		if (tmp_buf)
+			free_page(page);
+		else
+			tmp_buf = (unsigned char *) page;
+	}
+	
+	info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+	spin_lock_irqsave(&info->netlock, flags);
+	if (info->netcount) {
+		retval = -EBUSY;
+		spin_unlock_irqrestore(&info->netlock, flags);
+		goto cleanup;
+	}
+	info->count++;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	if (info->count == 1) {
+		/* 1st open on this device, init hardware */
+		retval = startup(info);
+		if (retval < 0)
+			goto cleanup;
+	}
+
+	retval = block_til_ready(tty, filp, info);
+	if (retval) {
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):block_til_ready(%s) returned %d\n",
+				 __FILE__,__LINE__, info->device_name, retval);
+		goto cleanup;
+	}
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):mgsl_open(%s) success\n",
+			 __FILE__,__LINE__, info->device_name);
+	retval = 0;
+	
+cleanup:			
+	if (retval) {
+		if (tty->count == 1)
+			info->tty = NULL; /* tty layer will release tty struct */
+		if(info->count)
+			info->count--;
+	}
+	
+	return retval;
+	
+}	/* end of mgsl_open() */
+
+/*
+ * /proc fs routines....
+ */
+
+static inline int line_info(char *buf, struct mgsl_struct *info)
+{
+	char	stat_buf[30];
+	int	ret;
+	unsigned long flags;
+
+	if (info->bus_type == MGSL_BUS_TYPE_PCI) {
+		ret = sprintf(buf, "%s:PCI io:%04X irq:%d mem:%08X lcr:%08X",
+			info->device_name, info->io_base, info->irq_level,
+			info->phys_memory_base, info->phys_lcr_base);
+	} else {
+		ret = sprintf(buf, "%s:(E)ISA io:%04X irq:%d dma:%d",
+			info->device_name, info->io_base, 
+			info->irq_level, info->dma_level);
+	}
+
+	/* output current serial signal states */
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+ 	usc_get_serial_signals(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+	stat_buf[0] = 0;
+	stat_buf[1] = 0;
+	if (info->serial_signals & SerialSignal_RTS)
+		strcat(stat_buf, "|RTS");
+	if (info->serial_signals & SerialSignal_CTS)
+		strcat(stat_buf, "|CTS");
+	if (info->serial_signals & SerialSignal_DTR)
+		strcat(stat_buf, "|DTR");
+	if (info->serial_signals & SerialSignal_DSR)
+		strcat(stat_buf, "|DSR");
+	if (info->serial_signals & SerialSignal_DCD)
+		strcat(stat_buf, "|CD");
+	if (info->serial_signals & SerialSignal_RI)
+		strcat(stat_buf, "|RI");
+
+	if (info->params.mode == MGSL_MODE_HDLC ||
+	    info->params.mode == MGSL_MODE_RAW ) {
+		ret += sprintf(buf+ret, " HDLC txok:%d rxok:%d",
+			      info->icount.txok, info->icount.rxok);
+		if (info->icount.txunder)
+			ret += sprintf(buf+ret, " txunder:%d", info->icount.txunder);
+		if (info->icount.txabort)
+			ret += sprintf(buf+ret, " txabort:%d", info->icount.txabort);
+		if (info->icount.rxshort)
+			ret += sprintf(buf+ret, " rxshort:%d", info->icount.rxshort);	
+		if (info->icount.rxlong)
+			ret += sprintf(buf+ret, " rxlong:%d", info->icount.rxlong);
+		if (info->icount.rxover)
+			ret += sprintf(buf+ret, " rxover:%d", info->icount.rxover);
+		if (info->icount.rxcrc)
+			ret += sprintf(buf+ret, " rxcrc:%d", info->icount.rxcrc);
+	} else {
+		ret += sprintf(buf+ret, " ASYNC tx:%d rx:%d",
+			      info->icount.tx, info->icount.rx);
+		if (info->icount.frame)
+			ret += sprintf(buf+ret, " fe:%d", info->icount.frame);
+		if (info->icount.parity)
+			ret += sprintf(buf+ret, " pe:%d", info->icount.parity);
+		if (info->icount.brk)
+			ret += sprintf(buf+ret, " brk:%d", info->icount.brk);	
+		if (info->icount.overrun)
+			ret += sprintf(buf+ret, " oe:%d", info->icount.overrun);
+	}
+	
+	/* Append serial signal status to end */
+	ret += sprintf(buf+ret, " %s\n", stat_buf+1);
+	
+	ret += sprintf(buf+ret, "txactive=%d bh_req=%d bh_run=%d pending_bh=%x\n",
+	 info->tx_active,info->bh_requested,info->bh_running,
+	 info->pending_bh);
+	 
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	{	
+	u16 Tcsr = usc_InReg( info, TCSR );
+	u16 Tdmr = usc_InDmaReg( info, TDMR );
+	u16 Ticr = usc_InReg( info, TICR );
+	u16 Rscr = usc_InReg( info, RCSR );
+	u16 Rdmr = usc_InDmaReg( info, RDMR );
+	u16 Ricr = usc_InReg( info, RICR );
+	u16 Icr = usc_InReg( info, ICR );
+	u16 Dccr = usc_InReg( info, DCCR );
+	u16 Tmr = usc_InReg( info, TMR );
+	u16 Tccr = usc_InReg( info, TCCR );
+	u16 Ccar = inw( info->io_base + CCAR );
+	ret += sprintf(buf+ret, "tcsr=%04X tdmr=%04X ticr=%04X rcsr=%04X rdmr=%04X\n"
+                        "ricr=%04X icr =%04X dccr=%04X tmr=%04X tccr=%04X ccar=%04X\n",
+	 		Tcsr,Tdmr,Ticr,Rscr,Rdmr,Ricr,Icr,Dccr,Tmr,Tccr,Ccar );
+	}
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+	return ret;
+	
+}	/* end of line_info() */
+
+/* mgsl_read_proc()
+ * 
+ * Called to print information about devices
+ * 
+ * Arguments:
+ * 	page	page of memory to hold returned info
+ * 	start	
+ * 	off
+ * 	count
+ * 	eof
+ * 	data
+ * 	
+ * Return Value:
+ */
+static int mgsl_read_proc(char *page, char **start, off_t off, int count,
+		 int *eof, void *data)
+{
+	int len = 0, l;
+	off_t	begin = 0;
+	struct mgsl_struct *info;
+	
+	len += sprintf(page, "synclink driver:%s\n", driver_version);
+	
+	info = mgsl_device_list;
+	while( info ) {
+		l = line_info(page + len, info);
+		len += l;
+		if (len+begin > off+count)
+			goto done;
+		if (len+begin < off) {
+			begin += len;
+			len = 0;
+		}
+		info = info->next_device;
+	}
+
+	*eof = 1;
+done:
+	if (off >= len+begin)
+		return 0;
+	*start = page + (off-begin);
+	return ((count < begin+len-off) ? count : begin+len-off);
+	
+}	/* end of mgsl_read_proc() */
+
+/* mgsl_allocate_dma_buffers()
+ * 
+ * 	Allocate and format DMA buffers (ISA adapter)
+ * 	or format shared memory buffers (PCI adapter).
+ * 
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	0 if success, otherwise error
+ */
+static int mgsl_allocate_dma_buffers(struct mgsl_struct *info)
+{
+	unsigned short BuffersPerFrame;
+
+	info->last_mem_alloc = 0;
+
+	/* Calculate the number of DMA buffers necessary to hold the */
+	/* largest allowable frame size. Note: If the max frame size is */
+	/* not an even multiple of the DMA buffer size then we need to */
+	/* round the buffer count per frame up one. */
+
+	BuffersPerFrame = (unsigned short)(info->max_frame_size/DMABUFFERSIZE);
+	if ( info->max_frame_size % DMABUFFERSIZE )
+		BuffersPerFrame++;
+
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+		/*
+		 * The PCI adapter has 256KBytes of shared memory to use.
+		 * This is 64 PAGE_SIZE buffers.
+		 *
+		 * The first page is used for padding at this time so the
+		 * buffer list does not begin at offset 0 of the PCI
+		 * adapter's shared memory.
+		 *
+		 * The 2nd page is used for the buffer list. A 4K buffer
+		 * list can hold 128 DMA_BUFFER structures at 32 bytes
+		 * each.
+		 *
+		 * This leaves 62 4K pages.
+		 *
+		 * The next N pages are used for transmit frame(s). We
+		 * reserve enough 4K page blocks to hold the required
+		 * number of transmit dma buffers (num_tx_dma_buffers),
+		 * each of MaxFrameSize size.
+		 *
+		 * Of the remaining pages (62-N), determine how many can
+		 * be used to receive full MaxFrameSize inbound frames
+		 */
+		info->tx_buffer_count = info->num_tx_dma_buffers * BuffersPerFrame;
+		info->rx_buffer_count = 62 - info->tx_buffer_count;
+	} else {
+		/* Calculate the number of PAGE_SIZE buffers needed for */
+		/* receive and transmit DMA buffers. */
+
+
+		/* Calculate the number of DMA buffers necessary to */
+		/* hold 7 max size receive frames and one max size transmit frame. */
+		/* The receive buffer count is bumped by one so we avoid an */
+		/* End of List condition if all receive buffers are used when */
+		/* using linked list DMA buffers. */
+
+		info->tx_buffer_count = info->num_tx_dma_buffers * BuffersPerFrame;
+		info->rx_buffer_count = (BuffersPerFrame * MAXRXFRAMES) + 6;
+		
+		/* 
+		 * limit total TxBuffers & RxBuffers to 62 4K total 
+		 * (ala PCI Allocation) 
+		 */
+		
+		if ( (info->tx_buffer_count + info->rx_buffer_count) > 62 )
+			info->rx_buffer_count = 62 - info->tx_buffer_count;
+
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("%s(%d):Allocating %d TX and %d RX DMA buffers.\n",
+			__FILE__,__LINE__, info->tx_buffer_count,info->rx_buffer_count);
+	
+	if ( mgsl_alloc_buffer_list_memory( info ) < 0 ||
+		  mgsl_alloc_frame_memory(info, info->rx_buffer_list, info->rx_buffer_count) < 0 || 
+		  mgsl_alloc_frame_memory(info, info->tx_buffer_list, info->tx_buffer_count) < 0 || 
+		  mgsl_alloc_intermediate_rxbuffer_memory(info) < 0  ||
+		  mgsl_alloc_intermediate_txbuffer_memory(info) < 0 ) {
+		printk("%s(%d):Can't allocate DMA buffer memory\n",__FILE__,__LINE__);
+		return -ENOMEM;
+	}
+	
+	mgsl_reset_rx_dma_buffers( info );
+  	mgsl_reset_tx_dma_buffers( info );
+
+	return 0;
+
+}	/* end of mgsl_allocate_dma_buffers() */
+
+/*
+ * mgsl_alloc_buffer_list_memory()
+ * 
+ * Allocate a common DMA buffer for use as the
+ * receive and transmit buffer lists.
+ * 
+ * A buffer list is a set of buffer entries where each entry contains
+ * a pointer to an actual buffer and a pointer to the next buffer entry
+ * (plus some other info about the buffer).
+ * 
+ * The buffer entries for a list are built to form a circular list so
+ * that when the entire list has been traversed you start back at the
+ * beginning.
+ * 
+ * This function allocates memory for just the buffer entries.
+ * The links (pointer to next entry) are filled in with the physical
+ * address of the next entry so the adapter can navigate the list
+ * using bus master DMA. The pointers to the actual buffers are filled
+ * out later when the actual buffers are allocated.
+ * 
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	0 if success, otherwise error
+ */
+static int mgsl_alloc_buffer_list_memory( struct mgsl_struct *info )
+{
+	unsigned int i;
+
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+		/* PCI adapter uses shared memory. */
+		info->buffer_list = info->memory_base + info->last_mem_alloc;
+		info->buffer_list_phys = info->last_mem_alloc;
+		info->last_mem_alloc += BUFFERLISTSIZE;
+	} else {
+		/* ISA adapter uses system memory. */
+		/* The buffer lists are allocated as a common buffer that both */
+		/* the processor and adapter can access. This allows the driver to */
+		/* inspect portions of the buffer while other portions are being */
+		/* updated by the adapter using Bus Master DMA. */
+
+		info->buffer_list = kmalloc(BUFFERLISTSIZE, GFP_KERNEL | GFP_DMA);
+		if ( info->buffer_list == NULL )
+			return -ENOMEM;
+			
+		info->buffer_list_phys = isa_virt_to_bus(info->buffer_list);
+	}
+
+	/* We got the memory for the buffer entry lists. */
+	/* Initialize the memory block to all zeros. */
+	memset( info->buffer_list, 0, BUFFERLISTSIZE );
+
+	/* Save virtual address pointers to the receive and */
+	/* transmit buffer lists. (Receive 1st). These pointers will */
+	/* be used by the processor to access the lists. */
+	info->rx_buffer_list = (DMABUFFERENTRY *)info->buffer_list;
+	info->tx_buffer_list = (DMABUFFERENTRY *)info->buffer_list;
+	info->tx_buffer_list += info->rx_buffer_count;
+
+	/*
+	 * Build the links for the buffer entry lists such that
+	 * two circular lists are built. (Transmit and Receive).
+	 *
+	 * Note: the links are physical addresses
+	 * which are read by the adapter to determine the next
+	 * buffer entry to use.
+	 */
+
+	for ( i = 0; i < info->rx_buffer_count; i++ ) {
+		/* calculate and store physical address of this buffer entry */
+		info->rx_buffer_list[i].phys_entry =
+			info->buffer_list_phys + (i * sizeof(DMABUFFERENTRY));
+
+		/* calculate and store physical address of */
+		/* next entry in cirular list of entries */
+
+		info->rx_buffer_list[i].link = info->buffer_list_phys;
+
+		if ( i < info->rx_buffer_count - 1 )
+			info->rx_buffer_list[i].link += (i + 1) * sizeof(DMABUFFERENTRY);
+	}
+
+	for ( i = 0; i < info->tx_buffer_count; i++ ) {
+		/* calculate and store physical address of this buffer entry */
+		info->tx_buffer_list[i].phys_entry = info->buffer_list_phys +
+			((info->rx_buffer_count + i) * sizeof(DMABUFFERENTRY));
+
+		/* calculate and store physical address of */
+		/* next entry in cirular list of entries */
+
+		info->tx_buffer_list[i].link = info->buffer_list_phys +
+			info->rx_buffer_count * sizeof(DMABUFFERENTRY);
+
+		if ( i < info->tx_buffer_count - 1 )
+			info->tx_buffer_list[i].link += (i + 1) * sizeof(DMABUFFERENTRY);
+	}
+
+	return 0;
+
+}	/* end of mgsl_alloc_buffer_list_memory() */
+
+/* Free DMA buffers allocated for use as the
+ * receive and transmit buffer lists.
+ * Warning:
+ * 
+ * 	The data transfer buffers associated with the buffer list
+ * 	MUST be freed before freeing the buffer list itself because
+ * 	the buffer list contains the information necessary to free
+ * 	the individual buffers!
+ */
+static void mgsl_free_buffer_list_memory( struct mgsl_struct *info )
+{
+	if ( info->buffer_list && info->bus_type != MGSL_BUS_TYPE_PCI )
+		kfree(info->buffer_list);
+		
+	info->buffer_list = NULL;
+	info->rx_buffer_list = NULL;
+	info->tx_buffer_list = NULL;
+
+}	/* end of mgsl_free_buffer_list_memory() */
+
+/*
+ * mgsl_alloc_frame_memory()
+ * 
+ * 	Allocate the frame DMA buffers used by the specified buffer list.
+ * 	Each DMA buffer will be one memory page in size. This is necessary
+ * 	because memory can fragment enough that it may be impossible
+ * 	contiguous pages.
+ * 
+ * Arguments:
+ * 
+ *	info		pointer to device instance data
+ * 	BufferList	pointer to list of buffer entries
+ * 	Buffercount	count of buffer entries in buffer list
+ * 
+ * Return Value:	0 if success, otherwise -ENOMEM
+ */
+static int mgsl_alloc_frame_memory(struct mgsl_struct *info,DMABUFFERENTRY *BufferList,int Buffercount)
+{
+	int i;
+	unsigned long phys_addr;
+
+	/* Allocate page sized buffers for the receive buffer list */
+
+	for ( i = 0; i < Buffercount; i++ ) {
+		if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+			/* PCI adapter uses shared memory buffers. */
+			BufferList[i].virt_addr = info->memory_base + info->last_mem_alloc;
+			phys_addr = info->last_mem_alloc;
+			info->last_mem_alloc += DMABUFFERSIZE;
+		} else {
+			/* ISA adapter uses system memory. */
+			BufferList[i].virt_addr = 
+				kmalloc(DMABUFFERSIZE, GFP_KERNEL | GFP_DMA);
+			if ( BufferList[i].virt_addr == NULL )
+				return -ENOMEM;
+			phys_addr = isa_virt_to_bus(BufferList[i].virt_addr);
+		}
+		BufferList[i].phys_addr = phys_addr;
+	}
+
+	return 0;
+
+}	/* end of mgsl_alloc_frame_memory() */
+
+/*
+ * mgsl_free_frame_memory()
+ * 
+ * 	Free the buffers associated with
+ * 	each buffer entry of a buffer list.
+ * 
+ * Arguments:
+ * 
+ *	info		pointer to device instance data
+ * 	BufferList	pointer to list of buffer entries
+ * 	Buffercount	count of buffer entries in buffer list
+ * 
+ * Return Value:	None
+ */
+static void mgsl_free_frame_memory(struct mgsl_struct *info, DMABUFFERENTRY *BufferList, int Buffercount)
+{
+	int i;
+
+	if ( BufferList ) {
+		for ( i = 0 ; i < Buffercount ; i++ ) {
+			if ( BufferList[i].virt_addr ) {
+				if ( info->bus_type != MGSL_BUS_TYPE_PCI )
+					kfree(BufferList[i].virt_addr);
+				BufferList[i].virt_addr = NULL;
+			}
+		}
+	}
+
+}	/* end of mgsl_free_frame_memory() */
+
+/* mgsl_free_dma_buffers()
+ * 
+ * 	Free DMA buffers
+ * 	
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_free_dma_buffers( struct mgsl_struct *info )
+{
+	mgsl_free_frame_memory( info, info->rx_buffer_list, info->rx_buffer_count );
+	mgsl_free_frame_memory( info, info->tx_buffer_list, info->tx_buffer_count );
+	mgsl_free_buffer_list_memory( info );
+
+}	/* end of mgsl_free_dma_buffers() */
+
+
+/*
+ * mgsl_alloc_intermediate_rxbuffer_memory()
+ * 
+ * 	Allocate a buffer large enough to hold max_frame_size. This buffer
+ *	is used to pass an assembled frame to the line discipline.
+ * 
+ * Arguments:
+ * 
+ *	info		pointer to device instance data
+ * 
+ * Return Value:	0 if success, otherwise -ENOMEM
+ */
+static int mgsl_alloc_intermediate_rxbuffer_memory(struct mgsl_struct *info)
+{
+	info->intermediate_rxbuffer = kmalloc(info->max_frame_size, GFP_KERNEL | GFP_DMA);
+	if ( info->intermediate_rxbuffer == NULL )
+		return -ENOMEM;
+
+	return 0;
+
+}	/* end of mgsl_alloc_intermediate_rxbuffer_memory() */
+
+/*
+ * mgsl_free_intermediate_rxbuffer_memory()
+ * 
+ * 
+ * Arguments:
+ * 
+ *	info		pointer to device instance data
+ * 
+ * Return Value:	None
+ */
+static void mgsl_free_intermediate_rxbuffer_memory(struct mgsl_struct *info)
+{
+	if ( info->intermediate_rxbuffer )
+		kfree(info->intermediate_rxbuffer);
+
+	info->intermediate_rxbuffer = NULL;
+
+}	/* end of mgsl_free_intermediate_rxbuffer_memory() */
+
+/*
+ * mgsl_alloc_intermediate_txbuffer_memory()
+ *
+ * 	Allocate intermdiate transmit buffer(s) large enough to hold max_frame_size.
+ * 	This buffer is used to load transmit frames into the adapter's dma transfer
+ * 	buffers when there is sufficient space.
+ *
+ * Arguments:
+ *
+ *	info		pointer to device instance data
+ *
+ * Return Value:	0 if success, otherwise -ENOMEM
+ */
+static int mgsl_alloc_intermediate_txbuffer_memory(struct mgsl_struct *info)
+{
+	int i;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("%s %s(%d)  allocating %d tx holding buffers\n",
+				info->device_name, __FILE__,__LINE__,info->num_tx_holding_buffers);
+
+	memset(info->tx_holding_buffers,0,sizeof(info->tx_holding_buffers));
+
+	for ( i=0; i<info->num_tx_holding_buffers; ++i) {
+		info->tx_holding_buffers[i].buffer =
+			kmalloc(info->max_frame_size, GFP_KERNEL);
+		if ( info->tx_holding_buffers[i].buffer == NULL )
+			return -ENOMEM;
+	}
+
+	return 0;
+
+}	/* end of mgsl_alloc_intermediate_txbuffer_memory() */
+
+/*
+ * mgsl_free_intermediate_txbuffer_memory()
+ *
+ *
+ * Arguments:
+ *
+ *	info		pointer to device instance data
+ *
+ * Return Value:	None
+ */
+static void mgsl_free_intermediate_txbuffer_memory(struct mgsl_struct *info)
+{
+	int i;
+
+	for ( i=0; i<info->num_tx_holding_buffers; ++i ) {
+		if ( info->tx_holding_buffers[i].buffer ) {
+				kfree(info->tx_holding_buffers[i].buffer);
+				info->tx_holding_buffers[i].buffer=NULL;
+		}
+	}
+
+	info->get_tx_holding_index = 0;
+	info->put_tx_holding_index = 0;
+	info->tx_holding_count = 0;
+
+}	/* end of mgsl_free_intermediate_txbuffer_memory() */
+
+
+/*
+ * load_next_tx_holding_buffer()
+ *
+ * attempts to load the next buffered tx request into the
+ * tx dma buffers
+ *
+ * Arguments:
+ *
+ *	info		pointer to device instance data
+ *
+ * Return Value:	1 if next buffered tx request loaded
+ * 			into adapter's tx dma buffer,
+ * 			0 otherwise
+ */
+static int load_next_tx_holding_buffer(struct mgsl_struct *info)
+{
+	int ret = 0;
+
+	if ( info->tx_holding_count ) {
+		/* determine if we have enough tx dma buffers
+		 * to accommodate the next tx frame
+		 */
+		struct tx_holding_buffer *ptx =
+			&info->tx_holding_buffers[info->get_tx_holding_index];
+		int num_free = num_free_tx_dma_buffers(info);
+		int num_needed = ptx->buffer_size / DMABUFFERSIZE;
+		if ( ptx->buffer_size % DMABUFFERSIZE )
+			++num_needed;
+
+		if (num_needed <= num_free) {
+			info->xmit_cnt = ptx->buffer_size;
+			mgsl_load_tx_dma_buffer(info,ptx->buffer,ptx->buffer_size);
+
+			--info->tx_holding_count;
+			if ( ++info->get_tx_holding_index >= info->num_tx_holding_buffers)
+				info->get_tx_holding_index=0;
+
+			/* restart transmit timer */
+			mod_timer(&info->tx_timer, jiffies + msecs_to_jiffies(5000));
+
+			ret = 1;
+		}
+	}
+
+	return ret;
+}
+
+/*
+ * save_tx_buffer_request()
+ *
+ * attempt to store transmit frame request for later transmission
+ *
+ * Arguments:
+ *
+ *	info		pointer to device instance data
+ * 	Buffer		pointer to buffer containing frame to load
+ * 	BufferSize	size in bytes of frame in Buffer
+ *
+ * Return Value:	1 if able to store, 0 otherwise
+ */
+static int save_tx_buffer_request(struct mgsl_struct *info,const char *Buffer, unsigned int BufferSize)
+{
+	struct tx_holding_buffer *ptx;
+
+	if ( info->tx_holding_count >= info->num_tx_holding_buffers ) {
+		return 0;	        /* all buffers in use */
+	}
+
+	ptx = &info->tx_holding_buffers[info->put_tx_holding_index];
+	ptx->buffer_size = BufferSize;
+	memcpy( ptx->buffer, Buffer, BufferSize);
+
+	++info->tx_holding_count;
+	if ( ++info->put_tx_holding_index >= info->num_tx_holding_buffers)
+		info->put_tx_holding_index=0;
+
+	return 1;
+}
+
+static int mgsl_claim_resources(struct mgsl_struct *info)
+{
+	if (request_region(info->io_base,info->io_addr_size,"synclink") == NULL) {
+		printk( "%s(%d):I/O address conflict on device %s Addr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->io_base);
+		return -ENODEV;
+	}
+	info->io_addr_requested = 1;
+	
+	if ( request_irq(info->irq_level,mgsl_interrupt,info->irq_flags,
+		info->device_name, info ) < 0 ) {
+		printk( "%s(%d):Cant request interrupt on device %s IRQ=%d\n",
+			__FILE__,__LINE__,info->device_name, info->irq_level );
+		goto errout;
+	}
+	info->irq_requested = 1;
+	
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+		if (request_mem_region(info->phys_memory_base,0x40000,"synclink") == NULL) {
+			printk( "%s(%d):mem addr conflict device %s Addr=%08X\n",
+				__FILE__,__LINE__,info->device_name, info->phys_memory_base);
+			goto errout;
+		}
+		info->shared_mem_requested = 1;
+		if (request_mem_region(info->phys_lcr_base + info->lcr_offset,128,"synclink") == NULL) {
+			printk( "%s(%d):lcr mem addr conflict device %s Addr=%08X\n",
+				__FILE__,__LINE__,info->device_name, info->phys_lcr_base + info->lcr_offset);
+			goto errout;
+		}
+		info->lcr_mem_requested = 1;
+
+		info->memory_base = ioremap(info->phys_memory_base,0x40000);
+		if (!info->memory_base) {
+			printk( "%s(%d):Cant map shared memory on device %s MemAddr=%08X\n",
+				__FILE__,__LINE__,info->device_name, info->phys_memory_base );
+			goto errout;
+		}
+		
+		if ( !mgsl_memory_test(info) ) {
+			printk( "%s(%d):Failed shared memory test %s MemAddr=%08X\n",
+				__FILE__,__LINE__,info->device_name, info->phys_memory_base );
+			goto errout;
+		}
+		
+		info->lcr_base = ioremap(info->phys_lcr_base,PAGE_SIZE) + info->lcr_offset;
+		if (!info->lcr_base) {
+			printk( "%s(%d):Cant map LCR memory on device %s MemAddr=%08X\n",
+				__FILE__,__LINE__,info->device_name, info->phys_lcr_base );
+			goto errout;
+		}
+		
+	} else {
+		/* claim DMA channel */
+		
+		if (request_dma(info->dma_level,info->device_name) < 0){
+			printk( "%s(%d):Cant request DMA channel on device %s DMA=%d\n",
+				__FILE__,__LINE__,info->device_name, info->dma_level );
+			mgsl_release_resources( info );
+			return -ENODEV;
+		}
+		info->dma_requested = 1;
+
+		/* ISA adapter uses bus master DMA */		
+		set_dma_mode(info->dma_level,DMA_MODE_CASCADE);
+		enable_dma(info->dma_level);
+	}
+	
+	if ( mgsl_allocate_dma_buffers(info) < 0 ) {
+		printk( "%s(%d):Cant allocate DMA buffers on device %s DMA=%d\n",
+			__FILE__,__LINE__,info->device_name, info->dma_level );
+		goto errout;
+	}	
+	
+	return 0;
+errout:
+	mgsl_release_resources(info);
+	return -ENODEV;
+
+}	/* end of mgsl_claim_resources() */
+
+static void mgsl_release_resources(struct mgsl_struct *info)
+{
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):mgsl_release_resources(%s) entry\n",
+			__FILE__,__LINE__,info->device_name );
+			
+	if ( info->irq_requested ) {
+		free_irq(info->irq_level, info);
+		info->irq_requested = 0;
+	}
+	if ( info->dma_requested ) {
+		disable_dma(info->dma_level);
+		free_dma(info->dma_level);
+		info->dma_requested = 0;
+	}
+	mgsl_free_dma_buffers(info);
+	mgsl_free_intermediate_rxbuffer_memory(info);
+     	mgsl_free_intermediate_txbuffer_memory(info);
+	
+	if ( info->io_addr_requested ) {
+		release_region(info->io_base,info->io_addr_size);
+		info->io_addr_requested = 0;
+	}
+	if ( info->shared_mem_requested ) {
+		release_mem_region(info->phys_memory_base,0x40000);
+		info->shared_mem_requested = 0;
+	}
+	if ( info->lcr_mem_requested ) {
+		release_mem_region(info->phys_lcr_base + info->lcr_offset,128);
+		info->lcr_mem_requested = 0;
+	}
+	if (info->memory_base){
+		iounmap(info->memory_base);
+		info->memory_base = NULL;
+	}
+	if (info->lcr_base){
+		iounmap(info->lcr_base - info->lcr_offset);
+		info->lcr_base = NULL;
+	}
+	
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):mgsl_release_resources(%s) exit\n",
+			__FILE__,__LINE__,info->device_name );
+			
+}	/* end of mgsl_release_resources() */
+
+/* mgsl_add_device()
+ * 
+ * 	Add the specified device instance data structure to the
+ * 	global linked list of devices and increment the device count.
+ * 	
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_add_device( struct mgsl_struct *info )
+{
+	info->next_device = NULL;
+	info->line = mgsl_device_count;
+	sprintf(info->device_name,"ttySL%d",info->line);
+	
+	if (info->line < MAX_TOTAL_DEVICES) {
+		if (maxframe[info->line])
+			info->max_frame_size = maxframe[info->line];
+		info->dosyncppp = dosyncppp[info->line];
+
+		if (txdmabufs[info->line]) {
+			info->num_tx_dma_buffers = txdmabufs[info->line];
+			if (info->num_tx_dma_buffers < 1)
+				info->num_tx_dma_buffers = 1;
+		}
+
+		if (txholdbufs[info->line]) {
+			info->num_tx_holding_buffers = txholdbufs[info->line];
+			if (info->num_tx_holding_buffers < 1)
+				info->num_tx_holding_buffers = 1;
+			else if (info->num_tx_holding_buffers > MAX_TX_HOLDING_BUFFERS)
+				info->num_tx_holding_buffers = MAX_TX_HOLDING_BUFFERS;
+		}
+	}
+
+	mgsl_device_count++;
+	
+	if ( !mgsl_device_list )
+		mgsl_device_list = info;
+	else {	
+		struct mgsl_struct *current_dev = mgsl_device_list;
+		while( current_dev->next_device )
+			current_dev = current_dev->next_device;
+		current_dev->next_device = info;
+	}
+	
+	if ( info->max_frame_size < 4096 )
+		info->max_frame_size = 4096;
+	else if ( info->max_frame_size > 65535 )
+		info->max_frame_size = 65535;
+	
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+		printk( "SyncLink PCI v%d %s: IO=%04X IRQ=%d Mem=%08X,%08X MaxFrameSize=%u\n",
+			info->hw_version + 1, info->device_name, info->io_base, info->irq_level,
+			info->phys_memory_base, info->phys_lcr_base,
+		     	info->max_frame_size );
+	} else {
+		printk( "SyncLink ISA %s: IO=%04X IRQ=%d DMA=%d MaxFrameSize=%u\n",
+			info->device_name, info->io_base, info->irq_level, info->dma_level,
+		     	info->max_frame_size );
+	}
+
+#ifdef CONFIG_HDLC
+	hdlcdev_init(info);
+#endif
+
+}	/* end of mgsl_add_device() */
+
+/* mgsl_allocate_device()
+ * 
+ * 	Allocate and initialize a device instance structure
+ * 	
+ * Arguments:		none
+ * Return Value:	pointer to mgsl_struct if success, otherwise NULL
+ */
+static struct mgsl_struct* mgsl_allocate_device(void)
+{
+	struct mgsl_struct *info;
+	
+	info = (struct mgsl_struct *)kmalloc(sizeof(struct mgsl_struct),
+		 GFP_KERNEL);
+		 
+	if (!info) {
+		printk("Error can't allocate device instance data\n");
+	} else {
+		memset(info, 0, sizeof(struct mgsl_struct));
+		info->magic = MGSL_MAGIC;
+		INIT_WORK(&info->task, mgsl_bh_handler, info);
+		info->max_frame_size = 4096;
+		info->close_delay = 5*HZ/10;
+		info->closing_wait = 30*HZ;
+		init_waitqueue_head(&info->open_wait);
+		init_waitqueue_head(&info->close_wait);
+		init_waitqueue_head(&info->status_event_wait_q);
+		init_waitqueue_head(&info->event_wait_q);
+		spin_lock_init(&info->irq_spinlock);
+		spin_lock_init(&info->netlock);
+		memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
+		info->idle_mode = HDLC_TXIDLE_FLAGS;		
+		info->num_tx_dma_buffers = 1;
+		info->num_tx_holding_buffers = 0;
+	}
+	
+	return info;
+
+}	/* end of mgsl_allocate_device()*/
+
+static struct tty_operations mgsl_ops = {
+	.open = mgsl_open,
+	.close = mgsl_close,
+	.write = mgsl_write,
+	.put_char = mgsl_put_char,
+	.flush_chars = mgsl_flush_chars,
+	.write_room = mgsl_write_room,
+	.chars_in_buffer = mgsl_chars_in_buffer,
+	.flush_buffer = mgsl_flush_buffer,
+	.ioctl = mgsl_ioctl,
+	.throttle = mgsl_throttle,
+	.unthrottle = mgsl_unthrottle,
+	.send_xchar = mgsl_send_xchar,
+	.break_ctl = mgsl_break,
+	.wait_until_sent = mgsl_wait_until_sent,
+ 	.read_proc = mgsl_read_proc,
+	.set_termios = mgsl_set_termios,
+	.stop = mgsl_stop,
+	.start = mgsl_start,
+	.hangup = mgsl_hangup,
+	.tiocmget = tiocmget,
+	.tiocmset = tiocmset,
+};
+
+/*
+ * perform tty device initialization
+ */
+static int mgsl_init_tty(void)
+{
+	int rc;
+
+	serial_driver = alloc_tty_driver(128);
+	if (!serial_driver)
+		return -ENOMEM;
+	
+	serial_driver->owner = THIS_MODULE;
+	serial_driver->driver_name = "synclink";
+	serial_driver->name = "ttySL";
+	serial_driver->major = ttymajor;
+	serial_driver->minor_start = 64;
+	serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	serial_driver->subtype = SERIAL_TYPE_NORMAL;
+	serial_driver->init_termios = tty_std_termios;
+	serial_driver->init_termios.c_cflag =
+		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	serial_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(serial_driver, &mgsl_ops);
+	if ((rc = tty_register_driver(serial_driver)) < 0) {
+		printk("%s(%d):Couldn't register serial driver\n",
+			__FILE__,__LINE__);
+		put_tty_driver(serial_driver);
+		serial_driver = NULL;
+		return rc;
+	}
+			
+ 	printk("%s %s, tty major#%d\n",
+		driver_name, driver_version,
+		serial_driver->major);
+	return 0;
+}
+
+/* enumerate user specified ISA adapters
+ */
+static void mgsl_enum_isa_devices(void)
+{
+	struct mgsl_struct *info;
+	int i;
+		
+	/* Check for user specified ISA devices */
+	
+	for (i=0 ;(i < MAX_ISA_DEVICES) && io[i] && irq[i]; i++){
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk("ISA device specified io=%04X,irq=%d,dma=%d\n",
+				io[i], irq[i], dma[i] );
+		
+		info = mgsl_allocate_device();
+		if ( !info ) {
+			/* error allocating device instance data */
+			if ( debug_level >= DEBUG_LEVEL_ERROR )
+				printk( "can't allocate device instance data.\n");
+			continue;
+		}
+		
+		/* Copy user configuration info to device instance data */
+		info->io_base = (unsigned int)io[i];
+		info->irq_level = (unsigned int)irq[i];
+		info->irq_level = irq_canonicalize(info->irq_level);
+		info->dma_level = (unsigned int)dma[i];
+		info->bus_type = MGSL_BUS_TYPE_ISA;
+		info->io_addr_size = 16;
+		info->irq_flags = 0;
+		
+		mgsl_add_device( info );
+	}
+}
+
+static void synclink_cleanup(void)
+{
+	int rc;
+	struct mgsl_struct *info;
+	struct mgsl_struct *tmp;
+
+	printk("Unloading %s: %s\n", driver_name, driver_version);
+
+	if (serial_driver) {
+		if ((rc = tty_unregister_driver(serial_driver)))
+			printk("%s(%d) failed to unregister tty driver err=%d\n",
+			       __FILE__,__LINE__,rc);
+		put_tty_driver(serial_driver);
+	}
+
+	info = mgsl_device_list;
+	while(info) {
+#ifdef CONFIG_HDLC
+		hdlcdev_exit(info);
+#endif
+		mgsl_release_resources(info);
+		tmp = info;
+		info = info->next_device;
+		kfree(tmp);
+	}
+	
+	if (tmp_buf) {
+		free_page((unsigned long) tmp_buf);
+		tmp_buf = NULL;
+	}
+	
+	if (pci_registered)
+		pci_unregister_driver(&synclink_pci_driver);
+}
+
+static int __init synclink_init(void)
+{
+	int rc;
+
+	if (break_on_load) {
+	 	mgsl_get_text_ptr();
+  		BREAKPOINT();
+	}
+
+ 	printk("%s %s\n", driver_name, driver_version);
+
+	mgsl_enum_isa_devices();
+	if ((rc = pci_register_driver(&synclink_pci_driver)) < 0)
+		printk("%s:failed to register PCI driver, error=%d\n",__FILE__,rc);
+	else
+		pci_registered = 1;
+
+	if ((rc = mgsl_init_tty()) < 0)
+		goto error;
+
+	return 0;
+
+error:
+	synclink_cleanup();
+	return rc;
+}
+
+static void __exit synclink_exit(void)
+{
+	synclink_cleanup();
+}
+
+module_init(synclink_init);
+module_exit(synclink_exit);
+
+/*
+ * usc_RTCmd()
+ *
+ * Issue a USC Receive/Transmit command to the
+ * Channel Command/Address Register (CCAR).
+ *
+ * Notes:
+ *
+ *    The command is encoded in the most significant 5 bits <15..11>
+ *    of the CCAR value. Bits <10..7> of the CCAR must be preserved
+ *    and Bits <6..0> must be written as zeros.
+ *
+ * Arguments:
+ *
+ *    info   pointer to device information structure
+ *    Cmd    command mask (use symbolic macros)
+ *
+ * Return Value:
+ *
+ *    None
+ */
+static void usc_RTCmd( struct mgsl_struct *info, u16 Cmd )
+{
+	/* output command to CCAR in bits <15..11> */
+	/* preserve bits <10..7>, bits <6..0> must be zero */
+
+	outw( Cmd + info->loopback_bits, info->io_base + CCAR );
+
+	/* Read to flush write to CCAR */
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+		inw( info->io_base + CCAR );
+
+}	/* end of usc_RTCmd() */
+
+/*
+ * usc_DmaCmd()
+ *
+ *    Issue a DMA command to the DMA Command/Address Register (DCAR).
+ *
+ * Arguments:
+ *
+ *    info   pointer to device information structure
+ *    Cmd    DMA command mask (usc_DmaCmd_XX Macros)
+ *
+ * Return Value:
+ *
+ *       None
+ */
+static void usc_DmaCmd( struct mgsl_struct *info, u16 Cmd )
+{
+	/* write command mask to DCAR */
+	outw( Cmd + info->mbre_bit, info->io_base );
+
+	/* Read to flush write to DCAR */
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+		inw( info->io_base );
+
+}	/* end of usc_DmaCmd() */
+
+/*
+ * usc_OutDmaReg()
+ *
+ *    Write a 16-bit value to a USC DMA register
+ *
+ * Arguments:
+ *
+ *    info      pointer to device info structure
+ *    RegAddr   register address (number) for write
+ *    RegValue  16-bit value to write to register
+ *
+ * Return Value:
+ *
+ *    None
+ *
+ */
+static void usc_OutDmaReg( struct mgsl_struct *info, u16 RegAddr, u16 RegValue )
+{
+	/* Note: The DCAR is located at the adapter base address */
+	/* Note: must preserve state of BIT8 in DCAR */
+
+	outw( RegAddr + info->mbre_bit, info->io_base );
+	outw( RegValue, info->io_base );
+
+	/* Read to flush write to DCAR */
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+		inw( info->io_base );
+
+}	/* end of usc_OutDmaReg() */
+ 
+/*
+ * usc_InDmaReg()
+ *
+ *    Read a 16-bit value from a DMA register
+ *
+ * Arguments:
+ *
+ *    info     pointer to device info structure
+ *    RegAddr  register address (number) to read from
+ *
+ * Return Value:
+ *
+ *    The 16-bit value read from register
+ *
+ */
+static u16 usc_InDmaReg( struct mgsl_struct *info, u16 RegAddr )
+{
+	/* Note: The DCAR is located at the adapter base address */
+	/* Note: must preserve state of BIT8 in DCAR */
+
+	outw( RegAddr + info->mbre_bit, info->io_base );
+	return inw( info->io_base );
+
+}	/* end of usc_InDmaReg() */
+
+/*
+ *
+ * usc_OutReg()
+ *
+ *    Write a 16-bit value to a USC serial channel register 
+ *
+ * Arguments:
+ *
+ *    info      pointer to device info structure
+ *    RegAddr   register address (number) to write to
+ *    RegValue  16-bit value to write to register
+ *
+ * Return Value:
+ *
+ *    None
+ *
+ */
+static void usc_OutReg( struct mgsl_struct *info, u16 RegAddr, u16 RegValue )
+{
+	outw( RegAddr + info->loopback_bits, info->io_base + CCAR );
+	outw( RegValue, info->io_base + CCAR );
+
+	/* Read to flush write to CCAR */
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+		inw( info->io_base + CCAR );
+
+}	/* end of usc_OutReg() */
+
+/*
+ * usc_InReg()
+ *
+ *    Reads a 16-bit value from a USC serial channel register
+ *
+ * Arguments:
+ *
+ *    info       pointer to device extension
+ *    RegAddr    register address (number) to read from
+ *
+ * Return Value:
+ *
+ *    16-bit value read from register
+ */
+static u16 usc_InReg( struct mgsl_struct *info, u16 RegAddr )
+{
+	outw( RegAddr + info->loopback_bits, info->io_base + CCAR );
+	return inw( info->io_base + CCAR );
+
+}	/* end of usc_InReg() */
+
+/* usc_set_sdlc_mode()
+ *
+ *    Set up the adapter for SDLC DMA communications.
+ *
+ * Arguments:		info    pointer to device instance data
+ * Return Value: 	NONE
+ */
+static void usc_set_sdlc_mode( struct mgsl_struct *info )
+{
+	u16 RegValue;
+	int PreSL1660;
+	
+	/*
+	 * determine if the IUSC on the adapter is pre-SL1660. If
+	 * not, take advantage of the UnderWait feature of more
+	 * modern chips. If an underrun occurs and this bit is set,
+	 * the transmitter will idle the programmed idle pattern
+	 * until the driver has time to service the underrun. Otherwise,
+	 * the dma controller may get the cycles previously requested
+	 * and begin transmitting queued tx data.
+	 */
+	usc_OutReg(info,TMCR,0x1f);
+	RegValue=usc_InReg(info,TMDR);
+	if ( RegValue == IUSC_PRE_SL1660 )
+		PreSL1660 = 1;
+	else
+		PreSL1660 = 0;
+	
+
+ 	if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
+ 	{
+ 	   /*
+ 	   ** Channel Mode Register (CMR)
+ 	   **
+ 	   ** <15..14>    10    Tx Sub Modes, Send Flag on Underrun
+ 	   ** <13>        0     0 = Transmit Disabled (initially)
+ 	   ** <12>        0     1 = Consecutive Idles share common 0
+ 	   ** <11..8>     1110  Transmitter Mode = HDLC/SDLC Loop
+ 	   ** <7..4>      0000  Rx Sub Modes, addr/ctrl field handling
+ 	   ** <3..0>      0110  Receiver Mode = HDLC/SDLC
+ 	   **
+ 	   ** 1000 1110 0000 0110 = 0x8e06
+ 	   */
+ 	   RegValue = 0x8e06;
+ 
+ 	   /*--------------------------------------------------
+ 	    * ignore user options for UnderRun Actions and
+ 	    * preambles
+ 	    *--------------------------------------------------*/
+ 	}
+ 	else
+ 	{	
+		/* Channel mode Register (CMR)
+		 *
+		 * <15..14>  00    Tx Sub modes, Underrun Action
+		 * <13>      0     1 = Send Preamble before opening flag
+		 * <12>      0     1 = Consecutive Idles share common 0
+		 * <11..8>   0110  Transmitter mode = HDLC/SDLC
+		 * <7..4>    0000  Rx Sub modes, addr/ctrl field handling
+		 * <3..0>    0110  Receiver mode = HDLC/SDLC
+		 *
+		 * 0000 0110 0000 0110 = 0x0606
+		 */
+		if (info->params.mode == MGSL_MODE_RAW) {
+			RegValue = 0x0001;		/* Set Receive mode = external sync */
+
+			usc_OutReg( info, IOCR,		/* Set IOCR DCD is RxSync Detect Input */
+				(unsigned short)((usc_InReg(info, IOCR) & ~(BIT13|BIT12)) | BIT12));
+
+			/*
+			 * TxSubMode:
+			 * 	CMR <15>		0	Don't send CRC on Tx Underrun
+			 * 	CMR <14>		x	undefined
+			 * 	CMR <13>		0	Send preamble before openning sync
+			 * 	CMR <12>		0	Send 8-bit syncs, 1=send Syncs per TxLength
+			 *
+			 * TxMode:
+			 * 	CMR <11-8)	0100	MonoSync
+			 *
+			 * 	0x00 0100 xxxx xxxx  04xx
+			 */
+			RegValue |= 0x0400;
+		}
+		else {
+
+		RegValue = 0x0606;
+
+		if ( info->params.flags & HDLC_FLAG_UNDERRUN_ABORT15 )
+			RegValue |= BIT14;
+		else if ( info->params.flags & HDLC_FLAG_UNDERRUN_FLAG )
+			RegValue |= BIT15;
+		else if ( info->params.flags & HDLC_FLAG_UNDERRUN_CRC )
+			RegValue |= BIT15 + BIT14;
+		}
+
+		if ( info->params.preamble != HDLC_PREAMBLE_PATTERN_NONE )
+			RegValue |= BIT13;
+	}
+
+	if ( info->params.mode == MGSL_MODE_HDLC &&
+		(info->params.flags & HDLC_FLAG_SHARE_ZERO) )
+		RegValue |= BIT12;
+
+	if ( info->params.addr_filter != 0xff )
+	{
+		/* set up receive address filtering */
+		usc_OutReg( info, RSR, info->params.addr_filter );
+		RegValue |= BIT4;
+	}
+
+	usc_OutReg( info, CMR, RegValue );
+	info->cmr_value = RegValue;
+
+	/* Receiver mode Register (RMR)
+	 *
+	 * <15..13>  000    encoding
+	 * <12..11>  00     FCS = 16bit CRC CCITT (x15 + x12 + x5 + 1)
+	 * <10>      1      1 = Set CRC to all 1s (use for SDLC/HDLC)
+	 * <9>       0      1 = Include Receive chars in CRC
+	 * <8>       1      1 = Use Abort/PE bit as abort indicator
+	 * <7..6>    00     Even parity
+	 * <5>       0      parity disabled
+	 * <4..2>    000    Receive Char Length = 8 bits
+	 * <1..0>    00     Disable Receiver
+	 *
+	 * 0000 0101 0000 0000 = 0x0500
+	 */
+
+	RegValue = 0x0500;
+
+	switch ( info->params.encoding ) {
+	case HDLC_ENCODING_NRZB:               RegValue |= BIT13; break;
+	case HDLC_ENCODING_NRZI_MARK:          RegValue |= BIT14; break;
+	case HDLC_ENCODING_NRZI_SPACE:	       RegValue |= BIT14 + BIT13; break;
+	case HDLC_ENCODING_BIPHASE_MARK:       RegValue |= BIT15; break;
+	case HDLC_ENCODING_BIPHASE_SPACE:      RegValue |= BIT15 + BIT13; break;
+	case HDLC_ENCODING_BIPHASE_LEVEL:      RegValue |= BIT15 + BIT14; break;
+	case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: RegValue |= BIT15 + BIT14 + BIT13; break;
+	}
+
+	if ( (info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_16_CCITT )
+		RegValue |= BIT9;
+	else if ( (info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_32_CCITT )
+		RegValue |= ( BIT12 | BIT10 | BIT9 );
+
+	usc_OutReg( info, RMR, RegValue );
+
+	/* Set the Receive count Limit Register (RCLR) to 0xffff. */
+	/* When an opening flag of an SDLC frame is recognized the */
+	/* Receive Character count (RCC) is loaded with the value in */
+	/* RCLR. The RCC is decremented for each received byte.  The */
+	/* value of RCC is stored after the closing flag of the frame */
+	/* allowing the frame size to be computed. */
+
+	usc_OutReg( info, RCLR, RCLRVALUE );
+
+	usc_RCmd( info, RCmd_SelectRicrdma_level );
+
+	/* Receive Interrupt Control Register (RICR)
+	 *
+	 * <15..8>	?	RxFIFO DMA Request Level
+	 * <7>		0	Exited Hunt IA (Interrupt Arm)
+	 * <6>		0	Idle Received IA
+	 * <5>		0	Break/Abort IA
+	 * <4>		0	Rx Bound IA
+	 * <3>		1	Queued status reflects oldest 2 bytes in FIFO
+	 * <2>		0	Abort/PE IA
+	 * <1>		1	Rx Overrun IA
+	 * <0>		0	Select TC0 value for readback
+	 *
+	 *	0000 0000 0000 1000 = 0x000a
+	 */
+
+	/* Carry over the Exit Hunt and Idle Received bits */
+	/* in case they have been armed by usc_ArmEvents.   */
+
+	RegValue = usc_InReg( info, RICR ) & 0xc0;
+
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+		usc_OutReg( info, RICR, (u16)(0x030a | RegValue) );
+	else
+		usc_OutReg( info, RICR, (u16)(0x140a | RegValue) );
+
+	/* Unlatch all Rx status bits and clear Rx status IRQ Pending */
+
+	usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+	usc_ClearIrqPendingBits( info, RECEIVE_STATUS );
+
+	/* Transmit mode Register (TMR)
+	 *	
+	 * <15..13>	000	encoding
+	 * <12..11>	00	FCS = 16bit CRC CCITT (x15 + x12 + x5 + 1)
+	 * <10>		1	1 = Start CRC as all 1s (use for SDLC/HDLC)
+	 * <9>		0	1 = Tx CRC Enabled
+	 * <8>		0	1 = Append CRC to end of transmit frame
+	 * <7..6>	00	Transmit parity Even
+	 * <5>		0	Transmit parity Disabled
+	 * <4..2>	000	Tx Char Length = 8 bits
+	 * <1..0>	00	Disable Transmitter
+	 *
+	 * 	0000 0100 0000 0000 = 0x0400
+	 */
+
+	RegValue = 0x0400;
+
+	switch ( info->params.encoding ) {
+	case HDLC_ENCODING_NRZB:               RegValue |= BIT13; break;
+	case HDLC_ENCODING_NRZI_MARK:          RegValue |= BIT14; break;
+	case HDLC_ENCODING_NRZI_SPACE:         RegValue |= BIT14 + BIT13; break;
+	case HDLC_ENCODING_BIPHASE_MARK:       RegValue |= BIT15; break;
+	case HDLC_ENCODING_BIPHASE_SPACE:      RegValue |= BIT15 + BIT13; break;
+	case HDLC_ENCODING_BIPHASE_LEVEL:      RegValue |= BIT15 + BIT14; break;
+	case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: RegValue |= BIT15 + BIT14 + BIT13; break;
+	}
+
+	if ( (info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_16_CCITT )
+		RegValue |= BIT9 + BIT8;
+	else if ( (info->params.crc_type & HDLC_CRC_MASK) == HDLC_CRC_32_CCITT )
+		RegValue |= ( BIT12 | BIT10 | BIT9 | BIT8);
+
+	usc_OutReg( info, TMR, RegValue );
+
+	usc_set_txidle( info );
+
+
+	usc_TCmd( info, TCmd_SelectTicrdma_level );
+
+	/* Transmit Interrupt Control Register (TICR)
+	 *
+	 * <15..8>	?	Transmit FIFO DMA Level
+	 * <7>		0	Present IA (Interrupt Arm)
+	 * <6>		0	Idle Sent IA
+	 * <5>		1	Abort Sent IA
+	 * <4>		1	EOF/EOM Sent IA
+	 * <3>		0	CRC Sent IA
+	 * <2>		1	1 = Wait for SW Trigger to Start Frame
+	 * <1>		1	Tx Underrun IA
+	 * <0>		0	TC0 constant on read back
+	 *
+	 *	0000 0000 0011 0110 = 0x0036
+	 */
+
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+		usc_OutReg( info, TICR, 0x0736 );
+	else								
+		usc_OutReg( info, TICR, 0x1436 );
+
+	usc_UnlatchTxstatusBits( info, TXSTATUS_ALL );
+	usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
+
+	/*
+	** Transmit Command/Status Register (TCSR)
+	**
+	** <15..12>	0000	TCmd
+	** <11> 	0/1	UnderWait
+	** <10..08>	000	TxIdle
+	** <7>		x	PreSent
+	** <6>         	x	IdleSent
+	** <5>         	x	AbortSent
+	** <4>         	x	EOF/EOM Sent
+	** <3>         	x	CRC Sent
+	** <2>         	x	All Sent
+	** <1>         	x	TxUnder
+	** <0>         	x	TxEmpty
+	** 
+	** 0000 0000 0000 0000 = 0x0000
+	*/
+	info->tcsr_value = 0;
+
+	if ( !PreSL1660 )
+		info->tcsr_value |= TCSR_UNDERWAIT;
+		
+	usc_OutReg( info, TCSR, info->tcsr_value );
+
+	/* Clock mode Control Register (CMCR)
+	 *
+	 * <15..14>	00	counter 1 Source = Disabled
+	 * <13..12> 	00	counter 0 Source = Disabled
+	 * <11..10> 	11	BRG1 Input is TxC Pin
+	 * <9..8>	11	BRG0 Input is TxC Pin
+	 * <7..6>	01	DPLL Input is BRG1 Output
+	 * <5..3>	XXX	TxCLK comes from Port 0
+	 * <2..0>   	XXX	RxCLK comes from Port 1
+	 *
+	 *	0000 1111 0111 0111 = 0x0f77
+	 */
+
+	RegValue = 0x0f40;
+
+	if ( info->params.flags & HDLC_FLAG_RXC_DPLL )
+		RegValue |= 0x0003;	/* RxCLK from DPLL */
+	else if ( info->params.flags & HDLC_FLAG_RXC_BRG )
+		RegValue |= 0x0004;	/* RxCLK from BRG0 */
+ 	else if ( info->params.flags & HDLC_FLAG_RXC_TXCPIN)
+ 		RegValue |= 0x0006;	/* RxCLK from TXC Input */
+	else
+		RegValue |= 0x0007;	/* RxCLK from Port1 */
+
+	if ( info->params.flags & HDLC_FLAG_TXC_DPLL )
+		RegValue |= 0x0018;	/* TxCLK from DPLL */
+	else if ( info->params.flags & HDLC_FLAG_TXC_BRG )
+		RegValue |= 0x0020;	/* TxCLK from BRG0 */
+ 	else if ( info->params.flags & HDLC_FLAG_TXC_RXCPIN)
+ 		RegValue |= 0x0038;	/* RxCLK from TXC Input */
+	else
+		RegValue |= 0x0030;	/* TxCLK from Port0 */
+
+	usc_OutReg( info, CMCR, RegValue );
+
+
+	/* Hardware Configuration Register (HCR)
+	 *
+	 * <15..14>	00	CTR0 Divisor:00=32,01=16,10=8,11=4
+	 * <13>		0	CTR1DSel:0=CTR0Div determines CTR0Div
+	 * <12>		0	CVOK:0=report code violation in biphase
+	 * <11..10>	00	DPLL Divisor:00=32,01=16,10=8,11=4
+	 * <9..8>	XX	DPLL mode:00=disable,01=NRZ,10=Biphase,11=Biphase Level
+	 * <7..6>	00	reserved
+	 * <5>		0	BRG1 mode:0=continuous,1=single cycle
+	 * <4>		X	BRG1 Enable
+	 * <3..2>	00	reserved
+	 * <1>		0	BRG0 mode:0=continuous,1=single cycle
+	 * <0>		0	BRG0 Enable
+	 */
+
+	RegValue = 0x0000;
+
+	if ( info->params.flags & (HDLC_FLAG_RXC_DPLL + HDLC_FLAG_TXC_DPLL) ) {
+		u32 XtalSpeed;
+		u32 DpllDivisor;
+		u16 Tc;
+
+		/*  DPLL is enabled. Use BRG1 to provide continuous reference clock  */
+		/*  for DPLL. DPLL mode in HCR is dependent on the encoding used. */
+
+		if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+			XtalSpeed = 11059200;
+		else
+			XtalSpeed = 14745600;
+
+		if ( info->params.flags & HDLC_FLAG_DPLL_DIV16 ) {
+			DpllDivisor = 16;
+			RegValue |= BIT10;
+		}
+		else if ( info->params.flags & HDLC_FLAG_DPLL_DIV8 ) {
+			DpllDivisor = 8;
+			RegValue |= BIT11;
+		}
+		else
+			DpllDivisor = 32;
+
+		/*  Tc = (Xtal/Speed) - 1 */
+		/*  If twice the remainder of (Xtal/Speed) is greater than Speed */
+		/*  then rounding up gives a more precise time constant. Instead */
+		/*  of rounding up and then subtracting 1 we just don't subtract */
+		/*  the one in this case. */
+
+ 		/*--------------------------------------------------
+ 		 * ejz: for DPLL mode, application should use the
+ 		 * same clock speed as the partner system, even 
+ 		 * though clocking is derived from the input RxData.
+ 		 * In case the user uses a 0 for the clock speed,
+ 		 * default to 0xffffffff and don't try to divide by
+ 		 * zero
+ 		 *--------------------------------------------------*/
+ 		if ( info->params.clock_speed )
+ 		{
+			Tc = (u16)((XtalSpeed/DpllDivisor)/info->params.clock_speed);
+			if ( !((((XtalSpeed/DpllDivisor) % info->params.clock_speed) * 2)
+			       / info->params.clock_speed) )
+				Tc--;
+ 		}
+ 		else
+ 			Tc = -1;
+ 				  
+
+		/* Write 16-bit Time Constant for BRG1 */
+		usc_OutReg( info, TC1R, Tc );
+
+		RegValue |= BIT4;		/* enable BRG1 */
+
+		switch ( info->params.encoding ) {
+		case HDLC_ENCODING_NRZ:
+		case HDLC_ENCODING_NRZB:
+		case HDLC_ENCODING_NRZI_MARK:
+		case HDLC_ENCODING_NRZI_SPACE: RegValue |= BIT8; break;
+		case HDLC_ENCODING_BIPHASE_MARK:
+		case HDLC_ENCODING_BIPHASE_SPACE: RegValue |= BIT9; break;
+		case HDLC_ENCODING_BIPHASE_LEVEL:
+		case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: RegValue |= BIT9 + BIT8; break;
+		}
+	}
+
+	usc_OutReg( info, HCR, RegValue );
+
+
+	/* Channel Control/status Register (CCSR)
+	 *
+	 * <15>		X	RCC FIFO Overflow status (RO)
+	 * <14>		X	RCC FIFO Not Empty status (RO)
+	 * <13>		0	1 = Clear RCC FIFO (WO)
+	 * <12>		X	DPLL Sync (RW)
+	 * <11>		X	DPLL 2 Missed Clocks status (RO)
+	 * <10>		X	DPLL 1 Missed Clock status (RO)
+	 * <9..8>	00	DPLL Resync on rising and falling edges (RW)
+	 * <7>		X	SDLC Loop On status (RO)
+	 * <6>		X	SDLC Loop Send status (RO)
+	 * <5>		1	Bypass counters for TxClk and RxClk (RW)
+	 * <4..2>   	000	Last Char of SDLC frame has 8 bits (RW)
+	 * <1..0>   	00	reserved
+	 *
+	 *	0000 0000 0010 0000 = 0x0020
+	 */
+
+	usc_OutReg( info, CCSR, 0x1020 );
+
+
+	if ( info->params.flags & HDLC_FLAG_AUTO_CTS ) {
+		usc_OutReg( info, SICR,
+			    (u16)(usc_InReg(info,SICR) | SICR_CTS_INACTIVE) );
+	}
+	
+
+	/* enable Master Interrupt Enable bit (MIE) */
+	usc_EnableMasterIrqBit( info );
+
+	usc_ClearIrqPendingBits( info, RECEIVE_STATUS + RECEIVE_DATA +
+				TRANSMIT_STATUS + TRANSMIT_DATA + MISC);
+
+	/* arm RCC underflow interrupt */
+	usc_OutReg(info, SICR, (u16)(usc_InReg(info,SICR) | BIT3));
+	usc_EnableInterrupts(info, MISC);
+
+	info->mbre_bit = 0;
+	outw( 0, info->io_base ); 			/* clear Master Bus Enable (DCAR) */
+	usc_DmaCmd( info, DmaCmd_ResetAllChannels );	/* disable both DMA channels */
+	info->mbre_bit = BIT8;
+	outw( BIT8, info->io_base );			/* set Master Bus Enable (DCAR) */
+
+	if (info->bus_type == MGSL_BUS_TYPE_ISA) {
+		/* Enable DMAEN (Port 7, Bit 14) */
+		/* This connects the DMA request signal to the ISA bus */
+		usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT15) & ~BIT14));
+	}
+
+	/* DMA Control Register (DCR)
+	 *
+	 * <15..14>	10	Priority mode = Alternating Tx/Rx
+	 *		01	Rx has priority
+	 *		00	Tx has priority
+	 *
+	 * <13>		1	Enable Priority Preempt per DCR<15..14>
+	 *			(WARNING DCR<11..10> must be 00 when this is 1)
+	 *		0	Choose activate channel per DCR<11..10>
+	 *
+	 * <12>		0	Little Endian for Array/List
+	 * <11..10>	00	Both Channels can use each bus grant
+	 * <9..6>	0000	reserved
+	 * <5>		0	7 CLK - Minimum Bus Re-request Interval
+	 * <4>		0	1 = drive D/C and S/D pins
+	 * <3>		1	1 = Add one wait state to all DMA cycles.
+	 * <2>		0	1 = Strobe /UAS on every transfer.
+	 * <1..0>	11	Addr incrementing only affects LS24 bits
+	 *
+	 *	0110 0000 0000 1011 = 0x600b
+	 */
+
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+		/* PCI adapter does not need DMA wait state */
+		usc_OutDmaReg( info, DCR, 0xa00b );
+	}
+	else
+		usc_OutDmaReg( info, DCR, 0x800b );
+
+
+	/* Receive DMA mode Register (RDMR)
+	 *
+	 * <15..14>	11	DMA mode = Linked List Buffer mode
+	 * <13>		1	RSBinA/L = store Rx status Block in Arrary/List entry
+	 * <12>		1	Clear count of List Entry after fetching
+	 * <11..10>	00	Address mode = Increment
+	 * <9>		1	Terminate Buffer on RxBound
+	 * <8>		0	Bus Width = 16bits
+	 * <7..0>	?	status Bits (write as 0s)
+	 *
+	 * 1111 0010 0000 0000 = 0xf200
+	 */
+
+	usc_OutDmaReg( info, RDMR, 0xf200 );
+
+
+	/* Transmit DMA mode Register (TDMR)
+	 *
+	 * <15..14>	11	DMA mode = Linked List Buffer mode
+	 * <13>		1	TCBinA/L = fetch Tx Control Block from List entry
+	 * <12>		1	Clear count of List Entry after fetching
+	 * <11..10>	00	Address mode = Increment
+	 * <9>		1	Terminate Buffer on end of frame
+	 * <8>		0	Bus Width = 16bits
+	 * <7..0>	?	status Bits (Read Only so write as 0)
+	 *
+	 *	1111 0010 0000 0000 = 0xf200
+	 */
+
+	usc_OutDmaReg( info, TDMR, 0xf200 );
+
+
+	/* DMA Interrupt Control Register (DICR)
+	 *
+	 * <15>		1	DMA Interrupt Enable
+	 * <14>		0	1 = Disable IEO from USC
+	 * <13>		0	1 = Don't provide vector during IntAck
+	 * <12>		1	1 = Include status in Vector
+	 * <10..2>	0	reserved, Must be 0s
+	 * <1>		0	1 = Rx DMA Interrupt Enabled
+	 * <0>		0	1 = Tx DMA Interrupt Enabled
+	 *
+	 *	1001 0000 0000 0000 = 0x9000
+	 */
+
+	usc_OutDmaReg( info, DICR, 0x9000 );
+
+	usc_InDmaReg( info, RDMR );		/* clear pending receive DMA IRQ bits */
+	usc_InDmaReg( info, TDMR );		/* clear pending transmit DMA IRQ bits */
+	usc_OutDmaReg( info, CDIR, 0x0303 );	/* clear IUS and Pending for Tx and Rx */
+
+	/* Channel Control Register (CCR)
+	 *
+	 * <15..14>	10	Use 32-bit Tx Control Blocks (TCBs)
+	 * <13>		0	Trigger Tx on SW Command Disabled
+	 * <12>		0	Flag Preamble Disabled
+	 * <11..10>	00	Preamble Length
+	 * <9..8>	00	Preamble Pattern
+	 * <7..6>	10	Use 32-bit Rx status Blocks (RSBs)
+	 * <5>		0	Trigger Rx on SW Command Disabled
+	 * <4..0>	0	reserved
+	 *
+	 *	1000 0000 1000 0000 = 0x8080
+	 */
+
+	RegValue = 0x8080;
+
+	switch ( info->params.preamble_length ) {
+	case HDLC_PREAMBLE_LENGTH_16BITS: RegValue |= BIT10; break;
+	case HDLC_PREAMBLE_LENGTH_32BITS: RegValue |= BIT11; break;
+	case HDLC_PREAMBLE_LENGTH_64BITS: RegValue |= BIT11 + BIT10; break;
+	}
+
+	switch ( info->params.preamble ) {
+	case HDLC_PREAMBLE_PATTERN_FLAGS: RegValue |= BIT8 + BIT12; break;
+	case HDLC_PREAMBLE_PATTERN_ONES:  RegValue |= BIT8; break;
+	case HDLC_PREAMBLE_PATTERN_10:    RegValue |= BIT9; break;
+	case HDLC_PREAMBLE_PATTERN_01:    RegValue |= BIT9 + BIT8; break;
+	}
+
+	usc_OutReg( info, CCR, RegValue );
+
+
+	/*
+	 * Burst/Dwell Control Register
+	 *
+	 * <15..8>	0x20	Maximum number of transfers per bus grant
+	 * <7..0>	0x00	Maximum number of clock cycles per bus grant
+	 */
+
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+		/* don't limit bus occupancy on PCI adapter */
+		usc_OutDmaReg( info, BDCR, 0x0000 );
+	}
+	else
+		usc_OutDmaReg( info, BDCR, 0x2000 );
+
+	usc_stop_transmitter(info);
+	usc_stop_receiver(info);
+	
+}	/* end of usc_set_sdlc_mode() */
+
+/* usc_enable_loopback()
+ *
+ * Set the 16C32 for internal loopback mode.
+ * The TxCLK and RxCLK signals are generated from the BRG0 and
+ * the TxD is looped back to the RxD internally.
+ *
+ * Arguments:		info	pointer to device instance data
+ *			enable	1 = enable loopback, 0 = disable
+ * Return Value:	None
+ */
+static void usc_enable_loopback(struct mgsl_struct *info, int enable)
+{
+	if (enable) {
+		/* blank external TXD output */
+		usc_OutReg(info,IOCR,usc_InReg(info,IOCR) | (BIT7+BIT6));
+	
+		/* Clock mode Control Register (CMCR)
+		 *
+		 * <15..14>	00	counter 1 Disabled
+		 * <13..12> 	00	counter 0 Disabled
+		 * <11..10> 	11	BRG1 Input is TxC Pin
+		 * <9..8>	11	BRG0 Input is TxC Pin
+		 * <7..6>	01	DPLL Input is BRG1 Output
+		 * <5..3>	100	TxCLK comes from BRG0
+		 * <2..0>   	100	RxCLK comes from BRG0
+		 *
+		 * 0000 1111 0110 0100 = 0x0f64
+		 */
+
+		usc_OutReg( info, CMCR, 0x0f64 );
+
+		/* Write 16-bit Time Constant for BRG0 */
+		/* use clock speed if available, otherwise use 8 for diagnostics */
+		if (info->params.clock_speed) {
+			if (info->bus_type == MGSL_BUS_TYPE_PCI)
+				usc_OutReg(info, TC0R, (u16)((11059200/info->params.clock_speed)-1));
+			else
+				usc_OutReg(info, TC0R, (u16)((14745600/info->params.clock_speed)-1));
+		} else
+			usc_OutReg(info, TC0R, (u16)8);
+
+		/* Hardware Configuration Register (HCR) Clear Bit 1, BRG0
+		   mode = Continuous Set Bit 0 to enable BRG0.  */
+		usc_OutReg( info, HCR, (u16)((usc_InReg( info, HCR ) & ~BIT1) | BIT0) );
+
+		/* Input/Output Control Reg, <2..0> = 100, Drive RxC pin with BRG0 */
+		usc_OutReg(info, IOCR, (u16)((usc_InReg(info, IOCR) & 0xfff8) | 0x0004));
+
+		/* set Internal Data loopback mode */
+		info->loopback_bits = 0x300;
+		outw( 0x0300, info->io_base + CCAR );
+	} else {
+		/* enable external TXD output */
+		usc_OutReg(info,IOCR,usc_InReg(info,IOCR) & ~(BIT7+BIT6));
+	
+		/* clear Internal Data loopback mode */
+		info->loopback_bits = 0;
+		outw( 0,info->io_base + CCAR );
+	}
+	
+}	/* end of usc_enable_loopback() */
+
+/* usc_enable_aux_clock()
+ *
+ * Enabled the AUX clock output at the specified frequency.
+ *
+ * Arguments:
+ *
+ *	info		pointer to device extension
+ *	data_rate	data rate of clock in bits per second
+ *			A data rate of 0 disables the AUX clock.
+ *
+ * Return Value:	None
+ */
+static void usc_enable_aux_clock( struct mgsl_struct *info, u32 data_rate )
+{
+	u32 XtalSpeed;
+	u16 Tc;
+
+	if ( data_rate ) {
+		if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+			XtalSpeed = 11059200;
+		else
+			XtalSpeed = 14745600;
+
+
+		/* Tc = (Xtal/Speed) - 1 */
+		/* If twice the remainder of (Xtal/Speed) is greater than Speed */
+		/* then rounding up gives a more precise time constant. Instead */
+		/* of rounding up and then subtracting 1 we just don't subtract */
+		/* the one in this case. */
+
+
+		Tc = (u16)(XtalSpeed/data_rate);
+		if ( !(((XtalSpeed % data_rate) * 2) / data_rate) )
+			Tc--;
+
+		/* Write 16-bit Time Constant for BRG0 */
+		usc_OutReg( info, TC0R, Tc );
+
+		/*
+		 * Hardware Configuration Register (HCR)
+		 * Clear Bit 1, BRG0 mode = Continuous
+		 * Set Bit 0 to enable BRG0.
+		 */
+
+		usc_OutReg( info, HCR, (u16)((usc_InReg( info, HCR ) & ~BIT1) | BIT0) );
+
+		/* Input/Output Control Reg, <2..0> = 100, Drive RxC pin with BRG0 */
+		usc_OutReg( info, IOCR, (u16)((usc_InReg(info, IOCR) & 0xfff8) | 0x0004) );
+	} else {
+		/* data rate == 0 so turn off BRG0 */
+		usc_OutReg( info, HCR, (u16)(usc_InReg( info, HCR ) & ~BIT0) );
+	}
+
+}	/* end of usc_enable_aux_clock() */
+
+/*
+ *
+ * usc_process_rxoverrun_sync()
+ *
+ *		This function processes a receive overrun by resetting the
+ *		receive DMA buffers and issuing a Purge Rx FIFO command
+ *		to allow the receiver to continue receiving.
+ *
+ * Arguments:
+ *
+ *	info		pointer to device extension
+ *
+ * Return Value: None
+ */
+static void usc_process_rxoverrun_sync( struct mgsl_struct *info )
+{
+	int start_index;
+	int end_index;
+	int frame_start_index;
+	int start_of_frame_found = FALSE;
+	int end_of_frame_found = FALSE;
+	int reprogram_dma = FALSE;
+
+	DMABUFFERENTRY *buffer_list = info->rx_buffer_list;
+	u32 phys_addr;
+
+	usc_DmaCmd( info, DmaCmd_PauseRxChannel );
+	usc_RCmd( info, RCmd_EnterHuntmode );
+	usc_RTCmd( info, RTCmd_PurgeRxFifo );
+
+	/* CurrentRxBuffer points to the 1st buffer of the next */
+	/* possibly available receive frame. */
+	
+	frame_start_index = start_index = end_index = info->current_rx_buffer;
+
+	/* Search for an unfinished string of buffers. This means */
+	/* that a receive frame started (at least one buffer with */
+	/* count set to zero) but there is no terminiting buffer */
+	/* (status set to non-zero). */
+
+	while( !buffer_list[end_index].count )
+	{
+		/* Count field has been reset to zero by 16C32. */
+		/* This buffer is currently in use. */
+
+		if ( !start_of_frame_found )
+		{
+			start_of_frame_found = TRUE;
+			frame_start_index = end_index;
+			end_of_frame_found = FALSE;
+		}
+
+		if ( buffer_list[end_index].status )
+		{
+			/* Status field has been set by 16C32. */
+			/* This is the last buffer of a received frame. */
+
+			/* We want to leave the buffers for this frame intact. */
+			/* Move on to next possible frame. */
+
+			start_of_frame_found = FALSE;
+			end_of_frame_found = TRUE;
+		}
+
+  		/* advance to next buffer entry in linked list */
+  		end_index++;
+  		if ( end_index == info->rx_buffer_count )
+  			end_index = 0;
+
+		if ( start_index == end_index )
+		{
+			/* The entire list has been searched with all Counts == 0 and */
+			/* all Status == 0. The receive buffers are */
+			/* completely screwed, reset all receive buffers! */
+			mgsl_reset_rx_dma_buffers( info );
+			frame_start_index = 0;
+			start_of_frame_found = FALSE;
+			reprogram_dma = TRUE;
+			break;
+		}
+	}
+
+	if ( start_of_frame_found && !end_of_frame_found )
+	{
+		/* There is an unfinished string of receive DMA buffers */
+		/* as a result of the receiver overrun. */
+
+		/* Reset the buffers for the unfinished frame */
+		/* and reprogram the receive DMA controller to start */
+		/* at the 1st buffer of unfinished frame. */
+
+		start_index = frame_start_index;
+
+		do
+		{
+			*((unsigned long *)&(info->rx_buffer_list[start_index++].count)) = DMABUFFERSIZE;
+
+  			/* Adjust index for wrap around. */
+  			if ( start_index == info->rx_buffer_count )
+  				start_index = 0;
+
+		} while( start_index != end_index );
+
+		reprogram_dma = TRUE;
+	}
+
+	if ( reprogram_dma )
+	{
+		usc_UnlatchRxstatusBits(info,RXSTATUS_ALL);
+		usc_ClearIrqPendingBits(info, RECEIVE_DATA|RECEIVE_STATUS);
+		usc_UnlatchRxstatusBits(info, RECEIVE_DATA|RECEIVE_STATUS);
+		
+		usc_EnableReceiver(info,DISABLE_UNCONDITIONAL);
+		
+		/* This empties the receive FIFO and loads the RCC with RCLR */
+		usc_OutReg( info, CCSR, (u16)(usc_InReg(info,CCSR) | BIT13) );
+
+		/* program 16C32 with physical address of 1st DMA buffer entry */
+		phys_addr = info->rx_buffer_list[frame_start_index].phys_entry;
+		usc_OutDmaReg( info, NRARL, (u16)phys_addr );
+		usc_OutDmaReg( info, NRARU, (u16)(phys_addr >> 16) );
+
+		usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+		usc_ClearIrqPendingBits( info, RECEIVE_DATA + RECEIVE_STATUS );
+		usc_EnableInterrupts( info, RECEIVE_STATUS );
+
+		/* 1. Arm End of Buffer (EOB) Receive DMA Interrupt (BIT2 of RDIAR) */
+		/* 2. Enable Receive DMA Interrupts (BIT1 of DICR) */
+
+		usc_OutDmaReg( info, RDIAR, BIT3 + BIT2 );
+		usc_OutDmaReg( info, DICR, (u16)(usc_InDmaReg(info,DICR) | BIT1) );
+		usc_DmaCmd( info, DmaCmd_InitRxChannel );
+		if ( info->params.flags & HDLC_FLAG_AUTO_DCD )
+			usc_EnableReceiver(info,ENABLE_AUTO_DCD);
+		else
+			usc_EnableReceiver(info,ENABLE_UNCONDITIONAL);
+	}
+	else
+	{
+		/* This empties the receive FIFO and loads the RCC with RCLR */
+		usc_OutReg( info, CCSR, (u16)(usc_InReg(info,CCSR) | BIT13) );
+		usc_RTCmd( info, RTCmd_PurgeRxFifo );
+	}
+
+}	/* end of usc_process_rxoverrun_sync() */
+
+/* usc_stop_receiver()
+ *
+ *	Disable USC receiver
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_stop_receiver( struct mgsl_struct *info )
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):usc_stop_receiver(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	/* Disable receive DMA channel. */
+	/* This also disables receive DMA channel interrupts */
+	usc_DmaCmd( info, DmaCmd_ResetRxChannel );
+
+	usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+	usc_ClearIrqPendingBits( info, RECEIVE_DATA + RECEIVE_STATUS );
+	usc_DisableInterrupts( info, RECEIVE_DATA + RECEIVE_STATUS );
+
+	usc_EnableReceiver(info,DISABLE_UNCONDITIONAL);
+
+	/* This empties the receive FIFO and loads the RCC with RCLR */
+	usc_OutReg( info, CCSR, (u16)(usc_InReg(info,CCSR) | BIT13) );
+	usc_RTCmd( info, RTCmd_PurgeRxFifo );
+
+	info->rx_enabled = 0;
+	info->rx_overflow = 0;
+	info->rx_rcc_underrun = 0;
+	
+}	/* end of stop_receiver() */
+
+/* usc_start_receiver()
+ *
+ *	Enable the USC receiver 
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_start_receiver( struct mgsl_struct *info )
+{
+	u32 phys_addr;
+	
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):usc_start_receiver(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	mgsl_reset_rx_dma_buffers( info );
+	usc_stop_receiver( info );
+
+	usc_OutReg( info, CCSR, (u16)(usc_InReg(info,CCSR) | BIT13) );
+	usc_RTCmd( info, RTCmd_PurgeRxFifo );
+
+	if ( info->params.mode == MGSL_MODE_HDLC ||
+		info->params.mode == MGSL_MODE_RAW ) {
+		/* DMA mode Transfers */
+		/* Program the DMA controller. */
+		/* Enable the DMA controller end of buffer interrupt. */
+
+		/* program 16C32 with physical address of 1st DMA buffer entry */
+		phys_addr = info->rx_buffer_list[0].phys_entry;
+		usc_OutDmaReg( info, NRARL, (u16)phys_addr );
+		usc_OutDmaReg( info, NRARU, (u16)(phys_addr >> 16) );
+
+		usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+		usc_ClearIrqPendingBits( info, RECEIVE_DATA + RECEIVE_STATUS );
+		usc_EnableInterrupts( info, RECEIVE_STATUS );
+
+		/* 1. Arm End of Buffer (EOB) Receive DMA Interrupt (BIT2 of RDIAR) */
+		/* 2. Enable Receive DMA Interrupts (BIT1 of DICR) */
+
+		usc_OutDmaReg( info, RDIAR, BIT3 + BIT2 );
+		usc_OutDmaReg( info, DICR, (u16)(usc_InDmaReg(info,DICR) | BIT1) );
+		usc_DmaCmd( info, DmaCmd_InitRxChannel );
+		if ( info->params.flags & HDLC_FLAG_AUTO_DCD )
+			usc_EnableReceiver(info,ENABLE_AUTO_DCD);
+		else
+			usc_EnableReceiver(info,ENABLE_UNCONDITIONAL);
+	} else {
+		usc_UnlatchRxstatusBits(info, RXSTATUS_ALL);
+		usc_ClearIrqPendingBits(info, RECEIVE_DATA + RECEIVE_STATUS);
+		usc_EnableInterrupts(info, RECEIVE_DATA);
+
+		usc_RTCmd( info, RTCmd_PurgeRxFifo );
+		usc_RCmd( info, RCmd_EnterHuntmode );
+
+		usc_EnableReceiver(info,ENABLE_UNCONDITIONAL);
+	}
+
+	usc_OutReg( info, CCSR, 0x1020 );
+
+	info->rx_enabled = 1;
+
+}	/* end of usc_start_receiver() */
+
+/* usc_start_transmitter()
+ *
+ *	Enable the USC transmitter and send a transmit frame if
+ *	one is loaded in the DMA buffers.
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_start_transmitter( struct mgsl_struct *info )
+{
+	u32 phys_addr;
+	unsigned int FrameSize;
+
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):usc_start_transmitter(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	if ( info->xmit_cnt ) {
+
+		/* If auto RTS enabled and RTS is inactive, then assert */
+		/* RTS and set a flag indicating that the driver should */
+		/* negate RTS when the transmission completes. */
+
+		info->drop_rts_on_tx_done = 0;
+
+		if ( info->params.flags & HDLC_FLAG_AUTO_RTS ) {
+			usc_get_serial_signals( info );
+			if ( !(info->serial_signals & SerialSignal_RTS) ) {
+				info->serial_signals |= SerialSignal_RTS;
+				usc_set_serial_signals( info );
+				info->drop_rts_on_tx_done = 1;
+			}
+		}
+
+
+		if ( info->params.mode == MGSL_MODE_ASYNC ) {
+			if ( !info->tx_active ) {
+				usc_UnlatchTxstatusBits(info, TXSTATUS_ALL);
+				usc_ClearIrqPendingBits(info, TRANSMIT_STATUS + TRANSMIT_DATA);
+				usc_EnableInterrupts(info, TRANSMIT_DATA);
+				usc_load_txfifo(info);
+			}
+		} else {
+			/* Disable transmit DMA controller while programming. */
+			usc_DmaCmd( info, DmaCmd_ResetTxChannel );
+			
+			/* Transmit DMA buffer is loaded, so program USC */
+			/* to send the frame contained in the buffers.	 */
+
+			FrameSize = info->tx_buffer_list[info->start_tx_dma_buffer].rcc;
+
+			/* if operating in Raw sync mode, reset the rcc component
+			 * of the tx dma buffer entry, otherwise, the serial controller
+			 * will send a closing sync char after this count.
+			 */
+	    		if ( info->params.mode == MGSL_MODE_RAW )
+				info->tx_buffer_list[info->start_tx_dma_buffer].rcc = 0;
+
+			/* Program the Transmit Character Length Register (TCLR) */
+			/* and clear FIFO (TCC is loaded with TCLR on FIFO clear) */
+			usc_OutReg( info, TCLR, (u16)FrameSize );
+
+			usc_RTCmd( info, RTCmd_PurgeTxFifo );
+
+			/* Program the address of the 1st DMA Buffer Entry in linked list */
+			phys_addr = info->tx_buffer_list[info->start_tx_dma_buffer].phys_entry;
+			usc_OutDmaReg( info, NTARL, (u16)phys_addr );
+			usc_OutDmaReg( info, NTARU, (u16)(phys_addr >> 16) );
+
+			usc_UnlatchTxstatusBits( info, TXSTATUS_ALL );
+			usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
+			usc_EnableInterrupts( info, TRANSMIT_STATUS );
+
+			if ( info->params.mode == MGSL_MODE_RAW &&
+					info->num_tx_dma_buffers > 1 ) {
+			   /* When running external sync mode, attempt to 'stream' transmit  */
+			   /* by filling tx dma buffers as they become available. To do this */
+			   /* we need to enable Tx DMA EOB Status interrupts :               */
+			   /*                                                                */
+			   /* 1. Arm End of Buffer (EOB) Transmit DMA Interrupt (BIT2 of TDIAR) */
+			   /* 2. Enable Transmit DMA Interrupts (BIT0 of DICR) */
+
+			   usc_OutDmaReg( info, TDIAR, BIT2|BIT3 );
+			   usc_OutDmaReg( info, DICR, (u16)(usc_InDmaReg(info,DICR) | BIT0) );
+			}
+
+			/* Initialize Transmit DMA Channel */
+			usc_DmaCmd( info, DmaCmd_InitTxChannel );
+			
+			usc_TCmd( info, TCmd_SendFrame );
+			
+			info->tx_timer.expires = jiffies + msecs_to_jiffies(5000);
+			add_timer(&info->tx_timer);	
+		}
+		info->tx_active = 1;
+	}
+
+	if ( !info->tx_enabled ) {
+		info->tx_enabled = 1;
+		if ( info->params.flags & HDLC_FLAG_AUTO_CTS )
+			usc_EnableTransmitter(info,ENABLE_AUTO_CTS);
+		else
+			usc_EnableTransmitter(info,ENABLE_UNCONDITIONAL);
+	}
+
+}	/* end of usc_start_transmitter() */
+
+/* usc_stop_transmitter()
+ *
+ *	Stops the transmitter and DMA
+ *
+ * Arguments:		info	pointer to device isntance data
+ * Return Value:	None
+ */
+static void usc_stop_transmitter( struct mgsl_struct *info )
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):usc_stop_transmitter(%s)\n",
+			 __FILE__,__LINE__, info->device_name );
+			 
+	del_timer(&info->tx_timer);	
+			 
+	usc_UnlatchTxstatusBits( info, TXSTATUS_ALL );
+	usc_ClearIrqPendingBits( info, TRANSMIT_STATUS + TRANSMIT_DATA );
+	usc_DisableInterrupts( info, TRANSMIT_STATUS + TRANSMIT_DATA );
+
+	usc_EnableTransmitter(info,DISABLE_UNCONDITIONAL);
+	usc_DmaCmd( info, DmaCmd_ResetTxChannel );
+	usc_RTCmd( info, RTCmd_PurgeTxFifo );
+
+	info->tx_enabled = 0;
+	info->tx_active  = 0;
+
+}	/* end of usc_stop_transmitter() */
+
+/* usc_load_txfifo()
+ *
+ *	Fill the transmit FIFO until the FIFO is full or
+ *	there is no more data to load.
+ *
+ * Arguments:		info	pointer to device extension (instance data)
+ * Return Value:	None
+ */
+static void usc_load_txfifo( struct mgsl_struct *info )
+{
+	int Fifocount;
+	u8 TwoBytes[2];
+	
+	if ( !info->xmit_cnt && !info->x_char )
+		return; 
+		
+	/* Select transmit FIFO status readback in TICR */
+	usc_TCmd( info, TCmd_SelectTicrTxFifostatus );
+
+	/* load the Transmit FIFO until FIFOs full or all data sent */
+
+	while( (Fifocount = usc_InReg(info, TICR) >> 8) && info->xmit_cnt ) {
+		/* there is more space in the transmit FIFO and */
+		/* there is more data in transmit buffer */
+
+		if ( (info->xmit_cnt > 1) && (Fifocount > 1) && !info->x_char ) {
+ 			/* write a 16-bit word from transmit buffer to 16C32 */
+				
+			TwoBytes[0] = info->xmit_buf[info->xmit_tail++];
+			info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+			TwoBytes[1] = info->xmit_buf[info->xmit_tail++];
+			info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+			
+			outw( *((u16 *)TwoBytes), info->io_base + DATAREG);
+				
+			info->xmit_cnt -= 2;
+			info->icount.tx += 2;
+		} else {
+			/* only 1 byte left to transmit or 1 FIFO slot left */
+			
+			outw( (inw( info->io_base + CCAR) & 0x0780) | (TDR+LSBONLY),
+				info->io_base + CCAR );
+			
+			if (info->x_char) {
+				/* transmit pending high priority char */
+				outw( info->x_char,info->io_base + CCAR );
+				info->x_char = 0;
+			} else {
+				outw( info->xmit_buf[info->xmit_tail++],info->io_base + CCAR );
+				info->xmit_tail = info->xmit_tail & (SERIAL_XMIT_SIZE-1);
+				info->xmit_cnt--;
+			}
+			info->icount.tx++;
+		}
+	}
+
+}	/* end of usc_load_txfifo() */
+
+/* usc_reset()
+ *
+ *	Reset the adapter to a known state and prepare it for further use.
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_reset( struct mgsl_struct *info )
+{
+	if ( info->bus_type == MGSL_BUS_TYPE_PCI ) {
+		int i;
+		u32 readval;
+
+		/* Set BIT30 of Misc Control Register */
+		/* (Local Control Register 0x50) to force reset of USC. */
+
+		volatile u32 *MiscCtrl = (u32 *)(info->lcr_base + 0x50);
+		u32 *LCR0BRDR = (u32 *)(info->lcr_base + 0x28);
+
+		info->misc_ctrl_value |= BIT30;
+		*MiscCtrl = info->misc_ctrl_value;
+
+		/*
+		 * Force at least 170ns delay before clearing 
+		 * reset bit. Each read from LCR takes at least 
+		 * 30ns so 10 times for 300ns to be safe.
+		 */
+		for(i=0;i<10;i++)
+			readval = *MiscCtrl;
+
+		info->misc_ctrl_value &= ~BIT30;
+		*MiscCtrl = info->misc_ctrl_value;
+
+		*LCR0BRDR = BUS_DESCRIPTOR(
+			1,		// Write Strobe Hold (0-3)
+			2,		// Write Strobe Delay (0-3)
+			2,		// Read Strobe Delay  (0-3)
+			0,		// NWDD (Write data-data) (0-3)
+			4,		// NWAD (Write Addr-data) (0-31)
+			0,		// NXDA (Read/Write Data-Addr) (0-3)
+			0,		// NRDD (Read Data-Data) (0-3)
+			5		// NRAD (Read Addr-Data) (0-31)
+			);
+	} else {
+		/* do HW reset */
+		outb( 0,info->io_base + 8 );
+	}
+
+	info->mbre_bit = 0;
+	info->loopback_bits = 0;
+	info->usc_idle_mode = 0;
+
+	/*
+	 * Program the Bus Configuration Register (BCR)
+	 *
+	 * <15>		0	Don't use separate address
+	 * <14..6>	0	reserved
+	 * <5..4>	00	IAckmode = Default, don't care
+	 * <3>		1	Bus Request Totem Pole output
+	 * <2>		1	Use 16 Bit data bus
+	 * <1>		0	IRQ Totem Pole output
+	 * <0>		0	Don't Shift Right Addr
+	 *
+	 * 0000 0000 0000 1100 = 0x000c
+	 *
+	 * By writing to io_base + SDPIN the Wait/Ack pin is
+	 * programmed to work as a Wait pin.
+	 */
+	
+	outw( 0x000c,info->io_base + SDPIN );
+
+
+	outw( 0,info->io_base );
+	outw( 0,info->io_base + CCAR );
+
+	/* select little endian byte ordering */
+	usc_RTCmd( info, RTCmd_SelectLittleEndian );
+
+
+	/* Port Control Register (PCR)
+	 *
+	 * <15..14>	11	Port 7 is Output (~DMAEN, Bit 14 : 0 = Enabled)
+	 * <13..12>	11	Port 6 is Output (~INTEN, Bit 12 : 0 = Enabled)
+	 * <11..10> 	00	Port 5 is Input (No Connect, Don't Care)
+	 * <9..8> 	00	Port 4 is Input (No Connect, Don't Care)
+	 * <7..6>	11	Port 3 is Output (~RTS, Bit 6 : 0 = Enabled )
+	 * <5..4>	11	Port 2 is Output (~DTR, Bit 4 : 0 = Enabled )
+	 * <3..2>	01	Port 1 is Input (Dedicated RxC)
+	 * <1..0>	01	Port 0 is Input (Dedicated TxC)
+	 *
+	 *	1111 0000 1111 0101 = 0xf0f5
+	 */
+
+	usc_OutReg( info, PCR, 0xf0f5 );
+
+
+	/*
+	 * Input/Output Control Register
+	 *
+	 * <15..14>	00	CTS is active low input
+	 * <13..12>	00	DCD is active low input
+	 * <11..10>	00	TxREQ pin is input (DSR)
+	 * <9..8>	00	RxREQ pin is input (RI)
+	 * <7..6>	00	TxD is output (Transmit Data)
+	 * <5..3>	000	TxC Pin in Input (14.7456MHz Clock)
+	 * <2..0>	100	RxC is Output (drive with BRG0)
+	 *
+	 *	0000 0000 0000 0100 = 0x0004
+	 */
+
+	usc_OutReg( info, IOCR, 0x0004 );
+
+}	/* end of usc_reset() */
+
+/* usc_set_async_mode()
+ *
+ *	Program adapter for asynchronous communications.
+ *
+ * Arguments:		info		pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_set_async_mode( struct mgsl_struct *info )
+{
+	u16 RegValue;
+
+	/* disable interrupts while programming USC */
+	usc_DisableMasterIrqBit( info );
+
+	outw( 0, info->io_base ); 			/* clear Master Bus Enable (DCAR) */
+	usc_DmaCmd( info, DmaCmd_ResetAllChannels );	/* disable both DMA channels */
+
+	usc_loopback_frame( info );
+
+	/* Channel mode Register (CMR)
+	 *
+	 * <15..14>	00	Tx Sub modes, 00 = 1 Stop Bit
+	 * <13..12>	00	              00 = 16X Clock
+	 * <11..8>	0000	Transmitter mode = Asynchronous
+	 * <7..6>	00	reserved?
+	 * <5..4>	00	Rx Sub modes, 00 = 16X Clock
+	 * <3..0>	0000	Receiver mode = Asynchronous
+	 *
+	 * 0000 0000 0000 0000 = 0x0
+	 */
+
+	RegValue = 0;
+	if ( info->params.stop_bits != 1 )
+		RegValue |= BIT14;
+	usc_OutReg( info, CMR, RegValue );
+
+	
+	/* Receiver mode Register (RMR)
+	 *
+	 * <15..13>	000	encoding = None
+	 * <12..08>	00000	reserved (Sync Only)
+	 * <7..6>   	00	Even parity
+	 * <5>		0	parity disabled
+	 * <4..2>	000	Receive Char Length = 8 bits
+	 * <1..0>	00	Disable Receiver
+	 *
+	 * 0000 0000 0000 0000 = 0x0
+	 */
+
+	RegValue = 0;
+
+	if ( info->params.data_bits != 8 )
+		RegValue |= BIT4+BIT3+BIT2;
+
+	if ( info->params.parity != ASYNC_PARITY_NONE ) {
+		RegValue |= BIT5;
+		if ( info->params.parity != ASYNC_PARITY_ODD )
+			RegValue |= BIT6;
+	}
+
+	usc_OutReg( info, RMR, RegValue );
+
+
+	/* Set IRQ trigger level */
+
+	usc_RCmd( info, RCmd_SelectRicrIntLevel );
+
+	
+	/* Receive Interrupt Control Register (RICR)
+	 *
+	 * <15..8>	?		RxFIFO IRQ Request Level
+	 *
+	 * Note: For async mode the receive FIFO level must be set
+	 * to 0 to aviod the situation where the FIFO contains fewer bytes
+	 * than the trigger level and no more data is expected.
+	 *
+	 * <7>		0		Exited Hunt IA (Interrupt Arm)
+	 * <6>		0		Idle Received IA
+	 * <5>		0		Break/Abort IA
+	 * <4>		0		Rx Bound IA
+	 * <3>		0		Queued status reflects oldest byte in FIFO
+	 * <2>		0		Abort/PE IA
+	 * <1>		0		Rx Overrun IA
+	 * <0>		0		Select TC0 value for readback
+	 *
+	 * 0000 0000 0100 0000 = 0x0000 + (FIFOLEVEL in MSB)
+	 */
+	
+	usc_OutReg( info, RICR, 0x0000 );
+
+	usc_UnlatchRxstatusBits( info, RXSTATUS_ALL );
+	usc_ClearIrqPendingBits( info, RECEIVE_STATUS );
+
+	
+	/* Transmit mode Register (TMR)
+	 *
+	 * <15..13>	000	encoding = None
+	 * <12..08>	00000	reserved (Sync Only)
+	 * <7..6>	00	Transmit parity Even
+	 * <5>		0	Transmit parity Disabled
+	 * <4..2>	000	Tx Char Length = 8 bits
+	 * <1..0>	00	Disable Transmitter
+	 *
+	 * 0000 0000 0000 0000 = 0x0
+	 */
+
+	RegValue = 0;
+
+	if ( info->params.data_bits != 8 )
+		RegValue |= BIT4+BIT3+BIT2;
+
+	if ( info->params.parity != ASYNC_PARITY_NONE ) {
+		RegValue |= BIT5;
+		if ( info->params.parity != ASYNC_PARITY_ODD )
+			RegValue |= BIT6;
+	}
+
+	usc_OutReg( info, TMR, RegValue );
+
+	usc_set_txidle( info );
+
+
+	/* Set IRQ trigger level */
+
+	usc_TCmd( info, TCmd_SelectTicrIntLevel );
+
+	
+	/* Transmit Interrupt Control Register (TICR)
+	 *
+	 * <15..8>	?	Transmit FIFO IRQ Level
+	 * <7>		0	Present IA (Interrupt Arm)
+	 * <6>		1	Idle Sent IA
+	 * <5>		0	Abort Sent IA
+	 * <4>		0	EOF/EOM Sent IA
+	 * <3>		0	CRC Sent IA
+	 * <2>		0	1 = Wait for SW Trigger to Start Frame
+	 * <1>		0	Tx Underrun IA
+	 * <0>		0	TC0 constant on read back
+	 *
+	 *	0000 0000 0100 0000 = 0x0040
+	 */
+
+	usc_OutReg( info, TICR, 0x1f40 );
+
+	usc_UnlatchTxstatusBits( info, TXSTATUS_ALL );
+	usc_ClearIrqPendingBits( info, TRANSMIT_STATUS );
+
+	usc_enable_async_clock( info, info->params.data_rate );
+
+	
+	/* Channel Control/status Register (CCSR)
+	 *
+	 * <15>		X	RCC FIFO Overflow status (RO)
+	 * <14>		X	RCC FIFO Not Empty status (RO)
+	 * <13>		0	1 = Clear RCC FIFO (WO)
+	 * <12>		X	DPLL in Sync status (RO)
+	 * <11>		X	DPLL 2 Missed Clocks status (RO)
+	 * <10>		X	DPLL 1 Missed Clock status (RO)
+	 * <9..8>	00	DPLL Resync on rising and falling edges (RW)
+	 * <7>		X	SDLC Loop On status (RO)
+	 * <6>		X	SDLC Loop Send status (RO)
+	 * <5>		1	Bypass counters for TxClk and RxClk (RW)
+	 * <4..2>   	000	Last Char of SDLC frame has 8 bits (RW)
+	 * <1..0>   	00	reserved
+	 *
+	 *	0000 0000 0010 0000 = 0x0020
+	 */
+	
+	usc_OutReg( info, CCSR, 0x0020 );
+
+	usc_DisableInterrupts( info, TRANSMIT_STATUS + TRANSMIT_DATA +
+			      RECEIVE_DATA + RECEIVE_STATUS );
+
+	usc_ClearIrqPendingBits( info, TRANSMIT_STATUS + TRANSMIT_DATA +
+				RECEIVE_DATA + RECEIVE_STATUS );
+
+	usc_EnableMasterIrqBit( info );
+
+	if (info->bus_type == MGSL_BUS_TYPE_ISA) {
+		/* Enable INTEN (Port 6, Bit12) */
+		/* This connects the IRQ request signal to the ISA bus */
+		usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT13) & ~BIT12));
+	}
+
+}	/* end of usc_set_async_mode() */
+
+/* usc_loopback_frame()
+ *
+ *	Loop back a small (2 byte) dummy SDLC frame.
+ *	Interrupts and DMA are NOT used. The purpose of this is to
+ *	clear any 'stale' status info left over from running in	async mode.
+ *
+ *	The 16C32 shows the strange behaviour of marking the 1st
+ *	received SDLC frame with a CRC error even when there is no
+ *	CRC error. To get around this a small dummy from of 2 bytes
+ *	is looped back when switching from async to sync mode.
+ *
+ * Arguments:		info		pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_loopback_frame( struct mgsl_struct *info )
+{
+	int i;
+	unsigned long oldmode = info->params.mode;
+
+	info->params.mode = MGSL_MODE_HDLC;
+	
+	usc_DisableMasterIrqBit( info );
+
+	usc_set_sdlc_mode( info );
+	usc_enable_loopback( info, 1 );
+
+	/* Write 16-bit Time Constant for BRG0 */
+	usc_OutReg( info, TC0R, 0 );
+	
+	/* Channel Control Register (CCR)
+	 *
+	 * <15..14>	00	Don't use 32-bit Tx Control Blocks (TCBs)
+	 * <13>		0	Trigger Tx on SW Command Disabled
+	 * <12>		0	Flag Preamble Disabled
+	 * <11..10>	00	Preamble Length = 8-Bits
+	 * <9..8>	01	Preamble Pattern = flags
+	 * <7..6>	10	Don't use 32-bit Rx status Blocks (RSBs)
+	 * <5>		0	Trigger Rx on SW Command Disabled
+	 * <4..0>	0	reserved
+	 *
+	 *	0000 0001 0000 0000 = 0x0100
+	 */
+
+	usc_OutReg( info, CCR, 0x0100 );
+
+	/* SETUP RECEIVER */
+	usc_RTCmd( info, RTCmd_PurgeRxFifo );
+	usc_EnableReceiver(info,ENABLE_UNCONDITIONAL);
+
+	/* SETUP TRANSMITTER */
+	/* Program the Transmit Character Length Register (TCLR) */
+	/* and clear FIFO (TCC is loaded with TCLR on FIFO clear) */
+	usc_OutReg( info, TCLR, 2 );
+	usc_RTCmd( info, RTCmd_PurgeTxFifo );
+
+	/* unlatch Tx status bits, and start transmit channel. */
+	usc_UnlatchTxstatusBits(info,TXSTATUS_ALL);
+	outw(0,info->io_base + DATAREG);
+
+	/* ENABLE TRANSMITTER */
+	usc_TCmd( info, TCmd_SendFrame );
+	usc_EnableTransmitter(info,ENABLE_UNCONDITIONAL);
+							
+	/* WAIT FOR RECEIVE COMPLETE */
+	for (i=0 ; i<1000 ; i++)
+		if (usc_InReg( info, RCSR ) & (BIT8 + BIT4 + BIT3 + BIT1))
+			break;
+
+	/* clear Internal Data loopback mode */
+	usc_enable_loopback(info, 0);
+
+	usc_EnableMasterIrqBit(info);
+
+	info->params.mode = oldmode;
+
+}	/* end of usc_loopback_frame() */
+
+/* usc_set_sync_mode()	Programs the USC for SDLC communications.
+ *
+ * Arguments:		info	pointer to adapter info structure
+ * Return Value:	None
+ */
+static void usc_set_sync_mode( struct mgsl_struct *info )
+{
+	usc_loopback_frame( info );
+	usc_set_sdlc_mode( info );
+
+	if (info->bus_type == MGSL_BUS_TYPE_ISA) {
+		/* Enable INTEN (Port 6, Bit12) */
+		/* This connects the IRQ request signal to the ISA bus */
+		usc_OutReg(info, PCR, (u16)((usc_InReg(info, PCR) | BIT13) & ~BIT12));
+	}
+
+	usc_enable_aux_clock(info, info->params.clock_speed);
+
+	if (info->params.loopback)
+		usc_enable_loopback(info,1);
+
+}	/* end of mgsl_set_sync_mode() */
+
+/* usc_set_txidle()	Set the HDLC idle mode for the transmitter.
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_set_txidle( struct mgsl_struct *info )
+{
+	u16 usc_idle_mode = IDLEMODE_FLAGS;
+
+	/* Map API idle mode to USC register bits */
+
+	switch( info->idle_mode ){
+	case HDLC_TXIDLE_FLAGS:			usc_idle_mode = IDLEMODE_FLAGS; break;
+	case HDLC_TXIDLE_ALT_ZEROS_ONES:	usc_idle_mode = IDLEMODE_ALT_ONE_ZERO; break;
+	case HDLC_TXIDLE_ZEROS:			usc_idle_mode = IDLEMODE_ZERO; break;
+	case HDLC_TXIDLE_ONES:			usc_idle_mode = IDLEMODE_ONE; break;
+	case HDLC_TXIDLE_ALT_MARK_SPACE:	usc_idle_mode = IDLEMODE_ALT_MARK_SPACE; break;
+	case HDLC_TXIDLE_SPACE:			usc_idle_mode = IDLEMODE_SPACE; break;
+	case HDLC_TXIDLE_MARK:			usc_idle_mode = IDLEMODE_MARK; break;
+	}
+
+	info->usc_idle_mode = usc_idle_mode;
+	//usc_OutReg(info, TCSR, usc_idle_mode);
+	info->tcsr_value &= ~IDLEMODE_MASK;	/* clear idle mode bits */
+	info->tcsr_value += usc_idle_mode;
+	usc_OutReg(info, TCSR, info->tcsr_value);
+
+	/*
+	 * if SyncLink WAN adapter is running in external sync mode, the
+	 * transmitter has been set to Monosync in order to try to mimic
+	 * a true raw outbound bit stream. Monosync still sends an open/close
+	 * sync char at the start/end of a frame. Try to match those sync
+	 * patterns to the idle mode set here
+	 */
+	if ( info->params.mode == MGSL_MODE_RAW ) {
+		unsigned char syncpat = 0;
+		switch( info->idle_mode ) {
+		case HDLC_TXIDLE_FLAGS:
+			syncpat = 0x7e;
+			break;
+		case HDLC_TXIDLE_ALT_ZEROS_ONES:
+			syncpat = 0x55;
+			break;
+		case HDLC_TXIDLE_ZEROS:
+		case HDLC_TXIDLE_SPACE:
+			syncpat = 0x00;
+			break;
+		case HDLC_TXIDLE_ONES:
+		case HDLC_TXIDLE_MARK:
+			syncpat = 0xff;
+			break;
+		case HDLC_TXIDLE_ALT_MARK_SPACE:
+			syncpat = 0xaa;
+			break;
+		}
+
+		usc_SetTransmitSyncChars(info,syncpat,syncpat);
+	}
+
+}	/* end of usc_set_txidle() */
+
+/* usc_get_serial_signals()
+ *
+ *	Query the adapter for the state of the V24 status (input) signals.
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_get_serial_signals( struct mgsl_struct *info )
+{
+	u16 status;
+
+	/* clear all serial signals except DTR and RTS */
+	info->serial_signals &= SerialSignal_DTR + SerialSignal_RTS;
+
+	/* Read the Misc Interrupt status Register (MISR) to get */
+	/* the V24 status signals. */
+
+	status = usc_InReg( info, MISR );
+
+	/* set serial signal bits to reflect MISR */
+
+	if ( status & MISCSTATUS_CTS )
+		info->serial_signals |= SerialSignal_CTS;
+
+	if ( status & MISCSTATUS_DCD )
+		info->serial_signals |= SerialSignal_DCD;
+
+	if ( status & MISCSTATUS_RI )
+		info->serial_signals |= SerialSignal_RI;
+
+	if ( status & MISCSTATUS_DSR )
+		info->serial_signals |= SerialSignal_DSR;
+
+}	/* end of usc_get_serial_signals() */
+
+/* usc_set_serial_signals()
+ *
+ *	Set the state of DTR and RTS based on contents of
+ *	serial_signals member of device extension.
+ *	
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void usc_set_serial_signals( struct mgsl_struct *info )
+{
+	u16 Control;
+	unsigned char V24Out = info->serial_signals;
+
+	/* get the current value of the Port Control Register (PCR) */
+
+	Control = usc_InReg( info, PCR );
+
+	if ( V24Out & SerialSignal_RTS )
+		Control &= ~(BIT6);
+	else
+		Control |= BIT6;
+
+	if ( V24Out & SerialSignal_DTR )
+		Control &= ~(BIT4);
+	else
+		Control |= BIT4;
+
+	usc_OutReg( info, PCR, Control );
+
+}	/* end of usc_set_serial_signals() */
+
+/* usc_enable_async_clock()
+ *
+ *	Enable the async clock at the specified frequency.
+ *
+ * Arguments:		info		pointer to device instance data
+ *			data_rate	data rate of clock in bps
+ *					0 disables the AUX clock.
+ * Return Value:	None
+ */
+static void usc_enable_async_clock( struct mgsl_struct *info, u32 data_rate )
+{
+	if ( data_rate )	{
+		/*
+		 * Clock mode Control Register (CMCR)
+		 * 
+		 * <15..14>     00      counter 1 Disabled
+		 * <13..12>     00      counter 0 Disabled
+		 * <11..10>     11      BRG1 Input is TxC Pin
+		 * <9..8>       11      BRG0 Input is TxC Pin
+		 * <7..6>       01      DPLL Input is BRG1 Output
+		 * <5..3>       100     TxCLK comes from BRG0
+		 * <2..0>       100     RxCLK comes from BRG0
+		 *
+		 * 0000 1111 0110 0100 = 0x0f64
+		 */
+		
+		usc_OutReg( info, CMCR, 0x0f64 );
+
+
+		/*
+		 * Write 16-bit Time Constant for BRG0
+		 * Time Constant = (ClkSpeed / data_rate) - 1
+		 * ClkSpeed = 921600 (ISA), 691200 (PCI)
+		 */
+
+		if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+			usc_OutReg( info, TC0R, (u16)((691200/data_rate) - 1) );
+		else
+			usc_OutReg( info, TC0R, (u16)((921600/data_rate) - 1) );
+
+		
+		/*
+		 * Hardware Configuration Register (HCR)
+		 * Clear Bit 1, BRG0 mode = Continuous
+		 * Set Bit 0 to enable BRG0.
+		 */
+
+		usc_OutReg( info, HCR,
+			    (u16)((usc_InReg( info, HCR ) & ~BIT1) | BIT0) );
+
+
+		/* Input/Output Control Reg, <2..0> = 100, Drive RxC pin with BRG0 */
+
+		usc_OutReg( info, IOCR,
+			    (u16)((usc_InReg(info, IOCR) & 0xfff8) | 0x0004) );
+	} else {
+		/* data rate == 0 so turn off BRG0 */
+		usc_OutReg( info, HCR, (u16)(usc_InReg( info, HCR ) & ~BIT0) );
+	}
+
+}	/* end of usc_enable_async_clock() */
+
+/*
+ * Buffer Structures:
+ *
+ * Normal memory access uses virtual addresses that can make discontiguous
+ * physical memory pages appear to be contiguous in the virtual address
+ * space (the processors memory mapping handles the conversions).
+ *
+ * DMA transfers require physically contiguous memory. This is because
+ * the DMA system controller and DMA bus masters deal with memory using
+ * only physical addresses.
+ *
+ * This causes a problem under Windows NT when large DMA buffers are
+ * needed. Fragmentation of the nonpaged pool prevents allocations of
+ * physically contiguous buffers larger than the PAGE_SIZE.
+ *
+ * However the 16C32 supports Bus Master Scatter/Gather DMA which
+ * allows DMA transfers to physically discontiguous buffers. Information
+ * about each data transfer buffer is contained in a memory structure
+ * called a 'buffer entry'. A list of buffer entries is maintained
+ * to track and control the use of the data transfer buffers.
+ *
+ * To support this strategy we will allocate sufficient PAGE_SIZE
+ * contiguous memory buffers to allow for the total required buffer
+ * space.
+ *
+ * The 16C32 accesses the list of buffer entries using Bus Master
+ * DMA. Control information is read from the buffer entries by the
+ * 16C32 to control data transfers. status information is written to
+ * the buffer entries by the 16C32 to indicate the status of completed
+ * transfers.
+ *
+ * The CPU writes control information to the buffer entries to control
+ * the 16C32 and reads status information from the buffer entries to
+ * determine information about received and transmitted frames.
+ *
+ * Because the CPU and 16C32 (adapter) both need simultaneous access
+ * to the buffer entries, the buffer entry memory is allocated with
+ * HalAllocateCommonBuffer(). This restricts the size of the buffer
+ * entry list to PAGE_SIZE.
+ *
+ * The actual data buffers on the other hand will only be accessed
+ * by the CPU or the adapter but not by both simultaneously. This allows
+ * Scatter/Gather packet based DMA procedures for using physically
+ * discontiguous pages.
+ */
+
+/*
+ * mgsl_reset_tx_dma_buffers()
+ *
+ * 	Set the count for all transmit buffers to 0 to indicate the
+ * 	buffer is available for use and set the current buffer to the
+ * 	first buffer. This effectively makes all buffers free and
+ * 	discards any data in buffers.
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_reset_tx_dma_buffers( struct mgsl_struct *info )
+{
+	unsigned int i;
+
+	for ( i = 0; i < info->tx_buffer_count; i++ ) {
+		*((unsigned long *)&(info->tx_buffer_list[i].count)) = 0;
+	}
+
+	info->current_tx_buffer = 0;
+	info->start_tx_dma_buffer = 0;
+	info->tx_dma_buffers_used = 0;
+
+	info->get_tx_holding_index = 0;
+	info->put_tx_holding_index = 0;
+	info->tx_holding_count = 0;
+
+}	/* end of mgsl_reset_tx_dma_buffers() */
+
+/*
+ * num_free_tx_dma_buffers()
+ *
+ * 	returns the number of free tx dma buffers available
+ *
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	number of free tx dma buffers
+ */
+static int num_free_tx_dma_buffers(struct mgsl_struct *info)
+{
+	return info->tx_buffer_count - info->tx_dma_buffers_used;
+}
+
+/*
+ * mgsl_reset_rx_dma_buffers()
+ * 
+ * 	Set the count for all receive buffers to DMABUFFERSIZE
+ * 	and set the current buffer to the first buffer. This effectively
+ * 	makes all buffers free and discards any data in buffers.
+ * 
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_reset_rx_dma_buffers( struct mgsl_struct *info )
+{
+	unsigned int i;
+
+	for ( i = 0; i < info->rx_buffer_count; i++ ) {
+		*((unsigned long *)&(info->rx_buffer_list[i].count)) = DMABUFFERSIZE;
+//		info->rx_buffer_list[i].count = DMABUFFERSIZE;
+//		info->rx_buffer_list[i].status = 0;
+	}
+
+	info->current_rx_buffer = 0;
+
+}	/* end of mgsl_reset_rx_dma_buffers() */
+
+/*
+ * mgsl_free_rx_frame_buffers()
+ * 
+ * 	Free the receive buffers used by a received SDLC
+ * 	frame such that the buffers can be reused.
+ * 
+ * Arguments:
+ * 
+ * 	info			pointer to device instance data
+ * 	StartIndex		index of 1st receive buffer of frame
+ * 	EndIndex		index of last receive buffer of frame
+ * 
+ * Return Value:	None
+ */
+static void mgsl_free_rx_frame_buffers( struct mgsl_struct *info, unsigned int StartIndex, unsigned int EndIndex )
+{
+	int Done = 0;
+	DMABUFFERENTRY *pBufEntry;
+	unsigned int Index;
+
+	/* Starting with 1st buffer entry of the frame clear the status */
+	/* field and set the count field to DMA Buffer Size. */
+
+	Index = StartIndex;
+
+	while( !Done ) {
+		pBufEntry = &(info->rx_buffer_list[Index]);
+
+		if ( Index == EndIndex ) {
+			/* This is the last buffer of the frame! */
+			Done = 1;
+		}
+
+		/* reset current buffer for reuse */
+//		pBufEntry->status = 0;
+//		pBufEntry->count = DMABUFFERSIZE;
+		*((unsigned long *)&(pBufEntry->count)) = DMABUFFERSIZE;
+
+		/* advance to next buffer entry in linked list */
+		Index++;
+		if ( Index == info->rx_buffer_count )
+			Index = 0;
+	}
+
+	/* set current buffer to next buffer after last buffer of frame */
+	info->current_rx_buffer = Index;
+
+}	/* end of free_rx_frame_buffers() */
+
+/* mgsl_get_rx_frame()
+ * 
+ * 	This function attempts to return a received SDLC frame from the
+ * 	receive DMA buffers. Only frames received without errors are returned.
+ *
+ * Arguments:	 	info	pointer to device extension
+ * Return Value:	1 if frame returned, otherwise 0
+ */
+static int mgsl_get_rx_frame(struct mgsl_struct *info)
+{
+	unsigned int StartIndex, EndIndex;	/* index of 1st and last buffers of Rx frame */
+	unsigned short status;
+	DMABUFFERENTRY *pBufEntry;
+	unsigned int framesize = 0;
+	int ReturnCode = 0;
+	unsigned long flags;
+	struct tty_struct *tty = info->tty;
+	int return_frame = 0;
+	
+	/*
+	 * current_rx_buffer points to the 1st buffer of the next available
+	 * receive frame. To find the last buffer of the frame look for
+	 * a non-zero status field in the buffer entries. (The status
+	 * field is set by the 16C32 after completing a receive frame.
+	 */
+
+	StartIndex = EndIndex = info->current_rx_buffer;
+
+	while( !info->rx_buffer_list[EndIndex].status ) {
+		/*
+		 * If the count field of the buffer entry is non-zero then
+		 * this buffer has not been used. (The 16C32 clears the count
+		 * field when it starts using the buffer.) If an unused buffer
+		 * is encountered then there are no frames available.
+		 */
+
+		if ( info->rx_buffer_list[EndIndex].count )
+			goto Cleanup;
+
+		/* advance to next buffer entry in linked list */
+		EndIndex++;
+		if ( EndIndex == info->rx_buffer_count )
+			EndIndex = 0;
+
+		/* if entire list searched then no frame available */
+		if ( EndIndex == StartIndex ) {
+			/* If this occurs then something bad happened,
+			 * all buffers have been 'used' but none mark
+			 * the end of a frame. Reset buffers and receiver.
+			 */
+
+			if ( info->rx_enabled ){
+				spin_lock_irqsave(&info->irq_spinlock,flags);
+				usc_start_receiver(info);
+				spin_unlock_irqrestore(&info->irq_spinlock,flags);
+			}
+			goto Cleanup;
+		}
+	}
+
+
+	/* check status of receive frame */
+	
+	status = info->rx_buffer_list[EndIndex].status;
+
+	if ( status & (RXSTATUS_SHORT_FRAME + RXSTATUS_OVERRUN +
+			RXSTATUS_CRC_ERROR + RXSTATUS_ABORT) ) {
+		if ( status & RXSTATUS_SHORT_FRAME )
+			info->icount.rxshort++;
+		else if ( status & RXSTATUS_ABORT )
+			info->icount.rxabort++;
+		else if ( status & RXSTATUS_OVERRUN )
+			info->icount.rxover++;
+		else {
+			info->icount.rxcrc++;
+			if ( info->params.crc_type & HDLC_CRC_RETURN_EX )
+				return_frame = 1;
+		}
+		framesize = 0;
+#ifdef CONFIG_HDLC
+		{
+			struct net_device_stats *stats = hdlc_stats(info->netdev);
+			stats->rx_errors++;
+			stats->rx_frame_errors++;
+		}
+#endif
+	} else
+		return_frame = 1;
+
+	if ( return_frame ) {
+		/* receive frame has no errors, get frame size.
+		 * The frame size is the starting value of the RCC (which was
+		 * set to 0xffff) minus the ending value of the RCC (decremented
+		 * once for each receive character) minus 2 for the 16-bit CRC.
+		 */
+
+		framesize = RCLRVALUE - info->rx_buffer_list[EndIndex].rcc;
+
+		/* adjust frame size for CRC if any */
+		if ( info->params.crc_type == HDLC_CRC_16_CCITT )
+			framesize -= 2;
+		else if ( info->params.crc_type == HDLC_CRC_32_CCITT )
+			framesize -= 4;		
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk("%s(%d):mgsl_get_rx_frame(%s) status=%04X size=%d\n",
+			__FILE__,__LINE__,info->device_name,status,framesize);
+			
+	if ( debug_level >= DEBUG_LEVEL_DATA )
+		mgsl_trace_block(info,info->rx_buffer_list[StartIndex].virt_addr,
+			min_t(int, framesize, DMABUFFERSIZE),0);
+		
+	if (framesize) {
+		if ( ( (info->params.crc_type & HDLC_CRC_RETURN_EX) &&
+				((framesize+1) > info->max_frame_size) ) ||
+			(framesize > info->max_frame_size) )
+			info->icount.rxlong++;
+		else {
+			/* copy dma buffer(s) to contiguous intermediate buffer */
+			int copy_count = framesize;
+			int index = StartIndex;
+			unsigned char *ptmp = info->intermediate_rxbuffer;
+
+			if ( !(status & RXSTATUS_CRC_ERROR))
+			info->icount.rxok++;
+			
+			while(copy_count) {
+				int partial_count;
+				if ( copy_count > DMABUFFERSIZE )
+					partial_count = DMABUFFERSIZE;
+				else
+					partial_count = copy_count;
+			
+				pBufEntry = &(info->rx_buffer_list[index]);
+				memcpy( ptmp, pBufEntry->virt_addr, partial_count );
+				ptmp += partial_count;
+				copy_count -= partial_count;
+				
+				if ( ++index == info->rx_buffer_count )
+					index = 0;
+			}
+
+			if ( info->params.crc_type & HDLC_CRC_RETURN_EX ) {
+				++framesize;
+				*ptmp = (status & RXSTATUS_CRC_ERROR ?
+						RX_CRC_ERROR :
+						RX_OK);
+
+				if ( debug_level >= DEBUG_LEVEL_DATA )
+					printk("%s(%d):mgsl_get_rx_frame(%s) rx frame status=%d\n",
+						__FILE__,__LINE__,info->device_name,
+						*ptmp);
+			}
+
+#ifdef CONFIG_HDLC
+			if (info->netcount)
+				hdlcdev_rx(info,info->intermediate_rxbuffer,framesize);
+			else
+#endif
+				ldisc_receive_buf(tty, info->intermediate_rxbuffer, info->flag_buf, framesize);
+		}
+	}
+	/* Free the buffers used by this frame. */
+	mgsl_free_rx_frame_buffers( info, StartIndex, EndIndex );
+
+	ReturnCode = 1;
+
+Cleanup:
+
+	if ( info->rx_enabled && info->rx_overflow ) {
+		/* The receiver needs to restarted because of 
+		 * a receive overflow (buffer or FIFO). If the 
+		 * receive buffers are now empty, then restart receiver.
+		 */
+
+		if ( !info->rx_buffer_list[EndIndex].status &&
+			info->rx_buffer_list[EndIndex].count ) {
+			spin_lock_irqsave(&info->irq_spinlock,flags);
+			usc_start_receiver(info);
+			spin_unlock_irqrestore(&info->irq_spinlock,flags);
+		}
+	}
+
+	return ReturnCode;
+
+}	/* end of mgsl_get_rx_frame() */
+
+/* mgsl_get_raw_rx_frame()
+ *
+ *     	This function attempts to return a received frame from the
+ *	receive DMA buffers when running in external loop mode. In this mode,
+ *	we will return at most one DMABUFFERSIZE frame to the application.
+ *	The USC receiver is triggering off of DCD going active to start a new
+ *	frame, and DCD going inactive to terminate the frame (similar to
+ *	processing a closing flag character).
+ *
+ *	In this routine, we will return DMABUFFERSIZE "chunks" at a time.
+ *	If DCD goes inactive, the last Rx DMA Buffer will have a non-zero
+ * 	status field and the RCC field will indicate the length of the
+ *	entire received frame. We take this RCC field and get the modulus
+ *	of RCC and DMABUFFERSIZE to determine if number of bytes in the
+ *	last Rx DMA buffer and return that last portion of the frame.
+ *
+ * Arguments:	 	info	pointer to device extension
+ * Return Value:	1 if frame returned, otherwise 0
+ */
+static int mgsl_get_raw_rx_frame(struct mgsl_struct *info)
+{
+	unsigned int CurrentIndex, NextIndex;
+	unsigned short status;
+	DMABUFFERENTRY *pBufEntry;
+	unsigned int framesize = 0;
+	int ReturnCode = 0;
+	unsigned long flags;
+	struct tty_struct *tty = info->tty;
+
+	/*
+ 	 * current_rx_buffer points to the 1st buffer of the next available
+	 * receive frame. The status field is set by the 16C32 after
+	 * completing a receive frame. If the status field of this buffer
+	 * is zero, either the USC is still filling this buffer or this
+	 * is one of a series of buffers making up a received frame.
+	 *
+	 * If the count field of this buffer is zero, the USC is either
+	 * using this buffer or has used this buffer. Look at the count
+	 * field of the next buffer. If that next buffer's count is
+	 * non-zero, the USC is still actively using the current buffer.
+	 * Otherwise, if the next buffer's count field is zero, the
+	 * current buffer is complete and the USC is using the next
+	 * buffer.
+	 */
+	CurrentIndex = NextIndex = info->current_rx_buffer;
+	++NextIndex;
+	if ( NextIndex == info->rx_buffer_count )
+		NextIndex = 0;
+
+	if ( info->rx_buffer_list[CurrentIndex].status != 0 ||
+		(info->rx_buffer_list[CurrentIndex].count == 0 &&
+			info->rx_buffer_list[NextIndex].count == 0)) {
+		/*
+	 	 * Either the status field of this dma buffer is non-zero
+		 * (indicating the last buffer of a receive frame) or the next
+	 	 * buffer is marked as in use -- implying this buffer is complete
+		 * and an intermediate buffer for this received frame.
+	 	 */
+
+		status = info->rx_buffer_list[CurrentIndex].status;
+
+		if ( status & (RXSTATUS_SHORT_FRAME + RXSTATUS_OVERRUN +
+				RXSTATUS_CRC_ERROR + RXSTATUS_ABORT) ) {
+			if ( status & RXSTATUS_SHORT_FRAME )
+				info->icount.rxshort++;
+			else if ( status & RXSTATUS_ABORT )
+				info->icount.rxabort++;
+			else if ( status & RXSTATUS_OVERRUN )
+				info->icount.rxover++;
+			else
+				info->icount.rxcrc++;
+			framesize = 0;
+		} else {
+			/*
+			 * A receive frame is available, get frame size and status.
+			 *
+			 * The frame size is the starting value of the RCC (which was
+			 * set to 0xffff) minus the ending value of the RCC (decremented
+			 * once for each receive character) minus 2 or 4 for the 16-bit
+			 * or 32-bit CRC.
+			 *
+			 * If the status field is zero, this is an intermediate buffer.
+			 * It's size is 4K.
+			 *
+			 * If the DMA Buffer Entry's Status field is non-zero, the
+			 * receive operation completed normally (ie: DCD dropped). The
+			 * RCC field is valid and holds the received frame size.
+			 * It is possible that the RCC field will be zero on a DMA buffer
+			 * entry with a non-zero status. This can occur if the total
+			 * frame size (number of bytes between the time DCD goes active
+			 * to the time DCD goes inactive) exceeds 65535 bytes. In this
+			 * case the 16C32 has underrun on the RCC count and appears to
+			 * stop updating this counter to let us know the actual received
+			 * frame size. If this happens (non-zero status and zero RCC),
+			 * simply return the entire RxDMA Buffer
+			 */
+			if ( status ) {
+				/*
+				 * In the event that the final RxDMA Buffer is
+				 * terminated with a non-zero status and the RCC
+				 * field is zero, we interpret this as the RCC
+				 * having underflowed (received frame > 65535 bytes).
+				 *
+				 * Signal the event to the user by passing back
+				 * a status of RxStatus_CrcError returning the full
+				 * buffer and let the app figure out what data is
+				 * actually valid
+				 */
+				if ( info->rx_buffer_list[CurrentIndex].rcc )
+					framesize = RCLRVALUE - info->rx_buffer_list[CurrentIndex].rcc;
+				else
+					framesize = DMABUFFERSIZE;
+			}
+			else
+				framesize = DMABUFFERSIZE;
+		}
+
+		if ( framesize > DMABUFFERSIZE ) {
+			/*
+			 * if running in raw sync mode, ISR handler for
+			 * End Of Buffer events terminates all buffers at 4K.
+			 * If this frame size is said to be >4K, get the
+			 * actual number of bytes of the frame in this buffer.
+			 */
+			framesize = framesize % DMABUFFERSIZE;
+		}
+
+
+		if ( debug_level >= DEBUG_LEVEL_BH )
+			printk("%s(%d):mgsl_get_raw_rx_frame(%s) status=%04X size=%d\n",
+				__FILE__,__LINE__,info->device_name,status,framesize);
+
+		if ( debug_level >= DEBUG_LEVEL_DATA )
+			mgsl_trace_block(info,info->rx_buffer_list[CurrentIndex].virt_addr,
+				min_t(int, framesize, DMABUFFERSIZE),0);
+
+		if (framesize) {
+			/* copy dma buffer(s) to contiguous intermediate buffer */
+			/* NOTE: we never copy more than DMABUFFERSIZE bytes	*/
+
+			pBufEntry = &(info->rx_buffer_list[CurrentIndex]);
+			memcpy( info->intermediate_rxbuffer, pBufEntry->virt_addr, framesize);
+			info->icount.rxok++;
+
+			ldisc_receive_buf(tty, info->intermediate_rxbuffer, info->flag_buf, framesize);
+		}
+
+		/* Free the buffers used by this frame. */
+		mgsl_free_rx_frame_buffers( info, CurrentIndex, CurrentIndex );
+
+		ReturnCode = 1;
+	}
+
+
+	if ( info->rx_enabled && info->rx_overflow ) {
+		/* The receiver needs to restarted because of
+		 * a receive overflow (buffer or FIFO). If the
+		 * receive buffers are now empty, then restart receiver.
+		 */
+
+		if ( !info->rx_buffer_list[CurrentIndex].status &&
+			info->rx_buffer_list[CurrentIndex].count ) {
+			spin_lock_irqsave(&info->irq_spinlock,flags);
+			usc_start_receiver(info);
+			spin_unlock_irqrestore(&info->irq_spinlock,flags);
+		}
+	}
+
+	return ReturnCode;
+
+}	/* end of mgsl_get_raw_rx_frame() */
+
+/* mgsl_load_tx_dma_buffer()
+ * 
+ * 	Load the transmit DMA buffer with the specified data.
+ * 
+ * Arguments:
+ * 
+ * 	info		pointer to device extension
+ * 	Buffer		pointer to buffer containing frame to load
+ * 	BufferSize	size in bytes of frame in Buffer
+ * 
+ * Return Value: 	None
+ */
+static void mgsl_load_tx_dma_buffer(struct mgsl_struct *info,
+		const char *Buffer, unsigned int BufferSize)
+{
+	unsigned short Copycount;
+	unsigned int i = 0;
+	DMABUFFERENTRY *pBufEntry;
+	
+	if ( debug_level >= DEBUG_LEVEL_DATA )
+		mgsl_trace_block(info,Buffer, min_t(int, BufferSize, DMABUFFERSIZE), 1);
+
+	if (info->params.flags & HDLC_FLAG_HDLC_LOOPMODE) {
+		/* set CMR:13 to start transmit when
+		 * next GoAhead (abort) is received
+		 */
+	 	info->cmr_value |= BIT13;			  
+	}
+		
+	/* begin loading the frame in the next available tx dma
+	 * buffer, remember it's starting location for setting
+	 * up tx dma operation
+	 */
+	i = info->current_tx_buffer;
+	info->start_tx_dma_buffer = i;
+
+	/* Setup the status and RCC (Frame Size) fields of the 1st */
+	/* buffer entry in the transmit DMA buffer list. */
+
+	info->tx_buffer_list[i].status = info->cmr_value & 0xf000;
+	info->tx_buffer_list[i].rcc    = BufferSize;
+	info->tx_buffer_list[i].count  = BufferSize;
+
+	/* Copy frame data from 1st source buffer to the DMA buffers. */
+	/* The frame data may span multiple DMA buffers. */
+
+	while( BufferSize ){
+		/* Get a pointer to next DMA buffer entry. */
+		pBufEntry = &info->tx_buffer_list[i++];
+			
+		if ( i == info->tx_buffer_count )
+			i=0;
+
+		/* Calculate the number of bytes that can be copied from */
+		/* the source buffer to this DMA buffer. */
+		if ( BufferSize > DMABUFFERSIZE )
+			Copycount = DMABUFFERSIZE;
+		else
+			Copycount = BufferSize;
+
+		/* Actually copy data from source buffer to DMA buffer. */
+		/* Also set the data count for this individual DMA buffer. */
+		if ( info->bus_type == MGSL_BUS_TYPE_PCI )
+			mgsl_load_pci_memory(pBufEntry->virt_addr, Buffer,Copycount);
+		else
+			memcpy(pBufEntry->virt_addr, Buffer, Copycount);
+
+		pBufEntry->count = Copycount;
+
+		/* Advance source pointer and reduce remaining data count. */
+		Buffer += Copycount;
+		BufferSize -= Copycount;
+
+		++info->tx_dma_buffers_used;
+	}
+
+	/* remember next available tx dma buffer */
+	info->current_tx_buffer = i;
+
+}	/* end of mgsl_load_tx_dma_buffer() */
+
+/*
+ * mgsl_register_test()
+ * 
+ * 	Performs a register test of the 16C32.
+ * 	
+ * Arguments:		info	pointer to device instance data
+ * Return Value:		TRUE if test passed, otherwise FALSE
+ */
+static BOOLEAN mgsl_register_test( struct mgsl_struct *info )
+{
+	static unsigned short BitPatterns[] =
+		{ 0x0000, 0xffff, 0xaaaa, 0x5555, 0x1234, 0x6969, 0x9696, 0x0f0f };
+	static unsigned int Patterncount = sizeof(BitPatterns)/sizeof(unsigned short);
+	unsigned int i;
+	BOOLEAN rc = TRUE;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	usc_reset(info);
+
+	/* Verify the reset state of some registers. */
+
+	if ( (usc_InReg( info, SICR ) != 0) ||
+		  (usc_InReg( info, IVR  ) != 0) ||
+		  (usc_InDmaReg( info, DIVR ) != 0) ){
+		rc = FALSE;
+	}
+
+	if ( rc == TRUE ){
+		/* Write bit patterns to various registers but do it out of */
+		/* sync, then read back and verify values. */
+
+		for ( i = 0 ; i < Patterncount ; i++ ) {
+			usc_OutReg( info, TC0R, BitPatterns[i] );
+			usc_OutReg( info, TC1R, BitPatterns[(i+1)%Patterncount] );
+			usc_OutReg( info, TCLR, BitPatterns[(i+2)%Patterncount] );
+			usc_OutReg( info, RCLR, BitPatterns[(i+3)%Patterncount] );
+			usc_OutReg( info, RSR,  BitPatterns[(i+4)%Patterncount] );
+			usc_OutDmaReg( info, TBCR, BitPatterns[(i+5)%Patterncount] );
+
+			if ( (usc_InReg( info, TC0R ) != BitPatterns[i]) ||
+				  (usc_InReg( info, TC1R ) != BitPatterns[(i+1)%Patterncount]) ||
+				  (usc_InReg( info, TCLR ) != BitPatterns[(i+2)%Patterncount]) ||
+				  (usc_InReg( info, RCLR ) != BitPatterns[(i+3)%Patterncount]) ||
+				  (usc_InReg( info, RSR )  != BitPatterns[(i+4)%Patterncount]) ||
+				  (usc_InDmaReg( info, TBCR ) != BitPatterns[(i+5)%Patterncount]) ){
+				rc = FALSE;
+				break;
+			}
+		}
+	}
+
+	usc_reset(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	return rc;
+
+}	/* end of mgsl_register_test() */
+
+/* mgsl_irq_test() 	Perform interrupt test of the 16C32.
+ * 
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	TRUE if test passed, otherwise FALSE
+ */
+static BOOLEAN mgsl_irq_test( struct mgsl_struct *info )
+{
+	unsigned long EndTime;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	usc_reset(info);
+
+	/*
+	 * Setup 16C32 to interrupt on TxC pin (14MHz clock) transition. 
+	 * The ISR sets irq_occurred to 1. 
+	 */
+
+	info->irq_occurred = FALSE;
+
+	/* Enable INTEN gate for ISA adapter (Port 6, Bit12) */
+	/* Enable INTEN (Port 6, Bit12) */
+	/* This connects the IRQ request signal to the ISA bus */
+	/* on the ISA adapter. This has no effect for the PCI adapter */
+	usc_OutReg( info, PCR, (unsigned short)((usc_InReg(info, PCR) | BIT13) & ~BIT12) );
+
+	usc_EnableMasterIrqBit(info);
+	usc_EnableInterrupts(info, IO_PIN);
+	usc_ClearIrqPendingBits(info, IO_PIN);
+	
+	usc_UnlatchIostatusBits(info, MISCSTATUS_TXC_LATCHED);
+	usc_EnableStatusIrqs(info, SICR_TXC_ACTIVE + SICR_TXC_INACTIVE);
+
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	EndTime=100;
+	while( EndTime-- && !info->irq_occurred ) {
+		msleep_interruptible(10);
+	}
+	
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	usc_reset(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+	if ( !info->irq_occurred ) 
+		return FALSE;
+	else
+		return TRUE;
+
+}	/* end of mgsl_irq_test() */
+
+/* mgsl_dma_test()
+ * 
+ * 	Perform a DMA test of the 16C32. A small frame is
+ * 	transmitted via DMA from a transmit buffer to a receive buffer
+ * 	using single buffer DMA mode.
+ * 	
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	TRUE if test passed, otherwise FALSE
+ */
+static BOOLEAN mgsl_dma_test( struct mgsl_struct *info )
+{
+	unsigned short FifoLevel;
+	unsigned long phys_addr;
+	unsigned int FrameSize;
+	unsigned int i;
+	char *TmpPtr;
+	BOOLEAN rc = TRUE;
+	unsigned short status=0;
+	unsigned long EndTime;
+	unsigned long flags;
+	MGSL_PARAMS tmp_params;
+
+	/* save current port options */
+	memcpy(&tmp_params,&info->params,sizeof(MGSL_PARAMS));
+	/* load default port options */
+	memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
+	
+#define TESTFRAMESIZE 40
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	
+	/* setup 16C32 for SDLC DMA transfer mode */
+
+	usc_reset(info);
+	usc_set_sdlc_mode(info);
+	usc_enable_loopback(info,1);
+	
+	/* Reprogram the RDMR so that the 16C32 does NOT clear the count
+	 * field of the buffer entry after fetching buffer address. This
+	 * way we can detect a DMA failure for a DMA read (which should be
+	 * non-destructive to system memory) before we try and write to
+	 * memory (where a failure could corrupt system memory).
+	 */
+
+	/* Receive DMA mode Register (RDMR)
+	 * 
+	 * <15..14>	11	DMA mode = Linked List Buffer mode
+	 * <13>		1	RSBinA/L = store Rx status Block in List entry
+	 * <12>		0	1 = Clear count of List Entry after fetching
+	 * <11..10>	00	Address mode = Increment
+	 * <9>		1	Terminate Buffer on RxBound
+	 * <8>		0	Bus Width = 16bits
+	 * <7..0>		?	status Bits (write as 0s)
+	 * 
+	 * 1110 0010 0000 0000 = 0xe200
+	 */
+
+	usc_OutDmaReg( info, RDMR, 0xe200 );
+	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+
+	/* SETUP TRANSMIT AND RECEIVE DMA BUFFERS */
+
+	FrameSize = TESTFRAMESIZE;
+
+	/* setup 1st transmit buffer entry: */
+	/* with frame size and transmit control word */
+
+	info->tx_buffer_list[0].count  = FrameSize;
+	info->tx_buffer_list[0].rcc    = FrameSize;
+	info->tx_buffer_list[0].status = 0x4000;
+
+	/* build a transmit frame in 1st transmit DMA buffer */
+
+	TmpPtr = info->tx_buffer_list[0].virt_addr;
+	for (i = 0; i < FrameSize; i++ )
+		*TmpPtr++ = i;
+
+	/* setup 1st receive buffer entry: */
+	/* clear status, set max receive buffer size */
+
+	info->rx_buffer_list[0].status = 0;
+	info->rx_buffer_list[0].count = FrameSize + 4;
+
+	/* zero out the 1st receive buffer */
+
+	memset( info->rx_buffer_list[0].virt_addr, 0, FrameSize + 4 );
+
+	/* Set count field of next buffer entries to prevent */
+	/* 16C32 from using buffers after the 1st one. */
+
+	info->tx_buffer_list[1].count = 0;
+	info->rx_buffer_list[1].count = 0;
+	
+
+	/***************************/
+	/* Program 16C32 receiver. */
+	/***************************/
+	
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+
+	/* setup DMA transfers */
+	usc_RTCmd( info, RTCmd_PurgeRxFifo );
+
+	/* program 16C32 receiver with physical address of 1st DMA buffer entry */
+	phys_addr = info->rx_buffer_list[0].phys_entry;
+	usc_OutDmaReg( info, NRARL, (unsigned short)phys_addr );
+	usc_OutDmaReg( info, NRARU, (unsigned short)(phys_addr >> 16) );
+
+	/* Clear the Rx DMA status bits (read RDMR) and start channel */
+	usc_InDmaReg( info, RDMR );
+	usc_DmaCmd( info, DmaCmd_InitRxChannel );
+
+	/* Enable Receiver (RMR <1..0> = 10) */
+	usc_OutReg( info, RMR, (unsigned short)((usc_InReg(info, RMR) & 0xfffc) | 0x0002) );
+	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+
+	/*************************************************************/
+	/* WAIT FOR RECEIVER TO DMA ALL PARAMETERS FROM BUFFER ENTRY */
+	/*************************************************************/
+
+	/* Wait 100ms for interrupt. */
+	EndTime = jiffies + msecs_to_jiffies(100);
+
+	for(;;) {
+		if (time_after(jiffies, EndTime)) {
+			rc = FALSE;
+			break;
+		}
+
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		status = usc_InDmaReg( info, RDMR );
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+		if ( !(status & BIT4) && (status & BIT5) ) {
+			/* INITG (BIT 4) is inactive (no entry read in progress) AND */
+			/* BUSY  (BIT 5) is active (channel still active). */
+			/* This means the buffer entry read has completed. */
+			break;
+		}
+	}
+
+
+	/******************************/
+	/* Program 16C32 transmitter. */
+	/******************************/
+	
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+
+	/* Program the Transmit Character Length Register (TCLR) */
+	/* and clear FIFO (TCC is loaded with TCLR on FIFO clear) */
+
+	usc_OutReg( info, TCLR, (unsigned short)info->tx_buffer_list[0].count );
+	usc_RTCmd( info, RTCmd_PurgeTxFifo );
+
+	/* Program the address of the 1st DMA Buffer Entry in linked list */
+
+	phys_addr = info->tx_buffer_list[0].phys_entry;
+	usc_OutDmaReg( info, NTARL, (unsigned short)phys_addr );
+	usc_OutDmaReg( info, NTARU, (unsigned short)(phys_addr >> 16) );
+
+	/* unlatch Tx status bits, and start transmit channel. */
+
+	usc_OutReg( info, TCSR, (unsigned short)(( usc_InReg(info, TCSR) & 0x0f00) | 0xfa) );
+	usc_DmaCmd( info, DmaCmd_InitTxChannel );
+
+	/* wait for DMA controller to fill transmit FIFO */
+
+	usc_TCmd( info, TCmd_SelectTicrTxFifostatus );
+	
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+
+	/**********************************/
+	/* WAIT FOR TRANSMIT FIFO TO FILL */
+	/**********************************/
+	
+	/* Wait 100ms */
+	EndTime = jiffies + msecs_to_jiffies(100);
+
+	for(;;) {
+		if (time_after(jiffies, EndTime)) {
+			rc = FALSE;
+			break;
+		}
+
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		FifoLevel = usc_InReg(info, TICR) >> 8;
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+			
+		if ( FifoLevel < 16 )
+			break;
+		else
+			if ( FrameSize < 32 ) {
+				/* This frame is smaller than the entire transmit FIFO */
+				/* so wait for the entire frame to be loaded. */
+				if ( FifoLevel <= (32 - FrameSize) )
+					break;
+			}
+	}
+
+
+	if ( rc == TRUE )
+	{
+		/* Enable 16C32 transmitter. */
+
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		
+		/* Transmit mode Register (TMR), <1..0> = 10, Enable Transmitter */
+		usc_TCmd( info, TCmd_SendFrame );
+		usc_OutReg( info, TMR, (unsigned short)((usc_InReg(info, TMR) & 0xfffc) | 0x0002) );
+		
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+						
+		/******************************/
+		/* WAIT FOR TRANSMIT COMPLETE */
+		/******************************/
+
+		/* Wait 100ms */
+		EndTime = jiffies + msecs_to_jiffies(100);
+
+		/* While timer not expired wait for transmit complete */
+
+		spin_lock_irqsave(&info->irq_spinlock,flags);
+		status = usc_InReg( info, TCSR );
+		spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+		while ( !(status & (BIT6+BIT5+BIT4+BIT2+BIT1)) ) {
+			if (time_after(jiffies, EndTime)) {
+				rc = FALSE;
+				break;
+			}
+
+			spin_lock_irqsave(&info->irq_spinlock,flags);
+			status = usc_InReg( info, TCSR );
+			spin_unlock_irqrestore(&info->irq_spinlock,flags);
+		}
+	}
+
+
+	if ( rc == TRUE ){
+		/* CHECK FOR TRANSMIT ERRORS */
+		if ( status & (BIT5 + BIT1) ) 
+			rc = FALSE;
+	}
+
+	if ( rc == TRUE ) {
+		/* WAIT FOR RECEIVE COMPLETE */
+
+		/* Wait 100ms */
+		EndTime = jiffies + msecs_to_jiffies(100);
+
+		/* Wait for 16C32 to write receive status to buffer entry. */
+		status=info->rx_buffer_list[0].status;
+		while ( status == 0 ) {
+			if (time_after(jiffies, EndTime)) {
+				rc = FALSE;
+				break;
+			}
+			status=info->rx_buffer_list[0].status;
+		}
+	}
+
+
+	if ( rc == TRUE ) {
+		/* CHECK FOR RECEIVE ERRORS */
+		status = info->rx_buffer_list[0].status;
+
+		if ( status & (BIT8 + BIT3 + BIT1) ) {
+			/* receive error has occurred */
+			rc = FALSE;
+		} else {
+			if ( memcmp( info->tx_buffer_list[0].virt_addr ,
+				info->rx_buffer_list[0].virt_addr, FrameSize ) ){
+				rc = FALSE;
+			}
+		}
+	}
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	usc_reset( info );
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	/* restore current port options */
+	memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
+	
+	return rc;
+
+}	/* end of mgsl_dma_test() */
+
+/* mgsl_adapter_test()
+ * 
+ * 	Perform the register, IRQ, and DMA tests for the 16C32.
+ * 	
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	0 if success, otherwise -ENODEV
+ */
+static int mgsl_adapter_test( struct mgsl_struct *info )
+{
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):Testing device %s\n",
+			__FILE__,__LINE__,info->device_name );
+			
+	if ( !mgsl_register_test( info ) ) {
+		info->init_error = DiagStatus_AddressFailure;
+		printk( "%s(%d):Register test failure for device %s Addr=%04X\n",
+			__FILE__,__LINE__,info->device_name, (unsigned short)(info->io_base) );
+		return -ENODEV;
+	}
+
+	if ( !mgsl_irq_test( info ) ) {
+		info->init_error = DiagStatus_IrqFailure;
+		printk( "%s(%d):Interrupt test failure for device %s IRQ=%d\n",
+			__FILE__,__LINE__,info->device_name, (unsigned short)(info->irq_level) );
+		return -ENODEV;
+	}
+
+	if ( !mgsl_dma_test( info ) ) {
+		info->init_error = DiagStatus_DmaFailure;
+		printk( "%s(%d):DMA test failure for device %s DMA=%d\n",
+			__FILE__,__LINE__,info->device_name, (unsigned short)(info->dma_level) );
+		return -ENODEV;
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):device %s passed diagnostics\n",
+			__FILE__,__LINE__,info->device_name );
+			
+	return 0;
+
+}	/* end of mgsl_adapter_test() */
+
+/* mgsl_memory_test()
+ * 
+ * 	Test the shared memory on a PCI adapter.
+ * 
+ * Arguments:		info	pointer to device instance data
+ * Return Value:	TRUE if test passed, otherwise FALSE
+ */
+static BOOLEAN mgsl_memory_test( struct mgsl_struct *info )
+{
+	static unsigned long BitPatterns[] = { 0x0, 0x55555555, 0xaaaaaaaa,
+											0x66666666, 0x99999999, 0xffffffff, 0x12345678 };
+	unsigned long Patterncount = sizeof(BitPatterns)/sizeof(unsigned long);
+	unsigned long i;
+	unsigned long TestLimit = SHARED_MEM_ADDRESS_SIZE/sizeof(unsigned long);
+	unsigned long * TestAddr;
+
+	if ( info->bus_type != MGSL_BUS_TYPE_PCI )
+		return TRUE;
+
+	TestAddr = (unsigned long *)info->memory_base;
+
+	/* Test data lines with test pattern at one location. */
+
+	for ( i = 0 ; i < Patterncount ; i++ ) {
+		*TestAddr = BitPatterns[i];
+		if ( *TestAddr != BitPatterns[i] )
+			return FALSE;
+	}
+
+	/* Test address lines with incrementing pattern over */
+	/* entire address range. */
+
+	for ( i = 0 ; i < TestLimit ; i++ ) {
+		*TestAddr = i * 4;
+		TestAddr++;
+	}
+
+	TestAddr = (unsigned long *)info->memory_base;
+
+	for ( i = 0 ; i < TestLimit ; i++ ) {
+		if ( *TestAddr != i * 4 )
+			return FALSE;
+		TestAddr++;
+	}
+
+	memset( info->memory_base, 0, SHARED_MEM_ADDRESS_SIZE );
+
+	return TRUE;
+
+}	/* End Of mgsl_memory_test() */
+
+
+/* mgsl_load_pci_memory()
+ * 
+ * 	Load a large block of data into the PCI shared memory.
+ * 	Use this instead of memcpy() or memmove() to move data
+ * 	into the PCI shared memory.
+ * 
+ * Notes:
+ * 
+ * 	This function prevents the PCI9050 interface chip from hogging
+ * 	the adapter local bus, which can starve the 16C32 by preventing
+ * 	16C32 bus master cycles.
+ * 
+ * 	The PCI9050 documentation says that the 9050 will always release
+ * 	control of the local bus after completing the current read
+ * 	or write operation.
+ * 
+ * 	It appears that as long as the PCI9050 write FIFO is full, the
+ * 	PCI9050 treats all of the writes as a single burst transaction
+ * 	and will not release the bus. This causes DMA latency problems
+ * 	at high speeds when copying large data blocks to the shared
+ * 	memory.
+ * 
+ * 	This function in effect, breaks the a large shared memory write
+ * 	into multiple transations by interleaving a shared memory read
+ * 	which will flush the write FIFO and 'complete' the write
+ * 	transation. This allows any pending DMA request to gain control
+ * 	of the local bus in a timely fasion.
+ * 
+ * Arguments:
+ * 
+ * 	TargetPtr	pointer to target address in PCI shared memory
+ * 	SourcePtr	pointer to source buffer for data
+ * 	count		count in bytes of data to copy
+ *
+ * Return Value:	None
+ */
+static void mgsl_load_pci_memory( char* TargetPtr, const char* SourcePtr,
+	unsigned short count )
+{
+	/* 16 32-bit writes @ 60ns each = 960ns max latency on local bus */
+#define PCI_LOAD_INTERVAL 64
+
+	unsigned short Intervalcount = count / PCI_LOAD_INTERVAL;
+	unsigned short Index;
+	unsigned long Dummy;
+
+	for ( Index = 0 ; Index < Intervalcount ; Index++ )
+	{
+		memcpy(TargetPtr, SourcePtr, PCI_LOAD_INTERVAL);
+		Dummy = *((volatile unsigned long *)TargetPtr);
+		TargetPtr += PCI_LOAD_INTERVAL;
+		SourcePtr += PCI_LOAD_INTERVAL;
+	}
+
+	memcpy( TargetPtr, SourcePtr, count % PCI_LOAD_INTERVAL );
+
+}	/* End Of mgsl_load_pci_memory() */
+
+static void mgsl_trace_block(struct mgsl_struct *info,const char* data, int count, int xmit)
+{
+	int i;
+	int linecount;
+	if (xmit)
+		printk("%s tx data:\n",info->device_name);
+	else
+		printk("%s rx data:\n",info->device_name);
+		
+	while(count) {
+		if (count > 16)
+			linecount = 16;
+		else
+			linecount = count;
+			
+		for(i=0;i<linecount;i++)
+			printk("%02X ",(unsigned char)data[i]);
+		for(;i<17;i++)
+			printk("   ");
+		for(i=0;i<linecount;i++) {
+			if (data[i]>=040 && data[i]<=0176)
+				printk("%c",data[i]);
+			else
+				printk(".");
+		}
+		printk("\n");
+		
+		data  += linecount;
+		count -= linecount;
+	}
+}	/* end of mgsl_trace_block() */
+
+/* mgsl_tx_timeout()
+ * 
+ * 	called when HDLC frame times out
+ * 	update stats and do tx completion processing
+ * 	
+ * Arguments:	context		pointer to device instance data
+ * Return Value:	None
+ */
+static void mgsl_tx_timeout(unsigned long context)
+{
+	struct mgsl_struct *info = (struct mgsl_struct*)context;
+	unsigned long flags;
+	
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):mgsl_tx_timeout(%s)\n",
+			__FILE__,__LINE__,info->device_name);
+	if(info->tx_active &&
+	   (info->params.mode == MGSL_MODE_HDLC ||
+	    info->params.mode == MGSL_MODE_RAW) ) {
+		info->icount.txtimeout++;
+	}
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	info->tx_active = 0;
+	info->xmit_cnt = info->xmit_head = info->xmit_tail = 0;
+
+	if ( info->params.flags & HDLC_FLAG_HDLC_LOOPMODE )
+		usc_loopmode_cancel_transmit( info );
+
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+	
+#ifdef CONFIG_HDLC
+	if (info->netcount)
+		hdlcdev_tx_done(info);
+	else
+#endif
+		mgsl_bh_transmit(info);
+	
+}	/* end of mgsl_tx_timeout() */
+
+/* signal that there are no more frames to send, so that
+ * line is 'released' by echoing RxD to TxD when current
+ * transmission is complete (or immediately if no tx in progress).
+ */
+static int mgsl_loopmode_send_done( struct mgsl_struct * info )
+{
+	unsigned long flags;
+	
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	if (info->params.flags & HDLC_FLAG_HDLC_LOOPMODE) {
+		if (info->tx_active)
+			info->loopmode_send_done_requested = TRUE;
+		else
+			usc_loopmode_send_done(info);
+	}
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	return 0;
+}
+
+/* release the line by echoing RxD to TxD
+ * upon completion of a transmit frame
+ */
+static void usc_loopmode_send_done( struct mgsl_struct * info )
+{
+ 	info->loopmode_send_done_requested = FALSE;
+ 	/* clear CMR:13 to 0 to start echoing RxData to TxData */
+ 	info->cmr_value &= ~BIT13;			  
+ 	usc_OutReg(info, CMR, info->cmr_value);
+}
+
+/* abort a transmit in progress while in HDLC LoopMode
+ */
+static void usc_loopmode_cancel_transmit( struct mgsl_struct * info )
+{
+ 	/* reset tx dma channel and purge TxFifo */
+ 	usc_RTCmd( info, RTCmd_PurgeTxFifo );
+ 	usc_DmaCmd( info, DmaCmd_ResetTxChannel );
+  	usc_loopmode_send_done( info );
+}
+
+/* for HDLC/SDLC LoopMode, setting CMR:13 after the transmitter is enabled
+ * is an Insert Into Loop action. Upon receipt of a GoAhead sequence (RxAbort)
+ * we must clear CMR:13 to begin repeating TxData to RxData
+ */
+static void usc_loopmode_insert_request( struct mgsl_struct * info )
+{
+ 	info->loopmode_insert_requested = TRUE;
+ 
+ 	/* enable RxAbort irq. On next RxAbort, clear CMR:13 to
+ 	 * begin repeating TxData on RxData (complete insertion)
+	 */
+ 	usc_OutReg( info, RICR, 
+		(usc_InReg( info, RICR ) | RXSTATUS_ABORT_RECEIVED ) );
+		
+	/* set CMR:13 to insert into loop on next GoAhead (RxAbort) */
+	info->cmr_value |= BIT13;
+ 	usc_OutReg(info, CMR, info->cmr_value);
+}
+
+/* return 1 if station is inserted into the loop, otherwise 0
+ */
+static int usc_loopmode_active( struct mgsl_struct * info)
+{
+ 	return usc_InReg( info, CCSR ) & BIT7 ? 1 : 0 ;
+}
+
+#ifdef CONFIG_HDLC
+
+/**
+ * called by generic HDLC layer when protocol selected (PPP, frame relay, etc.)
+ * set encoding and frame check sequence (FCS) options
+ *
+ * dev       pointer to network device structure
+ * encoding  serial encoding setting
+ * parity    FCS setting
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_attach(struct net_device *dev, unsigned short encoding,
+			  unsigned short parity)
+{
+	struct mgsl_struct *info = dev_to_port(dev);
+	unsigned char  new_encoding;
+	unsigned short new_crctype;
+
+	/* return error if TTY interface open */
+	if (info->count)
+		return -EBUSY;
+
+	switch (encoding)
+	{
+	case ENCODING_NRZ:        new_encoding = HDLC_ENCODING_NRZ; break;
+	case ENCODING_NRZI:       new_encoding = HDLC_ENCODING_NRZI_SPACE; break;
+	case ENCODING_FM_MARK:    new_encoding = HDLC_ENCODING_BIPHASE_MARK; break;
+	case ENCODING_FM_SPACE:   new_encoding = HDLC_ENCODING_BIPHASE_SPACE; break;
+	case ENCODING_MANCHESTER: new_encoding = HDLC_ENCODING_BIPHASE_LEVEL; break;
+	default: return -EINVAL;
+	}
+
+	switch (parity)
+	{
+	case PARITY_NONE:            new_crctype = HDLC_CRC_NONE; break;
+	case PARITY_CRC16_PR1_CCITT: new_crctype = HDLC_CRC_16_CCITT; break;
+	case PARITY_CRC32_PR1_CCITT: new_crctype = HDLC_CRC_32_CCITT; break;
+	default: return -EINVAL;
+	}
+
+	info->params.encoding = new_encoding;
+	info->params.crc_type = new_crctype;;
+
+	/* if network interface up, reprogram hardware */
+	if (info->netcount)
+		mgsl_program_hw(info);
+
+	return 0;
+}
+
+/**
+ * called by generic HDLC layer to send frame
+ *
+ * skb  socket buffer containing HDLC frame
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct mgsl_struct *info = dev_to_port(dev);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk(KERN_INFO "%s:hdlc_xmit(%s)\n",__FILE__,dev->name);
+
+	/* stop sending until this frame completes */
+	netif_stop_queue(dev);
+
+	/* copy data to device buffers */
+	info->xmit_cnt = skb->len;
+	mgsl_load_tx_dma_buffer(info, skb->data, skb->len);
+
+	/* update network statistics */
+	stats->tx_packets++;
+	stats->tx_bytes += skb->len;
+
+	/* done with socket buffer, so free it */
+	dev_kfree_skb(skb);
+
+	/* save start time for transmit timeout detection */
+	dev->trans_start = jiffies;
+
+	/* start hardware transmitter if necessary */
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	if (!info->tx_active)
+	 	usc_start_transmitter(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	return 0;
+}
+
+/**
+ * called by network layer when interface enabled
+ * claim resources and initialize hardware
+ *
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_open(struct net_device *dev)
+{
+	struct mgsl_struct *info = dev_to_port(dev);
+	int rc;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_open(%s)\n",__FILE__,dev->name);
+
+	/* generic HDLC layer open processing */
+	if ((rc = hdlc_open(dev)))
+		return rc;
+
+	/* arbitrate between network and tty opens */
+	spin_lock_irqsave(&info->netlock, flags);
+	if (info->count != 0 || info->netcount != 0) {
+		printk(KERN_WARNING "%s: hdlc_open returning busy\n", dev->name);
+		spin_unlock_irqrestore(&info->netlock, flags);
+		return -EBUSY;
+	}
+	info->netcount=1;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	/* claim resources and init adapter */
+	if ((rc = startup(info)) != 0) {
+		spin_lock_irqsave(&info->netlock, flags);
+		info->netcount=0;
+		spin_unlock_irqrestore(&info->netlock, flags);
+		return rc;
+	}
+
+	/* assert DTR and RTS, apply hardware settings */
+	info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+	mgsl_program_hw(info);
+
+	/* enable network layer transmit */
+	dev->trans_start = jiffies;
+	netif_start_queue(dev);
+
+	/* inform generic HDLC layer of current DCD status */
+	spin_lock_irqsave(&info->irq_spinlock, flags);
+	usc_get_serial_signals(info);
+	spin_unlock_irqrestore(&info->irq_spinlock, flags);
+	hdlc_set_carrier(info->serial_signals & SerialSignal_DCD, dev);
+
+	return 0;
+}
+
+/**
+ * called by network layer when interface is disabled
+ * shutdown hardware and release resources
+ *
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_close(struct net_device *dev)
+{
+	struct mgsl_struct *info = dev_to_port(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_close(%s)\n",__FILE__,dev->name);
+
+	netif_stop_queue(dev);
+
+	/* shutdown adapter and release resources */
+	shutdown(info);
+
+	hdlc_close(dev);
+
+	spin_lock_irqsave(&info->netlock, flags);
+	info->netcount=0;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	return 0;
+}
+
+/**
+ * called by network layer to process IOCTL call to network device
+ *
+ * dev  pointer to network device structure
+ * ifr  pointer to network interface request structure
+ * cmd  IOCTL command code
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	const size_t size = sizeof(sync_serial_settings);
+	sync_serial_settings new_line;
+	sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+	struct mgsl_struct *info = dev_to_port(dev);
+	unsigned int flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_ioctl(%s)\n",__FILE__,dev->name);
+
+	/* return error if TTY interface open */
+	if (info->count)
+		return -EBUSY;
+
+	if (cmd != SIOCWANDEV)
+		return hdlc_ioctl(dev, ifr, cmd);
+
+	switch(ifr->ifr_settings.type) {
+	case IF_GET_IFACE: /* return current sync_serial_settings */
+
+		ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+
+		flags = info->params.flags & (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					      HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					      HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					      HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN);
+
+		switch (flags){
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN): new_line.clock_type = CLOCK_EXT; break;
+		case (HDLC_FLAG_RXC_BRG    | HDLC_FLAG_TXC_BRG):    new_line.clock_type = CLOCK_INT; break;
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG):    new_line.clock_type = CLOCK_TXINT; break;
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN): new_line.clock_type = CLOCK_TXFROMRX; break;
+		default: new_line.clock_type = CLOCK_DEFAULT;
+		}
+
+		new_line.clock_rate = info->params.clock_speed;
+		new_line.loopback   = info->params.loopback ? 1:0;
+
+		if (copy_to_user(line, &new_line, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_IFACE_SYNC_SERIAL: /* set sync_serial_settings */
+
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		if (copy_from_user(&new_line, line, size))
+			return -EFAULT;
+
+		switch (new_line.clock_type)
+		{
+		case CLOCK_EXT:      flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN; break;
+		case CLOCK_TXFROMRX: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN; break;
+		case CLOCK_INT:      flags = HDLC_FLAG_RXC_BRG    | HDLC_FLAG_TXC_BRG;    break;
+		case CLOCK_TXINT:    flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG;    break;
+		case CLOCK_DEFAULT:  flags = info->params.flags &
+					     (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					      HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					      HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					      HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN); break;
+		default: return -EINVAL;
+		}
+
+		if (new_line.loopback != 0 && new_line.loopback != 1)
+			return -EINVAL;
+
+		info->params.flags &= ~(HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN);
+		info->params.flags |= flags;
+
+		info->params.loopback = new_line.loopback;
+
+		if (flags & (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG))
+			info->params.clock_speed = new_line.clock_rate;
+		else
+			info->params.clock_speed = 0;
+
+		/* if network interface up, reprogram hardware */
+		if (info->netcount)
+			mgsl_program_hw(info);
+		return 0;
+
+	default:
+		return hdlc_ioctl(dev, ifr, cmd);
+	}
+}
+
+/**
+ * called by network layer when transmit timeout is detected
+ *
+ * dev  pointer to network device structure
+ */
+static void hdlcdev_tx_timeout(struct net_device *dev)
+{
+	struct mgsl_struct *info = dev_to_port(dev);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("hdlcdev_tx_timeout(%s)\n",dev->name);
+
+	stats->tx_errors++;
+	stats->tx_aborted_errors++;
+
+	spin_lock_irqsave(&info->irq_spinlock,flags);
+	usc_stop_transmitter(info);
+	spin_unlock_irqrestore(&info->irq_spinlock,flags);
+
+	netif_wake_queue(dev);
+}
+
+/**
+ * called by device driver when transmit completes
+ * reenable network layer transmit if stopped
+ *
+ * info  pointer to device instance information
+ */
+static void hdlcdev_tx_done(struct mgsl_struct *info)
+{
+	if (netif_queue_stopped(info->netdev))
+		netif_wake_queue(info->netdev);
+}
+
+/**
+ * called by device driver when frame received
+ * pass frame to network layer
+ *
+ * info  pointer to device instance information
+ * buf   pointer to buffer contianing frame data
+ * size  count of data bytes in buf
+ */
+static void hdlcdev_rx(struct mgsl_struct *info, char *buf, int size)
+{
+	struct sk_buff *skb = dev_alloc_skb(size);
+	struct net_device *dev = info->netdev;
+	struct net_device_stats *stats = hdlc_stats(dev);
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("hdlcdev_rx(%s)\n",dev->name);
+
+	if (skb == NULL) {
+		printk(KERN_NOTICE "%s: can't alloc skb, dropping packet\n", dev->name);
+		stats->rx_dropped++;
+		return;
+	}
+
+	memcpy(skb_put(skb, size),buf,size);
+
+	skb->protocol = hdlc_type_trans(skb, info->netdev);
+
+	stats->rx_packets++;
+	stats->rx_bytes += size;
+
+	netif_rx(skb);
+
+	info->netdev->last_rx = jiffies;
+}
+
+/**
+ * called by device driver when adding device instance
+ * do generic HDLC initialization
+ *
+ * info  pointer to device instance information
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_init(struct mgsl_struct *info)
+{
+	int rc;
+	struct net_device *dev;
+	hdlc_device *hdlc;
+
+	/* allocate and initialize network and HDLC layer objects */
+
+	if (!(dev = alloc_hdlcdev(info))) {
+		printk(KERN_ERR "%s:hdlc device allocation failure\n",__FILE__);
+		return -ENOMEM;
+	}
+
+	/* for network layer reporting purposes only */
+	dev->base_addr = info->io_base;
+	dev->irq       = info->irq_level;
+	dev->dma       = info->dma_level;
+
+	/* network layer callbacks and settings */
+	dev->do_ioctl       = hdlcdev_ioctl;
+	dev->open           = hdlcdev_open;
+	dev->stop           = hdlcdev_close;
+	dev->tx_timeout     = hdlcdev_tx_timeout;
+	dev->watchdog_timeo = 10*HZ;
+	dev->tx_queue_len   = 50;
+
+	/* generic HDLC layer callbacks and settings */
+	hdlc         = dev_to_hdlc(dev);
+	hdlc->attach = hdlcdev_attach;
+	hdlc->xmit   = hdlcdev_xmit;
+
+	/* register objects with HDLC layer */
+	if ((rc = register_hdlc_device(dev))) {
+		printk(KERN_WARNING "%s:unable to register hdlc device\n",__FILE__);
+		free_netdev(dev);
+		return rc;
+	}
+
+	info->netdev = dev;
+	return 0;
+}
+
+/**
+ * called by device driver when removing device instance
+ * do generic HDLC cleanup
+ *
+ * info  pointer to device instance information
+ */
+static void hdlcdev_exit(struct mgsl_struct *info)
+{
+	unregister_hdlc_device(info->netdev);
+	free_netdev(info->netdev);
+	info->netdev = NULL;
+}
+
+#endif /* CONFIG_HDLC */
+
+
+static int __devinit synclink_init_one (struct pci_dev *dev,
+					const struct pci_device_id *ent)
+{
+	struct mgsl_struct *info;
+
+	if (pci_enable_device(dev)) {
+		printk("error enabling pci device %p\n", dev);
+		return -EIO;
+	}
+
+	if (!(info = mgsl_allocate_device())) {
+		printk("can't allocate device instance data.\n");
+		return -EIO;
+	}
+
+        /* Copy user configuration info to device instance data */
+		
+	info->io_base = pci_resource_start(dev, 2);
+	info->irq_level = dev->irq;
+	info->phys_memory_base = pci_resource_start(dev, 3);
+				
+        /* Because veremap only works on page boundaries we must map
+	 * a larger area than is actually implemented for the LCR
+	 * memory range. We map a full page starting at the page boundary.
+	 */
+	info->phys_lcr_base = pci_resource_start(dev, 0);
+	info->lcr_offset    = info->phys_lcr_base & (PAGE_SIZE-1);
+	info->phys_lcr_base &= ~(PAGE_SIZE-1);
+				
+	info->bus_type = MGSL_BUS_TYPE_PCI;
+	info->io_addr_size = 8;
+	info->irq_flags = SA_SHIRQ;
+
+	if (dev->device == 0x0210) {
+		/* Version 1 PCI9030 based universal PCI adapter */
+		info->misc_ctrl_value = 0x007c4080;
+		info->hw_version = 1;
+	} else {
+		/* Version 0 PCI9050 based 5V PCI adapter
+		 * A PCI9050 bug prevents reading LCR registers if 
+		 * LCR base address bit 7 is set. Maintain shadow
+		 * value so we can write to LCR misc control reg.
+		 */
+		info->misc_ctrl_value = 0x087e4546;
+		info->hw_version = 0;
+	}
+				
+	mgsl_add_device(info);
+
+	return 0;
+}
+
+static void __devexit synclink_remove_one (struct pci_dev *dev)
+{
+}
+
diff --git a/drivers/char/synclinkmp.c b/drivers/char/synclinkmp.c
new file mode 100644
index 0000000..ec949e4
--- /dev/null
+++ b/drivers/char/synclinkmp.c
@@ -0,0 +1,5671 @@
+/*
+ * $Id: synclinkmp.c,v 4.34 2005/03/04 15:07:10 paulkf Exp $
+ *
+ * Device driver for Microgate SyncLink Multiport
+ * high speed multiprotocol serial adapter.
+ *
+ * written by Paul Fulghum for Microgate Corporation
+ * paulkf@microgate.com
+ *
+ * Microgate and SyncLink are trademarks of Microgate Corporation
+ *
+ * Derived from serial.c written by Theodore Ts'o and Linus Torvalds
+ * This code is released under the GNU General Public License (GPL)
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define VERSION(ver,rel,seq) (((ver)<<16) | ((rel)<<8) | (seq))
+#if defined(__i386__)
+#  define BREAKPOINT() asm("   int $3");
+#else
+#  define BREAKPOINT() { }
+#endif
+
+#define MAX_DEVICES 12
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial.h>
+#include <linux/major.h>
+#include <linux/string.h>
+#include <linux/fcntl.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <asm/serial.h>
+#include <linux/delay.h>
+#include <linux/ioctl.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/dma.h>
+#include <linux/bitops.h>
+#include <asm/types.h>
+#include <linux/termios.h>
+#include <linux/workqueue.h>
+#include <linux/hdlc.h>
+
+#ifdef CONFIG_HDLC_MODULE
+#define CONFIG_HDLC 1
+#endif
+
+#define GET_USER(error,value,addr) error = get_user(value,addr)
+#define COPY_FROM_USER(error,dest,src,size) error = copy_from_user(dest,src,size) ? -EFAULT : 0
+#define PUT_USER(error,value,addr) error = put_user(value,addr)
+#define COPY_TO_USER(error,dest,src,size) error = copy_to_user(dest,src,size) ? -EFAULT : 0
+
+#include <asm/uaccess.h>
+
+#include "linux/synclink.h"
+
+static MGSL_PARAMS default_params = {
+	MGSL_MODE_HDLC,			/* unsigned long mode */
+	0,				/* unsigned char loopback; */
+	HDLC_FLAG_UNDERRUN_ABORT15,	/* unsigned short flags; */
+	HDLC_ENCODING_NRZI_SPACE,	/* unsigned char encoding; */
+	0,				/* unsigned long clock_speed; */
+	0xff,				/* unsigned char addr_filter; */
+	HDLC_CRC_16_CCITT,		/* unsigned short crc_type; */
+	HDLC_PREAMBLE_LENGTH_8BITS,	/* unsigned char preamble_length; */
+	HDLC_PREAMBLE_PATTERN_NONE,	/* unsigned char preamble; */
+	9600,				/* unsigned long data_rate; */
+	8,				/* unsigned char data_bits; */
+	1,				/* unsigned char stop_bits; */
+	ASYNC_PARITY_NONE		/* unsigned char parity; */
+};
+
+/* size in bytes of DMA data buffers */
+#define SCABUFSIZE 	1024
+#define SCA_MEM_SIZE	0x40000
+#define SCA_BASE_SIZE   512
+#define SCA_REG_SIZE    16
+#define SCA_MAX_PORTS   4
+#define SCAMAXDESC 	128
+
+#define	BUFFERLISTSIZE	4096
+
+/* SCA-I style DMA buffer descriptor */
+typedef struct _SCADESC
+{
+	u16	next;		/* lower l6 bits of next descriptor addr */
+	u16	buf_ptr;	/* lower 16 bits of buffer addr */
+	u8	buf_base;	/* upper 8 bits of buffer addr */
+	u8	pad1;
+	u16	length;		/* length of buffer */
+	u8	status;		/* status of buffer */
+	u8	pad2;
+} SCADESC, *PSCADESC;
+
+typedef struct _SCADESC_EX
+{
+	/* device driver bookkeeping section */
+	char 	*virt_addr;    	/* virtual address of data buffer */
+	u16	phys_entry;	/* lower 16-bits of physical address of this descriptor */
+} SCADESC_EX, *PSCADESC_EX;
+
+/* The queue of BH actions to be performed */
+
+#define BH_RECEIVE  1
+#define BH_TRANSMIT 2
+#define BH_STATUS   4
+
+#define IO_PIN_SHUTDOWN_LIMIT 100
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+struct	_input_signal_events {
+	int	ri_up;
+	int	ri_down;
+	int	dsr_up;
+	int	dsr_down;
+	int	dcd_up;
+	int	dcd_down;
+	int	cts_up;
+	int	cts_down;
+};
+
+/*
+ * Device instance data structure
+ */
+typedef struct _synclinkmp_info {
+	void *if_ptr;				/* General purpose pointer (used by SPPP) */
+	int			magic;
+	int			flags;
+	int			count;		/* count of opens */
+	int			line;
+	unsigned short		close_delay;
+	unsigned short		closing_wait;	/* time to wait before closing */
+
+	struct mgsl_icount	icount;
+
+	struct tty_struct 	*tty;
+	int			timeout;
+	int			x_char;		/* xon/xoff character */
+	int			blocked_open;	/* # of blocked opens */
+	u16			read_status_mask1;  /* break detection (SR1 indications) */
+	u16			read_status_mask2;  /* parity/framing/overun (SR2 indications) */
+	unsigned char 		ignore_status_mask1;  /* break detection (SR1 indications) */
+	unsigned char		ignore_status_mask2;  /* parity/framing/overun (SR2 indications) */
+	unsigned char 		*tx_buf;
+	int			tx_put;
+	int			tx_get;
+	int			tx_count;
+
+	wait_queue_head_t	open_wait;
+	wait_queue_head_t	close_wait;
+
+	wait_queue_head_t	status_event_wait_q;
+	wait_queue_head_t	event_wait_q;
+	struct timer_list	tx_timer;	/* HDLC transmit timeout timer */
+	struct _synclinkmp_info	*next_device;	/* device list link */
+	struct timer_list	status_timer;	/* input signal status check timer */
+
+	spinlock_t lock;		/* spinlock for synchronizing with ISR */
+	struct work_struct task;	 		/* task structure for scheduling bh */
+
+	u32 max_frame_size;			/* as set by device config */
+
+	u32 pending_bh;
+
+	int bh_running;				/* Protection from multiple */
+	int isr_overflow;
+	int bh_requested;
+
+	int dcd_chkcount;			/* check counts to prevent */
+	int cts_chkcount;			/* too many IRQs if a signal */
+	int dsr_chkcount;			/* is floating */
+	int ri_chkcount;
+
+	char *buffer_list;			/* virtual address of Rx & Tx buffer lists */
+	unsigned long buffer_list_phys;
+
+	unsigned int rx_buf_count;		/* count of total allocated Rx buffers */
+	SCADESC *rx_buf_list;   		/* list of receive buffer entries */
+	SCADESC_EX rx_buf_list_ex[SCAMAXDESC]; /* list of receive buffer entries */
+	unsigned int current_rx_buf;
+
+	unsigned int tx_buf_count;		/* count of total allocated Tx buffers */
+	SCADESC *tx_buf_list;		/* list of transmit buffer entries */
+	SCADESC_EX tx_buf_list_ex[SCAMAXDESC]; /* list of transmit buffer entries */
+	unsigned int last_tx_buf;
+
+	unsigned char *tmp_rx_buf;
+	unsigned int tmp_rx_buf_count;
+
+	int rx_enabled;
+	int rx_overflow;
+
+	int tx_enabled;
+	int tx_active;
+	u32 idle_mode;
+
+	unsigned char ie0_value;
+	unsigned char ie1_value;
+	unsigned char ie2_value;
+	unsigned char ctrlreg_value;
+	unsigned char old_signals;
+
+	char device_name[25];			/* device instance name */
+
+	int port_count;
+	int adapter_num;
+	int port_num;
+
+	struct _synclinkmp_info *port_array[SCA_MAX_PORTS];
+
+	unsigned int bus_type;			/* expansion bus type (ISA,EISA,PCI) */
+
+	unsigned int irq_level;			/* interrupt level */
+	unsigned long irq_flags;
+	int irq_requested;			/* nonzero if IRQ requested */
+
+	MGSL_PARAMS params;			/* communications parameters */
+
+	unsigned char serial_signals;		/* current serial signal states */
+
+	int irq_occurred;			/* for diagnostics use */
+	unsigned int init_error;		/* Initialization startup error */
+
+	u32 last_mem_alloc;
+	unsigned char* memory_base;		/* shared memory address (PCI only) */
+	u32 phys_memory_base;
+    	int shared_mem_requested;
+
+	unsigned char* sca_base;		/* HD64570 SCA Memory address */
+	u32 phys_sca_base;
+	u32 sca_offset;
+	int sca_base_requested;
+
+	unsigned char* lcr_base;		/* local config registers (PCI only) */
+	u32 phys_lcr_base;
+	u32 lcr_offset;
+	int lcr_mem_requested;
+
+	unsigned char* statctrl_base;		/* status/control register memory */
+	u32 phys_statctrl_base;
+	u32 statctrl_offset;
+	int sca_statctrl_requested;
+
+	u32 misc_ctrl_value;
+	char flag_buf[MAX_ASYNC_BUFFER_SIZE];
+	char char_buf[MAX_ASYNC_BUFFER_SIZE];
+	BOOLEAN drop_rts_on_tx_done;
+
+	struct	_input_signal_events	input_signal_events;
+
+	/* SPPP/Cisco HDLC device parts */
+	int netcount;
+	int dosyncppp;
+	spinlock_t netlock;
+
+#ifdef CONFIG_HDLC
+	struct net_device *netdev;
+#endif
+
+} SLMP_INFO;
+
+#define MGSL_MAGIC 0x5401
+
+/*
+ * define serial signal status change macros
+ */
+#define	MISCSTATUS_DCD_LATCHED	(SerialSignal_DCD<<8)	/* indicates change in DCD */
+#define MISCSTATUS_RI_LATCHED	(SerialSignal_RI<<8)	/* indicates change in RI */
+#define MISCSTATUS_CTS_LATCHED	(SerialSignal_CTS<<8)	/* indicates change in CTS */
+#define MISCSTATUS_DSR_LATCHED	(SerialSignal_DSR<<8)	/* change in DSR */
+
+/* Common Register macros */
+#define LPR	0x00
+#define PABR0	0x02
+#define PABR1	0x03
+#define WCRL	0x04
+#define WCRM	0x05
+#define WCRH	0x06
+#define DPCR	0x08
+#define DMER	0x09
+#define ISR0	0x10
+#define ISR1	0x11
+#define ISR2	0x12
+#define IER0	0x14
+#define IER1	0x15
+#define IER2	0x16
+#define ITCR	0x18
+#define INTVR 	0x1a
+#define IMVR	0x1c
+
+/* MSCI Register macros */
+#define TRB	0x20
+#define TRBL	0x20
+#define TRBH	0x21
+#define SR0	0x22
+#define SR1	0x23
+#define SR2	0x24
+#define SR3	0x25
+#define FST	0x26
+#define IE0	0x28
+#define IE1	0x29
+#define IE2	0x2a
+#define FIE	0x2b
+#define CMD	0x2c
+#define MD0	0x2e
+#define MD1	0x2f
+#define MD2	0x30
+#define CTL	0x31
+#define SA0	0x32
+#define SA1	0x33
+#define IDL	0x34
+#define TMC	0x35
+#define RXS	0x36
+#define TXS	0x37
+#define TRC0	0x38
+#define TRC1	0x39
+#define RRC	0x3a
+#define CST0	0x3c
+#define CST1	0x3d
+
+/* Timer Register Macros */
+#define TCNT	0x60
+#define TCNTL	0x60
+#define TCNTH	0x61
+#define TCONR	0x62
+#define TCONRL	0x62
+#define TCONRH	0x63
+#define TMCS	0x64
+#define TEPR	0x65
+
+/* DMA Controller Register macros */
+#define DARL	0x80
+#define DARH	0x81
+#define DARB	0x82
+#define BAR	0x80
+#define BARL	0x80
+#define BARH	0x81
+#define BARB	0x82
+#define SAR	0x84
+#define SARL	0x84
+#define SARH	0x85
+#define SARB	0x86
+#define CPB	0x86
+#define CDA	0x88
+#define CDAL	0x88
+#define CDAH	0x89
+#define EDA	0x8a
+#define EDAL	0x8a
+#define EDAH	0x8b
+#define BFL	0x8c
+#define BFLL	0x8c
+#define BFLH	0x8d
+#define BCR	0x8e
+#define BCRL	0x8e
+#define BCRH	0x8f
+#define DSR	0x90
+#define DMR	0x91
+#define FCT	0x93
+#define DIR	0x94
+#define DCMD	0x95
+
+/* combine with timer or DMA register address */
+#define TIMER0	0x00
+#define TIMER1	0x08
+#define TIMER2	0x10
+#define TIMER3	0x18
+#define RXDMA 	0x00
+#define TXDMA 	0x20
+
+/* SCA Command Codes */
+#define NOOP		0x00
+#define TXRESET		0x01
+#define TXENABLE	0x02
+#define TXDISABLE	0x03
+#define TXCRCINIT	0x04
+#define TXCRCEXCL	0x05
+#define TXEOM		0x06
+#define TXABORT		0x07
+#define MPON		0x08
+#define TXBUFCLR	0x09
+#define RXRESET		0x11
+#define RXENABLE	0x12
+#define RXDISABLE	0x13
+#define RXCRCINIT	0x14
+#define RXREJECT	0x15
+#define SEARCHMP	0x16
+#define RXCRCEXCL	0x17
+#define RXCRCCALC	0x18
+#define CHRESET		0x21
+#define HUNT		0x31
+
+/* DMA command codes */
+#define SWABORT		0x01
+#define FEICLEAR	0x02
+
+/* IE0 */
+#define TXINTE 		BIT7
+#define RXINTE 		BIT6
+#define TXRDYE 		BIT1
+#define RXRDYE 		BIT0
+
+/* IE1 & SR1 */
+#define UDRN   	BIT7
+#define IDLE   	BIT6
+#define SYNCD  	BIT4
+#define FLGD   	BIT4
+#define CCTS   	BIT3
+#define CDCD   	BIT2
+#define BRKD   	BIT1
+#define ABTD   	BIT1
+#define GAPD   	BIT1
+#define BRKE   	BIT0
+#define IDLD	BIT0
+
+/* IE2 & SR2 */
+#define EOM	BIT7
+#define PMP	BIT6
+#define SHRT	BIT6
+#define PE	BIT5
+#define ABT	BIT5
+#define FRME	BIT4
+#define RBIT	BIT4
+#define OVRN	BIT3
+#define CRCE	BIT2
+
+
+/*
+ * Global linked list of SyncLink devices
+ */
+static SLMP_INFO *synclinkmp_device_list = NULL;
+static int synclinkmp_adapter_count = -1;
+static int synclinkmp_device_count = 0;
+
+/*
+ * Set this param to non-zero to load eax with the
+ * .text section address and breakpoint on module load.
+ * This is useful for use with gdb and add-symbol-file command.
+ */
+static int break_on_load=0;
+
+/*
+ * Driver major number, defaults to zero to get auto
+ * assigned major number. May be forced as module parameter.
+ */
+static int ttymajor=0;
+
+/*
+ * Array of user specified options for ISA adapters.
+ */
+static int debug_level = 0;
+static int maxframe[MAX_DEVICES] = {0,};
+static int dosyncppp[MAX_DEVICES] = {0,};
+
+module_param(break_on_load, bool, 0);
+module_param(ttymajor, int, 0);
+module_param(debug_level, int, 0);
+module_param_array(maxframe, int, NULL, 0);
+module_param_array(dosyncppp, int, NULL, 0);
+
+static char *driver_name = "SyncLink MultiPort driver";
+static char *driver_version = "$Revision: 4.34 $";
+
+static int synclinkmp_init_one(struct pci_dev *dev,const struct pci_device_id *ent);
+static void synclinkmp_remove_one(struct pci_dev *dev);
+
+static struct pci_device_id synclinkmp_pci_tbl[] = {
+	{ PCI_VENDOR_ID_MICROGATE, PCI_DEVICE_ID_MICROGATE_SCA, PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, synclinkmp_pci_tbl);
+
+MODULE_LICENSE("GPL");
+
+static struct pci_driver synclinkmp_pci_driver = {
+	.name		= "synclinkmp",
+	.id_table	= synclinkmp_pci_tbl,
+	.probe		= synclinkmp_init_one,
+	.remove		= __devexit_p(synclinkmp_remove_one),
+};
+
+
+static struct tty_driver *serial_driver;
+
+/* number of characters left in xmit buffer before we ask for more */
+#define WAKEUP_CHARS 256
+
+
+/* tty callbacks */
+
+static int  open(struct tty_struct *tty, struct file * filp);
+static void close(struct tty_struct *tty, struct file * filp);
+static void hangup(struct tty_struct *tty);
+static void set_termios(struct tty_struct *tty, struct termios *old_termios);
+
+static int  write(struct tty_struct *tty, const unsigned char *buf, int count);
+static void put_char(struct tty_struct *tty, unsigned char ch);
+static void send_xchar(struct tty_struct *tty, char ch);
+static void wait_until_sent(struct tty_struct *tty, int timeout);
+static int  write_room(struct tty_struct *tty);
+static void flush_chars(struct tty_struct *tty);
+static void flush_buffer(struct tty_struct *tty);
+static void tx_hold(struct tty_struct *tty);
+static void tx_release(struct tty_struct *tty);
+
+static int  ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg);
+static int  read_proc(char *page, char **start, off_t off, int count,int *eof, void *data);
+static int  chars_in_buffer(struct tty_struct *tty);
+static void throttle(struct tty_struct * tty);
+static void unthrottle(struct tty_struct * tty);
+static void set_break(struct tty_struct *tty, int break_state);
+
+#ifdef CONFIG_HDLC
+#define dev_to_port(D) (dev_to_hdlc(D)->priv)
+static void hdlcdev_tx_done(SLMP_INFO *info);
+static void hdlcdev_rx(SLMP_INFO *info, char *buf, int size);
+static int  hdlcdev_init(SLMP_INFO *info);
+static void hdlcdev_exit(SLMP_INFO *info);
+#endif
+
+/* ioctl handlers */
+
+static int  get_stats(SLMP_INFO *info, struct mgsl_icount __user *user_icount);
+static int  get_params(SLMP_INFO *info, MGSL_PARAMS __user *params);
+static int  set_params(SLMP_INFO *info, MGSL_PARAMS __user *params);
+static int  get_txidle(SLMP_INFO *info, int __user *idle_mode);
+static int  set_txidle(SLMP_INFO *info, int idle_mode);
+static int  tx_enable(SLMP_INFO *info, int enable);
+static int  tx_abort(SLMP_INFO *info);
+static int  rx_enable(SLMP_INFO *info, int enable);
+static int  map_status(int signals);
+static int  modem_input_wait(SLMP_INFO *info,int arg);
+static int  wait_mgsl_event(SLMP_INFO *info, int __user *mask_ptr);
+static int  tiocmget(struct tty_struct *tty, struct file *file);
+static int  tiocmset(struct tty_struct *tty, struct file *file,
+		     unsigned int set, unsigned int clear);
+static void set_break(struct tty_struct *tty, int break_state);
+
+static void add_device(SLMP_INFO *info);
+static void device_init(int adapter_num, struct pci_dev *pdev);
+static int  claim_resources(SLMP_INFO *info);
+static void release_resources(SLMP_INFO *info);
+
+static int  startup(SLMP_INFO *info);
+static int  block_til_ready(struct tty_struct *tty, struct file * filp,SLMP_INFO *info);
+static void shutdown(SLMP_INFO *info);
+static void program_hw(SLMP_INFO *info);
+static void change_params(SLMP_INFO *info);
+
+static int  init_adapter(SLMP_INFO *info);
+static int  register_test(SLMP_INFO *info);
+static int  irq_test(SLMP_INFO *info);
+static int  loopback_test(SLMP_INFO *info);
+static int  adapter_test(SLMP_INFO *info);
+static int  memory_test(SLMP_INFO *info);
+
+static void reset_adapter(SLMP_INFO *info);
+static void reset_port(SLMP_INFO *info);
+static void async_mode(SLMP_INFO *info);
+static void hdlc_mode(SLMP_INFO *info);
+
+static void rx_stop(SLMP_INFO *info);
+static void rx_start(SLMP_INFO *info);
+static void rx_reset_buffers(SLMP_INFO *info);
+static void rx_free_frame_buffers(SLMP_INFO *info, unsigned int first, unsigned int last);
+static int  rx_get_frame(SLMP_INFO *info);
+
+static void tx_start(SLMP_INFO *info);
+static void tx_stop(SLMP_INFO *info);
+static void tx_load_fifo(SLMP_INFO *info);
+static void tx_set_idle(SLMP_INFO *info);
+static void tx_load_dma_buffer(SLMP_INFO *info, const char *buf, unsigned int count);
+
+static void get_signals(SLMP_INFO *info);
+static void set_signals(SLMP_INFO *info);
+static void enable_loopback(SLMP_INFO *info, int enable);
+static void set_rate(SLMP_INFO *info, u32 data_rate);
+
+static int  bh_action(SLMP_INFO *info);
+static void bh_handler(void* Context);
+static void bh_receive(SLMP_INFO *info);
+static void bh_transmit(SLMP_INFO *info);
+static void bh_status(SLMP_INFO *info);
+static void isr_timer(SLMP_INFO *info);
+static void isr_rxint(SLMP_INFO *info);
+static void isr_rxrdy(SLMP_INFO *info);
+static void isr_txint(SLMP_INFO *info);
+static void isr_txrdy(SLMP_INFO *info);
+static void isr_rxdmaok(SLMP_INFO *info);
+static void isr_rxdmaerror(SLMP_INFO *info);
+static void isr_txdmaok(SLMP_INFO *info);
+static void isr_txdmaerror(SLMP_INFO *info);
+static void isr_io_pin(SLMP_INFO *info, u16 status);
+
+static int  alloc_dma_bufs(SLMP_INFO *info);
+static void free_dma_bufs(SLMP_INFO *info);
+static int  alloc_buf_list(SLMP_INFO *info);
+static int  alloc_frame_bufs(SLMP_INFO *info, SCADESC *list, SCADESC_EX *list_ex,int count);
+static int  alloc_tmp_rx_buf(SLMP_INFO *info);
+static void free_tmp_rx_buf(SLMP_INFO *info);
+
+static void load_pci_memory(SLMP_INFO *info, char* dest, const char* src, unsigned short count);
+static void trace_block(SLMP_INFO *info, const char* data, int count, int xmit);
+static void tx_timeout(unsigned long context);
+static void status_timeout(unsigned long context);
+
+static unsigned char read_reg(SLMP_INFO *info, unsigned char addr);
+static void write_reg(SLMP_INFO *info, unsigned char addr, unsigned char val);
+static u16 read_reg16(SLMP_INFO *info, unsigned char addr);
+static void write_reg16(SLMP_INFO *info, unsigned char addr, u16 val);
+static unsigned char read_status_reg(SLMP_INFO * info);
+static void write_control_reg(SLMP_INFO * info);
+
+
+static unsigned char rx_active_fifo_level = 16;	// rx request FIFO activation level in bytes
+static unsigned char tx_active_fifo_level = 16;	// tx request FIFO activation level in bytes
+static unsigned char tx_negate_fifo_level = 32;	// tx request FIFO negation level in bytes
+
+static u32 misc_ctrl_value = 0x007e4040;
+static u32 lcr1_brdr_value = 0x00800029;
+
+static u32 read_ahead_count = 8;
+
+/* DPCR, DMA Priority Control
+ *
+ * 07..05  Not used, must be 0
+ * 04      BRC, bus release condition: 0=all transfers complete
+ *              1=release after 1 xfer on all channels
+ * 03      CCC, channel change condition: 0=every cycle
+ *              1=after each channel completes all xfers
+ * 02..00  PR<2..0>, priority 100=round robin
+ *
+ * 00000100 = 0x00
+ */
+static unsigned char dma_priority = 0x04;
+
+// Number of bytes that can be written to shared RAM
+// in a single write operation
+static u32 sca_pci_load_interval = 64;
+
+/*
+ * 1st function defined in .text section. Calling this function in
+ * init_module() followed by a breakpoint allows a remote debugger
+ * (gdb) to get the .text address for the add-symbol-file command.
+ * This allows remote debugging of dynamically loadable modules.
+ */
+static void* synclinkmp_get_text_ptr(void);
+static void* synclinkmp_get_text_ptr(void) {return synclinkmp_get_text_ptr;}
+
+static inline int sanity_check(SLMP_INFO *info,
+			       char *name, const char *routine)
+{
+#ifdef SANITY_CHECK
+	static const char *badmagic =
+		"Warning: bad magic number for synclinkmp_struct (%s) in %s\n";
+	static const char *badinfo =
+		"Warning: null synclinkmp_struct for (%s) in %s\n";
+
+	if (!info) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (info->magic != MGSL_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+#else
+	if (!info)
+		return 1;
+#endif
+	return 0;
+}
+
+/**
+ * line discipline callback wrappers
+ *
+ * The wrappers maintain line discipline references
+ * while calling into the line discipline.
+ *
+ * ldisc_receive_buf  - pass receive data to line discipline
+ */
+
+static void ldisc_receive_buf(struct tty_struct *tty,
+			      const __u8 *data, char *flags, int count)
+{
+	struct tty_ldisc *ld;
+	if (!tty)
+		return;
+	ld = tty_ldisc_ref(tty);
+	if (ld) {
+		if (ld->receive_buf)
+			ld->receive_buf(tty, data, flags, count);
+		tty_ldisc_deref(ld);
+	}
+}
+
+/* tty callbacks */
+
+/* Called when a port is opened.  Init and enable port.
+ */
+static int open(struct tty_struct *tty, struct file *filp)
+{
+	SLMP_INFO *info;
+	int retval, line;
+	unsigned long flags;
+
+	line = tty->index;
+	if ((line < 0) || (line >= synclinkmp_device_count)) {
+		printk("%s(%d): open with invalid line #%d.\n",
+			__FILE__,__LINE__,line);
+		return -ENODEV;
+	}
+
+	info = synclinkmp_device_list;
+	while(info && info->line != line)
+		info = info->next_device;
+	if (sanity_check(info, tty->name, "open"))
+		return -ENODEV;
+	if ( info->init_error ) {
+		printk("%s(%d):%s device is not allocated, init error=%d\n",
+			__FILE__,__LINE__,info->device_name,info->init_error);
+		return -ENODEV;
+	}
+
+	tty->driver_data = info;
+	info->tty = tty;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s open(), old ref count = %d\n",
+			 __FILE__,__LINE__,tty->driver->name, info->count);
+
+	/* If port is closing, signal caller to try again */
+	if (tty_hung_up_p(filp) || info->flags & ASYNC_CLOSING){
+		if (info->flags & ASYNC_CLOSING)
+			interruptible_sleep_on(&info->close_wait);
+		retval = ((info->flags & ASYNC_HUP_NOTIFY) ?
+			-EAGAIN : -ERESTARTSYS);
+		goto cleanup;
+	}
+
+	info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+	spin_lock_irqsave(&info->netlock, flags);
+	if (info->netcount) {
+		retval = -EBUSY;
+		spin_unlock_irqrestore(&info->netlock, flags);
+		goto cleanup;
+	}
+	info->count++;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	if (info->count == 1) {
+		/* 1st open on this device, init hardware */
+		retval = startup(info);
+		if (retval < 0)
+			goto cleanup;
+	}
+
+	retval = block_til_ready(tty, filp, info);
+	if (retval) {
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):%s block_til_ready() returned %d\n",
+				 __FILE__,__LINE__, info->device_name, retval);
+		goto cleanup;
+	}
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s open() success\n",
+			 __FILE__,__LINE__, info->device_name);
+	retval = 0;
+
+cleanup:
+	if (retval) {
+		if (tty->count == 1)
+			info->tty = NULL; /* tty layer will release tty struct */
+		if(info->count)
+			info->count--;
+	}
+
+	return retval;
+}
+
+/* Called when port is closed. Wait for remaining data to be
+ * sent. Disable port and free resources.
+ */
+static void close(struct tty_struct *tty, struct file *filp)
+{
+	SLMP_INFO * info = (SLMP_INFO *)tty->driver_data;
+
+	if (sanity_check(info, tty->name, "close"))
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s close() entry, count=%d\n",
+			 __FILE__,__LINE__, info->device_name, info->count);
+
+	if (!info->count)
+		return;
+
+	if (tty_hung_up_p(filp))
+		goto cleanup;
+
+	if ((tty->count == 1) && (info->count != 1)) {
+		/*
+		 * tty->count is 1 and the tty structure will be freed.
+		 * info->count should be one in this case.
+		 * if it's not, correct it so that the port is shutdown.
+		 */
+		printk("%s(%d):%s close: bad refcount; tty->count is 1, "
+		       "info->count is %d\n",
+			 __FILE__,__LINE__, info->device_name, info->count);
+		info->count = 1;
+	}
+
+	info->count--;
+
+	/* if at least one open remaining, leave hardware active */
+	if (info->count)
+		goto cleanup;
+
+	info->flags |= ASYNC_CLOSING;
+
+	/* set tty->closing to notify line discipline to
+	 * only process XON/XOFF characters. Only the N_TTY
+	 * discipline appears to use this (ppp does not).
+	 */
+	tty->closing = 1;
+
+	/* wait for transmit data to clear all layers */
+
+	if (info->closing_wait != ASYNC_CLOSING_WAIT_NONE) {
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):%s close() calling tty_wait_until_sent\n",
+				 __FILE__,__LINE__, info->device_name );
+		tty_wait_until_sent(tty, info->closing_wait);
+	}
+
+ 	if (info->flags & ASYNC_INITIALIZED)
+ 		wait_until_sent(tty, info->timeout);
+
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+
+	tty_ldisc_flush(tty);
+
+	shutdown(info);
+
+	tty->closing = 0;
+	info->tty = NULL;
+
+	if (info->blocked_open) {
+		if (info->close_delay) {
+			msleep_interruptible(jiffies_to_msecs(info->close_delay));
+		}
+		wake_up_interruptible(&info->open_wait);
+	}
+
+	info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+
+	wake_up_interruptible(&info->close_wait);
+
+cleanup:
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s close() exit, count=%d\n", __FILE__,__LINE__,
+			tty->driver->name, info->count);
+}
+
+/* Called by tty_hangup() when a hangup is signaled.
+ * This is the same as closing all open descriptors for the port.
+ */
+static void hangup(struct tty_struct *tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s hangup()\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (sanity_check(info, tty->name, "hangup"))
+		return;
+
+	flush_buffer(tty);
+	shutdown(info);
+
+	info->count = 0;
+	info->flags &= ~ASYNC_NORMAL_ACTIVE;
+	info->tty = NULL;
+
+	wake_up_interruptible(&info->open_wait);
+}
+
+/* Set new termios settings
+ */
+static void set_termios(struct tty_struct *tty, struct termios *old_termios)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s set_termios()\n", __FILE__,__LINE__,
+			tty->driver->name );
+
+	/* just return if nothing has changed */
+	if ((tty->termios->c_cflag == old_termios->c_cflag)
+	    && (RELEVANT_IFLAG(tty->termios->c_iflag)
+		== RELEVANT_IFLAG(old_termios->c_iflag)))
+	  return;
+
+	change_params(info);
+
+	/* Handle transition to B0 status */
+	if (old_termios->c_cflag & CBAUD &&
+	    !(tty->termios->c_cflag & CBAUD)) {
+		info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+		spin_lock_irqsave(&info->lock,flags);
+	 	set_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+
+	/* Handle transition away from B0 status */
+	if (!(old_termios->c_cflag & CBAUD) &&
+	    tty->termios->c_cflag & CBAUD) {
+		info->serial_signals |= SerialSignal_DTR;
+ 		if (!(tty->termios->c_cflag & CRTSCTS) ||
+ 		    !test_bit(TTY_THROTTLED, &tty->flags)) {
+			info->serial_signals |= SerialSignal_RTS;
+ 		}
+		spin_lock_irqsave(&info->lock,flags);
+	 	set_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+
+	/* Handle turning off CRTSCTS */
+	if (old_termios->c_cflag & CRTSCTS &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {
+		tty->hw_stopped = 0;
+		tx_release(tty);
+	}
+}
+
+/* Send a block of data
+ *
+ * Arguments:
+ *
+ * 	tty		pointer to tty information structure
+ * 	buf		pointer to buffer containing send data
+ * 	count		size of send data in bytes
+ *
+ * Return Value:	number of characters written
+ */
+static int write(struct tty_struct *tty,
+		 const unsigned char *buf, int count)
+{
+	int	c, ret = 0;
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s write() count=%d\n",
+		       __FILE__,__LINE__,info->device_name,count);
+
+	if (sanity_check(info, tty->name, "write"))
+		goto cleanup;
+
+	if (!tty || !info->tx_buf)
+		goto cleanup;
+
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		if (count > info->max_frame_size) {
+			ret = -EIO;
+			goto cleanup;
+		}
+		if (info->tx_active)
+			goto cleanup;
+		if (info->tx_count) {
+			/* send accumulated data from send_char() calls */
+			/* as frame and wait before accepting more data. */
+			tx_load_dma_buffer(info, info->tx_buf, info->tx_count);
+			goto start;
+		}
+		ret = info->tx_count = count;
+		tx_load_dma_buffer(info, buf, count);
+		goto start;
+	}
+
+	for (;;) {
+		c = min_t(int, count,
+			min(info->max_frame_size - info->tx_count - 1,
+			    info->max_frame_size - info->tx_put));
+		if (c <= 0)
+			break;
+			
+		memcpy(info->tx_buf + info->tx_put, buf, c);
+
+		spin_lock_irqsave(&info->lock,flags);
+		info->tx_put += c;
+		if (info->tx_put >= info->max_frame_size)
+			info->tx_put -= info->max_frame_size;
+		info->tx_count += c;
+		spin_unlock_irqrestore(&info->lock,flags);
+
+		buf += c;
+		count -= c;
+		ret += c;
+	}
+
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		if (count) {
+			ret = info->tx_count = 0;
+			goto cleanup;
+		}
+		tx_load_dma_buffer(info, info->tx_buf, info->tx_count);
+	}
+start:
+ 	if (info->tx_count && !tty->stopped && !tty->hw_stopped) {
+		spin_lock_irqsave(&info->lock,flags);
+		if (!info->tx_active)
+		 	tx_start(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+ 	}
+
+cleanup:
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk( "%s(%d):%s write() returning=%d\n",
+			__FILE__,__LINE__,info->device_name,ret);
+	return ret;
+}
+
+/* Add a character to the transmit buffer.
+ */
+static void put_char(struct tty_struct *tty, unsigned char ch)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO ) {
+		printk( "%s(%d):%s put_char(%d)\n",
+			__FILE__,__LINE__,info->device_name,ch);
+	}
+
+	if (sanity_check(info, tty->name, "put_char"))
+		return;
+
+	if (!tty || !info->tx_buf)
+		return;
+
+	spin_lock_irqsave(&info->lock,flags);
+
+	if ( (info->params.mode != MGSL_MODE_HDLC) ||
+	     !info->tx_active ) {
+
+		if (info->tx_count < info->max_frame_size - 1) {
+			info->tx_buf[info->tx_put++] = ch;
+			if (info->tx_put >= info->max_frame_size)
+				info->tx_put -= info->max_frame_size;
+			info->tx_count++;
+		}
+	}
+
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Send a high-priority XON/XOFF character
+ */
+static void send_xchar(struct tty_struct *tty, char ch)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s send_xchar(%d)\n",
+			 __FILE__,__LINE__, info->device_name, ch );
+
+	if (sanity_check(info, tty->name, "send_xchar"))
+		return;
+
+	info->x_char = ch;
+	if (ch) {
+		/* Make sure transmit interrupts are on */
+		spin_lock_irqsave(&info->lock,flags);
+		if (!info->tx_enabled)
+		 	tx_start(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+}
+
+/* Wait until the transmitter is empty.
+ */
+static void wait_until_sent(struct tty_struct *tty, int timeout)
+{
+	SLMP_INFO * info = (SLMP_INFO *)tty->driver_data;
+	unsigned long orig_jiffies, char_time;
+
+	if (!info )
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s wait_until_sent() entry\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (sanity_check(info, tty->name, "wait_until_sent"))
+		return;
+
+	if (!(info->flags & ASYNC_INITIALIZED))
+		goto exit;
+
+	orig_jiffies = jiffies;
+
+	/* Set check interval to 1/5 of estimated time to
+	 * send a character, and make it at least 1. The check
+	 * interval should also be less than the timeout.
+	 * Note: use tight timings here to satisfy the NIST-PCTS.
+	 */
+
+	if ( info->params.data_rate ) {
+	       	char_time = info->timeout/(32 * 5);
+		if (!char_time)
+			char_time++;
+	} else
+		char_time = 1;
+
+	if (timeout)
+		char_time = min_t(unsigned long, char_time, timeout);
+
+	if ( info->params.mode == MGSL_MODE_HDLC ) {
+		while (info->tx_active) {
+			msleep_interruptible(jiffies_to_msecs(char_time));
+			if (signal_pending(current))
+				break;
+			if (timeout && time_after(jiffies, orig_jiffies + timeout))
+				break;
+		}
+	} else {
+		//TODO: determine if there is something similar to USC16C32
+		// 	TXSTATUS_ALL_SENT status
+		while ( info->tx_active && info->tx_enabled) {
+			msleep_interruptible(jiffies_to_msecs(char_time));
+			if (signal_pending(current))
+				break;
+			if (timeout && time_after(jiffies, orig_jiffies + timeout))
+				break;
+		}
+	}
+
+exit:
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s wait_until_sent() exit\n",
+			 __FILE__,__LINE__, info->device_name );
+}
+
+/* Return the count of free bytes in transmit buffer
+ */
+static int write_room(struct tty_struct *tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	int ret;
+
+	if (sanity_check(info, tty->name, "write_room"))
+		return 0;
+
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		ret = (info->tx_active) ? 0 : HDLC_MAX_FRAME_SIZE;
+	} else {
+		ret = info->max_frame_size - info->tx_count - 1;
+		if (ret < 0)
+			ret = 0;
+	}
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s write_room()=%d\n",
+		       __FILE__, __LINE__, info->device_name, ret);
+
+	return ret;
+}
+
+/* enable transmitter and send remaining buffered characters
+ */
+static void flush_chars(struct tty_struct *tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):%s flush_chars() entry tx_count=%d\n",
+			__FILE__,__LINE__,info->device_name,info->tx_count);
+
+	if (sanity_check(info, tty->name, "flush_chars"))
+		return;
+
+	if (info->tx_count <= 0 || tty->stopped || tty->hw_stopped ||
+	    !info->tx_buf)
+		return;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):%s flush_chars() entry, starting transmitter\n",
+			__FILE__,__LINE__,info->device_name );
+
+	spin_lock_irqsave(&info->lock,flags);
+
+	if (!info->tx_active) {
+		if ( (info->params.mode == MGSL_MODE_HDLC) &&
+			info->tx_count ) {
+			/* operating in synchronous (frame oriented) mode */
+			/* copy data from circular tx_buf to */
+			/* transmit DMA buffer. */
+			tx_load_dma_buffer(info,
+				 info->tx_buf,info->tx_count);
+		}
+	 	tx_start(info);
+	}
+
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Discard all data in the send buffer
+ */
+static void flush_buffer(struct tty_struct *tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s flush_buffer() entry\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (sanity_check(info, tty->name, "flush_buffer"))
+		return;
+
+	spin_lock_irqsave(&info->lock,flags);
+	info->tx_count = info->tx_put = info->tx_get = 0;
+	del_timer(&info->tx_timer);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	wake_up_interruptible(&tty->write_wait);
+	tty_wakeup(tty);
+}
+
+/* throttle (stop) transmitter
+ */
+static void tx_hold(struct tty_struct *tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (sanity_check(info, tty->name, "tx_hold"))
+		return;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("%s(%d):%s tx_hold()\n",
+			__FILE__,__LINE__,info->device_name);
+
+	spin_lock_irqsave(&info->lock,flags);
+	if (info->tx_enabled)
+	 	tx_stop(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* release (start) transmitter
+ */
+static void tx_release(struct tty_struct *tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (sanity_check(info, tty->name, "tx_release"))
+		return;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("%s(%d):%s tx_release()\n",
+			__FILE__,__LINE__,info->device_name);
+
+	spin_lock_irqsave(&info->lock,flags);
+	if (!info->tx_enabled)
+	 	tx_start(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Service an IOCTL request
+ *
+ * Arguments:
+ *
+ * 	tty	pointer to tty instance data
+ * 	file	pointer to associated file object for device
+ * 	cmd	IOCTL command code
+ * 	arg	command argument/context
+ *
+ * Return Value:	0 if success, otherwise error code
+ */
+static int ioctl(struct tty_struct *tty, struct file *file,
+		 unsigned int cmd, unsigned long arg)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	int error;
+	struct mgsl_icount cnow;	/* kernel counter temps */
+	struct serial_icounter_struct __user *p_cuser;	/* user space */
+	unsigned long flags;
+	void __user *argp = (void __user *)arg;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s ioctl() cmd=%08X\n", __FILE__,__LINE__,
+			info->device_name, cmd );
+
+	if (sanity_check(info, tty->name, "ioctl"))
+		return -ENODEV;
+
+	if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+	    (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+		if (tty->flags & (1 << TTY_IO_ERROR))
+		    return -EIO;
+	}
+
+	switch (cmd) {
+	case MGSL_IOCGPARAMS:
+		return get_params(info, argp);
+	case MGSL_IOCSPARAMS:
+		return set_params(info, argp);
+	case MGSL_IOCGTXIDLE:
+		return get_txidle(info, argp);
+	case MGSL_IOCSTXIDLE:
+		return set_txidle(info, (int)arg);
+	case MGSL_IOCTXENABLE:
+		return tx_enable(info, (int)arg);
+	case MGSL_IOCRXENABLE:
+		return rx_enable(info, (int)arg);
+	case MGSL_IOCTXABORT:
+		return tx_abort(info);
+	case MGSL_IOCGSTATS:
+		return get_stats(info, argp);
+	case MGSL_IOCWAITEVENT:
+		return wait_mgsl_event(info, argp);
+	case MGSL_IOCLOOPTXDONE:
+		return 0; // TODO: Not supported, need to document
+		/* Wait for modem input (DCD,RI,DSR,CTS) change
+		 * as specified by mask in arg (TIOCM_RNG/DSR/CD/CTS)
+		 */
+	case TIOCMIWAIT:
+		return modem_input_wait(info,(int)arg);
+		
+		/*
+		 * Get counter of input serial line interrupts (DCD,RI,DSR,CTS)
+		 * Return: write counters to the user passed counter struct
+		 * NB: both 1->0 and 0->1 transitions are counted except for
+		 *     RI where only 0->1 is counted.
+		 */
+	case TIOCGICOUNT:
+		spin_lock_irqsave(&info->lock,flags);
+		cnow = info->icount;
+		spin_unlock_irqrestore(&info->lock,flags);
+		p_cuser = argp;
+		PUT_USER(error,cnow.cts, &p_cuser->cts);
+		if (error) return error;
+		PUT_USER(error,cnow.dsr, &p_cuser->dsr);
+		if (error) return error;
+		PUT_USER(error,cnow.rng, &p_cuser->rng);
+		if (error) return error;
+		PUT_USER(error,cnow.dcd, &p_cuser->dcd);
+		if (error) return error;
+		PUT_USER(error,cnow.rx, &p_cuser->rx);
+		if (error) return error;
+		PUT_USER(error,cnow.tx, &p_cuser->tx);
+		if (error) return error;
+		PUT_USER(error,cnow.frame, &p_cuser->frame);
+		if (error) return error;
+		PUT_USER(error,cnow.overrun, &p_cuser->overrun);
+		if (error) return error;
+		PUT_USER(error,cnow.parity, &p_cuser->parity);
+		if (error) return error;
+		PUT_USER(error,cnow.brk, &p_cuser->brk);
+		if (error) return error;
+		PUT_USER(error,cnow.buf_overrun, &p_cuser->buf_overrun);
+		if (error) return error;
+		return 0;
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+/*
+ * /proc fs routines....
+ */
+
+static inline int line_info(char *buf, SLMP_INFO *info)
+{
+	char	stat_buf[30];
+	int	ret;
+	unsigned long flags;
+
+	ret = sprintf(buf, "%s: SCABase=%08x Mem=%08X StatusControl=%08x LCR=%08X\n"
+		       "\tIRQ=%d MaxFrameSize=%u\n",
+		info->device_name,
+		info->phys_sca_base,
+		info->phys_memory_base,
+		info->phys_statctrl_base,
+		info->phys_lcr_base,
+		info->irq_level,
+		info->max_frame_size );
+
+	/* output current serial signal states */
+	spin_lock_irqsave(&info->lock,flags);
+ 	get_signals(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	stat_buf[0] = 0;
+	stat_buf[1] = 0;
+	if (info->serial_signals & SerialSignal_RTS)
+		strcat(stat_buf, "|RTS");
+	if (info->serial_signals & SerialSignal_CTS)
+		strcat(stat_buf, "|CTS");
+	if (info->serial_signals & SerialSignal_DTR)
+		strcat(stat_buf, "|DTR");
+	if (info->serial_signals & SerialSignal_DSR)
+		strcat(stat_buf, "|DSR");
+	if (info->serial_signals & SerialSignal_DCD)
+		strcat(stat_buf, "|CD");
+	if (info->serial_signals & SerialSignal_RI)
+		strcat(stat_buf, "|RI");
+
+	if (info->params.mode == MGSL_MODE_HDLC) {
+		ret += sprintf(buf+ret, "\tHDLC txok:%d rxok:%d",
+			      info->icount.txok, info->icount.rxok);
+		if (info->icount.txunder)
+			ret += sprintf(buf+ret, " txunder:%d", info->icount.txunder);
+		if (info->icount.txabort)
+			ret += sprintf(buf+ret, " txabort:%d", info->icount.txabort);
+		if (info->icount.rxshort)
+			ret += sprintf(buf+ret, " rxshort:%d", info->icount.rxshort);
+		if (info->icount.rxlong)
+			ret += sprintf(buf+ret, " rxlong:%d", info->icount.rxlong);
+		if (info->icount.rxover)
+			ret += sprintf(buf+ret, " rxover:%d", info->icount.rxover);
+		if (info->icount.rxcrc)
+			ret += sprintf(buf+ret, " rxlong:%d", info->icount.rxcrc);
+	} else {
+		ret += sprintf(buf+ret, "\tASYNC tx:%d rx:%d",
+			      info->icount.tx, info->icount.rx);
+		if (info->icount.frame)
+			ret += sprintf(buf+ret, " fe:%d", info->icount.frame);
+		if (info->icount.parity)
+			ret += sprintf(buf+ret, " pe:%d", info->icount.parity);
+		if (info->icount.brk)
+			ret += sprintf(buf+ret, " brk:%d", info->icount.brk);
+		if (info->icount.overrun)
+			ret += sprintf(buf+ret, " oe:%d", info->icount.overrun);
+	}
+
+	/* Append serial signal status to end */
+	ret += sprintf(buf+ret, " %s\n", stat_buf+1);
+
+	ret += sprintf(buf+ret, "\ttxactive=%d bh_req=%d bh_run=%d pending_bh=%x\n",
+	 info->tx_active,info->bh_requested,info->bh_running,
+	 info->pending_bh);
+
+	return ret;
+}
+
+/* Called to print information about devices
+ */
+int read_proc(char *page, char **start, off_t off, int count,
+	      int *eof, void *data)
+{
+	int len = 0, l;
+	off_t	begin = 0;
+	SLMP_INFO *info;
+
+	len += sprintf(page, "synclinkmp driver:%s\n", driver_version);
+
+	info = synclinkmp_device_list;
+	while( info ) {
+		l = line_info(page + len, info);
+		len += l;
+		if (len+begin > off+count)
+			goto done;
+		if (len+begin < off) {
+			begin += len;
+			len = 0;
+		}
+		info = info->next_device;
+	}
+
+	*eof = 1;
+done:
+	if (off >= len+begin)
+		return 0;
+	*start = page + (off-begin);
+	return ((count < begin+len-off) ? count : begin+len-off);
+}
+
+/* Return the count of bytes in transmit buffer
+ */
+static int chars_in_buffer(struct tty_struct *tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+
+	if (sanity_check(info, tty->name, "chars_in_buffer"))
+		return 0;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s chars_in_buffer()=%d\n",
+		       __FILE__, __LINE__, info->device_name, info->tx_count);
+
+	return info->tx_count;
+}
+
+/* Signal remote device to throttle send data (our receive data)
+ */
+static void throttle(struct tty_struct * tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s throttle() entry\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (sanity_check(info, tty->name, "throttle"))
+		return;
+
+	if (I_IXOFF(tty))
+		send_xchar(tty, STOP_CHAR(tty));
+
+ 	if (tty->termios->c_cflag & CRTSCTS) {
+		spin_lock_irqsave(&info->lock,flags);
+		info->serial_signals &= ~SerialSignal_RTS;
+	 	set_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+}
+
+/* Signal remote device to stop throttling send data (our receive data)
+ */
+static void unthrottle(struct tty_struct * tty)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s unthrottle() entry\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	if (sanity_check(info, tty->name, "unthrottle"))
+		return;
+
+	if (I_IXOFF(tty)) {
+		if (info->x_char)
+			info->x_char = 0;
+		else
+			send_xchar(tty, START_CHAR(tty));
+	}
+
+ 	if (tty->termios->c_cflag & CRTSCTS) {
+		spin_lock_irqsave(&info->lock,flags);
+		info->serial_signals |= SerialSignal_RTS;
+	 	set_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+}
+
+/* set or clear transmit break condition
+ * break_state	-1=set break condition, 0=clear
+ */
+static void set_break(struct tty_struct *tty, int break_state)
+{
+	unsigned char RegValue;
+	SLMP_INFO * info = (SLMP_INFO *)tty->driver_data;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s set_break(%d)\n",
+			 __FILE__,__LINE__, info->device_name, break_state);
+
+	if (sanity_check(info, tty->name, "set_break"))
+		return;
+
+	spin_lock_irqsave(&info->lock,flags);
+	RegValue = read_reg(info, CTL);
+ 	if (break_state == -1)
+		RegValue |= BIT3;
+	else
+		RegValue &= ~BIT3;
+	write_reg(info, CTL, RegValue);
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+#ifdef CONFIG_HDLC
+
+/**
+ * called by generic HDLC layer when protocol selected (PPP, frame relay, etc.)
+ * set encoding and frame check sequence (FCS) options
+ *
+ * dev       pointer to network device structure
+ * encoding  serial encoding setting
+ * parity    FCS setting
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_attach(struct net_device *dev, unsigned short encoding,
+			  unsigned short parity)
+{
+	SLMP_INFO *info = dev_to_port(dev);
+	unsigned char  new_encoding;
+	unsigned short new_crctype;
+
+	/* return error if TTY interface open */
+	if (info->count)
+		return -EBUSY;
+
+	switch (encoding)
+	{
+	case ENCODING_NRZ:        new_encoding = HDLC_ENCODING_NRZ; break;
+	case ENCODING_NRZI:       new_encoding = HDLC_ENCODING_NRZI_SPACE; break;
+	case ENCODING_FM_MARK:    new_encoding = HDLC_ENCODING_BIPHASE_MARK; break;
+	case ENCODING_FM_SPACE:   new_encoding = HDLC_ENCODING_BIPHASE_SPACE; break;
+	case ENCODING_MANCHESTER: new_encoding = HDLC_ENCODING_BIPHASE_LEVEL; break;
+	default: return -EINVAL;
+	}
+
+	switch (parity)
+	{
+	case PARITY_NONE:            new_crctype = HDLC_CRC_NONE; break;
+	case PARITY_CRC16_PR1_CCITT: new_crctype = HDLC_CRC_16_CCITT; break;
+	case PARITY_CRC32_PR1_CCITT: new_crctype = HDLC_CRC_32_CCITT; break;
+	default: return -EINVAL;
+	}
+
+	info->params.encoding = new_encoding;
+	info->params.crc_type = new_crctype;;
+
+	/* if network interface up, reprogram hardware */
+	if (info->netcount)
+		program_hw(info);
+
+	return 0;
+}
+
+/**
+ * called by generic HDLC layer to send frame
+ *
+ * skb  socket buffer containing HDLC frame
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	SLMP_INFO *info = dev_to_port(dev);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk(KERN_INFO "%s:hdlc_xmit(%s)\n",__FILE__,dev->name);
+
+	/* stop sending until this frame completes */
+	netif_stop_queue(dev);
+
+	/* copy data to device buffers */
+	info->tx_count = skb->len;
+	tx_load_dma_buffer(info, skb->data, skb->len);
+
+	/* update network statistics */
+	stats->tx_packets++;
+	stats->tx_bytes += skb->len;
+
+	/* done with socket buffer, so free it */
+	dev_kfree_skb(skb);
+
+	/* save start time for transmit timeout detection */
+	dev->trans_start = jiffies;
+
+	/* start hardware transmitter if necessary */
+	spin_lock_irqsave(&info->lock,flags);
+	if (!info->tx_active)
+	 	tx_start(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	return 0;
+}
+
+/**
+ * called by network layer when interface enabled
+ * claim resources and initialize hardware
+ *
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_open(struct net_device *dev)
+{
+	SLMP_INFO *info = dev_to_port(dev);
+	int rc;
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_open(%s)\n",__FILE__,dev->name);
+
+	/* generic HDLC layer open processing */
+	if ((rc = hdlc_open(dev)))
+		return rc;
+
+	/* arbitrate between network and tty opens */
+	spin_lock_irqsave(&info->netlock, flags);
+	if (info->count != 0 || info->netcount != 0) {
+		printk(KERN_WARNING "%s: hdlc_open returning busy\n", dev->name);
+		spin_unlock_irqrestore(&info->netlock, flags);
+		return -EBUSY;
+	}
+	info->netcount=1;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	/* claim resources and init adapter */
+	if ((rc = startup(info)) != 0) {
+		spin_lock_irqsave(&info->netlock, flags);
+		info->netcount=0;
+		spin_unlock_irqrestore(&info->netlock, flags);
+		return rc;
+	}
+
+	/* assert DTR and RTS, apply hardware settings */
+	info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+	program_hw(info);
+
+	/* enable network layer transmit */
+	dev->trans_start = jiffies;
+	netif_start_queue(dev);
+
+	/* inform generic HDLC layer of current DCD status */
+	spin_lock_irqsave(&info->lock, flags);
+	get_signals(info);
+	spin_unlock_irqrestore(&info->lock, flags);
+	hdlc_set_carrier(info->serial_signals & SerialSignal_DCD, dev);
+
+	return 0;
+}
+
+/**
+ * called by network layer when interface is disabled
+ * shutdown hardware and release resources
+ *
+ * dev  pointer to network device structure
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_close(struct net_device *dev)
+{
+	SLMP_INFO *info = dev_to_port(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_close(%s)\n",__FILE__,dev->name);
+
+	netif_stop_queue(dev);
+
+	/* shutdown adapter and release resources */
+	shutdown(info);
+
+	hdlc_close(dev);
+
+	spin_lock_irqsave(&info->netlock, flags);
+	info->netcount=0;
+	spin_unlock_irqrestore(&info->netlock, flags);
+
+	return 0;
+}
+
+/**
+ * called by network layer to process IOCTL call to network device
+ *
+ * dev  pointer to network device structure
+ * ifr  pointer to network interface request structure
+ * cmd  IOCTL command code
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	const size_t size = sizeof(sync_serial_settings);
+	sync_serial_settings new_line;
+	sync_serial_settings __user *line = ifr->ifr_settings.ifs_ifsu.sync;
+	SLMP_INFO *info = dev_to_port(dev);
+	unsigned int flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s:hdlcdev_ioctl(%s)\n",__FILE__,dev->name);
+
+	/* return error if TTY interface open */
+	if (info->count)
+		return -EBUSY;
+
+	if (cmd != SIOCWANDEV)
+		return hdlc_ioctl(dev, ifr, cmd);
+
+	switch(ifr->ifr_settings.type) {
+	case IF_GET_IFACE: /* return current sync_serial_settings */
+
+		ifr->ifr_settings.type = IF_IFACE_SYNC_SERIAL;
+		if (ifr->ifr_settings.size < size) {
+			ifr->ifr_settings.size = size; /* data size wanted */
+			return -ENOBUFS;
+		}
+
+		flags = info->params.flags & (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					      HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					      HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					      HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN);
+
+		switch (flags){
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN): new_line.clock_type = CLOCK_EXT; break;
+		case (HDLC_FLAG_RXC_BRG    | HDLC_FLAG_TXC_BRG):    new_line.clock_type = CLOCK_INT; break;
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG):    new_line.clock_type = CLOCK_TXINT; break;
+		case (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN): new_line.clock_type = CLOCK_TXFROMRX; break;
+		default: new_line.clock_type = CLOCK_DEFAULT;
+		}
+
+		new_line.clock_rate = info->params.clock_speed;
+		new_line.loopback   = info->params.loopback ? 1:0;
+
+		if (copy_to_user(line, &new_line, size))
+			return -EFAULT;
+		return 0;
+
+	case IF_IFACE_SYNC_SERIAL: /* set sync_serial_settings */
+
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+		if (copy_from_user(&new_line, line, size))
+			return -EFAULT;
+
+		switch (new_line.clock_type)
+		{
+		case CLOCK_EXT:      flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_TXCPIN; break;
+		case CLOCK_TXFROMRX: flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_RXCPIN; break;
+		case CLOCK_INT:      flags = HDLC_FLAG_RXC_BRG    | HDLC_FLAG_TXC_BRG;    break;
+		case CLOCK_TXINT:    flags = HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_TXC_BRG;    break;
+		case CLOCK_DEFAULT:  flags = info->params.flags &
+					     (HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					      HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					      HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					      HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN); break;
+		default: return -EINVAL;
+		}
+
+		if (new_line.loopback != 0 && new_line.loopback != 1)
+			return -EINVAL;
+
+		info->params.flags &= ~(HDLC_FLAG_RXC_RXCPIN | HDLC_FLAG_RXC_DPLL |
+					HDLC_FLAG_RXC_BRG    | HDLC_FLAG_RXC_TXCPIN |
+					HDLC_FLAG_TXC_TXCPIN | HDLC_FLAG_TXC_DPLL |
+					HDLC_FLAG_TXC_BRG    | HDLC_FLAG_TXC_RXCPIN);
+		info->params.flags |= flags;
+
+		info->params.loopback = new_line.loopback;
+
+		if (flags & (HDLC_FLAG_RXC_BRG | HDLC_FLAG_TXC_BRG))
+			info->params.clock_speed = new_line.clock_rate;
+		else
+			info->params.clock_speed = 0;
+
+		/* if network interface up, reprogram hardware */
+		if (info->netcount)
+			program_hw(info);
+		return 0;
+
+	default:
+		return hdlc_ioctl(dev, ifr, cmd);
+	}
+}
+
+/**
+ * called by network layer when transmit timeout is detected
+ *
+ * dev  pointer to network device structure
+ */
+static void hdlcdev_tx_timeout(struct net_device *dev)
+{
+	SLMP_INFO *info = dev_to_port(dev);
+	struct net_device_stats *stats = hdlc_stats(dev);
+	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("hdlcdev_tx_timeout(%s)\n",dev->name);
+
+	stats->tx_errors++;
+	stats->tx_aborted_errors++;
+
+	spin_lock_irqsave(&info->lock,flags);
+	tx_stop(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	netif_wake_queue(dev);
+}
+
+/**
+ * called by device driver when transmit completes
+ * reenable network layer transmit if stopped
+ *
+ * info  pointer to device instance information
+ */
+static void hdlcdev_tx_done(SLMP_INFO *info)
+{
+	if (netif_queue_stopped(info->netdev))
+		netif_wake_queue(info->netdev);
+}
+
+/**
+ * called by device driver when frame received
+ * pass frame to network layer
+ *
+ * info  pointer to device instance information
+ * buf   pointer to buffer contianing frame data
+ * size  count of data bytes in buf
+ */
+static void hdlcdev_rx(SLMP_INFO *info, char *buf, int size)
+{
+	struct sk_buff *skb = dev_alloc_skb(size);
+	struct net_device *dev = info->netdev;
+	struct net_device_stats *stats = hdlc_stats(dev);
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("hdlcdev_rx(%s)\n",dev->name);
+
+	if (skb == NULL) {
+		printk(KERN_NOTICE "%s: can't alloc skb, dropping packet\n", dev->name);
+		stats->rx_dropped++;
+		return;
+	}
+
+	memcpy(skb_put(skb, size),buf,size);
+
+	skb->protocol = hdlc_type_trans(skb, info->netdev);
+
+	stats->rx_packets++;
+	stats->rx_bytes += size;
+
+	netif_rx(skb);
+
+	info->netdev->last_rx = jiffies;
+}
+
+/**
+ * called by device driver when adding device instance
+ * do generic HDLC initialization
+ *
+ * info  pointer to device instance information
+ *
+ * returns 0 if success, otherwise error code
+ */
+static int hdlcdev_init(SLMP_INFO *info)
+{
+	int rc;
+	struct net_device *dev;
+	hdlc_device *hdlc;
+
+	/* allocate and initialize network and HDLC layer objects */
+
+	if (!(dev = alloc_hdlcdev(info))) {
+		printk(KERN_ERR "%s:hdlc device allocation failure\n",__FILE__);
+		return -ENOMEM;
+	}
+
+	/* for network layer reporting purposes only */
+	dev->mem_start = info->phys_sca_base;
+	dev->mem_end   = info->phys_sca_base + SCA_BASE_SIZE - 1;
+	dev->irq       = info->irq_level;
+
+	/* network layer callbacks and settings */
+	dev->do_ioctl       = hdlcdev_ioctl;
+	dev->open           = hdlcdev_open;
+	dev->stop           = hdlcdev_close;
+	dev->tx_timeout     = hdlcdev_tx_timeout;
+	dev->watchdog_timeo = 10*HZ;
+	dev->tx_queue_len   = 50;
+
+	/* generic HDLC layer callbacks and settings */
+	hdlc         = dev_to_hdlc(dev);
+	hdlc->attach = hdlcdev_attach;
+	hdlc->xmit   = hdlcdev_xmit;
+
+	/* register objects with HDLC layer */
+	if ((rc = register_hdlc_device(dev))) {
+		printk(KERN_WARNING "%s:unable to register hdlc device\n",__FILE__);
+		free_netdev(dev);
+		return rc;
+	}
+
+	info->netdev = dev;
+	return 0;
+}
+
+/**
+ * called by device driver when removing device instance
+ * do generic HDLC cleanup
+ *
+ * info  pointer to device instance information
+ */
+static void hdlcdev_exit(SLMP_INFO *info)
+{
+	unregister_hdlc_device(info->netdev);
+	free_netdev(info->netdev);
+	info->netdev = NULL;
+}
+
+#endif /* CONFIG_HDLC */
+
+
+/* Return next bottom half action to perform.
+ * Return Value:	BH action code or 0 if nothing to do.
+ */
+int bh_action(SLMP_INFO *info)
+{
+	unsigned long flags;
+	int rc = 0;
+
+	spin_lock_irqsave(&info->lock,flags);
+
+	if (info->pending_bh & BH_RECEIVE) {
+		info->pending_bh &= ~BH_RECEIVE;
+		rc = BH_RECEIVE;
+	} else if (info->pending_bh & BH_TRANSMIT) {
+		info->pending_bh &= ~BH_TRANSMIT;
+		rc = BH_TRANSMIT;
+	} else if (info->pending_bh & BH_STATUS) {
+		info->pending_bh &= ~BH_STATUS;
+		rc = BH_STATUS;
+	}
+
+	if (!rc) {
+		/* Mark BH routine as complete */
+		info->bh_running   = 0;
+		info->bh_requested = 0;
+	}
+
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	return rc;
+}
+
+/* Perform bottom half processing of work items queued by ISR.
+ */
+void bh_handler(void* Context)
+{
+	SLMP_INFO *info = (SLMP_INFO*)Context;
+	int action;
+
+	if (!info)
+		return;
+
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):%s bh_handler() entry\n",
+			__FILE__,__LINE__,info->device_name);
+
+	info->bh_running = 1;
+
+	while((action = bh_action(info)) != 0) {
+
+		/* Process work item */
+		if ( debug_level >= DEBUG_LEVEL_BH )
+			printk( "%s(%d):%s bh_handler() work item action=%d\n",
+				__FILE__,__LINE__,info->device_name, action);
+
+		switch (action) {
+
+		case BH_RECEIVE:
+			bh_receive(info);
+			break;
+		case BH_TRANSMIT:
+			bh_transmit(info);
+			break;
+		case BH_STATUS:
+			bh_status(info);
+			break;
+		default:
+			/* unknown work item ID */
+			printk("%s(%d):%s Unknown work item ID=%08X!\n",
+				__FILE__,__LINE__,info->device_name,action);
+			break;
+		}
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):%s bh_handler() exit\n",
+			__FILE__,__LINE__,info->device_name);
+}
+
+void bh_receive(SLMP_INFO *info)
+{
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):%s bh_receive()\n",
+			__FILE__,__LINE__,info->device_name);
+
+	while( rx_get_frame(info) );
+}
+
+void bh_transmit(SLMP_INFO *info)
+{
+	struct tty_struct *tty = info->tty;
+
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):%s bh_transmit() entry\n",
+			__FILE__,__LINE__,info->device_name);
+
+	if (tty) {
+		tty_wakeup(tty);
+		wake_up_interruptible(&tty->write_wait);
+	}
+}
+
+void bh_status(SLMP_INFO *info)
+{
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk( "%s(%d):%s bh_status() entry\n",
+			__FILE__,__LINE__,info->device_name);
+
+	info->ri_chkcount = 0;
+	info->dsr_chkcount = 0;
+	info->dcd_chkcount = 0;
+	info->cts_chkcount = 0;
+}
+
+void isr_timer(SLMP_INFO * info)
+{
+	unsigned char timer = (info->port_num & 1) ? TIMER2 : TIMER0;
+
+	/* IER2<7..4> = timer<3..0> interrupt enables (0=disabled) */
+	write_reg(info, IER2, 0);
+
+	/* TMCS, Timer Control/Status Register
+	 *
+	 * 07      CMF, Compare match flag (read only) 1=match
+	 * 06      ECMI, CMF Interrupt Enable: 0=disabled
+	 * 05      Reserved, must be 0
+	 * 04      TME, Timer Enable
+	 * 03..00  Reserved, must be 0
+	 *
+	 * 0000 0000
+	 */
+	write_reg(info, (unsigned char)(timer + TMCS), 0);
+
+	info->irq_occurred = TRUE;
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_timer()\n",
+			__FILE__,__LINE__,info->device_name);
+}
+
+void isr_rxint(SLMP_INFO * info)
+{
+ 	struct tty_struct *tty = info->tty;
+ 	struct	mgsl_icount *icount = &info->icount;
+	unsigned char status = read_reg(info, SR1) & info->ie1_value & (FLGD + IDLD + CDCD + BRKD);
+	unsigned char status2 = read_reg(info, SR2) & info->ie2_value & OVRN;
+
+	/* clear status bits */
+	if (status)
+		write_reg(info, SR1, status);
+
+	if (status2)
+		write_reg(info, SR2, status2);
+	
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_rxint status=%02X %02x\n",
+			__FILE__,__LINE__,info->device_name,status,status2);
+
+	if (info->params.mode == MGSL_MODE_ASYNC) {
+		if (status & BRKD) {
+			icount->brk++;
+
+			/* process break detection if tty control
+			 * is not set to ignore it
+			 */
+			if ( tty ) {
+				if (!(status & info->ignore_status_mask1)) {
+					if (info->read_status_mask1 & BRKD) {
+						*tty->flip.flag_buf_ptr = TTY_BREAK;
+						if (info->flags & ASYNC_SAK)
+							do_SAK(tty);
+					}
+				}
+			}
+		}
+	}
+	else {
+		if (status & (FLGD|IDLD)) {
+			if (status & FLGD)
+				info->icount.exithunt++;
+			else if (status & IDLD)
+				info->icount.rxidle++;
+			wake_up_interruptible(&info->event_wait_q);
+		}
+	}
+
+	if (status & CDCD) {
+		/* simulate a common modem status change interrupt
+		 * for our handler
+		 */
+		get_signals( info );
+		isr_io_pin(info,
+			MISCSTATUS_DCD_LATCHED|(info->serial_signals&SerialSignal_DCD));
+	}
+}
+
+/*
+ * handle async rx data interrupts
+ */
+void isr_rxrdy(SLMP_INFO * info)
+{
+	u16 status;
+	unsigned char DataByte;
+ 	struct tty_struct *tty = info->tty;
+ 	struct	mgsl_icount *icount = &info->icount;
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_rxrdy\n",
+			__FILE__,__LINE__,info->device_name);
+
+	while((status = read_reg(info,CST0)) & BIT0)
+	{
+		DataByte = read_reg(info,TRB);
+
+		if ( tty ) {
+			if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+				continue;
+
+			*tty->flip.char_buf_ptr = DataByte;
+			*tty->flip.flag_buf_ptr = 0;
+		}
+
+		icount->rx++;
+
+		if ( status & (PE + FRME + OVRN) ) {
+			printk("%s(%d):%s rxerr=%04X\n",
+				__FILE__,__LINE__,info->device_name,status);
+
+			/* update error statistics */
+			if (status & PE)
+				icount->parity++;
+			else if (status & FRME)
+				icount->frame++;
+			else if (status & OVRN)
+				icount->overrun++;
+
+			/* discard char if tty control flags say so */
+			if (status & info->ignore_status_mask2)
+				continue;
+
+			status &= info->read_status_mask2;
+
+			if ( tty ) {
+				if (status & PE)
+					*tty->flip.flag_buf_ptr = TTY_PARITY;
+				else if (status & FRME)
+					*tty->flip.flag_buf_ptr = TTY_FRAME;
+				if (status & OVRN) {
+					/* Overrun is special, since it's
+					 * reported immediately, and doesn't
+					 * affect the current character
+					 */
+					if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+						tty->flip.count++;
+						tty->flip.flag_buf_ptr++;
+						tty->flip.char_buf_ptr++;
+						*tty->flip.flag_buf_ptr = TTY_OVERRUN;
+					}
+				}
+			}
+		}	/* end of if (error) */
+
+		if ( tty ) {
+			tty->flip.flag_buf_ptr++;
+			tty->flip.char_buf_ptr++;
+			tty->flip.count++;
+		}
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_ISR ) {
+		printk("%s(%d):%s isr_rxrdy() flip count=%d\n",
+			__FILE__,__LINE__,info->device_name,
+			tty ? tty->flip.count : 0);
+		printk("%s(%d):%s rx=%d brk=%d parity=%d frame=%d overrun=%d\n",
+			__FILE__,__LINE__,info->device_name,
+			icount->rx,icount->brk,icount->parity,
+			icount->frame,icount->overrun);
+	}
+
+	if ( tty && tty->flip.count )
+		tty_flip_buffer_push(tty);
+}
+
+static void isr_txeom(SLMP_INFO * info, unsigned char status)
+{
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_txeom status=%02x\n",
+			__FILE__,__LINE__,info->device_name,status);
+
+	write_reg(info, TXDMA + DIR, 0x00); /* disable Tx DMA IRQs */
+	write_reg(info, TXDMA + DSR, 0xc0); /* clear IRQs and disable DMA */
+	write_reg(info, TXDMA + DCMD, SWABORT);	/* reset/init DMA channel */
+
+	if (status & UDRN) {
+		write_reg(info, CMD, TXRESET);
+		write_reg(info, CMD, TXENABLE);
+	} else
+		write_reg(info, CMD, TXBUFCLR);
+
+	/* disable and clear tx interrupts */
+	info->ie0_value &= ~TXRDYE;
+	info->ie1_value &= ~(IDLE + UDRN);
+	write_reg16(info, IE0, (unsigned short)((info->ie1_value << 8) + info->ie0_value));
+	write_reg(info, SR1, (unsigned char)(UDRN + IDLE));
+
+	if ( info->tx_active ) {
+		if (info->params.mode != MGSL_MODE_ASYNC) {
+			if (status & UDRN)
+				info->icount.txunder++;
+			else if (status & IDLE)
+				info->icount.txok++;
+		}
+
+		info->tx_active = 0;
+		info->tx_count = info->tx_put = info->tx_get = 0;
+
+		del_timer(&info->tx_timer);
+
+		if (info->params.mode != MGSL_MODE_ASYNC && info->drop_rts_on_tx_done ) {
+			info->serial_signals &= ~SerialSignal_RTS;
+			info->drop_rts_on_tx_done = 0;
+			set_signals(info);
+		}
+
+#ifdef CONFIG_HDLC
+		if (info->netcount)
+			hdlcdev_tx_done(info);
+		else
+#endif
+		{
+			if (info->tty && (info->tty->stopped || info->tty->hw_stopped)) {
+				tx_stop(info);
+				return;
+			}
+			info->pending_bh |= BH_TRANSMIT;
+		}
+	}
+}
+
+
+/*
+ * handle tx status interrupts
+ */
+void isr_txint(SLMP_INFO * info)
+{
+	unsigned char status = read_reg(info, SR1) & info->ie1_value & (UDRN + IDLE + CCTS);
+
+	/* clear status bits */
+	write_reg(info, SR1, status);
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_txint status=%02x\n",
+			__FILE__,__LINE__,info->device_name,status);
+
+	if (status & (UDRN + IDLE))
+		isr_txeom(info, status);
+
+	if (status & CCTS) {
+		/* simulate a common modem status change interrupt
+		 * for our handler
+		 */
+		get_signals( info );
+		isr_io_pin(info,
+			MISCSTATUS_CTS_LATCHED|(info->serial_signals&SerialSignal_CTS));
+
+	}
+}
+
+/*
+ * handle async tx data interrupts
+ */
+void isr_txrdy(SLMP_INFO * info)
+{
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_txrdy() tx_count=%d\n",
+			__FILE__,__LINE__,info->device_name,info->tx_count);
+
+	if (info->params.mode != MGSL_MODE_ASYNC) {
+		/* disable TXRDY IRQ, enable IDLE IRQ */
+		info->ie0_value &= ~TXRDYE;
+		info->ie1_value |= IDLE;
+		write_reg16(info, IE0, (unsigned short)((info->ie1_value << 8) + info->ie0_value));
+		return;
+	}
+
+	if (info->tty && (info->tty->stopped || info->tty->hw_stopped)) {
+		tx_stop(info);
+		return;
+	}
+
+	if ( info->tx_count )
+		tx_load_fifo( info );
+	else {
+		info->tx_active = 0;
+		info->ie0_value &= ~TXRDYE;
+		write_reg(info, IE0, info->ie0_value);
+	}
+
+	if (info->tx_count < WAKEUP_CHARS)
+		info->pending_bh |= BH_TRANSMIT;
+}
+
+void isr_rxdmaok(SLMP_INFO * info)
+{
+	/* BIT7 = EOT (end of transfer)
+	 * BIT6 = EOM (end of message/frame)
+	 */
+	unsigned char status = read_reg(info,RXDMA + DSR) & 0xc0;
+
+	/* clear IRQ (BIT0 must be 1 to prevent clearing DE bit) */
+	write_reg(info, RXDMA + DSR, (unsigned char)(status | 1));
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_rxdmaok(), status=%02x\n",
+			__FILE__,__LINE__,info->device_name,status);
+
+	info->pending_bh |= BH_RECEIVE;
+}
+
+void isr_rxdmaerror(SLMP_INFO * info)
+{
+	/* BIT5 = BOF (buffer overflow)
+	 * BIT4 = COF (counter overflow)
+	 */
+	unsigned char status = read_reg(info,RXDMA + DSR) & 0x30;
+
+	/* clear IRQ (BIT0 must be 1 to prevent clearing DE bit) */
+	write_reg(info, RXDMA + DSR, (unsigned char)(status | 1));
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_rxdmaerror(), status=%02x\n",
+			__FILE__,__LINE__,info->device_name,status);
+
+	info->rx_overflow = TRUE;
+	info->pending_bh |= BH_RECEIVE;
+}
+
+void isr_txdmaok(SLMP_INFO * info)
+{
+	unsigned char status_reg1 = read_reg(info, SR1);
+
+	write_reg(info, TXDMA + DIR, 0x00);	/* disable Tx DMA IRQs */
+	write_reg(info, TXDMA + DSR, 0xc0); /* clear IRQs and disable DMA */
+	write_reg(info, TXDMA + DCMD, SWABORT);	/* reset/init DMA channel */
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_txdmaok(), status=%02x\n",
+			__FILE__,__LINE__,info->device_name,status_reg1);
+
+	/* program TXRDY as FIFO empty flag, enable TXRDY IRQ */
+	write_reg16(info, TRC0, 0);
+	info->ie0_value |= TXRDYE;
+	write_reg(info, IE0, info->ie0_value);
+}
+
+void isr_txdmaerror(SLMP_INFO * info)
+{
+	/* BIT5 = BOF (buffer overflow)
+	 * BIT4 = COF (counter overflow)
+	 */
+	unsigned char status = read_reg(info,TXDMA + DSR) & 0x30;
+
+	/* clear IRQ (BIT0 must be 1 to prevent clearing DE bit) */
+	write_reg(info, TXDMA + DSR, (unsigned char)(status | 1));
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):%s isr_txdmaerror(), status=%02x\n",
+			__FILE__,__LINE__,info->device_name,status);
+}
+
+/* handle input serial signal changes
+ */
+void isr_io_pin( SLMP_INFO *info, u16 status )
+{
+ 	struct	mgsl_icount *icount;
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):isr_io_pin status=%04X\n",
+			__FILE__,__LINE__,status);
+
+	if (status & (MISCSTATUS_CTS_LATCHED | MISCSTATUS_DCD_LATCHED |
+	              MISCSTATUS_DSR_LATCHED | MISCSTATUS_RI_LATCHED) ) {
+		icount = &info->icount;
+		/* update input line counters */
+		if (status & MISCSTATUS_RI_LATCHED) {
+			icount->rng++;
+			if ( status & SerialSignal_RI )
+				info->input_signal_events.ri_up++;
+			else
+				info->input_signal_events.ri_down++;
+		}
+		if (status & MISCSTATUS_DSR_LATCHED) {
+			icount->dsr++;
+			if ( status & SerialSignal_DSR )
+				info->input_signal_events.dsr_up++;
+			else
+				info->input_signal_events.dsr_down++;
+		}
+		if (status & MISCSTATUS_DCD_LATCHED) {
+			if ((info->dcd_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT) {
+				info->ie1_value &= ~CDCD;
+				write_reg(info, IE1, info->ie1_value);
+			}
+			icount->dcd++;
+			if (status & SerialSignal_DCD) {
+				info->input_signal_events.dcd_up++;
+			} else
+				info->input_signal_events.dcd_down++;
+#ifdef CONFIG_HDLC
+			if (info->netcount)
+				hdlc_set_carrier(status & SerialSignal_DCD, info->netdev);
+#endif
+		}
+		if (status & MISCSTATUS_CTS_LATCHED)
+		{
+			if ((info->cts_chkcount)++ >= IO_PIN_SHUTDOWN_LIMIT) {
+				info->ie1_value &= ~CCTS;
+				write_reg(info, IE1, info->ie1_value);
+			}
+			icount->cts++;
+			if ( status & SerialSignal_CTS )
+				info->input_signal_events.cts_up++;
+			else
+				info->input_signal_events.cts_down++;
+		}
+		wake_up_interruptible(&info->status_event_wait_q);
+		wake_up_interruptible(&info->event_wait_q);
+
+		if ( (info->flags & ASYNC_CHECK_CD) &&
+		     (status & MISCSTATUS_DCD_LATCHED) ) {
+			if ( debug_level >= DEBUG_LEVEL_ISR )
+				printk("%s CD now %s...", info->device_name,
+				       (status & SerialSignal_DCD) ? "on" : "off");
+			if (status & SerialSignal_DCD)
+				wake_up_interruptible(&info->open_wait);
+			else {
+				if ( debug_level >= DEBUG_LEVEL_ISR )
+					printk("doing serial hangup...");
+				if (info->tty)
+					tty_hangup(info->tty);
+			}
+		}
+
+		if ( (info->flags & ASYNC_CTS_FLOW) &&
+		     (status & MISCSTATUS_CTS_LATCHED) ) {
+			if ( info->tty ) {
+				if (info->tty->hw_stopped) {
+					if (status & SerialSignal_CTS) {
+						if ( debug_level >= DEBUG_LEVEL_ISR )
+							printk("CTS tx start...");
+			 			info->tty->hw_stopped = 0;
+						tx_start(info);
+						info->pending_bh |= BH_TRANSMIT;
+						return;
+					}
+				} else {
+					if (!(status & SerialSignal_CTS)) {
+						if ( debug_level >= DEBUG_LEVEL_ISR )
+							printk("CTS tx stop...");
+			 			info->tty->hw_stopped = 1;
+						tx_stop(info);
+					}
+				}
+			}
+		}
+	}
+
+	info->pending_bh |= BH_STATUS;
+}
+
+/* Interrupt service routine entry point.
+ *
+ * Arguments:
+ * 	irq		interrupt number that caused interrupt
+ * 	dev_id		device ID supplied during interrupt registration
+ * 	regs		interrupted processor context
+ */
+static irqreturn_t synclinkmp_interrupt(int irq, void *dev_id,
+					struct pt_regs *regs)
+{
+	SLMP_INFO * info;
+	unsigned char status, status0, status1=0;
+	unsigned char dmastatus, dmastatus0, dmastatus1=0;
+	unsigned char timerstatus0, timerstatus1=0;
+	unsigned char shift;
+	unsigned int i;
+	unsigned short tmp;
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d): synclinkmp_interrupt(%d)entry.\n",
+			__FILE__,__LINE__,irq);
+
+	info = (SLMP_INFO *)dev_id;
+	if (!info)
+		return IRQ_NONE;
+
+	spin_lock(&info->lock);
+
+	for(;;) {
+
+		/* get status for SCA0 (ports 0-1) */
+		tmp = read_reg16(info, ISR0);	/* get ISR0 and ISR1 in one read */
+		status0 = (unsigned char)tmp;
+		dmastatus0 = (unsigned char)(tmp>>8);
+		timerstatus0 = read_reg(info, ISR2);
+
+		if ( debug_level >= DEBUG_LEVEL_ISR )
+			printk("%s(%d):%s status0=%02x, dmastatus0=%02x, timerstatus0=%02x\n",
+				__FILE__,__LINE__,info->device_name,
+				status0,dmastatus0,timerstatus0);
+
+		if (info->port_count == 4) {
+			/* get status for SCA1 (ports 2-3) */
+			tmp = read_reg16(info->port_array[2], ISR0);
+			status1 = (unsigned char)tmp;
+			dmastatus1 = (unsigned char)(tmp>>8);
+			timerstatus1 = read_reg(info->port_array[2], ISR2);
+
+			if ( debug_level >= DEBUG_LEVEL_ISR )
+				printk("%s(%d):%s status1=%02x, dmastatus1=%02x, timerstatus1=%02x\n",
+					__FILE__,__LINE__,info->device_name,
+					status1,dmastatus1,timerstatus1);
+		}
+
+		if (!status0 && !dmastatus0 && !timerstatus0 &&
+			 !status1 && !dmastatus1 && !timerstatus1)
+			break;
+
+		for(i=0; i < info->port_count ; i++) {
+			if (info->port_array[i] == NULL)
+				continue;
+			if (i < 2) {
+				status = status0;
+				dmastatus = dmastatus0;
+			} else {
+				status = status1;
+				dmastatus = dmastatus1;
+			}
+
+			shift = i & 1 ? 4 :0;
+
+			if (status & BIT0 << shift)
+				isr_rxrdy(info->port_array[i]);
+			if (status & BIT1 << shift)
+				isr_txrdy(info->port_array[i]);
+			if (status & BIT2 << shift)
+				isr_rxint(info->port_array[i]);
+			if (status & BIT3 << shift)
+				isr_txint(info->port_array[i]);
+
+			if (dmastatus & BIT0 << shift)
+				isr_rxdmaerror(info->port_array[i]);
+			if (dmastatus & BIT1 << shift)
+				isr_rxdmaok(info->port_array[i]);
+			if (dmastatus & BIT2 << shift)
+				isr_txdmaerror(info->port_array[i]);
+			if (dmastatus & BIT3 << shift)
+				isr_txdmaok(info->port_array[i]);
+		}
+
+		if (timerstatus0 & (BIT5 | BIT4))
+			isr_timer(info->port_array[0]);
+		if (timerstatus0 & (BIT7 | BIT6))
+			isr_timer(info->port_array[1]);
+		if (timerstatus1 & (BIT5 | BIT4))
+			isr_timer(info->port_array[2]);
+		if (timerstatus1 & (BIT7 | BIT6))
+			isr_timer(info->port_array[3]);
+	}
+
+	for(i=0; i < info->port_count ; i++) {
+		SLMP_INFO * port = info->port_array[i];
+
+		/* Request bottom half processing if there's something
+		 * for it to do and the bh is not already running.
+		 *
+		 * Note: startup adapter diags require interrupts.
+		 * do not request bottom half processing if the
+		 * device is not open in a normal mode.
+		 */
+		if ( port && (port->count || port->netcount) &&
+		     port->pending_bh && !port->bh_running &&
+		     !port->bh_requested ) {
+			if ( debug_level >= DEBUG_LEVEL_ISR )
+				printk("%s(%d):%s queueing bh task.\n",
+					__FILE__,__LINE__,port->device_name);
+			schedule_work(&port->task);
+			port->bh_requested = 1;
+		}
+	}
+
+	spin_unlock(&info->lock);
+
+	if ( debug_level >= DEBUG_LEVEL_ISR )
+		printk("%s(%d):synclinkmp_interrupt(%d)exit.\n",
+			__FILE__,__LINE__,irq);
+	return IRQ_HANDLED;
+}
+
+/* Initialize and start device.
+ */
+static int startup(SLMP_INFO * info)
+{
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("%s(%d):%s tx_releaseup()\n",__FILE__,__LINE__,info->device_name);
+
+	if (info->flags & ASYNC_INITIALIZED)
+		return 0;
+
+	if (!info->tx_buf) {
+		info->tx_buf = (unsigned char *)kmalloc(info->max_frame_size, GFP_KERNEL);
+		if (!info->tx_buf) {
+			printk(KERN_ERR"%s(%d):%s can't allocate transmit buffer\n",
+				__FILE__,__LINE__,info->device_name);
+			return -ENOMEM;
+		}
+	}
+
+	info->pending_bh = 0;
+
+	/* program hardware for current parameters */
+	reset_port(info);
+
+	change_params(info);
+
+	info->status_timer.expires = jiffies + msecs_to_jiffies(10);
+	add_timer(&info->status_timer);
+
+	if (info->tty)
+		clear_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	info->flags |= ASYNC_INITIALIZED;
+
+	return 0;
+}
+
+/* Called by close() and hangup() to shutdown hardware
+ */
+static void shutdown(SLMP_INFO * info)
+{
+	unsigned long flags;
+
+	if (!(info->flags & ASYNC_INITIALIZED))
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s synclinkmp_shutdown()\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	/* clear status wait queue because status changes */
+	/* can't happen after shutting down the hardware */
+	wake_up_interruptible(&info->status_event_wait_q);
+	wake_up_interruptible(&info->event_wait_q);
+
+	del_timer(&info->tx_timer);
+	del_timer(&info->status_timer);
+
+	if (info->tx_buf) {
+		kfree(info->tx_buf);
+		info->tx_buf = NULL;
+	}
+
+	spin_lock_irqsave(&info->lock,flags);
+
+	reset_port(info);
+
+ 	if (!info->tty || info->tty->termios->c_cflag & HUPCL) {
+ 		info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
+		set_signals(info);
+	}
+
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	if (info->tty)
+		set_bit(TTY_IO_ERROR, &info->tty->flags);
+
+	info->flags &= ~ASYNC_INITIALIZED;
+}
+
+static void program_hw(SLMP_INFO *info)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->lock,flags);
+
+	rx_stop(info);
+	tx_stop(info);
+
+	info->tx_count = info->tx_put = info->tx_get = 0;
+
+	if (info->params.mode == MGSL_MODE_HDLC || info->netcount)
+		hdlc_mode(info);
+	else
+		async_mode(info);
+
+	set_signals(info);
+
+	info->dcd_chkcount = 0;
+	info->cts_chkcount = 0;
+	info->ri_chkcount = 0;
+	info->dsr_chkcount = 0;
+
+	info->ie1_value |= (CDCD|CCTS);
+	write_reg(info, IE1, info->ie1_value);
+
+	get_signals(info);
+
+	if (info->netcount || (info->tty && info->tty->termios->c_cflag & CREAD) )
+		rx_start(info);
+
+	spin_unlock_irqrestore(&info->lock,flags);
+}
+
+/* Reconfigure adapter based on new parameters
+ */
+static void change_params(SLMP_INFO *info)
+{
+	unsigned cflag;
+	int bits_per_char;
+
+	if (!info->tty || !info->tty->termios)
+		return;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s change_params()\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	cflag = info->tty->termios->c_cflag;
+
+	/* if B0 rate (hangup) specified then negate DTR and RTS */
+	/* otherwise assert DTR and RTS */
+ 	if (cflag & CBAUD)
+		info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+	else
+		info->serial_signals &= ~(SerialSignal_RTS + SerialSignal_DTR);
+
+	/* byte size and parity */
+
+	switch (cflag & CSIZE) {
+	      case CS5: info->params.data_bits = 5; break;
+	      case CS6: info->params.data_bits = 6; break;
+	      case CS7: info->params.data_bits = 7; break;
+	      case CS8: info->params.data_bits = 8; break;
+	      /* Never happens, but GCC is too dumb to figure it out */
+	      default:  info->params.data_bits = 7; break;
+	      }
+
+	if (cflag & CSTOPB)
+		info->params.stop_bits = 2;
+	else
+		info->params.stop_bits = 1;
+
+	info->params.parity = ASYNC_PARITY_NONE;
+	if (cflag & PARENB) {
+		if (cflag & PARODD)
+			info->params.parity = ASYNC_PARITY_ODD;
+		else
+			info->params.parity = ASYNC_PARITY_EVEN;
+#ifdef CMSPAR
+		if (cflag & CMSPAR)
+			info->params.parity = ASYNC_PARITY_SPACE;
+#endif
+	}
+
+	/* calculate number of jiffies to transmit a full
+	 * FIFO (32 bytes) at specified data rate
+	 */
+	bits_per_char = info->params.data_bits +
+			info->params.stop_bits + 1;
+
+	/* if port data rate is set to 460800 or less then
+	 * allow tty settings to override, otherwise keep the
+	 * current data rate.
+	 */
+	if (info->params.data_rate <= 460800) {
+		info->params.data_rate = tty_get_baud_rate(info->tty);
+	}
+
+	if ( info->params.data_rate ) {
+		info->timeout = (32*HZ*bits_per_char) /
+				info->params.data_rate;
+	}
+	info->timeout += HZ/50;		/* Add .02 seconds of slop */
+
+	if (cflag & CRTSCTS)
+		info->flags |= ASYNC_CTS_FLOW;
+	else
+		info->flags &= ~ASYNC_CTS_FLOW;
+
+	if (cflag & CLOCAL)
+		info->flags &= ~ASYNC_CHECK_CD;
+	else
+		info->flags |= ASYNC_CHECK_CD;
+
+	/* process tty input control flags */
+
+	info->read_status_mask2 = OVRN;
+	if (I_INPCK(info->tty))
+		info->read_status_mask2 |= PE | FRME;
+ 	if (I_BRKINT(info->tty) || I_PARMRK(info->tty))
+ 		info->read_status_mask1 |= BRKD;
+	if (I_IGNPAR(info->tty))
+		info->ignore_status_mask2 |= PE | FRME;
+	if (I_IGNBRK(info->tty)) {
+		info->ignore_status_mask1 |= BRKD;
+		/* If ignoring parity and break indicators, ignore
+		 * overruns too.  (For real raw support).
+		 */
+		if (I_IGNPAR(info->tty))
+			info->ignore_status_mask2 |= OVRN;
+	}
+
+	program_hw(info);
+}
+
+static int get_stats(SLMP_INFO * info, struct mgsl_icount __user *user_icount)
+{
+	int err;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s get_params()\n",
+			 __FILE__,__LINE__, info->device_name);
+
+	COPY_TO_USER(err,user_icount, &info->icount, sizeof(struct mgsl_icount));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):%s get_stats() user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int get_params(SLMP_INFO * info, MGSL_PARAMS __user *user_params)
+{
+	int err;
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s get_params()\n",
+			 __FILE__,__LINE__, info->device_name);
+
+	COPY_TO_USER(err,user_params, &info->params, sizeof(MGSL_PARAMS));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):%s get_params() user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int set_params(SLMP_INFO * info, MGSL_PARAMS __user *new_params)
+{
+ 	unsigned long flags;
+	MGSL_PARAMS tmp_params;
+	int err;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s set_params\n",
+			__FILE__,__LINE__,info->device_name );
+	COPY_FROM_USER(err,&tmp_params, new_params, sizeof(MGSL_PARAMS));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):%s set_params() user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+
+	spin_lock_irqsave(&info->lock,flags);
+	memcpy(&info->params,&tmp_params,sizeof(MGSL_PARAMS));
+	spin_unlock_irqrestore(&info->lock,flags);
+
+ 	change_params(info);
+
+	return 0;
+}
+
+static int get_txidle(SLMP_INFO * info, int __user *idle_mode)
+{
+	int err;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s get_txidle()=%d\n",
+			 __FILE__,__LINE__, info->device_name, info->idle_mode);
+
+	COPY_TO_USER(err,idle_mode, &info->idle_mode, sizeof(int));
+	if (err) {
+		if ( debug_level >= DEBUG_LEVEL_INFO )
+			printk( "%s(%d):%s get_txidle() user buffer copy failed\n",
+				__FILE__,__LINE__,info->device_name);
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int set_txidle(SLMP_INFO * info, int idle_mode)
+{
+ 	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s set_txidle(%d)\n",
+			__FILE__,__LINE__,info->device_name, idle_mode );
+
+	spin_lock_irqsave(&info->lock,flags);
+	info->idle_mode = idle_mode;
+	tx_set_idle( info );
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+static int tx_enable(SLMP_INFO * info, int enable)
+{
+ 	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s tx_enable(%d)\n",
+			__FILE__,__LINE__,info->device_name, enable);
+
+	spin_lock_irqsave(&info->lock,flags);
+	if ( enable ) {
+		if ( !info->tx_enabled ) {
+			tx_start(info);
+		}
+	} else {
+		if ( info->tx_enabled )
+			tx_stop(info);
+	}
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+/* abort send HDLC frame
+ */
+static int tx_abort(SLMP_INFO * info)
+{
+ 	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s tx_abort()\n",
+			__FILE__,__LINE__,info->device_name);
+
+	spin_lock_irqsave(&info->lock,flags);
+	if ( info->tx_active && info->params.mode == MGSL_MODE_HDLC ) {
+		info->ie1_value &= ~UDRN;
+		info->ie1_value |= IDLE;
+		write_reg(info, IE1, info->ie1_value);	/* disable tx status interrupts */
+		write_reg(info, SR1, (unsigned char)(IDLE + UDRN));	/* clear pending */
+
+		write_reg(info, TXDMA + DSR, 0);		/* disable DMA channel */
+		write_reg(info, TXDMA + DCMD, SWABORT);	/* reset/init DMA channel */
+
+   		write_reg(info, CMD, TXABORT);
+	}
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+static int rx_enable(SLMP_INFO * info, int enable)
+{
+ 	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s rx_enable(%d)\n",
+			__FILE__,__LINE__,info->device_name,enable);
+
+	spin_lock_irqsave(&info->lock,flags);
+	if ( enable ) {
+		if ( !info->rx_enabled )
+			rx_start(info);
+	} else {
+		if ( info->rx_enabled )
+			rx_stop(info);
+	}
+	spin_unlock_irqrestore(&info->lock,flags);
+	return 0;
+}
+
+static int map_status(int signals)
+{
+	/* Map status bits to API event bits */
+
+	return ((signals & SerialSignal_DSR) ? MgslEvent_DsrActive : MgslEvent_DsrInactive) +
+	       ((signals & SerialSignal_CTS) ? MgslEvent_CtsActive : MgslEvent_CtsInactive) +
+	       ((signals & SerialSignal_DCD) ? MgslEvent_DcdActive : MgslEvent_DcdInactive) +
+	       ((signals & SerialSignal_RI)  ? MgslEvent_RiActive : MgslEvent_RiInactive);
+}
+
+/* wait for specified event to occur
+ */
+static int wait_mgsl_event(SLMP_INFO * info, int __user *mask_ptr)
+{
+ 	unsigned long flags;
+	int s;
+	int rc=0;
+	struct mgsl_icount cprev, cnow;
+	int events;
+	int mask;
+	struct	_input_signal_events oldsigs, newsigs;
+	DECLARE_WAITQUEUE(wait, current);
+
+	COPY_FROM_USER(rc,&mask, mask_ptr, sizeof(int));
+	if (rc) {
+		return  -EFAULT;
+	}
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s wait_mgsl_event(%d)\n",
+			__FILE__,__LINE__,info->device_name,mask);
+
+	spin_lock_irqsave(&info->lock,flags);
+
+	/* return immediately if state matches requested events */
+	get_signals(info);
+	s = map_status(info->serial_signals);
+
+	events = mask &
+		( ((s & SerialSignal_DSR) ? MgslEvent_DsrActive:MgslEvent_DsrInactive) +
+ 		  ((s & SerialSignal_DCD) ? MgslEvent_DcdActive:MgslEvent_DcdInactive) +
+		  ((s & SerialSignal_CTS) ? MgslEvent_CtsActive:MgslEvent_CtsInactive) +
+		  ((s & SerialSignal_RI)  ? MgslEvent_RiActive :MgslEvent_RiInactive) );
+	if (events) {
+		spin_unlock_irqrestore(&info->lock,flags);
+		goto exit;
+	}
+
+	/* save current irq counts */
+	cprev = info->icount;
+	oldsigs = info->input_signal_events;
+
+	/* enable hunt and idle irqs if needed */
+	if (mask & (MgslEvent_ExitHuntMode+MgslEvent_IdleReceived)) {
+		unsigned char oldval = info->ie1_value;
+		unsigned char newval = oldval +
+			 (mask & MgslEvent_ExitHuntMode ? FLGD:0) +
+			 (mask & MgslEvent_IdleReceived ? IDLD:0);
+		if ( oldval != newval ) {
+			info->ie1_value = newval;
+			write_reg(info, IE1, info->ie1_value);
+		}
+	}
+
+	set_current_state(TASK_INTERRUPTIBLE);
+	add_wait_queue(&info->event_wait_q, &wait);
+
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	for(;;) {
+		schedule();
+		if (signal_pending(current)) {
+			rc = -ERESTARTSYS;
+			break;
+		}
+
+		/* get current irq counts */
+		spin_lock_irqsave(&info->lock,flags);
+		cnow = info->icount;
+		newsigs = info->input_signal_events;
+		set_current_state(TASK_INTERRUPTIBLE);
+		spin_unlock_irqrestore(&info->lock,flags);
+
+		/* if no change, wait aborted for some reason */
+		if (newsigs.dsr_up   == oldsigs.dsr_up   &&
+		    newsigs.dsr_down == oldsigs.dsr_down &&
+		    newsigs.dcd_up   == oldsigs.dcd_up   &&
+		    newsigs.dcd_down == oldsigs.dcd_down &&
+		    newsigs.cts_up   == oldsigs.cts_up   &&
+		    newsigs.cts_down == oldsigs.cts_down &&
+		    newsigs.ri_up    == oldsigs.ri_up    &&
+		    newsigs.ri_down  == oldsigs.ri_down  &&
+		    cnow.exithunt    == cprev.exithunt   &&
+		    cnow.rxidle      == cprev.rxidle) {
+			rc = -EIO;
+			break;
+		}
+
+		events = mask &
+			( (newsigs.dsr_up   != oldsigs.dsr_up   ? MgslEvent_DsrActive:0)   +
+			  (newsigs.dsr_down != oldsigs.dsr_down ? MgslEvent_DsrInactive:0) +
+			  (newsigs.dcd_up   != oldsigs.dcd_up   ? MgslEvent_DcdActive:0)   +
+			  (newsigs.dcd_down != oldsigs.dcd_down ? MgslEvent_DcdInactive:0) +
+			  (newsigs.cts_up   != oldsigs.cts_up   ? MgslEvent_CtsActive:0)   +
+			  (newsigs.cts_down != oldsigs.cts_down ? MgslEvent_CtsInactive:0) +
+			  (newsigs.ri_up    != oldsigs.ri_up    ? MgslEvent_RiActive:0)    +
+			  (newsigs.ri_down  != oldsigs.ri_down  ? MgslEvent_RiInactive:0)  +
+			  (cnow.exithunt    != cprev.exithunt   ? MgslEvent_ExitHuntMode:0) +
+			  (cnow.rxidle      != cprev.rxidle     ? MgslEvent_IdleReceived:0) );
+		if (events)
+			break;
+
+		cprev = cnow;
+		oldsigs = newsigs;
+	}
+
+	remove_wait_queue(&info->event_wait_q, &wait);
+	set_current_state(TASK_RUNNING);
+
+
+	if (mask & (MgslEvent_ExitHuntMode + MgslEvent_IdleReceived)) {
+		spin_lock_irqsave(&info->lock,flags);
+		if (!waitqueue_active(&info->event_wait_q)) {
+			/* disable enable exit hunt mode/idle rcvd IRQs */
+			info->ie1_value &= ~(FLGD|IDLD);
+			write_reg(info, IE1, info->ie1_value);
+		}
+		spin_unlock_irqrestore(&info->lock,flags);
+	}
+exit:
+	if ( rc == 0 )
+		PUT_USER(rc, events, mask_ptr);
+
+	return rc;
+}
+
+static int modem_input_wait(SLMP_INFO *info,int arg)
+{
+ 	unsigned long flags;
+	int rc;
+	struct mgsl_icount cprev, cnow;
+	DECLARE_WAITQUEUE(wait, current);
+
+	/* save current irq counts */
+	spin_lock_irqsave(&info->lock,flags);
+	cprev = info->icount;
+	add_wait_queue(&info->status_event_wait_q, &wait);
+	set_current_state(TASK_INTERRUPTIBLE);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	for(;;) {
+		schedule();
+		if (signal_pending(current)) {
+			rc = -ERESTARTSYS;
+			break;
+		}
+
+		/* get new irq counts */
+		spin_lock_irqsave(&info->lock,flags);
+		cnow = info->icount;
+		set_current_state(TASK_INTERRUPTIBLE);
+		spin_unlock_irqrestore(&info->lock,flags);
+
+		/* if no change, wait aborted for some reason */
+		if (cnow.rng == cprev.rng && cnow.dsr == cprev.dsr &&
+		    cnow.dcd == cprev.dcd && cnow.cts == cprev.cts) {
+			rc = -EIO;
+			break;
+		}
+
+		/* check for change in caller specified modem input */
+		if ((arg & TIOCM_RNG && cnow.rng != cprev.rng) ||
+		    (arg & TIOCM_DSR && cnow.dsr != cprev.dsr) ||
+		    (arg & TIOCM_CD  && cnow.dcd != cprev.dcd) ||
+		    (arg & TIOCM_CTS && cnow.cts != cprev.cts)) {
+			rc = 0;
+			break;
+		}
+
+		cprev = cnow;
+	}
+	remove_wait_queue(&info->status_event_wait_q, &wait);
+	set_current_state(TASK_RUNNING);
+	return rc;
+}
+
+/* return the state of the serial control and status signals
+ */
+static int tiocmget(struct tty_struct *tty, struct file *file)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+	unsigned int result;
+ 	unsigned long flags;
+
+	spin_lock_irqsave(&info->lock,flags);
+ 	get_signals(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	result = ((info->serial_signals & SerialSignal_RTS) ? TIOCM_RTS:0) +
+		((info->serial_signals & SerialSignal_DTR) ? TIOCM_DTR:0) +
+		((info->serial_signals & SerialSignal_DCD) ? TIOCM_CAR:0) +
+		((info->serial_signals & SerialSignal_RI)  ? TIOCM_RNG:0) +
+		((info->serial_signals & SerialSignal_DSR) ? TIOCM_DSR:0) +
+		((info->serial_signals & SerialSignal_CTS) ? TIOCM_CTS:0);
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s tiocmget() value=%08X\n",
+			 __FILE__,__LINE__, info->device_name, result );
+	return result;
+}
+
+/* set modem control signals (DTR/RTS)
+ */
+static int tiocmset(struct tty_struct *tty, struct file *file,
+		    unsigned int set, unsigned int clear)
+{
+	SLMP_INFO *info = (SLMP_INFO *)tty->driver_data;
+ 	unsigned long flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s tiocmset(%x,%x)\n",
+			__FILE__,__LINE__,info->device_name, set, clear);
+
+	if (set & TIOCM_RTS)
+		info->serial_signals |= SerialSignal_RTS;
+	if (set & TIOCM_DTR)
+		info->serial_signals |= SerialSignal_DTR;
+	if (clear & TIOCM_RTS)
+		info->serial_signals &= ~SerialSignal_RTS;
+	if (clear & TIOCM_DTR)
+		info->serial_signals &= ~SerialSignal_DTR;
+
+	spin_lock_irqsave(&info->lock,flags);
+ 	set_signals(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	return 0;
+}
+
+
+
+/* Block the current process until the specified port is ready to open.
+ */
+static int block_til_ready(struct tty_struct *tty, struct file *filp,
+			   SLMP_INFO *info)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	int		retval;
+	int		do_clocal = 0, extra_count = 0;
+	unsigned long	flags;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s block_til_ready()\n",
+			 __FILE__,__LINE__, tty->driver->name );
+
+	if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){
+		/* nonblock mode is set or port is not enabled */
+		/* just verify that callout device is not active */
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+		return 0;
+	}
+
+	if (tty->termios->c_cflag & CLOCAL)
+		do_clocal = 1;
+
+	/* Wait for carrier detect and the line to become
+	 * free (i.e., not in use by the callout).  While we are in
+	 * this loop, info->count is dropped by one, so that
+	 * close() knows when to free things.  We restore it upon
+	 * exit, either normal or abnormal.
+	 */
+
+	retval = 0;
+	add_wait_queue(&info->open_wait, &wait);
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s block_til_ready() before block, count=%d\n",
+			 __FILE__,__LINE__, tty->driver->name, info->count );
+
+	spin_lock_irqsave(&info->lock, flags);
+	if (!tty_hung_up_p(filp)) {
+		extra_count = 1;
+		info->count--;
+	}
+	spin_unlock_irqrestore(&info->lock, flags);
+	info->blocked_open++;
+
+	while (1) {
+		if ((tty->termios->c_cflag & CBAUD)) {
+			spin_lock_irqsave(&info->lock,flags);
+			info->serial_signals |= SerialSignal_RTS + SerialSignal_DTR;
+		 	set_signals(info);
+			spin_unlock_irqrestore(&info->lock,flags);
+		}
+
+		set_current_state(TASK_INTERRUPTIBLE);
+
+		if (tty_hung_up_p(filp) || !(info->flags & ASYNC_INITIALIZED)){
+			retval = (info->flags & ASYNC_HUP_NOTIFY) ?
+					-EAGAIN : -ERESTARTSYS;
+			break;
+		}
+
+		spin_lock_irqsave(&info->lock,flags);
+	 	get_signals(info);
+		spin_unlock_irqrestore(&info->lock,flags);
+
+ 		if (!(info->flags & ASYNC_CLOSING) &&
+ 		    (do_clocal || (info->serial_signals & SerialSignal_DCD)) ) {
+ 			break;
+		}
+
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+
+		if (debug_level >= DEBUG_LEVEL_INFO)
+			printk("%s(%d):%s block_til_ready() count=%d\n",
+				 __FILE__,__LINE__, tty->driver->name, info->count );
+
+		schedule();
+	}
+
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&info->open_wait, &wait);
+
+	if (extra_count)
+		info->count++;
+	info->blocked_open--;
+
+	if (debug_level >= DEBUG_LEVEL_INFO)
+		printk("%s(%d):%s block_til_ready() after, count=%d\n",
+			 __FILE__,__LINE__, tty->driver->name, info->count );
+
+	if (!retval)
+		info->flags |= ASYNC_NORMAL_ACTIVE;
+
+	return retval;
+}
+
+int alloc_dma_bufs(SLMP_INFO *info)
+{
+	unsigned short BuffersPerFrame;
+	unsigned short BufferCount;
+
+	// Force allocation to start at 64K boundary for each port.
+	// This is necessary because *all* buffer descriptors for a port
+	// *must* be in the same 64K block. All descriptors on a port
+	// share a common 'base' address (upper 8 bits of 24 bits) programmed
+	// into the CBP register.
+	info->port_array[0]->last_mem_alloc = (SCA_MEM_SIZE/4) * info->port_num;
+
+	/* Calculate the number of DMA buffers necessary to hold the */
+	/* largest allowable frame size. Note: If the max frame size is */
+	/* not an even multiple of the DMA buffer size then we need to */
+	/* round the buffer count per frame up one. */
+
+	BuffersPerFrame = (unsigned short)(info->max_frame_size/SCABUFSIZE);
+	if ( info->max_frame_size % SCABUFSIZE )
+		BuffersPerFrame++;
+
+	/* calculate total number of data buffers (SCABUFSIZE) possible
+	 * in one ports memory (SCA_MEM_SIZE/4) after allocating memory
+	 * for the descriptor list (BUFFERLISTSIZE).
+	 */
+	BufferCount = (SCA_MEM_SIZE/4 - BUFFERLISTSIZE)/SCABUFSIZE;
+
+	/* limit number of buffers to maximum amount of descriptors */
+	if (BufferCount > BUFFERLISTSIZE/sizeof(SCADESC))
+		BufferCount = BUFFERLISTSIZE/sizeof(SCADESC);
+
+	/* use enough buffers to transmit one max size frame */
+	info->tx_buf_count = BuffersPerFrame + 1;
+
+	/* never use more than half the available buffers for transmit */
+	if (info->tx_buf_count > (BufferCount/2))
+		info->tx_buf_count = BufferCount/2;
+
+	if (info->tx_buf_count > SCAMAXDESC)
+		info->tx_buf_count = SCAMAXDESC;
+
+	/* use remaining buffers for receive */
+	info->rx_buf_count = BufferCount - info->tx_buf_count;
+
+	if (info->rx_buf_count > SCAMAXDESC)
+		info->rx_buf_count = SCAMAXDESC;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk("%s(%d):%s Allocating %d TX and %d RX DMA buffers.\n",
+			__FILE__,__LINE__, info->device_name,
+			info->tx_buf_count,info->rx_buf_count);
+
+	if ( alloc_buf_list( info ) < 0 ||
+		alloc_frame_bufs(info,
+		  			info->rx_buf_list,
+		  			info->rx_buf_list_ex,
+					info->rx_buf_count) < 0 ||
+		alloc_frame_bufs(info,
+					info->tx_buf_list,
+					info->tx_buf_list_ex,
+					info->tx_buf_count) < 0 ||
+		alloc_tmp_rx_buf(info) < 0 ) {
+		printk("%s(%d):%s Can't allocate DMA buffer memory\n",
+			__FILE__,__LINE__, info->device_name);
+		return -ENOMEM;
+	}
+
+	rx_reset_buffers( info );
+
+	return 0;
+}
+
+/* Allocate DMA buffers for the transmit and receive descriptor lists.
+ */
+int alloc_buf_list(SLMP_INFO *info)
+{
+	unsigned int i;
+
+	/* build list in adapter shared memory */
+	info->buffer_list = info->memory_base + info->port_array[0]->last_mem_alloc;
+	info->buffer_list_phys = info->port_array[0]->last_mem_alloc;
+	info->port_array[0]->last_mem_alloc += BUFFERLISTSIZE;
+
+	memset(info->buffer_list, 0, BUFFERLISTSIZE);
+
+	/* Save virtual address pointers to the receive and */
+	/* transmit buffer lists. (Receive 1st). These pointers will */
+	/* be used by the processor to access the lists. */
+	info->rx_buf_list = (SCADESC *)info->buffer_list;
+
+	info->tx_buf_list = (SCADESC *)info->buffer_list;
+	info->tx_buf_list += info->rx_buf_count;
+
+	/* Build links for circular buffer entry lists (tx and rx)
+	 *
+	 * Note: links are physical addresses read by the SCA device
+	 * to determine the next buffer entry to use.
+	 */
+
+	for ( i = 0; i < info->rx_buf_count; i++ ) {
+		/* calculate and store physical address of this buffer entry */
+		info->rx_buf_list_ex[i].phys_entry =
+			info->buffer_list_phys + (i * sizeof(SCABUFSIZE));
+
+		/* calculate and store physical address of */
+		/* next entry in cirular list of entries */
+		info->rx_buf_list[i].next = info->buffer_list_phys;
+		if ( i < info->rx_buf_count - 1 )
+			info->rx_buf_list[i].next += (i + 1) * sizeof(SCADESC);
+
+		info->rx_buf_list[i].length = SCABUFSIZE;
+	}
+
+	for ( i = 0; i < info->tx_buf_count; i++ ) {
+		/* calculate and store physical address of this buffer entry */
+		info->tx_buf_list_ex[i].phys_entry = info->buffer_list_phys +
+			((info->rx_buf_count + i) * sizeof(SCADESC));
+
+		/* calculate and store physical address of */
+		/* next entry in cirular list of entries */
+
+		info->tx_buf_list[i].next = info->buffer_list_phys +
+			info->rx_buf_count * sizeof(SCADESC);
+
+		if ( i < info->tx_buf_count - 1 )
+			info->tx_buf_list[i].next += (i + 1) * sizeof(SCADESC);
+	}
+
+	return 0;
+}
+
+/* Allocate the frame DMA buffers used by the specified buffer list.
+ */
+int alloc_frame_bufs(SLMP_INFO *info, SCADESC *buf_list,SCADESC_EX *buf_list_ex,int count)
+{
+	int i;
+	unsigned long phys_addr;
+
+	for ( i = 0; i < count; i++ ) {
+		buf_list_ex[i].virt_addr = info->memory_base + info->port_array[0]->last_mem_alloc;
+		phys_addr = info->port_array[0]->last_mem_alloc;
+		info->port_array[0]->last_mem_alloc += SCABUFSIZE;
+
+		buf_list[i].buf_ptr  = (unsigned short)phys_addr;
+		buf_list[i].buf_base = (unsigned char)(phys_addr >> 16);
+	}
+
+	return 0;
+}
+
+void free_dma_bufs(SLMP_INFO *info)
+{
+	info->buffer_list = NULL;
+	info->rx_buf_list = NULL;
+	info->tx_buf_list = NULL;
+}
+
+/* allocate buffer large enough to hold max_frame_size.
+ * This buffer is used to pass an assembled frame to the line discipline.
+ */
+int alloc_tmp_rx_buf(SLMP_INFO *info)
+{
+	info->tmp_rx_buf = kmalloc(info->max_frame_size, GFP_KERNEL);
+	if (info->tmp_rx_buf == NULL)
+		return -ENOMEM;
+	return 0;
+}
+
+void free_tmp_rx_buf(SLMP_INFO *info)
+{
+	if (info->tmp_rx_buf)
+		kfree(info->tmp_rx_buf);
+	info->tmp_rx_buf = NULL;
+}
+
+int claim_resources(SLMP_INFO *info)
+{
+	if (request_mem_region(info->phys_memory_base,SCA_MEM_SIZE,"synclinkmp") == NULL) {
+		printk( "%s(%d):%s mem addr conflict, Addr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_memory_base);
+		info->init_error = DiagStatus_AddressConflict;
+		goto errout;
+	}
+	else
+		info->shared_mem_requested = 1;
+
+	if (request_mem_region(info->phys_lcr_base + info->lcr_offset,128,"synclinkmp") == NULL) {
+		printk( "%s(%d):%s lcr mem addr conflict, Addr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_lcr_base);
+		info->init_error = DiagStatus_AddressConflict;
+		goto errout;
+	}
+	else
+		info->lcr_mem_requested = 1;
+
+	if (request_mem_region(info->phys_sca_base + info->sca_offset,SCA_BASE_SIZE,"synclinkmp") == NULL) {
+		printk( "%s(%d):%s sca mem addr conflict, Addr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_sca_base);
+		info->init_error = DiagStatus_AddressConflict;
+		goto errout;
+	}
+	else
+		info->sca_base_requested = 1;
+
+	if (request_mem_region(info->phys_statctrl_base + info->statctrl_offset,SCA_REG_SIZE,"synclinkmp") == NULL) {
+		printk( "%s(%d):%s stat/ctrl mem addr conflict, Addr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_statctrl_base);
+		info->init_error = DiagStatus_AddressConflict;
+		goto errout;
+	}
+	else
+		info->sca_statctrl_requested = 1;
+
+	info->memory_base = ioremap(info->phys_memory_base,SCA_MEM_SIZE);
+	if (!info->memory_base) {
+		printk( "%s(%d):%s Cant map shared memory, MemAddr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_memory_base );
+		info->init_error = DiagStatus_CantAssignPciResources;
+		goto errout;
+	}
+
+	info->lcr_base = ioremap(info->phys_lcr_base,PAGE_SIZE);
+	if (!info->lcr_base) {
+		printk( "%s(%d):%s Cant map LCR memory, MemAddr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_lcr_base );
+		info->init_error = DiagStatus_CantAssignPciResources;
+		goto errout;
+	}
+	info->lcr_base += info->lcr_offset;
+
+	info->sca_base = ioremap(info->phys_sca_base,PAGE_SIZE);
+	if (!info->sca_base) {
+		printk( "%s(%d):%s Cant map SCA memory, MemAddr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_sca_base );
+		info->init_error = DiagStatus_CantAssignPciResources;
+		goto errout;
+	}
+	info->sca_base += info->sca_offset;
+
+	info->statctrl_base = ioremap(info->phys_statctrl_base,PAGE_SIZE);
+	if (!info->statctrl_base) {
+		printk( "%s(%d):%s Cant map SCA Status/Control memory, MemAddr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_statctrl_base );
+		info->init_error = DiagStatus_CantAssignPciResources;
+		goto errout;
+	}
+	info->statctrl_base += info->statctrl_offset;
+
+	if ( !memory_test(info) ) {
+		printk( "%s(%d):Shared Memory Test failed for device %s MemAddr=%08X\n",
+			__FILE__,__LINE__,info->device_name, info->phys_memory_base );
+		info->init_error = DiagStatus_MemoryError;
+		goto errout;
+	}
+
+	return 0;
+
+errout:
+	release_resources( info );
+	return -ENODEV;
+}
+
+void release_resources(SLMP_INFO *info)
+{
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):%s release_resources() entry\n",
+			__FILE__,__LINE__,info->device_name );
+
+	if ( info->irq_requested ) {
+		free_irq(info->irq_level, info);
+		info->irq_requested = 0;
+	}
+
+	if ( info->shared_mem_requested ) {
+		release_mem_region(info->phys_memory_base,SCA_MEM_SIZE);
+		info->shared_mem_requested = 0;
+	}
+	if ( info->lcr_mem_requested ) {
+		release_mem_region(info->phys_lcr_base + info->lcr_offset,128);
+		info->lcr_mem_requested = 0;
+	}
+	if ( info->sca_base_requested ) {
+		release_mem_region(info->phys_sca_base + info->sca_offset,SCA_BASE_SIZE);
+		info->sca_base_requested = 0;
+	}
+	if ( info->sca_statctrl_requested ) {
+		release_mem_region(info->phys_statctrl_base + info->statctrl_offset,SCA_REG_SIZE);
+		info->sca_statctrl_requested = 0;
+	}
+
+	if (info->memory_base){
+		iounmap(info->memory_base);
+		info->memory_base = NULL;
+	}
+
+	if (info->sca_base) {
+		iounmap(info->sca_base - info->sca_offset);
+		info->sca_base=NULL;
+	}
+
+	if (info->statctrl_base) {
+		iounmap(info->statctrl_base - info->statctrl_offset);
+		info->statctrl_base=NULL;
+	}
+
+	if (info->lcr_base){
+		iounmap(info->lcr_base - info->lcr_offset);
+		info->lcr_base = NULL;
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):%s release_resources() exit\n",
+			__FILE__,__LINE__,info->device_name );
+}
+
+/* Add the specified device instance data structure to the
+ * global linked list of devices and increment the device count.
+ */
+void add_device(SLMP_INFO *info)
+{
+	info->next_device = NULL;
+	info->line = synclinkmp_device_count;
+	sprintf(info->device_name,"ttySLM%dp%d",info->adapter_num,info->port_num);
+
+	if (info->line < MAX_DEVICES) {
+		if (maxframe[info->line])
+			info->max_frame_size = maxframe[info->line];
+		info->dosyncppp = dosyncppp[info->line];
+	}
+
+	synclinkmp_device_count++;
+
+	if ( !synclinkmp_device_list )
+		synclinkmp_device_list = info;
+	else {
+		SLMP_INFO *current_dev = synclinkmp_device_list;
+		while( current_dev->next_device )
+			current_dev = current_dev->next_device;
+		current_dev->next_device = info;
+	}
+
+	if ( info->max_frame_size < 4096 )
+		info->max_frame_size = 4096;
+	else if ( info->max_frame_size > 65535 )
+		info->max_frame_size = 65535;
+
+	printk( "SyncLink MultiPort %s: "
+		"Mem=(%08x %08X %08x %08X) IRQ=%d MaxFrameSize=%u\n",
+		info->device_name,
+		info->phys_sca_base,
+		info->phys_memory_base,
+		info->phys_statctrl_base,
+		info->phys_lcr_base,
+		info->irq_level,
+		info->max_frame_size );
+
+#ifdef CONFIG_HDLC
+	hdlcdev_init(info);
+#endif
+}
+
+/* Allocate and initialize a device instance structure
+ *
+ * Return Value:	pointer to SLMP_INFO if success, otherwise NULL
+ */
+static SLMP_INFO *alloc_dev(int adapter_num, int port_num, struct pci_dev *pdev)
+{
+	SLMP_INFO *info;
+
+	info = (SLMP_INFO *)kmalloc(sizeof(SLMP_INFO),
+		 GFP_KERNEL);
+
+	if (!info) {
+		printk("%s(%d) Error can't allocate device instance data for adapter %d, port %d\n",
+			__FILE__,__LINE__, adapter_num, port_num);
+	} else {
+		memset(info, 0, sizeof(SLMP_INFO));
+		info->magic = MGSL_MAGIC;
+		INIT_WORK(&info->task, bh_handler, info);
+		info->max_frame_size = 4096;
+		info->close_delay = 5*HZ/10;
+		info->closing_wait = 30*HZ;
+		init_waitqueue_head(&info->open_wait);
+		init_waitqueue_head(&info->close_wait);
+		init_waitqueue_head(&info->status_event_wait_q);
+		init_waitqueue_head(&info->event_wait_q);
+		spin_lock_init(&info->netlock);
+		memcpy(&info->params,&default_params,sizeof(MGSL_PARAMS));
+		info->idle_mode = HDLC_TXIDLE_FLAGS;
+		info->adapter_num = adapter_num;
+		info->port_num = port_num;
+
+		/* Copy configuration info to device instance data */
+		info->irq_level = pdev->irq;
+		info->phys_lcr_base = pci_resource_start(pdev,0);
+		info->phys_sca_base = pci_resource_start(pdev,2);
+		info->phys_memory_base = pci_resource_start(pdev,3);
+		info->phys_statctrl_base = pci_resource_start(pdev,4);
+
+		/* Because veremap only works on page boundaries we must map
+		 * a larger area than is actually implemented for the LCR
+		 * memory range. We map a full page starting at the page boundary.
+		 */
+		info->lcr_offset    = info->phys_lcr_base & (PAGE_SIZE-1);
+		info->phys_lcr_base &= ~(PAGE_SIZE-1);
+
+		info->sca_offset    = info->phys_sca_base & (PAGE_SIZE-1);
+		info->phys_sca_base &= ~(PAGE_SIZE-1);
+
+		info->statctrl_offset    = info->phys_statctrl_base & (PAGE_SIZE-1);
+		info->phys_statctrl_base &= ~(PAGE_SIZE-1);
+
+		info->bus_type = MGSL_BUS_TYPE_PCI;
+		info->irq_flags = SA_SHIRQ;
+
+		init_timer(&info->tx_timer);
+		info->tx_timer.data = (unsigned long)info;
+		info->tx_timer.function = tx_timeout;
+
+		init_timer(&info->status_timer);
+		info->status_timer.data = (unsigned long)info;
+		info->status_timer.function = status_timeout;
+
+		/* Store the PCI9050 misc control register value because a flaw
+		 * in the PCI9050 prevents LCR registers from being read if
+		 * BIOS assigns an LCR base address with bit 7 set.
+		 *
+		 * Only the misc control register is accessed for which only
+		 * write access is needed, so set an initial value and change
+		 * bits to the device instance data as we write the value
+		 * to the actual misc control register.
+		 */
+		info->misc_ctrl_value = 0x087e4546;
+
+		/* initial port state is unknown - if startup errors
+		 * occur, init_error will be set to indicate the
+		 * problem. Once the port is fully initialized,
+		 * this value will be set to 0 to indicate the
+		 * port is available.
+		 */
+		info->init_error = -1;
+	}
+
+	return info;
+}
+
+void device_init(int adapter_num, struct pci_dev *pdev)
+{
+	SLMP_INFO *port_array[SCA_MAX_PORTS];
+	int port;
+
+	/* allocate device instances for up to SCA_MAX_PORTS devices */
+	for ( port = 0; port < SCA_MAX_PORTS; ++port ) {
+		port_array[port] = alloc_dev(adapter_num,port,pdev);
+		if( port_array[port] == NULL ) {
+			for ( --port; port >= 0; --port )
+				kfree(port_array[port]);
+			return;
+		}
+	}
+
+	/* give copy of port_array to all ports and add to device list  */
+	for ( port = 0; port < SCA_MAX_PORTS; ++port ) {
+		memcpy(port_array[port]->port_array,port_array,sizeof(port_array));
+		add_device( port_array[port] );
+		spin_lock_init(&port_array[port]->lock);
+	}
+
+	/* Allocate and claim adapter resources */
+	if ( !claim_resources(port_array[0]) ) {
+
+		alloc_dma_bufs(port_array[0]);
+
+		/* copy resource information from first port to others */
+		for ( port = 1; port < SCA_MAX_PORTS; ++port ) {
+			port_array[port]->lock  = port_array[0]->lock;
+			port_array[port]->irq_level     = port_array[0]->irq_level;
+			port_array[port]->memory_base   = port_array[0]->memory_base;
+			port_array[port]->sca_base      = port_array[0]->sca_base;
+			port_array[port]->statctrl_base = port_array[0]->statctrl_base;
+			port_array[port]->lcr_base      = port_array[0]->lcr_base;
+			alloc_dma_bufs(port_array[port]);
+		}
+
+		if ( request_irq(port_array[0]->irq_level,
+					synclinkmp_interrupt,
+					port_array[0]->irq_flags,
+					port_array[0]->device_name,
+					port_array[0]) < 0 ) {
+			printk( "%s(%d):%s Cant request interrupt, IRQ=%d\n",
+				__FILE__,__LINE__,
+				port_array[0]->device_name,
+				port_array[0]->irq_level );
+		}
+		else {
+			port_array[0]->irq_requested = 1;
+			adapter_test(port_array[0]);
+		}
+	}
+}
+
+static struct tty_operations ops = {
+	.open = open,
+	.close = close,
+	.write = write,
+	.put_char = put_char,
+	.flush_chars = flush_chars,
+	.write_room = write_room,
+	.chars_in_buffer = chars_in_buffer,
+	.flush_buffer = flush_buffer,
+	.ioctl = ioctl,
+	.throttle = throttle,
+	.unthrottle = unthrottle,
+	.send_xchar = send_xchar,
+	.break_ctl = set_break,
+	.wait_until_sent = wait_until_sent,
+ 	.read_proc = read_proc,
+	.set_termios = set_termios,
+	.stop = tx_hold,
+	.start = tx_release,
+	.hangup = hangup,
+	.tiocmget = tiocmget,
+	.tiocmset = tiocmset,
+};
+
+static void synclinkmp_cleanup(void)
+{
+	int rc;
+	SLMP_INFO *info;
+	SLMP_INFO *tmp;
+
+	printk("Unloading %s %s\n", driver_name, driver_version);
+
+	if (serial_driver) {
+		if ((rc = tty_unregister_driver(serial_driver)))
+			printk("%s(%d) failed to unregister tty driver err=%d\n",
+			       __FILE__,__LINE__,rc);
+		put_tty_driver(serial_driver);
+	}
+
+	/* reset devices */
+	info = synclinkmp_device_list;
+	while(info) {
+		reset_port(info);
+		info = info->next_device;
+	}
+
+	/* release devices */
+	info = synclinkmp_device_list;
+	while(info) {
+#ifdef CONFIG_HDLC
+		hdlcdev_exit(info);
+#endif
+		free_dma_bufs(info);
+		free_tmp_rx_buf(info);
+		if ( info->port_num == 0 ) {
+			if (info->sca_base)
+				write_reg(info, LPR, 1); /* set low power mode */
+			release_resources(info);
+		}
+		tmp = info;
+		info = info->next_device;
+		kfree(tmp);
+	}
+
+	pci_unregister_driver(&synclinkmp_pci_driver);
+}
+
+/* Driver initialization entry point.
+ */
+
+static int __init synclinkmp_init(void)
+{
+	int rc;
+
+	if (break_on_load) {
+	 	synclinkmp_get_text_ptr();
+  		BREAKPOINT();
+	}
+
+ 	printk("%s %s\n", driver_name, driver_version);
+
+	if ((rc = pci_register_driver(&synclinkmp_pci_driver)) < 0) {
+		printk("%s:failed to register PCI driver, error=%d\n",__FILE__,rc);
+		return rc;
+	}
+
+	serial_driver = alloc_tty_driver(128);
+	if (!serial_driver) {
+		rc = -ENOMEM;
+		goto error;
+	}
+
+	/* Initialize the tty_driver structure */
+
+	serial_driver->owner = THIS_MODULE;
+	serial_driver->driver_name = "synclinkmp";
+	serial_driver->name = "ttySLM";
+	serial_driver->major = ttymajor;
+	serial_driver->minor_start = 64;
+	serial_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	serial_driver->subtype = SERIAL_TYPE_NORMAL;
+	serial_driver->init_termios = tty_std_termios;
+	serial_driver->init_termios.c_cflag =
+		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	serial_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(serial_driver, &ops);
+	if ((rc = tty_register_driver(serial_driver)) < 0) {
+		printk("%s(%d):Couldn't register serial driver\n",
+			__FILE__,__LINE__);
+		put_tty_driver(serial_driver);
+		serial_driver = NULL;
+		goto error;
+	}
+
+ 	printk("%s %s, tty major#%d\n",
+		driver_name, driver_version,
+		serial_driver->major);
+
+	return 0;
+
+error:
+	synclinkmp_cleanup();
+	return rc;
+}
+
+static void __exit synclinkmp_exit(void)
+{
+	synclinkmp_cleanup();
+}
+
+module_init(synclinkmp_init);
+module_exit(synclinkmp_exit);
+
+/* Set the port for internal loopback mode.
+ * The TxCLK and RxCLK signals are generated from the BRG and
+ * the TxD is looped back to the RxD internally.
+ */
+void enable_loopback(SLMP_INFO *info, int enable)
+{
+	if (enable) {
+		/* MD2 (Mode Register 2)
+		 * 01..00  CNCT<1..0> Channel Connection 11=Local Loopback
+		 */
+		write_reg(info, MD2, (unsigned char)(read_reg(info, MD2) | (BIT1 + BIT0)));
+
+		/* degate external TxC clock source */
+		info->port_array[0]->ctrlreg_value |= (BIT0 << (info->port_num * 2));
+		write_control_reg(info);
+
+		/* RXS/TXS (Rx/Tx clock source)
+		 * 07      Reserved, must be 0
+		 * 06..04  Clock Source, 100=BRG
+		 * 03..00  Clock Divisor, 0000=1
+		 */
+		write_reg(info, RXS, 0x40);
+		write_reg(info, TXS, 0x40);
+
+	} else {
+		/* MD2 (Mode Register 2)
+	 	 * 01..00  CNCT<1..0> Channel connection, 0=normal
+		 */
+		write_reg(info, MD2, (unsigned char)(read_reg(info, MD2) & ~(BIT1 + BIT0)));
+
+		/* RXS/TXS (Rx/Tx clock source)
+		 * 07      Reserved, must be 0
+		 * 06..04  Clock Source, 000=RxC/TxC Pin
+		 * 03..00  Clock Divisor, 0000=1
+		 */
+		write_reg(info, RXS, 0x00);
+		write_reg(info, TXS, 0x00);
+	}
+
+	/* set LinkSpeed if available, otherwise default to 2Mbps */
+	if (info->params.clock_speed)
+		set_rate(info, info->params.clock_speed);
+	else
+		set_rate(info, 3686400);
+}
+
+/* Set the baud rate register to the desired speed
+ *
+ *	data_rate	data rate of clock in bits per second
+ *			A data rate of 0 disables the AUX clock.
+ */
+void set_rate( SLMP_INFO *info, u32 data_rate )
+{
+       	u32 TMCValue;
+       	unsigned char BRValue;
+	u32 Divisor=0;
+
+	/* fBRG = fCLK/(TMC * 2^BR)
+	 */
+	if (data_rate != 0) {
+		Divisor = 14745600/data_rate;
+		if (!Divisor)
+			Divisor = 1;
+
+		TMCValue = Divisor;
+
+		BRValue = 0;
+		if (TMCValue != 1 && TMCValue != 2) {
+			/* BRValue of 0 provides 50/50 duty cycle *only* when
+			 * TMCValue is 1 or 2. BRValue of 1 to 9 always provides
+			 * 50/50 duty cycle.
+			 */
+			BRValue = 1;
+			TMCValue >>= 1;
+		}
+
+		/* while TMCValue is too big for TMC register, divide
+		 * by 2 and increment BR exponent.
+		 */
+		for(; TMCValue > 256 && BRValue < 10; BRValue++)
+			TMCValue >>= 1;
+
+		write_reg(info, TXS,
+			(unsigned char)((read_reg(info, TXS) & 0xf0) | BRValue));
+		write_reg(info, RXS,
+			(unsigned char)((read_reg(info, RXS) & 0xf0) | BRValue));
+		write_reg(info, TMC, (unsigned char)TMCValue);
+	}
+	else {
+		write_reg(info, TXS,0);
+		write_reg(info, RXS,0);
+		write_reg(info, TMC, 0);
+	}
+}
+
+/* Disable receiver
+ */
+void rx_stop(SLMP_INFO *info)
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):%s rx_stop()\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	write_reg(info, CMD, RXRESET);
+
+	info->ie0_value &= ~RXRDYE;
+	write_reg(info, IE0, info->ie0_value);	/* disable Rx data interrupts */
+
+	write_reg(info, RXDMA + DSR, 0);	/* disable Rx DMA */
+	write_reg(info, RXDMA + DCMD, SWABORT);	/* reset/init Rx DMA */
+	write_reg(info, RXDMA + DIR, 0);	/* disable Rx DMA interrupts */
+
+	info->rx_enabled = 0;
+	info->rx_overflow = 0;
+}
+
+/* enable the receiver
+ */
+void rx_start(SLMP_INFO *info)
+{
+	int i;
+
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):%s rx_start()\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	write_reg(info, CMD, RXRESET);
+
+	if ( info->params.mode == MGSL_MODE_HDLC ) {
+		/* HDLC, disabe IRQ on rxdata */
+		info->ie0_value &= ~RXRDYE;
+		write_reg(info, IE0, info->ie0_value);
+
+		/* Reset all Rx DMA buffers and program rx dma */
+		write_reg(info, RXDMA + DSR, 0);		/* disable Rx DMA */
+		write_reg(info, RXDMA + DCMD, SWABORT);	/* reset/init Rx DMA */
+
+		for (i = 0; i < info->rx_buf_count; i++) {
+			info->rx_buf_list[i].status = 0xff;
+
+			// throttle to 4 shared memory writes at a time to prevent
+			// hogging local bus (keep latency time for DMA requests low).
+			if (!(i % 4))
+				read_status_reg(info);
+		}
+		info->current_rx_buf = 0;
+
+		/* set current/1st descriptor address */
+		write_reg16(info, RXDMA + CDA,
+			info->rx_buf_list_ex[0].phys_entry);
+
+		/* set new last rx descriptor address */
+		write_reg16(info, RXDMA + EDA,
+			info->rx_buf_list_ex[info->rx_buf_count - 1].phys_entry);
+
+		/* set buffer length (shared by all rx dma data buffers) */
+		write_reg16(info, RXDMA + BFL, SCABUFSIZE);
+
+		write_reg(info, RXDMA + DIR, 0x60);	/* enable Rx DMA interrupts (EOM/BOF) */
+		write_reg(info, RXDMA + DSR, 0xf2);	/* clear Rx DMA IRQs, enable Rx DMA */
+	} else {
+		/* async, enable IRQ on rxdata */
+		info->ie0_value |= RXRDYE;
+		write_reg(info, IE0, info->ie0_value);
+	}
+
+	write_reg(info, CMD, RXENABLE);
+
+	info->rx_overflow = FALSE;
+	info->rx_enabled = 1;
+}
+
+/* Enable the transmitter and send a transmit frame if
+ * one is loaded in the DMA buffers.
+ */
+void tx_start(SLMP_INFO *info)
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):%s tx_start() tx_count=%d\n",
+			 __FILE__,__LINE__, info->device_name,info->tx_count );
+
+	if (!info->tx_enabled ) {
+		write_reg(info, CMD, TXRESET);
+		write_reg(info, CMD, TXENABLE);
+		info->tx_enabled = TRUE;
+	}
+
+	if ( info->tx_count ) {
+
+		/* If auto RTS enabled and RTS is inactive, then assert */
+		/* RTS and set a flag indicating that the driver should */
+		/* negate RTS when the transmission completes. */
+
+		info->drop_rts_on_tx_done = 0;
+
+		if (info->params.mode != MGSL_MODE_ASYNC) {
+
+			if ( info->params.flags & HDLC_FLAG_AUTO_RTS ) {
+				get_signals( info );
+				if ( !(info->serial_signals & SerialSignal_RTS) ) {
+					info->serial_signals |= SerialSignal_RTS;
+					set_signals( info );
+					info->drop_rts_on_tx_done = 1;
+				}
+			}
+
+			write_reg16(info, TRC0,
+				(unsigned short)(((tx_negate_fifo_level-1)<<8) + tx_active_fifo_level));
+
+			write_reg(info, TXDMA + DSR, 0); 		/* disable DMA channel */
+			write_reg(info, TXDMA + DCMD, SWABORT);	/* reset/init DMA channel */
+	
+			/* set TX CDA (current descriptor address) */
+			write_reg16(info, TXDMA + CDA,
+				info->tx_buf_list_ex[0].phys_entry);
+	
+			/* set TX EDA (last descriptor address) */
+			write_reg16(info, TXDMA + EDA,
+				info->tx_buf_list_ex[info->last_tx_buf].phys_entry);
+	
+			/* enable underrun IRQ */
+			info->ie1_value &= ~IDLE;
+			info->ie1_value |= UDRN;
+			write_reg(info, IE1, info->ie1_value);
+			write_reg(info, SR1, (unsigned char)(IDLE + UDRN));
+	
+			write_reg(info, TXDMA + DIR, 0x40);		/* enable Tx DMA interrupts (EOM) */
+			write_reg(info, TXDMA + DSR, 0xf2);		/* clear Tx DMA IRQs, enable Tx DMA */
+	
+			info->tx_timer.expires = jiffies + msecs_to_jiffies(5000);
+			add_timer(&info->tx_timer);
+		}
+		else {
+			tx_load_fifo(info);
+			/* async, enable IRQ on txdata */
+			info->ie0_value |= TXRDYE;
+			write_reg(info, IE0, info->ie0_value);
+		}
+
+		info->tx_active = 1;
+	}
+}
+
+/* stop the transmitter and DMA
+ */
+void tx_stop( SLMP_INFO *info )
+{
+	if (debug_level >= DEBUG_LEVEL_ISR)
+		printk("%s(%d):%s tx_stop()\n",
+			 __FILE__,__LINE__, info->device_name );
+
+	del_timer(&info->tx_timer);
+
+	write_reg(info, TXDMA + DSR, 0);		/* disable DMA channel */
+	write_reg(info, TXDMA + DCMD, SWABORT);	/* reset/init DMA channel */
+
+	write_reg(info, CMD, TXRESET);
+
+	info->ie1_value &= ~(UDRN + IDLE);
+	write_reg(info, IE1, info->ie1_value);	/* disable tx status interrupts */
+	write_reg(info, SR1, (unsigned char)(IDLE + UDRN));	/* clear pending */
+
+	info->ie0_value &= ~TXRDYE;
+	write_reg(info, IE0, info->ie0_value);	/* disable tx data interrupts */
+
+	info->tx_enabled = 0;
+	info->tx_active  = 0;
+}
+
+/* Fill the transmit FIFO until the FIFO is full or
+ * there is no more data to load.
+ */
+void tx_load_fifo(SLMP_INFO *info)
+{
+	u8 TwoBytes[2];
+
+	/* do nothing is now tx data available and no XON/XOFF pending */
+
+	if ( !info->tx_count && !info->x_char )
+		return;
+
+	/* load the Transmit FIFO until FIFOs full or all data sent */
+
+	while( info->tx_count && (read_reg(info,SR0) & BIT1) ) {
+
+		/* there is more space in the transmit FIFO and */
+		/* there is more data in transmit buffer */
+
+		if ( (info->tx_count > 1) && !info->x_char ) {
+ 			/* write 16-bits */
+			TwoBytes[0] = info->tx_buf[info->tx_get++];
+			if (info->tx_get >= info->max_frame_size)
+				info->tx_get -= info->max_frame_size;
+			TwoBytes[1] = info->tx_buf[info->tx_get++];
+			if (info->tx_get >= info->max_frame_size)
+				info->tx_get -= info->max_frame_size;
+
+			write_reg16(info, TRB, *((u16 *)TwoBytes));
+
+			info->tx_count -= 2;
+			info->icount.tx += 2;
+		} else {
+			/* only 1 byte left to transmit or 1 FIFO slot left */
+
+			if (info->x_char) {
+				/* transmit pending high priority char */
+				write_reg(info, TRB, info->x_char);
+				info->x_char = 0;
+			} else {
+				write_reg(info, TRB, info->tx_buf[info->tx_get++]);
+				if (info->tx_get >= info->max_frame_size)
+					info->tx_get -= info->max_frame_size;
+				info->tx_count--;
+			}
+			info->icount.tx++;
+		}
+	}
+}
+
+/* Reset a port to a known state
+ */
+void reset_port(SLMP_INFO *info)
+{
+	if (info->sca_base) {
+
+		tx_stop(info);
+		rx_stop(info);
+
+		info->serial_signals &= ~(SerialSignal_DTR + SerialSignal_RTS);
+		set_signals(info);
+
+		/* disable all port interrupts */
+		info->ie0_value = 0;
+		info->ie1_value = 0;
+		info->ie2_value = 0;
+		write_reg(info, IE0, info->ie0_value);
+		write_reg(info, IE1, info->ie1_value);
+		write_reg(info, IE2, info->ie2_value);
+
+		write_reg(info, CMD, CHRESET);
+	}
+}
+
+/* Reset all the ports to a known state.
+ */
+void reset_adapter(SLMP_INFO *info)
+{
+	int i;
+
+	for ( i=0; i < SCA_MAX_PORTS; ++i) {
+		if (info->port_array[i])
+			reset_port(info->port_array[i]);
+	}
+}
+
+/* Program port for asynchronous communications.
+ */
+void async_mode(SLMP_INFO *info)
+{
+
+  	unsigned char RegValue;
+
+	tx_stop(info);
+	rx_stop(info);
+
+	/* MD0, Mode Register 0
+	 *
+	 * 07..05  PRCTL<2..0>, Protocol Mode, 000=async
+	 * 04      AUTO, Auto-enable (RTS/CTS/DCD)
+	 * 03      Reserved, must be 0
+	 * 02      CRCCC, CRC Calculation, 0=disabled
+	 * 01..00  STOP<1..0> Stop bits (00=1,10=2)
+	 *
+	 * 0000 0000
+	 */
+	RegValue = 0x00;
+	if (info->params.stop_bits != 1)
+		RegValue |= BIT1;
+	write_reg(info, MD0, RegValue);
+
+	/* MD1, Mode Register 1
+	 *
+	 * 07..06  BRATE<1..0>, bit rate, 00=1/1 01=1/16 10=1/32 11=1/64
+	 * 05..04  TXCHR<1..0>, tx char size, 00=8 bits,01=7,10=6,11=5
+	 * 03..02  RXCHR<1..0>, rx char size
+	 * 01..00  PMPM<1..0>, Parity mode, 00=none 10=even 11=odd
+	 *
+	 * 0100 0000
+	 */
+	RegValue = 0x40;
+	switch (info->params.data_bits) {
+	case 7: RegValue |= BIT4 + BIT2; break;
+	case 6: RegValue |= BIT5 + BIT3; break;
+	case 5: RegValue |= BIT5 + BIT4 + BIT3 + BIT2; break;
+	}
+	if (info->params.parity != ASYNC_PARITY_NONE) {
+		RegValue |= BIT1;
+		if (info->params.parity == ASYNC_PARITY_ODD)
+			RegValue |= BIT0;
+	}
+	write_reg(info, MD1, RegValue);
+
+	/* MD2, Mode Register 2
+	 *
+	 * 07..02  Reserved, must be 0
+	 * 01..00  CNCT<1..0> Channel connection, 0=normal
+	 *
+	 * 0000 0000
+	 */
+	RegValue = 0x00;
+	write_reg(info, MD2, RegValue);
+
+	/* RXS, Receive clock source
+	 *
+	 * 07      Reserved, must be 0
+	 * 06..04  RXCS<2..0>, clock source, 000=RxC Pin, 100=BRG, 110=DPLL
+	 * 03..00  RXBR<3..0>, rate divisor, 0000=1
+	 */
+	RegValue=BIT6;
+	write_reg(info, RXS, RegValue);
+
+	/* TXS, Transmit clock source
+	 *
+	 * 07      Reserved, must be 0
+	 * 06..04  RXCS<2..0>, clock source, 000=TxC Pin, 100=BRG, 110=Receive Clock
+	 * 03..00  RXBR<3..0>, rate divisor, 0000=1
+	 */
+	RegValue=BIT6;
+	write_reg(info, TXS, RegValue);
+
+	/* Control Register
+	 *
+	 * 6,4,2,0  CLKSEL<3..0>, 0 = TcCLK in, 1 = Auxclk out
+	 */
+	info->port_array[0]->ctrlreg_value |= (BIT0 << (info->port_num * 2));
+	write_control_reg(info);
+
+	tx_set_idle(info);
+
+	/* RRC Receive Ready Control 0
+	 *
+	 * 07..05  Reserved, must be 0
+	 * 04..00  RRC<4..0> Rx FIFO trigger active 0x00 = 1 byte
+	 */
+	write_reg(info, RRC, 0x00);
+
+	/* TRC0 Transmit Ready Control 0
+	 *
+	 * 07..05  Reserved, must be 0
+	 * 04..00  TRC<4..0> Tx FIFO trigger active 0x10 = 16 bytes
+	 */
+	write_reg(info, TRC0, 0x10);
+
+	/* TRC1 Transmit Ready Control 1
+	 *
+	 * 07..05  Reserved, must be 0
+	 * 04..00  TRC<4..0> Tx FIFO trigger inactive 0x1e = 31 bytes (full-1)
+	 */
+	write_reg(info, TRC1, 0x1e);
+
+	/* CTL, MSCI control register
+	 *
+	 * 07..06  Reserved, set to 0
+	 * 05      UDRNC, underrun control, 0=abort 1=CRC+flag (HDLC/BSC)
+	 * 04      IDLC, idle control, 0=mark 1=idle register
+	 * 03      BRK, break, 0=off 1 =on (async)
+	 * 02      SYNCLD, sync char load enable (BSC) 1=enabled
+	 * 01      GOP, go active on poll (LOOP mode) 1=enabled
+	 * 00      RTS, RTS output control, 0=active 1=inactive
+	 *
+	 * 0001 0001
+	 */
+	RegValue = 0x10;
+	if (!(info->serial_signals & SerialSignal_RTS))
+		RegValue |= 0x01;
+	write_reg(info, CTL, RegValue);
+
+	/* enable status interrupts */
+	info->ie0_value |= TXINTE + RXINTE;
+	write_reg(info, IE0, info->ie0_value);
+
+	/* enable break detect interrupt */
+	info->ie1_value = BRKD;
+	write_reg(info, IE1, info->ie1_value);
+
+	/* enable rx overrun interrupt */
+	info->ie2_value = OVRN;
+	write_reg(info, IE2, info->ie2_value);
+
+	set_rate( info, info->params.data_rate * 16 );
+
+	if (info->params.loopback)
+		enable_loopback(info,1);
+}
+
+/* Program the SCA for HDLC communications.
+ */
+void hdlc_mode(SLMP_INFO *info)
+{
+	unsigned char RegValue;
+	u32 DpllDivisor;
+
+	// Can't use DPLL because SCA outputs recovered clock on RxC when
+	// DPLL mode selected. This causes output contention with RxC receiver.
+	// Use of DPLL would require external hardware to disable RxC receiver
+	// when DPLL mode selected.
+	info->params.flags &= ~(HDLC_FLAG_TXC_DPLL + HDLC_FLAG_RXC_DPLL);
+
+	/* disable DMA interrupts */
+	write_reg(info, TXDMA + DIR, 0);
+	write_reg(info, RXDMA + DIR, 0);
+
+	/* MD0, Mode Register 0
+	 *
+	 * 07..05  PRCTL<2..0>, Protocol Mode, 100=HDLC
+	 * 04      AUTO, Auto-enable (RTS/CTS/DCD)
+	 * 03      Reserved, must be 0
+	 * 02      CRCCC, CRC Calculation, 1=enabled
+	 * 01      CRC1, CRC selection, 0=CRC-16,1=CRC-CCITT-16
+	 * 00      CRC0, CRC initial value, 1 = all 1s
+	 *
+	 * 1000 0001
+	 */
+	RegValue = 0x81;
+	if (info->params.flags & HDLC_FLAG_AUTO_CTS)
+		RegValue |= BIT4;
+	if (info->params.flags & HDLC_FLAG_AUTO_DCD)
+		RegValue |= BIT4;
+	if (info->params.crc_type == HDLC_CRC_16_CCITT)
+		RegValue |= BIT2 + BIT1;
+	write_reg(info, MD0, RegValue);
+
+	/* MD1, Mode Register 1
+	 *
+	 * 07..06  ADDRS<1..0>, Address detect, 00=no addr check
+	 * 05..04  TXCHR<1..0>, tx char size, 00=8 bits
+	 * 03..02  RXCHR<1..0>, rx char size, 00=8 bits
+	 * 01..00  PMPM<1..0>, Parity mode, 00=no parity
+	 *
+	 * 0000 0000
+	 */
+	RegValue = 0x00;
+	write_reg(info, MD1, RegValue);
+
+	/* MD2, Mode Register 2
+	 *
+	 * 07      NRZFM, 0=NRZ, 1=FM
+	 * 06..05  CODE<1..0> Encoding, 00=NRZ
+	 * 04..03  DRATE<1..0> DPLL Divisor, 00=8
+	 * 02      Reserved, must be 0
+	 * 01..00  CNCT<1..0> Channel connection, 0=normal
+	 *
+	 * 0000 0000
+	 */
+	RegValue = 0x00;
+	switch(info->params.encoding) {
+	case HDLC_ENCODING_NRZI:	  RegValue |= BIT5; break;
+	case HDLC_ENCODING_BIPHASE_MARK:  RegValue |= BIT7 + BIT5; break; /* aka FM1 */
+	case HDLC_ENCODING_BIPHASE_SPACE: RegValue |= BIT7 + BIT6; break; /* aka FM0 */
+	case HDLC_ENCODING_BIPHASE_LEVEL: RegValue |= BIT7; break; 	/* aka Manchester */
+#if 0
+	case HDLC_ENCODING_NRZB:	       				/* not supported */
+	case HDLC_ENCODING_NRZI_MARK:          				/* not supported */
+	case HDLC_ENCODING_DIFF_BIPHASE_LEVEL: 				/* not supported */
+#endif
+	}
+	if ( info->params.flags & HDLC_FLAG_DPLL_DIV16 ) {
+		DpllDivisor = 16;
+		RegValue |= BIT3;
+	} else if ( info->params.flags & HDLC_FLAG_DPLL_DIV8 ) {
+		DpllDivisor = 8;
+	} else {
+		DpllDivisor = 32;
+		RegValue |= BIT4;
+	}
+	write_reg(info, MD2, RegValue);
+
+
+	/* RXS, Receive clock source
+	 *
+	 * 07      Reserved, must be 0
+	 * 06..04  RXCS<2..0>, clock source, 000=RxC Pin, 100=BRG, 110=DPLL
+	 * 03..00  RXBR<3..0>, rate divisor, 0000=1
+	 */
+	RegValue=0;
+	if (info->params.flags & HDLC_FLAG_RXC_BRG)
+		RegValue |= BIT6;
+	if (info->params.flags & HDLC_FLAG_RXC_DPLL)
+		RegValue |= BIT6 + BIT5;
+	write_reg(info, RXS, RegValue);
+
+	/* TXS, Transmit clock source
+	 *
+	 * 07      Reserved, must be 0
+	 * 06..04  RXCS<2..0>, clock source, 000=TxC Pin, 100=BRG, 110=Receive Clock
+	 * 03..00  RXBR<3..0>, rate divisor, 0000=1
+	 */
+	RegValue=0;
+	if (info->params.flags & HDLC_FLAG_TXC_BRG)
+		RegValue |= BIT6;
+	if (info->params.flags & HDLC_FLAG_TXC_DPLL)
+		RegValue |= BIT6 + BIT5;
+	write_reg(info, TXS, RegValue);
+
+	if (info->params.flags & HDLC_FLAG_RXC_DPLL)
+		set_rate(info, info->params.clock_speed * DpllDivisor);
+	else
+		set_rate(info, info->params.clock_speed);
+
+	/* GPDATA (General Purpose I/O Data Register)
+	 *
+	 * 6,4,2,0  CLKSEL<3..0>, 0 = TcCLK in, 1 = Auxclk out
+	 */
+	if (info->params.flags & HDLC_FLAG_TXC_BRG)
+		info->port_array[0]->ctrlreg_value |= (BIT0 << (info->port_num * 2));
+	else
+		info->port_array[0]->ctrlreg_value &= ~(BIT0 << (info->port_num * 2));
+	write_control_reg(info);
+
+	/* RRC Receive Ready Control 0
+	 *
+	 * 07..05  Reserved, must be 0
+	 * 04..00  RRC<4..0> Rx FIFO trigger active
+	 */
+	write_reg(info, RRC, rx_active_fifo_level);
+
+	/* TRC0 Transmit Ready Control 0
+	 *
+	 * 07..05  Reserved, must be 0
+	 * 04..00  TRC<4..0> Tx FIFO trigger active
+	 */
+	write_reg(info, TRC0, tx_active_fifo_level);
+
+	/* TRC1 Transmit Ready Control 1
+	 *
+	 * 07..05  Reserved, must be 0
+	 * 04..00  TRC<4..0> Tx FIFO trigger inactive 0x1f = 32 bytes (full)
+	 */
+	write_reg(info, TRC1, (unsigned char)(tx_negate_fifo_level - 1));
+
+	/* DMR, DMA Mode Register
+	 *
+	 * 07..05  Reserved, must be 0
+	 * 04      TMOD, Transfer Mode: 1=chained-block
+	 * 03      Reserved, must be 0
+	 * 02      NF, Number of Frames: 1=multi-frame
+	 * 01      CNTE, Frame End IRQ Counter enable: 0=disabled
+	 * 00      Reserved, must be 0
+	 *
+	 * 0001 0100
+	 */
+	write_reg(info, TXDMA + DMR, 0x14);
+	write_reg(info, RXDMA + DMR, 0x14);
+
+	/* Set chain pointer base (upper 8 bits of 24 bit addr) */
+	write_reg(info, RXDMA + CPB,
+		(unsigned char)(info->buffer_list_phys >> 16));
+
+	/* Set chain pointer base (upper 8 bits of 24 bit addr) */
+	write_reg(info, TXDMA + CPB,
+		(unsigned char)(info->buffer_list_phys >> 16));
+
+	/* enable status interrupts. other code enables/disables
+	 * the individual sources for these two interrupt classes.
+	 */
+	info->ie0_value |= TXINTE + RXINTE;
+	write_reg(info, IE0, info->ie0_value);
+
+	/* CTL, MSCI control register
+	 *
+	 * 07..06  Reserved, set to 0
+	 * 05      UDRNC, underrun control, 0=abort 1=CRC+flag (HDLC/BSC)
+	 * 04      IDLC, idle control, 0=mark 1=idle register
+	 * 03      BRK, break, 0=off 1 =on (async)
+	 * 02      SYNCLD, sync char load enable (BSC) 1=enabled
+	 * 01      GOP, go active on poll (LOOP mode) 1=enabled
+	 * 00      RTS, RTS output control, 0=active 1=inactive
+	 *
+	 * 0001 0001
+	 */
+	RegValue = 0x10;
+	if (!(info->serial_signals & SerialSignal_RTS))
+		RegValue |= 0x01;
+	write_reg(info, CTL, RegValue);
+
+	/* preamble not supported ! */
+
+	tx_set_idle(info);
+	tx_stop(info);
+	rx_stop(info);
+
+	set_rate(info, info->params.clock_speed);
+
+	if (info->params.loopback)
+		enable_loopback(info,1);
+}
+
+/* Set the transmit HDLC idle mode
+ */
+void tx_set_idle(SLMP_INFO *info)
+{
+	unsigned char RegValue = 0xff;
+
+	/* Map API idle mode to SCA register bits */
+	switch(info->idle_mode) {
+	case HDLC_TXIDLE_FLAGS:			RegValue = 0x7e; break;
+	case HDLC_TXIDLE_ALT_ZEROS_ONES:	RegValue = 0xaa; break;
+	case HDLC_TXIDLE_ZEROS:			RegValue = 0x00; break;
+	case HDLC_TXIDLE_ONES:			RegValue = 0xff; break;
+	case HDLC_TXIDLE_ALT_MARK_SPACE:	RegValue = 0xaa; break;
+	case HDLC_TXIDLE_SPACE:			RegValue = 0x00; break;
+	case HDLC_TXIDLE_MARK:			RegValue = 0xff; break;
+	}
+
+	write_reg(info, IDL, RegValue);
+}
+
+/* Query the adapter for the state of the V24 status (input) signals.
+ */
+void get_signals(SLMP_INFO *info)
+{
+	u16 status = read_reg(info, SR3);
+	u16 gpstatus = read_status_reg(info);
+	u16 testbit;
+
+	/* clear all serial signals except DTR and RTS */
+	info->serial_signals &= SerialSignal_DTR + SerialSignal_RTS;
+
+	/* set serial signal bits to reflect MISR */
+
+	if (!(status & BIT3))
+		info->serial_signals |= SerialSignal_CTS;
+
+	if ( !(status & BIT2))
+		info->serial_signals |= SerialSignal_DCD;
+
+	testbit = BIT1 << (info->port_num * 2); // Port 0..3 RI is GPDATA<1,3,5,7>
+	if (!(gpstatus & testbit))
+		info->serial_signals |= SerialSignal_RI;
+
+	testbit = BIT0 << (info->port_num * 2); // Port 0..3 DSR is GPDATA<0,2,4,6>
+	if (!(gpstatus & testbit))
+		info->serial_signals |= SerialSignal_DSR;
+}
+
+/* Set the state of DTR and RTS based on contents of
+ * serial_signals member of device context.
+ */
+void set_signals(SLMP_INFO *info)
+{
+	unsigned char RegValue;
+	u16 EnableBit;
+
+	RegValue = read_reg(info, CTL);
+	if (info->serial_signals & SerialSignal_RTS)
+		RegValue &= ~BIT0;
+	else
+		RegValue |= BIT0;
+	write_reg(info, CTL, RegValue);
+
+	// Port 0..3 DTR is ctrl reg <1,3,5,7>
+	EnableBit = BIT1 << (info->port_num*2);
+	if (info->serial_signals & SerialSignal_DTR)
+		info->port_array[0]->ctrlreg_value &= ~EnableBit;
+	else
+		info->port_array[0]->ctrlreg_value |= EnableBit;
+	write_control_reg(info);
+}
+
+/*******************/
+/* DMA Buffer Code */
+/*******************/
+
+/* Set the count for all receive buffers to SCABUFSIZE
+ * and set the current buffer to the first buffer. This effectively
+ * makes all buffers free and discards any data in buffers.
+ */
+void rx_reset_buffers(SLMP_INFO *info)
+{
+	rx_free_frame_buffers(info, 0, info->rx_buf_count - 1);
+}
+
+/* Free the buffers used by a received frame
+ *
+ * info   pointer to device instance data
+ * first  index of 1st receive buffer of frame
+ * last   index of last receive buffer of frame
+ */
+void rx_free_frame_buffers(SLMP_INFO *info, unsigned int first, unsigned int last)
+{
+	int done = 0;
+
+	while(!done) {
+	        /* reset current buffer for reuse */
+		info->rx_buf_list[first].status = 0xff;
+
+	        if (first == last) {
+	                done = 1;
+	                /* set new last rx descriptor address */
+			write_reg16(info, RXDMA + EDA, info->rx_buf_list_ex[first].phys_entry);
+	        }
+
+	        first++;
+		if (first == info->rx_buf_count)
+			first = 0;
+	}
+
+	/* set current buffer to next buffer after last buffer of frame */
+	info->current_rx_buf = first;
+}
+
+/* Return a received frame from the receive DMA buffers.
+ * Only frames received without errors are returned.
+ *
+ * Return Value:	1 if frame returned, otherwise 0
+ */
+int rx_get_frame(SLMP_INFO *info)
+{
+	unsigned int StartIndex, EndIndex;	/* index of 1st and last buffers of Rx frame */
+	unsigned short status;
+	unsigned int framesize = 0;
+	int ReturnCode = 0;
+	unsigned long flags;
+	struct tty_struct *tty = info->tty;
+	unsigned char addr_field = 0xff;
+   	SCADESC *desc;
+	SCADESC_EX *desc_ex;
+
+CheckAgain:
+	/* assume no frame returned, set zero length */
+	framesize = 0;
+	addr_field = 0xff;
+
+	/*
+	 * current_rx_buf points to the 1st buffer of the next available
+	 * receive frame. To find the last buffer of the frame look for
+	 * a non-zero status field in the buffer entries. (The status
+	 * field is set by the 16C32 after completing a receive frame.
+	 */
+	StartIndex = EndIndex = info->current_rx_buf;
+
+	for ( ;; ) {
+		desc = &info->rx_buf_list[EndIndex];
+		desc_ex = &info->rx_buf_list_ex[EndIndex];
+
+		if (desc->status == 0xff)
+			goto Cleanup;	/* current desc still in use, no frames available */
+
+		if (framesize == 0 && info->params.addr_filter != 0xff)
+			addr_field = desc_ex->virt_addr[0];
+
+		framesize += desc->length;
+
+		/* Status != 0 means last buffer of frame */
+		if (desc->status)
+			break;
+
+		EndIndex++;
+		if (EndIndex == info->rx_buf_count)
+			EndIndex = 0;
+
+		if (EndIndex == info->current_rx_buf) {
+			/* all buffers have been 'used' but none mark	   */
+			/* the end of a frame. Reset buffers and receiver. */
+			if ( info->rx_enabled ){
+				spin_lock_irqsave(&info->lock,flags);
+				rx_start(info);
+				spin_unlock_irqrestore(&info->lock,flags);
+			}
+			goto Cleanup;
+		}
+
+	}
+
+	/* check status of receive frame */
+
+	/* frame status is byte stored after frame data
+	 *
+	 * 7 EOM (end of msg), 1 = last buffer of frame
+	 * 6 Short Frame, 1 = short frame
+	 * 5 Abort, 1 = frame aborted
+	 * 4 Residue, 1 = last byte is partial
+	 * 3 Overrun, 1 = overrun occurred during frame reception
+	 * 2 CRC,     1 = CRC error detected
+	 *
+	 */
+	status = desc->status;
+
+	/* ignore CRC bit if not using CRC (bit is undefined) */
+	/* Note:CRC is not save to data buffer */
+	if (info->params.crc_type == HDLC_CRC_NONE)
+		status &= ~BIT2;
+
+	if (framesize == 0 ||
+		 (addr_field != 0xff && addr_field != info->params.addr_filter)) {
+		/* discard 0 byte frames, this seems to occur sometime
+		 * when remote is idling flags.
+		 */
+		rx_free_frame_buffers(info, StartIndex, EndIndex);
+		goto CheckAgain;
+	}
+
+	if (framesize < 2)
+		status |= BIT6;
+
+	if (status & (BIT6+BIT5+BIT3+BIT2)) {
+		/* received frame has errors,
+		 * update counts and mark frame size as 0
+		 */
+		if (status & BIT6)
+			info->icount.rxshort++;
+		else if (status & BIT5)
+			info->icount.rxabort++;
+		else if (status & BIT3)
+			info->icount.rxover++;
+		else
+			info->icount.rxcrc++;
+
+		framesize = 0;
+#ifdef CONFIG_HDLC
+		{
+			struct net_device_stats *stats = hdlc_stats(info->netdev);
+			stats->rx_errors++;
+			stats->rx_frame_errors++;
+		}
+#endif
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_BH )
+		printk("%s(%d):%s rx_get_frame() status=%04X size=%d\n",
+			__FILE__,__LINE__,info->device_name,status,framesize);
+
+	if ( debug_level >= DEBUG_LEVEL_DATA )
+		trace_block(info,info->rx_buf_list_ex[StartIndex].virt_addr,
+			min_t(int, framesize,SCABUFSIZE),0);
+
+	if (framesize) {
+		if (framesize > info->max_frame_size)
+			info->icount.rxlong++;
+		else {
+			/* copy dma buffer(s) to contiguous intermediate buffer */
+			int copy_count = framesize;
+			int index = StartIndex;
+			unsigned char *ptmp = info->tmp_rx_buf;
+			info->tmp_rx_buf_count = framesize;
+
+			info->icount.rxok++;
+
+			while(copy_count) {
+				int partial_count = min(copy_count,SCABUFSIZE);
+				memcpy( ptmp,
+					info->rx_buf_list_ex[index].virt_addr,
+					partial_count );
+				ptmp += partial_count;
+				copy_count -= partial_count;
+
+				if ( ++index == info->rx_buf_count )
+					index = 0;
+			}
+
+#ifdef CONFIG_HDLC
+			if (info->netcount)
+				hdlcdev_rx(info,info->tmp_rx_buf,framesize);
+			else
+#endif
+				ldisc_receive_buf(tty,info->tmp_rx_buf,
+						  info->flag_buf, framesize);
+		}
+	}
+	/* Free the buffers used by this frame. */
+	rx_free_frame_buffers( info, StartIndex, EndIndex );
+
+	ReturnCode = 1;
+
+Cleanup:
+	if ( info->rx_enabled && info->rx_overflow ) {
+		/* Receiver is enabled, but needs to restarted due to
+		 * rx buffer overflow. If buffers are empty, restart receiver.
+		 */
+		if (info->rx_buf_list[EndIndex].status == 0xff) {
+			spin_lock_irqsave(&info->lock,flags);
+			rx_start(info);
+			spin_unlock_irqrestore(&info->lock,flags);
+		}
+	}
+
+	return ReturnCode;
+}
+
+/* load the transmit DMA buffer with data
+ */
+void tx_load_dma_buffer(SLMP_INFO *info, const char *buf, unsigned int count)
+{
+	unsigned short copy_count;
+	unsigned int i = 0;
+	SCADESC *desc;
+	SCADESC_EX *desc_ex;
+
+	if ( debug_level >= DEBUG_LEVEL_DATA )
+		trace_block(info,buf, min_t(int, count,SCABUFSIZE), 1);
+
+	/* Copy source buffer to one or more DMA buffers, starting with
+	 * the first transmit dma buffer.
+	 */
+	for(i=0;;)
+	{
+		copy_count = min_t(unsigned short,count,SCABUFSIZE);
+
+		desc = &info->tx_buf_list[i];
+		desc_ex = &info->tx_buf_list_ex[i];
+
+		load_pci_memory(info, desc_ex->virt_addr,buf,copy_count);
+
+		desc->length = copy_count;
+		desc->status = 0;
+
+		buf += copy_count;
+		count -= copy_count;
+
+		if (!count)
+			break;
+
+		i++;
+		if (i >= info->tx_buf_count)
+			i = 0;
+	}
+
+	info->tx_buf_list[i].status = 0x81;	/* set EOM and EOT status */
+	info->last_tx_buf = ++i;
+}
+
+int register_test(SLMP_INFO *info)
+{
+	static unsigned char testval[] = {0x00, 0xff, 0xaa, 0x55, 0x69, 0x96};
+	static unsigned int count = sizeof(testval)/sizeof(unsigned char);
+	unsigned int i;
+	int rc = TRUE;
+	unsigned long flags;
+
+	spin_lock_irqsave(&info->lock,flags);
+	reset_port(info);
+
+	/* assume failure */
+	info->init_error = DiagStatus_AddressFailure;
+
+	/* Write bit patterns to various registers but do it out of */
+	/* sync, then read back and verify values. */
+
+	for (i = 0 ; i < count ; i++) {
+		write_reg(info, TMC, testval[i]);
+		write_reg(info, IDL, testval[(i+1)%count]);
+		write_reg(info, SA0, testval[(i+2)%count]);
+		write_reg(info, SA1, testval[(i+3)%count]);
+
+		if ( (read_reg(info, TMC) != testval[i]) ||
+			  (read_reg(info, IDL) != testval[(i+1)%count]) ||
+			  (read_reg(info, SA0) != testval[(i+2)%count]) ||
+			  (read_reg(info, SA1) != testval[(i+3)%count]) )
+		{
+			rc = FALSE;
+			break;
+		}
+	}
+
+	reset_port(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	return rc;
+}
+
+int irq_test(SLMP_INFO *info)
+{
+	unsigned long timeout;
+	unsigned long flags;
+
+	unsigned char timer = (info->port_num & 1) ? TIMER2 : TIMER0;
+
+	spin_lock_irqsave(&info->lock,flags);
+	reset_port(info);
+
+	/* assume failure */
+	info->init_error = DiagStatus_IrqFailure;
+	info->irq_occurred = FALSE;
+
+	/* setup timer0 on SCA0 to interrupt */
+
+	/* IER2<7..4> = timer<3..0> interrupt enables (1=enabled) */
+	write_reg(info, IER2, (unsigned char)((info->port_num & 1) ? BIT6 : BIT4));
+
+	write_reg(info, (unsigned char)(timer + TEPR), 0);	/* timer expand prescale */
+	write_reg16(info, (unsigned char)(timer + TCONR), 1);	/* timer constant */
+
+
+	/* TMCS, Timer Control/Status Register
+	 *
+	 * 07      CMF, Compare match flag (read only) 1=match
+	 * 06      ECMI, CMF Interrupt Enable: 1=enabled
+	 * 05      Reserved, must be 0
+	 * 04      TME, Timer Enable
+	 * 03..00  Reserved, must be 0
+	 *
+	 * 0101 0000
+	 */
+	write_reg(info, (unsigned char)(timer + TMCS), 0x50);
+
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	timeout=100;
+	while( timeout-- && !info->irq_occurred ) {
+		msleep_interruptible(10);
+	}
+
+	spin_lock_irqsave(&info->lock,flags);
+	reset_port(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	return info->irq_occurred;
+}
+
+/* initialize individual SCA device (2 ports)
+ */
+static int sca_init(SLMP_INFO *info)
+{
+	/* set wait controller to single mem partition (low), no wait states */
+	write_reg(info, PABR0, 0);	/* wait controller addr boundary 0 */
+	write_reg(info, PABR1, 0);	/* wait controller addr boundary 1 */
+	write_reg(info, WCRL, 0);	/* wait controller low range */
+	write_reg(info, WCRM, 0);	/* wait controller mid range */
+	write_reg(info, WCRH, 0);	/* wait controller high range */
+
+	/* DPCR, DMA Priority Control
+	 *
+	 * 07..05  Not used, must be 0
+	 * 04      BRC, bus release condition: 0=all transfers complete
+	 * 03      CCC, channel change condition: 0=every cycle
+	 * 02..00  PR<2..0>, priority 100=round robin
+	 *
+	 * 00000100 = 0x04
+	 */
+	write_reg(info, DPCR, dma_priority);
+
+	/* DMA Master Enable, BIT7: 1=enable all channels */
+	write_reg(info, DMER, 0x80);
+
+	/* enable all interrupt classes */
+	write_reg(info, IER0, 0xff);	/* TxRDY,RxRDY,TxINT,RxINT (ports 0-1) */
+	write_reg(info, IER1, 0xff);	/* DMIB,DMIA (channels 0-3) */
+	write_reg(info, IER2, 0xf0);	/* TIRQ (timers 0-3) */
+
+	/* ITCR, interrupt control register
+	 * 07      IPC, interrupt priority, 0=MSCI->DMA
+	 * 06..05  IAK<1..0>, Acknowledge cycle, 00=non-ack cycle
+	 * 04      VOS, Vector Output, 0=unmodified vector
+	 * 03..00  Reserved, must be 0
+	 */
+	write_reg(info, ITCR, 0);
+
+	return TRUE;
+}
+
+/* initialize adapter hardware
+ */
+int init_adapter(SLMP_INFO *info)
+{
+	int i;
+
+	/* Set BIT30 of Local Control Reg 0x50 to reset SCA */
+	volatile u32 *MiscCtrl = (u32 *)(info->lcr_base + 0x50);
+	u32 readval;
+
+	info->misc_ctrl_value |= BIT30;
+	*MiscCtrl = info->misc_ctrl_value;
+
+	/*
+	 * Force at least 170ns delay before clearing
+	 * reset bit. Each read from LCR takes at least
+	 * 30ns so 10 times for 300ns to be safe.
+	 */
+	for(i=0;i<10;i++)
+		readval = *MiscCtrl;
+
+	info->misc_ctrl_value &= ~BIT30;
+	*MiscCtrl = info->misc_ctrl_value;
+
+	/* init control reg (all DTRs off, all clksel=input) */
+	info->ctrlreg_value = 0xaa;
+	write_control_reg(info);
+
+	{
+		volatile u32 *LCR1BRDR = (u32 *)(info->lcr_base + 0x2c);
+		lcr1_brdr_value &= ~(BIT5 + BIT4 + BIT3);
+
+		switch(read_ahead_count)
+		{
+		case 16:
+			lcr1_brdr_value |= BIT5 + BIT4 + BIT3;
+			break;
+		case 8:
+			lcr1_brdr_value |= BIT5 + BIT4;
+			break;
+		case 4:
+			lcr1_brdr_value |= BIT5 + BIT3;
+			break;
+		case 0:
+			lcr1_brdr_value |= BIT5;
+			break;
+		}
+
+		*LCR1BRDR = lcr1_brdr_value;
+		*MiscCtrl = misc_ctrl_value;
+	}
+
+	sca_init(info->port_array[0]);
+	sca_init(info->port_array[2]);
+
+	return TRUE;
+}
+
+/* Loopback an HDLC frame to test the hardware
+ * interrupt and DMA functions.
+ */
+int loopback_test(SLMP_INFO *info)
+{
+#define TESTFRAMESIZE 20
+
+	unsigned long timeout;
+	u16 count = TESTFRAMESIZE;
+	unsigned char buf[TESTFRAMESIZE];
+	int rc = FALSE;
+	unsigned long flags;
+
+	struct tty_struct *oldtty = info->tty;
+	u32 speed = info->params.clock_speed;
+
+	info->params.clock_speed = 3686400;
+	info->tty = NULL;
+
+	/* assume failure */
+	info->init_error = DiagStatus_DmaFailure;
+
+	/* build and send transmit frame */
+	for (count = 0; count < TESTFRAMESIZE;++count)
+		buf[count] = (unsigned char)count;
+
+	memset(info->tmp_rx_buf,0,TESTFRAMESIZE);
+
+	/* program hardware for HDLC and enabled receiver */
+	spin_lock_irqsave(&info->lock,flags);
+	hdlc_mode(info);
+	enable_loopback(info,1);
+       	rx_start(info);
+	info->tx_count = count;
+	tx_load_dma_buffer(info,buf,count);
+	tx_start(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	/* wait for receive complete */
+	/* Set a timeout for waiting for interrupt. */
+	for ( timeout = 100; timeout; --timeout ) {
+		msleep_interruptible(10);
+
+		if (rx_get_frame(info)) {
+			rc = TRUE;
+			break;
+		}
+	}
+
+	/* verify received frame length and contents */
+	if (rc == TRUE &&
+		( info->tmp_rx_buf_count != count ||
+		  memcmp(buf, info->tmp_rx_buf,count))) {
+		rc = FALSE;
+	}
+
+	spin_lock_irqsave(&info->lock,flags);
+	reset_adapter(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	info->params.clock_speed = speed;
+	info->tty = oldtty;
+
+	return rc;
+}
+
+/* Perform diagnostics on hardware
+ */
+int adapter_test( SLMP_INFO *info )
+{
+	unsigned long flags;
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):Testing device %s\n",
+			__FILE__,__LINE__,info->device_name );
+
+	spin_lock_irqsave(&info->lock,flags);
+	init_adapter(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	info->port_array[0]->port_count = 0;
+
+	if ( register_test(info->port_array[0]) &&
+		register_test(info->port_array[1])) {
+
+		info->port_array[0]->port_count = 2;
+
+		if ( register_test(info->port_array[2]) &&
+			register_test(info->port_array[3]) )
+			info->port_array[0]->port_count += 2;
+	}
+	else {
+		printk( "%s(%d):Register test failure for device %s Addr=%08lX\n",
+			__FILE__,__LINE__,info->device_name, (unsigned long)(info->phys_sca_base));
+		return -ENODEV;
+	}
+
+	if ( !irq_test(info->port_array[0]) ||
+		!irq_test(info->port_array[1]) ||
+		 (info->port_count == 4 && !irq_test(info->port_array[2])) ||
+		 (info->port_count == 4 && !irq_test(info->port_array[3]))) {
+		printk( "%s(%d):Interrupt test failure for device %s IRQ=%d\n",
+			__FILE__,__LINE__,info->device_name, (unsigned short)(info->irq_level) );
+		return -ENODEV;
+	}
+
+	if (!loopback_test(info->port_array[0]) ||
+		!loopback_test(info->port_array[1]) ||
+		 (info->port_count == 4 && !loopback_test(info->port_array[2])) ||
+		 (info->port_count == 4 && !loopback_test(info->port_array[3]))) {
+		printk( "%s(%d):DMA test failure for device %s\n",
+			__FILE__,__LINE__,info->device_name);
+		return -ENODEV;
+	}
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):device %s passed diagnostics\n",
+			__FILE__,__LINE__,info->device_name );
+
+	info->port_array[0]->init_error = 0;
+	info->port_array[1]->init_error = 0;
+	if ( info->port_count > 2 ) {
+		info->port_array[2]->init_error = 0;
+		info->port_array[3]->init_error = 0;
+	}
+
+	return 0;
+}
+
+/* Test the shared memory on a PCI adapter.
+ */
+int memory_test(SLMP_INFO *info)
+{
+	static unsigned long testval[] = { 0x0, 0x55555555, 0xaaaaaaaa,
+		0x66666666, 0x99999999, 0xffffffff, 0x12345678 };
+	unsigned long count = sizeof(testval)/sizeof(unsigned long);
+	unsigned long i;
+	unsigned long limit = SCA_MEM_SIZE/sizeof(unsigned long);
+	unsigned long * addr = (unsigned long *)info->memory_base;
+
+	/* Test data lines with test pattern at one location. */
+
+	for ( i = 0 ; i < count ; i++ ) {
+		*addr = testval[i];
+		if ( *addr != testval[i] )
+			return FALSE;
+	}
+
+	/* Test address lines with incrementing pattern over */
+	/* entire address range. */
+
+	for ( i = 0 ; i < limit ; i++ ) {
+		*addr = i * 4;
+		addr++;
+	}
+
+	addr = (unsigned long *)info->memory_base;
+
+	for ( i = 0 ; i < limit ; i++ ) {
+		if ( *addr != i * 4 )
+			return FALSE;
+		addr++;
+	}
+
+	memset( info->memory_base, 0, SCA_MEM_SIZE );
+	return TRUE;
+}
+
+/* Load data into PCI adapter shared memory.
+ *
+ * The PCI9050 releases control of the local bus
+ * after completing the current read or write operation.
+ *
+ * While the PCI9050 write FIFO not empty, the
+ * PCI9050 treats all of the writes as a single transaction
+ * and does not release the bus. This causes DMA latency problems
+ * at high speeds when copying large data blocks to the shared memory.
+ *
+ * This function breaks a write into multiple transations by
+ * interleaving a read which flushes the write FIFO and 'completes'
+ * the write transation. This allows any pending DMA request to gain control
+ * of the local bus in a timely fasion.
+ */
+void load_pci_memory(SLMP_INFO *info, char* dest, const char* src, unsigned short count)
+{
+	/* A load interval of 16 allows for 4 32-bit writes at */
+	/* 136ns each for a maximum latency of 542ns on the local bus.*/
+
+	unsigned short interval = count / sca_pci_load_interval;
+	unsigned short i;
+
+	for ( i = 0 ; i < interval ; i++ )
+	{
+		memcpy(dest, src, sca_pci_load_interval);
+		read_status_reg(info);
+		dest += sca_pci_load_interval;
+		src += sca_pci_load_interval;
+	}
+
+	memcpy(dest, src, count % sca_pci_load_interval);
+}
+
+void trace_block(SLMP_INFO *info,const char* data, int count, int xmit)
+{
+	int i;
+	int linecount;
+	if (xmit)
+		printk("%s tx data:\n",info->device_name);
+	else
+		printk("%s rx data:\n",info->device_name);
+
+	while(count) {
+		if (count > 16)
+			linecount = 16;
+		else
+			linecount = count;
+
+		for(i=0;i<linecount;i++)
+			printk("%02X ",(unsigned char)data[i]);
+		for(;i<17;i++)
+			printk("   ");
+		for(i=0;i<linecount;i++) {
+			if (data[i]>=040 && data[i]<=0176)
+				printk("%c",data[i]);
+			else
+				printk(".");
+		}
+		printk("\n");
+
+		data  += linecount;
+		count -= linecount;
+	}
+}	/* end of trace_block() */
+
+/* called when HDLC frame times out
+ * update stats and do tx completion processing
+ */
+void tx_timeout(unsigned long context)
+{
+	SLMP_INFO *info = (SLMP_INFO*)context;
+	unsigned long flags;
+
+	if ( debug_level >= DEBUG_LEVEL_INFO )
+		printk( "%s(%d):%s tx_timeout()\n",
+			__FILE__,__LINE__,info->device_name);
+	if(info->tx_active && info->params.mode == MGSL_MODE_HDLC) {
+		info->icount.txtimeout++;
+	}
+	spin_lock_irqsave(&info->lock,flags);
+	info->tx_active = 0;
+	info->tx_count = info->tx_put = info->tx_get = 0;
+
+	spin_unlock_irqrestore(&info->lock,flags);
+
+#ifdef CONFIG_HDLC
+	if (info->netcount)
+		hdlcdev_tx_done(info);
+	else
+#endif
+		bh_transmit(info);
+}
+
+/* called to periodically check the DSR/RI modem signal input status
+ */
+void status_timeout(unsigned long context)
+{
+	u16 status = 0;
+	SLMP_INFO *info = (SLMP_INFO*)context;
+	unsigned long flags;
+	unsigned char delta;
+
+
+	spin_lock_irqsave(&info->lock,flags);
+	get_signals(info);
+	spin_unlock_irqrestore(&info->lock,flags);
+
+	/* check for DSR/RI state change */
+
+	delta = info->old_signals ^ info->serial_signals;
+	info->old_signals = info->serial_signals;
+
+	if (delta & SerialSignal_DSR)
+		status |= MISCSTATUS_DSR_LATCHED|(info->serial_signals&SerialSignal_DSR);
+
+	if (delta & SerialSignal_RI)
+		status |= MISCSTATUS_RI_LATCHED|(info->serial_signals&SerialSignal_RI);
+
+	if (delta & SerialSignal_DCD)
+		status |= MISCSTATUS_DCD_LATCHED|(info->serial_signals&SerialSignal_DCD);
+
+	if (delta & SerialSignal_CTS)
+		status |= MISCSTATUS_CTS_LATCHED|(info->serial_signals&SerialSignal_CTS);
+
+	if (status)
+		isr_io_pin(info,status);
+
+	info->status_timer.data = (unsigned long)info;
+	info->status_timer.function = status_timeout;
+	info->status_timer.expires = jiffies + msecs_to_jiffies(10);
+	add_timer(&info->status_timer);
+}
+
+
+/* Register Access Routines -
+ * All registers are memory mapped
+ */
+#define CALC_REGADDR() \
+	unsigned char * RegAddr = (unsigned char*)(info->sca_base + Addr); \
+	if (info->port_num > 1) \
+		RegAddr += 256;	    		/* port 0-1 SCA0, 2-3 SCA1 */ \
+	if ( info->port_num & 1) { \
+		if (Addr > 0x7f) \
+			RegAddr += 0x40;	/* DMA access */ \
+		else if (Addr > 0x1f && Addr < 0x60) \
+			RegAddr += 0x20;	/* MSCI access */ \
+	}
+
+
+unsigned char read_reg(SLMP_INFO * info, unsigned char Addr)
+{
+	CALC_REGADDR();
+	return *RegAddr;
+}
+void write_reg(SLMP_INFO * info, unsigned char Addr, unsigned char Value)
+{
+	CALC_REGADDR();
+	*RegAddr = Value;
+}
+
+u16 read_reg16(SLMP_INFO * info, unsigned char Addr)
+{
+	CALC_REGADDR();
+	return *((u16 *)RegAddr);
+}
+
+void write_reg16(SLMP_INFO * info, unsigned char Addr, u16 Value)
+{
+	CALC_REGADDR();
+	*((u16 *)RegAddr) = Value;
+}
+
+unsigned char read_status_reg(SLMP_INFO * info)
+{
+	unsigned char *RegAddr = (unsigned char *)info->statctrl_base;
+	return *RegAddr;
+}
+
+void write_control_reg(SLMP_INFO * info)
+{
+	unsigned char *RegAddr = (unsigned char *)info->statctrl_base;
+	*RegAddr = info->port_array[0]->ctrlreg_value;
+}
+
+
+static int __devinit synclinkmp_init_one (struct pci_dev *dev,
+					  const struct pci_device_id *ent)
+{
+	if (pci_enable_device(dev)) {
+		printk("error enabling pci device %p\n", dev);
+		return -EIO;
+	}
+	device_init( ++synclinkmp_adapter_count, dev );
+	return 0;
+}
+
+static void __devexit synclinkmp_remove_one (struct pci_dev *dev)
+{
+}
diff --git a/drivers/char/sysrq.c b/drivers/char/sysrq.c
new file mode 100644
index 0000000..f59f7cb
--- /dev/null
+++ b/drivers/char/sysrq.c
@@ -0,0 +1,432 @@
+/* -*- linux-c -*-
+ *
+ *	$Id: sysrq.c,v 1.15 1998/08/23 14:56:41 mj Exp $
+ *
+ *	Linux Magic System Request Key Hacks
+ *
+ *	(c) 1997 Martin Mares <mj@atrey.karlin.mff.cuni.cz>
+ *	based on ideas by Pavel Machek <pavel@atrey.karlin.mff.cuni.cz>
+ *
+ *	(c) 2000 Crutcher Dunnavant <crutcher+kernel@datastacks.com>
+ *	overhauled to use key registration
+ *	based upon discusions in irc://irc.openprojects.net/#kernelnewbies
+ */
+
+#include <linux/config.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/tty.h>
+#include <linux/mount.h>
+#include <linux/kdev_t.h>
+#include <linux/major.h>
+#include <linux/reboot.h>
+#include <linux/sysrq.h>
+#include <linux/kbd_kern.h>
+#include <linux/quotaops.h>
+#include <linux/smp_lock.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/suspend.h>
+#include <linux/writeback.h>
+#include <linux/buffer_head.h>		/* for fsync_bdev() */
+#include <linux/swap.h>
+#include <linux/spinlock.h>
+#include <linux/vt_kern.h>
+#include <linux/workqueue.h>
+
+#include <asm/ptrace.h>
+
+/* Whether we react on sysrq keys or just ignore them */
+int sysrq_enabled = 1;
+
+/* Loglevel sysrq handler */
+static void sysrq_handle_loglevel(int key, struct pt_regs *pt_regs,
+				  struct tty_struct *tty) 
+{
+	int i;
+	i = key - '0';
+	console_loglevel = 7;
+	printk("Loglevel set to %d\n", i);
+	console_loglevel = i;
+}	
+static struct sysrq_key_op sysrq_loglevel_op = {
+	.handler	= sysrq_handle_loglevel,
+	.help_msg	= "loglevel0-8",
+	.action_msg	= "Changing Loglevel",
+	.enable_mask	= SYSRQ_ENABLE_LOG,
+};
+
+
+/* SAK sysrq handler */
+#ifdef CONFIG_VT
+static void sysrq_handle_SAK(int key, struct pt_regs *pt_regs,
+			     struct tty_struct *tty) 
+{
+	if (tty)
+		do_SAK(tty);
+	reset_vc(vc_cons[fg_console].d);
+}
+static struct sysrq_key_op sysrq_SAK_op = {
+	.handler	= sysrq_handle_SAK,
+	.help_msg	= "saK",
+	.action_msg	= "SAK",
+	.enable_mask	= SYSRQ_ENABLE_KEYBOARD,
+};
+#endif
+
+#ifdef CONFIG_VT
+/* unraw sysrq handler */
+static void sysrq_handle_unraw(int key, struct pt_regs *pt_regs,
+			       struct tty_struct *tty) 
+{
+	struct kbd_struct *kbd = &kbd_table[fg_console];
+
+	if (kbd)
+		kbd->kbdmode = VC_XLATE;
+}
+static struct sysrq_key_op sysrq_unraw_op = {
+	.handler	= sysrq_handle_unraw,
+	.help_msg	= "unRaw",
+	.action_msg	= "Keyboard mode set to XLATE",
+	.enable_mask	= SYSRQ_ENABLE_KEYBOARD,
+};
+#endif /* CONFIG_VT */
+
+/* reboot sysrq handler */
+static void sysrq_handle_reboot(int key, struct pt_regs *pt_regs,
+				struct tty_struct *tty) 
+{
+	local_irq_enable();
+	machine_restart(NULL);
+}
+
+static struct sysrq_key_op sysrq_reboot_op = {
+	.handler	= sysrq_handle_reboot,
+	.help_msg	= "reBoot",
+	.action_msg	= "Resetting",
+	.enable_mask	= SYSRQ_ENABLE_BOOT,
+};
+
+static void sysrq_handle_sync(int key, struct pt_regs *pt_regs,
+			      struct tty_struct *tty) 
+{
+	emergency_sync();
+}
+
+static struct sysrq_key_op sysrq_sync_op = {
+	.handler	= sysrq_handle_sync,
+	.help_msg	= "Sync",
+	.action_msg	= "Emergency Sync",
+	.enable_mask	= SYSRQ_ENABLE_SYNC,
+};
+
+static void sysrq_handle_mountro(int key, struct pt_regs *pt_regs,
+				 struct tty_struct *tty) 
+{
+	emergency_remount();
+}
+
+static struct sysrq_key_op sysrq_mountro_op = {
+	.handler	= sysrq_handle_mountro,
+	.help_msg	= "Unmount",
+	.action_msg	= "Emergency Remount R/O",
+	.enable_mask	= SYSRQ_ENABLE_REMOUNT,
+};
+
+/* END SYNC SYSRQ HANDLERS BLOCK */
+
+
+/* SHOW SYSRQ HANDLERS BLOCK */
+
+static void sysrq_handle_showregs(int key, struct pt_regs *pt_regs,
+				  struct tty_struct *tty) 
+{
+	if (pt_regs)
+		show_regs(pt_regs);
+}
+static struct sysrq_key_op sysrq_showregs_op = {
+	.handler	= sysrq_handle_showregs,
+	.help_msg	= "showPc",
+	.action_msg	= "Show Regs",
+	.enable_mask	= SYSRQ_ENABLE_DUMP,
+};
+
+
+static void sysrq_handle_showstate(int key, struct pt_regs *pt_regs,
+				   struct tty_struct *tty) 
+{
+	show_state();
+}
+static struct sysrq_key_op sysrq_showstate_op = {
+	.handler	= sysrq_handle_showstate,
+	.help_msg	= "showTasks",
+	.action_msg	= "Show State",
+	.enable_mask	= SYSRQ_ENABLE_DUMP,
+};
+
+
+static void sysrq_handle_showmem(int key, struct pt_regs *pt_regs,
+				 struct tty_struct *tty) 
+{
+	show_mem();
+}
+static struct sysrq_key_op sysrq_showmem_op = {
+	.handler	= sysrq_handle_showmem,
+	.help_msg	= "showMem",
+	.action_msg	= "Show Memory",
+	.enable_mask	= SYSRQ_ENABLE_DUMP,
+};
+
+/* SHOW SYSRQ HANDLERS BLOCK */
+
+
+/* SIGNAL SYSRQ HANDLERS BLOCK */
+
+/* signal sysrq helper function
+ * Sends a signal to all user processes */
+static void send_sig_all(int sig)
+{
+	struct task_struct *p;
+
+	for_each_process(p) {
+		if (p->mm && p->pid != 1)
+			/* Not swapper, init nor kernel thread */
+			force_sig(sig, p);
+	}
+}
+
+static void sysrq_handle_term(int key, struct pt_regs *pt_regs,
+			      struct tty_struct *tty) 
+{
+	send_sig_all(SIGTERM);
+	console_loglevel = 8;
+}
+static struct sysrq_key_op sysrq_term_op = {
+	.handler	= sysrq_handle_term,
+	.help_msg	= "tErm",
+	.action_msg	= "Terminate All Tasks",
+	.enable_mask	= SYSRQ_ENABLE_SIGNAL,
+};
+
+static void moom_callback(void *ignored)
+{
+	out_of_memory(GFP_KERNEL);
+}
+
+static DECLARE_WORK(moom_work, moom_callback, NULL);
+
+static void sysrq_handle_moom(int key, struct pt_regs *pt_regs,
+			      struct tty_struct *tty)
+{
+	schedule_work(&moom_work);
+}
+static struct sysrq_key_op sysrq_moom_op = {
+	.handler	= sysrq_handle_moom,
+	.help_msg	= "Full",
+	.action_msg	= "Manual OOM execution",
+};
+
+static void sysrq_handle_kill(int key, struct pt_regs *pt_regs,
+			      struct tty_struct *tty) 
+{
+	send_sig_all(SIGKILL);
+	console_loglevel = 8;
+}
+static struct sysrq_key_op sysrq_kill_op = {
+	.handler	= sysrq_handle_kill,
+	.help_msg	= "kIll",
+	.action_msg	= "Kill All Tasks",
+	.enable_mask	= SYSRQ_ENABLE_SIGNAL,
+};
+
+/* END SIGNAL SYSRQ HANDLERS BLOCK */
+
+static void sysrq_handle_unrt(int key, struct pt_regs *pt_regs,
+				struct tty_struct *tty)
+{
+	normalize_rt_tasks();
+}
+static struct sysrq_key_op sysrq_unrt_op = {
+	.handler	= sysrq_handle_unrt,
+	.help_msg	= "Nice",
+	.action_msg	= "Nice All RT Tasks",
+	.enable_mask	= SYSRQ_ENABLE_RTNICE,
+};
+
+/* Key Operations table and lock */
+static DEFINE_SPINLOCK(sysrq_key_table_lock);
+#define SYSRQ_KEY_TABLE_LENGTH 36
+static struct sysrq_key_op *sysrq_key_table[SYSRQ_KEY_TABLE_LENGTH] = {
+/* 0 */	&sysrq_loglevel_op,
+/* 1 */	&sysrq_loglevel_op,
+/* 2 */	&sysrq_loglevel_op,
+/* 3 */	&sysrq_loglevel_op,
+/* 4 */	&sysrq_loglevel_op,
+/* 5 */	&sysrq_loglevel_op,
+/* 6 */	&sysrq_loglevel_op,
+/* 7 */	&sysrq_loglevel_op,
+/* 8 */	&sysrq_loglevel_op,
+/* 9 */	&sysrq_loglevel_op,
+/* a */	NULL, /* Don't use for system provided sysrqs,
+		 it is handled specially on the sparc
+		 and will never arrive */
+/* b */	&sysrq_reboot_op,
+/* c */ NULL,
+/* d */	NULL,
+/* e */	&sysrq_term_op,
+/* f */	&sysrq_moom_op,
+/* g */	NULL,
+/* h */	NULL,
+/* i */	&sysrq_kill_op,
+/* j */	NULL,
+#ifdef CONFIG_VT
+/* k */	&sysrq_SAK_op,
+#else
+/* k */	NULL,
+#endif
+/* l */	NULL,
+/* m */	&sysrq_showmem_op,
+/* n */	&sysrq_unrt_op,
+/* o */	NULL, /* This will often be registered
+		 as 'Off' at init time */
+/* p */	&sysrq_showregs_op,
+/* q */	NULL,
+#ifdef CONFIG_VT
+/* r */	&sysrq_unraw_op,
+#else
+/* r */ NULL,
+#endif
+/* s */	&sysrq_sync_op,
+/* t */	&sysrq_showstate_op,
+/* u */	&sysrq_mountro_op,
+/* v */	NULL, /* May be assigned at init time by SMP VOYAGER */
+/* w */	NULL,
+/* x */	NULL,
+/* y */	NULL,
+/* z */	NULL
+};
+
+/* key2index calculation, -1 on invalid index */
+static int sysrq_key_table_key2index(int key) {
+	int retval;
+	if ((key >= '0') && (key <= '9')) {
+		retval = key - '0';
+	} else if ((key >= 'a') && (key <= 'z')) {
+		retval = key + 10 - 'a';
+	} else {
+		retval = -1;
+	}
+	return retval;
+}
+
+/*
+ * get and put functions for the table, exposed to modules.
+ */
+
+struct sysrq_key_op *__sysrq_get_key_op (int key) {
+        struct sysrq_key_op *op_p;
+        int i;
+	
+	i = sysrq_key_table_key2index(key);
+        op_p = (i == -1) ? NULL : sysrq_key_table[i];
+        return op_p;
+}
+
+void __sysrq_put_key_op (int key, struct sysrq_key_op *op_p) {
+        int i;
+
+	i = sysrq_key_table_key2index(key);
+        if (i != -1)
+                sysrq_key_table[i] = op_p;
+}
+
+/*
+ * This is the non-locking version of handle_sysrq
+ * It must/can only be called by sysrq key handlers,
+ * as they are inside of the lock
+ */
+
+void __handle_sysrq(int key, struct pt_regs *pt_regs, struct tty_struct *tty, int check_mask)
+{
+	struct sysrq_key_op *op_p;
+	int orig_log_level;
+	int i, j;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sysrq_key_table_lock, flags);
+	orig_log_level = console_loglevel;
+	console_loglevel = 7;
+	printk(KERN_INFO "SysRq : ");
+
+        op_p = __sysrq_get_key_op(key);
+        if (op_p) {
+		/* Should we check for enabled operations (/proc/sysrq-trigger should not)
+		 * and is the invoked operation enabled? */
+		if (!check_mask || sysrq_enabled == 1 ||
+		    (sysrq_enabled & op_p->enable_mask)) {
+			printk ("%s\n", op_p->action_msg);
+			console_loglevel = orig_log_level;
+			op_p->handler(key, pt_regs, tty);
+		}
+		else
+			printk("This sysrq operation is disabled.\n");
+	} else {
+		printk("HELP : ");
+		/* Only print the help msg once per handler */
+		for (i=0; i<SYSRQ_KEY_TABLE_LENGTH; i++) 
+		if (sysrq_key_table[i]) {
+			for (j=0; sysrq_key_table[i] != sysrq_key_table[j]; j++);
+			if (j == i)
+				printk ("%s ", sysrq_key_table[i]->help_msg);
+		}
+		printk ("\n");
+		console_loglevel = orig_log_level;
+	}
+	spin_unlock_irqrestore(&sysrq_key_table_lock, flags);
+}
+
+/*
+ * This function is called by the keyboard handler when SysRq is pressed
+ * and any other keycode arrives.
+ */
+
+void handle_sysrq(int key, struct pt_regs *pt_regs, struct tty_struct *tty)
+{
+	if (!sysrq_enabled)
+		return;
+	__handle_sysrq(key, pt_regs, tty, 1);
+}
+
+int __sysrq_swap_key_ops(int key, struct sysrq_key_op *insert_op_p,
+                                struct sysrq_key_op *remove_op_p) {
+
+	int retval;
+	unsigned long flags;
+
+	spin_lock_irqsave(&sysrq_key_table_lock, flags);
+	if (__sysrq_get_key_op(key) == remove_op_p) {
+		__sysrq_put_key_op(key, insert_op_p);
+		retval = 0;
+	} else {
+		retval = -1;
+	}
+	spin_unlock_irqrestore(&sysrq_key_table_lock, flags);
+
+	return retval;
+}
+
+int register_sysrq_key(int key, struct sysrq_key_op *op_p)
+{
+	return __sysrq_swap_key_ops(key, op_p, NULL);
+}
+
+int unregister_sysrq_key(int key, struct sysrq_key_op *op_p)
+{
+	return __sysrq_swap_key_ops(key, NULL, op_p);
+}
+
+EXPORT_SYMBOL(handle_sysrq);
+EXPORT_SYMBOL(register_sysrq_key);
+EXPORT_SYMBOL(unregister_sysrq_key);
diff --git a/drivers/char/tb0219.c b/drivers/char/tb0219.c
new file mode 100644
index 0000000..5413f29
--- /dev/null
+++ b/drivers/char/tb0219.c
@@ -0,0 +1,347 @@
+/*
+ *  Driver for TANBAC TB0219 base board.
+ *
+ *  Copyright (C) 2005  Yoichi Yuasa <yuasa@hh.iij4u.or.jp>
+ *
+ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include <asm/io.h>
+#include <asm/reboot.h>
+
+MODULE_AUTHOR("Yoichi Yuasa <yuasa@hh.iij4u.or.jp>");
+MODULE_DESCRIPTION("TANBAC TB0219 base board driver");
+MODULE_LICENSE("GPL");
+
+static int major;	/* default is dynamic major device number */
+module_param(major, int, 0);
+MODULE_PARM_DESC(major, "Major device number");
+
+static void (*old_machine_restart)(char *command);
+static void __iomem *tb0219_base;
+static spinlock_t tb0219_lock;
+
+#define tb0219_read(offset)		readw(tb0219_base + (offset))
+#define tb0219_write(offset, value)	writew((value), tb0219_base + (offset))
+
+#define TB0219_START	0x0a000000UL
+#define TB0219_SIZE	0x20UL
+
+#define TB0219_LED			0x00
+#define TB0219_GPIO_INPUT		0x02
+#define TB0219_GPIO_OUTPUT		0x04
+#define TB0219_DIP_SWITCH		0x06
+#define TB0219_MISC			0x08
+#define TB0219_RESET			0x0e
+#define TB0219_PCI_SLOT1_IRQ_STATUS	0x10
+#define TB0219_PCI_SLOT2_IRQ_STATUS	0x12
+#define TB0219_PCI_SLOT3_IRQ_STATUS	0x14
+
+typedef enum {
+	TYPE_LED,
+	TYPE_GPIO_OUTPUT,
+} tb0219_type_t;
+
+/*
+ * Minor device number
+ *	 0 = 7 segment LED
+ *
+ *	16 = GPIO IN 0
+ *	17 = GPIO IN 1
+ *	18 = GPIO IN 2
+ *	19 = GPIO IN 3
+ *	20 = GPIO IN 4
+ *	21 = GPIO IN 5
+ *	22 = GPIO IN 6
+ *	23 = GPIO IN 7
+ *
+ *	32 = GPIO OUT 0
+ *	33 = GPIO OUT 1
+ *	34 = GPIO OUT 2
+ *	35 = GPIO OUT 3
+ *	36 = GPIO OUT 4
+ *	37 = GPIO OUT 5
+ *	38 = GPIO OUT 6
+ *	39 = GPIO OUT 7
+ *
+ *	48 = DIP switch 1
+ *	49 = DIP switch 2
+ *	50 = DIP switch 3
+ *	51 = DIP switch 4
+ *	52 = DIP switch 5
+ *	53 = DIP switch 6
+ *	54 = DIP switch 7
+ *	55 = DIP switch 8
+ */
+
+static inline char get_led(void)
+{
+	return (char)tb0219_read(TB0219_LED);
+}
+
+static inline char get_gpio_input_pin(unsigned int pin)
+{
+	uint16_t values;
+
+	values = tb0219_read(TB0219_GPIO_INPUT);
+	if (values & (1 << pin))
+		return '1';
+
+	return '0';
+}
+
+static inline char get_gpio_output_pin(unsigned int pin)
+{
+	uint16_t values;
+
+	values = tb0219_read(TB0219_GPIO_OUTPUT);
+	if (values & (1 << pin))
+		return '1';
+
+	return '0';
+}
+
+static inline char get_dip_switch(unsigned int pin)
+{
+	uint16_t values;
+
+	values = tb0219_read(TB0219_DIP_SWITCH);
+	if (values & (1 << pin))
+		return '1';
+
+	return '0';
+}
+
+static inline int set_led(char command)
+{
+	tb0219_write(TB0219_LED, command);
+
+	return 0;
+}
+
+static inline int set_gpio_output_pin(unsigned int pin, char command)
+{
+	unsigned long flags;
+	uint16_t value;
+
+	if (command != '0' && command != '1')
+		return -EINVAL;
+
+	spin_lock_irqsave(&tb0219_lock, flags);
+	value = tb0219_read(TB0219_GPIO_OUTPUT);
+	if (command == '0')
+		value &= ~(1 << pin);
+	else
+		value |= 1 << pin;
+	tb0219_write(TB0219_GPIO_OUTPUT, value);
+	spin_unlock_irqrestore(&tb0219_lock, flags);
+
+	return 0;
+
+}
+
+static ssize_t tanbac_tb0219_read(struct file *file, char __user *buf, size_t len,
+                                  loff_t *ppos)
+{
+	unsigned int minor;
+	char value;
+
+	minor = iminor(file->f_dentry->d_inode);
+	switch (minor) {
+	case 0:
+		value = get_led();
+		break;
+	case 16 ... 23:
+		value = get_gpio_input_pin(minor - 16);
+		break;
+	case 32 ... 39:
+		value = get_gpio_output_pin(minor - 32);
+		break;
+	case 48 ... 55:
+		value = get_dip_switch(minor - 48);
+		break;
+	default:
+		return -EBADF;
+	}
+
+	if (len <= 0)
+		return -EFAULT;
+
+	if (put_user(value, buf))
+		return -EFAULT;
+
+	return 1;
+}
+
+static ssize_t tanbac_tb0219_write(struct file *file, const char __user *data,
+                                   size_t len, loff_t *ppos)
+{
+	unsigned int minor;
+	tb0219_type_t type;
+	size_t i;
+	int retval = 0;
+	char c;
+
+	minor = iminor(file->f_dentry->d_inode);
+	switch (minor) {
+	case 0:
+		type = TYPE_LED;
+		break;
+	case 32 ... 39:
+		type = TYPE_GPIO_OUTPUT;
+		break;
+	default:
+		return -EBADF;
+	}
+
+	for (i = 0; i < len; i++) {
+		if (get_user(c, data + i))
+			return -EFAULT;
+
+		switch (type) {
+		case TYPE_LED:
+			retval = set_led(c);
+			break;
+		case TYPE_GPIO_OUTPUT:
+			retval = set_gpio_output_pin(minor - 32, c);
+			break;
+		}
+
+		if (retval < 0)
+			break;
+	}
+
+	return i;
+}
+
+static int tanbac_tb0219_open(struct inode *inode, struct file *file)
+{
+	unsigned int minor;
+
+	minor = iminor(inode);
+	switch (minor) {
+	case 0:
+	case 16 ... 23:
+	case 32 ... 39:
+	case 48 ... 55:
+		return nonseekable_open(inode, file);
+	default:
+		break;
+	}
+
+	return -EBADF;
+}
+
+static int tanbac_tb0219_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static struct file_operations tb0219_fops = {
+	.owner		= THIS_MODULE,
+	.read		= tanbac_tb0219_read,
+	.write		= tanbac_tb0219_write,
+	.open		= tanbac_tb0219_open,
+	.release	= tanbac_tb0219_release,
+};
+
+static void tb0219_restart(char *command)
+{
+	tb0219_write(TB0219_RESET, 0);
+}
+
+static int tb0219_probe(struct device *dev)
+{
+	int retval;
+
+	if (request_mem_region(TB0219_START, TB0219_SIZE, "TB0219") == NULL)
+		return -EBUSY;
+
+	tb0219_base = ioremap(TB0219_START, TB0219_SIZE);
+	if (tb0219_base == NULL) {
+		release_mem_region(TB0219_START, TB0219_SIZE);
+		return -ENOMEM;
+	}
+
+	retval = register_chrdev(major, "TB0219", &tb0219_fops);
+	if (retval < 0) {
+		iounmap(tb0219_base);
+		tb0219_base = NULL;
+		release_mem_region(TB0219_START, TB0219_SIZE);
+		return retval;
+	}
+
+	spin_lock_init(&tb0219_lock);
+
+	old_machine_restart = _machine_restart;
+	_machine_restart = tb0219_restart;
+
+	if (major == 0) {
+		major = retval;
+		printk(KERN_INFO "TB0219: major number %d\n", major);
+	}
+
+	return 0;
+}
+
+static int tb0219_remove(struct device *dev)
+{
+	_machine_restart = old_machine_restart;
+
+	iounmap(tb0219_base);
+	tb0219_base = NULL;
+
+	release_mem_region(TB0219_START, TB0219_SIZE);
+
+	return 0;
+}
+
+static struct platform_device *tb0219_platform_device;
+
+static struct device_driver tb0219_device_driver = {
+	.name		= "TB0219",
+	.bus		= &platform_bus_type,
+	.probe		= tb0219_probe,
+	.remove		= tb0219_remove,
+};
+
+static int __devinit tanbac_tb0219_init(void)
+{
+	int retval;
+
+	tb0219_platform_device = platform_device_register_simple("TB0219", -1, NULL, 0);
+	if (IS_ERR(tb0219_platform_device))
+		return PTR_ERR(tb0219_platform_device);
+
+	retval = driver_register(&tb0219_device_driver);
+	if (retval < 0)
+		platform_device_unregister(tb0219_platform_device);
+
+	return retval;
+}
+
+static void __devexit tanbac_tb0219_exit(void)
+{
+	driver_unregister(&tb0219_device_driver);
+
+	platform_device_unregister(tb0219_platform_device);
+}
+
+module_init(tanbac_tb0219_init);
+module_exit(tanbac_tb0219_exit);
diff --git a/drivers/char/tipar.c b/drivers/char/tipar.c
new file mode 100644
index 0000000..0c5ba9d
--- /dev/null
+++ b/drivers/char/tipar.c
@@ -0,0 +1,564 @@
+/* Hey EMACS -*- linux-c -*-
+ *
+ * tipar - low level driver for handling a parallel link cable designed
+ * for Texas Instruments graphing calculators (http://lpg.ticalc.org).
+ * A part of the TiLP project.
+ *
+ * Copyright (C) 2000-2002, Romain Lievin <roms@lpg.ticalc.org>
+ * under the terms of the GNU General Public License.
+ *
+ * Various fixes & clean-up from the Linux Kernel Mailing List
+ * (Alan Cox, Richard B. Johnson, Christoph Hellwig).
+ */
+
+/* This driver should, in theory, work with any parallel port that has an
+ * appropriate low-level driver; all I/O is done through the parport
+ * abstraction layer.
+ *
+ * If this driver is built into the kernel, you can configure it using the
+ * kernel command-line.  For example:
+ *
+ *      tipar=timeout,delay       (set timeout and delay)
+ *
+ * If the driver is loaded as a module, similar functionality is available
+ * using module parameters.  The equivalent of the above commands would be:
+ *
+ *      # insmod tipar timeout=15 delay=10
+ */
+
+/* COMPATIBILITY WITH OLD KERNELS
+ *
+ * Usually, parallel cables were bound to ports at
+ * particular I/O addresses, as follows:
+ *
+ *      tipar0             0x378
+ *      tipar1             0x278
+ *      tipar2             0x3bc
+ *
+ *
+ * This driver, by default, binds tipar devices according to parport and
+ * the minor number.
+ *
+ */
+#undef DEBUG				/* change to #define to get debugging
+					 * output - for pr_debug() */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/fcntl.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <linux/bitops.h>
+#include <linux/devfs_fs_kernel.h>	/* DevFs support */
+#include <linux/parport.h>		/* Our code depend on parport */
+#include <linux/device.h>
+
+/*
+ * TI definitions
+ */
+#include <linux/ticable.h>
+
+/*
+ * Version Information
+ */
+#define DRIVER_VERSION "1.19"
+#define DRIVER_AUTHOR  "Romain Lievin <roms@lpg.ticalc.org>"
+#define DRIVER_DESC    "Device driver for TI/PC parallel link cables"
+#define DRIVER_LICENSE "GPL"
+
+#define VERSION(ver,rel,seq) (((ver)<<16) | ((rel)<<8) | (seq))
+
+/* ----- global variables --------------------------------------------- */
+
+struct tipar_struct {
+	struct pardevice *dev;	/* Parport device entry */
+};
+
+#define PP_NO 3
+static struct tipar_struct table[PP_NO];
+
+static int delay = IO_DELAY;	/* inter-bit delay in microseconds */
+static int timeout = TIMAXTIME;	/* timeout in tenth of seconds     */
+
+static unsigned int tp_count;	/* tipar count */
+static unsigned long opened;	/* opened devices */
+
+static struct class_simple *tipar_class;
+
+/* --- macros for parport access -------------------------------------- */
+
+#define r_dtr(x)        (parport_read_data(table[(x)].dev->port))
+#define r_str(x)        (parport_read_status(table[(x)].dev->port))
+#define w_ctr(x,y)      (parport_write_control(table[(x)].dev->port, (y)))
+#define w_dtr(x,y)      (parport_write_data(table[(x)].dev->port, (y)))
+
+/* --- setting states on the D-bus with the right timing: ------------- */
+
+static inline void
+outbyte(int value, int minor)
+{
+	w_dtr(minor, value);
+}
+
+static inline int
+inbyte(int minor)
+{
+	return (r_str(minor));
+}
+
+static inline void
+init_ti_parallel(int minor)
+{
+	outbyte(3, minor);
+}
+
+/* ----- global defines ----------------------------------------------- */
+
+#define START(x) { x = jiffies + (HZ * timeout) / 10; }
+#define WAIT(x)  { \
+  if (time_before((x), jiffies)) return -1; \
+  if (need_resched()) schedule(); }
+
+/* ----- D-bus bit-banging functions ---------------------------------- */
+
+/* D-bus protocol (45kbit/s max):
+                    1                 0                      0
+       _______        ______|______    __________|________    __________
+Red  :        ________      |      ____          |        ____
+       _        ____________|________      ______|__________       _____
+White:  ________            |        ______      |          _______
+*/
+
+/* Try to transmit a byte on the specified port (-1 if error). */
+static int
+put_ti_parallel(int minor, unsigned char data)
+{
+	unsigned int bit;
+	unsigned long max;
+
+	for (bit = 0; bit < 8; bit++) {
+		if (data & 1) {
+			outbyte(2, minor);
+			START(max);
+			do {
+				WAIT(max);
+			} while (inbyte(minor) & 0x10);
+
+			outbyte(3, minor);
+			START(max);
+			do {
+				WAIT(max);
+			} while (!(inbyte(minor) & 0x10));
+		} else {
+			outbyte(1, minor);
+			START(max);
+			do {
+				WAIT(max);
+			} while (inbyte(minor) & 0x20);
+
+			outbyte(3, minor);
+			START(max);
+			do {
+				WAIT(max);
+			} while (!(inbyte(minor) & 0x20));
+		}
+
+		data >>= 1;
+		udelay(delay);
+
+		if (need_resched())
+			schedule();
+	}
+
+	return 0;
+}
+
+/* Receive a byte on the specified port or -1 if error. */
+static int
+get_ti_parallel(int minor)
+{
+	unsigned int bit;
+	unsigned char v, data = 0;
+	unsigned long max;
+
+	for (bit = 0; bit < 8; bit++) {
+		START(max);
+		do {
+			WAIT(max);
+		} while ((v = inbyte(minor) & 0x30) == 0x30);
+
+		if (v == 0x10) {
+			data = (data >> 1) | 0x80;
+			outbyte(1, minor);
+			START(max);
+			do {
+				WAIT(max);
+			} while (!(inbyte(minor) & 0x20));
+			outbyte(3, minor);
+		} else {
+			data = data >> 1;
+			outbyte(2, minor);
+			START(max);
+			do {
+				WAIT(max);
+			} while (!(inbyte(minor) & 0x10));
+			outbyte(3, minor);
+		}
+
+		udelay(delay);
+		if (need_resched())
+			schedule();
+	}
+
+	return (int) data;
+}
+
+/* Try to detect a parallel link cable on the specified port */
+static int
+probe_ti_parallel(int minor)
+{
+	int i;
+	int seq[] = { 0x00, 0x20, 0x10, 0x30 };
+
+	for (i = 3; i >= 0; i--) {
+		outbyte(3, minor);
+		outbyte(i, minor);
+		udelay(delay);
+		pr_debug("tipar: Probing -> %i: 0x%02x 0x%02x\n", i,
+			data & 0x30, seq[i]);
+		if ((inbyte(minor) & 0x30) != seq[i]) {
+			outbyte(3, minor);
+			return -1;
+		}
+	}
+
+	outbyte(3, minor);
+	return 0;
+}
+
+/* ----- kernel module functions--------------------------------------- */
+
+static int
+tipar_open(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode) - TIPAR_MINOR;
+
+	if (minor > tp_count - 1)
+		return -ENXIO;
+
+	if (test_and_set_bit(minor, &opened))
+		return -EBUSY;
+
+	parport_claim_or_block(table[minor].dev);
+	init_ti_parallel(minor);
+	parport_release(table[minor].dev);
+
+	return nonseekable_open(inode, file);
+}
+
+static int
+tipar_close(struct inode *inode, struct file *file)
+{
+	unsigned int minor = iminor(inode) - TIPAR_MINOR;
+
+	if (minor > tp_count - 1)
+		return -ENXIO;
+
+	clear_bit(minor, &opened);
+
+	return 0;
+}
+
+static ssize_t
+tipar_write (struct file *file, const char __user *buf, size_t count,
+		loff_t * ppos)
+{
+	unsigned int minor = iminor(file->f_dentry->d_inode) - TIPAR_MINOR;
+	ssize_t n;
+
+	parport_claim_or_block(table[minor].dev);
+
+	for (n = 0; n < count; n++) {
+		unsigned char b;
+
+		if (get_user(b, buf + n)) {
+			n = -EFAULT;
+			goto out;
+		}
+
+		if (put_ti_parallel(minor, b) == -1) {
+			init_ti_parallel(minor);
+			n = -ETIMEDOUT;
+			goto out;
+		}
+	}
+      out:
+	parport_release(table[minor].dev);
+	return n;
+}
+
+static ssize_t
+tipar_read(struct file *file, char __user *buf, size_t count, loff_t * ppos)
+{
+	int b = 0;
+	unsigned int minor = iminor(file->f_dentry->d_inode) - TIPAR_MINOR;
+	ssize_t retval = 0;
+	ssize_t n = 0;
+
+	if (count == 0)
+		return 0;
+
+	parport_claim_or_block(table[minor].dev);
+
+	while (n < count) {
+		b = get_ti_parallel(minor);
+		if (b == -1) {
+			init_ti_parallel(minor);
+			retval = -ETIMEDOUT;
+			goto out;
+		} else {
+			if (put_user(b, buf + n)) {
+				retval = -EFAULT;
+				break;
+			} else
+				retval = ++n;
+		}
+
+		/* Non-blocking mode : try again ! */
+		if (file->f_flags & O_NONBLOCK) {
+			retval = -EAGAIN;
+			goto out;
+		}
+
+		/* Signal pending, try again ! */
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			goto out;
+		}
+
+		if (need_resched())
+			schedule();
+	}
+
+      out:
+	parport_release(table[minor].dev);
+	return retval;
+}
+
+static int
+tipar_ioctl(struct inode *inode, struct file *file,
+	    unsigned int cmd, unsigned long arg)
+{
+	int retval = 0;
+
+	switch (cmd) {
+	case IOCTL_TIPAR_DELAY:
+		delay = (int)arg;    //get_user(delay, &arg);
+		break;
+	case IOCTL_TIPAR_TIMEOUT:
+		if (arg != 0)
+                        timeout = (int)arg;
+                else
+                        retval = -EINVAL;
+	  break;
+	default:
+		retval = -ENOTTY;
+		break;
+	}
+
+	return retval;
+}
+
+/* ----- kernel module registering ------------------------------------ */
+
+static struct file_operations tipar_fops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.read = tipar_read,
+	.write = tipar_write,
+	.ioctl = tipar_ioctl,
+	.open = tipar_open,
+	.release = tipar_close,
+};
+
+/* --- initialisation code ------------------------------------- */
+
+#ifndef MODULE
+/*      You must set these - there is no sane way to probe for this cable.
+ *      You can use 'tipar=timeout,delay' to set these now. */
+static int __init
+tipar_setup(char *str)
+{
+	int ints[2];
+
+	str = get_options(str, ARRAY_SIZE(ints), ints);
+
+	if (ints[0] > 0) {
+		if (ints[1] != 0)
+                        timeout = ints[1];
+                else
+                        printk(KERN_WARNING "tipar: bad timeout value (0), "
+				"using default value instead");
+		if (ints[0] > 1) {
+			delay = ints[2];
+		}
+	}
+
+	return 1;
+}
+#endif
+
+/*
+ * Register our module into parport.
+ * Pass also 2 callbacks functions to parport: a pre-emptive function and an
+ * interrupt handler function (unused).
+ * Display a message such "tipar0: using parport0 (polling)".
+ */
+static int
+tipar_register(int nr, struct parport *port)
+{
+	int err = 0;
+
+	/* Register our module into parport */
+	table[nr].dev = parport_register_device(port, "tipar",
+						NULL, NULL, NULL, 0,
+						(void *) &table[nr]);
+
+	if (table[nr].dev == NULL) {
+		err = 1;
+		goto out;
+	}
+
+	class_simple_device_add(tipar_class, MKDEV(TIPAR_MAJOR,
+			TIPAR_MINOR + nr), NULL, "par%d", nr);
+	/* Use devfs, tree: /dev/ticables/par/[0..2] */
+	err = devfs_mk_cdev(MKDEV(TIPAR_MAJOR, TIPAR_MINOR + nr),
+			S_IFCHR | S_IRUGO | S_IWUGO,
+			"ticables/par/%d", nr);
+	if (err)
+		goto out_class;
+
+	/* Display informations */
+	pr_info("tipar%d: using %s (%s)\n", nr, port->name, (port->irq ==
+		PARPORT_IRQ_NONE) ? "polling" : "interrupt-driven");
+
+	if (probe_ti_parallel(nr) != -1)
+		pr_info("tipar%d: link cable found\n", nr);
+	else
+		pr_info("tipar%d: link cable not found\n", nr);
+
+	err = 0;
+	goto out;
+
+out_class:
+	class_simple_device_remove(MKDEV(TIPAR_MAJOR, TIPAR_MINOR + nr));
+	class_simple_destroy(tipar_class);
+out:
+	return err;
+}
+
+static void
+tipar_attach(struct parport *port)
+{
+	if (tp_count == PP_NO) {
+		pr_info("tipar: ignoring parallel port (max. %d)\n", PP_NO);
+		return;
+	}
+
+	if (!tipar_register(tp_count, port))
+		tp_count++;
+}
+
+static void
+tipar_detach(struct parport *port)
+{
+	/* Nothing to do */
+}
+
+static struct parport_driver tipar_driver = {
+	.name = "tipar",
+	.attach = tipar_attach,
+	.detach = tipar_detach,
+};
+
+static int __init
+tipar_init_module(void)
+{
+	int err = 0;
+
+	pr_info("tipar: parallel link cable driver, version %s\n",
+		DRIVER_VERSION);
+
+	if (register_chrdev(TIPAR_MAJOR, "tipar", &tipar_fops)) {
+		printk(KERN_ERR "tipar: unable to get major %d\n", TIPAR_MAJOR);
+		err = -EIO;
+		goto out;
+	}
+
+	/* Use devfs with tree: /dev/ticables/par/[0..2] */
+	devfs_mk_dir("ticables/par");
+
+	tipar_class = class_simple_create(THIS_MODULE, "ticables");
+	if (IS_ERR(tipar_class)) {
+		err = PTR_ERR(tipar_class);
+		goto out_chrdev;
+	}
+	if (parport_register_driver(&tipar_driver)) {
+		printk(KERN_ERR "tipar: unable to register with parport\n");
+		err = -EIO;
+		goto out;
+	}
+
+	err = 0;
+	goto out;
+
+out_chrdev:
+	unregister_chrdev(TIPAR_MAJOR, "tipar");
+out:
+	return err;	
+}
+
+static void __exit
+tipar_cleanup_module(void)
+{
+	unsigned int i;
+
+	/* Unregistering module */
+	parport_unregister_driver(&tipar_driver);
+
+	unregister_chrdev(TIPAR_MAJOR, "tipar");
+
+	for (i = 0; i < PP_NO; i++) {
+		if (table[i].dev == NULL)
+			continue;
+		parport_unregister_device(table[i].dev);
+		class_simple_device_remove(MKDEV(TIPAR_MAJOR, i));
+		devfs_remove("ticables/par/%d", i);
+	}
+	class_simple_destroy(tipar_class);
+	devfs_remove("ticables/par");
+
+	pr_info("tipar: module unloaded\n");
+}
+
+/* --------------------------------------------------------------------- */
+
+__setup("tipar=", tipar_setup);
+module_init(tipar_init_module);
+module_exit(tipar_cleanup_module);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Timeout (default=1.5 seconds)");
+module_param(delay, int, 0);
+MODULE_PARM_DESC(delay, "Inter-bit delay (default=10 microseconds)");
diff --git a/drivers/char/toshiba.c b/drivers/char/toshiba.c
new file mode 100644
index 0000000..58e21fe
--- /dev/null
+++ b/drivers/char/toshiba.c
@@ -0,0 +1,532 @@
+/* toshiba.c -- Linux driver for accessing the SMM on Toshiba laptops
+ *
+ * Copyright (c) 1996-2001  Jonathan A. Buzzard (jonathan@buzzard.org.uk)
+ *
+ * Valuable assistance and patches from:
+ *     Tom May <tom@you-bastards.com>
+ *     Rob Napier <rnapier@employees.org>
+ *
+ * Fn status port numbers for machine ID's courtesy of
+ *     0xfc02: Scott Eisert <scott.e@sky-eye.com>
+ *     0xfc04: Steve VanDevender <stevev@efn.org>
+ *     0xfc08: Garth Berry <garth@itsbruce.net>
+ *     0xfc0a: Egbert Eich <eich@xfree86.org>
+ *     0xfc10: Andrew Lofthouse <Andrew.Lofthouse@robins.af.mil>
+ *     0xfc11: Spencer Olson <solson@novell.com>
+ *     0xfc13: Claudius Frankewitz <kryp@gmx.de>
+ *     0xfc15: Tom May <tom@you-bastards.com>
+ *     0xfc17: Dave Konrad <konrad@xenia.it>
+ *     0xfc1a: George Betzos <betzos@engr.colostate.edu>
+ *     0xfc1b: Munemasa Wada <munemasa@jnovel.co.jp>
+ *     0xfc1d: Arthur Liu <armie@slap.mine.nu>
+ *     0xfc5a: Jacques L'helgoualc'h <lhh@free.fr>
+ *     0xfcd1: Mr. Dave Konrad <konrad@xenia.it>
+ *
+ * WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
+ *
+ *   This code is covered by the GNU GPL and you are free to make any
+ *   changes you wish to it under the terms of the license. However the
+ *   code has the potential to render your computer and/or someone else's
+ *   unusable. Please proceed with care when modifying the code.
+ *
+ * Note: Unfortunately the laptop hardware can close the System Configuration
+ *       Interface on it's own accord. It is therefore necessary for *all*
+ *       programs using this driver to be aware that *any* SCI call can fail at
+ *       *any* time. It is up to any program to be aware of this eventuality
+ *       and take appropriate steps.
+ *
+ * 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, 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.
+ *
+ * The information used to write this driver has been obtained by reverse
+ * engineering the software supplied by Toshiba for their portable computers in
+ * strict accordance with the European Council Directive 92/250/EEC on the legal
+ * protection of computer programs, and it's implementation into English Law by
+ * the Copyright (Computer Programs) Regulations 1992 (S.I. 1992 No.3233).
+ *
+ */
+
+#define TOSH_VERSION "1.11 26/9/2001"
+#define TOSH_DEBUG 0
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+
+#include <linux/toshiba.h>
+
+#define TOSH_MINOR_DEV 181
+
+static int tosh_id = 0x0000;
+static int tosh_bios = 0x0000;
+static int tosh_date = 0x0000;
+static int tosh_sci = 0x0000;
+static int tosh_fan = 0;
+
+static int tosh_fn = 0;
+
+module_param(tosh_fn, int, 0);
+
+
+static int tosh_ioctl(struct inode *, struct file *, unsigned int,
+	unsigned long);
+
+
+static struct file_operations tosh_fops = {
+	.owner		= THIS_MODULE,
+	.ioctl		= tosh_ioctl,
+};
+
+static struct miscdevice tosh_device = {
+	TOSH_MINOR_DEV,
+	"toshiba",
+	&tosh_fops
+};
+
+/*
+ * Read the Fn key status
+ */
+#ifdef CONFIG_PROC_FS
+static int tosh_fn_status(void)
+{
+        unsigned char scan;
+	unsigned long flags;
+
+	if (tosh_fn!=0) {
+		scan = inb(tosh_fn);
+	} else {
+		local_irq_save(flags);
+		outb(0x8e, 0xe4);
+		scan = inb(0xe5);
+		local_irq_restore(flags);
+	}
+
+        return (int) scan;
+}
+#endif
+
+
+/*
+ * For the Portage 610CT and the Tecra 700CS/700CDT emulate the HCI fan function
+ */
+static int tosh_emulate_fan(SMMRegisters *regs)
+{
+	unsigned long eax,ecx,flags;
+	unsigned char al;
+
+	eax = regs->eax & 0xff00;
+	ecx = regs->ecx & 0xffff;
+
+	/* Portage 610CT */
+
+	if (tosh_id==0xfccb) {
+		if (eax==0xfe00) {
+			/* fan status */
+			local_irq_save(flags);
+			outb(0xbe, 0xe4);
+			al = inb(0xe5);
+			local_irq_restore(flags);
+			regs->eax = 0x00;
+			regs->ecx = (unsigned int) (al & 0x01);
+		}
+		if ((eax==0xff00) && (ecx==0x0000)) {
+			/* fan off */
+			local_irq_save(flags);
+			outb(0xbe, 0xe4);
+			al = inb(0xe5);
+			outb(0xbe, 0xe4);
+			outb (al | 0x01, 0xe5);
+			local_irq_restore(flags);
+			regs->eax = 0x00;
+			regs->ecx = 0x00;
+		}
+		if ((eax==0xff00) && (ecx==0x0001)) {
+			/* fan on */
+			local_irq_save(flags);
+			outb(0xbe, 0xe4);
+			al = inb(0xe5);
+			outb(0xbe, 0xe4);
+			outb(al & 0xfe, 0xe5);
+			local_irq_restore(flags);
+			regs->eax = 0x00;
+			regs->ecx = 0x01;
+		}
+	}
+
+	/* Tecra 700CS/CDT */
+
+	if (tosh_id==0xfccc) {
+		if (eax==0xfe00) {
+			/* fan status */
+			local_irq_save(flags);
+			outb(0xe0, 0xe4);
+			al = inb(0xe5);
+			local_irq_restore(flags);
+			regs->eax = 0x00;
+			regs->ecx = al & 0x01;
+		}
+		if ((eax==0xff00) && (ecx==0x0000)) {
+			/* fan off */
+			local_irq_save(flags);
+			outb(0xe0, 0xe4);
+			al = inb(0xe5);
+			outw(0xe0 | ((al & 0xfe) << 8), 0xe4);
+			local_irq_restore(flags);
+			regs->eax = 0x00;
+			regs->ecx = 0x00;
+		}
+		if ((eax==0xff00) && (ecx==0x0001)) {
+			/* fan on */
+			local_irq_save(flags);
+			outb(0xe0, 0xe4);
+			al = inb(0xe5);
+			outw(0xe0 | ((al | 0x01) << 8), 0xe4);
+			local_irq_restore(flags);
+			regs->eax = 0x00;
+			regs->ecx = 0x01;
+		}
+	}
+
+	return 0;
+}
+
+
+/*
+ * Put the laptop into System Management Mode
+ */
+int tosh_smm(SMMRegisters *regs)
+{
+	int eax;
+
+	asm ("# load the values into the registers\n\t" \
+		"pushl %%eax\n\t" \
+		"movl 0(%%eax),%%edx\n\t" \
+		"push %%edx\n\t" \
+		"movl 4(%%eax),%%ebx\n\t" \
+		"movl 8(%%eax),%%ecx\n\t" \
+		"movl 12(%%eax),%%edx\n\t" \
+		"movl 16(%%eax),%%esi\n\t" \
+		"movl 20(%%eax),%%edi\n\t" \
+		"popl %%eax\n\t" \
+		"# call the System Management mode\n\t" \
+		"inb $0xb2,%%al\n\t"
+		"# fill out the memory with the values in the registers\n\t" \
+		"xchgl %%eax,(%%esp)\n\t"
+		"movl %%ebx,4(%%eax)\n\t" \
+		"movl %%ecx,8(%%eax)\n\t" \
+		"movl %%edx,12(%%eax)\n\t" \
+		"movl %%esi,16(%%eax)\n\t" \
+		"movl %%edi,20(%%eax)\n\t" \
+		"popl %%edx\n\t" \
+		"movl %%edx,0(%%eax)\n\t" \
+		"# setup the return value to the carry flag\n\t" \
+		"lahf\n\t" \
+		"shrl $8,%%eax\n\t" \
+		"andl $1,%%eax\n" \
+		: "=a" (eax)
+		: "a" (regs)
+		: "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+
+	return eax;
+}
+
+
+static int tosh_ioctl(struct inode *ip, struct file *fp, unsigned int cmd,
+	unsigned long arg)
+{
+	SMMRegisters regs;
+	SMMRegisters __user *argp = (SMMRegisters __user *)arg;
+	unsigned short ax,bx;
+	int err;
+
+	if (!argp)
+		return -EINVAL;
+
+	if (copy_from_user(&regs, argp, sizeof(SMMRegisters)))
+		return -EFAULT;
+
+	switch (cmd) {
+		case TOSH_SMM:
+			ax = regs.eax & 0xff00;
+			bx = regs.ebx & 0xffff;
+			/* block HCI calls to read/write memory & PCI devices */
+			if (((ax==0xff00) || (ax==0xfe00)) && (bx>0x0069))
+				return -EINVAL;
+
+			/* do we need to emulate the fan ? */
+			if (tosh_fan==1) {
+				if (((ax==0xf300) || (ax==0xf400)) && (bx==0x0004)) {
+					err = tosh_emulate_fan(&regs);
+					break;
+				}
+			}
+			err = tosh_smm(&regs);
+			break;
+		default:
+			return -EINVAL;
+	}
+
+        if (copy_to_user(argp, &regs, sizeof(SMMRegisters)))
+        	return -EFAULT;
+
+	return (err==0) ? 0:-EINVAL;
+}
+
+
+/*
+ * Print the information for /proc/toshiba
+ */
+#ifdef CONFIG_PROC_FS
+static int tosh_get_info(char *buffer, char **start, off_t fpos, int length)
+{
+	char *temp;
+	int key;
+
+	temp = buffer;
+	key = tosh_fn_status();
+
+	/* Arguments
+	     0) Linux driver version (this will change if format changes)
+	     1) Machine ID
+	     2) SCI version
+	     3) BIOS version (major, minor)
+	     4) BIOS date (in SCI date format)
+	     5) Fn Key status
+	*/
+
+	temp += sprintf(temp, "1.1 0x%04x %d.%d %d.%d 0x%04x 0x%02x\n",
+		tosh_id,
+		(tosh_sci & 0xff00)>>8,
+		tosh_sci & 0xff,
+		(tosh_bios & 0xff00)>>8,
+		tosh_bios & 0xff,
+		tosh_date,
+		key);
+
+	return temp-buffer;
+}
+#endif
+
+
+/*
+ * Determine which port to use for the Fn key status
+ */
+static void tosh_set_fn_port(void)
+{
+	switch (tosh_id) {
+		case 0xfc02: case 0xfc04: case 0xfc09: case 0xfc0a: case 0xfc10:
+		case 0xfc11: case 0xfc13: case 0xfc15: case 0xfc1a: case 0xfc1b:
+		case 0xfc5a:
+			tosh_fn = 0x62;
+			break;
+		case 0xfc08: case 0xfc17: case 0xfc1d: case 0xfcd1: case 0xfce0:
+		case 0xfce2:
+			tosh_fn = 0x68;
+			break;
+		default:
+			tosh_fn = 0x00;
+			break;
+	}
+
+	return;
+}
+
+
+/*
+ * Get the machine identification number of the current model
+ */
+static int tosh_get_machine_id(void)
+{
+	int id;
+	SMMRegisters regs;
+	unsigned short bx,cx;
+	unsigned long address;
+
+	id = (0x100*(int) isa_readb(0xffffe))+((int) isa_readb(0xffffa));
+	
+	/* do we have a SCTTable machine identication number on our hands */
+
+	if (id==0xfc2f) {
+
+		/* start by getting a pointer into the BIOS */
+
+		regs.eax = 0xc000;
+		regs.ebx = 0x0000;
+		regs.ecx = 0x0000;
+		tosh_smm(&regs);
+		bx = (unsigned short) (regs.ebx & 0xffff);
+
+		/* At this point in the Toshiba routines under MS Windows
+		   the bx register holds 0xe6f5. However my code is producing
+		   a different value! For the time being I will just fudge the
+		   value. This has been verified on a Satellite Pro 430CDT,
+		   Tecra 750CDT, Tecra 780DVD and Satellite 310CDT. */
+#if TOSH_DEBUG
+		printk("toshiba: debugging ID ebx=0x%04x\n", regs.ebx);
+#endif
+		bx = 0xe6f5;
+
+		/* now twiddle with our pointer a bit */
+
+		address = 0x000f0000+bx;
+		cx = isa_readw(address);
+		address = 0x000f0009+bx+cx;
+		cx = isa_readw(address);
+		address = 0x000f000a+cx;
+		cx = isa_readw(address);
+
+		/* now construct our machine identification number */
+
+		id = ((cx & 0xff)<<8)+((cx & 0xff00)>>8);
+	}
+
+	return id;
+}
+
+
+/*
+ * Probe for the presence of a Toshiba laptop
+ *
+ *   returns and non-zero if unable to detect the presence of a Toshiba
+ *   laptop, otherwise zero and determines the Machine ID, BIOS version and
+ *   date, and SCI version.
+ */
+static int tosh_probe(void)
+{
+	int i,major,minor,day,year,month,flag;
+	unsigned char signature[7] = { 0x54,0x4f,0x53,0x48,0x49,0x42,0x41 };
+	SMMRegisters regs;
+
+	/* extra sanity check for the string "TOSHIBA" in the BIOS because
+	   some machines that are not Toshiba's pass the next test */
+
+	for (i=0;i<7;i++) {
+		if (isa_readb(0xfe010+i)!=signature[i]) {
+			printk("toshiba: not a supported Toshiba laptop\n");
+			return -ENODEV;
+		}
+	}
+
+	/* call the Toshiba SCI support check routine */
+	
+	regs.eax = 0xf0f0;
+	regs.ebx = 0x0000;
+	regs.ecx = 0x0000;
+	flag = tosh_smm(&regs);
+
+	/* if this is not a Toshiba laptop carry flag is set and ah=0x86 */
+
+	if ((flag==1) || ((regs.eax & 0xff00)==0x8600)) {
+		printk("toshiba: not a supported Toshiba laptop\n");
+		return -ENODEV;
+	}
+
+	/* if we get this far then we are running on a Toshiba (probably)! */
+
+	tosh_sci = regs.edx & 0xffff;
+	
+	/* next get the machine ID of the current laptop */
+
+	tosh_id = tosh_get_machine_id();
+
+	/* get the BIOS version */
+
+	major = isa_readb(0xfe009)-'0';
+	minor = ((isa_readb(0xfe00b)-'0')*10)+(isa_readb(0xfe00c)-'0');
+	tosh_bios = (major*0x100)+minor;
+
+	/* get the BIOS date */
+
+	day = ((isa_readb(0xffff5)-'0')*10)+(isa_readb(0xffff6)-'0');
+	month = ((isa_readb(0xffff8)-'0')*10)+(isa_readb(0xffff9)-'0');
+	year = ((isa_readb(0xffffb)-'0')*10)+(isa_readb(0xffffc)-'0');
+	tosh_date = (((year-90) & 0x1f)<<10) | ((month & 0xf)<<6)
+		| ((day & 0x1f)<<1);
+
+
+	/* in theory we should check the ports we are going to use for the
+	   fn key detection (and the fan on the Portage 610/Tecra700), and
+	   then request them to stop other drivers using them. However as
+	   the keyboard driver grabs 0x60-0x6f and the pic driver grabs
+	   0xa0-0xbf we can't. We just have to live dangerously and use the
+	   ports anyway, oh boy! */
+
+	/* do we need to emulate the fan? */
+
+	if ((tosh_id==0xfccb) || (tosh_id==0xfccc))
+		tosh_fan = 1;
+
+	return 0;
+}
+
+int __init tosh_init(void)
+{
+	int retval;
+	/* are we running on a Toshiba laptop */
+
+	if (tosh_probe()!=0)
+		return -EIO;
+
+	printk(KERN_INFO "Toshiba System Managment Mode driver v"
+		TOSH_VERSION"\n");
+
+	/* set the port to use for Fn status if not specified as a parameter */
+	if (tosh_fn==0x00)
+		tosh_set_fn_port();
+
+	/* register the device file */
+	retval = misc_register(&tosh_device);
+	if(retval < 0)
+		return retval;
+
+#ifdef CONFIG_PROC_FS
+	/* register the proc entry */
+	if(create_proc_info_entry("toshiba", 0, NULL, tosh_get_info) == NULL){
+		misc_deregister(&tosh_device);
+		return -ENOMEM;
+	}
+#endif
+
+	return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+	return tosh_init();
+}
+
+void cleanup_module(void)
+{
+	/* remove the proc entry */
+
+	remove_proc_entry("toshiba", NULL);
+
+	/* unregister the device file */
+
+	misc_deregister(&tosh_device);
+}
+#endif
+
+MODULE_LICENSE("GPL");
+MODULE_PARM_DESC(tosh_fn, "User specified Fn key detection port");
+MODULE_AUTHOR("Jonathan Buzzard <jonathan@buzzard.org.uk>");
+MODULE_DESCRIPTION("Toshiba laptop SMM driver");
+MODULE_SUPPORTED_DEVICE("toshiba");
+
diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
new file mode 100644
index 0000000..7a96977
--- /dev/null
+++ b/drivers/char/tpm/Kconfig
@@ -0,0 +1,39 @@
+#
+# TPM device configuration
+#
+
+menu "TPM devices"
+
+config TCG_TPM
+	tristate "TPM Hardware Support"
+	depends on EXPERIMENTAL && PCI
+	---help---
+	  If you have a TPM security chip in your system, which
+	  implements the Trusted Computing Group's specification,
+	  say Yes and it will be accessible from within Linux.  For
+	  more information see <http://www.trustedcomputinggroup.org>. 
+	  An implementation of the Trusted Software Stack (TSS), the 
+	  userspace enablement piece of the specification, can be 
+	  obtained at: <http://sourceforge.net/projects/trousers>.  To 
+	  compile this driver as a module, choose M here; the module 
+	  will be called tpm. If unsure, say N.
+
+config TCG_NSC
+	tristate "National Semiconductor TPM Interface"
+	depends on TCG_TPM
+	---help---
+	  If you have a TPM security chip from National Semicondutor 
+	  say Yes and it will be accessible from within Linux.  To 
+	  compile this driver as a module, choose M here; the module 
+	  will be called tpm_nsc.
+
+config TCG_ATMEL
+	tristate "Atmel TPM Interface"
+	depends on TCG_TPM
+	---help---
+	  If you have a TPM security chip from Atmel say Yes and it 
+	  will be accessible from within Linux.  To compile this driver 
+	  as a module, choose M here; the module will be called tpm_atmel.
+
+endmenu
+
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
new file mode 100644
index 0000000..736d3df
--- /dev/null
+++ b/drivers/char/tpm/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for the kernel tpm device drivers.
+#
+obj-$(CONFIG_TCG_TPM) += tpm.o
+obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
+obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
+
diff --git a/drivers/char/tpm/tpm.c b/drivers/char/tpm/tpm.c
new file mode 100644
index 0000000..8318268
--- /dev/null
+++ b/drivers/char/tpm/tpm.c
@@ -0,0 +1,697 @@
+/*
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert@watson.ibm.com>
+ * Dave Safford <safford@watson.ibm.com>
+ * Reiner Sailer <sailer@watson.ibm.com>
+ * Kylene Hall <kjhall@us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org	 
+ *
+ * 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, version 2 of the
+ * License.
+ * 
+ * Note, the TPM chip is not interrupt driven (only polling)
+ * and can have very long timeouts (minutes!). Hence the unusual
+ * calls to schedule_timeout.
+ *
+ */
+
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include "tpm.h"
+
+#define	TPM_MINOR			224	/* officially assigned */
+
+#define	TPM_BUFSIZE			2048
+
+/* PCI configuration addresses */
+#define	PCI_GEN_PMCON_1			0xA0
+#define	PCI_GEN1_DEC			0xE4
+#define	PCI_LPC_EN			0xE6
+#define	PCI_GEN2_DEC			0xEC
+
+static LIST_HEAD(tpm_chip_list);
+static DEFINE_SPINLOCK(driver_lock);
+static int dev_mask[32];
+
+static void user_reader_timeout(unsigned long ptr)
+{
+	struct tpm_chip *chip = (struct tpm_chip *) ptr;
+
+	down(&chip->buffer_mutex);
+	atomic_set(&chip->data_pending, 0);
+	memset(chip->data_buffer, 0, TPM_BUFSIZE);
+	up(&chip->buffer_mutex);
+}
+
+void tpm_time_expired(unsigned long ptr)
+{
+	int *exp = (int *) ptr;
+	*exp = 1;
+}
+
+EXPORT_SYMBOL_GPL(tpm_time_expired);
+
+/*
+ * Initialize the LPC bus and enable the TPM ports
+ */
+int tpm_lpc_bus_init(struct pci_dev *pci_dev, u16 base)
+{
+	u32 lpcenable, tmp;
+	int is_lpcm = 0;
+
+	switch (pci_dev->vendor) {
+	case PCI_VENDOR_ID_INTEL:
+		switch (pci_dev->device) {
+		case PCI_DEVICE_ID_INTEL_82801CA_12:
+		case PCI_DEVICE_ID_INTEL_82801DB_12:
+			is_lpcm = 1;
+			break;
+		}
+		/* init ICH (enable LPC) */
+		pci_read_config_dword(pci_dev, PCI_GEN1_DEC, &lpcenable);
+		lpcenable |= 0x20000000;
+		pci_write_config_dword(pci_dev, PCI_GEN1_DEC, lpcenable);
+
+		if (is_lpcm) {
+			pci_read_config_dword(pci_dev, PCI_GEN1_DEC,
+					      &lpcenable);
+			if ((lpcenable & 0x20000000) == 0) {
+				dev_err(&pci_dev->dev,
+					"cannot enable LPC\n");
+				return -ENODEV;
+			}
+		}
+
+		/* initialize TPM registers */
+		pci_read_config_dword(pci_dev, PCI_GEN2_DEC, &tmp);
+
+		if (!is_lpcm)
+			tmp = (tmp & 0xFFFF0000) | (base & 0xFFF0);
+		else
+			tmp =
+			    (tmp & 0xFFFF0000) | (base & 0xFFF0) |
+			    0x00000001;
+
+		pci_write_config_dword(pci_dev, PCI_GEN2_DEC, tmp);
+
+		if (is_lpcm) {
+			pci_read_config_dword(pci_dev, PCI_GEN_PMCON_1,
+					      &tmp);
+			tmp |= 0x00000004;	/* enable CLKRUN */
+			pci_write_config_dword(pci_dev, PCI_GEN_PMCON_1,
+					       tmp);
+		}
+		tpm_write_index(0x0D, 0x55);	/* unlock 4F */
+		tpm_write_index(0x0A, 0x00);	/* int disable */
+		tpm_write_index(0x08, base);	/* base addr lo */
+		tpm_write_index(0x09, (base & 0xFF00) >> 8);	/* base addr hi */
+		tpm_write_index(0x0D, 0xAA);	/* lock 4F */
+		break;
+	case PCI_VENDOR_ID_AMD:
+		/* nothing yet */
+		break;
+	}
+
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_lpc_bus_init);
+
+/*
+ * Internal kernel interface to transmit TPM commands
+ */
+static ssize_t tpm_transmit(struct tpm_chip *chip, const char *buf,
+			    size_t bufsiz)
+{
+	ssize_t len;
+	u32 count;
+	__be32 *native_size;
+
+	native_size = (__force __be32 *) (buf + 2);
+	count = be32_to_cpu(*native_size);
+
+	if (count == 0)
+		return -ENODATA;
+	if (count > bufsiz) {
+		dev_err(&chip->pci_dev->dev,
+			"invalid count value %x %x \n", count, bufsiz);
+		return -E2BIG;
+	}
+
+	down(&chip->tpm_mutex);
+
+	if ((len = chip->vendor->send(chip, (u8 *) buf, count)) < 0) {
+		dev_err(&chip->pci_dev->dev,
+			"tpm_transmit: tpm_send: error %d\n", len);
+		return len;
+	}
+
+	down(&chip->timer_manipulation_mutex);
+	chip->time_expired = 0;
+	init_timer(&chip->device_timer);
+	chip->device_timer.function = tpm_time_expired;
+	chip->device_timer.expires = jiffies + 2 * 60 * HZ;
+	chip->device_timer.data = (unsigned long) &chip->time_expired;
+	add_timer(&chip->device_timer);
+	up(&chip->timer_manipulation_mutex);
+
+	do {
+		u8 status = inb(chip->vendor->base + 1);
+		if ((status & chip->vendor->req_complete_mask) ==
+		    chip->vendor->req_complete_val) {
+			down(&chip->timer_manipulation_mutex);
+			del_singleshot_timer_sync(&chip->device_timer);
+			up(&chip->timer_manipulation_mutex);
+			goto out_recv;
+		}
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(TPM_TIMEOUT);
+		rmb();
+	} while (!chip->time_expired);
+
+
+	chip->vendor->cancel(chip);
+	dev_err(&chip->pci_dev->dev, "Time expired\n");
+	up(&chip->tpm_mutex);
+	return -EIO;
+
+out_recv:
+	len = chip->vendor->recv(chip, (u8 *) buf, bufsiz);
+	if (len < 0)
+		dev_err(&chip->pci_dev->dev,
+			"tpm_transmit: tpm_recv: error %d\n", len);
+	up(&chip->tpm_mutex);
+	return len;
+}
+
+#define TPM_DIGEST_SIZE 20
+#define CAP_PCR_RESULT_SIZE 18
+static u8 cap_pcr[] = {
+	0, 193,			/* TPM_TAG_RQU_COMMAND */
+	0, 0, 0, 22,		/* length */
+	0, 0, 0, 101,		/* TPM_ORD_GetCapability */
+	0, 0, 0, 5,
+	0, 0, 0, 4,
+	0, 0, 1, 1
+};
+
+#define READ_PCR_RESULT_SIZE 30
+static u8 pcrread[] = {
+	0, 193,			/* TPM_TAG_RQU_COMMAND */
+	0, 0, 0, 14,		/* length */
+	0, 0, 0, 21,		/* TPM_ORD_PcrRead */
+	0, 0, 0, 0		/* PCR index */
+};
+
+static ssize_t show_pcrs(struct device *dev, char *buf)
+{
+	u8 data[READ_PCR_RESULT_SIZE];
+	ssize_t len;
+	int i, j, index, num_pcrs;
+	char *str = buf;
+
+	struct tpm_chip *chip =
+	    pci_get_drvdata(container_of(dev, struct pci_dev, dev));
+	if (chip == NULL)
+		return -ENODEV;
+
+	memcpy(data, cap_pcr, sizeof(cap_pcr));
+	if ((len = tpm_transmit(chip, data, sizeof(data)))
+	    < CAP_PCR_RESULT_SIZE)
+		return len;
+
+	num_pcrs = be32_to_cpu(*((__force __be32 *) (data + 14)));
+
+	for (i = 0; i < num_pcrs; i++) {
+		memcpy(data, pcrread, sizeof(pcrread));
+		index = cpu_to_be32(i);
+		memcpy(data + 10, &index, 4);
+		if ((len = tpm_transmit(chip, data, sizeof(data)))
+		    < READ_PCR_RESULT_SIZE)
+			return len;
+		str += sprintf(str, "PCR-%02d: ", i);
+		for (j = 0; j < TPM_DIGEST_SIZE; j++)
+			str += sprintf(str, "%02X ", *(data + 10 + j));
+		str += sprintf(str, "\n");
+	}
+	return str - buf;
+}
+
+static DEVICE_ATTR(pcrs, S_IRUGO, show_pcrs, NULL);
+
+#define  READ_PUBEK_RESULT_SIZE 314
+static u8 readpubek[] = {
+	0, 193,			/* TPM_TAG_RQU_COMMAND */
+	0, 0, 0, 30,		/* length */
+	0, 0, 0, 124,		/* TPM_ORD_ReadPubek */
+};
+
+static ssize_t show_pubek(struct device *dev, char *buf)
+{
+	u8 data[READ_PUBEK_RESULT_SIZE];
+	ssize_t len;
+	__be32 *native_val;
+	int i;
+	char *str = buf;
+
+	struct tpm_chip *chip =
+	    pci_get_drvdata(container_of(dev, struct pci_dev, dev));
+	if (chip == NULL)
+		return -ENODEV;
+
+	memcpy(data, readpubek, sizeof(readpubek));
+	memset(data + sizeof(readpubek), 0, 20);	/* zero nonce */
+
+	if ((len = tpm_transmit(chip, data, sizeof(data))) <
+	    READ_PUBEK_RESULT_SIZE)
+		return len;
+
+	/* 
+	   ignore header 10 bytes
+	   algorithm 32 bits (1 == RSA )
+	   encscheme 16 bits
+	   sigscheme 16 bits
+	   parameters (RSA 12->bytes: keybit, #primes, expbit)  
+	   keylenbytes 32 bits
+	   256 byte modulus
+	   ignore checksum 20 bytes
+	 */
+
+	native_val = (__force __be32 *) (data + 34);
+
+	str +=
+	    sprintf(str,
+		    "Algorithm: %02X %02X %02X %02X\nEncscheme: %02X %02X\n"
+		    "Sigscheme: %02X %02X\nParameters: %02X %02X %02X %02X"
+		    " %02X %02X %02X %02X %02X %02X %02X %02X\n"
+		    "Modulus length: %d\nModulus: \n",
+		    data[10], data[11], data[12], data[13], data[14],
+		    data[15], data[16], data[17], data[22], data[23],
+		    data[24], data[25], data[26], data[27], data[28],
+		    data[29], data[30], data[31], data[32], data[33],
+		    be32_to_cpu(*native_val)
+	    );
+
+	for (i = 0; i < 256; i++) {
+		str += sprintf(str, "%02X ", data[i + 39]);
+		if ((i + 1) % 16 == 0)
+			str += sprintf(str, "\n");
+	}
+	return str - buf;
+}
+
+static DEVICE_ATTR(pubek, S_IRUGO, show_pubek, NULL);
+
+#define CAP_VER_RESULT_SIZE 18
+static u8 cap_version[] = {
+	0, 193,			/* TPM_TAG_RQU_COMMAND */
+	0, 0, 0, 18,		/* length */
+	0, 0, 0, 101,		/* TPM_ORD_GetCapability */
+	0, 0, 0, 6,
+	0, 0, 0, 0
+};
+
+#define CAP_MANUFACTURER_RESULT_SIZE 18
+static u8 cap_manufacturer[] = {
+	0, 193,			/* TPM_TAG_RQU_COMMAND */
+	0, 0, 0, 22,		/* length */
+	0, 0, 0, 101,		/* TPM_ORD_GetCapability */
+	0, 0, 0, 5,
+	0, 0, 0, 4,
+	0, 0, 1, 3
+};
+
+static ssize_t show_caps(struct device *dev, char *buf)
+{
+	u8 data[READ_PUBEK_RESULT_SIZE];
+	ssize_t len;
+	char *str = buf;
+
+	struct tpm_chip *chip =
+	    pci_get_drvdata(container_of(dev, struct pci_dev, dev));
+	if (chip == NULL)
+		return -ENODEV;
+
+	memcpy(data, cap_manufacturer, sizeof(cap_manufacturer));
+
+	if ((len = tpm_transmit(chip, data, sizeof(data))) <
+	    CAP_MANUFACTURER_RESULT_SIZE)
+		return len;
+
+	str += sprintf(str, "Manufacturer: 0x%x\n",
+		       be32_to_cpu(*(data + 14)));
+
+	memcpy(data, cap_version, sizeof(cap_version));
+
+	if ((len = tpm_transmit(chip, data, sizeof(data))) <
+	    CAP_VER_RESULT_SIZE)
+		return len;
+
+	str +=
+	    sprintf(str, "TCG version: %d.%d\nFirmware version: %d.%d\n",
+		    (int) data[14], (int) data[15], (int) data[16],
+		    (int) data[17]);
+
+	return str - buf;
+}
+
+static DEVICE_ATTR(caps, S_IRUGO, show_caps, NULL);
+
+/*
+ * Device file system interface to the TPM
+ */
+int tpm_open(struct inode *inode, struct file *file)
+{
+	int rc = 0, minor = iminor(inode);
+	struct tpm_chip *chip = NULL, *pos;
+
+	spin_lock(&driver_lock);
+
+	list_for_each_entry(pos, &tpm_chip_list, list) {
+		if (pos->vendor->miscdev.minor == minor) {
+			chip = pos;
+			break;
+		}
+	}
+
+	if (chip == NULL) {
+		rc = -ENODEV;
+		goto err_out;
+	}
+
+	if (chip->num_opens) {
+		dev_dbg(&chip->pci_dev->dev,
+			"Another process owns this TPM\n");
+		rc = -EBUSY;
+		goto err_out;
+	}
+
+	chip->num_opens++;
+	pci_dev_get(chip->pci_dev);
+
+	spin_unlock(&driver_lock);
+
+	chip->data_buffer = kmalloc(TPM_BUFSIZE * sizeof(u8), GFP_KERNEL);
+	if (chip->data_buffer == NULL) {
+		chip->num_opens--;
+		pci_dev_put(chip->pci_dev);
+		return -ENOMEM;
+	}
+
+	atomic_set(&chip->data_pending, 0);
+
+	file->private_data = chip;
+	return 0;
+
+err_out:
+	spin_unlock(&driver_lock);
+	return rc;
+}
+
+EXPORT_SYMBOL_GPL(tpm_open);
+
+int tpm_release(struct inode *inode, struct file *file)
+{
+	struct tpm_chip *chip = file->private_data;
+	
+	file->private_data = NULL;
+
+	spin_lock(&driver_lock);
+	chip->num_opens--;
+	spin_unlock(&driver_lock);
+
+	down(&chip->timer_manipulation_mutex);
+	if (timer_pending(&chip->user_read_timer))
+		del_singleshot_timer_sync(&chip->user_read_timer);
+	else if (timer_pending(&chip->device_timer))
+		del_singleshot_timer_sync(&chip->device_timer);
+	up(&chip->timer_manipulation_mutex);
+
+	kfree(chip->data_buffer);
+	atomic_set(&chip->data_pending, 0);
+
+	pci_dev_put(chip->pci_dev);
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_release);
+
+ssize_t tpm_write(struct file * file, const char __user * buf,
+		  size_t size, loff_t * off)
+{
+	struct tpm_chip *chip = file->private_data;
+	int in_size = size, out_size;
+
+	/* cannot perform a write until the read has cleared
+	   either via tpm_read or a user_read_timer timeout */
+	while (atomic_read(&chip->data_pending) != 0) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(TPM_TIMEOUT);
+	}
+
+	down(&chip->buffer_mutex);
+
+	if (in_size > TPM_BUFSIZE)
+		in_size = TPM_BUFSIZE;
+
+	if (copy_from_user
+	    (chip->data_buffer, (void __user *) buf, in_size)) {
+		up(&chip->buffer_mutex);
+		return -EFAULT;
+	}
+
+	/* atomic tpm command send and result receive */
+	out_size = tpm_transmit(chip, chip->data_buffer, TPM_BUFSIZE);
+
+	atomic_set(&chip->data_pending, out_size);
+	up(&chip->buffer_mutex);
+
+	/* Set a timeout by which the reader must come claim the result */
+	down(&chip->timer_manipulation_mutex);
+	init_timer(&chip->user_read_timer);
+	chip->user_read_timer.function = user_reader_timeout;
+	chip->user_read_timer.data = (unsigned long) chip;
+	chip->user_read_timer.expires = jiffies + (60 * HZ);
+	add_timer(&chip->user_read_timer);
+	up(&chip->timer_manipulation_mutex);
+
+	return in_size;
+}
+
+EXPORT_SYMBOL_GPL(tpm_write);
+
+ssize_t tpm_read(struct file * file, char __user * buf,
+		 size_t size, loff_t * off)
+{
+	struct tpm_chip *chip = file->private_data;
+	int ret_size = -ENODATA;
+
+	if (atomic_read(&chip->data_pending) != 0) {	/* Result available */
+		down(&chip->timer_manipulation_mutex);
+		del_singleshot_timer_sync(&chip->user_read_timer);
+		up(&chip->timer_manipulation_mutex);
+
+		down(&chip->buffer_mutex);
+
+		ret_size = atomic_read(&chip->data_pending);
+		atomic_set(&chip->data_pending, 0);
+
+		if (ret_size == 0)	/* timeout just occurred */
+			ret_size = -ETIME;
+		else if (ret_size > 0) {	/* relay data */
+			if (size < ret_size)
+				ret_size = size;
+
+			if (copy_to_user((void __user *) buf,
+					 chip->data_buffer, ret_size)) {
+				ret_size = -EFAULT;
+			}
+		}
+		up(&chip->buffer_mutex);
+	}
+
+	return ret_size;
+}
+
+EXPORT_SYMBOL_GPL(tpm_read);
+
+void __devexit tpm_remove(struct pci_dev *pci_dev)
+{
+	struct tpm_chip *chip = pci_get_drvdata(pci_dev);
+
+	if (chip == NULL) {
+		dev_err(&pci_dev->dev, "No device data found\n");
+		return;
+	}
+
+	spin_lock(&driver_lock);
+
+	list_del(&chip->list);
+
+	spin_unlock(&driver_lock);
+
+	pci_set_drvdata(pci_dev, NULL);
+	misc_deregister(&chip->vendor->miscdev);
+
+	device_remove_file(&pci_dev->dev, &dev_attr_pubek);
+	device_remove_file(&pci_dev->dev, &dev_attr_pcrs);
+	device_remove_file(&pci_dev->dev, &dev_attr_caps);
+
+	pci_disable_device(pci_dev);
+
+	dev_mask[chip->dev_num / 32] &= !(1 << (chip->dev_num % 32));
+
+	kfree(chip);
+
+	pci_dev_put(pci_dev);
+}
+
+EXPORT_SYMBOL_GPL(tpm_remove);
+
+static u8 savestate[] = {
+	0, 193,			/* TPM_TAG_RQU_COMMAND */
+	0, 0, 0, 10,		/* blob length (in bytes) */
+	0, 0, 0, 152		/* TPM_ORD_SaveState */
+};
+
+/*
+ * We are about to suspend. Save the TPM state
+ * so that it can be restored.
+ */
+int tpm_pm_suspend(struct pci_dev *pci_dev, u32 pm_state)
+{
+	struct tpm_chip *chip = pci_get_drvdata(pci_dev);
+	if (chip == NULL)
+		return -ENODEV;
+
+	tpm_transmit(chip, savestate, sizeof(savestate));
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_pm_suspend);
+
+/*
+ * Resume from a power safe. The BIOS already restored
+ * the TPM state.
+ */
+int tpm_pm_resume(struct pci_dev *pci_dev)
+{
+	struct tpm_chip *chip = pci_get_drvdata(pci_dev);
+
+	if (chip == NULL)
+		return -ENODEV;
+
+	spin_lock(&driver_lock);
+	tpm_lpc_bus_init(pci_dev, chip->vendor->base);
+	spin_unlock(&driver_lock);
+
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_pm_resume);
+
+/*
+ * Called from tpm_<specific>.c probe function only for devices 
+ * the driver has determined it should claim.  Prior to calling
+ * this function the specific probe function has called pci_enable_device
+ * upon errant exit from this function specific probe function should call
+ * pci_disable_device
+ */
+int tpm_register_hardware(struct pci_dev *pci_dev,
+			  struct tpm_vendor_specific *entry)
+{
+	char devname[7];
+	struct tpm_chip *chip;
+	int i, j;
+
+	/* Driver specific per-device data */
+	chip = kmalloc(sizeof(*chip), GFP_KERNEL);
+	if (chip == NULL)
+		return -ENOMEM;
+
+	memset(chip, 0, sizeof(struct tpm_chip));
+
+	init_MUTEX(&chip->buffer_mutex);
+	init_MUTEX(&chip->tpm_mutex);
+	init_MUTEX(&chip->timer_manipulation_mutex);
+	INIT_LIST_HEAD(&chip->list);
+
+	chip->vendor = entry;
+
+	chip->dev_num = -1;
+
+	for (i = 0; i < 32; i++)
+		for (j = 0; j < 8; j++)
+			if ((dev_mask[i] & (1 << j)) == 0) {
+				chip->dev_num = i * 32 + j;
+				dev_mask[i] |= 1 << j;
+				goto dev_num_search_complete;
+			}
+
+dev_num_search_complete:
+	if (chip->dev_num < 0) {
+		dev_err(&pci_dev->dev,
+			"No available tpm device numbers\n");
+		kfree(chip);
+		return -ENODEV;
+	} else if (chip->dev_num == 0)
+		chip->vendor->miscdev.minor = TPM_MINOR;
+	else
+		chip->vendor->miscdev.minor = MISC_DYNAMIC_MINOR;
+
+	snprintf(devname, sizeof(devname), "%s%d", "tpm", chip->dev_num);
+	chip->vendor->miscdev.name = devname;
+
+	chip->vendor->miscdev.dev = &(pci_dev->dev);
+	chip->pci_dev = pci_dev_get(pci_dev);
+
+	if (misc_register(&chip->vendor->miscdev)) {
+		dev_err(&chip->pci_dev->dev,
+			"unable to misc_register %s, minor %d\n",
+			chip->vendor->miscdev.name,
+			chip->vendor->miscdev.minor);
+		pci_dev_put(pci_dev);
+		kfree(chip);
+		dev_mask[i] &= !(1 << j);
+		return -ENODEV;
+	}
+
+	pci_set_drvdata(pci_dev, chip);
+
+	list_add(&chip->list, &tpm_chip_list);
+
+	device_create_file(&pci_dev->dev, &dev_attr_pubek);
+	device_create_file(&pci_dev->dev, &dev_attr_pcrs);
+	device_create_file(&pci_dev->dev, &dev_attr_caps);
+
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(tpm_register_hardware);
+
+static int __init init_tpm(void)
+{
+	return 0;
+}
+
+static void __exit cleanup_tpm(void)
+{
+
+}
+
+module_init(init_tpm);
+module_exit(cleanup_tpm);
+
+MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
+MODULE_DESCRIPTION("TPM Driver");
+MODULE_VERSION("2.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
new file mode 100644
index 0000000..575cf5a
--- /dev/null
+++ b/drivers/char/tpm/tpm.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert@watson.ibm.com>
+ * Dave Safford <safford@watson.ibm.com>
+ * Reiner Sailer <sailer@watson.ibm.com>
+ * Kylene Hall <kjhall@us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org	 
+ *
+ * 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, version 2 of the
+ * License.
+ * 
+ */
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+
+#define TPM_TIMEOUT msecs_to_jiffies(5)
+
+/* TPM addresses */
+#define	TPM_ADDR			0x4E
+#define	TPM_DATA			0x4F
+
+struct tpm_chip;
+
+struct tpm_vendor_specific {
+	u8 req_complete_mask;
+	u8 req_complete_val;
+	u16 base;		/* TPM base address */
+
+	int (*recv) (struct tpm_chip *, u8 *, size_t);
+	int (*send) (struct tpm_chip *, u8 *, size_t);
+	void (*cancel) (struct tpm_chip *);
+	struct miscdevice miscdev;
+};
+
+struct tpm_chip {
+	struct pci_dev *pci_dev;	/* PCI device stuff */
+
+	int dev_num;		/* /dev/tpm# */
+	int num_opens;		/* only one allowed */
+	int time_expired;
+
+	/* Data passed to and from the tpm via the read/write calls */
+	u8 *data_buffer;
+	atomic_t data_pending;
+	struct semaphore buffer_mutex;
+
+	struct timer_list user_read_timer;	/* user needs to claim result */
+	struct semaphore tpm_mutex;	/* tpm is processing */
+	struct timer_list device_timer;	/* tpm is processing */
+	struct semaphore timer_manipulation_mutex;
+
+	struct tpm_vendor_specific *vendor;
+
+	struct list_head list;
+};
+
+static inline int tpm_read_index(int index)
+{
+	outb(index, TPM_ADDR);
+	return inb(TPM_DATA) & 0xFF;
+}
+
+static inline void tpm_write_index(int index, int value)
+{
+	outb(index, TPM_ADDR);
+	outb(value & 0xFF, TPM_DATA);
+}
+
+extern void tpm_time_expired(unsigned long);
+extern int tpm_lpc_bus_init(struct pci_dev *, u16);
+
+extern int tpm_register_hardware(struct pci_dev *,
+				 struct tpm_vendor_specific *);
+extern int tpm_open(struct inode *, struct file *);
+extern int tpm_release(struct inode *, struct file *);
+extern ssize_t tpm_write(struct file *, const char __user *, size_t,
+			 loff_t *);
+extern ssize_t tpm_read(struct file *, char __user *, size_t, loff_t *);
+extern void __devexit tpm_remove(struct pci_dev *);
+extern int tpm_pm_suspend(struct pci_dev *, u32);
+extern int tpm_pm_resume(struct pci_dev *);
diff --git a/drivers/char/tpm/tpm_atmel.c b/drivers/char/tpm/tpm_atmel.c
new file mode 100644
index 0000000..f9333e7
--- /dev/null
+++ b/drivers/char/tpm/tpm_atmel.c
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert@watson.ibm.com>
+ * Dave Safford <safford@watson.ibm.com>
+ * Reiner Sailer <sailer@watson.ibm.com>
+ * Kylene Hall <kjhall@us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org	 
+ *
+ * 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, version 2 of the
+ * License.
+ * 
+ */
+
+#include "tpm.h"
+
+/* Atmel definitions */
+#define	TPM_ATML_BASE			0x400
+
+/* write status bits */
+#define	ATML_STATUS_ABORT		0x01
+#define	ATML_STATUS_LASTBYTE		0x04
+
+/* read status bits */
+#define	ATML_STATUS_BUSY		0x01
+#define	ATML_STATUS_DATA_AVAIL		0x02
+#define	ATML_STATUS_REWRITE		0x04
+
+
+static int tpm_atml_recv(struct tpm_chip *chip, u8 * buf, size_t count)
+{
+	u8 status, *hdr = buf;
+	u32 size;
+	int i;
+	__be32 *native_size;
+
+	/* start reading header */
+	if (count < 6)
+		return -EIO;
+
+	for (i = 0; i < 6; i++) {
+		status = inb(chip->vendor->base + 1);
+		if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
+			dev_err(&chip->pci_dev->dev,
+				"error reading header\n");
+			return -EIO;
+		}
+		*buf++ = inb(chip->vendor->base);
+	}
+
+	/* size of the data received */
+	native_size = (__force __be32 *) (hdr + 2);
+	size = be32_to_cpu(*native_size);
+
+	if (count < size) {
+		dev_err(&chip->pci_dev->dev,
+			"Recv size(%d) less than available space\n", size);
+		for (; i < size; i++) {	/* clear the waiting data anyway */
+			status = inb(chip->vendor->base + 1);
+			if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
+				dev_err(&chip->pci_dev->dev,
+					"error reading data\n");
+				return -EIO;
+			}
+		}
+		return -EIO;
+	}
+
+	/* read all the data available */
+	for (; i < size; i++) {
+		status = inb(chip->vendor->base + 1);
+		if ((status & ATML_STATUS_DATA_AVAIL) == 0) {
+			dev_err(&chip->pci_dev->dev,
+				"error reading data\n");
+			return -EIO;
+		}
+		*buf++ = inb(chip->vendor->base);
+	}
+
+	/* make sure data available is gone */
+	status = inb(chip->vendor->base + 1);
+	if (status & ATML_STATUS_DATA_AVAIL) {
+		dev_err(&chip->pci_dev->dev, "data available is stuck\n");
+		return -EIO;
+	}
+
+	return size;
+}
+
+static int tpm_atml_send(struct tpm_chip *chip, u8 * buf, size_t count)
+{
+	int i;
+
+	dev_dbg(&chip->pci_dev->dev, "tpm_atml_send: ");
+	for (i = 0; i < count; i++) {
+		dev_dbg(&chip->pci_dev->dev, "0x%x(%d) ", buf[i], buf[i]);
+		outb(buf[i], chip->vendor->base);
+	}
+
+	return count;
+}
+
+static void tpm_atml_cancel(struct tpm_chip *chip)
+{
+	outb(ATML_STATUS_ABORT, chip->vendor->base + 1);
+}
+
+static struct file_operations atmel_ops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.open = tpm_open,
+	.read = tpm_read,
+	.write = tpm_write,
+	.release = tpm_release,
+};
+
+static struct tpm_vendor_specific tpm_atmel = {
+	.recv = tpm_atml_recv,
+	.send = tpm_atml_send,
+	.cancel = tpm_atml_cancel,
+	.req_complete_mask = ATML_STATUS_BUSY | ATML_STATUS_DATA_AVAIL,
+	.req_complete_val = ATML_STATUS_DATA_AVAIL,
+	.base = TPM_ATML_BASE,
+	.miscdev = { .fops = &atmel_ops, },
+};
+
+static int __devinit tpm_atml_init(struct pci_dev *pci_dev,
+				   const struct pci_device_id *pci_id)
+{
+	u8 version[4];
+	int rc = 0;
+
+	if (pci_enable_device(pci_dev))
+		return -EIO;
+
+	if (tpm_lpc_bus_init(pci_dev, TPM_ATML_BASE)) {
+		rc = -ENODEV;
+		goto out_err;
+	}
+
+	/* verify that it is an Atmel part */
+	if (tpm_read_index(4) != 'A' || tpm_read_index(5) != 'T'
+	    || tpm_read_index(6) != 'M' || tpm_read_index(7) != 'L') {
+		rc = -ENODEV;
+		goto out_err;
+	}
+
+	/* query chip for its version number */
+	if ((version[0] = tpm_read_index(0x00)) != 0xFF) {
+		version[1] = tpm_read_index(0x01);
+		version[2] = tpm_read_index(0x02);
+		version[3] = tpm_read_index(0x03);
+	} else {
+		dev_info(&pci_dev->dev, "version query failed\n");
+		rc = -ENODEV;
+		goto out_err;
+	}
+
+	if ((rc = tpm_register_hardware(pci_dev, &tpm_atmel)) < 0)
+		goto out_err;
+
+	dev_info(&pci_dev->dev,
+		 "Atmel TPM version %d.%d.%d.%d\n", version[0], version[1],
+		 version[2], version[3]);
+
+	return 0;
+out_err:
+	pci_disable_device(pci_dev);
+	return rc;
+}
+
+static struct pci_device_id tpm_pci_tbl[] __devinitdata = {
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0)},
+	{PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_LPC)},
+	{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, tpm_pci_tbl);
+
+static struct pci_driver atmel_pci_driver = {
+	.name = "tpm_atmel",
+	.id_table = tpm_pci_tbl,
+	.probe = tpm_atml_init,
+	.remove = __devexit_p(tpm_remove),
+	.suspend = tpm_pm_suspend,
+	.resume = tpm_pm_resume,
+};
+
+static int __init init_atmel(void)
+{
+	return pci_register_driver(&atmel_pci_driver);
+}
+
+static void __exit cleanup_atmel(void)
+{
+	pci_unregister_driver(&atmel_pci_driver);
+}
+
+module_init(init_atmel);
+module_exit(cleanup_atmel);
+
+MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
+MODULE_DESCRIPTION("TPM Driver");
+MODULE_VERSION("2.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/tpm/tpm_nsc.c b/drivers/char/tpm/tpm_nsc.c
new file mode 100644
index 0000000..9cce833a
--- /dev/null
+++ b/drivers/char/tpm/tpm_nsc.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2004 IBM Corporation
+ *
+ * Authors:
+ * Leendert van Doorn <leendert@watson.ibm.com>
+ * Dave Safford <safford@watson.ibm.com>
+ * Reiner Sailer <sailer@watson.ibm.com>
+ * Kylene Hall <kjhall@us.ibm.com>
+ *
+ * Maintained by: <tpmdd_devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org	 
+ *
+ * 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, version 2 of the
+ * License.
+ * 
+ */
+
+#include "tpm.h"
+
+/* National definitions */
+#define	TPM_NSC_BASE			0x360
+#define	TPM_NSC_IRQ			0x07
+
+#define	NSC_LDN_INDEX			0x07
+#define	NSC_SID_INDEX			0x20
+#define	NSC_LDC_INDEX			0x30
+#define	NSC_DIO_INDEX			0x60
+#define	NSC_CIO_INDEX			0x62
+#define	NSC_IRQ_INDEX			0x70
+#define	NSC_ITS_INDEX			0x71
+
+#define	NSC_STATUS			0x01
+#define	NSC_COMMAND			0x01
+#define	NSC_DATA			0x00
+
+/* status bits */
+#define	NSC_STATUS_OBF			0x01	/* output buffer full */
+#define	NSC_STATUS_IBF			0x02	/* input buffer full */
+#define	NSC_STATUS_F0			0x04	/* F0 */
+#define	NSC_STATUS_A2			0x08	/* A2 */
+#define	NSC_STATUS_RDY			0x10	/* ready to receive command */
+#define	NSC_STATUS_IBR			0x20	/* ready to receive data */
+
+/* command bits */
+#define	NSC_COMMAND_NORMAL		0x01	/* normal mode */
+#define	NSC_COMMAND_EOC			0x03
+#define	NSC_COMMAND_CANCEL		0x22
+
+/*
+ * Wait for a certain status to appear
+ */
+static int wait_for_stat(struct tpm_chip *chip, u8 mask, u8 val, u8 * data)
+{
+	int expired = 0;
+	struct timer_list status_timer =
+	    TIMER_INITIALIZER(tpm_time_expired, jiffies + 10 * HZ,
+			      (unsigned long) &expired);
+
+	/* status immediately available check */
+	*data = inb(chip->vendor->base + NSC_STATUS);
+	if ((*data & mask) == val)
+		return 0;
+
+	/* wait for status */
+	add_timer(&status_timer);
+	do {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(TPM_TIMEOUT);
+		*data = inb(chip->vendor->base + 1);
+		if ((*data & mask) == val) {
+			del_singleshot_timer_sync(&status_timer);
+			return 0;
+		}
+	}
+	while (!expired);
+
+	return -EBUSY;
+}
+
+static int nsc_wait_for_ready(struct tpm_chip *chip)
+{
+	int status;
+	int expired = 0;
+	struct timer_list status_timer =
+	    TIMER_INITIALIZER(tpm_time_expired, jiffies + 100,
+			      (unsigned long) &expired);
+
+	/* status immediately available check */
+	status = inb(chip->vendor->base + NSC_STATUS);
+	if (status & NSC_STATUS_OBF)
+		status = inb(chip->vendor->base + NSC_DATA);
+	if (status & NSC_STATUS_RDY)
+		return 0;
+
+	/* wait for status */
+	add_timer(&status_timer);
+	do {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(TPM_TIMEOUT);
+		status = inb(chip->vendor->base + NSC_STATUS);
+		if (status & NSC_STATUS_OBF)
+			status = inb(chip->vendor->base + NSC_DATA);
+		if (status & NSC_STATUS_RDY) {
+			del_singleshot_timer_sync(&status_timer);
+			return 0;
+		}
+	}
+	while (!expired);
+
+	dev_info(&chip->pci_dev->dev, "wait for ready failed\n");
+	return -EBUSY;
+}
+
+
+static int tpm_nsc_recv(struct tpm_chip *chip, u8 * buf, size_t count)
+{
+	u8 *buffer = buf;
+	u8 data, *p;
+	u32 size;
+	__be32 *native_size;
+
+	if (count < 6)
+		return -EIO;
+
+	if (wait_for_stat(chip, NSC_STATUS_F0, NSC_STATUS_F0, &data) < 0) {
+		dev_err(&chip->pci_dev->dev, "F0 timeout\n");
+		return -EIO;
+	}
+	if ((data =
+	     inb(chip->vendor->base + NSC_DATA)) != NSC_COMMAND_NORMAL) {
+		dev_err(&chip->pci_dev->dev, "not in normal mode (0x%x)\n",
+			data);
+		return -EIO;
+	}
+
+	/* read the whole packet */
+	for (p = buffer; p < &buffer[count]; p++) {
+		if (wait_for_stat
+		    (chip, NSC_STATUS_OBF, NSC_STATUS_OBF, &data) < 0) {
+			dev_err(&chip->pci_dev->dev,
+				"OBF timeout (while reading data)\n");
+			return -EIO;
+		}
+		if (data & NSC_STATUS_F0)
+			break;
+		*p = inb(chip->vendor->base + NSC_DATA);
+	}
+
+	if ((data & NSC_STATUS_F0) == 0) {
+		dev_err(&chip->pci_dev->dev, "F0 not set\n");
+		return -EIO;
+	}
+	if ((data = inb(chip->vendor->base + NSC_DATA)) != NSC_COMMAND_EOC) {
+		dev_err(&chip->pci_dev->dev,
+			"expected end of command(0x%x)\n", data);
+		return -EIO;
+	}
+
+	native_size = (__force __be32 *) (buf + 2);
+	size = be32_to_cpu(*native_size);
+
+	if (count < size)
+		return -EIO;
+
+	return size;
+}
+
+static int tpm_nsc_send(struct tpm_chip *chip, u8 * buf, size_t count)
+{
+	u8 data;
+	int i;
+
+	/*
+	 * If we hit the chip with back to back commands it locks up
+	 * and never set IBF. Hitting it with this "hammer" seems to
+	 * fix it. Not sure why this is needed, we followed the flow
+	 * chart in the manual to the letter.
+	 */
+	outb(NSC_COMMAND_CANCEL, chip->vendor->base + NSC_COMMAND);
+
+	if (nsc_wait_for_ready(chip) != 0)
+		return -EIO;
+
+	if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
+		dev_err(&chip->pci_dev->dev, "IBF timeout\n");
+		return -EIO;
+	}
+
+	outb(NSC_COMMAND_NORMAL, chip->vendor->base + NSC_COMMAND);
+	if (wait_for_stat(chip, NSC_STATUS_IBR, NSC_STATUS_IBR, &data) < 0) {
+		dev_err(&chip->pci_dev->dev, "IBR timeout\n");
+		return -EIO;
+	}
+
+	for (i = 0; i < count; i++) {
+		if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
+			dev_err(&chip->pci_dev->dev,
+				"IBF timeout (while writing data)\n");
+			return -EIO;
+		}
+		outb(buf[i], chip->vendor->base + NSC_DATA);
+	}
+
+	if (wait_for_stat(chip, NSC_STATUS_IBF, 0, &data) < 0) {
+		dev_err(&chip->pci_dev->dev, "IBF timeout\n");
+		return -EIO;
+	}
+	outb(NSC_COMMAND_EOC, chip->vendor->base + NSC_COMMAND);
+
+	return count;
+}
+
+static void tpm_nsc_cancel(struct tpm_chip *chip)
+{
+	outb(NSC_COMMAND_CANCEL, chip->vendor->base + NSC_COMMAND);
+}
+
+static struct file_operations nsc_ops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.open = tpm_open,
+	.read = tpm_read,
+	.write = tpm_write,
+	.release = tpm_release,
+};
+
+static struct tpm_vendor_specific tpm_nsc = {
+	.recv = tpm_nsc_recv,
+	.send = tpm_nsc_send,
+	.cancel = tpm_nsc_cancel,
+	.req_complete_mask = NSC_STATUS_OBF,
+	.req_complete_val = NSC_STATUS_OBF,
+	.base = TPM_NSC_BASE,
+	.miscdev = { .fops = &nsc_ops, },
+	
+};
+
+static int __devinit tpm_nsc_init(struct pci_dev *pci_dev,
+				  const struct pci_device_id *pci_id)
+{
+	int rc = 0;
+
+	if (pci_enable_device(pci_dev))
+		return -EIO;
+
+	if (tpm_lpc_bus_init(pci_dev, TPM_NSC_BASE)) {
+		rc = -ENODEV;
+		goto out_err;
+	}
+
+	/* verify that it is a National part (SID) */
+	if (tpm_read_index(NSC_SID_INDEX) != 0xEF) {
+		rc = -ENODEV;
+		goto out_err;
+	}
+
+	dev_dbg(&pci_dev->dev, "NSC TPM detected\n");
+	dev_dbg(&pci_dev->dev,
+		"NSC LDN 0x%x, SID 0x%x, SRID 0x%x\n",
+		tpm_read_index(0x07), tpm_read_index(0x20),
+		tpm_read_index(0x27));
+	dev_dbg(&pci_dev->dev,
+		"NSC SIOCF1 0x%x SIOCF5 0x%x SIOCF6 0x%x SIOCF8 0x%x\n",
+		tpm_read_index(0x21), tpm_read_index(0x25),
+		tpm_read_index(0x26), tpm_read_index(0x28));
+	dev_dbg(&pci_dev->dev, "NSC IO Base0 0x%x\n",
+		(tpm_read_index(0x60) << 8) | tpm_read_index(0x61));
+	dev_dbg(&pci_dev->dev, "NSC IO Base1 0x%x\n",
+		(tpm_read_index(0x62) << 8) | tpm_read_index(0x63));
+	dev_dbg(&pci_dev->dev, "NSC Interrupt number and wakeup 0x%x\n",
+		tpm_read_index(0x70));
+	dev_dbg(&pci_dev->dev, "NSC IRQ type select 0x%x\n",
+		tpm_read_index(0x71));
+	dev_dbg(&pci_dev->dev,
+		"NSC DMA channel select0 0x%x, select1 0x%x\n",
+		tpm_read_index(0x74), tpm_read_index(0x75));
+	dev_dbg(&pci_dev->dev,
+		"NSC Config "
+		"0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",
+		tpm_read_index(0xF0), tpm_read_index(0xF1),
+		tpm_read_index(0xF2), tpm_read_index(0xF3),
+		tpm_read_index(0xF4), tpm_read_index(0xF5),
+		tpm_read_index(0xF6), tpm_read_index(0xF7),
+		tpm_read_index(0xF8), tpm_read_index(0xF9));
+
+	dev_info(&pci_dev->dev,
+		 "NSC PC21100 TPM revision %d\n",
+		 tpm_read_index(0x27) & 0x1F);
+
+	if (tpm_read_index(NSC_LDC_INDEX) == 0)
+		dev_info(&pci_dev->dev, ": NSC TPM not active\n");
+
+	/* select PM channel 1 */
+	tpm_write_index(NSC_LDN_INDEX, 0x12);
+	tpm_read_index(NSC_LDN_INDEX);
+
+	/* disable the DPM module */
+	tpm_write_index(NSC_LDC_INDEX, 0);
+	tpm_read_index(NSC_LDC_INDEX);
+
+	/* set the data register base addresses */
+	tpm_write_index(NSC_DIO_INDEX, TPM_NSC_BASE >> 8);
+	tpm_write_index(NSC_DIO_INDEX + 1, TPM_NSC_BASE);
+	tpm_read_index(NSC_DIO_INDEX);
+	tpm_read_index(NSC_DIO_INDEX + 1);
+
+	/* set the command register base addresses */
+	tpm_write_index(NSC_CIO_INDEX, (TPM_NSC_BASE + 1) >> 8);
+	tpm_write_index(NSC_CIO_INDEX + 1, (TPM_NSC_BASE + 1));
+	tpm_read_index(NSC_DIO_INDEX);
+	tpm_read_index(NSC_DIO_INDEX + 1);
+
+	/* set the interrupt number to be used for the host interface */
+	tpm_write_index(NSC_IRQ_INDEX, TPM_NSC_IRQ);
+	tpm_write_index(NSC_ITS_INDEX, 0x00);
+	tpm_read_index(NSC_IRQ_INDEX);
+
+	/* enable the DPM module */
+	tpm_write_index(NSC_LDC_INDEX, 0x01);
+	tpm_read_index(NSC_LDC_INDEX);
+
+	if ((rc = tpm_register_hardware(pci_dev, &tpm_nsc)) < 0)
+		goto out_err;
+
+	return 0;
+
+out_err:
+	pci_disable_device(pci_dev);
+	return rc;
+}
+
+static struct pci_device_id tpm_pci_tbl[] __devinitdata = {
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12)},
+	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0)},
+	{PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_LPC)},
+	{0,}
+};
+
+MODULE_DEVICE_TABLE(pci, tpm_pci_tbl);
+
+static struct pci_driver nsc_pci_driver = {
+	.name = "tpm_nsc",
+	.id_table = tpm_pci_tbl,
+	.probe = tpm_nsc_init,
+	.remove = __devexit_p(tpm_remove),
+	.suspend = tpm_pm_suspend,
+	.resume = tpm_pm_resume,
+};
+
+static int __init init_nsc(void)
+{
+	return pci_register_driver(&nsc_pci_driver);
+}
+
+static void __exit cleanup_nsc(void)
+{
+	pci_unregister_driver(&nsc_pci_driver);
+}
+
+module_init(init_nsc);
+module_exit(cleanup_nsc);
+
+MODULE_AUTHOR("Leendert van Doorn (leendert@watson.ibm.com)");
+MODULE_DESCRIPTION("TPM Driver");
+MODULE_VERSION("2.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/tty_io.c b/drivers/char/tty_io.c
new file mode 100644
index 0000000..06e5a3f
--- /dev/null
+++ b/drivers/char/tty_io.c
@@ -0,0 +1,2980 @@
+/*
+ *  linux/drivers/char/tty_io.c
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ */
+
+/*
+ * 'tty_io.c' gives an orthogonal feeling to tty's, be they consoles
+ * or rs-channels. It also implements echoing, cooked mode etc.
+ *
+ * Kill-line thanks to John T Kohl, who also corrected VMIN = VTIME = 0.
+ *
+ * Modified by Theodore Ts'o, 9/14/92, to dynamically allocate the
+ * tty_struct and tty_queue structures.  Previously there was an array
+ * of 256 tty_struct's which was statically allocated, and the
+ * tty_queue structures were allocated at boot time.  Both are now
+ * dynamically allocated only when the tty is open.
+ *
+ * Also restructured routines so that there is more of a separation
+ * between the high-level tty routines (tty_io.c and tty_ioctl.c) and
+ * the low-level tty routines (serial.c, pty.c, console.c).  This
+ * makes for cleaner and more compact code.  -TYT, 9/17/92 
+ *
+ * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines
+ * which can be dynamically activated and de-activated by the line
+ * discipline handling modules (like SLIP).
+ *
+ * NOTE: pay no attention to the line discipline code (yet); its
+ * interface is still subject to change in this version...
+ * -- TYT, 1/31/92
+ *
+ * Added functionality to the OPOST tty handling.  No delays, but all
+ * other bits should be there.
+ *	-- Nick Holloway <alfie@dcs.warwick.ac.uk>, 27th May 1993.
+ *
+ * Rewrote canonical mode and added more termios flags.
+ * 	-- julian@uhunix.uhcc.hawaii.edu (J. Cowley), 13Jan94
+ *
+ * Reorganized FASYNC support so mouse code can share it.
+ *	-- ctm@ardi.com, 9Sep95
+ *
+ * New TIOCLINUX variants added.
+ *	-- mj@k332.feld.cvut.cz, 19-Nov-95
+ * 
+ * Restrict vt switching via ioctl()
+ *      -- grif@cs.ucr.edu, 5-Dec-95
+ *
+ * Move console and virtual terminal code to more appropriate files,
+ * implement CONFIG_VT and generalize console device interface.
+ *	-- Marko Kohtala <Marko.Kohtala@hut.fi>, March 97
+ *
+ * Rewrote init_dev and release_dev to eliminate races.
+ *	-- Bill Hawes <whawes@star.net>, June 97
+ *
+ * Added devfs support.
+ *      -- C. Scott Ananian <cananian@alumni.princeton.edu>, 13-Jan-1998
+ *
+ * Added support for a Unix98-style ptmx device.
+ *      -- C. Scott Ananian <cananian@alumni.princeton.edu>, 14-Jan-1998
+ *
+ * Reduced memory usage for older ARM systems
+ *      -- Russell King <rmk@arm.linux.org.uk>
+ *
+ * Move do_SAK() into process context.  Less stack use in devfs functions.
+ * alloc_tty_struct() always uses kmalloc() -- Andrew Morton <andrewm@uow.edu.eu> 17Mar01
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/signal.h>
+#include <linux/fcntl.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+#include <linux/devpts_fs.h>
+#include <linux/file.h>
+#include <linux/console.h>
+#include <linux/timer.h>
+#include <linux/ctype.h>
+#include <linux/kd.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+#include <linux/idr.h>
+#include <linux/wait.h>
+#include <linux/bitops.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/vt_kern.h>
+#include <linux/selection.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <linux/kmod.h>
+
+#undef TTY_DEBUG_HANGUP
+
+#define TTY_PARANOIA_CHECK 1
+#define CHECK_TTY_COUNT 1
+
+struct termios tty_std_termios = {	/* for the benefit of tty drivers  */
+	.c_iflag = ICRNL | IXON,
+	.c_oflag = OPOST | ONLCR,
+	.c_cflag = B38400 | CS8 | CREAD | HUPCL,
+	.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
+		   ECHOCTL | ECHOKE | IEXTEN,
+	.c_cc = INIT_C_CC
+};
+
+EXPORT_SYMBOL(tty_std_termios);
+
+/* This list gets poked at by procfs and various bits of boot up code. This
+   could do with some rationalisation such as pulling the tty proc function
+   into this file */
+   
+LIST_HEAD(tty_drivers);			/* linked list of tty drivers */
+
+/* Semaphore to protect creating and releasing a tty. This is shared with
+   vt.c for deeply disgusting hack reasons */
+DECLARE_MUTEX(tty_sem);
+
+#ifdef CONFIG_UNIX98_PTYS
+extern struct tty_driver *ptm_driver;	/* Unix98 pty masters; for /dev/ptmx */
+extern int pty_limit;		/* Config limit on Unix98 ptys */
+static DEFINE_IDR(allocated_ptys);
+static DECLARE_MUTEX(allocated_ptys_lock);
+static int ptmx_open(struct inode *, struct file *);
+#endif
+
+extern void disable_early_printk(void);
+
+static void initialize_tty_struct(struct tty_struct *tty);
+
+static ssize_t tty_read(struct file *, char __user *, size_t, loff_t *);
+static ssize_t tty_write(struct file *, const char __user *, size_t, loff_t *);
+ssize_t redirected_tty_write(struct file *, const char __user *, size_t, loff_t *);
+static unsigned int tty_poll(struct file *, poll_table *);
+static int tty_open(struct inode *, struct file *);
+static int tty_release(struct inode *, struct file *);
+int tty_ioctl(struct inode * inode, struct file * file,
+	      unsigned int cmd, unsigned long arg);
+static int tty_fasync(int fd, struct file * filp, int on);
+extern void rs_360_init(void);
+static void release_mem(struct tty_struct *tty, int idx);
+
+
+static struct tty_struct *alloc_tty_struct(void)
+{
+	struct tty_struct *tty;
+
+	tty = kmalloc(sizeof(struct tty_struct), GFP_KERNEL);
+	if (tty)
+		memset(tty, 0, sizeof(struct tty_struct));
+	return tty;
+}
+
+static inline void free_tty_struct(struct tty_struct *tty)
+{
+	kfree(tty->write_buf);
+	kfree(tty);
+}
+
+#define TTY_NUMBER(tty) ((tty)->index + (tty)->driver->name_base)
+
+char *tty_name(struct tty_struct *tty, char *buf)
+{
+	if (!tty) /* Hmm.  NULL pointer.  That's fun. */
+		strcpy(buf, "NULL tty");
+	else
+		strcpy(buf, tty->name);
+	return buf;
+}
+
+EXPORT_SYMBOL(tty_name);
+
+inline int tty_paranoia_check(struct tty_struct *tty, struct inode *inode,
+			      const char *routine)
+{
+#ifdef TTY_PARANOIA_CHECK
+	if (!tty) {
+		printk(KERN_WARNING
+			"null TTY for (%d:%d) in %s\n",
+			imajor(inode), iminor(inode), routine);
+		return 1;
+	}
+	if (tty->magic != TTY_MAGIC) {
+		printk(KERN_WARNING
+			"bad magic number for tty struct (%d:%d) in %s\n",
+			imajor(inode), iminor(inode), routine);
+		return 1;
+	}
+#endif
+	return 0;
+}
+
+static int check_tty_count(struct tty_struct *tty, const char *routine)
+{
+#ifdef CHECK_TTY_COUNT
+	struct list_head *p;
+	int count = 0;
+	
+	file_list_lock();
+	list_for_each(p, &tty->tty_files) {
+		count++;
+	}
+	file_list_unlock();
+	if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+	    tty->driver->subtype == PTY_TYPE_SLAVE &&
+	    tty->link && tty->link->count)
+		count++;
+	if (tty->count != count) {
+		printk(KERN_WARNING "Warning: dev (%s) tty->count(%d) "
+				    "!= #fd's(%d) in %s\n",
+		       tty->name, tty->count, count, routine);
+		return count;
+       }	
+#endif
+	return 0;
+}
+
+/*
+ *	This is probably overkill for real world processors but
+ *	they are not on hot paths so a little discipline won't do 
+ *	any harm.
+ */
+ 
+static void tty_set_termios_ldisc(struct tty_struct *tty, int num)
+{
+	down(&tty->termios_sem);
+	tty->termios->c_line = num;
+	up(&tty->termios_sem);
+}
+
+/*
+ *	This guards the refcounted line discipline lists. The lock
+ *	must be taken with irqs off because there are hangup path
+ *	callers who will do ldisc lookups and cannot sleep.
+ */
+ 
+static DEFINE_SPINLOCK(tty_ldisc_lock);
+static DECLARE_WAIT_QUEUE_HEAD(tty_ldisc_wait);
+static struct tty_ldisc tty_ldiscs[NR_LDISCS];	/* line disc dispatch table	*/
+
+int tty_register_ldisc(int disc, struct tty_ldisc *new_ldisc)
+{
+	unsigned long flags;
+	int ret = 0;
+	
+	if (disc < N_TTY || disc >= NR_LDISCS)
+		return -EINVAL;
+	
+	spin_lock_irqsave(&tty_ldisc_lock, flags);
+	if (new_ldisc) {
+		tty_ldiscs[disc] = *new_ldisc;
+		tty_ldiscs[disc].num = disc;
+		tty_ldiscs[disc].flags |= LDISC_FLAG_DEFINED;
+		tty_ldiscs[disc].refcount = 0;
+	} else {
+		if(tty_ldiscs[disc].refcount)
+			ret = -EBUSY;
+		else
+			tty_ldiscs[disc].flags &= ~LDISC_FLAG_DEFINED;
+	}
+	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+	
+	return ret;
+}
+
+EXPORT_SYMBOL(tty_register_ldisc);
+
+struct tty_ldisc *tty_ldisc_get(int disc)
+{
+	unsigned long flags;
+	struct tty_ldisc *ld;
+
+	if (disc < N_TTY || disc >= NR_LDISCS)
+		return NULL;
+	
+	spin_lock_irqsave(&tty_ldisc_lock, flags);
+
+	ld = &tty_ldiscs[disc];
+	/* Check the entry is defined */
+	if(ld->flags & LDISC_FLAG_DEFINED)
+	{
+		/* If the module is being unloaded we can't use it */
+		if (!try_module_get(ld->owner))
+		       	ld = NULL;
+		else /* lock it */
+			ld->refcount++;
+	}
+	else
+		ld = NULL;
+	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+	return ld;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_get);
+
+void tty_ldisc_put(int disc)
+{
+	struct tty_ldisc *ld;
+	unsigned long flags;
+	
+	if (disc < N_TTY || disc >= NR_LDISCS)
+		BUG();
+		
+	spin_lock_irqsave(&tty_ldisc_lock, flags);
+	ld = &tty_ldiscs[disc];
+	if(ld->refcount == 0)
+		BUG();
+	ld->refcount --;
+	module_put(ld->owner);
+	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+}
+	
+EXPORT_SYMBOL_GPL(tty_ldisc_put);
+
+static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld)
+{
+	tty->ldisc = *ld;
+	tty->ldisc.refcount = 0;
+}
+
+/**
+ *	tty_ldisc_try		-	internal helper
+ *	@tty: the tty
+ *
+ *	Make a single attempt to grab and bump the refcount on
+ *	the tty ldisc. Return 0 on failure or 1 on success. This is
+ *	used to implement both the waiting and non waiting versions
+ *	of tty_ldisc_ref
+ */
+
+static int tty_ldisc_try(struct tty_struct *tty)
+{
+	unsigned long flags;
+	struct tty_ldisc *ld;
+	int ret = 0;
+	
+	spin_lock_irqsave(&tty_ldisc_lock, flags);
+	ld = &tty->ldisc;
+	if(test_bit(TTY_LDISC, &tty->flags))
+	{
+		ld->refcount++;
+		ret = 1;
+	}
+	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+	return ret;
+}
+
+/**
+ *	tty_ldisc_ref_wait	-	wait for the tty ldisc
+ *	@tty: tty device
+ *
+ *	Dereference the line discipline for the terminal and take a 
+ *	reference to it. If the line discipline is in flux then 
+ *	wait patiently until it changes.
+ *
+ *	Note: Must not be called from an IRQ/timer context. The caller
+ *	must also be careful not to hold other locks that will deadlock
+ *	against a discipline change, such as an existing ldisc reference
+ *	(which we check for)
+ */
+ 
+struct tty_ldisc *tty_ldisc_ref_wait(struct tty_struct *tty)
+{
+	/* wait_event is a macro */
+	wait_event(tty_ldisc_wait, tty_ldisc_try(tty));
+	if(tty->ldisc.refcount == 0)
+		printk(KERN_ERR "tty_ldisc_ref_wait\n");
+	return &tty->ldisc;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_ref_wait);
+
+/**
+ *	tty_ldisc_ref		-	get the tty ldisc
+ *	@tty: tty device
+ *
+ *	Dereference the line discipline for the terminal and take a 
+ *	reference to it. If the line discipline is in flux then 
+ *	return NULL. Can be called from IRQ and timer functions.
+ */
+ 
+struct tty_ldisc *tty_ldisc_ref(struct tty_struct *tty)
+{
+	if(tty_ldisc_try(tty))
+		return &tty->ldisc;
+	return NULL;
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_ref);
+
+/**
+ *	tty_ldisc_deref		-	free a tty ldisc reference
+ *	@ld: reference to free up
+ *
+ *	Undoes the effect of tty_ldisc_ref or tty_ldisc_ref_wait. May
+ *	be called in IRQ context.
+ */
+ 
+void tty_ldisc_deref(struct tty_ldisc *ld)
+{
+	unsigned long flags;
+
+	if(ld == NULL)
+		BUG();
+		
+	spin_lock_irqsave(&tty_ldisc_lock, flags);
+	if(ld->refcount == 0)
+		printk(KERN_ERR "tty_ldisc_deref: no references.\n");
+	else
+		ld->refcount--;
+	if(ld->refcount == 0)
+		wake_up(&tty_ldisc_wait);
+	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_deref);
+
+/**
+ *	tty_ldisc_enable	-	allow ldisc use
+ *	@tty: terminal to activate ldisc on
+ *
+ *	Set the TTY_LDISC flag when the line discipline can be called
+ *	again. Do neccessary wakeups for existing sleepers.
+ *
+ *	Note: nobody should set this bit except via this function. Clearing
+ *	directly is allowed.
+ */
+
+static void tty_ldisc_enable(struct tty_struct *tty)
+{
+	set_bit(TTY_LDISC, &tty->flags);
+	wake_up(&tty_ldisc_wait);
+}
+	
+/**
+ *	tty_set_ldisc		-	set line discipline
+ *	@tty: the terminal to set
+ *	@ldisc: the line discipline
+ *
+ *	Set the discipline of a tty line. Must be called from a process
+ *	context.
+ */
+ 
+static int tty_set_ldisc(struct tty_struct *tty, int ldisc)
+{
+	int	retval = 0;
+	struct	tty_ldisc o_ldisc;
+	char buf[64];
+	int work;
+	unsigned long flags;
+	struct tty_ldisc *ld;
+
+	if ((ldisc < N_TTY) || (ldisc >= NR_LDISCS))
+		return -EINVAL;
+
+restart:
+
+	if (tty->ldisc.num == ldisc)
+		return 0;	/* We are already in the desired discipline */
+	
+	ld = tty_ldisc_get(ldisc);
+	/* Eduardo Blanco <ejbs@cs.cs.com.uy> */
+	/* Cyrus Durgin <cider@speakeasy.org> */
+	if (ld == NULL) {
+		request_module("tty-ldisc-%d", ldisc);
+		ld = tty_ldisc_get(ldisc);
+	}
+	if (ld == NULL)
+		return -EINVAL;
+
+	o_ldisc = tty->ldisc;
+
+	tty_wait_until_sent(tty, 0);
+
+	/*
+	 *	Make sure we don't change while someone holds a
+	 *	reference to the line discipline. The TTY_LDISC bit
+	 *	prevents anyone taking a reference once it is clear.
+	 *	We need the lock to avoid racing reference takers.
+	 */
+	 
+	spin_lock_irqsave(&tty_ldisc_lock, flags);
+	if(tty->ldisc.refcount)
+	{
+		/* Free the new ldisc we grabbed. Must drop the lock
+		   first. */
+		spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+		tty_ldisc_put(ldisc);
+		/*
+		 * There are several reasons we may be busy, including
+		 * random momentary I/O traffic. We must therefore
+		 * retry. We could distinguish between blocking ops
+		 * and retries if we made tty_ldisc_wait() smarter. That
+		 * is up for discussion.
+		 */
+		if(wait_event_interruptible(tty_ldisc_wait, tty->ldisc.refcount == 0) < 0)
+			return -ERESTARTSYS;			
+		goto restart;
+	}
+	clear_bit(TTY_LDISC, &tty->flags);	
+	clear_bit(TTY_DONT_FLIP, &tty->flags);
+	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+	
+	/*
+	 *	From this point on we know nobody has an ldisc
+	 *	usage reference, nor can they obtain one until
+	 *	we say so later on.
+	 */
+	 
+	work = cancel_delayed_work(&tty->flip.work);
+	/*
+	 * Wait for ->hangup_work and ->flip.work handlers to terminate
+	 */
+	 
+	flush_scheduled_work();
+	/* Shutdown the current discipline. */
+	if (tty->ldisc.close)
+		(tty->ldisc.close)(tty);
+
+	/* Now set up the new line discipline. */
+	tty_ldisc_assign(tty, ld);
+	tty_set_termios_ldisc(tty, ldisc);
+	if (tty->ldisc.open)
+		retval = (tty->ldisc.open)(tty);
+	if (retval < 0) {
+		tty_ldisc_put(ldisc);
+		/* There is an outstanding reference here so this is safe */
+		tty_ldisc_assign(tty, tty_ldisc_get(o_ldisc.num));
+		tty_set_termios_ldisc(tty, tty->ldisc.num);
+		if (tty->ldisc.open && (tty->ldisc.open(tty) < 0)) {
+			tty_ldisc_put(o_ldisc.num);
+			/* This driver is always present */
+			tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+			tty_set_termios_ldisc(tty, N_TTY);
+			if (tty->ldisc.open) {
+				int r = tty->ldisc.open(tty);
+
+				if (r < 0)
+					panic("Couldn't open N_TTY ldisc for "
+					      "%s --- error %d.",
+					      tty_name(tty, buf), r);
+			}
+		}
+	}
+	/* At this point we hold a reference to the new ldisc and a
+	   a reference to the old ldisc. If we ended up flipping back
+	   to the existing ldisc we have two references to it */
+	
+	if (tty->ldisc.num != o_ldisc.num && tty->driver->set_ldisc)
+		tty->driver->set_ldisc(tty);
+		
+	tty_ldisc_put(o_ldisc.num);
+	
+	/*
+	 *	Allow ldisc referencing to occur as soon as the driver
+	 *	ldisc callback completes.
+	 */
+	 
+	tty_ldisc_enable(tty);
+	
+	/* Restart it in case no characters kick it off. Safe if
+	   already running */
+	if(work)
+		schedule_delayed_work(&tty->flip.work, 1);
+	return retval;
+}
+
+/*
+ * This routine returns a tty driver structure, given a device number
+ */
+static struct tty_driver *get_tty_driver(dev_t device, int *index)
+{
+	struct tty_driver *p;
+
+	list_for_each_entry(p, &tty_drivers, tty_drivers) {
+		dev_t base = MKDEV(p->major, p->minor_start);
+		if (device < base || device >= base + p->num)
+			continue;
+		*index = device - base;
+		return p;
+	}
+	return NULL;
+}
+
+/*
+ * If we try to write to, or set the state of, a terminal and we're
+ * not in the foreground, send a SIGTTOU.  If the signal is blocked or
+ * ignored, go ahead and perform the operation.  (POSIX 7.2)
+ */
+int tty_check_change(struct tty_struct * tty)
+{
+	if (current->signal->tty != tty)
+		return 0;
+	if (tty->pgrp <= 0) {
+		printk(KERN_WARNING "tty_check_change: tty->pgrp <= 0!\n");
+		return 0;
+	}
+	if (process_group(current) == tty->pgrp)
+		return 0;
+	if (is_ignored(SIGTTOU))
+		return 0;
+	if (is_orphaned_pgrp(process_group(current)))
+		return -EIO;
+	(void) kill_pg(process_group(current), SIGTTOU, 1);
+	return -ERESTARTSYS;
+}
+
+EXPORT_SYMBOL(tty_check_change);
+
+static ssize_t hung_up_tty_read(struct file * file, char __user * buf,
+				size_t count, loff_t *ppos)
+{
+	return 0;
+}
+
+static ssize_t hung_up_tty_write(struct file * file, const char __user * buf,
+				 size_t count, loff_t *ppos)
+{
+	return -EIO;
+}
+
+/* No kernel lock held - none needed ;) */
+static unsigned int hung_up_tty_poll(struct file * filp, poll_table * wait)
+{
+	return POLLIN | POLLOUT | POLLERR | POLLHUP | POLLRDNORM | POLLWRNORM;
+}
+
+static int hung_up_tty_ioctl(struct inode * inode, struct file * file,
+			     unsigned int cmd, unsigned long arg)
+{
+	return cmd == TIOCSPGRP ? -ENOTTY : -EIO;
+}
+
+static struct file_operations tty_fops = {
+	.llseek		= no_llseek,
+	.read		= tty_read,
+	.write		= tty_write,
+	.poll		= tty_poll,
+	.ioctl		= tty_ioctl,
+	.open		= tty_open,
+	.release	= tty_release,
+	.fasync		= tty_fasync,
+};
+
+#ifdef CONFIG_UNIX98_PTYS
+static struct file_operations ptmx_fops = {
+	.llseek		= no_llseek,
+	.read		= tty_read,
+	.write		= tty_write,
+	.poll		= tty_poll,
+	.ioctl		= tty_ioctl,
+	.open		= ptmx_open,
+	.release	= tty_release,
+	.fasync		= tty_fasync,
+};
+#endif
+
+static struct file_operations console_fops = {
+	.llseek		= no_llseek,
+	.read		= tty_read,
+	.write		= redirected_tty_write,
+	.poll		= tty_poll,
+	.ioctl		= tty_ioctl,
+	.open		= tty_open,
+	.release	= tty_release,
+	.fasync		= tty_fasync,
+};
+
+static struct file_operations hung_up_tty_fops = {
+	.llseek		= no_llseek,
+	.read		= hung_up_tty_read,
+	.write		= hung_up_tty_write,
+	.poll		= hung_up_tty_poll,
+	.ioctl		= hung_up_tty_ioctl,
+	.release	= tty_release,
+};
+
+static DEFINE_SPINLOCK(redirect_lock);
+static struct file *redirect;
+
+/**
+ *	tty_wakeup	-	request more data
+ *	@tty: terminal
+ *
+ *	Internal and external helper for wakeups of tty. This function
+ *	informs the line discipline if present that the driver is ready
+ *	to receive more output data.
+ */
+ 
+void tty_wakeup(struct tty_struct *tty)
+{
+	struct tty_ldisc *ld;
+	
+	if (test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) {
+		ld = tty_ldisc_ref(tty);
+		if(ld) {
+			if(ld->write_wakeup)
+				ld->write_wakeup(tty);
+			tty_ldisc_deref(ld);
+		}
+	}
+	wake_up_interruptible(&tty->write_wait);
+}
+
+EXPORT_SYMBOL_GPL(tty_wakeup);
+
+/**
+ *	tty_ldisc_flush	-	flush line discipline queue
+ *	@tty: tty
+ *
+ *	Flush the line discipline queue (if any) for this tty. If there
+ *	is no line discipline active this is a no-op.
+ */
+ 
+void tty_ldisc_flush(struct tty_struct *tty)
+{
+	struct tty_ldisc *ld = tty_ldisc_ref(tty);
+	if(ld) {
+		if(ld->flush_buffer)
+			ld->flush_buffer(tty);
+		tty_ldisc_deref(ld);
+	}
+}
+
+EXPORT_SYMBOL_GPL(tty_ldisc_flush);
+	
+/*
+ * This can be called by the "eventd" kernel thread.  That is process synchronous,
+ * but doesn't hold any locks, so we need to make sure we have the appropriate
+ * locks for what we're doing..
+ */
+static void do_tty_hangup(void *data)
+{
+	struct tty_struct *tty = (struct tty_struct *) data;
+	struct file * cons_filp = NULL;
+	struct file *filp, *f = NULL;
+	struct task_struct *p;
+	struct tty_ldisc *ld;
+	int    closecount = 0, n;
+
+	if (!tty)
+		return;
+
+	/* inuse_filps is protected by the single kernel lock */
+	lock_kernel();
+
+	spin_lock(&redirect_lock);
+	if (redirect && redirect->private_data == tty) {
+		f = redirect;
+		redirect = NULL;
+	}
+	spin_unlock(&redirect_lock);
+	
+	check_tty_count(tty, "do_tty_hangup");
+	file_list_lock();
+	/* This breaks for file handles being sent over AF_UNIX sockets ? */
+	list_for_each_entry(filp, &tty->tty_files, f_list) {
+		if (filp->f_op->write == redirected_tty_write)
+			cons_filp = filp;
+		if (filp->f_op->write != tty_write)
+			continue;
+		closecount++;
+		tty_fasync(-1, filp, 0);	/* can't block */
+		filp->f_op = &hung_up_tty_fops;
+	}
+	file_list_unlock();
+	
+	/* FIXME! What are the locking issues here? This may me overdoing things..
+	 * this question is especially important now that we've removed the irqlock. */
+
+	ld = tty_ldisc_ref(tty);
+	if(ld != NULL)	/* We may have no line discipline at this point */
+	{
+		if (ld->flush_buffer)
+			ld->flush_buffer(tty);
+		if (tty->driver->flush_buffer)
+			tty->driver->flush_buffer(tty);
+		if ((test_bit(TTY_DO_WRITE_WAKEUP, &tty->flags)) &&
+		    ld->write_wakeup)
+			ld->write_wakeup(tty);
+		if (ld->hangup)
+			ld->hangup(tty);
+	}
+
+	/* FIXME: Once we trust the LDISC code better we can wait here for
+	   ldisc completion and fix the driver call race */
+	   
+	wake_up_interruptible(&tty->write_wait);
+	wake_up_interruptible(&tty->read_wait);
+
+	/*
+	 * Shutdown the current line discipline, and reset it to
+	 * N_TTY.
+	 */
+	if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS)
+	{
+		down(&tty->termios_sem);
+		*tty->termios = tty->driver->init_termios;
+		up(&tty->termios_sem);
+	}
+	
+	/* Defer ldisc switch */
+	/* tty_deferred_ldisc_switch(N_TTY);
+	
+	  This should get done automatically when the port closes and
+	  tty_release is called */
+	
+	read_lock(&tasklist_lock);
+	if (tty->session > 0) {
+		do_each_task_pid(tty->session, PIDTYPE_SID, p) {
+			if (p->signal->tty == tty)
+				p->signal->tty = NULL;
+			if (!p->signal->leader)
+				continue;
+			send_group_sig_info(SIGHUP, SEND_SIG_PRIV, p);
+			send_group_sig_info(SIGCONT, SEND_SIG_PRIV, p);
+			if (tty->pgrp > 0)
+				p->signal->tty_old_pgrp = tty->pgrp;
+		} while_each_task_pid(tty->session, PIDTYPE_SID, p);
+	}
+	read_unlock(&tasklist_lock);
+
+	tty->flags = 0;
+	tty->session = 0;
+	tty->pgrp = -1;
+	tty->ctrl_status = 0;
+	/*
+	 *	If one of the devices matches a console pointer, we
+	 *	cannot just call hangup() because that will cause
+	 *	tty->count and state->count to go out of sync.
+	 *	So we just call close() the right number of times.
+	 */
+	if (cons_filp) {
+		if (tty->driver->close)
+			for (n = 0; n < closecount; n++)
+				tty->driver->close(tty, cons_filp);
+	} else if (tty->driver->hangup)
+		(tty->driver->hangup)(tty);
+		
+	/* We don't want to have driver/ldisc interactions beyond
+	   the ones we did here. The driver layer expects no
+	   calls after ->hangup() from the ldisc side. However we
+	   can't yet guarantee all that */
+
+	set_bit(TTY_HUPPED, &tty->flags);
+	if (ld) {
+		tty_ldisc_enable(tty);
+		tty_ldisc_deref(ld);
+	}
+	unlock_kernel();
+	if (f)
+		fput(f);
+}
+
+void tty_hangup(struct tty_struct * tty)
+{
+#ifdef TTY_DEBUG_HANGUP
+	char	buf[64];
+	
+	printk(KERN_DEBUG "%s hangup...\n", tty_name(tty, buf));
+#endif
+	schedule_work(&tty->hangup_work);
+}
+
+EXPORT_SYMBOL(tty_hangup);
+
+void tty_vhangup(struct tty_struct * tty)
+{
+#ifdef TTY_DEBUG_HANGUP
+	char	buf[64];
+
+	printk(KERN_DEBUG "%s vhangup...\n", tty_name(tty, buf));
+#endif
+	do_tty_hangup((void *) tty);
+}
+EXPORT_SYMBOL(tty_vhangup);
+
+int tty_hung_up_p(struct file * filp)
+{
+	return (filp->f_op == &hung_up_tty_fops);
+}
+
+EXPORT_SYMBOL(tty_hung_up_p);
+
+/*
+ * This function is typically called only by the session leader, when
+ * it wants to disassociate itself from its controlling tty.
+ *
+ * It performs the following functions:
+ * 	(1)  Sends a SIGHUP and SIGCONT to the foreground process group
+ * 	(2)  Clears the tty from being controlling the session
+ * 	(3)  Clears the controlling tty for all processes in the
+ * 		session group.
+ *
+ * The argument on_exit is set to 1 if called when a process is
+ * exiting; it is 0 if called by the ioctl TIOCNOTTY.
+ */
+void disassociate_ctty(int on_exit)
+{
+	struct tty_struct *tty;
+	struct task_struct *p;
+	int tty_pgrp = -1;
+
+	lock_kernel();
+
+	down(&tty_sem);
+	tty = current->signal->tty;
+	if (tty) {
+		tty_pgrp = tty->pgrp;
+		up(&tty_sem);
+		if (on_exit && tty->driver->type != TTY_DRIVER_TYPE_PTY)
+			tty_vhangup(tty);
+	} else {
+		if (current->signal->tty_old_pgrp) {
+			kill_pg(current->signal->tty_old_pgrp, SIGHUP, on_exit);
+			kill_pg(current->signal->tty_old_pgrp, SIGCONT, on_exit);
+		}
+		up(&tty_sem);
+		unlock_kernel();	
+		return;
+	}
+	if (tty_pgrp > 0) {
+		kill_pg(tty_pgrp, SIGHUP, on_exit);
+		if (!on_exit)
+			kill_pg(tty_pgrp, SIGCONT, on_exit);
+	}
+
+	/* Must lock changes to tty_old_pgrp */
+	down(&tty_sem);
+	current->signal->tty_old_pgrp = 0;
+	tty->session = 0;
+	tty->pgrp = -1;
+
+	/* Now clear signal->tty under the lock */
+	read_lock(&tasklist_lock);
+	do_each_task_pid(current->signal->session, PIDTYPE_SID, p) {
+		p->signal->tty = NULL;
+	} while_each_task_pid(current->signal->session, PIDTYPE_SID, p);
+	read_unlock(&tasklist_lock);
+	up(&tty_sem);
+	unlock_kernel();
+}
+
+void stop_tty(struct tty_struct *tty)
+{
+	if (tty->stopped)
+		return;
+	tty->stopped = 1;
+	if (tty->link && tty->link->packet) {
+		tty->ctrl_status &= ~TIOCPKT_START;
+		tty->ctrl_status |= TIOCPKT_STOP;
+		wake_up_interruptible(&tty->link->read_wait);
+	}
+	if (tty->driver->stop)
+		(tty->driver->stop)(tty);
+}
+
+EXPORT_SYMBOL(stop_tty);
+
+void start_tty(struct tty_struct *tty)
+{
+	if (!tty->stopped || tty->flow_stopped)
+		return;
+	tty->stopped = 0;
+	if (tty->link && tty->link->packet) {
+		tty->ctrl_status &= ~TIOCPKT_STOP;
+		tty->ctrl_status |= TIOCPKT_START;
+		wake_up_interruptible(&tty->link->read_wait);
+	}
+	if (tty->driver->start)
+		(tty->driver->start)(tty);
+
+	/* If we have a running line discipline it may need kicking */
+	tty_wakeup(tty);
+	wake_up_interruptible(&tty->write_wait);
+}
+
+EXPORT_SYMBOL(start_tty);
+
+static ssize_t tty_read(struct file * file, char __user * buf, size_t count, 
+			loff_t *ppos)
+{
+	int i;
+	struct tty_struct * tty;
+	struct inode *inode;
+	struct tty_ldisc *ld;
+
+	tty = (struct tty_struct *)file->private_data;
+	inode = file->f_dentry->d_inode;
+	if (tty_paranoia_check(tty, inode, "tty_read"))
+		return -EIO;
+	if (!tty || (test_bit(TTY_IO_ERROR, &tty->flags)))
+		return -EIO;
+
+	/* We want to wait for the line discipline to sort out in this
+	   situation */
+	ld = tty_ldisc_ref_wait(tty);
+	lock_kernel();
+	if (ld->read)
+		i = (ld->read)(tty,file,buf,count);
+	else
+		i = -EIO;
+	tty_ldisc_deref(ld);
+	unlock_kernel();
+	if (i > 0)
+		inode->i_atime = current_fs_time(inode->i_sb);
+	return i;
+}
+
+/*
+ * Split writes up in sane blocksizes to avoid
+ * denial-of-service type attacks
+ */
+static inline ssize_t do_tty_write(
+	ssize_t (*write)(struct tty_struct *, struct file *, const unsigned char *, size_t),
+	struct tty_struct *tty,
+	struct file *file,
+	const char __user *buf,
+	size_t count)
+{
+	ssize_t ret = 0, written = 0;
+	unsigned int chunk;
+	
+	if (down_interruptible(&tty->atomic_write)) {
+		return -ERESTARTSYS;
+	}
+
+	/*
+	 * We chunk up writes into a temporary buffer. This
+	 * simplifies low-level drivers immensely, since they
+	 * don't have locking issues and user mode accesses.
+	 *
+	 * But if TTY_NO_WRITE_SPLIT is set, we should use a
+	 * big chunk-size..
+	 *
+	 * The default chunk-size is 2kB, because the NTTY
+	 * layer has problems with bigger chunks. It will
+	 * claim to be able to handle more characters than
+	 * it actually does.
+	 */
+	chunk = 2048;
+	if (test_bit(TTY_NO_WRITE_SPLIT, &tty->flags))
+		chunk = 65536;
+	if (count < chunk)
+		chunk = count;
+
+	/* write_buf/write_cnt is protected by the atomic_write semaphore */
+	if (tty->write_cnt < chunk) {
+		unsigned char *buf;
+
+		if (chunk < 1024)
+			chunk = 1024;
+
+		buf = kmalloc(chunk, GFP_KERNEL);
+		if (!buf) {
+			up(&tty->atomic_write);
+			return -ENOMEM;
+		}
+		kfree(tty->write_buf);
+		tty->write_cnt = chunk;
+		tty->write_buf = buf;
+	}
+
+	/* Do the write .. */
+	for (;;) {
+		size_t size = count;
+		if (size > chunk)
+			size = chunk;
+		ret = -EFAULT;
+		if (copy_from_user(tty->write_buf, buf, size))
+			break;
+		lock_kernel();
+		ret = write(tty, file, tty->write_buf, size);
+		unlock_kernel();
+		if (ret <= 0)
+			break;
+		written += ret;
+		buf += ret;
+		count -= ret;
+		if (!count)
+			break;
+		ret = -ERESTARTSYS;
+		if (signal_pending(current))
+			break;
+		cond_resched();
+	}
+	if (written) {
+		struct inode *inode = file->f_dentry->d_inode;
+		inode->i_mtime = current_fs_time(inode->i_sb);
+		ret = written;
+	}
+	up(&tty->atomic_write);
+	return ret;
+}
+
+
+static ssize_t tty_write(struct file * file, const char __user * buf, size_t count,
+			 loff_t *ppos)
+{
+	struct tty_struct * tty;
+	struct inode *inode = file->f_dentry->d_inode;
+	ssize_t ret;
+	struct tty_ldisc *ld;
+	
+	tty = (struct tty_struct *)file->private_data;
+	if (tty_paranoia_check(tty, inode, "tty_write"))
+		return -EIO;
+	if (!tty || !tty->driver->write || (test_bit(TTY_IO_ERROR, &tty->flags)))
+		return -EIO;
+
+	ld = tty_ldisc_ref_wait(tty);		
+	if (!ld->write)
+		ret = -EIO;
+	else
+		ret = do_tty_write(ld->write, tty, file, buf, count);
+	tty_ldisc_deref(ld);
+	return ret;
+}
+
+ssize_t redirected_tty_write(struct file * file, const char __user * buf, size_t count,
+			 loff_t *ppos)
+{
+	struct file *p = NULL;
+
+	spin_lock(&redirect_lock);
+	if (redirect) {
+		get_file(redirect);
+		p = redirect;
+	}
+	spin_unlock(&redirect_lock);
+
+	if (p) {
+		ssize_t res;
+		res = vfs_write(p, buf, count, &p->f_pos);
+		fput(p);
+		return res;
+	}
+
+	return tty_write(file, buf, count, ppos);
+}
+
+static char ptychar[] = "pqrstuvwxyzabcde";
+
+static inline void pty_line_name(struct tty_driver *driver, int index, char *p)
+{
+	int i = index + driver->name_base;
+	/* ->name is initialized to "ttyp", but "tty" is expected */
+	sprintf(p, "%s%c%x",
+			driver->subtype == PTY_TYPE_SLAVE ? "tty" : driver->name,
+			ptychar[i >> 4 & 0xf], i & 0xf);
+}
+
+static inline void tty_line_name(struct tty_driver *driver, int index, char *p)
+{
+	sprintf(p, "%s%d", driver->name, index + driver->name_base);
+}
+
+/*
+ * WSH 06/09/97: Rewritten to remove races and properly clean up after a
+ * failed open.  The new code protects the open with a semaphore, so it's
+ * really quite straightforward.  The semaphore locking can probably be
+ * relaxed for the (most common) case of reopening a tty.
+ */
+static int init_dev(struct tty_driver *driver, int idx,
+	struct tty_struct **ret_tty)
+{
+	struct tty_struct *tty, *o_tty;
+	struct termios *tp, **tp_loc, *o_tp, **o_tp_loc;
+	struct termios *ltp, **ltp_loc, *o_ltp, **o_ltp_loc;
+	int retval=0;
+
+	/* check whether we're reopening an existing tty */
+	if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
+		tty = devpts_get_tty(idx);
+		if (tty && driver->subtype == PTY_TYPE_MASTER)
+			tty = tty->link;
+	} else {
+		tty = driver->ttys[idx];
+	}
+	if (tty) goto fast_track;
+
+	/*
+	 * First time open is complex, especially for PTY devices.
+	 * This code guarantees that either everything succeeds and the
+	 * TTY is ready for operation, or else the table slots are vacated
+	 * and the allocated memory released.  (Except that the termios 
+	 * and locked termios may be retained.)
+	 */
+
+	if (!try_module_get(driver->owner)) {
+		retval = -ENODEV;
+		goto end_init;
+	}
+
+	o_tty = NULL;
+	tp = o_tp = NULL;
+	ltp = o_ltp = NULL;
+
+	tty = alloc_tty_struct();
+	if(!tty)
+		goto fail_no_mem;
+	initialize_tty_struct(tty);
+	tty->driver = driver;
+	tty->index = idx;
+	tty_line_name(driver, idx, tty->name);
+
+	if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
+		tp_loc = &tty->termios;
+		ltp_loc = &tty->termios_locked;
+	} else {
+		tp_loc = &driver->termios[idx];
+		ltp_loc = &driver->termios_locked[idx];
+	}
+
+	if (!*tp_loc) {
+		tp = (struct termios *) kmalloc(sizeof(struct termios),
+						GFP_KERNEL);
+		if (!tp)
+			goto free_mem_out;
+		*tp = driver->init_termios;
+	}
+
+	if (!*ltp_loc) {
+		ltp = (struct termios *) kmalloc(sizeof(struct termios),
+						 GFP_KERNEL);
+		if (!ltp)
+			goto free_mem_out;
+		memset(ltp, 0, sizeof(struct termios));
+	}
+
+	if (driver->type == TTY_DRIVER_TYPE_PTY) {
+		o_tty = alloc_tty_struct();
+		if (!o_tty)
+			goto free_mem_out;
+		initialize_tty_struct(o_tty);
+		o_tty->driver = driver->other;
+		o_tty->index = idx;
+		tty_line_name(driver->other, idx, o_tty->name);
+
+		if (driver->flags & TTY_DRIVER_DEVPTS_MEM) {
+			o_tp_loc = &o_tty->termios;
+			o_ltp_loc = &o_tty->termios_locked;
+		} else {
+			o_tp_loc = &driver->other->termios[idx];
+			o_ltp_loc = &driver->other->termios_locked[idx];
+		}
+
+		if (!*o_tp_loc) {
+			o_tp = (struct termios *)
+				kmalloc(sizeof(struct termios), GFP_KERNEL);
+			if (!o_tp)
+				goto free_mem_out;
+			*o_tp = driver->other->init_termios;
+		}
+
+		if (!*o_ltp_loc) {
+			o_ltp = (struct termios *)
+				kmalloc(sizeof(struct termios), GFP_KERNEL);
+			if (!o_ltp)
+				goto free_mem_out;
+			memset(o_ltp, 0, sizeof(struct termios));
+		}
+
+		/*
+		 * Everything allocated ... set up the o_tty structure.
+		 */
+		if (!(driver->other->flags & TTY_DRIVER_DEVPTS_MEM)) {
+			driver->other->ttys[idx] = o_tty;
+		}
+		if (!*o_tp_loc)
+			*o_tp_loc = o_tp;
+		if (!*o_ltp_loc)
+			*o_ltp_loc = o_ltp;
+		o_tty->termios = *o_tp_loc;
+		o_tty->termios_locked = *o_ltp_loc;
+		driver->other->refcount++;
+		if (driver->subtype == PTY_TYPE_MASTER)
+			o_tty->count++;
+
+		/* Establish the links in both directions */
+		tty->link   = o_tty;
+		o_tty->link = tty;
+	}
+
+	/* 
+	 * All structures have been allocated, so now we install them.
+	 * Failures after this point use release_mem to clean up, so 
+	 * there's no need to null out the local pointers.
+	 */
+	if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+		driver->ttys[idx] = tty;
+	}
+	
+	if (!*tp_loc)
+		*tp_loc = tp;
+	if (!*ltp_loc)
+		*ltp_loc = ltp;
+	tty->termios = *tp_loc;
+	tty->termios_locked = *ltp_loc;
+	driver->refcount++;
+	tty->count++;
+
+	/* 
+	 * Structures all installed ... call the ldisc open routines.
+	 * If we fail here just call release_mem to clean up.  No need
+	 * to decrement the use counts, as release_mem doesn't care.
+	 */
+
+	if (tty->ldisc.open) {
+		retval = (tty->ldisc.open)(tty);
+		if (retval)
+			goto release_mem_out;
+	}
+	if (o_tty && o_tty->ldisc.open) {
+		retval = (o_tty->ldisc.open)(o_tty);
+		if (retval) {
+			if (tty->ldisc.close)
+				(tty->ldisc.close)(tty);
+			goto release_mem_out;
+		}
+		tty_ldisc_enable(o_tty);
+	}
+	tty_ldisc_enable(tty);
+	goto success;
+
+	/*
+	 * This fast open can be used if the tty is already open.
+	 * No memory is allocated, and the only failures are from
+	 * attempting to open a closing tty or attempting multiple
+	 * opens on a pty master.
+	 */
+fast_track:
+	if (test_bit(TTY_CLOSING, &tty->flags)) {
+		retval = -EIO;
+		goto end_init;
+	}
+	if (driver->type == TTY_DRIVER_TYPE_PTY &&
+	    driver->subtype == PTY_TYPE_MASTER) {
+		/*
+		 * special case for PTY masters: only one open permitted, 
+		 * and the slave side open count is incremented as well.
+		 */
+		if (tty->count) {
+			retval = -EIO;
+			goto end_init;
+		}
+		tty->link->count++;
+	}
+	tty->count++;
+	tty->driver = driver; /* N.B. why do this every time?? */
+
+	/* FIXME */
+	if(!test_bit(TTY_LDISC, &tty->flags))
+		printk(KERN_ERR "init_dev but no ldisc\n");
+success:
+	*ret_tty = tty;
+	
+	/* All paths come through here to release the semaphore */
+end_init:
+	return retval;
+
+	/* Release locally allocated memory ... nothing placed in slots */
+free_mem_out:
+	if (o_tp)
+		kfree(o_tp);
+	if (o_tty)
+		free_tty_struct(o_tty);
+	if (ltp)
+		kfree(ltp);
+	if (tp)
+		kfree(tp);
+	free_tty_struct(tty);
+
+fail_no_mem:
+	module_put(driver->owner);
+	retval = -ENOMEM;
+	goto end_init;
+
+	/* call the tty release_mem routine to clean out this slot */
+release_mem_out:
+	printk(KERN_INFO "init_dev: ldisc open failed, "
+			 "clearing slot %d\n", idx);
+	release_mem(tty, idx);
+	goto end_init;
+}
+
+/*
+ * Releases memory associated with a tty structure, and clears out the
+ * driver table slots.
+ */
+static void release_mem(struct tty_struct *tty, int idx)
+{
+	struct tty_struct *o_tty;
+	struct termios *tp;
+	int devpts = tty->driver->flags & TTY_DRIVER_DEVPTS_MEM;
+
+	if ((o_tty = tty->link) != NULL) {
+		if (!devpts)
+			o_tty->driver->ttys[idx] = NULL;
+		if (o_tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) {
+			tp = o_tty->termios;
+			if (!devpts)
+				o_tty->driver->termios[idx] = NULL;
+			kfree(tp);
+
+			tp = o_tty->termios_locked;
+			if (!devpts)
+				o_tty->driver->termios_locked[idx] = NULL;
+			kfree(tp);
+		}
+		o_tty->magic = 0;
+		o_tty->driver->refcount--;
+		file_list_lock();
+		list_del_init(&o_tty->tty_files);
+		file_list_unlock();
+		free_tty_struct(o_tty);
+	}
+
+	if (!devpts)
+		tty->driver->ttys[idx] = NULL;
+	if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) {
+		tp = tty->termios;
+		if (!devpts)
+			tty->driver->termios[idx] = NULL;
+		kfree(tp);
+
+		tp = tty->termios_locked;
+		if (!devpts)
+			tty->driver->termios_locked[idx] = NULL;
+		kfree(tp);
+	}
+
+	tty->magic = 0;
+	tty->driver->refcount--;
+	file_list_lock();
+	list_del_init(&tty->tty_files);
+	file_list_unlock();
+	module_put(tty->driver->owner);
+	free_tty_struct(tty);
+}
+
+/*
+ * Even releasing the tty structures is a tricky business.. We have
+ * to be very careful that the structures are all released at the
+ * same time, as interrupts might otherwise get the wrong pointers.
+ *
+ * WSH 09/09/97: rewritten to avoid some nasty race conditions that could
+ * lead to double frees or releasing memory still in use.
+ */
+static void release_dev(struct file * filp)
+{
+	struct tty_struct *tty, *o_tty;
+	int	pty_master, tty_closing, o_tty_closing, do_sleep;
+	int	devpts_master, devpts;
+	int	idx;
+	char	buf[64];
+	unsigned long flags;
+	
+	tty = (struct tty_struct *)filp->private_data;
+	if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "release_dev"))
+		return;
+
+	check_tty_count(tty, "release_dev");
+
+	tty_fasync(-1, filp, 0);
+
+	idx = tty->index;
+	pty_master = (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+		      tty->driver->subtype == PTY_TYPE_MASTER);
+	devpts = (tty->driver->flags & TTY_DRIVER_DEVPTS_MEM) != 0;
+	devpts_master = pty_master && devpts;
+	o_tty = tty->link;
+
+#ifdef TTY_PARANOIA_CHECK
+	if (idx < 0 || idx >= tty->driver->num) {
+		printk(KERN_DEBUG "release_dev: bad idx when trying to "
+				  "free (%s)\n", tty->name);
+		return;
+	}
+	if (!(tty->driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+		if (tty != tty->driver->ttys[idx]) {
+			printk(KERN_DEBUG "release_dev: driver.table[%d] not tty "
+			       "for (%s)\n", idx, tty->name);
+			return;
+		}
+		if (tty->termios != tty->driver->termios[idx]) {
+			printk(KERN_DEBUG "release_dev: driver.termios[%d] not termios "
+			       "for (%s)\n",
+			       idx, tty->name);
+			return;
+		}
+		if (tty->termios_locked != tty->driver->termios_locked[idx]) {
+			printk(KERN_DEBUG "release_dev: driver.termios_locked[%d] not "
+			       "termios_locked for (%s)\n",
+			       idx, tty->name);
+			return;
+		}
+	}
+#endif
+
+#ifdef TTY_DEBUG_HANGUP
+	printk(KERN_DEBUG "release_dev of %s (tty count=%d)...",
+	       tty_name(tty, buf), tty->count);
+#endif
+
+#ifdef TTY_PARANOIA_CHECK
+	if (tty->driver->other &&
+	     !(tty->driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+		if (o_tty != tty->driver->other->ttys[idx]) {
+			printk(KERN_DEBUG "release_dev: other->table[%d] "
+					  "not o_tty for (%s)\n",
+			       idx, tty->name);
+			return;
+		}
+		if (o_tty->termios != tty->driver->other->termios[idx]) {
+			printk(KERN_DEBUG "release_dev: other->termios[%d] "
+					  "not o_termios for (%s)\n",
+			       idx, tty->name);
+			return;
+		}
+		if (o_tty->termios_locked != 
+		      tty->driver->other->termios_locked[idx]) {
+			printk(KERN_DEBUG "release_dev: other->termios_locked["
+					  "%d] not o_termios_locked for (%s)\n",
+			       idx, tty->name);
+			return;
+		}
+		if (o_tty->link != tty) {
+			printk(KERN_DEBUG "release_dev: bad pty pointers\n");
+			return;
+		}
+	}
+#endif
+	if (tty->driver->close)
+		tty->driver->close(tty, filp);
+
+	/*
+	 * Sanity check: if tty->count is going to zero, there shouldn't be
+	 * any waiters on tty->read_wait or tty->write_wait.  We test the
+	 * wait queues and kick everyone out _before_ actually starting to
+	 * close.  This ensures that we won't block while releasing the tty
+	 * structure.
+	 *
+	 * The test for the o_tty closing is necessary, since the master and
+	 * slave sides may close in any order.  If the slave side closes out
+	 * first, its count will be one, since the master side holds an open.
+	 * Thus this test wouldn't be triggered at the time the slave closes,
+	 * so we do it now.
+	 *
+	 * Note that it's possible for the tty to be opened again while we're
+	 * flushing out waiters.  By recalculating the closing flags before
+	 * each iteration we avoid any problems.
+	 */
+	while (1) {
+		/* Guard against races with tty->count changes elsewhere and
+		   opens on /dev/tty */
+		   
+		down(&tty_sem);
+		tty_closing = tty->count <= 1;
+		o_tty_closing = o_tty &&
+			(o_tty->count <= (pty_master ? 1 : 0));
+		up(&tty_sem);
+		do_sleep = 0;
+
+		if (tty_closing) {
+			if (waitqueue_active(&tty->read_wait)) {
+				wake_up(&tty->read_wait);
+				do_sleep++;
+			}
+			if (waitqueue_active(&tty->write_wait)) {
+				wake_up(&tty->write_wait);
+				do_sleep++;
+			}
+		}
+		if (o_tty_closing) {
+			if (waitqueue_active(&o_tty->read_wait)) {
+				wake_up(&o_tty->read_wait);
+				do_sleep++;
+			}
+			if (waitqueue_active(&o_tty->write_wait)) {
+				wake_up(&o_tty->write_wait);
+				do_sleep++;
+			}
+		}
+		if (!do_sleep)
+			break;
+
+		printk(KERN_WARNING "release_dev: %s: read/write wait queue "
+				    "active!\n", tty_name(tty, buf));
+		schedule();
+	}	
+
+	/*
+	 * The closing flags are now consistent with the open counts on 
+	 * both sides, and we've completed the last operation that could 
+	 * block, so it's safe to proceed with closing.
+	 */
+	 
+	down(&tty_sem);
+	if (pty_master) {
+		if (--o_tty->count < 0) {
+			printk(KERN_WARNING "release_dev: bad pty slave count "
+					    "(%d) for %s\n",
+			       o_tty->count, tty_name(o_tty, buf));
+			o_tty->count = 0;
+		}
+	}
+	if (--tty->count < 0) {
+		printk(KERN_WARNING "release_dev: bad tty->count (%d) for %s\n",
+		       tty->count, tty_name(tty, buf));
+		tty->count = 0;
+	}
+	up(&tty_sem);
+	
+	/*
+	 * We've decremented tty->count, so we need to remove this file
+	 * descriptor off the tty->tty_files list; this serves two
+	 * purposes:
+	 *  - check_tty_count sees the correct number of file descriptors
+	 *    associated with this tty.
+	 *  - do_tty_hangup no longer sees this file descriptor as
+	 *    something that needs to be handled for hangups.
+	 */
+	file_kill(filp);
+	filp->private_data = NULL;
+
+	/*
+	 * Perform some housekeeping before deciding whether to return.
+	 *
+	 * Set the TTY_CLOSING flag if this was the last open.  In the
+	 * case of a pty we may have to wait around for the other side
+	 * to close, and TTY_CLOSING makes sure we can't be reopened.
+	 */
+	if(tty_closing)
+		set_bit(TTY_CLOSING, &tty->flags);
+	if(o_tty_closing)
+		set_bit(TTY_CLOSING, &o_tty->flags);
+
+	/*
+	 * If _either_ side is closing, make sure there aren't any
+	 * processes that still think tty or o_tty is their controlling
+	 * tty.
+	 */
+	if (tty_closing || o_tty_closing) {
+		struct task_struct *p;
+
+		read_lock(&tasklist_lock);
+		do_each_task_pid(tty->session, PIDTYPE_SID, p) {
+			p->signal->tty = NULL;
+		} while_each_task_pid(tty->session, PIDTYPE_SID, p);
+		if (o_tty)
+			do_each_task_pid(o_tty->session, PIDTYPE_SID, p) {
+				p->signal->tty = NULL;
+			} while_each_task_pid(o_tty->session, PIDTYPE_SID, p);
+		read_unlock(&tasklist_lock);
+	}
+
+	/* check whether both sides are closing ... */
+	if (!tty_closing || (o_tty && !o_tty_closing))
+		return;
+	
+#ifdef TTY_DEBUG_HANGUP
+	printk(KERN_DEBUG "freeing tty structure...");
+#endif
+	/*
+	 * Prevent flush_to_ldisc() from rescheduling the work for later.  Then
+	 * kill any delayed work. As this is the final close it does not
+	 * race with the set_ldisc code path.
+	 */
+	clear_bit(TTY_LDISC, &tty->flags);
+	clear_bit(TTY_DONT_FLIP, &tty->flags);
+	cancel_delayed_work(&tty->flip.work);
+
+	/*
+	 * Wait for ->hangup_work and ->flip.work handlers to terminate
+	 */
+	 
+	flush_scheduled_work();
+	
+	/*
+	 * Wait for any short term users (we know they are just driver
+	 * side waiters as the file is closing so user count on the file
+	 * side is zero.
+	 */
+	spin_lock_irqsave(&tty_ldisc_lock, flags);
+	while(tty->ldisc.refcount)
+	{
+		spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+		wait_event(tty_ldisc_wait, tty->ldisc.refcount == 0);
+		spin_lock_irqsave(&tty_ldisc_lock, flags);
+	}
+	spin_unlock_irqrestore(&tty_ldisc_lock, flags);
+	/*
+	 * Shutdown the current line discipline, and reset it to N_TTY.
+	 * N.B. why reset ldisc when we're releasing the memory??
+	 *
+	 * FIXME: this MUST get fixed for the new reflocking
+	 */
+	if (tty->ldisc.close)
+		(tty->ldisc.close)(tty);
+	tty_ldisc_put(tty->ldisc.num);
+	
+	/*
+	 *	Switch the line discipline back
+	 */
+	tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+	tty_set_termios_ldisc(tty,N_TTY); 
+	if (o_tty) {
+		/* FIXME: could o_tty be in setldisc here ? */
+		clear_bit(TTY_LDISC, &o_tty->flags);
+		if (o_tty->ldisc.close)
+			(o_tty->ldisc.close)(o_tty);
+		tty_ldisc_put(o_tty->ldisc.num);
+		tty_ldisc_assign(o_tty, tty_ldisc_get(N_TTY));
+		tty_set_termios_ldisc(o_tty,N_TTY); 
+	}
+	/*
+	 * The release_mem function takes care of the details of clearing
+	 * the slots and preserving the termios structure.
+	 */
+	release_mem(tty, idx);
+
+#ifdef CONFIG_UNIX98_PTYS
+	/* Make this pty number available for reallocation */
+	if (devpts) {
+		down(&allocated_ptys_lock);
+		idr_remove(&allocated_ptys, idx);
+		up(&allocated_ptys_lock);
+	}
+#endif
+
+}
+
+/*
+ * tty_open and tty_release keep up the tty count that contains the
+ * number of opens done on a tty. We cannot use the inode-count, as
+ * different inodes might point to the same tty.
+ *
+ * Open-counting is needed for pty masters, as well as for keeping
+ * track of serial lines: DTR is dropped when the last close happens.
+ * (This is not done solely through tty->count, now.  - Ted 1/27/92)
+ *
+ * The termios state of a pty is reset on first open so that
+ * settings don't persist across reuse.
+ */
+static int tty_open(struct inode * inode, struct file * filp)
+{
+	struct tty_struct *tty;
+	int noctty, retval;
+	struct tty_driver *driver;
+	int index;
+	dev_t device = inode->i_rdev;
+	unsigned short saved_flags = filp->f_flags;
+
+	nonseekable_open(inode, filp);
+	
+retry_open:
+	noctty = filp->f_flags & O_NOCTTY;
+	index  = -1;
+	retval = 0;
+	
+	down(&tty_sem);
+
+	if (device == MKDEV(TTYAUX_MAJOR,0)) {
+		if (!current->signal->tty) {
+			up(&tty_sem);
+			return -ENXIO;
+		}
+		driver = current->signal->tty->driver;
+		index = current->signal->tty->index;
+		filp->f_flags |= O_NONBLOCK; /* Don't let /dev/tty block */
+		/* noctty = 1; */
+		goto got_driver;
+	}
+#ifdef CONFIG_VT
+	if (device == MKDEV(TTY_MAJOR,0)) {
+		extern struct tty_driver *console_driver;
+		driver = console_driver;
+		index = fg_console;
+		noctty = 1;
+		goto got_driver;
+	}
+#endif
+	if (device == MKDEV(TTYAUX_MAJOR,1)) {
+		driver = console_device(&index);
+		if (driver) {
+			/* Don't let /dev/console block */
+			filp->f_flags |= O_NONBLOCK;
+			noctty = 1;
+			goto got_driver;
+		}
+		up(&tty_sem);
+		return -ENODEV;
+	}
+
+	driver = get_tty_driver(device, &index);
+	if (!driver) {
+		up(&tty_sem);
+		return -ENODEV;
+	}
+got_driver:
+	retval = init_dev(driver, index, &tty);
+	up(&tty_sem);
+	if (retval)
+		return retval;
+
+	filp->private_data = tty;
+	file_move(filp, &tty->tty_files);
+	check_tty_count(tty, "tty_open");
+	if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+	    tty->driver->subtype == PTY_TYPE_MASTER)
+		noctty = 1;
+#ifdef TTY_DEBUG_HANGUP
+	printk(KERN_DEBUG "opening %s...", tty->name);
+#endif
+	if (!retval) {
+		if (tty->driver->open)
+			retval = tty->driver->open(tty, filp);
+		else
+			retval = -ENODEV;
+	}
+	filp->f_flags = saved_flags;
+
+	if (!retval && test_bit(TTY_EXCLUSIVE, &tty->flags) && !capable(CAP_SYS_ADMIN))
+		retval = -EBUSY;
+
+	if (retval) {
+#ifdef TTY_DEBUG_HANGUP
+		printk(KERN_DEBUG "error %d in opening %s...", retval,
+		       tty->name);
+#endif
+		release_dev(filp);
+		if (retval != -ERESTARTSYS)
+			return retval;
+		if (signal_pending(current))
+			return retval;
+		schedule();
+		/*
+		 * Need to reset f_op in case a hangup happened.
+		 */
+		if (filp->f_op == &hung_up_tty_fops)
+			filp->f_op = &tty_fops;
+		goto retry_open;
+	}
+	if (!noctty &&
+	    current->signal->leader &&
+	    !current->signal->tty &&
+	    tty->session == 0) {
+	    	task_lock(current);
+		current->signal->tty = tty;
+		task_unlock(current);
+		current->signal->tty_old_pgrp = 0;
+		tty->session = current->signal->session;
+		tty->pgrp = process_group(current);
+	}
+	return 0;
+}
+
+#ifdef CONFIG_UNIX98_PTYS
+static int ptmx_open(struct inode * inode, struct file * filp)
+{
+	struct tty_struct *tty;
+	int retval;
+	int index;
+	int idr_ret;
+
+	nonseekable_open(inode, filp);
+
+	/* find a device that is not in use. */
+	down(&allocated_ptys_lock);
+	if (!idr_pre_get(&allocated_ptys, GFP_KERNEL)) {
+		up(&allocated_ptys_lock);
+		return -ENOMEM;
+	}
+	idr_ret = idr_get_new(&allocated_ptys, NULL, &index);
+	if (idr_ret < 0) {
+		up(&allocated_ptys_lock);
+		if (idr_ret == -EAGAIN)
+			return -ENOMEM;
+		return -EIO;
+	}
+	if (index >= pty_limit) {
+		idr_remove(&allocated_ptys, index);
+		up(&allocated_ptys_lock);
+		return -EIO;
+	}
+	up(&allocated_ptys_lock);
+
+	down(&tty_sem);
+	retval = init_dev(ptm_driver, index, &tty);
+	up(&tty_sem);
+	
+	if (retval)
+		goto out;
+
+	set_bit(TTY_PTY_LOCK, &tty->flags); /* LOCK THE SLAVE */
+	filp->private_data = tty;
+	file_move(filp, &tty->tty_files);
+
+	retval = -ENOMEM;
+	if (devpts_pty_new(tty->link))
+		goto out1;
+
+	check_tty_count(tty, "tty_open");
+	retval = ptm_driver->open(tty, filp);
+	if (!retval)
+		return 0;
+out1:
+	release_dev(filp);
+out:
+	down(&allocated_ptys_lock);
+	idr_remove(&allocated_ptys, index);
+	up(&allocated_ptys_lock);
+	return retval;
+}
+#endif
+
+static int tty_release(struct inode * inode, struct file * filp)
+{
+	lock_kernel();
+	release_dev(filp);
+	unlock_kernel();
+	return 0;
+}
+
+/* No kernel lock held - fine */
+static unsigned int tty_poll(struct file * filp, poll_table * wait)
+{
+	struct tty_struct * tty;
+	struct tty_ldisc *ld;
+	int ret = 0;
+
+	tty = (struct tty_struct *)filp->private_data;
+	if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "tty_poll"))
+		return 0;
+		
+	ld = tty_ldisc_ref_wait(tty);
+	if (ld->poll)
+		ret = (ld->poll)(tty, filp, wait);
+	tty_ldisc_deref(ld);
+	return ret;
+}
+
+static int tty_fasync(int fd, struct file * filp, int on)
+{
+	struct tty_struct * tty;
+	int retval;
+
+	tty = (struct tty_struct *)filp->private_data;
+	if (tty_paranoia_check(tty, filp->f_dentry->d_inode, "tty_fasync"))
+		return 0;
+	
+	retval = fasync_helper(fd, filp, on, &tty->fasync);
+	if (retval <= 0)
+		return retval;
+
+	if (on) {
+		if (!waitqueue_active(&tty->read_wait))
+			tty->minimum_to_wake = 1;
+		retval = f_setown(filp, (-tty->pgrp) ? : current->pid, 0);
+		if (retval)
+			return retval;
+	} else {
+		if (!tty->fasync && !waitqueue_active(&tty->read_wait))
+			tty->minimum_to_wake = N_TTY_BUF_SIZE;
+	}
+	return 0;
+}
+
+static int tiocsti(struct tty_struct *tty, char __user *p)
+{
+	char ch, mbz = 0;
+	struct tty_ldisc *ld;
+	
+	if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN))
+		return -EPERM;
+	if (get_user(ch, p))
+		return -EFAULT;
+	ld = tty_ldisc_ref_wait(tty);
+	ld->receive_buf(tty, &ch, &mbz, 1);
+	tty_ldisc_deref(ld);
+	return 0;
+}
+
+static int tiocgwinsz(struct tty_struct *tty, struct winsize __user * arg)
+{
+	if (copy_to_user(arg, &tty->winsize, sizeof(*arg)))
+		return -EFAULT;
+	return 0;
+}
+
+static int tiocswinsz(struct tty_struct *tty, struct tty_struct *real_tty,
+	struct winsize __user * arg)
+{
+	struct winsize tmp_ws;
+
+	if (copy_from_user(&tmp_ws, arg, sizeof(*arg)))
+		return -EFAULT;
+	if (!memcmp(&tmp_ws, &tty->winsize, sizeof(*arg)))
+		return 0;
+#ifdef CONFIG_VT
+	if (tty->driver->type == TTY_DRIVER_TYPE_CONSOLE) {
+		int rc;
+
+		acquire_console_sem();
+		rc = vc_resize(tty->driver_data, tmp_ws.ws_col, tmp_ws.ws_row);
+		release_console_sem();
+		if (rc)
+			return -ENXIO;
+	}
+#endif
+	if (tty->pgrp > 0)
+		kill_pg(tty->pgrp, SIGWINCH, 1);
+	if ((real_tty->pgrp != tty->pgrp) && (real_tty->pgrp > 0))
+		kill_pg(real_tty->pgrp, SIGWINCH, 1);
+	tty->winsize = tmp_ws;
+	real_tty->winsize = tmp_ws;
+	return 0;
+}
+
+static int tioccons(struct file *file)
+{
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+	if (file->f_op->write == redirected_tty_write) {
+		struct file *f;
+		spin_lock(&redirect_lock);
+		f = redirect;
+		redirect = NULL;
+		spin_unlock(&redirect_lock);
+		if (f)
+			fput(f);
+		return 0;
+	}
+	spin_lock(&redirect_lock);
+	if (redirect) {
+		spin_unlock(&redirect_lock);
+		return -EBUSY;
+	}
+	get_file(file);
+	redirect = file;
+	spin_unlock(&redirect_lock);
+	return 0;
+}
+
+
+static int fionbio(struct file *file, int __user *p)
+{
+	int nonblock;
+
+	if (get_user(nonblock, p))
+		return -EFAULT;
+
+	if (nonblock)
+		file->f_flags |= O_NONBLOCK;
+	else
+		file->f_flags &= ~O_NONBLOCK;
+	return 0;
+}
+
+static int tiocsctty(struct tty_struct *tty, int arg)
+{
+	task_t *p;
+
+	if (current->signal->leader &&
+	    (current->signal->session == tty->session))
+		return 0;
+	/*
+	 * The process must be a session leader and
+	 * not have a controlling tty already.
+	 */
+	if (!current->signal->leader || current->signal->tty)
+		return -EPERM;
+	if (tty->session > 0) {
+		/*
+		 * This tty is already the controlling
+		 * tty for another session group!
+		 */
+		if ((arg == 1) && capable(CAP_SYS_ADMIN)) {
+			/*
+			 * Steal it away
+			 */
+
+			read_lock(&tasklist_lock);
+			do_each_task_pid(tty->session, PIDTYPE_SID, p) {
+				p->signal->tty = NULL;
+			} while_each_task_pid(tty->session, PIDTYPE_SID, p);
+			read_unlock(&tasklist_lock);
+		} else
+			return -EPERM;
+	}
+	task_lock(current);
+	current->signal->tty = tty;
+	task_unlock(current);
+	current->signal->tty_old_pgrp = 0;
+	tty->session = current->signal->session;
+	tty->pgrp = process_group(current);
+	return 0;
+}
+
+static int tiocgpgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+	/*
+	 * (tty == real_tty) is a cheap way of
+	 * testing if the tty is NOT a master pty.
+	 */
+	if (tty == real_tty && current->signal->tty != real_tty)
+		return -ENOTTY;
+	return put_user(real_tty->pgrp, p);
+}
+
+static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+	pid_t pgrp;
+	int retval = tty_check_change(real_tty);
+
+	if (retval == -EIO)
+		return -ENOTTY;
+	if (retval)
+		return retval;
+	if (!current->signal->tty ||
+	    (current->signal->tty != real_tty) ||
+	    (real_tty->session != current->signal->session))
+		return -ENOTTY;
+	if (get_user(pgrp, p))
+		return -EFAULT;
+	if (pgrp < 0)
+		return -EINVAL;
+	if (session_of_pgrp(pgrp) != current->signal->session)
+		return -EPERM;
+	real_tty->pgrp = pgrp;
+	return 0;
+}
+
+static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p)
+{
+	/*
+	 * (tty == real_tty) is a cheap way of
+	 * testing if the tty is NOT a master pty.
+	*/
+	if (tty == real_tty && current->signal->tty != real_tty)
+		return -ENOTTY;
+	if (real_tty->session <= 0)
+		return -ENOTTY;
+	return put_user(real_tty->session, p);
+}
+
+static int tiocsetd(struct tty_struct *tty, int __user *p)
+{
+	int ldisc;
+
+	if (get_user(ldisc, p))
+		return -EFAULT;
+	return tty_set_ldisc(tty, ldisc);
+}
+
+static int send_break(struct tty_struct *tty, int duration)
+{
+	tty->driver->break_ctl(tty, -1);
+	if (!signal_pending(current)) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule_timeout(duration);
+	}
+	tty->driver->break_ctl(tty, 0);
+	if (signal_pending(current))
+		return -EINTR;
+	return 0;
+}
+
+static int
+tty_tiocmget(struct tty_struct *tty, struct file *file, int __user *p)
+{
+	int retval = -EINVAL;
+
+	if (tty->driver->tiocmget) {
+		retval = tty->driver->tiocmget(tty, file);
+
+		if (retval >= 0)
+			retval = put_user(retval, p);
+	}
+	return retval;
+}
+
+static int
+tty_tiocmset(struct tty_struct *tty, struct file *file, unsigned int cmd,
+	     unsigned __user *p)
+{
+	int retval = -EINVAL;
+
+	if (tty->driver->tiocmset) {
+		unsigned int set, clear, val;
+
+		retval = get_user(val, p);
+		if (retval)
+			return retval;
+
+		set = clear = 0;
+		switch (cmd) {
+		case TIOCMBIS:
+			set = val;
+			break;
+		case TIOCMBIC:
+			clear = val;
+			break;
+		case TIOCMSET:
+			set = val;
+			clear = ~val;
+			break;
+		}
+
+		set &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP;
+		clear &= TIOCM_DTR|TIOCM_RTS|TIOCM_OUT1|TIOCM_OUT2|TIOCM_LOOP;
+
+		retval = tty->driver->tiocmset(tty, file, set, clear);
+	}
+	return retval;
+}
+
+/*
+ * Split this up, as gcc can choke on it otherwise..
+ */
+int tty_ioctl(struct inode * inode, struct file * file,
+	      unsigned int cmd, unsigned long arg)
+{
+	struct tty_struct *tty, *real_tty;
+	void __user *p = (void __user *)arg;
+	int retval;
+	struct tty_ldisc *ld;
+	
+	tty = (struct tty_struct *)file->private_data;
+	if (tty_paranoia_check(tty, inode, "tty_ioctl"))
+		return -EINVAL;
+
+	real_tty = tty;
+	if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+	    tty->driver->subtype == PTY_TYPE_MASTER)
+		real_tty = tty->link;
+
+	/*
+	 * Break handling by driver
+	 */
+	if (!tty->driver->break_ctl) {
+		switch(cmd) {
+		case TIOCSBRK:
+		case TIOCCBRK:
+			if (tty->driver->ioctl)
+				return tty->driver->ioctl(tty, file, cmd, arg);
+			return -EINVAL;
+			
+		/* These two ioctl's always return success; even if */
+		/* the driver doesn't support them. */
+		case TCSBRK:
+		case TCSBRKP:
+			if (!tty->driver->ioctl)
+				return 0;
+			retval = tty->driver->ioctl(tty, file, cmd, arg);
+			if (retval == -ENOIOCTLCMD)
+				retval = 0;
+			return retval;
+		}
+	}
+
+	/*
+	 * Factor out some common prep work
+	 */
+	switch (cmd) {
+	case TIOCSETD:
+	case TIOCSBRK:
+	case TIOCCBRK:
+	case TCSBRK:
+	case TCSBRKP:			
+		retval = tty_check_change(tty);
+		if (retval)
+			return retval;
+		if (cmd != TIOCCBRK) {
+			tty_wait_until_sent(tty, 0);
+			if (signal_pending(current))
+				return -EINTR;
+		}
+		break;
+	}
+
+	switch (cmd) {
+		case TIOCSTI:
+			return tiocsti(tty, p);
+		case TIOCGWINSZ:
+			return tiocgwinsz(tty, p);
+		case TIOCSWINSZ:
+			return tiocswinsz(tty, real_tty, p);
+		case TIOCCONS:
+			return real_tty!=tty ? -EINVAL : tioccons(file);
+		case FIONBIO:
+			return fionbio(file, p);
+		case TIOCEXCL:
+			set_bit(TTY_EXCLUSIVE, &tty->flags);
+			return 0;
+		case TIOCNXCL:
+			clear_bit(TTY_EXCLUSIVE, &tty->flags);
+			return 0;
+		case TIOCNOTTY:
+			if (current->signal->tty != tty)
+				return -ENOTTY;
+			if (current->signal->leader)
+				disassociate_ctty(0);
+			task_lock(current);
+			current->signal->tty = NULL;
+			task_unlock(current);
+			return 0;
+		case TIOCSCTTY:
+			return tiocsctty(tty, arg);
+		case TIOCGPGRP:
+			return tiocgpgrp(tty, real_tty, p);
+		case TIOCSPGRP:
+			return tiocspgrp(tty, real_tty, p);
+		case TIOCGSID:
+			return tiocgsid(tty, real_tty, p);
+		case TIOCGETD:
+			/* FIXME: check this is ok */
+			return put_user(tty->ldisc.num, (int __user *)p);
+		case TIOCSETD:
+			return tiocsetd(tty, p);
+#ifdef CONFIG_VT
+		case TIOCLINUX:
+			return tioclinux(tty, arg);
+#endif
+		/*
+		 * Break handling
+		 */
+		case TIOCSBRK:	/* Turn break on, unconditionally */
+			tty->driver->break_ctl(tty, -1);
+			return 0;
+			
+		case TIOCCBRK:	/* Turn break off, unconditionally */
+			tty->driver->break_ctl(tty, 0);
+			return 0;
+		case TCSBRK:   /* SVID version: non-zero arg --> no break */
+			/*
+			 * XXX is the above comment correct, or the
+			 * code below correct?  Is this ioctl used at
+			 * all by anyone?
+			 */
+			if (!arg)
+				return send_break(tty, HZ/4);
+			return 0;
+		case TCSBRKP:	/* support for POSIX tcsendbreak() */	
+			return send_break(tty, arg ? arg*(HZ/10) : HZ/4);
+
+		case TIOCMGET:
+			return tty_tiocmget(tty, file, p);
+
+		case TIOCMSET:
+		case TIOCMBIC:
+		case TIOCMBIS:
+			return tty_tiocmset(tty, file, cmd, p);
+	}
+	if (tty->driver->ioctl) {
+		retval = (tty->driver->ioctl)(tty, file, cmd, arg);
+		if (retval != -ENOIOCTLCMD)
+			return retval;
+	}
+	ld = tty_ldisc_ref_wait(tty);
+	retval = -EINVAL;
+	if (ld->ioctl) {
+		retval = ld->ioctl(tty, file, cmd, arg);
+		if (retval == -ENOIOCTLCMD)
+			retval = -EINVAL;
+	}
+	tty_ldisc_deref(ld);
+	return retval;
+}
+
+
+/*
+ * This implements the "Secure Attention Key" ---  the idea is to
+ * prevent trojan horses by killing all processes associated with this
+ * tty when the user hits the "Secure Attention Key".  Required for
+ * super-paranoid applications --- see the Orange Book for more details.
+ * 
+ * This code could be nicer; ideally it should send a HUP, wait a few
+ * seconds, then send a INT, and then a KILL signal.  But you then
+ * have to coordinate with the init process, since all processes associated
+ * with the current tty must be dead before the new getty is allowed
+ * to spawn.
+ *
+ * Now, if it would be correct ;-/ The current code has a nasty hole -
+ * it doesn't catch files in flight. We may send the descriptor to ourselves
+ * via AF_UNIX socket, close it and later fetch from socket. FIXME.
+ *
+ * Nasty bug: do_SAK is being called in interrupt context.  This can
+ * deadlock.  We punt it up to process context.  AKPM - 16Mar2001
+ */
+static void __do_SAK(void *arg)
+{
+#ifdef TTY_SOFT_SAK
+	tty_hangup(tty);
+#else
+	struct tty_struct *tty = arg;
+	struct task_struct *p;
+	int session;
+	int		i;
+	struct file	*filp;
+	struct tty_ldisc *disc;
+	
+	if (!tty)
+		return;
+	session  = tty->session;
+	
+	/* We don't want an ldisc switch during this */
+	disc = tty_ldisc_ref(tty);
+	if (disc && disc->flush_buffer)
+		disc->flush_buffer(tty);
+	tty_ldisc_deref(disc);
+
+	if (tty->driver->flush_buffer)
+		tty->driver->flush_buffer(tty);
+	
+	read_lock(&tasklist_lock);
+	do_each_task_pid(session, PIDTYPE_SID, p) {
+		if (p->signal->tty == tty || session > 0) {
+			printk(KERN_NOTICE "SAK: killed process %d"
+			    " (%s): p->signal->session==tty->session\n",
+			    p->pid, p->comm);
+			send_sig(SIGKILL, p, 1);
+			continue;
+		}
+		task_lock(p);
+		if (p->files) {
+			spin_lock(&p->files->file_lock);
+			for (i=0; i < p->files->max_fds; i++) {
+				filp = fcheck_files(p->files, i);
+				if (!filp)
+					continue;
+				if (filp->f_op->read == tty_read &&
+				    filp->private_data == tty) {
+					printk(KERN_NOTICE "SAK: killed process %d"
+					    " (%s): fd#%d opened to the tty\n",
+					    p->pid, p->comm, i);
+					send_sig(SIGKILL, p, 1);
+					break;
+				}
+			}
+			spin_unlock(&p->files->file_lock);
+		}
+		task_unlock(p);
+	} while_each_task_pid(session, PIDTYPE_SID, p);
+	read_unlock(&tasklist_lock);
+#endif
+}
+
+/*
+ * The tq handling here is a little racy - tty->SAK_work may already be queued.
+ * Fortunately we don't need to worry, because if ->SAK_work is already queued,
+ * the values which we write to it will be identical to the values which it
+ * already has. --akpm
+ */
+void do_SAK(struct tty_struct *tty)
+{
+	if (!tty)
+		return;
+	PREPARE_WORK(&tty->SAK_work, __do_SAK, tty);
+	schedule_work(&tty->SAK_work);
+}
+
+EXPORT_SYMBOL(do_SAK);
+
+/*
+ * This routine is called out of the software interrupt to flush data
+ * from the flip buffer to the line discipline. 
+ */
+ 
+static void flush_to_ldisc(void *private_)
+{
+	struct tty_struct *tty = (struct tty_struct *) private_;
+	unsigned char	*cp;
+	char		*fp;
+	int		count;
+	unsigned long 	flags;
+	struct tty_ldisc *disc;
+
+	disc = tty_ldisc_ref(tty);
+	if (disc == NULL)	/*  !TTY_LDISC */
+		return;
+
+	if (test_bit(TTY_DONT_FLIP, &tty->flags)) {
+		/*
+		 * Do it after the next timer tick:
+		 */
+		schedule_delayed_work(&tty->flip.work, 1);
+		goto out;
+	}
+	spin_lock_irqsave(&tty->read_lock, flags);
+	if (tty->flip.buf_num) {
+		cp = tty->flip.char_buf + TTY_FLIPBUF_SIZE;
+		fp = tty->flip.flag_buf + TTY_FLIPBUF_SIZE;
+		tty->flip.buf_num = 0;
+		tty->flip.char_buf_ptr = tty->flip.char_buf;
+		tty->flip.flag_buf_ptr = tty->flip.flag_buf;
+	} else {
+		cp = tty->flip.char_buf;
+		fp = tty->flip.flag_buf;
+		tty->flip.buf_num = 1;
+		tty->flip.char_buf_ptr = tty->flip.char_buf + TTY_FLIPBUF_SIZE;
+		tty->flip.flag_buf_ptr = tty->flip.flag_buf + TTY_FLIPBUF_SIZE;
+	}
+	count = tty->flip.count;
+	tty->flip.count = 0;
+	spin_unlock_irqrestore(&tty->read_lock, flags);
+
+	disc->receive_buf(tty, cp, fp, count);
+out:
+	tty_ldisc_deref(disc);
+}
+
+/*
+ * Routine which returns the baud rate of the tty
+ *
+ * Note that the baud_table needs to be kept in sync with the
+ * include/asm/termbits.h file.
+ */
+static int baud_table[] = {
+	0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+	9600, 19200, 38400, 57600, 115200, 230400, 460800,
+#ifdef __sparc__
+	76800, 153600, 307200, 614400, 921600
+#else
+	500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000,
+	2500000, 3000000, 3500000, 4000000
+#endif
+};
+
+static int n_baud_table = ARRAY_SIZE(baud_table);
+
+/**
+ *	tty_termios_baud_rate
+ *	@termios: termios structure
+ *
+ *	Convert termios baud rate data into a speed. This should be called
+ *	with the termios lock held if this termios is a terminal termios
+ *	structure. May change the termios data.
+ */
+ 
+int tty_termios_baud_rate(struct termios *termios)
+{
+	unsigned int cbaud;
+	
+	cbaud = termios->c_cflag & CBAUD;
+
+	if (cbaud & CBAUDEX) {
+		cbaud &= ~CBAUDEX;
+
+		if (cbaud < 1 || cbaud + 15 > n_baud_table)
+			termios->c_cflag &= ~CBAUDEX;
+		else
+			cbaud += 15;
+	}
+	return baud_table[cbaud];
+}
+
+EXPORT_SYMBOL(tty_termios_baud_rate);
+
+/**
+ *	tty_get_baud_rate	-	get tty bit rates
+ *	@tty: tty to query
+ *
+ *	Returns the baud rate as an integer for this terminal. The
+ *	termios lock must be held by the caller and the terminal bit
+ *	flags may be updated.
+ */
+ 
+int tty_get_baud_rate(struct tty_struct *tty)
+{
+	int baud = tty_termios_baud_rate(tty->termios);
+
+	if (baud == 38400 && tty->alt_speed) {
+		if (!tty->warned) {
+			printk(KERN_WARNING "Use of setserial/setrocket to "
+					    "set SPD_* flags is deprecated\n");
+			tty->warned = 1;
+		}
+		baud = tty->alt_speed;
+	}
+	
+	return baud;
+}
+
+EXPORT_SYMBOL(tty_get_baud_rate);
+
+/**
+ *	tty_flip_buffer_push	-	terminal
+ *	@tty: tty to push
+ *
+ *	Queue a push of the terminal flip buffers to the line discipline. This
+ *	function must not be called from IRQ context if tty->low_latency is set.
+ *
+ *	In the event of the queue being busy for flipping the work will be
+ *	held off and retried later.
+ */
+
+void tty_flip_buffer_push(struct tty_struct *tty)
+{
+	if (tty->low_latency)
+		flush_to_ldisc((void *) tty);
+	else
+		schedule_delayed_work(&tty->flip.work, 1);
+}
+
+EXPORT_SYMBOL(tty_flip_buffer_push);
+
+/*
+ * This subroutine initializes a tty structure.
+ */
+static void initialize_tty_struct(struct tty_struct *tty)
+{
+	memset(tty, 0, sizeof(struct tty_struct));
+	tty->magic = TTY_MAGIC;
+	tty_ldisc_assign(tty, tty_ldisc_get(N_TTY));
+	tty->pgrp = -1;
+	tty->overrun_time = jiffies;
+	tty->flip.char_buf_ptr = tty->flip.char_buf;
+	tty->flip.flag_buf_ptr = tty->flip.flag_buf;
+	INIT_WORK(&tty->flip.work, flush_to_ldisc, tty);
+	init_MUTEX(&tty->flip.pty_sem);
+	init_MUTEX(&tty->termios_sem);
+	init_waitqueue_head(&tty->write_wait);
+	init_waitqueue_head(&tty->read_wait);
+	INIT_WORK(&tty->hangup_work, do_tty_hangup, tty);
+	sema_init(&tty->atomic_read, 1);
+	sema_init(&tty->atomic_write, 1);
+	spin_lock_init(&tty->read_lock);
+	INIT_LIST_HEAD(&tty->tty_files);
+	INIT_WORK(&tty->SAK_work, NULL, NULL);
+}
+
+/*
+ * The default put_char routine if the driver did not define one.
+ */
+static void tty_default_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	tty->driver->write(tty, &ch, 1);
+}
+
+static struct class_simple *tty_class;
+
+/**
+ * tty_register_device - register a tty device
+ * @driver: the tty driver that describes the tty device
+ * @index: the index in the tty driver for this tty device
+ * @device: a struct device that is associated with this tty device.
+ *	This field is optional, if there is no known struct device for this
+ *	tty device it can be set to NULL safely.
+ *
+ * This call is required to be made to register an individual tty device if
+ * the tty driver's flags have the TTY_DRIVER_NO_DEVFS bit set.  If that
+ * bit is not set, this function should not be called.
+ */
+void tty_register_device(struct tty_driver *driver, unsigned index,
+			 struct device *device)
+{
+	char name[64];
+	dev_t dev = MKDEV(driver->major, driver->minor_start) + index;
+
+	if (index >= driver->num) {
+		printk(KERN_ERR "Attempt to register invalid tty line number "
+		       " (%d).\n", index);
+		return;
+	}
+
+	devfs_mk_cdev(dev, S_IFCHR | S_IRUSR | S_IWUSR,
+			"%s%d", driver->devfs_name, index + driver->name_base);
+
+	if (driver->type == TTY_DRIVER_TYPE_PTY)
+		pty_line_name(driver, index, name);
+	else
+		tty_line_name(driver, index, name);
+	class_simple_device_add(tty_class, dev, device, name);
+}
+
+/**
+ * tty_unregister_device - unregister a tty device
+ * @driver: the tty driver that describes the tty device
+ * @index: the index in the tty driver for this tty device
+ *
+ * If a tty device is registered with a call to tty_register_device() then
+ * this function must be made when the tty device is gone.
+ */
+void tty_unregister_device(struct tty_driver *driver, unsigned index)
+{
+	devfs_remove("%s%d", driver->devfs_name, index + driver->name_base);
+	class_simple_device_remove(MKDEV(driver->major, driver->minor_start) + index);
+}
+
+EXPORT_SYMBOL(tty_register_device);
+EXPORT_SYMBOL(tty_unregister_device);
+
+struct tty_driver *alloc_tty_driver(int lines)
+{
+	struct tty_driver *driver;
+
+	driver = kmalloc(sizeof(struct tty_driver), GFP_KERNEL);
+	if (driver) {
+		memset(driver, 0, sizeof(struct tty_driver));
+		driver->magic = TTY_DRIVER_MAGIC;
+		driver->num = lines;
+		/* later we'll move allocation of tables here */
+	}
+	return driver;
+}
+
+void put_tty_driver(struct tty_driver *driver)
+{
+	kfree(driver);
+}
+
+void tty_set_operations(struct tty_driver *driver, struct tty_operations *op)
+{
+	driver->open = op->open;
+	driver->close = op->close;
+	driver->write = op->write;
+	driver->put_char = op->put_char;
+	driver->flush_chars = op->flush_chars;
+	driver->write_room = op->write_room;
+	driver->chars_in_buffer = op->chars_in_buffer;
+	driver->ioctl = op->ioctl;
+	driver->set_termios = op->set_termios;
+	driver->throttle = op->throttle;
+	driver->unthrottle = op->unthrottle;
+	driver->stop = op->stop;
+	driver->start = op->start;
+	driver->hangup = op->hangup;
+	driver->break_ctl = op->break_ctl;
+	driver->flush_buffer = op->flush_buffer;
+	driver->set_ldisc = op->set_ldisc;
+	driver->wait_until_sent = op->wait_until_sent;
+	driver->send_xchar = op->send_xchar;
+	driver->read_proc = op->read_proc;
+	driver->write_proc = op->write_proc;
+	driver->tiocmget = op->tiocmget;
+	driver->tiocmset = op->tiocmset;
+}
+
+
+EXPORT_SYMBOL(alloc_tty_driver);
+EXPORT_SYMBOL(put_tty_driver);
+EXPORT_SYMBOL(tty_set_operations);
+
+/*
+ * Called by a tty driver to register itself.
+ */
+int tty_register_driver(struct tty_driver *driver)
+{
+	int error;
+        int i;
+	dev_t dev;
+	void **p = NULL;
+
+	if (driver->flags & TTY_DRIVER_INSTALLED)
+		return 0;
+
+	if (!(driver->flags & TTY_DRIVER_DEVPTS_MEM)) {
+		p = kmalloc(driver->num * 3 * sizeof(void *), GFP_KERNEL);
+		if (!p)
+			return -ENOMEM;
+		memset(p, 0, driver->num * 3 * sizeof(void *));
+	}
+
+	if (!driver->major) {
+		error = alloc_chrdev_region(&dev, driver->minor_start, driver->num,
+						(char*)driver->name);
+		if (!error) {
+			driver->major = MAJOR(dev);
+			driver->minor_start = MINOR(dev);
+		}
+	} else {
+		dev = MKDEV(driver->major, driver->minor_start);
+		error = register_chrdev_region(dev, driver->num,
+						(char*)driver->name);
+	}
+	if (error < 0) {
+		kfree(p);
+		return error;
+	}
+
+	if (p) {
+		driver->ttys = (struct tty_struct **)p;
+		driver->termios = (struct termios **)(p + driver->num);
+		driver->termios_locked = (struct termios **)(p + driver->num * 2);
+	} else {
+		driver->ttys = NULL;
+		driver->termios = NULL;
+		driver->termios_locked = NULL;
+	}
+
+	cdev_init(&driver->cdev, &tty_fops);
+	driver->cdev.owner = driver->owner;
+	error = cdev_add(&driver->cdev, dev, driver->num);
+	if (error) {
+		cdev_del(&driver->cdev);
+		unregister_chrdev_region(dev, driver->num);
+		driver->ttys = NULL;
+		driver->termios = driver->termios_locked = NULL;
+		kfree(p);
+		return error;
+	}
+
+	if (!driver->put_char)
+		driver->put_char = tty_default_put_char;
+	
+	list_add(&driver->tty_drivers, &tty_drivers);
+	
+	if ( !(driver->flags & TTY_DRIVER_NO_DEVFS) ) {
+		for(i = 0; i < driver->num; i++)
+		    tty_register_device(driver, i, NULL);
+	}
+	proc_tty_register_driver(driver);
+	return 0;
+}
+
+EXPORT_SYMBOL(tty_register_driver);
+
+/*
+ * Called by a tty driver to unregister itself.
+ */
+int tty_unregister_driver(struct tty_driver *driver)
+{
+	int i;
+	struct termios *tp;
+	void *p;
+
+	if (driver->refcount)
+		return -EBUSY;
+
+	unregister_chrdev_region(MKDEV(driver->major, driver->minor_start),
+				driver->num);
+
+	list_del(&driver->tty_drivers);
+
+	/*
+	 * Free the termios and termios_locked structures because
+	 * we don't want to get memory leaks when modular tty
+	 * drivers are removed from the kernel.
+	 */
+	for (i = 0; i < driver->num; i++) {
+		tp = driver->termios[i];
+		if (tp) {
+			driver->termios[i] = NULL;
+			kfree(tp);
+		}
+		tp = driver->termios_locked[i];
+		if (tp) {
+			driver->termios_locked[i] = NULL;
+			kfree(tp);
+		}
+		if (!(driver->flags & TTY_DRIVER_NO_DEVFS))
+			tty_unregister_device(driver, i);
+	}
+	p = driver->ttys;
+	proc_tty_unregister_driver(driver);
+	driver->ttys = NULL;
+	driver->termios = driver->termios_locked = NULL;
+	kfree(p);
+	cdev_del(&driver->cdev);
+	return 0;
+}
+
+EXPORT_SYMBOL(tty_unregister_driver);
+
+
+/*
+ * Initialize the console device. This is called *early*, so
+ * we can't necessarily depend on lots of kernel help here.
+ * Just do some early initializations, and do the complex setup
+ * later.
+ */
+void __init console_init(void)
+{
+	initcall_t *call;
+
+	/* Setup the default TTY line discipline. */
+	(void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
+
+	/*
+	 * set up the console device so that later boot sequences can 
+	 * inform about problems etc..
+	 */
+#ifdef CONFIG_EARLY_PRINTK
+	disable_early_printk();
+#endif
+#ifdef CONFIG_SERIAL_68360
+ 	/* This is not a console initcall. I know not what it's doing here.
+	   So I haven't moved it. dwmw2 */
+        rs_360_init();
+#endif
+	call = __con_initcall_start;
+	while (call < __con_initcall_end) {
+		(*call)();
+		call++;
+	}
+}
+
+#ifdef CONFIG_VT
+extern int vty_init(void);
+#endif
+
+static int __init tty_class_init(void)
+{
+	tty_class = class_simple_create(THIS_MODULE, "tty");
+	if (IS_ERR(tty_class))
+		return PTR_ERR(tty_class);
+	return 0;
+}
+
+postcore_initcall(tty_class_init);
+
+/* 3/2004 jmc: why do these devices exist? */
+
+static struct cdev tty_cdev, console_cdev;
+#ifdef CONFIG_UNIX98_PTYS
+static struct cdev ptmx_cdev;
+#endif
+#ifdef CONFIG_VT
+static struct cdev vc0_cdev;
+#endif
+
+/*
+ * Ok, now we can initialize the rest of the tty devices and can count
+ * on memory allocations, interrupts etc..
+ */
+static int __init tty_init(void)
+{
+	cdev_init(&tty_cdev, &tty_fops);
+	if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
+	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
+		panic("Couldn't register /dev/tty driver\n");
+	devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 0), S_IFCHR|S_IRUGO|S_IWUGO, "tty");
+	class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");
+
+	cdev_init(&console_cdev, &console_fops);
+	if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
+	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
+		panic("Couldn't register /dev/console driver\n");
+	devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 1), S_IFCHR|S_IRUSR|S_IWUSR, "console");
+	class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 1), NULL, "console");
+
+#ifdef CONFIG_UNIX98_PTYS
+	cdev_init(&ptmx_cdev, &ptmx_fops);
+	if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
+	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
+		panic("Couldn't register /dev/ptmx driver\n");
+	devfs_mk_cdev(MKDEV(TTYAUX_MAJOR, 2), S_IFCHR|S_IRUGO|S_IWUGO, "ptmx");
+	class_simple_device_add(tty_class, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
+#endif
+
+#ifdef CONFIG_VT
+	cdev_init(&vc0_cdev, &console_fops);
+	if (cdev_add(&vc0_cdev, MKDEV(TTY_MAJOR, 0), 1) ||
+	    register_chrdev_region(MKDEV(TTY_MAJOR, 0), 1, "/dev/vc/0") < 0)
+		panic("Couldn't register /dev/tty0 driver\n");
+	devfs_mk_cdev(MKDEV(TTY_MAJOR, 0), S_IFCHR|S_IRUSR|S_IWUSR, "vc/0");
+	class_simple_device_add(tty_class, MKDEV(TTY_MAJOR, 0), NULL, "tty0");
+
+	vty_init();
+#endif
+	return 0;
+}
+module_init(tty_init);
diff --git a/drivers/char/tty_ioctl.c b/drivers/char/tty_ioctl.c
new file mode 100644
index 0000000..5859799
--- /dev/null
+++ b/drivers/char/tty_ioctl.c
@@ -0,0 +1,551 @@
+/*
+ *  linux/drivers/char/tty_ioctl.c
+ *
+ *  Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
+ *
+ * Modified by Fred N. van Kempen, 01/29/93, to add line disciplines
+ * which can be dynamically activated and de-activated by the line
+ * discipline handling modules (like SLIP).
+ */
+
+#include <linux/types.h>
+#include <linux/termios.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/tty.h>
+#include <linux/fcntl.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#undef TTY_DEBUG_WAIT_UNTIL_SENT
+
+#undef	DEBUG
+
+/*
+ * Internal flag options for termios setting behavior
+ */
+#define TERMIOS_FLUSH	1
+#define TERMIOS_WAIT	2
+#define TERMIOS_TERMIO	4
+
+void tty_wait_until_sent(struct tty_struct * tty, long timeout)
+{
+	DECLARE_WAITQUEUE(wait, current);
+
+#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
+	char buf[64];
+	
+	printk(KERN_DEBUG "%s wait until sent...\n", tty_name(tty, buf));
+#endif
+	if (!tty->driver->chars_in_buffer)
+		return;
+	add_wait_queue(&tty->write_wait, &wait);
+	if (!timeout)
+		timeout = MAX_SCHEDULE_TIMEOUT;
+	do {
+#ifdef TTY_DEBUG_WAIT_UNTIL_SENT
+		printk(KERN_DEBUG "waiting %s...(%d)\n", tty_name(tty, buf),
+		       tty->driver->chars_in_buffer(tty));
+#endif
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (signal_pending(current))
+			goto stop_waiting;
+		if (!tty->driver->chars_in_buffer(tty))
+			break;
+		timeout = schedule_timeout(timeout);
+	} while (timeout);
+	if (tty->driver->wait_until_sent)
+		tty->driver->wait_until_sent(tty, timeout);
+stop_waiting:
+	set_current_state(TASK_RUNNING);
+	remove_wait_queue(&tty->write_wait, &wait);
+}
+
+EXPORT_SYMBOL(tty_wait_until_sent);
+
+static void unset_locked_termios(struct termios *termios,
+				 struct termios *old,
+				 struct termios *locked)
+{
+	int	i;
+	
+#define NOSET_MASK(x,y,z) (x = ((x) & ~(z)) | ((y) & (z)))
+
+	if (!locked) {
+		printk(KERN_WARNING "Warning?!? termios_locked is NULL.\n");
+		return;
+	}
+
+	NOSET_MASK(termios->c_iflag, old->c_iflag, locked->c_iflag);
+	NOSET_MASK(termios->c_oflag, old->c_oflag, locked->c_oflag);
+	NOSET_MASK(termios->c_cflag, old->c_cflag, locked->c_cflag);
+	NOSET_MASK(termios->c_lflag, old->c_lflag, locked->c_lflag);
+	termios->c_line = locked->c_line ? old->c_line : termios->c_line;
+	for (i=0; i < NCCS; i++)
+		termios->c_cc[i] = locked->c_cc[i] ?
+			old->c_cc[i] : termios->c_cc[i];
+}
+
+static void change_termios(struct tty_struct * tty, struct termios * new_termios)
+{
+	int canon_change;
+	struct termios old_termios = *tty->termios;
+	struct tty_ldisc *ld;
+	
+	/*
+	 *	Perform the actual termios internal changes under lock.
+	 */
+	 
+
+	/* FIXME: we need to decide on some locking/ordering semantics
+	   for the set_termios notification eventually */
+	down(&tty->termios_sem);
+
+	*tty->termios = *new_termios;
+	unset_locked_termios(tty->termios, &old_termios, tty->termios_locked);
+	canon_change = (old_termios.c_lflag ^ tty->termios->c_lflag) & ICANON;
+	if (canon_change) {
+		memset(&tty->read_flags, 0, sizeof tty->read_flags);
+		tty->canon_head = tty->read_tail;
+		tty->canon_data = 0;
+		tty->erasing = 0;
+	}
+	
+	
+	if (canon_change && !L_ICANON(tty) && tty->read_cnt)
+		/* Get characters left over from canonical mode. */
+		wake_up_interruptible(&tty->read_wait);
+
+	/* See if packet mode change of state. */
+
+	if (tty->link && tty->link->packet) {
+		int old_flow = ((old_termios.c_iflag & IXON) &&
+				(old_termios.c_cc[VSTOP] == '\023') &&
+				(old_termios.c_cc[VSTART] == '\021'));
+		int new_flow = (I_IXON(tty) &&
+				STOP_CHAR(tty) == '\023' &&
+				START_CHAR(tty) == '\021');
+		if (old_flow != new_flow) {
+			tty->ctrl_status &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP);
+			if (new_flow)
+				tty->ctrl_status |= TIOCPKT_DOSTOP;
+			else
+				tty->ctrl_status |= TIOCPKT_NOSTOP;
+			wake_up_interruptible(&tty->link->read_wait);
+		}
+	}
+	   
+	if (tty->driver->set_termios)
+		(*tty->driver->set_termios)(tty, &old_termios);
+
+	ld = tty_ldisc_ref(tty);
+	if (ld != NULL) {
+		if (ld->set_termios)
+			(ld->set_termios)(tty, &old_termios);
+		tty_ldisc_deref(ld);
+	}
+	up(&tty->termios_sem);
+}
+
+static int set_termios(struct tty_struct * tty, void __user *arg, int opt)
+{
+	struct termios tmp_termios;
+	struct tty_ldisc *ld;
+	int retval = tty_check_change(tty);
+
+	if (retval)
+		return retval;
+
+	if (opt & TERMIOS_TERMIO) {
+		memcpy(&tmp_termios, tty->termios, sizeof(struct termios));
+		if (user_termio_to_kernel_termios(&tmp_termios,
+						(struct termio __user *)arg))
+			return -EFAULT;
+	} else {
+		if (user_termios_to_kernel_termios(&tmp_termios,
+						(struct termios __user *)arg))
+			return -EFAULT;
+	}
+
+	ld = tty_ldisc_ref(tty);
+	
+	if (ld != NULL) {
+		if ((opt & TERMIOS_FLUSH) && ld->flush_buffer)
+			ld->flush_buffer(tty);
+		tty_ldisc_deref(ld);
+	}
+	
+	if (opt & TERMIOS_WAIT) {
+		tty_wait_until_sent(tty, 0);
+		if (signal_pending(current))
+			return -EINTR;
+	}
+
+	change_termios(tty, &tmp_termios);
+	return 0;
+}
+
+static int get_termio(struct tty_struct * tty, struct termio __user * termio)
+{
+	if (kernel_termios_to_user_termio(termio, tty->termios))
+		return -EFAULT;
+	return 0;
+}
+
+static unsigned long inq_canon(struct tty_struct * tty)
+{
+	int nr, head, tail;
+
+	if (!tty->canon_data || !tty->read_buf)
+		return 0;
+	head = tty->canon_head;
+	tail = tty->read_tail;
+	nr = (head - tail) & (N_TTY_BUF_SIZE-1);
+	/* Skip EOF-chars.. */
+	while (head != tail) {
+		if (test_bit(tail, tty->read_flags) &&
+		    tty->read_buf[tail] == __DISABLED_CHAR)
+			nr--;
+		tail = (tail+1) & (N_TTY_BUF_SIZE-1);
+	}
+	return nr;
+}
+
+#ifdef TIOCGETP
+/*
+ * These are deprecated, but there is limited support..
+ *
+ * The "sg_flags" translation is a joke..
+ */
+static int get_sgflags(struct tty_struct * tty)
+{
+	int flags = 0;
+
+	if (!(tty->termios->c_lflag & ICANON)) {
+		if (tty->termios->c_lflag & ISIG)
+			flags |= 0x02;		/* cbreak */
+		else
+			flags |= 0x20;		/* raw */
+	}
+	if (tty->termios->c_lflag & ECHO)
+		flags |= 0x08;			/* echo */
+	if (tty->termios->c_oflag & OPOST)
+		if (tty->termios->c_oflag & ONLCR)
+			flags |= 0x10;		/* crmod */
+	return flags;
+}
+
+static int get_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
+{
+	struct sgttyb tmp;
+
+	down(&tty->termios_sem);
+	tmp.sg_ispeed = 0;
+	tmp.sg_ospeed = 0;
+	tmp.sg_erase = tty->termios->c_cc[VERASE];
+	tmp.sg_kill = tty->termios->c_cc[VKILL];
+	tmp.sg_flags = get_sgflags(tty);
+	up(&tty->termios_sem);
+	
+	return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0;
+}
+
+static void set_sgflags(struct termios * termios, int flags)
+{
+	termios->c_iflag = ICRNL | IXON;
+	termios->c_oflag = 0;
+	termios->c_lflag = ISIG | ICANON;
+	if (flags & 0x02) {	/* cbreak */
+		termios->c_iflag = 0;
+		termios->c_lflag &= ~ICANON;
+	}
+	if (flags & 0x08) {		/* echo */
+		termios->c_lflag |= ECHO | ECHOE | ECHOK |
+				    ECHOCTL | ECHOKE | IEXTEN;
+	}
+	if (flags & 0x10) {		/* crmod */
+		termios->c_oflag |= OPOST | ONLCR;
+	}
+	if (flags & 0x20) {	/* raw */
+		termios->c_iflag = 0;
+		termios->c_lflag &= ~(ISIG | ICANON);
+	}
+	if (!(termios->c_lflag & ICANON)) {
+		termios->c_cc[VMIN] = 1;
+		termios->c_cc[VTIME] = 0;
+	}
+}
+
+static int set_sgttyb(struct tty_struct * tty, struct sgttyb __user * sgttyb)
+{
+	int retval;
+	struct sgttyb tmp;
+	struct termios termios;
+
+	retval = tty_check_change(tty);
+	if (retval)
+		return retval;
+	
+	if (copy_from_user(&tmp, sgttyb, sizeof(tmp)))
+		return -EFAULT;
+
+	down(&tty->termios_sem);		
+	termios =  *tty->termios;
+	termios.c_cc[VERASE] = tmp.sg_erase;
+	termios.c_cc[VKILL] = tmp.sg_kill;
+	set_sgflags(&termios, tmp.sg_flags);
+	up(&tty->termios_sem);
+	change_termios(tty, &termios);
+	return 0;
+}
+#endif
+
+#ifdef TIOCGETC
+static int get_tchars(struct tty_struct * tty, struct tchars __user * tchars)
+{
+	struct tchars tmp;
+
+	tmp.t_intrc = tty->termios->c_cc[VINTR];
+	tmp.t_quitc = tty->termios->c_cc[VQUIT];
+	tmp.t_startc = tty->termios->c_cc[VSTART];
+	tmp.t_stopc = tty->termios->c_cc[VSTOP];
+	tmp.t_eofc = tty->termios->c_cc[VEOF];
+	tmp.t_brkc = tty->termios->c_cc[VEOL2];	/* what is brkc anyway? */
+	return copy_to_user(tchars, &tmp, sizeof(tmp)) ? -EFAULT : 0;
+}
+
+static int set_tchars(struct tty_struct * tty, struct tchars __user * tchars)
+{
+	struct tchars tmp;
+
+	if (copy_from_user(&tmp, tchars, sizeof(tmp)))
+		return -EFAULT;
+	tty->termios->c_cc[VINTR] = tmp.t_intrc;
+	tty->termios->c_cc[VQUIT] = tmp.t_quitc;
+	tty->termios->c_cc[VSTART] = tmp.t_startc;
+	tty->termios->c_cc[VSTOP] = tmp.t_stopc;
+	tty->termios->c_cc[VEOF] = tmp.t_eofc;
+	tty->termios->c_cc[VEOL2] = tmp.t_brkc;	/* what is brkc anyway? */
+	return 0;
+}
+#endif
+
+#ifdef TIOCGLTC
+static int get_ltchars(struct tty_struct * tty, struct ltchars __user * ltchars)
+{
+	struct ltchars tmp;
+
+	tmp.t_suspc = tty->termios->c_cc[VSUSP];
+	tmp.t_dsuspc = tty->termios->c_cc[VSUSP];	/* what is dsuspc anyway? */
+	tmp.t_rprntc = tty->termios->c_cc[VREPRINT];
+	tmp.t_flushc = tty->termios->c_cc[VEOL2];	/* what is flushc anyway? */
+	tmp.t_werasc = tty->termios->c_cc[VWERASE];
+	tmp.t_lnextc = tty->termios->c_cc[VLNEXT];
+	return copy_to_user(ltchars, &tmp, sizeof(tmp)) ? -EFAULT : 0;
+}
+
+static int set_ltchars(struct tty_struct * tty, struct ltchars __user * ltchars)
+{
+	struct ltchars tmp;
+
+	if (copy_from_user(&tmp, ltchars, sizeof(tmp)))
+		return -EFAULT;
+
+	tty->termios->c_cc[VSUSP] = tmp.t_suspc;
+	tty->termios->c_cc[VEOL2] = tmp.t_dsuspc;	/* what is dsuspc anyway? */
+	tty->termios->c_cc[VREPRINT] = tmp.t_rprntc;
+	tty->termios->c_cc[VEOL2] = tmp.t_flushc;	/* what is flushc anyway? */
+	tty->termios->c_cc[VWERASE] = tmp.t_werasc;
+	tty->termios->c_cc[VLNEXT] = tmp.t_lnextc;
+	return 0;
+}
+#endif
+
+/*
+ * Send a high priority character to the tty.
+ */
+static void send_prio_char(struct tty_struct *tty, char ch)
+{
+	int	was_stopped = tty->stopped;
+
+	if (tty->driver->send_xchar) {
+		tty->driver->send_xchar(tty, ch);
+		return;
+	}
+	if (was_stopped)
+		start_tty(tty);
+	tty->driver->write(tty, &ch, 1);
+	if (was_stopped)
+		stop_tty(tty);
+}
+
+int n_tty_ioctl(struct tty_struct * tty, struct file * file,
+		       unsigned int cmd, unsigned long arg)
+{
+	struct tty_struct * real_tty;
+	void __user *p = (void __user *)arg;
+	int retval;
+	struct tty_ldisc *ld;
+
+	if (tty->driver->type == TTY_DRIVER_TYPE_PTY &&
+	    tty->driver->subtype == PTY_TYPE_MASTER)
+		real_tty = tty->link;
+	else
+		real_tty = tty;
+
+	switch (cmd) {
+#ifdef TIOCGETP
+		case TIOCGETP:
+			return get_sgttyb(real_tty, (struct sgttyb __user *) arg);
+		case TIOCSETP:
+		case TIOCSETN:
+			return set_sgttyb(real_tty, (struct sgttyb __user *) arg);
+#endif
+#ifdef TIOCGETC
+		case TIOCGETC:
+			return get_tchars(real_tty, p);
+		case TIOCSETC:
+			return set_tchars(real_tty, p);
+#endif
+#ifdef TIOCGLTC
+		case TIOCGLTC:
+			return get_ltchars(real_tty, p);
+		case TIOCSLTC:
+			return set_ltchars(real_tty, p);
+#endif
+		case TCGETS:
+			if (kernel_termios_to_user_termios((struct termios __user *)arg, real_tty->termios))
+				return -EFAULT;
+			return 0;
+		case TCSETSF:
+			return set_termios(real_tty, p,  TERMIOS_FLUSH | TERMIOS_WAIT);
+		case TCSETSW:
+			return set_termios(real_tty, p, TERMIOS_WAIT);
+		case TCSETS:
+			return set_termios(real_tty, p, 0);
+		case TCGETA:
+			return get_termio(real_tty, p);
+		case TCSETAF:
+			return set_termios(real_tty, p, TERMIOS_FLUSH | TERMIOS_WAIT | TERMIOS_TERMIO);
+		case TCSETAW:
+			return set_termios(real_tty, p, TERMIOS_WAIT | TERMIOS_TERMIO);
+		case TCSETA:
+			return set_termios(real_tty, p, TERMIOS_TERMIO);
+		case TCXONC:
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+			switch (arg) {
+			case TCOOFF:
+				if (!tty->flow_stopped) {
+					tty->flow_stopped = 1;
+					stop_tty(tty);
+				}
+				break;
+			case TCOON:
+				if (tty->flow_stopped) {
+					tty->flow_stopped = 0;
+					start_tty(tty);
+				}
+				break;
+			case TCIOFF:
+				if (STOP_CHAR(tty) != __DISABLED_CHAR)
+					send_prio_char(tty, STOP_CHAR(tty));
+				break;
+			case TCION:
+				if (START_CHAR(tty) != __DISABLED_CHAR)
+					send_prio_char(tty, START_CHAR(tty));
+				break;
+			default:
+				return -EINVAL;
+			}
+			return 0;
+		case TCFLSH:
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+				
+			ld = tty_ldisc_ref(tty);
+			switch (arg) {
+			case TCIFLUSH:
+				if (ld->flush_buffer)
+					ld->flush_buffer(tty);
+				break;
+			case TCIOFLUSH:
+				if (ld->flush_buffer)
+					ld->flush_buffer(tty);
+				/* fall through */
+			case TCOFLUSH:
+				if (tty->driver->flush_buffer)
+					tty->driver->flush_buffer(tty);
+				break;
+			default:
+				tty_ldisc_deref(ld);
+				return -EINVAL;
+			}
+			tty_ldisc_deref(ld);
+			return 0;
+		case TIOCOUTQ:
+			return put_user(tty->driver->chars_in_buffer ?
+					tty->driver->chars_in_buffer(tty) : 0,
+					(int __user *) arg);
+		case TIOCINQ:
+			retval = tty->read_cnt;
+			if (L_ICANON(tty))
+				retval = inq_canon(tty);
+			return put_user(retval, (unsigned int __user *) arg);
+		case TIOCGLCKTRMIOS:
+			if (kernel_termios_to_user_termios((struct termios __user *)arg, real_tty->termios_locked))
+				return -EFAULT;
+			return 0;
+
+		case TIOCSLCKTRMIOS:
+			if (!capable(CAP_SYS_ADMIN))
+				return -EPERM;
+			if (user_termios_to_kernel_termios(real_tty->termios_locked, (struct termios __user *) arg))
+				return -EFAULT;
+			return 0;
+
+		case TIOCPKT:
+		{
+			int pktmode;
+
+			if (tty->driver->type != TTY_DRIVER_TYPE_PTY ||
+			    tty->driver->subtype != PTY_TYPE_MASTER)
+				return -ENOTTY;
+			if (get_user(pktmode, (int __user *) arg))
+				return -EFAULT;
+			if (pktmode) {
+				if (!tty->packet) {
+					tty->packet = 1;
+					tty->link->ctrl_status = 0;
+				}
+			} else
+				tty->packet = 0;
+			return 0;
+		}
+		case TIOCGSOFTCAR:
+			return put_user(C_CLOCAL(tty) ? 1 : 0, (int __user *)arg);
+		case TIOCSSOFTCAR:
+			if (get_user(arg, (unsigned int __user *) arg))
+				return -EFAULT;
+			down(&tty->termios_sem);
+			tty->termios->c_cflag =
+				((tty->termios->c_cflag & ~CLOCAL) |
+				 (arg ? CLOCAL : 0));
+			up(&tty->termios_sem);
+			return 0;
+		default:
+			return -ENOIOCTLCMD;
+		}
+}
+
+EXPORT_SYMBOL(n_tty_ioctl);
diff --git a/drivers/char/vc_screen.c b/drivers/char/vc_screen.c
new file mode 100644
index 0000000..7abe405
--- /dev/null
+++ b/drivers/char/vc_screen.c
@@ -0,0 +1,509 @@
+/*
+ * linux/drivers/char/vc_screen.c
+ *
+ * Provide access to virtual console memory.
+ * /dev/vcs0: the screen as it is being viewed right now (possibly scrolled)
+ * /dev/vcsN: the screen of /dev/ttyN (1 <= N <= 63)
+ *            [minor: N]
+ *
+ * /dev/vcsaN: idem, but including attributes, and prefixed with
+ *	the 4 bytes lines,columns,x,y (as screendump used to give).
+ *	Attribute/character pair is in native endianity.
+ *            [minor: N+128]
+ *
+ * This replaces screendump and part of selection, so that the system
+ * administrator can control access using file system permissions.
+ *
+ * aeb@cwi.nl - efter Friedas begravelse - 950211
+ *
+ * machek@k332.feld.cvut.cz - modified not to send characters to wrong console
+ *	 - fixed some fatal off-by-one bugs (0-- no longer == -1 -> looping and looping and looping...)
+ *	 - making it shorter - scr_readw are macros which expand in PRETTY long code
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/vt_kern.h>
+#include <linux/selection.h>
+#include <linux/kbd_kern.h>
+#include <linux/console.h>
+#include <linux/smp_lock.h>
+#include <linux/device.h>
+#include <asm/uaccess.h>
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+
+#undef attr
+#undef org
+#undef addr
+#define HEADER_SIZE	4
+
+static int
+vcs_size(struct inode *inode)
+{
+	int size;
+	int minor = iminor(inode);
+	int currcons = minor & 127;
+	struct vc_data *vc;
+
+	if (currcons == 0)
+		currcons = fg_console;
+	else
+		currcons--;
+	if (!vc_cons_allocated(currcons))
+		return -ENXIO;
+	vc = vc_cons[currcons].d;
+
+	size = vc->vc_rows * vc->vc_cols;
+
+	if (minor & 128)
+		size = 2*size + HEADER_SIZE;
+	return size;
+}
+
+static loff_t vcs_lseek(struct file *file, loff_t offset, int orig)
+{
+	int size;
+
+	down(&con_buf_sem);
+	size = vcs_size(file->f_dentry->d_inode);
+	switch (orig) {
+		default:
+			up(&con_buf_sem);
+			return -EINVAL;
+		case 2:
+			offset += size;
+			break;
+		case 1:
+			offset += file->f_pos;
+		case 0:
+			break;
+	}
+	if (offset < 0 || offset > size) {
+		up(&con_buf_sem);
+		return -EINVAL;
+	}
+	file->f_pos = offset;
+	up(&con_buf_sem);
+	return file->f_pos;
+}
+
+
+static ssize_t
+vcs_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	struct inode *inode = file->f_dentry->d_inode;
+	unsigned int currcons = iminor(inode);
+	struct vc_data *vc;
+	long pos;
+	long viewed, attr, read;
+	int col, maxcol;
+	unsigned short *org = NULL;
+	ssize_t ret;
+
+	down(&con_buf_sem);
+
+	pos = *ppos;
+
+	/* Select the proper current console and verify
+	 * sanity of the situation under the console lock.
+	 */
+	acquire_console_sem();
+
+	attr = (currcons & 128);
+	currcons = (currcons & 127);
+	if (currcons == 0) {
+		currcons = fg_console;
+		viewed = 1;
+	} else {
+		currcons--;
+		viewed = 0;
+	}
+	ret = -ENXIO;
+	if (!vc_cons_allocated(currcons))
+		goto unlock_out;
+	vc = vc_cons[currcons].d;
+
+	ret = -EINVAL;
+	if (pos < 0)
+		goto unlock_out;
+	read = 0;
+	ret = 0;
+	while (count) {
+		char *con_buf0, *con_buf_start;
+		long this_round, size;
+		ssize_t orig_count;
+		long p = pos;
+
+		/* Check whether we are above size each round,
+		 * as copy_to_user at the end of this loop
+		 * could sleep.
+		 */
+		size = vcs_size(inode);
+		if (pos >= size)
+			break;
+		if (count > size - pos)
+			count = size - pos;
+
+		this_round = count;
+		if (this_round > CON_BUF_SIZE)
+			this_round = CON_BUF_SIZE;
+
+		/* Perform the whole read into the local con_buf.
+		 * Then we can drop the console spinlock and safely
+		 * attempt to move it to userspace.
+		 */
+
+		con_buf_start = con_buf0 = con_buf;
+		orig_count = this_round;
+		maxcol = vc->vc_cols;
+		if (!attr) {
+			org = screen_pos(vc, p, viewed);
+			col = p % maxcol;
+			p += maxcol - col;
+			while (this_round-- > 0) {
+				*con_buf0++ = (vcs_scr_readw(vc, org++) & 0xff);
+				if (++col == maxcol) {
+					org = screen_pos(vc, p, viewed);
+					col = 0;
+					p += maxcol;
+				}
+			}
+		} else {
+			if (p < HEADER_SIZE) {
+				size_t tmp_count;
+
+				con_buf0[0] = (char)vc->vc_rows;
+				con_buf0[1] = (char)vc->vc_cols;
+				getconsxy(vc, con_buf0 + 2);
+
+				con_buf_start += p;
+				this_round += p;
+				if (this_round > CON_BUF_SIZE) {
+					this_round = CON_BUF_SIZE;
+					orig_count = this_round - p;
+				}
+
+				tmp_count = HEADER_SIZE;
+				if (tmp_count > this_round)
+					tmp_count = this_round;
+
+				/* Advance state pointers and move on. */
+				this_round -= tmp_count;
+				p = HEADER_SIZE;
+				con_buf0 = con_buf + HEADER_SIZE;
+				/* If this_round >= 0, then p is even... */
+			} else if (p & 1) {
+				/* Skip first byte for output if start address is odd
+				 * Update region sizes up/down depending on free
+				 * space in buffer.
+				 */
+				con_buf_start++;
+				if (this_round < CON_BUF_SIZE)
+					this_round++;
+				else
+					orig_count--;
+			}
+			if (this_round > 0) {
+				unsigned short *tmp_buf = (unsigned short *)con_buf0;
+
+				p -= HEADER_SIZE;
+				p /= 2;
+				col = p % maxcol;
+
+				org = screen_pos(vc, p, viewed);
+				p += maxcol - col;
+
+				/* Buffer has even length, so we can always copy
+				 * character + attribute. We do not copy last byte
+				 * to userspace if this_round is odd.
+				 */
+				this_round = (this_round + 1) >> 1;
+
+				while (this_round) {
+					*tmp_buf++ = vcs_scr_readw(vc, org++);
+					this_round --;
+					if (++col == maxcol) {
+						org = screen_pos(vc, p, viewed);
+						col = 0;
+						p += maxcol;
+					}
+				}
+			}
+		}
+
+		/* Finally, release the console semaphore while we push
+		 * all the data to userspace from our temporary buffer.
+		 *
+		 * AKPM: Even though it's a semaphore, we should drop it because
+		 * the pagefault handling code may want to call printk().
+		 */
+
+		release_console_sem();
+		ret = copy_to_user(buf, con_buf_start, orig_count);
+		acquire_console_sem();
+
+		if (ret) {
+			read += (orig_count - ret);
+			ret = -EFAULT;
+			break;
+		}
+		buf += orig_count;
+		pos += orig_count;
+		read += orig_count;
+		count -= orig_count;
+	}
+	*ppos += read;
+	if (read)
+		ret = read;
+unlock_out:
+	release_console_sem();
+	up(&con_buf_sem);
+	return ret;
+}
+
+static ssize_t
+vcs_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	struct inode *inode = file->f_dentry->d_inode;
+	unsigned int currcons = iminor(inode);
+	struct vc_data *vc;
+	long pos;
+	long viewed, attr, size, written;
+	char *con_buf0;
+	int col, maxcol;
+	u16 *org0 = NULL, *org = NULL;
+	size_t ret;
+
+	down(&con_buf_sem);
+
+	pos = *ppos;
+
+	/* Select the proper current console and verify
+	 * sanity of the situation under the console lock.
+	 */
+	acquire_console_sem();
+
+	attr = (currcons & 128);
+	currcons = (currcons & 127);
+
+	if (currcons == 0) {
+		currcons = fg_console;
+		viewed = 1;
+	} else {
+		currcons--;
+		viewed = 0;
+	}
+	ret = -ENXIO;
+	if (!vc_cons_allocated(currcons))
+		goto unlock_out;
+	vc = vc_cons[currcons].d;
+
+	size = vcs_size(inode);
+	ret = -EINVAL;
+	if (pos < 0 || pos > size)
+		goto unlock_out;
+	if (count > size - pos)
+		count = size - pos;
+	written = 0;
+	while (count) {
+		long this_round = count;
+		size_t orig_count;
+		long p;
+
+		if (this_round > CON_BUF_SIZE)
+			this_round = CON_BUF_SIZE;
+
+		/* Temporarily drop the console lock so that we can read
+		 * in the write data from userspace safely.
+		 */
+		release_console_sem();
+		ret = copy_from_user(con_buf, buf, this_round);
+		acquire_console_sem();
+
+		if (ret) {
+			this_round -= ret;
+			if (!this_round) {
+				/* Abort loop if no data were copied. Otherwise
+				 * fail with -EFAULT.
+				 */
+				if (written)
+					break;
+				ret = -EFAULT;
+				goto unlock_out;
+			}
+		}
+
+		/* The vcs_size might have changed while we slept to grab
+		 * the user buffer, so recheck.
+		 * Return data written up to now on failure.
+		 */
+		size = vcs_size(inode);
+		if (pos >= size)
+			break;
+		if (this_round > size - pos)
+			this_round = size - pos;
+
+		/* OK, now actually push the write to the console
+		 * under the lock using the local kernel buffer.
+		 */
+
+		con_buf0 = con_buf;
+		orig_count = this_round;
+		maxcol = vc->vc_cols;
+		p = pos;
+		if (!attr) {
+			org0 = org = screen_pos(vc, p, viewed);
+			col = p % maxcol;
+			p += maxcol - col;
+
+			while (this_round > 0) {
+				unsigned char c = *con_buf0++;
+
+				this_round--;
+				vcs_scr_writew(vc,
+					       (vcs_scr_readw(vc, org) & 0xff00) | c, org);
+				org++;
+				if (++col == maxcol) {
+					org = screen_pos(vc, p, viewed);
+					col = 0;
+					p += maxcol;
+				}
+			}
+		} else {
+			if (p < HEADER_SIZE) {
+				char header[HEADER_SIZE];
+
+				getconsxy(vc, header + 2);
+				while (p < HEADER_SIZE && this_round > 0) {
+					this_round--;
+					header[p++] = *con_buf0++;
+				}
+				if (!viewed)
+					putconsxy(vc, header + 2);
+			}
+			p -= HEADER_SIZE;
+			col = (p/2) % maxcol;
+			if (this_round > 0) {
+				org0 = org = screen_pos(vc, p/2, viewed);
+				if ((p & 1) && this_round > 0) {
+					char c;
+
+					this_round--;
+					c = *con_buf0++;
+#ifdef __BIG_ENDIAN
+					vcs_scr_writew(vc, c |
+					     (vcs_scr_readw(vc, org) & 0xff00), org);
+#else
+					vcs_scr_writew(vc, (c << 8) |
+					     (vcs_scr_readw(vc, org) & 0xff), org);
+#endif
+					org++;
+					p++;
+					if (++col == maxcol) {
+						org = screen_pos(vc, p/2, viewed);
+						col = 0;
+					}
+				}
+				p /= 2;
+				p += maxcol - col;
+			}
+			while (this_round > 1) {
+				unsigned short w;
+
+				w = get_unaligned(((const unsigned short *)con_buf0));
+				vcs_scr_writew(vc, w, org++);
+				con_buf0 += 2;
+				this_round -= 2;
+				if (++col == maxcol) {
+					org = screen_pos(vc, p, viewed);
+					col = 0;
+					p += maxcol;
+				}
+			}
+			if (this_round > 0) {
+				unsigned char c;
+
+				c = *con_buf0++;
+#ifdef __BIG_ENDIAN
+				vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff) | (c << 8), org);
+#else
+				vcs_scr_writew(vc, (vcs_scr_readw(vc, org) & 0xff00) | c, org);
+#endif
+			}
+		}
+		count -= orig_count;
+		written += orig_count;
+		buf += orig_count;
+		pos += orig_count;
+		if (org0)
+			update_region(vc, (unsigned long)(org0), org - org0);
+	}
+	*ppos += written;
+	ret = written;
+
+unlock_out:
+	release_console_sem();
+
+	up(&con_buf_sem);
+
+	return ret;
+}
+
+static int
+vcs_open(struct inode *inode, struct file *filp)
+{
+	unsigned int currcons = iminor(inode) & 127;
+	if(currcons && !vc_cons_allocated(currcons-1))
+		return -ENXIO;
+	return 0;
+}
+
+static struct file_operations vcs_fops = {
+	.llseek		= vcs_lseek,
+	.read		= vcs_read,
+	.write		= vcs_write,
+	.open		= vcs_open,
+};
+
+static struct class_simple *vc_class;
+
+void vcs_make_devfs(struct tty_struct *tty)
+{
+	devfs_mk_cdev(MKDEV(VCS_MAJOR, tty->index + 1),
+			S_IFCHR|S_IRUSR|S_IWUSR,
+			"vcc/%u", tty->index + 1);
+	devfs_mk_cdev(MKDEV(VCS_MAJOR, tty->index + 129),
+			S_IFCHR|S_IRUSR|S_IWUSR,
+			"vcc/a%u", tty->index + 1);
+	class_simple_device_add(vc_class, MKDEV(VCS_MAJOR, tty->index + 1), NULL, "vcs%u", tty->index + 1);
+	class_simple_device_add(vc_class, MKDEV(VCS_MAJOR, tty->index + 129), NULL, "vcsa%u", tty->index + 1);
+}
+void vcs_remove_devfs(struct tty_struct *tty)
+{
+	devfs_remove("vcc/%u", tty->index + 1);
+	devfs_remove("vcc/a%u", tty->index + 1);
+	class_simple_device_remove(MKDEV(VCS_MAJOR, tty->index + 1));
+	class_simple_device_remove(MKDEV(VCS_MAJOR, tty->index + 129));
+}
+
+int __init vcs_init(void)
+{
+	if (register_chrdev(VCS_MAJOR, "vcs", &vcs_fops))
+		panic("unable to get major %d for vcs device", VCS_MAJOR);
+	vc_class = class_simple_create(THIS_MODULE, "vc");
+
+	devfs_mk_cdev(MKDEV(VCS_MAJOR, 0), S_IFCHR|S_IRUSR|S_IWUSR, "vcc/0");
+	devfs_mk_cdev(MKDEV(VCS_MAJOR, 128), S_IFCHR|S_IRUSR|S_IWUSR, "vcc/a0");
+	class_simple_device_add(vc_class, MKDEV(VCS_MAJOR, 0), NULL, "vcs");
+	class_simple_device_add(vc_class, MKDEV(VCS_MAJOR, 128), NULL, "vcsa");
+	return 0;
+}
diff --git a/drivers/char/viocons.c b/drivers/char/viocons.c
new file mode 100644
index 0000000..44f5fb4
--- /dev/null
+++ b/drivers/char/viocons.c
@@ -0,0 +1,1195 @@
+/* -*- linux-c -*-
+ *
+ *  drivers/char/viocons.c
+ *
+ *  iSeries Virtual Terminal
+ *
+ *  Authors: Dave Boutcher <boutcher@us.ibm.com>
+ *           Ryan Arnold <ryanarn@us.ibm.com>
+ *           Colin Devilbiss <devilbis@us.ibm.com>
+ *           Stephen Rothwell <sfr@au1.ibm.com>
+ *
+ * (C) Copyright 2000, 2001, 2002, 2003, 2004 IBM Corporation
+ *
+ * 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) anyu 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/errno.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/console.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <asm/ioctls.h>
+#include <linux/kd.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/sysrq.h>
+
+#include <asm/iSeries/vio.h>
+
+#include <asm/iSeries/HvLpEvent.h>
+#include <asm/iSeries/HvCallEvent.h>
+#include <asm/iSeries/HvLpConfig.h>
+#include <asm/iSeries/HvCall.h>
+
+#ifdef CONFIG_VT
+#error You must turn off CONFIG_VT to use CONFIG_VIOCONS
+#endif
+
+#define VIOTTY_MAGIC (0x0DCB)
+#define VTTY_PORTS 10
+
+#define VIOCONS_KERN_WARN	KERN_WARNING "viocons: "
+#define VIOCONS_KERN_INFO	KERN_INFO "viocons: "
+
+static DEFINE_SPINLOCK(consolelock);
+static DEFINE_SPINLOCK(consoleloglock);
+
+#ifdef CONFIG_MAGIC_SYSRQ
+static int vio_sysrq_pressed;
+extern int sysrq_enabled;
+#endif
+
+/*
+ * The structure of the events that flow between us and OS/400.  You can't
+ * mess with this unless the OS/400 side changes too
+ */
+struct viocharlpevent {
+	struct HvLpEvent event;
+	u32 reserved;
+	u16 version;
+	u16 subtype_result_code;
+	u8 virtual_device;
+	u8 len;
+	u8 data[VIOCHAR_MAX_DATA];
+};
+
+#define VIOCHAR_WINDOW		10
+#define VIOCHAR_HIGHWATERMARK	3
+
+enum viocharsubtype {
+	viocharopen = 0x0001,
+	viocharclose = 0x0002,
+	viochardata = 0x0003,
+	viocharack = 0x0004,
+	viocharconfig = 0x0005
+};
+
+enum viochar_rc {
+	viochar_rc_ebusy = 1
+};
+
+#define VIOCHAR_NUM_BUF		16
+
+/*
+ * Our port information.  We store a pointer to one entry in the
+ * tty_driver_data
+ */
+static struct port_info {
+	int magic;
+	struct tty_struct *tty;
+	HvLpIndex lp;
+	u8 vcons;
+	u64 seq;	/* sequence number of last HV send */
+	u64 ack;	/* last ack from HV */
+/*
+ * When we get writes faster than we can send it to the partition,
+ * buffer the data here. Note that used is a bit map of used buffers.
+ * It had better have enough bits to hold VIOCHAR_NUM_BUF the bitops assume
+ * it is a multiple of unsigned long
+ */
+	unsigned long used;
+	u8 *buffer[VIOCHAR_NUM_BUF];
+	int bufferBytes[VIOCHAR_NUM_BUF];
+	int curbuf;
+	int bufferOverflow;
+	int overflowMessage;
+} port_info[VTTY_PORTS];
+
+#define viochar_is_console(pi)	((pi) == &port_info[0])
+#define viochar_port(pi)	((pi) - &port_info[0])
+
+static void initDataEvent(struct viocharlpevent *viochar, HvLpIndex lp);
+
+static struct tty_driver *viotty_driver;
+
+void hvlog(char *fmt, ...)
+{
+	int i;
+	unsigned long flags;
+	va_list args;
+	static char buf[256];
+
+	spin_lock_irqsave(&consoleloglock, flags);
+	va_start(args, fmt);
+	i = vscnprintf(buf, sizeof(buf) - 1, fmt, args);
+	va_end(args);
+	buf[i++] = '\r';
+	HvCall_writeLogBuffer(buf, i);
+	spin_unlock_irqrestore(&consoleloglock, flags);
+}
+
+void hvlogOutput(const char *buf, int count)
+{
+	unsigned long flags;
+	int begin;
+	int index;
+	static const char cr = '\r';
+
+	begin = 0;
+	spin_lock_irqsave(&consoleloglock, flags);
+	for (index = 0; index < count; index++) {
+		if (buf[index] == '\n') {
+			/*
+			 * Start right after the last '\n' or at the zeroth
+			 * array position and output the number of characters
+			 * including the newline.
+			 */
+			HvCall_writeLogBuffer(&buf[begin], index - begin + 1);
+			begin = index + 1;
+			HvCall_writeLogBuffer(&cr, 1);
+		}
+	}
+	if ((index - begin) > 0)
+		HvCall_writeLogBuffer(&buf[begin], index - begin);
+	spin_unlock_irqrestore(&consoleloglock, flags);
+}
+
+/*
+ * Make sure we're pointing to a valid port_info structure.  Shamelessly
+ * plagerized from serial.c
+ */
+static inline int viotty_paranoia_check(struct port_info *pi,
+					char *name, const char *routine)
+{
+	static const char *bad_pi_addr = VIOCONS_KERN_WARN
+		"warning: bad address for port_info struct (%s) in %s\n";
+	static const char *badmagic = VIOCONS_KERN_WARN
+		"warning: bad magic number for port_info struct (%s) in %s\n";
+
+	if ((pi < &port_info[0]) || (viochar_port(pi) > VTTY_PORTS)) {
+		printk(bad_pi_addr, name, routine);
+		return 1;
+	}
+	if (pi->magic != VIOTTY_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * Add data to our pending-send buffers.  
+ *
+ * NOTE: Don't use printk in here because it gets nastily recursive.
+ * hvlog can be used to log to the hypervisor buffer
+ */
+static int buffer_add(struct port_info *pi, const char *buf, size_t len)
+{
+	size_t bleft;
+	size_t curlen;
+	const char *curbuf;
+	int nextbuf;
+
+	curbuf = buf;
+	bleft = len;
+	while (bleft > 0) {
+		/*
+		 * If there is no space left in the current buffer, we have
+		 * filled everything up, so return.  If we filled the previous
+		 * buffer we would already have moved to the next one.
+		 */
+		if (pi->bufferBytes[pi->curbuf] == VIOCHAR_MAX_DATA) {
+			hvlog ("\n\rviocons: No overflow buffer available for memcpy().\n");
+			pi->bufferOverflow++;
+			pi->overflowMessage = 1;
+			break;
+		}
+
+		/*
+		 * Turn on the "used" bit for this buffer.  If it's already on,
+		 * that's fine.
+		 */
+		set_bit(pi->curbuf, &pi->used);
+
+		/*
+		 * See if this buffer has been allocated.  If not, allocate it.
+		 */
+		if (pi->buffer[pi->curbuf] == NULL) {
+			pi->buffer[pi->curbuf] =
+			    kmalloc(VIOCHAR_MAX_DATA, GFP_ATOMIC);
+			if (pi->buffer[pi->curbuf] == NULL) {
+				hvlog("\n\rviocons: kmalloc failed allocating spaces for buffer %d.",
+					pi->curbuf);
+				break;
+			}
+		}
+
+		/* Figure out how much we can copy into this buffer. */
+		if (bleft < (VIOCHAR_MAX_DATA - pi->bufferBytes[pi->curbuf]))
+			curlen = bleft;
+		else
+			curlen = VIOCHAR_MAX_DATA - pi->bufferBytes[pi->curbuf];
+
+		/* Copy the data into the buffer. */
+		memcpy(pi->buffer[pi->curbuf] + pi->bufferBytes[pi->curbuf],
+				curbuf, curlen);
+
+		pi->bufferBytes[pi->curbuf] += curlen;
+		curbuf += curlen;
+		bleft -= curlen;
+
+		/*
+		 * Now see if we've filled this buffer.  If not then
+		 * we'll try to use it again later.  If we've filled it
+		 * up then we'll advance the curbuf to the next in the
+		 * circular queue.
+		 */
+		if (pi->bufferBytes[pi->curbuf] == VIOCHAR_MAX_DATA) {
+			nextbuf = (pi->curbuf + 1) % VIOCHAR_NUM_BUF;
+			/*
+			 * Move to the next buffer if it hasn't been used yet
+			 */
+			if (test_bit(nextbuf, &pi->used) == 0)
+				pi->curbuf = nextbuf;
+		}
+	}
+	return len - bleft;
+}
+
+/*
+ * Send pending data
+ *
+ * NOTE: Don't use printk in here because it gets nastily recursive.
+ * hvlog can be used to log to the hypervisor buffer
+ */
+static void send_buffers(struct port_info *pi)
+{
+	HvLpEvent_Rc hvrc;
+	int nextbuf;
+	struct viocharlpevent *viochar;
+	unsigned long flags;
+
+	spin_lock_irqsave(&consolelock, flags);
+
+	viochar = (struct viocharlpevent *)
+	    vio_get_event_buffer(viomajorsubtype_chario);
+
+	/* Make sure we got a buffer */
+	if (viochar == NULL) {
+		hvlog("\n\rviocons: Can't get viochar buffer in sendBuffers().");
+		spin_unlock_irqrestore(&consolelock, flags);
+		return;
+	}
+
+	if (pi->used == 0) {
+		hvlog("\n\rviocons: in sendbuffers(), but no buffers used.\n");
+		vio_free_event_buffer(viomajorsubtype_chario, viochar);
+		spin_unlock_irqrestore(&consolelock, flags);
+		return;
+	}
+
+	/*
+	 * curbuf points to the buffer we're filling.  We want to
+	 * start sending AFTER this one.  
+	 */
+	nextbuf = (pi->curbuf + 1) % VIOCHAR_NUM_BUF;
+
+	/*
+	 * Loop until we find a buffer with the used bit on
+	 */
+	while (test_bit(nextbuf, &pi->used) == 0)
+		nextbuf = (nextbuf + 1) % VIOCHAR_NUM_BUF;
+
+	initDataEvent(viochar, pi->lp);
+
+	/*
+	 * While we have buffers with data, and our send window
+	 * is open, send them
+	 */
+	while ((test_bit(nextbuf, &pi->used)) &&
+	       ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) {
+		viochar->len = pi->bufferBytes[nextbuf];
+		viochar->event.xCorrelationToken = pi->seq++;
+		viochar->event.xSizeMinus1 =
+			offsetof(struct viocharlpevent, data) + viochar->len;
+
+		memcpy(viochar->data, pi->buffer[nextbuf], viochar->len);
+
+		hvrc = HvCallEvent_signalLpEvent(&viochar->event);
+		if (hvrc) {
+			/*
+			 * MUST unlock the spinlock before doing a printk
+			 */
+			vio_free_event_buffer(viomajorsubtype_chario, viochar);
+			spin_unlock_irqrestore(&consolelock, flags);
+
+			printk(VIOCONS_KERN_WARN
+			       "error sending event! return code %d\n",
+			       (int)hvrc);
+			return;
+		}
+
+		/*
+		 * clear the used bit, zero the number of bytes in
+		 * this buffer, and move to the next buffer
+		 */
+		clear_bit(nextbuf, &pi->used);
+		pi->bufferBytes[nextbuf] = 0;
+		nextbuf = (nextbuf + 1) % VIOCHAR_NUM_BUF;
+	}
+
+	/*
+	 * If we have emptied all the buffers, start at 0 again.
+	 * this will re-use any allocated buffers
+	 */
+	if (pi->used == 0) {
+		pi->curbuf = 0;
+
+		if (pi->overflowMessage)
+			pi->overflowMessage = 0;
+
+		if (pi->tty) {
+			tty_wakeup(pi->tty);
+		}
+	}
+
+	vio_free_event_buffer(viomajorsubtype_chario, viochar);
+	spin_unlock_irqrestore(&consolelock, flags);
+}
+
+/*
+ * Our internal writer.  Gets called both from the console device and
+ * the tty device.  the tty pointer will be NULL if called from the console.
+ * Return total number of bytes "written".
+ *
+ * NOTE: Don't use printk in here because it gets nastily recursive.  hvlog
+ * can be used to log to the hypervisor buffer
+ */
+static int internal_write(struct port_info *pi, const char *buf, size_t len)
+{
+	HvLpEvent_Rc hvrc;
+	size_t bleft;
+	size_t curlen;
+	const char *curbuf;
+	unsigned long flags;
+	struct viocharlpevent *viochar;
+
+	/*
+	 * Write to the hvlog of inbound data are now done prior to
+	 * calling internal_write() since internal_write() is only called in
+	 * the event that an lp event path is active, which isn't the case for
+	 * logging attempts prior to console initialization.
+	 *
+	 * If there is already data queued for this port, send it prior to
+	 * attempting to send any new data.
+	 */
+	if (pi->used)
+		send_buffers(pi);
+
+	spin_lock_irqsave(&consolelock, flags);
+
+	viochar = vio_get_event_buffer(viomajorsubtype_chario);
+	if (viochar == NULL) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		hvlog("\n\rviocons: Can't get vio buffer in internal_write().");
+		return -EAGAIN;
+	}
+	initDataEvent(viochar, pi->lp);
+
+	curbuf = buf;
+	bleft = len;
+
+	while ((bleft > 0) && (pi->used == 0) &&
+	       ((pi->seq - pi->ack) < VIOCHAR_WINDOW)) {
+		if (bleft > VIOCHAR_MAX_DATA)
+			curlen = VIOCHAR_MAX_DATA;
+		else
+			curlen = bleft;
+
+		viochar->event.xCorrelationToken = pi->seq++;
+		memcpy(viochar->data, curbuf, curlen);
+		viochar->len = curlen;
+		viochar->event.xSizeMinus1 =
+		    offsetof(struct viocharlpevent, data) + curlen;
+
+		hvrc = HvCallEvent_signalLpEvent(&viochar->event);
+		if (hvrc) {
+			hvlog("viocons: error sending event! %d\n", (int)hvrc);
+			goto out;
+		}
+		curbuf += curlen;
+		bleft -= curlen;
+	}
+
+	/* If we didn't send it all, buffer as much of it as we can. */
+	if (bleft > 0)
+		bleft -= buffer_add(pi, curbuf, bleft);
+out:
+	vio_free_event_buffer(viomajorsubtype_chario, viochar);
+	spin_unlock_irqrestore(&consolelock, flags);
+	return len - bleft;
+}
+
+static struct port_info *get_port_data(struct tty_struct *tty)
+{
+	unsigned long flags;
+	struct port_info *pi;
+
+	spin_lock_irqsave(&consolelock, flags);
+	if (tty) {
+		pi = (struct port_info *)tty->driver_data;
+		if (!pi || viotty_paranoia_check(pi, tty->name,
+					     "get_port_data")) {
+			pi = NULL;
+		}
+	} else
+		/*
+		 * If this is the console device, use the lp from
+		 * the first port entry
+		 */
+		pi = &port_info[0];
+	spin_unlock_irqrestore(&consolelock, flags);
+	return pi;
+}
+
+/*
+ * Initialize the common fields in a charLpEvent
+ */
+static void initDataEvent(struct viocharlpevent *viochar, HvLpIndex lp)
+{
+	memset(viochar, 0, sizeof(struct viocharlpevent));
+
+	viochar->event.xFlags.xValid = 1;
+	viochar->event.xFlags.xFunction = HvLpEvent_Function_Int;
+	viochar->event.xFlags.xAckInd = HvLpEvent_AckInd_NoAck;
+	viochar->event.xFlags.xAckType = HvLpEvent_AckType_DeferredAck;
+	viochar->event.xType = HvLpEvent_Type_VirtualIo;
+	viochar->event.xSubtype = viomajorsubtype_chario | viochardata;
+	viochar->event.xSourceLp = HvLpConfig_getLpIndex();
+	viochar->event.xTargetLp = lp;
+	viochar->event.xSizeMinus1 = sizeof(struct viocharlpevent);
+	viochar->event.xSourceInstanceId = viopath_sourceinst(lp);
+	viochar->event.xTargetInstanceId = viopath_targetinst(lp);
+}
+
+/*
+ * early console device write
+ */
+static void viocons_write_early(struct console *co, const char *s, unsigned count)
+{
+	hvlogOutput(s, count);
+}
+
+/*
+ * console device write
+ */
+static void viocons_write(struct console *co, const char *s, unsigned count)
+{
+	int index;
+	int begin;
+	struct port_info *pi;
+
+	static const char cr = '\r';
+
+	/*
+	 * Check port data first because the target LP might be valid but
+	 * simply not active, in which case we want to hvlog the output.
+	 */
+	pi = get_port_data(NULL);
+	if (pi == NULL) {
+		hvlog("\n\rviocons_write: unable to get port data.");
+		return;
+	}
+
+	hvlogOutput(s, count);
+
+	if (!viopath_isactive(pi->lp))
+		return;
+
+	/* 
+	 * Any newline character found will cause a
+	 * carriage return character to be emitted as well. 
+	 */
+	begin = 0;
+	for (index = 0; index < count; index++) {
+		if (s[index] == '\n') {
+			/* 
+			 * Newline found. Print everything up to and 
+			 * including the newline
+			 */
+			internal_write(pi, &s[begin], index - begin + 1);
+			begin = index + 1;
+			/* Emit a carriage return as well */
+			internal_write(pi, &cr, 1);
+		}
+	}
+
+	/* If any characters left to write, write them now */
+	if ((index - begin) > 0)
+		internal_write(pi, &s[begin], index - begin);
+}
+
+/*
+ * Work out the device associate with this console
+ */
+static struct tty_driver *viocons_device(struct console *c, int *index)
+{
+	*index = c->index;
+	return viotty_driver;
+}
+
+/*
+ * console device I/O methods
+ */
+static struct console viocons_early = {
+	.name = "viocons",
+	.write = viocons_write_early,
+	.flags = CON_PRINTBUFFER,
+	.index = -1,
+};
+
+static struct console viocons = {
+	.name = "viocons",
+	.write = viocons_write,
+	.device = viocons_device,
+	.flags = CON_PRINTBUFFER,
+	.index = -1,
+};
+
+/*
+ * TTY Open method
+ */
+static int viotty_open(struct tty_struct *tty, struct file *filp)
+{
+	int port;
+	unsigned long flags;
+	struct port_info *pi;
+
+	port = tty->index;
+
+	if ((port < 0) || (port >= VTTY_PORTS))
+		return -ENODEV;
+
+	spin_lock_irqsave(&consolelock, flags);
+
+	pi = &port_info[port];
+	/* If some other TTY is already connected here, reject the open */
+	if ((pi->tty) && (pi->tty != tty)) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		printk(VIOCONS_KERN_WARN
+		       "attempt to open device twice from different ttys\n");
+		return -EBUSY;
+	}
+	tty->driver_data = pi;
+	pi->tty = tty;
+	spin_unlock_irqrestore(&consolelock, flags);
+
+	return 0;
+}
+
+/*
+ * TTY Close method
+ */
+static void viotty_close(struct tty_struct *tty, struct file *filp)
+{
+	unsigned long flags;
+	struct port_info *pi;
+
+	spin_lock_irqsave(&consolelock, flags);
+	pi = (struct port_info *)tty->driver_data;
+
+	if (!pi || viotty_paranoia_check(pi, tty->name, "viotty_close")) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		return;
+	}
+	if (tty->count == 1)
+		pi->tty = NULL;
+	spin_unlock_irqrestore(&consolelock, flags);
+}
+
+/*
+ * TTY Write method
+ */
+static int viotty_write(struct tty_struct *tty, const unsigned char *buf,
+		int count)
+{
+	struct port_info *pi;
+
+	pi = get_port_data(tty);
+	if (pi == NULL) {
+		hvlog("\n\rviotty_write: no port data.");
+		return -ENODEV;
+	}
+
+	if (viochar_is_console(pi))
+		hvlogOutput(buf, count);
+
+	/*
+	 * If the path to this LP is closed, don't bother doing anything more.
+	 * just dump the data on the floor and return count.  For some reason
+	 * some user level programs will attempt to probe available tty's and
+	 * they'll attempt a viotty_write on an invalid port which maps to an
+	 * invalid target lp.  If this is the case then ignore the
+	 * viotty_write call and, since the viopath isn't active to this
+	 * partition, return count.
+	 */
+	if (!viopath_isactive(pi->lp))
+		return count;
+
+	return internal_write(pi, buf, count);
+}
+
+/*
+ * TTY put_char method
+ */
+static void viotty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	struct port_info *pi;
+
+	pi = get_port_data(tty);
+	if (pi == NULL)
+		return;
+
+	/* This will append '\r' as well if the char is '\n' */
+	if (viochar_is_console(pi))
+		hvlogOutput(&ch, 1);
+
+	if (viopath_isactive(pi->lp))
+		internal_write(pi, &ch, 1);
+}
+
+/*
+ * TTY write_room method
+ */
+static int viotty_write_room(struct tty_struct *tty)
+{
+	int i;
+	int room = 0;
+	struct port_info *pi;
+	unsigned long flags;
+
+	spin_lock_irqsave(&consolelock, flags);
+	pi = (struct port_info *)tty->driver_data;
+	if (!pi || viotty_paranoia_check(pi, tty->name, "viotty_write_room")) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		return 0;
+	}
+
+	/* If no buffers are used, return the max size. */
+	if (pi->used == 0) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		return VIOCHAR_MAX_DATA * VIOCHAR_NUM_BUF;
+	}
+
+	/*
+	 * We retain the spinlock because we want to get an accurate
+	 * count and it can change on us between each operation if we
+	 * don't hold the spinlock.
+	 */
+	for (i = 0; ((i < VIOCHAR_NUM_BUF) && (room < VIOCHAR_MAX_DATA)); i++)
+		room += (VIOCHAR_MAX_DATA - pi->bufferBytes[i]);
+	spin_unlock_irqrestore(&consolelock, flags);
+
+	if (room > VIOCHAR_MAX_DATA)
+		room = VIOCHAR_MAX_DATA;
+	return room;
+}
+
+/*
+ * TTY chars_in_buffer method
+ */
+static int viotty_chars_in_buffer(struct tty_struct *tty)
+{
+	return 0;
+}
+
+static int viotty_ioctl(struct tty_struct *tty, struct file *file,
+			unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+	/*
+	 * the ioctls below read/set the flags usually shown in the leds
+	 * don't use them - they will go away without warning
+	 */
+	case KDGETLED:
+	case KDGKBLED:
+		return put_user(0, (char *)arg);
+
+	case KDSKBLED:
+		return 0;
+	}
+
+	return n_tty_ioctl(tty, file, cmd, arg);
+}
+
+/*
+ * Handle an open charLpEvent.  Could be either interrupt or ack
+ */
+static void vioHandleOpenEvent(struct HvLpEvent *event)
+{
+	unsigned long flags;
+	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+	u8 port = cevent->virtual_device;
+	struct port_info *pi;
+	int reject = 0;
+
+	if (event->xFlags.xFunction == HvLpEvent_Function_Ack) {
+		if (port >= VTTY_PORTS)
+			return;
+
+		spin_lock_irqsave(&consolelock, flags);
+		/* Got the lock, don't cause console output */
+
+		pi = &port_info[port];
+		if (event->xRc == HvLpEvent_Rc_Good) {
+			pi->seq = pi->ack = 0;
+			/*
+			 * This line allows connections from the primary
+			 * partition but once one is connected from the
+			 * primary partition nothing short of a reboot
+			 * of linux will allow access from the hosting
+			 * partition again without a required iSeries fix.
+			 */
+			pi->lp = event->xTargetLp;
+		}
+
+		spin_unlock_irqrestore(&consolelock, flags);
+		if (event->xRc != HvLpEvent_Rc_Good)
+			printk(VIOCONS_KERN_WARN
+			       "handle_open_event: event->xRc == (%d).\n",
+			       event->xRc);
+
+		if (event->xCorrelationToken != 0) {
+			atomic_t *aptr= (atomic_t *)event->xCorrelationToken;
+			atomic_set(aptr, 1);
+		} else
+			printk(VIOCONS_KERN_WARN
+			       "weird...got open ack without atomic\n");
+		return;
+	}
+
+	/* This had better require an ack, otherwise complain */
+	if (event->xFlags.xAckInd != HvLpEvent_AckInd_DoAck) {
+		printk(VIOCONS_KERN_WARN "viocharopen without ack bit!\n");
+		return;
+	}
+
+	spin_lock_irqsave(&consolelock, flags);
+	/* Got the lock, don't cause console output */
+
+	/* Make sure this is a good virtual tty */
+	if (port >= VTTY_PORTS) {
+		event->xRc = HvLpEvent_Rc_SubtypeError;
+		cevent->subtype_result_code = viorc_openRejected;
+		/*
+		 * Flag state here since we can't printk while holding
+		 * a spinlock.
+		 */
+		reject = 1;
+	} else {
+		pi = &port_info[port];
+		if ((pi->lp != HvLpIndexInvalid) &&
+				(pi->lp != event->xSourceLp)) {
+			/*
+			 * If this is tty is already connected to a different
+			 * partition, fail.
+			 */
+			event->xRc = HvLpEvent_Rc_SubtypeError;
+			cevent->subtype_result_code = viorc_openRejected;
+			reject = 2;
+		} else {
+			pi->lp = event->xSourceLp;
+			event->xRc = HvLpEvent_Rc_Good;
+			cevent->subtype_result_code = viorc_good;
+			pi->seq = pi->ack = 0;
+			reject = 0;
+		}
+	}
+
+	spin_unlock_irqrestore(&consolelock, flags);
+
+	if (reject == 1)
+		printk(VIOCONS_KERN_WARN "open rejected: bad virtual tty.\n");
+	else if (reject == 2)
+		printk(VIOCONS_KERN_WARN
+			"open rejected: console in exclusive use by another partition.\n");
+
+	/* Return the acknowledgement */
+	HvCallEvent_ackLpEvent(event);
+}
+
+/*
+ * Handle a close charLpEvent.  This should ONLY be an Interrupt because the
+ * virtual console should never actually issue a close event to the hypervisor
+ * because the virtual console never goes away.  A close event coming from the
+ * hypervisor simply means that there are no client consoles connected to the
+ * virtual console.
+ *
+ * Regardless of the number of connections masqueraded on the other side of
+ * the hypervisor ONLY ONE close event should be called to accompany the ONE
+ * open event that is called.  The close event should ONLY be called when NO
+ * MORE connections (masqueraded or not) exist on the other side of the
+ * hypervisor.
+ */
+static void vioHandleCloseEvent(struct HvLpEvent *event)
+{
+	unsigned long flags;
+	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+	u8 port = cevent->virtual_device;
+
+	if (event->xFlags.xFunction == HvLpEvent_Function_Int) {
+		if (port >= VTTY_PORTS) {
+			printk(VIOCONS_KERN_WARN
+					"close message from invalid virtual device.\n");
+			return;
+		}
+
+		/* For closes, just mark the console partition invalid */
+		spin_lock_irqsave(&consolelock, flags);
+		/* Got the lock, don't cause console output */
+
+		if (port_info[port].lp == event->xSourceLp)
+			port_info[port].lp = HvLpIndexInvalid;
+
+		spin_unlock_irqrestore(&consolelock, flags);
+		printk(VIOCONS_KERN_INFO "close from %d\n", event->xSourceLp);
+	} else
+		printk(VIOCONS_KERN_WARN
+				"got unexpected close acknowlegement\n");
+}
+
+/*
+ * Handle a config charLpEvent.  Could be either interrupt or ack
+ */
+static void vioHandleConfig(struct HvLpEvent *event)
+{
+	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+
+	HvCall_writeLogBuffer(cevent->data, cevent->len);
+
+	if (cevent->data[0] == 0x01)
+		printk(VIOCONS_KERN_INFO "window resized to %d: %d: %d: %d\n",
+		       cevent->data[1], cevent->data[2],
+		       cevent->data[3], cevent->data[4]);
+	else
+		printk(VIOCONS_KERN_WARN "unknown config event\n");
+}
+
+/*
+ * Handle a data charLpEvent. 
+ */
+static void vioHandleData(struct HvLpEvent *event)
+{
+	struct tty_struct *tty;
+	unsigned long flags;
+	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+	struct port_info *pi;
+	int index;
+	u8 port = cevent->virtual_device;
+
+	if (port >= VTTY_PORTS) {
+		printk(VIOCONS_KERN_WARN "data on invalid virtual device %d\n",
+				port);
+		return;
+	}
+
+	/*
+	 * Hold the spinlock so that we don't take an interrupt that
+	 * changes tty between the time we fetch the port_info
+	 * pointer and the time we paranoia check.
+	 */
+	spin_lock_irqsave(&consolelock, flags);
+	pi = &port_info[port];
+
+	/*
+	 * Change 05/01/2003 - Ryan Arnold: If a partition other than
+	 * the current exclusive partition tries to send us data
+	 * events then just drop them on the floor because we don't
+	 * want his stinking data.  He isn't authorized to receive
+	 * data because he wasn't the first one to get the console,
+	 * therefore he shouldn't be allowed to send data either.
+	 * This will work without an iSeries fix.
+	 */
+	if (pi->lp != event->xSourceLp) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		return;
+	}
+
+	tty = pi->tty;
+	if (tty == NULL) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		printk(VIOCONS_KERN_WARN "no tty for virtual device %d\n",
+				port);
+		return;
+	}
+
+	if (tty->magic != TTY_MAGIC) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		printk(VIOCONS_KERN_WARN "tty bad magic\n");
+		return;
+	}
+
+	/*
+	 * Just to be paranoid, make sure the tty points back to this port
+	 */
+	pi = (struct port_info *)tty->driver_data;
+	if (!pi || viotty_paranoia_check(pi, tty->name, "vioHandleData")) {
+		spin_unlock_irqrestore(&consolelock, flags);
+		return;
+	}
+	spin_unlock_irqrestore(&consolelock, flags);
+
+	/*
+	 * Change 07/21/2003 - Ryan Arnold: functionality added to
+	 * support sysrq utilizing ^O as the sysrq key.  The sysrq
+	 * functionality will only work if built into the kernel and
+	 * then only if sysrq is enabled through the proc filesystem.
+	 */
+	for (index = 0; index < cevent->len; index++) {
+#ifdef CONFIG_MAGIC_SYSRQ
+		if (sysrq_enabled) {
+			/* 0x0f is the ascii character for ^O */
+			if (cevent->data[index] == '\x0f') {
+				vio_sysrq_pressed = 1;
+				/*
+				 * continue because we don't want to add
+				 * the sysrq key into the data string.
+				 */
+				continue;
+			} else if (vio_sysrq_pressed) {
+				handle_sysrq(cevent->data[index], NULL, tty);
+				vio_sysrq_pressed = 0;
+				/*
+				 * continue because we don't want to add
+				 * the sysrq sequence into the data string.
+				 */
+				continue;
+			}
+		}
+#endif
+		/*
+		 * The sysrq sequence isn't included in this check if
+		 * sysrq is enabled and compiled into the kernel because
+		 * the sequence will never get inserted into the buffer.
+		 * Don't attempt to copy more data into the buffer than we
+		 * have room for because it would fail without indication.
+		 */
+		if ((tty->flip.count + 1) > TTY_FLIPBUF_SIZE) {
+			printk(VIOCONS_KERN_WARN "input buffer overflow!\n");
+			break;
+		}
+		tty_insert_flip_char(tty, cevent->data[index], TTY_NORMAL);
+	}
+
+	/* if cevent->len == 0 then no data was added to the buffer and flip.count == 0 */
+	if (tty->flip.count)
+		/* The next call resets flip.count when the data is flushed. */
+		tty_flip_buffer_push(tty);
+}
+
+/*
+ * Handle an ack charLpEvent. 
+ */
+static void vioHandleAck(struct HvLpEvent *event)
+{
+	struct viocharlpevent *cevent = (struct viocharlpevent *)event;
+	unsigned long flags;
+	u8 port = cevent->virtual_device;
+
+	if (port >= VTTY_PORTS) {
+		printk(VIOCONS_KERN_WARN "data on invalid virtual device\n");
+		return;
+	}
+
+	spin_lock_irqsave(&consolelock, flags);
+	port_info[port].ack = event->xCorrelationToken;
+	spin_unlock_irqrestore(&consolelock, flags);
+
+	if (port_info[port].used)
+		send_buffers(&port_info[port]);
+}
+
+/*
+ * Handle charLpEvents and route to the appropriate routine
+ */
+static void vioHandleCharEvent(struct HvLpEvent *event)
+{
+	int charminor;
+
+	if (event == NULL)
+		return;
+
+	charminor = event->xSubtype & VIOMINOR_SUBTYPE_MASK;
+	switch (charminor) {
+	case viocharopen:
+		vioHandleOpenEvent(event);
+		break;
+	case viocharclose:
+		vioHandleCloseEvent(event);
+		break;
+	case viochardata:
+		vioHandleData(event);
+		break;
+	case viocharack:
+		vioHandleAck(event);
+		break;
+	case viocharconfig:
+		vioHandleConfig(event);
+		break;
+	default:
+		if ((event->xFlags.xFunction == HvLpEvent_Function_Int) &&
+		    (event->xFlags.xAckInd == HvLpEvent_AckInd_DoAck)) {
+			event->xRc = HvLpEvent_Rc_InvalidSubtype;
+			HvCallEvent_ackLpEvent(event);
+		}
+	}
+}
+
+/*
+ * Send an open event
+ */
+static int send_open(HvLpIndex remoteLp, void *sem)
+{
+	return HvCallEvent_signalLpEventFast(remoteLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_chario | viocharopen,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(remoteLp),
+			viopath_targetinst(remoteLp),
+			(u64)(unsigned long)sem, VIOVERSION << 16,
+			0, 0, 0, 0);
+}
+
+static struct tty_operations serial_ops = {
+	.open = viotty_open,
+	.close = viotty_close,
+	.write = viotty_write,
+	.put_char = viotty_put_char,
+	.write_room = viotty_write_room,
+	.chars_in_buffer = viotty_chars_in_buffer,
+	.ioctl = viotty_ioctl,
+};
+
+static int __init viocons_init2(void)
+{
+	atomic_t wait_flag;
+	int rc;
+
+	/* +2 for fudge */
+	rc = viopath_open(HvLpConfig_getPrimaryLpIndex(),
+			viomajorsubtype_chario, VIOCHAR_WINDOW + 2);
+	if (rc)
+		printk(VIOCONS_KERN_WARN "error opening to primary %d\n", rc);
+
+	if (viopath_hostLp == HvLpIndexInvalid)
+		vio_set_hostlp();
+
+	/*
+	 * And if the primary is not the same as the hosting LP, open to the 
+	 * hosting lp
+	 */
+	if ((viopath_hostLp != HvLpIndexInvalid) &&
+	    (viopath_hostLp != HvLpConfig_getPrimaryLpIndex())) {
+		printk(VIOCONS_KERN_INFO "open path to hosting (%d)\n",
+				viopath_hostLp);
+		rc = viopath_open(viopath_hostLp, viomajorsubtype_chario,
+				VIOCHAR_WINDOW + 2);	/* +2 for fudge */
+		if (rc)
+			printk(VIOCONS_KERN_WARN
+				"error opening to partition %d: %d\n",
+				viopath_hostLp, rc);
+	}
+
+	if (vio_setHandler(viomajorsubtype_chario, vioHandleCharEvent) < 0)
+		printk(VIOCONS_KERN_WARN
+				"error seting handler for console events!\n");
+
+	/*
+	 * First, try to open the console to the hosting lp.
+	 * Wait on a semaphore for the response.
+	 */
+	atomic_set(&wait_flag, 0);
+	if ((viopath_isactive(viopath_hostLp)) &&
+	    (send_open(viopath_hostLp, (void *)&wait_flag) == 0)) {
+		printk(VIOCONS_KERN_INFO "hosting partition %d\n",
+			viopath_hostLp);
+		while (atomic_read(&wait_flag) == 0)
+			mb();
+		atomic_set(&wait_flag, 0);
+	}
+
+	/*
+	 * If we don't have an active console, try the primary
+	 */
+	if ((!viopath_isactive(port_info[0].lp)) &&
+	    (viopath_isactive(HvLpConfig_getPrimaryLpIndex())) &&
+	    (send_open(HvLpConfig_getPrimaryLpIndex(), (void *)&wait_flag)
+	     == 0)) {
+		printk(VIOCONS_KERN_INFO "opening console to primary partition\n");
+		while (atomic_read(&wait_flag) == 0)
+			mb();
+	}
+
+	/* Initialize the tty_driver structure */
+	viotty_driver = alloc_tty_driver(VTTY_PORTS);
+	viotty_driver->owner = THIS_MODULE;
+	viotty_driver->driver_name = "vioconsole";
+	viotty_driver->devfs_name = "vcs/";
+	viotty_driver->name = "tty";
+	viotty_driver->name_base = 1;
+	viotty_driver->major = TTY_MAJOR;
+	viotty_driver->minor_start = 1;
+	viotty_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+	viotty_driver->subtype = 1;
+	viotty_driver->init_termios = tty_std_termios;
+	viotty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
+	tty_set_operations(viotty_driver, &serial_ops);
+
+	if (tty_register_driver(viotty_driver)) {
+		printk(VIOCONS_KERN_WARN "couldn't register console driver\n");
+		put_tty_driver(viotty_driver);
+		viotty_driver = NULL;
+	}
+
+	unregister_console(&viocons_early);
+	register_console(&viocons);
+
+	return 0;
+}
+
+static int __init viocons_init(void)
+{
+	int i;
+
+	printk(VIOCONS_KERN_INFO "registering console\n");
+	for (i = 0; i < VTTY_PORTS; i++) {
+		port_info[i].lp = HvLpIndexInvalid;
+		port_info[i].magic = VIOTTY_MAGIC;
+	}
+	HvCall_setLogBufferFormatAndCodepage(HvCall_LogBuffer_ASCII, 437);
+	register_console(&viocons_early);
+	return 0;
+}
+
+console_initcall(viocons_init);
+module_init(viocons_init2);
diff --git a/drivers/char/viotape.c b/drivers/char/viotape.c
new file mode 100644
index 0000000..aea3cbf
--- /dev/null
+++ b/drivers/char/viotape.c
@@ -0,0 +1,1129 @@
+/* -*- linux-c -*-
+ *  drivers/char/viotape.c
+ *
+ *  iSeries Virtual Tape
+ *
+ *  Authors: Dave Boutcher <boutcher@us.ibm.com>
+ *           Ryan Arnold <ryanarn@us.ibm.com>
+ *           Colin Devilbiss <devilbis@us.ibm.com>
+ *           Stephen Rothwell <sfr@au1.ibm.com>
+ *
+ * (C) Copyright 2000-2004 IBM Corporation
+ *
+ * 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) anyu 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This routine provides access to tape drives owned and managed by an OS/400
+ * partition running on the same box as this Linux partition.
+ *
+ * All tape operations are performed by sending messages back and forth to
+ * the OS/400 partition.  The format of the messages is defined in
+ * iSeries/vio.h
+ */
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/mtio.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/major.h>
+#include <linux/completion.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+#include <asm/uaccess.h>
+#include <asm/ioctls.h>
+
+#include <asm/vio.h>
+#include <asm/iSeries/vio.h>
+#include <asm/iSeries/HvLpEvent.h>
+#include <asm/iSeries/HvCallEvent.h>
+#include <asm/iSeries/HvLpConfig.h>
+
+#define VIOTAPE_VERSION		"1.2"
+#define VIOTAPE_MAXREQ		1
+
+#define VIOTAPE_KERN_WARN	KERN_WARNING "viotape: "
+#define VIOTAPE_KERN_INFO	KERN_INFO "viotape: "
+
+static int viotape_numdev;
+
+/*
+ * The minor number follows the conventions of the SCSI tape drives.  The
+ * rewind and mode are encoded in the minor #.  We use this struct to break
+ * them out
+ */
+struct viot_devinfo_struct {
+	int devno;
+	int mode;
+	int rewind;
+};
+
+#define VIOTAPOP_RESET          0
+#define VIOTAPOP_FSF	        1
+#define VIOTAPOP_BSF	        2
+#define VIOTAPOP_FSR	        3
+#define VIOTAPOP_BSR	        4
+#define VIOTAPOP_WEOF	        5
+#define VIOTAPOP_REW	        6
+#define VIOTAPOP_NOP	        7
+#define VIOTAPOP_EOM	        8
+#define VIOTAPOP_ERASE          9
+#define VIOTAPOP_SETBLK        10
+#define VIOTAPOP_SETDENSITY    11
+#define VIOTAPOP_SETPOS	       12
+#define VIOTAPOP_GETPOS	       13
+#define VIOTAPOP_SETPART       14
+#define VIOTAPOP_UNLOAD        15
+
+struct viotapelpevent {
+	struct HvLpEvent event;
+	u32 reserved;
+	u16 version;
+	u16 sub_type_result;
+	u16 tape;
+	u16 flags;
+	u32 token;
+	u64 len;
+	union {
+		struct {
+			u32 tape_op;
+			u32 count;
+		} op;
+		struct {
+			u32 type;
+			u32 resid;
+			u32 dsreg;
+			u32 gstat;
+			u32 erreg;
+			u32 file_no;
+			u32 block_no;
+		} get_status;
+		struct {
+			u32 block_no;
+		} get_pos;
+	} u;
+};
+
+enum viotapesubtype {
+	viotapeopen = 0x0001,
+	viotapeclose = 0x0002,
+	viotaperead = 0x0003,
+	viotapewrite = 0x0004,
+	viotapegetinfo = 0x0005,
+	viotapeop = 0x0006,
+	viotapegetpos = 0x0007,
+	viotapesetpos = 0x0008,
+	viotapegetstatus = 0x0009
+};
+
+enum viotaperc {
+	viotape_InvalidRange = 0x0601,
+	viotape_InvalidToken = 0x0602,
+	viotape_DMAError = 0x0603,
+	viotape_UseError = 0x0604,
+	viotape_ReleaseError = 0x0605,
+	viotape_InvalidTape = 0x0606,
+	viotape_InvalidOp = 0x0607,
+	viotape_TapeErr = 0x0608,
+
+	viotape_AllocTimedOut = 0x0640,
+	viotape_BOTEnc = 0x0641,
+	viotape_BlankTape = 0x0642,
+	viotape_BufferEmpty = 0x0643,
+	viotape_CleanCartFound = 0x0644,
+	viotape_CmdNotAllowed = 0x0645,
+	viotape_CmdNotSupported = 0x0646,
+	viotape_DataCheck = 0x0647,
+	viotape_DecompressErr = 0x0648,
+	viotape_DeviceTimeout = 0x0649,
+	viotape_DeviceUnavail = 0x064a,
+	viotape_DeviceBusy = 0x064b,
+	viotape_EndOfMedia = 0x064c,
+	viotape_EndOfTape = 0x064d,
+	viotape_EquipCheck = 0x064e,
+	viotape_InsufficientRs = 0x064f,
+	viotape_InvalidLogBlk = 0x0650,
+	viotape_LengthError = 0x0651,
+	viotape_LibDoorOpen = 0x0652,
+	viotape_LoadFailure = 0x0653,
+	viotape_NotCapable = 0x0654,
+	viotape_NotOperational = 0x0655,
+	viotape_NotReady = 0x0656,
+	viotape_OpCancelled = 0x0657,
+	viotape_PhyLinkErr = 0x0658,
+	viotape_RdyNotBOT = 0x0659,
+	viotape_TapeMark = 0x065a,
+	viotape_WriteProt = 0x065b
+};
+
+static const struct vio_error_entry viotape_err_table[] = {
+	{ viotape_InvalidRange, EIO, "Internal error" },
+	{ viotape_InvalidToken, EIO, "Internal error" },
+	{ viotape_DMAError, EIO, "DMA error" },
+	{ viotape_UseError, EIO, "Internal error" },
+	{ viotape_ReleaseError, EIO, "Internal error" },
+	{ viotape_InvalidTape, EIO, "Invalid tape device" },
+	{ viotape_InvalidOp, EIO, "Invalid operation" },
+	{ viotape_TapeErr, EIO, "Tape error" },
+	{ viotape_AllocTimedOut, EBUSY, "Allocate timed out" },
+	{ viotape_BOTEnc, EIO, "Beginning of tape encountered" },
+	{ viotape_BlankTape, EIO, "Blank tape" },
+	{ viotape_BufferEmpty, EIO, "Buffer empty" },
+	{ viotape_CleanCartFound, ENOMEDIUM, "Cleaning cartridge found" },
+	{ viotape_CmdNotAllowed, EIO, "Command not allowed" },
+	{ viotape_CmdNotSupported, EIO, "Command not supported" },
+	{ viotape_DataCheck, EIO, "Data check" },
+	{ viotape_DecompressErr, EIO, "Decompression error" },
+	{ viotape_DeviceTimeout, EBUSY, "Device timeout" },
+	{ viotape_DeviceUnavail, EIO, "Device unavailable" },
+	{ viotape_DeviceBusy, EBUSY, "Device busy" },
+	{ viotape_EndOfMedia, ENOSPC, "End of media" },
+	{ viotape_EndOfTape, ENOSPC, "End of tape" },
+	{ viotape_EquipCheck, EIO, "Equipment check" },
+	{ viotape_InsufficientRs, EOVERFLOW, "Insufficient tape resources" },
+	{ viotape_InvalidLogBlk, EIO, "Invalid logical block location" },
+	{ viotape_LengthError, EOVERFLOW, "Length error" },
+	{ viotape_LibDoorOpen, EBUSY, "Door open" },
+	{ viotape_LoadFailure, ENOMEDIUM, "Load failure" },
+	{ viotape_NotCapable, EIO, "Not capable" },
+	{ viotape_NotOperational, EIO, "Not operational" },
+	{ viotape_NotReady, EIO, "Not ready" },
+	{ viotape_OpCancelled, EIO, "Operation cancelled" },
+	{ viotape_PhyLinkErr, EIO, "Physical link error" },
+	{ viotape_RdyNotBOT, EIO, "Ready but not beginning of tape" },
+	{ viotape_TapeMark, EIO, "Tape mark" },
+	{ viotape_WriteProt, EROFS, "Write protection error" },
+	{ 0, 0, NULL },
+};
+
+/* Maximum number of tapes we support */
+#define VIOTAPE_MAX_TAPE	HVMAXARCHITECTEDVIRTUALTAPES
+#define MAX_PARTITIONS		4
+
+/* defines for current tape state */
+#define VIOT_IDLE		0
+#define VIOT_READING		1
+#define VIOT_WRITING		2
+
+/* Our info on the tapes */
+struct tape_descr {
+	char rsrcname[10];
+	char type[4];
+	char model[3];
+};
+
+static struct tape_descr *viotape_unitinfo;
+static dma_addr_t viotape_unitinfo_token;
+
+static struct mtget viomtget[VIOTAPE_MAX_TAPE];
+
+static struct class_simple *tape_class;
+
+static struct device *tape_device[VIOTAPE_MAX_TAPE];
+
+/*
+ * maintain the current state of each tape (and partition)
+ * so that we know when to write EOF marks.
+ */
+static struct {
+	unsigned char	cur_part;
+	int		dev_handle;
+	unsigned char	part_stat_rwi[MAX_PARTITIONS];
+} state[VIOTAPE_MAX_TAPE];
+
+/* We single-thread */
+static struct semaphore reqSem;
+
+/*
+ * When we send a request, we use this struct to get the response back
+ * from the interrupt handler
+ */
+struct op_struct {
+	void			*buffer;
+	dma_addr_t		dmaaddr;
+	size_t			count;
+	int			rc;
+	int			non_blocking;
+	struct completion	com;
+	struct device		*dev;
+	struct op_struct	*next;
+};
+
+static spinlock_t	op_struct_list_lock;
+static struct op_struct	*op_struct_list;
+
+/* forward declaration to resolve interdependence */
+static int chg_state(int index, unsigned char new_state, struct file *file);
+
+/* procfs support */
+static int proc_viotape_show(struct seq_file *m, void *v)
+{
+	int i;
+
+	seq_printf(m, "viotape driver version " VIOTAPE_VERSION "\n");
+	for (i = 0; i < viotape_numdev; i++) {
+		seq_printf(m, "viotape device %d is iSeries resource %10.10s"
+				"type %4.4s, model %3.3s\n",
+				i, viotape_unitinfo[i].rsrcname,
+				viotape_unitinfo[i].type,
+				viotape_unitinfo[i].model);
+	}
+	return 0;
+}
+
+static int proc_viotape_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, proc_viotape_show, NULL);
+}
+
+static struct file_operations proc_viotape_operations = {
+	.open		= proc_viotape_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+/* Decode the device minor number into its parts */
+void get_dev_info(struct inode *ino, struct viot_devinfo_struct *devi)
+{
+	devi->devno = iminor(ino) & 0x1F;
+	devi->mode = (iminor(ino) & 0x60) >> 5;
+	/* if bit is set in the minor, do _not_ rewind automatically */
+	devi->rewind = (iminor(ino) & 0x80) == 0;
+}
+
+/* This is called only from the exit and init paths, so no need for locking */
+static void clear_op_struct_pool(void)
+{
+	while (op_struct_list) {
+		struct op_struct *toFree = op_struct_list;
+		op_struct_list = op_struct_list->next;
+		kfree(toFree);
+	}
+}
+
+/* Likewise, this is only called from the init path */
+static int add_op_structs(int structs)
+{
+	int i;
+
+	for (i = 0; i < structs; ++i) {
+		struct op_struct *new_struct =
+			kmalloc(sizeof(*new_struct), GFP_KERNEL);
+		if (!new_struct) {
+			clear_op_struct_pool();
+			return -ENOMEM;
+		}
+		new_struct->next = op_struct_list;
+		op_struct_list = new_struct;
+	}
+	return 0;
+}
+
+/* Allocate an op structure from our pool */
+static struct op_struct *get_op_struct(void)
+{
+	struct op_struct *retval;
+	unsigned long flags;
+
+	spin_lock_irqsave(&op_struct_list_lock, flags);
+	retval = op_struct_list;
+	if (retval)
+		op_struct_list = retval->next;
+	spin_unlock_irqrestore(&op_struct_list_lock, flags);
+	if (retval) {
+		memset(retval, 0, sizeof(*retval));
+		init_completion(&retval->com);
+	}
+
+	return retval;
+}
+
+/* Return an op structure to our pool */
+static void free_op_struct(struct op_struct *op_struct)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&op_struct_list_lock, flags);
+	op_struct->next = op_struct_list;
+	op_struct_list = op_struct;
+	spin_unlock_irqrestore(&op_struct_list_lock, flags);
+}
+
+/* Map our tape return codes to errno values */
+int tape_rc_to_errno(int tape_rc, char *operation, int tapeno)
+{
+	const struct vio_error_entry *err;
+
+	if (tape_rc == 0)
+		return 0;
+
+	err = vio_lookup_rc(viotape_err_table, tape_rc);
+	printk(VIOTAPE_KERN_WARN "error(%s) 0x%04x on Device %d (%-10s): %s\n",
+			operation, tape_rc, tapeno,
+			viotape_unitinfo[tapeno].rsrcname, err->msg);
+	return -err->errno;
+}
+
+/* Get info on all tapes from OS/400 */
+static int get_viotape_info(void)
+{
+	HvLpEvent_Rc hvrc;
+	int i;
+	size_t len = sizeof(*viotape_unitinfo) * VIOTAPE_MAX_TAPE;
+	struct op_struct *op = get_op_struct();
+
+	if (op == NULL)
+		return -ENOMEM;
+
+	viotape_unitinfo = dma_alloc_coherent(iSeries_vio_dev, len,
+		&viotape_unitinfo_token, GFP_ATOMIC);
+	if (viotape_unitinfo == NULL) {
+		free_op_struct(op);
+		return -ENOMEM;
+	}
+
+	memset(viotape_unitinfo, 0, len);
+
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_tape | viotapegetinfo,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64) (unsigned long) op, VIOVERSION << 16,
+			viotape_unitinfo_token, len, 0, 0);
+	if (hvrc != HvLpEvent_Rc_Good) {
+		printk(VIOTAPE_KERN_WARN "hv error on op %d\n",
+				(int)hvrc);
+		free_op_struct(op);
+		return -EIO;
+	}
+
+	wait_for_completion(&op->com);
+
+	free_op_struct(op);
+
+	for (i = 0;
+	     ((i < VIOTAPE_MAX_TAPE) && (viotape_unitinfo[i].rsrcname[0]));
+	     i++)
+		viotape_numdev++;
+	return 0;
+}
+
+
+/* Write */
+static ssize_t viotap_write(struct file *file, const char *buf,
+		size_t count, loff_t * ppos)
+{
+	HvLpEvent_Rc hvrc;
+	unsigned short flags = file->f_flags;
+	int noblock = ((flags & O_NONBLOCK) != 0);
+	ssize_t ret;
+	struct viot_devinfo_struct devi;
+	struct op_struct *op = get_op_struct();
+
+	if (op == NULL)
+		return -ENOMEM;
+
+	get_dev_info(file->f_dentry->d_inode, &devi);
+
+	/*
+	 * We need to make sure we can send a request.  We use
+	 * a semaphore to keep track of # requests in use.  If
+	 * we are non-blocking, make sure we don't block on the
+	 * semaphore
+	 */
+	if (noblock) {
+		if (down_trylock(&reqSem)) {
+			ret = -EWOULDBLOCK;
+			goto free_op;
+		}
+	} else
+		down(&reqSem);
+
+	/* Allocate a DMA buffer */
+	op->dev = tape_device[devi.devno];
+	op->buffer = dma_alloc_coherent(op->dev, count, &op->dmaaddr,
+			GFP_ATOMIC);
+
+	if (op->buffer == NULL) {
+		printk(VIOTAPE_KERN_WARN
+				"error allocating dma buffer for len %ld\n",
+				count);
+		ret = -EFAULT;
+		goto up_sem;
+	}
+
+	/* Copy the data into the buffer */
+	if (copy_from_user(op->buffer, buf, count)) {
+		printk(VIOTAPE_KERN_WARN "tape: error on copy from user\n");
+		ret = -EFAULT;
+		goto free_dma;
+	}
+
+	op->non_blocking = noblock;
+	init_completion(&op->com);
+	op->count = count;
+
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_tape | viotapewrite,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)(unsigned long)op, VIOVERSION << 16,
+			((u64)devi.devno << 48) | op->dmaaddr, count, 0, 0);
+	if (hvrc != HvLpEvent_Rc_Good) {
+		printk(VIOTAPE_KERN_WARN "hv error on op %d\n",
+				(int)hvrc);
+		ret = -EIO;
+		goto free_dma;
+	}
+
+	if (noblock)
+		return count;
+
+	wait_for_completion(&op->com);
+
+	if (op->rc)
+		ret = tape_rc_to_errno(op->rc, "write", devi.devno);
+	else {
+		chg_state(devi.devno, VIOT_WRITING, file);
+		ret = op->count;
+	}
+
+free_dma:
+	dma_free_coherent(op->dev, count, op->buffer, op->dmaaddr);
+up_sem:
+	up(&reqSem);
+free_op:
+	free_op_struct(op);
+	return ret;
+}
+
+/* read */
+static ssize_t viotap_read(struct file *file, char *buf, size_t count,
+		loff_t *ptr)
+{
+	HvLpEvent_Rc hvrc;
+	unsigned short flags = file->f_flags;
+	struct op_struct *op = get_op_struct();
+	int noblock = ((flags & O_NONBLOCK) != 0);
+	ssize_t ret;
+	struct viot_devinfo_struct devi;
+
+	if (op == NULL)
+		return -ENOMEM;
+
+	get_dev_info(file->f_dentry->d_inode, &devi);
+
+	/*
+	 * We need to make sure we can send a request.  We use
+	 * a semaphore to keep track of # requests in use.  If
+	 * we are non-blocking, make sure we don't block on the
+	 * semaphore
+	 */
+	if (noblock) {
+		if (down_trylock(&reqSem)) {
+			ret = -EWOULDBLOCK;
+			goto free_op;
+		}
+	} else
+		down(&reqSem);
+
+	chg_state(devi.devno, VIOT_READING, file);
+
+	/* Allocate a DMA buffer */
+	op->dev = tape_device[devi.devno];
+	op->buffer = dma_alloc_coherent(op->dev, count, &op->dmaaddr,
+			GFP_ATOMIC);
+	if (op->buffer == NULL) {
+		ret = -EFAULT;
+		goto up_sem;
+	}
+
+	op->count = count;
+	init_completion(&op->com);
+
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_tape | viotaperead,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)(unsigned long)op, VIOVERSION << 16,
+			((u64)devi.devno << 48) | op->dmaaddr, count, 0, 0);
+	if (hvrc != HvLpEvent_Rc_Good) {
+		printk(VIOTAPE_KERN_WARN "tape hv error on op %d\n",
+				(int)hvrc);
+		ret = -EIO;
+		goto free_dma;
+	}
+
+	wait_for_completion(&op->com);
+
+	if (op->rc)
+		ret = tape_rc_to_errno(op->rc, "read", devi.devno);
+	else {
+		ret = op->count;
+		if (ret && copy_to_user(buf, op->buffer, ret)) {
+			printk(VIOTAPE_KERN_WARN "error on copy_to_user\n");
+			ret = -EFAULT;
+		}
+	}
+
+free_dma:
+	dma_free_coherent(op->dev, count, op->buffer, op->dmaaddr);
+up_sem:
+	up(&reqSem);
+free_op:
+	free_op_struct(op);
+	return ret;
+}
+
+/* ioctl */
+static int viotap_ioctl(struct inode *inode, struct file *file,
+		unsigned int cmd, unsigned long arg)
+{
+	HvLpEvent_Rc hvrc;
+	int ret;
+	struct viot_devinfo_struct devi;
+	struct mtop mtc;
+	u32 myOp;
+	struct op_struct *op = get_op_struct();
+
+	if (op == NULL)
+		return -ENOMEM;
+
+	get_dev_info(file->f_dentry->d_inode, &devi);
+
+	down(&reqSem);
+
+	ret = -EINVAL;
+
+	switch (cmd) {
+	case MTIOCTOP:
+		ret = -EFAULT;
+		/*
+		 * inode is null if and only if we (the kernel)
+		 * made the request
+		 */
+		if (inode == NULL)
+			memcpy(&mtc, (void *) arg, sizeof(struct mtop));
+		else if (copy_from_user((char *)&mtc, (char *)arg,
+					sizeof(struct mtop)))
+			goto free_op;
+
+		ret = -EIO;
+		switch (mtc.mt_op) {
+		case MTRESET:
+			myOp = VIOTAPOP_RESET;
+			break;
+		case MTFSF:
+			myOp = VIOTAPOP_FSF;
+			break;
+		case MTBSF:
+			myOp = VIOTAPOP_BSF;
+			break;
+		case MTFSR:
+			myOp = VIOTAPOP_FSR;
+			break;
+		case MTBSR:
+			myOp = VIOTAPOP_BSR;
+			break;
+		case MTWEOF:
+			myOp = VIOTAPOP_WEOF;
+			break;
+		case MTREW:
+			myOp = VIOTAPOP_REW;
+			break;
+		case MTNOP:
+			myOp = VIOTAPOP_NOP;
+			break;
+		case MTEOM:
+			myOp = VIOTAPOP_EOM;
+			break;
+		case MTERASE:
+			myOp = VIOTAPOP_ERASE;
+			break;
+		case MTSETBLK:
+			myOp = VIOTAPOP_SETBLK;
+			break;
+		case MTSETDENSITY:
+			myOp = VIOTAPOP_SETDENSITY;
+			break;
+		case MTTELL:
+			myOp = VIOTAPOP_GETPOS;
+			break;
+		case MTSEEK:
+			myOp = VIOTAPOP_SETPOS;
+			break;
+		case MTSETPART:
+			myOp = VIOTAPOP_SETPART;
+			break;
+		case MTOFFL:
+			myOp = VIOTAPOP_UNLOAD;
+			break;
+		default:
+			printk(VIOTAPE_KERN_WARN "MTIOCTOP called "
+					"with invalid op 0x%x\n", mtc.mt_op);
+			goto free_op;
+		}
+
+		/*
+		 * if we moved the head, we are no longer
+		 * reading or writing
+		 */
+		switch (mtc.mt_op) {
+		case MTFSF:
+		case MTBSF:
+		case MTFSR:
+		case MTBSR:
+		case MTTELL:
+		case MTSEEK:
+		case MTREW:
+			chg_state(devi.devno, VIOT_IDLE, file);
+		}
+
+		init_completion(&op->com);
+		hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+				HvLpEvent_Type_VirtualIo,
+				viomajorsubtype_tape | viotapeop,
+				HvLpEvent_AckInd_DoAck,
+				HvLpEvent_AckType_ImmediateAck,
+				viopath_sourceinst(viopath_hostLp),
+				viopath_targetinst(viopath_hostLp),
+				(u64)(unsigned long)op,
+				VIOVERSION << 16,
+				((u64)devi.devno << 48), 0,
+				(((u64)myOp) << 32) | mtc.mt_count, 0);
+		if (hvrc != HvLpEvent_Rc_Good) {
+			printk(VIOTAPE_KERN_WARN "hv error on op %d\n",
+					(int)hvrc);
+			goto free_op;
+		}
+		wait_for_completion(&op->com);
+		ret = tape_rc_to_errno(op->rc, "tape operation", devi.devno);
+		goto free_op;
+
+	case MTIOCGET:
+		ret = -EIO;
+		init_completion(&op->com);
+		hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+				HvLpEvent_Type_VirtualIo,
+				viomajorsubtype_tape | viotapegetstatus,
+				HvLpEvent_AckInd_DoAck,
+				HvLpEvent_AckType_ImmediateAck,
+				viopath_sourceinst(viopath_hostLp),
+				viopath_targetinst(viopath_hostLp),
+				(u64)(unsigned long)op, VIOVERSION << 16,
+				((u64)devi.devno << 48), 0, 0, 0);
+		if (hvrc != HvLpEvent_Rc_Good) {
+			printk(VIOTAPE_KERN_WARN "hv error on op %d\n",
+					(int)hvrc);
+			goto free_op;
+		}
+		wait_for_completion(&op->com);
+
+		/* Operation is complete - grab the error code */
+		ret = tape_rc_to_errno(op->rc, "get status", devi.devno);
+		free_op_struct(op);
+		up(&reqSem);
+
+		if ((ret == 0) && copy_to_user((void *)arg,
+					&viomtget[devi.devno],
+					sizeof(viomtget[0])))
+			ret = -EFAULT;
+		return ret;
+	case MTIOCPOS:
+		printk(VIOTAPE_KERN_WARN "Got an (unsupported) MTIOCPOS\n");
+		break;
+	default:
+		printk(VIOTAPE_KERN_WARN "got an unsupported ioctl 0x%0x\n",
+				cmd);
+		break;
+	}
+
+free_op:
+	free_op_struct(op);
+	up(&reqSem);
+	return ret;
+}
+
+static int viotap_open(struct inode *inode, struct file *file)
+{
+	HvLpEvent_Rc hvrc;
+	struct viot_devinfo_struct devi;
+	int ret;
+	struct op_struct *op = get_op_struct();
+
+	if (op == NULL)
+		return -ENOMEM;
+
+	get_dev_info(file->f_dentry->d_inode, &devi);
+
+	/* Note: We currently only support one mode! */
+	if ((devi.devno >= viotape_numdev) || (devi.mode)) {
+		ret = -ENODEV;
+		goto free_op;
+	}
+
+	init_completion(&op->com);
+
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_tape | viotapeopen,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)(unsigned long)op, VIOVERSION << 16,
+			((u64)devi.devno << 48), 0, 0, 0);
+	if (hvrc != 0) {
+		printk(VIOTAPE_KERN_WARN "bad rc on signalLpEvent %d\n",
+				(int) hvrc);
+		ret = -EIO;
+		goto free_op;
+	}
+
+	wait_for_completion(&op->com);
+	ret = tape_rc_to_errno(op->rc, "open", devi.devno);
+
+free_op:
+	free_op_struct(op);
+	return ret;
+}
+
+
+static int viotap_release(struct inode *inode, struct file *file)
+{
+	HvLpEvent_Rc hvrc;
+	struct viot_devinfo_struct devi;
+	int ret = 0;
+	struct op_struct *op = get_op_struct();
+
+	if (op == NULL)
+		return -ENOMEM;
+	init_completion(&op->com);
+
+	get_dev_info(file->f_dentry->d_inode, &devi);
+
+	if (devi.devno >= viotape_numdev) {
+		ret = -ENODEV;
+		goto free_op;
+	}
+
+	chg_state(devi.devno, VIOT_IDLE, file);
+
+	if (devi.rewind) {
+		hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+				HvLpEvent_Type_VirtualIo,
+				viomajorsubtype_tape | viotapeop,
+				HvLpEvent_AckInd_DoAck,
+				HvLpEvent_AckType_ImmediateAck,
+				viopath_sourceinst(viopath_hostLp),
+				viopath_targetinst(viopath_hostLp),
+				(u64)(unsigned long)op, VIOVERSION << 16,
+				((u64)devi.devno << 48), 0,
+				((u64)VIOTAPOP_REW) << 32, 0);
+		wait_for_completion(&op->com);
+
+		tape_rc_to_errno(op->rc, "rewind", devi.devno);
+	}
+
+	hvrc = HvCallEvent_signalLpEventFast(viopath_hostLp,
+			HvLpEvent_Type_VirtualIo,
+			viomajorsubtype_tape | viotapeclose,
+			HvLpEvent_AckInd_DoAck, HvLpEvent_AckType_ImmediateAck,
+			viopath_sourceinst(viopath_hostLp),
+			viopath_targetinst(viopath_hostLp),
+			(u64)(unsigned long)op, VIOVERSION << 16,
+			((u64)devi.devno << 48), 0, 0, 0);
+	if (hvrc != 0) {
+		printk(VIOTAPE_KERN_WARN "bad rc on signalLpEvent %d\n",
+				(int) hvrc);
+		ret = -EIO;
+		goto free_op;
+	}
+
+	wait_for_completion(&op->com);
+
+	if (op->rc)
+		printk(VIOTAPE_KERN_WARN "close failed\n");
+
+free_op:
+	free_op_struct(op);
+	return ret;
+}
+
+struct file_operations viotap_fops = {
+	owner: THIS_MODULE,
+	read: viotap_read,
+	write: viotap_write,
+	ioctl: viotap_ioctl,
+	open: viotap_open,
+	release: viotap_release,
+};
+
+/* Handle interrupt events for tape */
+static void vioHandleTapeEvent(struct HvLpEvent *event)
+{
+	int tapeminor;
+	struct op_struct *op;
+	struct viotapelpevent *tevent = (struct viotapelpevent *)event;
+
+	if (event == NULL) {
+		/* Notification that a partition went away! */
+		if (!viopath_isactive(viopath_hostLp)) {
+			/* TODO! Clean up */
+		}
+		return;
+	}
+
+	tapeminor = event->xSubtype & VIOMINOR_SUBTYPE_MASK;
+	op = (struct op_struct *)event->xCorrelationToken;
+	switch (tapeminor) {
+	case viotapegetinfo:
+	case viotapeopen:
+	case viotapeclose:
+		op->rc = tevent->sub_type_result;
+		complete(&op->com);
+		break;
+	case viotaperead:
+		op->rc = tevent->sub_type_result;
+		op->count = tevent->len;
+		complete(&op->com);
+		break;
+	case viotapewrite:
+		if (op->non_blocking) {
+			dma_free_coherent(op->dev, op->count,
+					op->buffer, op->dmaaddr);
+			free_op_struct(op);
+			up(&reqSem);
+		} else {
+			op->rc = tevent->sub_type_result;
+			op->count = tevent->len;
+			complete(&op->com);
+		}
+		break;
+	case viotapeop:
+	case viotapegetpos:
+	case viotapesetpos:
+	case viotapegetstatus:
+		if (op) {
+			op->count = tevent->u.op.count;
+			op->rc = tevent->sub_type_result;
+			if (!op->non_blocking)
+				complete(&op->com);
+		}
+		break;
+	default:
+		printk(VIOTAPE_KERN_WARN "weird ack\n");
+	}
+}
+
+static int viotape_probe(struct vio_dev *vdev, const struct vio_device_id *id)
+{
+	char tapename[32];
+	int i = vdev->unit_address;
+	int j;
+
+	if (i >= viotape_numdev)
+		return -ENODEV;
+
+	tape_device[i] = &vdev->dev;
+
+	state[i].cur_part = 0;
+	for (j = 0; j < MAX_PARTITIONS; ++j)
+		state[i].part_stat_rwi[j] = VIOT_IDLE;
+	class_simple_device_add(tape_class, MKDEV(VIOTAPE_MAJOR, i), NULL,
+			"iseries!vt%d", i);
+	class_simple_device_add(tape_class, MKDEV(VIOTAPE_MAJOR, i | 0x80),
+			NULL, "iseries!nvt%d", i);
+	devfs_mk_cdev(MKDEV(VIOTAPE_MAJOR, i), S_IFCHR | S_IRUSR | S_IWUSR,
+			"iseries/vt%d", i);
+	devfs_mk_cdev(MKDEV(VIOTAPE_MAJOR, i | 0x80),
+			S_IFCHR | S_IRUSR | S_IWUSR, "iseries/nvt%d", i);
+	sprintf(tapename, "iseries/vt%d", i);
+	state[i].dev_handle = devfs_register_tape(tapename);
+	printk(VIOTAPE_KERN_INFO "tape %s is iSeries "
+			"resource %10.10s type %4.4s, model %3.3s\n",
+			tapename, viotape_unitinfo[i].rsrcname,
+			viotape_unitinfo[i].type, viotape_unitinfo[i].model);
+	return 0;
+}
+
+static int viotape_remove(struct vio_dev *vdev)
+{
+	int i = vdev->unit_address;
+
+	devfs_remove("iseries/nvt%d", i);
+	devfs_remove("iseries/vt%d", i);
+	devfs_unregister_tape(state[i].dev_handle);
+	class_simple_device_remove(MKDEV(VIOTAPE_MAJOR, i | 0x80));
+	class_simple_device_remove(MKDEV(VIOTAPE_MAJOR, i));
+	return 0;
+}
+
+/**
+ * viotape_device_table: Used by vio.c to match devices that we
+ * support.
+ */
+static struct vio_device_id viotape_device_table[] __devinitdata = {
+	{ "viotape", "" },
+	{ 0, }
+};
+
+MODULE_DEVICE_TABLE(vio, viotape_device_table);
+static struct vio_driver viotape_driver = {
+	.name = "viotape",
+	.id_table = viotape_device_table,
+	.probe = viotape_probe,
+	.remove = viotape_remove
+};
+
+
+int __init viotap_init(void)
+{
+	int ret;
+	struct proc_dir_entry *e;
+
+	op_struct_list = NULL;
+	if ((ret = add_op_structs(VIOTAPE_MAXREQ)) < 0) {
+		printk(VIOTAPE_KERN_WARN "couldn't allocate op structs\n");
+		return ret;
+	}
+	spin_lock_init(&op_struct_list_lock);
+
+	sema_init(&reqSem, VIOTAPE_MAXREQ);
+
+	if (viopath_hostLp == HvLpIndexInvalid) {
+		vio_set_hostlp();
+		if (viopath_hostLp == HvLpIndexInvalid) {
+			ret = -ENODEV;
+			goto clear_op;
+		}
+	}
+
+	ret = viopath_open(viopath_hostLp, viomajorsubtype_tape,
+			VIOTAPE_MAXREQ + 2);
+	if (ret) {
+		printk(VIOTAPE_KERN_WARN
+				"error on viopath_open to hostlp %d\n", ret);
+		ret = -EIO;
+		goto clear_op;
+	}
+
+	printk(VIOTAPE_KERN_INFO "vers " VIOTAPE_VERSION
+			", hosting partition %d\n", viopath_hostLp);
+
+	vio_setHandler(viomajorsubtype_tape, vioHandleTapeEvent);
+
+	ret = register_chrdev(VIOTAPE_MAJOR, "viotape", &viotap_fops);
+	if (ret < 0) {
+		printk(VIOTAPE_KERN_WARN "Error registering viotape device\n");
+		goto clear_handler;
+	}
+
+	tape_class = class_simple_create(THIS_MODULE, "tape");
+	if (IS_ERR(tape_class)) {
+		printk(VIOTAPE_KERN_WARN "Unable to allocat class\n");
+		ret = PTR_ERR(tape_class);
+		goto unreg_chrdev;
+	}
+
+	if ((ret = get_viotape_info()) < 0) {
+		printk(VIOTAPE_KERN_WARN "Unable to obtain virtual device information");
+		goto unreg_class;
+	}
+
+	ret = vio_register_driver(&viotape_driver);
+	if (ret)
+		goto unreg_class;
+
+	e = create_proc_entry("iSeries/viotape", S_IFREG|S_IRUGO, NULL);
+	if (e) {
+		e->owner = THIS_MODULE;
+		e->proc_fops = &proc_viotape_operations;
+	}
+
+	return 0;
+
+unreg_class:
+	class_simple_destroy(tape_class);
+unreg_chrdev:
+	unregister_chrdev(VIOTAPE_MAJOR, "viotape");
+clear_handler:
+	vio_clearHandler(viomajorsubtype_tape);
+	viopath_close(viopath_hostLp, viomajorsubtype_tape, VIOTAPE_MAXREQ + 2);
+clear_op:
+	clear_op_struct_pool();
+	return ret;
+}
+
+/* Give a new state to the tape object */
+static int chg_state(int index, unsigned char new_state, struct file *file)
+{
+	unsigned char *cur_state =
+	    &state[index].part_stat_rwi[state[index].cur_part];
+	int rc = 0;
+
+	/* if the same state, don't bother */
+	if (*cur_state == new_state)
+		return 0;
+
+	/* write an EOF if changing from writing to some other state */
+	if (*cur_state == VIOT_WRITING) {
+		struct mtop write_eof = { MTWEOF, 1 };
+
+		rc = viotap_ioctl(NULL, file, MTIOCTOP,
+				  (unsigned long)&write_eof);
+	}
+	*cur_state = new_state;
+	return rc;
+}
+
+/* Cleanup */
+static void __exit viotap_exit(void)
+{
+	int ret;
+
+	remove_proc_entry("iSeries/viotape", NULL);
+	vio_unregister_driver(&viotape_driver);
+	class_simple_destroy(tape_class);
+	ret = unregister_chrdev(VIOTAPE_MAJOR, "viotape");
+	if (ret < 0)
+		printk(VIOTAPE_KERN_WARN "Error unregistering device: %d\n",
+				ret);
+	if (viotape_unitinfo)
+		dma_free_coherent(iSeries_vio_dev,
+				sizeof(viotape_unitinfo[0]) * VIOTAPE_MAX_TAPE,
+				viotape_unitinfo, viotape_unitinfo_token);
+	viopath_close(viopath_hostLp, viomajorsubtype_tape, VIOTAPE_MAXREQ + 2);
+	vio_clearHandler(viomajorsubtype_tape);
+	clear_op_struct_pool();
+}
+
+MODULE_LICENSE("GPL");
+module_init(viotap_init);
+module_exit(viotap_exit);
diff --git a/drivers/char/vme_scc.c b/drivers/char/vme_scc.c
new file mode 100644
index 0000000..19ba836
--- /dev/null
+++ b/drivers/char/vme_scc.c
@@ -0,0 +1,1056 @@
+/*
+ * drivers/char/vme_scc.c: MVME147, MVME162, BVME6000 SCC serial ports
+ * implementation.
+ * Copyright 1999 Richard Hirst <richard@sleepie.demon.co.uk>
+ *
+ * Based on atari_SCC.c which was
+ *   Copyright 1994-95 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
+ *   Partially based on PC-Linux serial.c by Linus Torvalds and Theodore Ts'o
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/kdev_t.h>
+#include <asm/io.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/mm.h>
+#include <linux/serial.h>
+#include <linux/fcntl.h>
+#include <linux/major.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/miscdevice.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <asm/setup.h>
+#include <asm/bootinfo.h>
+
+#ifdef CONFIG_MVME147_SCC
+#include <asm/mvme147hw.h>
+#endif
+#ifdef CONFIG_MVME162_SCC
+#include <asm/mvme16xhw.h>
+#endif
+#ifdef CONFIG_BVME6000_SCC
+#include <asm/bvme6000hw.h>
+#endif
+
+#include <linux/generic_serial.h>
+#include "scc.h"
+
+
+#define CHANNEL_A	0
+#define CHANNEL_B	1
+
+#define SCC_MINOR_BASE	64
+
+/* Shadows for all SCC write registers */
+static unsigned char scc_shadow[2][16];
+
+/* Location to access for SCC register access delay */
+static volatile unsigned char *scc_del = NULL;
+
+/* To keep track of STATUS_REG state for detection of Ext/Status int source */
+static unsigned char scc_last_status_reg[2];
+
+/***************************** Prototypes *****************************/
+
+/* Function prototypes */
+static void scc_disable_tx_interrupts(void * ptr);
+static void scc_enable_tx_interrupts(void * ptr);
+static void scc_disable_rx_interrupts(void * ptr);
+static void scc_enable_rx_interrupts(void * ptr);
+static int  scc_get_CD(void * ptr);
+static void scc_shutdown_port(void * ptr);
+static int scc_set_real_termios(void  *ptr);
+static void scc_hungup(void  *ptr);
+static void scc_close(void  *ptr);
+static int scc_chars_in_buffer(void * ptr);
+static int scc_open(struct tty_struct * tty, struct file * filp);
+static int scc_ioctl(struct tty_struct * tty, struct file * filp,
+                     unsigned int cmd, unsigned long arg);
+static void scc_throttle(struct tty_struct *tty);
+static void scc_unthrottle(struct tty_struct *tty);
+static irqreturn_t scc_tx_int(int irq, void *data, struct pt_regs *fp);
+static irqreturn_t scc_rx_int(int irq, void *data, struct pt_regs *fp);
+static irqreturn_t scc_stat_int(int irq, void *data, struct pt_regs *fp);
+static irqreturn_t scc_spcond_int(int irq, void *data, struct pt_regs *fp);
+static void scc_setsignals(struct scc_port *port, int dtr, int rts);
+static void scc_break_ctl(struct tty_struct *tty, int break_state);
+
+static struct tty_driver *scc_driver;
+
+struct scc_port scc_ports[2];
+
+int scc_initialized = 0;
+
+/*---------------------------------------------------------------------------
+ * Interface from generic_serial.c back here
+ *--------------------------------------------------------------------------*/
+
+static struct real_driver scc_real_driver = {
+        scc_disable_tx_interrupts,
+        scc_enable_tx_interrupts,
+        scc_disable_rx_interrupts,
+        scc_enable_rx_interrupts,
+        scc_get_CD,
+        scc_shutdown_port,
+        scc_set_real_termios,
+        scc_chars_in_buffer,
+        scc_close,
+        scc_hungup,
+        NULL
+};
+
+
+static struct tty_operations scc_ops = {
+	.open	= scc_open,
+	.close = gs_close,
+	.write = gs_write,
+	.put_char = gs_put_char,
+	.flush_chars = gs_flush_chars,
+	.write_room = gs_write_room,
+	.chars_in_buffer = gs_chars_in_buffer,
+	.flush_buffer = gs_flush_buffer,
+	.ioctl = scc_ioctl,
+	.throttle = scc_throttle,
+	.unthrottle = scc_unthrottle,
+	.set_termios = gs_set_termios,
+	.stop = gs_stop,
+	.start = gs_start,
+	.hangup = gs_hangup,
+	.break_ctl = scc_break_ctl,
+};
+
+/*----------------------------------------------------------------------------
+ * vme_scc_init() and support functions
+ *---------------------------------------------------------------------------*/
+
+static int scc_init_drivers(void)
+{
+	int error;
+
+	scc_driver = alloc_tty_driver(2);
+	if (!scc_driver)
+		return -ENOMEM;
+	scc_driver->owner = THIS_MODULE;
+	scc_driver->driver_name = "scc";
+	scc_driver->name = "ttyS";
+	scc_driver->devfs_name = "tts/";
+	scc_driver->major = TTY_MAJOR;
+	scc_driver->minor_start = SCC_MINOR_BASE;
+	scc_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	scc_driver->subtype = SERIAL_TYPE_NORMAL;
+	scc_driver->init_termios = tty_std_termios;
+	scc_driver->init_termios.c_cflag =
+	  B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	scc_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(scc_driver, &scc_ops);
+
+	if ((error = tty_register_driver(scc_driver))) {
+		printk(KERN_ERR "scc: Couldn't register scc driver, error = %d\n",
+		       error);
+		put_tty_driver(scc_driver);
+		return 1;
+	}
+
+	return 0;
+}
+
+
+/* ports[] array is indexed by line no (i.e. [0] for ttyS0, [1] for ttyS1).
+ */
+
+static void scc_init_portstructs(void)
+{
+	struct scc_port *port;
+	int i;
+
+	for (i = 0; i < 2; i++) {
+		port = scc_ports + i;
+		port->gs.magic = SCC_MAGIC;
+		port->gs.close_delay = HZ/2;
+		port->gs.closing_wait = 30 * HZ;
+		port->gs.rd = &scc_real_driver;
+#ifdef NEW_WRITE_LOCKING
+		port->gs.port_write_sem = MUTEX;
+#endif
+		init_waitqueue_head(&port->gs.open_wait);
+		init_waitqueue_head(&port->gs.close_wait);
+	}
+}
+
+
+#ifdef CONFIG_MVME147_SCC
+static int mvme147_scc_init(void)
+{
+	struct scc_port *port;
+
+	printk(KERN_INFO "SCC: MVME147 Serial Driver\n");
+	/* Init channel A */
+	port = &scc_ports[0];
+	port->channel = CHANNEL_A;
+	port->ctrlp = (volatile unsigned char *)M147_SCC_A_ADDR;
+	port->datap = port->ctrlp + 1;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(MVME147_IRQ_SCCA_TX, scc_tx_int, SA_INTERRUPT,
+		            "SCC-A TX", port);
+	request_irq(MVME147_IRQ_SCCA_STAT, scc_stat_int, SA_INTERRUPT,
+		            "SCC-A status", port);
+	request_irq(MVME147_IRQ_SCCA_RX, scc_rx_int, SA_INTERRUPT,
+		            "SCC-A RX", port);
+	request_irq(MVME147_IRQ_SCCA_SPCOND, scc_spcond_int, SA_INTERRUPT,
+		            "SCC-A special cond", port);
+	{
+		SCC_ACCESS_INIT(port);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+		/* Set the interrupt vector */
+		SCCwrite(INT_VECTOR_REG, MVME147_IRQ_SCC_BASE);
+		/* Interrupt parameters: vector includes status, status low */
+		SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+		SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+	}
+
+	/* Init channel B */
+	port = &scc_ports[1];
+	port->channel = CHANNEL_B;
+	port->ctrlp = (volatile unsigned char *)M147_SCC_B_ADDR;
+	port->datap = port->ctrlp + 1;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(MVME147_IRQ_SCCB_TX, scc_tx_int, SA_INTERRUPT,
+		            "SCC-B TX", port);
+	request_irq(MVME147_IRQ_SCCB_STAT, scc_stat_int, SA_INTERRUPT,
+		            "SCC-B status", port);
+	request_irq(MVME147_IRQ_SCCB_RX, scc_rx_int, SA_INTERRUPT,
+		            "SCC-B RX", port);
+	request_irq(MVME147_IRQ_SCCB_SPCOND, scc_spcond_int, SA_INTERRUPT,
+		            "SCC-B special cond", port);
+	{
+		SCC_ACCESS_INIT(port);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+	}
+
+        /* Ensure interrupts are enabled in the PCC chip */
+        m147_pcc->serial_cntrl=PCC_LEVEL_SERIAL|PCC_INT_ENAB;
+
+	/* Initialise the tty driver structures and register */
+	scc_init_portstructs();
+	scc_init_drivers();
+
+	return 0;
+}
+#endif
+
+
+#ifdef CONFIG_MVME162_SCC
+static int mvme162_scc_init(void)
+{
+	struct scc_port *port;
+
+	if (!(mvme16x_config & MVME16x_CONFIG_GOT_SCCA))
+		return (-ENODEV);
+
+	printk(KERN_INFO "SCC: MVME162 Serial Driver\n");
+	/* Init channel A */
+	port = &scc_ports[0];
+	port->channel = CHANNEL_A;
+	port->ctrlp = (volatile unsigned char *)MVME_SCC_A_ADDR;
+	port->datap = port->ctrlp + 2;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(MVME162_IRQ_SCCA_TX, scc_tx_int, SA_INTERRUPT,
+		            "SCC-A TX", port);
+	request_irq(MVME162_IRQ_SCCA_STAT, scc_stat_int, SA_INTERRUPT,
+		            "SCC-A status", port);
+	request_irq(MVME162_IRQ_SCCA_RX, scc_rx_int, SA_INTERRUPT,
+		            "SCC-A RX", port);
+	request_irq(MVME162_IRQ_SCCA_SPCOND, scc_spcond_int, SA_INTERRUPT,
+		            "SCC-A special cond", port);
+	{
+		SCC_ACCESS_INIT(port);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+		/* Set the interrupt vector */
+		SCCwrite(INT_VECTOR_REG, MVME162_IRQ_SCC_BASE);
+		/* Interrupt parameters: vector includes status, status low */
+		SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+		SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+	}
+
+	/* Init channel B */
+	port = &scc_ports[1];
+	port->channel = CHANNEL_B;
+	port->ctrlp = (volatile unsigned char *)MVME_SCC_B_ADDR;
+	port->datap = port->ctrlp + 2;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(MVME162_IRQ_SCCB_TX, scc_tx_int, SA_INTERRUPT,
+		            "SCC-B TX", port);
+	request_irq(MVME162_IRQ_SCCB_STAT, scc_stat_int, SA_INTERRUPT,
+		            "SCC-B status", port);
+	request_irq(MVME162_IRQ_SCCB_RX, scc_rx_int, SA_INTERRUPT,
+		            "SCC-B RX", port);
+	request_irq(MVME162_IRQ_SCCB_SPCOND, scc_spcond_int, SA_INTERRUPT,
+		            "SCC-B special cond", port);
+
+	{
+		SCC_ACCESS_INIT(port);	/* Either channel will do */
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+	}
+
+        /* Ensure interrupts are enabled in the MC2 chip */
+        *(volatile char *)0xfff4201d = 0x14;
+
+	/* Initialise the tty driver structures and register */
+	scc_init_portstructs();
+	scc_init_drivers();
+
+	return 0;
+}
+#endif
+
+
+#ifdef CONFIG_BVME6000_SCC
+static int bvme6000_scc_init(void)
+{
+	struct scc_port *port;
+
+	printk(KERN_INFO "SCC: BVME6000 Serial Driver\n");
+	/* Init channel A */
+	port = &scc_ports[0];
+	port->channel = CHANNEL_A;
+	port->ctrlp = (volatile unsigned char *)BVME_SCC_A_ADDR;
+	port->datap = port->ctrlp + 4;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(BVME_IRQ_SCCA_TX, scc_tx_int, SA_INTERRUPT,
+		            "SCC-A TX", port);
+	request_irq(BVME_IRQ_SCCA_STAT, scc_stat_int, SA_INTERRUPT,
+		            "SCC-A status", port);
+	request_irq(BVME_IRQ_SCCA_RX, scc_rx_int, SA_INTERRUPT,
+		            "SCC-A RX", port);
+	request_irq(BVME_IRQ_SCCA_SPCOND, scc_spcond_int, SA_INTERRUPT,
+		            "SCC-A special cond", port);
+	{
+		SCC_ACCESS_INIT(port);
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+		/* Set the interrupt vector */
+		SCCwrite(INT_VECTOR_REG, BVME_IRQ_SCC_BASE);
+		/* Interrupt parameters: vector includes status, status low */
+		SCCwrite(MASTER_INT_CTRL, MIC_VEC_INCL_STAT);
+		SCCmod(MASTER_INT_CTRL, 0xff, MIC_MASTER_INT_ENAB);
+	}
+
+	/* Init channel B */
+	port = &scc_ports[1];
+	port->channel = CHANNEL_B;
+	port->ctrlp = (volatile unsigned char *)BVME_SCC_B_ADDR;
+	port->datap = port->ctrlp + 4;
+	port->port_a = &scc_ports[0];
+	port->port_b = &scc_ports[1];
+	request_irq(BVME_IRQ_SCCB_TX, scc_tx_int, SA_INTERRUPT,
+		            "SCC-B TX", port);
+	request_irq(BVME_IRQ_SCCB_STAT, scc_stat_int, SA_INTERRUPT,
+		            "SCC-B status", port);
+	request_irq(BVME_IRQ_SCCB_RX, scc_rx_int, SA_INTERRUPT,
+		            "SCC-B RX", port);
+	request_irq(BVME_IRQ_SCCB_SPCOND, scc_spcond_int, SA_INTERRUPT,
+		            "SCC-B special cond", port);
+
+	{
+		SCC_ACCESS_INIT(port);	/* Either channel will do */
+
+		/* disable interrupts for this channel */
+		SCCwrite(INT_AND_DMA_REG, 0);
+	}
+
+	/* Initialise the tty driver structures and register */
+	scc_init_portstructs();
+	scc_init_drivers();
+
+	return 0;
+}
+#endif
+
+
+static int vme_scc_init(void)
+{
+	int res = -ENODEV;
+
+#ifdef CONFIG_MVME147_SCC
+	if (MACH_IS_MVME147)
+		res = mvme147_scc_init();
+#endif
+#ifdef CONFIG_MVME162_SCC
+	if (MACH_IS_MVME16x)
+		res = mvme162_scc_init();
+#endif
+#ifdef CONFIG_BVME6000_SCC
+	if (MACH_IS_BVME6000)
+		res = bvme6000_scc_init();
+#endif
+	return res;
+}
+
+module_init(vme_scc_init);
+
+
+/*---------------------------------------------------------------------------
+ * Interrupt handlers
+ *--------------------------------------------------------------------------*/
+
+static irqreturn_t scc_rx_int(int irq, void *data, struct pt_regs *fp)
+{
+	unsigned char	ch;
+	struct scc_port *port = data;
+	struct tty_struct *tty = port->gs.tty;
+	SCC_ACCESS_INIT(port);
+
+	ch = SCCread_NB(RX_DATA_REG);
+	if (!tty) {
+		printk(KERN_WARNING "scc_rx_int with NULL tty!\n");
+		SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+		return IRQ_HANDLED;
+	}
+	if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+		*tty->flip.char_buf_ptr = ch;
+		*tty->flip.flag_buf_ptr = 0;
+		tty->flip.flag_buf_ptr++;
+		tty->flip.char_buf_ptr++;
+		tty->flip.count++;
+	}
+
+	/* Check if another character is already ready; in that case, the
+	 * spcond_int() function must be used, because this character may have an
+	 * error condition that isn't signalled by the interrupt vector used!
+	 */
+	if (SCCread(INT_PENDING_REG) &
+	    (port->channel == CHANNEL_A ? IPR_A_RX : IPR_B_RX)) {
+		scc_spcond_int (irq, data, fp);
+		return IRQ_HANDLED;
+	}
+
+	SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+
+	tty_flip_buffer_push(tty);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t scc_spcond_int(int irq, void *data, struct pt_regs *fp)
+{
+	struct scc_port *port = data;
+	struct tty_struct *tty = port->gs.tty;
+	unsigned char	stat, ch, err;
+	int		int_pending_mask = port->channel == CHANNEL_A ?
+			                   IPR_A_RX : IPR_B_RX;
+	SCC_ACCESS_INIT(port);
+	
+	if (!tty) {
+		printk(KERN_WARNING "scc_spcond_int with NULL tty!\n");
+		SCCwrite(COMMAND_REG, CR_ERROR_RESET);
+		SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+		return IRQ_HANDLED;
+	}
+	do {
+		stat = SCCread(SPCOND_STATUS_REG);
+		ch = SCCread_NB(RX_DATA_REG);
+
+		if (stat & SCSR_RX_OVERRUN)
+			err = TTY_OVERRUN;
+		else if (stat & SCSR_PARITY_ERR)
+			err = TTY_PARITY;
+		else if (stat & SCSR_CRC_FRAME_ERR)
+			err = TTY_FRAME;
+		else
+			err = 0;
+
+		if (tty->flip.count < TTY_FLIPBUF_SIZE) {
+			*tty->flip.char_buf_ptr = ch;
+			*tty->flip.flag_buf_ptr = err;
+			tty->flip.flag_buf_ptr++;
+			tty->flip.char_buf_ptr++;
+			tty->flip.count++;
+		}
+
+		/* ++TeSche: *All* errors have to be cleared manually,
+		 * else the condition persists for the next chars
+		 */
+		if (err)
+		  SCCwrite(COMMAND_REG, CR_ERROR_RESET);
+
+	} while(SCCread(INT_PENDING_REG) & int_pending_mask);
+
+	SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+
+	tty_flip_buffer_push(tty);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t scc_tx_int(int irq, void *data, struct pt_regs *fp)
+{
+	struct scc_port *port = data;
+	SCC_ACCESS_INIT(port);
+
+	if (!port->gs.tty) {
+		printk(KERN_WARNING "scc_tx_int with NULL tty!\n");
+		SCCmod (INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+		SCCwrite(COMMAND_REG, CR_TX_PENDING_RESET);
+		SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+		return IRQ_HANDLED;
+	}
+	while ((SCCread_NB(STATUS_REG) & SR_TX_BUF_EMPTY)) {
+		if (port->x_char) {
+			SCCwrite(TX_DATA_REG, port->x_char);
+			port->x_char = 0;
+		}
+		else if ((port->gs.xmit_cnt <= 0) || port->gs.tty->stopped ||
+				port->gs.tty->hw_stopped)
+			break;
+		else {
+			SCCwrite(TX_DATA_REG, port->gs.xmit_buf[port->gs.xmit_tail++]);
+			port->gs.xmit_tail = port->gs.xmit_tail & (SERIAL_XMIT_SIZE-1);
+			if (--port->gs.xmit_cnt <= 0)
+				break;
+		}
+	}
+	if ((port->gs.xmit_cnt <= 0) || port->gs.tty->stopped ||
+			port->gs.tty->hw_stopped) {
+		/* disable tx interrupts */
+		SCCmod (INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+		SCCwrite(COMMAND_REG, CR_TX_PENDING_RESET);   /* disable tx_int on next tx underrun? */
+		port->gs.flags &= ~GS_TX_INTEN;
+	}
+	if (port->gs.tty && port->gs.xmit_cnt <= port->gs.wakeup_chars)
+		tty_wakeup(port->gs.tty);
+
+	SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+	return IRQ_HANDLED;
+}
+
+
+static irqreturn_t scc_stat_int(int irq, void *data, struct pt_regs *fp)
+{
+	struct scc_port *port = data;
+	unsigned channel = port->channel;
+	unsigned char	last_sr, sr, changed;
+	SCC_ACCESS_INIT(port);
+
+	last_sr = scc_last_status_reg[channel];
+	sr = scc_last_status_reg[channel] = SCCread_NB(STATUS_REG);
+	changed = last_sr ^ sr;
+
+	if (changed & SR_DCD) {
+		port->c_dcd = !!(sr & SR_DCD);
+		if (!(port->gs.flags & ASYNC_CHECK_CD))
+			;	/* Don't report DCD changes */
+		else if (port->c_dcd) {
+			wake_up_interruptible(&port->gs.open_wait);
+		}
+		else {
+			if (port->gs.tty)
+				tty_hangup (port->gs.tty);
+		}
+	}
+	SCCwrite(COMMAND_REG, CR_EXTSTAT_RESET);
+	SCCwrite_NB(COMMAND_REG, CR_HIGHEST_IUS_RESET);
+	return IRQ_HANDLED;
+}
+
+
+/*---------------------------------------------------------------------------
+ * generic_serial.c callback funtions
+ *--------------------------------------------------------------------------*/
+
+static void scc_disable_tx_interrupts(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+
+	local_irq_save(flags);
+	SCCmod(INT_AND_DMA_REG, ~IDR_TX_INT_ENAB, 0);
+	port->gs.flags &= ~GS_TX_INTEN;
+	local_irq_restore(flags);
+}
+
+
+static void scc_enable_tx_interrupts(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+
+	local_irq_save(flags);
+	SCCmod(INT_AND_DMA_REG, 0xff, IDR_TX_INT_ENAB);
+	/* restart the transmitter */
+	scc_tx_int (0, port, 0);
+	local_irq_restore(flags);
+}
+
+
+static void scc_disable_rx_interrupts(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+
+	local_irq_save(flags);
+	SCCmod(INT_AND_DMA_REG,
+	    ~(IDR_RX_INT_MASK|IDR_PARERR_AS_SPCOND|IDR_EXTSTAT_INT_ENAB), 0);
+	local_irq_restore(flags);
+}
+
+
+static void scc_enable_rx_interrupts(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+
+	local_irq_save(flags);
+	SCCmod(INT_AND_DMA_REG, 0xff,
+		IDR_EXTSTAT_INT_ENAB|IDR_PARERR_AS_SPCOND|IDR_RX_INT_ALL);
+	local_irq_restore(flags);
+}
+
+
+static int scc_get_CD(void *ptr)
+{
+	struct scc_port *port = ptr;
+	unsigned channel = port->channel;
+
+	return !!(scc_last_status_reg[channel] & SR_DCD);
+}
+
+
+static void scc_shutdown_port(void *ptr)
+{
+	struct scc_port *port = ptr;
+
+	port->gs.flags &= ~ GS_ACTIVE;
+	if (port->gs.tty && port->gs.tty->termios->c_cflag & HUPCL) {
+		scc_setsignals (port, 0, 0);
+	}
+}
+
+
+static int scc_set_real_termios (void *ptr)
+{
+	/* the SCC has char sizes 5,7,6,8 in that order! */
+	static int chsize_map[4] = { 0, 2, 1, 3 };
+	unsigned cflag, baud, chsize, channel, brgval = 0;
+	unsigned long flags;
+	struct scc_port *port = ptr;
+	SCC_ACCESS_INIT(port);
+
+	if (!port->gs.tty || !port->gs.tty->termios) return 0;
+
+	channel = port->channel;
+
+	if (channel == CHANNEL_A)
+		return 0;		/* Settings controlled by boot PROM */
+
+	cflag  = port->gs.tty->termios->c_cflag;
+	baud = port->gs.baud;
+	chsize = (cflag & CSIZE) >> 4;
+
+	if (baud == 0) {
+		/* speed == 0 -> drop DTR */
+		local_irq_save(flags);
+		SCCmod(TX_CTRL_REG, ~TCR_DTR, 0);
+		local_irq_restore(flags);
+		return 0;
+	}
+	else if ((MACH_IS_MVME16x && (baud < 50 || baud > 38400)) ||
+		 (MACH_IS_MVME147 && (baud < 50 || baud > 19200)) ||
+		 (MACH_IS_BVME6000 &&(baud < 50 || baud > 76800))) {
+		printk(KERN_NOTICE "SCC: Bad speed requested, %d\n", baud);
+		return 0;
+	}
+
+	if (cflag & CLOCAL)
+		port->gs.flags &= ~ASYNC_CHECK_CD;
+	else
+		port->gs.flags |= ASYNC_CHECK_CD;
+
+#ifdef CONFIG_MVME147_SCC
+	if (MACH_IS_MVME147)
+		brgval = (M147_SCC_PCLK + baud/2) / (16 * 2 * baud) - 2;
+#endif
+#ifdef CONFIG_MVME162_SCC
+	if (MACH_IS_MVME16x)
+		brgval = (MVME_SCC_PCLK + baud/2) / (16 * 2 * baud) - 2;
+#endif
+#ifdef CONFIG_BVME6000_SCC
+	if (MACH_IS_BVME6000)
+		brgval = (BVME_SCC_RTxC + baud/2) / (16 * 2 * baud) - 2;
+#endif
+	/* Now we have all parameters and can go to set them: */
+	local_irq_save(flags);
+
+	/* receiver's character size and auto-enables */
+	SCCmod(RX_CTRL_REG, ~(RCR_CHSIZE_MASK|RCR_AUTO_ENAB_MODE),
+			(chsize_map[chsize] << 6) |
+			((cflag & CRTSCTS) ? RCR_AUTO_ENAB_MODE : 0));
+	/* parity and stop bits (both, Tx and Rx), clock mode never changes */
+	SCCmod (AUX1_CTRL_REG,
+		~(A1CR_PARITY_MASK | A1CR_MODE_MASK),
+		((cflag & PARENB
+		  ? (cflag & PARODD ? A1CR_PARITY_ODD : A1CR_PARITY_EVEN)
+		  : A1CR_PARITY_NONE)
+		 | (cflag & CSTOPB ? A1CR_MODE_ASYNC_2 : A1CR_MODE_ASYNC_1)));
+	/* sender's character size, set DTR for valid baud rate */
+	SCCmod(TX_CTRL_REG, ~TCR_CHSIZE_MASK, chsize_map[chsize] << 5 | TCR_DTR);
+	/* clock sources never change */
+	/* disable BRG before changing the value */
+	SCCmod(DPLL_CTRL_REG, ~DCR_BRG_ENAB, 0);
+	/* BRG value */
+	SCCwrite(TIMER_LOW_REG, brgval & 0xff);
+	SCCwrite(TIMER_HIGH_REG, (brgval >> 8) & 0xff);
+	/* BRG enable, and clock source never changes */
+	SCCmod(DPLL_CTRL_REG, 0xff, DCR_BRG_ENAB);
+
+	local_irq_restore(flags);
+
+	return 0;
+}
+
+
+static int scc_chars_in_buffer (void *ptr)
+{
+	struct scc_port *port = ptr;
+	SCC_ACCESS_INIT(port);
+
+	return (SCCread (SPCOND_STATUS_REG) & SCSR_ALL_SENT) ? 0  : 1;
+}
+
+
+/* Comment taken from sx.c (2.4.0):
+   I haven't the foggiest why the decrement use count has to happen
+   here. The whole linux serial drivers stuff needs to be redesigned.
+   My guess is that this is a hack to minimize the impact of a bug
+   elsewhere. Thinking about it some more. (try it sometime) Try
+   running minicom on a serial port that is driven by a modularized
+   driver. Have the modem hangup. Then remove the driver module. Then
+   exit minicom.  I expect an "oops".  -- REW */
+
+static void scc_hungup(void *ptr)
+{
+	scc_disable_tx_interrupts(ptr);
+	scc_disable_rx_interrupts(ptr);
+}
+
+
+static void scc_close(void *ptr)
+{
+	scc_disable_tx_interrupts(ptr);
+	scc_disable_rx_interrupts(ptr);
+}
+
+
+/*---------------------------------------------------------------------------
+ * Internal support functions
+ *--------------------------------------------------------------------------*/
+
+static void scc_setsignals(struct scc_port *port, int dtr, int rts)
+{
+	unsigned long flags;
+	unsigned char t;
+	SCC_ACCESS_INIT(port);
+
+	local_irq_save(flags);
+	t = SCCread(TX_CTRL_REG);
+	if (dtr >= 0) t = dtr? (t | TCR_DTR): (t & ~TCR_DTR);
+	if (rts >= 0) t = rts? (t | TCR_RTS): (t & ~TCR_RTS);
+	SCCwrite(TX_CTRL_REG, t);
+	local_irq_restore(flags);
+}
+
+
+static void scc_send_xchar(struct tty_struct *tty, char ch)
+{
+	struct scc_port *port = (struct scc_port *)tty->driver_data;
+
+	port->x_char = ch;
+	if (ch)
+		scc_enable_tx_interrupts(port);
+}
+
+
+/*---------------------------------------------------------------------------
+ * Driver entrypoints referenced from above
+ *--------------------------------------------------------------------------*/
+
+static int scc_open (struct tty_struct * tty, struct file * filp)
+{
+	int line = tty->index;
+	int retval;
+	struct scc_port *port = &scc_ports[line];
+	int i, channel = port->channel;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+#if defined(CONFIG_MVME162_SCC) || defined(CONFIG_MVME147_SCC)
+	static const struct {
+		unsigned reg, val;
+	} mvme_init_tab[] = {
+		/* Values for MVME162 and MVME147 */
+		/* no parity, 1 stop bit, async, 1:16 */
+		{ AUX1_CTRL_REG, A1CR_PARITY_NONE|A1CR_MODE_ASYNC_1|A1CR_CLKMODE_x16 },
+		/* parity error is special cond, ints disabled, no DMA */
+		{ INT_AND_DMA_REG, IDR_PARERR_AS_SPCOND | IDR_RX_INT_DISAB },
+		/* Rx 8 bits/char, no auto enable, Rx off */
+		{ RX_CTRL_REG, RCR_CHSIZE_8 },
+		/* DTR off, Tx 8 bits/char, RTS off, Tx off */
+		{ TX_CTRL_REG, TCR_CHSIZE_8 },
+		/* special features off */
+		{ AUX2_CTRL_REG, 0 },
+		{ CLK_CTRL_REG, CCR_RXCLK_BRG | CCR_TXCLK_BRG },
+		{ DPLL_CTRL_REG, DCR_BRG_ENAB | DCR_BRG_USE_PCLK },
+		/* Start Rx */
+		{ RX_CTRL_REG, RCR_RX_ENAB | RCR_CHSIZE_8 },
+		/* Start Tx */
+		{ TX_CTRL_REG, TCR_TX_ENAB | TCR_RTS | TCR_DTR | TCR_CHSIZE_8 },
+		/* Ext/Stat ints: DCD only */
+		{ INT_CTRL_REG, ICR_ENAB_DCD_INT },
+		/* Reset Ext/Stat ints */
+		{ COMMAND_REG, CR_EXTSTAT_RESET },
+		/* ...again */
+		{ COMMAND_REG, CR_EXTSTAT_RESET },
+	};
+#endif
+#if defined(CONFIG_BVME6000_SCC)
+	static const struct {
+		unsigned reg, val;
+	} bvme_init_tab[] = {
+		/* Values for BVME6000 */
+		/* no parity, 1 stop bit, async, 1:16 */
+		{ AUX1_CTRL_REG, A1CR_PARITY_NONE|A1CR_MODE_ASYNC_1|A1CR_CLKMODE_x16 },
+		/* parity error is special cond, ints disabled, no DMA */
+		{ INT_AND_DMA_REG, IDR_PARERR_AS_SPCOND | IDR_RX_INT_DISAB },
+		/* Rx 8 bits/char, no auto enable, Rx off */
+		{ RX_CTRL_REG, RCR_CHSIZE_8 },
+		/* DTR off, Tx 8 bits/char, RTS off, Tx off */
+		{ TX_CTRL_REG, TCR_CHSIZE_8 },
+		/* special features off */
+		{ AUX2_CTRL_REG, 0 },
+		{ CLK_CTRL_REG, CCR_RTxC_XTAL | CCR_RXCLK_BRG | CCR_TXCLK_BRG },
+		{ DPLL_CTRL_REG, DCR_BRG_ENAB },
+		/* Start Rx */
+		{ RX_CTRL_REG, RCR_RX_ENAB | RCR_CHSIZE_8 },
+		/* Start Tx */
+		{ TX_CTRL_REG, TCR_TX_ENAB | TCR_RTS | TCR_DTR | TCR_CHSIZE_8 },
+		/* Ext/Stat ints: DCD only */
+		{ INT_CTRL_REG, ICR_ENAB_DCD_INT },
+		/* Reset Ext/Stat ints */
+		{ COMMAND_REG, CR_EXTSTAT_RESET },
+		/* ...again */
+		{ COMMAND_REG, CR_EXTSTAT_RESET },
+	};
+#endif
+	if (!(port->gs.flags & ASYNC_INITIALIZED)) {
+		local_irq_save(flags);
+#if defined(CONFIG_MVME147_SCC) || defined(CONFIG_MVME162_SCC)
+		if (MACH_IS_MVME147 || MACH_IS_MVME16x) {
+			for (i=0; i<sizeof(mvme_init_tab)/sizeof(*mvme_init_tab); ++i)
+				SCCwrite(mvme_init_tab[i].reg, mvme_init_tab[i].val);
+		}
+#endif
+#if defined(CONFIG_BVME6000_SCC)
+		if (MACH_IS_BVME6000) {
+			for (i=0; i<sizeof(bvme_init_tab)/sizeof(*bvme_init_tab); ++i)
+				SCCwrite(bvme_init_tab[i].reg, bvme_init_tab[i].val);
+		}
+#endif
+
+		/* remember status register for detection of DCD and CTS changes */
+		scc_last_status_reg[channel] = SCCread(STATUS_REG);
+
+		port->c_dcd = 0;	/* Prevent initial 1->0 interrupt */
+		scc_setsignals (port, 1,1);
+		local_irq_restore(flags);
+	}
+
+	tty->driver_data = port;
+	port->gs.tty = tty;
+	port->gs.count++;
+	retval = gs_init_port(&port->gs);
+	if (retval) {
+		port->gs.count--;
+		return retval;
+	}
+	port->gs.flags |= GS_ACTIVE;
+	retval = gs_block_til_ready(port, filp);
+
+	if (retval) {
+		port->gs.count--;
+		return retval;
+	}
+
+	port->c_dcd = scc_get_CD (port);
+
+	scc_enable_rx_interrupts(port);
+
+	return 0;
+}
+
+
+static void scc_throttle (struct tty_struct * tty)
+{
+	struct scc_port *port = (struct scc_port *)tty->driver_data;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+
+	if (tty->termios->c_cflag & CRTSCTS) {
+		local_irq_save(flags);
+		SCCmod(TX_CTRL_REG, ~TCR_RTS, 0);
+		local_irq_restore(flags);
+	}
+	if (I_IXOFF(tty))
+		scc_send_xchar(tty, STOP_CHAR(tty));
+}
+
+
+static void scc_unthrottle (struct tty_struct * tty)
+{
+	struct scc_port *port = (struct scc_port *)tty->driver_data;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+
+	if (tty->termios->c_cflag & CRTSCTS) {
+		local_irq_save(flags);
+		SCCmod(TX_CTRL_REG, 0xff, TCR_RTS);
+		local_irq_restore(flags);
+	}
+	if (I_IXOFF(tty))
+		scc_send_xchar(tty, START_CHAR(tty));
+}
+
+
+static int scc_ioctl(struct tty_struct *tty, struct file *file,
+		     unsigned int cmd, unsigned long arg)
+{
+	return -ENOIOCTLCMD;
+}
+
+
+static void scc_break_ctl(struct tty_struct *tty, int break_state)
+{
+	struct scc_port *port = (struct scc_port *)tty->driver_data;
+	unsigned long	flags;
+	SCC_ACCESS_INIT(port);
+
+	local_irq_save(flags);
+	SCCmod(TX_CTRL_REG, ~TCR_SEND_BREAK, 
+			break_state ? TCR_SEND_BREAK : 0);
+	local_irq_restore(flags);
+}
+
+
+/*---------------------------------------------------------------------------
+ * Serial console stuff...
+ *--------------------------------------------------------------------------*/
+
+#define scc_delay() do { __asm__ __volatile__ (" nop; nop"); } while (0)
+
+static void scc_ch_write (char ch)
+{
+	volatile char *p = NULL;
+	
+#ifdef CONFIG_MVME147_SCC
+	if (MACH_IS_MVME147)
+		p = (volatile char *)M147_SCC_A_ADDR;
+#endif
+#ifdef CONFIG_MVME162_SCC
+	if (MACH_IS_MVME16x)
+		p = (volatile char *)MVME_SCC_A_ADDR;
+#endif
+#ifdef CONFIG_BVME6000_SCC
+	if (MACH_IS_BVME6000)
+		p = (volatile char *)BVME_SCC_A_ADDR;
+#endif
+
+	do {
+		scc_delay();
+	}
+	while (!(*p & 4));
+	scc_delay();
+	*p = 8;
+	scc_delay();
+	*p = ch;
+}
+
+/* The console must be locked when we get here. */
+
+static void scc_console_write (struct console *co, const char *str, unsigned count)
+{
+	unsigned long	flags;
+
+	local_irq_save(flags);
+
+	while (count--)
+	{
+		if (*str == '\n')
+			scc_ch_write ('\r');
+		scc_ch_write (*str++);
+	}
+	local_irq_restore(flags);
+}
+
+static struct tty_driver *scc_console_device(struct console *c, int *index)
+{
+	*index = c->index;
+	return scc_driver;
+}
+
+
+static int __init scc_console_setup(struct console *co, char *options)
+{
+	return 0;
+}
+
+
+static struct console sercons = {
+	.name		= "ttyS",
+	.write		= scc_console_write,
+	.device		= scc_console_device,
+	.setup		= scc_console_setup,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+};
+
+
+static int __init vme_scc_console_init(void)
+{
+	if (vme_brdtype == VME_TYPE_MVME147 ||
+			vme_brdtype == VME_TYPE_MVME162 ||
+			vme_brdtype == VME_TYPE_MVME172 ||
+			vme_brdtype == VME_TYPE_BVME4000 ||
+			vme_brdtype == VME_TYPE_BVME6000)
+		register_console(&sercons);
+	return 0;
+}
+console_initcall(vme_scc_console_init);
diff --git a/drivers/char/vr41xx_rtc.c b/drivers/char/vr41xx_rtc.c
new file mode 100644
index 0000000..a6dbe4d
--- /dev/null
+++ b/drivers/char/vr41xx_rtc.c
@@ -0,0 +1,709 @@
+/*
+ *  Driver for NEC VR4100 series  Real Time Clock unit.
+ *
+ *  Copyright (C) 2003-2005  Yoichi Yuasa <yuasa@hh.iij4u.or.jp>
+ *
+ *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/mc146818rtc.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/rtc.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+
+#include <asm/div64.h>
+#include <asm/io.h>
+#include <asm/time.h>
+#include <asm/uaccess.h>
+#include <asm/vr41xx/vr41xx.h>
+
+MODULE_AUTHOR("Yoichi Yuasa <yuasa@hh.iij4u.or.jp>");
+MODULE_DESCRIPTION("NEC VR4100 series RTC driver");
+MODULE_LICENSE("GPL");
+
+#define RTC1_TYPE1_START	0x0b0000c0UL
+#define RTC1_TYPE1_END		0x0b0000dfUL
+#define RTC2_TYPE1_START	0x0b0001c0UL
+#define RTC2_TYPE1_END		0x0b0001dfUL
+
+#define RTC1_TYPE2_START	0x0f000100UL
+#define RTC1_TYPE2_END		0x0f00011fUL
+#define RTC2_TYPE2_START	0x0f000120UL
+#define RTC2_TYPE2_END		0x0f00013fUL
+
+#define RTC1_SIZE		0x20
+#define RTC2_SIZE		0x20
+
+/* RTC 1 registers */
+#define ETIMELREG		0x00
+#define ETIMEMREG		0x02
+#define ETIMEHREG		0x04
+/* RFU */
+#define ECMPLREG		0x08
+#define ECMPMREG		0x0a
+#define ECMPHREG		0x0c
+/* RFU */
+#define RTCL1LREG		0x10
+#define RTCL1HREG		0x12
+#define RTCL1CNTLREG		0x14
+#define RTCL1CNTHREG		0x16
+#define RTCL2LREG		0x18
+#define RTCL2HREG		0x1a
+#define RTCL2CNTLREG		0x1c
+#define RTCL2CNTHREG		0x1e
+
+/* RTC 2 registers */
+#define TCLKLREG		0x00
+#define TCLKHREG		0x02
+#define TCLKCNTLREG		0x04
+#define TCLKCNTHREG		0x06
+/* RFU */
+#define RTCINTREG		0x1e
+ #define TCLOCK_INT		0x08
+ #define RTCLONG2_INT		0x04
+ #define RTCLONG1_INT		0x02
+ #define ELAPSEDTIME_INT	0x01
+
+#define RTC_FREQUENCY		32768
+#define MAX_PERIODIC_RATE	6553
+#define MAX_USER_PERIODIC_RATE	64
+
+static void __iomem *rtc1_base;
+static void __iomem *rtc2_base;
+
+#define rtc1_read(offset)		readw(rtc1_base + (offset))
+#define rtc1_write(offset, value)	writew((value), rtc1_base + (offset))
+
+#define rtc2_read(offset)		readw(rtc2_base + (offset))
+#define rtc2_write(offset, value)	writew((value), rtc2_base + (offset))
+
+static unsigned long epoch = 1970;	/* Jan 1 1970 00:00:00 */
+
+static spinlock_t rtc_task_lock;
+static wait_queue_head_t rtc_wait;
+static unsigned long rtc_irq_data;
+static struct fasync_struct *rtc_async_queue;
+static rtc_task_t *rtc_callback;
+static char rtc_name[] = "RTC";
+static unsigned long periodic_frequency;
+static unsigned long periodic_count;
+
+typedef enum {
+	RTC_RELEASE,
+	RTC_OPEN,
+} rtc_status_t;
+
+static rtc_status_t rtc_status;
+
+typedef enum {
+	FUNCTION_RTC_IOCTL,
+	FUNCTION_RTC_CONTROL,
+} rtc_callfrom_t;
+
+struct resource rtc_resource[2] = {
+	{	.name	= rtc_name,
+		.flags	= IORESOURCE_MEM,	},
+	{	.name	= rtc_name,
+		.flags	= IORESOURCE_MEM,	},
+};
+
+#define RTC_NUM_RESOURCES	sizeof(rtc_resource) / sizeof(struct resource)
+
+static inline unsigned long read_elapsed_second(void)
+{
+	unsigned long first_low, first_mid, first_high;
+	unsigned long second_low, second_mid, second_high;
+
+	do {
+		first_low = rtc1_read(ETIMELREG);
+		first_mid = rtc1_read(ETIMEMREG);
+		first_high = rtc1_read(ETIMEHREG);
+		second_low = rtc1_read(ETIMELREG);
+		second_mid = rtc1_read(ETIMEMREG);
+		second_high = rtc1_read(ETIMEHREG);
+	} while (first_low != second_low || first_mid != second_mid ||
+	         first_high != second_high);
+
+	return (first_high << 17) | (first_mid << 1) | (first_low >> 15);
+}
+
+static inline void write_elapsed_second(unsigned long sec)
+{
+	spin_lock_irq(&rtc_lock);
+
+	rtc1_write(ETIMELREG, (uint16_t)(sec << 15));
+	rtc1_write(ETIMEMREG, (uint16_t)(sec >> 1));
+	rtc1_write(ETIMEHREG, (uint16_t)(sec >> 17));
+
+	spin_unlock_irq(&rtc_lock);
+}
+
+static void set_alarm(struct rtc_time *time)
+{
+	unsigned long alarm_sec;
+
+	alarm_sec = mktime(time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
+	                   time->tm_hour, time->tm_min, time->tm_sec);
+
+	spin_lock_irq(&rtc_lock);
+
+	rtc1_write(ECMPLREG, (uint16_t)(alarm_sec << 15));
+	rtc1_write(ECMPMREG, (uint16_t)(alarm_sec >> 1));
+	rtc1_write(ECMPHREG, (uint16_t)(alarm_sec >> 17));
+
+	spin_unlock_irq(&rtc_lock);
+}
+
+static void read_alarm(struct rtc_time *time)
+{
+	unsigned long low, mid, high;
+
+	spin_lock_irq(&rtc_lock);
+
+	low = rtc1_read(ECMPLREG);
+	mid = rtc1_read(ECMPMREG);
+	high = rtc1_read(ECMPHREG);
+
+	spin_unlock_irq(&rtc_lock);
+
+	to_tm((high << 17) | (mid << 1) | (low >> 15), time);
+	time->tm_year -= 1900;
+}
+
+static void read_time(struct rtc_time *time)
+{
+	unsigned long epoch_sec, elapsed_sec;
+
+	epoch_sec = mktime(epoch, 1, 1, 0, 0, 0);
+	elapsed_sec = read_elapsed_second();
+
+	to_tm(epoch_sec + elapsed_sec, time);
+	time->tm_year -= 1900;
+}
+
+static void set_time(struct rtc_time *time)
+{
+	unsigned long epoch_sec, current_sec;
+
+	epoch_sec = mktime(epoch, 1, 1, 0, 0, 0);
+	current_sec = mktime(time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
+	                     time->tm_hour, time->tm_min, time->tm_sec);
+
+	write_elapsed_second(current_sec - epoch_sec);
+}
+
+static ssize_t rtc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	unsigned long irq_data;
+	int retval = 0;
+
+	if (count != sizeof(unsigned int) && count != sizeof(unsigned long))
+		return -EINVAL;
+
+	add_wait_queue(&rtc_wait, &wait);
+
+	do {
+		__set_current_state(TASK_INTERRUPTIBLE);
+
+		spin_lock_irq(&rtc_lock);
+		irq_data = rtc_irq_data;
+		rtc_irq_data = 0;
+		spin_unlock_irq(&rtc_lock);
+
+		if (irq_data != 0)
+			break;
+
+		if (file->f_flags & O_NONBLOCK) {
+			retval = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+	} while (1);
+
+	if (retval == 0) {
+		if (count == sizeof(unsigned int)) {
+			retval = put_user(irq_data, (unsigned int __user *)buf);
+			if (retval == 0)
+				retval = sizeof(unsigned int);
+		} else {
+			retval = put_user(irq_data, (unsigned long __user *)buf);
+			if (retval == 0)
+				retval = sizeof(unsigned long);
+		}
+
+	}
+
+	__set_current_state(TASK_RUNNING);
+	remove_wait_queue(&rtc_wait, &wait);
+
+	return retval;
+}
+
+static unsigned int rtc_poll(struct file *file, struct poll_table_struct *table)
+{
+	poll_wait(file, &rtc_wait, table);
+
+	if (rtc_irq_data != 0)
+		return POLLIN | POLLRDNORM;
+
+	return 0;
+}
+
+static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, rtc_callfrom_t from)
+{
+	struct rtc_time time;
+	unsigned long count;
+
+	switch (cmd) {
+	case RTC_AIE_ON:
+		enable_irq(ELAPSEDTIME_IRQ);
+		break;
+	case RTC_AIE_OFF:
+		disable_irq(ELAPSEDTIME_IRQ);
+		break;
+	case RTC_PIE_ON:
+		enable_irq(RTCLONG1_IRQ);
+		break;
+	case RTC_PIE_OFF:
+		disable_irq(RTCLONG1_IRQ);
+		break;
+	case RTC_ALM_SET:
+		if (copy_from_user(&time, (struct rtc_time __user *)arg,
+		                   sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		set_alarm(&time);
+		break;
+	case RTC_ALM_READ:
+		memset(&time, 0, sizeof(struct rtc_time));
+		read_alarm(&time);
+		break;
+	case RTC_RD_TIME:
+		memset(&time, 0, sizeof(struct rtc_time));
+		read_time(&time);
+		if (copy_to_user((void __user *)arg, &time, sizeof(struct rtc_time)))
+			return -EFAULT;
+		break;
+	case RTC_SET_TIME:
+		if (capable(CAP_SYS_TIME) == 0)
+			return -EACCES;
+
+		if (copy_from_user(&time, (struct rtc_time __user *)arg,
+		                   sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		set_time(&time);
+		break;
+	case RTC_IRQP_READ:
+		return put_user(periodic_frequency, (unsigned long __user *)arg);
+		break;
+	case RTC_IRQP_SET:
+		if (arg > MAX_PERIODIC_RATE)
+			return -EINVAL;
+
+		if (from == FUNCTION_RTC_IOCTL && arg > MAX_USER_PERIODIC_RATE &&
+		    capable(CAP_SYS_RESOURCE) == 0)
+			return -EACCES;
+
+		periodic_frequency = arg;
+
+		count = RTC_FREQUENCY;
+		do_div(count, arg);
+
+		periodic_count = count;
+
+		spin_lock_irq(&rtc_lock);
+
+		rtc1_write(RTCL1LREG, count);
+		rtc1_write(RTCL1HREG, count >> 16);
+
+		spin_unlock_irq(&rtc_lock);
+		break;
+	case RTC_EPOCH_READ:
+		return put_user(epoch, (unsigned long __user *)arg);
+	case RTC_EPOCH_SET:
+		/* Doesn't support before 1900 */
+		if (arg < 1900)
+			return -EINVAL;
+
+		if (capable(CAP_SYS_TIME) == 0)
+			return -EACCES;
+
+		epoch = arg;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+                     unsigned long arg)
+{
+	return rtc_do_ioctl(cmd, arg, FUNCTION_RTC_IOCTL);
+}
+
+static int rtc_open(struct inode *inode, struct file *file)
+{
+	spin_lock_irq(&rtc_lock);
+
+	if (rtc_status == RTC_OPEN) {
+		spin_unlock_irq(&rtc_lock);
+		return -EBUSY;
+	}
+
+	rtc_status = RTC_OPEN;
+	rtc_irq_data = 0;
+
+	spin_unlock_irq(&rtc_lock);
+
+	return 0;
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+	if (file->f_flags & FASYNC)
+		(void)fasync_helper(-1, file, 0, &rtc_async_queue);
+
+	spin_lock_irq(&rtc_lock);
+
+	rtc1_write(ECMPLREG, 0);
+	rtc1_write(ECMPMREG, 0);
+	rtc1_write(ECMPHREG, 0);
+	rtc1_write(RTCL1LREG, 0);
+	rtc1_write(RTCL1HREG, 0);
+
+	rtc_status = RTC_RELEASE;
+
+	spin_unlock_irq(&rtc_lock);
+
+	disable_irq(ELAPSEDTIME_IRQ);
+	disable_irq(RTCLONG1_IRQ);
+
+	return 0;
+}
+
+static int rtc_fasync(int fd, struct file *file, int on)
+{
+	return fasync_helper(fd, file, on, &rtc_async_queue);
+}
+
+static struct file_operations rtc_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= rtc_read,
+	.poll		= rtc_poll,
+	.ioctl		= rtc_ioctl,
+	.open		= rtc_open,
+	.release	= rtc_release,
+	.fasync		= rtc_fasync,
+};
+
+static irqreturn_t elapsedtime_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	spin_lock(&rtc_lock);
+	rtc2_write(RTCINTREG, ELAPSEDTIME_INT);
+
+	rtc_irq_data += 0x100;
+	rtc_irq_data &= ~0xff;
+	rtc_irq_data |= RTC_AF;
+	spin_unlock(&rtc_lock);
+
+	spin_lock(&rtc_lock);
+	if (rtc_callback)
+		rtc_callback->func(rtc_callback->private_data);
+	spin_unlock(&rtc_lock);
+
+	wake_up_interruptible(&rtc_wait);
+
+	kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t rtclong1_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long count = periodic_count;
+
+	spin_lock(&rtc_lock);
+	rtc2_write(RTCINTREG, RTCLONG1_INT);
+
+	rtc1_write(RTCL1LREG, count);
+	rtc1_write(RTCL1HREG, count >> 16);
+
+	rtc_irq_data += 0x100;
+	rtc_irq_data &= ~0xff;
+	rtc_irq_data |= RTC_PF;
+	spin_unlock(&rtc_lock);
+
+	spin_lock(&rtc_task_lock);
+	if (rtc_callback)
+		rtc_callback->func(rtc_callback->private_data);
+	spin_unlock(&rtc_task_lock);
+
+	wake_up_interruptible(&rtc_wait);
+
+	kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
+
+	return IRQ_HANDLED;
+}
+
+int rtc_register(rtc_task_t *task)
+{
+	if (task == NULL || task->func == NULL)
+		return -EINVAL;
+
+	spin_lock_irq(&rtc_lock);
+	if (rtc_status == RTC_OPEN) {
+		spin_unlock_irq(&rtc_lock);
+		return -EBUSY;
+	}
+
+	spin_lock(&rtc_task_lock);
+	if (rtc_callback != NULL) {
+		spin_unlock(&rtc_task_lock);
+		spin_unlock_irq(&rtc_task_lock);
+		return -EBUSY;
+	}
+
+	rtc_callback = task;
+	spin_unlock(&rtc_task_lock);
+
+	rtc_status = RTC_OPEN;
+
+	spin_unlock_irq(&rtc_lock);
+
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(rtc_register);
+
+int rtc_unregister(rtc_task_t *task)
+{
+	spin_lock_irq(&rtc_task_lock);
+	if (task == NULL || rtc_callback != task) {
+		spin_unlock_irq(&rtc_task_lock);
+		return -ENXIO;
+	}
+
+	spin_lock(&rtc_lock);
+
+	rtc1_write(ECMPLREG, 0);
+	rtc1_write(ECMPMREG, 0);
+	rtc1_write(ECMPHREG, 0);
+	rtc1_write(RTCL1LREG, 0);
+	rtc1_write(RTCL1HREG, 0);
+
+	rtc_status = RTC_RELEASE;
+
+	spin_unlock(&rtc_lock);
+
+	rtc_callback = NULL;
+
+	spin_unlock_irq(&rtc_task_lock);
+
+	disable_irq(ELAPSEDTIME_IRQ);
+	disable_irq(RTCLONG1_IRQ);
+
+	return 0;
+}
+
+EXPORT_SYMBOL_GPL(rtc_unregister);
+
+int rtc_control(rtc_task_t *task, unsigned int cmd, unsigned long arg)
+{
+	int retval = 0;
+
+	spin_lock_irq(&rtc_task_lock);
+
+	if (rtc_callback != task)
+		retval = -ENXIO;
+	else
+		rtc_do_ioctl(cmd, arg, FUNCTION_RTC_CONTROL);
+
+	spin_unlock_irq(&rtc_task_lock);
+
+	return retval;
+}
+
+EXPORT_SYMBOL_GPL(rtc_control);
+
+static struct miscdevice rtc_miscdevice = {
+	.minor	= RTC_MINOR,
+	.name	= rtc_name,
+	.fops	= &rtc_fops,
+};
+
+static int rtc_probe(struct device *dev)
+{
+	struct platform_device *pdev;
+	unsigned int irq;
+	int retval;
+
+	pdev = to_platform_device(dev);
+	if (pdev->num_resources != 2)
+		return -EBUSY;
+
+	rtc1_base = ioremap(pdev->resource[0].start, RTC1_SIZE);
+	if (rtc1_base == NULL)
+		return -EBUSY;
+
+	rtc2_base = ioremap(pdev->resource[1].start, RTC2_SIZE);
+	if (rtc2_base == NULL) {
+		iounmap(rtc1_base);
+		rtc1_base = NULL;
+		return -EBUSY;
+	}
+
+	retval = misc_register(&rtc_miscdevice);
+	if (retval < 0) {
+		iounmap(rtc1_base);
+		iounmap(rtc2_base);
+		rtc1_base = NULL;
+		rtc2_base = NULL;
+		return retval;
+	}
+
+	spin_lock_irq(&rtc_lock);
+
+	rtc1_write(ECMPLREG, 0);
+	rtc1_write(ECMPMREG, 0);
+	rtc1_write(ECMPHREG, 0);
+	rtc1_write(RTCL1LREG, 0);
+	rtc1_write(RTCL1HREG, 0);
+
+	rtc_status = RTC_RELEASE;
+	rtc_irq_data = 0;
+
+	spin_unlock_irq(&rtc_lock);
+
+	init_waitqueue_head(&rtc_wait);
+
+	irq = ELAPSEDTIME_IRQ;
+	retval = request_irq(irq, elapsedtime_interrupt, SA_INTERRUPT,
+	                     "elapsed_time", NULL);
+	if (retval == 0) {
+		irq = RTCLONG1_IRQ;
+		retval = request_irq(irq, rtclong1_interrupt, SA_INTERRUPT,
+		                     "rtclong1", NULL);
+	}
+
+	if (retval < 0) {
+		printk(KERN_ERR "rtc: IRQ%d is busy\n", irq);
+		if (irq == RTCLONG1_IRQ)
+			free_irq(ELAPSEDTIME_IRQ, NULL);
+		iounmap(rtc1_base);
+		iounmap(rtc2_base);
+		rtc1_base = NULL;
+		rtc2_base = NULL;
+		return retval;
+	}
+
+	disable_irq(ELAPSEDTIME_IRQ);
+	disable_irq(RTCLONG1_IRQ);
+
+	spin_lock_init(&rtc_task_lock);
+
+	printk(KERN_INFO "rtc: Real Time Clock of NEC VR4100 series\n");
+
+	return 0;
+}
+
+static int rtc_remove(struct device *dev)
+{
+	int retval;
+
+	retval = misc_deregister(&rtc_miscdevice);
+	if (retval < 0)
+		return retval;
+
+	free_irq(ELAPSEDTIME_IRQ, NULL);
+	free_irq(RTCLONG1_IRQ, NULL);
+	if (rtc1_base != NULL)
+		iounmap(rtc1_base);
+	if (rtc2_base != NULL)
+		iounmap(rtc2_base);
+
+	return 0;
+}
+
+static struct platform_device *rtc_platform_device;
+
+static struct device_driver rtc_device_driver = {
+	.name		= rtc_name,
+	.bus		= &platform_bus_type,
+	.probe		= rtc_probe,
+	.remove		= rtc_remove,
+};
+
+static int __devinit vr41xx_rtc_init(void)
+{
+	int retval;
+
+	switch (current_cpu_data.cputype) {
+	case CPU_VR4111:
+	case CPU_VR4121:
+		rtc_resource[0].start = RTC1_TYPE1_START;
+		rtc_resource[0].end = RTC1_TYPE1_END;
+		rtc_resource[1].start = RTC2_TYPE1_START;
+		rtc_resource[1].end = RTC2_TYPE1_END;
+		break;
+	case CPU_VR4122:
+	case CPU_VR4131:
+	case CPU_VR4133:
+		rtc_resource[0].start = RTC1_TYPE2_START;
+		rtc_resource[0].end = RTC1_TYPE2_END;
+		rtc_resource[1].start = RTC2_TYPE2_START;
+		rtc_resource[1].end = RTC2_TYPE2_END;
+		break;
+	default:
+		return -ENODEV;
+		break;
+	}
+
+	rtc_platform_device = platform_device_register_simple("RTC", -1, rtc_resource, RTC_NUM_RESOURCES);
+	if (IS_ERR(rtc_platform_device))
+		return PTR_ERR(rtc_platform_device);
+
+	retval = driver_register(&rtc_device_driver);
+	if (retval < 0)
+		platform_device_unregister(rtc_platform_device);
+
+	return retval;
+}
+
+static void __devexit vr41xx_rtc_exit(void)
+{
+	driver_unregister(&rtc_device_driver);
+
+	platform_device_unregister(rtc_platform_device);
+}
+
+module_init(vr41xx_rtc_init);
+module_exit(vr41xx_rtc_exit);
diff --git a/drivers/char/vt.c b/drivers/char/vt.c
new file mode 100644
index 0000000..e5ef1df
--- /dev/null
+++ b/drivers/char/vt.c
@@ -0,0 +1,3242 @@
+/*
+ *  linux/drivers/char/vt.c
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds
+ */
+
+/*
+ * Hopefully this will be a rather complete VT102 implementation.
+ *
+ * Beeping thanks to John T Kohl.
+ *
+ * Virtual Consoles, Screen Blanking, Screen Dumping, Color, Graphics
+ *   Chars, and VT100 enhancements by Peter MacDonald.
+ *
+ * Copy and paste function by Andrew Haylett,
+ *   some enhancements by Alessandro Rubini.
+ *
+ * Code to check for different video-cards mostly by Galen Hunt,
+ * <g-hunt@ee.utah.edu>
+ *
+ * Rudimentary ISO 10646/Unicode/UTF-8 character set support by
+ * Markus Kuhn, <mskuhn@immd4.informatik.uni-erlangen.de>.
+ *
+ * Dynamic allocation of consoles, aeb@cwi.nl, May 1994
+ * Resizing of consoles, aeb, 940926
+ *
+ * Code for xterm like mouse click reporting by Peter Orbaek 20-Jul-94
+ * <poe@daimi.aau.dk>
+ *
+ * User-defined bell sound, new setterm control sequences and printk
+ * redirection by Martin Mares <mj@k332.feld.cvut.cz> 19-Nov-95
+ *
+ * APM screenblank bug fixed Takashi Manabe <manabe@roy.dsl.tutics.tut.jp>
+ *
+ * Merge with the abstract console driver by Geert Uytterhoeven
+ * <geert@linux-m68k.org>, Jan 1997.
+ *
+ *   Original m68k console driver modifications by
+ *
+ *     - Arno Griffioen <arno@usn.nl>
+ *     - David Carter <carter@cs.bris.ac.uk>
+ * 
+ *   The abstract console driver provides a generic interface for a text
+ *   console. It supports VGA text mode, frame buffer based graphical consoles
+ *   and special graphics processors that are only accessible through some
+ *   registers (e.g. a TMS340x0 GSP).
+ *
+ *   The interface to the hardware is specified using a special structure
+ *   (struct consw) which contains function pointers to console operations
+ *   (see <linux/console.h> for more information).
+ *
+ * Support for changeable cursor shape
+ * by Pavel Machek <pavel@atrey.karlin.mff.cuni.cz>, August 1997
+ *
+ * Ported to i386 and con_scrolldelta fixed
+ * by Emmanuel Marty <core@ggi-project.org>, April 1998
+ *
+ * Resurrected character buffers in videoram plus lots of other trickery
+ * by Martin Mares <mj@atrey.karlin.mff.cuni.cz>, July 1998
+ *
+ * Removed old-style timers, introduced console_timer, made timer
+ * deletion SMP-safe.  17Jun00, Andrew Morton <andrewm@uow.edu.au>
+ *
+ * Removed console_lock, enabled interrupts across all console operations
+ * 13 March 2001, Andrew Morton
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/kd.h>
+#include <linux/slab.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/vt_kern.h>
+#include <linux/selection.h>
+#include <linux/tiocl.h>
+#include <linux/kbd_kern.h>
+#include <linux/consolemap.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/config.h>
+#include <linux/workqueue.h>
+#include <linux/bootmem.h>
+#include <linux/pm.h>
+#include <linux/font.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+
+const struct consw *conswitchp;
+
+/* A bitmap for codes <32. A bit of 1 indicates that the code
+ * corresponding to that bit number invokes some special action
+ * (such as cursor movement) and should not be displayed as a
+ * glyph unless the disp_ctrl mode is explicitly enabled.
+ */
+#define CTRL_ACTION 0x0d00ff81
+#define CTRL_ALWAYS 0x0800f501	/* Cannot be overridden by disp_ctrl */
+
+/*
+ * Here is the default bell parameters: 750HZ, 1/8th of a second
+ */
+#define DEFAULT_BELL_PITCH	750
+#define DEFAULT_BELL_DURATION	(HZ/8)
+
+extern void vcs_make_devfs(struct tty_struct *tty);
+extern void vcs_remove_devfs(struct tty_struct *tty);
+
+extern void console_map_init(void);
+#ifdef CONFIG_PROM_CONSOLE
+extern void prom_con_init(void);
+#endif
+#ifdef CONFIG_MDA_CONSOLE
+extern int mda_console_init(void);
+#endif
+
+struct vc vc_cons [MAX_NR_CONSOLES];
+
+#ifndef VT_SINGLE_DRIVER
+static const struct consw *con_driver_map[MAX_NR_CONSOLES];
+#endif
+
+static int con_open(struct tty_struct *, struct file *);
+static void vc_init(struct vc_data *vc, unsigned int rows,
+		    unsigned int cols, int do_clear);
+static void gotoxy(struct vc_data *vc, int new_x, int new_y);
+static void save_cur(struct vc_data *vc);
+static void reset_terminal(struct vc_data *vc, int do_clear);
+static void con_flush_chars(struct tty_struct *tty);
+static void set_vesa_blanking(char __user *p);
+static void set_cursor(struct vc_data *vc);
+static void hide_cursor(struct vc_data *vc);
+static void console_callback(void *ignored);
+static void blank_screen_t(unsigned long dummy);
+static void set_palette(struct vc_data *vc);
+
+static int printable;		/* Is console ready for printing? */
+
+/*
+ * ignore_poke: don't unblank the screen when things are typed.  This is
+ * mainly for the privacy of braille terminal users.
+ */
+static int ignore_poke;
+
+int do_poke_blanked_console;
+int console_blanked;
+
+static int vesa_blank_mode; /* 0:none 1:suspendV 2:suspendH 3:powerdown */
+static int blankinterval = 10*60*HZ;
+static int vesa_off_interval;
+
+static DECLARE_WORK(console_work, console_callback, NULL);
+
+/*
+ * fg_console is the current virtual console,
+ * last_console is the last used one,
+ * want_console is the console we want to switch to,
+ * kmsg_redirect is the console for kernel messages,
+ */
+int fg_console;
+int last_console;
+int want_console = -1;
+int kmsg_redirect;
+
+/*
+ * For each existing display, we have a pointer to console currently visible
+ * on that display, allowing consoles other than fg_console to be refreshed
+ * appropriately. Unless the low-level driver supplies its own display_fg
+ * variable, we use this one for the "master display".
+ */
+static struct vc_data *master_display_fg;
+
+/*
+ * Unfortunately, we need to delay tty echo when we're currently writing to the
+ * console since the code is (and always was) not re-entrant, so we schedule
+ * all flip requests to process context with schedule-task() and run it from
+ * console_callback().
+ */
+
+/*
+ * For the same reason, we defer scrollback to the console callback.
+ */
+static int scrollback_delta;
+
+/*
+ * Hook so that the power management routines can (un)blank
+ * the console on our behalf.
+ */
+int (*console_blank_hook)(int);
+
+static struct timer_list console_timer;
+static int blank_state;
+static int blank_timer_expired;
+enum {
+	blank_off = 0,
+	blank_normal_wait,
+	blank_vesa_wait,
+};
+
+/*
+ *	Low-Level Functions
+ */
+
+#define IS_FG(vc)	((vc)->vc_num == fg_console)
+
+#ifdef VT_BUF_VRAM_ONLY
+#define DO_UPDATE(vc)	0
+#else
+#define DO_UPDATE(vc)	CON_IS_VISIBLE(vc)
+#endif
+
+static inline unsigned short *screenpos(struct vc_data *vc, int offset, int viewed)
+{
+	unsigned short *p;
+	
+	if (!viewed)
+		p = (unsigned short *)(vc->vc_origin + offset);
+	else if (!vc->vc_sw->con_screen_pos)
+		p = (unsigned short *)(vc->vc_visible_origin + offset);
+	else
+		p = vc->vc_sw->con_screen_pos(vc, offset);
+	return p;
+}
+
+static inline void scrolldelta(int lines)
+{
+	scrollback_delta += lines;
+	schedule_console_callback();
+}
+
+void schedule_console_callback(void)
+{
+	schedule_work(&console_work);
+}
+
+static void scrup(struct vc_data *vc, unsigned int t, unsigned int b, int nr)
+{
+	unsigned short *d, *s;
+
+	if (t+nr >= b)
+		nr = b - t - 1;
+	if (b > vc->vc_rows || t >= b || nr < 1)
+		return;
+	if (CON_IS_VISIBLE(vc) && vc->vc_sw->con_scroll(vc, t, b, SM_UP, nr))
+		return;
+	d = (unsigned short *)(vc->vc_origin + vc->vc_size_row * t);
+	s = (unsigned short *)(vc->vc_origin + vc->vc_size_row * (t + nr));
+	scr_memmovew(d, s, (b - t - nr) * vc->vc_size_row);
+	scr_memsetw(d + (b - t - nr) * vc->vc_cols, vc->vc_video_erase_char,
+		    vc->vc_size_row * nr);
+}
+
+static void scrdown(struct vc_data *vc, unsigned int t, unsigned int b, int nr)
+{
+	unsigned short *s;
+	unsigned int step;
+
+	if (t+nr >= b)
+		nr = b - t - 1;
+	if (b > vc->vc_rows || t >= b || nr < 1)
+		return;
+	if (CON_IS_VISIBLE(vc) && vc->vc_sw->con_scroll(vc, t, b, SM_DOWN, nr))
+		return;
+	s = (unsigned short *)(vc->vc_origin + vc->vc_size_row * t);
+	step = vc->vc_cols * nr;
+	scr_memmovew(s + step, s, (b - t - nr) * vc->vc_size_row);
+	scr_memsetw(s, vc->vc_video_erase_char, 2 * step);
+}
+
+static void do_update_region(struct vc_data *vc, unsigned long start, int count)
+{
+#ifndef VT_BUF_VRAM_ONLY
+	unsigned int xx, yy, offset;
+	u16 *p;
+
+	p = (u16 *) start;
+	if (!vc->vc_sw->con_getxy) {
+		offset = (start - vc->vc_origin) / 2;
+		xx = offset % vc->vc_cols;
+		yy = offset / vc->vc_cols;
+	} else {
+		int nxx, nyy;
+		start = vc->vc_sw->con_getxy(vc, start, &nxx, &nyy);
+		xx = nxx; yy = nyy;
+	}
+	for(;;) {
+		u16 attrib = scr_readw(p) & 0xff00;
+		int startx = xx;
+		u16 *q = p;
+		while (xx < vc->vc_cols && count) {
+			if (attrib != (scr_readw(p) & 0xff00)) {
+				if (p > q)
+					vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+				startx = xx;
+				q = p;
+				attrib = scr_readw(p) & 0xff00;
+			}
+			p++;
+			xx++;
+			count--;
+		}
+		if (p > q)
+			vc->vc_sw->con_putcs(vc, q, p-q, yy, startx);
+		if (!count)
+			break;
+		xx = 0;
+		yy++;
+		if (vc->vc_sw->con_getxy) {
+			p = (u16 *)start;
+			start = vc->vc_sw->con_getxy(vc, start, NULL, NULL);
+		}
+	}
+#endif
+}
+
+void update_region(struct vc_data *vc, unsigned long start, int count)
+{
+	WARN_CONSOLE_UNLOCKED();
+
+	if (DO_UPDATE(vc)) {
+		hide_cursor(vc);
+		do_update_region(vc, start, count);
+		set_cursor(vc);
+	}
+}
+
+/* Structure of attributes is hardware-dependent */
+
+static u8 build_attr(struct vc_data *vc, u8 _color, u8 _intensity, u8 _blink, u8 _underline, u8 _reverse)
+{
+	if (vc->vc_sw->con_build_attr)
+		return vc->vc_sw->con_build_attr(vc, _color, _intensity, _blink, _underline, _reverse);
+
+#ifndef VT_BUF_VRAM_ONLY
+/*
+ * ++roman: I completely changed the attribute format for monochrome
+ * mode (!can_do_color). The formerly used MDA (monochrome display
+ * adapter) format didn't allow the combination of certain effects.
+ * Now the attribute is just a bit vector:
+ *  Bit 0..1: intensity (0..2)
+ *  Bit 2   : underline
+ *  Bit 3   : reverse
+ *  Bit 7   : blink
+ */
+	{
+	u8 a = vc->vc_color;
+	if (!vc->vc_can_do_color)
+		return _intensity |
+		       (_underline ? 4 : 0) |
+		       (_reverse ? 8 : 0) |
+		       (_blink ? 0x80 : 0);
+	if (_underline)
+		a = (a & 0xf0) | vc->vc_ulcolor;
+	else if (_intensity == 0)
+		a = (a & 0xf0) | vc->vc_ulcolor;
+	if (_reverse)
+		a = ((a) & 0x88) | ((((a) >> 4) | ((a) << 4)) & 0x77);
+	if (_blink)
+		a ^= 0x80;
+	if (_intensity == 2)
+		a ^= 0x08;
+	if (vc->vc_hi_font_mask == 0x100)
+		a <<= 1;
+	return a;
+	}
+#else
+	return 0;
+#endif
+}
+
+static void update_attr(struct vc_data *vc)
+{
+	vc->vc_attr = build_attr(vc, vc->vc_color, vc->vc_intensity, vc->vc_blink, vc->vc_underline, vc->vc_reverse ^ vc->vc_decscnm);
+	vc->vc_video_erase_char = (build_attr(vc, vc->vc_color, 1, vc->vc_blink, 0, vc->vc_decscnm) << 8) | ' ';
+}
+
+/* Note: inverting the screen twice should revert to the original state */
+void invert_screen(struct vc_data *vc, int offset, int count, int viewed)
+{
+	unsigned short *p;
+
+	WARN_CONSOLE_UNLOCKED();
+
+	count /= 2;
+	p = screenpos(vc, offset, viewed);
+	if (vc->vc_sw->con_invert_region)
+		vc->vc_sw->con_invert_region(vc, p, count);
+#ifndef VT_BUF_VRAM_ONLY
+	else {
+		u16 *q = p;
+		int cnt = count;
+		u16 a;
+
+		if (!vc->vc_can_do_color) {
+			while (cnt--) {
+			    a = scr_readw(q);
+			    a ^= 0x0800;
+			    scr_writew(a, q);
+			    q++;
+			}
+		} else if (vc->vc_hi_font_mask == 0x100) {
+			while (cnt--) {
+				a = scr_readw(q);
+				a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) | (((a) & 0x0e00) << 4);
+				scr_writew(a, q);
+				q++;
+			}
+		} else {
+			while (cnt--) {
+				a = scr_readw(q);
+				a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) | (((a) & 0x0700) << 4);
+				scr_writew(a, q);
+				q++;
+			}
+		}
+	}
+#endif
+	if (DO_UPDATE(vc))
+		do_update_region(vc, (unsigned long) p, count);
+}
+
+/* used by selection: complement pointer position */
+void complement_pos(struct vc_data *vc, int offset)
+{
+	static unsigned short *p;
+	static unsigned short old;
+	static unsigned short oldx, oldy;
+
+	WARN_CONSOLE_UNLOCKED();
+
+	if (p) {
+		scr_writew(old, p);
+		if (DO_UPDATE(vc))
+			vc->vc_sw->con_putc(vc, old, oldy, oldx);
+	}
+	if (offset == -1)
+		p = NULL;
+	else {
+		unsigned short new;
+		p = screenpos(vc, offset, 1);
+		old = scr_readw(p);
+		new = old ^ vc->vc_complement_mask;
+		scr_writew(new, p);
+		if (DO_UPDATE(vc)) {
+			oldx = (offset >> 1) % vc->vc_cols;
+			oldy = (offset >> 1) / vc->vc_cols;
+			vc->vc_sw->con_putc(vc, new, oldy, oldx);
+		}
+	}
+}
+
+static void insert_char(struct vc_data *vc, unsigned int nr)
+{
+	unsigned short *p, *q = (unsigned short *)vc->vc_pos;
+
+	p = q + vc->vc_cols - nr - vc->vc_x;
+	while (--p >= q)
+		scr_writew(scr_readw(p), p + nr);
+	scr_memsetw(q, vc->vc_video_erase_char, nr * 2);
+	vc->vc_need_wrap = 0;
+	if (DO_UPDATE(vc)) {
+		unsigned short oldattr = vc->vc_attr;
+		vc->vc_sw->con_bmove(vc, vc->vc_y, vc->vc_x, vc->vc_y, vc->vc_x + nr, 1,
+				     vc->vc_cols - vc->vc_x - nr);
+		vc->vc_attr = vc->vc_video_erase_char >> 8;
+		while (nr--)
+			vc->vc_sw->con_putc(vc, vc->vc_video_erase_char, vc->vc_y, vc->vc_x + nr);
+		vc->vc_attr = oldattr;
+	}
+}
+
+static void delete_char(struct vc_data *vc, unsigned int nr)
+{
+	unsigned int i = vc->vc_x;
+	unsigned short *p = (unsigned short *)vc->vc_pos;
+
+	while (++i <= vc->vc_cols - nr) {
+		scr_writew(scr_readw(p+nr), p);
+		p++;
+	}
+	scr_memsetw(p, vc->vc_video_erase_char, nr * 2);
+	vc->vc_need_wrap = 0;
+	if (DO_UPDATE(vc)) {
+		unsigned short oldattr = vc->vc_attr;
+		vc->vc_sw->con_bmove(vc, vc->vc_y, vc->vc_x + nr, vc->vc_y, vc->vc_x, 1,
+				     vc->vc_cols - vc->vc_x - nr);
+		vc->vc_attr = vc->vc_video_erase_char >> 8;
+		while (nr--)
+			vc->vc_sw->con_putc(vc, vc->vc_video_erase_char, vc->vc_y,
+				     vc->vc_cols - 1 - nr);
+		vc->vc_attr = oldattr;
+	}
+}
+
+static int softcursor_original;
+
+static void add_softcursor(struct vc_data *vc)
+{
+	int i = scr_readw((u16 *) vc->vc_pos);
+	u32 type = vc->vc_cursor_type;
+
+	if (! (type & 0x10)) return;
+	if (softcursor_original != -1) return;
+	softcursor_original = i;
+	i |= ((type >> 8) & 0xff00 );
+	i ^= ((type) & 0xff00 );
+	if ((type & 0x20) && ((softcursor_original & 0x7000) == (i & 0x7000))) i ^= 0x7000;
+	if ((type & 0x40) && ((i & 0x700) == ((i & 0x7000) >> 4))) i ^= 0x0700;
+	scr_writew(i, (u16 *) vc->vc_pos);
+	if (DO_UPDATE(vc))
+		vc->vc_sw->con_putc(vc, i, vc->vc_y, vc->vc_x);
+}
+
+static void hide_softcursor(struct vc_data *vc)
+{
+	if (softcursor_original != -1) {
+		scr_writew(softcursor_original, (u16 *)vc->vc_pos);
+		if (DO_UPDATE(vc))
+			vc->vc_sw->con_putc(vc, softcursor_original,
+					vc->vc_y, vc->vc_x);
+		softcursor_original = -1;
+	}
+}
+
+static void hide_cursor(struct vc_data *vc)
+{
+	if (vc == sel_cons)
+		clear_selection();
+	vc->vc_sw->con_cursor(vc, CM_ERASE);
+	hide_softcursor(vc);
+}
+
+static void set_cursor(struct vc_data *vc)
+{
+	if (!IS_FG(vc) || console_blanked ||
+	    vc->vc_mode == KD_GRAPHICS)
+		return;
+	if (vc->vc_deccm) {
+		if (vc == sel_cons)
+			clear_selection();
+		add_softcursor(vc);
+		if ((vc->vc_cursor_type & 0x0f) != 1)
+			vc->vc_sw->con_cursor(vc, CM_DRAW);
+	} else
+		hide_cursor(vc);
+}
+
+static void set_origin(struct vc_data *vc)
+{
+	WARN_CONSOLE_UNLOCKED();
+
+	if (!CON_IS_VISIBLE(vc) ||
+	    !vc->vc_sw->con_set_origin ||
+	    !vc->vc_sw->con_set_origin(vc))
+		vc->vc_origin = (unsigned long)vc->vc_screenbuf;
+	vc->vc_visible_origin = vc->vc_origin;
+	vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
+	vc->vc_pos = vc->vc_origin + vc->vc_size_row * vc->vc_y + 2 * vc->vc_x;
+}
+
+static inline void save_screen(struct vc_data *vc)
+{
+	WARN_CONSOLE_UNLOCKED();
+
+	if (vc->vc_sw->con_save_screen)
+		vc->vc_sw->con_save_screen(vc);
+}
+
+/*
+ *	Redrawing of screen
+ */
+
+static void clear_buffer_attributes(struct vc_data *vc)
+{
+	unsigned short *p = (unsigned short *)vc->vc_origin;
+	int count = vc->vc_screenbuf_size / 2;
+	int mask = vc->vc_hi_font_mask | 0xff;
+
+	for (; count > 0; count--, p++) {
+		scr_writew((scr_readw(p)&mask) | (vc->vc_video_erase_char & ~mask), p);
+	}
+}
+
+void redraw_screen(struct vc_data *vc, int is_switch)
+{
+	int redraw = 0;
+
+	WARN_CONSOLE_UNLOCKED();
+
+	if (!vc) {
+		/* strange ... */
+		/* printk("redraw_screen: tty %d not allocated ??\n", new_console+1); */
+		return;
+	}
+
+	if (is_switch) {
+		struct vc_data *old_vc = vc_cons[fg_console].d;
+		if (old_vc == vc)
+			return;
+		if (!CON_IS_VISIBLE(vc))
+			redraw = 1;
+		*vc->vc_display_fg = vc;
+		fg_console = vc->vc_num;
+		hide_cursor(old_vc);
+		if (!CON_IS_VISIBLE(old_vc)) {
+			save_screen(old_vc);
+			set_origin(old_vc);
+		}
+	} else {
+		hide_cursor(vc);
+		redraw = 1;
+	}
+
+	if (redraw) {
+		int update;
+		int old_was_color = vc->vc_can_do_color;
+
+		set_origin(vc);
+		update = vc->vc_sw->con_switch(vc);
+		set_palette(vc);
+		/*
+		 * If console changed from mono<->color, the best we can do
+		 * is to clear the buffer attributes. As it currently stands,
+		 * rebuilding new attributes from the old buffer is not doable
+		 * without overly complex code.
+		 */
+		if (old_was_color != vc->vc_can_do_color) {
+			update_attr(vc);
+			clear_buffer_attributes(vc);
+		}
+		if (update && vc->vc_mode != KD_GRAPHICS)
+			do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+	}
+	set_cursor(vc);
+	if (is_switch) {
+		set_leds();
+		compute_shiftstate();
+	}
+}
+
+/*
+ *	Allocation, freeing and resizing of VTs.
+ */
+
+int vc_cons_allocated(unsigned int i)
+{
+	return (i < MAX_NR_CONSOLES && vc_cons[i].d);
+}
+
+static void visual_init(struct vc_data *vc, int num, int init)
+{
+	/* ++Geert: vc->vc_sw->con_init determines console size */
+	if (vc->vc_sw)
+		module_put(vc->vc_sw->owner);
+	vc->vc_sw = conswitchp;
+#ifndef VT_SINGLE_DRIVER
+	if (con_driver_map[num])
+		vc->vc_sw = con_driver_map[num];
+#endif
+	__module_get(vc->vc_sw->owner);
+	vc->vc_num = num;
+	vc->vc_display_fg = &master_display_fg;
+	vc->vc_uni_pagedir_loc = &vc->vc_uni_pagedir;
+	vc->vc_uni_pagedir = 0;
+	vc->vc_hi_font_mask = 0;
+	vc->vc_complement_mask = 0;
+	vc->vc_can_do_color = 0;
+	vc->vc_sw->con_init(vc, init);
+	if (!vc->vc_complement_mask)
+		vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
+	vc->vc_s_complement_mask = vc->vc_complement_mask;
+	vc->vc_size_row = vc->vc_cols << 1;
+	vc->vc_screenbuf_size = vc->vc_rows * vc->vc_size_row;
+}
+
+int vc_allocate(unsigned int currcons)	/* return 0 on success */
+{
+	WARN_CONSOLE_UNLOCKED();
+
+	if (currcons >= MAX_NR_CONSOLES)
+		return -ENXIO;
+	if (!vc_cons[currcons].d) {
+	    struct vc_data *vc;
+
+	    /* prevent users from taking too much memory */
+	    if (currcons >= MAX_NR_USER_CONSOLES && !capable(CAP_SYS_RESOURCE))
+	      return -EPERM;
+
+	    /* due to the granularity of kmalloc, we waste some memory here */
+	    /* the alloc is done in two steps, to optimize the common situation
+	       of a 25x80 console (structsize=216, screenbuf_size=4000) */
+	    /* although the numbers above are not valid since long ago, the
+	       point is still up-to-date and the comment still has its value
+	       even if only as a historical artifact.  --mj, July 1998 */
+	    vc = kmalloc(sizeof(struct vc_data), GFP_KERNEL);
+	    if (!vc)
+		return -ENOMEM;
+	    memset(vc, 0, sizeof(*vc));
+	    vc_cons[currcons].d = vc;
+	    visual_init(vc, currcons, 1);
+	    if (!*vc->vc_uni_pagedir_loc)
+		con_set_default_unimap(vc);
+	    vc->vc_screenbuf = kmalloc(vc->vc_screenbuf_size, GFP_KERNEL);
+	    if (!vc->vc_screenbuf) {
+		kfree(vc);
+		vc_cons[currcons].d = NULL;
+		return -ENOMEM;
+	    }
+	    vc->vc_kmalloced = 1;
+	    vc_init(vc, vc->vc_rows, vc->vc_cols, 1);
+	}
+	return 0;
+}
+
+static inline int resize_screen(struct vc_data *vc, int width, int height)
+{
+	/* Resizes the resolution of the display adapater */
+	int err = 0;
+
+	if (vc->vc_mode != KD_GRAPHICS && vc->vc_sw->con_resize)
+		err = vc->vc_sw->con_resize(vc, width, height);
+	return err;
+}
+
+/*
+ * Change # of rows and columns (0 means unchanged/the size of fg_console)
+ * [this is to be used together with some user program
+ * like resize that changes the hardware videomode]
+ */
+#define VC_RESIZE_MAXCOL (32767)
+#define VC_RESIZE_MAXROW (32767)
+int vc_resize(struct vc_data *vc, unsigned int cols, unsigned int lines)
+{
+	unsigned long old_origin, new_origin, new_scr_end, rlth, rrem, err = 0;
+	unsigned int old_cols, old_rows, old_row_size, old_screen_size;
+	unsigned int new_cols, new_rows, new_row_size, new_screen_size;
+	unsigned short *newscreen;
+
+	WARN_CONSOLE_UNLOCKED();
+
+	if (!vc)
+		return -ENXIO;
+
+	if (cols > VC_RESIZE_MAXCOL || lines > VC_RESIZE_MAXROW)
+		return -EINVAL;
+
+	new_cols = (cols ? cols : vc->vc_cols);
+	new_rows = (lines ? lines : vc->vc_rows);
+	new_row_size = new_cols << 1;
+	new_screen_size = new_row_size * new_rows;
+
+	if (new_cols == vc->vc_cols && new_rows == vc->vc_rows)
+		return 0;
+
+	newscreen = (unsigned short *) kmalloc(new_screen_size, GFP_USER);
+	if (!newscreen)
+		return -ENOMEM;
+
+	old_rows = vc->vc_rows;
+	old_cols = vc->vc_cols;
+	old_row_size = vc->vc_size_row;
+	old_screen_size = vc->vc_screenbuf_size;
+
+	err = resize_screen(vc, new_cols, new_rows);
+	if (err) {
+		kfree(newscreen);
+		return err;
+	}
+
+	vc->vc_rows = new_rows;
+	vc->vc_cols = new_cols;
+	vc->vc_size_row = new_row_size;
+	vc->vc_screenbuf_size = new_screen_size;
+
+	rlth = min(old_row_size, new_row_size);
+	rrem = new_row_size - rlth;
+	old_origin = vc->vc_origin;
+	new_origin = (long) newscreen;
+	new_scr_end = new_origin + new_screen_size;
+	if (new_rows < old_rows)
+		old_origin += (old_rows - new_rows) * old_row_size;
+
+	update_attr(vc);
+
+	while (old_origin < vc->vc_scr_end) {
+		scr_memcpyw((unsigned short *) new_origin, (unsigned short *) old_origin, rlth);
+		if (rrem)
+			scr_memsetw((void *)(new_origin + rlth), vc->vc_video_erase_char, rrem);
+		old_origin += old_row_size;
+		new_origin += new_row_size;
+	}
+	if (new_scr_end > new_origin)
+		scr_memsetw((void *)new_origin, vc->vc_video_erase_char, new_scr_end - new_origin);
+	if (vc->vc_kmalloced)
+		kfree(vc->vc_screenbuf);
+	vc->vc_screenbuf = newscreen;
+	vc->vc_kmalloced = 1;
+	vc->vc_screenbuf_size = new_screen_size;
+	set_origin(vc);
+
+	/* do part of a reset_terminal() */
+	vc->vc_top = 0;
+	vc->vc_bottom = vc->vc_rows;
+	gotoxy(vc, vc->vc_x, vc->vc_y);
+	save_cur(vc);
+
+	if (vc->vc_tty) {
+		struct winsize ws, *cws = &vc->vc_tty->winsize;
+
+		memset(&ws, 0, sizeof(ws));
+		ws.ws_row = vc->vc_rows;
+		ws.ws_col = vc->vc_cols;
+		ws.ws_ypixel = vc->vc_scan_lines;
+		if ((ws.ws_row != cws->ws_row || ws.ws_col != cws->ws_col) &&
+		    vc->vc_tty->pgrp > 0)
+			kill_pg(vc->vc_tty->pgrp, SIGWINCH, 1);
+		*cws = ws;
+	}
+
+	if (CON_IS_VISIBLE(vc))
+		update_screen(vc);
+	return err;
+}
+
+
+void vc_disallocate(unsigned int currcons)
+{
+	WARN_CONSOLE_UNLOCKED();
+
+	if (vc_cons_allocated(currcons)) {
+		struct vc_data *vc = vc_cons[currcons].d;
+		vc->vc_sw->con_deinit(vc);
+		if (vc->vc_kmalloced)
+			kfree(vc->vc_screenbuf);
+		if (currcons >= MIN_NR_CONSOLES)
+			kfree(vc);
+		vc_cons[currcons].d = NULL;
+	}
+}
+
+/*
+ *	VT102 emulator
+ */
+
+#define set_kbd(vc, x)	set_vc_kbd_mode(kbd_table + (vc)->vc_num, (x))
+#define clr_kbd(vc, x)	clr_vc_kbd_mode(kbd_table + (vc)->vc_num, (x))
+#define is_kbd(vc, x)	vc_kbd_mode(kbd_table + (vc)->vc_num, (x))
+
+#define decarm		VC_REPEAT
+#define decckm		VC_CKMODE
+#define kbdapplic	VC_APPLIC
+#define lnm		VC_CRLF
+
+/*
+ * this is what the terminal answers to a ESC-Z or csi0c query.
+ */
+#define VT100ID "\033[?1;2c"
+#define VT102ID "\033[?6c"
+
+unsigned char color_table[] = { 0, 4, 2, 6, 1, 5, 3, 7,
+				       8,12,10,14, 9,13,11,15 };
+
+/* the default colour table, for VGA+ colour systems */
+int default_red[] = {0x00,0xaa,0x00,0xaa,0x00,0xaa,0x00,0xaa,
+    0x55,0xff,0x55,0xff,0x55,0xff,0x55,0xff};
+int default_grn[] = {0x00,0x00,0xaa,0x55,0x00,0x00,0xaa,0xaa,
+    0x55,0x55,0xff,0xff,0x55,0x55,0xff,0xff};
+int default_blu[] = {0x00,0x00,0x00,0x00,0xaa,0xaa,0xaa,0xaa,
+    0x55,0x55,0x55,0x55,0xff,0xff,0xff,0xff};
+
+/*
+ * gotoxy() must verify all boundaries, because the arguments
+ * might also be negative. If the given position is out of
+ * bounds, the cursor is placed at the nearest margin.
+ */
+static void gotoxy(struct vc_data *vc, int new_x, int new_y)
+{
+	int min_y, max_y;
+
+	if (new_x < 0)
+		vc->vc_x = 0;
+	else {
+		if (new_x >= vc->vc_cols)
+			vc->vc_x = vc->vc_cols - 1;
+		else
+			vc->vc_x = new_x;
+	}
+
+ 	if (vc->vc_decom) {
+		min_y = vc->vc_top;
+		max_y = vc->vc_bottom;
+	} else {
+		min_y = 0;
+		max_y = vc->vc_rows;
+	}
+	if (new_y < min_y)
+		vc->vc_y = min_y;
+	else if (new_y >= max_y)
+		vc->vc_y = max_y - 1;
+	else
+		vc->vc_y = new_y;
+	vc->vc_pos = vc->vc_origin + vc->vc_y * vc->vc_size_row + (vc->vc_x<<1);
+	vc->vc_need_wrap = 0;
+}
+
+/* for absolute user moves, when decom is set */
+static void gotoxay(struct vc_data *vc, int new_x, int new_y)
+{
+	gotoxy(vc, new_x, vc->vc_decom ? (vc->vc_top + new_y) : new_y);
+}
+
+void scrollback(struct vc_data *vc, int lines)
+{
+	if (!lines)
+		lines = vc->vc_rows / 2;
+	scrolldelta(-lines);
+}
+
+void scrollfront(struct vc_data *vc, int lines)
+{
+	if (!lines)
+		lines = vc->vc_rows / 2;
+	scrolldelta(lines);
+}
+
+static void lf(struct vc_data *vc)
+{
+    	/* don't scroll if above bottom of scrolling region, or
+	 * if below scrolling region
+	 */
+    	if (vc->vc_y + 1 == vc->vc_bottom)
+		scrup(vc, vc->vc_top, vc->vc_bottom, 1);
+	else if (vc->vc_y < vc->vc_rows - 1) {
+	    	vc->vc_y++;
+		vc->vc_pos += vc->vc_size_row;
+	}
+	vc->vc_need_wrap = 0;
+}
+
+static void ri(struct vc_data *vc)
+{
+    	/* don't scroll if below top of scrolling region, or
+	 * if above scrolling region
+	 */
+	if (vc->vc_y == vc->vc_top)
+		scrdown(vc, vc->vc_top, vc->vc_bottom, 1);
+	else if (vc->vc_y > 0) {
+		vc->vc_y--;
+		vc->vc_pos -= vc->vc_size_row;
+	}
+	vc->vc_need_wrap = 0;
+}
+
+static inline void cr(struct vc_data *vc)
+{
+	vc->vc_pos -= vc->vc_x << 1;
+	vc->vc_need_wrap = vc->vc_x = 0;
+}
+
+static inline void bs(struct vc_data *vc)
+{
+	if (vc->vc_x) {
+		vc->vc_pos -= 2;
+		vc->vc_x--;
+		vc->vc_need_wrap = 0;
+	}
+}
+
+static inline void del(struct vc_data *vc)
+{
+	/* ignored */
+}
+
+static void csi_J(struct vc_data *vc, int vpar)
+{
+	unsigned int count;
+	unsigned short * start;
+
+	switch (vpar) {
+		case 0:	/* erase from cursor to end of display */
+			count = (vc->vc_scr_end - vc->vc_pos) >> 1;
+			start = (unsigned short *)vc->vc_pos;
+			if (DO_UPDATE(vc)) {
+				/* do in two stages */
+				vc->vc_sw->con_clear(vc, vc->vc_y, vc->vc_x, 1,
+					      vc->vc_cols - vc->vc_x);
+				vc->vc_sw->con_clear(vc, vc->vc_y + 1, 0,
+					      vc->vc_rows - vc->vc_y - 1,
+					      vc->vc_cols);
+			}
+			break;
+		case 1:	/* erase from start to cursor */
+			count = ((vc->vc_pos - vc->vc_origin) >> 1) + 1;
+			start = (unsigned short *)vc->vc_origin;
+			if (DO_UPDATE(vc)) {
+				/* do in two stages */
+				vc->vc_sw->con_clear(vc, 0, 0, vc->vc_y,
+					      vc->vc_cols);
+				vc->vc_sw->con_clear(vc, vc->vc_y, 0, 1,
+					      vc->vc_x + 1);
+			}
+			break;
+		case 2: /* erase whole display */
+			count = vc->vc_cols * vc->vc_rows;
+			start = (unsigned short *)vc->vc_origin;
+			if (DO_UPDATE(vc))
+				vc->vc_sw->con_clear(vc, 0, 0,
+					      vc->vc_rows,
+					      vc->vc_cols);
+			break;
+		default:
+			return;
+	}
+	scr_memsetw(start, vc->vc_video_erase_char, 2 * count);
+	vc->vc_need_wrap = 0;
+}
+
+static void csi_K(struct vc_data *vc, int vpar)
+{
+	unsigned int count;
+	unsigned short * start;
+
+	switch (vpar) {
+		case 0:	/* erase from cursor to end of line */
+			count = vc->vc_cols - vc->vc_x;
+			start = (unsigned short *)vc->vc_pos;
+			if (DO_UPDATE(vc))
+				vc->vc_sw->con_clear(vc, vc->vc_y, vc->vc_x, 1,
+						     vc->vc_cols - vc->vc_x);
+			break;
+		case 1:	/* erase from start of line to cursor */
+			start = (unsigned short *)(vc->vc_pos - (vc->vc_x << 1));
+			count = vc->vc_x + 1;
+			if (DO_UPDATE(vc))
+				vc->vc_sw->con_clear(vc, vc->vc_y, 0, 1,
+						     vc->vc_x + 1);
+			break;
+		case 2: /* erase whole line */
+			start = (unsigned short *)(vc->vc_pos - (vc->vc_x << 1));
+			count = vc->vc_cols;
+			if (DO_UPDATE(vc))
+				vc->vc_sw->con_clear(vc, vc->vc_y, 0, 1,
+					      vc->vc_cols);
+			break;
+		default:
+			return;
+	}
+	scr_memsetw(start, vc->vc_video_erase_char, 2 * count);
+	vc->vc_need_wrap = 0;
+}
+
+static void csi_X(struct vc_data *vc, int vpar) /* erase the following vpar positions */
+{					  /* not vt100? */
+	int count;
+
+	if (!vpar)
+		vpar++;
+	count = (vpar > vc->vc_cols - vc->vc_x) ? (vc->vc_cols - vc->vc_x) : vpar;
+
+	scr_memsetw((unsigned short *)vc->vc_pos, vc->vc_video_erase_char, 2 * count);
+	if (DO_UPDATE(vc))
+		vc->vc_sw->con_clear(vc, vc->vc_y, vc->vc_x, 1, count);
+	vc->vc_need_wrap = 0;
+}
+
+static void default_attr(struct vc_data *vc)
+{
+	vc->vc_intensity = 1;
+	vc->vc_underline = 0;
+	vc->vc_reverse = 0;
+	vc->vc_blink = 0;
+	vc->vc_color = vc->vc_def_color;
+}
+
+/* console_sem is held */
+static void csi_m(struct vc_data *vc)
+{
+	int i;
+
+	for (i = 0; i <= vc->vc_npar; i++)
+		switch (vc->vc_par[i]) {
+			case 0:	/* all attributes off */
+				default_attr(vc);
+				break;
+			case 1:
+				vc->vc_intensity = 2;
+				break;
+			case 2:
+				vc->vc_intensity = 0;
+				break;
+			case 4:
+				vc->vc_underline = 1;
+				break;
+			case 5:
+				vc->vc_blink = 1;
+				break;
+			case 7:
+				vc->vc_reverse = 1;
+				break;
+			case 10: /* ANSI X3.64-1979 (SCO-ish?)
+				  * Select primary font, don't display
+				  * control chars if defined, don't set
+				  * bit 8 on output.
+				  */
+				vc->vc_translate = set_translate(vc->vc_charset == 0
+						? vc->vc_G0_charset
+						: vc->vc_G1_charset, vc);
+				vc->vc_disp_ctrl = 0;
+				vc->vc_toggle_meta = 0;
+				break;
+			case 11: /* ANSI X3.64-1979 (SCO-ish?)
+				  * Select first alternate font, lets
+				  * chars < 32 be displayed as ROM chars.
+				  */
+				vc->vc_translate = set_translate(IBMPC_MAP, vc);
+				vc->vc_disp_ctrl = 1;
+				vc->vc_toggle_meta = 0;
+				break;
+			case 12: /* ANSI X3.64-1979 (SCO-ish?)
+				  * Select second alternate font, toggle
+				  * high bit before displaying as ROM char.
+				  */
+				vc->vc_translate = set_translate(IBMPC_MAP, vc);
+				vc->vc_disp_ctrl = 1;
+				vc->vc_toggle_meta = 1;
+				break;
+			case 21:
+			case 22:
+				vc->vc_intensity = 1;
+				break;
+			case 24:
+				vc->vc_underline = 0;
+				break;
+			case 25:
+				vc->vc_blink = 0;
+				break;
+			case 27:
+				vc->vc_reverse = 0;
+				break;
+			case 38: /* ANSI X3.64-1979 (SCO-ish?)
+				  * Enables underscore, white foreground
+				  * with white underscore (Linux - use
+				  * default foreground).
+				  */
+				vc->vc_color = (vc->vc_def_color & 0x0f) | (vc->vc_color & 0xf0);
+				vc->vc_underline = 1;
+				break;
+			case 39: /* ANSI X3.64-1979 (SCO-ish?)
+				  * Disable underline option.
+				  * Reset colour to default? It did this
+				  * before...
+				  */
+				vc->vc_color = (vc->vc_def_color & 0x0f) | (vc->vc_color & 0xf0);
+				vc->vc_underline = 0;
+				break;
+			case 49:
+				vc->vc_color = (vc->vc_def_color & 0xf0) | (vc->vc_color & 0x0f);
+				break;
+			default:
+				if (vc->vc_par[i] >= 30 && vc->vc_par[i] <= 37)
+					vc->vc_color = color_table[vc->vc_par[i] - 30]
+						| (vc->vc_color & 0xf0);
+				else if (vc->vc_par[i] >= 40 && vc->vc_par[i] <= 47)
+					vc->vc_color = (color_table[vc->vc_par[i] - 40] << 4)
+						| (vc->vc_color & 0x0f);
+				break;
+		}
+	update_attr(vc);
+}
+
+static void respond_string(const char *p, struct tty_struct *tty)
+{
+	while (*p) {
+		tty_insert_flip_char(tty, *p, 0);
+		p++;
+	}
+	con_schedule_flip(tty);
+}
+
+static void cursor_report(struct vc_data *vc, struct tty_struct *tty)
+{
+	char buf[40];
+
+	sprintf(buf, "\033[%d;%dR", vc->vc_y + (vc->vc_decom ? vc->vc_top + 1 : 1), vc->vc_x + 1);
+	respond_string(buf, tty);
+}
+
+static inline void status_report(struct tty_struct *tty)
+{
+	respond_string("\033[0n", tty);	/* Terminal ok */
+}
+
+static inline void respond_ID(struct tty_struct * tty)
+{
+	respond_string(VT102ID, tty);
+}
+
+void mouse_report(struct tty_struct *tty, int butt, int mrx, int mry)
+{
+	char buf[8];
+
+	sprintf(buf, "\033[M%c%c%c", (char)(' ' + butt), (char)('!' + mrx),
+		(char)('!' + mry));
+	respond_string(buf, tty);
+}
+
+/* invoked via ioctl(TIOCLINUX) and through set_selection */
+int mouse_reporting(void)
+{
+	return vc_cons[fg_console].d->vc_report_mouse;
+}
+
+/* console_sem is held */
+static void set_mode(struct vc_data *vc, int on_off)
+{
+	int i;
+
+	for (i = 0; i <= vc->vc_npar; i++)
+		if (vc->vc_ques) {
+			switch(vc->vc_par[i]) {	/* DEC private modes set/reset */
+			case 1:			/* Cursor keys send ^[Ox/^[[x */
+				if (on_off)
+					set_kbd(vc, decckm);
+				else
+					clr_kbd(vc, decckm);
+				break;
+			case 3:	/* 80/132 mode switch unimplemented */
+				vc->vc_deccolm = on_off;
+#if 0
+				vc_resize(deccolm ? 132 : 80, vc->vc_rows);
+				/* this alone does not suffice; some user mode
+				   utility has to change the hardware regs */
+#endif
+				break;
+			case 5:			/* Inverted screen on/off */
+				if (vc->vc_decscnm != on_off) {
+					vc->vc_decscnm = on_off;
+					invert_screen(vc, 0, vc->vc_screenbuf_size, 0);
+					update_attr(vc);
+				}
+				break;
+			case 6:			/* Origin relative/absolute */
+				vc->vc_decom = on_off;
+				gotoxay(vc, 0, 0);
+				break;
+			case 7:			/* Autowrap on/off */
+				vc->vc_decawm = on_off;
+				break;
+			case 8:			/* Autorepeat on/off */
+				if (on_off)
+					set_kbd(vc, decarm);
+				else
+					clr_kbd(vc, decarm);
+				break;
+			case 9:
+				vc->vc_report_mouse = on_off ? 1 : 0;
+				break;
+			case 25:		/* Cursor on/off */
+				vc->vc_deccm = on_off;
+				break;
+			case 1000:
+				vc->vc_report_mouse = on_off ? 2 : 0;
+				break;
+			}
+		} else {
+			switch(vc->vc_par[i]) {	/* ANSI modes set/reset */
+			case 3:			/* Monitor (display ctrls) */
+				vc->vc_disp_ctrl = on_off;
+				break;
+			case 4:			/* Insert Mode on/off */
+				vc->vc_decim = on_off;
+				break;
+			case 20:		/* Lf, Enter == CrLf/Lf */
+				if (on_off)
+					set_kbd(vc, lnm);
+				else
+					clr_kbd(vc, lnm);
+				break;
+			}
+		}
+}
+
+/* console_sem is held */
+static void setterm_command(struct vc_data *vc)
+{
+	switch(vc->vc_par[0]) {
+		case 1:	/* set color for underline mode */
+			if (vc->vc_can_do_color &&
+					vc->vc_par[1] < 16) {
+				vc->vc_ulcolor = color_table[vc->vc_par[1]];
+				if (vc->vc_underline)
+					update_attr(vc);
+			}
+			break;
+		case 2:	/* set color for half intensity mode */
+			if (vc->vc_can_do_color &&
+					vc->vc_par[1] < 16) {
+				vc->vc_halfcolor = color_table[vc->vc_par[1]];
+				if (vc->vc_intensity == 0)
+					update_attr(vc);
+			}
+			break;
+		case 8:	/* store colors as defaults */
+			vc->vc_def_color = vc->vc_attr;
+			if (vc->vc_hi_font_mask == 0x100)
+				vc->vc_def_color >>= 1;
+			default_attr(vc);
+			update_attr(vc);
+			break;
+		case 9:	/* set blanking interval */
+			blankinterval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60 * HZ;
+			poke_blanked_console();
+			break;
+		case 10: /* set bell frequency in Hz */
+			if (vc->vc_npar >= 1)
+				vc->vc_bell_pitch = vc->vc_par[1];
+			else
+				vc->vc_bell_pitch = DEFAULT_BELL_PITCH;
+			break;
+		case 11: /* set bell duration in msec */
+			if (vc->vc_npar >= 1)
+				vc->vc_bell_duration = (vc->vc_par[1] < 2000) ?
+					vc->vc_par[1] * HZ / 1000 : 0;
+			else
+				vc->vc_bell_duration = DEFAULT_BELL_DURATION;
+			break;
+		case 12: /* bring specified console to the front */
+			if (vc->vc_par[1] >= 1 && vc_cons_allocated(vc->vc_par[1] - 1))
+				set_console(vc->vc_par[1] - 1);
+			break;
+		case 13: /* unblank the screen */
+			poke_blanked_console();
+			break;
+		case 14: /* set vesa powerdown interval */
+			vesa_off_interval = ((vc->vc_par[1] < 60) ? vc->vc_par[1] : 60) * 60 * HZ;
+			break;
+		case 15: /* activate the previous console */
+			set_console(last_console);
+			break;
+	}
+}
+
+/* console_sem is held */
+static void csi_at(struct vc_data *vc, unsigned int nr)
+{
+	if (nr > vc->vc_cols - vc->vc_x)
+		nr = vc->vc_cols - vc->vc_x;
+	else if (!nr)
+		nr = 1;
+	insert_char(vc, nr);
+}
+
+/* console_sem is held */
+static void csi_L(struct vc_data *vc, unsigned int nr)
+{
+	if (nr > vc->vc_rows - vc->vc_y)
+		nr = vc->vc_rows - vc->vc_y;
+	else if (!nr)
+		nr = 1;
+	scrdown(vc, vc->vc_y, vc->vc_bottom, nr);
+	vc->vc_need_wrap = 0;
+}
+
+/* console_sem is held */
+static void csi_P(struct vc_data *vc, unsigned int nr)
+{
+	if (nr > vc->vc_cols - vc->vc_x)
+		nr = vc->vc_cols - vc->vc_x;
+	else if (!nr)
+		nr = 1;
+	delete_char(vc, nr);
+}
+
+/* console_sem is held */
+static void csi_M(struct vc_data *vc, unsigned int nr)
+{
+	if (nr > vc->vc_rows - vc->vc_y)
+		nr = vc->vc_rows - vc->vc_y;
+	else if (!nr)
+		nr=1;
+	scrup(vc, vc->vc_y, vc->vc_bottom, nr);
+	vc->vc_need_wrap = 0;
+}
+
+/* console_sem is held (except via vc_init->reset_terminal */
+static void save_cur(struct vc_data *vc)
+{
+	vc->vc_saved_x		= vc->vc_x;
+	vc->vc_saved_y		= vc->vc_y;
+	vc->vc_s_intensity	= vc->vc_intensity;
+	vc->vc_s_underline	= vc->vc_underline;
+	vc->vc_s_blink		= vc->vc_blink;
+	vc->vc_s_reverse	= vc->vc_reverse;
+	vc->vc_s_charset	= vc->vc_charset;
+	vc->vc_s_color		= vc->vc_color;
+	vc->vc_saved_G0		= vc->vc_G0_charset;
+	vc->vc_saved_G1		= vc->vc_G1_charset;
+}
+
+/* console_sem is held */
+static void restore_cur(struct vc_data *vc)
+{
+	gotoxy(vc, vc->vc_saved_x, vc->vc_saved_y);
+	vc->vc_intensity	= vc->vc_s_intensity;
+	vc->vc_underline	= vc->vc_s_underline;
+	vc->vc_blink		= vc->vc_s_blink;
+	vc->vc_reverse		= vc->vc_s_reverse;
+	vc->vc_charset		= vc->vc_s_charset;
+	vc->vc_color		= vc->vc_s_color;
+	vc->vc_G0_charset	= vc->vc_saved_G0;
+	vc->vc_G1_charset	= vc->vc_saved_G1;
+	vc->vc_translate	= set_translate(vc->vc_charset ? vc->vc_G1_charset : vc->vc_G0_charset, vc);
+	update_attr(vc);
+	vc->vc_need_wrap = 0;
+}
+
+enum { ESnormal, ESesc, ESsquare, ESgetpars, ESgotpars, ESfunckey,
+	EShash, ESsetG0, ESsetG1, ESpercent, ESignore, ESnonstd,
+	ESpalette };
+
+/* console_sem is held (except via vc_init()) */
+static void reset_terminal(struct vc_data *vc, int do_clear)
+{
+	vc->vc_top		= 0;
+	vc->vc_bottom		= vc->vc_rows;
+	vc->vc_state		= ESnormal;
+	vc->vc_ques		= 0;
+	vc->vc_translate	= set_translate(LAT1_MAP, vc);
+	vc->vc_G0_charset	= LAT1_MAP;
+	vc->vc_G1_charset	= GRAF_MAP;
+	vc->vc_charset		= 0;
+	vc->vc_need_wrap	= 0;
+	vc->vc_report_mouse	= 0;
+	vc->vc_utf		= 0;
+	vc->vc_utf_count	= 0;
+
+	vc->vc_disp_ctrl	= 0;
+	vc->vc_toggle_meta	= 0;
+
+	vc->vc_decscnm		= 0;
+	vc->vc_decom		= 0;
+	vc->vc_decawm		= 1;
+	vc->vc_deccm		= 1;
+	vc->vc_decim		= 0;
+
+	set_kbd(vc, decarm);
+	clr_kbd(vc, decckm);
+	clr_kbd(vc, kbdapplic);
+	clr_kbd(vc, lnm);
+	kbd_table[vc->vc_num].lockstate = 0;
+	kbd_table[vc->vc_num].slockstate = 0;
+	kbd_table[vc->vc_num].ledmode = LED_SHOW_FLAGS;
+	kbd_table[vc->vc_num].ledflagstate = kbd_table[vc->vc_num].default_ledflagstate;
+	/* do not do set_leds here because this causes an endless tasklet loop
+	   when the keyboard hasn't been initialized yet */
+
+	vc->vc_cursor_type = CUR_DEFAULT;
+	vc->vc_complement_mask = vc->vc_s_complement_mask;
+
+	default_attr(vc);
+	update_attr(vc);
+
+	vc->vc_tab_stop[0]	= 0x01010100;
+	vc->vc_tab_stop[1]	=
+	vc->vc_tab_stop[2]	=
+	vc->vc_tab_stop[3]	=
+	vc->vc_tab_stop[4]	= 0x01010101;
+
+	vc->vc_bell_pitch = DEFAULT_BELL_PITCH;
+	vc->vc_bell_duration = DEFAULT_BELL_DURATION;
+
+	gotoxy(vc, 0, 0);
+	save_cur(vc);
+	if (do_clear)
+	    csi_J(vc, 2);
+}
+
+/* console_sem is held */
+static void do_con_trol(struct tty_struct *tty, struct vc_data *vc, int c)
+{
+	/*
+	 *  Control characters can be used in the _middle_
+	 *  of an escape sequence.
+	 */
+	switch (c) {
+	case 0:
+		return;
+	case 7:
+		if (vc->vc_bell_duration)
+			kd_mksound(vc->vc_bell_pitch, vc->vc_bell_duration);
+		return;
+	case 8:
+		bs(vc);
+		return;
+	case 9:
+		vc->vc_pos -= (vc->vc_x << 1);
+		while (vc->vc_x < vc->vc_cols - 1) {
+			vc->vc_x++;
+			if (vc->vc_tab_stop[vc->vc_x >> 5] & (1 << (vc->vc_x & 31)))
+				break;
+		}
+		vc->vc_pos += (vc->vc_x << 1);
+		return;
+	case 10: case 11: case 12:
+		lf(vc);
+		if (!is_kbd(vc, lnm))
+			return;
+	case 13:
+		cr(vc);
+		return;
+	case 14:
+		vc->vc_charset = 1;
+		vc->vc_translate = set_translate(vc->vc_G1_charset, vc);
+		vc->vc_disp_ctrl = 1;
+		return;
+	case 15:
+		vc->vc_charset = 0;
+		vc->vc_translate = set_translate(vc->vc_G0_charset, vc);
+		vc->vc_disp_ctrl = 0;
+		return;
+	case 24: case 26:
+		vc->vc_state = ESnormal;
+		return;
+	case 27:
+		vc->vc_state = ESesc;
+		return;
+	case 127:
+		del(vc);
+		return;
+	case 128+27:
+		vc->vc_state = ESsquare;
+		return;
+	}
+	switch(vc->vc_state) {
+	case ESesc:
+		vc->vc_state = ESnormal;
+		switch (c) {
+		case '[':
+			vc->vc_state = ESsquare;
+			return;
+		case ']':
+			vc->vc_state = ESnonstd;
+			return;
+		case '%':
+			vc->vc_state = ESpercent;
+			return;
+		case 'E':
+			cr(vc);
+			lf(vc);
+			return;
+		case 'M':
+			ri(vc);
+			return;
+		case 'D':
+			lf(vc);
+			return;
+		case 'H':
+			vc->vc_tab_stop[vc->vc_x >> 5] |= (1 << (vc->vc_x & 31));
+			return;
+		case 'Z':
+			respond_ID(tty);
+			return;
+		case '7':
+			save_cur(vc);
+			return;
+		case '8':
+			restore_cur(vc);
+			return;
+		case '(':
+			vc->vc_state = ESsetG0;
+			return;
+		case ')':
+			vc->vc_state = ESsetG1;
+			return;
+		case '#':
+			vc->vc_state = EShash;
+			return;
+		case 'c':
+			reset_terminal(vc, 1);
+			return;
+		case '>':  /* Numeric keypad */
+			clr_kbd(vc, kbdapplic);
+			return;
+		case '=':  /* Appl. keypad */
+			set_kbd(vc, kbdapplic);
+			return;
+		}
+		return;
+	case ESnonstd:
+		if (c=='P') {   /* palette escape sequence */
+			for (vc->vc_npar = 0; vc->vc_npar < NPAR; vc->vc_npar++)
+				vc->vc_par[vc->vc_npar] = 0;
+			vc->vc_npar = 0;
+			vc->vc_state = ESpalette;
+			return;
+		} else if (c=='R') {   /* reset palette */
+			reset_palette(vc);
+			vc->vc_state = ESnormal;
+		} else
+			vc->vc_state = ESnormal;
+		return;
+	case ESpalette:
+		if ( (c>='0'&&c<='9') || (c>='A'&&c<='F') || (c>='a'&&c<='f') ) {
+			vc->vc_par[vc->vc_npar++] = (c > '9' ? (c & 0xDF) - 'A' + 10 : c - '0');
+			if (vc->vc_npar == 7) {
+				int i = vc->vc_par[0] * 3, j = 1;
+				vc->vc_palette[i] = 16 * vc->vc_par[j++];
+				vc->vc_palette[i++] += vc->vc_par[j++];
+				vc->vc_palette[i] = 16 * vc->vc_par[j++];
+				vc->vc_palette[i++] += vc->vc_par[j++];
+				vc->vc_palette[i] = 16 * vc->vc_par[j++];
+				vc->vc_palette[i] += vc->vc_par[j];
+				set_palette(vc);
+				vc->vc_state = ESnormal;
+			}
+		} else
+			vc->vc_state = ESnormal;
+		return;
+	case ESsquare:
+		for (vc->vc_npar = 0; vc->vc_npar < NPAR; vc->vc_npar++)
+			vc->vc_par[vc->vc_npar] = 0;
+		vc->vc_npar = 0;
+		vc->vc_state = ESgetpars;
+		if (c == '[') { /* Function key */
+			vc->vc_state=ESfunckey;
+			return;
+		}
+		vc->vc_ques = (c == '?');
+		if (vc->vc_ques)
+			return;
+	case ESgetpars:
+		if (c == ';' && vc->vc_npar < NPAR - 1) {
+			vc->vc_npar++;
+			return;
+		} else if (c>='0' && c<='9') {
+			vc->vc_par[vc->vc_npar] *= 10;
+			vc->vc_par[vc->vc_npar] += c - '0';
+			return;
+		} else
+			vc->vc_state = ESgotpars;
+	case ESgotpars:
+		vc->vc_state = ESnormal;
+		switch(c) {
+		case 'h':
+			set_mode(vc, 1);
+			return;
+		case 'l':
+			set_mode(vc, 0);
+			return;
+		case 'c':
+			if (vc->vc_ques) {
+				if (vc->vc_par[0])
+					vc->vc_cursor_type = vc->vc_par[0] | (vc->vc_par[1] << 8) | (vc->vc_par[2] << 16);
+				else
+					vc->vc_cursor_type = CUR_DEFAULT;
+				return;
+			}
+			break;
+		case 'm':
+			if (vc->vc_ques) {
+				clear_selection();
+				if (vc->vc_par[0])
+					vc->vc_complement_mask = vc->vc_par[0] << 8 | vc->vc_par[1];
+				else
+					vc->vc_complement_mask = vc->vc_s_complement_mask;
+				return;
+			}
+			break;
+		case 'n':
+			if (!vc->vc_ques) {
+				if (vc->vc_par[0] == 5)
+					status_report(tty);
+				else if (vc->vc_par[0] == 6)
+					cursor_report(vc, tty);
+			}
+			return;
+		}
+		if (vc->vc_ques) {
+			vc->vc_ques = 0;
+			return;
+		}
+		switch(c) {
+		case 'G': case '`':
+			if (vc->vc_par[0])
+				vc->vc_par[0]--;
+			gotoxy(vc, vc->vc_par[0], vc->vc_y);
+			return;
+		case 'A':
+			if (!vc->vc_par[0])
+				vc->vc_par[0]++;
+			gotoxy(vc, vc->vc_x, vc->vc_y - vc->vc_par[0]);
+			return;
+		case 'B': case 'e':
+			if (!vc->vc_par[0])
+				vc->vc_par[0]++;
+			gotoxy(vc, vc->vc_x, vc->vc_y + vc->vc_par[0]);
+			return;
+		case 'C': case 'a':
+			if (!vc->vc_par[0])
+				vc->vc_par[0]++;
+			gotoxy(vc, vc->vc_x + vc->vc_par[0], vc->vc_y);
+			return;
+		case 'D':
+			if (!vc->vc_par[0])
+				vc->vc_par[0]++;
+			gotoxy(vc, vc->vc_x - vc->vc_par[0], vc->vc_y);
+			return;
+		case 'E':
+			if (!vc->vc_par[0])
+				vc->vc_par[0]++;
+			gotoxy(vc, 0, vc->vc_y + vc->vc_par[0]);
+			return;
+		case 'F':
+			if (!vc->vc_par[0])
+				vc->vc_par[0]++;
+			gotoxy(vc, 0, vc->vc_y - vc->vc_par[0]);
+			return;
+		case 'd':
+			if (vc->vc_par[0])
+				vc->vc_par[0]--;
+			gotoxay(vc, vc->vc_x ,vc->vc_par[0]);
+			return;
+		case 'H': case 'f':
+			if (vc->vc_par[0])
+				vc->vc_par[0]--;
+			if (vc->vc_par[1])
+				vc->vc_par[1]--;
+			gotoxay(vc, vc->vc_par[1], vc->vc_par[0]);
+			return;
+		case 'J':
+			csi_J(vc, vc->vc_par[0]);
+			return;
+		case 'K':
+			csi_K(vc, vc->vc_par[0]);
+			return;
+		case 'L':
+			csi_L(vc, vc->vc_par[0]);
+			return;
+		case 'M':
+			csi_M(vc, vc->vc_par[0]);
+			return;
+		case 'P':
+			csi_P(vc, vc->vc_par[0]);
+			return;
+		case 'c':
+			if (!vc->vc_par[0])
+				respond_ID(tty);
+			return;
+		case 'g':
+			if (!vc->vc_par[0])
+				vc->vc_tab_stop[vc->vc_x >> 5] &= ~(1 << (vc->vc_x & 31));
+			else if (vc->vc_par[0] == 3) {
+				vc->vc_tab_stop[0] =
+					vc->vc_tab_stop[1] =
+					vc->vc_tab_stop[2] =
+					vc->vc_tab_stop[3] =
+					vc->vc_tab_stop[4] = 0;
+			}
+			return;
+		case 'm':
+			csi_m(vc);
+			return;
+		case 'q': /* DECLL - but only 3 leds */
+			/* map 0,1,2,3 to 0,1,2,4 */
+			if (vc->vc_par[0] < 4)
+				setledstate(kbd_table + vc->vc_num,
+					    (vc->vc_par[0] < 3) ? vc->vc_par[0] : 4);
+			return;
+		case 'r':
+			if (!vc->vc_par[0])
+				vc->vc_par[0]++;
+			if (!vc->vc_par[1])
+				vc->vc_par[1] = vc->vc_rows;
+			/* Minimum allowed region is 2 lines */
+			if (vc->vc_par[0] < vc->vc_par[1] &&
+			    vc->vc_par[1] <= vc->vc_rows) {
+				vc->vc_top = vc->vc_par[0] - 1;
+				vc->vc_bottom = vc->vc_par[1];
+				gotoxay(vc, 0, 0);
+			}
+			return;
+		case 's':
+			save_cur(vc);
+			return;
+		case 'u':
+			restore_cur(vc);
+			return;
+		case 'X':
+			csi_X(vc, vc->vc_par[0]);
+			return;
+		case '@':
+			csi_at(vc, vc->vc_par[0]);
+			return;
+		case ']': /* setterm functions */
+			setterm_command(vc);
+			return;
+		}
+		return;
+	case ESpercent:
+		vc->vc_state = ESnormal;
+		switch (c) {
+		case '@':  /* defined in ISO 2022 */
+			vc->vc_utf = 0;
+			return;
+		case 'G':  /* prelim official escape code */
+		case '8':  /* retained for compatibility */
+			vc->vc_utf = 1;
+			return;
+		}
+		return;
+	case ESfunckey:
+		vc->vc_state = ESnormal;
+		return;
+	case EShash:
+		vc->vc_state = ESnormal;
+		if (c == '8') {
+			/* DEC screen alignment test. kludge :-) */
+			vc->vc_video_erase_char =
+				(vc->vc_video_erase_char & 0xff00) | 'E';
+			csi_J(vc, 2);
+			vc->vc_video_erase_char =
+				(vc->vc_video_erase_char & 0xff00) | ' ';
+			do_update_region(vc, vc->vc_origin, vc->vc_screenbuf_size / 2);
+		}
+		return;
+	case ESsetG0:
+		if (c == '0')
+			vc->vc_G0_charset = GRAF_MAP;
+		else if (c == 'B')
+			vc->vc_G0_charset = LAT1_MAP;
+		else if (c == 'U')
+			vc->vc_G0_charset = IBMPC_MAP;
+		else if (c == 'K')
+			vc->vc_G0_charset = USER_MAP;
+		if (vc->vc_charset == 0)
+			vc->vc_translate = set_translate(vc->vc_G0_charset, vc);
+		vc->vc_state = ESnormal;
+		return;
+	case ESsetG1:
+		if (c == '0')
+			vc->vc_G1_charset = GRAF_MAP;
+		else if (c == 'B')
+			vc->vc_G1_charset = LAT1_MAP;
+		else if (c == 'U')
+			vc->vc_G1_charset = IBMPC_MAP;
+		else if (c == 'K')
+			vc->vc_G1_charset = USER_MAP;
+		if (vc->vc_charset == 1)
+			vc->vc_translate = set_translate(vc->vc_G1_charset, vc);
+		vc->vc_state = ESnormal;
+		return;
+	default:
+		vc->vc_state = ESnormal;
+	}
+}
+
+/* This is a temporary buffer used to prepare a tty console write
+ * so that we can easily avoid touching user space while holding the
+ * console spinlock.  It is allocated in con_init and is shared by
+ * this code and the vc_screen read/write tty calls.
+ *
+ * We have to allocate this statically in the kernel data section
+ * since console_init (and thus con_init) are called before any
+ * kernel memory allocation is available.
+ */
+char con_buf[CON_BUF_SIZE];
+DECLARE_MUTEX(con_buf_sem);
+
+/* acquires console_sem */
+static int do_con_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+#ifdef VT_BUF_VRAM_ONLY
+#define FLUSH do { } while(0);
+#else
+#define FLUSH if (draw_x >= 0) { \
+	vc->vc_sw->con_putcs(vc, (u16 *)draw_from, (u16 *)draw_to - (u16 *)draw_from, vc->vc_y, draw_x); \
+	draw_x = -1; \
+	}
+#endif
+
+	int c, tc, ok, n = 0, draw_x = -1;
+	unsigned int currcons;
+	unsigned long draw_from = 0, draw_to = 0;
+	struct vc_data *vc;
+	u16 himask, charmask;
+	const unsigned char *orig_buf = NULL;
+	int orig_count;
+
+	if (in_interrupt())
+		return count;
+
+	might_sleep();
+
+	acquire_console_sem();
+	vc = tty->driver_data;
+	if (vc == NULL) {
+		printk(KERN_ERR "vt: argh, driver_data is NULL !\n");
+		release_console_sem();
+		return 0;
+	}
+
+	currcons = vc->vc_num;
+	if (!vc_cons_allocated(currcons)) {
+	    /* could this happen? */
+	    static int error = 0;
+	    if (!error) {
+		error = 1;
+		printk("con_write: tty %d not allocated\n", currcons+1);
+	    }
+	    release_console_sem();
+	    return 0;
+	}
+	release_console_sem();
+
+	orig_buf = buf;
+	orig_count = count;
+
+	/* At this point 'buf' is guaranteed to be a kernel buffer
+	 * and therefore no access to userspace (and therefore sleeping)
+	 * will be needed.  The con_buf_sem serializes all tty based
+	 * console rendering and vcs write/read operations.  We hold
+	 * the console spinlock during the entire write.
+	 */
+
+	acquire_console_sem();
+
+	vc = tty->driver_data;
+	if (vc == NULL) {
+		printk(KERN_ERR "vt: argh, driver_data _became_ NULL !\n");
+		release_console_sem();
+		goto out;
+	}
+
+	himask = vc->vc_hi_font_mask;
+	charmask = himask ? 0x1ff : 0xff;
+
+	/* undraw cursor first */
+	if (IS_FG(vc))
+		hide_cursor(vc);
+
+	while (!tty->stopped && count) {
+		int orig = *buf;
+		c = orig;
+		buf++;
+		n++;
+		count--;
+
+		/* Do no translation at all in control states */
+		if (vc->vc_state != ESnormal) {
+			tc = c;
+		} else if (vc->vc_utf) {
+		    /* Combine UTF-8 into Unicode */
+		    /* Incomplete characters silently ignored */
+		    if(c > 0x7f) {
+			if (vc->vc_utf_count > 0 && (c & 0xc0) == 0x80) {
+				vc->vc_utf_char = (vc->vc_utf_char << 6) | (c & 0x3f);
+				vc->vc_utf_count--;
+				if (vc->vc_utf_count == 0)
+				    tc = c = vc->vc_utf_char;
+				else continue;
+			} else {
+				if ((c & 0xe0) == 0xc0) {
+				    vc->vc_utf_count = 1;
+				    vc->vc_utf_char = (c & 0x1f);
+				} else if ((c & 0xf0) == 0xe0) {
+				    vc->vc_utf_count = 2;
+				    vc->vc_utf_char = (c & 0x0f);
+				} else if ((c & 0xf8) == 0xf0) {
+				    vc->vc_utf_count = 3;
+				    vc->vc_utf_char = (c & 0x07);
+				} else if ((c & 0xfc) == 0xf8) {
+				    vc->vc_utf_count = 4;
+				    vc->vc_utf_char = (c & 0x03);
+				} else if ((c & 0xfe) == 0xfc) {
+				    vc->vc_utf_count = 5;
+				    vc->vc_utf_char = (c & 0x01);
+				} else
+				    vc->vc_utf_count = 0;
+				continue;
+			      }
+		    } else {
+		      tc = c;
+		      vc->vc_utf_count = 0;
+		    }
+		} else {	/* no utf */
+		  tc = vc->vc_translate[vc->vc_toggle_meta ? (c | 0x80) : c];
+		}
+
+                /* If the original code was a control character we
+                 * only allow a glyph to be displayed if the code is
+                 * not normally used (such as for cursor movement) or
+                 * if the disp_ctrl mode has been explicitly enabled.
+                 * Certain characters (as given by the CTRL_ALWAYS
+                 * bitmap) are always displayed as control characters,
+                 * as the console would be pretty useless without
+                 * them; to display an arbitrary font position use the
+                 * direct-to-font zone in UTF-8 mode.
+                 */
+                ok = tc && (c >= 32 ||
+			    (!vc->vc_utf && !(((vc->vc_disp_ctrl ? CTRL_ALWAYS
+						: CTRL_ACTION) >> c) & 1)))
+			&& (c != 127 || vc->vc_disp_ctrl)
+			&& (c != 128+27);
+
+		if (vc->vc_state == ESnormal && ok) {
+			/* Now try to find out how to display it */
+			tc = conv_uni_to_pc(vc, tc);
+			if ( tc == -4 ) {
+                                /* If we got -4 (not found) then see if we have
+                                   defined a replacement character (U+FFFD) */
+                                tc = conv_uni_to_pc(vc, 0xfffd);
+
+				/* One reason for the -4 can be that we just
+				   did a clear_unimap();
+				   try at least to show something. */
+				if (tc == -4)
+				     tc = c;
+                        } else if ( tc == -3 ) {
+                                /* Bad hash table -- hope for the best */
+                                tc = c;
+                        }
+			if (tc & ~charmask)
+                                continue; /* Conversion failed */
+
+			if (vc->vc_need_wrap || vc->vc_decim)
+				FLUSH
+			if (vc->vc_need_wrap) {
+				cr(vc);
+				lf(vc);
+			}
+			if (vc->vc_decim)
+				insert_char(vc, 1);
+			scr_writew(himask ?
+				     ((vc->vc_attr << 8) & ~himask) + ((tc & 0x100) ? himask : 0) + (tc & 0xff) :
+				     (vc->vc_attr << 8) + tc,
+				   (u16 *) vc->vc_pos);
+			if (DO_UPDATE(vc) && draw_x < 0) {
+				draw_x = vc->vc_x;
+				draw_from = vc->vc_pos;
+			}
+			if (vc->vc_x == vc->vc_cols - 1) {
+				vc->vc_need_wrap = vc->vc_decawm;
+				draw_to = vc->vc_pos + 2;
+			} else {
+				vc->vc_x++;
+				draw_to = (vc->vc_pos += 2);
+			}
+			continue;
+		}
+		FLUSH
+		do_con_trol(tty, vc, orig);
+	}
+	FLUSH
+	console_conditional_schedule();
+	release_console_sem();
+
+out:
+	return n;
+#undef FLUSH
+}
+
+/*
+ * This is the console switching callback.
+ *
+ * Doing console switching in a process context allows
+ * us to do the switches asynchronously (needed when we want
+ * to switch due to a keyboard interrupt).  Synchronization
+ * with other console code and prevention of re-entrancy is
+ * ensured with console_sem.
+ */
+static void console_callback(void *ignored)
+{
+	acquire_console_sem();
+
+	if (want_console >= 0) {
+		if (want_console != fg_console &&
+		    vc_cons_allocated(want_console)) {
+			hide_cursor(vc_cons[fg_console].d);
+			change_console(vc_cons[want_console].d);
+			/* we only changed when the console had already
+			   been allocated - a new console is not created
+			   in an interrupt routine */
+		}
+		want_console = -1;
+	}
+	if (do_poke_blanked_console) { /* do not unblank for a LED change */
+		do_poke_blanked_console = 0;
+		poke_blanked_console();
+	}
+	if (scrollback_delta) {
+		struct vc_data *vc = vc_cons[fg_console].d;
+		clear_selection();
+		if (vc->vc_mode == KD_TEXT)
+			vc->vc_sw->con_scrolldelta(vc, scrollback_delta);
+		scrollback_delta = 0;
+	}
+	if (blank_timer_expired) {
+		do_blank_screen(0);
+		blank_timer_expired = 0;
+	}
+
+	release_console_sem();
+}
+
+void set_console(int nr)
+{
+	want_console = nr;
+	schedule_console_callback();
+}
+
+struct tty_driver *console_driver;
+
+#ifdef CONFIG_VT_CONSOLE
+
+/*
+ *	Console on virtual terminal
+ *
+ * The console must be locked when we get here.
+ */
+
+static void vt_console_print(struct console *co, const char *b, unsigned count)
+{
+	struct vc_data *vc = vc_cons[fg_console].d;
+	unsigned char c;
+	static unsigned long printing;
+	const ushort *start;
+	ushort cnt = 0;
+	ushort myx;
+
+	/* console busy or not yet initialized */
+	if (!printable || test_and_set_bit(0, &printing))
+		return;
+
+	if (kmsg_redirect && vc_cons_allocated(kmsg_redirect - 1))
+		vc = vc_cons[kmsg_redirect - 1].d;
+
+	/* read `x' only after setting currcons properly (otherwise
+	   the `x' macro will read the x of the foreground console). */
+	myx = vc->vc_x;
+
+	if (!vc_cons_allocated(fg_console)) {
+		/* impossible */
+		/* printk("vt_console_print: tty %d not allocated ??\n", currcons+1); */
+		goto quit;
+	}
+
+	if (vc->vc_mode != KD_TEXT)
+		goto quit;
+
+	/* undraw cursor first */
+	if (IS_FG(vc))
+		hide_cursor(vc);
+
+	start = (ushort *)vc->vc_pos;
+
+	/* Contrived structure to try to emulate original need_wrap behaviour
+	 * Problems caused when we have need_wrap set on '\n' character */
+	while (count--) {
+		c = *b++;
+		if (c == 10 || c == 13 || c == 8 || vc->vc_need_wrap) {
+			if (cnt > 0) {
+				if (CON_IS_VISIBLE(vc))
+					vc->vc_sw->con_putcs(vc, start, cnt, vc->vc_y, vc->vc_x);
+				vc->vc_x += cnt;
+				if (vc->vc_need_wrap)
+					vc->vc_x--;
+				cnt = 0;
+			}
+			if (c == 8) {		/* backspace */
+				bs(vc);
+				start = (ushort *)vc->vc_pos;
+				myx = vc->vc_x;
+				continue;
+			}
+			if (c != 13)
+				lf(vc);
+			cr(vc);
+			start = (ushort *)vc->vc_pos;
+			myx = vc->vc_x;
+			if (c == 10 || c == 13)
+				continue;
+		}
+		scr_writew((vc->vc_attr << 8) + c, (unsigned short *)vc->vc_pos);
+		cnt++;
+		if (myx == vc->vc_cols - 1) {
+			vc->vc_need_wrap = 1;
+			continue;
+		}
+		vc->vc_pos += 2;
+		myx++;
+	}
+	if (cnt > 0) {
+		if (CON_IS_VISIBLE(vc))
+			vc->vc_sw->con_putcs(vc, start, cnt, vc->vc_y, vc->vc_x);
+		vc->vc_x += cnt;
+		if (vc->vc_x == vc->vc_cols) {
+			vc->vc_x--;
+			vc->vc_need_wrap = 1;
+		}
+	}
+	set_cursor(vc);
+
+quit:
+	clear_bit(0, &printing);
+}
+
+static struct tty_driver *vt_console_device(struct console *c, int *index)
+{
+	*index = c->index ? c->index-1 : fg_console;
+	return console_driver;
+}
+
+static struct console vt_console_driver = {
+	.name		= "tty",
+	.write		= vt_console_print,
+	.device		= vt_console_device,
+	.unblank	= unblank_screen,
+	.flags		= CON_PRINTBUFFER,
+	.index		= -1,
+};
+#endif
+
+/*
+ *	Handling of Linux-specific VC ioctls
+ */
+
+/*
+ * Generally a bit racy with respect to console_sem().
+ *
+ * There are some functions which don't need it.
+ *
+ * There are some functions which can sleep for arbitrary periods
+ * (paste_selection) but we don't need the lock there anyway.
+ *
+ * set_selection has locking, and definitely needs it
+ */
+
+int tioclinux(struct tty_struct *tty, unsigned long arg)
+{
+	char type, data;
+	char __user *p = (char __user *)arg;
+	int lines;
+	int ret;
+
+	if (tty->driver->type != TTY_DRIVER_TYPE_CONSOLE)
+		return -EINVAL;
+	if (current->signal->tty != tty && !capable(CAP_SYS_ADMIN))
+		return -EPERM;
+	if (get_user(type, p))
+		return -EFAULT;
+	ret = 0;
+	switch (type)
+	{
+		case TIOCL_SETSEL:
+			acquire_console_sem();
+			ret = set_selection((struct tiocl_selection __user *)(p+1), tty);
+			release_console_sem();
+			break;
+		case TIOCL_PASTESEL:
+			ret = paste_selection(tty);
+			break;
+		case TIOCL_UNBLANKSCREEN:
+			unblank_screen();
+			break;
+		case TIOCL_SELLOADLUT:
+			ret = sel_loadlut(p);
+			break;
+		case TIOCL_GETSHIFTSTATE:
+			
+	/*
+	 * Make it possible to react to Shift+Mousebutton.
+	 * Note that 'shift_state' is an undocumented
+	 * kernel-internal variable; programs not closely
+	 * related to the kernel should not use this.
+	 */
+	 		data = shift_state;
+			ret = __put_user(data, p);
+			break;
+		case TIOCL_GETMOUSEREPORTING:
+			data = mouse_reporting();
+			ret = __put_user(data, p);
+			break;
+		case TIOCL_SETVESABLANK:
+			set_vesa_blanking(p);
+			break;
+		case TIOCL_SETKMSGREDIRECT:
+			if (!capable(CAP_SYS_ADMIN)) {
+				ret = -EPERM;
+			} else {
+				if (get_user(data, p+1))
+					ret = -EFAULT;
+				else
+					kmsg_redirect = data;
+			}
+			break;
+		case TIOCL_GETFGCONSOLE:
+			ret = fg_console;
+			break;
+		case TIOCL_SCROLLCONSOLE:
+			if (get_user(lines, (s32 __user *)(p+4))) {
+				ret = -EFAULT;
+			} else {
+				scrollfront(vc_cons[fg_console].d, lines);
+				ret = 0;
+			}
+			break;
+		case TIOCL_BLANKSCREEN:	/* until explicitly unblanked, not only poked */
+			ignore_poke = 1;
+			do_blank_screen(0);
+			break;
+		case TIOCL_BLANKEDSCREEN:
+			ret = console_blanked;
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+	}
+	return ret;
+}
+
+/*
+ * /dev/ttyN handling
+ */
+
+static int con_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+	int	retval;
+
+	retval = do_con_write(tty, buf, count);
+	con_flush_chars(tty);
+
+	return retval;
+}
+
+static void con_put_char(struct tty_struct *tty, unsigned char ch)
+{
+	if (in_interrupt())
+		return;	/* n_r3964 calls put_char() from interrupt context */
+	do_con_write(tty, &ch, 1);
+}
+
+static int con_write_room(struct tty_struct *tty)
+{
+	if (tty->stopped)
+		return 0;
+	return 4096;		/* No limit, really; we're not buffering */
+}
+
+static int con_chars_in_buffer(struct tty_struct *tty)
+{
+	return 0;		/* we're not buffering */
+}
+
+/*
+ * con_throttle and con_unthrottle are only used for
+ * paste_selection(), which has to stuff in a large number of
+ * characters...
+ */
+static void con_throttle(struct tty_struct *tty)
+{
+}
+
+static void con_unthrottle(struct tty_struct *tty)
+{
+	struct vc_data *vc = tty->driver_data;
+
+	wake_up_interruptible(&vc->paste_wait);
+}
+
+/*
+ * Turn the Scroll-Lock LED on when the tty is stopped
+ */
+static void con_stop(struct tty_struct *tty)
+{
+	int console_num;
+	if (!tty)
+		return;
+	console_num = tty->index;
+	if (!vc_cons_allocated(console_num))
+		return;
+	set_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK);
+	set_leds();
+}
+
+/*
+ * Turn the Scroll-Lock LED off when the console is started
+ */
+static void con_start(struct tty_struct *tty)
+{
+	int console_num;
+	if (!tty)
+		return;
+	console_num = tty->index;
+	if (!vc_cons_allocated(console_num))
+		return;
+	clr_vc_kbd_led(kbd_table + console_num, VC_SCROLLOCK);
+	set_leds();
+}
+
+static void con_flush_chars(struct tty_struct *tty)
+{
+	struct vc_data *vc;
+
+	if (in_interrupt())	/* from flush_to_ldisc */
+		return;
+
+	/* if we race with con_close(), vt may be null */
+	acquire_console_sem();
+	vc = tty->driver_data;
+	if (vc)
+		set_cursor(vc);
+	release_console_sem();
+}
+
+/*
+ * Allocate the console screen memory.
+ */
+static int con_open(struct tty_struct *tty, struct file *filp)
+{
+	unsigned int currcons = tty->index;
+	int ret = 0;
+
+	acquire_console_sem();
+	if (tty->count == 1) {
+		ret = vc_allocate(currcons);
+		if (ret == 0) {
+			struct vc_data *vc = vc_cons[currcons].d;
+			tty->driver_data = vc;
+			vc->vc_tty = tty;
+
+			if (!tty->winsize.ws_row && !tty->winsize.ws_col) {
+				tty->winsize.ws_row = vc_cons[currcons].d->vc_rows;
+				tty->winsize.ws_col = vc_cons[currcons].d->vc_cols;
+			}
+			release_console_sem();
+			vcs_make_devfs(tty);
+			return ret;
+		}
+	}
+	release_console_sem();
+	return ret;
+}
+
+/*
+ * We take tty_sem in here to prevent another thread from coming in via init_dev
+ * and taking a ref against the tty while we're in the process of forgetting
+ * about it and cleaning things up.
+ *
+ * This is because vcs_remove_devfs() can sleep and will drop the BKL.
+ */
+static void con_close(struct tty_struct *tty, struct file *filp)
+{
+	down(&tty_sem);
+	acquire_console_sem();
+	if (tty && tty->count == 1) {
+		struct vc_data *vc = tty->driver_data;
+
+		if (vc)
+			vc->vc_tty = NULL;
+		tty->driver_data = NULL;
+		release_console_sem();
+		vcs_remove_devfs(tty);
+		up(&tty_sem);
+		/*
+		 * tty_sem is released, but we still hold BKL, so there is
+		 * still exclusion against init_dev()
+		 */
+		return;
+	}
+	release_console_sem();
+	up(&tty_sem);
+}
+
+static void vc_init(struct vc_data *vc, unsigned int rows,
+		    unsigned int cols, int do_clear)
+{
+	int j, k ;
+
+	vc->vc_cols = cols;
+	vc->vc_rows = rows;
+	vc->vc_size_row = cols << 1;
+	vc->vc_screenbuf_size = vc->vc_rows * vc->vc_size_row;
+
+	set_origin(vc);
+	vc->vc_pos = vc->vc_origin;
+	reset_vc(vc);
+	for (j=k=0; j<16; j++) {
+		vc->vc_palette[k++] = default_red[j] ;
+		vc->vc_palette[k++] = default_grn[j] ;
+		vc->vc_palette[k++] = default_blu[j] ;
+	}
+	vc->vc_def_color       = 0x07;   /* white */
+	vc->vc_ulcolor		= 0x0f;   /* bold white */
+	vc->vc_halfcolor       = 0x08;   /* grey */
+	init_waitqueue_head(&vc->paste_wait);
+	reset_terminal(vc, do_clear);
+}
+
+/*
+ * This routine initializes console interrupts, and does nothing
+ * else. If you want the screen to clear, call tty_write with
+ * the appropriate escape-sequence.
+ */
+
+static int __init con_init(void)
+{
+	const char *display_desc = NULL;
+	struct vc_data *vc;
+	unsigned int currcons = 0;
+
+	acquire_console_sem();
+
+	if (conswitchp)
+		display_desc = conswitchp->con_startup();
+	if (!display_desc) {
+		fg_console = 0;
+		release_console_sem();
+		return 0;
+	}
+
+	init_timer(&console_timer);
+	console_timer.function = blank_screen_t;
+	if (blankinterval) {
+		blank_state = blank_normal_wait;
+		mod_timer(&console_timer, jiffies + blankinterval);
+	}
+
+	/*
+	 * kmalloc is not running yet - we use the bootmem allocator.
+	 */
+	for (currcons = 0; currcons < MIN_NR_CONSOLES; currcons++) {
+		vc_cons[currcons].d = vc = alloc_bootmem(sizeof(struct vc_data));
+		visual_init(vc, currcons, 1);
+		vc->vc_screenbuf = (unsigned short *)alloc_bootmem(vc->vc_screenbuf_size);
+		vc->vc_kmalloced = 0;
+		vc_init(vc, vc->vc_rows, vc->vc_cols,
+			currcons || !vc->vc_sw->con_save_screen);
+	}
+	currcons = fg_console = 0;
+	master_display_fg = vc = vc_cons[currcons].d;
+	set_origin(vc);
+	save_screen(vc);
+	gotoxy(vc, vc->vc_x, vc->vc_y);
+	csi_J(vc, 0);
+	update_screen(vc);
+	printk("Console: %s %s %dx%d",
+		vc->vc_can_do_color ? "colour" : "mono",
+		display_desc, vc->vc_cols, vc->vc_rows);
+	printable = 1;
+	printk("\n");
+
+	release_console_sem();
+
+#ifdef CONFIG_VT_CONSOLE
+	register_console(&vt_console_driver);
+#endif
+	return 0;
+}
+console_initcall(con_init);
+
+static struct tty_operations con_ops = {
+	.open = con_open,
+	.close = con_close,
+	.write = con_write,
+	.write_room = con_write_room,
+	.put_char = con_put_char,
+	.flush_chars = con_flush_chars,
+	.chars_in_buffer = con_chars_in_buffer,
+	.ioctl = vt_ioctl,
+	.stop = con_stop,
+	.start = con_start,
+	.throttle = con_throttle,
+	.unthrottle = con_unthrottle,
+};
+
+int __init vty_init(void)
+{
+	vcs_init();
+
+	console_driver = alloc_tty_driver(MAX_NR_CONSOLES);
+	if (!console_driver)
+		panic("Couldn't allocate console driver\n");
+	console_driver->owner = THIS_MODULE;
+	console_driver->devfs_name = "vc/";
+	console_driver->name = "tty";
+	console_driver->name_base = 1;
+	console_driver->major = TTY_MAJOR;
+	console_driver->minor_start = 1;
+	console_driver->type = TTY_DRIVER_TYPE_CONSOLE;
+	console_driver->init_termios = tty_std_termios;
+	console_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_RESET_TERMIOS;
+	tty_set_operations(console_driver, &con_ops);
+	if (tty_register_driver(console_driver))
+		panic("Couldn't register console driver\n");
+
+	kbd_init();
+	console_map_init();
+#ifdef CONFIG_PROM_CONSOLE
+	prom_con_init();
+#endif
+#ifdef CONFIG_MDA_CONSOLE
+	mda_console_init();
+#endif
+	return 0;
+}
+
+#ifndef VT_SINGLE_DRIVER
+
+/*
+ *	If we support more console drivers, this function is used
+ *	when a driver wants to take over some existing consoles
+ *	and become default driver for newly opened ones.
+ */
+
+int take_over_console(const struct consw *csw, int first, int last, int deflt)
+{
+	int i, j = -1;
+	const char *desc;
+	struct module *owner;
+
+	owner = csw->owner;
+	if (!try_module_get(owner))
+		return -ENODEV;
+
+	acquire_console_sem();
+
+	desc = csw->con_startup();
+	if (!desc) {
+		release_console_sem();
+		module_put(owner);
+		return -ENODEV;
+	}
+	if (deflt) {
+		if (conswitchp)
+			module_put(conswitchp->owner);
+		__module_get(owner);
+		conswitchp = csw;
+	}
+
+	for (i = first; i <= last; i++) {
+		int old_was_color;
+		struct vc_data *vc = vc_cons[i].d;
+
+		if (con_driver_map[i])
+			module_put(con_driver_map[i]->owner);
+		__module_get(owner);
+		con_driver_map[i] = csw;
+
+		if (!vc || !vc->vc_sw)
+			continue;
+
+		j = i;
+		if (CON_IS_VISIBLE(vc))
+			save_screen(vc);
+		old_was_color = vc->vc_can_do_color;
+		vc->vc_sw->con_deinit(vc);
+		vc->vc_origin = (unsigned long)vc->vc_screenbuf;
+		vc->vc_visible_origin = vc->vc_origin;
+		vc->vc_scr_end = vc->vc_origin + vc->vc_screenbuf_size;
+		vc->vc_pos = vc->vc_origin + vc->vc_size_row * vc->vc_y + 2 * vc->vc_x;
+		visual_init(vc, i, 0);
+		update_attr(vc);
+
+		/* If the console changed between mono <-> color, then
+		 * the attributes in the screenbuf will be wrong.  The
+		 * following resets all attributes to something sane.
+		 */
+		if (old_was_color != vc->vc_can_do_color)
+			clear_buffer_attributes(vc);
+
+		if (CON_IS_VISIBLE(vc))
+			update_screen(vc);
+	}
+	printk("Console: switching ");
+	if (!deflt)
+		printk("consoles %d-%d ", first+1, last+1);
+	if (j >= 0)
+		printk("to %s %s %dx%d\n",
+		       vc_cons[j].d->vc_can_do_color ? "colour" : "mono",
+		       desc, vc_cons[j].d->vc_cols, vc_cons[j].d->vc_rows);
+	else
+		printk("to %s\n", desc);
+
+	release_console_sem();
+
+	module_put(owner);
+	return 0;
+}
+
+void give_up_console(const struct consw *csw)
+{
+	int i;
+
+	for(i = 0; i < MAX_NR_CONSOLES; i++)
+		if (con_driver_map[i] == csw) {
+			module_put(csw->owner);
+			con_driver_map[i] = NULL;
+		}
+}
+
+#endif
+
+/*
+ *	Screen blanking
+ */
+
+static void set_vesa_blanking(char __user *p)
+{
+    unsigned int mode;
+    get_user(mode, p + 1);
+    vesa_blank_mode = (mode < 4) ? mode : 0;
+}
+
+/*
+ * This is called by a timer handler
+ */
+static void vesa_powerdown(void)
+{
+    struct vc_data *c = vc_cons[fg_console].d;
+    /*
+     *  Power down if currently suspended (1 or 2),
+     *  suspend if currently blanked (0),
+     *  else do nothing (i.e. already powered down (3)).
+     *  Called only if powerdown features are allowed.
+     */
+    switch (vesa_blank_mode) {
+    case VESA_NO_BLANKING:
+	    c->vc_sw->con_blank(c, VESA_VSYNC_SUSPEND+1, 0);
+	    break;
+    case VESA_VSYNC_SUSPEND:
+    case VESA_HSYNC_SUSPEND:
+	    c->vc_sw->con_blank(c, VESA_POWERDOWN+1, 0);
+	    break;
+    }
+}
+
+void do_blank_screen(int entering_gfx)
+{
+	struct vc_data *vc = vc_cons[fg_console].d;
+	int i;
+
+	WARN_CONSOLE_UNLOCKED();
+
+	if (console_blanked) {
+		if (blank_state == blank_vesa_wait) {
+			blank_state = blank_off;
+			vesa_powerdown();
+
+		}
+		return;
+	}
+	if (blank_state != blank_normal_wait)
+		return;
+	blank_state = blank_off;
+
+	/* entering graphics mode? */
+	if (entering_gfx) {
+		hide_cursor(vc);
+		save_screen(vc);
+		vc->vc_sw->con_blank(vc, -1, 1);
+		console_blanked = fg_console + 1;
+		set_origin(vc);
+		return;
+	}
+
+	/* don't blank graphics */
+	if (vc->vc_mode != KD_TEXT) {
+		console_blanked = fg_console + 1;
+		return;
+	}
+
+	hide_cursor(vc);
+	del_timer_sync(&console_timer);
+	blank_timer_expired = 0;
+
+	save_screen(vc);
+	/* In case we need to reset origin, blanking hook returns 1 */
+	i = vc->vc_sw->con_blank(vc, 1, 0);
+	console_blanked = fg_console + 1;
+	if (i)
+		set_origin(vc);
+
+	if (console_blank_hook && console_blank_hook(1))
+		return;
+
+	if (vesa_off_interval) {
+		blank_state = blank_vesa_wait,
+		mod_timer(&console_timer, jiffies + vesa_off_interval);
+	}
+
+    	if (vesa_blank_mode)
+		vc->vc_sw->con_blank(vc, vesa_blank_mode + 1, 0);
+}
+EXPORT_SYMBOL(do_blank_screen);
+
+/*
+ * Called by timer as well as from vt_console_driver
+ */
+void do_unblank_screen(int leaving_gfx)
+{
+	struct vc_data *vc;
+
+	/* This should now always be called from a "sane" (read: can schedule)
+	 * context for the sake of the low level drivers, except in the special
+	 * case of oops_in_progress
+	 */
+	if (!oops_in_progress)
+		might_sleep();
+
+	WARN_CONSOLE_UNLOCKED();
+
+	ignore_poke = 0;
+	if (!console_blanked)
+		return;
+	if (!vc_cons_allocated(fg_console)) {
+		/* impossible */
+		printk("unblank_screen: tty %d not allocated ??\n", fg_console+1);
+		return;
+	}
+	vc = vc_cons[fg_console].d;
+	if (vc->vc_mode != KD_TEXT)
+		return; /* but leave console_blanked != 0 */
+
+	if (blankinterval) {
+		mod_timer(&console_timer, jiffies + blankinterval);
+		blank_state = blank_normal_wait;
+	}
+
+	console_blanked = 0;
+	if (vc->vc_sw->con_blank(vc, 0, leaving_gfx))
+		/* Low-level driver cannot restore -> do it ourselves */
+		update_screen(vc);
+	if (console_blank_hook)
+		console_blank_hook(0);
+	set_palette(vc);
+	set_cursor(vc);
+}
+EXPORT_SYMBOL(do_unblank_screen);
+
+/*
+ * This is called by the outside world to cause a forced unblank, mostly for
+ * oopses. Currently, I just call do_unblank_screen(0), but we could eventually
+ * call it with 1 as an argument and so force a mode restore... that may kill
+ * X or at least garbage the screen but would also make the Oops visible...
+ */
+void unblank_screen(void)
+{
+	do_unblank_screen(0);
+}
+
+/*
+ * We defer the timer blanking to work queue so it can take the console semaphore
+ * (console operations can still happen at irq time, but only from printk which
+ * has the console semaphore. Not perfect yet, but better than no locking
+ */
+static void blank_screen_t(unsigned long dummy)
+{
+	blank_timer_expired = 1;
+	schedule_work(&console_work);
+}
+
+void poke_blanked_console(void)
+{
+	WARN_CONSOLE_UNLOCKED();
+
+	/* Add this so we quickly catch whoever might call us in a non
+	 * safe context. Nowadays, unblank_screen() isn't to be called in
+	 * atomic contexts and is allowed to schedule (with the special case
+	 * of oops_in_progress, but that isn't of any concern for this
+	 * function. --BenH.
+	 */
+	might_sleep();
+
+	/* This isn't perfectly race free, but a race here would be mostly harmless,
+	 * at worse, we'll do a spurrious blank and it's unlikely
+	 */
+	del_timer(&console_timer);
+	blank_timer_expired = 0;
+
+	if (ignore_poke || !vc_cons[fg_console].d || vc_cons[fg_console].d->vc_mode == KD_GRAPHICS)
+		return;
+	if (console_blanked)
+		unblank_screen();
+	else if (blankinterval) {
+		mod_timer(&console_timer, jiffies + blankinterval);
+		blank_state = blank_normal_wait;
+	}
+}
+
+/*
+ *	Palettes
+ */
+
+static void set_palette(struct vc_data *vc)
+{
+	WARN_CONSOLE_UNLOCKED();
+
+	if (vc->vc_mode != KD_GRAPHICS)
+		vc->vc_sw->con_set_palette(vc, color_table);
+}
+
+static int set_get_cmap(unsigned char __user *arg, int set)
+{
+    int i, j, k;
+
+    WARN_CONSOLE_UNLOCKED();
+
+    for (i = 0; i < 16; i++)
+	if (set) {
+	    get_user(default_red[i], arg++);
+	    get_user(default_grn[i], arg++);
+	    get_user(default_blu[i], arg++);
+	} else {
+	    put_user(default_red[i], arg++);
+	    put_user(default_grn[i], arg++);
+	    put_user(default_blu[i], arg++);
+	}
+    if (set) {
+	for (i = 0; i < MAX_NR_CONSOLES; i++)
+	    if (vc_cons_allocated(i)) {
+		for (j = k = 0; j < 16; j++) {
+		    vc_cons[i].d->vc_palette[k++] = default_red[j];
+		    vc_cons[i].d->vc_palette[k++] = default_grn[j];
+		    vc_cons[i].d->vc_palette[k++] = default_blu[j];
+		}
+		set_palette(vc_cons[i].d);
+	    }
+    }
+    return 0;
+}
+
+/*
+ * Load palette into the DAC registers. arg points to a colour
+ * map, 3 bytes per colour, 16 colours, range from 0 to 255.
+ */
+
+int con_set_cmap(unsigned char __user *arg)
+{
+	int rc;
+
+	acquire_console_sem();
+	rc = set_get_cmap (arg,1);
+	release_console_sem();
+
+	return rc;
+}
+
+int con_get_cmap(unsigned char __user *arg)
+{
+	int rc;
+
+	acquire_console_sem();
+	rc = set_get_cmap (arg,0);
+	release_console_sem();
+
+	return rc;
+}
+
+void reset_palette(struct vc_data *vc)
+{
+	int j, k;
+	for (j=k=0; j<16; j++) {
+		vc->vc_palette[k++] = default_red[j];
+		vc->vc_palette[k++] = default_grn[j];
+		vc->vc_palette[k++] = default_blu[j];
+	}
+	set_palette(vc);
+}
+
+/*
+ *  Font switching
+ *
+ *  Currently we only support fonts up to 32 pixels wide, at a maximum height
+ *  of 32 pixels. Userspace fontdata is stored with 32 bytes (shorts/ints, 
+ *  depending on width) reserved for each character which is kinda wasty, but 
+ *  this is done in order to maintain compatibility with the EGA/VGA fonts. It 
+ *  is upto the actual low-level console-driver convert data into its favorite
+ *  format (maybe we should add a `fontoffset' field to the `display'
+ *  structure so we won't have to convert the fontdata all the time.
+ *  /Jes
+ */
+
+#define max_font_size 65536
+
+static int con_font_get(struct vc_data *vc, struct console_font_op *op)
+{
+	struct console_font font;
+	int rc = -EINVAL;
+	int c;
+
+	if (vc->vc_mode != KD_TEXT)
+		return -EINVAL;
+
+	if (op->data) {
+		font.data = kmalloc(max_font_size, GFP_KERNEL);
+		if (!font.data)
+			return -ENOMEM;
+	} else
+		font.data = NULL;
+
+	acquire_console_sem();
+	if (vc->vc_sw->con_font_get)
+		rc = vc->vc_sw->con_font_get(vc, &font);
+	else
+		rc = -ENOSYS;
+	release_console_sem();
+
+	if (rc)
+		goto out;
+
+	c = (font.width+7)/8 * 32 * font.charcount;
+	
+	if (op->data && font.charcount > op->charcount)
+		rc = -ENOSPC;
+	if (!(op->flags & KD_FONT_FLAG_OLD)) {
+		if (font.width > op->width || font.height > op->height) 
+			rc = -ENOSPC;
+	} else {
+		if (font.width != 8)
+			rc = -EIO;
+		else if ((op->height && font.height > op->height) ||
+			 font.height > 32)
+			rc = -ENOSPC;
+	}
+	if (rc)
+		goto out;
+
+	op->height = font.height;
+	op->width = font.width;
+	op->charcount = font.charcount;
+
+	if (op->data && copy_to_user(op->data, font.data, c))
+		rc = -EFAULT;
+
+out:
+	kfree(font.data);
+	return rc;
+}
+
+static int con_font_set(struct vc_data *vc, struct console_font_op *op)
+{
+	struct console_font font;
+	int rc = -EINVAL;
+	int size;
+
+	if (vc->vc_mode != KD_TEXT)
+		return -EINVAL;
+	if (!op->data)
+		return -EINVAL;
+	if (op->charcount > 512)
+		return -EINVAL;
+	if (!op->height) {		/* Need to guess font height [compat] */
+		int h, i;
+		u8 __user *charmap = op->data;
+		u8 tmp;
+		
+		/* If from KDFONTOP ioctl, don't allow things which can be done in userland,
+		   so that we can get rid of this soon */
+		if (!(op->flags & KD_FONT_FLAG_OLD))
+			return -EINVAL;
+		for (h = 32; h > 0; h--)
+			for (i = 0; i < op->charcount; i++) {
+				if (get_user(tmp, &charmap[32*i+h-1]))
+					return -EFAULT;
+				if (tmp)
+					goto nonzero;
+			}
+		return -EINVAL;
+	nonzero:
+		op->height = h;
+	}
+	if (op->width <= 0 || op->width > 32 || op->height > 32)
+		return -EINVAL;
+	size = (op->width+7)/8 * 32 * op->charcount;
+	if (size > max_font_size)
+		return -ENOSPC;
+	font.charcount = op->charcount;
+	font.height = op->height;
+	font.width = op->width;
+	font.data = kmalloc(size, GFP_KERNEL);
+	if (!font.data)
+		return -ENOMEM;
+	if (copy_from_user(font.data, op->data, size)) {
+		kfree(font.data);
+		return -EFAULT;
+	}
+	acquire_console_sem();
+	if (vc->vc_sw->con_font_set)
+		rc = vc->vc_sw->con_font_set(vc, &font, op->flags);
+	else
+		rc = -ENOSYS;
+	release_console_sem();
+	kfree(font.data);
+	return rc;
+}
+
+static int con_font_default(struct vc_data *vc, struct console_font_op *op)
+{
+	struct console_font font = {.width = op->width, .height = op->height};
+	char name[MAX_FONT_NAME];
+	char *s = name;
+	int rc;
+
+	if (vc->vc_mode != KD_TEXT)
+		return -EINVAL;
+
+	if (!op->data)
+		s = NULL;
+	else if (strncpy_from_user(name, op->data, MAX_FONT_NAME - 1) < 0)
+		return -EFAULT;
+	else
+		name[MAX_FONT_NAME - 1] = 0;
+
+	acquire_console_sem();
+	if (vc->vc_sw->con_font_default)
+		rc = vc->vc_sw->con_font_default(vc, &font, s);
+	else
+		rc = -ENOSYS;
+	release_console_sem();
+	if (!rc) {
+		op->width = font.width;
+		op->height = font.height;
+	}
+	return rc;
+}
+
+static int con_font_copy(struct vc_data *vc, struct console_font_op *op)
+{
+	int con = op->height;
+	int rc;
+
+	if (vc->vc_mode != KD_TEXT)
+		return -EINVAL;
+
+	acquire_console_sem();
+	if (!vc->vc_sw->con_font_copy)
+		rc = -ENOSYS;
+	else if (con < 0 || !vc_cons_allocated(con))
+		rc = -ENOTTY;
+	else if (con == vc->vc_num)	/* nothing to do */
+		rc = 0;
+	else
+		rc = vc->vc_sw->con_font_copy(vc, con);
+	release_console_sem();
+	return rc;
+}
+
+int con_font_op(struct vc_data *vc, struct console_font_op *op)
+{
+	switch (op->op) {
+	case KD_FONT_OP_SET:
+		return con_font_set(vc, op);
+	case KD_FONT_OP_GET:
+		return con_font_get(vc, op);
+	case KD_FONT_OP_SET_DEFAULT:
+		return con_font_default(vc, op);
+	case KD_FONT_OP_COPY:
+		return con_font_copy(vc, op);
+	}
+	return -ENOSYS;
+}
+
+/*
+ *	Interface exported to selection and vcs.
+ */
+
+/* used by selection */
+u16 screen_glyph(struct vc_data *vc, int offset)
+{
+	u16 w = scr_readw(screenpos(vc, offset, 1));
+	u16 c = w & 0xff;
+
+	if (w & vc->vc_hi_font_mask)
+		c |= 0x100;
+	return c;
+}
+
+/* used by vcs - note the word offset */
+unsigned short *screen_pos(struct vc_data *vc, int w_offset, int viewed)
+{
+	return screenpos(vc, 2 * w_offset, viewed);
+}
+
+void getconsxy(struct vc_data *vc, unsigned char *p)
+{
+	p[0] = vc->vc_x;
+	p[1] = vc->vc_y;
+}
+
+void putconsxy(struct vc_data *vc, unsigned char *p)
+{
+	gotoxy(vc, p[0], p[1]);
+	set_cursor(vc);
+}
+
+u16 vcs_scr_readw(struct vc_data *vc, const u16 *org)
+{
+	if ((unsigned long)org == vc->vc_pos && softcursor_original != -1)
+		return softcursor_original;
+	return scr_readw(org);
+}
+
+void vcs_scr_writew(struct vc_data *vc, u16 val, u16 *org)
+{
+	scr_writew(val, org);
+	if ((unsigned long)org == vc->vc_pos) {
+		softcursor_original = -1;
+		add_softcursor(vc);
+	}
+}
+
+/*
+ *	Visible symbols for modules
+ */
+
+EXPORT_SYMBOL(color_table);
+EXPORT_SYMBOL(default_red);
+EXPORT_SYMBOL(default_grn);
+EXPORT_SYMBOL(default_blu);
+EXPORT_SYMBOL(update_region);
+EXPORT_SYMBOL(redraw_screen);
+EXPORT_SYMBOL(vc_resize);
+EXPORT_SYMBOL(fg_console);
+EXPORT_SYMBOL(console_blank_hook);
+EXPORT_SYMBOL(console_blanked);
+EXPORT_SYMBOL(vc_cons);
+#ifndef VT_SINGLE_DRIVER
+EXPORT_SYMBOL(take_over_console);
+EXPORT_SYMBOL(give_up_console);
+#endif
diff --git a/drivers/char/vt_ioctl.c b/drivers/char/vt_ioctl.c
new file mode 100644
index 0000000..5d386f4
--- /dev/null
+++ b/drivers/char/vt_ioctl.c
@@ -0,0 +1,1201 @@
+/*
+ *  linux/drivers/char/vt_ioctl.c
+ *
+ *  Copyright (C) 1992 obz under the linux copyright
+ *
+ *  Dynamic diacritical handling - aeb@cwi.nl - Dec 1993
+ *  Dynamic keymap and string allocation - aeb@cwi.nl - May 1994
+ *  Restrict VT switching via ioctl() - grif@cs.ucr.edu - Dec 1995
+ *  Some code moved for less code duplication - Andi Kleen - Mar 1997
+ *  Check put/get_user, cleanups - acme@conectiva.com.br - Jun 2001
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/tty.h>
+#include <linux/timer.h>
+#include <linux/kernel.h>
+#include <linux/kd.h>
+#include <linux/vt.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/major.h>
+#include <linux/fs.h>
+#include <linux/console.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <linux/kbd_kern.h>
+#include <linux/vt_kern.h>
+#include <linux/kbd_diacr.h>
+#include <linux/selection.h>
+
+static char vt_dont_switch;
+extern struct tty_driver *console_driver;
+
+#define VT_IS_IN_USE(i)	(console_driver->ttys[i] && console_driver->ttys[i]->count)
+#define VT_BUSY(i)	(VT_IS_IN_USE(i) || i == fg_console || vc_cons[i].d == sel_cons)
+
+/*
+ * Console (vt and kd) routines, as defined by USL SVR4 manual, and by
+ * experimentation and study of X386 SYSV handling.
+ *
+ * One point of difference: SYSV vt's are /dev/vtX, which X >= 0, and
+ * /dev/console is a separate ttyp. Under Linux, /dev/tty0 is /dev/console,
+ * and the vc start at /dev/ttyX, X >= 1. We maintain that here, so we will
+ * always treat our set of vt as numbered 1..MAX_NR_CONSOLES (corresponding to
+ * ttys 0..MAX_NR_CONSOLES-1). Explicitly naming VT 0 is illegal, but using
+ * /dev/tty0 (fg_console) as a target is legal, since an implicit aliasing
+ * to the current console is done by the main ioctl code.
+ */
+
+#ifdef CONFIG_X86
+#include <linux/syscalls.h>
+#endif
+
+static void complete_change_console(struct vc_data *vc);
+
+/*
+ * these are the valid i/o ports we're allowed to change. they map all the
+ * video ports
+ */
+#define GPFIRST 0x3b4
+#define GPLAST 0x3df
+#define GPNUM (GPLAST - GPFIRST + 1)
+
+#define i (tmp.kb_index)
+#define s (tmp.kb_table)
+#define v (tmp.kb_value)
+static inline int
+do_kdsk_ioctl(int cmd, struct kbentry __user *user_kbe, int perm, struct kbd_struct *kbd)
+{
+	struct kbentry tmp;
+	ushort *key_map, val, ov;
+
+	if (copy_from_user(&tmp, user_kbe, sizeof(struct kbentry)))
+		return -EFAULT;
+
+	switch (cmd) {
+	case KDGKBENT:
+		key_map = key_maps[s];
+		if (key_map) {
+		    val = U(key_map[i]);
+		    if (kbd->kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES)
+			val = K_HOLE;
+		} else
+		    val = (i ? K_HOLE : K_NOSUCHMAP);
+		return put_user(val, &user_kbe->kb_value);
+	case KDSKBENT:
+		if (!perm)
+			return -EPERM;
+		if (!i && v == K_NOSUCHMAP) {
+			/* disallocate map */
+			key_map = key_maps[s];
+			if (s && key_map) {
+			    key_maps[s] = NULL;
+			    if (key_map[0] == U(K_ALLOCATED)) {
+					kfree(key_map);
+					keymap_count--;
+			    }
+			}
+			break;
+		}
+
+		if (KTYP(v) < NR_TYPES) {
+		    if (KVAL(v) > max_vals[KTYP(v)])
+				return -EINVAL;
+		} else
+		    if (kbd->kbdmode != VC_UNICODE)
+				return -EINVAL;
+
+		/* ++Geert: non-PC keyboards may generate keycode zero */
+#if !defined(__mc68000__) && !defined(__powerpc__)
+		/* assignment to entry 0 only tests validity of args */
+		if (!i)
+			break;
+#endif
+
+		if (!(key_map = key_maps[s])) {
+			int j;
+
+			if (keymap_count >= MAX_NR_OF_USER_KEYMAPS &&
+			    !capable(CAP_SYS_RESOURCE))
+				return -EPERM;
+
+			key_map = (ushort *) kmalloc(sizeof(plain_map),
+						     GFP_KERNEL);
+			if (!key_map)
+				return -ENOMEM;
+			key_maps[s] = key_map;
+			key_map[0] = U(K_ALLOCATED);
+			for (j = 1; j < NR_KEYS; j++)
+				key_map[j] = U(K_HOLE);
+			keymap_count++;
+		}
+		ov = U(key_map[i]);
+		if (v == ov)
+			break;	/* nothing to do */
+		/*
+		 * Attention Key.
+		 */
+		if (((ov == K_SAK) || (v == K_SAK)) && !capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		key_map[i] = U(v);
+		if (!s && (KTYP(ov) == KT_SHIFT || KTYP(v) == KT_SHIFT))
+			compute_shiftstate();
+		break;
+	}
+	return 0;
+}
+#undef i
+#undef s
+#undef v
+
+static inline int 
+do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, int perm)
+{
+	struct kbkeycode tmp;
+	int kc = 0;
+
+	if (copy_from_user(&tmp, user_kbkc, sizeof(struct kbkeycode)))
+		return -EFAULT;
+	switch (cmd) {
+	case KDGETKEYCODE:
+		kc = getkeycode(tmp.scancode);
+		if (kc >= 0)
+			kc = put_user(kc, &user_kbkc->keycode);
+		break;
+	case KDSETKEYCODE:
+		if (!perm)
+			return -EPERM;
+		kc = setkeycode(tmp.scancode, tmp.keycode);
+		break;
+	}
+	return kc;
+}
+
+static inline int
+do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm)
+{
+	struct kbsentry *kbs;
+	char *p;
+	u_char *q;
+	u_char __user *up;
+	int sz;
+	int delta;
+	char *first_free, *fj, *fnw;
+	int i, j, k;
+	int ret;
+
+	kbs = kmalloc(sizeof(*kbs), GFP_KERNEL);
+	if (!kbs) {
+		ret = -ENOMEM;
+		goto reterr;
+	}
+
+	/* we mostly copy too much here (512bytes), but who cares ;) */
+	if (copy_from_user(kbs, user_kdgkb, sizeof(struct kbsentry))) {
+		ret = -EFAULT;
+		goto reterr;
+	}
+	kbs->kb_string[sizeof(kbs->kb_string)-1] = '\0';
+	i = kbs->kb_func;
+
+	switch (cmd) {
+	case KDGKBSENT:
+		sz = sizeof(kbs->kb_string) - 1; /* sz should have been
+						  a struct member */
+		up = user_kdgkb->kb_string;
+		p = func_table[i];
+		if(p)
+			for ( ; *p && sz; p++, sz--)
+				if (put_user(*p, up++)) {
+					ret = -EFAULT;
+					goto reterr;
+				}
+		if (put_user('\0', up)) {
+			ret = -EFAULT;
+			goto reterr;
+		}
+		kfree(kbs);
+		return ((p && *p) ? -EOVERFLOW : 0);
+	case KDSKBSENT:
+		if (!perm) {
+			ret = -EPERM;
+			goto reterr;
+		}
+
+		q = func_table[i];
+		first_free = funcbufptr + (funcbufsize - funcbufleft);
+		for (j = i+1; j < MAX_NR_FUNC && !func_table[j]; j++) 
+			;
+		if (j < MAX_NR_FUNC)
+			fj = func_table[j];
+		else
+			fj = first_free;
+
+		delta = (q ? -strlen(q) : 1) + strlen(kbs->kb_string);
+		if (delta <= funcbufleft) { 	/* it fits in current buf */
+		    if (j < MAX_NR_FUNC) {
+			memmove(fj + delta, fj, first_free - fj);
+			for (k = j; k < MAX_NR_FUNC; k++)
+			    if (func_table[k])
+				func_table[k] += delta;
+		    }
+		    if (!q)
+		      func_table[i] = fj;
+		    funcbufleft -= delta;
+		} else {			/* allocate a larger buffer */
+		    sz = 256;
+		    while (sz < funcbufsize - funcbufleft + delta)
+		      sz <<= 1;
+		    fnw = (char *) kmalloc(sz, GFP_KERNEL);
+		    if(!fnw) {
+		      ret = -ENOMEM;
+		      goto reterr;
+		    }
+
+		    if (!q)
+		      func_table[i] = fj;
+		    if (fj > funcbufptr)
+			memmove(fnw, funcbufptr, fj - funcbufptr);
+		    for (k = 0; k < j; k++)
+		      if (func_table[k])
+			func_table[k] = fnw + (func_table[k] - funcbufptr);
+
+		    if (first_free > fj) {
+			memmove(fnw + (fj - funcbufptr) + delta, fj, first_free - fj);
+			for (k = j; k < MAX_NR_FUNC; k++)
+			  if (func_table[k])
+			    func_table[k] = fnw + (func_table[k] - funcbufptr) + delta;
+		    }
+		    if (funcbufptr != func_buf)
+		      kfree(funcbufptr);
+		    funcbufptr = fnw;
+		    funcbufleft = funcbufleft - delta + sz - funcbufsize;
+		    funcbufsize = sz;
+		}
+		strcpy(func_table[i], kbs->kb_string);
+		break;
+	}
+	ret = 0;
+reterr:
+	kfree(kbs);
+	return ret;
+}
+
+static inline int 
+do_fontx_ioctl(int cmd, struct consolefontdesc __user *user_cfd, int perm, struct console_font_op *op)
+{
+	struct consolefontdesc cfdarg;
+	int i;
+
+	if (copy_from_user(&cfdarg, user_cfd, sizeof(struct consolefontdesc))) 
+		return -EFAULT;
+ 	
+	switch (cmd) {
+	case PIO_FONTX:
+		if (!perm)
+			return -EPERM;
+		op->op = KD_FONT_OP_SET;
+		op->flags = KD_FONT_FLAG_OLD;
+		op->width = 8;
+		op->height = cfdarg.charheight;
+		op->charcount = cfdarg.charcount;
+		op->data = cfdarg.chardata;
+		return con_font_op(vc_cons[fg_console].d, op);
+	case GIO_FONTX: {
+		op->op = KD_FONT_OP_GET;
+		op->flags = KD_FONT_FLAG_OLD;
+		op->width = 8;
+		op->height = cfdarg.charheight;
+		op->charcount = cfdarg.charcount;
+		op->data = cfdarg.chardata;
+		i = con_font_op(vc_cons[fg_console].d, op);
+		if (i)
+			return i;
+		cfdarg.charheight = op->height;
+		cfdarg.charcount = op->charcount;
+		if (copy_to_user(user_cfd, &cfdarg, sizeof(struct consolefontdesc)))
+			return -EFAULT;
+		return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static inline int 
+do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud, int perm, struct vc_data *vc)
+{
+	struct unimapdesc tmp;
+
+	if (copy_from_user(&tmp, user_ud, sizeof tmp))
+		return -EFAULT;
+	if (tmp.entries)
+		if (!access_ok(VERIFY_WRITE, tmp.entries,
+				tmp.entry_ct*sizeof(struct unipair)))
+			return -EFAULT;
+	switch (cmd) {
+	case PIO_UNIMAP:
+		if (!perm)
+			return -EPERM;
+		return con_set_unimap(vc, tmp.entry_ct, tmp.entries);
+	case GIO_UNIMAP:
+		if (!perm && fg_console != vc->vc_num)
+			return -EPERM;
+		return con_get_unimap(vc, tmp.entry_ct, &(user_ud->entry_ct), tmp.entries);
+	}
+	return 0;
+}
+
+/*
+ * We handle the console-specific ioctl's here.  We allow the
+ * capability to modify any console, not just the fg_console. 
+ */
+int vt_ioctl(struct tty_struct *tty, struct file * file,
+	     unsigned int cmd, unsigned long arg)
+{
+	struct vc_data *vc = (struct vc_data *)tty->driver_data;
+	struct console_font_op op;	/* used in multiple places here */
+	struct kbd_struct * kbd;
+	unsigned int console;
+	unsigned char ucval;
+	void __user *up = (void __user *)arg;
+	int i, perm;
+	
+	console = vc->vc_num;
+
+	if (!vc_cons_allocated(console)) 	/* impossible? */
+		return -ENOIOCTLCMD;
+
+	/*
+	 * To have permissions to do most of the vt ioctls, we either have
+	 * to be the owner of the tty, or have CAP_SYS_TTY_CONFIG.
+	 */
+	perm = 0;
+	if (current->signal->tty == tty || capable(CAP_SYS_TTY_CONFIG))
+		perm = 1;
+ 
+	kbd = kbd_table + console;
+	switch (cmd) {
+	case KIOCSOUND:
+		if (!perm)
+			return -EPERM;
+		if (arg)
+			arg = 1193182 / arg;
+		kd_mksound(arg, 0);
+		return 0;
+
+	case KDMKTONE:
+		if (!perm)
+			return -EPERM;
+	{
+		unsigned int ticks, count;
+		
+		/*
+		 * Generate the tone for the appropriate number of ticks.
+		 * If the time is zero, turn off sound ourselves.
+		 */
+		ticks = HZ * ((arg >> 16) & 0xffff) / 1000;
+		count = ticks ? (arg & 0xffff) : 0;
+		if (count)
+			count = 1193182 / count;
+		kd_mksound(count, ticks);
+		return 0;
+	}
+
+	case KDGKBTYPE:
+		/*
+		 * this is naive.
+		 */
+		ucval = KB_101;
+		goto setchar;
+
+		/*
+		 * These cannot be implemented on any machine that implements
+		 * ioperm() in user level (such as Alpha PCs) or not at all.
+		 *
+		 * XXX: you should never use these, just call ioperm directly..
+		 */
+#ifdef CONFIG_X86
+	case KDADDIO:
+	case KDDELIO:
+		/*
+		 * KDADDIO and KDDELIO may be able to add ports beyond what
+		 * we reject here, but to be safe...
+		 */
+		if (arg < GPFIRST || arg > GPLAST)
+			return -EINVAL;
+		return sys_ioperm(arg, 1, (cmd == KDADDIO)) ? -ENXIO : 0;
+
+	case KDENABIO:
+	case KDDISABIO:
+		return sys_ioperm(GPFIRST, GPNUM,
+				  (cmd == KDENABIO)) ? -ENXIO : 0;
+#endif
+
+	/* Linux m68k/i386 interface for setting the keyboard delay/repeat rate */
+		
+	case KDKBDREP:
+	{
+		struct kbd_repeat kbrep;
+		int err;
+		
+		if (!capable(CAP_SYS_TTY_CONFIG))
+			return -EPERM;
+
+		if (copy_from_user(&kbrep, up, sizeof(struct kbd_repeat)))
+			return -EFAULT;
+		err = kbd_rate(&kbrep);
+		if (err)
+			return err;
+		if (copy_to_user(up, &kbrep, sizeof(struct kbd_repeat)))
+			return -EFAULT;
+		return 0;
+	}
+
+	case KDSETMODE:
+		/*
+		 * currently, setting the mode from KD_TEXT to KD_GRAPHICS
+		 * doesn't do a whole lot. i'm not sure if it should do any
+		 * restoration of modes or what...
+		 *
+		 * XXX It should at least call into the driver, fbdev's definitely
+		 * need to restore their engine state. --BenH
+		 */
+		if (!perm)
+			return -EPERM;
+		switch (arg) {
+		case KD_GRAPHICS:
+			break;
+		case KD_TEXT0:
+		case KD_TEXT1:
+			arg = KD_TEXT;
+		case KD_TEXT:
+			break;
+		default:
+			return -EINVAL;
+		}
+		if (vc->vc_mode == (unsigned char) arg)
+			return 0;
+		vc->vc_mode = (unsigned char) arg;
+		if (console != fg_console)
+			return 0;
+		/*
+		 * explicitly blank/unblank the screen if switching modes
+		 */
+		acquire_console_sem();
+		if (arg == KD_TEXT)
+			do_unblank_screen(1);
+		else
+			do_blank_screen(1);
+		release_console_sem();
+		return 0;
+
+	case KDGETMODE:
+		ucval = vc->vc_mode;
+		goto setint;
+
+	case KDMAPDISP:
+	case KDUNMAPDISP:
+		/*
+		 * these work like a combination of mmap and KDENABIO.
+		 * this could be easily finished.
+		 */
+		return -EINVAL;
+
+	case KDSKBMODE:
+		if (!perm)
+			return -EPERM;
+		switch(arg) {
+		  case K_RAW:
+			kbd->kbdmode = VC_RAW;
+			break;
+		  case K_MEDIUMRAW:
+			kbd->kbdmode = VC_MEDIUMRAW;
+			break;
+		  case K_XLATE:
+			kbd->kbdmode = VC_XLATE;
+			compute_shiftstate();
+			break;
+		  case K_UNICODE:
+			kbd->kbdmode = VC_UNICODE;
+			compute_shiftstate();
+			break;
+		  default:
+			return -EINVAL;
+		}
+		tty_ldisc_flush(tty);
+		return 0;
+
+	case KDGKBMODE:
+		ucval = ((kbd->kbdmode == VC_RAW) ? K_RAW :
+				 (kbd->kbdmode == VC_MEDIUMRAW) ? K_MEDIUMRAW :
+				 (kbd->kbdmode == VC_UNICODE) ? K_UNICODE :
+				 K_XLATE);
+		goto setint;
+
+	/* this could be folded into KDSKBMODE, but for compatibility
+	   reasons it is not so easy to fold KDGKBMETA into KDGKBMODE */
+	case KDSKBMETA:
+		switch(arg) {
+		  case K_METABIT:
+			clr_vc_kbd_mode(kbd, VC_META);
+			break;
+		  case K_ESCPREFIX:
+			set_vc_kbd_mode(kbd, VC_META);
+			break;
+		  default:
+			return -EINVAL;
+		}
+		return 0;
+
+	case KDGKBMETA:
+		ucval = (vc_kbd_mode(kbd, VC_META) ? K_ESCPREFIX : K_METABIT);
+	setint:
+		return put_user(ucval, (int __user *)arg); 
+
+	case KDGETKEYCODE:
+	case KDSETKEYCODE:
+		if(!capable(CAP_SYS_TTY_CONFIG))
+			perm=0;
+		return do_kbkeycode_ioctl(cmd, up, perm);
+
+	case KDGKBENT:
+	case KDSKBENT:
+		return do_kdsk_ioctl(cmd, up, perm, kbd);
+
+	case KDGKBSENT:
+	case KDSKBSENT:
+		return do_kdgkb_ioctl(cmd, up, perm);
+
+	case KDGKBDIACR:
+	{
+		struct kbdiacrs __user *a = up;
+
+		if (put_user(accent_table_size, &a->kb_cnt))
+			return -EFAULT;
+		if (copy_to_user(a->kbdiacr, accent_table, accent_table_size*sizeof(struct kbdiacr)))
+			return -EFAULT;
+		return 0;
+	}
+
+	case KDSKBDIACR:
+	{
+		struct kbdiacrs __user *a = up;
+		unsigned int ct;
+
+		if (!perm)
+			return -EPERM;
+		if (get_user(ct,&a->kb_cnt))
+			return -EFAULT;
+		if (ct >= MAX_DIACR)
+			return -EINVAL;
+		accent_table_size = ct;
+		if (copy_from_user(accent_table, a->kbdiacr, ct*sizeof(struct kbdiacr)))
+			return -EFAULT;
+		return 0;
+	}
+
+	/* the ioctls below read/set the flags usually shown in the leds */
+	/* don't use them - they will go away without warning */
+	case KDGKBLED:
+		ucval = kbd->ledflagstate | (kbd->default_ledflagstate << 4);
+		goto setchar;
+
+	case KDSKBLED:
+		if (!perm)
+			return -EPERM;
+		if (arg & ~0x77)
+			return -EINVAL;
+		kbd->ledflagstate = (arg & 7);
+		kbd->default_ledflagstate = ((arg >> 4) & 7);
+		set_leds();
+		return 0;
+
+	/* the ioctls below only set the lights, not the functions */
+	/* for those, see KDGKBLED and KDSKBLED above */
+	case KDGETLED:
+		ucval = getledstate();
+	setchar:
+		return put_user(ucval, (char __user *)arg);
+
+	case KDSETLED:
+		if (!perm)
+		  return -EPERM;
+		setledstate(kbd, arg);
+		return 0;
+
+	/*
+	 * A process can indicate its willingness to accept signals
+	 * generated by pressing an appropriate key combination.
+	 * Thus, one can have a daemon that e.g. spawns a new console
+	 * upon a keypress and then changes to it.
+	 * See also the kbrequest field of inittab(5).
+	 */
+	case KDSIGACCEPT:
+	{
+		extern int spawnpid, spawnsig;
+		if (!perm || !capable(CAP_KILL))
+		  return -EPERM;
+		if (arg < 1 || arg > _NSIG || arg == SIGKILL)
+		  return -EINVAL;
+		spawnpid = current->pid;
+		spawnsig = arg;
+		return 0;
+	}
+
+	case VT_SETMODE:
+	{
+		struct vt_mode tmp;
+
+		if (!perm)
+			return -EPERM;
+		if (copy_from_user(&tmp, up, sizeof(struct vt_mode)))
+			return -EFAULT;
+		if (tmp.mode != VT_AUTO && tmp.mode != VT_PROCESS)
+			return -EINVAL;
+		acquire_console_sem();
+		vc->vt_mode = tmp;
+		/* the frsig is ignored, so we set it to 0 */
+		vc->vt_mode.frsig = 0;
+		vc->vt_pid = current->pid;
+		/* no switch is required -- saw@shade.msu.ru */
+		vc->vt_newvt = -1;
+		release_console_sem();
+		return 0;
+	}
+
+	case VT_GETMODE:
+	{
+		struct vt_mode tmp;
+		int rc;
+
+		acquire_console_sem();
+		memcpy(&tmp, &vc->vt_mode, sizeof(struct vt_mode));
+		release_console_sem();
+
+		rc = copy_to_user(up, &tmp, sizeof(struct vt_mode));
+		return rc ? -EFAULT : 0;
+	}
+
+	/*
+	 * Returns global vt state. Note that VT 0 is always open, since
+	 * it's an alias for the current VT, and people can't use it here.
+	 * We cannot return state for more than 16 VTs, since v_state is short.
+	 */
+	case VT_GETSTATE:
+	{
+		struct vt_stat __user *vtstat = up;
+		unsigned short state, mask;
+
+		if (put_user(fg_console + 1, &vtstat->v_active))
+			return -EFAULT;
+		state = 1;	/* /dev/tty0 is always open */
+		for (i = 0, mask = 2; i < MAX_NR_CONSOLES && mask; ++i, mask <<= 1)
+			if (VT_IS_IN_USE(i))
+				state |= mask;
+		return put_user(state, &vtstat->v_state);
+	}
+
+	/*
+	 * Returns the first available (non-opened) console.
+	 */
+	case VT_OPENQRY:
+		for (i = 0; i < MAX_NR_CONSOLES; ++i)
+			if (! VT_IS_IN_USE(i))
+				break;
+		ucval = i < MAX_NR_CONSOLES ? (i+1) : -1;
+		goto setint;		 
+
+	/*
+	 * ioctl(fd, VT_ACTIVATE, num) will cause us to switch to vt # num,
+	 * with num >= 1 (switches to vt 0, our console, are not allowed, just
+	 * to preserve sanity).
+	 */
+	case VT_ACTIVATE:
+		if (!perm)
+			return -EPERM;
+		if (arg == 0 || arg > MAX_NR_CONSOLES)
+			return -ENXIO;
+		arg--;
+		acquire_console_sem();
+		i = vc_allocate(arg);
+		release_console_sem();
+		if (i)
+			return i;
+		set_console(arg);
+		return 0;
+
+	/*
+	 * wait until the specified VT has been activated
+	 */
+	case VT_WAITACTIVE:
+		if (!perm)
+			return -EPERM;
+		if (arg == 0 || arg > MAX_NR_CONSOLES)
+			return -ENXIO;
+		return vt_waitactive(arg-1);
+
+	/*
+	 * If a vt is under process control, the kernel will not switch to it
+	 * immediately, but postpone the operation until the process calls this
+	 * ioctl, allowing the switch to complete.
+	 *
+	 * According to the X sources this is the behavior:
+	 *	0:	pending switch-from not OK
+	 *	1:	pending switch-from OK
+	 *	2:	completed switch-to OK
+	 */
+	case VT_RELDISP:
+		if (!perm)
+			return -EPERM;
+		if (vc->vt_mode.mode != VT_PROCESS)
+			return -EINVAL;
+
+		/*
+		 * Switching-from response
+		 */
+		if (vc->vt_newvt >= 0) {
+			if (arg == 0)
+				/*
+				 * Switch disallowed, so forget we were trying
+				 * to do it.
+				 */
+				vc->vt_newvt = -1;
+
+			else {
+				/*
+				 * The current vt has been released, so
+				 * complete the switch.
+				 */
+				int newvt;
+				acquire_console_sem();
+				newvt = vc->vt_newvt;
+				vc->vt_newvt = -1;
+				i = vc_allocate(newvt);
+				if (i) {
+					release_console_sem();
+					return i;
+				}
+				/*
+				 * When we actually do the console switch,
+				 * make sure we are atomic with respect to
+				 * other console switches..
+				 */
+				complete_change_console(vc_cons[newvt].d);
+				release_console_sem();
+			}
+		}
+
+		/*
+		 * Switched-to response
+		 */
+		else
+		{
+			/*
+			 * If it's just an ACK, ignore it
+			 */
+			if (arg != VT_ACKACQ)
+				return -EINVAL;
+		}
+
+		return 0;
+
+	 /*
+	  * Disallocate memory associated to VT (but leave VT1)
+	  */
+	 case VT_DISALLOCATE:
+		if (arg > MAX_NR_CONSOLES)
+			return -ENXIO;
+		if (arg == 0) {
+		    /* disallocate all unused consoles, but leave 0 */
+			acquire_console_sem();
+			for (i=1; i<MAX_NR_CONSOLES; i++)
+				if (! VT_BUSY(i))
+					vc_disallocate(i);
+			release_console_sem();
+		} else {
+			/* disallocate a single console, if possible */
+			arg--;
+			if (VT_BUSY(arg))
+				return -EBUSY;
+			if (arg) {			      /* leave 0 */
+				acquire_console_sem();
+				vc_disallocate(arg);
+				release_console_sem();
+			}
+		}
+		return 0;
+
+	case VT_RESIZE:
+	{
+		struct vt_sizes __user *vtsizes = up;
+		ushort ll,cc;
+		if (!perm)
+			return -EPERM;
+		if (get_user(ll, &vtsizes->v_rows) ||
+		    get_user(cc, &vtsizes->v_cols))
+			return -EFAULT;
+		for (i = 0; i < MAX_NR_CONSOLES; i++) {
+			acquire_console_sem();
+			vc_resize(vc_cons[i].d, cc, ll);
+			release_console_sem();
+		}
+		return 0;
+	}
+
+	case VT_RESIZEX:
+	{
+		struct vt_consize __user *vtconsize = up;
+		ushort ll,cc,vlin,clin,vcol,ccol;
+		if (!perm)
+			return -EPERM;
+		if (!access_ok(VERIFY_READ, vtconsize,
+				sizeof(struct vt_consize)))
+			return -EFAULT;
+		__get_user(ll, &vtconsize->v_rows);
+		__get_user(cc, &vtconsize->v_cols);
+		__get_user(vlin, &vtconsize->v_vlin);
+		__get_user(clin, &vtconsize->v_clin);
+		__get_user(vcol, &vtconsize->v_vcol);
+		__get_user(ccol, &vtconsize->v_ccol);
+		vlin = vlin ? vlin : vc->vc_scan_lines;
+		if (clin) {
+			if (ll) {
+				if (ll != vlin/clin)
+					return -EINVAL; /* Parameters don't add up */
+			} else 
+				ll = vlin/clin;
+		}
+		if (vcol && ccol) {
+			if (cc) {
+				if (cc != vcol/ccol)
+					return -EINVAL;
+			} else
+				cc = vcol/ccol;
+		}
+
+		if (clin > 32)
+			return -EINVAL;
+		    
+		for (i = 0; i < MAX_NR_CONSOLES; i++) {
+			if (!vc_cons[i].d)
+				continue;
+			acquire_console_sem();
+			if (vlin)
+				vc_cons[i].d->vc_scan_lines = vlin;
+			if (clin)
+				vc_cons[i].d->vc_font.height = clin;
+			vc_resize(vc_cons[i].d, cc, ll);
+			release_console_sem();
+		}
+  		return 0;
+	}
+
+	case PIO_FONT: {
+		if (!perm)
+			return -EPERM;
+		op.op = KD_FONT_OP_SET;
+		op.flags = KD_FONT_FLAG_OLD | KD_FONT_FLAG_DONT_RECALC;	/* Compatibility */
+		op.width = 8;
+		op.height = 0;
+		op.charcount = 256;
+		op.data = up;
+		return con_font_op(vc_cons[fg_console].d, &op);
+	}
+
+	case GIO_FONT: {
+		op.op = KD_FONT_OP_GET;
+		op.flags = KD_FONT_FLAG_OLD;
+		op.width = 8;
+		op.height = 32;
+		op.charcount = 256;
+		op.data = up;
+		return con_font_op(vc_cons[fg_console].d, &op);
+	}
+
+	case PIO_CMAP:
+                if (!perm)
+			return -EPERM;
+                return con_set_cmap(up);
+
+	case GIO_CMAP:
+                return con_get_cmap(up);
+
+	case PIO_FONTX:
+	case GIO_FONTX:
+		return do_fontx_ioctl(cmd, up, perm, &op);
+
+	case PIO_FONTRESET:
+	{
+		if (!perm)
+			return -EPERM;
+
+#ifdef BROKEN_GRAPHICS_PROGRAMS
+		/* With BROKEN_GRAPHICS_PROGRAMS defined, the default
+		   font is not saved. */
+		return -ENOSYS;
+#else
+		{
+		op.op = KD_FONT_OP_SET_DEFAULT;
+		op.data = NULL;
+		i = con_font_op(vc_cons[fg_console].d, &op);
+		if (i)
+			return i;
+		con_set_default_unimap(vc_cons[fg_console].d);
+		return 0;
+		}
+#endif
+	}
+
+	case KDFONTOP: {
+		if (copy_from_user(&op, up, sizeof(op)))
+			return -EFAULT;
+		if (!perm && op.op != KD_FONT_OP_GET)
+			return -EPERM;
+		i = con_font_op(vc, &op);
+		if (i) return i;
+		if (copy_to_user(up, &op, sizeof(op)))
+			return -EFAULT;
+		return 0;
+	}
+
+	case PIO_SCRNMAP:
+		if (!perm)
+			return -EPERM;
+		return con_set_trans_old(up);
+
+	case GIO_SCRNMAP:
+		return con_get_trans_old(up);
+
+	case PIO_UNISCRNMAP:
+		if (!perm)
+			return -EPERM;
+		return con_set_trans_new(up);
+
+	case GIO_UNISCRNMAP:
+		return con_get_trans_new(up);
+
+	case PIO_UNIMAPCLR:
+	      { struct unimapinit ui;
+		if (!perm)
+			return -EPERM;
+		i = copy_from_user(&ui, up, sizeof(struct unimapinit));
+		if (i) return -EFAULT;
+		con_clear_unimap(vc, &ui);
+		return 0;
+	      }
+
+	case PIO_UNIMAP:
+	case GIO_UNIMAP:
+		return do_unimap_ioctl(cmd, up, perm, vc);
+
+	case VT_LOCKSWITCH:
+		if (!capable(CAP_SYS_TTY_CONFIG))
+		   return -EPERM;
+		vt_dont_switch = 1;
+		return 0;
+	case VT_UNLOCKSWITCH:
+		if (!capable(CAP_SYS_TTY_CONFIG))
+		   return -EPERM;
+		vt_dont_switch = 0;
+		return 0;
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+/*
+ * Sometimes we want to wait until a particular VT has been activated. We
+ * do it in a very simple manner. Everybody waits on a single queue and
+ * get woken up at once. Those that are satisfied go on with their business,
+ * while those not ready go back to sleep. Seems overkill to add a wait
+ * to each vt just for this - usually this does nothing!
+ */
+static DECLARE_WAIT_QUEUE_HEAD(vt_activate_queue);
+
+/*
+ * Sleeps until a vt is activated, or the task is interrupted. Returns
+ * 0 if activation, -EINTR if interrupted.
+ */
+int vt_waitactive(int vt)
+{
+	int retval;
+	DECLARE_WAITQUEUE(wait, current);
+
+	add_wait_queue(&vt_activate_queue, &wait);
+	for (;;) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		retval = 0;
+		if (vt == fg_console)
+			break;
+		retval = -EINTR;
+		if (signal_pending(current))
+			break;
+		schedule();
+	}
+	remove_wait_queue(&vt_activate_queue, &wait);
+	current->state = TASK_RUNNING;
+	return retval;
+}
+
+#define vt_wake_waitactive() wake_up(&vt_activate_queue)
+
+void reset_vc(struct vc_data *vc)
+{
+	vc->vc_mode = KD_TEXT;
+	kbd_table[vc->vc_num].kbdmode = VC_XLATE;
+	vc->vt_mode.mode = VT_AUTO;
+	vc->vt_mode.waitv = 0;
+	vc->vt_mode.relsig = 0;
+	vc->vt_mode.acqsig = 0;
+	vc->vt_mode.frsig = 0;
+	vc->vt_pid = -1;
+	vc->vt_newvt = -1;
+	if (!in_interrupt())    /* Via keyboard.c:SAK() - akpm */
+		reset_palette(vc);
+}
+
+/*
+ * Performs the back end of a vt switch
+ */
+static void complete_change_console(struct vc_data *vc)
+{
+	unsigned char old_vc_mode;
+
+	last_console = fg_console;
+
+	/*
+	 * If we're switching, we could be going from KD_GRAPHICS to
+	 * KD_TEXT mode or vice versa, which means we need to blank or
+	 * unblank the screen later.
+	 */
+	old_vc_mode = vc_cons[fg_console].d->vc_mode;
+	switch_screen(vc);
+
+	/*
+	 * This can't appear below a successful kill_proc().  If it did,
+	 * then the *blank_screen operation could occur while X, having
+	 * received acqsig, is waking up on another processor.  This
+	 * condition can lead to overlapping accesses to the VGA range
+	 * and the framebuffer (causing system lockups).
+	 *
+	 * To account for this we duplicate this code below only if the
+	 * controlling process is gone and we've called reset_vc.
+	 */
+	if (old_vc_mode != vc->vc_mode) {
+		if (vc->vc_mode == KD_TEXT)
+			do_unblank_screen(1);
+		else
+			do_blank_screen(1);
+	}
+
+	/*
+	 * If this new console is under process control, send it a signal
+	 * telling it that it has acquired. Also check if it has died and
+	 * clean up (similar to logic employed in change_console())
+	 */
+	if (vc->vt_mode.mode == VT_PROCESS) {
+		/*
+		 * Send the signal as privileged - kill_proc() will
+		 * tell us if the process has gone or something else
+		 * is awry
+		 */
+		if (kill_proc(vc->vt_pid, vc->vt_mode.acqsig, 1) != 0) {
+		/*
+		 * The controlling process has died, so we revert back to
+		 * normal operation. In this case, we'll also change back
+		 * to KD_TEXT mode. I'm not sure if this is strictly correct
+		 * but it saves the agony when the X server dies and the screen
+		 * remains blanked due to KD_GRAPHICS! It would be nice to do
+		 * this outside of VT_PROCESS but there is no single process
+		 * to account for and tracking tty count may be undesirable.
+		 */
+			reset_vc(vc);
+
+			if (old_vc_mode != vc->vc_mode) {
+				if (vc->vc_mode == KD_TEXT)
+					do_unblank_screen(1);
+				else
+					do_blank_screen(1);
+			}
+		}
+	}
+
+	/*
+	 * Wake anyone waiting for their VT to activate
+	 */
+	vt_wake_waitactive();
+	return;
+}
+
+/*
+ * Performs the front-end of a vt switch
+ */
+void change_console(struct vc_data *new_vc)
+{
+	struct vc_data *vc;
+
+	if (!new_vc || new_vc->vc_num == fg_console || vt_dont_switch)
+		return;
+
+	/*
+	 * If this vt is in process mode, then we need to handshake with
+	 * that process before switching. Essentially, we store where that
+	 * vt wants to switch to and wait for it to tell us when it's done
+	 * (via VT_RELDISP ioctl).
+	 *
+	 * We also check to see if the controlling process still exists.
+	 * If it doesn't, we reset this vt to auto mode and continue.
+	 * This is a cheap way to track process control. The worst thing
+	 * that can happen is: we send a signal to a process, it dies, and
+	 * the switch gets "lost" waiting for a response; hopefully, the
+	 * user will try again, we'll detect the process is gone (unless
+	 * the user waits just the right amount of time :-) and revert the
+	 * vt to auto control.
+	 */
+	vc = vc_cons[fg_console].d;
+	if (vc->vt_mode.mode == VT_PROCESS) {
+		/*
+		 * Send the signal as privileged - kill_proc() will
+		 * tell us if the process has gone or something else
+		 * is awry
+		 */
+		if (kill_proc(vc->vt_pid, vc->vt_mode.relsig, 1) == 0) {
+			/*
+			 * It worked. Mark the vt to switch to and
+			 * return. The process needs to send us a
+			 * VT_RELDISP ioctl to complete the switch.
+			 */
+			vc->vt_newvt = new_vc->vc_num;
+			return;
+		}
+
+		/*
+		 * The controlling process has died, so we revert back to
+		 * normal operation. In this case, we'll also change back
+		 * to KD_TEXT mode. I'm not sure if this is strictly correct
+		 * but it saves the agony when the X server dies and the screen
+		 * remains blanked due to KD_GRAPHICS! It would be nice to do
+		 * this outside of VT_PROCESS but there is no single process
+		 * to account for and tracking tty count may be undesirable.
+		 */
+		reset_vc(vc);
+
+		/*
+		 * Fall through to normal (VT_AUTO) handling of the switch...
+		 */
+	}
+
+	/*
+	 * Ignore all switches in KD_GRAPHICS+VT_AUTO mode
+	 */
+	if (vc->vc_mode == KD_GRAPHICS)
+		return;
+
+	complete_change_console(new_vc);
+}
diff --git a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig
new file mode 100644
index 0000000..06a31da
--- /dev/null
+++ b/drivers/char/watchdog/Kconfig
@@ -0,0 +1,549 @@
+#
+# Watchdog device configuration
+#
+
+menu "Watchdog Cards"
+
+config WATCHDOG
+	bool "Watchdog Timer Support"
+	---help---
+	  If you say Y here (and to one of the following options) and create a
+	  character special file /dev/watchdog with major number 10 and minor
+	  number 130 using mknod ("man mknod"), you will get a watchdog, i.e.:
+	  subsequently opening the file and then failing to write to it for
+	  longer than 1 minute will result in rebooting the machine. This
+	  could be useful for a networked machine that needs to come back
+	  online as fast as possible after a lock-up. There's both a watchdog
+	  implementation entirely in software (which can sometimes fail to
+	  reboot the machine) and a driver for hardware watchdog boards, which
+	  are more robust and can also keep track of the temperature inside
+	  your computer. For details, read <file:Documentation/watchdog/watchdog.txt>
+	  in the kernel source.
+
+	  The watchdog is usually used together with the watchdog daemon
+	  which is available from
+	  <ftp://ibiblio.org/pub/Linux/system/daemons/watchdog/>. This daemon can
+	  also monitor NFS connections and can reboot the machine when the process
+	  table is full.
+
+	  If unsure, say N.
+
+config WATCHDOG_NOWAYOUT
+	bool "Disable watchdog shutdown on close"
+	depends on WATCHDOG
+	help
+	  The default watchdog behaviour (which you get if you say N here) is
+	  to stop the timer if the process managing it closes the file
+	  /dev/watchdog. It's always remotely possible that this process might
+	  get killed. If you say Y here, the watchdog cannot be stopped once
+	  it has been started.
+
+#
+# General Watchdog drivers
+#
+
+comment "Watchdog Device Drivers"
+	depends on WATCHDOG
+
+# Architecture Independant
+
+config SOFT_WATCHDOG
+	tristate "Software watchdog"
+	depends on WATCHDOG
+	help
+	  A software monitoring watchdog. This will fail to reboot your system
+	  from some situations that the hardware watchdog will recover
+	  from. Equally it's a lot cheaper to install.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called softdog.
+
+# ARM Architecture
+
+config 21285_WATCHDOG
+	tristate "DC21285 watchdog"
+	depends on WATCHDOG && FOOTBRIDGE
+	help
+	  The Intel Footbridge chip contains a builtin watchdog circuit. Say Y
+	  here if you wish to use this. Alternatively say M to compile the
+	  driver as a module, which will be called wdt285.
+
+	  This driver does not work on all machines. In particular, early CATS
+	  boards have hardware problems that will cause the machine to simply
+	  lock up if the watchdog fires.
+
+	  "If in doubt, leave it out" - say N.
+
+config 977_WATCHDOG
+	tristate "NetWinder WB83C977 watchdog"
+	depends on WATCHDOG && FOOTBRIDGE && ARCH_NETWINDER
+	help
+	  Say Y here to include support for the WB977 watchdog included in
+	  NetWinder machines. Alternatively say M to compile the driver as
+	  a module, which will be called wdt977.
+
+	  Not sure? It's safe to say N.
+
+config IXP4XX_WATCHDOG
+	tristate "IXP4xx Watchdog"
+	depends on WATCHDOG && ARCH_IXP4XX
+	help
+	  Say Y here if to include support for the watchdog timer
+	  in the Intel IXP4xx network processors. This driver can
+	  be built as a module by choosing M. The module will
+	  be called ixp4xx_wdt.
+
+	  Note: The internal IXP4xx watchdog does a soft CPU reset
+	  which doesn't reset any peripherals. There are circumstances
+	  where the watchdog will fail to reset the board correctly
+	  (e.g., if the boot ROM is in an unreadable state).
+
+	  Say N if you are unsure.
+
+config IXP2000_WATCHDOG
+	tristate "IXP2000 Watchdog"
+	depends on WATCHDOG && ARCH_IXP2000
+	help
+	  Say Y here if to include support for the watchdog timer
+	  in the Intel IXP2000(2400, 2800, 2850) network processors.
+	  This driver can be built as a module by choosing M. The module
+	  will be called ixp2000_wdt.
+
+	  Say N if you are unsure.
+
+config S3C2410_WATCHDOG
+	tristate "S3C2410 Watchdog"
+	depends on WATCHDOG && ARCH_S3C2410
+	help
+	  Watchdog timer block in the Samsung S3C2410 chips. This will
+	  reboot the system when the timer expires with the watchdog
+	  enabled.
+
+	  The driver is limited by the speed of the system's PCLK
+	  signal, so with reasonbaly fast systems (PCLK around 50-66MHz)
+	  then watchdog intervals of over approximately 20seconds are
+	  unavailable.
+
+	  The driver can be built as a module by choosing M, and will
+	  be called s3c2410_wdt
+
+config SA1100_WATCHDOG
+	tristate "SA1100/PXA2xx watchdog"
+	depends on WATCHDOG && ( ARCH_SA1100 || ARCH_PXA )
+	help
+	  Watchdog timer embedded into SA11x0 and PXA2xx chips. This will
+	  reboot your system when timeout is reached.
+
+	  NOTE: once enabled, this timer cannot be disabled.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sa1100_wdt.
+
+# X86 (i386 + ia64 + x86_64) Architecture
+
+config ACQUIRE_WDT
+	tristate "Acquire SBC Watchdog Timer"
+	depends on WATCHDOG && X86
+	---help---
+	  This is the driver for the hardware watchdog on Single Board
+	  Computers produced by Acquire Inc (and others). This watchdog
+	  simply watches your kernel to make sure it doesn't freeze, and if
+	  it does, it reboots your computer after a certain amount of time.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called acquirewdt.
+
+	  Most people will say N.
+
+config ADVANTECH_WDT
+	tristate "Advantech SBC Watchdog Timer"
+	depends on WATCHDOG && X86
+	help
+	  If you are configuring a Linux kernel for the Advantech single-board
+	  computer, say `Y' here to support its built-in watchdog timer
+	  feature. More information can be found at
+	  <http://www.advantech.com.tw/products/>
+
+config ALIM1535_WDT
+	tristate "ALi M1535 PMU Watchdog Timer"
+	depends on WATCHDOG && X86 && PCI
+	---help---
+	  This is the driver for the hardware watchdog on the ALi M1535 PMU.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called alim1535_wdt.
+
+	  Most people will say N.
+
+config ALIM7101_WDT
+	tristate "ALi M7101 PMU Computer Watchdog"
+	depends on WATCHDOG && X86 && PCI
+	help
+	  This is the driver for the hardware watchdog on the ALi M7101 PMU
+	  as used in the x86 Cobalt servers.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called alim7101_wdt.
+
+	  Most people will say N.
+
+config SC520_WDT
+	tristate "AMD Elan SC520 processor Watchdog"
+	depends on WATCHDOG && X86
+	help
+	  This is the driver for the hardware watchdog built in to the
+	  AMD "Elan" SC520 microcomputer commonly used in embedded systems.
+	  This watchdog simply watches your kernel to make sure it doesn't
+	  freeze, and if it does, it reboots your computer after a certain
+	  amount of time.
+
+	  You can compile this driver directly into the kernel, or use
+	  it as a module.  The module will be called sc520_wdt.
+
+config EUROTECH_WDT
+	tristate "Eurotech CPU-1220/1410 Watchdog Timer"
+	depends on WATCHDOG && X86
+	help
+	  Enable support for the watchdog timer on the Eurotech CPU-1220 and
+	  CPU-1410 cards.  These are PC/104 SBCs. Spec sheets and product
+	  information are at <http://www.eurotech.it/>.
+
+config IB700_WDT
+	tristate "IB700 SBC Watchdog Timer"
+	depends on WATCHDOG && X86
+	---help---
+	  This is the driver for the hardware watchdog on the IB700 Single
+	  Board Computer produced by TMC Technology (www.tmc-uk.com). This watchdog
+	  simply watches your kernel to make sure it doesn't freeze, and if
+	  it does, it reboots your computer after a certain amount of time.
+
+	  This driver is like the WDT501 driver but for slightly different hardware.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ib700wdt.
+
+	  Most people will say N.
+
+config WAFER_WDT
+	tristate "ICP Wafer 5823 Single Board Computer Watchdog"
+	depends on WATCHDOG && X86
+	help
+	  This is a driver for the hardware watchdog on the ICP Wafer 5823
+	  Single Board Computer (and probably other similar models).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called wafer5823wdt.
+
+config I8XX_TCO
+	tristate "Intel i8xx TCO Timer/Watchdog"
+	depends on WATCHDOG && (X86 || IA64) && PCI
+	---help---
+	  Hardware driver for the TCO timer built into the Intel 82801
+	  I/O Controller Hub family.  The TCO (Total Cost of Ownership)
+	  timer is a watchdog timer that will reboot the machine after
+	  its second expiration. The expiration time can be configured
+	  with the "heartbeat" parameter.
+
+	  On some motherboards the driver may fail to reset the chipset's
+	  NO_REBOOT flag which prevents the watchdog from rebooting the
+	  machine. If this is the case you will get a kernel message like
+	  "failed to reset NO_REBOOT flag, reboot disabled by hardware".
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called i8xx_tco.
+
+config SC1200_WDT
+	tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog"
+	depends on WATCHDOG && X86
+	help
+	  This is a driver for National Semiconductor PC87307/PC97307 hardware
+	  watchdog cards as found on the SC1200. This watchdog is mainly used
+	  for power management purposes and can be used to power down the device
+	  during inactivity periods (includes interrupt activity monitoring).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called sc1200wdt.
+
+	  Most people will say N.
+
+config SCx200_WDT
+	tristate "National Semiconductor SCx200 Watchdog"
+	depends on WATCHDOG && SCx200 && PCI
+	help
+	  Enable the built-in watchdog timer support on the National
+	  Semiconductor SCx200 processors.
+
+	  If compiled as a module, it will be called scx200_wdt.
+
+config 60XX_WDT
+	tristate "SBC-60XX Watchdog Timer"
+	depends on WATCHDOG && X86
+	help
+	  This driver can be used with the watchdog timer found on some
+	  single board computers, namely the 6010 PII based computer.
+	  It may well work with other cards.  It reads port 0x443 to enable
+	  and re-set the watchdog timer, and reads port 0x45 to disable
+	  the watchdog.  If you have a card that behave in similar ways,
+	  you can probably make this driver work with your card as well.
+
+	  You can compile this driver directly into the kernel, or use
+	  it as a module.  The module will be called sbc60xxwdt.
+
+config CPU5_WDT
+	tristate "SMA CPU5 Watchdog"
+	depends on WATCHDOG && X86
+	---help---
+	  TBD.
+	  To compile this driver as a module, choose M here: the
+	  module will be called cpu5wdt.
+
+config W83627HF_WDT
+	tristate "W83627HF Watchdog Timer"
+	depends on WATCHDOG && X86
+	---help---
+	  This is the driver for the hardware watchdog on the W83627HF chipset
+	  as used in Advantech PC-9578 and Tyan S2721-533 motherboards
+	  (and likely others).  This watchdog simply watches your kernel to
+	  make sure it doesn't freeze, and if it does, it reboots your computer
+	  after a certain amount of time.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called w83627hf_wdt.
+
+	  Most people will say N.
+
+config W83877F_WDT
+	tristate "W83877F (EMACS) Watchdog Timer"
+	depends on WATCHDOG && X86
+	---help---
+	  This is the driver for the hardware watchdog on the W83877F chipset
+	  as used in EMACS PC-104 motherboards (and likely others).  This
+	  watchdog simply watches your kernel to make sure it doesn't freeze,
+	  and if it does, it reboots your computer after a certain amount of
+	  time.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called w83877f_wdt.
+
+	  Most people will say N.
+
+config MACHZ_WDT
+	tristate "ZF MachZ Watchdog"
+	depends on WATCHDOG && X86
+	---help---
+	  If you are using a ZF Micro MachZ processor, say Y here, otherwise
+	  N.  This is the driver for the watchdog timer builtin on that
+	  processor using ZF-Logic interface.  This watchdog simply watches
+	  your kernel to make sure it doesn't freeze, and if it does, it
+	  reboots your computer after a certain amount of time.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called machzwd.
+
+# PowerPC Architecture
+
+config 8xx_WDT
+	tristate "MPC8xx Watchdog Timer"
+	depends on WATCHDOG && 8xx
+
+# MIPS Architecture
+
+config INDYDOG
+	tristate "Indy/I2 Hardware Watchdog"
+	depends on WATCHDOG && SGI_IP22
+	help
+	  Hardwaredriver for the Indy's/I2's watchdog. This is a
+	  watchdog timer that will reboot the machine after a 60 second
+	  timer expired and no process has written to /dev/watchdog during
+	  that time.
+
+# S390 Architecture
+
+config ZVM_WATCHDOG
+	tristate "z/VM Watchdog Timer"
+	depends on WATCHDOG && ARCH_S390
+	help
+	  IBM s/390 and zSeries machines running under z/VM 5.1 or later
+	  provide a virtual watchdog timer to their guest that cause a
+	  user define Control Program command to be executed after a
+	  timeout.
+
+	  To compile this driver as a module, choose M here. The module
+	  will be called vmwatchdog.
+
+# SUPERH Architecture
+
+config SH_WDT
+	tristate "SuperH Watchdog"
+	depends on WATCHDOG && SUPERH
+	help
+	  This driver adds watchdog support for the integrated watchdog in the
+	  SuperH processors. If you have one of these processors and wish
+	  to have watchdog support enabled, say Y, otherwise say N.
+
+	  As a side note, saying Y here will automatically boost HZ to 1000
+	  so that the timer has a chance to clear the overflow counter. On
+	  slower systems (such as the SH-2 and SH-3) this will likely yield
+	  some performance issues. As such, the WDT should be avoided here
+	  unless it is absolutely necessary.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called shwdt.
+
+# SPARC64 Architecture
+
+config WATCHDOG_CP1XXX
+	tristate "CP1XXX Hardware Watchdog support"
+	depends on WATCHDOG && SPARC64 && PCI
+	---help---
+	  This is the driver for the hardware watchdog timers present on
+	  Sun Microsystems CompactPCI models CP1400 and CP1500.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cpwatchdog.
+
+	  If you do not have a CompactPCI model CP1400 or CP1500, or
+	  another UltraSPARC-IIi-cEngine boardset with hardware watchdog,
+	  you should say N to this option.
+
+config WATCHDOG_RIO
+	tristate "RIO Hardware Watchdog support"
+	depends on WATCHDOG && SPARC64 && PCI
+	help
+	  Say Y here to support the hardware watchdog capability on Sun RIO
+	  machines.  The watchdog timeout period is normally one minute but
+	  can be changed with a boot-time parameter.
+
+#
+# ISA-based Watchdog Cards
+#
+
+comment "ISA-based Watchdog Cards"
+	depends on WATCHDOG && ISA
+
+config PCWATCHDOG
+	tristate "Berkshire Products ISA-PC Watchdog"
+	depends on WATCHDOG && ISA
+	---help---
+	  This is the driver for the Berkshire Products ISA-PC Watchdog card.
+	  This card simply watches your kernel to make sure it doesn't freeze,
+	  and if it does, it reboots your computer after a certain amount of
+	  time. This driver is like the WDT501 driver but for different
+	  hardware. Please read <file:Documentation/watchdog/pcwd-watchdog.txt>. The PC
+	  watchdog cards can be ordered from <http://www.berkprod.com/>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pcwd.
+
+	  Most people will say N.
+
+config MIXCOMWD
+	tristate "Mixcom Watchdog"
+	depends on WATCHDOG && ISA
+	---help---
+	  This is a driver for the Mixcom hardware watchdog cards.  This
+	  watchdog simply watches your kernel to make sure it doesn't freeze,
+	  and if it does, it reboots your computer after a certain amount of
+	  time.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mixcomwd.
+
+	  Most people will say N.
+
+config WDT
+	tristate "WDT Watchdog timer"
+	depends on WATCHDOG && ISA
+	---help---
+	  If you have a WDT500P or WDT501P watchdog board, say Y here,
+	  otherwise N. It is not possible to probe for this board, which means
+	  that you have to inform the kernel about the IO port and IRQ that
+	  is needed (you can do this via the io and irq parameters)
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called wdt.
+
+config WDT_501
+	bool "WDT501 features"
+	depends on WDT
+	help
+	  Saying Y here and creating a character special file /dev/temperature
+	  with major number 10 and minor number 131 ("man mknod") will give
+	  you a thermometer inside your computer: reading from
+	  /dev/temperature yields one byte, the temperature in degrees
+	  Fahrenheit. This works only if you have a WDT501P watchdog board
+	  installed.
+
+	  If you want to enable the Fan Tachometer on the WDT501P, then you
+	  can do this via the tachometer parameter. Only do this if you have a
+	  fan tachometer actually set up.
+
+#
+# PCI-based Watchdog Cards
+#
+
+comment "PCI-based Watchdog Cards"
+	depends on WATCHDOG && PCI
+
+config PCIPCWATCHDOG
+	tristate "Berkshire Products PCI-PC Watchdog"
+	depends on WATCHDOG && PCI
+	---help---
+	  This is the driver for the Berkshire Products PCI-PC Watchdog card.
+	  This card simply watches your kernel to make sure it doesn't freeze,
+	  and if it does, it reboots your computer after a certain amount of
+	  time. The card can also monitor the internal temperature of the PC.
+	  More info is available at <http://www.berkprod.com/pci_pc_watchdog.htm>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pcwd_pci.
+
+	  Most people will say N.
+
+config WDTPCI
+	tristate "PCI-WDT500/501 Watchdog timer"
+	depends on WATCHDOG && PCI
+	---help---
+	  If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called wdt_pci.
+
+config WDT_501_PCI
+	bool "PCI-WDT501 features"
+	depends on WDTPCI
+	help
+	  Saying Y here and creating a character special file /dev/temperature
+	  with major number 10 and minor number 131 ("man mknod") will give
+	  you a thermometer inside your computer: reading from
+	  /dev/temperature yields one byte, the temperature in degrees
+	  Fahrenheit. This works only if you have a PCI-WDT501 watchdog board
+	  installed.
+
+	  If you want to enable the Fan Tachometer on the PCI-WDT501, then you
+	  can do this via the tachometer parameter. Only do this if you have a
+	  fan tachometer actually set up.
+
+#
+# USB-based Watchdog Cards
+#
+
+comment "USB-based Watchdog Cards"
+	depends on WATCHDOG && USB
+
+config USBPCWATCHDOG
+	tristate "Berkshire Products USB-PC Watchdog"
+	depends on WATCHDOG && USB
+	---help---
+	  This is the driver for the Berkshire Products USB-PC Watchdog card.
+	  This card simply watches your kernel to make sure it doesn't freeze,
+	  and if it does, it reboots your computer after a certain amount of
+	  time. The card can also monitor the internal temperature of the PC.
+	  More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pcwd_usb.
+
+	  Most people will say N.
+
+endmenu
diff --git a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile
new file mode 100644
index 0000000..1cd27ef
--- /dev/null
+++ b/drivers/char/watchdog/Makefile
@@ -0,0 +1,42 @@
+#
+# Makefile for the WatchDog device drivers.
+#
+
+obj-$(CONFIG_PCWATCHDOG) += pcwd.o
+obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
+obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o
+obj-$(CONFIG_IB700_WDT) += ib700wdt.o
+obj-$(CONFIG_MIXCOMWD) += mixcomwd.o
+obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o
+obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o
+obj-$(CONFIG_WDT) += wdt.o
+obj-$(CONFIG_WDTPCI) += wdt_pci.o
+obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
+obj-$(CONFIG_977_WATCHDOG) += wdt977.o
+obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o
+obj-$(CONFIG_MACHZ_WDT) += machzwd.o
+obj-$(CONFIG_SH_WDT) += shwdt.o
+obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o
+obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o
+obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o
+obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o
+obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o
+obj-$(CONFIG_SC520_WDT) += sc520_wdt.o
+obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o
+obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o
+obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
+obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o
+obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o
+obj-$(CONFIG_INDYDOG) += indydog.o
+obj-$(CONFIG_PCIPCWATCHDOG) += pcwd_pci.o
+obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o
+obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o
+obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o
+obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o
+
+# Only one watchdog can succeed. We probe the hardware watchdog
+# drivers first, then the softdog driver.  This means if your hardware
+# watchdog dies or is 'borrowed' for some reason the software watchdog
+# still gives you some cover.
+
+obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
diff --git a/drivers/char/watchdog/acquirewdt.c b/drivers/char/watchdog/acquirewdt.c
new file mode 100644
index 0000000..8f30212
--- /dev/null
+++ b/drivers/char/watchdog/acquirewdt.c
@@ -0,0 +1,332 @@
+/*
+ *	Acquire Single Board Computer Watchdog Timer driver
+ *
+ *      Based on wdt.c. Original copyright messages:
+ *
+ *	(c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *				http://www.redhat.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.
+ *
+ *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 1995    Alan Cox <alan@redhat.com>
+ *
+ *      14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ *          Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *          Can't add timeout - driver doesn't allow changing value
+ */
+
+/*
+ *	Theory of Operation:
+ *		The Watch-Dog Timer is provided to ensure that standalone
+ *		Systems can always recover from catastrophic conditions that
+ *		caused the CPU to crash. This condition may have occured by
+ *		external EMI or a software bug. When the CPU stops working
+ *		correctly, hardware on the board will either perform a hardware
+ *		reset (cold boot) or a non-maskable interrupt (NMI) to bring the
+ *		system back to a known state.
+ *
+ *		The Watch-Dog Timer is controlled by two I/O Ports.
+ *		  443 hex	- Read	- Enable or refresh the Watch-Dog Timer
+ *		  043 hex	- Read	- Disable the Watch-Dog Timer
+ *
+ *		To enable the Watch-Dog Timer, a read from I/O port 443h must
+ *		be performed. This will enable and activate the countdown timer
+ *		which will eventually time out and either reset the CPU or cause
+ *		an NMI depending on the setting of a jumper. To ensure that this
+ *		reset condition does not occur, the Watch-Dog Timer must be
+ *		periodically refreshed by reading the same I/O port 443h.
+ *		The Watch-Dog Timer is disabled by reading I/O port 043h.
+ *
+ *		The Watch-Dog Timer Time-Out Period is set via jumpers.
+ *		It can be 1, 2, 10, 20, 110 or 220 seconds.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define WATCHDOG_NAME "Acquire WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WATCHDOG_HEARTBEAT 0	/* There is no way to see what the correct time-out period is */
+
+static unsigned long acq_is_open;
+static char expect_close;
+
+/*
+ *	You must set these - there is no sane way to probe for this board.
+ */
+
+static int wdt_stop = 0x43;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
+
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ *	Kernel methods.
+ */
+
+static void acq_keepalive(void)
+{
+	/* Write a watchdog value */
+	inb_p(wdt_start);
+}
+
+static void acq_stop(void)
+{
+	/* Turn the card off */
+	inb_p(wdt_stop);
+}
+
+/*
+ *	/dev/watchdog handling.
+ */
+
+static ssize_t acq_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if(count) {
+		if (!nowayout) {
+			size_t i;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			expect_close = 0;
+
+			/* scan to see whether or not we got the magic character */
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, buf + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+
+		/* Well, anyhow someone wrote to us, we should return that favour */
+		acq_keepalive();
+	}
+	return count;
+}
+
+static int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	int options, retval = -EINVAL;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident =
+	{
+		.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "Acquire WDT",
+	};
+
+	switch(cmd)
+	{
+	case WDIOC_GETSUPPORT:
+	  return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+	  return put_user(0, p);
+
+	case WDIOC_KEEPALIVE:
+	  acq_keepalive();
+	  return 0;
+
+	case WDIOC_GETTIMEOUT:
+	  return put_user(WATCHDOG_HEARTBEAT, p);
+
+	case WDIOC_SETOPTIONS:
+	{
+	    if (get_user(options, p))
+	      return -EFAULT;
+
+	    if (options & WDIOS_DISABLECARD)
+	    {
+	      acq_stop();
+	      retval = 0;
+	    }
+
+	    if (options & WDIOS_ENABLECARD)
+	    {
+	      acq_keepalive();
+	      retval = 0;
+	    }
+
+	    return retval;
+	}
+
+	default:
+	  return -ENOIOCTLCMD;
+	}
+}
+
+static int acq_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &acq_is_open))
+		return -EBUSY;
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	/* Activate */
+	acq_keepalive();
+	return nonseekable_open(inode, file);
+}
+
+static int acq_close(struct inode *inode, struct file *file)
+{
+	if (expect_close == 42) {
+		acq_stop();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		acq_keepalive();
+	}
+	clear_bit(0, &acq_is_open);
+	expect_close = 0;
+	return 0;
+}
+
+/*
+ *	Notifier for system down
+ */
+
+static int acq_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if(code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the WDT off */
+		acq_stop();
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations acq_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= acq_write,
+	.ioctl		= acq_ioctl,
+	.open		= acq_open,
+	.release	= acq_close,
+};
+
+static struct miscdevice acq_miscdev=
+{
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &acq_fops,
+};
+
+/*
+ *	The WDT card needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block acq_notifier =
+{
+	.notifier_call = acq_notify_sys,
+};
+
+static int __init acq_init(void)
+{
+	int ret;
+
+	printk(KERN_INFO "WDT driver for Acquire single board computer initialising.\n");
+
+	if (wdt_stop != wdt_start) {
+		if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
+			printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+				wdt_stop);
+			ret = -EIO;
+			goto out;
+		}
+	}
+
+	if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
+		printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			wdt_start);
+		ret = -EIO;
+		goto unreg_stop;
+	}
+
+	ret = register_reboot_notifier(&acq_notifier);
+	if (ret != 0) {
+		printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		goto unreg_regions;
+	}
+
+	ret = misc_register(&acq_miscdev);
+	if (ret != 0) {
+		printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto unreg_reboot;
+	}
+
+	printk (KERN_INFO PFX "initialized. (nowayout=%d)\n",
+		nowayout);
+
+	return 0;
+
+unreg_reboot:
+	unregister_reboot_notifier(&acq_notifier);
+unreg_regions:
+	release_region(wdt_start, 1);
+unreg_stop:
+	if (wdt_stop != wdt_start)
+		release_region(wdt_stop, 1);
+out:
+	return ret;
+}
+
+static void __exit acq_exit(void)
+{
+	misc_deregister(&acq_miscdev);
+	unregister_reboot_notifier(&acq_notifier);
+	if(wdt_stop != wdt_start)
+		release_region(wdt_stop,1);
+	release_region(wdt_start,1);
+}
+
+module_init(acq_init);
+module_exit(acq_exit);
+
+MODULE_AUTHOR("David Woodhouse");
+MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/advantechwdt.c b/drivers/char/watchdog/advantechwdt.c
new file mode 100644
index 0000000..ea73c83
--- /dev/null
+++ b/drivers/char/watchdog/advantechwdt.c
@@ -0,0 +1,333 @@
+/*
+ *	Advantech Single Board Computer WDT driver
+ *
+ *	(c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ *	Based on acquirewdt.c which is based on wdt.c.
+ *	Original copyright messages:
+ *
+ *	(c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *				http://www.redhat.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.
+ *
+ *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 1995    Alan Cox <alan@redhat.com>
+ *
+ *	14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ *	    Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *
+ *	16-Oct-2002 Rob Radez <rob@osinvestor.com>
+ *	    Clean up ioctls, clean up init + exit, add expect close support,
+ *	    add wdt_start and wdt_stop as parameters.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define WATCHDOG_NAME "Advantech WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WATCHDOG_TIMEOUT 60		/* 60 sec default timeout */
+
+static unsigned long advwdt_is_open;
+static char adv_expect_close;
+
+/*
+ *	You must set these - there is no sane way to probe for this board.
+ *
+ *	To enable or restart, write the timeout value in seconds (1 to 63)
+ *	to I/O port wdt_start.  To disable, read I/O port wdt_stop.
+ *	Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but
+ *	check your manual (at least the PCA-6159 seems to be different -
+ *	the manual says wdt_stop is 0x43, not 0x443).
+ *	(0x43 is also a write-only control register for the 8254 timer!)
+ */
+
+static int wdt_stop = 0x443;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)");
+
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)");
+
+static int timeout = WATCHDOG_TIMEOUT;	/* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ *	Kernel methods.
+ */
+
+static void
+advwdt_ping(void)
+{
+	/* Write a watchdog value */
+	outb_p(timeout, wdt_start);
+}
+
+static void
+advwdt_disable(void)
+{
+	inb_p(wdt_stop);
+}
+
+static ssize_t
+advwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	if (count) {
+		if (!nowayout) {
+			size_t i;
+
+			adv_expect_close = 0;
+
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, buf+i))
+					return -EFAULT;
+				if (c == 'V')
+					adv_expect_close = 42;
+			}
+		}
+		advwdt_ping();
+	}
+	return count;
+}
+
+static int
+advwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	  unsigned long arg)
+{
+	int new_timeout;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "Advantech WDT",
+	};
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+	  if (copy_to_user(argp, &ident, sizeof(ident)))
+	    return -EFAULT;
+	  break;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+	  return put_user(0, p);
+
+	case WDIOC_KEEPALIVE:
+	  advwdt_ping();
+	  break;
+
+	case WDIOC_SETTIMEOUT:
+	  if (get_user(new_timeout, p))
+		  return -EFAULT;
+	  if ((new_timeout < 1) || (new_timeout > 63))
+		  return -EINVAL;
+	  timeout = new_timeout;
+	  advwdt_ping();
+	  /* Fall */
+
+	case WDIOC_GETTIMEOUT:
+	  return put_user(timeout, p);
+
+	case WDIOC_SETOPTIONS:
+	{
+	  int options, retval = -EINVAL;
+
+	  if (get_user(options, p))
+	    return -EFAULT;
+
+	  if (options & WDIOS_DISABLECARD) {
+	    advwdt_disable();
+	    retval = 0;
+	  }
+
+	  if (options & WDIOS_ENABLECARD) {
+	    advwdt_ping();
+	    retval = 0;
+	  }
+
+	  return retval;
+	}
+
+	default:
+	  return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static int
+advwdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &advwdt_is_open))
+		return -EBUSY;
+	/*
+	 *	Activate
+	 */
+
+	advwdt_ping();
+	return nonseekable_open(inode, file);
+}
+
+static int
+advwdt_close(struct inode *inode, struct file *file)
+{
+	if (adv_expect_close == 42) {
+		advwdt_disable();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		advwdt_ping();
+	}
+	clear_bit(0, &advwdt_is_open);
+	adv_expect_close = 0;
+	return 0;
+}
+
+/*
+ *	Notifier for system down
+ */
+
+static int
+advwdt_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT) {
+		/* Turn the WDT off */
+		advwdt_disable();
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations advwdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= advwdt_write,
+	.ioctl		= advwdt_ioctl,
+	.open		= advwdt_open,
+	.release	= advwdt_close,
+};
+
+static struct miscdevice advwdt_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &advwdt_fops,
+};
+
+/*
+ *	The WDT needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block advwdt_notifier = {
+	.notifier_call = advwdt_notify_sys,
+};
+
+static int __init
+advwdt_init(void)
+{
+	int ret;
+
+	printk(KERN_INFO "WDT driver for Advantech single board computer initialising.\n");
+
+	if (timeout < 1 || timeout > 63) {
+		timeout = WATCHDOG_TIMEOUT;
+		printk (KERN_INFO PFX "timeout value must be 1<=x<=63, using %d\n",
+			timeout);
+	}
+
+	if (wdt_stop != wdt_start) {
+		if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
+			printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+				wdt_stop);
+			ret = -EIO;
+			goto out;
+		}
+	}
+
+	if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
+		printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			wdt_start);
+		ret = -EIO;
+		goto unreg_stop;
+	}
+
+	ret = register_reboot_notifier(&advwdt_notifier);
+	if (ret != 0) {
+		printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		goto unreg_regions;
+	}
+
+	ret = misc_register(&advwdt_miscdev);
+	if (ret != 0) {
+		printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto unreg_reboot;
+	}
+
+	printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
+		timeout, nowayout);
+
+out:
+	return ret;
+unreg_reboot:
+	unregister_reboot_notifier(&advwdt_notifier);
+unreg_regions:
+	release_region(wdt_start, 1);
+unreg_stop:
+	if (wdt_stop != wdt_start)
+		release_region(wdt_stop, 1);
+	goto out;
+}
+
+static void __exit
+advwdt_exit(void)
+{
+	misc_deregister(&advwdt_miscdev);
+	unregister_reboot_notifier(&advwdt_notifier);
+	if(wdt_stop != wdt_start)
+		release_region(wdt_stop,1);
+	release_region(wdt_start,1);
+}
+
+module_init(advwdt_init);
+module_exit(advwdt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>");
+MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/alim1535_wdt.c b/drivers/char/watchdog/alim1535_wdt.c
new file mode 100644
index 0000000..35dcbf8
--- /dev/null
+++ b/drivers/char/watchdog/alim1535_wdt.c
@@ -0,0 +1,463 @@
+/*
+ *	Watchdog for the 7101 PMU version found in the ALi M1535 chipsets
+ *
+ *	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.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#define WATCHDOG_NAME "ALi_M1535"
+#define PFX WATCHDOG_NAME ": "
+#define WATCHDOG_TIMEOUT 60	/* 60 sec default timeout */
+
+/* internal variables */
+static unsigned long ali_is_open;
+static char ali_expect_release;
+static struct pci_dev *ali_pci;
+static u32 ali_timeout_bits;	/* stores the computed timeout */
+static spinlock_t ali_lock;	/* Guards the hardware */
+
+/* module parameters */
+static int timeout = WATCHDOG_TIMEOUT;
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (0<timeout<18000, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ *	ali_start	-	start watchdog countdown
+ *
+ *	Starts the timer running providing the timer has a counter
+ *	configuration set.
+ */
+
+static void ali_start(void)
+{
+	u32 val;
+
+	spin_lock(&ali_lock);
+
+	pci_read_config_dword(ali_pci, 0xCC, &val);
+	val &= ~0x3F;	/* Mask count */
+	val |= (1<<25) | ali_timeout_bits;
+	pci_write_config_dword(ali_pci, 0xCC, val);
+
+	spin_unlock(&ali_lock);
+}
+
+/*
+ *	ali_stop	-	stop the timer countdown
+ *
+ *	Stop the ALi watchdog countdown
+ */
+
+static void ali_stop(void)
+{
+	u32 val;
+
+	spin_lock(&ali_lock);
+
+	pci_read_config_dword(ali_pci, 0xCC, &val);
+	val &= ~0x3F;	/* Mask count to zero (disabled) */
+	val &= ~(1<<25);/* and for safety mask the reset enable */
+	pci_write_config_dword(ali_pci, 0xCC, val);
+
+	spin_unlock(&ali_lock);
+}
+
+/*
+ *	ali_keepalive	-	send a keepalive to the watchdog
+ *
+ *      Send a keepalive to the timer (actually we restart the timer).
+ */
+
+static void ali_keepalive(void)
+{
+	ali_start();
+}
+
+/*
+ *	ali_settimer	-	compute the timer reload value
+ *	@t: time in seconds
+ *
+ *	Computes the timeout values needed
+ */
+
+static int ali_settimer(int t)
+{
+	if(t < 0)
+		return -EINVAL;
+	else if(t < 60)
+		ali_timeout_bits = t|(1<<6);
+	else if(t < 3600)
+		ali_timeout_bits = (t/60)|(1<<7);
+	else if(t < 18000)
+		ali_timeout_bits = (t/300)|(1<<6)|(1<<7);
+	else return -EINVAL;
+
+	timeout = t;
+	return 0;
+}
+
+/*
+ *	/dev/watchdog handling
+ */
+
+/*
+ *	ali_write	-	writes to ALi watchdog
+ *	@file: file from VFS
+ *	@data: user address of data
+ *	@len: length of data
+ *	@ppos: pointer to the file offset
+ *
+ *	Handle a write to the ALi watchdog. Writing to the file pings
+ *	the watchdog and resets it. Writing the magic 'V' sequence allows
+ *	the next close to turn off the watchdog.
+ */
+
+static ssize_t ali_write(struct file *file, const char __user *data,
+			      size_t len, loff_t * ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			ali_expect_release = 0;
+
+			/* scan to see whether or not we got the magic character */
+			for (i = 0; i != len; i++) {
+				char c;
+				if(get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					ali_expect_release = 42;
+			}
+		}
+
+		/* someone wrote to us, we should reload the timer */
+		ali_start();
+	}
+	return len;
+}
+
+/*
+ *	ali_ioctl	-	handle watchdog ioctls
+ *	@inode: VFS inode
+ *	@file: VFS file pointer
+ *	@cmd: ioctl number
+ *	@arg: arguments to the ioctl
+ *
+ *	Handle the watchdog ioctls supported by the ALi driver. Really
+ *	we want an extension to enable irq ack monitoring and the like
+ */
+
+static int ali_ioctl(struct inode *inode, struct file *file,
+			  unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options =		WDIOF_KEEPALIVEPING |
+					WDIOF_SETTIMEOUT |
+					WDIOF_MAGICCLOSE,
+		.firmware_version =	0,
+		.identity =		"ALi M1535 WatchDog Timer",
+	};
+
+	switch (cmd) {
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident,
+				sizeof (ident)) ? -EFAULT : 0;
+
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+
+		case WDIOC_KEEPALIVE:
+			ali_keepalive();
+			return 0;
+
+		case WDIOC_SETOPTIONS:
+		{
+			int new_options, retval = -EINVAL;
+
+			if (get_user (new_options, p))
+				return -EFAULT;
+
+			if (new_options & WDIOS_DISABLECARD) {
+				ali_stop();
+				retval = 0;
+			}
+
+			if (new_options & WDIOS_ENABLECARD) {
+				ali_start();
+				retval = 0;
+			}
+
+			return retval;
+		}
+
+		case WDIOC_SETTIMEOUT:
+		{
+			int new_timeout;
+
+			if (get_user(new_timeout, p))
+				return -EFAULT;
+
+			if (ali_settimer(new_timeout))
+			    return -EINVAL;
+
+			ali_keepalive();
+			/* Fall */
+		}
+
+		case WDIOC_GETTIMEOUT:
+			return put_user(timeout, p);
+
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+/*
+ *	ali_open	-	handle open of ali watchdog
+ *	@inode: inode from VFS
+ *	@file: file from VFS
+ *
+ *	Open the ALi watchdog device. Ensure only one person opens it
+ *	at a time. Also start the watchdog running.
+ */
+
+static int ali_open(struct inode *inode, struct file *file)
+{
+	/* /dev/watchdog can only be opened once */
+	if (test_and_set_bit(0, &ali_is_open))
+		return -EBUSY;
+
+	/* Activate */
+	ali_start();
+	return nonseekable_open(inode, file);
+}
+
+/*
+ *	ali_release	-	close an ALi watchdog
+ *	@inode: inode from VFS
+ *	@file: file from VFS
+ *
+ *	Close the ALi watchdog device. Actual shutdown of the timer
+ *	only occurs if the magic sequence has been set.
+ */
+
+static int ali_release(struct inode *inode, struct file *file)
+{
+	/*
+	 *      Shut off the timer.
+	 */
+	if (ali_expect_release == 42) {
+		ali_stop();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		ali_keepalive();
+	}
+	clear_bit(0, &ali_is_open);
+	ali_expect_release = 0;
+	return 0;
+}
+
+/*
+ *	ali_notify_sys	-	System down notifier
+ *
+ *	Notifier for system down
+ */
+
+
+static int ali_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the WDT off */
+		ali_stop();
+	}
+
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Data for PCI driver interface
+ *
+ *	This data only exists for exporting the supported
+ *	PCI ids via MODULE_DEVICE_TABLE.  We do not actually
+ *	register a pci_driver, because someone else might one day
+ *	want to register another driver on the same PCI id.
+ */
+
+static struct pci_device_id ali_pci_tbl[] = {
+	{ PCI_VENDOR_ID_AL, 1535, PCI_ANY_ID, PCI_ANY_ID,},
+	{ 0, },
+};
+MODULE_DEVICE_TABLE(pci, ali_pci_tbl);
+
+/*
+ *	ali_find_watchdog	-	find a 1535 and 7101
+ *
+ *	Scans the PCI hardware for a 1535 series bridge and matching 7101
+ *	watchdog device. This may be overtight but it is better to be safe
+ */
+
+static int __init ali_find_watchdog(void)
+{
+	struct pci_dev *pdev;
+	u32 wdog;
+
+	/* Check for a 1535 series bridge */
+	pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x1535, NULL);
+	if(pdev == NULL)
+		return -ENODEV;
+
+	/* Check for the a 7101 PMU */
+	pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x7101, NULL);
+	if(pdev == NULL)
+		return -ENODEV;
+
+	if(pci_enable_device(pdev))
+		return -EIO;
+
+	ali_pci = pdev;
+
+	/*
+	 *	Initialize the timer bits
+	 */
+	pci_read_config_dword(pdev, 0xCC, &wdog);
+
+	wdog &= ~0x3F;		/* Timer bits */
+	wdog &= ~((1<<27)|(1<<26)|(1<<25)|(1<<24));	/* Issued events */
+	wdog &= ~((1<<16)|(1<<13)|(1<<12)|(1<<11)|(1<<10)|(1<<9));	/* No monitor bits */
+
+	pci_write_config_dword(pdev, 0xCC, wdog);
+
+	return 0;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations ali_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.write =	ali_write,
+	.ioctl =	ali_ioctl,
+	.open =		ali_open,
+	.release =	ali_release,
+};
+
+static struct miscdevice ali_miscdev = {
+	.minor =	WATCHDOG_MINOR,
+	.name =		"watchdog",
+	.fops =		&ali_fops,
+};
+
+static struct notifier_block ali_notifier = {
+	.notifier_call =	ali_notify_sys,
+};
+
+/*
+ *	watchdog_init	-	module initialiser
+ *
+ *	Scan for a suitable watchdog and if so initialize it. Return an error
+ *	if we cannot, the error causes the module to unload
+ */
+
+static int __init watchdog_init(void)
+{
+	int ret;
+
+	spin_lock_init(&ali_lock);
+
+	/* Check whether or not the hardware watchdog is there */
+	if (ali_find_watchdog() != 0) {
+		return -ENODEV;
+	}
+
+	/* Check that the timeout value is within it's range ; if not reset to the default */
+	if (timeout < 1 || timeout >= 18000) {
+		timeout = WATCHDOG_TIMEOUT;
+		printk(KERN_INFO PFX "timeout value must be 0<timeout<18000, using %d\n",
+			timeout);
+	}
+
+	/* Calculate the watchdog's timeout */
+	ali_settimer(timeout);
+
+	ret = misc_register(&ali_miscdev);
+	if (ret != 0) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto out;
+	}
+
+	ret = register_reboot_notifier(&ali_notifier);
+	if (ret != 0) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		goto unreg_miscdev;
+	}
+
+	printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
+		timeout, nowayout);
+
+out:
+	return ret;
+unreg_miscdev:
+	misc_deregister(&ali_miscdev);
+	goto out;
+}
+
+/*
+ *	watchdog_exit	-	module de-initialiser
+ *
+ *	Called while unloading a successfully installed watchdog module.
+ */
+
+static void __exit watchdog_exit(void)
+{
+	/* Stop the timer before we leave */
+	ali_stop();
+
+	/* Deregister */
+	unregister_reboot_notifier(&ali_notifier);
+	misc_deregister(&ali_miscdev);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/alim7101_wdt.c b/drivers/char/watchdog/alim7101_wdt.c
new file mode 100644
index 0000000..90c091d
--- /dev/null
+++ b/drivers/char/watchdog/alim7101_wdt.c
@@ -0,0 +1,421 @@
+/*
+ *	ALi M7101 PMU Computer Watchdog Timer driver
+ *
+ *	Based on w83877f_wdt.c by Scott Jennings <linuxdrivers@oro.net>
+ *	and the Cobalt kernel WDT timer driver by Tim Hockin
+ *	                                      <thockin@cobaltnet.com>
+ *
+ *	(c)2002 Steve Hill <steve@navaho.co.uk>
+ *
+ *  This WDT driver is different from most other Linux WDT
+ *  drivers in that the driver will ping the watchdog by itself,
+ *  because this particular WDT has a very short timeout (1.6
+ *  seconds) and it would be insane to count on any userspace
+ *  daemon always getting scheduled within that time frame.
+ *
+ *  Additions:
+ *   Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs
+ *                  found on very old cobalt hardware.
+ *                  -- Mike Waychison <michael.waychison@sun.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define OUR_NAME "alim7101_wdt"
+#define PFX OUR_NAME ": "
+
+#define WDT_ENABLE 0x9C
+#define WDT_DISABLE 0x8C
+
+#define ALI_7101_WDT    0x92
+#define ALI_7101_GPIO   0x7D
+#define ALI_7101_GPIO_O 0x7E
+#define ALI_WDT_ARM     0x01
+
+/*
+ * We're going to use a 1 second timeout.
+ * If we reset the watchdog every ~250ms we should be safe.  */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ */
+
+#define WATCHDOG_TIMEOUT 30            /* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+static int use_gpio = 0; /* Use the pic (for a1d revision alim7101) */
+module_param(use_gpio, int, 0);
+MODULE_PARM_DESC(use_gpio, "Use the gpio watchdog.  (required by old cobalt boards)");
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static char wdt_expect_close;
+static struct pci_dev *alim7101_pmu;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ *	Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
+	 * we agree to ping the WDT
+	 */
+	char	tmp;
+
+	if(time_before(jiffies, next_heartbeat))
+	{
+		/* Ping the WDT (this is actually a disarm/arm sequence) */
+		pci_read_config_byte(alim7101_pmu, 0x92, &tmp);
+		pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
+		pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM));
+		if (use_gpio) {
+			pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp);
+			pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp
+					| 0x20);
+			pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp
+					& ~0x20);
+		}
+	} else {
+		printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+	}
+	/* Re-set the timer interval */
+	timer.expires = jiffies + WDT_INTERVAL;
+	add_timer(&timer);
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_change(int writeval)
+{
+	char	tmp;
+
+	pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp);
+	if (writeval == WDT_ENABLE) {
+		pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM));
+		if (use_gpio) {
+			pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp);
+			pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp & ~0x20);
+		}
+
+	} else {
+		pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
+		if (use_gpio) {
+			pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp);
+			pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | 0x20);
+		}
+	}
+}
+
+static void wdt_startup(void)
+{
+	next_heartbeat = jiffies + (timeout * HZ);
+
+	/* We must enable before we kick off the timer in case the timer
+	   occurs as we ping it */
+
+	wdt_change(WDT_ENABLE);
+
+	/* Start the timer */
+	timer.expires = jiffies + WDT_INTERVAL;
+	add_timer(&timer);
+
+
+	printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
+}
+
+static void wdt_turnoff(void)
+{
+	/* Stop the timer */
+	del_timer_sync(&timer);
+	wdt_change(WDT_DISABLE);
+	printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+	/* user land ping */
+	next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if(count) {
+		if (!nowayout) {
+			size_t ofs;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			wdt_expect_close = 0;
+
+			/* now scan */
+			for (ofs = 0; ofs != count; ofs++) {
+				char c;
+				if (get_user(c, buf+ofs))
+					return -EFAULT;
+				if (c == 'V')
+					wdt_expect_close = 42;
+			}
+		}
+		/* someone wrote to us, we should restart timer */
+		wdt_keepalive();
+	}
+	return count;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+	/* Just in case we're already talking to someone... */
+	if(test_and_set_bit(0, &wdt_is_open))
+		return -EBUSY;
+	/* Good, fire up the show */
+	wdt_startup();
+	return nonseekable_open(inode, file);
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+	if(wdt_expect_close == 42)
+		wdt_turnoff();
+	else {
+		/* wim: shouldn't there be a: del_timer(&timer); */
+		printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
+	}
+	clear_bit(0, &wdt_is_open);
+	wdt_expect_close = 0;
+	return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident =
+	{
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "ALiM7101",
+	};
+
+	switch(cmd)
+	{
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+		case WDIOC_KEEPALIVE:
+			wdt_keepalive();
+			return 0;
+		case WDIOC_SETOPTIONS:
+		{
+			int new_options, retval = -EINVAL;
+
+			if(get_user(new_options, p))
+				return -EFAULT;
+
+			if(new_options & WDIOS_DISABLECARD) {
+				wdt_turnoff();
+				retval = 0;
+			}
+
+			if(new_options & WDIOS_ENABLECARD) {
+				wdt_startup();
+				retval = 0;
+			}
+
+			return retval;
+		}
+		case WDIOC_SETTIMEOUT:
+		{
+			int new_timeout;
+
+			if(get_user(new_timeout, p))
+				return -EFAULT;
+
+			if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
+				return -EINVAL;
+
+			timeout = new_timeout;
+			wdt_keepalive();
+			/* Fall through */
+		}
+		case WDIOC_GETTIMEOUT:
+			return put_user(timeout, p);
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+static struct file_operations wdt_fops = {
+	.owner=		THIS_MODULE,
+	.llseek=	no_llseek,
+	.write=		fop_write,
+	.open=		fop_open,
+	.release=	fop_close,
+	.ioctl=		fop_ioctl,
+};
+
+static struct miscdevice wdt_miscdev = {
+	.minor=WATCHDOG_MINOR,
+	.name="watchdog",
+	.fops=&wdt_fops,
+};
+
+/*
+ *	Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code==SYS_DOWN || code==SYS_HALT)
+		wdt_turnoff();
+
+	if (code==SYS_RESTART) {
+		/*
+		 * Cobalt devices have no way of rebooting themselves other than
+		 * getting the watchdog to pull reset, so we restart the watchdog on
+		 * reboot with no heartbeat
+		 */
+		wdt_change(WDT_ENABLE);
+		printk(KERN_INFO PFX "Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second.\n");
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	The WDT needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier=
+{
+	.notifier_call = wdt_notify_sys,
+};
+
+static void __exit alim7101_wdt_unload(void)
+{
+	wdt_turnoff();
+	/* Deregister */
+	misc_deregister(&wdt_miscdev);
+	unregister_reboot_notifier(&wdt_notifier);
+}
+
+static int __init alim7101_wdt_init(void)
+{
+	int rc = -EBUSY;
+	struct pci_dev *ali1543_south;
+	char tmp;
+
+	printk(KERN_INFO PFX "Steve Hill <steve@navaho.co.uk>.\n");
+	alim7101_pmu = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,NULL);
+	if (!alim7101_pmu) {
+		printk(KERN_INFO PFX "ALi M7101 PMU not present - WDT not set\n");
+		return -EBUSY;
+	}
+
+	/* Set the WDT in the PMU to 1 second */
+	pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02);
+
+	ali1543_south = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, NULL);
+	if (!ali1543_south) {
+		printk(KERN_INFO PFX "ALi 1543 South-Bridge not present - WDT not set\n");
+		return -EBUSY;
+	}
+	pci_read_config_byte(ali1543_south, 0x5e, &tmp);
+	if ((tmp & 0x1e) == 0x00) {
+		if (!use_gpio) {
+			printk(KERN_INFO PFX "Detected old alim7101 revision 'a1d'.  If this is a cobalt board, set the 'use_gpio' module parameter.\n");
+			return -EBUSY;
+		} 
+		nowayout = 1;
+	} else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) {
+		printk(KERN_INFO PFX "ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n");
+		return -EBUSY;
+	}
+
+	if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
+	{
+		timeout = WATCHDOG_TIMEOUT;
+		printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
+			timeout);
+	}
+
+	init_timer(&timer);
+	timer.function = wdt_timer_ping;
+	timer.data = 1;
+
+	rc = misc_register(&wdt_miscdev);
+	if (rc) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			wdt_miscdev.minor, rc);
+		goto err_out;
+	}
+
+	rc = register_reboot_notifier(&wdt_notifier);
+	if (rc) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			rc);
+		goto err_out_miscdev;
+	}
+
+	if (nowayout) {
+		__module_get(THIS_MODULE);
+	}
+
+	printk(KERN_INFO PFX "WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n",
+		timeout, nowayout);
+	return 0;
+
+err_out_miscdev:
+	misc_deregister(&wdt_miscdev);
+err_out:
+	return rc;
+}
+
+module_init(alim7101_wdt_init);
+module_exit(alim7101_wdt_unload);
+
+MODULE_AUTHOR("Steve Hill");
+MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/cpu5wdt.c b/drivers/char/watchdog/cpu5wdt.c
new file mode 100644
index 0000000..2865dac
--- /dev/null
+++ b/drivers/char/watchdog/cpu5wdt.c
@@ -0,0 +1,303 @@
+/*
+ * sma cpu5 watchdog driver
+ *
+ * Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.de>
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#include <linux/watchdog.h>
+
+/* adjustable parameters */
+
+static int verbose = 0;
+static int port = 0x91;
+static int ticks = 10000;
+
+#define PFX			"cpu5wdt: "
+
+#define CPU5WDT_EXTENT          0x0A
+
+#define CPU5WDT_STATUS_REG      0x00
+#define CPU5WDT_TIME_A_REG      0x02
+#define CPU5WDT_TIME_B_REG      0x03
+#define CPU5WDT_MODE_REG        0x04
+#define CPU5WDT_TRIGGER_REG     0x07
+#define CPU5WDT_ENABLE_REG      0x08
+#define CPU5WDT_RESET_REG       0x09
+
+#define CPU5WDT_INTERVAL	(HZ/10+1)
+
+/* some device data */
+
+static struct {
+	struct semaphore stop;
+	volatile int running;
+	struct timer_list timer;
+	volatile int queue;
+	int default_ticks;
+	unsigned long inuse;
+} cpu5wdt_device;
+
+/* generic helper functions */
+
+static void cpu5wdt_trigger(unsigned long unused)
+{
+	if ( verbose > 2 )
+		printk(KERN_DEBUG PFX "trigger at %i ticks\n", ticks);
+
+	if( cpu5wdt_device.running )
+		ticks--;
+
+	/* keep watchdog alive */
+	outb(1, port + CPU5WDT_TRIGGER_REG);
+
+	/* requeue?? */
+	if( cpu5wdt_device.queue && ticks ) {
+		cpu5wdt_device.timer.expires = jiffies + CPU5WDT_INTERVAL;
+		add_timer(&cpu5wdt_device.timer);
+	}
+	else {
+		/* ticks doesn't matter anyway */
+		up(&cpu5wdt_device.stop);
+	}
+
+}
+
+static void cpu5wdt_reset(void)
+{
+	ticks = cpu5wdt_device.default_ticks;
+
+	if ( verbose )
+		printk(KERN_DEBUG PFX "reset (%i ticks)\n", (int) ticks);
+
+}
+
+static void cpu5wdt_start(void)
+{
+	if ( !cpu5wdt_device.queue ) {
+		cpu5wdt_device.queue = 1;
+		outb(0, port + CPU5WDT_TIME_A_REG);
+		outb(0, port + CPU5WDT_TIME_B_REG);
+		outb(1, port + CPU5WDT_MODE_REG);
+		outb(0, port + CPU5WDT_RESET_REG);
+		outb(0, port + CPU5WDT_ENABLE_REG);
+		cpu5wdt_device.timer.expires = jiffies + CPU5WDT_INTERVAL;
+		add_timer(&cpu5wdt_device.timer);
+	}
+	/* if process dies, counter is not decremented */
+	cpu5wdt_device.running++;
+}
+
+static int cpu5wdt_stop(void)
+{
+	if ( cpu5wdt_device.running )
+		cpu5wdt_device.running = 0;
+
+	ticks = cpu5wdt_device.default_ticks;
+
+	if ( verbose )
+		printk(KERN_CRIT PFX "stop not possible\n");
+
+	return -EIO;
+}
+
+/* filesystem operations */
+
+static int cpu5wdt_open(struct inode *inode, struct file *file)
+{
+	if ( test_and_set_bit(0, &cpu5wdt_device.inuse) )
+		return -EBUSY;
+
+	return nonseekable_open(inode, file);
+}
+
+static int cpu5wdt_release(struct inode *inode, struct file *file)
+{
+	clear_bit(0, &cpu5wdt_device.inuse);
+	return 0;
+}
+
+static int cpu5wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	unsigned int value;
+	static struct watchdog_info ident =
+	{
+		.options = WDIOF_CARDRESET,
+		.identity = "CPU5 WDT",
+	};
+
+	switch(cmd) {
+		case WDIOC_KEEPALIVE:
+			cpu5wdt_reset();
+			break;
+		case WDIOC_GETSTATUS:
+			value = inb(port + CPU5WDT_STATUS_REG);
+			value = (value >> 2) & 1;
+			if ( copy_to_user(argp, &value, sizeof(int)) )
+				return -EFAULT;
+			break;
+		case WDIOC_GETSUPPORT:
+			if ( copy_to_user(argp, &ident, sizeof(ident)) )
+				return -EFAULT;
+			break;
+		case WDIOC_SETOPTIONS:
+			if ( copy_from_user(&value, argp, sizeof(int)) )
+				return -EFAULT;
+			switch(value) {
+				case WDIOS_ENABLECARD:
+					cpu5wdt_start();
+					break;
+				case WDIOS_DISABLECARD:
+					return cpu5wdt_stop();
+				default:
+					return -EINVAL;
+			}
+			break;
+		default:
+    			return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static ssize_t cpu5wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	if ( !count )
+		return -EIO;
+
+	cpu5wdt_reset();
+
+	return count;
+}
+
+static struct file_operations cpu5wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.ioctl		= cpu5wdt_ioctl,
+	.open		= cpu5wdt_open,
+	.write		= cpu5wdt_write,
+	.release	= cpu5wdt_release,
+};
+
+static struct miscdevice cpu5wdt_misc = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &cpu5wdt_fops,
+};
+
+/* init/exit function */
+
+static int __devinit cpu5wdt_init(void)
+{
+	unsigned int val;
+	int err;
+
+	if ( verbose )
+		printk(KERN_DEBUG PFX "port=0x%x, verbose=%i\n", port, verbose);
+
+	if ( (err = misc_register(&cpu5wdt_misc)) < 0 ) {
+		printk(KERN_ERR PFX "misc_register failed\n");
+		goto no_misc;
+	}
+
+	if ( !request_region(port, CPU5WDT_EXTENT, PFX) ) {
+		printk(KERN_ERR PFX "request_region failed\n");
+		err = -EBUSY;
+		goto no_port;
+	}
+
+	/* watchdog reboot? */
+	val = inb(port + CPU5WDT_STATUS_REG);
+	val = (val >> 2) & 1;
+	if ( !val )
+		printk(KERN_INFO PFX "sorry, was my fault\n");
+
+	init_MUTEX_LOCKED(&cpu5wdt_device.stop);
+	cpu5wdt_device.queue = 0;
+
+	clear_bit(0, &cpu5wdt_device.inuse);
+
+	init_timer(&cpu5wdt_device.timer);
+	cpu5wdt_device.timer.function = cpu5wdt_trigger;
+	cpu5wdt_device.timer.data = 0;
+
+	cpu5wdt_device.default_ticks = ticks;
+
+	printk(KERN_INFO PFX "init success\n");
+
+	return 0;
+
+no_port:
+	misc_deregister(&cpu5wdt_misc);
+no_misc:
+	return err;
+}
+
+static int __devinit cpu5wdt_init_module(void)
+{
+	return cpu5wdt_init();
+}
+
+static void __devexit cpu5wdt_exit(void)
+{
+	if ( cpu5wdt_device.queue ) {
+		cpu5wdt_device.queue = 0;
+		down(&cpu5wdt_device.stop);
+	}
+
+	misc_deregister(&cpu5wdt_misc);
+
+	release_region(port, CPU5WDT_EXTENT);
+
+}
+
+static void __devexit cpu5wdt_exit_module(void)
+{
+	cpu5wdt_exit();
+}
+
+/* module entry points */
+
+module_init(cpu5wdt_init_module);
+module_exit(cpu5wdt_exit_module);
+
+MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>");
+MODULE_DESCRIPTION("sma cpu5 watchdog driver");
+MODULE_SUPPORTED_DEVICE("sma cpu5 watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+module_param(port, int, 0);
+MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91");
+
+module_param(verbose, int, 0);
+MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)");
+
+module_param(ticks, int, 0);
+MODULE_PARM_DESC(ticks, "count down ticks, default is 10000");
diff --git a/drivers/char/watchdog/eurotechwdt.c b/drivers/char/watchdog/eurotechwdt.c
new file mode 100644
index 0000000..d10e554
--- /dev/null
+++ b/drivers/char/watchdog/eurotechwdt.c
@@ -0,0 +1,474 @@
+/*
+ *	Eurotech CPU-1220/1410 on board WDT driver
+ *
+ *	(c) Copyright 2001 Ascensit <support@ascensit.com>
+ *	(c) Copyright 2001 Rodolfo Giometti <giometti@ascensit.com>
+ *	(c) Copyright 2002 Rob Radez <rob@osinvestor.com>
+ *
+ *	Based on wdt.c.
+ *	Original copyright messages:
+ *
+ *      (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *                              http://www.redhat.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.
+ *
+ *      Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *      warranty for any of this software. This material is provided
+ *      "AS-IS" and at no charge.
+ *
+ *      (c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>*
+ */
+
+/* Changelog:
+ *
+ * 2002/04/25 - Rob Radez
+ *	clean up #includes
+ *	clean up locking
+ *	make __setup param unique
+ *	proper options in watchdog_info
+ *	add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls
+ *	add expect_close support
+ *
+ * 2001 - Rodolfo Giometti
+ *	Initial release
+ *
+ * 2002.05.30 - Joel Becker <joel.becker@oracle.com>
+ * 	Added Matt Domsch's nowayout module option.
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+static unsigned long eurwdt_is_open;
+static int eurwdt_timeout;
+static char eur_expect_close;
+
+/*
+ * You must set these - there is no sane way to probe for this board.
+ * You can use eurwdt=x,y to set these now.
+ */
+
+static int io = 0x3f0;
+static int irq = 10;
+static char *ev = "int";
+
+#define WDT_TIMEOUT		60                /* 1 minute */
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Some symbolic names
+ */
+
+#define WDT_CTRL_REG		0x30
+#define WDT_OUTPIN_CFG		0xe2
+#define WDT_EVENT_INT		0x00
+#define WDT_EVENT_REBOOT	0x08
+#define WDT_UNIT_SEL		0xf1
+#define WDT_UNIT_SECS		0x80
+#define WDT_TIMEOUT_VAL		0xf2
+#define WDT_TIMER_CFG		0xf3
+
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "Eurotech WDT io port (default=0x3f0)");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "Eurotech WDT irq (default=10)");
+module_param(ev, charp, 0);
+MODULE_PARM_DESC(ev, "Eurotech WDT event type (default is `int')");
+
+
+/*
+ * Programming support
+ */
+
+static inline void eurwdt_write_reg(u8 index, u8 data)
+{
+	outb(index, io);
+	outb(data, io+1);
+}
+
+static inline void eurwdt_lock_chip(void)
+{
+	outb(0xaa, io);
+}
+
+static inline void eurwdt_unlock_chip(void)
+{
+	outb(0x55, io);
+	eurwdt_write_reg(0x07, 0x08);	/* set the logical device */
+}
+
+static inline void eurwdt_set_timeout(int timeout)
+{
+	eurwdt_write_reg(WDT_TIMEOUT_VAL, (u8) timeout);
+}
+
+static inline void eurwdt_disable_timer(void)
+{
+	eurwdt_set_timeout(0);
+}
+
+static void eurwdt_activate_timer(void)
+{
+	eurwdt_disable_timer();
+	eurwdt_write_reg(WDT_CTRL_REG, 0x01);	/* activate the WDT */
+	eurwdt_write_reg(WDT_OUTPIN_CFG, !strcmp("int", ev) ? WDT_EVENT_INT : WDT_EVENT_REBOOT);
+
+	/* Setting interrupt line */
+	if (irq == 2 || irq > 15 || irq < 0) {
+		printk(KERN_ERR ": invalid irq number\n");
+		irq = 0;	/* if invalid we disable interrupt */
+	}
+	if (irq == 0)
+		printk(KERN_INFO ": interrupt disabled\n");
+
+	eurwdt_write_reg(WDT_TIMER_CFG, irq<<4);
+
+	eurwdt_write_reg(WDT_UNIT_SEL, WDT_UNIT_SECS);	/* we use seconds */
+	eurwdt_set_timeout(0);	/* the default timeout */
+}
+
+
+/*
+ * Kernel methods.
+ */
+
+static irqreturn_t eurwdt_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	printk(KERN_CRIT "timeout WDT timeout\n");
+
+#ifdef ONLY_TESTING
+	printk(KERN_CRIT "Would Reboot.\n");
+#else
+	printk(KERN_CRIT "Initiating system reboot.\n");
+	machine_restart(NULL);
+#endif
+	return IRQ_HANDLED;
+}
+
+
+/**
+ * eurwdt_ping:
+ *
+ * Reload counter one with the watchdog timeout.
+ */
+
+static void eurwdt_ping(void)
+{
+	/* Write the watchdog default value */
+	eurwdt_set_timeout(eurwdt_timeout);
+}
+
+/**
+ * eurwdt_write:
+ * @file: file handle to the watchdog
+ * @buf: buffer to write (unused as data does not matter here
+ * @count: count of bytes
+ * @ppos: pointer to the position to write. No seeks allowed
+ *
+ * A write to a watchdog device is defined as a keepalive signal. Any
+ * write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t eurwdt_write(struct file *file, const char __user *buf,
+size_t count, loff_t *ppos)
+{
+	if (count) 	{
+		if (!nowayout) {
+			size_t i;
+
+			eur_expect_close = 0;
+
+			for (i = 0; i != count; i++) {
+				char c;
+				if(get_user(c, buf+i))
+					return -EFAULT;
+				if (c == 'V')
+					eur_expect_close = 42;
+			}
+		}
+		eurwdt_ping();	/* the default timeout */
+	}
+
+	return count;
+}
+
+/**
+ * eurwdt_ioctl:
+ * @inode: inode of the device
+ * @file: file handle to the device
+ * @cmd: watchdog command
+ * @arg: argument pointer
+ *
+ * The watchdog API defines a common set of functions for all watchdogs
+ * according to their available features.
+ */
+
+static int eurwdt_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options	  = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity	  = "WDT Eurotech CPU-1220/1410",
+	};
+
+	int time;
+	int options, retval = -EINVAL;
+
+	switch(cmd) {
+	default:
+		return -ENOIOCTLCMD;
+
+	case WDIOC_GETSUPPORT:
+		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		return put_user(0, p);
+
+	case WDIOC_KEEPALIVE:
+		eurwdt_ping();
+		return 0;
+
+	case WDIOC_SETTIMEOUT:
+		if (copy_from_user(&time, p, sizeof(int)))
+			return -EFAULT;
+
+		/* Sanity check */
+		if (time < 0 || time > 255)
+			return -EINVAL;
+
+		eurwdt_timeout = time;
+		eurwdt_set_timeout(time);
+		/* Fall */
+
+	case WDIOC_GETTIMEOUT:
+		return put_user(eurwdt_timeout, p);
+
+	case WDIOC_SETOPTIONS:
+		if (get_user(options, p))
+			return -EFAULT;
+		if (options & WDIOS_DISABLECARD) {
+			eurwdt_disable_timer();
+			retval = 0;
+		}
+		if (options & WDIOS_ENABLECARD) {
+			eurwdt_activate_timer();
+			eurwdt_ping();
+			retval = 0;
+		}
+		return retval;
+	}
+}
+
+/**
+ * eurwdt_open:
+ * @inode: inode of device
+ * @file: file handle to device
+ *
+ * The misc device has been opened. The watchdog device is single
+ * open and on opening we load the counter.
+ */
+
+static int eurwdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &eurwdt_is_open))
+		return -EBUSY;
+	eurwdt_timeout = WDT_TIMEOUT;	/* initial timeout */
+	/* Activate the WDT */
+	eurwdt_activate_timer();
+	return nonseekable_open(inode, file);
+}
+
+/**
+ * eurwdt_release:
+ * @inode: inode to board
+ * @file: file handle to board
+ *
+ * The watchdog has a configurable API. There is a religious dispute
+ * between people who want their watchdog to be able to shut down and
+ * those who want to be sure if the watchdog manager dies the machine
+ * reboots. In the former case we disable the counters, in the latter
+ * case you have to open it again very soon.
+ */
+
+static int eurwdt_release(struct inode *inode, struct file *file)
+{
+	if (eur_expect_close == 42) {
+		eurwdt_disable_timer();
+	} else {
+		printk(KERN_CRIT "eurwdt: Unexpected close, not stopping watchdog!\n");
+		eurwdt_ping();
+	}
+	clear_bit(0, &eurwdt_is_open);
+	eur_expect_close = 0;
+	return 0;
+}
+
+/**
+ * eurwdt_notify_sys:
+ * @this: our notifier block
+ * @code: the event being reported
+ * @unused: unused
+ *
+ * Our notifier is called on system shutdowns. We want to turn the card
+ * off at reboot otherwise the machine will reboot again during memory
+ * test or worse yet during the following fsck. This would suck, in fact
+ * trust me - if it happens it does suck.
+ */
+
+static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT) {
+		/* Turn the card off */
+		eurwdt_disable_timer();
+	}
+
+	return NOTIFY_DONE;
+}
+
+/*
+ * Kernel Interfaces
+ */
+
+
+static struct file_operations eurwdt_fops = {
+	.owner	= THIS_MODULE,
+	.llseek	= no_llseek,
+	.write	= eurwdt_write,
+	.ioctl	= eurwdt_ioctl,
+	.open	= eurwdt_open,
+	.release	= eurwdt_release,
+};
+
+static struct miscdevice eurwdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &eurwdt_fops,
+};
+
+/*
+ * The WDT card needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+
+static struct notifier_block eurwdt_notifier = {
+	.notifier_call = eurwdt_notify_sys,
+};
+
+/**
+ * cleanup_module:
+ *
+ * Unload the watchdog. You cannot do this with any file handles open.
+ * If your watchdog is set to continue ticking on close and you unload
+ * it, well it keeps ticking. We won't get the interrupt but the board
+ * will not touch PC memory so all is fine. You just have to load a new
+ * module in 60 seconds or reboot.
+ */
+
+static void __exit eurwdt_exit(void)
+{
+	eurwdt_lock_chip();
+
+	misc_deregister(&eurwdt_miscdev);
+
+	unregister_reboot_notifier(&eurwdt_notifier);
+	release_region(io, 2);
+	free_irq(irq, NULL);
+}
+
+/**
+ * eurwdt_init:
+ *
+ * Set up the WDT watchdog board. After grabbing the resources
+ * we require we need also to unlock the device.
+ * The open() function will actually kick the board off.
+ */
+
+static int __init eurwdt_init(void)
+{
+	int ret;
+
+	ret = misc_register(&eurwdt_miscdev);
+	if (ret) {
+		printk(KERN_ERR "eurwdt: can't misc_register on minor=%d\n",
+		WATCHDOG_MINOR);
+		goto out;
+	}
+
+	ret = request_irq(irq, eurwdt_interrupt, SA_INTERRUPT, "eurwdt", NULL);
+	if(ret) {
+		printk(KERN_ERR "eurwdt: IRQ %d is not free.\n", irq);
+		goto outmisc;
+	}
+
+	if (!request_region(io, 2, "eurwdt")) {
+		printk(KERN_ERR "eurwdt: IO %X is not free.\n", io);
+		ret = -EBUSY;
+		goto outirq;
+	}
+
+	ret = register_reboot_notifier(&eurwdt_notifier);
+	if (ret) {
+		printk(KERN_ERR "eurwdt: can't register reboot notifier (err=%d)\n", ret);
+		goto outreg;
+	}
+
+	eurwdt_unlock_chip();
+
+	ret = 0;
+	printk(KERN_INFO "Eurotech WDT driver 0.01 at %X (Interrupt %d)"
+		" - timeout event: %s\n",
+		io, irq, (!strcmp("int", ev) ? "int" : "reboot"));
+
+out:
+	return ret;
+
+outreg:
+	release_region(io, 2);
+
+outirq:
+	free_irq(irq, NULL);
+
+outmisc:
+	misc_deregister(&eurwdt_miscdev);
+	goto out;
+}
+
+module_init(eurwdt_init);
+module_exit(eurwdt_exit);
+
+MODULE_AUTHOR("Rodolfo Giometti");
+MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/i8xx_tco.c b/drivers/char/watchdog/i8xx_tco.c
new file mode 100644
index 0000000..c337978
--- /dev/null
+++ b/drivers/char/watchdog/i8xx_tco.c
@@ -0,0 +1,535 @@
+/*
+ *	i8xx_tco 0.07:	TCO timer driver for i8xx chipsets
+ *
+ *	(c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
+ *				http://www.kernelconcepts.de
+ *
+ *	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.
+ *
+ *	Neither kernel concepts nor Nils Faerber admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 2000	kernel concepts <nils@kernelconcepts.de>
+ *				developed for
+ *                              Jentro AG, Haar/Munich (Germany)
+ *
+ *	TCO timer driver for i8xx chipsets
+ *	based on softdog.c by Alan Cox <alan@redhat.com>
+ *
+ *	The TCO timer is implemented in the following I/O controller hubs:
+ *	(See the intel documentation on http://developer.intel.com.)
+ *	82801AA  (ICH)    : document number 290655-003, 290677-014,
+ *	82801AB  (ICHO)   : document number 290655-003, 290677-014,
+ *	82801BA  (ICH2)   : document number 290687-002, 298242-027,
+ *	82801BAM (ICH2-M) : document number 290687-002, 298242-027,
+ *	82801CA  (ICH3-S) : document number 290733-003, 290739-013,
+ *	82801CAM (ICH3-M) : document number 290716-001, 290718-007,
+ *	82801DB  (ICH4)   : document number 290744-001, 290745-020,
+ *	82801DBM (ICH4-M) : document number 252337-001, 252663-005,
+ *	82801E   (C-ICH)  : document number 273599-001, 273645-002,
+ *	82801EB  (ICH5)   : document number 252516-001, 252517-003,
+ *	82801ER  (ICH5R)  : document number 252516-001, 252517-003,
+ *	82801FB  (ICH6)   : document number 301473-002, 301474-007,
+ *	82801FR  (ICH6R)  : document number 301473-002, 301474-007,
+ *	82801FBM (ICH6-M) : document number 301473-002, 301474-007,
+ *	82801FW  (ICH6W)  : document number 301473-001, 301474-007,
+ *	82801FRW (ICH6RW) : document number 301473-001, 301474-007
+ *
+ *  20000710 Nils Faerber
+ *	Initial Version 0.01
+ *  20000728 Nils Faerber
+ *	0.02 Fix for SMI_EN->TCO_EN bit, some cleanups
+ *  20011214 Matt Domsch <Matt_Domsch@dell.com>
+ *	0.03 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *	     Didn't add timeout option as i810_margin already exists.
+ *  20020224 Joel Becker, Wim Van Sebroeck
+ *	0.04 Support for 82801CA(M) chipset, timer margin needs to be > 3,
+ *	     add support for WDIOC_SETTIMEOUT and WDIOC_GETTIMEOUT.
+ *  20020412 Rob Radez <rob@osinvestor.com>, Wim Van Sebroeck
+ *	0.05 Fix possible timer_alive race, add expect close support,
+ *	     clean up ioctls (WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS and
+ *	     WDIOC_SETOPTIONS), made i810tco_getdevice __init,
+ *	     removed boot_status, removed tco_timer_read,
+ *	     added support for 82801DB and 82801E chipset,
+ *	     added support for 82801EB and 8280ER chipset,
+ *	     general cleanup.
+ *  20030921 Wim Van Sebroeck <wim@iguana.be>
+ *	0.06 change i810_margin to heartbeat, use module_param,
+ *	     added notify system support, renamed module to i8xx_tco.
+ *  20050128 Wim Van Sebroeck <wim@iguana.be>
+ *	0.07 Added support for the ICH4-M, ICH6, ICH6R, ICH6-M, ICH6W and ICH6RW
+ *	     chipsets. Also added support for the "undocumented" ICH7 chipset.
+ */
+
+/*
+ *	Includes, defines, variables, module parameters, ...
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include "i8xx_tco.h"
+
+/* Module and version information */
+#define TCO_VERSION "0.07"
+#define TCO_MODULE_NAME "i8xx TCO timer"
+#define TCO_DRIVER_NAME   TCO_MODULE_NAME ", v" TCO_VERSION
+#define PFX TCO_MODULE_NAME ": "
+
+/* internal variables */
+static unsigned int ACPIBASE;
+static spinlock_t tco_lock;	/* Guards the hardware */
+static unsigned long timer_alive;
+static char tco_expect_close;
+static struct pci_dev *i8xx_tco_pci;
+
+/* module parameters */
+#define WATCHDOG_HEARTBEAT 30	/* 30 sec default heartbeat (2<heartbeat<39) */
+static int heartbeat = WATCHDOG_HEARTBEAT;  /* in seconds */
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Some TCO specific functions
+ */
+
+static inline unsigned char seconds_to_ticks(int seconds)
+{
+	/* the internal timer is stored as ticks which decrement
+	 * every 0.6 seconds */
+	return (seconds * 10) / 6;
+}
+
+static int tco_timer_start (void)
+{
+	unsigned char val;
+
+	spin_lock(&tco_lock);
+	val = inb (TCO1_CNT + 1);
+	val &= 0xf7;
+	outb (val, TCO1_CNT + 1);
+	val = inb (TCO1_CNT + 1);
+	spin_unlock(&tco_lock);
+
+	if (val & 0x08)
+		return -1;
+	return 0;
+}
+
+static int tco_timer_stop (void)
+{
+	unsigned char val;
+
+	spin_lock(&tco_lock);
+	val = inb (TCO1_CNT + 1);
+	val |= 0x08;
+	outb (val, TCO1_CNT + 1);
+	val = inb (TCO1_CNT + 1);
+	spin_unlock(&tco_lock);
+
+	if ((val & 0x08) == 0)
+		return -1;
+	return 0;
+}
+
+static int tco_timer_keepalive (void)
+{
+	spin_lock(&tco_lock);
+	outb (0x01, TCO1_RLD);
+	spin_unlock(&tco_lock);
+	return 0;
+}
+
+static int tco_timer_set_heartbeat (int t)
+{
+	unsigned char val;
+	unsigned char tmrval;
+
+	tmrval = seconds_to_ticks(t);
+	/* from the specs: */
+	/* "Values of 0h-3h are ignored and should not be attempted" */
+	if (tmrval > 0x3f || tmrval < 0x04)
+		return -EINVAL;
+
+	/* Write new heartbeat to watchdog */
+	spin_lock(&tco_lock);
+	val = inb (TCO1_TMR);
+	val &= 0xc0;
+	val |= tmrval;
+	outb (val, TCO1_TMR);
+	val = inb (TCO1_TMR);
+	spin_unlock(&tco_lock);
+
+	if ((val & 0x3f) != tmrval)
+		return -EINVAL;
+
+	heartbeat = t;
+	return 0;
+}
+
+/*
+ *	/dev/watchdog handling
+ */
+
+static int i8xx_tco_open (struct inode *inode, struct file *file)
+{
+	/* /dev/watchdog can only be opened once */
+	if (test_and_set_bit(0, &timer_alive))
+		return -EBUSY;
+
+	/*
+	 *      Reload and activate timer
+	 */
+	tco_timer_keepalive ();
+	tco_timer_start ();
+	return nonseekable_open(inode, file);
+}
+
+static int i8xx_tco_release (struct inode *inode, struct file *file)
+{
+	/*
+	 *      Shut off the timer.
+	 */
+	if (tco_expect_close == 42) {
+		tco_timer_stop ();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		tco_timer_keepalive ();
+	}
+	clear_bit(0, &timer_alive);
+	tco_expect_close = 0;
+	return 0;
+}
+
+static ssize_t i8xx_tco_write (struct file *file, const char __user *data,
+			      size_t len, loff_t * ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			tco_expect_close = 0;
+
+			/* scan to see whether or not we got the magic character */
+			for (i = 0; i != len; i++) {
+				char c;
+				if(get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					tco_expect_close = 42;
+			}
+		}
+
+		/* someone wrote to us, we should reload the timer */
+		tco_timer_keepalive ();
+	}
+	return len;
+}
+
+static int i8xx_tco_ioctl (struct inode *inode, struct file *file,
+			  unsigned int cmd, unsigned long arg)
+{
+	int new_options, retval = -EINVAL;
+	int new_heartbeat;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options =		WDIOF_SETTIMEOUT |
+					WDIOF_KEEPALIVEPING |
+					WDIOF_MAGICCLOSE,
+		.firmware_version =	0,
+		.identity =		TCO_MODULE_NAME,
+	};
+
+	switch (cmd) {
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident,
+				sizeof (ident)) ? -EFAULT : 0;
+
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user (0, p);
+
+		case WDIOC_KEEPALIVE:
+			tco_timer_keepalive ();
+			return 0;
+
+		case WDIOC_SETOPTIONS:
+		{
+			if (get_user (new_options, p))
+				return -EFAULT;
+
+			if (new_options & WDIOS_DISABLECARD) {
+				tco_timer_stop ();
+				retval = 0;
+			}
+
+			if (new_options & WDIOS_ENABLECARD) {
+				tco_timer_keepalive ();
+				tco_timer_start ();
+				retval = 0;
+			}
+
+			return retval;
+		}
+
+		case WDIOC_SETTIMEOUT:
+		{
+			if (get_user(new_heartbeat, p))
+				return -EFAULT;
+
+			if (tco_timer_set_heartbeat(new_heartbeat))
+			    return -EINVAL;
+
+			tco_timer_keepalive ();
+			/* Fall */
+		}
+
+		case WDIOC_GETTIMEOUT:
+			return put_user(heartbeat, p);
+
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+/*
+ *	Notify system
+ */
+
+static int i8xx_tco_notify_sys (struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the WDT off */
+		tco_timer_stop ();
+	}
+
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations i8xx_tco_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.write =	i8xx_tco_write,
+	.ioctl =	i8xx_tco_ioctl,
+	.open =		i8xx_tco_open,
+	.release =	i8xx_tco_release,
+};
+
+static struct miscdevice i8xx_tco_miscdev = {
+	.minor =	WATCHDOG_MINOR,
+	.name =		"watchdog",
+	.fops =		&i8xx_tco_fops,
+};
+
+static struct notifier_block i8xx_tco_notifier = {
+	.notifier_call =	i8xx_tco_notify_sys,
+};
+
+/*
+ * Data for PCI driver interface
+ *
+ * This data only exists for exporting the supported
+ * PCI ids via MODULE_DEVICE_TABLE.  We do not actually
+ * register a pci_driver, because someone else might one day
+ * want to register another driver on the same PCI id.
+ */
+static struct pci_device_id i8xx_tco_pci_tbl[] = {
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801E_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_2,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1,	PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0, },			/* End of list */
+};
+MODULE_DEVICE_TABLE (pci, i8xx_tco_pci_tbl);
+
+/*
+ *	Init & exit routines
+ */
+
+static unsigned char __init i8xx_tco_getdevice (void)
+{
+	struct pci_dev *dev = NULL;
+	u8 val1, val2;
+	u16 badr;
+	/*
+	 *      Find the PCI device
+	 */
+
+	while ((dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
+		if (pci_match_device(i8xx_tco_pci_tbl, dev)) {
+			i8xx_tco_pci = dev;
+			break;
+		}
+	}
+
+	if (i8xx_tco_pci) {
+		/*
+		 *      Find the ACPI base I/O address which is the base
+		 *      for the TCO registers (TCOBASE=ACPIBASE + 0x60)
+		 *      ACPIBASE is bits [15:7] from 0x40-0x43
+		 */
+		pci_read_config_byte (i8xx_tco_pci, 0x40, &val1);
+		pci_read_config_byte (i8xx_tco_pci, 0x41, &val2);
+		badr = ((val2 << 1) | (val1 >> 7)) << 7;
+		ACPIBASE = badr;
+		/* Something's wrong here, ACPIBASE has to be set */
+		if (badr == 0x0001 || badr == 0x0000) {
+			printk (KERN_ERR PFX "failed to get TCOBASE address\n");
+			return 0;
+		}
+		/*
+		 * Check chipset's NO_REBOOT bit
+		 */
+		pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
+		if (val1 & 0x02) {
+			val1 &= 0xfd;
+			pci_write_config_byte (i8xx_tco_pci, 0xd4, val1);
+			pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
+			if (val1 & 0x02) {
+				printk (KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
+				return 0;	/* Cannot reset NO_REBOOT bit */
+			}
+		}
+		/* Set the TCO_EN bit in SMI_EN register */
+		if (!request_region (SMI_EN + 1, 1, "i8xx TCO")) {
+			printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+				SMI_EN + 1);
+			return 0;
+		}
+		val1 = inb (SMI_EN + 1);
+		val1 &= 0xdf;
+		outb (val1, SMI_EN + 1);
+		release_region (SMI_EN + 1, 1);
+		return 1;
+	}
+	return 0;
+}
+
+static int __init watchdog_init (void)
+{
+	int ret;
+
+	spin_lock_init(&tco_lock);
+
+	/* Check whether or not the hardware watchdog is there */
+	if (!i8xx_tco_getdevice () || i8xx_tco_pci == NULL)
+		return -ENODEV;
+
+	if (!request_region (TCOBASE, 0x10, "i8xx TCO")) {
+		printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			TCOBASE);
+		ret = -EIO;
+		goto out;
+	}
+
+	/* Clear out the (probably old) status */
+	outb (0, TCO1_STS);
+	outb (3, TCO2_STS);
+
+	/* Check that the heartbeat value is within it's range ; if not reset to the default */
+	if (tco_timer_set_heartbeat (heartbeat)) {
+		heartbeat = WATCHDOG_HEARTBEAT;
+		tco_timer_set_heartbeat (heartbeat);
+		printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39, using %d\n",
+			heartbeat);
+	}
+
+	ret = register_reboot_notifier(&i8xx_tco_notifier);
+	if (ret != 0) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		goto unreg_region;
+	}
+
+	ret = misc_register(&i8xx_tco_miscdev);
+	if (ret != 0) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto unreg_notifier;
+	}
+
+	tco_timer_stop ();
+
+	printk (KERN_INFO PFX "initialized (0x%04x). heartbeat=%d sec (nowayout=%d)\n",
+		TCOBASE, heartbeat, nowayout);
+
+	return 0;
+
+unreg_notifier:
+	unregister_reboot_notifier(&i8xx_tco_notifier);
+unreg_region:
+	release_region (TCOBASE, 0x10);
+out:
+	return ret;
+}
+
+static void __exit watchdog_cleanup (void)
+{
+	u8 val;
+
+	/* Stop the timer before we leave */
+	if (!nowayout)
+		tco_timer_stop ();
+
+	/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
+	pci_read_config_byte (i8xx_tco_pci, 0xd4, &val);
+	val |= 0x02;
+	pci_write_config_byte (i8xx_tco_pci, 0xd4, val);
+
+	/* Deregister */
+	misc_deregister (&i8xx_tco_miscdev);
+	unregister_reboot_notifier(&i8xx_tco_notifier);
+	release_region (TCOBASE, 0x10);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_cleanup);
+
+MODULE_AUTHOR("Nils Faerber");
+MODULE_DESCRIPTION("TCO timer driver for i8xx chipsets");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/i8xx_tco.h b/drivers/char/watchdog/i8xx_tco.h
new file mode 100644
index 0000000..cc14eb8
--- /dev/null
+++ b/drivers/char/watchdog/i8xx_tco.h
@@ -0,0 +1,42 @@
+/*
+ *	i8xx_tco:	TCO timer driver for i8xx chipsets
+ *
+ *	(c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
+ *				http://www.kernelconcepts.de
+ *
+ *	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.
+ *
+ *	Neither kernel concepts nor Nils Faerber admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 2000	kernel concepts <nils@kernelconcepts.de>
+ *				developed for
+ *                              Jentro AG, Haar/Munich (Germany)
+ *
+ *	TCO timer driver for i8xx chipsets
+ *	based on softdog.c by Alan Cox <alan@redhat.com>
+ *
+ *	For history and the complete list of supported I/O Controller Hub's
+ *	see i8xx_tco.c
+ */
+
+
+/*
+ * Some address definitions for the TCO
+ */
+
+#define	TCOBASE		ACPIBASE + 0x60	/* TCO base address		*/
+#define TCO1_RLD	TCOBASE + 0x00	/* TCO Timer Reload and Current Value */
+#define TCO1_TMR	TCOBASE + 0x01	/* TCO Timer Initial Value	*/
+#define	TCO1_DAT_IN	TCOBASE + 0x02	/* TCO Data In Register		*/
+#define	TCO1_DAT_OUT	TCOBASE + 0x03	/* TCO Data Out Register	*/
+#define	TCO1_STS	TCOBASE + 0x04	/* TCO1 Status Register		*/
+#define	TCO2_STS	TCOBASE + 0x06	/* TCO2 Status Register		*/
+#define TCO1_CNT	TCOBASE + 0x08	/* TCO1 Control Register	*/
+#define TCO2_CNT	TCOBASE + 0x0a	/* TCO2 Control Register	*/
+
+#define	SMI_EN		ACPIBASE + 0x30	/* SMI Control and Enable Register */
diff --git a/drivers/char/watchdog/ib700wdt.c b/drivers/char/watchdog/ib700wdt.c
new file mode 100644
index 0000000..d974f16
--- /dev/null
+++ b/drivers/char/watchdog/ib700wdt.c
@@ -0,0 +1,352 @@
+/*
+ *	IB700 Single Board Computer WDT driver
+ *
+ *	(c) Copyright 2001 Charles Howes <chowes@vsol.net>
+ *
+ *      Based on advantechwdt.c which is based on acquirewdt.c which
+ *       is based on wdt.c.
+ *
+ *	(c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ *	Based on acquirewdt.c which is based on wdt.c.
+ *	Original copyright messages:
+ *
+ *	(c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *				http://www.redhat.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.
+ *
+ *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 1995    Alan Cox <alan@redhat.com>
+ *
+ *      14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ *           Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *           Added timeout module option to override default
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/moduleparam.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+static unsigned long ibwdt_is_open;
+static spinlock_t ibwdt_lock;
+static char expect_close;
+
+#define PFX "ib700wdt: "
+
+/*
+ *
+ * Watchdog Timer Configuration
+ *
+ * The function of the watchdog timer is to reset the system
+ * automatically and is defined at I/O port 0443H.  To enable the
+ * watchdog timer and allow the system to reset, write I/O port 0443H.
+ * To disable the timer, write I/O port 0441H for the system to stop the
+ * watchdog function.  The timer has a tolerance of 20% for its
+ * intervals.
+ *
+ * The following describes how the timer should be programmed.
+ *
+ * Enabling Watchdog:
+ * MOV AX,000FH (Choose the values from 0 to F)
+ * MOV DX,0443H
+ * OUT DX,AX
+ *
+ * Disabling Watchdog:
+ * MOV AX,000FH (Any value is fine.)
+ * MOV DX,0441H
+ * OUT DX,AX
+ *
+ * Watchdog timer control table:
+ * Level   Value  Time/sec | Level Value Time/sec
+ *   1       F       0     |   9     7      16
+ *   2       E       2     |   10    6      18
+ *   3       D       4     |   11    5      20
+ *   4       C       6     |   12    4      22
+ *   5       B       8     |   13    3      24
+ *   6       A       10    |   14    2      26
+ *   7       9       12    |   15    1      28
+ *   8       8       14    |   16    0      30
+ *
+ */
+
+static int wd_times[] = {
+	30,	/* 0x0 */
+	28,	/* 0x1 */
+	26,	/* 0x2 */
+	24,	/* 0x3 */
+	22,	/* 0x4 */
+	20,	/* 0x5 */
+	18,	/* 0x6 */
+	16,	/* 0x7 */
+	14,	/* 0x8 */
+	12,	/* 0x9 */
+	10,	/* 0xA */
+	8,	/* 0xB */
+	6,	/* 0xC */
+	4,	/* 0xD */
+	2,	/* 0xE */
+	0,	/* 0xF */
+};
+
+#define WDT_STOP 0x441
+#define WDT_START 0x443
+
+/* Default timeout */
+#define WD_TIMO 0		/* 30 seconds +/- 20%, from table */
+
+static int wd_margin = WD_TIMO;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+
+/*
+ *	Kernel methods.
+ */
+
+static void
+ibwdt_ping(void)
+{
+	/* Write a watchdog value */
+	outb_p(wd_margin, WDT_START);
+}
+
+static ssize_t
+ibwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	if (count) {
+		if (!nowayout) {
+			size_t i;
+
+			/* In case it was set long ago */
+			expect_close = 0;
+
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, buf + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		ibwdt_ping();
+	}
+	return count;
+}
+
+static int
+ibwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	  unsigned long arg)
+{
+	int i, new_margin;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+
+	static struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "IB700 WDT",
+	};
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+	  if (copy_to_user(argp, &ident, sizeof(ident)))
+	    return -EFAULT;
+	  break;
+
+	case WDIOC_GETSTATUS:
+	  return put_user(0, p);
+
+	case WDIOC_KEEPALIVE:
+	  ibwdt_ping();
+	  break;
+
+	case WDIOC_SETTIMEOUT:
+	  if (get_user(new_margin, p))
+		  return -EFAULT;
+	  if ((new_margin < 0) || (new_margin > 30))
+		  return -EINVAL;
+	  for (i = 0x0F; i > -1; i--)
+		  if (wd_times[i] > new_margin)
+			  break;
+	  wd_margin = i;
+	  ibwdt_ping();
+	  /* Fall */
+
+	case WDIOC_GETTIMEOUT:
+	  return put_user(wd_times[wd_margin], p);
+	  break;
+
+	default:
+	  return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static int
+ibwdt_open(struct inode *inode, struct file *file)
+{
+	spin_lock(&ibwdt_lock);
+	if (test_and_set_bit(0, &ibwdt_is_open)) {
+		spin_unlock(&ibwdt_lock);
+		return -EBUSY;
+	}
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	/* Activate */
+	ibwdt_ping();
+	spin_unlock(&ibwdt_lock);
+	return nonseekable_open(inode, file);
+}
+
+static int
+ibwdt_close(struct inode *inode, struct file *file)
+{
+	spin_lock(&ibwdt_lock);
+	if (expect_close == 42)
+		outb_p(0, WDT_STOP);
+	else
+		printk(KERN_CRIT PFX "WDT device closed unexpectedly.  WDT will not stop!\n");
+
+	clear_bit(0, &ibwdt_is_open);
+	expect_close = 0;
+	spin_unlock(&ibwdt_lock);
+	return 0;
+}
+
+/*
+ *	Notifier for system down
+ */
+
+static int
+ibwdt_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT) {
+		/* Turn the WDT off */
+		outb_p(0, WDT_STOP);
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations ibwdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= ibwdt_write,
+	.ioctl		= ibwdt_ioctl,
+	.open		= ibwdt_open,
+	.release	= ibwdt_close,
+};
+
+static struct miscdevice ibwdt_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &ibwdt_fops,
+};
+
+/*
+ *	The WDT needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block ibwdt_notifier = {
+	.notifier_call = ibwdt_notify_sys,
+};
+
+static int __init ibwdt_init(void)
+{
+	int res;
+
+	printk(KERN_INFO PFX "WDT driver for IB700 single board computer initialising.\n");
+
+	spin_lock_init(&ibwdt_lock);
+	res = misc_register(&ibwdt_miscdev);
+	if (res) {
+		printk (KERN_ERR PFX "failed to register misc device\n");
+		goto out_nomisc;
+	}
+
+#if WDT_START != WDT_STOP
+	if (!request_region(WDT_STOP, 1, "IB700 WDT")) {
+		printk (KERN_ERR PFX "STOP method I/O %X is not available.\n", WDT_STOP);
+		res = -EIO;
+		goto out_nostopreg;
+	}
+#endif
+
+	if (!request_region(WDT_START, 1, "IB700 WDT")) {
+		printk (KERN_ERR PFX "START method I/O %X is not available.\n", WDT_START);
+		res = -EIO;
+		goto out_nostartreg;
+	}
+	res = register_reboot_notifier(&ibwdt_notifier);
+	if (res) {
+		printk (KERN_ERR PFX "Failed to register reboot notifier.\n");
+		goto out_noreboot;
+	}
+	return 0;
+
+out_noreboot:
+	release_region(WDT_START, 1);
+out_nostartreg:
+#if WDT_START != WDT_STOP
+	release_region(WDT_STOP, 1);
+#endif
+out_nostopreg:
+	misc_deregister(&ibwdt_miscdev);
+out_nomisc:
+	return res;
+}
+
+static void __exit
+ibwdt_exit(void)
+{
+	misc_deregister(&ibwdt_miscdev);
+	unregister_reboot_notifier(&ibwdt_notifier);
+#if WDT_START != WDT_STOP
+	release_region(WDT_STOP,1);
+#endif
+	release_region(WDT_START,1);
+}
+
+module_init(ibwdt_init);
+module_exit(ibwdt_exit);
+
+MODULE_AUTHOR("Charles Howes <chowes@vsol.net>");
+MODULE_DESCRIPTION("IB700 SBC watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+/* end of ib700wdt.c */
diff --git a/drivers/char/watchdog/indydog.c b/drivers/char/watchdog/indydog.c
new file mode 100644
index 0000000..6af2c79
--- /dev/null
+++ b/drivers/char/watchdog/indydog.c
@@ -0,0 +1,221 @@
+/*
+ *	IndyDog	0.3	A Hardware Watchdog Device for SGI IP22
+ *
+ *	(c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, All Rights Reserved.
+ *
+ *	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.
+ *
+ *	based on softdog.c by Alan Cox <alan@redhat.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/sgi/mc.h>
+
+#define PFX "indydog: "
+static int indydog_alive;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+#define WATCHDOG_TIMEOUT 30		/* 30 sec default timeout */
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void indydog_start(void)
+{
+	u32 mc_ctrl0 = sgimc->cpuctrl0;
+
+	mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG;
+	sgimc->cpuctrl0 = mc_ctrl0;
+}
+
+static void indydog_stop(void)
+{
+	u32 mc_ctrl0 = sgimc->cpuctrl0;
+
+	mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG;
+	sgimc->cpuctrl0 = mc_ctrl0;
+
+	printk(KERN_INFO PFX "Stopped watchdog timer.\n");
+}
+
+static void indydog_ping(void)
+{
+	sgimc->watchdogt = 0;
+}
+
+/*
+ *	Allow only one person to hold it open
+ */
+static int indydog_open(struct inode *inode, struct file *file)
+{
+	if (indydog_alive)
+		return -EBUSY;
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	/* Activate timer */
+	indydog_start();
+	indydog_ping();
+
+	indydog_alive = 1;
+	printk(KERN_INFO "Started watchdog timer.\n");
+
+	return nonseekable_open(inode, file);
+}
+
+static int indydog_release(struct inode *inode, struct file *file)
+{
+	/* Shut off the timer.
+	 * Lock it in if it's a module and we defined ...NOWAYOUT */
+	if (!nowayout)
+		indydog_stop();		/* Turn the WDT off */
+
+	indydog_alive = 0;
+
+	return 0;
+}
+
+static ssize_t indydog_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+	/* Refresh the timer. */
+	if (len) {
+		indydog_ping();
+	}
+	return len;
+}
+
+static int indydog_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	int options, retval = -EINVAL;
+	static struct watchdog_info ident = {
+		.options		= WDIOF_KEEPALIVEPING |
+					  WDIOF_MAGICCLOSE,
+		.firmware_version	= 0,
+		.identity		= "Hardware Watchdog for SGI IP22",
+	};
+
+	switch (cmd) {
+		default:
+			return -ENOIOCTLCMD;
+		case WDIOC_GETSUPPORT:
+			if (copy_to_user((struct watchdog_info *)arg,
+					 &ident, sizeof(ident)))
+				return -EFAULT;
+			return 0;
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0,(int *)arg);
+		case WDIOC_KEEPALIVE:
+			indydog_ping();
+			return 0;
+		case WDIOC_GETTIMEOUT:
+			return put_user(WATCHDOG_TIMEOUT,(int *)arg);
+		case WDIOC_SETOPTIONS:
+		{
+			if (get_user(options, (int *)arg))
+				return -EFAULT;
+
+			if (options & WDIOS_DISABLECARD) {
+				indydog_stop();
+				retval = 0;
+			}
+
+			if (options & WDIOS_ENABLECARD) {
+				indydog_start();
+				retval = 0;
+			}
+
+			return retval;
+		}
+	}
+}
+
+static int indydog_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT)
+		indydog_stop();		/* Turn the WDT off */
+
+	return NOTIFY_DONE;
+}
+
+static struct file_operations indydog_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= indydog_write,
+	.ioctl		= indydog_ioctl,
+	.open		= indydog_open,
+	.release	= indydog_release,
+};
+
+static struct miscdevice indydog_miscdev = {
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &indydog_fops,
+};
+
+static struct notifier_block indydog_notifier = {
+	.notifier_call = indydog_notify_sys,
+};
+
+static char banner[] __initdata =
+	KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n";
+
+static int __init watchdog_init(void)
+{
+	int ret;
+
+	ret = register_reboot_notifier(&indydog_notifier);
+	if (ret) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		return ret;
+	}
+
+	ret = misc_register(&indydog_miscdev);
+	if (ret) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		unregister_reboot_notifier(&indydog_notifier);
+		return ret;
+	}
+
+	printk(banner);
+
+	return 0;
+}
+
+static void __exit watchdog_exit(void)
+{
+	misc_deregister(&indydog_miscdev);
+	unregister_reboot_notifier(&indydog_notifier);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>");
+MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/ixp2000_wdt.c b/drivers/char/watchdog/ixp2000_wdt.c
new file mode 100644
index 0000000..ab659d3
--- /dev/null
+++ b/drivers/char/watchdog/ixp2000_wdt.c
@@ -0,0 +1,219 @@
+/*
+ * drivers/watchdog/ixp2000_wdt.c
+ *
+ * Watchdog driver for Intel IXP2000 network processors
+ *
+ * Adapted from the IXP4xx watchdog driver by Lennert Buytenhek.
+ * The original version carries these notices:
+ *
+ * Author: Deepak Saxena <dsaxena@plexity.net>
+ *
+ * Copyright 2004 (c) MontaVista, Software, Inc.
+ * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * This file is licensed under  the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <asm/hardware.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+static unsigned int heartbeat = 60;	/* (secs) Default is 1 minute */
+static unsigned long wdt_status;
+
+#define	WDT_IN_USE		0
+#define	WDT_OK_TO_CLOSE		1
+
+static unsigned long wdt_tick_rate;
+
+static void
+wdt_enable(void)
+{
+	ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE);
+	ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE);
+	ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate);
+	ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE);
+}
+
+static void
+wdt_disable(void)
+{
+	ixp2000_reg_write(IXP2000_T4_CTL, 0);
+}
+
+static void
+wdt_keepalive(void)
+{
+	ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate);
+}
+
+static int
+ixp2000_wdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+		return -EBUSY;
+
+	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+	wdt_enable();
+
+	return nonseekable_open(inode, file);
+}
+
+static ssize_t
+ixp2000_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+			for (i = 0; i != len; i++) {
+				char c;
+
+				if (get_user(c, data + i))
+					return -EFAULT;
+				if (c == 'V')
+					set_bit(WDT_OK_TO_CLOSE, &wdt_status);
+			}
+		}
+		wdt_keepalive();
+	}
+
+	return len;
+}
+
+
+static struct watchdog_info ident = {
+	.options	= WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
+				WDIOF_KEEPALIVEPING,
+	.identity	= "IXP2000 Watchdog",
+};
+
+static int
+ixp2000_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+			unsigned long arg)
+{
+	int ret = -ENOIOCTLCMD;
+	int time;
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		ret = copy_to_user((struct watchdog_info *)arg, &ident,
+				   sizeof(ident)) ? -EFAULT : 0;
+		break;
+
+	case WDIOC_GETSTATUS:
+		ret = put_user(0, (int *)arg);
+		break;
+
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0, (int *)arg);
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		ret = get_user(time, (int *)arg);
+		if (ret)
+			break;
+
+		if (time <= 0 || time > 60) {
+			ret = -EINVAL;
+			break;
+		}
+
+		heartbeat = time;
+		wdt_keepalive();
+		/* Fall through */
+
+	case WDIOC_GETTIMEOUT:
+		ret = put_user(heartbeat, (int *)arg);
+		break;
+
+	case WDIOC_KEEPALIVE:
+		wdt_enable();
+		ret = 0;
+		break;
+	}
+
+	return ret;
+}
+
+static int
+ixp2000_wdt_release(struct inode *inode, struct file *file)
+{
+	if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
+		wdt_disable();
+	} else {
+		printk(KERN_CRIT "WATCHDOG: Device closed unexpectdly - "
+					"timer will not stop\n");
+	}
+
+	clear_bit(WDT_IN_USE, &wdt_status);
+	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+	return 0;
+}
+
+
+static struct file_operations ixp2000_wdt_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= ixp2000_wdt_write,
+	.ioctl		= ixp2000_wdt_ioctl,
+	.open		= ixp2000_wdt_open,
+	.release	= ixp2000_wdt_release,
+};
+
+static struct miscdevice ixp2000_wdt_miscdev =
+{
+	.minor		= WATCHDOG_MINOR,
+	.name		= "IXP2000 Watchdog",
+	.fops		= &ixp2000_wdt_fops,
+};
+
+static int __init ixp2000_wdt_init(void)
+{
+	wdt_tick_rate = (*IXP2000_T1_CLD * HZ)/ 256;;
+
+	return misc_register(&ixp2000_wdt_miscdev);
+}
+
+static void __exit ixp2000_wdt_exit(void)
+{
+	misc_deregister(&ixp2000_wdt_miscdev);
+}
+
+module_init(ixp2000_wdt_init);
+module_exit(ixp2000_wdt_exit);
+
+MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net">);
+MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
diff --git a/drivers/char/watchdog/ixp4xx_wdt.c b/drivers/char/watchdog/ixp4xx_wdt.c
new file mode 100644
index 0000000..82396e0
--- /dev/null
+++ b/drivers/char/watchdog/ixp4xx_wdt.c
@@ -0,0 +1,230 @@
+/*
+ * drivers/watchdog/ixp4xx_wdt.c
+ *
+ * Watchdog driver for Intel IXP4xx network processors
+ *
+ * Author: Deepak Saxena <dsaxena@plexity.net>
+ *
+ * Copyright 2004 (c) MontaVista, Software, Inc.
+ * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
+ *
+ * This file is licensed under  the terms of the GNU General Public
+ * License version 2. This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+#include <asm/hardware.h>
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+static int heartbeat = 60;	/* (secs) Default is 1 minute */
+static unsigned long wdt_status;
+static unsigned long boot_status;
+
+#define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL)
+
+#define	WDT_IN_USE		0
+#define	WDT_OK_TO_CLOSE		1
+
+static void
+wdt_enable(void)
+{
+	*IXP4XX_OSWK = IXP4XX_WDT_KEY;
+	*IXP4XX_OSWE = 0;
+	*IXP4XX_OSWT = WDT_TICK_RATE * heartbeat;
+	*IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE;
+	*IXP4XX_OSWK = 0;
+}
+
+static void
+wdt_disable(void)
+{
+	*IXP4XX_OSWK = IXP4XX_WDT_KEY;
+	*IXP4XX_OSWE = 0;
+	*IXP4XX_OSWK = 0;
+}
+
+static int
+ixp4xx_wdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(WDT_IN_USE, &wdt_status))
+		return -EBUSY;
+
+	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+	wdt_enable();
+
+	return nonseekable_open(inode, file);
+}
+
+static ssize_t
+ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+			for (i = 0; i != len; i++) {
+				char c;
+
+				if (get_user(c, data + i))
+					return -EFAULT;
+				if (c == 'V')
+					set_bit(WDT_OK_TO_CLOSE, &wdt_status);
+			}
+		}
+		wdt_enable();
+	}
+
+	return len;
+}
+
+static struct watchdog_info ident = {
+	.options	= WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
+			  WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+	.identity	= "IXP4xx Watchdog",
+};
+
+
+static int
+ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+			unsigned long arg)
+{
+	int ret = -ENOIOCTLCMD;
+	int time;
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		ret = copy_to_user((struct watchdog_info *)arg, &ident,
+				   sizeof(ident)) ? -EFAULT : 0;
+		break;
+
+	case WDIOC_GETSTATUS:
+		ret = put_user(0, (int *)arg);
+		break;
+
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(boot_status, (int *)arg);
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		ret = get_user(time, (int *)arg);
+		if (ret)
+			break;
+
+		if (time <= 0 || time > 60) {
+			ret = -EINVAL;
+			break;
+		}
+
+		heartbeat = time;
+		wdt_enable();
+		/* Fall through */
+
+	case WDIOC_GETTIMEOUT:
+		ret = put_user(heartbeat, (int *)arg);
+		break;
+
+	case WDIOC_KEEPALIVE:
+		wdt_enable();
+		ret = 0;
+		break;
+	}
+	return ret;
+}
+
+static int
+ixp4xx_wdt_release(struct inode *inode, struct file *file)
+{
+	if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) {
+		wdt_disable();
+	} else {
+		printk(KERN_CRIT "WATCHDOG: Device closed unexpectdly - "
+					"timer will not stop\n");
+	}
+
+	clear_bit(WDT_IN_USE, &wdt_status);
+	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
+
+	return 0;
+}
+
+
+static struct file_operations ixp4xx_wdt_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= ixp4xx_wdt_write,
+	.ioctl		= ixp4xx_wdt_ioctl,
+	.open		= ixp4xx_wdt_open,
+	.release	= ixp4xx_wdt_release,
+};
+
+static struct miscdevice ixp4xx_wdt_miscdev =
+{
+	.minor		= WATCHDOG_MINOR,
+	.name		= "IXP4xx Watchdog",
+	.fops		= &ixp4xx_wdt_fops,
+};
+
+static int __init ixp4xx_wdt_init(void)
+{
+	int ret;
+	unsigned long processor_id;
+
+	asm("mrc p15, 0, %0, cr0, cr0, 0;" : "=r"(processor_id) :);
+	if (!(processor_id & 0xf)) {
+		printk("IXP4XXX Watchdog: Rev. A0 CPU detected - "
+			"watchdog disabled\n");
+
+		return -ENODEV;
+	}
+
+	ret = misc_register(&ixp4xx_wdt_miscdev);
+	if (ret == 0)
+		printk("IXP4xx Watchdog Timer: heartbeat %d sec\n", heartbeat);
+
+	boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ?
+			WDIOF_CARDRESET : 0;
+
+	return ret;
+}
+
+static void __exit ixp4xx_wdt_exit(void)
+{
+	misc_deregister(&ixp4xx_wdt_miscdev);
+}
+
+
+module_init(ixp4xx_wdt_init);
+module_exit(ixp4xx_wdt_exit);
+
+MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>");
+MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
diff --git a/drivers/char/watchdog/machzwd.c b/drivers/char/watchdog/machzwd.c
new file mode 100644
index 0000000..9da395f
--- /dev/null
+++ b/drivers/char/watchdog/machzwd.c
@@ -0,0 +1,501 @@
+/*
+ *  MachZ ZF-Logic Watchdog Timer driver for Linux
+ *
+ *
+ *  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.
+ *
+ *  The author does NOT admit liability nor provide warranty for
+ *  any of this software. This material is provided "AS-IS" in
+ *  the hope that it may be useful for others.
+ *
+ *  Author: Fernando Fuganti <fuganti@conectiva.com.br>
+ *
+ *  Based on sbc60xxwdt.c by Jakob Oestergaard
+ *
+ *
+ *  We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the
+ *  following periods:
+ *      wd#1 - 2 seconds;
+ *      wd#2 - 7.2 ms;
+ *  After the expiration of wd#1, it can generate a NMI, SCI, SMI, or
+ *  a system RESET and it starts wd#2 that unconditionaly will RESET
+ *  the system when the counter reaches zero.
+ *
+ *  14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ *      Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+/* ports */
+#define ZF_IOBASE	0x218
+#define INDEX		0x218
+#define DATA_B		0x219
+#define DATA_W		0x21A
+#define DATA_D		0x21A
+
+/* indexes */			/* size */
+#define ZFL_VERSION	0x02	/* 16   */
+#define CONTROL 	0x10	/* 16   */
+#define STATUS		0x12	/* 8    */
+#define COUNTER_1	0x0C	/* 16   */
+#define COUNTER_2	0x0E	/* 8    */
+#define PULSE_LEN	0x0F	/* 8    */
+
+/* controls */
+#define ENABLE_WD1	0x0001
+#define ENABLE_WD2	0x0002
+#define RESET_WD1	0x0010
+#define RESET_WD2	0x0020
+#define GEN_SCI		0x0100
+#define GEN_NMI		0x0200
+#define GEN_SMI		0x0400
+#define GEN_RESET	0x0800
+
+
+/* utilities */
+
+#define WD1	0
+#define WD2	1
+
+#define zf_writew(port, data)  { outb(port, INDEX); outw(data, DATA_W); }
+#define zf_writeb(port, data)  { outb(port, INDEX); outb(data, DATA_B); }
+#define zf_get_ZFL_version()   zf_readw(ZFL_VERSION)
+
+
+static unsigned short zf_readw(unsigned char port)
+{
+	outb(port, INDEX);
+	return inw(DATA_W);
+}
+
+
+MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>");
+MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+#define PFX "machzwd"
+
+static struct watchdog_info zf_info = {
+	.options		= WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+	.firmware_version	= 1,
+	.identity		= "ZF-Logic watchdog",
+};
+
+
+/*
+ * action refers to action taken when watchdog resets
+ * 0 = GEN_RESET
+ * 1 = GEN_SMI
+ * 2 = GEN_NMI
+ * 3 = GEN_SCI
+ * defaults to GEN_RESET (0)
+ */
+static int action = 0;
+module_param(action, int, 0);
+MODULE_PARM_DESC(action, "after watchdog resets, generate: 0 = RESET(*)  1 = SMI  2 = NMI  3 = SCI");
+
+static int zf_action = GEN_RESET;
+static unsigned long zf_is_open;
+static char zf_expect_close;
+static spinlock_t zf_lock;
+static spinlock_t zf_port_lock;
+static struct timer_list zf_timer;
+static unsigned long next_heartbeat = 0;
+
+
+/* timeout for user land heart beat (10 seconds) */
+#define ZF_USER_TIMEO (HZ*10)
+
+/* timeout for hardware watchdog (~500ms) */
+#define ZF_HW_TIMEO (HZ/2)
+
+/* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */
+#define ZF_CTIMEOUT 0xffff
+
+#ifndef ZF_DEBUG
+#	define dprintk(format, args...)
+#else
+#	define dprintk(format, args...) printk(KERN_DEBUG PFX ":%s:%d: " format, __FUNCTION__, __LINE__ , ## args)
+#endif
+
+
+static inline void zf_set_status(unsigned char new)
+{
+	zf_writeb(STATUS, new);
+}
+
+
+/* CONTROL register functions */
+
+static inline unsigned short zf_get_control(void)
+{
+	return zf_readw(CONTROL);
+}
+
+static inline void zf_set_control(unsigned short new)
+{
+	zf_writew(CONTROL, new);
+}
+
+
+/* WD#? counter functions */
+/*
+ *	Just set counter value
+ */
+
+static inline void zf_set_timer(unsigned short new, unsigned char n)
+{
+	switch(n){
+		case WD1:
+			zf_writew(COUNTER_1, new);
+		case WD2:
+			zf_writeb(COUNTER_2, new > 0xff ? 0xff : new);
+		default:
+			return;
+	}
+}
+
+/*
+ * stop hardware timer
+ */
+static void zf_timer_off(void)
+{
+	unsigned int ctrl_reg = 0;
+	unsigned long flags;
+
+	/* stop internal ping */
+	del_timer_sync(&zf_timer);
+
+	spin_lock_irqsave(&zf_port_lock, flags);
+	/* stop watchdog timer */
+	ctrl_reg = zf_get_control();
+	ctrl_reg |= (ENABLE_WD1|ENABLE_WD2);	/* disable wd1 and wd2 */
+	ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2);
+	zf_set_control(ctrl_reg);
+	spin_unlock_irqrestore(&zf_port_lock, flags);
+
+	printk(KERN_INFO PFX ": Watchdog timer is now disabled\n");
+}
+
+
+/*
+ * start hardware timer
+ */
+static void zf_timer_on(void)
+{
+	unsigned int ctrl_reg = 0;
+	unsigned long flags;
+
+	spin_lock_irqsave(&zf_port_lock, flags);
+
+	zf_writeb(PULSE_LEN, 0xff);
+
+	zf_set_timer(ZF_CTIMEOUT, WD1);
+
+	/* user land ping */
+	next_heartbeat = jiffies + ZF_USER_TIMEO;
+
+	/* start the timer for internal ping */
+	zf_timer.expires = jiffies + ZF_HW_TIMEO;
+
+	add_timer(&zf_timer);
+
+	/* start watchdog timer */
+	ctrl_reg = zf_get_control();
+	ctrl_reg |= (ENABLE_WD1|zf_action);
+	zf_set_control(ctrl_reg);
+	spin_unlock_irqrestore(&zf_port_lock, flags);
+
+	printk(KERN_INFO PFX ": Watchdog timer is now enabled\n");
+}
+
+
+static void zf_ping(unsigned long data)
+{
+	unsigned int ctrl_reg = 0;
+	unsigned long flags;
+
+	zf_writeb(COUNTER_2, 0xff);
+
+	if(time_before(jiffies, next_heartbeat)){
+
+		dprintk("time_before: %ld\n", next_heartbeat - jiffies);
+
+		/*
+		 * reset event is activated by transition from 0 to 1 on
+		 * RESET_WD1 bit and we assume that it is already zero...
+		 */
+
+		spin_lock_irqsave(&zf_port_lock, flags);
+		ctrl_reg = zf_get_control();
+		ctrl_reg |= RESET_WD1;
+		zf_set_control(ctrl_reg);
+
+		/* ...and nothing changes until here */
+		ctrl_reg &= ~(RESET_WD1);
+		zf_set_control(ctrl_reg);
+		spin_unlock_irqrestore(&zf_port_lock, flags);
+
+		zf_timer.expires = jiffies + ZF_HW_TIMEO;
+		add_timer(&zf_timer);
+	}else{
+		printk(KERN_CRIT PFX ": I will reset your machine\n");
+	}
+}
+
+static ssize_t zf_write(struct file *file, const char __user *buf, size_t count,
+								loff_t *ppos)
+{
+	/* See if we got the magic character */
+	if(count){
+
+		/*
+		 * no need to check for close confirmation
+		 * no way to disable watchdog ;)
+		 */
+		if (!nowayout) {
+			size_t ofs;
+
+			/*
+			 * note: just in case someone wrote the magic character
+			 * five months ago...
+			 */
+			zf_expect_close = 0;
+
+			/* now scan */
+			for (ofs = 0; ofs != count; ofs++){
+				char c;
+				if (get_user(c, buf + ofs))
+					return -EFAULT;
+				if (c == 'V'){
+					zf_expect_close = 42;
+					dprintk("zf_expect_close = 42\n");
+				}
+			}
+		}
+
+		/*
+		 * Well, anyhow someone wrote to us,
+		 * we should return that favour
+		 */
+		next_heartbeat = jiffies + ZF_USER_TIMEO;
+		dprintk("user ping at %ld\n", jiffies);
+
+	}
+
+	return count;
+}
+
+static int zf_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	switch(cmd){
+		case WDIOC_GETSUPPORT:
+			if (copy_to_user(argp, &zf_info, sizeof(zf_info)))
+				return -EFAULT;
+			break;
+
+		case WDIOC_GETSTATUS:
+			return put_user(0, p);
+
+		case WDIOC_KEEPALIVE:
+			zf_ping(0);
+			break;
+
+		default:
+			return -ENOIOCTLCMD;
+	}
+
+	return 0;
+}
+
+static int zf_open(struct inode *inode, struct file *file)
+{
+	spin_lock(&zf_lock);
+	if(test_and_set_bit(0, &zf_is_open)) {
+		spin_unlock(&zf_lock);
+		return -EBUSY;
+	}
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	spin_unlock(&zf_lock);
+
+	zf_timer_on();
+
+	return nonseekable_open(inode, file);
+}
+
+static int zf_close(struct inode *inode, struct file *file)
+{
+	if(zf_expect_close == 42){
+		zf_timer_off();
+	} else {
+		del_timer(&zf_timer);
+		printk(KERN_ERR PFX ": device file closed unexpectedly. Will not stop the WDT!\n");
+	}
+
+	spin_lock(&zf_lock);
+	clear_bit(0, &zf_is_open);
+	spin_unlock(&zf_lock);
+
+	zf_expect_close = 0;
+
+	return 0;
+}
+
+/*
+ * Notifier for system down
+ */
+
+static int zf_notify_sys(struct notifier_block *this, unsigned long code,
+								void *unused)
+{
+	if(code == SYS_DOWN || code == SYS_HALT){
+		zf_timer_off();
+	}
+
+	return NOTIFY_DONE;
+}
+
+
+
+
+static struct file_operations zf_fops = {
+	.owner          = THIS_MODULE,
+	.llseek         = no_llseek,
+	.write          = zf_write,
+	.ioctl          = zf_ioctl,
+	.open           = zf_open,
+	.release        = zf_close,
+};
+
+static struct miscdevice zf_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &zf_fops,
+};
+ 
+
+/*
+ * The device needs to learn about soft shutdowns in order to
+ * turn the timebomb registers off.
+ */
+static struct notifier_block zf_notifier = {
+	.notifier_call = zf_notify_sys,
+};
+
+static void __init zf_show_action(int act)
+{
+	char *str[] = { "RESET", "SMI", "NMI", "SCI" };
+
+	printk(KERN_INFO PFX ": Watchdog using action = %s\n", str[act]);
+}
+
+static int __init zf_init(void)
+{
+	int ret;
+
+	printk(KERN_INFO PFX ": MachZ ZF-Logic Watchdog driver initializing.\n");
+
+	ret = zf_get_ZFL_version();
+	printk("%#x\n", ret);
+	if((!ret) || (ret != 0xffff)){
+		printk(KERN_WARNING PFX ": no ZF-Logic found\n");
+		return -ENODEV;
+	}
+
+	if((action <= 3) && (action >= 0)){
+		zf_action = zf_action>>action;
+	} else
+		action = 0;
+
+	zf_show_action(action);
+
+	spin_lock_init(&zf_lock);
+	spin_lock_init(&zf_port_lock);
+
+	ret = misc_register(&zf_miscdev);
+	if (ret){
+		printk(KERN_ERR "can't misc_register on minor=%d\n",
+							WATCHDOG_MINOR);
+		goto out;
+	}
+
+	if(!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")){
+		printk(KERN_ERR "cannot reserve I/O ports at %d\n",
+							ZF_IOBASE);
+		ret = -EBUSY;
+		goto no_region;
+	}
+
+	ret = register_reboot_notifier(&zf_notifier);
+	if(ret){
+		printk(KERN_ERR "can't register reboot notifier (err=%d)\n",
+									ret);
+		goto no_reboot;
+	}
+
+	zf_set_status(0);
+	zf_set_control(0);
+
+	/* this is the timer that will do the hard work */
+	init_timer(&zf_timer);
+	zf_timer.function = zf_ping;
+	zf_timer.data = 0;
+
+	return 0;
+
+no_reboot:
+	release_region(ZF_IOBASE, 3);
+no_region:
+	misc_deregister(&zf_miscdev);
+out:
+	return ret;
+}
+
+
+static void __exit zf_exit(void)
+{
+	zf_timer_off();
+
+	misc_deregister(&zf_miscdev);
+	unregister_reboot_notifier(&zf_notifier);
+	release_region(ZF_IOBASE, 3);
+}
+
+module_init(zf_init);
+module_exit(zf_exit);
diff --git a/drivers/char/watchdog/mixcomwd.c b/drivers/char/watchdog/mixcomwd.c
new file mode 100644
index 0000000..3143e4a
--- /dev/null
+++ b/drivers/char/watchdog/mixcomwd.c
@@ -0,0 +1,306 @@
+/*
+ * MixCom Watchdog: A Simple Hardware Watchdog Device
+ * Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis
+ *
+ * Author: Gergely Madarasz <gorgo@itc.hu>
+ *
+ * Copyright (c) 1999 ITConsult-Pro Co. <info@itc.hu>
+ *
+ * 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.
+ *
+ * Version 0.1 (99/04/15):
+ *		- first version
+ *
+ * Version 0.2 (99/06/16):
+ *		- added kernel timer watchdog ping after close
+ *		  since the hardware does not support watchdog shutdown
+ *
+ * Version 0.3 (99/06/21):
+ *		- added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls
+ *
+ * Version 0.3.1 (99/06/22):
+ *		- allow module removal while internal timer is active,
+ *		  print warning about probable reset
+ *
+ * Version 0.4 (99/11/15):
+ *		- support for one more type board
+ *
+ * Version 0.5 (2001/12/14) Matt Domsch <Matt_Domsch@dell.com>
+ *              - added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *
+ */
+
+#define VERSION "0.5"
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+static int mixcomwd_ioports[] = { 0x180, 0x280, 0x380, 0x000 };
+
+#define MIXCOM_WATCHDOG_OFFSET 0xc10
+#define MIXCOM_ID 0x11
+#define FLASHCOM_WATCHDOG_OFFSET 0x4
+#define FLASHCOM_ID 0x18
+
+static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */
+
+static int watchdog_port;
+static int mixcomwd_timer_alive;
+static struct timer_list mixcomwd_timer = TIMER_INITIALIZER(NULL, 0, 0);
+static char expect_close;
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void mixcomwd_ping(void)
+{
+	outb_p(55,watchdog_port);
+	return;
+}
+
+static void mixcomwd_timerfun(unsigned long d)
+{
+	mixcomwd_ping();
+
+	mod_timer(&mixcomwd_timer,jiffies+ 5*HZ);
+}
+
+/*
+ *	Allow only one person to hold it open
+ */
+
+static int mixcomwd_open(struct inode *inode, struct file *file)
+{
+	if(test_and_set_bit(0,&mixcomwd_opened)) {
+		return -EBUSY;
+	}
+	mixcomwd_ping();
+
+	if (nowayout) {
+		/*
+		 * fops_get() code via open() has already done
+		 * a try_module_get() so it is safe to do the
+		 * __module_get().
+		 */
+		__module_get(THIS_MODULE);
+	} else {
+		if(mixcomwd_timer_alive) {
+			del_timer(&mixcomwd_timer);
+			mixcomwd_timer_alive=0;
+		}
+	}
+	return nonseekable_open(inode, file);
+}
+
+static int mixcomwd_release(struct inode *inode, struct file *file)
+{
+	if (expect_close == 42) {
+		if(mixcomwd_timer_alive) {
+			printk(KERN_ERR "mixcomwd: release called while internal timer alive");
+			return -EBUSY;
+		}
+		init_timer(&mixcomwd_timer);
+		mixcomwd_timer.expires=jiffies + 5 * HZ;
+		mixcomwd_timer.function=mixcomwd_timerfun;
+		mixcomwd_timer.data=0;
+		mixcomwd_timer_alive=1;
+		add_timer(&mixcomwd_timer);
+	} else {
+		printk(KERN_CRIT "mixcomwd: WDT device closed unexpectedly.  WDT will not stop!\n");
+	}
+
+	clear_bit(0,&mixcomwd_opened);
+	expect_close=0;
+	return 0;
+}
+
+
+static ssize_t mixcomwd_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
+{
+	if(len)
+	{
+		if (!nowayout) {
+			size_t i;
+
+			/* In case it was set long ago */
+			expect_close = 0;
+
+			for (i = 0; i != len; i++) {
+				char c;
+				if (get_user(c, data + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		mixcomwd_ping();
+	}
+	return len;
+}
+
+static int mixcomwd_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int status;
+	static struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "MixCOM watchdog",
+	};
+
+	switch(cmd)
+	{
+		case WDIOC_GETSTATUS:
+			status=mixcomwd_opened;
+			if (!nowayout) {
+				status|=mixcomwd_timer_alive;
+			}
+			if (copy_to_user(p, &status, sizeof(int))) {
+				return -EFAULT;
+			}
+			break;
+		case WDIOC_GETSUPPORT:
+			if (copy_to_user(argp, &ident, sizeof(ident))) {
+				return -EFAULT;
+			}
+			break;
+		case WDIOC_KEEPALIVE:
+			mixcomwd_ping();
+			break;
+		default:
+			return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static struct file_operations mixcomwd_fops=
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= mixcomwd_write,
+	.ioctl		= mixcomwd_ioctl,
+	.open		= mixcomwd_open,
+	.release	= mixcomwd_release,
+};
+
+static struct miscdevice mixcomwd_miscdev=
+{
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &mixcomwd_fops,
+};
+
+static int __init mixcomwd_checkcard(int port)
+{
+	int id;
+
+	port += MIXCOM_WATCHDOG_OFFSET;
+	if (!request_region(port, 1, "MixCOM watchdog")) {
+		return 0;
+	}
+
+	id=inb_p(port) & 0x3f;
+	if(id!=MIXCOM_ID) {
+		release_region(port, 1);
+		return 0;
+	}
+	return port;
+}
+
+static int __init flashcom_checkcard(int port)
+{
+	int id;
+
+	port += FLASHCOM_WATCHDOG_OFFSET;
+	if (!request_region(port, 1, "MixCOM watchdog")) {
+		return 0;
+	}
+
+	id=inb_p(port);
+ 	if(id!=FLASHCOM_ID) {
+		release_region(port, 1);
+		return 0;
+	}
+ 	return port;
+ }
+
+static int __init mixcomwd_init(void)
+{
+	int i;
+	int ret;
+	int found=0;
+
+	for (i = 0; !found && mixcomwd_ioports[i] != 0; i++) {
+		watchdog_port = mixcomwd_checkcard(mixcomwd_ioports[i]);
+		if (watchdog_port) {
+			found = 1;
+		}
+	}
+
+	/* The FlashCOM card can be set up at 0x300 -> 0x378, in 0x8 jumps */
+	for (i = 0x300; !found && i < 0x380; i+=0x8) {
+		watchdog_port = flashcom_checkcard(i);
+		if (watchdog_port) {
+			found = 1;
+		}
+	}
+
+	if (!found) {
+		printk("mixcomwd: No card detected, or port not available.\n");
+		return -ENODEV;
+	}
+
+	ret = misc_register(&mixcomwd_miscdev);
+	if (ret)
+	{
+		release_region(watchdog_port, 1);
+		return ret;
+	}
+
+	printk(KERN_INFO "MixCOM watchdog driver v%s, watchdog port at 0x%3x\n",VERSION,watchdog_port);
+
+	return 0;
+}
+
+static void __exit mixcomwd_exit(void)
+{
+	if (!nowayout) {
+		if(mixcomwd_timer_alive) {
+			printk(KERN_WARNING "mixcomwd: I quit now, hardware will"
+			       " probably reboot!\n");
+			del_timer(&mixcomwd_timer);
+			mixcomwd_timer_alive=0;
+		}
+	}
+	release_region(watchdog_port,1);
+	misc_deregister(&mixcomwd_miscdev);
+}
+
+module_init(mixcomwd_init);
+module_exit(mixcomwd_exit);
+
+MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>");
+MODULE_DESCRIPTION("MixCom Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/mpc8xx_wdt.c b/drivers/char/watchdog/mpc8xx_wdt.c
new file mode 100644
index 0000000..56d62ba
--- /dev/null
+++ b/drivers/char/watchdog/mpc8xx_wdt.c
@@ -0,0 +1,164 @@
+/*
+ * mpc8xx_wdt.c - MPC8xx watchdog userspace interface
+ *
+ * Author: Florian Schirmer <jolt@tuxbox.org>
+ *
+ * 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <asm/8xx_immap.h>
+#include <asm/uaccess.h>
+#include <syslib/m8xx_wdt.h>
+
+static unsigned long wdt_opened;
+static int wdt_status;
+
+static void mpc8xx_wdt_handler_disable(void)
+{
+	volatile immap_t *imap = (volatile immap_t *)IMAP_ADDR;
+
+	imap->im_sit.sit_piscr &= ~(PISCR_PIE | PISCR_PTE);
+
+	printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler deactivated\n");
+}
+
+static void mpc8xx_wdt_handler_enable(void)
+{
+	volatile immap_t *imap = (volatile immap_t *)IMAP_ADDR;
+
+	imap->im_sit.sit_piscr |= PISCR_PIE | PISCR_PTE;
+
+	printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler activated\n");
+}
+
+static int mpc8xx_wdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &wdt_opened))
+		return -EBUSY;
+
+	m8xx_wdt_reset();
+	mpc8xx_wdt_handler_disable();
+
+	return 0;
+}
+
+static int mpc8xx_wdt_release(struct inode *inode, struct file *file)
+{
+	m8xx_wdt_reset();
+
+#if !defined(CONFIG_WATCHDOG_NOWAYOUT)
+	mpc8xx_wdt_handler_enable();
+#endif
+
+	clear_bit(0, &wdt_opened);
+
+	return 0;
+}
+
+static ssize_t mpc8xx_wdt_write(struct file *file, const char *data, size_t len,
+				loff_t * ppos)
+{
+	if (ppos != &file->f_pos)
+		return -ESPIPE;
+
+	if (len)
+		m8xx_wdt_reset();
+
+	return len;
+}
+
+static int mpc8xx_wdt_ioctl(struct inode *inode, struct file *file,
+			    unsigned int cmd, unsigned long arg)
+{
+	int timeout;
+	static struct watchdog_info info = {
+		.options = WDIOF_KEEPALIVEPING,
+		.firmware_version = 0,
+		.identity = "MPC8xx watchdog",
+	};
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user((void *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		break;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		if (put_user(wdt_status, (int *)arg))
+			return -EFAULT;
+		wdt_status &= ~WDIOF_KEEPALIVEPING;
+		break;
+
+	case WDIOC_GETTEMP:
+		return -EOPNOTSUPP;
+
+	case WDIOC_SETOPTIONS:
+		return -EOPNOTSUPP;
+
+	case WDIOC_KEEPALIVE:
+		m8xx_wdt_reset();
+		wdt_status |= WDIOF_KEEPALIVEPING;
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		return -EOPNOTSUPP;
+
+	case WDIOC_GETTIMEOUT:
+		timeout = m8xx_wdt_get_timeout();
+		if (put_user(timeout, (int *)arg))
+			return -EFAULT;
+		break;
+
+	default:
+		return -ENOIOCTLCMD;
+	}
+
+	return 0;
+}
+
+static struct file_operations mpc8xx_wdt_fops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.write = mpc8xx_wdt_write,
+	.ioctl = mpc8xx_wdt_ioctl,
+	.open = mpc8xx_wdt_open,
+	.release = mpc8xx_wdt_release,
+};
+
+static struct miscdevice mpc8xx_wdt_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &mpc8xx_wdt_fops,
+};
+
+static int __init mpc8xx_wdt_init(void)
+{
+	return misc_register(&mpc8xx_wdt_miscdev);
+}
+
+static void __exit mpc8xx_wdt_exit(void)
+{
+	misc_deregister(&mpc8xx_wdt_miscdev);
+
+	m8xx_wdt_reset();
+	mpc8xx_wdt_handler_enable();
+}
+
+module_init(mpc8xx_wdt_init);
+module_exit(mpc8xx_wdt_exit);
+
+MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>");
+MODULE_DESCRIPTION("MPC8xx watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/pcwd.c b/drivers/char/watchdog/pcwd.c
new file mode 100644
index 0000000..592dca1
--- /dev/null
+++ b/drivers/char/watchdog/pcwd.c
@@ -0,0 +1,926 @@
+/*
+ * PC Watchdog Driver
+ * by Ken Hollis (khollis@bitgate.com)
+ *
+ * Permission granted from Simon Machell (73244.1270@compuserve.com)
+ * Written for the Linux Kernel, and GPLed by Ken Hollis
+ *
+ * 960107	Added request_region routines, modulized the whole thing.
+ * 960108	Fixed end-of-file pointer (Thanks to Dan Hollis), added
+ *		WD_TIMEOUT define.
+ * 960216	Added eof marker on the file, and changed verbose messages.
+ * 960716	Made functional and cosmetic changes to the source for
+ *		inclusion in Linux 2.0.x kernels, thanks to Alan Cox.
+ * 960717	Removed read/seek routines, replaced with ioctl.  Also, added
+ *		check_region command due to Alan's suggestion.
+ * 960821	Made changes to compile in newer 2.0.x kernels.  Added
+ *		"cold reboot sense" entry.
+ * 960825	Made a few changes to code, deleted some defines and made
+ *		typedefs to replace them.  Made heartbeat reset only available
+ *		via ioctl, and removed the write routine.
+ * 960828	Added new items for PC Watchdog Rev.C card.
+ * 960829	Changed around all of the IOCTLs, added new features,
+ *		added watchdog disable/re-enable routines.  Added firmware
+ *		version reporting.  Added read routine for temperature.
+ *		Removed some extra defines, added an autodetect Revision
+ *		routine.
+ * 961006       Revised some documentation, fixed some cosmetic bugs.  Made
+ *              drivers to panic the system if it's overheating at bootup.
+ * 961118	Changed some verbiage on some of the output, tidied up
+ *		code bits, and added compatibility to 2.1.x.
+ * 970912       Enabled board on open and disable on close.
+ * 971107	Took account of recent VFS changes (broke read).
+ * 971210       Disable board on initialisation in case board already ticking.
+ * 971222       Changed open/close for temperature handling
+ *              Michael Meskes <meskes@debian.org>.
+ * 980112       Used minor numbers from include/linux/miscdevice.h
+ * 990403       Clear reset status after reading control status register in
+ *              pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>]
+ * 990605	Made changes to code to support Firmware 1.22a, added
+ *		fairly useless proc entry.
+ * 990610	removed said useless proc code for the merge <alan>
+ * 000403	Removed last traces of proc code. <davej>
+ * 011214	Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com>
+ *              Added timeout module option to override default
+ */
+
+/*
+ *	A bells and whistles driver is available from http://www.pcwd.de/
+ *	More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/config.h>
+#include <linux/wait.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/reboot.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#define WD_VER                  "1.16 (06/12/2004)"
+#define PFX			"pcwd: "
+
+/*
+ * It should be noted that PCWD_REVISION_B was removed because A and B
+ * are essentially the same types of card, with the exception that B
+ * has temperature reporting.  Since I didn't receive a Rev.B card,
+ * the Rev.B card is not supported.  (It's a good thing too, as they
+ * are no longer in production.)
+ */
+#define	PCWD_REVISION_A		1
+#define	PCWD_REVISION_C		2
+
+/*
+ * These are the defines that describe the control status bits for the
+ * PC Watchdog card, revision A.
+ */
+#define WD_WDRST                0x01	/* Previously reset state */
+#define WD_T110                 0x02	/* Temperature overheat sense */
+#define WD_HRTBT                0x04	/* Heartbeat sense */
+#define WD_RLY2                 0x08	/* External relay triggered */
+#define WD_SRLY2                0x80	/* Software external relay triggered */
+
+/*
+ * These are the defines that describe the control status bits for the
+ * PC Watchdog card, revision C.
+ */
+#define WD_REVC_WTRP            0x01	/* Watchdog Trip status */
+#define WD_REVC_HRBT            0x02	/* Watchdog Heartbeat */
+#define WD_REVC_TTRP            0x04	/* Temperature Trip status */
+
+/* max. time we give an ISA watchdog card to process a command */
+/* 500ms for each 4 bit response (according to spec.) */
+#define ISA_COMMAND_TIMEOUT     1000
+
+/* Watchdog's internal commands */
+#define CMD_ISA_IDLE                    0x00
+#define CMD_ISA_VERSION_INTEGER         0x01
+#define CMD_ISA_VERSION_TENTH           0x02
+#define CMD_ISA_VERSION_HUNDRETH        0x03
+#define CMD_ISA_VERSION_MINOR           0x04
+#define CMD_ISA_SWITCH_SETTINGS         0x05
+#define CMD_ISA_DELAY_TIME_2SECS        0x0A
+#define CMD_ISA_DELAY_TIME_4SECS        0x0B
+#define CMD_ISA_DELAY_TIME_8SECS        0x0C
+
+/*
+ * We are using an kernel timer to do the pinging of the watchdog
+ * every ~500ms. We try to set the internal heartbeat of the
+ * watchdog to 2 ms.
+ */
+
+#define WDT_INTERVAL (HZ/2+1)
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* internal variables */
+static atomic_t open_allowed = ATOMIC_INIT(1);
+static char expect_close;
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static int temp_panic;
+static int revision;			/* The card's revision */
+static int supports_temp;		/* Wether or not the card has a temperature device */
+static int command_mode;		/* Wether or not the card is in command mode */
+static int initial_status;		/* The card's boot status */
+static int current_readport;		/* The cards I/O address */
+static spinlock_t io_lock;
+
+/* module parameters */
+#define WATCHDOG_HEARTBEAT 60		/* 60 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<=heartbeat<=7200, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ *	Internal functions
+ */
+
+static int send_isa_command(int cmd)
+{
+	int i;
+	int control_status;
+	int port0, last_port0;	/* Double read for stabilising */
+
+	/* The WCMD bit must be 1 and the command is only 4 bits in size */
+	control_status = (cmd & 0x0F) | 0x80;
+	outb_p(control_status, current_readport + 2);
+	udelay(ISA_COMMAND_TIMEOUT);
+
+	port0 = inb_p(current_readport);
+	for (i = 0; i < 25; ++i) {
+		last_port0 = port0;
+		port0 = inb_p(current_readport);
+
+		if (port0 == last_port0)
+			break;	/* Data is stable */
+
+		udelay (250);
+	}
+
+	return port0;
+}
+
+static int set_command_mode(void)
+{
+	int i, found=0, count=0;
+
+	/* Set the card into command mode */
+	spin_lock(&io_lock);
+	while ((!found) && (count < 3)) {
+		i = send_isa_command(CMD_ISA_IDLE);
+
+		if (i == 0x00)
+			found = 1;
+		else if (i == 0xF3) {
+			/* Card does not like what we've done to it */
+			outb_p(0x00, current_readport + 2);
+			udelay(1200);	/* Spec says wait 1ms */
+			outb_p(0x00, current_readport + 2);
+			udelay(ISA_COMMAND_TIMEOUT);
+		}
+		count++;
+	}
+	spin_unlock(&io_lock);
+	command_mode = found;
+
+	return(found);
+}
+
+static void unset_command_mode(void)
+{
+	/* Set the card into normal mode */
+	spin_lock(&io_lock);
+	outb_p(0x00, current_readport + 2);
+	udelay(ISA_COMMAND_TIMEOUT);
+	spin_unlock(&io_lock);
+
+	command_mode = 0;
+}
+
+static void pcwd_timer_ping(unsigned long data)
+{
+	int wdrst_stat;
+
+	/* If we got a heartbeat pulse within the WDT_INTERVAL
+	 * we agree to ping the WDT */
+	if(time_before(jiffies, next_heartbeat)) {
+		/* Ping the watchdog */
+		spin_lock(&io_lock);
+		if (revision == PCWD_REVISION_A) {
+			/*  Rev A cards are reset by setting the WD_WDRST bit in register 1 */
+			wdrst_stat = inb_p(current_readport);
+			wdrst_stat &= 0x0F;
+			wdrst_stat |= WD_WDRST;
+
+			outb_p(wdrst_stat, current_readport + 1);
+		} else {
+			/* Re-trigger watchdog by writing to port 0 */
+			outb_p(0x00, current_readport);
+		}
+
+		/* Re-set the timer interval */
+		mod_timer(&timer, jiffies + WDT_INTERVAL);
+
+		spin_unlock(&io_lock);
+	} else {
+		printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+	}
+}
+
+static int pcwd_start(void)
+{
+	int stat_reg;
+
+	next_heartbeat = jiffies + (heartbeat * HZ);
+
+	/* Start the timer */
+	mod_timer(&timer, jiffies + WDT_INTERVAL);
+
+	/* Enable the port */
+	if (revision == PCWD_REVISION_C) {
+		spin_lock(&io_lock);
+		outb_p(0x00, current_readport + 3);
+		udelay(ISA_COMMAND_TIMEOUT);
+		stat_reg = inb_p(current_readport + 2);
+		spin_unlock(&io_lock);
+		if (stat_reg & 0x10) {
+			printk(KERN_INFO PFX "Could not start watchdog\n");
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+static int pcwd_stop(void)
+{
+	int stat_reg;
+
+	/* Stop the timer */
+	del_timer(&timer);
+
+	/*  Disable the board  */
+	if (revision == PCWD_REVISION_C) {
+		spin_lock(&io_lock);
+		outb_p(0xA5, current_readport + 3);
+		udelay(ISA_COMMAND_TIMEOUT);
+		outb_p(0xA5, current_readport + 3);
+		udelay(ISA_COMMAND_TIMEOUT);
+		stat_reg = inb_p(current_readport + 2);
+		spin_unlock(&io_lock);
+		if ((stat_reg & 0x10) == 0) {
+			printk(KERN_INFO PFX "Could not stop watchdog\n");
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+static int pcwd_keepalive(void)
+{
+	/* user land ping */
+	next_heartbeat = jiffies + (heartbeat * HZ);
+	return 0;
+}
+
+static int pcwd_set_heartbeat(int t)
+{
+	if ((t < 2) || (t > 7200)) /* arbitrary upper limit */
+		return -EINVAL;
+
+	heartbeat = t;
+	return 0;
+}
+
+static int pcwd_get_status(int *status)
+{
+	int card_status;
+
+	*status=0;
+	spin_lock(&io_lock);
+	if (revision == PCWD_REVISION_A)
+		/* Rev A cards return status information from
+		 * the base register, which is used for the
+		 * temperature in other cards. */
+		card_status = inb(current_readport);
+	else {
+		/* Rev C cards return card status in the base
+		 * address + 1 register. And use different bits
+		 * to indicate a card initiated reset, and an
+		 * over-temperature condition. And the reboot
+		 * status can be reset. */
+		card_status = inb(current_readport + 1);
+	}
+	spin_unlock(&io_lock);
+
+	if (revision == PCWD_REVISION_A) {
+		if (card_status & WD_WDRST)
+			*status |= WDIOF_CARDRESET;
+
+		if (card_status & WD_T110) {
+			*status |= WDIOF_OVERHEAT;
+			if (temp_panic) {
+				printk (KERN_INFO PFX "Temperature overheat trip!\n");
+				machine_power_off();
+			}
+		}
+	} else {
+		if (card_status & WD_REVC_WTRP)
+			*status |= WDIOF_CARDRESET;
+
+		if (card_status & WD_REVC_TTRP) {
+			*status |= WDIOF_OVERHEAT;
+			if (temp_panic) {
+				printk (KERN_INFO PFX "Temperature overheat trip!\n");
+				machine_power_off();
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int pcwd_clear_status(void)
+{
+	if (revision == PCWD_REVISION_C) {
+		spin_lock(&io_lock);
+		outb_p(0x00, current_readport + 1); /* clear reset status */
+		spin_unlock(&io_lock);
+	}
+	return 0;
+}
+
+static int pcwd_get_temperature(int *temperature)
+{
+	/* check that port 0 gives temperature info and no command results */
+	if (command_mode)
+		return -1;
+
+	*temperature = 0;
+	if (!supports_temp)
+		return -ENODEV;
+
+	/*
+	 * Convert celsius to fahrenheit, since this was
+	 * the decided 'standard' for this return value.
+	 */
+	spin_lock(&io_lock);
+	*temperature = ((inb(current_readport)) * 9 / 5) + 32;
+	spin_unlock(&io_lock);
+
+	return 0;
+}
+
+/*
+ *	/dev/watchdog handling
+ */
+
+static int pcwd_ioctl(struct inode *inode, struct file *file,
+		      unsigned int cmd, unsigned long arg)
+{
+	int rv;
+	int status;
+	int temperature;
+	int new_heartbeat;
+	int __user *argp = (int __user *)arg;
+	static struct watchdog_info ident = {
+		.options =		WDIOF_OVERHEAT |
+					WDIOF_CARDRESET |
+					WDIOF_KEEPALIVEPING |
+					WDIOF_SETTIMEOUT |
+					WDIOF_MAGICCLOSE,
+		.firmware_version =	1,
+		.identity =		"PCWD",
+	};
+
+	switch(cmd) {
+	default:
+		return -ENOIOCTLCMD;
+
+	case WDIOC_GETSUPPORT:
+		if(copy_to_user(argp, &ident, sizeof(ident)))
+			return -EFAULT;
+		return 0;
+
+	case WDIOC_GETSTATUS:
+		pcwd_get_status(&status);
+		return put_user(status, argp);
+
+	case WDIOC_GETBOOTSTATUS:
+		return put_user(initial_status, argp);
+
+	case WDIOC_GETTEMP:
+		if (pcwd_get_temperature(&temperature))
+			return -EFAULT;
+
+		return put_user(temperature, argp);
+
+	case WDIOC_SETOPTIONS:
+		if (revision == PCWD_REVISION_C)
+		{
+			if(copy_from_user(&rv, argp, sizeof(int)))
+				return -EFAULT;
+
+			if (rv & WDIOS_DISABLECARD)
+			{
+				return pcwd_stop();
+			}
+
+			if (rv & WDIOS_ENABLECARD)
+			{
+				return pcwd_start();
+			}
+
+			if (rv & WDIOS_TEMPPANIC)
+			{
+				temp_panic = 1;
+			}
+		}
+		return -EINVAL;
+
+	case WDIOC_KEEPALIVE:
+		pcwd_keepalive();
+		return 0;
+
+	case WDIOC_SETTIMEOUT:
+		if (get_user(new_heartbeat, argp))
+			return -EFAULT;
+
+		if (pcwd_set_heartbeat(new_heartbeat))
+			return -EINVAL;
+
+		pcwd_keepalive();
+		/* Fall */
+
+	case WDIOC_GETTIMEOUT:
+		return put_user(heartbeat, argp);
+	}
+
+	return 0;
+}
+
+static ssize_t pcwd_write(struct file *file, const char __user *buf, size_t len,
+			  loff_t *ppos)
+{
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			/* In case it was set long ago */
+			expect_close = 0;
+
+			for (i = 0; i != len; i++) {
+				char c;
+
+				if (get_user(c, buf + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		pcwd_keepalive();
+	}
+	return len;
+}
+
+static int pcwd_open(struct inode *inode, struct file *file)
+{
+	if (!atomic_dec_and_test(&open_allowed) ) {
+		atomic_inc( &open_allowed );
+		return -EBUSY;
+	}
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	/* Activate */
+	pcwd_start();
+	pcwd_keepalive();
+	return nonseekable_open(inode, file);
+}
+
+static int pcwd_close(struct inode *inode, struct file *file)
+{
+	if (expect_close == 42) {
+		pcwd_stop();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		pcwd_keepalive();
+	}
+	expect_close = 0;
+	atomic_inc( &open_allowed );
+	return 0;
+}
+
+/*
+ *	/dev/temperature handling
+ */
+
+static ssize_t pcwd_temp_read(struct file *file, char __user *buf, size_t count,
+			 loff_t *ppos)
+{
+	int temperature;
+
+	if (pcwd_get_temperature(&temperature))
+		return -EFAULT;
+
+	if (copy_to_user(buf, &temperature, 1))
+		return -EFAULT;
+
+	return 1;
+}
+
+static int pcwd_temp_open(struct inode *inode, struct file *file)
+{
+	if (!supports_temp)
+		return -ENODEV;
+
+	return nonseekable_open(inode, file);
+}
+
+static int pcwd_temp_close(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+/*
+ *	Notify system
+ */
+
+static int pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the WDT off */
+		pcwd_stop();
+	}
+
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations pcwd_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= pcwd_write,
+	.ioctl		= pcwd_ioctl,
+	.open		= pcwd_open,
+	.release	= pcwd_close,
+};
+
+static struct miscdevice pcwd_miscdev = {
+	.minor =	WATCHDOG_MINOR,
+	.name =		"watchdog",
+	.fops =		&pcwd_fops,
+};
+
+static struct file_operations pcwd_temp_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= pcwd_temp_read,
+	.open		= pcwd_temp_open,
+	.release	= pcwd_temp_close,
+};
+
+static struct miscdevice temp_miscdev = {
+	.minor =	TEMP_MINOR,
+	.name =		"temperature",
+	.fops =		&pcwd_temp_fops,
+};
+
+static struct notifier_block pcwd_notifier = {
+	.notifier_call =	pcwd_notify_sys,
+};
+
+/*
+ *	Init & exit routines
+ */
+
+static inline void get_support(void)
+{
+	if (inb(current_readport) != 0xF0)
+		supports_temp = 1;
+}
+
+static inline int get_revision(void)
+{
+	int r = PCWD_REVISION_C;
+
+	spin_lock(&io_lock);
+	/* REV A cards use only 2 io ports; test
+	 * presumes a floating bus reads as 0xff. */
+	if ((inb(current_readport + 2) == 0xFF) ||
+	    (inb(current_readport + 3) == 0xFF))
+		r=PCWD_REVISION_A;
+	spin_unlock(&io_lock);
+
+	return r;
+}
+
+static inline char *get_firmware(void)
+{
+	int one, ten, hund, minor;
+	char *ret;
+
+	ret = kmalloc(6, GFP_KERNEL);
+	if(ret == NULL)
+		return NULL;
+
+	if (set_command_mode()) {
+		one = send_isa_command(CMD_ISA_VERSION_INTEGER);
+		ten = send_isa_command(CMD_ISA_VERSION_TENTH);
+		hund = send_isa_command(CMD_ISA_VERSION_HUNDRETH);
+		minor = send_isa_command(CMD_ISA_VERSION_MINOR);
+		sprintf(ret, "%c.%c%c%c", one, ten, hund, minor);
+	}
+	else
+		sprintf(ret, "ERROR");
+
+	unset_command_mode();
+	return(ret);
+}
+
+static inline int get_option_switches(void)
+{
+	int rv=0;
+
+	if (set_command_mode()) {
+		/* Get switch settings */
+		rv = send_isa_command(CMD_ISA_SWITCH_SETTINGS);
+	}
+
+	unset_command_mode();
+	return(rv);
+}
+
+static int __devinit pcwatchdog_init(int base_addr)
+{
+	int ret;
+	char *firmware;
+	int option_switches;
+
+	cards_found++;
+	if (cards_found == 1)
+		printk(KERN_INFO PFX "v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER);
+
+	if (cards_found > 1) {
+		printk(KERN_ERR PFX "This driver only supports 1 device\n");
+		return -ENODEV;
+	}
+
+	if (base_addr == 0x0000) {
+		printk(KERN_ERR PFX "No I/O-Address for card detected\n");
+		return -ENODEV;
+	}
+	current_readport = base_addr;
+
+	/* Check card's revision */
+	revision = get_revision();
+
+	if (!request_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4, "PCWD")) {
+		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			current_readport);
+		current_readport = 0x0000;
+		return -EIO;
+	}
+
+	/* Initial variables */
+	supports_temp = 0;
+	temp_panic = 0;
+	initial_status = 0x0000;
+
+	/* get the boot_status */
+	pcwd_get_status(&initial_status);
+
+	/* clear the "card caused reboot" flag */
+	pcwd_clear_status();
+
+	init_timer(&timer);
+	timer.function = pcwd_timer_ping;
+	timer.data = 0;
+
+	/*  Disable the board  */
+	pcwd_stop();
+
+	/*  Check whether or not the card supports the temperature device */
+	get_support();
+
+	/* Get some extra info from the hardware (in command/debug/diag mode) */
+	if (revision == PCWD_REVISION_A)
+		printk(KERN_INFO PFX "ISA-PC Watchdog (REV.A) detected at port 0x%04x\n", current_readport);
+	else if (revision == PCWD_REVISION_C) {
+		firmware = get_firmware();
+		printk(KERN_INFO PFX "ISA-PC Watchdog (REV.C) detected at port 0x%04x (Firmware version: %s)\n",
+			current_readport, firmware);
+		kfree(firmware);
+		option_switches = get_option_switches();
+		printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+			option_switches,
+			((option_switches & 0x10) ? "ON" : "OFF"),
+			((option_switches & 0x08) ? "ON" : "OFF"));
+
+		/* Reprogram internal heartbeat to 2 seconds */
+		if (set_command_mode()) {
+			send_isa_command(CMD_ISA_DELAY_TIME_2SECS);
+			unset_command_mode();
+		}
+	} else {
+		/* Should NEVER happen, unless get_revision() fails. */
+		printk(KERN_INFO PFX "Unable to get revision\n");
+		release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+		current_readport = 0x0000;
+		return -1;
+	}
+
+	if (supports_temp)
+		printk(KERN_INFO PFX "Temperature Option Detected\n");
+
+	if (initial_status & WDIOF_CARDRESET)
+		printk(KERN_INFO PFX "Previous reboot was caused by the card\n");
+
+	if (initial_status & WDIOF_OVERHEAT) {
+		printk(KERN_EMERG PFX "Card senses a CPU Overheat. Panicking!\n");
+		printk(KERN_EMERG PFX "CPU Overheat\n");
+	}
+
+	if (initial_status == 0)
+		printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n");
+
+	/* Check that the heartbeat value is within it's range ; if not reset to the default */
+        if (pcwd_set_heartbeat(heartbeat)) {
+                pcwd_set_heartbeat(WATCHDOG_HEARTBEAT);
+                printk(KERN_INFO PFX "heartbeat value must be 2<=heartbeat<=7200, using %d\n",
+                        WATCHDOG_HEARTBEAT);
+	}
+
+	ret = register_reboot_notifier(&pcwd_notifier);
+	if (ret) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+		current_readport = 0x0000;
+		return ret;
+	}
+
+	if (supports_temp) {
+		ret = misc_register(&temp_miscdev);
+		if (ret) {
+			printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+				TEMP_MINOR, ret);
+			unregister_reboot_notifier(&pcwd_notifier);
+			release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+			current_readport = 0x0000;
+			return ret;
+		}
+	}
+
+	ret = misc_register(&pcwd_miscdev);
+	if (ret) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		if (supports_temp)
+			misc_deregister(&temp_miscdev);
+		unregister_reboot_notifier(&pcwd_notifier);
+		release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+		current_readport = 0x0000;
+		return ret;
+	}
+
+	printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+		heartbeat, nowayout);
+
+	return 0;
+}
+
+static void __devexit pcwatchdog_exit(void)
+{
+	/*  Disable the board  */
+	if (!nowayout)
+		pcwd_stop();
+
+	/* Deregister */
+	misc_deregister(&pcwd_miscdev);
+	if (supports_temp)
+		misc_deregister(&temp_miscdev);
+	unregister_reboot_notifier(&pcwd_notifier);
+	release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4);
+	current_readport = 0x0000;
+}
+
+/*
+ *  The ISA cards have a heartbeat bit in one of the registers, which
+ *  register is card dependent.  The heartbeat bit is monitored, and if
+ *  found, is considered proof that a Berkshire card has been found.
+ *  The initial rate is once per second at board start up, then twice
+ *  per second for normal operation.
+ */
+static int __init pcwd_checkcard(int base_addr)
+{
+	int port0, last_port0;	/* Reg 0, in case it's REV A */
+	int port1, last_port1;	/* Register 1 for REV C cards */
+	int i;
+	int retval;
+
+	if (!request_region (base_addr, 4, "PCWD")) {
+		printk (KERN_INFO PFX "Port 0x%04x unavailable\n", base_addr);
+		return 0;
+	}
+
+	retval = 0;
+
+	port0 = inb_p(base_addr);	/* For REV A boards */
+	port1 = inb_p(base_addr + 1);	/* For REV C boards */
+	if (port0 != 0xff || port1 != 0xff) {
+		/* Not an 'ff' from a floating bus, so must be a card! */
+		for (i = 0; i < 4; ++i) {
+
+			msleep(500);
+
+			last_port0 = port0;
+			last_port1 = port1;
+
+			port0 = inb_p(base_addr);
+			port1 = inb_p(base_addr + 1);
+
+			/* Has either hearbeat bit changed?  */
+			if ((port0 ^ last_port0) & WD_HRTBT ||
+			    (port1 ^ last_port1) & WD_REVC_HRBT) {
+				retval = 1;
+				break;
+			}
+		}
+	}
+	release_region (base_addr, 4);
+
+	return retval;
+}
+
+/*
+ * These are the auto-probe addresses available.
+ *
+ * Revision A only uses ports 0x270 and 0x370.  Revision C introduced 0x350.
+ * Revision A has an address range of 2 addresses, while Revision C has 4.
+ */
+static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 };
+
+static int __init pcwd_init_module(void)
+{
+	int i, found = 0;
+
+	spin_lock_init(&io_lock);
+
+	for (i = 0; pcwd_ioports[i] != 0; i++) {
+		if (pcwd_checkcard(pcwd_ioports[i])) {
+			if (!(pcwatchdog_init(pcwd_ioports[i])))
+				found++;
+		}
+	}
+
+	if (!found) {
+		printk (KERN_INFO PFX "No card detected, or port not available\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit pcwd_cleanup_module(void)
+{
+	if (current_readport)
+		pcwatchdog_exit();
+	return;
+}
+
+module_init(pcwd_init_module);
+module_exit(pcwd_cleanup_module);
+
+MODULE_AUTHOR("Ken Hollis <kenji@bitgate.com>");
+MODULE_DESCRIPTION("Berkshire ISA-PC Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);
diff --git a/drivers/char/watchdog/pcwd_pci.c b/drivers/char/watchdog/pcwd_pci.c
new file mode 100644
index 0000000..8ce0666
--- /dev/null
+++ b/drivers/char/watchdog/pcwd_pci.c
@@ -0,0 +1,677 @@
+/*
+ *	Berkshire PCI-PC Watchdog Card Driver
+ *
+ *	(c) Copyright 2003 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ *	Based on source code of the following authors:
+ *	  Ken Hollis <kenji@bitgate.com>,
+ *	  Lindsay Harris <lindsay@bluegum.com>,
+ *	  Alan Cox <alan@redhat.com>,
+ *	  Matt Domsch <Matt_Domsch@dell.com>,
+ *	  Rob Radez <rob@osinvestor.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.
+ *
+ *	Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ *	provide warranty for any of this software. This material is
+ *	provided "AS-IS" and at no charge.
+ */
+
+/*
+ *	A bells and whistles driver is available from http://www.pcwd.de/
+ *	More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/
+ */
+
+/*
+ *	Includes, defines, variables, module parameters, ...
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+/* Module and version information */
+#define WATCHDOG_VERSION "1.01"
+#define WATCHDOG_DATE "15 Mar 2005"
+#define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog"
+#define WATCHDOG_NAME "pcwd_pci"
+#define PFX WATCHDOG_NAME ": "
+#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n"
+
+/* Stuff for the PCI ID's  */
+#ifndef PCI_VENDOR_ID_QUICKLOGIC
+#define PCI_VENDOR_ID_QUICKLOGIC    0x11e3
+#endif
+
+#ifndef PCI_DEVICE_ID_WATCHDOG_PCIPCWD
+#define PCI_DEVICE_ID_WATCHDOG_PCIPCWD 0x5030
+#endif
+
+/*
+ * These are the defines that describe the control status bits for the
+ * PCI-PC Watchdog card.
+ */
+#define WD_PCI_WTRP             0x01	/* Watchdog Trip status */
+#define WD_PCI_HRBT             0x02	/* Watchdog Heartbeat */
+#define WD_PCI_TTRP             0x04	/* Temperature Trip status */
+
+/* according to documentation max. time to process a command for the pci
+ * watchdog card is 100 ms, so we give it 150 ms to do it's job */
+#define PCI_COMMAND_TIMEOUT	150
+
+/* Watchdog's internal commands */
+#define CMD_GET_STATUS			0x04
+#define CMD_GET_FIRMWARE_VERSION	0x08
+#define CMD_READ_WATCHDOG_TIMEOUT	0x18
+#define CMD_WRITE_WATCHDOG_TIMEOUT	0x19
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* internal variables */
+static int temp_panic;
+static unsigned long is_active;
+static char expect_release;
+static struct {
+	int supports_temp;	/* Wether or not the card has a temperature device */
+	int boot_status;	/* The card's boot status */
+	unsigned long io_addr;	/* The cards I/O address */
+	spinlock_t io_lock;
+	struct pci_dev *pdev;
+} pcipcwd_private;
+
+/* module parameters */
+#define WATCHDOG_HEARTBEAT 2	/* 2 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ *	Internal functions
+ */
+
+static int send_command(int cmd, int *msb, int *lsb)
+{
+	int got_response, count;
+
+	spin_lock(&pcipcwd_private.io_lock);
+	/* If a command requires data it should be written first.
+	 * Data for commands with 8 bits of data should be written to port 4.
+	 * Commands with 16 bits of data, should be written as LSB to port 4
+	 * and MSB to port 5.
+	 * After the required data has been written then write the command to
+	 * port 6. */
+	outb_p(*lsb, pcipcwd_private.io_addr + 4);
+	outb_p(*msb, pcipcwd_private.io_addr + 5);
+	outb_p(cmd, pcipcwd_private.io_addr + 6);
+
+	/* wait till the pci card processed the command, signaled by
+	 * the WRSP bit in port 2 and give it a max. timeout of
+	 * PCI_COMMAND_TIMEOUT to process */
+	got_response = inb_p(pcipcwd_private.io_addr + 2) & 0x40;
+	for (count = 0; (count < PCI_COMMAND_TIMEOUT) && (!got_response); count++) {
+		mdelay(1);
+		got_response = inb_p(pcipcwd_private.io_addr + 2) & 0x40;
+	}
+
+	if (got_response) {
+		/* read back response */
+		*lsb = inb_p(pcipcwd_private.io_addr + 4);
+		*msb = inb_p(pcipcwd_private.io_addr + 5);
+
+		/* clear WRSP bit */
+		inb_p(pcipcwd_private.io_addr + 6);
+	}
+	spin_unlock(&pcipcwd_private.io_lock);
+
+	return got_response;
+}
+
+static int pcipcwd_start(void)
+{
+	int stat_reg;
+
+	spin_lock(&pcipcwd_private.io_lock);
+	outb_p(0x00, pcipcwd_private.io_addr + 3);
+	udelay(1000);
+
+	stat_reg = inb_p(pcipcwd_private.io_addr + 2);
+	spin_unlock(&pcipcwd_private.io_lock);
+
+	if (stat_reg & 0x10) {
+		printk(KERN_ERR PFX "Card timer not enabled\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int pcipcwd_stop(void)
+{
+	int stat_reg;
+
+	spin_lock(&pcipcwd_private.io_lock);
+	outb_p(0xA5, pcipcwd_private.io_addr + 3);
+	udelay(1000);
+
+	outb_p(0xA5, pcipcwd_private.io_addr + 3);
+	udelay(1000);
+
+	stat_reg = inb_p(pcipcwd_private.io_addr + 2);
+	spin_unlock(&pcipcwd_private.io_lock);
+
+	if (!(stat_reg & 0x10)) {
+		printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int pcipcwd_keepalive(void)
+{
+	/* Re-trigger watchdog by writing to port 0 */
+	outb_p(0x42, pcipcwd_private.io_addr);
+	return 0;
+}
+
+static int pcipcwd_set_heartbeat(int t)
+{
+	int t_msb = t / 256;
+	int t_lsb = t % 256;
+
+	if ((t < 0x0001) || (t > 0xFFFF))
+		return -EINVAL;
+
+	/* Write new heartbeat to watchdog */
+	send_command(CMD_WRITE_WATCHDOG_TIMEOUT, &t_msb, &t_lsb);
+
+	heartbeat = t;
+	return 0;
+}
+
+static int pcipcwd_get_status(int *status)
+{
+	int new_status;
+
+	*status=0;
+	new_status = inb_p(pcipcwd_private.io_addr + 1);
+	if (new_status & WD_PCI_WTRP)
+		*status |= WDIOF_CARDRESET;
+	if (new_status & WD_PCI_TTRP) {
+		*status |= WDIOF_OVERHEAT;
+		if (temp_panic)
+			panic(PFX "Temperature overheat trip!\n");
+	}
+
+	return 0;
+}
+
+static int pcipcwd_clear_status(void)
+{
+	outb_p(0x01, pcipcwd_private.io_addr + 1);
+	return 0;
+}
+
+static int pcipcwd_get_temperature(int *temperature)
+{
+	*temperature = 0;
+	if (!pcipcwd_private.supports_temp)
+		return -ENODEV;
+
+	/*
+	 * Convert celsius to fahrenheit, since this was
+	 * the decided 'standard' for this return value.
+	 */
+	*temperature = ((inb_p(pcipcwd_private.io_addr)) * 9 / 5) + 32;
+
+	return 0;
+}
+
+/*
+ *	/dev/watchdog handling
+ */
+
+static ssize_t pcipcwd_write(struct file *file, const char __user *data,
+			      size_t len, loff_t *ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			expect_release = 0;
+
+			/* scan to see whether or not we got the magic character */
+			for (i = 0; i != len; i++) {
+				char c;
+				if(get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_release = 42;
+			}
+		}
+
+		/* someone wrote to us, we should reload the timer */
+		pcipcwd_keepalive();
+	}
+	return len;
+}
+
+static int pcipcwd_ioctl(struct inode *inode, struct file *file,
+			  unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options =		WDIOF_OVERHEAT |
+					WDIOF_CARDRESET |
+					WDIOF_KEEPALIVEPING |
+					WDIOF_SETTIMEOUT |
+					WDIOF_MAGICCLOSE,
+		.firmware_version =	1,
+		.identity =		WATCHDOG_DRIVER_NAME,
+	};
+
+	switch (cmd) {
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident,
+				sizeof (ident)) ? -EFAULT : 0;
+
+		case WDIOC_GETSTATUS:
+		{
+			int status;
+
+			pcipcwd_get_status(&status);
+
+			return put_user(status, p);
+		}
+
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(pcipcwd_private.boot_status, p);
+
+		case WDIOC_GETTEMP:
+		{
+			int temperature;
+
+			if (pcipcwd_get_temperature(&temperature))
+				return -EFAULT;
+
+			return put_user(temperature, p);
+		}
+
+		case WDIOC_KEEPALIVE:
+			pcipcwd_keepalive();
+			return 0;
+
+		case WDIOC_SETOPTIONS:
+		{
+			int new_options, retval = -EINVAL;
+
+			if (get_user (new_options, p))
+				return -EFAULT;
+
+			if (new_options & WDIOS_DISABLECARD) {
+				pcipcwd_stop();
+				retval = 0;
+			}
+
+			if (new_options & WDIOS_ENABLECARD) {
+				pcipcwd_start();
+				retval = 0;
+			}
+
+			if (new_options & WDIOS_TEMPPANIC) {
+				temp_panic = 1;
+				retval = 0;
+			}
+
+			return retval;
+		}
+
+		case WDIOC_SETTIMEOUT:
+		{
+			int new_heartbeat;
+
+			if (get_user(new_heartbeat, p))
+				return -EFAULT;
+
+			if (pcipcwd_set_heartbeat(new_heartbeat))
+			    return -EINVAL;
+
+			pcipcwd_keepalive();
+			/* Fall */
+		}
+
+		case WDIOC_GETTIMEOUT:
+			return put_user(heartbeat, p);
+
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+static int pcipcwd_open(struct inode *inode, struct file *file)
+{
+	/* /dev/watchdog can only be opened once */
+	if (test_and_set_bit(0, &is_active))
+		return -EBUSY;
+
+	/* Activate */
+	pcipcwd_start();
+	pcipcwd_keepalive();
+	return nonseekable_open(inode, file);
+}
+
+static int pcipcwd_release(struct inode *inode, struct file *file)
+{
+	/*
+	 *      Shut off the timer.
+	 */
+	if (expect_release == 42) {
+		pcipcwd_stop();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		pcipcwd_keepalive();
+	}
+	expect_release = 0;
+	clear_bit(0, &is_active);
+	return 0;
+}
+
+/*
+ *	/dev/temperature handling
+ */
+
+static ssize_t pcipcwd_temp_read(struct file *file, char __user *data,
+				size_t len, loff_t *ppos)
+{
+	int temperature;
+
+	if (pcipcwd_get_temperature(&temperature))
+		return -EFAULT;
+
+	if (copy_to_user (data, &temperature, 1))
+		return -EFAULT;
+
+	return 1;
+}
+
+static int pcipcwd_temp_open(struct inode *inode, struct file *file)
+{
+	if (!pcipcwd_private.supports_temp)
+		return -ENODEV;
+
+	return nonseekable_open(inode, file);
+}
+
+static int pcipcwd_temp_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+/*
+ *	Notify system
+ */
+
+static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the WDT off */
+		pcipcwd_stop();
+	}
+
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations pcipcwd_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.write =	pcipcwd_write,
+	.ioctl =	pcipcwd_ioctl,
+	.open =		pcipcwd_open,
+	.release =	pcipcwd_release,
+};
+
+static struct miscdevice pcipcwd_miscdev = {
+	.minor =	WATCHDOG_MINOR,
+	.name =		"watchdog",
+	.fops =		&pcipcwd_fops,
+};
+
+static struct file_operations pcipcwd_temp_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.read =		pcipcwd_temp_read,
+	.open =		pcipcwd_temp_open,
+	.release =	pcipcwd_temp_release,
+};
+
+static struct miscdevice pcipcwd_temp_miscdev = {
+	.minor =	TEMP_MINOR,
+	.name =		"temperature",
+	.fops =		&pcipcwd_temp_fops,
+};
+
+static struct notifier_block pcipcwd_notifier = {
+	.notifier_call =	pcipcwd_notify_sys,
+};
+
+/*
+ *	Init & exit routines
+ */
+
+static inline void check_temperature_support(void)
+{
+	if (inb_p(pcipcwd_private.io_addr) != 0xF0)
+		pcipcwd_private.supports_temp = 1;
+}
+
+static int __devinit pcipcwd_card_init(struct pci_dev *pdev,
+		const struct pci_device_id *ent)
+{
+	int ret = -EIO;
+	int got_fw_rev, fw_rev_major, fw_rev_minor;
+	char fw_ver_str[20];
+	char option_switches;
+
+	cards_found++;
+	if (cards_found == 1)
+		printk(KERN_INFO PFX DRIVER_VERSION);
+
+	if (cards_found > 1) {
+		printk(KERN_ERR PFX "This driver only supports 1 device\n");
+		return -ENODEV;
+	}
+
+	if (pci_enable_device(pdev)) {
+		printk(KERN_ERR PFX "Not possible to enable PCI Device\n");
+		return -ENODEV;
+	}
+
+	if (pci_resource_start(pdev, 0) == 0x0000) {
+		printk(KERN_ERR PFX "No I/O-Address for card detected\n");
+		ret = -ENODEV;
+		goto err_out_disable_device;
+	}
+
+	pcipcwd_private.pdev = pdev;
+	pcipcwd_private.io_addr = pci_resource_start(pdev, 0);
+
+	if (pci_request_regions(pdev, WATCHDOG_NAME)) {
+		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			(int) pcipcwd_private.io_addr);
+		ret = -EIO;
+		goto err_out_disable_device;
+	}
+
+	/* get the boot_status */
+	pcipcwd_get_status(&pcipcwd_private.boot_status);
+
+	/* clear the "card caused reboot" flag */
+	pcipcwd_clear_status();
+
+	/* disable card */
+	pcipcwd_stop();
+
+	/* Check whether or not the card supports the temperature device */
+	check_temperature_support();
+
+	/* Get the Firmware Version */
+	got_fw_rev = send_command(CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor);
+	if (got_fw_rev) {
+		sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor);
+	} else {
+		sprintf(fw_ver_str, "<card no answer>");
+	}
+
+	/* Get switch settings */
+	option_switches = inb_p(pcipcwd_private.io_addr + 3);
+
+	printk(KERN_INFO PFX "Found card at port 0x%04x (Firmware: %s) %s temp option\n",
+		(int) pcipcwd_private.io_addr, fw_ver_str,
+		(pcipcwd_private.supports_temp ? "with" : "without"));
+
+	printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+		option_switches,
+		((option_switches & 0x10) ? "ON" : "OFF"),
+		((option_switches & 0x08) ? "ON" : "OFF"));
+
+	if (pcipcwd_private.boot_status & WDIOF_CARDRESET)
+		printk(KERN_INFO PFX "Previous reset was caused by the Watchdog card\n");
+
+	if (pcipcwd_private.boot_status & WDIOF_OVERHEAT)
+		printk(KERN_INFO PFX "Card sensed a CPU Overheat\n");
+
+	if (pcipcwd_private.boot_status == 0)
+		printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n");
+
+	/* Check that the heartbeat value is within it's range ; if not reset to the default */
+	if (pcipcwd_set_heartbeat(heartbeat)) {
+		pcipcwd_set_heartbeat(WATCHDOG_HEARTBEAT);
+		printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
+			WATCHDOG_HEARTBEAT);
+	}
+
+	ret = register_reboot_notifier(&pcipcwd_notifier);
+	if (ret != 0) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		goto err_out_release_region;
+	}
+
+	if (pcipcwd_private.supports_temp) {
+		ret = misc_register(&pcipcwd_temp_miscdev);
+		if (ret != 0) {
+			printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+				TEMP_MINOR, ret);
+			goto err_out_unregister_reboot;
+		}
+	}
+
+	ret = misc_register(&pcipcwd_miscdev);
+	if (ret != 0) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto err_out_misc_deregister;
+	}
+
+	printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+		heartbeat, nowayout);
+
+	return 0;
+
+err_out_misc_deregister:
+	if (pcipcwd_private.supports_temp)
+		misc_deregister(&pcipcwd_temp_miscdev);
+err_out_unregister_reboot:
+	unregister_reboot_notifier(&pcipcwd_notifier);
+err_out_release_region:
+	pci_release_regions(pdev);
+err_out_disable_device:
+	pci_disable_device(pdev);
+	return ret;
+}
+
+static void __devexit pcipcwd_card_exit(struct pci_dev *pdev)
+{
+	/* Stop the timer before we leave */
+	if (!nowayout)
+		pcipcwd_stop();
+
+	/* Deregister */
+	misc_deregister(&pcipcwd_miscdev);
+	if (pcipcwd_private.supports_temp)
+		misc_deregister(&pcipcwd_temp_miscdev);
+	unregister_reboot_notifier(&pcipcwd_notifier);
+	pci_release_regions(pdev);
+	pci_disable_device(pdev);
+	cards_found--;
+}
+
+static struct pci_device_id pcipcwd_pci_tbl[] = {
+	{ PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCIPCWD,
+		PCI_ANY_ID, PCI_ANY_ID, },
+	{ 0 },			/* End of list */
+};
+MODULE_DEVICE_TABLE(pci, pcipcwd_pci_tbl);
+
+static struct pci_driver pcipcwd_driver = {
+	.name		= WATCHDOG_NAME,
+	.id_table	= pcipcwd_pci_tbl,
+	.probe		= pcipcwd_card_init,
+	.remove		= __devexit_p(pcipcwd_card_exit),
+};
+
+static int __init pcipcwd_init_module(void)
+{
+	spin_lock_init (&pcipcwd_private.io_lock);
+
+	return pci_register_driver(&pcipcwd_driver);
+}
+
+static void __exit pcipcwd_cleanup_module(void)
+{
+	pci_unregister_driver(&pcipcwd_driver);
+}
+
+module_init(pcipcwd_init_module);
+module_exit(pcipcwd_cleanup_module);
+
+MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>");
+MODULE_DESCRIPTION("Berkshire PCI-PC Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);
diff --git a/drivers/char/watchdog/pcwd_usb.c b/drivers/char/watchdog/pcwd_usb.c
new file mode 100644
index 0000000..1127201
--- /dev/null
+++ b/drivers/char/watchdog/pcwd_usb.c
@@ -0,0 +1,796 @@
+/*
+ *	Berkshire USB-PC Watchdog Card Driver
+ *
+ *	(c) Copyright 2004 Wim Van Sebroeck <wim@iguana.be>.
+ *
+ *	Based on source code of the following authors:
+ *	  Ken Hollis <kenji@bitgate.com>,
+ *	  Alan Cox <alan@redhat.com>,
+ *	  Matt Domsch <Matt_Domsch@dell.com>,
+ *	  Rob Radez <rob@osinvestor.com>,
+ *	  Greg Kroah-Hartman <greg@kroah.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.
+ *
+ *	Neither Wim Van Sebroeck nor Iguana vzw. admit liability nor
+ *	provide warranty for any of this software. This material is
+ *	provided "AS-IS" and at no charge.
+ *
+ *	Thanks also to Simon Machell at Berkshire Products Inc. for
+ *	providing the test hardware. More info is available at
+ *	http://www.berkprod.com/ or http://www.pcwatchdog.com/
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/fs.h>
+#include <linux/smp_lock.h>
+#include <linux/completion.h>
+#include <asm/uaccess.h>
+#include <linux/usb.h>
+
+
+#ifdef CONFIG_USB_DEBUG
+	static int debug = 1;
+#else
+	static int debug;
+#endif
+
+/* Use our own dbg macro */
+#undef dbg
+#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG PFX format "\n" , ## arg); } while (0)
+
+
+/* Module and Version Information */
+#define DRIVER_VERSION "1.01"
+#define DRIVER_DATE "15 Mar 2005"
+#define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>"
+#define DRIVER_DESC "Berkshire USB-PC Watchdog driver"
+#define DRIVER_LICENSE "GPL"
+#define DRIVER_NAME "pcwd_usb"
+#define PFX DRIVER_NAME ": "
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE(DRIVER_LICENSE);
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);
+
+/* Module Parameters */
+module_param(debug, int, 0);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
+
+#define WATCHDOG_HEARTBEAT 2	/* 2 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/* The vendor and product id's for the USB-PC Watchdog card */
+#define USB_PCWD_VENDOR_ID	0x0c98
+#define USB_PCWD_PRODUCT_ID	0x1140
+
+/* table of devices that work with this driver */
+static struct usb_device_id usb_pcwd_table [] = {
+	{ USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) },
+	{ }					/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, usb_pcwd_table);
+
+/* according to documentation max. time to process a command for the USB
+ * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */
+#define USB_COMMAND_TIMEOUT	250
+
+/* Watchdog's internal commands */
+#define CMD_READ_TEMP			0x02	/* Read Temperature; Re-trigger Watchdog */
+#define CMD_TRIGGER			CMD_READ_TEMP
+#define CMD_GET_STATUS			0x04	/* Get Status Information */
+#define CMD_GET_FIRMWARE_VERSION	0x08	/* Get Firmware Version */
+#define CMD_GET_DIP_SWITCH_SETTINGS	0x0c	/* Get Dip Switch Settings */
+#define CMD_READ_WATCHDOG_TIMEOUT	0x18	/* Read Current Watchdog Time */
+#define CMD_WRITE_WATCHDOG_TIMEOUT	0x19	/* Write Current Watchdog Time */
+#define CMD_ENABLE_WATCHDOG		0x30	/* Enable / Disable Watchdog */
+#define CMD_DISABLE_WATCHDOG		CMD_ENABLE_WATCHDOG
+
+/* Some defines that I like to be somewhere else like include/linux/usb_hid.h */
+#define HID_REQ_SET_REPORT		0x09
+#define HID_DT_REPORT			(USB_TYPE_CLASS | 0x02)
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int cards_found;
+
+/* some internal variables */
+static unsigned long is_active;
+static char expect_release;
+
+/* Structure to hold all of our device specific stuff */
+struct usb_pcwd_private {
+	struct usb_device *	udev;			/* save off the usb device pointer */
+	struct usb_interface *	interface;		/* the interface for this device */
+
+	unsigned int		interface_number;	/* the interface number used for cmd's */
+
+	unsigned char *		intr_buffer;		/* the buffer to intr data */
+	dma_addr_t		intr_dma;		/* the dma address for the intr buffer */
+	size_t			intr_size;		/* the size of the intr buffer */
+	struct urb *		intr_urb;		/* the urb used for the intr pipe */
+
+	unsigned char		cmd_command;		/* The command that is reported back */
+	unsigned char		cmd_data_msb;		/* The data MSB that is reported back */
+	unsigned char		cmd_data_lsb;		/* The data LSB that is reported back */
+	atomic_t		cmd_received;		/* true if we received a report after a command */
+
+	int			exists;			/* Wether or not the device exists */
+	struct semaphore	sem;			/* locks this structure */
+};
+static struct usb_pcwd_private *usb_pcwd_device;
+
+/* prevent races between open() and disconnect() */
+static DECLARE_MUTEX (disconnect_sem);
+
+/* local function prototypes */
+static int usb_pcwd_probe	(struct usb_interface *interface, const struct usb_device_id *id);
+static void usb_pcwd_disconnect	(struct usb_interface *interface);
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver usb_pcwd_driver = {
+	.owner =	THIS_MODULE,
+	.name =		DRIVER_NAME,
+	.probe =	usb_pcwd_probe,
+	.disconnect =	usb_pcwd_disconnect,
+	.id_table =	usb_pcwd_table,
+};
+
+
+static void usb_pcwd_intr_done(struct urb *urb, struct pt_regs *regs)
+{
+	struct usb_pcwd_private *usb_pcwd = (struct usb_pcwd_private *)urb->context;
+	unsigned char *data = usb_pcwd->intr_buffer;
+	int retval;
+
+	switch (urb->status) {
+	case 0:			/* success */
+		break;
+	case -ECONNRESET:	/* unlink */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* this urb is terminated, clean up */
+		dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status);
+		return;
+	/* -EPIPE:  should clear the halt */
+	default:		/* error */
+		dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status);
+		goto resubmit;
+	}
+
+	dbg("received following data cmd=0x%02x msb=0x%02x lsb=0x%02x",
+		data[0], data[1], data[2]);
+
+	usb_pcwd->cmd_command  = data[0];
+	usb_pcwd->cmd_data_msb = data[1];
+	usb_pcwd->cmd_data_lsb = data[2];
+
+	/* notify anyone waiting that the cmd has finished */
+	atomic_set (&usb_pcwd->cmd_received, 1);
+
+resubmit:
+	retval = usb_submit_urb (urb, GFP_ATOMIC);
+	if (retval)
+		printk(KERN_ERR PFX "can't resubmit intr, usb_submit_urb failed with result %d\n",
+			retval);
+}
+
+static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd, unsigned char cmd,
+	unsigned char *msb, unsigned char *lsb)
+{
+	int got_response, count;
+	unsigned char buf[6];
+
+	/* We will not send any commands if the USB PCWD device does not exist */
+	if ((!usb_pcwd) || (!usb_pcwd->exists))
+		return -1;
+
+	/* The USB PC Watchdog uses a 6 byte report format. The board currently uses
+	 * only 3 of the six bytes of the report. */
+	buf[0] = cmd;			/* Byte 0 = CMD */
+	buf[1] = *msb;			/* Byte 1 = Data MSB */
+	buf[2] = *lsb;			/* Byte 2 = Data LSB */
+	buf[3] = buf[4] = buf[5] = 0;	/* All other bytes not used */
+
+	dbg("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x",
+		buf[0], buf[1], buf[2]);
+
+	atomic_set (&usb_pcwd->cmd_received, 0);
+
+	if (usb_control_msg(usb_pcwd->udev, usb_sndctrlpipe(usb_pcwd->udev, 0),
+			HID_REQ_SET_REPORT, HID_DT_REPORT,
+			0x0200, usb_pcwd->interface_number, buf, sizeof(buf),
+			USB_COMMAND_TIMEOUT) != sizeof(buf)) {
+		dbg("usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x\n", cmd, *msb, *lsb);
+	}
+	/* wait till the usb card processed the command,
+	 * with a max. timeout of USB_COMMAND_TIMEOUT */
+	got_response = 0;
+	for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response); count++) {
+		mdelay(1);
+		if (atomic_read (&usb_pcwd->cmd_received))
+			got_response = 1;
+	}
+
+	if ((got_response) && (cmd == usb_pcwd->cmd_command)) {
+		/* read back response */
+		*msb = usb_pcwd->cmd_data_msb;
+		*lsb = usb_pcwd->cmd_data_lsb;
+	}
+
+	return got_response;
+}
+
+static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd)
+{
+	unsigned char msb = 0x00;
+	unsigned char lsb = 0x00;
+	int retval;
+
+	/* Enable Watchdog */
+	retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, &msb, &lsb);
+
+	if ((retval == 0) || (lsb == 0)) {
+		printk(KERN_ERR PFX "Card did not acknowledge enable attempt\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd)
+{
+	unsigned char msb = 0xA5;
+	unsigned char lsb = 0xC3;
+	int retval;
+
+	/* Disable Watchdog */
+	retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, &msb, &lsb);
+
+	if ((retval == 0) || (lsb != 0)) {
+		printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int usb_pcwd_keepalive(struct usb_pcwd_private *usb_pcwd)
+{
+	unsigned char dummy;
+
+	/* Re-trigger Watchdog */
+	usb_pcwd_send_command(usb_pcwd, CMD_TRIGGER, &dummy, &dummy);
+
+	return 0;
+}
+
+static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t)
+{
+	unsigned char msb = t / 256;
+	unsigned char lsb = t % 256;
+
+	if ((t < 0x0001) || (t > 0xFFFF))
+		return -EINVAL;
+
+	/* Write new heartbeat to watchdog */
+	usb_pcwd_send_command(usb_pcwd, CMD_WRITE_WATCHDOG_TIMEOUT, &msb, &lsb);
+
+	heartbeat = t;
+	return 0;
+}
+
+static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, int *temperature)
+{
+	unsigned char msb, lsb;
+
+	usb_pcwd_send_command(usb_pcwd, CMD_READ_TEMP, &msb, &lsb);
+
+	/*
+	 * Convert celsius to fahrenheit, since this was
+	 * the decided 'standard' for this return value.
+	 */
+	*temperature = (lsb * 9 / 5) + 32;
+
+	return 0;
+}
+
+/*
+ *	/dev/watchdog handling
+ */
+
+static ssize_t usb_pcwd_write(struct file *file, const char __user *data,
+			      size_t len, loff_t *ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			expect_release = 0;
+
+			/* scan to see whether or not we got the magic character */
+			for (i = 0; i != len; i++) {
+				char c;
+				if(get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_release = 42;
+			}
+		}
+
+		/* someone wrote to us, we should reload the timer */
+		usb_pcwd_keepalive(usb_pcwd_device);
+	}
+	return len;
+}
+
+static int usb_pcwd_ioctl(struct inode *inode, struct file *file,
+			  unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options =		WDIOF_KEEPALIVEPING |
+					WDIOF_SETTIMEOUT |
+					WDIOF_MAGICCLOSE,
+		.firmware_version =	1,
+		.identity =		DRIVER_NAME,
+	};
+
+	switch (cmd) {
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident,
+				sizeof (ident)) ? -EFAULT : 0;
+
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+
+		case WDIOC_GETTEMP:
+		{
+			int temperature;
+
+			if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature))
+				return -EFAULT;
+
+			return put_user(temperature, p);
+		}
+
+		case WDIOC_KEEPALIVE:
+			usb_pcwd_keepalive(usb_pcwd_device);
+			return 0;
+
+		case WDIOC_SETOPTIONS:
+		{
+			int new_options, retval = -EINVAL;
+
+			if (get_user (new_options, p))
+				return -EFAULT;
+
+			if (new_options & WDIOS_DISABLECARD) {
+				usb_pcwd_stop(usb_pcwd_device);
+				retval = 0;
+			}
+
+			if (new_options & WDIOS_ENABLECARD) {
+				usb_pcwd_start(usb_pcwd_device);
+				retval = 0;
+			}
+
+			return retval;
+		}
+
+		case WDIOC_SETTIMEOUT:
+		{
+			int new_heartbeat;
+
+			if (get_user(new_heartbeat, p))
+				return -EFAULT;
+
+			if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat))
+			    return -EINVAL;
+
+			usb_pcwd_keepalive(usb_pcwd_device);
+			/* Fall */
+		}
+
+		case WDIOC_GETTIMEOUT:
+			return put_user(heartbeat, p);
+
+		default:
+			return -ENOIOCTLCMD;
+	}
+}
+
+static int usb_pcwd_open(struct inode *inode, struct file *file)
+{
+	/* /dev/watchdog can only be opened once */
+	if (test_and_set_bit(0, &is_active))
+		return -EBUSY;
+
+	/* Activate */
+	usb_pcwd_start(usb_pcwd_device);
+	usb_pcwd_keepalive(usb_pcwd_device);
+	return nonseekable_open(inode, file);
+}
+
+static int usb_pcwd_release(struct inode *inode, struct file *file)
+{
+	/*
+	 *      Shut off the timer.
+	 */
+	if (expect_release == 42) {
+		usb_pcwd_stop(usb_pcwd_device);
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		usb_pcwd_keepalive(usb_pcwd_device);
+	}
+	expect_release = 0;
+	clear_bit(0, &is_active);
+	return 0;
+}
+
+/*
+ *	/dev/temperature handling
+ */
+
+static ssize_t usb_pcwd_temperature_read(struct file *file, char __user *data,
+				size_t len, loff_t *ppos)
+{
+	int temperature;
+
+	if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature))
+		return -EFAULT;
+
+	if (copy_to_user(data, &temperature, 1))
+		return -EFAULT;
+
+	return 1;
+}
+
+static int usb_pcwd_temperature_open(struct inode *inode, struct file *file)
+{
+	return nonseekable_open(inode, file);
+}
+
+static int usb_pcwd_temperature_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+/*
+ *	Notify system
+ */
+
+static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the WDT off */
+		usb_pcwd_stop(usb_pcwd_device);
+	}
+
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations usb_pcwd_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.write =	usb_pcwd_write,
+	.ioctl =	usb_pcwd_ioctl,
+	.open =		usb_pcwd_open,
+	.release =	usb_pcwd_release,
+};
+
+static struct miscdevice usb_pcwd_miscdev = {
+	.minor =	WATCHDOG_MINOR,
+	.name =		"watchdog",
+	.fops =		&usb_pcwd_fops,
+};
+
+static struct file_operations usb_pcwd_temperature_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.read =		usb_pcwd_temperature_read,
+	.open =		usb_pcwd_temperature_open,
+	.release =	usb_pcwd_temperature_release,
+};
+
+static struct miscdevice usb_pcwd_temperature_miscdev = {
+	.minor =	TEMP_MINOR,
+	.name =		"temperature",
+	.fops =		&usb_pcwd_temperature_fops,
+};
+
+static struct notifier_block usb_pcwd_notifier = {
+	.notifier_call =	usb_pcwd_notify_sys,
+};
+
+/**
+ *	usb_pcwd_delete
+ */
+static inline void usb_pcwd_delete (struct usb_pcwd_private *usb_pcwd)
+{
+	if (usb_pcwd->intr_urb != NULL)
+		usb_free_urb (usb_pcwd->intr_urb);
+	if (usb_pcwd->intr_buffer != NULL)
+		usb_buffer_free(usb_pcwd->udev, usb_pcwd->intr_size,
+				usb_pcwd->intr_buffer, usb_pcwd->intr_dma);
+	kfree (usb_pcwd);
+}
+
+/**
+ *	usb_pcwd_probe
+ *
+ *	Called by the usb core when a new device is connected that it thinks
+ *	this driver might be interested in.
+ */
+static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(interface);
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+	struct usb_pcwd_private *usb_pcwd = NULL;
+	int pipe, maxp;
+	int retval = -ENOMEM;
+	int got_fw_rev;
+	unsigned char fw_rev_major, fw_rev_minor;
+	char fw_ver_str[20];
+	unsigned char option_switches, dummy;
+
+	cards_found++;
+	if (cards_found > 1) {
+		printk(KERN_ERR PFX "This driver only supports 1 device\n");
+		return -ENODEV;
+	}
+
+	/* get the active interface descriptor */
+	iface_desc = interface->cur_altsetting;
+
+	/* check out that we have a HID device */
+	if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) {
+		printk(KERN_ERR PFX "The device isn't a Human Interface Device\n");
+		return -ENODEV;
+	}
+
+	/* check out the endpoint: it has to be Interrupt & IN */
+	endpoint = &iface_desc->endpoint[0].desc;
+
+	if (!((endpoint->bEndpointAddress & USB_DIR_IN) &&
+	     ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
+				== USB_ENDPOINT_XFER_INT))) {
+		/* we didn't find a Interrupt endpoint with direction IN */
+		printk(KERN_ERR PFX "Couldn't find an INTR & IN endpoint\n");
+		return -ENODEV;
+	}
+
+	/* get a handle to the interrupt data pipe */
+	pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
+	maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe));
+
+	/* allocate memory for our device and initialize it */
+	usb_pcwd = kmalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL);
+	if (usb_pcwd == NULL) {
+		printk(KERN_ERR PFX "Out of memory\n");
+		goto error;
+	}
+	memset (usb_pcwd, 0x00, sizeof (*usb_pcwd));
+
+	usb_pcwd_device = usb_pcwd;
+
+	init_MUTEX (&usb_pcwd->sem);
+	usb_pcwd->udev = udev;
+	usb_pcwd->interface = interface;
+	usb_pcwd->interface_number = iface_desc->desc.bInterfaceNumber;
+	usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ? le16_to_cpu(endpoint->wMaxPacketSize) : 8);
+
+	/* set up the memory buffer's */
+	if (!(usb_pcwd->intr_buffer = usb_buffer_alloc(udev, usb_pcwd->intr_size, SLAB_ATOMIC, &usb_pcwd->intr_dma))) {
+		printk(KERN_ERR PFX "Out of memory\n");
+		goto error;
+	}
+
+	/* allocate the urb's */
+	usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!usb_pcwd->intr_urb) {
+		printk(KERN_ERR PFX "Out of memory\n");
+		goto error;
+	}
+
+	/* initialise the intr urb's */
+	usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe,
+			usb_pcwd->intr_buffer, usb_pcwd->intr_size,
+			usb_pcwd_intr_done, usb_pcwd, endpoint->bInterval);
+	usb_pcwd->intr_urb->transfer_dma = usb_pcwd->intr_dma;
+	usb_pcwd->intr_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	/* register our interrupt URB with the USB system */
+	if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) {
+		printk(KERN_ERR PFX "Problem registering interrupt URB\n");
+		retval = -EIO; /* failure */
+		goto error;
+	}
+
+	/* The device exists and can be communicated with */
+	usb_pcwd->exists = 1;
+
+	/* disable card */
+	usb_pcwd_stop(usb_pcwd);
+
+	/* Get the Firmware Version */
+	got_fw_rev = usb_pcwd_send_command(usb_pcwd, CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor);
+	if (got_fw_rev) {
+		sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor);
+	} else {
+		sprintf(fw_ver_str, "<card no answer>");
+	}
+
+	printk(KERN_INFO PFX "Found card (Firmware: %s) with temp option\n",
+		fw_ver_str);
+
+	/* Get switch settings */
+	usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy, &option_switches);
+
+	printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n",
+		option_switches,
+		((option_switches & 0x10) ? "ON" : "OFF"),
+		((option_switches & 0x08) ? "ON" : "OFF"));
+
+	/* Check that the heartbeat value is within it's range ; if not reset to the default */
+	if (usb_pcwd_set_heartbeat(usb_pcwd, heartbeat)) {
+		usb_pcwd_set_heartbeat(usb_pcwd, WATCHDOG_HEARTBEAT);
+		printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
+			WATCHDOG_HEARTBEAT);
+	}
+
+	retval = register_reboot_notifier(&usb_pcwd_notifier);
+	if (retval != 0) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			retval);
+		goto error;
+	}
+
+	retval = misc_register(&usb_pcwd_temperature_miscdev);
+	if (retval != 0) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			TEMP_MINOR, retval);
+		goto err_out_unregister_reboot;
+	}
+
+	retval = misc_register(&usb_pcwd_miscdev);
+	if (retval != 0) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, retval);
+		goto err_out_misc_deregister;
+	}
+
+	/* we can register the device now, as it is ready */
+	usb_set_intfdata (interface, usb_pcwd);
+
+	printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+		heartbeat, nowayout);
+
+	return 0;
+
+err_out_misc_deregister:
+	misc_deregister(&usb_pcwd_temperature_miscdev);
+err_out_unregister_reboot:
+	unregister_reboot_notifier(&usb_pcwd_notifier);
+error:
+	usb_pcwd_delete (usb_pcwd);
+	usb_pcwd_device = NULL;
+	return retval;
+}
+
+
+/**
+ *	usb_pcwd_disconnect
+ *
+ *	Called by the usb core when the device is removed from the system.
+ *
+ *	This routine guarantees that the driver will not submit any more urbs
+ *	by clearing dev->udev.
+ */
+static void usb_pcwd_disconnect(struct usb_interface *interface)
+{
+	struct usb_pcwd_private *usb_pcwd;
+
+	/* prevent races with open() */
+	down (&disconnect_sem);
+
+	usb_pcwd = usb_get_intfdata (interface);
+	usb_set_intfdata (interface, NULL);
+
+	down (&usb_pcwd->sem);
+
+	/* Stop the timer before we leave */
+	if (!nowayout)
+		usb_pcwd_stop(usb_pcwd);
+
+	/* We should now stop communicating with the USB PCWD device */
+	usb_pcwd->exists = 0;
+
+	/* Deregister */
+	misc_deregister(&usb_pcwd_miscdev);
+	misc_deregister(&usb_pcwd_temperature_miscdev);
+	unregister_reboot_notifier(&usb_pcwd_notifier);
+
+	up (&usb_pcwd->sem);
+
+	/* Delete the USB PCWD device */
+	usb_pcwd_delete(usb_pcwd);
+
+	cards_found--;
+
+	up (&disconnect_sem);
+
+	printk(KERN_INFO PFX "USB PC Watchdog disconnected\n");
+}
+
+
+
+/**
+ *	usb_pcwd_init
+ */
+static int __init usb_pcwd_init(void)
+{
+	int result;
+
+	/* register this driver with the USB subsystem */
+	result = usb_register(&usb_pcwd_driver);
+	if (result) {
+		printk(KERN_ERR PFX "usb_register failed. Error number %d\n",
+		    result);
+		return result;
+	}
+
+	printk(KERN_INFO PFX DRIVER_DESC " v" DRIVER_VERSION " (" DRIVER_DATE ")\n");
+	return 0;
+}
+
+
+/**
+ *	usb_pcwd_exit
+ */
+static void __exit usb_pcwd_exit(void)
+{
+	/* deregister this driver with the USB subsystem */
+	usb_deregister(&usb_pcwd_driver);
+}
+
+
+module_init (usb_pcwd_init);
+module_exit (usb_pcwd_exit);
diff --git a/drivers/char/watchdog/s3c2410_wdt.c b/drivers/char/watchdog/s3c2410_wdt.c
new file mode 100644
index 0000000..1699d2c
--- /dev/null
+++ b/drivers/char/watchdog/s3c2410_wdt.c
@@ -0,0 +1,516 @@
+/* linux/drivers/char/watchdog/s3c2410_wdt.c
+ *
+ * Copyright (c) 2004 Simtec Electronics
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ * S3C2410 Watchdog Timer Support
+ *
+ * Based on, softdog.c by Alan Cox,
+ *     (c) Copyright 1996 Alan Cox <alan@redhat.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * Changelog:
+ *	05-Oct-2004	BJD	Added semaphore init to stop crashes on open
+ *				Fixed tmr_count / wdt_count confusion
+ *				Added configurable debug
+ *
+ *	11-Jan-2004	BJD	Fixed divide-by-2 in timeout code
+ *
+ *	10-Mar-2005	LCVR	Changed S3C2410_VA to S3C24XX_VA
+*/
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <asm/arch/map.h>
+#include <asm/hardware/clock.h>
+
+#undef S3C24XX_VA_WATCHDOG
+#define S3C24XX_VA_WATCHDOG (0)
+
+#include <asm/arch/regs-watchdog.h>
+
+#define PFX "s3c2410-wdt: "
+
+#define CONFIG_S3C2410_WATCHDOG_ATBOOT		(0)
+#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME	(15)
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+static int tmr_margin	= CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;
+static int tmr_atboot	= CONFIG_S3C2410_WATCHDOG_ATBOOT;
+static int soft_noboot	= 0;
+static int debug	= 0;
+
+module_param(tmr_margin,  int, 0);
+module_param(tmr_atboot,  int, 0);
+module_param(nowayout,    int, 0);
+module_param(soft_noboot, int, 0);
+module_param(debug,	  int, 0);
+
+MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")");
+
+MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT));
+
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)");
+
+MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");
+
+
+typedef enum close_state {
+	CLOSE_STATE_NOT,
+	CLOSE_STATE_ALLOW=0x4021
+} close_state_t;
+
+static DECLARE_MUTEX(open_lock);
+
+static struct resource	*wdt_mem;
+static struct resource	*wdt_irq;
+static struct clk	*wdt_clock;
+static void __iomem	*wdt_base;
+static unsigned int	 wdt_count;
+static close_state_t	 allow_close;
+
+/* watchdog control routines */
+
+#define DBG(msg...) do { \
+	if (debug) \
+		printk(KERN_INFO msg); \
+	} while(0)
+
+/* functions */
+
+static int s3c2410wdt_keepalive(void)
+{
+	writel(wdt_count, wdt_base + S3C2410_WTCNT);
+	return 0;
+}
+
+static int s3c2410wdt_stop(void)
+{
+	unsigned long wtcon;
+
+	wtcon = readl(wdt_base + S3C2410_WTCON);
+	wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
+	writel(wtcon, wdt_base + S3C2410_WTCON);
+
+	return 0;
+}
+
+static int s3c2410wdt_start(void)
+{
+	unsigned long wtcon;
+
+	s3c2410wdt_stop();
+
+	wtcon = readl(wdt_base + S3C2410_WTCON);
+	wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;
+
+	if (soft_noboot) {
+		wtcon |= S3C2410_WTCON_INTEN;
+		wtcon &= ~S3C2410_WTCON_RSTEN;
+	} else {
+		wtcon &= ~S3C2410_WTCON_INTEN;
+		wtcon |= S3C2410_WTCON_RSTEN;
+	}
+
+	DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
+	    __FUNCTION__, wdt_count, wtcon);
+
+	writel(wdt_count, wdt_base + S3C2410_WTDAT);
+	writel(wdt_count, wdt_base + S3C2410_WTCNT);
+	writel(wtcon, wdt_base + S3C2410_WTCON);
+
+	return 0;
+}
+
+static int s3c2410wdt_set_heartbeat(int timeout)
+{
+	unsigned int freq = clk_get_rate(wdt_clock);
+	unsigned int count;
+	unsigned int divisor = 1;
+	unsigned long wtcon;
+
+	if (timeout < 1)
+		return -EINVAL;
+
+	freq /= 128;
+	count = timeout * freq;
+
+	DBG("%s: count=%d, timeout=%d, freq=%d\n",
+	    __FUNCTION__, count, timeout, freq);
+
+	/* if the count is bigger than the watchdog register,
+	   then work out what we need to do (and if) we can
+	   actually make this value
+	*/
+
+	if (count >= 0x10000) {
+		for (divisor = 1; divisor <= 0x100; divisor++) {
+			if ((count / divisor) < 0x10000)
+				break;
+		}
+
+		if ((count / divisor) >= 0x10000) {
+			printk(KERN_ERR PFX "timeout %d too big\n", timeout);
+			return -EINVAL;
+		}
+	}
+
+	tmr_margin = timeout;
+
+	DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
+	    __FUNCTION__, timeout, divisor, count, count/divisor);
+
+	count /= divisor;
+	wdt_count = count;
+
+	/* update the pre-scaler */
+	wtcon = readl(wdt_base + S3C2410_WTCON);
+	wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
+	wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);
+
+	writel(count, wdt_base + S3C2410_WTDAT);
+	writel(wtcon, wdt_base + S3C2410_WTCON);
+
+	return 0;
+}
+
+/*
+ *	/dev/watchdog handling
+ */
+
+static int s3c2410wdt_open(struct inode *inode, struct file *file)
+{
+	if(down_trylock(&open_lock))
+		return -EBUSY;
+
+	if (nowayout) {
+		__module_get(THIS_MODULE);
+	} else {
+		allow_close = CLOSE_STATE_ALLOW;
+	}
+
+	/* start the timer */
+	s3c2410wdt_start();
+	return nonseekable_open(inode, file);
+}
+
+static int s3c2410wdt_release(struct inode *inode, struct file *file)
+{
+	/*
+	 *	Shut off the timer.
+	 * 	Lock it in if it's a module and we set nowayout
+	 */
+	if (allow_close == CLOSE_STATE_ALLOW) {
+		s3c2410wdt_stop();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		s3c2410wdt_keepalive();
+	}
+
+	allow_close = CLOSE_STATE_NOT;
+	up(&open_lock);
+	return 0;
+}
+
+static ssize_t s3c2410wdt_write(struct file *file, const char __user *data,
+				size_t len, loff_t *ppos)
+{
+	/*
+	 *	Refresh the timer.
+	 */
+	if(len) {
+		if (!nowayout) {
+			size_t i;
+
+			/* In case it was set long ago */
+			allow_close = CLOSE_STATE_NOT;
+
+			for (i = 0; i != len; i++) {
+				char c;
+
+				if (get_user(c, data + i))
+					return -EFAULT;
+				if (c == 'V')
+					allow_close = CLOSE_STATE_ALLOW;
+			}
+		}
+
+		s3c2410wdt_keepalive();
+	}
+	return len;
+}
+
+#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE
+
+static struct watchdog_info s3c2410_wdt_ident = {
+	.options          =     OPTIONS,
+	.firmware_version =	0,
+	.identity         =	"S3C2410 Watchdog",
+};
+
+
+static int s3c2410wdt_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int new_margin;
+
+	switch (cmd) {
+		default:
+			return -ENOIOCTLCMD;
+
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &s3c2410_wdt_ident,
+				sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0;
+
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+
+		case WDIOC_KEEPALIVE:
+			s3c2410wdt_keepalive();
+			return 0;
+
+		case WDIOC_SETTIMEOUT:
+			if (get_user(new_margin, p))
+				return -EFAULT;
+
+			if (s3c2410wdt_set_heartbeat(new_margin))
+				return -EINVAL;
+
+			s3c2410wdt_keepalive();
+			return put_user(tmr_margin, p);
+
+		case WDIOC_GETTIMEOUT:
+			return put_user(tmr_margin, p);
+	}
+}
+
+/*
+ *	Notifier for system down
+ */
+
+static int s3c2410wdt_notify_sys(struct notifier_block *this, unsigned long code,
+			      void *unused)
+{
+	if(code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the WDT off */
+		s3c2410wdt_stop();
+	}
+	return NOTIFY_DONE;
+}
+
+/* kernel interface */
+
+static struct file_operations s3c2410wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= s3c2410wdt_write,
+	.ioctl		= s3c2410wdt_ioctl,
+	.open		= s3c2410wdt_open,
+	.release	= s3c2410wdt_release,
+};
+
+static struct miscdevice s3c2410wdt_miscdev = {
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &s3c2410wdt_fops,
+};
+
+static struct notifier_block s3c2410wdt_notifier = {
+	.notifier_call	= s3c2410wdt_notify_sys,
+};
+
+/* interrupt handler code */
+
+static irqreturn_t s3c2410wdt_irq(int irqno, void *param,
+				  struct pt_regs *regs)
+{
+	printk(KERN_INFO PFX "Watchdog timer expired!\n");
+
+	s3c2410wdt_keepalive();
+	return IRQ_HANDLED;
+}
+/* device interface */
+
+static int s3c2410wdt_probe(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct resource *res;
+	int started = 0;
+	int ret;
+	int size;
+
+	DBG("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev);
+
+	/* get the memory region for the watchdog timer */
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		printk(KERN_INFO PFX "failed to get memory region resouce\n");
+		return -ENOENT;
+	}
+
+	size = (res->end-res->start)+1;
+	wdt_mem = request_mem_region(res->start, size, pdev->name);
+	if (wdt_mem == NULL) {
+		printk(KERN_INFO PFX "failed to get memory region\n");
+		return -ENOENT;
+	}
+
+	wdt_base = ioremap(res->start, size);
+	if (wdt_base == 0) {
+		printk(KERN_INFO PFX "failed to ioremap() region\n");
+		return -EINVAL;
+	}
+
+	DBG("probe: mapped wdt_base=%p\n", wdt_base);
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (res == NULL) {
+		printk(KERN_INFO PFX "failed to get irq resource\n");
+		return -ENOENT;
+	}
+
+	ret = request_irq(res->start, s3c2410wdt_irq, 0, pdev->name, dev);
+	if (ret != 0) {
+		printk(KERN_INFO PFX "failed to install irq (%d)\n", ret);
+		return ret;
+	}
+
+	wdt_clock = clk_get(dev, "watchdog");
+	if (wdt_clock == NULL) {
+		printk(KERN_INFO PFX "failed to find watchdog clock source\n");
+		return -ENOENT;
+	}
+
+	clk_use(wdt_clock);
+	clk_enable(wdt_clock);
+
+	/* see if we can actually set the requested timer margin, and if
+	 * not, try the default value */
+
+	if (s3c2410wdt_set_heartbeat(tmr_margin)) {
+		started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
+
+		if (started == 0) {
+			printk(KERN_INFO PFX "tmr_margin value out of range, default %d used\n",
+			       CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
+		} else {
+			printk(KERN_INFO PFX "default timer value is out of range, cannot start\n");
+		}
+	}
+
+	ret = register_reboot_notifier(&s3c2410wdt_notifier);
+	if (ret) {
+		printk (KERN_ERR PFX "cannot register reboot notifier (%d)\n",
+			ret);
+		return ret;
+	}
+
+	ret = misc_register(&s3c2410wdt_miscdev);
+	if (ret) {
+		printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n",
+			WATCHDOG_MINOR, ret);
+		unregister_reboot_notifier(&s3c2410wdt_notifier);
+		return ret;
+	}
+
+	if (tmr_atboot && started == 0) {
+		printk(KERN_INFO PFX "Starting Watchdog Timer\n");
+		s3c2410wdt_start();
+	}
+
+	return 0;
+}
+
+static int s3c2410wdt_remove(struct device *dev)
+{
+	if (wdt_mem != NULL) {
+		release_resource(wdt_mem);
+		kfree(wdt_mem);
+		wdt_mem = NULL;
+	}
+
+	if (wdt_irq != NULL) {
+		free_irq(wdt_irq->start, dev);
+		wdt_irq = NULL;
+	}
+
+	if (wdt_clock != NULL) {
+		clk_disable(wdt_clock);
+		clk_unuse(wdt_clock);
+		clk_put(wdt_clock);
+		wdt_clock = NULL;
+	}
+
+	misc_deregister(&s3c2410wdt_miscdev);
+	return 0;
+}
+
+static struct device_driver s3c2410wdt_driver = {
+	.name		= "s3c2410-wdt",
+	.bus		= &platform_bus_type,
+	.probe		= s3c2410wdt_probe,
+	.remove		= s3c2410wdt_remove,
+};
+
+
+
+static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n";
+
+static int __init watchdog_init(void)
+{
+	printk(banner);
+	return driver_register(&s3c2410wdt_driver);
+}
+
+static void __exit watchdog_exit(void)
+{
+	driver_unregister(&s3c2410wdt_driver);
+	unregister_reboot_notifier(&s3c2410wdt_notifier);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
+MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/sa1100_wdt.c b/drivers/char/watchdog/sa1100_wdt.c
new file mode 100644
index 0000000..34e8f7b
--- /dev/null
+++ b/drivers/char/watchdog/sa1100_wdt.c
@@ -0,0 +1,223 @@
+/*
+ *	Watchdog driver for the SA11x0/PXA2xx
+ *
+ *      (c) Copyright 2000 Oleg Drokin <green@crimea.edu>
+ *          Based on SoftDog driver by Alan Cox <alan@redhat.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.
+ *
+ *	Neither Oleg Drokin nor iXcelerator.com admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 2000           Oleg Drokin <green@crimea.edu>
+ *
+ *      27/11/2000 Initial release
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/init.h>
+
+#ifdef CONFIG_ARCH_PXA
+#include <asm/arch/pxa-regs.h>
+#endif
+
+#include <asm/hardware.h>
+#include <asm/bitops.h>
+#include <asm/uaccess.h>
+
+#define OSCR_FREQ		CLOCK_TICK_RATE
+#define SA1100_CLOSE_MAGIC	(0x5afc4453)
+
+static unsigned long sa1100wdt_users;
+static int expect_close;
+static int pre_margin;
+static int boot_status;
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+/*
+ *	Allow only one person to hold it open
+ */
+static int sa1100dog_open(struct inode *inode, struct file *file)
+{
+	nonseekable_open(inode, file);
+	if (test_and_set_bit(1,&sa1100wdt_users))
+		return -EBUSY;
+
+	/* Activate SA1100 Watchdog timer */
+	OSMR3 = OSCR + pre_margin;
+	OSSR = OSSR_M3;
+	OWER = OWER_WME;
+	OIER |= OIER_E3;
+	return 0;
+}
+
+/*
+ *	Shut off the timer.
+ * 	Lock it in if it's a module and we defined ...NOWAYOUT
+ *	Oddly, the watchdog can only be enabled, but we can turn off
+ *	the interrupt, which appears to prevent the watchdog timing out.
+ */
+static int sa1100dog_release(struct inode *inode, struct file *file)
+{
+	OSMR3 = OSCR + pre_margin;
+
+	if (expect_close == SA1100_CLOSE_MAGIC) {
+		OIER &= ~OIER_E3;
+	} else {
+		printk(KERN_CRIT "WATCHDOG: WDT device closed unexpectedly.  WDT will not stop!\n");
+	}
+
+	clear_bit(1, &sa1100wdt_users);
+	expect_close = 0;
+
+	return 0;
+}
+
+static ssize_t sa1100dog_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			expect_close = 0;
+
+			for (i = 0; i != len; i++) {
+				char c;
+
+				if (get_user(c, data + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = SA1100_CLOSE_MAGIC;
+			}
+		}
+		/* Refresh OSMR3 timer. */
+		OSMR3 = OSCR + pre_margin;
+	}
+
+	return len;
+}
+
+static struct watchdog_info ident = {
+	.options	= WDIOF_CARDRESET | WDIOF_MAGICCLOSE |
+			  WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
+	.identity	= "SA1100 Watchdog",
+};
+
+static int sa1100dog_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	int ret = -ENOIOCTLCMD;
+	int time;
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		ret = copy_to_user((struct watchdog_info *)arg, &ident,
+				   sizeof(ident)) ? -EFAULT : 0;
+		break;
+
+	case WDIOC_GETSTATUS:
+		ret = put_user(0, (int *)arg);
+		break;
+
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(boot_status, (int *)arg);
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		ret = get_user(time, (int *)arg);
+		if (ret)
+			break;
+
+		if (time <= 0 || time > 255) {
+			ret = -EINVAL;
+			break;
+		}
+
+		pre_margin = OSCR_FREQ * time;
+		OSMR3 = OSCR + pre_margin;
+		/*fall through*/
+
+	case WDIOC_GETTIMEOUT:
+		ret = put_user(pre_margin / OSCR_FREQ, (int *)arg);
+		break;
+
+	case WDIOC_KEEPALIVE:
+		OSMR3 = OSCR + pre_margin;
+		ret = 0;
+		break;
+	}
+	return ret;
+}
+
+static struct file_operations sa1100dog_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= sa1100dog_write,
+	.ioctl		= sa1100dog_ioctl,
+	.open		= sa1100dog_open,
+	.release	= sa1100dog_release,
+};
+
+static struct miscdevice sa1100dog_miscdev =
+{
+	.minor		= WATCHDOG_MINOR,
+	.name		= "SA1100/PXA2xx watchdog",
+	.fops		= &sa1100dog_fops,
+};
+
+static int margin __initdata = 60;		/* (secs) Default is 1 minute */
+
+static int __init sa1100dog_init(void)
+{
+	int ret;
+
+	/*
+	 * Read the reset status, and save it for later.  If
+	 * we suspend, RCSR will be cleared, and the watchdog
+	 * reset reason will be lost.
+	 */
+	boot_status = (RCSR & RCSR_WDR) ? WDIOF_CARDRESET : 0;
+	pre_margin = OSCR_FREQ * margin;
+
+	ret = misc_register(&sa1100dog_miscdev);
+	if (ret == 0)
+		printk("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n",
+		       margin);
+
+	return ret;
+}
+
+static void __exit sa1100dog_exit(void)
+{
+	misc_deregister(&sa1100dog_miscdev);
+}
+
+module_init(sa1100dog_init);
+module_exit(sa1100dog_exit);
+
+MODULE_AUTHOR("Oleg Drokin <green@crimea.edu>");
+MODULE_DESCRIPTION("SA1100/PXA2xx Watchdog");
+
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
+
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/sbc60xxwdt.c b/drivers/char/watchdog/sbc60xxwdt.c
new file mode 100644
index 0000000..d7de988
--- /dev/null
+++ b/drivers/char/watchdog/sbc60xxwdt.c
@@ -0,0 +1,413 @@
+/*
+ *	60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x
+ *
+ *      Based on acquirewdt.c by Alan Cox.
+ *
+ *	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.
+ *
+ *	The author does NOT admit liability nor provide warranty for
+ *	any of this software. This material is provided "AS-IS" in
+ *	the hope that it may be useful for others.
+ *
+ *	(c) Copyright 2000    Jakob Oestergaard <jakob@unthought.net>
+ *
+ *           12/4 - 2000      [Initial revision]
+ *           25/4 - 2000      Added /dev/watchdog support
+ *           09/5 - 2001      [smj@oro.net] fixed fop_write to "return 1" on success
+ *           12/4 - 2002      [rob@osinvestor.com] eliminate fop_read
+ *                            fix possible wdt_is_open race
+ *                            add CONFIG_WATCHDOG_NOWAYOUT support
+ *                            remove lock_kernel/unlock_kernel pairs
+ *                            added KERN_* to printk's
+ *                            got rid of extraneous comments
+ *                            changed watchdog_info to correctly reflect what the driver offers
+ *                            added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT,
+ *                            WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
+ *           09/8 - 2003      [wim@iguana.be] cleanup of trailing spaces
+ *                            use module_param
+ *                            made timeout (the emulated heartbeat) a module_param
+ *                            made the keepalive ping an internal subroutine
+ *                            made wdt_stop and wdt_start module params
+ *                            added extra printk's for startup problems
+ *                            added MODULE_AUTHOR and MODULE_DESCRIPTION info
+ *
+ *
+ *  This WDT driver is different from the other Linux WDT
+ *  drivers in the following ways:
+ *  *)  The driver will ping the watchdog by itself, because this
+ *      particular WDT has a very short timeout (one second) and it
+ *      would be insane to count on any userspace daemon always
+ *      getting scheduled within that time frame.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define OUR_NAME "sbc60xxwdt"
+#define PFX OUR_NAME ": "
+
+/*
+ * You must set these - The driver cannot probe for the settings
+ */
+
+static int wdt_stop = 0x45;
+module_param(wdt_stop, int, 0);
+MODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)");
+
+static int wdt_start = 0x443;
+module_param(wdt_start, int, 0);
+MODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)");
+
+/*
+ * The 60xx board can use watchdog timeout values from one second
+ * to several minutes.  The default is one second, so if we reset
+ * the watchdog every ~250ms we should be safe.
+ */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ * If the daemon pulses us every 25 seconds, we can still afford
+ * a 5 second scheduling delay on the (high priority) daemon. That
+ * should be sufficient for a box under any load.
+ */
+
+#define WATCHDOG_TIMEOUT 30		/* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT;	/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static char wdt_expect_close;
+
+/*
+ *	Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
+	 * we agree to ping the WDT
+	 */
+	if(time_before(jiffies, next_heartbeat))
+	{
+		/* Ping the WDT by reading from wdt_start */
+		inb_p(wdt_start);
+		/* Re-set the timer interval */
+		timer.expires = jiffies + WDT_INTERVAL;
+		add_timer(&timer);
+	} else {
+		printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+	}
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_startup(void)
+{
+	next_heartbeat = jiffies + (timeout * HZ);
+
+	/* Start the timer */
+	timer.expires = jiffies + WDT_INTERVAL;
+	add_timer(&timer);
+	printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
+}
+
+static void wdt_turnoff(void)
+{
+	/* Stop the timer */
+	del_timer(&timer);
+	inb_p(wdt_stop);
+	printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+	/* user land ping */
+	next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if(count)
+	{
+		if (!nowayout)
+		{
+			size_t ofs;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			wdt_expect_close = 0;
+
+			/* scan to see whether or not we got the magic character */
+			for(ofs = 0; ofs != count; ofs++)
+			{
+				char c;
+				if(get_user(c, buf+ofs))
+					return -EFAULT;
+				if(c == 'V')
+					wdt_expect_close = 42;
+			}
+		}
+
+		/* Well, anyhow someone wrote to us, we should return that favour */
+		wdt_keepalive();
+	}
+	return count;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+	nonseekable_open(inode, file);
+
+	/* Just in case we're already talking to someone... */
+	if(test_and_set_bit(0, &wdt_is_open))
+		return -EBUSY;
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	/* Good, fire up the show */
+	wdt_startup();
+	return 0;
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+	if(wdt_expect_close == 42)
+		wdt_turnoff();
+	else {
+		del_timer(&timer);
+		printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
+	}
+	clear_bit(0, &wdt_is_open);
+	wdt_expect_close = 0;
+	return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident=
+	{
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "SBC60xx",
+	};
+
+	switch(cmd)
+	{
+		default:
+			return -ENOIOCTLCMD;
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+		case WDIOC_KEEPALIVE:
+			wdt_keepalive();
+			return 0;
+		case WDIOC_SETOPTIONS:
+		{
+			int new_options, retval = -EINVAL;
+
+			if(get_user(new_options, p))
+				return -EFAULT;
+
+			if(new_options & WDIOS_DISABLECARD) {
+				wdt_turnoff();
+				retval = 0;
+			}
+
+			if(new_options & WDIOS_ENABLECARD) {
+				wdt_startup();
+				retval = 0;
+			}
+
+			return retval;
+		}
+		case WDIOC_SETTIMEOUT:
+		{
+			int new_timeout;
+
+			if(get_user(new_timeout, p))
+				return -EFAULT;
+
+			if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
+				return -EINVAL;
+
+			timeout = new_timeout;
+			wdt_keepalive();
+			/* Fall through */
+		}
+		case WDIOC_GETTIMEOUT:
+			return put_user(timeout, p);
+	}
+}
+
+static struct file_operations wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= fop_write,
+	.open		= fop_open,
+	.release	= fop_close,
+	.ioctl		= fop_ioctl,
+};
+
+static struct miscdevice wdt_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &wdt_fops,
+};
+
+/*
+ *	Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if(code==SYS_DOWN || code==SYS_HALT)
+		wdt_turnoff();
+	return NOTIFY_DONE;
+}
+
+/*
+ *	The WDT needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier=
+{
+	.notifier_call = wdt_notify_sys,
+};
+
+static void __exit sbc60xxwdt_unload(void)
+{
+	wdt_turnoff();
+
+	/* Deregister */
+	misc_deregister(&wdt_miscdev);
+
+	unregister_reboot_notifier(&wdt_notifier);
+	if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
+		release_region(wdt_stop,1);
+	release_region(wdt_start,1);
+}
+
+static int __init sbc60xxwdt_init(void)
+{
+	int rc = -EBUSY;
+
+	if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
+	{
+		timeout = WATCHDOG_TIMEOUT;
+		printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
+			timeout);
+ 	}
+
+	if (!request_region(wdt_start, 1, "SBC 60XX WDT"))
+	{
+		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			wdt_start);
+		rc = -EIO;
+		goto err_out;
+	}
+
+	/* We cannot reserve 0x45 - the kernel already has! */
+	if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
+	{
+		if (!request_region(wdt_stop, 1, "SBC 60XX WDT"))
+		{
+			printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+				wdt_stop);
+			rc = -EIO;
+			goto err_out_region1;
+		}
+	}
+
+	init_timer(&timer);
+	timer.function = wdt_timer_ping;
+	timer.data = 0;
+
+	rc = misc_register(&wdt_miscdev);
+	if (rc)
+	{
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			wdt_miscdev.minor, rc);
+		goto err_out_region2;
+	}
+
+	rc = register_reboot_notifier(&wdt_notifier);
+	if (rc)
+	{
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			rc);
+		goto err_out_miscdev;
+	}
+
+	printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n",
+		timeout, nowayout);
+
+	return 0;
+
+err_out_miscdev:
+	misc_deregister(&wdt_miscdev);
+err_out_region2:
+	if ((wdt_stop != 0x45) && (wdt_stop != wdt_start))
+		release_region(wdt_stop,1);
+err_out_region1:
+	release_region(wdt_start,1);
+err_out:
+	return rc;
+}
+
+module_init(sbc60xxwdt_init);
+module_exit(sbc60xxwdt_unload);
+
+MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>");
+MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/sc1200wdt.c b/drivers/char/watchdog/sc1200wdt.c
new file mode 100644
index 0000000..24401e8
--- /dev/null
+++ b/drivers/char/watchdog/sc1200wdt.c
@@ -0,0 +1,467 @@
+/*
+ *	National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
+ *	(c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>,
+ *			All Rights Reserved.
+ *	Based on wdt.c and wdt977.c by Alan Cox and Woody Suwalski respectively.
+ *
+ *	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.
+ *
+ *	The author(s) of this software shall not be held liable for damages
+ *	of any nature resulting due to the use of this software. This
+ *	software is provided AS-IS with no warranties.
+ *
+ *	Changelog:
+ *	20020220 Zwane Mwaikambo	Code based on datasheet, no hardware.
+ *	20020221 Zwane Mwaikambo	Cleanups as suggested by Jeff Garzik and Alan Cox.
+ *	20020222 Zwane Mwaikambo	Added probing.
+ *	20020225 Zwane Mwaikambo	Added ISAPNP support.
+ *	20020412 Rob Radez		Broke out start/stop functions
+ *		 <rob@osinvestor.com>	Return proper status instead of temperature warning
+ *					Add WDIOC_GETBOOTSTATUS and WDIOC_SETOPTIONS ioctls
+ *					Fix CONFIG_WATCHDOG_NOWAYOUT
+ *	20020530 Joel Becker		Add Matt Domsch's nowayout module option
+ *	20030116 Adam Belay		Updated to the latest pnp code
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/spinlock.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/pnp.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+
+#include <asm/semaphore.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define SC1200_MODULE_VER	"build 20020303"
+#define SC1200_MODULE_NAME	"sc1200wdt"
+#define PFX			SC1200_MODULE_NAME ": "
+
+#define	MAX_TIMEOUT	255	/* 255 minutes */
+#define PMIR		(io)	/* Power Management Index Register */
+#define PMDR		(io+1)	/* Power Management Data Register */
+
+/* Data Register indexes */
+#define FER1		0x00	/* Function enable register 1 */
+#define FER2		0x01	/* Function enable register 2 */
+#define PMC1		0x02	/* Power Management Ctrl 1 */
+#define PMC2		0x03	/* Power Management Ctrl 2 */
+#define PMC3		0x04	/* Power Management Ctrl 3 */
+#define WDTO		0x05	/* Watchdog timeout register */
+#define	WDCF		0x06	/* Watchdog config register */
+#define WDST		0x07	/* Watchdog status register */
+
+/* WDCF bitfields - which devices assert WDO */
+#define KBC_IRQ		0x01	/* Keyboard Controller */
+#define MSE_IRQ		0x02	/* Mouse */
+#define UART1_IRQ	0x03	/* Serial0 */
+#define UART2_IRQ	0x04	/* Serial1 */
+/* 5 -7 are reserved */
+
+static char banner[] __initdata = KERN_INFO PFX SC1200_MODULE_VER;
+static int timeout = 1;
+static int io = -1;
+static int io_len = 2;		/* for non plug and play */
+static struct semaphore open_sem;
+static char expect_close;
+static spinlock_t sc1200wdt_lock;	/* io port access serialisation */
+
+#if defined CONFIG_PNP
+static int isapnp = 1;
+static struct pnp_dev *wdt_dev;
+
+module_param(isapnp, int, 0);
+MODULE_PARM_DESC(isapnp, "When set to 0 driver ISA PnP support will be disabled");
+#endif
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "io port");
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+
+
+/* Read from Data Register */
+static inline void sc1200wdt_read_data(unsigned char index, unsigned char *data)
+{
+	spin_lock(&sc1200wdt_lock);
+	outb_p(index, PMIR);
+	*data = inb(PMDR);
+	spin_unlock(&sc1200wdt_lock);
+}
+
+
+/* Write to Data Register */
+static inline void sc1200wdt_write_data(unsigned char index, unsigned char data)
+{
+	spin_lock(&sc1200wdt_lock);
+	outb_p(index, PMIR);
+	outb(data, PMDR);
+	spin_unlock(&sc1200wdt_lock);
+}
+
+
+static void sc1200wdt_start(void)
+{
+	unsigned char reg;
+
+	sc1200wdt_read_data(WDCF, &reg);
+	/* assert WDO when any of the following interrupts are triggered too */
+	reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ);
+	sc1200wdt_write_data(WDCF, reg);
+	/* set the timeout and get the ball rolling */
+	sc1200wdt_write_data(WDTO, timeout);
+}
+
+
+static void sc1200wdt_stop(void)
+{
+	sc1200wdt_write_data(WDTO, 0);
+}
+
+
+/* This returns the status of the WDO signal, inactive high. */
+static inline int sc1200wdt_status(void)
+{
+	unsigned char ret;
+
+	sc1200wdt_read_data(WDST, &ret);
+	/* If the bit is inactive, the watchdog is enabled, so return
+	 * KEEPALIVEPING which is a bit of a kludge because there's nothing
+	 * else for enabled/disabled status
+	 */
+	return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING;	/* bits 1 - 7 are undefined */
+}
+
+
+static int sc1200wdt_open(struct inode *inode, struct file *file)
+{
+	nonseekable_open(inode, file);
+
+	/* allow one at a time */
+	if (down_trylock(&open_sem))
+		return -EBUSY;
+
+	if (timeout > MAX_TIMEOUT)
+		timeout = MAX_TIMEOUT;
+
+	sc1200wdt_start();
+	printk(KERN_INFO PFX "Watchdog enabled, timeout = %d min(s)", timeout);
+
+	return 0;
+}
+
+
+static int sc1200wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int new_timeout;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 0,
+		.identity = "PC87307/PC97307",
+	};
+
+	switch (cmd) {
+		default:
+			return -ENOIOCTLCMD;	/* Keep Pavel Machek amused ;) */
+
+		case WDIOC_GETSUPPORT:
+			if (copy_to_user(argp, &ident, sizeof ident))
+				return -EFAULT;
+			return 0;
+
+		case WDIOC_GETSTATUS:
+			return put_user(sc1200wdt_status(), p);
+
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+
+		case WDIOC_KEEPALIVE:
+			sc1200wdt_write_data(WDTO, timeout);
+			return 0;
+
+		case WDIOC_SETTIMEOUT:
+			if (get_user(new_timeout, p))
+				return -EFAULT;
+
+			/* the API states this is given in secs */
+			new_timeout /= 60;
+			if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)
+				return -EINVAL;
+
+			timeout = new_timeout;
+			sc1200wdt_write_data(WDTO, timeout);
+			/* fall through and return the new timeout */
+
+		case WDIOC_GETTIMEOUT:
+			return put_user(timeout * 60, p);
+
+		case WDIOC_SETOPTIONS:
+		{
+			int options, retval = -EINVAL;
+
+			if (get_user(options, p))
+				return -EFAULT;
+
+			if (options & WDIOS_DISABLECARD) {
+				sc1200wdt_stop();
+				retval = 0;
+			}
+
+			if (options & WDIOS_ENABLECARD) {
+				sc1200wdt_start();
+				retval = 0;
+			}
+
+			return retval;
+		}
+	}
+}
+
+
+static int sc1200wdt_release(struct inode *inode, struct file *file)
+{
+	if (expect_close == 42) {
+		sc1200wdt_stop();
+		printk(KERN_INFO PFX "Watchdog disabled\n");
+	} else {
+		sc1200wdt_write_data(WDTO, timeout);
+		printk(KERN_CRIT PFX "Unexpected close!, timeout = %d min(s)\n", timeout);
+	}
+	up(&open_sem);
+	expect_close = 0;
+
+	return 0;
+}
+
+
+static ssize_t sc1200wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
+{
+	if (len) {
+		if (!nowayout) {
+			size_t i;
+
+			expect_close = 0;
+
+			for (i = 0; i != len; i++) {
+				char c;
+
+				if (get_user(c, data+i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+
+		sc1200wdt_write_data(WDTO, timeout);
+		return len;
+	}
+
+	return 0;
+}
+
+
+static int sc1200wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT)
+		sc1200wdt_stop();
+
+	return NOTIFY_DONE;
+}
+
+
+static struct notifier_block sc1200wdt_notifier =
+{
+	.notifier_call =	sc1200wdt_notify_sys,
+};
+
+static struct file_operations sc1200wdt_fops =
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= sc1200wdt_write,
+	.ioctl		= sc1200wdt_ioctl,
+	.open		= sc1200wdt_open,
+	.release	= sc1200wdt_release,
+};
+
+static struct miscdevice sc1200wdt_miscdev =
+{
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &sc1200wdt_fops,
+};
+
+
+static int __init sc1200wdt_probe(void)
+{
+	/* The probe works by reading the PMC3 register's default value of 0x0e
+	 * there is one caveat, if the device disables the parallel port or any
+	 * of the UARTs we won't be able to detect it.
+	 * Nb. This could be done with accuracy by reading the SID registers, but
+	 * we don't have access to those io regions.
+	 */
+
+	unsigned char reg;
+
+	sc1200wdt_read_data(PMC3, &reg);
+	reg &= 0x0f;				/* we don't want the UART busy bits */
+	return (reg == 0x0e) ? 0 : -ENODEV;
+}
+
+
+#if defined CONFIG_PNP
+
+static struct pnp_device_id scl200wdt_pnp_devices[] = {
+	/* National Semiconductor PC87307/PC97307 watchdog component */
+	{.id = "NSC0800", .driver_data = 0},
+	{.id = ""},
+};
+
+static int scl200wdt_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id)
+{
+	/* this driver only supports one card at a time */
+	if (wdt_dev || !isapnp)
+		return -EBUSY;
+
+	wdt_dev = dev;
+	io = pnp_port_start(wdt_dev, 0);
+	io_len = pnp_port_len(wdt_dev, 0);
+
+	if (!request_region(io, io_len, SC1200_MODULE_NAME)) {
+		printk(KERN_ERR PFX "Unable to register IO port %#x\n", io);
+		return -EBUSY;
+	}
+
+	printk(KERN_INFO "scl200wdt: PnP device found at io port %#x/%d\n", io, io_len);
+	return 0;
+}
+
+static void scl200wdt_pnp_remove(struct pnp_dev * dev)
+{
+	if (wdt_dev){
+		release_region(io, io_len);
+		wdt_dev = NULL;
+	}
+}
+
+static struct pnp_driver scl200wdt_pnp_driver = {
+	.name		= "scl200wdt",
+	.id_table	= scl200wdt_pnp_devices,
+	.probe		= scl200wdt_pnp_probe,
+	.remove		= scl200wdt_pnp_remove,
+};
+
+#endif /* CONFIG_PNP */
+
+
+static int __init sc1200wdt_init(void)
+{
+	int ret;
+
+	printk(banner);
+
+	spin_lock_init(&sc1200wdt_lock);
+	sema_init(&open_sem, 1);
+
+#if defined CONFIG_PNP
+	if (isapnp) {
+		ret = pnp_register_driver(&scl200wdt_pnp_driver);
+		if (ret)
+			goto out_clean;
+	}
+#endif
+
+	if (io == -1) {
+		printk(KERN_ERR PFX "io parameter must be specified\n");
+		ret = -EINVAL;
+		goto out_clean;
+	}
+
+#if defined CONFIG_PNP
+	/* now that the user has specified an IO port and we haven't detected
+	 * any devices, disable pnp support */
+	isapnp = 0;
+	pnp_unregister_driver(&scl200wdt_pnp_driver);
+#endif
+
+	if (!request_region(io, io_len, SC1200_MODULE_NAME)) {
+		printk(KERN_ERR PFX "Unable to register IO port %#x\n", io);
+		ret = -EBUSY;
+		goto out_clean;
+	}
+
+	ret = sc1200wdt_probe();
+	if (ret)
+		goto out_io;
+
+	ret = register_reboot_notifier(&sc1200wdt_notifier);
+	if (ret) {
+		printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret);
+		goto out_io;
+	}
+
+	ret = misc_register(&sc1200wdt_miscdev);
+	if (ret) {
+		printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR);
+		goto out_rbt;
+	}
+
+	/* ret = 0 */
+
+out_clean:
+	return ret;
+
+out_rbt:
+	unregister_reboot_notifier(&sc1200wdt_notifier);
+
+out_io:
+	release_region(io, io_len);
+
+	goto out_clean;
+}
+
+
+static void __exit sc1200wdt_exit(void)
+{
+	misc_deregister(&sc1200wdt_miscdev);
+	unregister_reboot_notifier(&sc1200wdt_notifier);
+
+#if defined CONFIG_PNP
+	if(isapnp)
+		pnp_unregister_driver(&scl200wdt_pnp_driver);
+	else
+#endif
+	release_region(io, io_len);
+}
+
+module_init(sc1200wdt_init);
+module_exit(sc1200wdt_exit);
+
+MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>");
+MODULE_DESCRIPTION("Driver for National Semiconductor PC87307/PC97307 watchdog component");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/sc520_wdt.c b/drivers/char/watchdog/sc520_wdt.c
new file mode 100644
index 0000000..f6d143e
--- /dev/null
+++ b/drivers/char/watchdog/sc520_wdt.c
@@ -0,0 +1,447 @@
+/*
+ *	AMD Elan SC520 processor Watchdog Timer driver
+ *
+ *      Based on acquirewdt.c by Alan Cox,
+ *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
+ *
+ *	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.
+ *
+ *	The authors do NOT admit liability nor provide warranty for
+ *	any of this software. This material is provided "AS-IS" in
+ *      the hope that it may be useful for others.
+ *
+ *	(c) Copyright 2001    Scott Jennings <linuxdrivers@oro.net>
+ *           9/27 - 2001      [Initial release]
+ *
+ *	Additional fixes Alan Cox
+ *	-	Fixed formatting
+ *	-	Removed debug printks
+ *	-	Fixed SMP built kernel deadlock
+ *	-	Switched to private locks not lock_kernel
+ *	-	Used ioremap/writew/readw
+ *	-	Added NOWAYOUT support
+ *	4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com>
+ *	-	Change comments
+ *	-	Eliminate fop_llseek
+ *	-	Change CONFIG_WATCHDOG_NOWAYOUT semantics
+ *	-	Add KERN_* tags to printks
+ *	-	fix possible wdt_is_open race
+ *	-	Report proper capabilities in watchdog_info
+ *	-	Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT,
+ *		GETTIMEOUT, SETOPTIONS} ioctls
+ *	09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be>
+ *	-	cleanup of trailing spaces
+ *	-	added extra printk's for startup problems
+ *	-	use module_param
+ *	-	made timeout (the emulated heartbeat) a module_param
+ *	-	made the keepalive ping an internal subroutine
+ *	3/27 - 2004 Changes by Sean Young <sean@mess.org>
+ *	-	set MMCR_BASE to 0xfffef000
+ *	-	CBAR does not need to be read
+ *	-	removed debugging printks
+ *
+ *  This WDT driver is different from most other Linux WDT
+ *  drivers in that the driver will ping the watchdog by itself,
+ *  because this particular WDT has a very short timeout (1.6
+ *  seconds) and it would be insane to count on any userspace
+ *  daemon always getting scheduled within that time frame.
+ *
+ *  This driver uses memory mapped IO, and spinlock.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define OUR_NAME "sc520_wdt"
+#define PFX OUR_NAME ": "
+
+/*
+ * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
+ *
+ *   0: 492us    2: 1.01s    4: 4.03s   6: 16.22s
+ *   1: 503ms    3: 2.01s    5: 8.05s   7: 32.21s
+ *
+ * We will program the SC520 watchdog for a timeout of 2.01s.
+ * If we reset the watchdog every ~250ms we should be safe.
+ */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ */
+
+#define WATCHDOG_TIMEOUT 30		/* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT;	/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * AMD Elan SC520 - Watchdog Timer Registers
+ */
+#define MMCR_BASE	0xfffef000	/* The default base address */
+#define OFFS_WDTMRCTL	0xCB0	/* Watchdog Timer Control Register */
+
+/* WDT Control Register bit definitions */
+#define WDT_EXP_SEL_01	0x0001	/* [01] Time-out = 496 us (with 33 Mhz clk). */
+#define WDT_EXP_SEL_02	0x0002	/* [02] Time-out = 508 ms (with 33 Mhz clk). */
+#define WDT_EXP_SEL_03	0x0004	/* [03] Time-out = 1.02 s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_04	0x0008	/* [04] Time-out = 2.03 s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_05	0x0010	/* [05] Time-out = 4.07 s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_06	0x0020	/* [06] Time-out = 8.13 s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_07	0x0040	/* [07] Time-out = 16.27s (with 33 Mhz clk). */
+#define WDT_EXP_SEL_08	0x0080	/* [08] Time-out = 32.54s (with 33 Mhz clk). */
+#define WDT_IRQ_FLG	0x1000	/* [12] Interrupt Request Flag */
+#define WDT_WRST_ENB	0x4000	/* [14] Watchdog Timer Reset Enable */
+#define WDT_ENB		0x8000	/* [15] Watchdog Timer Enable */
+
+static __u16 __iomem *wdtmrctl;
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static char wdt_expect_close;
+static spinlock_t wdt_spinlock;
+
+/*
+ *	Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
+	 * we agree to ping the WDT
+	 */
+	if(time_before(jiffies, next_heartbeat))
+	{
+		/* Ping the WDT */
+		spin_lock(&wdt_spinlock);
+		writew(0xAAAA, wdtmrctl);
+		writew(0x5555, wdtmrctl);
+		spin_unlock(&wdt_spinlock);
+
+		/* Re-set the timer interval */
+		timer.expires = jiffies + WDT_INTERVAL;
+		add_timer(&timer);
+	} else {
+		printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+	}
+}
+
+/*
+ *	Utility routines
+ */
+
+static void wdt_config(int writeval)
+{
+	__u16 dummy;
+	unsigned long flags;
+
+	/* buy some time (ping) */
+	spin_lock_irqsave(&wdt_spinlock, flags);
+	dummy=readw(wdtmrctl);	/* ensure write synchronization */
+	writew(0xAAAA, wdtmrctl);
+	writew(0x5555, wdtmrctl);
+	/* unlock WDT = make WDT configuration register writable one time */
+	writew(0x3333, wdtmrctl);
+	writew(0xCCCC, wdtmrctl);
+	/* write WDT configuration register */
+	writew(writeval, wdtmrctl);
+	spin_unlock_irqrestore(&wdt_spinlock, flags);
+}
+
+static int wdt_startup(void)
+{
+	next_heartbeat = jiffies + (timeout * HZ);
+
+	/* Start the timer */
+	timer.expires = jiffies + WDT_INTERVAL;
+	add_timer(&timer);
+
+	/* Start the watchdog */
+	wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04);
+
+	printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
+	return 0;
+}
+
+static int wdt_turnoff(void)
+{
+	/* Stop the timer */
+	del_timer(&timer);
+
+	/* Stop the watchdog */
+	wdt_config(0);
+
+	printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
+	return 0;
+}
+
+static int wdt_keepalive(void)
+{
+	/* user land ping */
+	next_heartbeat = jiffies + (timeout * HZ);
+	return 0;
+}
+
+static int wdt_set_heartbeat(int t)
+{
+	if ((t < 1) || (t > 3600))	/* arbitrary upper limit */
+		return -EINVAL;
+
+	timeout = t;
+	return 0;
+}
+
+/*
+ *	/dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if(count) {
+		if (!nowayout) {
+			size_t ofs;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			wdt_expect_close = 0;
+
+			/* now scan */
+			for(ofs = 0; ofs != count; ofs++) {
+				char c;
+				if (get_user(c, buf + ofs))
+					return -EFAULT;
+				if(c == 'V')
+					wdt_expect_close = 42;
+			}
+		}
+
+		/* Well, anyhow someone wrote to us, we should return that favour */
+		wdt_keepalive();
+	}
+	return count;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+	nonseekable_open(inode, file);
+
+	/* Just in case we're already talking to someone... */
+	if(test_and_set_bit(0, &wdt_is_open))
+		return -EBUSY;
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	/* Good, fire up the show */
+	wdt_startup();
+	return 0;
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+	if(wdt_expect_close == 42) {
+		wdt_turnoff();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		wdt_keepalive();
+	}
+	clear_bit(0, &wdt_is_open);
+	wdt_expect_close = 0;
+	return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "SC520",
+	};
+
+	switch(cmd)
+	{
+		default:
+			return -ENOIOCTLCMD;
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+		case WDIOC_KEEPALIVE:
+			wdt_keepalive();
+			return 0;
+		case WDIOC_SETOPTIONS:
+		{
+			int new_options, retval = -EINVAL;
+
+			if(get_user(new_options, p))
+				return -EFAULT;
+
+			if(new_options & WDIOS_DISABLECARD) {
+				wdt_turnoff();
+				retval = 0;
+			}
+
+			if(new_options & WDIOS_ENABLECARD) {
+				wdt_startup();
+				retval = 0;
+			}
+
+			return retval;
+		}
+		case WDIOC_SETTIMEOUT:
+		{
+			int new_timeout;
+
+			if(get_user(new_timeout, p))
+				return -EFAULT;
+
+			if(wdt_set_heartbeat(new_timeout))
+				return -EINVAL;
+
+			wdt_keepalive();
+			/* Fall through */
+		}
+		case WDIOC_GETTIMEOUT:
+			return put_user(timeout, p);
+	}
+}
+
+static struct file_operations wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= fop_write,
+	.open		= fop_open,
+	.release	= fop_close,
+	.ioctl		= fop_ioctl,
+};
+
+static struct miscdevice wdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &wdt_fops,
+};
+
+/*
+ *	Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if(code==SYS_DOWN || code==SYS_HALT)
+		wdt_turnoff();
+	return NOTIFY_DONE;
+}
+
+/*
+ *	The WDT needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier = {
+	.notifier_call = wdt_notify_sys,
+};
+
+static void __exit sc520_wdt_unload(void)
+{
+	if (!nowayout)
+		wdt_turnoff();
+
+	/* Deregister */
+	misc_deregister(&wdt_miscdev);
+	unregister_reboot_notifier(&wdt_notifier);
+	iounmap(wdtmrctl);
+}
+
+static int __init sc520_wdt_init(void)
+{
+	int rc = -EBUSY;
+
+	spin_lock_init(&wdt_spinlock);
+
+	init_timer(&timer);
+	timer.function = wdt_timer_ping;
+	timer.data = 0;
+
+	/* Check that the timeout value is within it's range ; if not reset to the default */
+	if (wdt_set_heartbeat(timeout)) {
+		wdt_set_heartbeat(WATCHDOG_TIMEOUT);
+		printk(KERN_INFO PFX "timeout value must be 1<=timeout<=3600, using %d\n",
+			WATCHDOG_TIMEOUT);
+	}
+
+	wdtmrctl = ioremap((unsigned long)(MMCR_BASE + OFFS_WDTMRCTL), 2);
+	if (!wdtmrctl) {
+		printk(KERN_ERR PFX "Unable to remap memory\n");
+		rc = -ENOMEM;
+		goto err_out_region2;
+	}
+
+	rc = register_reboot_notifier(&wdt_notifier);
+	if (rc) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			rc);
+		goto err_out_ioremap;
+	}
+
+	rc = misc_register(&wdt_miscdev);
+	if (rc) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, rc);
+		goto err_out_notifier;
+	}
+
+	printk(KERN_INFO PFX "WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n",
+		timeout,nowayout);
+
+	return 0;
+
+err_out_notifier:
+	unregister_reboot_notifier(&wdt_notifier);
+err_out_ioremap:
+	iounmap(wdtmrctl);
+err_out_region2:
+	return rc;
+}
+
+module_init(sc520_wdt_init);
+module_exit(sc520_wdt_unload);
+
+MODULE_AUTHOR("Scott and Bill Jennings");
+MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/scx200_wdt.c b/drivers/char/watchdog/scx200_wdt.c
new file mode 100644
index 0000000..b569670
--- /dev/null
+++ b/drivers/char/watchdog/scx200_wdt.c
@@ -0,0 +1,274 @@
+/* drivers/char/watchdog/scx200_wdt.c
+
+   National Semiconductor SCx200 Watchdog support
+
+   Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
+
+   Some code taken from:
+   National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
+   (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.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.
+
+   The author(s) of this software shall not be held liable for damages
+   of any nature resulting due to the use of this software. This
+   software is provided AS-IS with no warranties. */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+#include <linux/scx200.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#define NAME "scx200_wdt"
+
+MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
+MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+#ifndef CONFIG_WATCHDOG_NOWAYOUT
+#define CONFIG_WATCHDOG_NOWAYOUT 0
+#endif
+
+static int margin = 60;		/* in seconds */
+module_param(margin, int, 0);
+MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
+
+static int nowayout = CONFIG_WATCHDOG_NOWAYOUT;
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
+
+static u16 wdto_restart;
+static struct semaphore open_semaphore;
+static char expect_close;
+
+/* Bits of the WDCNFG register */
+#define W_ENABLE 0x00fa		/* Enable watchdog */
+#define W_DISABLE 0x0000	/* Disable watchdog */
+
+/* The scaling factor for the timer, this depends on the value of W_ENABLE */
+#define W_SCALE (32768/1024)
+
+static void scx200_wdt_ping(void)
+{
+	outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
+}
+
+static void scx200_wdt_update_margin(void)
+{
+	printk(KERN_INFO NAME ": timer margin %d seconds\n", margin);
+	wdto_restart = margin * W_SCALE;
+}
+
+static void scx200_wdt_enable(void)
+{
+	printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n",
+	       wdto_restart);
+
+	outw(0, scx200_cb_base + SCx200_WDT_WDTO);
+	outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
+	outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
+
+	scx200_wdt_ping();
+}
+
+static void scx200_wdt_disable(void)
+{
+	printk(KERN_DEBUG NAME ": disabling watchdog timer\n");
+
+	outw(0, scx200_cb_base + SCx200_WDT_WDTO);
+	outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
+	outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
+}
+
+static int scx200_wdt_open(struct inode *inode, struct file *file)
+{
+	/* only allow one at a time */
+	if (down_trylock(&open_semaphore))
+		return -EBUSY;
+	scx200_wdt_enable();
+
+	return nonseekable_open(inode, file);
+}
+
+static int scx200_wdt_release(struct inode *inode, struct file *file)
+{
+	if (expect_close != 42) {
+		printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n");
+	} else if (!nowayout) {
+		scx200_wdt_disable();
+	}
+	expect_close = 0;
+	up(&open_semaphore);
+
+	return 0;
+}
+
+static int scx200_wdt_notify_sys(struct notifier_block *this,
+				      unsigned long code, void *unused)
+{
+	if (code == SYS_HALT || code == SYS_POWER_OFF)
+		if (!nowayout)
+			scx200_wdt_disable();
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block scx200_wdt_notifier =
+{
+	.notifier_call = scx200_wdt_notify_sys,
+};
+
+static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
+				     size_t len, loff_t *ppos)
+{
+	/* check for a magic close character */
+	if (len)
+	{
+		size_t i;
+
+		scx200_wdt_ping();
+
+		expect_close = 0;
+		for (i = 0; i < len; ++i) {
+			char c;
+			if (get_user(c, data+i))
+				return -EFAULT;
+			if (c == 'V')
+				expect_close = 42;
+		}
+
+		return len;
+	}
+
+	return 0;
+}
+
+static int scx200_wdt_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.identity = "NatSemi SCx200 Watchdog",
+		.firmware_version = 1,
+		.options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING),
+	};
+	int new_margin;
+
+	switch (cmd) {
+	default:
+		return -ENOIOCTLCMD;
+	case WDIOC_GETSUPPORT:
+		if(copy_to_user(argp, &ident, sizeof(ident)))
+			return -EFAULT;
+		return 0;
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		if (put_user(0, p))
+			return -EFAULT;
+		return 0;
+	case WDIOC_KEEPALIVE:
+		scx200_wdt_ping();
+		return 0;
+	case WDIOC_SETTIMEOUT:
+		if (get_user(new_margin, p))
+			return -EFAULT;
+		if (new_margin < 1)
+			return -EINVAL;
+		margin = new_margin;
+		scx200_wdt_update_margin();
+		scx200_wdt_ping();
+	case WDIOC_GETTIMEOUT:
+		if (put_user(margin, p))
+			return -EFAULT;
+		return 0;
+	}
+}
+
+static struct file_operations scx200_wdt_fops = {
+	.owner	 = THIS_MODULE,
+	.llseek	 = no_llseek,
+	.write   = scx200_wdt_write,
+	.ioctl   = scx200_wdt_ioctl,
+	.open    = scx200_wdt_open,
+	.release = scx200_wdt_release,
+};
+
+static struct miscdevice scx200_wdt_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name  = NAME,
+	.fops  = &scx200_wdt_fops,
+};
+
+static int __init scx200_wdt_init(void)
+{
+	int r;
+
+	printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n");
+
+	/* check that we have found the configuration block */
+	if (!scx200_cb_present())
+		return -ENODEV;
+
+	if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
+			    SCx200_WDT_SIZE,
+			    "NatSemi SCx200 Watchdog")) {
+		printk(KERN_WARNING NAME ": watchdog I/O region busy\n");
+		return -EBUSY;
+	}
+
+	scx200_wdt_update_margin();
+	scx200_wdt_disable();
+
+	sema_init(&open_semaphore, 1);
+
+	r = misc_register(&scx200_wdt_miscdev);
+	if (r) {
+		release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+				SCx200_WDT_SIZE);
+		return r;
+	}
+
+	r = register_reboot_notifier(&scx200_wdt_notifier);
+	if (r) {
+		printk(KERN_ERR NAME ": unable to register reboot notifier");
+		misc_deregister(&scx200_wdt_miscdev);
+		release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+				SCx200_WDT_SIZE);
+		return r;
+	}
+
+	return 0;
+}
+
+static void __exit scx200_wdt_cleanup(void)
+{
+	unregister_reboot_notifier(&scx200_wdt_notifier);
+	misc_deregister(&scx200_wdt_miscdev);
+	release_region(scx200_cb_base + SCx200_WDT_OFFSET,
+		       SCx200_WDT_SIZE);
+}
+
+module_init(scx200_wdt_init);
+module_exit(scx200_wdt_cleanup);
+
+/*
+    Local variables:
+        compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules"
+        c-basic-offset: 8
+    End:
+*/
diff --git a/drivers/char/watchdog/shwdt.c b/drivers/char/watchdog/shwdt.c
new file mode 100644
index 0000000..3bc9272
--- /dev/null
+++ b/drivers/char/watchdog/shwdt.c
@@ -0,0 +1,452 @@
+/*
+ * drivers/char/watchdog/shwdt.c
+ *
+ * Watchdog driver for integrated watchdog in the SuperH processors.
+ *
+ * Copyright (C) 2001, 2002, 2003 Paul Mundt <lethal@linux-sh.org>
+ *
+ * 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.
+ *
+ * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ *     Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *
+ * 19-Apr-2002 Rob Radez <rob@osinvestor.com>
+ *     Added expect close support, made emulated timeout runtime changeable
+ *     general cleanups, add some ioctls
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/notifier.h>
+#include <linux/ioport.h>
+#include <linux/fs.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/watchdog.h>
+
+#define PFX "shwdt: "
+
+/*
+ * Default clock division ratio is 5.25 msecs. For an additional table of
+ * values, consult the asm-sh/watchdog.h. Overload this at module load
+ * time.
+ *
+ * In order for this to work reliably we need to have HZ set to 1000 or
+ * something quite higher than 100 (or we need a proper high-res timer
+ * implementation that will deal with this properly), otherwise the 10ms
+ * resolution of a jiffy is enough to trigger the overflow. For things like
+ * the SH-4 and SH-5, this isn't necessarily that big of a problem, though
+ * for the SH-2 and SH-3, this isn't recommended unless the WDT is absolutely
+ * necssary.
+ *
+ * As a result of this timing problem, the only modes that are particularly
+ * feasible are the 4096 and the 2048 divisors, which yeild 5.25 and 2.62ms
+ * overflow periods respectively.
+ *
+ * Also, since we can't really expect userspace to be responsive enough
+ * before the overflow happens, we maintain two seperate timers .. One in
+ * the kernel for clearing out WOVF every 2ms or so (again, this depends on
+ * HZ == 1000), and another for monitoring userspace writes to the WDT device.
+ *
+ * As such, we currently use a configurable heartbeat interval which defaults
+ * to 30s. In this case, the userspace daemon is only responsible for periodic
+ * writes to the device before the next heartbeat is scheduled. If the daemon
+ * misses its deadline, the kernel timer will allow the WDT to overflow.
+ */
+static int clock_division_ratio = WTCSR_CKS_4096;
+
+#define next_ping_period(cks)	msecs_to_jiffies(cks - 4)
+
+static unsigned long shwdt_is_open;
+static struct watchdog_info sh_wdt_info;
+static char shwdt_expect_close;
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+
+#define WATCHDOG_HEARTBEAT 30			/* 30 sec default heartbeat */
+static int heartbeat = WATCHDOG_HEARTBEAT;	/* in seconds */
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+/**
+ * 	sh_wdt_start - Start the Watchdog
+ *
+ * 	Starts the watchdog.
+ */
+static void sh_wdt_start(void)
+{
+	__u8 csr;
+
+	next_heartbeat = jiffies + (heartbeat * HZ);
+	mod_timer(&timer, next_ping_period(clock_division_ratio));
+
+	csr = sh_wdt_read_csr();
+	csr |= WTCSR_WT | clock_division_ratio;
+	sh_wdt_write_csr(csr);
+
+	sh_wdt_write_cnt(0);
+
+	/*
+	 * These processors have a bit of an inconsistent initialization
+	 * process.. starting with SH-3, RSTS was moved to WTCSR, and the
+	 * RSTCSR register was removed.
+	 *
+	 * On the SH-2 however, in addition with bits being in different
+	 * locations, we must deal with RSTCSR outright..
+	 */
+	csr = sh_wdt_read_csr();
+	csr |= WTCSR_TME;
+	csr &= ~WTCSR_RSTS;
+	sh_wdt_write_csr(csr);
+
+#ifdef CONFIG_CPU_SH2
+	/*
+	 * Whoever came up with the RSTCSR semantics must've been smoking
+	 * some of the good stuff, since in addition to the WTCSR/WTCNT write
+	 * brain-damage, it's managed to fuck things up one step further..
+	 *
+	 * If we need to clear the WOVF bit, the upper byte has to be 0xa5..
+	 * but if we want to touch RSTE or RSTS, the upper byte has to be
+	 * 0x5a..
+	 */
+	csr = sh_wdt_read_rstcsr();
+	csr &= ~RSTCSR_RSTS;
+	sh_wdt_write_rstcsr(csr);
+#endif
+}
+
+/**
+ * 	sh_wdt_stop - Stop the Watchdog
+ *
+ * 	Stops the watchdog.
+ */
+static void sh_wdt_stop(void)
+{
+	__u8 csr;
+
+	del_timer(&timer);
+
+	csr = sh_wdt_read_csr();
+	csr &= ~WTCSR_TME;
+	sh_wdt_write_csr(csr);
+}
+
+/**
+ * 	sh_wdt_keepalive - Keep the Userspace Watchdog Alive
+ *
+ * 	The Userspace watchdog got a KeepAlive: schedule the next heartbeat.
+ */
+static void sh_wdt_keepalive(void)
+{
+	next_heartbeat = jiffies + (heartbeat * HZ);
+}
+
+/**
+ * 	sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat
+ *
+ * 	Set the Userspace Watchdog heartbeat
+ */
+static int sh_wdt_set_heartbeat(int t)
+{
+	if ((t < 1) || (t > 3600)) /* arbitrary upper limit */
+		return -EINVAL;
+
+	heartbeat = t;
+	return 0;
+}
+
+/**
+ * 	sh_wdt_ping - Ping the Watchdog
+ *
+ *	@data: Unused
+ *
+ * 	Clears overflow bit, resets timer counter.
+ */
+static void sh_wdt_ping(unsigned long data)
+{
+	if (time_before(jiffies, next_heartbeat)) {
+		__u8 csr;
+
+		csr = sh_wdt_read_csr();
+		csr &= ~WTCSR_IOVF;
+		sh_wdt_write_csr(csr);
+
+		sh_wdt_write_cnt(0);
+
+		mod_timer(&timer, next_ping_period(clock_division_ratio));
+	} else {
+		printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+	}
+}
+
+/**
+ * 	sh_wdt_open - Open the Device
+ *
+ * 	@inode: inode of device
+ * 	@file: file handle of device
+ *
+ * 	Watchdog device is opened and started.
+ */
+static int sh_wdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &shwdt_is_open))
+		return -EBUSY;
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	sh_wdt_start();
+
+	return nonseekable_open(inode, file);
+}
+
+/**
+ * 	sh_wdt_close - Close the Device
+ *
+ * 	@inode: inode of device
+ * 	@file: file handle of device
+ *
+ * 	Watchdog device is closed and stopped.
+ */
+static int sh_wdt_close(struct inode *inode, struct file *file)
+{
+	if (shwdt_expect_close == 42) {
+		sh_wdt_stop();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		sh_wdt_keepalive();
+	}
+
+	clear_bit(0, &shwdt_is_open);
+	shwdt_expect_close = 0;
+
+	return 0;
+}
+
+/**
+ * 	sh_wdt_write - Write to Device
+ *
+ * 	@file: file handle of device
+ * 	@buf: buffer to write
+ * 	@count: length of buffer
+ * 	@ppos: offset
+ *
+ * 	Pings the watchdog on write.
+ */
+static ssize_t sh_wdt_write(struct file *file, const char *buf,
+			    size_t count, loff_t *ppos)
+{
+	if (count) {
+		if (!nowayout) {
+			size_t i;
+
+			shwdt_expect_close = 0;
+
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, buf + i))
+					return -EFAULT;
+				if (c == 'V')
+					shwdt_expect_close = 42;
+			}
+		}
+		sh_wdt_keepalive();
+	}
+
+	return count;
+}
+
+/**
+ * 	sh_wdt_ioctl - Query Device
+ *
+ * 	@inode: inode of device
+ * 	@file: file handle of device
+ * 	@cmd: watchdog command
+ * 	@arg: argument
+ *
+ * 	Query basic information from the device or ping it, as outlined by the
+ * 	watchdog API.
+ */
+static int sh_wdt_ioctl(struct inode *inode, struct file *file,
+			unsigned int cmd, unsigned long arg)
+{
+	int new_heartbeat;
+	int options, retval = -EINVAL;
+
+	switch (cmd) {
+		case WDIOC_GETSUPPORT:
+			return copy_to_user((struct watchdog_info *)arg,
+					  &sh_wdt_info,
+					  sizeof(sh_wdt_info)) ? -EFAULT : 0;
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, (int *)arg);
+		case WDIOC_KEEPALIVE:
+			sh_wdt_keepalive();
+			return 0;
+		case WDIOC_SETTIMEOUT:
+			if (get_user(new_heartbeat, (int *)arg))
+				return -EFAULT;
+
+			if (sh_wdt_set_heartbeat(new_heartbeat))
+				return -EINVAL;
+
+			sh_wdt_keepalive();
+			/* Fall */
+		case WDIOC_GETTIMEOUT:
+			return put_user(heartbeat, (int *)arg);
+		case WDIOC_SETOPTIONS:
+			if (get_user(options, (int *)arg))
+				return -EFAULT;
+
+			if (options & WDIOS_DISABLECARD) {
+				sh_wdt_stop();
+				retval = 0;
+			}
+
+			if (options & WDIOS_ENABLECARD) {
+				sh_wdt_start();
+				retval = 0;
+			}
+
+			return retval;
+		default:
+			return -ENOIOCTLCMD;
+	}
+
+	return 0;
+}
+
+/**
+ * 	sh_wdt_notify_sys - Notifier Handler
+ *
+ * 	@this: notifier block
+ * 	@code: notifier event
+ * 	@unused: unused
+ *
+ * 	Handles specific events, such as turning off the watchdog during a
+ * 	shutdown event.
+ */
+static int sh_wdt_notify_sys(struct notifier_block *this,
+			     unsigned long code, void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT) {
+		sh_wdt_stop();
+	}
+
+	return NOTIFY_DONE;
+}
+
+static struct file_operations sh_wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= sh_wdt_write,
+	.ioctl		= sh_wdt_ioctl,
+	.open		= sh_wdt_open,
+	.release	= sh_wdt_close,
+};
+
+static struct watchdog_info sh_wdt_info = {
+	.options		= WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+	.firmware_version	= 1,
+	.identity		= "SH WDT",
+};
+
+static struct notifier_block sh_wdt_notifier = {
+	.notifier_call		= sh_wdt_notify_sys,
+};
+
+static struct miscdevice sh_wdt_miscdev = {
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &sh_wdt_fops,
+};
+
+/**
+ * 	sh_wdt_init - Initialize module
+ *
+ * 	Registers the device and notifier handler. Actual device
+ * 	initialization is handled by sh_wdt_open().
+ */
+static int __init sh_wdt_init(void)
+{
+	int rc;
+
+	if ((clock_division_ratio < 0x5) || (clock_division_ratio > 0x7)) {
+		clock_division_ratio = WTCSR_CKS_4096;
+		printk(KERN_INFO PFX "clock_division_ratio value must be 0x5<=x<=0x7, using %d\n",
+			clock_division_ratio);
+	}
+
+	if (sh_wdt_set_heartbeat(heartbeat))
+	{
+		heartbeat = WATCHDOG_HEARTBEAT;
+		printk(KERN_INFO PFX "heartbeat value must be 1<=x<=3600, using %d\n",
+			heartbeat);
+	}
+
+	init_timer(&timer);
+	timer.function = sh_wdt_ping;
+	timer.data = 0;
+
+	rc = register_reboot_notifier(&sh_wdt_notifier);
+	if (rc) {
+		printk(KERN_ERR PFX "Can't register reboot notifier (err=%d)\n", rc);
+		return rc;
+	}
+
+	rc = misc_register(&sh_wdt_miscdev);
+	if (rc) {
+		printk(KERN_ERR PFX "Can't register miscdev on minor=%d (err=%d)\n",
+			sh_wdt_miscdev.minor, rc);
+		unregister_reboot_notifier(&sh_wdt_notifier);
+		return rc;
+	}
+
+	printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+		heartbeat, nowayout);
+
+	return 0;
+}
+
+/**
+ * 	sh_wdt_exit - Deinitialize module
+ *
+ * 	Unregisters the device and notifier handler. Actual device
+ * 	deinitialization is handled by sh_wdt_close().
+ */
+static void __exit sh_wdt_exit(void)
+{
+	misc_deregister(&sh_wdt_miscdev);
+	unregister_reboot_notifier(&sh_wdt_notifier);
+}
+
+MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>");
+MODULE_DESCRIPTION("SuperH watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+module_param(clock_division_ratio, int, 0);
+MODULE_PARM_DESC(clock_division_ratio, "Clock division ratio. Valid ranges are from 0x5 (1.31ms) to 0x7 (5.25ms). Defaults to 0x7.");
+
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<=heartbeat<=3600, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+module_init(sh_wdt_init);
+module_exit(sh_wdt_exit);
+
diff --git a/drivers/char/watchdog/softdog.c b/drivers/char/watchdog/softdog.c
new file mode 100644
index 0000000..1179034
--- /dev/null
+++ b/drivers/char/watchdog/softdog.c
@@ -0,0 +1,309 @@
+/*
+ *	SoftDog	0.07:	A Software Watchdog Device
+ *
+ *	(c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *				http://www.redhat.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.
+ *
+ *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ *	Software only watchdog driver. Unlike its big brother the WDT501P
+ *	driver this won't always recover a failed machine.
+ *
+ *  03/96: Angelo Haritsis <ah@doc.ic.ac.uk> :
+ *	Modularised.
+ *	Added soft_margin; use upon insmod to change the timer delay.
+ *	NB: uses same minor as wdt (WATCHDOG_MINOR); we could use separate
+ *	    minors.
+ *
+ *  19980911 Alan Cox
+ *	Made SMP safe for 2.3.x
+ *
+ *  20011127 Joel Becker (jlbec@evilplan.org>
+ *	Added soft_noboot; Allows testing the softdog trigger without
+ *	requiring a recompile.
+ *	Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT.
+ *
+ *  20020530 Joel Becker <joel.becker@oracle.com>
+ *  	Added Matt Domsch's nowayout module option.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+#define PFX "SoftDog: "
+
+#define TIMER_MARGIN	60		/* Default is 60 seconds */
+static int soft_margin = TIMER_MARGIN;	/* in seconds */
+module_param(soft_margin, int, 0);
+MODULE_PARM_DESC(soft_margin, "Watchdog soft_margin in seconds. (0<soft_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+#ifdef ONLY_TESTING
+static int soft_noboot = 1;
+#else
+static int soft_noboot = 0;
+#endif  /* ONLY_TESTING */
+
+module_param(soft_noboot, int, 0);
+MODULE_PARM_DESC(soft_noboot, "Softdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)");
+
+/*
+ *	Our timer
+ */
+
+static void watchdog_fire(unsigned long);
+
+static struct timer_list watchdog_ticktock =
+		TIMER_INITIALIZER(watchdog_fire, 0, 0);
+static unsigned long timer_alive;
+static char expect_close;
+
+
+/*
+ *	If the timer expires..
+ */
+
+static void watchdog_fire(unsigned long data)
+{
+	if (soft_noboot)
+		printk(KERN_CRIT PFX "Triggered - Reboot ignored.\n");
+	else
+	{
+		printk(KERN_CRIT PFX "Initiating system reboot.\n");
+		machine_restart(NULL);
+		printk(KERN_CRIT PFX "Reboot didn't ?????\n");
+	}
+}
+
+/*
+ *	Softdog operations
+ */
+
+static int softdog_keepalive(void)
+{
+	mod_timer(&watchdog_ticktock, jiffies+(soft_margin*HZ));
+	return 0;
+}
+
+static int softdog_stop(void)
+{
+	del_timer(&watchdog_ticktock);
+	return 0;
+}
+
+static int softdog_set_heartbeat(int t)
+{
+	if ((t < 0x0001) || (t > 0xFFFF))
+		return -EINVAL;
+
+	soft_margin = t;
+	return 0;
+}
+
+/*
+ *	/dev/watchdog handling
+ */
+
+static int softdog_open(struct inode *inode, struct file *file)
+{
+	if(test_and_set_bit(0, &timer_alive))
+		return -EBUSY;
+	if (nowayout)
+		__module_get(THIS_MODULE);
+	/*
+	 *	Activate timer
+	 */
+	softdog_keepalive();
+	return nonseekable_open(inode, file);
+}
+
+static int softdog_release(struct inode *inode, struct file *file)
+{
+	/*
+	 *	Shut off the timer.
+	 * 	Lock it in if it's a module and we set nowayout
+	 */
+	if (expect_close == 42) {
+		softdog_stop();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		softdog_keepalive();
+	}
+	clear_bit(0, &timer_alive);
+	expect_close = 0;
+	return 0;
+}
+
+static ssize_t softdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
+{
+	/*
+	 *	Refresh the timer.
+	 */
+	if(len) {
+		if (!nowayout) {
+			size_t i;
+
+			/* In case it was set long ago */
+			expect_close = 0;
+
+			for (i = 0; i != len; i++) {
+				char c;
+
+				if (get_user(c, data + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		softdog_keepalive();
+	}
+	return len;
+}
+
+static int softdog_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int new_margin;
+	static struct watchdog_info ident = {
+		.options =		WDIOF_SETTIMEOUT |
+					WDIOF_KEEPALIVEPING |
+					WDIOF_MAGICCLOSE,
+		.firmware_version =	0,
+		.identity =		"Software Watchdog",
+	};
+	switch (cmd) {
+		default:
+			return -ENOIOCTLCMD;
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident,
+				sizeof(ident)) ? -EFAULT : 0;
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+		case WDIOC_KEEPALIVE:
+			softdog_keepalive();
+			return 0;
+		case WDIOC_SETTIMEOUT:
+			if (get_user(new_margin, p))
+				return -EFAULT;
+			if (softdog_set_heartbeat(new_margin))
+				return -EINVAL;
+			softdog_keepalive();
+			/* Fall */
+		case WDIOC_GETTIMEOUT:
+			return put_user(soft_margin, p);
+	}
+}
+
+/*
+ *	Notifier for system down
+ */
+
+static int softdog_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if(code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the WDT off */
+		softdog_stop();
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations softdog_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= softdog_write,
+	.ioctl		= softdog_ioctl,
+	.open		= softdog_open,
+	.release	= softdog_release,
+};
+
+static struct miscdevice softdog_miscdev = {
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &softdog_fops,
+};
+
+static struct notifier_block softdog_notifier = {
+	.notifier_call	= softdog_notify_sys,
+};
+
+static char banner[] __initdata = KERN_INFO "Software Watchdog Timer: 0.07 initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d)\n";
+
+static int __init watchdog_init(void)
+{
+	int ret;
+
+	/* Check that the soft_margin value is within it's range ; if not reset to the default */
+	if (softdog_set_heartbeat(soft_margin)) {
+		softdog_set_heartbeat(TIMER_MARGIN);
+		printk(KERN_INFO PFX "soft_margin value must be 0<soft_margin<65536, using %d\n",
+			TIMER_MARGIN);
+	}
+
+	ret = register_reboot_notifier(&softdog_notifier);
+	if (ret) {
+		printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		return ret;
+	}
+
+	ret = misc_register(&softdog_miscdev);
+	if (ret) {
+		printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		unregister_reboot_notifier(&softdog_notifier);
+		return ret;
+	}
+
+	printk(banner, soft_noboot, soft_margin, nowayout);
+
+	return 0;
+}
+
+static void __exit watchdog_exit(void)
+{
+	misc_deregister(&softdog_miscdev);
+	unregister_reboot_notifier(&softdog_notifier);
+}
+
+module_init(watchdog_init);
+module_exit(watchdog_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Software Watchdog Device Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/w83627hf_wdt.c b/drivers/char/watchdog/w83627hf_wdt.c
new file mode 100644
index 0000000..813c970
--- /dev/null
+++ b/drivers/char/watchdog/w83627hf_wdt.c
@@ -0,0 +1,362 @@
+/*
+ *	w83627hf WDT driver
+ *
+ *	(c) Copyright 2003 Pádraig Brady <P@draigBrady.com>
+ *
+ *	Based on advantechwdt.c which is based on wdt.c.
+ *	Original copyright messages:
+ *
+ *	(c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
+ *
+ *	(c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *				http://www.redhat.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.
+ *
+ *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 1995    Alan Cox <alan@redhat.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define WATCHDOG_NAME "w83627hf WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WATCHDOG_TIMEOUT 60		/* 60 sec default timeout */
+
+static unsigned long wdt_is_open;
+static char expect_close;
+
+/* You must set this - there is no sane way to probe for this board. */
+static int wdt_io = 0x2E;
+module_param(wdt_io, int, 0);
+MODULE_PARM_DESC(wdt_io, "w83627hf WDT io port (default 0x2E)");
+
+static int timeout = WATCHDOG_TIMEOUT;	/* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ *	Kernel methods.
+ */
+
+#define WDT_EFER (wdt_io+0)   /* Extended Function Enable Registers */
+#define WDT_EFIR (wdt_io+0)   /* Extended Function Index Register (same as EFER) */
+#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */
+
+static void
+w83627hf_select_wd_register(void)
+{
+	outb_p(0x87, WDT_EFER); /* Enter extended function mode */
+	outb_p(0x87, WDT_EFER); /* Again according to manual */
+
+	outb_p(0x07, WDT_EFER); /* point to logical device number reg */
+	outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */
+	outb_p(0x30, WDT_EFER); /* select CR30 */
+	outb_p(0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */
+}
+
+static void
+w83627hf_unselect_wd_register(void)
+{
+	outb_p(0xAA, WDT_EFER); /* Leave extended function mode */
+}
+
+/* tyan motherboards seem to set F5 to 0x4C ?
+ * So explicitly init to appropriate value. */
+static void
+w83627hf_init(void)
+{
+	unsigned char t;
+
+	w83627hf_select_wd_register();
+
+	outb_p(0xF5, WDT_EFER); /* Select CRF5 */
+	t=inb_p(WDT_EFDR);      /* read CRF5 */
+	t&=~0x0C;               /* set second mode & disable keyboard turning off watchdog */
+	outb_p(t, WDT_EFDR);    /* Write back to CRF5 */
+
+	w83627hf_unselect_wd_register();
+}
+
+static void
+wdt_ctrl(int timeout)
+{
+	w83627hf_select_wd_register();
+
+	outb_p(0xF6, WDT_EFER);    /* Select CRF6 */
+	outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF6 */
+
+	w83627hf_unselect_wd_register();
+}
+
+static int
+wdt_ping(void)
+{
+	wdt_ctrl(timeout);
+	return 0;
+}
+
+static int
+wdt_disable(void)
+{
+	wdt_ctrl(0);
+	return 0;
+}
+
+static int
+wdt_set_heartbeat(int t)
+{
+	if ((t < 1) || (t > 63))
+		return -EINVAL;
+
+	timeout = t;
+	return 0;
+}
+
+static ssize_t
+wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	if (count) {
+		if (!nowayout) {
+			size_t i;
+
+			expect_close = 0;
+
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, buf+i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		wdt_ping();
+	}
+	return count;
+}
+
+static int
+wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	  unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int new_timeout;
+	static struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "W83627HF WDT",
+	};
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+	  if (copy_to_user(argp, &ident, sizeof(ident)))
+	    return -EFAULT;
+	  break;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+	  return put_user(0, p);
+
+	case WDIOC_KEEPALIVE:
+	  wdt_ping();
+	  break;
+
+	case WDIOC_SETTIMEOUT:
+	  if (get_user(new_timeout, p))
+		  return -EFAULT;
+	  if (wdt_set_heartbeat(new_timeout))
+		  return -EINVAL;
+	  wdt_ping();
+	  /* Fall */
+
+	case WDIOC_GETTIMEOUT:
+	  return put_user(timeout, p);
+
+	case WDIOC_SETOPTIONS:
+	{
+	  int options, retval = -EINVAL;
+
+	  if (get_user(options, p))
+	    return -EFAULT;
+
+	  if (options & WDIOS_DISABLECARD) {
+	    wdt_disable();
+	    retval = 0;
+	  }
+
+	  if (options & WDIOS_ENABLECARD) {
+	    wdt_ping();
+	    retval = 0;
+	  }
+
+	  return retval;
+	}
+
+	default:
+	  return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static int
+wdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &wdt_is_open))
+		return -EBUSY;
+	/*
+	 *	Activate
+	 */
+
+	wdt_ping();
+	return nonseekable_open(inode, file);
+}
+
+static int
+wdt_close(struct inode *inode, struct file *file)
+{
+	if (expect_close == 42) {
+		wdt_disable();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		wdt_ping();
+	}
+	expect_close = 0;
+	clear_bit(0, &wdt_is_open);
+	return 0;
+}
+
+/*
+ *	Notifier for system down
+ */
+
+static int
+wdt_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT) {
+		/* Turn the WDT off */
+		wdt_disable();
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= wdt_write,
+	.ioctl		= wdt_ioctl,
+	.open		= wdt_open,
+	.release	= wdt_close,
+};
+
+static struct miscdevice wdt_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &wdt_fops,
+};
+
+/*
+ *	The WDT needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier = {
+	.notifier_call = wdt_notify_sys,
+};
+
+static int __init
+wdt_init(void)
+{
+	int ret;
+
+	printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF Super I/O chip initialising.\n");
+
+	if (wdt_set_heartbeat(timeout)) {
+		wdt_set_heartbeat(WATCHDOG_TIMEOUT);
+		printk (KERN_INFO PFX "timeout value must be 1<=timeout<=63, using %d\n",
+			WATCHDOG_TIMEOUT);
+	}
+
+	if (!request_region(wdt_io, 1, WATCHDOG_NAME)) {
+		printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			wdt_io);
+		ret = -EIO;
+		goto out;
+	}
+
+	w83627hf_init();
+
+	ret = register_reboot_notifier(&wdt_notifier);
+	if (ret != 0) {
+		printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		goto unreg_regions;
+	}
+
+	ret = misc_register(&wdt_miscdev);
+	if (ret != 0) {
+		printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto unreg_reboot;
+	}
+
+	printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
+		timeout, nowayout);
+
+out:
+	return ret;
+unreg_reboot:
+	unregister_reboot_notifier(&wdt_notifier);
+unreg_regions:
+	release_region(wdt_io, 1);
+	goto out;
+}
+
+static void __exit
+wdt_exit(void)
+{
+	misc_deregister(&wdt_miscdev);
+	unregister_reboot_notifier(&wdt_notifier);
+	release_region(wdt_io,1);
+}
+
+module_init(wdt_init);
+module_exit(wdt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>");
+MODULE_DESCRIPTION("w38627hf WDT driver");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/w83877f_wdt.c b/drivers/char/watchdog/w83877f_wdt.c
new file mode 100644
index 0000000..bccbd4d
--- /dev/null
+++ b/drivers/char/watchdog/w83877f_wdt.c
@@ -0,0 +1,426 @@
+/*
+ *	W83877F Computer Watchdog Timer driver
+ *
+ *      Based on acquirewdt.c by Alan Cox,
+ *           and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
+ *
+ *	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.
+ *
+ *	The authors do NOT admit liability nor provide warranty for
+ *	any of this software. This material is provided "AS-IS" in
+ *      the hope that it may be useful for others.
+ *
+ *	(c) Copyright 2001    Scott Jennings <linuxdrivers@oro.net>
+ *
+ *           4/19 - 2001      [Initial revision]
+ *           9/27 - 2001      Added spinlocking
+ *           4/12 - 2002      [rob@osinvestor.com] Eliminate extra comments
+ *                            Eliminate fop_read
+ *                            Eliminate extra spin_unlock
+ *                            Added KERN_* tags to printks
+ *                            add CONFIG_WATCHDOG_NOWAYOUT support
+ *                            fix possible wdt_is_open race
+ *                            changed watchdog_info to correctly reflect what the driver offers
+ *                            added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT,
+ *                            WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls
+ *           09/8 - 2003      [wim@iguana.be] cleanup of trailing spaces
+ *                            added extra printk's for startup problems
+ *                            use module_param
+ *                            made timeout (the emulated heartbeat) a module_param
+ *                            made the keepalive ping an internal subroutine
+ *
+ *  This WDT driver is different from most other Linux WDT
+ *  drivers in that the driver will ping the watchdog by itself,
+ *  because this particular WDT has a very short timeout (1.6
+ *  seconds) and it would be insane to count on any userspace
+ *  daemon always getting scheduled within that time frame.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define OUR_NAME "w83877f_wdt"
+#define PFX OUR_NAME ": "
+
+#define ENABLE_W83877F_PORT 0x3F0
+#define ENABLE_W83877F 0x87
+#define DISABLE_W83877F 0xAA
+#define WDT_PING 0x443
+#define WDT_REGISTER 0x14
+#define WDT_ENABLE 0x9C
+#define WDT_DISABLE 0x8C
+
+/*
+ * The W83877F seems to be fixed at 1.6s timeout (at least on the
+ * EMACS PC-104 board I'm using). If we reset the watchdog every
+ * ~250ms we should be safe.  */
+
+#define WDT_INTERVAL (HZ/4+1)
+
+/*
+ * We must not require too good response from the userspace daemon.
+ * Here we require the userspace daemon to send us a heartbeat
+ * char to /dev/watchdog every 30 seconds.
+ */
+
+#define WATCHDOG_TIMEOUT 30            /* 30 sec default timeout */
+static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
+
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void wdt_timer_ping(unsigned long);
+static struct timer_list timer;
+static unsigned long next_heartbeat;
+static unsigned long wdt_is_open;
+static char wdt_expect_close;
+static spinlock_t wdt_spinlock;
+
+/*
+ *	Whack the dog
+ */
+
+static void wdt_timer_ping(unsigned long data)
+{
+	/* If we got a heartbeat pulse within the WDT_US_INTERVAL
+	 * we agree to ping the WDT
+	 */
+	if(time_before(jiffies, next_heartbeat))
+	{
+		/* Ping the WDT */
+		spin_lock(&wdt_spinlock);
+
+		/* Ping the WDT by reading from WDT_PING */
+		inb_p(WDT_PING);
+
+		/* Re-set the timer interval */
+		timer.expires = jiffies + WDT_INTERVAL;
+		add_timer(&timer);
+
+		spin_unlock(&wdt_spinlock);
+
+	} else {
+		printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n");
+	}
+}
+
+/*
+ * Utility routines
+ */
+
+static void wdt_change(int writeval)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&wdt_spinlock, flags);
+
+	/* buy some time */
+	inb_p(WDT_PING);
+
+	/* make W83877F available */
+	outb_p(ENABLE_W83877F,  ENABLE_W83877F_PORT);
+	outb_p(ENABLE_W83877F,  ENABLE_W83877F_PORT);
+
+	/* enable watchdog */
+	outb_p(WDT_REGISTER,    ENABLE_W83877F_PORT);
+	outb_p(writeval,        ENABLE_W83877F_PORT+1);
+
+	/* lock the W8387FF away */
+	outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT);
+
+	spin_unlock_irqrestore(&wdt_spinlock, flags);
+}
+
+static void wdt_startup(void)
+{
+	next_heartbeat = jiffies + (timeout * HZ);
+
+	/* Start the timer */
+	timer.expires = jiffies + WDT_INTERVAL;
+	add_timer(&timer);
+
+	wdt_change(WDT_ENABLE);
+
+	printk(KERN_INFO PFX "Watchdog timer is now enabled.\n");
+}
+
+static void wdt_turnoff(void)
+{
+	/* Stop the timer */
+	del_timer(&timer);
+
+	wdt_change(WDT_DISABLE);
+
+	printk(KERN_INFO PFX "Watchdog timer is now disabled...\n");
+}
+
+static void wdt_keepalive(void)
+{
+	/* user land ping */
+	next_heartbeat = jiffies + (timeout * HZ);
+}
+
+/*
+ * /dev/watchdog handling
+ */
+
+static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if(count)
+	{
+		if (!nowayout)
+		{
+			size_t ofs;
+
+			/* note: just in case someone wrote the magic character
+			 * five months ago... */
+			wdt_expect_close = 0;
+
+			/* scan to see whether or not we got the magic character */
+			for(ofs = 0; ofs != count; ofs++)
+			{
+				char c;
+				if (get_user(c, buf + ofs))
+					return -EFAULT;
+				if (c == 'V')
+					wdt_expect_close = 42;
+			}
+		}
+
+		/* someone wrote to us, we should restart timer */
+		wdt_keepalive();
+	}
+	return count;
+}
+
+static int fop_open(struct inode * inode, struct file * file)
+{
+	/* Just in case we're already talking to someone... */
+	if(test_and_set_bit(0, &wdt_is_open))
+		return -EBUSY;
+
+	/* Good, fire up the show */
+	wdt_startup();
+	return nonseekable_open(inode, file);
+}
+
+static int fop_close(struct inode * inode, struct file * file)
+{
+	if(wdt_expect_close == 42)
+		wdt_turnoff();
+	else {
+		del_timer(&timer);
+		printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n");
+	}
+	clear_bit(0, &wdt_is_open);
+	wdt_expect_close = 0;
+	return 0;
+}
+
+static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident=
+	{
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "W83877F",
+	};
+
+	switch(cmd)
+	{
+		default:
+			return -ENOIOCTLCMD;
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+		case WDIOC_KEEPALIVE:
+			wdt_keepalive();
+			return 0;
+		case WDIOC_SETOPTIONS:
+		{
+			int new_options, retval = -EINVAL;
+
+			if(get_user(new_options, p))
+				return -EFAULT;
+
+			if(new_options & WDIOS_DISABLECARD) {
+				wdt_turnoff();
+				retval = 0;
+			}
+
+			if(new_options & WDIOS_ENABLECARD) {
+				wdt_startup();
+				retval = 0;
+			}
+
+			return retval;
+		}
+		case WDIOC_SETTIMEOUT:
+		{
+			int new_timeout;
+
+			if(get_user(new_timeout, p))
+				return -EFAULT;
+
+			if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */
+				return -EINVAL;
+
+			timeout = new_timeout;
+			wdt_keepalive();
+			/* Fall through */
+		}
+		case WDIOC_GETTIMEOUT:
+			return put_user(timeout, p);
+	}
+}
+
+static struct file_operations wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= fop_write,
+	.open		= fop_open,
+	.release	= fop_close,
+	.ioctl		= fop_ioctl,
+};
+
+static struct miscdevice wdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &wdt_fops,
+};
+
+/*
+ *	Notifier for system down
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if(code==SYS_DOWN || code==SYS_HALT)
+		wdt_turnoff();
+	return NOTIFY_DONE;
+}
+
+/*
+ *	The WDT needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier=
+{
+	.notifier_call = wdt_notify_sys,
+};
+
+static void __exit w83877f_wdt_unload(void)
+{
+	wdt_turnoff();
+
+	/* Deregister */
+	misc_deregister(&wdt_miscdev);
+
+	unregister_reboot_notifier(&wdt_notifier);
+	release_region(WDT_PING,1);
+	release_region(ENABLE_W83877F_PORT,2);
+}
+
+static int __init w83877f_wdt_init(void)
+{
+	int rc = -EBUSY;
+
+	spin_lock_init(&wdt_spinlock);
+
+	if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */
+	{
+		timeout = WATCHDOG_TIMEOUT;
+		printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n",
+			timeout);
+	}
+
+	if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT"))
+	{
+		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			ENABLE_W83877F_PORT);
+		rc = -EIO;
+		goto err_out;
+	}
+
+	if (!request_region(WDT_PING, 1, "W8387FF WDT"))
+	{
+		printk(KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			WDT_PING);
+		rc = -EIO;
+		goto err_out_region1;
+	}
+
+	init_timer(&timer);
+	timer.function = wdt_timer_ping;
+	timer.data = 0;
+
+	rc = misc_register(&wdt_miscdev);
+	if (rc)
+	{
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			wdt_miscdev.minor, rc);
+		goto err_out_region2;
+	}
+
+	rc = register_reboot_notifier(&wdt_notifier);
+	if (rc)
+	{
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			rc);
+		goto err_out_miscdev;
+	}
+
+	printk(KERN_INFO PFX "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n",
+		timeout, nowayout);
+
+	return 0;
+
+err_out_miscdev:
+	misc_deregister(&wdt_miscdev);
+err_out_region2:
+	release_region(WDT_PING,1);
+err_out_region1:
+	release_region(ENABLE_W83877F_PORT,2);
+err_out:
+	return rc;
+}
+
+module_init(w83877f_wdt_init);
+module_exit(w83877f_wdt_unload);
+
+MODULE_AUTHOR("Scott and Bill Jennings");
+MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/wafer5823wdt.c b/drivers/char/watchdog/wafer5823wdt.c
new file mode 100644
index 0000000..abb0bea
--- /dev/null
+++ b/drivers/char/watchdog/wafer5823wdt.c
@@ -0,0 +1,330 @@
+/*
+ *	ICP Wafer 5823 Single Board Computer WDT driver
+ *      http://www.icpamerica.com/wafer_5823.php
+ *      May also work on other similar models
+ *
+ *	(c) Copyright 2002 Justin Cormack <justin@street-vision.com>
+ *
+ *      Release 0.02
+ *
+ *	Based on advantechwdt.c which is based on wdt.c.
+ *	Original copyright messages:
+ *
+ *	(c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *				http://www.redhat.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.
+ *
+ *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define WATCHDOG_NAME "Wafer 5823 WDT"
+#define PFX WATCHDOG_NAME ": "
+#define WD_TIMO 60			/* 60 sec default timeout */
+
+static unsigned long wafwdt_is_open;
+static char expect_close;
+static spinlock_t wafwdt_lock;
+
+/*
+ *	You must set these - there is no sane way to probe for this board.
+ *
+ *      To enable, write the timeout value in seconds (1 to 255) to I/O
+ *      port WDT_START, then read the port to start the watchdog. To pat
+ *      the dog, read port WDT_STOP to stop the timer, then read WDT_START
+ *      to restart it again.
+ */
+
+static int wdt_stop = 0x843;
+static int wdt_start = 0x443;
+
+static int timeout = WD_TIMO;  /* in seconds */
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=255, default=" __MODULE_STRING(WD_TIMO) ".");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+static void wafwdt_ping(void)
+{
+	/* pat watchdog */
+	spin_lock(&wafwdt_lock);
+	inb_p(wdt_stop);
+	inb_p(wdt_start);
+	spin_unlock(&wafwdt_lock);
+}
+
+static void wafwdt_start(void)
+{
+	/* start up watchdog */
+	outb_p(timeout, wdt_start);
+	inb_p(wdt_start);
+}
+
+static void
+wafwdt_stop(void)
+{
+	/* stop watchdog */
+	inb_p(wdt_stop);
+}
+
+static ssize_t wafwdt_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
+{
+	/* See if we got the magic character 'V' and reload the timer */
+	if (count) {
+		if (!nowayout) {
+			size_t i;
+
+			/* In case it was set long ago */
+			expect_close = 0;
+
+			/* scan to see whether or not we got the magic character */
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, buf + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		/* Well, anyhow someone wrote to us, we should return that favour */
+		wafwdt_ping();
+	}
+	return count;
+}
+
+static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	     unsigned long arg)
+{
+	int new_timeout;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	static struct watchdog_info ident = {
+		.options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
+		.firmware_version = 1,
+		.identity = "Wafer 5823 WDT",
+	};
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &ident, sizeof (ident)))
+			return -EFAULT;
+		break;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		return put_user(0, p);
+
+	case WDIOC_KEEPALIVE:
+		wafwdt_ping();
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		if (get_user(new_timeout, p))
+			return -EFAULT;
+		if ((new_timeout < 1) || (new_timeout > 255))
+			return -EINVAL;
+		timeout = new_timeout;
+		wafwdt_stop();
+		wafwdt_start();
+		/* Fall */
+	case WDIOC_GETTIMEOUT:
+		return put_user(timeout, p);
+
+	case WDIOC_SETOPTIONS:
+	{
+		int options, retval = -EINVAL;
+
+		if (get_user(options, p))
+			return -EFAULT;
+
+		if (options & WDIOS_DISABLECARD) {
+			wafwdt_start();
+			retval = 0;
+		}
+
+		if (options & WDIOS_ENABLECARD) {
+			wafwdt_stop();
+			retval = 0;
+		}
+
+		return retval;
+	}
+
+	default:
+		return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static int wafwdt_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, &wafwdt_is_open))
+		return -EBUSY;
+
+	/*
+	 *      Activate
+	 */
+	wafwdt_start();
+	return nonseekable_open(inode, file);
+}
+
+static int
+wafwdt_close(struct inode *inode, struct file *file)
+{
+	if (expect_close == 42) {
+		wafwdt_stop();
+	} else {
+		printk(KERN_CRIT PFX "WDT device closed unexpectedly.  WDT will not stop!\n");
+		wafwdt_ping();
+	}
+	clear_bit(0, &wafwdt_is_open);
+	expect_close = 0;
+	return 0;
+}
+
+/*
+ *	Notifier for system down
+ */
+
+static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT) {
+		/* Turn the WDT off */
+		wafwdt_stop();
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+static struct file_operations wafwdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= wafwdt_write,
+	.ioctl		= wafwdt_ioctl,
+	.open		= wafwdt_open,
+	.release	= wafwdt_close,
+};
+
+static struct miscdevice wafwdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &wafwdt_fops,
+};
+
+/*
+ *	The WDT needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block wafwdt_notifier = {
+	.notifier_call = wafwdt_notify_sys,
+};
+
+static int __init wafwdt_init(void)
+{
+	int ret;
+
+	printk(KERN_INFO "WDT driver for Wafer 5823 single board computer initialising.\n");
+
+	spin_lock_init(&wafwdt_lock);
+
+	if (timeout < 1 || timeout > 255) {
+		timeout = WD_TIMO;
+		printk (KERN_INFO PFX "timeout value must be 1<=x<=255, using %d\n",
+			timeout);
+	}
+
+	if (wdt_stop != wdt_start) {
+		if(!request_region(wdt_stop, 1, "Wafer 5823 WDT")) {
+			printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			wdt_stop);
+			ret = -EIO;
+			goto error;
+		}
+	}
+
+	if(!request_region(wdt_start, 1, "Wafer 5823 WDT")) {
+		printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
+			wdt_start);
+		ret = -EIO;
+		goto error2;
+	}
+
+	ret = register_reboot_notifier(&wafwdt_notifier);
+	if (ret != 0) {
+		printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			ret);
+		goto error3;
+	}
+
+	ret = misc_register(&wafwdt_miscdev);
+	if (ret != 0) {
+		printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto error4;
+	}
+
+	printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n",
+		timeout, nowayout);
+
+	return ret;
+error4:
+	unregister_reboot_notifier(&wafwdt_notifier);
+error3:
+	release_region(wdt_start, 1);
+error2:
+	if (wdt_stop != wdt_start)
+		release_region(wdt_stop, 1);
+error:
+	return ret;
+}
+
+static void __exit wafwdt_exit(void)
+{
+	misc_deregister(&wafwdt_miscdev);
+	unregister_reboot_notifier(&wafwdt_notifier);
+	if(wdt_stop != wdt_start)
+		release_region(wdt_stop, 1);
+	release_region(wdt_start, 1);
+}
+
+module_init(wafwdt_init);
+module_exit(wafwdt_exit);
+
+MODULE_AUTHOR("Justin Cormack");
+MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+/* end of wafer5823wdt.c */
diff --git a/drivers/char/watchdog/wd501p.h b/drivers/char/watchdog/wd501p.h
new file mode 100644
index 0000000..84e60eb
--- /dev/null
+++ b/drivers/char/watchdog/wd501p.h
@@ -0,0 +1,52 @@
+/*
+ *	Industrial Computer Source WDT500/501 driver
+ *
+ *	(c) Copyright 1995	CymruNET Ltd
+ *				Innovation Centre
+ *				Singleton Park
+ *				Swansea
+ *				Wales
+ *				UK
+ *				SA2 8PP
+ *
+ *	http://www.cymru.net
+ *
+ *	This driver is provided under the GNU General Public License, incorporated
+ *	herein by reference. The driver is provided without warranty or 
+ *	support.
+ *
+ *	Release 0.04.
+ *
+ */
+
+#include <linux/config.h>
+
+#define WDT_COUNT0		(io+0)
+#define WDT_COUNT1		(io+1)
+#define WDT_COUNT2		(io+2)
+#define WDT_CR			(io+3)
+#define WDT_SR			(io+4)	/* Start buzzer on PCI write */
+#define WDT_RT			(io+5)	/* Stop buzzer on PCI write */
+#define WDT_BUZZER		(io+6)	/* PCI only: rd=disable, wr=enable */
+#define WDT_DC			(io+7)
+
+/* The following are only on the PCI card, they're outside of I/O space on
+ * the ISA card: */
+#define WDT_CLOCK		(io+12)	/* COUNT2: rd=16.67MHz, wr=2.0833MHz */
+/* inverted opto isolated reset output: */
+#define WDT_OPTONOTRST		(io+13)	/* wr=enable, rd=disable */
+/* opto isolated reset output: */
+#define WDT_OPTORST		(io+14)	/* wr=enable, rd=disable */
+/* programmable outputs: */
+#define WDT_PROGOUT		(io+15)	/* wr=enable, rd=disable */
+
+								/* FAN 501 500 */
+#define WDC_SR_WCCR		1	/* Active low */	/*  X   X   X  */
+#define WDC_SR_TGOOD		2				/*  X   X   -  */
+#define WDC_SR_ISOI0		4				/*  X   X   X  */
+#define WDC_SR_ISII1		8				/*  X   X   X  */
+#define WDC_SR_FANGOOD		16				/*  X   -   -  */
+#define WDC_SR_PSUOVER		32	/* Active low */	/*  X   X   -  */
+#define WDC_SR_PSUUNDR		64	/* Active low */	/*  X   X   -  */
+#define WDC_SR_IRQ		128	/* Active low */	/*  X   X   X  */
+
diff --git a/drivers/char/watchdog/wdt.c b/drivers/char/watchdog/wdt.c
new file mode 100644
index 0000000..5684aa3
--- /dev/null
+++ b/drivers/char/watchdog/wdt.c
@@ -0,0 +1,647 @@
+/*
+ *	Industrial Computer Source WDT500/501 driver
+ *
+ *	(c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *				http://www.redhat.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.
+ *
+ *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ *	Release 0.10.
+ *
+ *	Fixes
+ *		Dave Gregorich	:	Modularisation and minor bugs
+ *		Alan Cox	:	Added the watchdog ioctl() stuff
+ *		Alan Cox	:	Fixed the reboot problem (as noted by
+ *					Matt Crocker).
+ *		Alan Cox	:	Added wdt= boot option
+ *		Alan Cox	:	Cleaned up copy/user stuff
+ *		Tim Hockin	:	Added insmod parameters, comment cleanup
+ *					Parameterized timeout
+ *		Tigran Aivazian	:	Restructured wdt_init() to handle failures
+ *		Joel Becker	:	Added WDIOC_GET/SETTIMEOUT
+ *		Matt Domsch	:	Added nowayout module option
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/fs.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include "wd501p.h"
+
+static unsigned long wdt_is_open;
+static char expect_close;
+
+/*
+ *	Module parameters
+ */
+
+#define WD_TIMO 60			/* Default heartbeat = 60 seconds */
+
+static int heartbeat = WD_TIMO;
+static int wd_heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/* You must set these - there is no sane way to probe for this board. */
+static int io=0x240;
+static int irq=11;
+
+module_param(io, int, 0);
+MODULE_PARM_DESC(io, "WDT io port (default=0x240)");
+module_param(irq, int, 0);
+MODULE_PARM_DESC(irq, "WDT irq (default=11)");
+
+#ifdef CONFIG_WDT_501
+/* Support for the Fan Tachometer on the WDT501-P */
+static int tachometer;
+
+module_param(tachometer, int, 0);
+MODULE_PARM_DESC(tachometer, "WDT501-P Fan Tachometer support (0=disable, default=0)");
+#endif /* CONFIG_WDT_501 */
+
+/*
+ *	Programming support
+ */
+
+static void wdt_ctr_mode(int ctr, int mode)
+{
+	ctr<<=6;
+	ctr|=0x30;
+	ctr|=(mode<<1);
+	outb_p(ctr, WDT_CR);
+}
+
+static void wdt_ctr_load(int ctr, int val)
+{
+	outb_p(val&0xFF, WDT_COUNT0+ctr);
+	outb_p(val>>8, WDT_COUNT0+ctr);
+}
+
+/**
+ *	wdt_start:
+ *
+ *	Start the watchdog driver.
+ */
+
+static int wdt_start(void)
+{
+	inb_p(WDT_DC);			/* Disable watchdog */
+	wdt_ctr_mode(0,3);		/* Program CTR0 for Mode 3: Square Wave Generator */
+	wdt_ctr_mode(1,2);		/* Program CTR1 for Mode 2: Rate Generator */
+	wdt_ctr_mode(2,0);		/* Program CTR2 for Mode 0: Pulse on Terminal Count */
+	wdt_ctr_load(0, 8948);		/* Count at 100Hz */
+	wdt_ctr_load(1,wd_heartbeat);	/* Heartbeat */
+	wdt_ctr_load(2,65535);		/* Length of reset pulse */
+	outb_p(0, WDT_DC);		/* Enable watchdog */
+	return 0;
+}
+
+/**
+ *	wdt_stop:
+ *
+ *	Stop the watchdog driver.
+ */
+
+static int wdt_stop (void)
+{
+	/* Turn the card off */
+	inb_p(WDT_DC);			/* Disable watchdog */
+	wdt_ctr_load(2,0);		/* 0 length reset pulses now */
+	return 0;
+}
+
+/**
+ *	wdt_ping:
+ *
+ *	Reload counter one with the watchdog heartbeat. We don't bother reloading
+ *	the cascade counter.
+ */
+
+static int wdt_ping(void)
+{
+	/* Write a watchdog value */
+	inb_p(WDT_DC);			/* Disable watchdog */
+	wdt_ctr_mode(1,2);		/* Re-Program CTR1 for Mode 2: Rate Generator */
+	wdt_ctr_load(1,wd_heartbeat);	/* Heartbeat */
+	outb_p(0, WDT_DC);		/* Enable watchdog */
+	return 0;
+}
+
+/**
+ *	wdt_set_heartbeat:
+ *	@t:		the new heartbeat value that needs to be set.
+ *
+ *	Set a new heartbeat value for the watchdog device. If the heartbeat value is
+ *	incorrect we keep the old value and return -EINVAL. If successfull we
+ *	return 0.
+ */
+static int wdt_set_heartbeat(int t)
+{
+	if ((t < 1) || (t > 65535))
+		return -EINVAL;
+
+	heartbeat = t;
+	wd_heartbeat = t * 100;
+	return 0;
+}
+
+/**
+ *	wdt_get_status:
+ *	@status:		the new status.
+ *
+ *	Extract the status information from a WDT watchdog device. There are
+ *	several board variants so we have to know which bits are valid. Some
+ *	bits default to one and some to zero in order to be maximally painful.
+ *
+ *	we then map the bits onto the status ioctl flags.
+ */
+
+static int wdt_get_status(int *status)
+{
+	unsigned char new_status=inb_p(WDT_SR);
+
+	*status=0;
+	if (new_status & WDC_SR_ISOI0)
+		*status |= WDIOF_EXTERN1;
+	if (new_status & WDC_SR_ISII1)
+		*status |= WDIOF_EXTERN2;
+#ifdef CONFIG_WDT_501
+	if (!(new_status & WDC_SR_TGOOD))
+		*status |= WDIOF_OVERHEAT;
+	if (!(new_status & WDC_SR_PSUOVER))
+		*status |= WDIOF_POWEROVER;
+	if (!(new_status & WDC_SR_PSUUNDR))
+		*status |= WDIOF_POWERUNDER;
+	if (tachometer) {
+		if (!(new_status & WDC_SR_FANGOOD))
+			*status |= WDIOF_FANFAULT;
+	}
+#endif /* CONFIG_WDT_501 */
+	return 0;
+}
+
+#ifdef CONFIG_WDT_501
+/**
+ *	wdt_get_temperature:
+ *
+ *	Reports the temperature in degrees Fahrenheit. The API is in
+ *	farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static int wdt_get_temperature(int *temperature)
+{
+	unsigned short c=inb_p(WDT_RT);
+
+	*temperature = (c * 11 / 15) + 7;
+	return 0;
+}
+#endif /* CONFIG_WDT_501 */
+
+/**
+ *	wdt_interrupt:
+ *	@irq:		Interrupt number
+ *	@dev_id:	Unused as we don't allow multiple devices.
+ *	@regs:		Unused.
+ *
+ *	Handle an interrupt from the board. These are raised when the status
+ *	map changes in what the board considers an interesting way. That means
+ *	a failure condition occurring.
+ */
+
+static irqreturn_t wdt_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	/*
+	 *	Read the status register see what is up and
+	 *	then printk it.
+	 */
+	unsigned char status=inb_p(WDT_SR);
+
+	printk(KERN_CRIT "WDT status %d\n", status);
+
+#ifdef CONFIG_WDT_501
+	if (!(status & WDC_SR_TGOOD))
+		printk(KERN_CRIT "Overheat alarm.(%d)\n",inb_p(WDT_RT));
+	if (!(status & WDC_SR_PSUOVER))
+		printk(KERN_CRIT "PSU over voltage.\n");
+	if (!(status & WDC_SR_PSUUNDR))
+		printk(KERN_CRIT "PSU under voltage.\n");
+	if (tachometer) {
+		if (!(status & WDC_SR_FANGOOD))
+			printk(KERN_CRIT "Possible fan fault.\n");
+	}
+#endif /* CONFIG_WDT_501 */
+	if (!(status & WDC_SR_WCCR))
+#ifdef SOFTWARE_REBOOT
+#ifdef ONLY_TESTING
+		printk(KERN_CRIT "Would Reboot.\n");
+#else
+		printk(KERN_CRIT "Initiating system reboot.\n");
+		machine_restart(NULL);
+#endif
+#else
+		printk(KERN_CRIT "Reset in 5ms.\n");
+#endif
+	return IRQ_HANDLED;
+}
+
+
+/**
+ *	wdt_write:
+ *	@file: file handle to the watchdog
+ *	@buf: buffer to write (unused as data does not matter here
+ *	@count: count of bytes
+ *	@ppos: pointer to the position to write. No seeks allowed
+ *
+ *	A write to a watchdog device is defined as a keepalive signal. Any
+ *	write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	if(count) {
+		if (!nowayout) {
+			size_t i;
+
+			/* In case it was set long ago */
+			expect_close = 0;
+
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, buf + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		wdt_ping();
+	}
+	return count;
+}
+
+/**
+ *	wdt_ioctl:
+ *	@inode: inode of the device
+ *	@file: file handle to the device
+ *	@cmd: watchdog command
+ *	@arg: argument pointer
+ *
+ *	The watchdog API defines a common set of functions for all watchdogs
+ *	according to their available features. We only actually usefully support
+ *	querying capabilities and current status.
+ */
+
+static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+	int new_heartbeat;
+	int status;
+
+	static struct watchdog_info ident = {
+		.options =		WDIOF_SETTIMEOUT|
+					WDIOF_MAGICCLOSE|
+					WDIOF_KEEPALIVEPING,
+		.firmware_version =	1,
+		.identity =		"WDT500/501",
+	};
+
+	/* Add options according to the card we have */
+	ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2);
+#ifdef CONFIG_WDT_501
+	ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER);
+	if (tachometer)
+		ident.options |= WDIOF_FANFAULT;
+#endif /* CONFIG_WDT_501 */
+
+	switch(cmd)
+	{
+		default:
+			return -ENOIOCTLCMD;
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+
+		case WDIOC_GETSTATUS:
+			wdt_get_status(&status);
+			return put_user(status, p);
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+		case WDIOC_KEEPALIVE:
+			wdt_ping();
+			return 0;
+		case WDIOC_SETTIMEOUT:
+			if (get_user(new_heartbeat, p))
+				return -EFAULT;
+
+			if (wdt_set_heartbeat(new_heartbeat))
+				return -EINVAL;
+
+			wdt_ping();
+			/* Fall */
+		case WDIOC_GETTIMEOUT:
+			return put_user(heartbeat, p);
+	}
+}
+
+/**
+ *	wdt_open:
+ *	@inode: inode of device
+ *	@file: file handle to device
+ *
+ *	The watchdog device has been opened. The watchdog device is single
+ *	open and on opening we load the counters. Counter zero is a 100Hz
+ *	cascade, into counter 1 which downcounts to reboot. When the counter
+ *	triggers counter 2 downcounts the length of the reset pulse which
+ *	set set to be as long as possible.
+ */
+
+static int wdt_open(struct inode *inode, struct file *file)
+{
+	if(test_and_set_bit(0, &wdt_is_open))
+		return -EBUSY;
+	/*
+	 *	Activate
+	 */
+	wdt_start();
+	return nonseekable_open(inode, file);
+}
+
+/**
+ *	wdt_release:
+ *	@inode: inode to board
+ *	@file: file handle to board
+ *
+ *	The watchdog has a configurable API. There is a religious dispute
+ *	between people who want their watchdog to be able to shut down and
+ *	those who want to be sure if the watchdog manager dies the machine
+ *	reboots. In the former case we disable the counters, in the latter
+ *	case you have to open it again very soon.
+ */
+
+static int wdt_release(struct inode *inode, struct file *file)
+{
+	if (expect_close == 42) {
+		wdt_stop();
+		clear_bit(0, &wdt_is_open);
+	} else {
+		printk(KERN_CRIT "wdt: WDT device closed unexpectedly.  WDT will not stop!\n");
+		wdt_ping();
+	}
+	expect_close = 0;
+	return 0;
+}
+
+#ifdef CONFIG_WDT_501
+/**
+ *	wdt_temp_read:
+ *	@file: file handle to the watchdog board
+ *	@buf: buffer to write 1 byte into
+ *	@count: length of buffer
+ *	@ptr: offset (no seek allowed)
+ *
+ *	Temp_read reports the temperature in degrees Fahrenheit. The API is in
+ *	farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static ssize_t wdt_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
+{
+	int temperature;
+
+	if (wdt_get_temperature(&temperature))
+		return -EFAULT;
+
+	if (copy_to_user (buf, &temperature, 1))
+		return -EFAULT;
+
+	return 1;
+}
+
+/**
+ *	wdt_temp_open:
+ *	@inode: inode of device
+ *	@file: file handle to device
+ *
+ *	The temperature device has been opened.
+ */
+
+static int wdt_temp_open(struct inode *inode, struct file *file)
+{
+	return nonseekable_open(inode, file);
+}
+
+/**
+ *	wdt_temp_release:
+ *	@inode: inode to board
+ *	@file: file handle to board
+ *
+ *	The temperature device has been closed.
+ */
+
+static int wdt_temp_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+#endif /* CONFIG_WDT_501 */
+
+/**
+ *	notify_sys:
+ *	@this: our notifier block
+ *	@code: the event being reported
+ *	@unused: unused
+ *
+ *	Our notifier is called on system shutdowns. We want to turn the card
+ *	off at reboot otherwise the machine will reboot again during memory
+ *	test or worse yet during the following fsck. This would suck, in fact
+ *	trust me - if it happens it does suck.
+ */
+
+static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if(code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the card off */
+		wdt_stop();
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+
+static struct file_operations wdt_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= wdt_write,
+	.ioctl		= wdt_ioctl,
+	.open		= wdt_open,
+	.release	= wdt_release,
+};
+
+static struct miscdevice wdt_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &wdt_fops,
+};
+
+#ifdef CONFIG_WDT_501
+static struct file_operations wdt_temp_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= wdt_temp_read,
+	.open		= wdt_temp_open,
+	.release	= wdt_temp_release,
+};
+
+static struct miscdevice temp_miscdev = {
+	.minor	= TEMP_MINOR,
+	.name	= "temperature",
+	.fops	= &wdt_temp_fops,
+};
+#endif /* CONFIG_WDT_501 */
+
+/*
+ *	The WDT card needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block wdt_notifier = {
+	.notifier_call = wdt_notify_sys,
+};
+
+/**
+ *	cleanup_module:
+ *
+ *	Unload the watchdog. You cannot do this with any file handles open.
+ *	If your watchdog is set to continue ticking on close and you unload
+ *	it, well it keeps ticking. We won't get the interrupt but the board
+ *	will not touch PC memory so all is fine. You just have to load a new
+ *	module in 60 seconds or reboot.
+ */
+
+static void __exit wdt_exit(void)
+{
+	misc_deregister(&wdt_miscdev);
+#ifdef CONFIG_WDT_501
+	misc_deregister(&temp_miscdev);
+#endif /* CONFIG_WDT_501 */
+	unregister_reboot_notifier(&wdt_notifier);
+	free_irq(irq, NULL);
+	release_region(io,8);
+}
+
+/**
+ * 	wdt_init:
+ *
+ *	Set up the WDT watchdog board. All we have to do is grab the
+ *	resources we require and bitch if anyone beat us to them.
+ *	The open() function will actually kick the board off.
+ */
+
+static int __init wdt_init(void)
+{
+	int ret;
+
+	/* Check that the heartbeat value is within it's range ; if not reset to the default */
+	if (wdt_set_heartbeat(heartbeat)) {
+		wdt_set_heartbeat(WD_TIMO);
+		printk(KERN_INFO "wdt: heartbeat value must be 0<heartbeat<65536, using %d\n",
+			WD_TIMO);
+	}
+
+	if (!request_region(io, 8, "wdt501p")) {
+		printk(KERN_ERR "wdt: I/O address 0x%04x already in use\n", io);
+		ret = -EBUSY;
+		goto out;
+	}
+
+	ret = request_irq(irq, wdt_interrupt, SA_INTERRUPT, "wdt501p", NULL);
+	if(ret) {
+		printk(KERN_ERR "wdt: IRQ %d is not free.\n", irq);
+		goto outreg;
+	}
+
+	ret = register_reboot_notifier(&wdt_notifier);
+	if(ret) {
+		printk(KERN_ERR "wdt: cannot register reboot notifier (err=%d)\n", ret);
+		goto outirq;
+	}
+
+#ifdef CONFIG_WDT_501
+	ret = misc_register(&temp_miscdev);
+	if (ret) {
+		printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n",
+			TEMP_MINOR, ret);
+		goto outrbt;
+	}
+#endif /* CONFIG_WDT_501 */
+
+	ret = misc_register(&wdt_miscdev);
+	if (ret) {
+		printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto outmisc;
+	}
+
+	ret = 0;
+	printk(KERN_INFO "WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n",
+		io, irq, heartbeat, nowayout);
+#ifdef CONFIG_WDT_501
+	printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled"));
+#endif /* CONFIG_WDT_501 */
+
+out:
+	return ret;
+
+outmisc:
+#ifdef CONFIG_WDT_501
+	misc_deregister(&temp_miscdev);
+outrbt:
+#endif /* CONFIG_WDT_501 */
+	unregister_reboot_notifier(&wdt_notifier);
+outirq:
+	free_irq(irq, NULL);
+outreg:
+	release_region(io,8);
+	goto out;
+}
+
+module_init(wdt_init);
+module_exit(wdt_exit);
+
+MODULE_AUTHOR("Alan Cox");
+MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/watchdog/wdt285.c b/drivers/char/watchdog/wdt285.c
new file mode 100644
index 0000000..52825a1
--- /dev/null
+++ b/drivers/char/watchdog/wdt285.c
@@ -0,0 +1,229 @@
+/*
+ *	Intel 21285 watchdog driver
+ *	Copyright (c) Phil Blundell <pb@nexus.co.uk>, 1998
+ *
+ *	based on
+ *
+ *	SoftDog	0.05:	A Software Watchdog Device
+ *
+ *	(c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *
+ *	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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/hardware/dec21285.h>
+
+/*
+ * Define this to stop the watchdog actually rebooting the machine.
+ */
+#undef ONLY_TESTING
+
+static unsigned int soft_margin = 60;		/* in seconds */
+static unsigned int reload;
+static unsigned long timer_alive;
+
+#ifdef ONLY_TESTING
+/*
+ *	If the timer expires..
+ */
+static void watchdog_fire(int irq, void *dev_id, struct pt_regs *regs)
+{
+	printk(KERN_CRIT "Watchdog: Would Reboot.\n");
+	*CSR_TIMER4_CNTL = 0;
+	*CSR_TIMER4_CLR = 0;
+}
+#endif
+
+/*
+ *	Refresh the timer.
+ */
+static void watchdog_ping(void)
+{
+	*CSR_TIMER4_LOAD = reload;
+}
+
+/*
+ *	Allow only one person to hold it open
+ */
+static int watchdog_open(struct inode *inode, struct file *file)
+{
+	int ret;
+
+	if (*CSR_SA110_CNTL & (1 << 13))
+		return -EBUSY;
+
+	if (test_and_set_bit(1, &timer_alive))
+		return -EBUSY;
+
+	reload = soft_margin * (mem_fclk_21285 / 256);
+
+	*CSR_TIMER4_CLR = 0;
+	watchdog_ping();
+	*CSR_TIMER4_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD
+		| TIMER_CNTL_DIV256;
+
+#ifdef ONLY_TESTING
+	ret = request_irq(IRQ_TIMER4, watchdog_fire, 0, "watchdog", NULL);
+	if (ret) {
+		*CSR_TIMER4_CNTL = 0;
+		clear_bit(1, &timer_alive);
+	}
+#else
+	/*
+	 * Setting this bit is irreversible; once enabled, there is
+	 * no way to disable the watchdog.
+	 */
+	*CSR_SA110_CNTL |= 1 << 13;
+
+	ret = 0;
+#endif
+	nonseekable_open(inode, file);
+	return ret;
+}
+
+/*
+ *	Shut off the timer.
+ *	Note: if we really have enabled the watchdog, there
+ *	is no way to turn off.
+ */
+static int watchdog_release(struct inode *inode, struct file *file)
+{
+#ifdef ONLY_TESTING
+	free_irq(IRQ_TIMER4, NULL);
+	clear_bit(1, &timer_alive);
+#endif
+	return 0;
+}
+
+static ssize_t
+watchdog_write(struct file *file, const char *data, size_t len, loff_t *ppos)
+{
+	/*
+	 *	Refresh the timer.
+	 */
+	if (len)
+		watchdog_ping();
+
+	return len;
+}
+
+static struct watchdog_info ident = {
+	.options	= WDIOF_SETTIMEOUT,
+	.identity	= "Footbridge Watchdog",
+};
+
+static int
+watchdog_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	       unsigned long arg)
+{
+	unsigned int new_margin;
+	int ret = -ENOIOCTLCMD;
+
+	switch(cmd) {
+	case WDIOC_GETSUPPORT:
+		ret = 0;
+		if (copy_to_user((void *)arg, &ident, sizeof(ident)))
+			ret = -EFAULT;
+		break;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		ret = put_user(0,(int *)arg);
+		break;
+
+	case WDIOC_KEEPALIVE:
+		watchdog_ping();
+		ret = 0;
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		ret = get_user(new_margin, (int *)arg);
+		if (ret)
+			break;
+
+		/* Arbitrary, can't find the card's limits */
+		if (new_margin < 0 || new_margin > 60) {
+			ret = -EINVAL;
+			break;
+		}
+
+		soft_margin = new_margin;
+		reload = soft_margin * (mem_fclk_21285 / 256);
+		watchdog_ping();
+		/* Fall */
+	case WDIOC_GETTIMEOUT:
+		ret = put_user(soft_margin, (int *)arg);
+		break;
+	}
+	return ret;
+}
+
+static struct file_operations watchdog_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= watchdog_write,
+	.ioctl		= watchdog_ioctl,
+	.open		= watchdog_open,
+	.release	= watchdog_release,
+};
+
+static struct miscdevice watchdog_miscdev = {
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &watchdog_fops,
+};
+
+static int __init footbridge_watchdog_init(void)
+{
+	int retval;
+
+	if (machine_is_netwinder())
+		return -ENODEV;
+
+	retval = misc_register(&watchdog_miscdev);
+	if (retval < 0)
+		return retval;
+
+	printk("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n",
+	       soft_margin);
+
+	if (machine_is_cats())
+		printk("Warning: Watchdog reset may not work on this machine.\n");
+	return 0;
+}
+
+static void __exit footbridge_watchdog_exit(void)
+{
+	misc_deregister(&watchdog_miscdev);
+}
+
+MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>");
+MODULE_DESCRIPTION("Footbridge watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+
+module_param(soft_margin, int, 0);
+MODULE_PARM_DESC(soft_margin,"Watchdog timeout in seconds");
+
+module_init(footbridge_watchdog_init);
+module_exit(footbridge_watchdog_exit);
diff --git a/drivers/char/watchdog/wdt977.c b/drivers/char/watchdog/wdt977.c
new file mode 100644
index 0000000..072e9b2
--- /dev/null
+++ b/drivers/char/watchdog/wdt977.c
@@ -0,0 +1,459 @@
+/*
+ *	Wdt977	0.03:	A Watchdog Device for Netwinder W83977AF chip
+ *
+ *	(c) Copyright 1998 Rebel.com (Woody Suwalski <woody@netwinder.org>)
+ *
+ *			-----------------------
+ *
+ *	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.
+ *
+ *			-----------------------
+ *      14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
+ *           Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
+ *	19-Dec-2001 Woody Suwalski: Netwinder fixes, ioctl interface
+ *	06-Jan-2002 Woody Suwalski: For compatibility, convert all timeouts
+ *				    from minutes to seconds.
+ *      07-Jul-2003 Daniele Bellucci: Audit return code of misc_register in
+ *                                    nwwatchdog_init.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/watchdog.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/mach-types.h>
+#include <asm/uaccess.h>
+
+#define PFX "Wdt977: "
+#define WATCHDOG_MINOR	130
+
+#define	DEFAULT_TIMEOUT	60			/* default timeout in seconds */
+
+static	int timeout = DEFAULT_TIMEOUT;
+static	int timeoutM;				/* timeout in minutes */
+static	unsigned long timer_alive;
+static	int testmode;
+static	char expect_close;
+
+module_param(timeout, int, 0);
+MODULE_PARM_DESC(timeout,"Watchdog timeout in seconds (60..15300), default=" __MODULE_STRING(DEFAULT_TIMEOUT) ")");
+module_param(testmode, int, 0);
+MODULE_PARM_DESC(testmode,"Watchdog testmode (1 = no reboot), default=0");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+/*
+ * Start the watchdog
+ */
+
+static int wdt977_start(void)
+{
+	/* unlock the SuperIO chip */
+	outb(0x87,0x370);
+	outb(0x87,0x370);
+
+	/* select device Aux2 (device=8) and set watchdog regs F2, F3 and F4
+	 * F2 has the timeout in minutes
+	 * F3 could be set to the POWER LED blink (with GP17 set to PowerLed)
+	 *   at timeout, and to reset timer on kbd/mouse activity (not impl.)
+	 * F4 is used to just clear the TIMEOUT'ed state (bit 0)
+	 */
+	outb(0x07,0x370);
+	outb(0x08,0x371);
+	outb(0xF2,0x370);
+	outb(timeoutM,0x371);
+	outb(0xF3,0x370);
+	outb(0x00,0x371);	/* another setting is 0E for kbd/mouse/LED */
+	outb(0xF4,0x370);
+	outb(0x00,0x371);
+
+	/* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */
+	/* in test mode watch the bit 1 on F4 to indicate "triggered" */
+	if (!testmode)
+	{
+		outb(0x07,0x370);
+		outb(0x07,0x371);
+		outb(0xE6,0x370);
+		outb(0x08,0x371);
+	}
+
+	/* lock the SuperIO chip */
+	outb(0xAA,0x370);
+
+	printk(KERN_INFO PFX "activated.\n");
+
+	return 0;
+}
+
+/*
+ * Stop the watchdog
+ */
+
+static int wdt977_stop(void)
+{
+	/* unlock the SuperIO chip */
+	outb(0x87,0x370);
+	outb(0x87,0x370);
+
+	/* select device Aux2 (device=8) and set watchdog regs F2,F3 and F4
+	* F3 is reset to its default state
+	* F4 can clear the TIMEOUT'ed state (bit 0) - back to default
+	* We can not use GP17 as a PowerLed, as we use its usage as a RedLed
+	*/
+	outb(0x07,0x370);
+	outb(0x08,0x371);
+	outb(0xF2,0x370);
+	outb(0xFF,0x371);
+	outb(0xF3,0x370);
+	outb(0x00,0x371);
+	outb(0xF4,0x370);
+	outb(0x00,0x371);
+	outb(0xF2,0x370);
+	outb(0x00,0x371);
+
+	/* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */
+	outb(0x07,0x370);
+	outb(0x07,0x371);
+	outb(0xE6,0x370);
+	outb(0x08,0x371);
+
+	/* lock the SuperIO chip */
+	outb(0xAA,0x370);
+
+	printk(KERN_INFO PFX "shutdown.\n");
+
+	return 0;
+}
+
+/*
+ * Send a keepalive ping to the watchdog
+ * This is done by simply re-writing the timeout to reg. 0xF2
+ */
+
+static int wdt977_keepalive(void)
+{
+	/* unlock the SuperIO chip */
+	outb(0x87,0x370);
+	outb(0x87,0x370);
+
+	/* select device Aux2 (device=8) and kicks watchdog reg F2 */
+	/* F2 has the timeout in minutes */
+	outb(0x07,0x370);
+	outb(0x08,0x371);
+	outb(0xF2,0x370);
+	outb(timeoutM,0x371);
+
+	/* lock the SuperIO chip */
+	outb(0xAA,0x370);
+
+	return 0;
+}
+
+/*
+ * Set the watchdog timeout value
+ */
+
+static int wdt977_set_timeout(int t)
+{
+	int tmrval;
+
+	/* convert seconds to minutes, rounding up */
+	tmrval = (t + 59) / 60;
+
+	if (machine_is_netwinder()) {
+		/* we have a hw bug somewhere, so each 977 minute is actually only 30sec
+		 *  this limits the max timeout to half of device max of 255 minutes...
+		 */
+		tmrval += tmrval;
+	}
+
+	if ((tmrval < 1) || (tmrval > 255))
+		return -EINVAL;
+
+	/* timeout is the timeout in seconds, timeoutM is the timeout in minutes) */
+	timeout = t;
+	timeoutM = tmrval;
+	return 0;
+}
+
+/*
+ * Get the watchdog status
+ */
+
+static int wdt977_get_status(int *status)
+{
+	int new_status;
+
+	*status=0;
+
+	/* unlock the SuperIO chip */
+	outb(0x87,0x370);
+	outb(0x87,0x370);
+
+	/* select device Aux2 (device=8) and read watchdog reg F4 */
+	outb(0x07,0x370);
+	outb(0x08,0x371);
+	outb(0xF4,0x370);
+	new_status = inb(0x371);
+
+	/* lock the SuperIO chip */
+	outb(0xAA,0x370);
+
+	if (new_status & 1)
+		*status |= WDIOF_CARDRESET;
+
+	return 0;
+}
+
+
+/*
+ *	/dev/watchdog handling
+ */
+
+static int wdt977_open(struct inode *inode, struct file *file)
+{
+	/* If the watchdog is alive we don't need to start it again */
+	if( test_and_set_bit(0,&timer_alive) )
+		return -EBUSY;
+
+	if (nowayout)
+		__module_get(THIS_MODULE);
+
+	wdt977_start();
+	return nonseekable_open(inode, file);
+}
+
+static int wdt977_release(struct inode *inode, struct file *file)
+{
+	/*
+	 *	Shut off the timer.
+	 * 	Lock it in if it's a module and we set nowayout
+	 */
+	if (expect_close == 42)
+	{
+		wdt977_stop();
+		clear_bit(0,&timer_alive);
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
+		wdt977_keepalive();
+	}
+	expect_close = 0;
+	return 0;
+}
+
+
+/*
+ *      wdt977_write:
+ *      @file: file handle to the watchdog
+ *      @buf: buffer to write (unused as data does not matter here
+ *      @count: count of bytes
+ *      @ppos: pointer to the position to write. No seeks allowed
+ *
+ *      A write to a watchdog device is defined as a keepalive signal. Any
+ *      write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdt977_write(struct file *file, const char __user *buf,
+			    size_t count, loff_t *ppos)
+{
+	if (count) {
+		if (!nowayout) {
+			size_t i;
+
+			/* In case it was set long ago */
+			expect_close = 0;
+
+			for (i = 0; i != count; i++) {
+				char c;
+				if (get_user(c, buf + i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+
+		wdt977_keepalive();
+	}
+	return count;
+}
+
+/*
+ *      wdt977_ioctl:
+ *      @inode: inode of the device
+ *      @file: file handle to the device
+ *      @cmd: watchdog command
+ *      @arg: argument pointer
+ *
+ *      The watchdog API defines a common set of functions for all watchdogs
+ *      according to their available features.
+ */
+
+static struct watchdog_info ident = {
+	.options =		WDIOF_SETTIMEOUT |
+				WDIOF_MAGICCLOSE |
+				WDIOF_KEEPALIVEPING,
+	.firmware_version =	1,
+	.identity =		"Winbond 83977",
+};
+
+static int wdt977_ioctl(struct inode *inode, struct file *file,
+	unsigned int cmd, unsigned long arg)
+{
+	int status;
+	int new_options, retval = -EINVAL;
+	int new_timeout;
+	union {
+		struct watchdog_info __user *ident;
+		int __user *i;
+	} uarg;
+
+	uarg.i = (int __user *)arg;
+
+	switch(cmd)
+	{
+	default:
+		return -ENOIOCTLCMD;
+
+	case WDIOC_GETSUPPORT:
+		return copy_to_user(uarg.ident, &ident,
+			sizeof(ident)) ? -EFAULT : 0;
+
+	case WDIOC_GETSTATUS:
+		wdt977_get_status(&status);
+		return put_user(status, uarg.i);
+
+	case WDIOC_GETBOOTSTATUS:
+		return put_user(0, uarg.i);
+
+	case WDIOC_KEEPALIVE:
+		wdt977_keepalive();
+		return 0;
+
+	case WDIOC_SETOPTIONS:
+		if (get_user (new_options, uarg.i))
+			return -EFAULT;
+
+		if (new_options & WDIOS_DISABLECARD) {
+			wdt977_stop();
+			retval = 0;
+		}
+
+		if (new_options & WDIOS_ENABLECARD) {
+			wdt977_start();
+			retval = 0;
+		}
+
+		return retval;
+
+	case WDIOC_SETTIMEOUT:
+		if (get_user(new_timeout, uarg.i))
+			return -EFAULT;
+
+		if (wdt977_set_timeout(new_timeout))
+		    return -EINVAL;
+
+		wdt977_keepalive();
+		/* Fall */
+
+	case WDIOC_GETTIMEOUT:
+		return put_user(timeout, uarg.i);
+
+	}
+}
+
+static int wdt977_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if(code==SYS_DOWN || code==SYS_HALT)
+		wdt977_stop();
+	return NOTIFY_DONE;
+}
+
+static struct file_operations wdt977_fops=
+{
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= wdt977_write,
+	.ioctl		= wdt977_ioctl,
+	.open		= wdt977_open,
+	.release	= wdt977_release,
+};
+
+static struct miscdevice wdt977_miscdev=
+{
+	.minor		= WATCHDOG_MINOR,
+	.name		= "watchdog",
+	.fops		= &wdt977_fops,
+};
+
+static struct notifier_block wdt977_notifier = {
+	.notifier_call = wdt977_notify_sys,
+};
+
+static int __init nwwatchdog_init(void)
+{
+	int retval;
+	if (!machine_is_netwinder())
+		return -ENODEV;
+
+	/* Check that the timeout value is within it's range ; if not reset to the default */
+	if (wdt977_set_timeout(timeout)) {
+		wdt977_set_timeout(DEFAULT_TIMEOUT);
+		printk(KERN_INFO PFX "timeout value must be 60<timeout<15300, using %d\n",
+			DEFAULT_TIMEOUT);
+	}
+
+	retval = register_reboot_notifier(&wdt977_notifier);
+	if (retval) {
+		printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
+			retval);
+		return retval;
+	}
+
+	retval = misc_register(&wdt977_miscdev);
+	if (retval) {
+		printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, retval);
+		unregister_reboot_notifier(&wdt977_notifier);
+		return retval;
+	}
+
+	printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d, testmode = %i)\n",
+		timeout, nowayout, testmode);
+
+	return 0;
+}
+
+static void __exit nwwatchdog_exit(void)
+{
+	misc_deregister(&wdt977_miscdev);
+	unregister_reboot_notifier(&wdt977_notifier);
+}
+
+module_init(nwwatchdog_init);
+module_exit(nwwatchdog_exit);
+
+MODULE_AUTHOR("Woody Suwalski <woody@netwinder.org>");
+MODULE_DESCRIPTION("W83977AF Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
diff --git a/drivers/char/watchdog/wdt_pci.c b/drivers/char/watchdog/wdt_pci.c
new file mode 100644
index 0000000..7651ded
--- /dev/null
+++ b/drivers/char/watchdog/wdt_pci.c
@@ -0,0 +1,763 @@
+/*
+ *	Industrial Computer Source PCI-WDT500/501 driver
+ *
+ *	(c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved.
+ *				http://www.redhat.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.
+ *
+ *	Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
+ *	warranty for any of this software. This material is provided
+ *	"AS-IS" and at no charge.
+ *
+ *	(c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ *	Release 0.10.
+ *
+ *	Fixes
+ *		Dave Gregorich	:	Modularisation and minor bugs
+ *		Alan Cox	:	Added the watchdog ioctl() stuff
+ *		Alan Cox	:	Fixed the reboot problem (as noted by
+ *					Matt Crocker).
+ *		Alan Cox	:	Added wdt= boot option
+ *		Alan Cox	:	Cleaned up copy/user stuff
+ *		Tim Hockin	:	Added insmod parameters, comment cleanup
+ *					Parameterized timeout
+ *		JP Nollmann	:	Added support for PCI wdt501p
+ *		Alan Cox	:	Split ISA and PCI cards into two drivers
+ *		Jeff Garzik	:	PCI cleanups
+ *		Tigran Aivazian	:	Restructured wdtpci_init_one() to handle failures
+ *		Joel Becker 	:	Added WDIOC_GET/SETTIMEOUT
+ *		Zwane Mwaikambo	:	Magic char closing, locking changes, cleanups
+ *		Matt Domsch	:	nowayout module option
+ */
+
+#include <linux/config.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+#include <linux/watchdog.h>
+#include <linux/ioport.h>
+#include <linux/notifier.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/pci.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#define WDT_IS_PCI
+#include "wd501p.h"
+
+#define PFX "wdt_pci: "
+
+/*
+ * Until Access I/O gets their application for a PCI vendor ID approved,
+ * I don't think that it's appropriate to move these constants into the
+ * regular pci_ids.h file. -- JPN 2000/01/18
+ */
+
+#ifndef PCI_VENDOR_ID_ACCESSIO
+#define PCI_VENDOR_ID_ACCESSIO 0x494f
+#endif
+#ifndef PCI_DEVICE_ID_WDG_CSM
+#define PCI_DEVICE_ID_WDG_CSM 0x22c0
+#endif
+
+/* We can only use 1 card due to the /dev/watchdog restriction */
+static int dev_count;
+
+static struct semaphore open_sem;
+static spinlock_t wdtpci_lock;
+static char expect_close;
+
+static int io;
+static int irq;
+
+/* Default timeout */
+#define WD_TIMO 60			/* Default heartbeat = 60 seconds */
+
+static int heartbeat = WD_TIMO;
+static int wd_heartbeat;
+module_param(heartbeat, int, 0);
+MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")");
+
+#ifdef CONFIG_WATCHDOG_NOWAYOUT
+static int nowayout = 1;
+#else
+static int nowayout = 0;
+#endif
+
+module_param(nowayout, int, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
+
+#ifdef CONFIG_WDT_501_PCI
+/* Support for the Fan Tachometer on the PCI-WDT501 */
+static int tachometer;
+
+module_param(tachometer, int, 0);
+MODULE_PARM_DESC(tachometer, "PCI-WDT501 Fan Tachometer support (0=disable, default=0)");
+#endif /* CONFIG_WDT_501_PCI */
+
+/*
+ *	Programming support
+ */
+
+static void wdtpci_ctr_mode(int ctr, int mode)
+{
+	ctr<<=6;
+	ctr|=0x30;
+	ctr|=(mode<<1);
+	outb_p(ctr, WDT_CR);
+}
+
+static void wdtpci_ctr_load(int ctr, int val)
+{
+	outb_p(val&0xFF, WDT_COUNT0+ctr);
+	outb_p(val>>8, WDT_COUNT0+ctr);
+}
+
+/**
+ *	wdtpci_start:
+ *
+ *	Start the watchdog driver.
+ */
+
+static int wdtpci_start(void)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdtpci_lock, flags);
+
+	/*
+	 * "pet" the watchdog, as Access says.
+	 * This resets the clock outputs.
+	 */
+	inb_p(WDT_DC);			/* Disable watchdog */
+	wdtpci_ctr_mode(2,0);		/* Program CTR2 for Mode 0: Pulse on Terminal Count */
+	outb_p(0, WDT_DC);		/* Enable watchdog */
+
+	inb_p(WDT_DC);			/* Disable watchdog */
+	outb_p(0, WDT_CLOCK);		/* 2.0833MHz clock */
+	inb_p(WDT_BUZZER);		/* disable */
+	inb_p(WDT_OPTONOTRST);		/* disable */
+	inb_p(WDT_OPTORST);		/* disable */
+	inb_p(WDT_PROGOUT);		/* disable */
+	wdtpci_ctr_mode(0,3);		/* Program CTR0 for Mode 3: Square Wave Generator */
+	wdtpci_ctr_mode(1,2);		/* Program CTR1 for Mode 2: Rate Generator */
+	wdtpci_ctr_mode(2,1);		/* Program CTR2 for Mode 1: Retriggerable One-Shot */
+	wdtpci_ctr_load(0,20833);	/* count at 100Hz */
+	wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */
+	/* DO NOT LOAD CTR2 on PCI card! -- JPN */
+	outb_p(0, WDT_DC);		/* Enable watchdog */
+
+	spin_unlock_irqrestore(&wdtpci_lock, flags);
+	return 0;
+}
+
+/**
+ *	wdtpci_stop:
+ *
+ *	Stop the watchdog driver.
+ */
+
+static int wdtpci_stop (void)
+{
+	unsigned long flags;
+
+	/* Turn the card off */
+	spin_lock_irqsave(&wdtpci_lock, flags);
+	inb_p(WDT_DC);			/* Disable watchdog */
+	wdtpci_ctr_load(2,0);		/* 0 length reset pulses now */
+	spin_unlock_irqrestore(&wdtpci_lock, flags);
+	return 0;
+}
+
+/**
+ *	wdtpci_ping:
+ *
+ *	Reload counter one with the watchdog heartbeat. We don't bother reloading
+ *	the cascade counter.
+ */
+
+static int wdtpci_ping(void)
+{
+	unsigned long flags;
+
+	/* Write a watchdog value */
+	spin_lock_irqsave(&wdtpci_lock, flags);
+	inb_p(WDT_DC);			/* Disable watchdog */
+	wdtpci_ctr_mode(1,2);		/* Re-Program CTR1 for Mode 2: Rate Generator */
+	wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */
+	outb_p(0, WDT_DC);		/* Enable watchdog */
+	spin_unlock_irqrestore(&wdtpci_lock, flags);
+	return 0;
+}
+
+/**
+ *	wdtpci_set_heartbeat:
+ *	@t:		the new heartbeat value that needs to be set.
+ *
+ *	Set a new heartbeat value for the watchdog device. If the heartbeat value is
+ *	incorrect we keep the old value and return -EINVAL. If successfull we
+ *	return 0.
+ */
+static int wdtpci_set_heartbeat(int t)
+{
+	/* Arbitrary, can't find the card's limits */
+	if ((t < 1) || (t > 65535))
+		return -EINVAL;
+
+	heartbeat = t;
+	wd_heartbeat = t * 100;
+	return 0;
+}
+
+/**
+ *	wdtpci_get_status:
+ *	@status:		the new status.
+ *
+ *	Extract the status information from a WDT watchdog device. There are
+ *	several board variants so we have to know which bits are valid. Some
+ *	bits default to one and some to zero in order to be maximally painful.
+ *
+ *	we then map the bits onto the status ioctl flags.
+ */
+
+static int wdtpci_get_status(int *status)
+{
+	unsigned char new_status=inb_p(WDT_SR);
+
+	*status=0;
+	if (new_status & WDC_SR_ISOI0)
+		*status |= WDIOF_EXTERN1;
+	if (new_status & WDC_SR_ISII1)
+		*status |= WDIOF_EXTERN2;
+#ifdef CONFIG_WDT_501_PCI
+	if (!(new_status & WDC_SR_TGOOD))
+		*status |= WDIOF_OVERHEAT;
+	if (!(new_status & WDC_SR_PSUOVER))
+		*status |= WDIOF_POWEROVER;
+	if (!(new_status & WDC_SR_PSUUNDR))
+		*status |= WDIOF_POWERUNDER;
+	if (tachometer) {
+		if (!(new_status & WDC_SR_FANGOOD))
+			*status |= WDIOF_FANFAULT;
+	}
+#endif /* CONFIG_WDT_501_PCI */
+	return 0;
+}
+
+#ifdef CONFIG_WDT_501_PCI
+/**
+ *	wdtpci_get_temperature:
+ *
+ *	Reports the temperature in degrees Fahrenheit. The API is in
+ *	farenheit. It was designed by an imperial measurement luddite.
+ */
+
+static int wdtpci_get_temperature(int *temperature)
+{
+	unsigned short c=inb_p(WDT_RT);
+
+	*temperature = (c * 11 / 15) + 7;
+	return 0;
+}
+#endif /* CONFIG_WDT_501_PCI */
+
+/**
+ *	wdtpci_interrupt:
+ *	@irq:		Interrupt number
+ *	@dev_id:	Unused as we don't allow multiple devices.
+ *	@regs:		Unused.
+ *
+ *	Handle an interrupt from the board. These are raised when the status
+ *	map changes in what the board considers an interesting way. That means
+ *	a failure condition occurring.
+ */
+
+static irqreturn_t wdtpci_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	/*
+	 *	Read the status register see what is up and
+	 *	then printk it.
+	 */
+	unsigned char status=inb_p(WDT_SR);
+
+	printk(KERN_CRIT PFX "status %d\n", status);
+
+#ifdef CONFIG_WDT_501_PCI
+	if (!(status & WDC_SR_TGOOD))
+ 		printk(KERN_CRIT PFX "Overheat alarm.(%d)\n",inb_p(WDT_RT));
+	if (!(status & WDC_SR_PSUOVER))
+ 		printk(KERN_CRIT PFX "PSU over voltage.\n");
+	if (!(status & WDC_SR_PSUUNDR))
+ 		printk(KERN_CRIT PFX "PSU under voltage.\n");
+	if (tachometer) {
+		if (!(status & WDC_SR_FANGOOD))
+			printk(KERN_CRIT PFX "Possible fan fault.\n");
+	}
+#endif /* CONFIG_WDT_501_PCI */
+	if (!(status&WDC_SR_WCCR))
+#ifdef SOFTWARE_REBOOT
+#ifdef ONLY_TESTING
+		printk(KERN_CRIT PFX "Would Reboot.\n");
+#else
+		printk(KERN_CRIT PFX "Initiating system reboot.\n");
+		machine_restart(NULL);
+#endif
+#else
+		printk(KERN_CRIT PFX "Reset in 5ms.\n");
+#endif
+	return IRQ_HANDLED;
+}
+
+
+/**
+ *	wdtpci_write:
+ *	@file: file handle to the watchdog
+ *	@buf: buffer to write (unused as data does not matter here
+ *	@count: count of bytes
+ *	@ppos: pointer to the position to write. No seeks allowed
+ *
+ *	A write to a watchdog device is defined as a keepalive signal. Any
+ *	write of data will do, as we we don't define content meaning.
+ */
+
+static ssize_t wdtpci_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	if (count) {
+		if (!nowayout) {
+			size_t i;
+
+			expect_close = 0;
+
+			for (i = 0; i != count; i++) {
+				char c;
+				if(get_user(c, buf+i))
+					return -EFAULT;
+				if (c == 'V')
+					expect_close = 42;
+			}
+		}
+		wdtpci_ping();
+	}
+
+	return count;
+}
+
+/**
+ *	wdtpci_ioctl:
+ *	@inode: inode of the device
+ *	@file: file handle to the device
+ *	@cmd: watchdog command
+ *	@arg: argument pointer
+ *
+ *	The watchdog API defines a common set of functions for all watchdogs
+ *	according to their available features. We only actually usefully support
+ *	querying capabilities and current status.
+ */
+
+static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	int new_heartbeat;
+	int status;
+	void __user *argp = (void __user *)arg;
+	int __user *p = argp;
+
+	static struct watchdog_info ident = {
+		.options =		WDIOF_SETTIMEOUT|
+					WDIOF_MAGICCLOSE|
+					WDIOF_KEEPALIVEPING,
+		.firmware_version =	1,
+		.identity =		"PCI-WDT500/501",
+	};
+
+	/* Add options according to the card we have */
+	ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2);
+#ifdef CONFIG_WDT_501_PCI
+	ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER);
+	if (tachometer)
+		ident.options |= WDIOF_FANFAULT;
+#endif /* CONFIG_WDT_501_PCI */
+
+	switch(cmd)
+	{
+		default:
+			return -ENOIOCTLCMD;
+		case WDIOC_GETSUPPORT:
+			return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0;
+
+		case WDIOC_GETSTATUS:
+			wdtpci_get_status(&status);
+			return put_user(status, p);
+		case WDIOC_GETBOOTSTATUS:
+			return put_user(0, p);
+		case WDIOC_KEEPALIVE:
+			wdtpci_ping();
+			return 0;
+		case WDIOC_SETTIMEOUT:
+			if (get_user(new_heartbeat, p))
+				return -EFAULT;
+
+			if (wdtpci_set_heartbeat(new_heartbeat))
+				return -EINVAL;
+
+			wdtpci_ping();
+			/* Fall */
+		case WDIOC_GETTIMEOUT:
+			return put_user(heartbeat, p);
+	}
+}
+
+/**
+ *	wdtpci_open:
+ *	@inode: inode of device
+ *	@file: file handle to device
+ *
+ *	The watchdog device has been opened. The watchdog device is single
+ *	open and on opening we load the counters. Counter zero is a 100Hz
+ *	cascade, into counter 1 which downcounts to reboot. When the counter
+ *	triggers counter 2 downcounts the length of the reset pulse which
+ *	set set to be as long as possible.
+ */
+
+static int wdtpci_open(struct inode *inode, struct file *file)
+{
+	if (down_trylock(&open_sem))
+		return -EBUSY;
+
+	if (nowayout) {
+		__module_get(THIS_MODULE);
+	}
+	/*
+	 *	Activate
+	 */
+	wdtpci_start();
+	return nonseekable_open(inode, file);
+}
+
+/**
+ *	wdtpci_release:
+ *	@inode: inode to board
+ *	@file: file handle to board
+ *
+ *	The watchdog has a configurable API. There is a religious dispute
+ *	between people who want their watchdog to be able to shut down and
+ *	those who want to be sure if the watchdog manager dies the machine
+ *	reboots. In the former case we disable the counters, in the latter
+ *	case you have to open it again very soon.
+ */
+
+static int wdtpci_release(struct inode *inode, struct file *file)
+{
+	if (expect_close == 42) {
+		wdtpci_stop();
+	} else {
+		printk(KERN_CRIT PFX "Unexpected close, not stopping timer!");
+		wdtpci_ping();
+	}
+	expect_close = 0;
+	up(&open_sem);
+	return 0;
+}
+
+#ifdef CONFIG_WDT_501_PCI
+/**
+ *	wdtpci_temp_read:
+ *	@file: file handle to the watchdog board
+ *	@buf: buffer to write 1 byte into
+ *	@count: length of buffer
+ *	@ptr: offset (no seek allowed)
+ *
+ *	Read reports the temperature in degrees Fahrenheit. The API is in
+ *	fahrenheit. It was designed by an imperial measurement luddite.
+ */
+
+static ssize_t wdtpci_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
+{
+	int temperature;
+
+	if (wdtpci_get_temperature(&temperature))
+		return -EFAULT;
+
+	if (copy_to_user (buf, &temperature, 1))
+		return -EFAULT;
+
+	return 1;
+}
+
+/**
+ *	wdtpci_temp_open:
+ *	@inode: inode of device
+ *	@file: file handle to device
+ *
+ *	The temperature device has been opened.
+ */
+
+static int wdtpci_temp_open(struct inode *inode, struct file *file)
+{
+	return nonseekable_open(inode, file);
+}
+
+/**
+ *	wdtpci_temp_release:
+ *	@inode: inode to board
+ *	@file: file handle to board
+ *
+ *	The temperature device has been closed.
+ */
+
+static int wdtpci_temp_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+#endif /* CONFIG_WDT_501_PCI */
+
+/**
+ *	notify_sys:
+ *	@this: our notifier block
+ *	@code: the event being reported
+ *	@unused: unused
+ *
+ *	Our notifier is called on system shutdowns. We want to turn the card
+ *	off at reboot otherwise the machine will reboot again during memory
+ *	test or worse yet during the following fsck. This would suck, in fact
+ *	trust me - if it happens it does suck.
+ */
+
+static int wdtpci_notify_sys(struct notifier_block *this, unsigned long code,
+	void *unused)
+{
+	if (code==SYS_DOWN || code==SYS_HALT) {
+		/* Turn the card off */
+		wdtpci_stop();
+	}
+	return NOTIFY_DONE;
+}
+
+/*
+ *	Kernel Interfaces
+ */
+
+
+static struct file_operations wdtpci_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.write		= wdtpci_write,
+	.ioctl		= wdtpci_ioctl,
+	.open		= wdtpci_open,
+	.release	= wdtpci_release,
+};
+
+static struct miscdevice wdtpci_miscdev = {
+	.minor	= WATCHDOG_MINOR,
+	.name	= "watchdog",
+	.fops	= &wdtpci_fops,
+};
+
+#ifdef CONFIG_WDT_501_PCI
+static struct file_operations wdtpci_temp_fops = {
+	.owner		= THIS_MODULE,
+	.llseek		= no_llseek,
+	.read		= wdtpci_temp_read,
+	.open		= wdtpci_temp_open,
+	.release	= wdtpci_temp_release,
+};
+
+static struct miscdevice temp_miscdev = {
+	.minor	= TEMP_MINOR,
+	.name	= "temperature",
+	.fops	= &wdtpci_temp_fops,
+};
+#endif /* CONFIG_WDT_501_PCI */
+
+/*
+ *	The WDT card needs to learn about soft shutdowns in order to
+ *	turn the timebomb registers off.
+ */
+
+static struct notifier_block wdtpci_notifier = {
+	.notifier_call = wdtpci_notify_sys,
+};
+
+
+static int __devinit wdtpci_init_one (struct pci_dev *dev,
+				   const struct pci_device_id *ent)
+{
+	int ret = -EIO;
+
+	dev_count++;
+	if (dev_count > 1) {
+		printk (KERN_ERR PFX "this driver only supports 1 device\n");
+		return -ENODEV;
+	}
+
+	if (pci_enable_device (dev)) {
+		printk (KERN_ERR PFX "Not possible to enable PCI Device\n");
+		return -ENODEV;
+	}
+
+	if (pci_resource_start (dev, 2) == 0x0000) {
+		printk (KERN_ERR PFX "No I/O-Address for card detected\n");
+		ret = -ENODEV;
+		goto out_pci;
+	}
+
+	sema_init(&open_sem, 1);
+	spin_lock_init(&wdtpci_lock);
+
+	irq = dev->irq;
+	io = pci_resource_start (dev, 2);
+
+	if (request_region (io, 16, "wdt_pci") == NULL) {
+		printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", io);
+		goto out_pci;
+	}
+
+	if (request_irq (irq, wdtpci_interrupt, SA_INTERRUPT | SA_SHIRQ,
+			 "wdt_pci", &wdtpci_miscdev)) {
+		printk (KERN_ERR PFX "IRQ %d is not free\n", irq);
+		goto out_reg;
+	}
+
+	printk ("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%04x (Interrupt %d)\n",
+		io, irq);
+
+	/* Check that the heartbeat value is within it's range ; if not reset to the default */
+	if (wdtpci_set_heartbeat(heartbeat)) {
+		wdtpci_set_heartbeat(WD_TIMO);
+		printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n",
+			WD_TIMO);
+	}
+
+	ret = register_reboot_notifier (&wdtpci_notifier);
+	if (ret) {
+		printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret);
+		goto out_irq;
+	}
+
+#ifdef CONFIG_WDT_501_PCI
+	ret = misc_register (&temp_miscdev);
+	if (ret) {
+		printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			TEMP_MINOR, ret);
+		goto out_rbt;
+	}
+#endif /* CONFIG_WDT_501_PCI */
+
+	ret = misc_register (&wdtpci_miscdev);
+	if (ret) {
+		printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
+			WATCHDOG_MINOR, ret);
+		goto out_misc;
+	}
+
+	printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n",
+		heartbeat, nowayout);
+#ifdef CONFIG_WDT_501_PCI
+	printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled"));
+#endif /* CONFIG_WDT_501_PCI */
+
+	ret = 0;
+out:
+	return ret;
+
+out_misc:
+#ifdef CONFIG_WDT_501_PCI
+	misc_deregister(&temp_miscdev);
+out_rbt:
+#endif /* CONFIG_WDT_501_PCI */
+	unregister_reboot_notifier(&wdtpci_notifier);
+out_irq:
+	free_irq(irq, &wdtpci_miscdev);
+out_reg:
+	release_region (io, 16);
+out_pci:
+	pci_disable_device(dev);
+	goto out;
+}
+
+
+static void __devexit wdtpci_remove_one (struct pci_dev *pdev)
+{
+	/* here we assume only one device will ever have
+	 * been picked up and registered by probe function */
+	misc_deregister(&wdtpci_miscdev);
+#ifdef CONFIG_WDT_501_PCI
+	misc_deregister(&temp_miscdev);
+#endif /* CONFIG_WDT_501_PCI */
+	unregister_reboot_notifier(&wdtpci_notifier);
+	free_irq(irq, &wdtpci_miscdev);
+	release_region(io, 16);
+	pci_disable_device(pdev);
+	dev_count--;
+}
+
+
+static struct pci_device_id wdtpci_pci_tbl[] = {
+	{
+		.vendor	   = PCI_VENDOR_ID_ACCESSIO,
+		.device	   = PCI_DEVICE_ID_WDG_CSM,
+		.subvendor = PCI_ANY_ID,
+		.subdevice = PCI_ANY_ID,
+	},
+	{ 0, }, /* terminate list */
+};
+MODULE_DEVICE_TABLE(pci, wdtpci_pci_tbl);
+
+
+static struct pci_driver wdtpci_driver = {
+	.name		= "wdt_pci",
+	.id_table	= wdtpci_pci_tbl,
+	.probe		= wdtpci_init_one,
+	.remove		= __devexit_p(wdtpci_remove_one),
+};
+
+
+/**
+ *	wdtpci_cleanup:
+ *
+ *	Unload the watchdog. You cannot do this with any file handles open.
+ *	If your watchdog is set to continue ticking on close and you unload
+ *	it, well it keeps ticking. We won't get the interrupt but the board
+ *	will not touch PC memory so all is fine. You just have to load a new
+ *	module in xx seconds or reboot.
+ */
+
+static void __exit wdtpci_cleanup(void)
+{
+	pci_unregister_driver (&wdtpci_driver);
+}
+
+
+/**
+ * 	wdtpci_init:
+ *
+ *	Set up the WDT watchdog board. All we have to do is grab the
+ *	resources we require and bitch if anyone beat us to them.
+ *	The open() function will actually kick the board off.
+ */
+
+static int __init wdtpci_init(void)
+{
+	return pci_register_driver (&wdtpci_driver);
+}
+
+
+module_init(wdtpci_init);
+module_exit(wdtpci_cleanup);
+
+MODULE_AUTHOR("JP Nollmann, Alan Cox");
+MODULE_DESCRIPTION("Driver for the ICS PCI-WDT500/501 watchdog cards");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
+MODULE_ALIAS_MISCDEV(TEMP_MINOR);